GOB: Add support for dBase III files

Implementing o7_opendBase, o7_closedBase and o7_getDBString

svn-id: r55676
This commit is contained in:
Sven Hesse 2011-01-31 10:49:43 +00:00
parent 0f0dbe7b78
commit b187551a01
7 changed files with 588 additions and 21 deletions

175
engines/gob/databases.cpp Normal file
View file

@ -0,0 +1,175 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "common/file.h"
#include "gob/databases.h"
namespace Gob {
Databases::Databases() : _language(Common::UNK_LANG) {
}
Databases::~Databases() {
}
void Databases::setLanguage(Common::Language language) {
Common::String lang;
if (language == Common::UNK_LANG)
lang = "";
else if (language == Common::EN_ANY)
lang = "E";
else if (language == Common::EN_GRB)
lang = "E";
else if (language == Common::EN_USA)
lang = "E";
else if (language == Common::DE_DEU)
lang = "G";
else if (language == Common::FR_FRA)
lang = "F";
else
warning("Databases::setLanguage(): Language \"%s\" not supported",
Common::getLanguageDescription(language));
if (!_databases.empty() && (lang != _language))
warning("Databases::setLanguage(): \"%s\" != \"%s\" and there's still databases open!",
_language.c_str(), lang.c_str());
_language = lang;
}
bool Databases::open(const Common::String &id, const Common::String &file) {
if (_databases.contains(id)) {
warning("Databases::open(): A database with the ID \"%s\" already exists", id.c_str());
return false;
}
Common::File dbFile;
if (!dbFile.open(file)) {
warning("Databases::open(): No such file \"%s\"", file.c_str());
return false;
}
dBase db;
if (!db.load(dbFile)) {
warning("Databases::open(): Failed loading database file \"%s\"", file.c_str());
return false;
}
_databases.setVal(id, Common::StringMap());
DBMap::iterator map = _databases.find(id);
assert(map != _databases.end());
if (!buildMap(db, map->_value)) {
warning("Databases::open(): Failed building a map for database \"%s\"", file.c_str());
_databases.erase(map);
return false;
}
return true;
}
bool Databases::close(const Common::String &id) {
DBMap::iterator db = _databases.find(id);
if (db == _databases.end()) {
warning("Databases::open(): A database with the ID \"%s\" does not exist", id.c_str());
return false;
}
_databases.erase(db);
return true;
}
bool Databases::getString(const Common::String &id, Common::String group,
Common::String section, Common::String keyword, Common::String &result) const {
DBMap::iterator db = _databases.find(id);
if (db == _databases.end()) {
warning("Databases::getString(): A database with the ID \"%s\" does not exist", id.c_str());
return false;
}
if (_language.empty()) {
warning("Databases::getString(): No language set");
return false;
}
Common::String key = _language + ":" + group + ":" + section + ":" + keyword;
Common::StringMap::const_iterator entry = db->_value.find(key);
if (entry == db->_value.end())
return false;
result = entry->_value;
return true;
}
int Databases::findField(const dBase &db, const Common::String &field,
dBase::Type type) const {
const Common::Array<dBase::Field> &fields = db.getFields();
for (uint i = 0; i < fields.size(); i++) {
if (!fields[i].name.equalsIgnoreCase(field))
continue;
if (fields[i].type != type)
return -1;
return i;
}
return -1;
}
bool Databases::buildMap(const dBase &db, Common::StringMap &map) const {
int fLanguage = findField(db, "Langage", dBase::kTypeString);
int fGroup = findField(db, "Nom" , dBase::kTypeString);
int fSection = findField(db, "Section", dBase::kTypeString);
int fKeyword = findField(db, "Motcle" , dBase::kTypeString);
int fText = findField(db, "Texte" , dBase::kTypeString);
if ((fLanguage < 0) || (fGroup < 0) || (fSection < 0) || (fKeyword < 0) || (fText < 0))
return false;
const Common::Array<dBase::Record> &records = db.getRecords();
Common::Array<dBase::Record>::const_iterator record;
for (record = records.begin(); record != records.end(); ++record) {
Common::String key;
key += db.getString(*record, fLanguage) + ":";
key += db.getString(*record, fGroup ) + ":";
key += db.getString(*record, fSection ) + ":";
key += db.getString(*record, fKeyword );
map.setVal(key, db.getString(*record, fText));
}
return true;
}
} // End of namespace Gob

64
engines/gob/databases.h Normal file
View file

@ -0,0 +1,64 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#ifndef GOB_DATABASES_H
#define GOB_DATABASES_H
#include "common/str.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/util.h"
#include "gob/dbase.h"
namespace Gob {
class Databases {
public:
Databases();
~Databases();
void setLanguage(Common::Language language);
bool open(const Common::String &id, const Common::String &file);
bool close(const Common::String &id);
bool getString(const Common::String &id, Common::String group,
Common::String section, Common::String keyword, Common::String &result) const;
private:
typedef Common::HashMap<Common::String, Common::StringMap, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> DBMap;
DBMap _databases;
Common::String _language;
int findField(const dBase &db, const Common::String &field, dBase::Type type) const;
bool buildMap(const dBase &db, Common::StringMap &map) const;
};
} // End of namespace Gob
#endif // GOB_DATABASES_H

