aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSven Hesse2011-01-31 10:49:43 +0000
committerSven Hesse2011-01-31 10:49:43 +0000
commitb187551a019c56f85972abf8514f878c9c4dc6cf (patch)
tree770005faf3020d80de57255a28db7bc629a60fa3
parent0f0dbe7b7871079c1bd6cc5e2c85fe4ca1629942 (diff)
downloadscummvm-rg350-b187551a019c56f85972abf8514f878c9c4dc6cf.tar.gz
scummvm-rg350-b187551a019c56f85972abf8514f878c9c4dc6cf.tar.bz2
scummvm-rg350-b187551a019c56f85972abf8514f878c9c4dc6cf.zip
GOB: Add support for dBase III files
Implementing o7_opendBase, o7_closedBase and o7_getDBString svn-id: r55676
-rw-r--r--engines/gob/databases.cpp175
-rw-r--r--engines/gob/databases.h64
-rw-r--r--engines/gob/dbase.cpp195
-rw-r--r--engines/gob/dbase.h103
-rw-r--r--engines/gob/inter.h2
-rw-r--r--engines/gob/inter_v7.cpp66
-rw-r--r--engines/gob/module.mk2
7 files changed, 587 insertions, 20 deletions
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<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
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<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
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::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
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<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
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 &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) {
+ 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 \