#include "StuffHeaders.hpp" #include "Database.hpp" #include #include #include #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(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(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(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(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(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(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(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(m_record); m_record = data; #if defined(_ARMOR) const_cast(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(m_record)->Unhook(this); #if defined(_ARMOR) const_cast(m_record)->m_referenceCount = 0; #endif delete const_cast(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(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(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(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(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(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; im_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(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(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; iReadBytes(&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; im_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(db_stream.GetPointer()), record_size ); MemoryStream data_stream( static_cast(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; iSave(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.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(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; im_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(db_stream.GetPointer()), record_size ); MemoryStream data_stream( static_cast(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(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.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(db_stream.GetPointer()), real_length); m_indexSize = record_size; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Database::~Database() { Check_Object(this); for (int i=0; im_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(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(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; im_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; im_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 { }