195
engines/gob/dbase.cpp Normal file
View file

@ -0,0 +1,195 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "gob/dbase.h"
namespace Gob {
dBase::dBase() : _recordData(0) {
clear();
}
dBase::~dBase() {
clear();
}
bool dBase::load(Common::SeekableReadStream &stream) {
clear();
uint32 startPos = stream.pos();
_version = stream.readByte();
if ((_version != 0x03) && (_version != 0x83))
// Unsupported version
return false;
// TODO: Add support for memo files. A memo file is an external data file
// .DBT, segmented into "blocks". Each memo field in a record is an
// index this file.
_hasMemo = (_version & 0x80) != 0;
_lastUpdate.tm_year = stream.readByte();
_lastUpdate.tm_mon = stream.readByte();
_lastUpdate.tm_mday = stream.readByte();
_lastUpdate.tm_hour = 0;
_lastUpdate.tm_min = 0;
_lastUpdate.tm_sec = 0;
uint32 recordCount = stream.readUint32LE();
uint32 headerSize = stream.readUint16LE();
uint32 recordSize = stream.readUint16LE();
stream.skip(20); // Reserved
// Read all field descriptions, 0x0D is the end marker
uint32 fieldsLength = 0;
while (!stream.eos() && !stream.err() && (stream.readByte() != 0x0D)) {
Field field;
stream.skip(-1);
field.name = readString(stream, 11);
field.type = (Type) stream.readByte();
stream.skip(4); // Field data address
field.size = stream.readByte();
field.decimals = stream.readByte();
fieldsLength += field.size;
stream.skip(14); // Reserved and/or useless for us
_fields.push_back(field);
}
if (stream.eos() || stream.err())
return false;
if ((stream.pos() - startPos) != headerSize)
// Corrupted file / unknown format
return false;
if (recordSize != (fieldsLength + 1))
// Corrupted file / unknown format
return false;
_recordData = new byte[recordSize * recordCount];
if (stream.read(_recordData, recordSize * recordCount) != (recordSize * recordCount))
return false;
if (stream.readByte() != 0x1A)
// Missing end marker
return false;
uint32 fieldCount = _fields.size();
// Create the records array
_records.resize(recordCount);
for (uint32 i = 0; i < recordCount; i++) {
Record &record = _records[i];
const byte *data = _recordData + i * recordSize;
char status = *data++;
if ((status != ' ') && (status != '*'))
// Corrupted file / unknown format
return false;
record.deleted = status == '*';
record.fields.resize(fieldCount);
for (uint32 j = 0; j < fieldCount; j++) {
record.fields[j] = data;
data += _fields[j].size;
}
}
return true;
}
void dBase::clear() {
memset(&_lastUpdate, 0, sizeof(_lastUpdate));
_version = 0;
_hasMemo = false;
_fields.clear();
_records.clear();
delete[] _recordData;
_recordData = 0;
}
byte dBase::getVersion() const {
return _version;
}
TimeDate dBase::getLastUpdate() const {
return _lastUpdate;
}
const Common::Array<dBase::Field> &dBase::getFields() const {
return _fields;
}
const Common::Array<dBase::Record> &dBase::getRecords() const {
return _records;
}
Common::String dBase::getString(const Record &record, int field) const {
assert(_fields[field].type == kTypeString);
uint32 fieldLength = stringLength(record.fields[field], _fields[field].size);
return Common::String((const char *) record.fields[field], fieldLength);
}
// String fields are padded with spaces. This finds the real length.
inline uint32 dBase::stringLength(const byte *data, uint32 max) {
while (max-- > 0)
if ((data[max] != 0x20) && (data[max] != 0x00))
return max + 1;
return 0;
}
// Read a constant-length string out of a stream.
inline Common::String dBase::readString(Common::SeekableReadStream &stream, int n) {
Common::String str;
char c;
while (n-- > 0) {
if ((c = stream.readByte()) == '\0')
break;
str += c;
}
if (n > 0)
stream.skip(n);
return str;
}
} // End of namespace Gob

103
engines/gob/dbase.h Normal file
View file

