From b187551a019c56f85972abf8514f878c9c4dc6cf Mon Sep 17 00:00:00 2001 From: Sven Hesse Date: Mon, 31 Jan 2011 10:49:43 +0000 Subject: GOB: Add support for dBase III files Implementing o7_opendBase, o7_closedBase and o7_getDBString svn-id: r55676 --- engines/gob/databases.cpp | 175 +++++++++++++++++++++++++++++++++++++++++ engines/gob/databases.h | 64 +++++++++++++++ engines/gob/dbase.cpp | 195 ++++++++++++++++++++++++++++++++++++++++++++++ engines/gob/dbase.h | 103 ++++++++++++++++++++++++ engines/gob/inter.h | 2 + engines/gob/inter_v7.cpp | 66 +++++++++++----- engines/gob/module.mk | 2 + 7 files changed, 587 insertions(+), 20 deletions(-) create mode 100644 engines/gob/databases.cpp create mode 100644 engines/gob/databases.h create mode 100644 engines/gob/dbase.cpp create mode 100644 engines/gob/dbase.h (limited to 'engines/gob') diff --git a/engines/gob/databases.cpp b/engines/gob/databases.cpp new file mode 100644 index 0000000000..714b5a64a9 --- /dev/null +++ b/engines/gob/databases.cpp @@ -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 &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 &records = db.getRecords(); + + Common::Array::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 diff --git a/engines/gob/databases.h b/engines/gob/databases.h new file mode 100644 index 0000000000..cb76ae3b86 --- /dev/null +++ b/engines/gob/databases.h @@ -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 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 diff --git a/engines/gob/dbase.cpp b/engines/gob/dbase.cpp new file mode 100644 index 0000000000..ce6c748663 --- /dev/null +++ b/engines/gob/dbase.cpp @@ -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::getFields() const { + return _fields; +} + +const Common::Array &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 diff --git a/engines/gob/dbase.h b/engines/gob/dbase.h new file mode 100644 index 0000000000..5f260f965f --- /dev/null +++ b/engines/gob/dbase.h @@ -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 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 &getFields() const; + const Common::Array &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 _fields; + Common::Array _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 diff --git a/engines/gob/inter.h b/engines/gob/inter.h index 87a548d9e8..d9cd4639d9 100644 --- a/engines/gob/inter.h +++ b/engines/gob/inter.h @@ -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); diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp index bfa647ab5a..5c979c629e 100644 --- a/engines/gob/inter_v7.cpp +++ b/engines/gob/inter_v7.cpp @@ -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; + } + + _databases.setLanguage(_vm->_language); - WRITE_VAR(27, 0); // Failure + 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(); - - warning("Addy Stub: Get DB string: \"%s\", \"%s\", \"%s\", \"%s\", %d", - id.c_str(), group.c_str(), section.c_str(), keyword.c_str(), varIndex); + Common::String result; + if (!_databases.getString(id, group, section, keyword, result)) { + WRITE_VAR(27, 0); // Failure + storeString(""); + return; + } - WRITE_VAR(27, 0); // Failure + storeString(result.c_str()); + WRITE_VAR(27, 1); // Success } void Inter_v7::o7_oemToANSI(OpGobParams ¶ms) { @@ -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) { + case TYPE_ARRAY_INT8: + WRITE_VARO_UINT8(index, atoi(value)); + break; - WRITE_VARO_UINT32(index, atoi(value)); + case TYPE_VAR_INT16: + case TYPE_VAR_INT32_AS_INT16: + case TYPE_ARRAY_INT16: + WRITE_VARO_UINT16(index, atoi(value)); + break; - } else if (type == TYPE_VAR_INT16) { + case TYPE_VAR_INT32: + case TYPE_ARRAY_INT32: + WRITE_VARO_UINT32(index, atoi(value)); + break; - WRITE_VARO_UINT16(index, atoi(value)); + default: + warning("Inter_v7::storeString(): Requested to store a string into type %d", type); + break; } } diff --git a/engines/gob/module.mk b/engines/gob/module.mk index 4df47a611a..752e8554e3 100644 --- a/engines/gob/module.mk +++ b/engines/gob/module.mk @@ -3,6 +3,8 @@ MODULE := engines/gob MODULE_OBJS := \ console.o \ dataio.o \ + databases.o \ + dbase.o \ detection.o \ draw.o \ draw_v1.o \ -- cgit v1.2.3