#include "StuffHeaders.hpp"
#include "Database.hpp"
#include <GameOS\ToolOS.hpp>
#include <GameOS\Network.hpp>
#include <mbstring.h>
#define __mbsrchr(s,c) (char*)_mbsrchr((const unsigned char*)(s),(c))

#if 1
#define DATABASE_LOG(string) LOG_BLOCK("Database::" string)
#else
#define DATABASE_LOG(string)
#endif
extern volatile int ProcessingError;
extern void _stdcall ExitGameOS();

//===========================================================================//
// File:	Database.cpp                                                     //
// Project: GameOS									                         //
// Contents: Database functionality											 //												 
//---------------------------------------------------------------------------//
//																			 //
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 02/20/99  AG    Created													 //
//																			 //
//===========================================================================//

HGOSHEAP Stuff::g_DatabaseHeap = NULL;
bool(__cdecl *NoCDMessageFunction)() = NULL;

#define RATIO 0.95f

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
static DWORD GenerateHash(const char* name)
{
	DWORD hash=0;

	while (*name)
	{
		hash=(hash<<4) + (*name&15);
		if (hash&0xf0000000)
			hash=hash^(hash>>28);
		name++;
	}
	return hash|0x80000000;
}

//
//###########################################################################
//##############################    Record    ###############################
//###########################################################################
//

class Stuff::Record
{
public:
	Record(
		const RecordHandle *handle,
		DWORD record_hash,
		BYTE name_length
	);
	Record(
		const RecordHandle *handle,
		DWORD record_hash,
		BYTE name_length,
		Record *record
	);
	Record(
		__int64 last_modified,
		DWORD data_length,
		DWORD real_length,
		DWORD offset,
		WORD id,
		BYTE name_length,
		MemoryStream *stream
	);
	~Record()
		{
			Check_Object(this); Verify(!m_referenceCount);
			if (m_deleteData && m_buffer) delete m_buffer;
		}

	void Save(
		DatabaseHandle *handle,
		MemoryStream *record_stream,
		MemoryStream *data_stream
	);

	void SaveNoChange(
		DatabaseHandle *handle,
		MemoryStream *record_stream,
		MemoryStream *data_stream
	);


	void LoadData(DatabaseHandle *handle);
	void LoadDataNoDecompress(DatabaseHandle *handle);
	void UnloadData();
	void AbandonData();
	void Unhook(const RecordHandle *handle);

	__int64
		m_lastModified;				// Time record was last modifyed

	BYTE
		*m_buffer;					// the buffer data has been uncompressed into
	Record
		*m_nextIDRecord,			// offset to chain of records that share the same hash
		*m_nextNameRecord;			// offset to chain of records that share the same hash
	DWORD
		m_hash,						// Hash value
		m_realLength,				// length of the compressed block - if zero then data is not compressed
		m_dataLength,
		m_dataOffset;				// possibly compressed data

	#if defined(_ARMOR)
		DWORD m_referenceCount;
	#endif

	WORD
		m_ID;						// ID
	bool
		m_deleteData;
	BYTE
		m_nameLength;
	char
		m_name[1];

	enum {
		e_FixedLength = sizeof(__int64) + 3*sizeof(DWORD) + sizeof(WORD) + sizeof(BYTE)
	};

	void TestInstance() const
		{}
};

//
//###########################################################################
//#############################    Database    ##############################
//###########################################################################
//

class Stuff::Database
{
public:
	enum {
		e_DataBlockSize=1021,
		e_Tag = 'DBV#',
		e_Version = 4,
		e_FixedLength = 4*sizeof(DWORD) + 2*sizeof(WORD)
	};

	Database(DatabaseHandle *handle, DWORD content_version);
	Database(DatabaseHandle *handle, DWORD content_version, HGOSFILE file);
	Database(DatabaseHandle *handle, DWORD content_version, MemoryStream *stream);
	~Database();

	void LoadRecords(MemoryStream *stream, bool preload);
	void Save();
	void ScatterSave();

	DatabaseHandle
		*m_handle;
	DWORD
		m_tag,							// Magic number to identity file
		m_formatVersion,				// Format Version number
		m_contentVersion,				// Content Version number
		m_indexSize;
	WORD
		m_numberOfRecords,				// Number of used records in database
		m_nextRecordID;					// Sequential index assigned in ADD
	Stuff::Record
		*m_idOffsets[e_DataBlockSize],	// Offsets to DatabaseRecords ( sorted by index )
		*m_nameOffsets[e_DataBlockSize];	// Offsets to DatabaseRecords ( sorted by HASH )

	void TestInstance() const
		{Verify(m_tag == e_Tag && m_formatVersion <= e_Version);}

	static int
		s_FilesOpened;
};

