mojo_client/src/Client/SCPDatabase.cpp
false_genesis 76ebbe5cf3 * rewrote the SCP data storage. note that all database files must now be placed in a directory added with "AddDBPath <path>" and must contain a #dbname tag. the db now converts SCP (text) to SCC (binary) files, which increases access speed a lot. also less RAM used in most cases.
* replaced "LoadSCP" func with "LoadDB <dbname>" (different syntax!); removed all other scp db related funcs except "getscpvalue"
* the GUI can now show texts stored in databases
* added displaying status to SceneLogin
* misc fixes/cleanups
2008-04-19 23:45:37 +00:00

840 lines
27 KiB
C++

#include <fstream>
#include "common.h"
#include "Auth/MD5Hash.h"
#include "SCPDatabase.h"
#define HEADER_SIZE (21*sizeof(uint32))
inline char *gettypename(uint32 ty)
{
return (ty==0 ? "INT" : (ty==1 ? "FLOAT" : "STRING"));
}
// file-globally declared pointer holder. NOT multi-instance-safe for now!!
struct memblock
{
memblock() : ptr(NULL), size(0) {}
memblock(uint8 *p, uint32 s) : size(s), ptr(p) {}
~memblock() { if(ptr) delete [] ptr; }
uint8 *ptr;
uint32 size;
};
TypeStorage<memblock> Pointers;
SCPDatabase::~SCPDatabase()
{
DEBUG(logdebug("Deleting SCPDatabase '%s'",_name.c_str()));
DropAll();
}
SCPDatabase::SCPDatabase()
{
_stringbuf = NULL;
_intbuf = NULL;
_compact = false;
}
void SCPDatabase::DropAll(void)
{
DropTextData();
if(_stringbuf)
delete [] _stringbuf;
if(_intbuf)
delete [] _intbuf;
_indexes.clear();
_indexes_reverse.clear();
_fielddefs.clear();
_stringbuf = NULL;
_intbuf = NULL;
_stringsize = 0;
_compact = false;
}
void SCPDatabase::DropTextData(void)
{
DEBUG(logdebug("Dropping plaintext parts of DB '%s'",_name.c_str()));
for(SCPSourceList::iterator it = sources.begin(); it != sources.end(); it++)
Pointers.Delete(*it);
sources.clear();
fields.clear();
}
void *SCPDatabase::GetPtr(uint32 index, char *entry)
{
std::map<uint32,uint32>::iterator it = _indexes.find(index);
if(it == _indexes.end())
return NULL;
std::map<std::string,SCPFieldDef>::iterator fi = _fielddefs.find(entry);
if(fi == _fielddefs.end())
return NULL;
uint32 target_row = _indexes[index];
uint32 field_id = _fielddefs[entry].id;
return (void*)&_intbuf[(_fields_per_row * target_row) + field_id];
}
void *SCPDatabase::GetPtrByField(uint32 index, uint32 entry)
{
std::map<uint32,uint32>::iterator it = _indexes.find(index);
if(it == _indexes.end())
return NULL;
uint32 target_row = _indexes[index];
return (void*)&_intbuf[(_fields_per_row * target_row) + entry];
}
uint32 SCPDatabase::GetFieldByUint32Value(char *entry, uint32 val)
{
std::map<std::string,SCPFieldDef>::iterator fi = _fielddefs.find(entry);
if(fi == _fielddefs.end())
return SCP_INVALID_INT;
uint32 field_id = _fielddefs[entry].id;
return GetFieldByUint32Value(field_id,val);
}
uint32 SCPDatabase::GetFieldByUint32Value(uint32 entry, uint32 val)
{
for(uint32 row = 0; row < _rowcount; row++)
if(_intbuf[row * _fields_per_row + entry] == val)
return _indexes_reverse[row];
return SCP_INVALID_INT;
}
uint32 SCPDatabase::GetFieldByIntValue(char *entry, int32 val)
{
std::map<std::string,SCPFieldDef>::iterator fi = _fielddefs.find(entry);
if(fi == _fielddefs.end())
return SCP_INVALID_INT;
uint32 field_id = _fielddefs[entry].id;
return GetFieldByIntValue(field_id,val);
}
uint32 SCPDatabase::GetFieldByIntValue(uint32 entry, int32 val)
{
for(uint32 row = 0; row < _rowcount; row++)
if((int)_intbuf[row * _fields_per_row + entry] == val)
return _indexes_reverse[row];
return (int)SCP_INVALID_INT;
}
uint32 SCPDatabase::GetFieldByStringValue(char *entry, char *val)
{
std::map<std::string,SCPFieldDef>::iterator fi = _fielddefs.find(entry);
if(fi == _fielddefs.end())
return SCP_INVALID_INT;
uint32 field_id = _fielddefs[entry].id;
return GetFieldByStringValue(field_id,val);
}
uint32 SCPDatabase::GetFieldByStringValue(uint32 entry, char *val)
{
for(uint32 row = 0; row < _rowcount; row++)
if(!stricmp(GetStringByOffset(_intbuf[row * _fields_per_row + entry]), val))
return row;
return SCP_INVALID_INT;
}
uint32 SCPDatabase::GetFieldType(char *entry)
{
std::map<std::string,SCPFieldDef>::iterator it = _fielddefs.find(entry);
if(it != _fielddefs.end())
return _fielddefs[entry].type;
return SCP_INVALID_INT;
}
uint32 SCPDatabase::GetFieldId(char *entry)
{
std::map<std::string,SCPFieldDef>::iterator it = _fielddefs.find(entry);
if(it != _fielddefs.end())
return _fielddefs[entry].id;
return SCP_INVALID_INT;
}
SCPDatabase *SCPDatabaseMgr::GetDB(std::string n, bool create)
{
return create ? _map.Get(n) : _map.GetNoCreate(n);
}
uint32 SCPDatabaseMgr::AutoLoadFile(char *fn)
{
char *buf;
uint32 size;
// check if file was loaded before; use memory data if this is the case
if(memblock *mb = Pointers.GetNoCreate(fn))
{
size = mb->size;
buf = (char*)mb->ptr;
}
else // and if not, read the file from disk
{
std::fstream fh;
size = GetFileSize(fn);
if(!size)
return 0;
fh.open(fn,std::ios_base::in | std::ios_base::binary);
if( !fh.is_open() )
return 0;
buf = new char[size];
fh.read(buf,size);
fh.close();
// store the loaded file buffer so we can reuse it later if necessary
Pointers.Assign(fn, new memblock((uint8*)buf,size));
}
std::string line,dbname,entry,value;
SCPDatabase *db = NULL;
uint32 id = 0, sections = 0;
for(uint32 pos = 0; pos < size; pos++)
{
if(buf[pos] == '\n' || buf[pos] == 10 || buf[pos] == 13)
{
if(line.empty())
continue;
while(line.size() && (line[0]==' ' || line[0]=='\t'))
line.erase(0,1);
uint32 eq = line.find("=");
if(eq != std::string::npos)
{
entry=stringToLower(line.substr(0,eq));
value=line.substr(eq+1,line.length()-1);
if(!stricmp(entry.c_str(),"#dbname") && value.size())
{
dbname = value;
db = GetDB(dbname,true); // create db if not existing
}
else if(db)
db->fields[id][entry] = value;
}
else if(line[0]=='[')
{
id=(uint32)toInt(line.c_str()+1); // start reading after '['
sections++;
}
line.clear();
}
else
line += buf[pos];
}
db->sources.insert(fn);
return sections;
}
////////////////////////////////////////////////////////
// SCP compiler
////////////////////////////////////////////////////////
// check the datatype that will be used for this string value
uint32 SCPDatabaseMgr::GetDataTypeFromString(char *s)
{
bool isint = true, first = true;
for(;*s;s++) // check every char until \0 is reached
{
if(!isdigit(*s) && !(first && (*s=='+' || *s=='-'))) // if not in 0-9 or first beeing "+" or "-" it cant be int...
{
isint = false;
if(*s != '.') // and if the char beeing not int isnt a dot (3.1415), it cant be float either
return SCP_TYPE_STRING;
}
first = false;
}
return isint ? SCP_TYPE_INT : SCP_TYPE_FLOAT;
}
bool SCPDatabaseMgr::Compact(char *dbname, char *outfile)
{
logdebug("Compacting database '%s' into file '%s'", dbname, outfile);
SCPDatabase *db = GetDB(dbname);
if(!db || db->fields.empty() || db->sources.empty())
{
logerror("Compact(\"%s\",\"%s\") failed, DB doesn't exist or is empty",dbname,outfile);
return false;
}
std::map<std::string, SCPFieldDef> fieldIdMap;
std::map<uint32,uint32> idToSectionMap;
ByteBuffer stringdata(5000);
uint32 cur_idx, pass = 0;
std::string line;
uint32 section=0;
uint32 *membuf = NULL; // stores ints, floats and string offsets from ByteBuffer stringdata
uint32 blocksize;
std::string entry,value;
uint32 field_id;
// some preparations
uint32 nStrings = 0, nRows = db->fields.size(), nFields; // pre-declared
stringdata << ""; // write an empty string so that offset 0 will always return empty string
// the whole process is divided into 2 passes:
// - pass 0 autodetects the datatypes stored in the dirfferent entries (SCPFieldDef)
// - pass 1 creates the data field holding the values and string offsets, and also stuffs string values into the ByteBuffer
while(pass < 2)
{
cur_idx = 1; // stores the current index of the last entry added. index 0 is always field_id!
section = 0; // stores how many sections are done already
for(SCPFieldMap::iterator fmit = db->fields.begin(); fmit != db->fields.end(); fmit++)
{
SCPEntryMap& emap = fmit->second;
field_id = fmit->first; // stores the field id (e.g. "[154]" in the scp file)
idToSectionMap[field_id] = section; // since not every field id exists, store field id and its section number
// for faster lookup, less overhead, and smaller files
// write the field id into row position 0 of the data field
if(pass == 1)
{
ASSERT(membuf != NULL);
uint32 pos = section * nFields;
ASSERT(pos < blocksize);
membuf[pos] = field_id;
}
for(SCPEntryMap::iterator eit = emap.begin(); eit != emap.end(); eit++)
{
// "entry=value" in the scp file
entry = eit->first;
value = eit->second;
// pass 0 - autodetect field types
if(pass == 0)
{
if(fieldIdMap.find(entry) == fieldIdMap.end())
{
SCPFieldDef d;
d.id = cur_idx++;
d.type = GetDataTypeFromString((char*)value.c_str());
fieldIdMap[entry] = d;
DEBUG(logdebug("Found new key: '%s' id: %u type: %s", entry.c_str(), d.id, gettypename(d.type) ));
}
else
{
SCPFieldDef& d = fieldIdMap[entry];
uint32 _oldtype = d.type;
// int can be changed to float or string, float only to string. changing back not allowed.
if(d.type == SCP_TYPE_INT)
{
d.type = GetDataTypeFromString((char*)value.c_str());
}
else if(d.type == SCP_TYPE_FLOAT)
{
uint32 newtype = GetDataTypeFromString((char*)value.c_str());
if(newtype == SCP_TYPE_STRING)
d.type = newtype;
}
if(_oldtype != d.type)
{
logdebug("Key '%s' id %u changed from %s to %s (field_id: %u)", entry.c_str(), d.id, gettypename(_oldtype), gettypename(d.type),field_id);
}
}
}
// pass 1 - create the data field
else if(pass == 1)
{
ASSERT(membuf != NULL);
SCPFieldDef d = fieldIdMap[entry];
uint32 pos = (section * nFields) + d.id;
ASSERT(pos < blocksize);
if(d.type == SCP_TYPE_STRING)
{
membuf[pos] = stringdata.wpos();
stringdata << value;
nStrings++;
}
else if(d.type == SCP_TYPE_INT)
{
membuf[pos] = atoi(value.c_str());
}
else if(d.type == SCP_TYPE_FLOAT)
{
((float*)membuf)[pos] = atof(value.c_str());
}
}
}
section++;
}
// if the first pass is done, allocate the data field for the second pass
if(!pass)
{
nFields = fieldIdMap.size() + 1; // add the one field used for the field ID
ASSERT(section == nRows);
blocksize = nRows * nFields; // +1 because we store the field id here also
DEBUG(logdebug("SCP: allocating %u*%u = %u integers (%u bytes)",nRows,fieldIdMap.size()+1,blocksize, blocksize*sizeof(uint32)));
membuf = new uint32[blocksize];
memset(membuf, 0, blocksize * sizeof(uint32));
}
pass++;
}
// used header fields, some are pre-declared above, if needed
uint32 offsMD5, nMD5, sizeMD5;
uint32 offsIndexes, nIndexes, sizeIndexes;
uint32 offsFields, sizeFields;
uint32 offsData;
uint32 offsStrings, sizeStrings;
// MD5 hashes of source files
SCPSourceList& src = db->sources;
ByteBuffer md5buf;
nMD5 = 0;
for(SCPSourceList::iterator it = src.begin(); it != src.end(); it++)
{
memblock *mb = Pointers.GetNoCreate(*it);
if(!mb)
{
// if we reach this point there was really some big f*** up
logerror("SCP Compact: memblock for file '%s' doesn't exist",it->c_str());
continue;
}
MD5Hash md5;
md5.Update(mb->ptr,mb->size);
md5.Finalize();
md5buf << *it;
md5buf.append(md5.GetDigest(),md5.GetLength());
nMD5++;
}
sizeMD5 = md5buf.size();
// field types, sorted by IDs
ByteBuffer fieldbuf;
// put the entries and their type into ByteBuffer, sorted by their position in the membuf rows
// note that the first field in the data row is always the field id, so the values start from 1
for(std::map<std::string,SCPFieldDef>::iterator itf = fieldIdMap.begin(); itf != fieldIdMap.end(); itf++)
{
fieldbuf << itf->first << itf->second.id << itf->second.type; // entry name, id, type.
}
sizeFields = fieldbuf.size();
// index -> ID lookup table, e.g. data with ID 500 will have field index 214, because some IDs in between are missing
// it *could* be calculated at load-time from the existing data field, but this way is faster when random-accessing the file itself
// (what we dont do anyway, for now)
ByteBuffer indexbuf;
for(std::map<uint32,uint32>::iterator itx = idToSectionMap.begin(); itx != idToSectionMap.end(); itx++)
{
indexbuf << itx->first << itx->second; // field id; row number
}
nIndexes = idToSectionMap.size();
sizeIndexes = indexbuf.size();
// string data
// -- most of it is handled somewhere above
sizeStrings = stringdata.size();
// precalc absolute offsets, header is 18 bytes large
offsMD5 = HEADER_SIZE;
offsIndexes = offsMD5 + sizeMD5;
offsFields = offsIndexes + sizeIndexes;
offsData = offsFields + sizeFields;
offsStrings = offsData + blocksize * sizeof(uint32);
// buffer the file header
ByteBuffer hbuf;
hbuf.append("SCPC",4); // identifier
hbuf << (uint32)0; // flags, not yet used
hbuf << (uint32)0 << (uint32)0 << (uint32)0 << (uint32)0; // padding, not yet used
hbuf << offsMD5 << nMD5 << sizeMD5;
hbuf << offsIndexes << nIndexes << sizeIndexes;
hbuf << offsFields << nFields << sizeFields;
hbuf << offsData << section << blocksize * sizeof(uint32);
hbuf << offsStrings << nStrings << sizeStrings;
FILE *fh = fopen(outfile,"wb");
if(!fh)
return false;
if(fh)
{
fwrite(hbuf.contents(),hbuf.size(),1,fh);
if(sizeMD5) // just in case no md5sums are stored
fwrite(md5buf.contents(),sizeMD5,1,fh);
fwrite(indexbuf.contents(),sizeIndexes,1,fh);
fwrite(fieldbuf.contents(),sizeFields,1,fh);
fwrite(membuf,sizeof(uint32),blocksize,fh);
fwrite(stringdata.contents(),sizeStrings,1,fh);
}
fclose(fh);
if(!db)
db = GetDB(dbname,true); // create if not exist
db->_compact = true;
db->_name = dbname;
// drop all data no longer needed if the database is compacted
db->DropTextData();
// we keep the membuf, since the compiled data are now usable as if loaded directly from a file
// associate it with the buffers used by the db accessing functions
db->_stringbuf = new char[sizeStrings];
db->_stringsize = sizeStrings;
memcpy(db->_stringbuf,stringdata.contents(),sizeStrings);
db->_intbuf = membuf; // <<-- do NOT drop the membuf, its still used and will be deleted with ~SCPDatabase()!!
db->_fields_per_row = nFields;
db->_rowcount = nRows;
db->_indexes = idToSectionMap;
db->_fielddefs = fieldIdMap;
for(std::map<uint32,uint32>::iterator it = idToSectionMap.begin(); it != idToSectionMap.end(); it++)
db->_indexes_reverse[it->second] = it->first;
return true;
}
uint32 SCPDatabaseMgr::SearchAndLoad(char *dbname, bool no_compiled)
{
uint32 count = 0;
std::deque<std::string> goodfiles;
for(std::deque<std::string>::iterator it = _paths.begin(); it != _paths.end(); it++)
{
std::deque<std::string> files = GetFileList(*it);
sort(files.begin(),files.end()); // rough alphabetical sort
for(std::deque<std::string>::iterator itf = files.begin(); itf != files.end(); itf++)
{
std::string& fn = *itf;
if(fn.length() < 5)
continue;
std::string filepath = *it + fn;
// check for special case: <dbname>.ccp in this directory? load it and skip the rest
if(!no_compiled && !stricmp(std::string(dbname).append(".ccp").c_str(), fn.c_str()))
{
logdebug("Loading pre-compacted database '%s%s'", it->c_str(), fn.c_str());
DropDB(dbname); // if sth got loaded before, remove that
// load SCC database file and skip rest
if(LoadCompactSCP((char*)filepath.c_str(), dbname))
{
logdebug("Loaded '%s' -> %s, skipping scp files",filepath.c_str(),dbname);
return 1;
}
}
else if(!stricmp(fn.c_str() + fn.length() - 4, ".scp"))
{
// skip 0-byte files
if(GetFileSize(filepath.c_str()))
goodfiles.push_back(filepath);
}
}
}
logdetail("Pre-compacted SCC file for '%s' invalid, creating from SCP (%u files total)",dbname,goodfiles.size());
for(std::deque<std::string>::iterator it = goodfiles.begin(); it != goodfiles.end(); it++)
{
bool load_it = false;
std::fstream fh;
fh.open( it->c_str() , std::ios_base::in | std::ios_base::binary);
if( !fh.is_open() )
continue;
uint32 size = 1000; // search for #dbname tag in first 1000 bytes
char *buf = new char[size];
memset(buf,0,size);
fh.read(buf,size);
fh.close();
std::string line,dbn;
for(uint32 pos = 0; pos < size; pos++)
{
if(buf[pos] == '\n' || buf[pos] == 10 || buf[pos] == 13)
{
if(line.empty())
continue;
while(line.size() && (line[0]==' ' || line[0]=='\t'))
line.erase(0,1);
if(line[0] == '#')
{
if(!strnicmp(line.c_str(),"#dbname=",8) && !stricmp(line.c_str()+8, dbname))
{
load_it = true;
break;
}
}
line.clear();
}
else
line += buf[pos];
}
delete [] buf;
if(load_it)
{
logdebug("File '%s' matching database '%s', loading", it->c_str(), dbname);
count++;
uint32 sections = AutoLoadFile((char*)it->c_str());
}
}
char fn[100];
sprintf(fn,"./cache/%s.ccp",dbname);
Compact(dbname, fn);
return count;
}
void SCPDatabaseMgr::AddSearchPath(char *path)
{
std::string p;
// normalize path, use '/' instead of '\'. needed for that check below
uint32 len = strlen(path);
for(uint32 i = 0; i < len; i++)
{
if(path[i] == '\\')
p += '/';
else
p += path[i];
}
if(p[p.size()-1] != '/')
p += '/';
for(std::deque<std::string>::iterator it = _paths.begin(); it != _paths.end(); it++)
{
// windows doesnt care about UPPER/lowercase, while *nix does;
// a path shouldnt be added twice or unexpected behavior is likely to occur
#if PLATFORM == PLATFORM_WIN32
if(!stricmp(p.c_str(), it->c_str()))
return;
#else
if(p == *it)
return;
#endif
}
_paths.push_back(p);
}
bool SCPDatabaseMgr::LoadCompactSCP(char *fn, char *dbname)
{
uint32 filesize = GetFileSize(fn);
if(filesize < HEADER_SIZE)
{
logerror("Database file '%s' is too small!",fn);
return false;
}
std::fstream fh;
fh.open(fn, std::ios_base::in | std::ios_base::binary);
if(!fh.is_open())
{
logerror("Error opening '%s'",fn);
return false;
}
ByteBuffer bb;
bb.resize(filesize);
fh.read((char*)bb.contents(), filesize);
fh.close();
char tag[4];
uint32 flags, padding[4];
uint32 offsMD5, nMD5, sizeMD5;
uint32 offsIndexes, nIndexes, sizeIndexes;
uint32 offsFields, nFields, sizeFields;
uint32 offsData, nRows, sizeData;
uint32 offsStrings, nStrings, sizeStrings;
bb.read((uint8*)&tag[0],4);
if(memcmp(tag,"SCPC",4))
{
logerror("'%s' is not a compact database file!",fn);
return false;
}
bb >> flags;
bb >> padding[0] >> padding[1] >> padding[2] >> padding[3];
bb >> offsMD5 >> nMD5 >> sizeMD5;
bb >> offsIndexes >> nIndexes >> sizeIndexes;
bb >> offsFields >> nFields >> sizeFields;
bb >> offsData >> nRows >> sizeData;
bb >> offsStrings >> nStrings >> sizeStrings;
SCPDatabase *db = GetDB(dbname,true);
db->_name = dbname;
db->_compact = true;
ByteBuffer md5buf(sizeMD5);
md5buf.resize(sizeMD5);
// read MD5 block
bb.rpos(offsMD5);
if(bb.rpos() == offsMD5)
bb.read((uint8*)md5buf.contents(),sizeMD5);
else
{
logerror("'%s' has wrong MD5 offset, can't load",fn);
return false;
}
for(uint32 i = 0; i < nMD5; i++)
{
// read filename and MD5 hash from compiled database
uint8 buf[MD5_DIGEST_LENGTH];
std::string refFn;
md5buf >> refFn;
md5buf.read(buf,MD5_DIGEST_LENGTH);
// load the file referred to
uint32 refFileSize = GetFileSize(refFn.c_str());
FILE *refFile = fopen(refFn.c_str(), "rb");
if(!refFile)
{
logdebug("Not loading '%s', file doesn't exist",fn);
return false;
}
uint8 *refFileBuf = new uint8[refFileSize];
fread(refFileBuf,sizeof(uint8),refFileSize,refFile);
fclose(refFile);
db->sources.insert(refFn);
Pointers.Assign(refFn, new memblock(refFileBuf,refFileSize));
MD5Hash md5;
md5.Update(refFileBuf,refFileSize);
md5.Finalize();
if(memcmp(buf, md5.GetDigest(), MD5_DIGEST_LENGTH))
{
logdebug("MD5-check: '%s' has changed!", refFn.c_str());
return false;
}
else
{
logdebug("MD5-check: '%s' -> OK",refFn.c_str());
}
}
// everything good so far? we reached this point? then its likely that the rest of the file is ok, alloc remaining buffers
ByteBuffer indexbuf(sizeIndexes);
ByteBuffer fieldsbuf(sizeFields);
indexbuf.resize(sizeIndexes);
fieldsbuf.resize(sizeFields);
// read indexes block
bb.rpos(offsIndexes);
if(bb.rpos() == offsIndexes)
bb.read((uint8*)indexbuf.contents(),sizeIndexes);
else
{
logerror("'%s' has wrong indexes offset, can't load",fn);
return false;
}
// read field definitions buf
bb.rpos(offsFields);
if(bb.rpos() == offsFields)
bb.read((uint8*)fieldsbuf.contents(),sizeFields);
else
{
logerror("'%s' has wrong field defs offset, can't load",fn);
return false;
}
// main data and string blocks follow below
for(uint32 i = 0; i < nIndexes; i++)
{
uint32 field_id, row;
indexbuf >> field_id >> row;
db->_indexes[field_id] = row;
db->_indexes_reverse[row] = field_id;
}
for(uint32 i = 0; i < nFields - 1; i++) // the first field (index column) is never written to the file!
{
SCPFieldDef fieldd;
std::string fieldn;
fieldsbuf >> fieldn >> fieldd.id >> fieldd.type;
db->_fielddefs[fieldn] = fieldd;
}
// read main data block
ASSERT(nRows * nFields == sizeData / sizeof(uint32));
bb.rpos(offsData);
if(bb.rpos() == offsData)
{
db->_intbuf = new uint32[nRows * nFields];
bb.read((uint8*)db->_intbuf, sizeData); // load this somewhat fast and without a for loop
}
else
{
logerror("'%s' has wrong data offset, can't load",fn);
return false;
}
// read strings
bb.rpos(offsStrings);
if(bb.rpos() == offsStrings)
{
db->_stringbuf = new char[sizeStrings];
bb.read((uint8*)db->_stringbuf,sizeStrings);
}
else
{
logerror("'%s' has wrong strings offset, can't load",fn);
return false;
}
db->_stringsize = sizeStrings;
db->_rowcount = nRows;
db->_fields_per_row = nFields;
db->DropTextData(); // delete pointers to file content created at md5 comparison
logdebug("'%s' loaded successfully",fn);
return true;
}
// used only for debugging
void SCPDatabase::DumpStructureToFile(char *fn)
{
std::ofstream f;
f.open(fn);
if(!f.is_open())
return;
uint32 *ftype = new uint32[_fields_per_row];
ftype[0] = SCP_TYPE_INT;
f << "Fields: (0 is always index field)\n";
for(std::map<std::string,SCPFieldDef>::iterator it = _fielddefs.begin(); it != _fielddefs.end(); it++)
{
f << "-> Name: " << it->first << ", ID: " << it->second.id << ", type: " << gettypename(it->second.type) << "\n";
ftype[it->second.id] = it->second.type;
}
f << "\n";
for(uint32 row = 0; row < _rowcount; row++)
{
for(uint32 column = 0; column < _fields_per_row; column++)
{
if(ftype[column] == SCP_TYPE_INT)
f << *((int*)&_intbuf[row * _fields_per_row + column]) << "\t";
else if(ftype[column] == SCP_TYPE_FLOAT)
f << *((float*)&_intbuf[row * _fields_per_row + column]) << "\t";
else
f << "S_" << _intbuf[row * _fields_per_row + column] << "\t";
}
f << "\n";
}
f << "\nStrings:\n";
for(uint32 i = 0; i < _stringsize; i++)
{
if(!_stringbuf[i])
{
i++;
if(i >= _stringsize)
break;
f << "\n" << i << ": ";
}
f << _stringbuf[i];
}
}