@ -0,0 +1,103 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#ifndef GOB_DBASE_H
#define GOB_DBASE_H
#include "common/system.h"
#include "common/util.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/array.h"
namespace Gob {
/**
* A class for reading dBase files.
*
* Only dBase III files supported for now, and only field type
* string is actually useful. Further missing is reading of MDX
* index files and support for the external "Memo" data file.
*/
class dBase {
public:
enum Type {
kTypeString = 0x43, // 'C'
kTypeDate = 0x44, // 'D'
kTypeBool = 0x4C, // 'L'
kTypeMemo = 0x4D, // 'M'
kTypeNumber = 0x4E // 'N'
};
/** A field description. */
struct Field {
Common::String name; ///< Name of the field.
Type type; ///< Type of the field.
uint8 size; ///< Size of raw field data in bytes.
uint8 decimals; ///< Number of decimals the field holds.
};
/** A record. */
struct Record {
bool deleted; ///< Has this record been deleted?
Common::Array<const byte *> fields; ///< Raw field data.
};
dBase();
~dBase();
bool load(Common::SeekableReadStream &stream);
void clear();
byte getVersion() const;
/** Return the date the database was last updated. */
TimeDate getLastUpdate() const;
const Common::Array<Field> &getFields() const;
const Common::Array<Record> &getRecords() const;
/** Extract a string out of raw field data. */
Common::String getString(const Record &record, int field) const;
private:
byte _version;
bool _hasMemo;
TimeDate _lastUpdate;
Common::Array<Field> _fields;
Common::Array<Record> _records;
byte *_recordData;
static inline uint32 stringLength(const byte *data, uint32 max);
static inline Common::String readString(Common::SeekableReadStream &stream, int n);
};
} // End of namespace Gob
#endif // GOB_DBASE_H

View file

@ -32,6 +32,7 @@
#include "gob/goblin.h"
#include "gob/variables.h"
#include "gob/iniconfig.h"
#include "gob/databases.h"
namespace Gob {
@ -625,6 +626,7 @@ protected:
private:
INIConfig _inis;
Databases _databases;
void storeValue(uint16 index, uint16 type, uint32 value);
void storeValue(uint32 value);

View file

@ -413,17 +413,23 @@ void Inter_v7::o7_opendBase() {
dbFile += ".DBF";
warning("Addy Stub: Open dBase \"%s\" (\"%s\")", id.c_str(), dbFile.c_str());
if (!_databases.open(id, dbFile)) {
WRITE_VAR(27, 0); // Failure
return;
}
WRITE_VAR(27, 0); // Failure
_databases.setLanguage(_vm->_language);
WRITE_VAR(27, 1); // Success
}
void Inter_v7::o7_closedBase() {
Common::String id = _vm->_game->_script->evalString();
warning("Addy Stub: Close dBase \"%s\"", id.c_str());
WRITE_VAR(27, 0); // Failure
if (_databases.close(id))
WRITE_VAR(27, 1); // Success
else
WRITE_VAR(27, 0); // Failure
}
void Inter_v7::o7_getDBString() {
@ -432,12 +438,15 @@ void Inter_v7::o7_getDBString() {
Common::String section = _vm->_game->_script->evalString();
Common::String keyword = _vm->_game->_script->evalString();
uint16 varIndex = _vm->_game->_script->readVarIndex();
Common::String result;
if (!_databases.getString(id, group, section, keyword, result)) {
WRITE_VAR(27, 0); // Failure
storeString("");
return;
}
warning("Addy Stub: Get DB string: \"%s\", \"%s\", \"%s\", \"%s\", %d",
id.c_str(), group.c_str(), section.c_str(), keyword.c_str(), varIndex);
WRITE_VAR(27, 0); // Failure
storeString(result.c_str());
WRITE_VAR(27, 1); // Success
}
void Inter_v7::o7_oemToANSI(OpGobParams &params) {
@ -470,23 +479,40 @@ void Inter_v7::storeValue(uint32 value) {
}
void Inter_v7::storeString(uint16 index, uint16 type, const char *value) {
if (type == TYPE_VAR_STR) {
char *str = GET_VARO_STR(index);
uint32 maxLength = _vm->_global->_inter_animDataSize * 4 - 1;
char *str = GET_VARO_STR(index);
strncpy(str, value, _vm->_global->_inter_animDataSize);
str[_vm->_global->_inter_animDataSize - 1] = '\0';
switch (type) {
case TYPE_VAR_STR:
if (strlen(value) > maxLength)
warning("Inter_v7::storeString(): String too long");
} else if (type == TYPE_IMM_INT8) {
Common::strlcpy(str, value, maxLength);
break;
strcpy(GET_VARO_STR(index), value);
case TYPE_IMM_INT8:
case TYPE_VAR_INT8:
strcpy(str, value);
break;
} else if (type == TYPE_VAR_INT32) {
WRITE_VARO_UINT32(index, atoi(value));
} else if (type == TYPE_VAR_INT16) {
case TYPE_ARRAY_INT8:
WRITE_VARO_UINT8(index, atoi(value));
break;
case TYPE_VAR_INT16:
case TYPE_VAR_INT32_AS_INT16:
case TYPE_ARRAY_INT16:
WRITE_VARO_UINT16(index, atoi(value));
break;
case TYPE_VAR_INT32:
case TYPE_ARRAY_INT32:
WRITE_VARO_UINT32(index, atoi(value));
break;
default:
warning("Inter_v7::storeString(): Requested to store a string into type %d", type);
break;
}
}

View file

@ -3,6 +3,8 @@ MODULE := engines/gob
MODULE_OBJS := \
console.o \
dataio.o \
databases.o \
dbase.o \
detection.o \
draw.o \
draw_v1.o \