//
//###########################################################################
//##############################    Record    ###############################
//###########################################################################
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Record::Record(
	const RecordHandle *handle,
	DWORD record_hash,
	BYTE name_length
)
{
	Check_Pointer(this);
	Check_Object(handle);
	DatabaseHandle *db_handle = handle->m_databaseHandle;
	Check_Object(db_handle);
	Database *db = db_handle->m_dataBase;
	Check_Object(db);

	//
	//------------------
	// Set up the record
	//------------------
	//
	m_ID = handle->m_ID;
	Verify(m_ID <= db->m_numberOfRecords+1);
	m_hash = record_hash;
	m_nameLength = name_length;
	m_dataLength = handle->m_length;
	m_lastModified = gos_GetTimeDate();
	#if defined(_ARMOR)
		m_referenceCount = 0;
	#endif

	//
	//----------------------------------------------------
	// If there is data to write out, put it in the buffer
	//----------------------------------------------------
	//
	if (m_dataLength>0)
	{
		m_buffer = new(db_handle->m_heap) BYTE[m_dataLength];
		Check_Pointer(m_buffer);
		memcpy(m_buffer, handle->m_data, m_dataLength);
		m_deleteData = true;
	}
	else
	{
		m_deleteData = false;
		m_buffer = NULL;
	}
	m_dataOffset = -1;
	m_realLength = m_dataLength;

	//
	//------------------
	// Store in database
	//------------------
	//
	DWORD index=m_ID % Database::e_DataBlockSize;
	m_nextIDRecord = db->m_idOffsets[index];
	db->m_idOffsets[index] = this;
	if(handle->m_name)
		memcpy(m_name, handle->m_name, name_length+1);
	else
		*m_name = 0;
	index = record_hash % Database::e_DataBlockSize;
	m_nextNameRecord = db->m_nameOffsets[index];
	db->m_nameOffsets[index] = this;

	//
	//----------------------------
	// Increase the database count
	//----------------------------
	//
	db->m_numberOfRecords++;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Record::Record(
	const RecordHandle *handle,
	DWORD record_hash,
	BYTE name_length,
	Record *record
)
{
	Check_Pointer(this);
	Check_Object(handle);
	DatabaseHandle *db_handle = handle->m_databaseHandle;
	Check_Object(db_handle);
	Database *db = db_handle->m_dataBase;
	Check_Object(db);

	//
	//------------------
	// Set up the record
	//------------------
	//
	m_ID = handle->m_ID;
	Verify(m_ID == record->m_ID);
	m_hash = record_hash;
	m_nameLength = name_length;
	m_dataLength = handle->m_length;
	m_lastModified = gos_GetTimeDate();
	#if defined(_ARMOR)
		m_referenceCount = 0;
	#endif

	//
	//------------------------------------------------------------------
	// If there is data to write out, put it in the buffer.  If the data
	// hasn't changed, don't let the old record delete the memory
	//------------------------------------------------------------------
	//
	if (m_dataLength>0)
	{
		if (record->m_buffer == handle->m_data)
		{
			Verify(record->m_dataLength == handle->m_length);
			m_buffer = record->m_buffer;
			Verify(!record->m_referenceCount);
			m_deleteData = record->m_deleteData;
			record->m_deleteData = false;
		}
		else
		{
			m_buffer = new(db_handle->m_heap) BYTE[m_dataLength];
			Check_Pointer(m_buffer);
			memcpy(m_buffer, handle->m_data, m_dataLength);
			m_deleteData = true;
		}
	}
	else
	{
		m_deleteData = false;
		m_buffer = NULL;
	}
	m_dataOffset = -1;
	m_realLength = m_dataLength;

	//
	//------------------
	// Store in database
	//------------------
	//
	DWORD index=m_ID % Database::e_DataBlockSize;
	m_nextIDRecord = db->m_idOffsets[index];
	db->m_idOffsets[index] = this;
	if(handle->m_name)
		memcpy(m_name, handle->m_name, name_length+1);
	else
		*m_name = 0;
	index = record_hash % Database::e_DataBlockSize;
	m_nextNameRecord = db->m_nameOffsets[index];
	db->m_nameOffsets[index] = this;

	//
	//----------------------------
	// Increase the database count
	//----------------------------
	//
	db->m_numberOfRecords++;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Record::Record(
	__int64 last_modified,
	DWORD data_length,
	DWORD real_length,
	DWORD offset,
	WORD id,
	BYTE name_length,
	MemoryStream *stream
)
{
	Check_Pointer(this);
	Check_Object(stream);

	//
	//------------------------------
	// Read the data from the stream
	//------------------------------
	//
	m_lastModified = last_modified;
	m_dataLength = data_length;
	m_realLength = real_length;
	m_dataOffset = offset;
	m_ID = id;
	m_nameLength = name_length;
	#if defined(_ARMOR)
		m_referenceCount = 0;
	#endif
	if(m_nameLength > 0)
		stream->ReadBytes(m_name, m_nameLength);
	m_name[m_nameLength] = '\0';

	//
	//---------------------------------------------------------------
	// Set up the buffer to indicate the data hasn't been read in yet
	//---------------------------------------------------------------
	//
	m_buffer = NULL;
	m_deleteData = false;
	m_hash = GenerateHash(m_name);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::Save(
				  DatabaseHandle *handle,
				  MemoryStream *record_stream,
				  MemoryStream *data_stream
				  )
{
	Check_Pointer(this);
	Check_Object(handle);
	Check_Object(record_stream);
	Check_Object(data_stream);

	//
	//--------------------------------
	// Write out the modification time
	//--------------------------------
	//
	record_stream->WriteBytes(&m_lastModified, sizeof(m_lastModified));

	//
	//-----------------------------------------------------------------
	// If this is a zero length record, just write out the empty record
	//-----------------------------------------------------------------
	//
	if (!m_realLength)
	{
		Verify(!m_dataLength);
		*record_stream << (DWORD)0 << (DWORD)0 << (DWORD)0;
		*record_stream << m_ID << m_nameLength;
		if(m_nameLength > 0)
			record_stream->WriteBytes(m_name, m_nameLength);
		return;
	}

	//
	//-------------------------------------------------
	// Load the buffer if it hasn't already been loaded
	//-------------------------------------------------
	//
	if (!m_buffer)
		LoadData(handle);

	//
	//----------------------------------------------------------
	// If the offset is -1, we will need to test for compression
	//----------------------------------------------------------
	//
#if defined(_ARMOR)
	BYTE *buffer = new(handle->m_heap) BYTE[m_dataLength+4];
	*Cast_Pointer(int*,&buffer[m_dataLength]) = '%y!P';
#else
	BYTE *buffer = new(handle->m_heap) BYTE[m_dataLength];
#endif
	m_realLength = gos_LZCompress(buffer, m_buffer, m_dataLength, m_dataLength);
	Verify(m_realLength <= m_dataLength);
	Verify(*Cast_Pointer(int*,&buffer[m_dataLength]) == '%y!P');
	if (!m_realLength || ((Scalar)m_realLength)/m_dataLength > RATIO)
	{
		delete buffer;
		buffer = NULL;
		m_realLength = m_dataLength;
	}

	//
	//-----------------------------------------------------
	// Write the record to the record portion of the stream
	//-----------------------------------------------------
	//
	*record_stream << m_dataLength << m_realLength;
	m_dataOffset = data_stream->GetIndex();
	*record_stream << m_dataOffset;
	*record_stream << m_ID << m_nameLength;
	if(m_nameLength > 0)
		record_stream->WriteBytes(m_name, m_nameLength);

	//
	//-----------------------------------
	// Write the data to the data portion
	//-----------------------------------
	//
	Verify(m_realLength>0);
	if (buffer)
	{
		Verify(m_realLength < m_dataLength);
		data_stream->WriteBytes(buffer, m_realLength);
		delete buffer;
	}
	else
	{
		Verify(m_realLength == m_dataLength);
		data_stream->WriteBytes(m_buffer, m_realLength);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::SaveNoChange(
				  DatabaseHandle *handle,
				  MemoryStream *record_stream,
				  MemoryStream *data_stream
				  )
{
	Check_Pointer(this);
	Check_Object(handle);
	Check_Object(record_stream);
	Check_Object(data_stream);

	//
	//--------------------------------
	// Write out the modification time
	//--------------------------------
	//
	record_stream->WriteBytes(&m_lastModified, sizeof(m_lastModified));

	//
	//-----------------------------------------------------------------
	// If this is a zero length record, just write out the empty record
	//-----------------------------------------------------------------
	//
	if (!m_realLength)
	{
		Verify(!m_dataLength);
		*record_stream << (DWORD)0 << (DWORD)0 << (DWORD)0;
		*record_stream << m_ID << m_nameLength;
		if(m_nameLength > 0)
			record_stream->WriteBytes(m_name, m_nameLength);
		return;
	}

	//
	//-------------------------------------------------
	// Load the buffer if it hasn't already been loaded
	//-------------------------------------------------
	//
	if (!m_buffer)
		LoadDataNoDecompress(handle);

	//
	//-----------------------------------------------------
	// Write the record to the record portion of the stream
	//-----------------------------------------------------
	//
	*record_stream << m_dataLength << m_realLength;
	m_dataOffset = data_stream->GetIndex();
	*record_stream << m_dataOffset;
	*record_stream << m_ID << m_nameLength;
	if(m_nameLength > 0)
		record_stream->WriteBytes(m_name, m_nameLength);


	data_stream->WriteBytes(m_buffer, m_realLength);
	
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::LoadData(DatabaseHandle *handle)
{
	Check_Pointer(this);
	Verify(!m_buffer && m_dataOffset != -1);
	Check_Object(handle);
	DATABASE_LOG("Load Data");

	//
	//----------------------------------------
	// Read in the data stored in the gos file
	//----------------------------------------
	//
	Database *db = handle->m_dataBase;
	Check_Object(db);
	BYTE *buffer = NULL;
	if (handle->m_fileHandle)
	{
		DATABASE_LOG("Load Data::Read File");
		gos_SeekFile(handle->m_fileHandle, FROMSTART, db->m_indexSize+m_dataOffset);
		buffer = m_buffer = new(handle->m_heap) BYTE[m_realLength];
		m_deleteData = true;
		gos_ReadFile(handle->m_fileHandle, m_buffer, m_realLength);
	}
	else
	{
		m_buffer = handle->m_fileAddress + db->m_indexSize + m_dataOffset;
		Verify(!m_deleteData);
	}

	//
	//-------------------------------
	// Decompress the file if need be
	//-------------------------------
	//
	if (m_realLength < m_dataLength)
	{
		DATABASE_LOG("Load Data::Decompress");
		m_deleteData = true;
		BYTE *new_buffer = new(handle->m_heap) BYTE[m_dataLength];
		#if defined(_ARMOR)
			DWORD bytes =
		#endif
		gos_LZDecompress(new_buffer, m_buffer, m_realLength);
		Verify(bytes == m_dataLength);
		if (buffer)
		{
			Verify(buffer == m_buffer);
			delete buffer;
		}
		m_buffer = new_buffer;
	}
	else
		Verify(m_realLength == m_dataLength);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::LoadDataNoDecompress(DatabaseHandle *handle)
{
	Check_Pointer(this);
	Verify(!m_buffer && m_dataOffset != -1);
	Check_Object(handle);

	//
	//----------------------------------------
	// Read in the data stored in the gos file
	//----------------------------------------
	//
	Database *db = handle->m_dataBase;
	Check_Object(db);
	BYTE *buffer = NULL;
	if (handle->m_fileHandle)
	{
		gos_SeekFile(handle->m_fileHandle, FROMSTART, db->m_indexSize+m_dataOffset);
		buffer = m_buffer = new(handle->m_heap) BYTE[m_realLength];
		m_deleteData = true;
		gos_ReadFile(handle->m_fileHandle, m_buffer, m_realLength);
	}
	else
	{
		m_buffer = handle->m_fileAddress + db->m_indexSize + m_dataOffset;
		Verify(!m_deleteData);
	}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::UnloadData()
{
	Check_Object(this);
	Verify(m_referenceCount < 2);

	//
	//--------------------------------------
	// Delete the data if it has been loaded
	//--------------------------------------
	//
	if (m_deleteData && m_dataOffset != -1)
	{
		Check_Pointer(m_buffer);
		delete m_buffer;
		m_buffer = NULL;
		m_deleteData = false;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::AbandonData()
{
	Check_Object(this);
	Verify(m_referenceCount == 1);

	//
	//--------------------------------------
	// Delete the data if it has been loaded
	//--------------------------------------
	//
	if (m_deleteData && m_dataOffset != -1)
	{
		Check_Pointer(m_buffer);
		m_buffer = NULL;
		m_deleteData = false;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Record::Unhook(const RecordHandle *handle)
{
	Check_Pointer(this);
	Check_Object(handle);
	DatabaseHandle *db_handle = handle->m_databaseHandle;
	Check_Object(db_handle);
	Database *db = db_handle->m_dataBase;
	Check_Object(db);
	Check_Object(this);
	Verify(m_referenceCount == 1);

	//
	//--------------------------------------------
	// Make sure that we can find the record by ID
	//--------------------------------------------
	//
	Record* record;
	DWORD index = m_ID % Database::e_DataBlockSize;
	#ifdef _ARMOR
		record = db->m_idOffsets[index];
		while (record)
		{
			Check_Object(record);
			if (record->m_ID == m_ID)
				break;
			record = record->m_nextIDRecord;
		}	
		Verify(this == record);
	#endif

	//
	//-------------------------------------------
	// Now remove the record from the index database
	//-------------------------------------------
	//
	record = db->m_idOffsets[index];
	if (record == this)
		db->m_idOffsets[index] = m_nextIDRecord;
	else
	{
		while (record)
		{
			Check_Object(record);
			if (record->m_nextIDRecord == this)
			{
				record->m_nextIDRecord = m_nextIDRecord;
				break;
			}
			record = record->m_nextIDRecord;
		}	
	}

	//
	//------------------------------------
	// Now remove from hash index database
	//------------------------------------
	//
	index = m_hash % Database::e_DataBlockSize;
	record = db->m_nameOffsets[index];
	if (record == this)
		db->m_nameOffsets[index] = m_nextNameRecord;
	else
	{
		while (record)
		{
			Check_Object(record);
			if (record->m_nextNameRecord == this)
			{
				record->m_nextNameRecord = m_nextNameRecord;
				break;
			}
			record = record->m_nextNameRecord;
		}	
	}
	db->m_numberOfRecords--;
}

//
//###########################################################################
//###########################    RecordHandle    ############################
//###########################################################################
//

static Record *NextIteratedRecord = NULL;

#if defined(_ARMOR)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RecordHandle::RecordHandle()
{
	Check_Object(this);
	m_record = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RecordHandle::~RecordHandle()
{
	Check_Object(this);

	if (m_record)
	{
		Check_Object(m_record);
		Verify(m_record->m_referenceCount > 0);
		--const_cast<Record*>(m_record)->m_referenceCount;
	}
}
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::Add()
{
	Check_Object(this);
	Verify(!m_record);

	//
	//------------------
	// Find our database
	//------------------
	//
	Check_Object(m_databaseHandle);
	m_databaseHandle->m_dirtyFlag = true;
	Database* db = m_databaseHandle->m_dataBase;
	Check_Object(db);

	//
	//-------------------------------------------
	// Make sure the record doesn't already exist
	//-------------------------------------------
	//
	Verify(!m_record);
	#ifdef _ARMOR
		if (m_name)
		{
			RecordHandle dup_check = *this;
			if (dup_check.FindName(false))
				STOP(( "Duplicate Record" ));
		}
	#endif
	
	//
	//---------------------
	// Set the record index
	//---------------------
	//
	m_ID=db->m_nextRecordID++;

	//
	//---------------------------------------------------
	// Figure out how long the name is and its hash value
	//---------------------------------------------------
	//
	DWORD record_hash;
	BYTE name_length;
	if (m_name)
	{
		record_hash = GenerateHash(m_name);
		Verify(strlen(m_name)<256);
		name_length = static_cast<BYTE>(strlen(m_name));
		Verify(name_length > 0);
	}
	else
	{
		record_hash = 0;
		name_length = 0;
	}

	//
	//------------------
	// Set up the record
	//------------------
	//
	Verify(!m_record);
	Record *data =
		new(new(m_databaseHandle->m_heap) BYTE[sizeof(*m_record) + name_length])
			Record(this, record_hash, name_length);
	Check_Object(data);
	m_data = data->m_buffer;
	if (m_name)
		m_name = data->m_name;
	m_timeStamp = data->m_lastModified;

	//
	//------------------
	// Update statistics
	//------------------
	//
	m_record = data;
	#if defined(_ARMOR)
		const_cast<Record*>(m_record)->m_referenceCount = 1;
	#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::Replace()
{
	Check_Object(this);

	//
	//------------------
	// Find our database
	//------------------
	//
	Check_Object(m_databaseHandle);
	m_databaseHandle->m_dirtyFlag = true;
	
	//
	//----------------------
	// Unhook the old record
	//----------------------
	//
	Check_Object(m_record);
	const_cast<Record*>(m_record)->Unhook(this);

	//
	//---------------------------------------------------
	// Figure out how long the name is and its hash value
	//---------------------------------------------------
	//
	DWORD record_hash;
	BYTE name_length;
	if (m_name)
	{
		record_hash = GenerateHash(m_name);
		Verify(strlen(m_name) < 256);
		name_length = static_cast<BYTE>(strlen(m_name));
		Verify(name_length > 0);
	}
	else
	{
		record_hash = 0;
		name_length = 0;
	}

	//
	//------------------
	// Set up the record
	//------------------
	//
	#if defined(_ARMOR)
		Verify(m_record->m_referenceCount == 1);
		const_cast<Record*>(m_record)->m_referenceCount = 0;
	#endif
	Record *data =
		new(new(m_databaseHandle->m_heap) BYTE[sizeof(*m_record) + name_length])
			Record(this, record_hash, name_length, const_cast<Record*>(m_record));
	Check_Object(data);
	m_data = data->m_buffer;
	if (m_name)
		m_name = data->m_name;
	m_timeStamp = data->m_lastModified;

	//
	//------------------
	// Update statistics
	//------------------
	//
	delete const_cast<Record*>(m_record);
	m_record = data;
	#if defined(_ARMOR)
		const_cast<Record*>(m_record)->m_referenceCount = 1;
	#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::Delete()
{
	Check_Object(this);

	//
	//------------------
	// Find our database
	//------------------
	//
	Check_Object(m_databaseHandle);
	m_databaseHandle->m_dirtyFlag = true;
	
	//
	//----------------------
	// Delete the old record
	//----------------------
	//
	Check_Object(m_record);
	const_cast<Record*>(m_record)->Unhook(this);
	#if defined(_ARMOR)
		const_cast<Record*>(m_record)->m_referenceCount = 0;
	#endif
	delete const_cast<Record*>(m_record);
	m_record = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool RecordHandle::FindID(bool load)
{
	Check_Object(this);
	Check_Object(m_databaseHandle);
	DATABASE_LOG("Find ID");

	Database* db = m_databaseHandle->m_dataBase;
	Check_Object(db);

	DWORD index=m_ID % Database::e_DataBlockSize;
	Record* record = db->m_idOffsets[index];
	while (record)
	{
		Check_Object(record);
		if (record->m_ID==m_ID)
		{
			m_length = record->m_dataLength;
			if (load && !record->m_buffer && record->m_dataOffset!=-1 && record->m_dataLength>0)
				record->LoadData(m_databaseHandle);
			m_data = record->m_buffer;
			m_name = record->m_name;
			m_record = record;
			#if defined(_ARMOR)
				++const_cast<Record*>(m_record)->m_referenceCount;
			#endif
			m_timeStamp = record->m_lastModified;
			return true;
		}
		record = record->m_nextIDRecord;
	}

	m_record = NULL;
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool RecordHandle::FindName(bool load)
{
	Check_Object(this);
	Check_Object(m_databaseHandle);

	DATABASE_LOG("Find Name");
	Database* db = m_databaseHandle->m_dataBase;
	Check_Object(db);

	Check_Pointer(m_name);
	DWORD hash = GenerateHash(m_name);
	DWORD index = hash % Database::e_DataBlockSize;

	Record* record = db->m_nameOffsets[index];
	while (record)
	{
		Check_Object(record);
		if (record->m_hash == hash && !_stricmp(m_name,record->m_name))
		{
			m_length = record->m_dataLength;
			if (load && !record->m_buffer && record->m_dataOffset!=-1 && record->m_dataLength>0)
				record->LoadData(m_databaseHandle);
			m_data = record->m_buffer;
			m_ID = record->m_ID;
			m_name = record->m_name;
			m_record = record;
			#if defined(_ARMOR)
				++const_cast<Record*>(m_record)->m_referenceCount;
			#endif
			m_timeStamp = record->m_lastModified;
			return true;
		}
		record = record->m_nextNameRecord;
	}	

	m_record = NULL;
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::LoadData()
{
	Check_Object(this);
	Check_Object(m_record);
	Check_Object(m_databaseHandle);

	if (!m_record->m_buffer && m_record->m_dataOffset != -1 && m_record->m_dataLength>0)
		const_cast<Record*>(m_record)->LoadData(m_databaseHandle);
	m_data = m_record->m_buffer;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::UnloadData()
{
	Check_Object(this);
	Check_Object(m_record);
	Check_Object(m_databaseHandle);
	Verify(m_databaseHandle->m_fileHandle || !m_record->m_deleteData || m_record->m_dataOffset == -1);

	const_cast<Record*>(m_record)->UnloadData();
	m_data = m_record->m_buffer;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::AbandonData()
{
	Check_Object(this);
	Check_Object(m_record);
	Check_Object(m_databaseHandle);

	const_cast<Record*>(m_record)->AbandonData();
	m_data = m_record->m_buffer;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int RecordHandle::GetDiskOffset()
{
	Check_Object(this);
	Check_Object(m_record);
	Check_Object(m_databaseHandle);
	Database* db = m_databaseHandle->m_dataBase;
	Check_Object(db);

	Verify(m_record->m_realLength == m_record->m_dataLength);
	return db->m_indexSize + m_record->m_dataOffset;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DWORD RecordHandle::GetRealRecordSize()
{
	Check_Object(this);
	Check_Object(m_record);
	Check_Object(m_databaseHandle);

	return m_record->m_realLength;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::First()
{
	Check_Object(this);
	Check_Object(m_databaseHandle);
	Database* db = m_databaseHandle->m_dataBase;
	Check_Object(db);

	m_record = NULL;
	for (DWORD i=0; i<Database::e_DataBlockSize; i++)
	{
		NextIteratedRecord = db->m_idOffsets[i];
		if (NextIteratedRecord)
			break;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool RecordHandle::ReadAndNext()
{
	Check_Object(this);
	Check_Object(m_databaseHandle);
	Database* db = m_databaseHandle->m_dataBase;
	Check_Object(db);

	#if defined(_ARMOR)
		if (m_record)
			--const_cast<Record*>(m_record)->m_referenceCount;
	#endif
	if (!NextIteratedRecord)
	{
		m_record = NULL;
		return false;
	}

	Record* &data = NextIteratedRecord;
	Check_Object(data);
	m_ID = data->m_ID;
	m_length = data->m_dataLength;
	m_data = NULL;
	m_name = data->m_name;
	m_record = data;
	#if defined(_ARMOR)
		++const_cast<Record*>(m_record)->m_referenceCount;
	#endif
	m_timeStamp = data->m_lastModified;

	data = data->m_nextIDRecord;
	if (!data)
	{
		DWORD index=m_ID % Database::e_DataBlockSize;
		while (++index < Database::e_DataBlockSize)
		{
			data = db->m_idOffsets[index];
			if (data)
				return true;
		}
		data = NULL;
		return true;
	}

	NextIteratedRecord = data;
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void RecordHandle::TestInstance() const
{
}

//
//###########################################################################
//#############################    Database    ##############################
//###########################################################################
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int Database::s_FilesOpened = 0;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Database::Database(DatabaseHandle *handle, DWORD content_version)
{
	Check_Pointer(this);
	Check_Object(handle);

	m_handle = handle;
	m_tag = e_Tag;
	m_formatVersion = e_Version;
	m_contentVersion = content_version;
	m_nextRecordID = 0;
	m_numberOfRecords = 0;
	m_indexSize = 0;
	memset(m_idOffsets, 0, sizeof(m_idOffsets));
	memset(m_nameOffsets, 0, sizeof(m_nameOffsets));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Database::Database(DatabaseHandle *handle, DWORD content_version, HGOSFILE file)
{
	Check_Pointer(this);
	Check_Object(handle);
	Check_Pointer(file);

	//
	//----------------------------
	// Read in the database header
	//----------------------------
	//
	m_handle = handle;
	gos_ReadFile(file, &m_tag, sizeof(m_tag));
	if (m_tag != e_Tag)
		STOP(("Invalid or Corrupt file"));
	gos_ReadFile(file, &m_formatVersion, sizeof(m_formatVersion));
	if (m_formatVersion > e_Version)
		STOP(("Application must be recompiled to use this database"));
	gos_ReadFile(file, &m_contentVersion, sizeof(m_contentVersion));
	if (m_formatVersion < e_Version || m_contentVersion < content_version)
	{
		gos_CloseFile(file);
		handle->m_fileHandle = NULL;
		m_handle = handle;
		m_tag = e_Tag;
		m_formatVersion = e_Version;
		m_contentVersion = content_version;
		m_nextRecordID = 0;
		m_numberOfRecords = 0;
		m_indexSize = 0;
		memset(m_idOffsets, 0, sizeof(m_idOffsets));
		memset(m_nameOffsets, 0, sizeof(m_nameOffsets));
		return;
	}
	gos_ReadFile(file, &m_indexSize, sizeof(m_indexSize));
	gos_ReadFile(file, &m_numberOfRecords, sizeof(m_numberOfRecords));
	gos_ReadFile(file, &m_nextRecordID, sizeof(m_nextRecordID));

	//
	//------------------------------
	// Now read in the chunk of data
	//------------------------------
	//
	DWORD record_size = m_indexSize - e_FixedLength;
	BYTE *buffer = new(handle->m_heap) BYTE[record_size];
	Check_Pointer(buffer);
	gos_ReadFile(file, buffer, record_size);
	MemoryStream stream(buffer, record_size);
	LoadRecords(&stream, false);
	handle->m_fileLength = gos_SeekFile(file, FROMEND, 0);
	delete buffer;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Database::Database(DatabaseHandle *handle, DWORD content_version, MemoryStream *stream)
{
	Check_Pointer(this);
	Check_Object(stream);
	Check_Pointer(handle);

	//
	//----------------------------
	// Read in the database header
	//----------------------------
	//
	m_handle = handle;
	*stream >> m_tag;
	if (m_tag != e_Tag)
		STOP(("Invalid or Corrupt file"));
	*stream >> m_formatVersion;
	if (m_formatVersion > e_Version)
		STOP(("Application must be recompiled to use this database"));
	*stream >> m_contentVersion;
	if (m_formatVersion < e_Version || m_contentVersion < content_version)
		STOP(("Obsolete database"));
	*stream >> m_indexSize;
	*stream >> m_numberOfRecords >> m_nextRecordID;
	LoadRecords(stream, true);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Database::LoadRecords(MemoryStream *stream, bool preload)
{
	Check_Object(this);
	Check_Object(stream);

	//
	//-----------------------------------------------------------
	// Now create all the records and hook them into the database
	//-----------------------------------------------------------
	//
	memset(m_idOffsets, 0, sizeof(m_idOffsets));
	memset(m_nameOffsets, 0, sizeof(m_nameOffsets));
	for (WORD i=0; i<m_numberOfRecords; ++i)
	{
		__int64 last_modified;
		stream->ReadBytes(&last_modified, sizeof(last_modified));
		DWORD data_length, real_length, offset;
		*stream >> data_length >> real_length >> offset;
		WORD id;
		BYTE name_length;
		*stream >> id >> name_length;
		Record *record =
			new(new(m_handle->m_heap) BYTE[sizeof(Record) + name_length]) Record(
				last_modified,
				data_length,
				real_length,
				offset,
				id,
				name_length,
				stream
			);
		Check_Object(record);
		DWORD index=record->m_ID % Database::e_DataBlockSize;
		record->m_nextIDRecord = m_idOffsets[index];
		m_idOffsets[index] = record;
		index = record->m_hash % Database::e_DataBlockSize;
		record->m_nextNameRecord = m_nameOffsets[index];
		m_nameOffsets[index] = record;

		//
		//--------------------------------------------------------
		// If we are to preload, set the memory buffer accordingly
		//--------------------------------------------------------
		//
		if (preload && record->m_realLength == record->m_dataLength)
			record->m_buffer = m_handle->m_fileAddress + m_indexSize + record->m_dataOffset;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Database::Save()
{
	Check_Object(this);

	//
	//----------------------------------------------------------------------
	// First we have to spin through all the records to find out the size of
	// memory we'll have to allocate
	//----------------------------------------------------------------------
	//
	DWORD data_size=0;
	DWORD record_size=Database::e_FixedLength;
	WORD i;
	for (i=0; i<Database::e_DataBlockSize; ++i)
	{
		Record* record = m_idOffsets[i];
		while (record)
		{
			Check_Object(record);
			record_size += Record::e_FixedLength + record->m_nameLength;
			data_size += record->m_dataLength;
			record = record->m_nextIDRecord;
		}
	}

	//
	//----------------------------------------------------------------------
	// Allocate space in the destination stream to hold the entire database,
	// then make a second memorystream rooted where the data should start
	// writing to
	//----------------------------------------------------------------------
	//
	DynamicMemoryStream db_stream(record_size + data_size);

	MemoryStream
		record_stream(
			static_cast<BYTE*>(db_stream.GetPointer()),
			record_size
			);
	MemoryStream
		data_stream(
			static_cast<BYTE*>(db_stream.GetPointer())+record_size,
			data_size
			);
	//
	//----------------------------------
	// Now write out the database header
	//----------------------------------
	//
	record_stream << m_tag << m_formatVersion << m_contentVersion;
	record_stream << record_size << m_numberOfRecords << m_nextRecordID;


#if 0

	//
	//----------------------
	// Write out the records
	//----------------------
	//
	for (i=0; i<Database::e_DataBlockSize; ++i)
	{
		Record* record = m_idOffsets[i];
		while (record)
		{
			Check_Object(record);
			record->Save(m_handle, &record_stream, &data_stream);
			Verify(!record->m_buffer || record->m_deleteData);
			record = record->m_nextIDRecord;
		}
	}



#endif


	for (i = 0; i < m_numberOfRecords; ++i)
	{
		Stuff::RecordHandle record;
		record.m_databaseHandle = m_handle;

		record.m_ID = i;
		#if defined(_ARMOR)
			bool result =
		#endif
		record.FindID(true);
		Verify(result);

		Verify(record.m_record != NULL);
		Record* test_record = const_cast<Record*>(record.m_record);
		test_record->Save(m_handle, &record_stream, &data_stream);
		Verify(!test_record->m_buffer || test_record->m_deleteData);
	}


	Verify(record_stream.GetBytesRemaining() == 0);
	Verify(data_stream.GetBytesRemaining() >= 0);
	DWORD real_length = record_size + data_stream.GetBytesUsed();

	//
	//------------------------------------------------------
	// Now we tell the handle to actually write out the data
	//------------------------------------------------------
	//
	m_handle->Save(static_cast<BYTE*>(db_stream.GetPointer()), real_length);
	m_indexSize = record_size;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void Database::ScatterSave()
{
	Check_Object(this);

	//
	//----------------------------------------------------------------------
	// First we have to spin through all the records to find out the size of
	// memory we'll have to allocate
	//----------------------------------------------------------------------
	//
	DWORD data_size=0;
	DWORD record_size=Database::e_FixedLength;
	WORD i;
	for (i=0; i<Database::e_DataBlockSize; ++i)
	{
		Record* record = m_idOffsets[i];
		while (record)
		{
			Check_Object(record);
			record_size += Record::e_FixedLength + record->m_nameLength;
			data_size += record->m_dataLength;
			record = record->m_nextIDRecord;
		}
	}

	//
	//----------------------------------------------------------------------
	// Allocate space in the destination stream to hold the entire database,
	// then make a second memorystream rooted where the data should start
	// writing to
	//----------------------------------------------------------------------
	//
	DynamicMemoryStream db_stream(record_size + data_size);

	MemoryStream
		record_stream(
			static_cast<BYTE*>(db_stream.GetPointer()),
			record_size
			);
	MemoryStream
		data_stream(
			static_cast<BYTE*>(db_stream.GetPointer())+record_size,
			data_size
			);
	//
	//----------------------------------
	// Now write out the database header
	//----------------------------------
	//
	record_stream << m_tag << m_formatVersion << m_contentVersion;
	record_stream << record_size << m_numberOfRecords << m_nextRecordID;

	//
	//----------------------
	// Write out the records
	//----------------------
	//

	WORD start_value = (WORD)Random::GetLessThan(m_numberOfRecords);
	
	
	Stuff::RecordHandle record;
	record.m_databaseHandle = m_handle;


	for (i=start_value; i<m_numberOfRecords; ++i)
	{
		
		record.m_ID = i;
		#if defined(_ARMOR)
			bool result =
		#endif
		record.FindID(true);
		Verify(result);

		Verify(record.m_record != NULL);
		Record* test_record = const_cast<Record*>(record.m_record);
		Check_Object(test_record);


		test_record->SaveNoChange(m_handle, &record_stream, &data_stream);
		Verify(!test_record->m_buffer || test_record->m_deleteData);
		
		
	}

	for (i=0; i < start_value; ++i)
	{
		
		record.m_ID = i;
		#if defined(_ARMOR)
			bool result =
		#endif
		record.FindID(true);
		Verify(result);

		Verify(record.m_record != NULL);
		Record* test_record = const_cast<Record*>(record.m_record);
		Check_Object(test_record);

		test_record->SaveNoChange(m_handle, &record_stream, &data_stream);
		Verify(!test_record->m_buffer || test_record->m_deleteData);
		
		
	}


	Verify(record_stream.GetBytesRemaining() == 0);
	Verify(data_stream.GetBytesRemaining() >= 0);
	DWORD real_length = record_size + data_stream.GetBytesUsed();

	//
	//------------------------------------------------------
	// Now we tell the handle to actually write out the data
	//------------------------------------------------------
	//
	m_handle->Save(static_cast<BYTE*>(db_stream.GetPointer()), real_length);
	m_indexSize = record_size;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Database::~Database()
{
	Check_Object(this);
	for (int i=0; i<e_DataBlockSize; ++i)
	{
		Record *record = m_idOffsets[i];
		while (record)
		{
			Check_Object(record);
			Record *next = record->m_nextIDRecord;
			delete record;
			record = next;
		}
	}
}

//
//###########################################################################
//##########################    DatabaseHandle    ###########################
//###########################################################################
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DatabaseHandle::DatabaseHandle(
	const char* filename,
	bool read_only,
	DWORD content_version
)
{
	//
	//-----------------------------------------------------
	// Create the database heap if it doesn't already exist
	//-----------------------------------------------------
	//
	m_fileName = filename;
	if (!Database::s_FilesOpened++)
		g_DatabaseHeap = gos_CreateMemoryHeap("Database", 0, Stuff::g_LibraryHeap);
	const char* root_name = __mbsrchr(filename, '\\');
	if (!root_name)
		root_name = filename;
	else
		++root_name;
	m_heap = gos_CreateMemoryHeap(const_cast<char*>(root_name), 0, g_DatabaseHeap);
	m_dirtyFlag = false;
	m_contentVersion = content_version;

	//
	//-------------------------------------
	// Disable the file hooks for this part
	//-------------------------------------
	//
	void (__stdcall *old_get_hook)(const char*, BYTE**, DWORD*) = Environment.HookGetFile;
	Environment.HookGetFile = NULL;
	bool (__stdcall *old_exist_hook)(const char*) = Environment.HookDoesFileExist;
	Environment.HookDoesFileExist = NULL;
	gos_PushCurrentHeap(m_heap);

	//
	//------------------------
	// Initialize the pointers
	//------------------------
	//
	m_readOnly = read_only;
	m_MMHandle = NULL;
	m_fileAddress = NULL;
	m_fileLength = 0;
	m_fileHandle = NULL;
		
	//
	//-----------------------------
	// If we find the file, open it
	//-----------------------------
	//
	if (gos_DoesFileExist(filename))
	{
		gos_OpenFile(&m_fileHandle, filename, READONLY);
		m_dataBase = new(m_heap) Database(this, content_version, m_fileHandle);
	}

	//
	//-----------------------------------------------------------------------------------
	// Otherwise, we have to look in the registry for the CD key to try and find the file
	// there
	//-----------------------------------------------------------------------------------
	//
	else if (read_only)
	{
		char cd_path[256];
		DWORD size=sizeof(cd_path);
		gos_LoadDataFromRegistry(
			"CDPath",
			cd_path,
			&size,
			TRUE
		);
		if (size>0)
		{
			size--;
			Verify(strlen(cd_path) == size);
			m_fileName=MString(cd_path)+m_fileName;
			bool res=true;
			while(!gos_DoesFileExist(m_fileName) && res)
				{
				if(NoCDMessageFunction)
					res=NoCDMessageFunction();
				
				if(!res || !NoCDMessageFunction)
				{				
				#ifdef LAB_ONLY
				
					STOP(("Can't open %s", filename));
				#endif
					ProcessingError=1;
					ExitGameOS();
				}
				
				}
			
			gos_OpenFile(&m_fileHandle, m_fileName, READONLY);
			m_dataBase = new(m_heap) Database(this, content_version, m_fileHandle);
		}
		else
			STOP(("Can't open %s", filename));
	}
	//
	//------------------------------------------
	// The file doesn't exist, so create it here
	//------------------------------------------
	//
	else
		m_dataBase = new(m_heap) Database(this, content_version);



	//
	//-------------------------------------
	// Disable the file hooks for this part
	//-------------------------------------
	//
	gos_PopCurrentHeap();
	Environment.HookGetFile = old_get_hook;
	Environment.HookDoesFileExist = old_exist_hook;
}



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DatabaseHandle::DatabaseHandle(
	const char* filename,
	MemoryStream *stream,
	DWORD content_version
)
{
	//
	//-----------------------------------------------------
	// Create the database heap if it doesn't already exist
	//-----------------------------------------------------
	//
	m_fileName = filename;
	if (!Database::s_FilesOpened++)
		g_DatabaseHeap = gos_CreateMemoryHeap("Database", 0, Stuff::g_LibraryHeap);
	const char* root_name = __mbsrchr(filename, '\\');
	if (!root_name)
		root_name = filename;
	else
		++root_name;
	m_heap = gos_CreateMemoryHeap(const_cast<char*>(root_name), 0, g_DatabaseHeap);
	m_dirtyFlag = false;
	m_contentVersion = content_version;
	
	m_readOnly = false;
	m_fileHandle = NULL;
	m_MMHandle = NULL;
	m_fileAddress = (unsigned char*)stream->GetPointer();
	m_fileLength = stream->GetSize();
	
	m_dataBase = new(m_heap) Database(this, content_version, stream);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DatabaseHandle::~DatabaseHandle()
{
	Check_Object(this);

	//
	//---------------------------------------------------------------
	// If we are closing a read only file, release the memmap handle,
	// otherwise delete the database object
	//---------------------------------------------------------------
	//
	Check_Object(m_dataBase);
	delete m_dataBase;

	//
	//--------------------
	// Close off the files
	//--------------------
	//
	if (m_fileHandle)
	{
		Verify(!m_MMHandle);
		gos_CloseFile(m_fileHandle);
	}
	else if (m_MMHandle)
		gos_CloseMemoryMappedFile(m_MMHandle);

	//
	//----------------------------------------------------
	// Delete the memory heap if this is the last database
	//----------------------------------------------------
	//
	gos_DestroyMemoryHeap(m_heap);
	if (--Database::s_FilesOpened == 0)
		gos_DestroyMemoryHeap(g_DatabaseHeap);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::Save()
{
	Check_Object(this);

	//
	//--------------------------------------
	// If there were no changes, we are done
	//--------------------------------------
	//
	if (!m_dirtyFlag)
		return;

	m_dataBase->Save();
	m_dirtyFlag = false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::ScatterSave()
{
	Check_Object(this);



	m_dataBase->ScatterSave();
	m_dirtyFlag = false;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::SaveAs(const char* filename)
{
	Check_Object(this);

	m_fileName = filename;
	m_dataBase->Save();
	m_dirtyFlag = false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::Save(BYTE *address, DWORD length)
{
	Check_Object(this);

	//
	//--------------------------------------
	// If we are using a file, write to that
	//--------------------------------------
	//
	if (m_fileHandle)
		gos_CloseFile(m_fileHandle);

	gos_OpenFile(&m_fileHandle, m_fileName, READWRITE);
	gos_SeekFile(m_fileHandle, FROMSTART, 0);
	gos_WriteFile(m_fileHandle, address, length);
	gos_CloseFile(m_fileHandle);
	gos_OpenFile(&m_fileHandle, m_fileName, READONLY);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::SpewLoadedRecords()
{
	Check_Object(this);

	//
	//----------------------
	// Write out the records
	//----------------------
	//
	#if defined(LAB_ONLY)
		for (DWORD i=0; i<Database::e_DataBlockSize; ++i)
		{
			Record* record = m_dataBase->m_idOffsets[i];
			while (record)
			{
				Check_Object(record);
				if (record->m_deleteData)
					SPEWALWAYS((NULL, "%d\t%s", record->m_dataLength, record->m_name));
				record = record->m_nextIDRecord;
			}
		}
	#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::UnloadRecords()
{
	Check_Object(this);

	//
	//----------------------
	// Write out the records
	//----------------------
	//
	for (DWORD i=0; i<Database::e_DataBlockSize; ++i)
	{
		Record* record = m_dataBase->m_idOffsets[i];
		while (record)
		{
			Check_Object(record);
			record->UnloadData();
			record = record->m_nextIDRecord;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
WORD DatabaseHandle::GetNumberOfRecords()
{
	Check_Object(this);
	return m_dataBase->m_numberOfRecords;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

__int64 DatabaseHandle::CalculateCRC(int key)
{
	Check_Object(this);

	//SPEW(("jerryeds", "START DATABASE CRC-----------------------------"));

	__int64 total_crc = 0;
	//__int64 acumulator = 0;
	//size_t acumulator_size = 0;

	size_t bytes_left = m_fileLength - m_dataBase->m_indexSize;
	WORD *word_data;
	BYTE *temp_buf = NULL;
	if (m_fileAddress)
		word_data = Cast_Pointer(WORD*, m_fileAddress + m_dataBase->m_indexSize);
	else
	{
		temp_buf = new BYTE[bytes_left];
		gos_SeekFile(m_fileHandle, FROMSTART, m_dataBase->m_indexSize);
		gos_ReadFile(m_fileHandle, temp_buf, bytes_left);
		word_data = Cast_Pointer(WORD*, temp_buf);
	}
	Check_Pointer(word_data);
	

#if 0
	__int64 local_acumulator = 0;

	while (bytes_left > 0)
	{
		if (bytes_left > 1)
		{
			Check_Pointer(word_data);

			acumulator += *word_data;
			local_acumulator += *word_data;
			word_data += 1;
			acumulator_size += sizeof(WORD);
			bytes_left -= sizeof(WORD);
		}
		else
		{
			BYTE *sub_data = (BYTE*)word_data;

			Check_Pointer(sub_data);

			acumulator += *sub_data;
			local_acumulator += *sub_data;
			acumulator_size += sizeof(BYTE);
			bytes_left -= sizeof(BYTE);
		}
	}

	//SPEW(("jerryeds", "%s : %u",handle.m_name, local_acumulator ));

	// every 2 meg we xor with the crc...
	if (acumulator_size)
	{
		total_crc = total_crc ^ acumulator;
	}


	//SPEW(("jerryeds", "STOP DATABASE CRC-----------------------------"));

#endif


	MD5_CTX resource_context;
	GUID resource_hash;
	unsigned char * buff=(unsigned char *) &resource_hash;
	
	MD5Init(&resource_context);
	MD5Update(&resource_context, (unsigned char *) &key, 4);
	MD5Update(&resource_context, (unsigned char *) word_data, bytes_left);
	
	MD5Final(buff, &resource_context);


	__int64 first_half = (__int64)buff[0];
	__int64 second_half = (__int64)buff[8];

	total_crc = first_half ^ second_half;

	if (temp_buf)
		delete temp_buf;



	return total_crc;

}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
__int64 DatabaseHandle::CalculateDateCRC()
{

	return gos_FileTimeStamp(m_fileName);

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DatabaseHandle::TestInstance() const
{
}
