Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2037 lines
51 KiB
C++
2037 lines
51 KiB
C++
#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
|
|
{
|
|
}
|