aboutsummaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/advancedDetector.cpp6
-rw-r--r--common/advancedDetector.h2
-rw-r--r--common/archive.cpp344
-rw-r--r--common/archive.h231
-rw-r--r--common/array.h66
-rw-r--r--common/config-file.cpp68
-rw-r--r--common/config-manager.cpp9
-rw-r--r--common/events.h23
-rw-r--r--common/file.cpp444
-rw-r--r--common/file.h48
-rw-r--r--common/fs.cpp58
-rw-r--r--common/fs.h46
-rw-r--r--common/hash-str.h4
-rw-r--r--common/hashmap.cpp116
-rw-r--r--common/hashmap.h272
-rw-r--r--common/iff_container.h16
-rw-r--r--common/keyboard.h2
-rw-r--r--common/list.h5
-rw-r--r--common/md5.cpp11
-rw-r--r--common/md5.h5
-rw-r--r--common/memorypool.cpp111
-rw-r--r--common/memorypool.h45
-rw-r--r--common/module.mk1
-rw-r--r--common/ptr.h4
-rw-r--r--common/queue.h94
-rw-r--r--common/str.cpp244
-rw-r--r--common/str.h107
-rw-r--r--common/stream.cpp94
-rw-r--r--common/stream.h193
-rw-r--r--common/system.cpp10
-rw-r--r--common/system.h13
-rw-r--r--common/unarj.cpp348
-rw-r--r--common/unarj.h83
-rw-r--r--common/unzip.h50
-rw-r--r--common/util.cpp50
-rw-r--r--common/util.h23
-rw-r--r--common/xmlparser.cpp11
-rw-r--r--common/zlib.cpp308
-rw-r--r--common/zlib.h57
39 files changed, 2368 insertions, 1254 deletions
diff --git a/common/advancedDetector.cpp b/common/advancedDetector.cpp
index 522b24163e..1b0db4755a 100644
--- a/common/advancedDetector.cpp
+++ b/common/advancedDetector.cpp
@@ -82,12 +82,12 @@ static void upgradeTargetIfNecessary(const Common::ADParams &params) {
if (params.obsoleteList == 0)
return;
- const char *gameid = ConfMan.get("gameid").c_str();
+ String gameid = ConfMan.get("gameid");
for (const Common::ADObsoleteGameID *o = params.obsoleteList; o->from; ++o) {
- if (!scumm_stricmp(gameid, o->from)) {
+ if (gameid.equalsIgnoreCase(o->from)) {
gameid = o->to;
- ConfMan.set("gameid", o->to);
+ ConfMan.set("gameid", gameid);
if (o->platform != Common::kPlatformUnknown)
ConfMan.set("platform", Common::getPlatformCode(o->platform));
diff --git a/common/advancedDetector.h b/common/advancedDetector.h
index 40f5823d1b..b72b9fbfc4 100644
--- a/common/advancedDetector.h
+++ b/common/advancedDetector.h
@@ -28,8 +28,6 @@
#include "common/fs.h"
#include "common/error.h"
-#include "base/game.h" // For PlainGameDescriptor and GameList
-
#include "engines/metaengine.h"
namespace Common {
diff --git a/common/archive.cpp b/common/archive.cpp
new file mode 100644
index 0000000000..7e17fdca32
--- /dev/null
+++ b/common/archive.cpp
@@ -0,0 +1,344 @@
+/* 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/archive.h"
+#include "common/fs.h"
+#include "common/util.h"
+
+namespace Common {
+
+
+int Archive::matchPattern(StringList &list, const String &pattern) {
+ // Get all "names" (TODO: "files" ?)
+ StringList allNames;
+ getAllNames(allNames);
+
+ int matches = 0;
+
+ // need to match lowercase key
+ String lowercasePattern = pattern;
+ lowercasePattern.toLowercase();
+
+ StringList::iterator it = allNames.begin();
+ for ( ; it != allNames.end(); it++) {
+ if (it->matchString(lowercasePattern)) {
+ list.push_back(*it);
+ matches++;
+ }
+ }
+
+ return matches;
+}
+
+
+FSDirectory::FSDirectory(const FilesystemNode &node, int depth)
+ : _node(node), _cached(false), _depth(depth) {
+}
+
+FSDirectory::FSDirectory(const String &name, int depth)
+ : _node(name), _cached(false), _depth(depth) {
+}
+
+FSDirectory::~FSDirectory() {
+}
+
+FilesystemNode FSDirectory::getFSNode() const {
+ return _node;
+}
+
+FilesystemNode FSDirectory::lookupCache(NodeCache &cache, const String &name) {
+ // make caching as lazy as possible
+ if (!name.empty()) {
+ if (!_cached) {
+ cacheDirectoryRecursive(_node, _depth, "");
+ _cached = true;
+ }
+
+ if (cache.contains(name))
+ return cache[name];
+ }
+
+ return FilesystemNode();
+}
+
+bool FSDirectory::hasFile(const String &name) {
+ if (name.empty() || !_node.isDirectory()) {
+ return false;
+ }
+
+ FilesystemNode node = lookupCache(_fileCache, name);
+ return node.exists();
+}
+
+SeekableReadStream *FSDirectory::openFile(const String &name) {
+ if (name.empty() || !_node.isDirectory()) {
+ return 0;
+ }
+
+ FilesystemNode node = lookupCache(_fileCache, name);
+
+ if (!node.exists()) {
+ warning("FSDirectory::openFile: FilesystemNode does not exist");
+ return 0;
+ } else if (node.isDirectory()) {
+ warning("FSDirectory::openFile: FilesystemNode is a directory");
+ return 0;
+ }
+
+ SeekableReadStream *stream = node.openForReading();
+ if (!stream) {
+ warning("FSDirectory::openFile: Can't create stream for file '%s'", name.c_str());
+ }
+
+ return stream;
+}
+
+FSDirectory *FSDirectory::getSubDirectory(const String &name) {
+ if (name.empty() || !_node.isDirectory()) {
+ return 0;
+ }
+
+ FilesystemNode node = lookupCache(_subDirCache, name);
+ return new FSDirectory(node);
+}
+
+void FSDirectory::cacheDirectoryRecursive(FilesystemNode node, int depth, const String& prefix) {
+ if (depth <= 0) {
+ return;
+ }
+
+ FSList list;
+ node.getChildren(list, FilesystemNode::kListAll, false);
+
+ FSList::iterator it = list.begin();
+ for ( ; it != list.end(); it++) {
+ String name = prefix + (*it).getName();
+
+ // don't touch name as it might be used for warning messages
+ String lowercaseName = name;
+ lowercaseName.toLowercase();
+
+ // since the hashmap is case insensitive, we need to check for clashes when caching
+ if ((*it).isDirectory()) {
+ if (_subDirCache.contains(lowercaseName)) {
+ warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring sub-directory '%s'", name.c_str());
+ } else {
+ cacheDirectoryRecursive(*it, depth - 1, lowercaseName + "/");
+ _subDirCache[lowercaseName] = *it;
+ }
+ } else {
+ if (_fileCache.contains(lowercaseName)) {
+ warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring file '%s'", name.c_str());
+ } else {
+ _fileCache[lowercaseName] = *it;
+ }
+ }
+ }
+
+}
+
+int FSDirectory::matchPattern(StringList &list, const String &pattern) {
+ if (!_node.isDirectory())
+ return 0;
+
+ // Cache dir data
+ if (!_cached) {
+ cacheDirectoryRecursive(_node, _depth, "");
+ _cached = true;
+ }
+
+ // Small optimization: Ensure the StringList has to grow at most once
+ list.reserve(list.size() + _fileCache.size());
+
+ // Add all filenames from our cache
+ NodeCache::iterator it = _fileCache.begin();
+ for ( ; it != _fileCache.end(); it++) {
+ if (it->_key.matchString(pattern))
+ list.push_back(it->_key);
+ }
+
+ return _fileCache.size();
+}
+
+int FSDirectory::getAllNames(StringList &list) {
+ if (!_node.isDirectory())
+ return 0;
+
+ // Cache dir data
+ if (!_cached) {
+ cacheDirectoryRecursive(_node, _depth, "");
+ _cached = true;
+ }
+
+ // Small optimization: Ensure the StringList has to grow at most once
+ list.reserve(list.size() + _fileCache.size());
+
+ // Add all filenames from our cache
+ NodeCache::iterator it = _fileCache.begin();
+ for ( ; it != _fileCache.end(); it++) {
+ list.push_back((*it)._key);
+ }
+
+ return _fileCache.size();
+}
+
+
+
+SearchSet::ArchiveList::iterator SearchSet::find(const String &name) const {
+ ArchiveList::iterator it = _list.begin();
+ for ( ; it != _list.end(); it++) {
+ if ((*it)._name == name) {
+ break;
+ }
+ }
+ return it;
+}
+
+/*
+ Keep the nodes sorted according to descending priorities.
+ In case two or node nodes have the same priority, insertion
+ order prevails.
+*/
+void SearchSet::insert(const Node &node) {
+ ArchiveList::iterator it = _list.begin();
+ for ( ; it != _list.end(); it++) {
+ if ((*it)._priority < node._priority) {
+ break;
+ }
+ }
+ _list.insert(it, node);
+}
+
+void SearchSet::add(const String& name, ArchivePtr archive, uint priority) {
+ if (find(name) == _list.end()) {
+ Node node = { priority, name, archive };
+ insert(node);
+ } else {
+ warning("SearchSet::add: archive '%s' already present", name.c_str());
+ }
+
+}
+
+void SearchSet::remove(const String& name) {
+ ArchiveList::iterator it = find(name);
+ if (it != _list.end()) {
+ _list.erase(it);
+ }
+}
+
+bool SearchSet::hasArchive(const String &name) const {
+ return (find(name) != _list.end());
+}
+
+void SearchSet::clear() {
+ _list.clear();
+}
+
+void SearchSet::setPriority(const String& name, uint priority) {
+ ArchiveList::iterator it = find(name);
+ if (it == _list.end()) {
+ warning("SearchSet::setPriority: archive '%s' is not present", name.c_str());
+ return;
+ }
+
+ if (priority == (*it)._priority) {
+ return;
+ }
+
+ Node node(*it);
+ _list.erase(it);
+ node._priority = priority;
+ insert(node);
+}
+
+bool SearchSet::hasFile(const String &name) {
+ if (name.empty()) {
+ return false;
+ }
+
+ ArchiveList::iterator it = _list.begin();
+ for ( ; it != _list.end(); it++) {
+ if ((*it)._arc->hasFile(name)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int SearchSet::matchPattern(StringList &list, const String &pattern) {
+ int matches = 0;
+
+ ArchiveList::iterator it = _list.begin();
+ for ( ; it != _list.end(); it++) {
+ matches += (*it)._arc->matchPattern(list, pattern);
+ }
+
+ return matches;
+}
+
+int SearchSet::getAllNames(StringList &list) {
+ int matches = 0;
+
+ ArchiveList::iterator it = _list.begin();
+ for ( ; it != _list.end(); it++) {
+ matches += (*it)._arc->getAllNames(list);
+ }
+
+ return matches;
+}
+
+SeekableReadStream *SearchSet::openFile(const String &name) {
+ if (name.empty()) {
+ return 0;
+ }
+
+ ArchiveList::iterator it = _list.begin();
+ for ( ; it != _list.end(); it++) {
+ if ((*it)._arc->hasFile(name)) {
+ return (*it)._arc->openFile(name);
+ }
+ }
+
+ return 0;
+}
+
+
+DECLARE_SINGLETON(SearchManager);
+
+void SearchManager::addArchive(const String &name, ArchivePtr archive) {
+ add(name, archive);
+}
+
+void SearchManager::addDirectory(const String &name, const String &directory) {
+ addDirectoryRecursive(name, 1);
+}
+
+void SearchManager::addDirectoryRecursive(const String &name, const String &directory, int depth) {
+ add(name, SharedPtr<FSDirectory>(new FSDirectory(directory, depth)));
+}
+
+
+} // namespace Common
diff --git a/common/archive.h b/common/archive.h
new file mode 100644
index 0000000000..89ea6a5ce2
--- /dev/null
+++ b/common/archive.h
@@ -0,0 +1,231 @@
+/* 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 COMMON_ARCHIVES_H
+#define COMMON_ARCHIVES_H
+
+#include "common/fs.h"
+#include "common/str.h"
+#include "common/hash-str.h"
+#include "common/list.h"
+#include "common/ptr.h"
+#include "common/singleton.h"
+#include "common/stream.h"
+
+namespace Common {
+
+/**
+ * FilePtr is a convenient way to keep track of a SeekableReadStream without
+ * having to worry about releasing its memory.
+ */
+typedef SharedPtr<SeekableReadStream> FilePtr;
+
+/**
+ * Archive allows searches of (file)names into an arbitrary container.
+ * It also supports opening a file and returning an usable input stream.
+ */
+class Archive {
+public:
+ virtual ~Archive() { }
+
+ /**
+ * Check if a name is present in the Archive. Patterns are not allowed,
+ * as this is meant to be a quick File::exists() replacement.
+ */
+ virtual bool hasFile(const String &name) = 0;
+
+ /**
+ * Add all the names present in the Archive which match pattern to
+ * list. Returned names can be used as parameters to openFile.
+ * Must not remove elements from the list.
+ *
+ * @return the number of names added to list
+ */
+ virtual int matchPattern(StringList &list, const String &pattern);
+
+ /**
+ * Add all the names present in the Archive to list. Returned
+ * names can be used as parameters to openFile.
+ * Must not remove elements from the list.
+ *
+ * @return the number of names added to list
+ */
+ virtual int getAllNames(StringList &list) = 0;
+
+ /**
+ * Create a stream bound to a file in the archive.
+ * @return the newly created input stream
+ */
+ virtual SeekableReadStream *openFile(const String &name) = 0;
+};
+
+
+typedef SharedPtr<Archive> ArchivePtr;
+
+
+/**
+ * FSDirectory models a directory tree from the filesystem and allows users
+ * to access it through the Archive interface. FSDirectory can represent a
+ * single directory, or a tree with specified depth, rooted in a 'base'
+ * directory.
+ * Searching is case-insensitive, as the main intended goal is supporting
+ * retrieval of game data. First case-insensitive match is returned when
+ * searching, thus making FSDirectory heavily dependant on the underlying
+ * FilesystemNode implementation.
+ */
+class FSDirectory : public Archive {
+ FilesystemNode _node;
+
+ // Caches are case insensitive, clashes are dealt with when creating
+ // Key is stored in lowercase.
+ typedef HashMap<String, FilesystemNode, IgnoreCase_Hash, IgnoreCase_EqualTo> NodeCache;
+ NodeCache _fileCache, _subDirCache;
+
+ // look for a match
+ FilesystemNode lookupCache(NodeCache &cache, const String &name);
+
+ // cache management
+ void cacheDirectoryRecursive(FilesystemNode node, int depth, const String& prefix);
+ bool _cached;
+ int _depth;
+
+public:
+ /**
+ * Create a FSDirectory representing a tree with the specified depth. Will result in an
+ * unbound FSDirectory if name is not found on the filesystem or is not a directory.
+ */
+ FSDirectory(const String &name, int depth = 1);
+
+ /**
+ * Create a FSDirectory representing a tree with the specified depth. Will result in an
+ * unbound FSDirectory if node does not exist or is not a directory.
+ */
+ FSDirectory(const FilesystemNode &node, int depth = 1);
+
+ virtual ~FSDirectory();
+
+ /**
+ * This return the underlying FSNode of the FSDirectory.
+ */
+ FilesystemNode getFSNode() const;
+
+ /**
+ * Create a new FSDirectory pointing to a sub directory of the instance.
+ * @return a new FSDirectory instance
+ */
+ FSDirectory *getSubDirectory(const String &name);
+
+ virtual bool hasFile(const String &name);
+ virtual int matchPattern(StringList &list, const String &pattern);
+ virtual int getAllNames(StringList &list);
+ virtual SeekableReadStream *openFile(const String &name);
+};
+
+
+/**
+ * SearchSet enables access to a group of Archives through the Archive interface.
+ * Its intended usage is a situation in which there are no name clashes among names in the
+ * contained Archives, hence the simplistic policy of always looking for the first
+ * match. SearchSet *DOES* guarantee that searches are performed in *DESCENDING*
+ * priority order. In case of conflicting priorities, insertion order prevails.
+ */
+class SearchSet : public Archive {
+ struct Node {
+ uint _priority;
+ String _name;
+ ArchivePtr _arc;
+ };
+ typedef List<Node> ArchiveList;
+ ArchiveList _list;
+
+ ArchiveList::iterator find(const String &name) const;
+
+ // Add an archive keeping the list sorted by ascending priorities.
+ void insert(const Node& node);
+
+public:
+ /**
+ * Add a new archive to the searchable set.
+ */
+ void add(const String& name, ArchivePtr archive, uint priority = 0);
+
+ /**
+ * Remove an archive from the searchable set.
+ */
+ void remove(const String& name);
+
+ /**
+ * Check if a given archive name is already present.
+ */
+ bool hasArchive(const String &name) const;
+
+ /**
+ * Empties the searchable set.
+ */
+ void clear();
+
+ /**
+ * Change the order of searches.
+ */
+ void setPriority(const String& name, uint priority);
+
+ virtual bool hasFile(const String &name);
+ virtual int matchPattern(StringList &list, const String &pattern);
+ virtual int getAllNames(StringList &list);
+
+ /**
+ * Implements openFile from Archive base class. The current policy is
+ * opening the first file encountered that matches the name.
+ */
+ virtual SeekableReadStream *openFile(const String &name);
+};
+
+
+class SearchManager : public Singleton<SearchManager>, public SearchSet {
+public:
+ /**
+ * Add an existing Archive. This is meant to support searching in system-specific
+ * archives, namely the MACOSX/IPHONE bundles.
+ */
+ void addArchive(const String &name, ArchivePtr archive);
+
+ /**
+ * Create and add a FSDirectory by name
+ */
+ void addDirectory(const String &name, const String &directory);
+
+ /**
+ * Create and add a FSDirectory and its subdirectories by name
+ */
+ void addDirectoryRecursive(const String &name, const String &directory, int depth = 4);
+
+};
+
+/** Shortcut for accessing the search manager. */
+#define SearchMan Common::SearchManager::instance()
+
+} // namespace Common
+
+#endif
diff --git a/common/array.h b/common/array.h
index 854ce5b91d..693c024d11 100644
--- a/common/array.h
+++ b/common/array.h
@@ -35,7 +35,7 @@ class Array {
protected:
uint _capacity;
uint _size;
- T *_data;
+ T *_storage;
public:
typedef T *iterator;
@@ -44,41 +44,41 @@ public:
typedef T value_type;
public:
- Array() : _capacity(0), _size(0), _data(0) {}
- Array(const Array<T> &array) : _capacity(0), _size(0), _data(0) {
+ Array() : _capacity(0), _size(0), _storage(0) {}
+ Array(const Array<T> &array) : _capacity(0), _size(0), _storage(0) {
_size = array._size;
_capacity = _size + 32;
- _data = new T[_capacity];
- copy(array._data, array._data + _size, _data);
+ _storage = new T[_capacity];
+ copy(array._storage, array._storage + _size, _storage);
}
~Array() {
- delete[] _data;
+ delete[] _storage;
}
void push_back(const T &element) {
ensureCapacity(_size + 1);
- _data[_size++] = element;
+ _storage[_size++] = element;
}
void push_back(const Array<T> &array) {
ensureCapacity(_size + array._size);
- copy(array._data, array._data + array._size, _data + _size);
+ copy(array._storage, array._storage + array._size, _storage + _size);
_size += array._size;
}
void insert_at(int idx, const T &element) {
assert(idx >= 0 && (uint)idx <= _size);
ensureCapacity(_size + 1);
- copy_backward(_data + idx, _data + _size, _data + _size + 1);
- _data[idx] = element;
+ copy_backward(_storage + idx, _storage + _size, _storage + _size + 1);
+ _storage[idx] = element;
_size++;
}
T remove_at(int idx) {
assert(idx >= 0 && (uint)idx < _size);
- T tmp = _data[idx];
- copy(_data + idx + 1, _data + _size, _data + idx);
+ T tmp = _storage[idx];
+ copy(_storage + idx + 1, _storage + _size, _storage + idx);
_size--;
return tmp;
}
@@ -87,23 +87,23 @@ public:
T& operator[](int idx) {
assert(idx >= 0 && (uint)idx < _size);
- return _data[idx];
+ return _storage[idx];
}
const T& operator[](int idx) const {
assert(idx >= 0 && (uint)idx < _size);
- return _data[idx];
+ return _storage[idx];
}
Array<T>& operator=(const Array<T> &array) {
if (this == &array)
return *this;
- delete[] _data;
+ delete[] _storage;
_size = array._size;
_capacity = _size + 32;
- _data = new T[_capacity];
- copy(array._data, array._data + _size, _data);
+ _storage = new T[_capacity];
+ copy(array._storage, array._storage + _size, _storage);
return *this;
}
@@ -113,8 +113,8 @@ public:
}
void clear() {
- delete[] _data;
- _data = 0;
+ delete[] _storage;
+ _storage = 0;
_size = 0;
_capacity = 0;
}
@@ -125,33 +125,33 @@ public:
iterator begin() {
- return _data;
+ return _storage;
}
iterator end() {
- return _data + _size;
+ return _storage + _size;
}
const_iterator begin() const {
- return _data;
+ return _storage;
}
const_iterator end() const {
- return _data + _size;
+ return _storage + _size;
}
void reserve(uint newCapacity) {
if (newCapacity <= _capacity)
return;
- T *old_data = _data;
+ T *old_storage = _storage;
_capacity = newCapacity;
- _data = new T[newCapacity];
+ _storage = new T[newCapacity];
- if (old_data) {
+ if (old_storage) {
// Copy old data
- copy(old_data, old_data + _size, _data);
- delete[] old_data;
+ copy(old_storage, old_storage + _size, _storage);
+ delete[] old_storage;
}
}
@@ -159,14 +159,14 @@ public:
if (newSize == _size)
return;
- T *old_data = _data;
+ T *old_storage = _storage;
_capacity = newSize;
- _data = new T[newSize];
- if (old_data) {
+ _storage = new T[newSize];
+ if (old_storage) {
// Copy old data
int cnt = (_size < newSize ? _size : newSize);
- copy(old_data, old_data + cnt, _data);
- delete[] old_data;
+ copy(old_storage, old_storage + cnt, _storage);
+ delete[] old_storage;
}
_size = newSize;
}
diff --git a/common/config-file.cpp b/common/config-file.cpp
index 9f54c9ddde..c3764a02da 100644
--- a/common/config-file.cpp
+++ b/common/config-file.cpp
@@ -77,7 +77,6 @@ bool ConfigFile::loadFromSaveFile(const char *filename) {
}
bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
- char buf[MAXLINELEN];
Section section;
KeyValue kv;
String comment;
@@ -86,18 +85,21 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
// TODO: Detect if a section occurs multiple times (or likewise, if
// a key occurs multiple times inside one section).
- while (!stream.eos()) {
+ while (!stream.eos() && !stream.ioFailed()) {
lineno++;
- if (!stream.readLine(buf, MAXLINELEN))
- break;
- if (buf[0] == '#') {
+ // Read a line
+ String line = stream.readLine();
+
+ if (line.size() == 0) {
+ // Do nothing
+ } else if (line[0] == '#') {
// Accumulate comments here. Once we encounter either the start
// of a new section, or a key-value-pair, we associate the value
// of the 'comment' variable with that entity.
- comment += buf;
+ comment += line;
comment += "\n";
- } else if (buf[0] == '(') {
+ } else if (line[0] == '(') {
// HACK: The following is a hack added by Kirben to support the
// "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard".
//
@@ -105,11 +107,11 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
// but the current design of this class doesn't allow to do that
// in a nice fashion (a "isMustard" parameter is *not* a nice
// solution).
- comment += buf;
+ comment += line;
comment += "\n";
- } else if (buf[0] == '[') {
+ } else if (line[0] == '[') {
// It's a new section which begins here.
- char *p = buf + 1;
+ const char *p = line.c_str() + 1;
// Get the section name, and check whether it's valid (that
// is, verify that it only consists of alphanumerics,
// dashes and underscores).
@@ -121,23 +123,25 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
else if (*p != ']')
error("ConfigFile::loadFromStream: Invalid character '%c' occured in section name in line %d", *p, lineno);
- *p = 0;
-
// Previous section is finished now, store it.
if (!section.name.empty())
_sections.push_back(section);
- section.name = buf + 1;
+ section.name = String(line.c_str() + 1, p);
section.keys.clear();
section.comment = comment;
comment.clear();
assert(isValidName(section.name));
} else {
- // Skip leading & trailing whitespaces
- char *t = rtrim(ltrim(buf));
-
- // Skip empty lines
+ // This line should be a line with a 'key=value' pair, or an empty one.
+
+ // Skip leading whitespaces
+ const char *t = line.c_str();
+ while (isspace(*t))
+ t++;
+
+ // Skip empty lines / lines with only whitespace
if (*t == 0)
continue;
@@ -146,14 +150,20 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
error("ConfigFile::loadFromStream: Key/value pair found outside a section in line %d", lineno);
}
- // Split string at '=' into 'key' and 'value'.
- char *p = strchr(t, '=');
+ // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
+ const char *p = strchr(t, '=');
if (!p)
- error("ConfigFile::loadFromStream: Junk found in line line %d: '%s'", lineno, t);
- *p = 0;
+ error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
+
+ // Extract the key/value pair
+ kv.key = String(t, p);
+ kv.value = String(p + 1);
+
+ // Trim of spaces
+ kv.key.trim();
+ kv.value.trim();
- kv.key = rtrim(t);
- kv.value = ltrim(p + 1);
+ // Store comment
kv.comment = comment;
comment.clear();
@@ -225,7 +235,7 @@ bool ConfigFile::saveToStream(WriteStream &stream) {
void ConfigFile::removeSection(const String &section) {
assert(isValidName(section));
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
- if (!scumm_stricmp(section.c_str(), i->name.c_str())) {
+ if (section.equalsIgnoreCase(i->name)) {
_sections.erase(i);
return;
}
@@ -318,7 +328,7 @@ const ConfigFile::SectionKeyList ConfigFile::getKeys(const String &section) cons
ConfigFile::Section *ConfigFile::getSection(const String &section) {
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
- if (!scumm_stricmp(section.c_str(), i->name.c_str())) {
+ if (section.equalsIgnoreCase(i->name)) {
return &(*i);
}
}
@@ -327,7 +337,7 @@ ConfigFile::Section *ConfigFile::getSection(const String &section) {
const ConfigFile::Section *ConfigFile::getSection(const String &section) const {
for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) {
- if (!scumm_stricmp(section.c_str(), i->name.c_str())) {
+ if (section.equalsIgnoreCase(i->name)) {
return &(*i);
}
}
@@ -340,7 +350,7 @@ bool ConfigFile::Section::hasKey(const String &key) const {
const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const {
for (List<KeyValue>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
- if (!scumm_stricmp(key.c_str(), i->key.c_str())) {
+ if (key.equalsIgnoreCase(i->key)) {
return &(*i);
}
}
@@ -349,7 +359,7 @@ const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const
void ConfigFile::Section::setKey(const String &key, const String &value) {
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
- if (!scumm_stricmp(key.c_str(), i->key.c_str())) {
+ if (key.equalsIgnoreCase(i->key)) {
i->value = value;
return;
}
@@ -363,7 +373,7 @@ void ConfigFile::Section::setKey(const String &key, const String &value) {
void ConfigFile::Section::removeKey(const String &key) {
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
- if (!scumm_stricmp(key.c_str(), i->key.c_str())) {
+ if (key.equalsIgnoreCase(i->key)) {
keys.erase(i);
return;
}
diff --git a/common/config-manager.cpp b/common/config-manager.cpp
index 138fcc201f..b741757cc5 100644
--- a/common/config-manager.cpp
+++ b/common/config-manager.cpp
@@ -103,13 +103,7 @@ void ConfigManager::loadFromStream(SeekableReadStream &stream) {
lineno++;
// Read a line
- String line;
- while (line.lastChar() != '\n') {
- char buf[256];
- if (!stream.readLine_NEW(buf, 256))
- break;
- line += buf;
- }
+ String line = stream.readLine();
if (line.size() == 0) {
// Do nothing
@@ -118,6 +112,7 @@ void ConfigManager::loadFromStream(SeekableReadStream &stream) {
// of a new domain, or a key-value-pair, we associate the value
// of the 'comment' variable with that entity.
comment += line;
+ comment += "\n";
} else if (line[0] == '[') {
// It's a new domain which begins here.
const char *p = line.c_str() + 1;
diff --git a/common/events.h b/common/events.h
index d0cb740692..f01282765a 100644
--- a/common/events.h
+++ b/common/events.h
@@ -27,6 +27,7 @@
#define COMMON_EVENTS_H
#include "common/keyboard.h"
+#include "common/queue.h"
#include "common/rect.h"
#include "common/system.h"
#include "common/noncopyable.h"
@@ -58,6 +59,9 @@ enum EventType {
EVENT_MBUTTONDOWN = 13,
EVENT_MBUTTONUP = 14,
+ EVENT_MAINMENU = 15,
+ EVENT_RTL = 16,
+
EVENT_QUIT = 10,
EVENT_SCREEN_CHANGED = 11,
/**
@@ -142,6 +146,11 @@ public:
*/
virtual bool pollEvent(Common::Event &event) = 0;
+ /**
+ * Pushes a "fake" event of the specified type into the event queue
+ */
+ virtual void pushEvent(Common::Event event) = 0;
+
/** Register random source so it can be serialized in game test purposes **/
virtual void registerRandomSource(Common::RandomSource &rnd, const char *name) = 0;
@@ -166,6 +175,17 @@ public:
*/
virtual int shouldQuit() const = 0;
+ /**
+ * Should we return to the launcher?
+ */
+ virtual int shouldRTL() const = 0;
+
+ /**
+ * Reset the "return to launcher" flag (as returned shouldRTL()) to false.
+ * Used when we have returned to the launcher.
+ */
+ virtual void resetRTL() = 0;
+
// Optional: check whether a given key is currently pressed ????
//virtual bool isKeyPressed(int keycode) = 0;
@@ -173,6 +193,9 @@ public:
// TODO: Consider removing OSystem::getScreenChangeID and
// replacing it by a generic getScreenChangeID method here
+protected:
+
+ Common::Queue<Common::Event> artificialEventQueue;
};
} // End of namespace Common
diff --git a/common/file.cpp b/common/file.cpp
index fb837b9499..cf396a32cd 100644
--- a/common/file.cpp
+++ b/common/file.cpp
@@ -23,254 +23,51 @@
*
*/
+#include "common/archive.h"
#include "common/file.h"
#include "common/fs.h"
-#include "common/hashmap.h"
#include "common/util.h"
-#include "common/hash-str.h"
-#include <errno.h>
-
-#if defined(MACOSX) || defined(IPHONE)
-#include "CoreFoundation/CoreFoundation.h"
-#endif
-
-#ifdef __PLAYSTATION2__
- // for those replaced fopen/fread/etc functions
- typedef unsigned long uint64;
- typedef signed long int64;
- #include "backends/platform/ps2/fileio.h"
-
- #define fopen(a, b) ps2_fopen(a, b)
- #define fclose(a) ps2_fclose(a)
- #define fseek(a, b, c) ps2_fseek(a, b, c)
- #define ftell(a) ps2_ftell(a)
- #define feof(a) ps2_feof(a)
- #define fread(a, b, c, d) ps2_fread(a, b, c, d)
- #define fwrite(a, b, c, d) ps2_fwrite(a, b, c, d)
-
- //#define fprintf ps2_fprintf // used in common/util.cpp
- //#define fflush(a) ps2_fflush(a) // used in common/util.cpp
-
- //#define fgetc(a) ps2_fgetc(a) // not used
- //#define fgets(a, b, c) ps2_fgets(a, b, c) // not used
- //#define fputc(a, b) ps2_fputc(a, b) // not used
- //#define fputs(a, b) ps2_fputs(a, b) // not used
-
- //#define fsize(a) ps2_fsize(a) // not used -- and it is not a standard function either
-#endif
-
-#ifdef __DS__
-
- // These functions replease the standard library functions of the same name.
- // As this header is included after the standard one, I have the chance to #define
- // all of these to my own code.
- //
- // A #define is the only way, as redefinig the functions would cause linker errors.
-
- // These functions need to be #undef'ed, as their original definition
- // in devkitarm is done with #includes (ugh!)
- #undef feof
- #undef clearerr
- //#undef getc
- //#undef ferror
-
- #include "backends/fs/ds/ds-fs.h"
-
-
- //void std_fprintf(FILE* handle, const char* fmt, ...); // used in common/util.cpp
- //void std_fflush(FILE* handle); // used in common/util.cpp
-
- //char* std_fgets(char* str, int size, FILE* file); // not used
- //int std_getc(FILE* handle); // not used
- //char* std_getcwd(char* dir, int dunno); // not used
- //void std_cwd(char* dir); // not used
- //int std_ferror(FILE* handle); // not used
-
- // Only functions used in the ScummVM source have been defined here!
- #define fopen(name, mode) DS::std_fopen(name, mode)
- #define fclose(handle) DS::std_fclose(handle)
- #define fread(ptr, size, items, file) DS::std_fread(ptr, size, items, file)
- #define fwrite(ptr, size, items, file) DS::std_fwrite(ptr, size, items, file)
- #define feof(handle) DS::std_feof(handle)
- #define ftell(handle) DS::std_ftell(handle)
- #define fseek(handle, offset, whence) DS::std_fseek(handle, offset, whence)
- #define clearerr(handle) DS::std_clearerr(handle)
-
- //#define printf(fmt, ...) consolePrintf(fmt, ##__VA_ARGS__)
-
- //#define fprintf(file, fmt, ...) { char str[128]; sprintf(str, fmt, ##__VA_ARGS__); DS::std_fwrite(str, strlen(str), 1, file); }
- //#define fflush(file) DS::std_fflush(file) // used in common/util.cpp
-
- //#define fgets(str, size, file) DS::std_fgets(str, size, file) // not used
- //#define getc(handle) DS::std_getc(handle) // not used
- //#define getcwd(dir, dunno) DS::std_getcwd(dir, dunno) // not used
- #define ferror(handle) DS::std_ferror(handle)
-
-#endif
-
-#ifdef __SYMBIAN32__
- #undef feof
- #undef clearerr
-
- #define FILE void
-
- FILE* symbian_fopen(const char* name, const char* mode);
- void symbian_fclose(FILE* handle);
- size_t symbian_fread(const void* ptr, size_t size, size_t numItems, FILE* handle);
- size_t symbian_fwrite(const void* ptr, size_t size, size_t numItems, FILE* handle);
- bool symbian_feof(FILE* handle);
- long int symbian_ftell(FILE* handle);
- int symbian_fseek(FILE* handle, long int offset, int whence);
- void symbian_clearerr(FILE* handle);
-
- // Only functions used in the ScummVM source have been defined here!
- #define fopen(name, mode) symbian_fopen(name, mode)
- #define fclose(handle) symbian_fclose(handle)
- #define fread(ptr, size, items, file) symbian_fread(ptr, size, items, file)
- #define fwrite(ptr, size, items, file) symbian_fwrite(ptr, size, items, file)
- #define feof(handle) symbian_feof(handle)
- #define ftell(handle) symbian_ftell(handle)
- #define fseek(handle, offset, whence) symbian_fseek(handle, offset, whence)
- #define clearerr(handle) symbian_clearerr(handle)
-#endif
+#include "common/system.h"
namespace Common {
-typedef HashMap<String, int> StringIntMap;
-
-// The following two objects could be turned into static members of class
-// File. However, then we would be forced to #include hashmap in file.h
-// which seems to be a high price just for a simple beautification...
-static StringIntMap *_defaultDirectories;
-static StringMap *_filesMap;
-
-static FILE *fopenNoCase(const String &filename, const String &directory, const char *mode) {
- FILE *file;
- String dirBuf(directory);
- String fileBuf(filename);
-
-#if !defined(__GP32__) && !defined(PALMOS_MODE)
- // Add a trailing slash, if necessary.
- if (!dirBuf.empty()) {
- const char c = dirBuf.lastChar();
- if (c != ':' && c != '/' && c != '\\')
- dirBuf += '/';
- }
-#endif
-
- // Append the filename to the path string
- String pathBuf(dirBuf);
- pathBuf += fileBuf;
-
- //
- // Try to open the file normally
- //
- file = fopen(pathBuf.c_str(), mode);
-
- //
- // Try again, with file name converted to upper case
- //
- if (!file) {
- fileBuf.toUppercase();
- pathBuf = dirBuf + fileBuf;
- file = fopen(pathBuf.c_str(), mode);
- }
-
- //
- // Try again, with file name converted to lower case
- //
- if (!file) {
- fileBuf.toLowercase();
- pathBuf = dirBuf + fileBuf;
- file = fopen(pathBuf.c_str(), mode);
- }
-
- //
- // Try again, with file name capitalized
- //
- if (!file) {
- fileBuf.toLowercase();
- fileBuf.setChar(toupper(fileBuf[0]),0);
- pathBuf = dirBuf + fileBuf;
- file = fopen(pathBuf.c_str(), mode);
- }
-
-#ifdef __amigaos4__
- //
- // Work around for possibility that someone uses AmigaOS "newlib" build with SmartFileSystem (blocksize 512 bytes), leading
- // to buffer size being only 512 bytes. "Clib2" sets the buffer size to 8KB, resulting smooth movie playback. This forces the buffer
- // to be enough also when using "newlib" compile on SFS.
- //
- if (file) {
- setvbuf(file, NULL, _IOFBF, 8192);
- }
-#endif
+static Common::SearchSet *s_searchSet = 0;
- return file;
-}
void File::addDefaultDirectory(const String &directory) {
FilesystemNode dir(directory);
addDefaultDirectoryRecursive(dir, 1);
}
-void File::addDefaultDirectoryRecursive(const String &directory, int level, const String &prefix) {
+void File::addDefaultDirectoryRecursive(const String &directory, int level) {
FilesystemNode dir(directory);
- addDefaultDirectoryRecursive(dir, level, prefix);
+ addDefaultDirectoryRecursive(dir, level);
}
void File::addDefaultDirectory(const FilesystemNode &directory) {
addDefaultDirectoryRecursive(directory, 1);
}
-void File::addDefaultDirectoryRecursive(const FilesystemNode &dir, int level, const String &prefix) {
- if (level <= 0)
+void File::addDefaultDirectoryRecursive(const FilesystemNode &dir, int level) {
+ if (level <= 0 || !dir.exists() || !dir.isDirectory())
return;
- FSList fslist;
- if (!dir.getChildren(fslist, FilesystemNode::kListAll)) {
- // Failed listing the contents of this node, so it is either not a
- // directory, or just doesn't exist at all.
- return;
+ if (!s_searchSet) {
+ s_searchSet = new Common::SearchSet();
+ g_system->addSysArchivesToSearchSet(*s_searchSet);
}
- if (!_defaultDirectories)
- _defaultDirectories = new StringIntMap;
-
- // Do not add directories multiple times, unless this time they are added
- // with a bigger depth.
- const String &directory(dir.getPath());
- if (_defaultDirectories->contains(directory) && (*_defaultDirectories)[directory] >= level)
- return;
- (*_defaultDirectories)[directory] = level;
-
- if (!_filesMap)
- _filesMap = new StringMap;
-
- for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
- if (file->isDirectory()) {
- addDefaultDirectoryRecursive(file->getPath(), level - 1, prefix + file->getName() + "/");
- } else {
- String lfn(prefix);
- lfn += file->getName();
- lfn.toLowercase();
- if (!_filesMap->contains(lfn)) {
- (*_filesMap)[lfn] = file->getPath();
- }
- }
- }
+ Common::ArchivePtr dataArchive(new Common::FSDirectory(dir, level));
+ s_searchSet->add(dir.getPath(), dataArchive, 1);
}
void File::resetDefaultDirectories() {
- delete _defaultDirectories;
- delete _filesMap;
-
- _defaultDirectories = 0;
- _filesMap = 0;
+ delete s_searchSet;
+ s_searchSet = 0;
}
File::File()
- : _handle(0), _ioFailed(false) {
+ : _handle(0) {
}
File::~File() {
@@ -285,51 +82,21 @@ bool File::open(const String &filename) {
_name.clear();
clearIOFailed();
- String fname(filename);
- fname.toLowercase();
-
- if (_filesMap && _filesMap->contains(fname)) {
- fname = (*_filesMap)[fname];
- debug(3, "Opening hashed: %s", fname.c_str());
- _handle = fopen(fname.c_str(), "rb");
- } else if (_filesMap && _filesMap->contains(fname + ".")) {
+ if (s_searchSet && s_searchSet->hasFile(filename)) {
+ debug(3, "Opening hashed: %s", filename.c_str());
+ _handle = s_searchSet->openFile(filename);
+ } else if (s_searchSet && s_searchSet->hasFile(filename + ".")) {
// WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails"
// sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot)
- fname = (*_filesMap)[fname + "."];
- debug(3, "Opening hashed: %s", fname.c_str());
- _handle = fopen(fname.c_str(), "rb");
+ debug(3, "Opening hashed: %s.", filename.c_str());
+ _handle = s_searchSet->openFile(filename);
} else {
-
- if (_defaultDirectories) {
- // Try all default directories
- StringIntMap::const_iterator x(_defaultDirectories->begin());
- for (; _handle == NULL && x != _defaultDirectories->end(); ++x) {
- _handle = fopenNoCase(filename, x->_key, "rb");
- }
- }
-
// Last resort: try the current directory
- if (_handle == NULL)
- _handle = fopenNoCase(filename, "", "rb");
-
- // Last last (really) resort: try looking inside the application bundle on Mac OS X for the lowercase file.
-#if defined(MACOSX) || defined(IPHONE)
- if (!_handle) {
- CFStringRef cfFileName = CFStringCreateWithBytes(NULL, (const UInt8 *)filename.c_str(), filename.size(), kCFStringEncodingASCII, false);
- CFURLRef fileUrl = CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfFileName, NULL, NULL);
- if (fileUrl) {
- UInt8 buf[256];
- if (CFURLGetFileSystemRepresentation(fileUrl, false, (UInt8 *)buf, 256)) {
- _handle = fopen((char *)buf, "rb");
- }
- CFRelease(fileUrl);
- }
- CFRelease(cfFileName);
- }
-#endif
-
+ FilesystemNode file(filename);
+ if (file.exists() && !file.isDirectory())
+ _handle = file.openForReading();
}
-
+
if (_handle == NULL)
debug(2, "File %s not opened", filename.c_str());
else
@@ -341,18 +108,12 @@ bool File::open(const String &filename) {
bool File::open(const FilesystemNode &node) {
if (!node.exists()) {
- warning("File::open: Trying to open a FilesystemNode which does not exist");
+ warning("File::open: FilesystemNode does not exist");
return false;
} else if (node.isDirectory()) {
- warning("File::open: Trying to open a FilesystemNode which is a directory");
+ warning("File::open: FilesystemNode is a directory");
return false;
- } /*else if (!node.isReadable() && mode == kFileReadMode) {
- warning("File::open: Trying to open an unreadable FilesystemNode object for reading");
- return false;
- } else if (!node.isWritable() && mode == kFileWriteMode) {
- warning("File::open: Trying to open an unwritable FilesystemNode object for writing");
- return false;
- }*/
+ }
String filename(node.getName());
@@ -363,7 +124,7 @@ bool File::open(const FilesystemNode &node) {
clearIOFailed();
_name.clear();
- _handle = fopen(node.getPath().c_str(), "rb");
+ _handle = node.openForReading();
if (_handle == NULL)
debug(2, "File %s not found", filename.c_str());
@@ -374,39 +135,24 @@ bool File::open(const FilesystemNode &node) {
}
bool File::exists(const String &filename) {
- // First try to find the file via a FilesystemNode (in case an absolute
- // path was passed). This is only used to filter out directories.
- FilesystemNode file(filename);
- if (file.exists())
- return !file.isDirectory();
-
- // See if the file is already mapped
- if (_filesMap && _filesMap->contains(filename)) {
- FilesystemNode file2((*_filesMap)[filename]);
-
- if (file2.exists())
- return !file2.isDirectory();
- }
-
- // Try all default directories
- if (_defaultDirectories) {
- StringIntMap::const_iterator i(_defaultDirectories->begin());
- for (; i != _defaultDirectories->end(); ++i) {
- FilesystemNode file2(i->_key + filename);
-
- if(file2.exists())
- return !file2.isDirectory();
- }
+ if (s_searchSet && s_searchSet->hasFile(filename)) {
+ return true;
+ } else if (s_searchSet && s_searchSet->hasFile(filename + ".")) {
+ // WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails"
+ // sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot)
+ return true;
+ } else {
+ // Last resort: try the current directory
+ FilesystemNode file(filename);
+ if (file.exists() && !file.isDirectory())
+ return true;
}
-
- //Try opening the file inside the local directory as a last resort
- File tmp;
- return tmp.open(filename);
+
+ return false;
}
void File::close() {
- if (_handle)
- fclose((FILE *)_handle);
+ delete _handle;
_handle = NULL;
}
@@ -416,59 +162,47 @@ bool File::isOpen() const {
bool File::ioFailed() const {
// TODO/FIXME: Just use ferror() here?
- return _ioFailed != 0;
+ return !_handle || _handle->ioFailed();
}
void File::clearIOFailed() {
- // TODO/FIXME: Just use clearerr() here?
- _ioFailed = false;
+ if (_handle)
+ _handle->clearIOFailed();
}
-bool File::eof() const {
+bool File::err() const {
assert(_handle);
-
- return feof((FILE *)_handle) != 0;
+ return _handle->err();
}
-uint32 File::pos() const {
+void File::clearErr() {
assert(_handle);
-
- return ftell((FILE *)_handle);
+ _handle->clearErr();
}
-uint32 File::size() const {
+bool File::eos() const {
assert(_handle);
+ return _handle->eos();
+}
- uint32 oldPos = ftell((FILE *)_handle);
- fseek((FILE *)_handle, 0, SEEK_END);
- uint32 length = ftell((FILE *)_handle);
- fseek((FILE *)_handle, oldPos, SEEK_SET);
-
- return length;
+int32 File::pos() const {
+ assert(_handle);
+ return _handle->pos();
}
-void File::seek(int32 offs, int whence) {
+int32 File::size() const {
assert(_handle);
+ return _handle->size();
+}
- if (fseek((FILE *)_handle, offs, whence) != 0)
- clearerr((FILE *)_handle);
+bool File::seek(int32 offs, int whence) {
+ assert(_handle);
+ return _handle->seek(offs, whence);
}
uint32 File::read(void *ptr, uint32 len) {
- byte *ptr2 = (byte *)ptr;
- uint32 real_len;
-
assert(_handle);
-
- if (len == 0)
- return 0;
-
- real_len = fread(ptr2, 1, len, (FILE *)_handle);
- if (real_len < len) {
- _ioFailed = true;
- }
-
- return real_len;
+ return _handle->read(ptr, len);
}
@@ -483,32 +217,19 @@ bool DumpFile::open(const String &filename) {
assert(!filename.empty());
assert(!_handle);
- String fname(filename);
- fname.toLowercase();
-
- _handle = fopenNoCase(filename, "", "wb");
-
- if (_handle == NULL)
- debug(2, "Failed to open '%s' for writing", filename.c_str());
-
- return _handle != NULL;
+ FilesystemNode node(filename);
+ return open(node);
}
bool DumpFile::open(const FilesystemNode &node) {
assert(!_handle);
if (node.isDirectory()) {
- warning("File::open: Trying to open a FilesystemNode which is a directory");
- return false;
- } /*else if (!node.isReadable() && mode == kFileReadMode) {
- warning("File::open: Trying to open an unreadable FilesystemNode object for reading");
+ warning("DumpFile::open: FilesystemNode is a directory");
return false;
- } else if (!node.isWritable() && mode == kFileWriteMode) {
- warning("File::open: Trying to open an unwritable FilesystemNode object for writing");
- return false;
- }*/
+ }
- _handle = fopen(node.getPath().c_str(), "wb");
+ _handle = node.openForWriting();
if (_handle == NULL)
debug(2, "File %s not found", node.getName().c_str());
@@ -517,8 +238,7 @@ bool DumpFile::open(const FilesystemNode &node) {
}
void DumpFile::close() {
- if (_handle)
- fclose((FILE *)_handle);
+ delete _handle;
_handle = NULL;
}
@@ -526,36 +246,24 @@ bool DumpFile::isOpen() const {
return _handle != NULL;
}
-bool DumpFile::ioFailed() const {
- assert(_handle);
- return ferror((FILE *)_handle) != 0;
-}
-
-void DumpFile::clearIOFailed() {
+bool DumpFile::err() const {
assert(_handle);
- clearerr((FILE *)_handle);
+ return _handle->ioFailed();
}
-bool DumpFile::eof() const {
+void DumpFile::clearErr() {
assert(_handle);
- return feof((FILE *)_handle) != 0;
+ _handle->clearIOFailed();
}
uint32 DumpFile::write(const void *ptr, uint32 len) {
assert(_handle);
-
- if (len == 0)
- return 0;
-
- return (uint32)fwrite(ptr, 1, len, (FILE *)_handle);
+ return _handle->write(ptr, len);
}
-void DumpFile::flush() {
+bool DumpFile::flush() {
assert(_handle);
- // TODO: Should check the return value of fflush, and if it is non-zero,
- // check errno and set an error flag.
- fflush((FILE *)_handle);
+ return _handle->flush();
}
-
} // End of namespace Common
diff --git a/common/file.h b/common/file.h
index 3adeb6ff36..a2739f795f 100644
--- a/common/file.h
+++ b/common/file.h
@@ -31,20 +31,17 @@
#include "common/str.h"
#include "common/stream.h"
-class FilesystemNode;
-
namespace Common {
+class FilesystemNode;
+
/**
* TODO: vital to document this core class properly!!! For both users and implementors
*/
class File : public SeekableReadStream, public NonCopyable {
protected:
/** File handle to the actual file; 0 if no file is open. */
- void *_handle;
-
- /** Status flag which tells about recent I/O failures. */
- bool _ioFailed;
+ SeekableReadStream *_handle;
/** The name of this file, for debugging. */
String _name;
@@ -52,10 +49,10 @@ protected:
public:
static void addDefaultDirectory(const String &directory);
- static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = "");
+ static void addDefaultDirectoryRecursive(const String &directory, int level = 4);
static void addDefaultDirectory(const FilesystemNode &directory);
- static void addDefaultDirectoryRecursive(const FilesystemNode &directory, int level = 4, const String &prefix = "");
+ static void addDefaultDirectoryRecursive(const FilesystemNode &directory, int level = 4);
static void resetDefaultDirectories();
@@ -93,18 +90,13 @@ public:
bool ioFailed() const;
void clearIOFailed();
- bool eos() const { return eof(); }
-
- /**
- * Checks for end of file.
- *
- * @return: true if the end of file is reached, false otherwise.
- */
- virtual bool eof() const;
+ bool err() const;
+ void clearErr();
+ bool eos() const;
- virtual uint32 pos() const;
- virtual uint32 size() const;
- void seek(int32 offs, int whence = SEEK_SET);
+ virtual int32 pos() const;
+ virtual int32 size() const;
+ bool seek(int32 offs, int whence = SEEK_SET);
uint32 read(void *dataPtr, uint32 dataSize);
};
@@ -118,7 +110,7 @@ public:
class DumpFile : public WriteStream, public NonCopyable {
protected:
/** File handle to the actual file; 0 if no file is open. */
- void *_handle;
+ WriteStream *_handle;
public:
DumpFile();
@@ -136,24 +128,14 @@ public:
*/
bool isOpen() const;
-
- bool ioFailed() const;
- void clearIOFailed();
- bool eos() const { return eof(); }
-
- /**
- * Checks for end of file.
- *
- * @return: true if the end of file is reached, false otherwise.
- */
- virtual bool eof() const;
+ bool err() const;
+ void clearErr();
virtual uint32 write(const void *dataPtr, uint32 dataSize);
- virtual void flush();
+ virtual bool flush();
};
-
} // End of namespace Common
#endif
diff --git a/common/fs.cpp b/common/fs.cpp
index 3f585c6038..4d31ac09fa 100644
--- a/common/fs.cpp
+++ b/common/fs.cpp
@@ -23,12 +23,11 @@
*/
#include "common/util.h"
-#include "common/file.h"
#include "common/system.h"
#include "backends/fs/abstract-fs.h"
#include "backends/fs/fs-factory.h"
-//namespace Common {
+namespace Common {
FilesystemNode::FilesystemNode() {
}
@@ -52,7 +51,7 @@ bool FilesystemNode::operator<(const FilesystemNode& node) const {
if (isDirectory() != node.isDirectory())
return isDirectory();
- return scumm_stricmp(getDisplayName().c_str(), node.getDisplayName().c_str()) < 0;
+ return getDisplayName().compareToIgnoreCase(node.getDisplayName()) < 0;
}
bool FilesystemNode::exists() const {
@@ -63,10 +62,10 @@ bool FilesystemNode::exists() const {
}
FilesystemNode FilesystemNode::getChild(const Common::String &n) const {
- if (_realNode == 0)
- return *this;
+ // If this node is invalid or not a directory, return an invalid node
+ if (_realNode == 0 || !_realNode->isDirectory())
+ return FilesystemNode();
- assert(_realNode->isDirectory());
AbstractFilesystemNode *node = _realNode->getChild(n);
return FilesystemNode(node);
}
@@ -155,7 +154,7 @@ bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool h
} else {
Common::String filename = entry->getName();
filename.toUppercase();
- if (Common::matchString(filename.c_str(), pattern.c_str())) {
+ if (filename.matchString(pattern)) {
results.push_back(*entry);
if (!exhaustive)
@@ -174,40 +173,31 @@ bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool h
return !results.empty();
}
-Common::SeekableReadStream *FilesystemNode::openForReading() {
+Common::SeekableReadStream *FilesystemNode::openForReading() const {
if (_realNode == 0)
return 0;
-#if 0
- return _realNode->openForReading();
-#else
- // FIXME: Until we support openForReading in AbstractFilesystemNode,
- // we just use Common::File.
- Common::File *confFile = new Common::File();
- assert(confFile);
- if (!confFile->open(*this)) {
- delete confFile;
- confFile = 0;
+
+ if (!_realNode->exists()) {
+ warning("FilesystemNode::openForReading: FilesystemNode does not exist");
+ return false;
+ } else if (_realNode->isDirectory()) {
+ warning("FilesystemNode::openForReading: FilesystemNode is a directory");
+ return false;
}
- return confFile;
-#endif
+
+ return _realNode->openForReading();
}
-Common::WriteStream *FilesystemNode::openForWriting() {
+Common::WriteStream *FilesystemNode::openForWriting() const {
if (_realNode == 0)
return 0;
-#if 0
- return _realNode->openForWriting();
-#else
- // FIXME: Until we support openForWriting in AbstractFilesystemNode,
- // we just use Common::DumpFile.
- Common::DumpFile *confFile = new Common::DumpFile();
- assert(confFile);
- if (!confFile->open(*this)) {
- delete confFile;
- confFile = 0;
+
+ if (_realNode->isDirectory()) {
+ warning("FilesystemNode::openForWriting: FilesystemNode is a directory");
+ return 0;
}
- return confFile;
-#endif
+
+ return _realNode->openForWriting();
}
-//} // End of namespace Common
+} // End of namespace Common
diff --git a/common/fs.h b/common/fs.h
index 972e0d86af..c5f7ca6b4c 100644
--- a/common/fs.h
+++ b/common/fs.h
@@ -32,15 +32,10 @@
class AbstractFilesystemNode;
namespace Common {
- class SeekableReadStream;
- class WriteStream;
-}
-
-//namespace Common {
class FilesystemNode;
-//class SeekableReadStream;
-//class WriteStream;
+class SeekableReadStream;
+class WriteStream;
/**
* List of multiple file system nodes. E.g. the contents of a given directory.
@@ -107,17 +102,29 @@ public:
virtual bool exists() const;
/**
- * Fetch a child node of this node, with the given name. Only valid for
- * directory nodes (an assertion is triggered otherwise).
- * If no child node with the given name exists, an invalid node is returned.
+ * Create a new node referring to a child node of the current node, which
+ * must be a directory node (otherwise an invalid node is returned).
+ * If a child matching the name exists, a normal node for it is returned.
+ * If no child with the name exists, a node for it is still returned,
+ * but exists() will return 'false' for it. This node can however be used
+ * to create a new file using the openForWriting() method.
+ *
+ * @todo If openForWriting() (or a hypothetical future mkdir() method) is used,
+ * this should affect what exists/isDirectory/isReadable/isWritable return
+ * for existing nodes. However, this is not the case for many existing
+ * FSNode implementations. Either fix those, or document that FSNodes
+ * can become 'stale'...
+ *
+ * @param name the name of a child of this directory
+ * @return the node referring to the child with the given name
*/
FilesystemNode getChild(const Common::String &name) const;
/**
- * Return a list of child nodes of this directory node. If called on a node
+ * Return a list of all child nodes of this directory node. If called on a node
* that does not represent a directory, false is returned.
*
- * @return true if succesful, false otherwise (e.g. when the directory does not exist).
+ * @return true if successful, false otherwise (e.g. when the directory does not exist).
*/
virtual bool getChildren(FSList &fslist, ListMode mode = kListDirectoriesOnly, bool hidden = false) const;
@@ -141,10 +148,11 @@ public:
virtual Common::String getName() const;
/**
- * Return a string representation of the file which can be passed to fopen(),
- * and is suitable for archiving (i.e. writing to the config file).
- * This will usually be a 'path' (hence the name of the method), but can
- * be anything that fulfills the above criterions.
+ * Return a string representation of the file which is suitable for
+ * archiving (i.e. writing to the config file). This will usually be a
+ * 'path' (hence the name of the method), but can be anything that meets
+ * the above criterions. What a 'path' is differs greatly from system to
+ * system anyway.
*
* @note Do not assume that this string contains (back)slashes or any
* other kind of 'path separators'.
@@ -222,7 +230,7 @@ public:
*
* @return pointer to the stream object, 0 in case of a failure
*/
- virtual Common::SeekableReadStream *openForReading();
+ virtual Common::SeekableReadStream *openForReading() const;
/**
* Creates a WriteStream instance corresponding to the file
@@ -231,9 +239,9 @@ public:
*
* @return pointer to the stream object, 0 in case of a failure
*/
- virtual Common::WriteStream *openForWriting();
+ virtual Common::WriteStream *openForWriting() const;
};
-//} // End of namespace Common
+} // End of namespace Common
#endif //COMMON_FS_H
diff --git a/common/hash-str.h b/common/hash-str.h
index f64b62daed..40557037e7 100644
--- a/common/hash-str.h
+++ b/common/hash-str.h
@@ -39,7 +39,7 @@ inline uint hashit_lower(const String &str) { return hashit_lower(str.c_str());
// FIXME: The following functors obviously are not consistently named
struct CaseSensitiveString_EqualTo {
- bool operator()(const String& x, const String& y) const { return strcmp(x.c_str(), y.c_str()) == 0; }
+ bool operator()(const String& x, const String& y) const { return x.equals(y); }
};
struct CaseSensitiveString_Hash {
@@ -48,7 +48,7 @@ struct CaseSensitiveString_Hash {
struct IgnoreCase_EqualTo {
- bool operator()(const String& x, const String& y) const { return scumm_stricmp(x.c_str(), y.c_str()) == 0; }
+ bool operator()(const String& x, const String& y) const { return x.equalsIgnoreCase(y); }
};
struct IgnoreCase_Hash {
diff --git a/common/hashmap.cpp b/common/hashmap.cpp
index 4749234740..b8f2608901 100644
--- a/common/hashmap.cpp
+++ b/common/hashmap.cpp
@@ -24,70 +24,86 @@
*/
// The hash map (associative array) implementation in this file is
-// based on code by Andrew Y. Ng, 1996:
-
-/*
- * Copyright (c) 1998-2003 Massachusetts Institute of Technology.
- * This code was developed as part of the Haystack research project
- * (http://haystack.lcs.mit.edu/). Permission is hereby granted,
- * free of charge, to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of the Software, and to permit
- * persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
+// based on the PyDict implementation of CPython. The erase() method
+// is based on example code in the Wikipedia article on Hash tables.
#include "common/hashmap.h"
namespace Common {
-// const char *:
+// Hash function for strings, taken from CPython.
uint hashit(const char *p) {
- uint hash = 0;
+ uint hash = *p << 7;
byte c;
- while ((c = *p++))
- hash = (hash * 31 + c);
- return hash;
+ int size = 0;
+ while ((c = *p++)) {
+ hash = (1000003 * hash) ^ c;
+ size++;
+ }
+ return hash ^ size;
}
+// Like hashit, but converts every char to lowercase before hashing.
uint hashit_lower(const char *p) {
- uint hash = 0;
+ uint hash = tolower(*p) << 7;
byte c;
- while ((c = *p++))
- hash = (hash * 31 + tolower(c));
- return hash;
+ int size = 0;
+ while ((c = *p++)) {
+ hash = (1000003 * hash) ^ tolower(c);
+ size++;
+ }
+ return hash ^ size;
}
-// The following table is taken from the GNU ISO C++ Library's hashtable.h file.
-static const uint primes[] = {
- 53ul, 97ul, 193ul, 389ul, 769ul,
- 1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
- 49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
- 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
- 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
- 1610612741ul, 3221225473ul, 4294967291ul
-};
+#ifdef DEBUG_HASH_COLLISIONS
+static double
+ g_collisions = 0,
+ g_lookups = 0,
+ g_collPerLook = 0,
+ g_capacity = 0,
+ g_size = 0;
+static int g_max_capacity = 0, g_max_size = 0;
+static int g_totalHashmaps = 0;
+static int g_stats[4] = {0,0,0,0};
-uint nextTableSize(uint x) {
- int i = 0;
- while (x >= primes[i])
- i++;
- return primes[i];
-}
+void updateHashCollisionStats(int collisions, int lookups, int arrsize, int nele) {
+ g_collisions += collisions;
+ g_lookups += lookups;
+ if (lookups)
+ g_collPerLook += (double)collisions / (double)lookups;
+ g_capacity += arrsize;
+ g_size += nele;
+ g_totalHashmaps++;
+
+ if (3*nele <= 2*8)
+ g_stats[0]++;
+ if (3*nele <= 2*16)
+ g_stats[1]++;
+ if (3*nele <= 2*32)
+ g_stats[2]++;
+ if (3*nele <= 2*64)
+ g_stats[3]++;
+
+ g_max_capacity = MAX(g_max_capacity, arrsize);
+ g_max_size = MAX(g_max_size, nele);
+ fprintf(stdout, "%d hashmaps: colls %.1f; lookups %.1f; ratio %.3f%%; size %f (max: %d); capacity %f (max: %d)\n",
+ g_totalHashmaps,
+ g_collisions / g_totalHashmaps,
+ g_lookups / g_totalHashmaps,
+ 100 * g_collPerLook / g_totalHashmaps,
+ g_size / g_totalHashmaps, g_max_size,
+ g_capacity / g_totalHashmaps, g_max_capacity);
+ fprintf(stdout, " %d less than %d; %d less than %d; %d less than %d; %d less than %d\n",
+ g_stats[0], 2*8/3,
+ g_stats[1],2*16/3,
+ g_stats[2],2*32/3,
+ g_stats[3],2*64/3);
+
+ // TODO:
+ // * Should record the maximal size of the map during its lifetime, not that at its death
+ // * Should do some statistics: how many maps are less than 2/3*8, 2/3*16, 2/3*32, ...
+}
+#endif
} // End of namespace Common
diff --git a/common/hashmap.h b/common/hashmap.h
index 69f367de97..81f5ee84b4 100644
--- a/common/hashmap.h
+++ b/common/hashmap.h
@@ -24,32 +24,8 @@
*/
// The hash map (associative array) implementation in this file is
-// based on code by Andrew Y. Ng, 1996:
-
-/*
- * Copyright (c) 1998-2003 Massachusetts Institute of Technology.
- * This code was developed as part of the Haystack research project
- * (http://haystack.lcs.mit.edu/). Permission is hereby granted,
- * free of charge, to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of the Software, and to permit
- * persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- */
+// based on the PyDict implementation of CPython. The erase() method
+// is based on example code in the Wikipedia article on Hash tables.
#ifndef COMMON_HASHMAP_H
#define COMMON_HASHMAP_H
@@ -74,11 +50,6 @@
namespace Common {
-// The table sizes ideally are primes. We use a helper function to find
-// suitable table sizes.
-uint nextTableSize(uint x);
-
-
// Enable the following #define if you want to check how many collisions the
// code produces (many collisions indicate either a bad hash function, or a
// hash table that is too small).
@@ -113,9 +84,24 @@ public:
Node(const Key &key) : _key(key), _value() {}
};
+ enum {
+ HASHMAP_PERTURB_SHIFT = 5,
+ HASHMAP_MIN_CAPACITY = 16,
+
+ // The quotient of the next two constants controls how much the
+ // internal storage of the hashmap may fill up before being
+ // increased automatically.
+ // Note: the quotient of these two must be between and different
+ // from 0 and 1.
+ HASHMAP_LOADFACTOR_NUMERATOR = 2,
+ HASHMAP_LOADFACTOR_DENOMINATOR = 3,
+
+ HASHMAP_MEMORYPOOL_SIZE = HASHMAP_MIN_CAPACITY * HASHMAP_LOADFACTOR_NUMERATOR / HASHMAP_LOADFACTOR_DENOMINATOR
+ };
+
#ifdef USE_HASHMAP_MEMORY_POOL
- MemoryPool _nodePool;
+ FixedSizeMemoryPool<sizeof(Node), HASHMAP_MEMORYPOOL_SIZE> _nodePool;
Node *allocNode(const Key &key) {
void* mem = _nodePool.malloc();
@@ -136,8 +122,9 @@ public:
}
#endif
- Node **_arr; // hashtable of size arrsize.
- uint _arrsize, _nele;
+ Node **_storage; // hashtable of size arrsize.
+ uint _mask; /**< Capacity of the HashMap minus one; must be a power of two of minus one */
+ uint _size;
HashFunc _hash;
EqualFunc _equal;
@@ -152,7 +139,7 @@ public:
void assign(const HM_t &map);
int lookup(const Key &key) const;
int lookupAndCreateIfMissing(const Key &key);
- void expand_array(uint newsize);
+ void expandStorage(uint newCapacity);
template<class T> friend class IteratorImpl;
@@ -174,8 +161,8 @@ public:
NodeType *deref() const {
assert(_hashmap != 0);
- assert(_idx < _hashmap->_arrsize);
- Node *node = _hashmap->_arr[_idx];
+ assert(_idx <= _hashmap->_mask);
+ Node *node = _hashmap->_storage[_idx];
assert(node != 0);
return node;
}
@@ -195,8 +182,8 @@ public:
assert(_hashmap);
do {
_idx++;
- } while (_idx < _hashmap->_arrsize && _hashmap->_arr[_idx] == 0);
- if (_idx >= _hashmap->_arrsize)
+ } while (_idx <= _hashmap->_mask && _hashmap->_storage[_idx] == 0);
+ if (_idx > _hashmap->_mask)
_idx = (uint)-1;
return *this;
@@ -223,7 +210,7 @@ public:
// Remove the previous content and ...
clear();
- delete[] _arr;
+ delete[] _storage;
// ... copy the new stuff.
assign(map);
return *this;
@@ -242,12 +229,12 @@ public:
void erase(const Key &key);
- uint size() const { return _nele; }
+ uint size() const { return _size; }
iterator begin() {
// Find and return the _key non-empty entry
- for (uint ctr = 0; ctr < _arrsize; ++ctr) {
- if (_arr[ctr])
+ for (uint ctr = 0; ctr <= _mask; ++ctr) {
+ if (_storage[ctr])
return iterator(ctr, this);
}
return end();
@@ -258,8 +245,8 @@ public:
const_iterator begin() const {
// Find and return the first non-empty entry
- for (uint ctr = 0; ctr < _arrsize; ++ctr) {
- if (_arr[ctr])
+ for (uint ctr = 0; ctr <= _mask; ++ctr) {
+ if (_storage[ctr])
return const_iterator(ctr, this);
}
return end();
@@ -270,14 +257,14 @@ public:
iterator find(const Key &key) {
uint ctr = lookup(key);
- if (_arr[ctr])
+ if (_storage[ctr])
return iterator(ctr, this);
return end();
}
const_iterator find(const Key &key) const {
uint ctr = lookup(key);
- if (_arr[ctr])
+ if (_storage[ctr])
return const_iterator(ctr, this);
return end();
}
@@ -285,7 +272,7 @@ public:
// TODO: insert() method?
bool empty() const {
- return (_nele == 0);
+ return (_size == 0);
}
};
@@ -297,16 +284,13 @@ public:
*/
template<class Key, class Val, class HashFunc, class EqualFunc>
HashMap<Key, Val, HashFunc, EqualFunc>::HashMap() :
-#ifdef USE_HASHMAP_MEMORY_POOL
- _nodePool(sizeof(Node)),
-#endif
_defaultVal() {
- _arrsize = nextTableSize(0);
- _arr = new Node *[_arrsize];
- assert(_arr != NULL);
- memset(_arr, 0, _arrsize * sizeof(Node *));
+ _mask = HASHMAP_MIN_CAPACITY - 1;
+ _storage = new Node *[HASHMAP_MIN_CAPACITY];
+ assert(_storage != NULL);
+ memset(_storage, 0, HASHMAP_MIN_CAPACITY * sizeof(Node *));
- _nele = 0;
+ _size = 0;
#ifdef DEBUG_HASH_COLLISIONS
_collisions = 0;
@@ -321,9 +305,6 @@ HashMap<Key, Val, HashFunc, EqualFunc>::HashMap() :
*/
template<class Key, class Val, class HashFunc, class EqualFunc>
HashMap<Key, Val, HashFunc, EqualFunc>::HashMap(const HM_t &map) :
-#ifdef USE_HASHMAP_MEMORY_POOL
- _nodePool(sizeof(Node)),
-#endif
_defaultVal() {
assign(map);
}
@@ -333,11 +314,15 @@ HashMap<Key, Val, HashFunc, EqualFunc>::HashMap(const HM_t &map) :
*/
template<class Key, class Val, class HashFunc, class EqualFunc>
HashMap<Key, Val, HashFunc, EqualFunc>::~HashMap() {
- for (uint ctr = 0; ctr < _arrsize; ++ctr)
- if (_arr[ctr] != NULL)
- freeNode(_arr[ctr]);
+ for (uint ctr = 0; ctr <= _mask; ++ctr)
+ if (_storage[ctr] != NULL)
+ freeNode(_storage[ctr]);
- delete[] _arr;
+ delete[] _storage;
+#ifdef DEBUG_HASH_COLLISIONS
+ extern void updateHashCollisionStats(int, int, int, int);
+ updateHashCollisionStats(_collisions, _lookups, _mask+1, _size);
+#endif
}
/**
@@ -349,95 +334,102 @@ HashMap<Key, Val, HashFunc, EqualFunc>::~HashMap() {
*/
template<class Key, class Val, class HashFunc, class EqualFunc>
void HashMap<Key, Val, HashFunc, EqualFunc>::assign(const HM_t &map) {
- _arrsize = map._arrsize;
- _arr = new Node *[_arrsize];
- assert(_arr != NULL);
- memset(_arr, 0, _arrsize * sizeof(Node *));
+ _mask = map._mask;
+ _storage = new Node *[_mask+1];
+ assert(_storage != NULL);
+ memset(_storage, 0, (_mask+1) * sizeof(Node *));
// Simply clone the map given to us, one by one.
- _nele = 0;
- for (uint ctr = 0; ctr < _arrsize; ++ctr) {
- if (map._arr[ctr] != NULL) {
- _arr[ctr] = allocNode(map._arr[ctr]->_key);
- _arr[ctr]->_value = map._arr[ctr]->_value;
- _nele++;
+ _size = 0;
+ for (uint ctr = 0; ctr <= _mask; ++ctr) {
+ if (map._storage[ctr] != NULL) {
+ _storage[ctr] = allocNode(map._storage[ctr]->_key);
+ _storage[ctr]->_value = map._storage[ctr]->_value;
+ _size++;
}
}
// Perform a sanity check (to help track down hashmap corruption)
- assert(_nele == map._nele);
+ assert(_size == map._size);
}
template<class Key, class Val, class HashFunc, class EqualFunc>
void HashMap<Key, Val, HashFunc, EqualFunc>::clear(bool shrinkArray) {
- for (uint ctr = 0; ctr < _arrsize; ++ctr) {
- if (_arr[ctr] != NULL) {
- freeNode(_arr[ctr]);
- _arr[ctr] = NULL;
+ for (uint ctr = 0; ctr <= _mask; ++ctr) {
+ if (_storage[ctr] != NULL) {
+ freeNode(_storage[ctr]);
+ _storage[ctr] = NULL;
}
}
- if (shrinkArray && _arrsize > nextTableSize(0)) {
- delete[] _arr;
+#ifdef USE_HASHMAP_MEMORY_POOL
+ _nodePool.freeUnusedPages();
+#endif
+
+ if (shrinkArray && _mask >= HASHMAP_MIN_CAPACITY) {
+ delete[] _storage;
- _arrsize = nextTableSize(0);
- _arr = new Node *[_arrsize];
- assert(_arr != NULL);
- memset(_arr, 0, _arrsize * sizeof(Node *));
+ _mask = HASHMAP_MIN_CAPACITY;
+ _storage = new Node *[HASHMAP_MIN_CAPACITY];
+ assert(_storage != NULL);
+ memset(_storage, 0, HASHMAP_MIN_CAPACITY * sizeof(Node *));
}
- _nele = 0;
+ _size = 0;
}
template<class Key, class Val, class HashFunc, class EqualFunc>
-void HashMap<Key, Val, HashFunc, EqualFunc>::expand_array(uint newsize) {
- assert(newsize > _arrsize);
- uint ctr, dex;
+void HashMap<Key, Val, HashFunc, EqualFunc>::expandStorage(uint newCapacity) {
+ assert(newCapacity > _mask+1);
- const uint old_nele = _nele;
- const uint old_arrsize = _arrsize;
- Node **old_arr = _arr;
+ const uint old_size = _size;
+ const uint old_mask = _mask;
+ Node **old_storage = _storage;
// allocate a new array
- _nele = 0;
- _arrsize = newsize;
- _arr = new Node *[_arrsize];
- assert(_arr != NULL);
- memset(_arr, 0, _arrsize * sizeof(Node *));
+ _size = 0;
+ _mask = newCapacity - 1;
+ _storage = new Node *[newCapacity];
+ assert(_storage != NULL);
+ memset(_storage, 0, newCapacity * sizeof(Node *));
// rehash all the old elements
- for (ctr = 0; ctr < old_arrsize; ++ctr) {
- if (old_arr[ctr] == NULL)
+ for (uint ctr = 0; ctr <= old_mask; ++ctr) {
+ if (old_storage[ctr] == NULL)
continue;
// Insert the element from the old table into the new table.
// Since we know that no key exists twice in the old table, we
// can do this slightly better than by calling lookup, since we
// don't have to call _equal().
- dex = _hash(old_arr[ctr]->_key) % _arrsize;
- while (_arr[dex] != NULL) {
- dex = (dex + 1) % _arrsize;
+ const uint hash = _hash(old_storage[ctr]->_key);
+ uint idx = hash & _mask;
+ for (uint perturb = hash; _storage[idx] != NULL; perturb >>= HASHMAP_PERTURB_SHIFT) {
+ idx = (5 * idx + perturb + 1) & _mask;
}
- _arr[dex] = old_arr[ctr];
- _nele++;
+ _storage[idx] = old_storage[ctr];
+ _size++;
}
// Perform a sanity check: Old number of elements should match the new one!
// This check will fail if some previous operation corrupted this hashmap.
- assert(_nele == old_nele);
+ assert(_size == old_size);
- delete[] old_arr;
+ delete[] old_storage;
return;
}
template<class Key, class Val, class HashFunc, class EqualFunc>
int HashMap<Key, Val, HashFunc, EqualFunc>::lookup(const Key &key) const {
- uint ctr = _hash(key) % _arrsize;
+ const uint hash = _hash(key);
+ uint ctr = hash & _mask;
+ for (uint perturb = hash; ; perturb >>= HASHMAP_PERTURB_SHIFT) {
+ if (_storage[ctr] == NULL || _equal(_storage[ctr]->_key, key))
+ break;
- while (_arr[ctr] != NULL && !_equal(_arr[ctr]->_key, key)) {
- ctr = (ctr + 1) % _arrsize;
+ ctr = (5 * ctr + perturb + 1) & _mask;
#ifdef DEBUG_HASH_COLLISIONS
_collisions++;
@@ -448,7 +440,7 @@ int HashMap<Key, Val, HashFunc, EqualFunc>::lookup(const Key &key) const {
_lookups++;
fprintf(stderr, "collisions %d, lookups %d, ratio %f in HashMap %p; size %d num elements %d\n",
_collisions, _lookups, ((double) _collisions / (double)_lookups),
- (const void *)this, _arrsize, _nele);
+ (const void *)this, _mask+1, _size);
#endif
return ctr;
@@ -458,13 +450,15 @@ template<class Key, class Val, class HashFunc, class EqualFunc>
int HashMap<Key, Val, HashFunc, EqualFunc>::lookupAndCreateIfMissing(const Key &key) {
uint ctr = lookup(key);
- if (_arr[ctr] == NULL) {
- _arr[ctr] = allocNode(key);
- _nele++;
+ if (_storage[ctr] == NULL) {
+ _storage[ctr] = allocNode(key);
+ _size++;
- // Keep the load factor below 75%.
- if (_nele > _arrsize * 75 / 100) {
- expand_array(nextTableSize(_arrsize));
+ // Keep the load factor below a certain threshold.
+ uint capacity = _mask + 1;
+ if (_size * HASHMAP_LOADFACTOR_DENOMINATOR > capacity * HASHMAP_LOADFACTOR_NUMERATOR) {
+ capacity = capacity < 500 ? (capacity * 4) : (capacity * 2);
+ expandStorage(capacity);
ctr = lookup(key);
}
}
@@ -476,7 +470,7 @@ int HashMap<Key, Val, HashFunc, EqualFunc>::lookupAndCreateIfMissing(const Key &
template<class Key, class Val, class HashFunc, class EqualFunc>
bool HashMap<Key, Val, HashFunc, EqualFunc>::contains(const Key &key) const {
uint ctr = lookup(key);
- return (_arr[ctr] != NULL);
+ return (_storage[ctr] != NULL);
}
template<class Key, class Val, class HashFunc, class EqualFunc>
@@ -492,15 +486,15 @@ const Val &HashMap<Key, Val, HashFunc, EqualFunc>::operator[](const Key &key) co
template<class Key, class Val, class HashFunc, class EqualFunc>
Val &HashMap<Key, Val, HashFunc, EqualFunc>::getVal(const Key &key) {
uint ctr = lookupAndCreateIfMissing(key);
- assert(_arr[ctr] != NULL);
- return _arr[ctr]->_value;
+ assert(_storage[ctr] != NULL);
+ return _storage[ctr]->_value;
}
template<class Key, class Val, class HashFunc, class EqualFunc>
const Val &HashMap<Key, Val, HashFunc, EqualFunc>::getVal(const Key &key) const {
uint ctr = lookup(key);
- if (_arr[ctr] != NULL)
- return _arr[ctr]->_value;
+ if (_storage[ctr] != NULL)
+ return _storage[ctr]->_value;
else
return _defaultVal;
}
@@ -508,38 +502,50 @@ const Val &HashMap<Key, Val, HashFunc, EqualFunc>::getVal(const Key &key) const
template<class Key, class Val, class HashFunc, class EqualFunc>
void HashMap<Key, Val, HashFunc, EqualFunc>::setVal(const Key &key, const Val &val) {
uint ctr = lookupAndCreateIfMissing(key);
- assert(_arr[ctr] != NULL);
- _arr[ctr]->_value = val;
+ assert(_storage[ctr] != NULL);
+ _storage[ctr]->_value = val;
}
template<class Key, class Val, class HashFunc, class EqualFunc>
void HashMap<Key, Val, HashFunc, EqualFunc>::erase(const Key &key) {
// This is based on code in the Wikipedia article on Hash tables.
- uint i = lookup(key);
- if (_arr[i] == NULL)
+
+ const uint hash = _hash(key);
+ uint i = hash & _mask;
+ uint perturb;
+
+ for (perturb = hash; ; perturb >>= HASHMAP_PERTURB_SHIFT) {
+ if (_storage[i] == NULL || _equal(_storage[i]->_key, key))
+ break;
+
+ i = (5 * i + perturb + 1) & _mask;
+ }
+
+ if (_storage[i] == NULL)
return; // key wasn't present, so no work has to be done
+
// If we remove a key, we must check all subsequent keys and possibly
// reinsert them.
uint j = i;
- freeNode(_arr[i]);
- _arr[i] = NULL;
- while (true) {
+ freeNode(_storage[i]);
+ _storage[i] = NULL;
+ for (perturb = hash; ; perturb >>= HASHMAP_PERTURB_SHIFT) {
// Look at the next table slot
- j = (j + 1) % _arrsize;
+ j = (5 * j + perturb + 1) & _mask;
// If the next slot is empty, we are done
- if (_arr[j] == NULL)
+ if (_storage[j] == NULL)
break;
// Compute the slot where the content of the next slot should normally be,
// assuming an empty table, and check whether we have to move it.
- uint k = _hash(_arr[j]->_key) % _arrsize;
+ uint k = _hash(_storage[j]->_key) & _mask;
if ((j > i && (k <= i || k > j)) ||
(j < i && (k <= i && k > j)) ) {
- _arr[i] = _arr[j];
+ _storage[i] = _storage[j];
i = j;
}
}
- _arr[i] = NULL;
- _nele--;
+ _storage[i] = NULL;
+ _size--;
return;
}
diff --git a/common/iff_container.h b/common/iff_container.h
index 9d477276fc..a70548abd4 100644
--- a/common/iff_container.h
+++ b/common/iff_container.h
@@ -162,7 +162,7 @@ public:
void incBytesRead(uint32 inc) {
bytesRead += inc;
if (bytesRead > size) {
- error("Chunk overead");
+ error("Chunk overread");
}
}
@@ -172,19 +172,23 @@ public:
bytesRead = 0;
}
+ bool hasReadAll() const {
+ return (size - bytesRead) == 0;
+ }
+
void feed() {
if (size % 2) {
size++;
}
- while (!_input->eos() && !eos()) {
+ while (!hasReadAll()) {
readByte();
}
}
// Common::ReadStream implementation
- bool eos() const {
- return (size - bytesRead) == 0;
- }
+ bool eos() const { return _input->eos(); }
+ bool err() const { return _input->err(); }
+ void clearErr() { _input->clearErr(); }
uint32 read(void *dataPtr, uint32 dataSize) {
incBytesRead(dataSize);
@@ -209,7 +213,7 @@ public:
_chunk.feed();
_formChunk.incBytesRead(_chunk.size);
- if (_formChunk.eos())
+ if (_formChunk.hasReadAll())
return 0;
_formChunk.incBytesRead(8);
diff --git a/common/keyboard.h b/common/keyboard.h
index 93579cbed6..9b6558dbff 100644
--- a/common/keyboard.h
+++ b/common/keyboard.h
@@ -182,7 +182,7 @@ enum KeyCode {
};
/**
- * List of certan special and some fake 'ascii' values used in keyboard events.
+ * List of certain special and some fake 'ascii' values used in keyboard events.
* The values for the function keys listed here are based on what certain SCUMM
* games expect in their scripts.
* @todo Get rid of the function key values, and instead enforce that engines use
diff --git a/common/list.h b/common/list.h
index c4e7b47644..0372d217b7 100644
--- a/common/list.h
+++ b/common/list.h
@@ -209,6 +209,11 @@ public:
++i;
}
+ void pop_front() {
+ iterator i = begin();
+ i = erase(i);
+ }
+
List<t_T> &operator=(const List<t_T> &list) {
if (this != &list) {
diff --git a/common/md5.cpp b/common/md5.cpp
index edce9d8e4e..4eeb3d9a39 100644
--- a/common/md5.cpp
+++ b/common/md5.cpp
@@ -29,6 +29,7 @@
*/
#include "common/file.h"
+#include "common/fs.h"
#include "common/md5.h"
#include "common/util.h"
#include "common/endian.h"
@@ -256,8 +257,16 @@ bool md5_file(const FilesystemNode &file, uint8 digest[16], uint32 length) {
warning("md5_file: using a directory FilesystemNode");
return false;
}
+
+ ReadStream *stream = file.openForReading();
+ if (!stream) {
+ warning("md5_file: failed to open '%s'", file.getPath().c_str());
+ return false;
+ }
- return md5_file(file.getPath().c_str(), digest, length);
+ bool result = md5_file(*stream, digest, length);
+ delete stream;
+ return result;
}
bool md5_file(const char *name, uint8 digest[16], uint32 length) {
diff --git a/common/md5.h b/common/md5.h
index e7879dc6df..a8642b1322 100644
--- a/common/md5.h
+++ b/common/md5.h
@@ -26,11 +26,12 @@
#define COMMON_MD5_H
#include "common/scummsys.h"
-#include "common/fs.h"
-#include "common/stream.h"
namespace Common {
+class FilesystemNode;
+class ReadStream;
+
bool md5_file(const char *name, uint8 digest[16], uint32 length = 0);
bool md5_file(const FilesystemNode &file, uint8 digest[16], uint32 length = 0);
bool md5_file(ReadStream &stream, uint8 digest[16], uint32 length = 0);
diff --git a/common/memorypool.cpp b/common/memorypool.cpp
index f3dfb7975f..12307ba5d6 100644
--- a/common/memorypool.cpp
+++ b/common/memorypool.cpp
@@ -28,22 +28,6 @@
namespace Common {
-static const size_t CHUNK_PAGE_SIZE = 32;
-
-void* MemoryPool::allocPage() {
- void* result = ::malloc(CHUNK_PAGE_SIZE * _chunkSize);
- _pages.push_back(result);
- void* current = result;
- for (size_t i = 1; i < CHUNK_PAGE_SIZE; ++i) {
- void* next = ((char*)current + _chunkSize);
- *(void**)current = next;
-
- current = next;
- }
- *(void**)current = NULL;
- return result;
-}
-
MemoryPool::MemoryPool(size_t chunkSize) {
// You must at least fit the pointer in the node (technically unneeded considering the next rounding statement)
_chunkSize = MAX(chunkSize, sizeof(void*));
@@ -52,38 +36,68 @@ MemoryPool::MemoryPool(size_t chunkSize) {
_chunkSize = (_chunkSize + sizeof(void*) - 1) & (~(sizeof(void*) - 1));
_next = NULL;
+
+ _chunksPerPage = 8;
}
MemoryPool::~MemoryPool() {
- for (size_t i = 0; i<_pages.size(); ++i)
- ::free(_pages[i]);
+ for (size_t i = 0; i < _pages.size(); ++i)
+ ::free(_pages[i].start);
+}
+
+void MemoryPool::allocPage() {
+ Page page;
+
+ // Allocate a new page
+ page.numChunks = _chunksPerPage;
+ page.start = ::malloc(page.numChunks * _chunkSize);
+ assert(page.start);
+ _pages.push_back(page);
+
+ // Next time, we'll alocate a page twice as big as this one.
+ _chunksPerPage *= 2;
+
+ // Add the page to the pool of free chunk
+ addPageToPool(page);
+}
+
+void MemoryPool::addPageToPool(const Page &page) {
+
+ // Add all chunks of the new page to the linked list (pool) of free chunks
+ void *current = page.start;
+ for (size_t i = 1; i < page.numChunks; ++i) {
+ void *next = ((char*)current + _chunkSize);
+ *(void **)current = next;
+
+ current = next;
+ }
+
+ // Last chunk points to the old _next
+ *(void**)current = _next;
+
+ // From now on, the first free chunk is the first chunk of the new page
+ _next = page.start;
}
-void* MemoryPool::malloc() {
-#if 1
- if (!_next)
- _next = allocPage();
+void *MemoryPool::malloc() {
+ if (!_next) // No free chunks left? Allocate a new page
+ allocPage();
- void* result = _next;
+ assert(_next);
+ void *result = _next;
_next = *(void**)result;
return result;
-#else
- return ::malloc(_chunkSize);
-#endif
}
void MemoryPool::free(void* ptr) {
-#if 1
+ // Add the chunk back to (the start of) the list of free chunks
*(void**)ptr = _next;
_next = ptr;
-#else
- ::free(ptr);
-#endif
}
// Technically not compliant C++ to compare unrelated pointers. In practice...
-bool MemoryPool::isPointerInPage(void* ptr, void* page) {
- return (ptr >= page) && (ptr < (char*)page + CHUNK_PAGE_SIZE * _chunkSize);
+bool MemoryPool::isPointerInPage(void *ptr, const Page &page) {
+ return (ptr >= page.start) && (ptr < (char*)page.start + page.numChunks * _chunkSize);
}
void MemoryPool::freeUnusedPages() {
@@ -94,9 +108,10 @@ void MemoryPool::freeUnusedPages() {
numberOfFreeChunksPerPage[i] = 0;
}
- void* iterator = _next;
+ // Compute for each page how many chunks in it are still in use.
+ void *iterator = _next;
while (iterator) {
- // This should be a binary search
+ // TODO: This should be a binary search (requiring us to keep _pages sorted)
for (size_t i = 0; i < _pages.size(); ++i) {
if (isPointerInPage(iterator, _pages[i])) {
++numberOfFreeChunksPerPage[i];
@@ -106,12 +121,32 @@ void MemoryPool::freeUnusedPages() {
iterator = *(void**)iterator;
}
+ // Free all pages which are not in use.
+ // TODO: Might want to reset _chunksPerPage here (e.g. to the largest
+ // _pages[i].numChunks value still in use).
size_t freedPagesCount = 0;
- for (size_t i = 0; i < _pages.size(); ++i) {
- if (numberOfFreeChunksPerPage[i] == CHUNK_PAGE_SIZE) {
- ::free(_pages[i]);
- _pages[i] = NULL; // TODO : Remove NULL values
+ for (size_t i = 0; i < _pages.size(); ++i) {
+ if (numberOfFreeChunksPerPage[i] == _pages[i].numChunks) {
+ // Remove all chunks of this page from the list of free chunks
+ void **iter2 = &_next;
+ while (*iter2) {
+ if (isPointerInPage(*iter2, _pages[i]))
+ *iter2 = **(void***)iter2;
+ else
+ iter2 = *(void***)iter2;
+ }
+ ::free(_pages[i].start);
++freedPagesCount;
+ _pages[i].start = NULL;
+ }
+ }
+
+ for (size_t i = 0; i < _pages.size(); ) {
+ if (_pages[i].start == NULL) {
+ _pages.remove_at(i);
+ // We just removed an entry, so we do not advance "i"
+ } else {
+ ++i;
}
}
diff --git a/common/memorypool.h b/common/memorypool.h
index fcbacabc5c..dd2e8f13a4 100644
--- a/common/memorypool.h
+++ b/common/memorypool.h
@@ -32,26 +32,57 @@
namespace Common {
class MemoryPool {
-private:
+protected:
MemoryPool(const MemoryPool&);
MemoryPool& operator=(const MemoryPool&);
+
+ struct Page {
+ void *start;
+ size_t numChunks;
+ };
size_t _chunkSize;
- Array<void*> _pages;
- void* _next;
+ Array<Page> _pages;
+ void *_next;
+ size_t _chunksPerPage;
+
+ void allocPage();
+ void addPageToPool(const Page &page);
+ bool isPointerInPage(void *ptr, const Page &page);
- void* allocPage();
- bool isPointerInPage(void* ptr, void* page);
public:
MemoryPool(size_t chunkSize);
~MemoryPool();
- void* malloc();
- void free(void* ptr);
+ void *malloc();
+ void free(void *ptr);
void freeUnusedPages();
};
+template<size_t CHUNK_SIZE, size_t NUM_INTERNAL_CHUNKS = 32>
+class FixedSizeMemoryPool : public MemoryPool {
+private:
+ enum {
+ REAL_CHUNK_SIZE = (CHUNK_SIZE + sizeof(void*) - 1) & (~(sizeof(void*) - 1))
+ };
+
+ byte _storage[NUM_INTERNAL_CHUNKS * REAL_CHUNK_SIZE];
+public:
+ FixedSizeMemoryPool() : MemoryPool(CHUNK_SIZE) {
+ assert(REAL_CHUNK_SIZE == _chunkSize);
+ // Insert some static storage
+ Page internalPage = { _storage, NUM_INTERNAL_CHUNKS };
+ addPageToPool(internalPage);
+ }
+};
+
+template<size_t CHUNK_SIZE>
+class FixedSizeMemoryPool<CHUNK_SIZE,0> : public MemoryPool {
+public:
+ FixedSizeMemoryPool() : MemoryPool(CHUNK_SIZE) {}
+};
+
} // End of namespace Common
#endif
diff --git a/common/module.mk b/common/module.mk
index ed15bf75ea..e04af5270b 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -2,6 +2,7 @@ MODULE := common
MODULE_OBJS := \
advancedDetector.o \
+ archive.o \
config-file.o \
config-manager.o \
file.o \
diff --git a/common/ptr.h b/common/ptr.h
index c6fcaa4f75..99bc82a2d3 100644
--- a/common/ptr.h
+++ b/common/ptr.h
@@ -218,8 +218,4 @@ bool operator!=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r)
return l.get() != r.get();
}
-
#endif
-
-
-
diff --git a/common/queue.h b/common/queue.h
new file mode 100644
index 0000000000..f1881345e8
--- /dev/null
+++ b/common/queue.h
@@ -0,0 +1,94 @@
+/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/branches/gsoc2008-rtl/common/stack.h $
+ * $Id$
+ */
+
+#ifndef COMMON_QUEUE_H
+#define COMMON_QUEUE_H
+
+#include "common/scummsys.h"
+#include "common/list.h"
+
+namespace Common {
+
+/**
+ * Variable size Queue class, implemented using our List class.
+ */
+template<class T>
+class Queue {
+public:
+ typedef T value_type;
+
+public:
+ Queue<T>() : _impl() {}
+ Queue<T>(const Queue<T> &queue) : _impl(queue._impl) {}
+
+ Queue<T> &operator=(const Queue<T> &queue) {
+ _impl = queue._impl;
+ return *this;
+ }
+
+ bool empty() const {
+ return _impl.empty();
+ }
+
+ void clear() {
+ _impl.clear();
+ }
+
+ void push(const T &x) {
+ _impl.push_back(x);
+ }
+
+ T &front() {
+ return *_impl.begin();
+ }
+
+ const T &front() const {
+ return *_impl.begin();
+ }
+
+ T &back() {
+ return *_impl.reverse_begin();
+ }
+
+ const T &back() const {
+ return *_impl.reverse_begin();
+ }
+
+ T pop() {
+ T tmp = front();
+ _impl.pop_front();
+ return tmp;
+ }
+
+ int size() const {
+ return _impl.size();
+ }
+
+private:
+ List<T> _impl;
+};
+
+} // End of namespace Common
+
+#endif
diff --git a/common/str.cpp b/common/str.cpp
index 5f8d4ffb7e..a415e376c9 100644
--- a/common/str.cpp
+++ b/common/str.cpp
@@ -34,28 +34,24 @@ const String String::emptyString;
const char *String::emptyString = "";
#endif
-static int computeCapacity(int len) {
- // By default, for the capacity we use the nearest multiple of 32
- // that leaves at least 16 chars of extra space (in case the string
- // grows a bit).
- // Finally, we subtract 1 to compensate for the trailing zero byte.
- len += 16;
- return ((len + 32 - 1) & ~0x1F) - 1;
+static uint32 computeCapacity(uint32 len) {
+ // By default, for the capacity we use the next multiple of 32
+ return ((len + 32 - 1) & ~0x1F);
}
-String::String(const char *str) : _len(0), _str(_storage) {
+String::String(const char *str) : _size(0), _str(_storage) {
if (str == 0) {
_storage[0] = 0;
- _len = 0;
+ _size = 0;
} else
initWithCStr(str, strlen(str));
}
-String::String(const char *str, uint32 len) : _len(0), _str(_storage) {
+String::String(const char *str, uint32 len) : _size(0), _str(_storage) {
initWithCStr(str, len);
}
-String::String(const char *beginP, const char *endP) : _len(0), _str(_storage) {
+String::String(const char *beginP, const char *endP) : _size(0), _str(_storage) {
assert(endP >= beginP);
initWithCStr(beginP, endP - beginP);
}
@@ -67,13 +63,13 @@ void String::initWithCStr(const char *str, uint32 len) {
// for GCC 2.95.x compatibility (see also tracker item #1602879).
_storage[0] = 0;
- _len = len;
+ _size = len;
if (len >= _builtinCapacity) {
// Not enough internal storage, so allocate more
- _extern._capacity = computeCapacity(len);
+ _extern._capacity = computeCapacity(len+1);
_extern._refCount = 0;
- _str = (char *)malloc(_extern._capacity+1);
+ _str = (char *)malloc(_extern._capacity);
assert(_str != 0);
}
@@ -83,10 +79,10 @@ void String::initWithCStr(const char *str, uint32 len) {
}
String::String(const String &str)
- : _len(str._len), _str(str.isStorageIntern() ? _storage : str._str) {
+ : _size(str._size), _str(str.isStorageIntern() ? _storage : str._str) {
if (str.isStorageIntern()) {
// String in internal storage: just copy it
- memcpy(_storage, str._storage, _builtinCapacity);
+ memcpy(_storage, str._storage, sizeof(_storage));
} else {
// String in external storage: use refcount mechanism
str.incRefCount();
@@ -97,14 +93,14 @@ String::String(const String &str)
}
String::String(char c)
-: _len(0), _str(_storage) {
+: _size(0), _str(_storage) {
_storage[0] = c;
_storage[1] = 0;
// TODO/FIXME: There is no reason for the following check -- we *do*
// allow strings to contain 0 bytes!
- _len = (c == 0) ? 0 : 1;
+ _size = (c == 0) ? 0 : 1;
}
String::~String() {
@@ -112,16 +108,16 @@ String::~String() {
}
void String::makeUnique() {
- ensureCapacity(_len, true);
+ ensureCapacity(_size, true);
}
/**
- * Ensure that enough storage is available to store at least new_len
+ * Ensure that enough storage is available to store at least new_size
* characters plus a null byte. In addition, if we currently share
* the storage with another string, unshare it, so that we can safely
* write to the storage.
*/
-void String::ensureCapacity(uint32 new_len, bool keep_old) {
+void String::ensureCapacity(uint32 new_size, bool keep_old) {
bool isShared;
uint32 curCapacity, newCapacity;
char *newStorage;
@@ -129,7 +125,7 @@ void String::ensureCapacity(uint32 new_len, bool keep_old) {
if (isStorageIntern()) {
isShared = false;
- curCapacity = _builtinCapacity - 1;
+ curCapacity = _builtinCapacity;
} else {
isShared = (oldRefCount && *oldRefCount > 1);
curCapacity = _extern._capacity;
@@ -137,30 +133,30 @@ void String::ensureCapacity(uint32 new_len, bool keep_old) {
// Special case: If there is enough space, and we do not share
// the storage, then there is nothing to do.
- if (!isShared && new_len <= curCapacity)
+ if (!isShared && new_size < curCapacity)
return;
- if (isShared && new_len <= _builtinCapacity - 1) {
+ if (isShared && new_size < _builtinCapacity) {
// We share the storage, but there is enough internal storage: Use that.
newStorage = _storage;
- newCapacity = _builtinCapacity - 1;
+ newCapacity = _builtinCapacity;
} else {
// We need to allocate storage on the heap!
// Compute a suitable new capacity limit
- newCapacity = computeCapacity(new_len);
+ newCapacity = MAX(curCapacity * 2, computeCapacity(new_size+1));
// Allocate new storage
- newStorage = (char *)malloc(newCapacity+1);
+ newStorage = (char *)malloc(newCapacity);
assert(newStorage);
}
// Copy old data if needed, elsewise reset the new storage.
if (keep_old) {
- assert(_len <= newCapacity);
- memcpy(newStorage, _str, _len + 1);
+ assert(_size < newCapacity);
+ memcpy(newStorage, _str, _size + 1);
} else {
- _len = 0;
+ _size = 0;
newStorage[0] = 0;
}
@@ -210,7 +206,7 @@ void String::decRefCount(int *oldRefCount) {
String& String::operator =(const char *str) {
uint32 len = strlen(str);
ensureCapacity(len, false);
- _len = len;
+ _size = len;
memmove(_str, str, len + 1);
return *this;
}
@@ -221,16 +217,16 @@ String &String::operator =(const String &str) {
if (str.isStorageIntern()) {
decRefCount(_extern._refCount);
- _len = str._len;
+ _size = str._size;
_str = _storage;
- memcpy(_str, str._str, _len + 1);
+ memcpy(_str, str._str, _size + 1);
} else {
str.incRefCount();
decRefCount(_extern._refCount);
_extern._refCount = str._extern._refCount;
_extern._capacity = str._extern._capacity;
- _len = str._len;
+ _size = str._size;
_str = str._str;
}
@@ -240,7 +236,7 @@ String &String::operator =(const String &str) {
String& String::operator =(char c) {
decRefCount(_extern._refCount);
_str = _storage;
- _len = 1;
+ _size = 1;
_str[0] = c;
_str[1] = 0;
return *this;
@@ -249,30 +245,30 @@ String& String::operator =(char c) {
String &String::operator +=(const char *str) {
int len = strlen(str);
if (len > 0) {
- ensureCapacity(_len + len, true);
+ ensureCapacity(_size + len, true);
- memcpy(_str + _len, str, len + 1);
- _len += len;
+ memcpy(_str + _size, str, len + 1);
+ _size += len;
}
return *this;
}
String &String::operator +=(const String &str) {
- int len = str._len;
+ int len = str._size;
if (len > 0) {
- ensureCapacity(_len + len, true);
+ ensureCapacity(_size + len, true);
- memcpy(_str + _len, str._str, len + 1);
- _len += len;
+ memcpy(_str + _size, str._str, len + 1);
+ _size += len;
}
return *this;
}
String &String::operator +=(char c) {
- ensureCapacity(_len + 1, true);
+ ensureCapacity(_size + 1, true);
- _str[_len++] = c;
- _str[_len] = 0;
+ _str[_size++] = c;
+ _str[_size] = 0;
return *this;
}
@@ -293,10 +289,10 @@ bool String::hasPrefix(const char *x) const {
bool String::hasSuffix(const char *x) const {
assert(x != 0);
// Compare x with the end of _str.
- const uint32 x_len = strlen(x);
- if (x_len > _len)
+ const uint32 x_size = strlen(x);
+ if (x_size > _size)
return false;
- const char *y = c_str() + _len - x_len;
+ const char *y = c_str() + _size - x_size;
while (*x && *x == *y) {
++x;
++y;
@@ -315,66 +311,74 @@ bool String::contains(char x) const {
return strchr(c_str(), x) != NULL;
}
+bool String::matchString(const char *pat) const {
+ return Common::matchString(c_str(), pat);
+}
+
+bool String::matchString(const String &pat) const {
+ return Common::matchString(c_str(), pat.c_str());
+}
+
void String::deleteLastChar() {
- deleteChar(_len - 1);
+ deleteChar(_size - 1);
}
void String::deleteChar(uint32 p) {
- assert(p < _len);
+ assert(p < _size);
makeUnique();
- while (p++ < _len)
+ while (p++ < _size)
_str[p-1] = _str[p];
- _len--;
+ _size--;
}
void String::clear() {
decRefCount(_extern._refCount);
- _len = 0;
+ _size = 0;
_str = _storage;
_storage[0] = 0;
}
void String::setChar(char c, uint32 p) {
- assert(p <= _len);
+ assert(p <= _size);
makeUnique();
_str[p] = c;
}
void String::insertChar(char c, uint32 p) {
- assert(p <= _len);
+ assert(p <= _size);
- ensureCapacity(_len + 1, true);
- _len++;
- for (uint32 i = _len; i > p; --i)
+ ensureCapacity(_size + 1, true);
+ _size++;
+ for (uint32 i = _size; i > p; --i)
_str[i] = _str[i-1];
_str[p] = c;
}
void String::toLowercase() {
makeUnique();
- for (uint32 i = 0; i < _len; ++i)
+ for (uint32 i = 0; i < _size; ++i)
_str[i] = tolower(_str[i]);
}
void String::toUppercase() {
makeUnique();
- for (uint32 i = 0; i < _len; ++i)
+ for (uint32 i = 0; i < _size; ++i)
_str[i] = toupper(_str[i]);
}
void String::trim() {
- if (_len == 0)
+ if (_size == 0)
return;
makeUnique();
// Trim trailing whitespace
- while (_len >= 1 && isspace(_str[_len-1]))
- _len--;
- _str[_len] = 0;
+ while (_size >= 1 && isspace(_str[_size-1]))
+ _size--;
+ _str[_size] = 0;
// Trim leading whitespace
char *t = _str;
@@ -382,8 +386,8 @@ void String::trim() {
t++;
if (t != _str) {
- _len -= t - _str;
- memmove(_str, t, _len + 1);
+ _size -= t - _str;
+ memmove(_str, t, _size + 1);
}
}
@@ -524,4 +528,112 @@ char *trim(char *t) {
return rtrim(ltrim(t));
}
+Common::String lastPathComponent(const Common::String &path, const char sep) {
+ const char *str = path.c_str();
+ const char *last = str + path.size();
+
+ // Skip over trailing slashes
+ while (last > str && *(last-1) == sep)
+ --last;
+
+ // Path consisted of only slashes -> return empty string
+ if (last == str)
+ return Common::String();
+
+ // Now scan the whole component
+ const char *first = last - 1;
+ while (first >= str && *first != sep)
+ --first;
+
+ if (*first == sep)
+ first++;
+
+ return Common::String(first, last);
+}
+
+Common::String normalizePath(const Common::String &path, const char sep) {
+ if (path.empty())
+ return path;
+
+ const char *cur = path.c_str();
+ Common::String result;
+
+ // If there is a leading slash, preserve that:
+ if (*cur == sep) {
+ result += sep;
+ while (*cur == sep)
+ ++cur;
+ }
+
+ // Scan till the end of the String
+ while (*cur != 0) {
+ const char *start = cur;
+
+ // Scan till the next path separator resp. the end of the string
+ while (*cur != sep && *cur != 0)
+ cur++;
+
+ const Common::String component(start, cur);
+
+ // Skip empty components and dot components, add all others
+ if (!component.empty() && component != ".") {
+ // Add a separator before the component, unless the result
+ // string already ends with one (which happens only if the
+ // path *starts* with a separator).
+ if (!result.empty() && result.lastChar() != sep)
+ result += sep;
+
+ // Add the component
+ result += component;
+ }
+
+ // Skip over separator chars
+ while (*cur == sep)
+ cur++;
+ }
+
+ return result;
+}
+
+bool matchString(const char *str, const char *pat) {
+ assert(str);
+ assert(pat);
+
+ const char *p = 0;
+ const char *q = 0;
+
+ for (;;) {
+ switch (*pat) {
+ case '*':
+ // Record pattern / string possition for backtracking
+ p = ++pat;
+ q = str;
+ // If pattern ended with * -> match
+ if (!*pat)
+ return true;
+ break;
+
+ default:
+ if (*pat != *str) {
+ if (p) {
+ // No match, oops -> try to backtrack
+ pat = p;
+ str = ++q;
+ if (!*str)
+ return !*pat;
+ break;
+ }
+ else
+ return false;
+ }
+ // fallthrough
+ case '?':
+ if (!*str)
+ return !*pat;
+ pat++;
+ str++;
+ }
+ }
+}
+
} // End of namespace Common
diff --git a/common/str.h b/common/str.h
index 3479fee8e4..20914c1f1f 100644
--- a/common/str.h
+++ b/common/str.h
@@ -61,7 +61,7 @@ protected:
* a lot. Yes, we limit ourselves to strings shorter than 4GB --
* on purpose :-).
*/
- uint32 _len;
+ uint32 _size;
/**
* Pointer to the actual string storage. Either points to _storage,
@@ -97,7 +97,7 @@ public:
#endif
/** Construct a new empty string. */
- String() : _len(0), _str(_storage) { _storage[0] = 0; }
+ String() : _size(0), _str(_storage) { _storage[0] = 0; }
/** Construct a new string from the given NULL-terminated C string. */
String(const char *str);
@@ -149,14 +149,38 @@ public:
bool contains(const char *x) const;
bool contains(char x) const;
+ /**
+ * Simple DOS-style pattern matching function (understands * and ? like used in DOS).
+ * Taken from exult/files/listfiles.cc
+ *
+ * Token meaning:
+ * "*": any character, any amount of times.
+ * "?": any character, only once.
+ *
+ * Example strings/patterns:
+ * String: monkey.s01 Pattern: monkey.s?? => true
+ * String: monkey.s101 Pattern: monkey.s?? => false
+ * String: monkey.s99 Pattern: monkey.s?1 => false
+ * String: monkey.s101 Pattern: monkey.s* => true
+ * String: monkey.s99 Pattern: monkey.s*1 => false
+ *
+ * @param str Text to be matched against the given pattern.
+ * @param pat Glob pattern.
+ *
+ * @return true if str matches the pattern, false otherwise.
+ */
+ bool matchString(const char *pat) const;
+ bool matchString(const String &pat) const;
+
+
inline const char *c_str() const { return _str; }
- inline uint size() const { return _len; }
+ inline uint size() const { return _size; }
- inline bool empty() const { return (_len == 0); }
- char lastChar() const { return (_len > 0) ? _str[_len-1] : 0; }
+ inline bool empty() const { return (_size == 0); }
+ char lastChar() const { return (_size > 0) ? _str[_size-1] : 0; }
char operator [](int idx) const {
- assert(_str && idx >= 0 && idx < (int)_len);
+ assert(_str && idx >= 0 && idx < (int)_size);
return _str[idx];
}
@@ -172,11 +196,19 @@ public:
/** Set character c at position p. */
void insertChar(char c, uint32 p);
+ /** Clears the string, making it empty. */
void clear();
+ /** Convert all characters in the string to lowercase. */
void toLowercase();
+
+ /** Convert all characters in the string to uppercase. */
void toUppercase();
+ /**
+ * Removes trailing and leading whitespaces. Uses isspace() to decide
+ * what is whitespace and what not.
+ */
void trim();
uint hash() const;
@@ -203,7 +235,7 @@ public:
protected:
void makeUnique();
- void ensureCapacity(uint32 new_len, bool keep_old);
+ void ensureCapacity(uint32 new_size, bool keep_old);
void incRefCount() const;
void decRefCount(int *oldRefCount);
void initWithCStr(const char *str, uint32 len);
@@ -218,7 +250,7 @@ String operator +(const String &x, const char *y);
String operator +(const String &x, char y);
String operator +(char x, const String &y);
-// Some useful additional comparision operators for Strings
+// Some useful additional comparison operators for Strings
bool operator == (const char *x, const String &y);
bool operator != (const char *x, const String &y);
@@ -227,16 +259,67 @@ extern char *ltrim(char *t);
extern char *rtrim(char *t);
extern char *trim(char *t);
+
+/**
+ * Returns the last component of a given path.
+ *
+ * Examples:
+ * /foo/bar.txt would return 'bar.txt'
+ * /foo/bar/ would return 'bar'
+ * /foo/./bar// would return 'bar'
+ *
+ * @param path the path of which we want to know the last component
+ * @param sep character used to separate path components
+ * @return The last component of the path.
+ */
+Common::String lastPathComponent(const Common::String &path, const char sep);
+
+/**
+ * Normalize a gien path to a canonical form. In particular:
+ * - trailing separators are removed: /foo/bar/ -> /foo/bar
+ * - double separators (= empty components) are removed: /foo//bar -> /foo/bar
+ * - dot components are removed: /foo/./bar -> /foo/bar
+ *
+ * @todo remove double dot components: /foo/baz/../bar -> /foo/bar
+ *
+ * @param path the path to normalize
+ * @param sep the separator token (usually '/' on Unix-style systems, or '\\' on Windows based stuff)
+ * @return the normalized path
+ */
+Common::String normalizePath(const Common::String &path, const char sep);
+
+
+/**
+ * Simple DOS-style pattern matching function (understands * and ? like used in DOS).
+ * Taken from exult/files/listfiles.cc
+ *
+ * Token meaning:
+ * "*": any character, any amount of times.
+ * "?": any character, only once.
+ *
+ * Example strings/patterns:
+ * String: monkey.s01 Pattern: monkey.s?? => true
+ * String: monkey.s101 Pattern: monkey.s?? => false
+ * String: monkey.s99 Pattern: monkey.s?1 => false
+ * String: monkey.s101 Pattern: monkey.s* => true
+ * String: monkey.s99 Pattern: monkey.s*1 => false
+ *
+ * @param str Text to be matched against the given pattern.
+ * @param pat Glob pattern.
+ *
+ * @return true if str matches the pattern, false otherwise.
+ */
+bool matchString(const char *str, const char *pat);
+
+
class StringList : public Array<String> {
public:
void push_back(const char *str) {
- ensureCapacity(_size + 1);
- _data[_size++] = str;
+ Array<String>::push_back(str);
}
void push_back(const String &str) {
- ensureCapacity(_size + 1);
- _data[_size++] = str;
+ Array<String>::push_back(str);
}
};
diff --git a/common/stream.cpp b/common/stream.cpp
index e06cc28415..9bcc29550f 100644
--- a/common/stream.cpp
+++ b/common/stream.cpp
@@ -43,8 +43,10 @@ MemoryReadStream *ReadStream::readStream(uint32 dataSize) {
uint32 MemoryReadStream::read(void *dataPtr, uint32 dataSize) {
// Read at most as many bytes as are still available...
- if (dataSize > _size - _pos)
+ if (dataSize > _size - _pos) {
dataSize = _size - _pos;
+ _eos = true;
+ }
memcpy(dataPtr, _ptr, dataSize);
if (_encbyte) {
@@ -60,7 +62,7 @@ uint32 MemoryReadStream::read(void *dataPtr, uint32 dataSize) {
return dataSize;
}
-void MemoryReadStream::seek(int32 offs, int whence) {
+bool MemoryReadStream::seek(int32 offs, int whence) {
// Pre-Condition
assert(_pos <= _size);
switch (whence) {
@@ -81,12 +83,16 @@ void MemoryReadStream::seek(int32 offs, int whence) {
}
// Post-Condition
assert(_pos <= _size);
+
+ // Reset end-of-stream flag on a successful seek
+ _eos = false;
+ return true; // FIXME: STREAM REWRITE
}
#define LF 0x0A
#define CR 0x0D
-char *SeekableReadStream::readLine(char *buf, size_t bufSize) {
+char *SeekableReadStream::readLine_OLD(char *buf, size_t bufSize) {
assert(buf && bufSize > 0);
char *p = buf;
size_t len = 0;
@@ -156,19 +162,27 @@ char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
// If end-of-file occurs before any characters are read, return NULL
// and the buffer contents remain unchanged.
- if (eos() || ioFailed()) {
+ if (eos() || err()) {
return 0;
}
- // Loop as long as the stream has not ended, there is still free
- // space in the buffer, and the line has not ended
- while (!eos() && len + 1 < bufSize && c != LF) {
+ // Loop as long as there is still free space in the buffer,
+ // and the line has not ended
+ while (len + 1 < bufSize && c != LF) {
c = readByte();
-
- // If end-of-file occurs before any characters are read, return
- // NULL and the buffer contents remain unchanged. If an error
- /// occurs, return NULL and the buffer contents are indeterminate.
- if (ioFailed() || (len == 0 && eos()))
+
+ if (eos()) {
+ // If end-of-file occurs before any characters are read, return
+ // NULL and the buffer contents remain unchanged.
+ if (len == 0)
+ return 0;
+
+ break;
+ }
+
+ // If an error occurs, return NULL and the buffer contents
+ // are indeterminate.
+ if (err())
return 0;
// Check for CR or CR/LF
@@ -178,8 +192,18 @@ char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
if (c == CR) {
// Look at the next char -- is it LF? If not, seek back
c = readByte();
- if (c != LF && !eos())
+
+ if (err()) {
+ return 0; // error: the buffer contents are indeterminate
+ }
+ if (eos()) {
+ // The CR was the last character in the file.
+ // Reset the eos() flag since we successfully finished a line
+ clearErr();
+ } else if (c != LF) {
seek(-1, SEEK_CUR);
+ }
+
// Treat CR & CR/LF as plain LF
c = LF;
}
@@ -188,25 +212,37 @@ char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
len++;
}
- // FIXME:
- // This should fix a bug while using readLine with Common::File
- // it seems that it sets the eos flag after an invalid read
- // and at the same time the ioFailed flag
- // the config file parser fails out of that reason for the new themes
- if (eos()) {
- clearIOFailed();
- }
-
// We always terminate the buffer if no error occured
*p = 0;
return buf;
}
+String SeekableReadStream::readLine() {
+ // Read a line
+ String line;
+ while (line.lastChar() != '\n') {
+ char buf[256];
+ if (!readLine_NEW(buf, 256))
+ break;
+ line += buf;
+ }
+
+ if (line.lastChar() == '\n')
+ line.deleteLastChar();
+
+ return line;
+}
+
+
uint32 SubReadStream::read(void *dataPtr, uint32 dataSize) {
- dataSize = MIN(dataSize, _end - _pos);
+ if (dataSize > _end - _pos) {
+ dataSize = _end - _pos;
+ _eos = true;
+ }
dataSize = _parentStream->read(dataPtr, dataSize);
+ _eos |= _parentStream->eos();
_pos += dataSize;
return dataSize;
@@ -219,9 +255,10 @@ SeekableSubReadStream::SeekableSubReadStream(SeekableReadStream *parentStream, u
assert(_begin <= _end);
_pos = _begin;
_parentStream->seek(_pos);
+ _eos = false;
}
-void SeekableSubReadStream::seek(int32 offset, int whence) {
+bool SeekableSubReadStream::seek(int32 offset, int whence) {
assert(_pos >= _begin);
assert(_pos <= _end);
@@ -239,7 +276,10 @@ void SeekableSubReadStream::seek(int32 offset, int whence) {
assert(_pos >= _begin);
assert(_pos <= _end);
- _parentStream->seek(_pos);
+ bool ret = _parentStream->seek(_pos);
+ if (ret) _eos = false; // reset eos on successful seek
+
+ return ret;
}
BufferedReadStream::BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream)
@@ -304,7 +344,7 @@ BufferedSeekableReadStream::BufferedSeekableReadStream(SeekableReadStream *paren
_parentStream(parentStream) {
}
-void BufferedSeekableReadStream::seek(int32 offset, int whence) {
+bool BufferedSeekableReadStream::seek(int32 offset, int whence) {
// If it is a "local" seek, we may get away with "seeking" around
// in the buffer only.
// Note: We could try to handle SEEK_END and SEEK_SET, too, but
@@ -319,6 +359,8 @@ void BufferedSeekableReadStream::seek(int32 offset, int whence) {
_pos = _bufSize;
_parentStream->seek(offset, whence);
}
+
+ return true; // FIXME: STREAM REWRITE
}
} // End of namespace Common
diff --git a/common/stream.h b/common/stream.h
index 01a946e685..02677e0dbb 100644
--- a/common/stream.h
+++ b/common/stream.h
@@ -41,19 +41,30 @@ public:
virtual ~Stream() {}
/**
- * Returns true if any I/O failure occurred.
- * This flag is never cleared automatically. In order to clear it,
- * client code has to call clearIOFailed() explicitly.
- *
- * @todo Instead of returning a plain bool, maybe we should define
- * a list of error codes which can be returned here.
+ * DEPRECATED: Use err() or eos() instead.
+ * Returns true if any I/O failure occurred or the end of the
+ * stream was reached while reading.
*/
- virtual bool ioFailed() const { return false; }
+ virtual bool ioFailed() const { return err(); }
/**
+ * DEPRECATED: Don't use this unless you are still using ioFailed().
* Reset the I/O error status.
*/
- virtual void clearIOFailed() {}
+ virtual void clearIOFailed() { clearErr(); }
+
+ /**
+ * Returns true if an I/O failure occurred.
+ * This flag is never cleared automatically. In order to clear it,
+ * client code has to call clearErr() explicitly.
+ */
+ virtual bool err() const { return false; }
+
+ /**
+ * Reset the I/O error status as returned by err().
+ * For a ReadStream, also reset the end-of-stream status returned by eos().
+ */
+ virtual void clearErr() {}
};
/**
@@ -75,8 +86,10 @@ public:
* Commit any buffered data to the underlying channel or
* storage medium; unbuffered streams can use the default
* implementation.
+ *
+ * @return true on success, false in case of a failure
*/
- virtual void flush() {}
+ virtual bool flush() { return true; }
/**
* Finalize and close this stream. To be called right before this
@@ -85,7 +98,7 @@ public:
* closing (and this flushing, if buffered) the stream.
*
* After this method has been called, no further writes may be
- * peformed on the stream. Calling ioFailed() is allowed.
+ * performed on the stream. Calling err() is allowed.
*
* By default, this just flushes the stream.
*/
@@ -151,7 +164,9 @@ public:
class ReadStream : virtual public Stream {
public:
/**
- * Returns true if the end of the stream has been reached.
+ * Returns true if a read failed because the stream has been reached.
+ * This flag is cleared by clearErr().
+ * For a SeekableReadStream, it is also cleared by a successful seek.
*/
virtual bool eos() const = 0;
@@ -167,13 +182,19 @@ public:
// The remaining methods all have default implementations; subclasses
- // need not (and should not) overload them.
+ // in general should not overload them.
/**
- * Read am unsigned byte from the stream and return it.
+ * DEPRECATED
+ * Default implementation for backward compatibility
+ */
+ virtual bool ioFailed() { return (eos() || err()); }
+
+ /**
+ * Read an unsigned byte from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
byte readByte() {
byte b = 0;
@@ -185,7 +206,7 @@ public:
* Read a signed byte from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
int8 readSByte() {
int8 b = 0;
@@ -198,7 +219,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
uint16 readUint16LE() {
uint16 a = readByte();
@@ -211,7 +232,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
uint32 readUint32LE() {
uint32 a = readUint16LE();
@@ -224,7 +245,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
uint16 readUint16BE() {
uint16 b = readByte();
@@ -237,7 +258,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
uint32 readUint32BE() {
uint32 b = readUint16BE();
@@ -250,7 +271,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
int16 readSint16LE() {
return (int16)readUint16LE();
@@ -261,7 +282,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
int32 readSint32LE() {
return (int32)readUint32LE();
@@ -272,7 +293,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
int16 readSint16BE() {
return (int16)readUint16BE();
@@ -283,7 +304,7 @@ public:
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
- * calling ioFailed()).
+ * calling err() and eos() ).
*/
int32 readSint32BE() {
return (int32)readUint32BE();
@@ -293,7 +314,9 @@ public:
* Read the specified amount of data into a malloc'ed buffer
* which then is wrapped into a MemoryReadStream.
* The returned stream might contain less data than requested,
- * if reading more failed.
+ * if reading more failed, because of an I/O error or because
+ * the end of the stream was reached. Which can be determined by
+ * calling err() and eos().
*/
MemoryReadStream *readStream(uint32 dataSize);
@@ -303,57 +326,89 @@ public:
/**
* Interface for a seekable & readable data stream.
*
- * @todo We really need better error handling here!
- * Like seek should somehow indicate whether it failed.
+ * @todo Get rid of SEEK_SET, SEEK_CUR, or SEEK_END, use our own constants
*/
class SeekableReadStream : virtual public ReadStream {
public:
- virtual uint32 pos() const = 0;
- virtual uint32 size() const = 0;
-
- virtual void seek(int32 offset, int whence = SEEK_SET) = 0;
-
- void skip(uint32 offset) { seek(offset, SEEK_CUR); }
-
/**
- * Read one line of text from a CR or CR/LF terminated plain text file.
- * This method is a rough analog of the (f)gets function.
+ * Obtains the current value of the stream position indicator of the
+ * stream.
*
- * @bug A main difference (and flaw) in this function is that there is no
- * way to detect that a line exceeeds the length of the buffer.
- * Code which needs this should use the new readLine_NEW() method instead.
+ * @return the current position indicator, or -1 if an error occurred.
+ */
+ virtual int32 pos() const = 0;
+
+ /**
+ * Obtains the total size of the stream, measured in bytes.
+ * If this value is unknown or can not be computed, -1 is returned.
*
- * @param buf the buffer to store into
- * @param bufSize the size of the buffer
- * @return a pointer to the read string, or NULL if an error occurred
+ * @return the size of the stream, or -1 if an error occurred
+ */
+ virtual int32 size() const = 0;
+
+ /**
+ * Sets the stream position indicator for the stream. The new position,
+ * measured in bytes, is obtained by adding offset bytes to the position
+ * specified by whence. If whence is set to SEEK_SET, SEEK_CUR, or
+ * SEEK_END, the offset is relative to the start of the file, the current
+ * position indicator, or end-of-file, respectively. A successful call
+ * to the seek() method clears the end-of-file indicator for the stream.
*
- * @note The line terminator (CR or CR/LF) is stripped and not inserted
- * into the buffer.
+ * @param offset the relative offset in bytes
+ * @param whence the seek reference: SEEK_SET, SEEK_CUR, or SEEK_END
+ * @return true on success, false in case of a failure
+ */
+ virtual bool seek(int32 offset, int whence = SEEK_SET) = 0;
+
+ /**
+ * TODO: Get rid of this??? Or keep it and document it
+ * @return true on success, false in case of a failure
+ */
+ virtual bool skip(uint32 offset) { return seek(offset, SEEK_CUR); }
+
+ /**
+ * DEPRECATED: Do not use this method! Instead use readLine_NEW() or readline().
*/
- virtual char *readLine(char *buf, size_t bufSize);
+ virtual char *readLine_OLD(char *buf, size_t bufSize);
/**
* Reads at most one less than the number of characters specified
* by bufSize from the and stores them in the string buf. Reading
- * stops when the end of a line is reached (CR, CR/LF or LF), at
- * end-of-file or error. The newline, if any, is retained (CR and
- * CR/LF are translated to LF = 0xA = '\n'). If any characters are
- * read and there is no error, a `\0' character is appended to end
- * the string.
+ * stops when the end of a line is reached (CR, CR/LF or LF), and
+ * at end-of-file or error. The newline, if any, is retained (CR
+ * and CR/LF are translated to LF = 0xA = '\n'). If any characters
+ * are read and there is no error, a `\0' character is appended
+ * to end the string.
*
* Upon successful completion, return a pointer to the string. If
* end-of-file occurs before any characters are read, returns NULL
* and the buffer contents remain unchanged. If an error occurs,
* returns NULL and the buffer contents are indeterminate.
* This method does not distinguish between end-of-file and error;
- * callers muse use ioFailed() or eos() to determine which occurred.
+ * callers must use err() or eos() to determine which occurred.
+ *
+ * @note This methods is closely modeled after the standard fgets()
+ * function from stdio.h.
*
* @param buf the buffer to store into
* @param bufSize the size of the buffer
* @return a pointer to the read string, or NULL if an error occurred
*/
virtual char *readLine_NEW(char *s, size_t bufSize);
+
+
+ /**
+ * Reads a full line and returns it as a Common::String. Reading
+ * stops when the end of a line is reached (CR, CR/LF or LF), and
+ * at end-of-file or error.
+ *
+ * Upon successful completion, return a string with the content
+ * of the line, *without* the end of a line marker. This method
+ * does not indicate whether an error occured. Callers muse use
+ * ioFailed() or eos() to determine whether an exception occurred.
+ */
+ virtual String readLine();
};
/**
@@ -369,19 +424,23 @@ protected:
bool _disposeParentStream;
uint32 _pos;
uint32 _end;
+ bool _eos;
public:
SubReadStream(ReadStream *parentStream, uint32 end, bool disposeParentStream = false)
: _parentStream(parentStream),
_disposeParentStream(disposeParentStream),
_pos(0),
- _end(end) {
+ _end(end),
+ _eos(false) {
assert(parentStream);
}
~SubReadStream() {
if (_disposeParentStream) delete _parentStream;
}
- virtual bool eos() const { return _pos == _end; }
+ virtual bool eos() const { return _eos; }
+ virtual bool err() const { return _parentStream->err(); }
+ virtual void clearErr() { _eos = false; _parentStream->clearErr(); }
virtual uint32 read(void *dataPtr, uint32 dataSize);
};
@@ -397,10 +456,10 @@ protected:
public:
SeekableSubReadStream(SeekableReadStream *parentStream, uint32 begin, uint32 end, bool disposeParentStream = false);
- virtual uint32 pos() const { return _pos - _begin; }
- virtual uint32 size() const { return _end - _begin; }
+ virtual int32 pos() const { return _pos - _begin; }
+ virtual int32 size() const { return _end - _begin; }
- virtual void seek(int32 offset, int whence = SEEK_SET);
+ virtual bool seek(int32 offset, int whence = SEEK_SET);
};
/**
@@ -453,6 +512,8 @@ public:
virtual bool eos() const { return (_pos == _bufSize) && _parentStream->eos(); }
virtual bool ioFailed() const { return _parentStream->ioFailed(); }
virtual void clearIOFailed() { _parentStream->clearIOFailed(); }
+ virtual bool err() const { return _parentStream->err(); }
+ virtual void clearErr() { _parentStream->clearErr(); }
virtual uint32 read(void *dataPtr, uint32 dataSize);
};
@@ -467,10 +528,10 @@ protected:
public:
BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false);
- virtual uint32 pos() const { return _parentStream->pos() - (_bufSize - _pos); }
- virtual uint32 size() const { return _parentStream->size(); }
+ virtual int32 pos() const { return _parentStream->pos() - (_bufSize - _pos); }
+ virtual int32 size() const { return _parentStream->size(); }
- virtual void seek(int32 offset, int whence = SEEK_SET);
+ virtual bool seek(int32 offset, int whence = SEEK_SET);
};
@@ -487,6 +548,7 @@ private:
uint32 _pos;
byte _encbyte;
bool _disposeMemory;
+ bool _eos;
public:
@@ -501,7 +563,8 @@ public:
_size(dataSize),
_pos(0),
_encbyte(0),
- _disposeMemory(disposeMemory) {}
+ _disposeMemory(disposeMemory),
+ _eos(false) {}
~MemoryReadStream() {
if (_disposeMemory)
@@ -512,11 +575,13 @@ public:
uint32 read(void *dataPtr, uint32 dataSize);
- bool eos() const { return _pos == _size; }
- uint32 pos() const { return _pos; }
- uint32 size() const { return _size; }
+ bool eos() const { return _eos; }
+ void clearErr() { _eos = false; }
+
+ int32 pos() const { return _pos; }
+ int32 size() const { return _size; }
- void seek(int32 offs, int whence = SEEK_SET);
+ bool seek(int32 offs, int whence = SEEK_SET);
};
@@ -569,7 +634,6 @@ public:
return dataSize;
}
- bool eos() const { return _pos == _bufSize; }
uint32 pos() const { return _pos; }
uint32 size() const { return _bufSize; }
};
@@ -623,7 +687,6 @@ public:
return dataSize;
}
- bool eos() const { return false; }
uint32 pos() const { return _pos; }
uint32 size() const { return _size; }
diff --git a/common/system.cpp b/common/system.cpp
index d0548cdd2d..d9bc027e91 100644
--- a/common/system.cpp
+++ b/common/system.cpp
@@ -115,7 +115,8 @@ Common::EventManager *OSystem::getEventManager() {
void OSystem::clearScreen() {
Graphics::Surface *screen = lockScreen();
- memset(screen->pixels, 0, screen->h * screen->pitch);
+ if (screen && screen->pixels)
+ memset(screen->pixels, 0, screen->h * screen->pitch);
unlockScreen();
}
@@ -125,10 +126,9 @@ FIXME: The config file loading code below needs to be cleaned up.
Port specific variants should be pushed into the respective ports.
Ideally, the default OSystem::openConfigFileForReading/Writing methods
- should be removed completely.
+ should be removed completely.
*/
-#include "common/file.h"
#ifdef __PLAYSTATION2__
#include "backends/platform/ps2/systemps2.h"
@@ -163,7 +163,7 @@ static Common::String getDefaultConfigFileName() {
}
Common::SeekableReadStream *OSystem::openConfigFileForReading() {
- FilesystemNode file(getDefaultConfigFileName());
+ Common::FilesystemNode file(getDefaultConfigFileName());
return file.openForReading();
}
@@ -171,7 +171,7 @@ Common::WriteStream *OSystem::openConfigFileForWriting() {
#ifdef __DC__
return 0;
#else
- FilesystemNode file(getDefaultConfigFileName());
+ Common::FilesystemNode file(getDefaultConfigFileName());
return file.openForWriting();
#endif
}
diff --git a/common/system.h b/common/system.h
index 501d0802fd..cb9dbedad7 100644
--- a/common/system.h
+++ b/common/system.h
@@ -43,6 +43,7 @@ namespace Common {
struct Event;
class EventManager;
class SaveFileManager;
+ class SearchSet;
class TimerManager;
class SeekableReadStream;
class WriteStream;
@@ -907,6 +908,18 @@ public:
virtual FilesystemFactory *getFilesystemFactory() = 0;
/**
+ * Add system specific Common::Archive objects to the given SearchSet.
+ * E.g. on Unix the dir corresponding to DATA_PATH (if set), or on
+ * Mac OS X the 'Resource' dir in the app bundle.
+ *
+ * @todo Come up with a better name. This one sucks.
+ *
+ * @param s the SearchSet to which the system specific dirs, if any, are added
+ * @param priority the priority with which those dirs are added
+ */
+ virtual void addSysArchivesToSearchSet(Common::SearchSet &s, uint priority = 0) {}
+
+ /**
* Open the default config file for reading, by returning a suitable
* ReadStream instance. It is the callers responsiblity to delete
* the stream after use.
diff --git a/common/unarj.cpp b/common/unarj.cpp
index 9a7766a41f..244a296efb 100644
--- a/common/unarj.cpp
+++ b/common/unarj.cpp
@@ -23,28 +23,9 @@
*
*/
-// Heavily based on Unarj 2.65
-
-/* UNARJ.C, UNARJ, R JUNG, 06/05/02
- * Main Extractor routine
- * Copyright (c) 1991-2002 by ARJ Software, Inc. All rights reserved.
- *
- * This code may be freely used in programs that are NOT ARJ archivers
- * (both compress and extract ARJ archives).
- *
- * If you wish to distribute a modified version of this program, you
- * MUST indicate that it is a modified version both in the program and
- * source code.
- *
- * We are holding the copyright on the source code, so please do not
- * delete our name from the program files or from the documentation.
- *
- * We wish to give credit to Haruhiko Okumura for providing the
- * basic ideas for ARJ and UNARJ in his program AR. Please note
- * that UNARJ is significantly different from AR from an archive
- * structural point of view.
- *
- */
+//
+// This file is heavily based on the arj code available under the GPL
+// from http://arj.sourceforge.net/ , version 3.10.22 .
#include "common/scummsys.h"
#include "common/util.h"
@@ -52,18 +33,38 @@
namespace Common {
+#define HEADER_ID 0xEA60
+#define HEADER_ID_HI 0xEA
+#define HEADER_ID_LO 0x60
+
+#define FIRST_HDR_SIZE 30
+#define HEADERSIZE_MAX (FIRST_HDR_SIZE + 10 + ARJ_FILENAME_MAX + ARJ_COMMENT_MAX)
+#define CRC_MASK 0xFFFFFFFFL
+#define HSLIMIT_ARJ 524288L
+
+#define CBIT 9
+#define PBIT 5
+#define TBIT 5
+
+//
+// Source for InitCRC, GetCRC: crc32.c
+//
+
static uint32 CRCtable[256];
static void InitCRC(void) {
const uint32 poly = 0xEDB88320;
int i, j;
- uint32 n;
+ uint32 r;
for (i = 0; i < 256; i++) {
- n = i;
+ r = i;
for (j = 0; j < 8; j++)
- n = (n & 1) ? ((n >> 1) ^ poly) : (n >> 1);
- CRCtable[i] = n;
+ if (r & 1)
+ r = (r >> 1) ^ poly;
+ else
+ r >>= 1;
+ CRCtable[i] = r;
}
}
@@ -124,41 +125,47 @@ void ArjFile::registerArchive(const String &filename) {
debug(0, "ArjFile::registerArchive(%s): Located %d files", filename.c_str(), _headers.size());
}
+//
+// Source for findHeader and readHeader: arj_arcv.c
+//
+
int32 ArjFile::findHeader(void) {
- long arcpos, lastpos;
- int c;
+ long end_pos, tmp_pos;
+ int id;
byte header[HEADERSIZE_MAX];
uint32 crc;
- uint16 headersize;
+ uint16 basic_hdr_size;
- arcpos = _currArchive.pos();
+ tmp_pos = _currArchive.pos();
_currArchive.seek(0L, SEEK_END);
- lastpos = _currArchive.pos() - 2;
- if (lastpos > MAXSFX)
- lastpos = MAXSFX;
-
- for ( ; arcpos < lastpos; arcpos++) {
- _currArchive.seek(arcpos, SEEK_SET);
- c = _currArchive.readByte();
- while (arcpos < lastpos) {
- if (c != HEADER_ID_LO) // low order first
- c = _currArchive.readByte();
- else if ((c = _currArchive.readByte()) == HEADER_ID_HI)
- break;
- arcpos++;
+ end_pos = _currArchive.pos() - 2;
+ if (end_pos >= tmp_pos + HSLIMIT_ARJ)
+ end_pos = tmp_pos + HSLIMIT_ARJ;
+
+ while (tmp_pos < end_pos) {
+ _currArchive.seek(tmp_pos, SEEK_SET);
+ id = _currArchive.readByte();
+ while (tmp_pos < end_pos) {
+ if (id == HEADER_ID_LO)
+ if ((id = _currArchive.readByte()) == HEADER_ID_HI)
+ break;
+ else
+ id = _currArchive.readByte();
+ tmp_pos++;
}
- if (arcpos >= lastpos)
- break;
- if ((headersize = _currArchive.readUint16LE()) <= HEADERSIZE_MAX) {
- _currArchive.read(header, headersize);
- crc = GetCRC(header, headersize);
+ if (tmp_pos >= end_pos)
+ return -1;
+ if ((basic_hdr_size = _currArchive.readUint16LE()) <= HEADERSIZE_MAX) {
+ _currArchive.read(header, basic_hdr_size);
+ crc = GetCRC(header, basic_hdr_size);
if (crc == _currArchive.readUint32LE()) {
- _currArchive.seek(arcpos, SEEK_SET);
- return arcpos;
+ _currArchive.seek(tmp_pos, SEEK_SET);
+ return tmp_pos;
}
}
+ tmp_pos++;
}
- return -1; // could not find a valid header
+ return -1;
}
ArjHeader *ArjFile::readHeader() {
@@ -166,6 +173,7 @@ ArjHeader *ArjFile::readHeader() {
ArjHeader *head;
byte headData[HEADERSIZE_MAX];
+ // Strictly check the header ID
header.id = _currArchive.readUint16LE();
if (header.id != HEADER_ID) {
warning("ArjFile::readHeader(): Bad header ID (%x)", header.id);
@@ -199,7 +207,7 @@ ArjHeader *ArjFile::readHeader() {
header.flags = readS.readByte();
header.method = readS.readByte();
header.fileType = readS.readByte();
- (void)readS.readByte();
+ (void)readS.readByte(); // password_modifier
header.timeStamp = readS.readUint32LE();
header.compSize = readS.readSint32LE();
header.origSize = readS.readSint32LE();
@@ -208,20 +216,20 @@ ArjHeader *ArjFile::readHeader() {
header.fileMode = readS.readUint16LE();
header.hostData = readS.readUint16LE();
+ // static int check_file_size()
if (header.origSize < 0 || header.compSize < 0) {
warning("ArjFile::readHeader(): Wrong file size");
return NULL;
}
- strncpy(header.filename, (const char *)&headData[header.firstHdrSize], FNAME_MAX);
+ strncpy(header.filename, (const char *)&headData[header.firstHdrSize], ARJ_FILENAME_MAX);
- strncpy(header.comment, (const char *)&headData[header.firstHdrSize + strlen(header.filename) + 1], COMMENT_MAX);
+ strncpy(header.comment, (const char *)&headData[header.firstHdrSize + strlen(header.filename) + 1], ARJ_COMMENT_MAX);
- /* if extheadersize == 0 then no CRC */
- /* otherwise read extheader data and read 4 bytes for CRC */
-
- while ((header.extHeaderSize = _currArchive.readUint16LE()) != 0)
- _currArchive.seek((long)(header.extHeaderSize + 4), SEEK_CUR);
+ // Process extended headers, if any
+ uint16 extHeaderSize;
+ while ((extHeaderSize = _currArchive.readUint16LE()) != 0)
+ _currArchive.seek((long)(extHeaderSize + 4), SEEK_CUR);
header.pos = _currArchive.pos();
@@ -320,53 +328,70 @@ bool ArjFile::eos() {
return _uncompressed->eos();
}
-uint32 ArjFile::pos() {
+int32 ArjFile::pos() {
return _uncompressed->pos();
}
-uint32 ArjFile::size() {
+int32 ArjFile::size() {
return _uncompressed->size();
}
-void ArjFile::seek(int32 offset, int whence) {
- _uncompressed->seek(offset, whence);
+bool ArjFile::seek(int32 offset, int whence) {
+ return _uncompressed->seek(offset, whence);
}
+//
+// Source for init_getbits: arj_file.c (decode_start_stub)
+//
+
void ArjFile::init_getbits() {
_bitbuf = 0;
- _subbitbuf = 0;
+ _bytebuf = 0;
_bitcount = 0;
- fillbuf(2 * CHAR_BIT);
+ fillbuf(ARJ_CHAR_BIT * 2);
}
-void ArjFile::fillbuf(int n) { // Shift bitbuf n bits left, read n bits
- _bitbuf = (_bitbuf << n) & 0xFFFF; /* lose the first n bits */
- while (n > _bitcount) {
- _bitbuf |= _subbitbuf << (n -= _bitcount);
- if (_compsize != 0) {
+//
+// Source for fillbuf, getbits: decode.c
+//
+
+void ArjFile::fillbuf(int n) {
+ while (_bitcount < n) {
+ _bitbuf = (_bitbuf << _bitcount) | (_bytebuf >> (8 - _bitcount));
+ n -= _bitcount;
+ if (_compsize > 0) {
_compsize--;
- _subbitbuf = _compressed->readByte();
- } else
- _subbitbuf = 0;
- _bitcount = CHAR_BIT;
+ _bytebuf = _compressed->readByte();
+ } else {
+ _bytebuf = 0;
+ }
+ _bitcount = 8;
}
- _bitbuf |= _subbitbuf >> (_bitcount -= n);
+ _bitcount -= n;
+ _bitbuf = ( _bitbuf << n) | (_bytebuf >> (8-n));
+ _bytebuf <<= n;
}
+// Reads a series of bits into the input buffer */
uint16 ArjFile::getbits(int n) {
- uint16 x;
+ uint16 rc;
- x = _bitbuf >> (2 * CHAR_BIT - n);
+ rc = _bitbuf >> (ARJ_CODE_BIT - n);
fillbuf(n);
- return x;
+ return rc;
}
-/* Huffman decode routines */
+//
+// Huffman decode routines
+// Source: decode.c
+//
+// Creates a table for decoding
void ArjFile::make_table(int nchar, byte *bitlen, int tablebits, uint16 *table, int tablesize) {
- uint16 count[17], weight[17], start[18], *p;
+ uint16 count[17], weight[17], start[18];
+ uint16 *p;
uint i, k, len, ch, jutbits, avail, nextcode, mask;
for (i = 1; i <= 16; i++)
@@ -415,7 +440,8 @@ void ArjFile::make_table(int nchar, byte *bitlen, int tablebits, uint16 *table,
while (i != 0) {
if (*p == 0) {
_right[avail] = _left[avail] = 0;
- *p = avail++;
+ *p = avail;
+ avail++;
}
if (k & mask)
p = &_right[*p];
@@ -430,6 +456,7 @@ void ArjFile::make_table(int nchar, byte *bitlen, int tablebits, uint16 *table,
}
}
+// Reads length of data pending
void ArjFile::read_pt_len(int nn, int nbit, int i_special) {
int i, n;
int16 c;
@@ -445,9 +472,9 @@ void ArjFile::read_pt_len(int nn, int nbit, int i_special) {
} else {
i = 0;
while (i < n) {
- c = _bitbuf >> (13);
+ c = _bitbuf >> 13;
if (c == 7) {
- mask = 1 << (12);
+ mask = 1 << 12;
while (mask & _bitbuf) {
mask >>= 1;
c++;
@@ -463,10 +490,11 @@ void ArjFile::read_pt_len(int nn, int nbit, int i_special) {
}
while (i < nn)
_pt_len[i++] = 0;
- make_table(nn, _pt_len, 8, _pt_table, PTABLESIZE); // replaced sizeof
+ make_table(nn, _pt_len, 8, _pt_table, ARJ_PTABLESIZE);
}
}
+// Reads a character table
void ArjFile::read_c_len() {
int16 i, c, n;
uint16 mask;
@@ -474,82 +502,87 @@ void ArjFile::read_c_len() {
n = getbits(CBIT);
if (n == 0) {
c = getbits(CBIT);
- for (i = 0; i < NC; i++)
+ for (i = 0; i < ARJ_NC; i++)
_c_len[i] = 0;
- for (i = 0; i < CTABLESIZE; i++)
+ for (i = 0; i < ARJ_CTABLESIZE; i++)
_c_table[i] = c;
} else {
i = 0;
while (i < n) {
c = _pt_table[_bitbuf >> (8)];
- if (c >= NT) {
- mask = 1 << (7);
+ if (c >= ARJ_NT) {
+ mask = 1 << 7;
do {
if (_bitbuf & mask)
c = _right[c];
else
c = _left[c];
mask >>= 1;
- } while (c >= NT);
+ } while (c >= ARJ_NT);
}
fillbuf((int)(_pt_len[c]));
if (c <= 2) {
if (c == 0)
c = 1;
- else if (c == 1)
- c = getbits(4) + 3;
- else
- c = getbits(CBIT) + 20;
+ else if (c == 1) {
+ c = getbits(4);
+ c += 3;
+ } else {
+ c = getbits(CBIT);
+ c += 20;
+ }
while (--c >= 0)
_c_len[i++] = 0;
}
else
_c_len[i++] = (byte)(c - 2);
}
- while (i < NC)
+ while (i < ARJ_NC)
_c_len[i++] = 0;
- make_table(NC, _c_len, 12, _c_table, CTABLESIZE); // replaced sizeof
+ make_table(ARJ_NC, _c_len, 12, _c_table, ARJ_CTABLESIZE);
}
}
+// Decodes a single character
uint16 ArjFile::decode_c() {
uint16 j, mask;
if (_blocksize == 0) {
- _blocksize = getbits(16);
- read_pt_len(NT, TBIT, 3);
+ _blocksize = getbits(ARJ_CODE_BIT);
+ read_pt_len(ARJ_NT, TBIT, 3);
read_c_len();
- read_pt_len(NP, PBIT, -1);
+ read_pt_len(ARJ_NP, PBIT, -1);
}
_blocksize--;
j = _c_table[_bitbuf >> 4];
- if (j >= NC) {
- mask = 1 << (3);
+ if (j >= ARJ_NC) {
+ mask = 1 << 3;
do {
if (_bitbuf & mask)
j = _right[j];
else
j = _left[j];
mask >>= 1;
- } while (j >= NC);
+ } while (j >= ARJ_NC);
}
fillbuf((int)(_c_len[j]));
return j;
}
+// Decodes a control character
uint16 ArjFile::decode_p() {
uint16 j, mask;
- j = _pt_table[_bitbuf >> (8)];
- if (j >= NP) {
- mask = 1 << (7);
+ j = _pt_table[_bitbuf >> 8];
+ if (j >= ARJ_NP) {
+ mask = 1 << 7;
do {
if (_bitbuf & mask)
j = _right[j];
else
j = _left[j];
mask >>= 1;
- } while (j >= NP);
+ } while (j >= ARJ_NP);
}
fillbuf((int)(_pt_len[j]));
if (j != 0) {
@@ -559,63 +592,59 @@ uint16 ArjFile::decode_p() {
return j;
}
+// Initializes memory for decoding
void ArjFile::decode_start() {
_blocksize = 0;
init_getbits();
}
+// Decodes the entire file
void ArjFile::decode() {
int16 i;
- int16 j;
- int16 c;
int16 r;
+ int16 c;
+ int16 j;
int32 count;
decode_start();
- count = 0;
+ count = _origsize;
r = 0;
- while (count < _origsize) {
+ while (count > 0) {
if ((c = decode_c()) <= ARJ_UCHAR_MAX) {
- _text[r] = (byte) c;
- count++;
- if (++r >= DDICSIZ) {
+ _ntext[r] = (byte) c;
+ count--;
+ if (++r >= ARJ_DICSIZ) {
r = 0;
- _outstream->write(_text, DDICSIZ);
+ _outstream->write(_ntext, ARJ_DICSIZ);
}
} else {
- j = c - (ARJ_UCHAR_MAX + 1 - THRESHOLD);
- count += j;
- i = decode_p();
- if ((i = r - i - 1) < 0)
- i += DDICSIZ;
- if (r > i && r < DDICSIZ - MAXMATCH - 1) {
+ j = c - (ARJ_UCHAR_MAX + 1 - ARJ_THRESHOLD);
+ count -= j;
+ i = r - decode_p() - 1;
+ if (i < 0)
+ i += ARJ_DICSIZ;
+ if (r > i && r < ARJ_DICSIZ - ARJ_MAXMATCH - 1) {
while (--j >= 0)
- _text[r++] = _text[i++];
+ _ntext[r++] = _ntext[i++];
} else {
while (--j >= 0) {
- _text[r] = _text[i];
- if (++r >= DDICSIZ) {
+ _ntext[r] = _ntext[i];
+ if (++r >= ARJ_DICSIZ) {
r = 0;
- _outstream->write(_text, DDICSIZ);
+ _outstream->write(_ntext, ARJ_DICSIZ);
}
- if (++i >= DDICSIZ)
+ if (++i >= ARJ_DICSIZ)
i = 0;
}
}
}
}
- if (r != 0)
- _outstream->write(_text, r);
+ if (r > 0)
+ _outstream->write(_ntext, r);
}
-/* Macros */
-
-#define BFIL {_getbuf|=_bitbuf>>_getlen;fillbuf(CODE_BIT-_getlen);_getlen=CODE_BIT;}
-#define GETBIT(c) {if(_getlen<=0)BFIL c=(_getbuf&0x8000)!=0;_getbuf<<=1;_getlen--;}
-#define BPUL(l) {_getbuf<<=l;_getlen-=l;}
-#define GETBITS(c,l) {if(_getlen<l)BFIL c=(uint16)_getbuf>>(CODE_BIT-l);BPUL(l)}
-
+// Backward pointer decoding
int16 ArjFile::decode_ptr() {
int16 c = 0;
int16 width;
@@ -623,20 +652,21 @@ int16 ArjFile::decode_ptr() {
int16 pwr;
plus = 0;
- pwr = 1 << (STRTP);
- for (width = (STRTP); width < (STOPP); width++) {
- GETBIT(c);
+ pwr = 1 << 9;
+ for (width = 9; width < 13; width++) {
+ c = getbits(1);
if (c == 0)
break;
plus += pwr;
pwr <<= 1;
}
if (width != 0)
- GETBITS(c, width);
+ c = getbits(width);
c += plus;
return c;
}
+// Reference length decoding
int16 ArjFile::decode_len() {
int16 c = 0;
int16 width;
@@ -644,62 +674,60 @@ int16 ArjFile::decode_len() {
int16 pwr;
plus = 0;
- pwr = 1 << (STRTL);
- for (width = (STRTL); width < (STOPL); width++) {
- GETBIT(c);
+ pwr = 1;
+ for (width = 0; width < 7; width++) {
+ c = getbits(1);
if (c == 0)
break;
plus += pwr;
pwr <<= 1;
}
if (width != 0)
- GETBITS(c, width);
+ c = getbits(width);
c += plus;
return c;
}
+// Decodes the entire file, using method 4
void ArjFile::decode_f() {
int16 i;
int16 j;
int16 c;
int16 r;
- int16 pos1;
- int32 count;
+ uint32 ncount;
init_getbits();
+ ncount = 0;
_getlen = _getbuf = 0;
- count = 0;
r = 0;
- while (count < _origsize) {
+ while (ncount < (uint32)_origsize) {
c = decode_len();
if (c == 0) {
- GETBITS(c, CHAR_BIT);
- _text[r] = (byte)c;
- count++;
- if (++r >= DDICSIZ) {
+ ncount++;
+ _ntext[r] = (byte)getbits(8);
+ if (++r >= ARJ_FDICSIZ) {
r = 0;
- _outstream->write(_text, DDICSIZ);
+ _outstream->write(_ntext, ARJ_FDICSIZ);
}
} else {
- j = c - 1 + THRESHOLD;
- count += j;
- pos1 = decode_ptr();
- if ((i = r - pos1 - 1) < 0)
- i += DDICSIZ;
+ j = c - 1 + ARJ_THRESHOLD;
+ ncount += j;
+ if ((i = r - decode_ptr() - 1) < 0)
+ i += ARJ_FDICSIZ;
while (j-- > 0) {
- _text[r] = _text[i];
- if (++r >= DDICSIZ) {
+ _ntext[r] = _ntext[i];
+ if (++r >= ARJ_FDICSIZ) {
r = 0;
- _outstream->write(_text, DDICSIZ);
+ _outstream->write(_ntext, ARJ_FDICSIZ);
}
- if (++i >= DDICSIZ)
+ if (++i >= ARJ_FDICSIZ)
i = 0;
}
}
}
if (r != 0)
- _outstream->write(_text, r);
+ _outstream->write(_ntext, r);
}
diff --git a/common/unarj.h b/common/unarj.h
index c8965968f6..52e0d13948 100644
--- a/common/unarj.h
+++ b/common/unarj.h
@@ -31,46 +31,31 @@
namespace Common {
-#define HEADER_ID 0xEA60
-#define HEADER_ID_HI 0xEA
-#define HEADER_ID_LO 0x60
-#define FIRST_HDR_SIZE 30
-#define FIRST_HDR_SIZE_V 34
-#define COMMENT_MAX 2048
-#define FNAME_MAX 512
-#define HEADERSIZE_MAX (FIRST_HDR_SIZE + 10 + FNAME_MAX + COMMENT_MAX)
-#define CRC_MASK 0xFFFFFFFFL
-#define MAXSFX 25000L
-
-#define CODE_BIT 16
-#define CHAR_BIT 8
-#define ARJ_UCHAR_MAX 255 // UCHAR_MAX is defined in limits.h in MSVC
-#define THRESHOLD 3
-#define DDICSIZ 26624
-#define MAXDICBIT 16
-#define MATCHBIT 8
-#define MAXMATCH 256
-#define NC (ARJ_UCHAR_MAX + MAXMATCH + 2 - THRESHOLD)
-#define NP (MAXDICBIT + 1)
-#define CBIT 9
-#define NT (CODE_BIT + 3)
-#define PBIT 5
-#define TBIT 5
-
-#if NT > NP
-#define NPT NT
+#define ARJ_UCHAR_MAX 255
+#define ARJ_CHAR_BIT 8
+
+#define ARJ_COMMENT_MAX 2048
+#define ARJ_FILENAME_MAX 512
+
+#define ARJ_CODE_BIT 16
+#define ARJ_THRESHOLD 3
+#define ARJ_DICSIZ 26624
+#define ARJ_FDICSIZ ARJ_DICSIZ
+#define ARJ_MAXDICBIT 16
+#define ARJ_MAXMATCH 256
+#define ARJ_NC (ARJ_UCHAR_MAX + ARJ_MAXMATCH + 2 - ARJ_THRESHOLD)
+#define ARJ_NP (ARJ_MAXDICBIT + 1)
+#define ARJ_NT (ARJ_CODE_BIT + 3)
+
+#if ARJ_NT > ARJ_NP
+#define ARJ_NPT ARJ_NT
#else
-#define NPT NP
+#define ARJ_NPT ARJ_NP
#endif
-#define CTABLESIZE 4096
-#define PTABLESIZE 256
+#define ARJ_CTABLESIZE 4096
+#define ARJ_PTABLESIZE 256
-#define STRTP 9
-#define STOPP 13
-
-#define STRTL 0
-#define STOPL 7
struct ArjHeader {
int32 pos;
@@ -92,9 +77,8 @@ struct ArjHeader {
uint16 entryPos;
uint16 fileMode;
uint16 hostData;
- char filename[FNAME_MAX];
- char comment[COMMENT_MAX];
- uint16 extHeaderSize;
+ char filename[ARJ_FILENAME_MAX];
+ char comment[ARJ_COMMENT_MAX];
uint32 headerCrc;
};
@@ -115,9 +99,9 @@ public:
uint32 read(void *dataPtr, uint32 dataSize);
bool eos();
- uint32 pos();
- uint32 size();
- void seek(int32 offset, int whence = SEEK_SET);
+ int32 pos();
+ int32 size();
+ bool seek(int32 offset, int whence = SEEK_SET);
bool isOpen() { return _isOpen; }
private:
@@ -143,6 +127,7 @@ private:
void decode_f();
uint16 _bitbuf;
+ uint16 _bytebuf;
int32 _compsize;
int32 _origsize;
byte _subbitbuf;
@@ -163,18 +148,18 @@ private:
int16 decode_len(void);
private:
- byte _text[DDICSIZ];
+ byte _ntext[ARJ_FDICSIZ];
int16 _getlen;
int16 _getbuf;
- uint16 _left[2 * NC - 1];
- uint16 _right[2 * NC - 1];
- byte _c_len[NC];
- byte _pt_len[NPT];
+ uint16 _left[2 * ARJ_NC - 1];
+ uint16 _right[2 * ARJ_NC - 1];
+ byte _c_len[ARJ_NC];
+ byte _pt_len[ARJ_NPT];
- uint16 _c_table[CTABLESIZE];
- uint16 _pt_table[PTABLESIZE];
+ uint16 _c_table[ARJ_CTABLESIZE];
+ uint16 _pt_table[ARJ_PTABLESIZE];
uint16 _blocksize;
diff --git a/common/unzip.h b/common/unzip.h
index 2d888fe5b1..43faaf4a74 100644
--- a/common/unzip.h
+++ b/common/unzip.h
@@ -65,9 +65,12 @@
#ifndef _unz_H
#define _unz_H
+#ifdef USE_ZLIB
+
#include "common/scummsys.h"
+#include "common/archive.h"
+#include "common/stream.h"
-#ifdef USE_ZLIB
#ifdef __cplusplus
extern "C" {
@@ -302,6 +305,49 @@ extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file,
}
#endif
-#endif
+
+class ZipArchive : Common::Archive {
+ unzFile _zipFile;
+
+public:
+ ZipArchive(const Common::String &name) {
+ _zipFile = unzOpen(name.c_str());
+ }
+ ~ZipArchive() {
+ unzClose(_zipFile);
+ }
+
+ virtual bool hasFile(const Common::String &name) {
+ return (_zipFile && unzLocateFile(_zipFile, name.c_str(), 2) == UNZ_OK);
+ }
+
+ virtual int getAllNames(Common::StringList &list) {
+ // TODO
+ return 0;
+ }
+
+ virtual Common::SeekableReadStream *openFile(const Common::String &name) {
+ if (!_zipFile)
+ return 0;
+
+ unzLocateFile(_zipFile, name.c_str(), 2);
+
+ unz_file_info fileInfo;
+ unzOpenCurrentFile(_zipFile);
+ unzGetCurrentFileInfo(_zipFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
+ byte *buffer = (byte *)calloc(fileInfo.uncompressed_size+1, 1);
+ assert(buffer);
+ unzReadCurrentFile(_zipFile, buffer, fileInfo.uncompressed_size);
+ unzCloseCurrentFile(_zipFile);
+ return new Common::MemoryReadStream(buffer, fileInfo.uncompressed_size+1, true);
+
+ // FIXME: instead of reading all into a memory stream, we could
+ // instead create a new ZipStream class. But then we have to be
+ // careful to handle the case where the client code opens multiple
+ // files in the archive and tries to use them indepenendtly.
+ }
+};
+
+#endif // USE_ZLIB
#endif /* _unz_H */
diff --git a/common/util.cpp b/common/util.cpp
index 6f0fdcb233..f68d253ec3 100644
--- a/common/util.cpp
+++ b/common/util.cpp
@@ -63,39 +63,6 @@ extern bool isSmartphone(void);
namespace Common {
-bool matchString(const char *str, const char *pat) {
- const char *p = 0;
- const char *q = 0;
-
- for (;;) {
- switch (*pat) {
- case '*':
- p = ++pat;
- q = str;
- break;
-
- default:
- if (*pat != *str) {
- if (p) {
- pat = p;
- str = ++q;
- if (!*str)
- return !*pat;
- break;
- }
- else
- return false;
- }
- // fallthrough
- case '?':
- if (!*str)
- return !*pat;
- pat++;
- str++;
- }
- }
-}
-
StringTokenizer::StringTokenizer(const String &str, const String &delimiters) : _str(str), _delimiters(delimiters) {
reset();
}
@@ -237,10 +204,9 @@ Language parseLanguage(const String &str) {
if (str.empty())
return UNK_LANG;
- const char *s = str.c_str();
const LanguageDescription *l = g_languages;
for (; l->code; ++l) {
- if (!scumm_stricmp(l->code, s))
+ if (str.equalsIgnoreCase(l->code))
return l->id;
}
@@ -278,6 +244,7 @@ const PlatformDescription g_platforms[] = {
{"c64", "c64", "c64", "Commodore 64", kPlatformC64},
{"pc", "dos", "ibm", "DOS", kPlatformPC},
{"pc98", "pc98", "pc98", "PC-98", kPlatformPC98},
+ {"wii", "wii", "wii", "Nintendo Wii", kPlatformWii},
// The 'official' spelling seems to be "FM-TOWNS" (e.g. in the Indy4 demo).
// However, on the net many variations can be seen, like "FMTOWNS",
@@ -299,20 +266,18 @@ Platform parsePlatform(const String &str) {
if (str.empty())
return kPlatformUnknown;
- const char *s = str.c_str();
-
// Handle some special case separately, for compatibility with old config
// files.
- if (!strcmp(s, "1"))
+ if (str == "1")
return kPlatformAmiga;
- else if (!strcmp(s, "2"))
+ else if (str == "2")
return kPlatformAtariST;
- else if (!strcmp(s, "3"))
+ else if (str == "3")
return kPlatformMacintosh;
const PlatformDescription *l = g_platforms;
for (; l->code; ++l) {
- if (!scumm_stricmp(l->code, s) || !scumm_stricmp(l->code2, s) || !scumm_stricmp(l->abbrev, s))
+ if (str.equalsIgnoreCase(l->code) || str.equalsIgnoreCase(l->code2) || str.equalsIgnoreCase(l->abbrev))
return l->id;
}
@@ -364,10 +329,9 @@ RenderMode parseRenderMode(const String &str) {
if (str.empty())
return kRenderDefault;
- const char *s = str.c_str();
const RenderModeDescription *l = g_renderModes;
for (; l->code; ++l) {
- if (!scumm_stricmp(l->code, s))
+ if (str.equalsIgnoreCase(l->code))
return l->id;
}
diff --git a/common/util.h b/common/util.h
index c23513596c..32f07181c4 100644
--- a/common/util.h
+++ b/common/util.h
@@ -53,28 +53,6 @@ template<typename T> inline void SWAP(T &a, T &b) { T tmp = a; a = b; b = tmp; }
namespace Common {
/**
- * Simple DOS-style pattern matching function (understands * and ? like used in DOS).
- * Taken from exult/files/listfiles.cc
- *
- * Token meaning:
- * "*": any character, any amount of times.
- * "?": any character, only once.
- *
- * Example strings/patterns:
- * String: monkey.s?? Pattern: monkey.s01 => true
- * String: monkey.s?? Pattern: monkey.s101 => false
- * String: monkey.s?1 Pattern: monkey.s99 => false
- * String: monkey.s* Pattern: monkey.s101 => true
- * String: monkey.s*1 Pattern: monkey.s99 => false
- *
- * @param str Text to be matched against the given pattern.
- * @param pat Glob pattern.
- *
- * @return true if str matches the pattern, false otherwise.
- */
-bool matchString(const char *str, const char *pat);
-
-/**
* A simple non-optimized string tokenizer.
*
* Example of use:
@@ -210,6 +188,7 @@ enum Platform {
kPlatformApple2GS,
kPlatformPC98,
+ kPlatformWii,
kPlatformUnknown = -1
};
diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp
index 44bf03336e..b93a5205be 100644
--- a/common/xmlparser.cpp
+++ b/common/xmlparser.cpp
@@ -61,7 +61,7 @@ bool XMLParser::parserError(const char *errorString, ...) {
char lineStr[70];
_stream->seek(original_pos - 35, SEEK_SET);
- _stream->readLine(lineStr, 70);
+ _stream->readLine_NEW(lineStr, 70);
for (int i = 0; i < 70; ++i)
if (lineStr[i] == '\n')
@@ -106,15 +106,16 @@ bool XMLParser::parseActiveKey(bool closed) {
key->layout = layout->children[key->name];
Common::StringMap localMap = key->values;
+ int keyCount = localMap.size();
for (Common::List<XMLKeyLayout::XMLKeyProperty>::const_iterator i = key->layout->properties.begin(); i != key->layout->properties.end(); ++i) {
- if (localMap.contains(i->name))
- localMap.erase(i->name);
- else if (i->required)
+ if (i->required && !localMap.contains(i->name))
return parserError("Missing required property '%s' inside key '%s'", i->name.c_str(), key->name.c_str());
+ else if (localMap.contains(i->name))
+ keyCount--;
}
- if (localMap.empty() == false)
+ if (keyCount > 0)
return parserError("Unhandled property inside key '%s': '%s'", key->name.c_str(), localMap.begin()->_key.c_str());
} else {
diff --git a/common/zlib.cpp b/common/zlib.cpp
index 7e14a9e3ab..4ed233b4b0 100644
--- a/common/zlib.cpp
+++ b/common/zlib.cpp
@@ -8,37 +8,327 @@
* 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/zlib.h"
+#include "common/util.h"
#if defined(USE_ZLIB)
+ #ifdef __SYMBIAN32__
+ #include <zlib\zlib.h>
+ #else
+ #include <zlib.h>
+ #endif
-#ifdef __SYMBIAN32__
-#include <zlib\zlib.h>
-#else
-#include <zlib.h>
+ #if ZLIB_VERNUM < 0x1204
+ #error Version 1.2.0.4 or newer of zlib is required for this code
+ #endif
#endif
+
namespace Common {
-int uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen) {
- return ::uncompress(dst, dstLen, src, srcLen);
+#if defined(USE_ZLIB)
+
+bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen) {
+ return Z_OK == ::uncompress(dst, dstLen, src, srcLen);
}
-} // end of namespace Common
+/**
+ * A simple wrapper class which can be used to wrap around an arbitrary
+ * other SeekableReadStream and will then provide on-the-fly decompression support.
+ * Assumes the compressed data to be in gzip format.
+ */
+class GZipReadStream : public Common::SeekableReadStream {
+protected:
+ enum {
+ BUFSIZE = 16384 // 1 << MAX_WBITS
+ };
+
+ byte _buf[BUFSIZE];
+
+ Common::SeekableReadStream *_wrapped;
+ z_stream _stream;
+ int _zlibErr;
+ uint32 _pos;
+ uint32 _origSize;
+ bool _eos;
+
+public:
+
+ GZipReadStream(Common::SeekableReadStream *w) : _wrapped(w) {
+ assert(w != 0);
+
+ _stream.zalloc = Z_NULL;
+ _stream.zfree = Z_NULL;
+ _stream.opaque = Z_NULL;
+
+ // Verify file header is correct
+ w->seek(0, SEEK_SET);
+ uint16 header = w->readUint16BE();
+ assert(header == 0x1F8B ||
+ ((header & 0x0F00) == 0x0800 && header % 31 == 0));
+
+ if (header == 0x1F8B) {
+ // Retrieve the original file size
+ w->seek(-4, SEEK_END);
+ _origSize = w->readUint32LE();
+ } else {
+ // Original size not available in zlib format
+ _origSize = 0;
+ }
+ _pos = 0;
+ w->seek(0, SEEK_SET);
+ _eos = false;
+
+ // Adding 32 to windowBits indicates to zlib that it is supposed to
+ // automatically detect whether gzip or zlib headers are used for
+ // the compressed file. This feature was added in zlib 1.2.0.4,
+ // released 10 August 2003.
+ // Note: This is *crucial* for savegame compatibility, do *not* remove!
+ _zlibErr = inflateInit2(&_stream, MAX_WBITS + 32);
+ if (_zlibErr != Z_OK)
+ return;
+
+ // Setup input buffer
+ _stream.next_in = _buf;
+ _stream.avail_in = 0;
+ }
+
+ ~GZipReadStream() {
+ inflateEnd(&_stream);
+ delete _wrapped;
+ }
+
+ bool err() const { return (_zlibErr != Z_OK) && (_zlibErr != Z_STREAM_END); }
+ void clearErr() {
+ // only reset _eos; I/O errors are not recoverable
+ _eos = false;
+ }
+
+ uint32 read(void *dataPtr, uint32 dataSize) {
+ _stream.next_out = (byte *)dataPtr;
+ _stream.avail_out = dataSize;
+
+ // Keep going while we get no error
+ while (_zlibErr == Z_OK && _stream.avail_out) {
+ if (_stream.avail_in == 0 && !_wrapped->eos()) {
+ // If we are out of input data: Read more data, if available.
+ _stream.next_in = _buf;
+ _stream.avail_in = _wrapped->read(_buf, BUFSIZE);
+ }
+ _zlibErr = inflate(&_stream, Z_NO_FLUSH);
+ }
+
+ // Update the position counter
+ _pos += dataSize - _stream.avail_out;
+
+ if (_zlibErr == Z_STREAM_END && _stream.avail_out > 0)
+ _eos = true;
+
+ return dataSize - _stream.avail_out;
+ }
+
+ bool eos() const {
+ return _eos;
+ }
+ int32 pos() const {
+ return _pos;
+ }
+ int32 size() const {
+ return _origSize;
+ }
+ bool seek(int32 offset, int whence = SEEK_SET) {
+ int32 newPos = 0;
+ assert(whence != SEEK_END); // SEEK_END not supported
+ switch(whence) {
+ case SEEK_SET:
+ newPos = offset;
+ break;
+ case SEEK_CUR:
+ newPos = _pos + offset;
+ }
+
+ assert(newPos >= 0);
+
+ if ((uint32)newPos < _pos) {
+ // To search backward, we have to restart the whole decompression
+ // from the start of the file. A rather wasteful operation, best
+ // to avoid it. :/
+#if DEBUG
+ warning("Backward seeking in GZipReadStream detected");
+#endif
+ _pos = 0;
+ _wrapped->seek(0, SEEK_SET);
+ _zlibErr = inflateReset(&_stream);
+ if (_zlibErr != Z_OK)
+ return false; // FIXME: STREAM REWRITE
+ _stream.next_in = _buf;
+ _stream.avail_in = 0;
+ }
+
+ offset = newPos - _pos;
+
+ // Skip the given amount of data (very inefficient if one tries to skip
+ // huge amounts of data, but usually client code will only skip a few
+ // bytes, so this should be fine.
+ byte tmpBuf[1024];
+ while (!err() && offset > 0) {
+ offset -= read(tmpBuf, MIN((int32)sizeof(tmpBuf), offset));
+ }
+
+ _eos = false;
+ return true; // FIXME: STREAM REWRITE
+ }
+};
+
+/**
+ * A simple wrapper class which can be used to wrap around an arbitrary
+ * other WriteStream and will then provide on-the-fly compression support.
+ * The compressed data is written in the gzip format.
+ */
+class GZipWriteStream : public Common::WriteStream {
+protected:
+ enum {
+ BUFSIZE = 16384 // 1 << MAX_WBITS
+ };
+
+ byte _buf[BUFSIZE];
+ Common::WriteStream *_wrapped;
+ z_stream _stream;
+ int _zlibErr;
+
+ void processData(int flushType) {
+ // This function is called by both write() and finalize().
+ while (_zlibErr == Z_OK && (_stream.avail_in || flushType == Z_FINISH)) {
+ if (_stream.avail_out == 0) {
+ if (_wrapped->write(_buf, BUFSIZE) != BUFSIZE) {
+ _zlibErr = Z_ERRNO;
+ break;
+ }
+ _stream.next_out = _buf;
+ _stream.avail_out = BUFSIZE;
+ }
+ _zlibErr = deflate(&_stream, flushType);
+ }
+ }
+
+public:
+ GZipWriteStream(Common::WriteStream *w) : _wrapped(w) {
+ assert(w != 0);
+ _stream.zalloc = Z_NULL;
+ _stream.zfree = Z_NULL;
+ _stream.opaque = Z_NULL;
+
+ // Adding 16 to windowBits indicates to zlib that it is supposed to
+ // write gzip headers. This feature was added in zlib 1.2.0.4,
+ // released 10 August 2003.
+ // Note: This is *crucial* for savegame compatibility, do *not* remove!
+ _zlibErr = deflateInit2(&_stream,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ MAX_WBITS + 16,
+ 8,
+ Z_DEFAULT_STRATEGY);
+ assert(_zlibErr == Z_OK);
+
+ _stream.next_out = _buf;
+ _stream.avail_out = BUFSIZE;
+ _stream.avail_in = 0;
+ _stream.next_in = 0;
+ }
+
+ ~GZipWriteStream() {
+ finalize();
+ deflateEnd(&_stream);
+ delete _wrapped;
+ }
+
+ bool err() const {
+ // CHECKME: does Z_STREAM_END make sense here?
+ return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->err();
+ }
+
+ void clearErr() {
+ // Note: we don't reset the _zlibErr here, as it is not
+ // clear in general how
+ _wrapped->clearErr();
+ }
+
+ void finalize() {
+ if (_zlibErr != Z_OK)
+ return;
+
+ // Process whatever remaining data there is.
+ processData(Z_FINISH);
+ // Since processData only writes out blocks of size BUFSIZE,
+ // we may have to flush some stragglers.
+ uint remainder = BUFSIZE - _stream.avail_out;
+ if (remainder > 0) {
+ if (_wrapped->write(_buf, remainder) != remainder) {
+ _zlibErr = Z_ERRNO;
+ }
+ }
+
+ // Finalize the wrapped savefile, too
+ _wrapped->finalize();
+ }
+
+ uint32 write(const void *dataPtr, uint32 dataSize) {
+ if (err())
+ return 0;
+
+ // Hook in the new data ...
+ // Note: We need to make a const_cast here, as zlib is not aware
+ // of the const keyword.
+ _stream.next_in = const_cast<byte *>((const byte *)dataPtr);
+ _stream.avail_in = dataSize;
+
+ // ... and flush it to disk
+ processData(Z_NO_FLUSH);
+
+ return dataSize - _stream.avail_in;
+ }
+};
+
+#endif // USE_ZLIB
+
+Common::SeekableReadStream *wrapCompressedReadStream(Common::SeekableReadStream *toBeWrapped) {
+#if defined(USE_ZLIB)
+ if (toBeWrapped) {
+ uint16 header = toBeWrapped->readUint16BE();
+ bool isCompressed = (header == 0x1F8B ||
+ ((header & 0x0F00) == 0x0800 &&
+ header % 31 == 0));
+ toBeWrapped->seek(-2, SEEK_CUR);
+ if (isCompressed)
+ return new GZipReadStream(toBeWrapped);
+ }
#endif
+ return toBeWrapped;
+}
+
+Common::WriteStream *wrapCompressedWriteStream(Common::WriteStream *toBeWrapped) {
+#if defined(USE_ZLIB)
+ if (toBeWrapped)
+ return new GZipWriteStream(toBeWrapped);
+#endif
+ return toBeWrapped;
+}
+
+} // End of namespace Common
diff --git a/common/zlib.h b/common/zlib.h
index 62e9f98c01..5fd13e842d 100644
--- a/common/zlib.h
+++ b/common/zlib.h
@@ -8,44 +8,65 @@
* 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/scummsys.h"
-
-#if defined(USE_ZLIB)
-
#ifndef COMMON_ZLIB_H
#define COMMON_ZLIB_H
-#ifdef __SYMBIAN32__
-#include <zlib\zlib.h>
-#else
-#include <zlib.h>
-#endif
+#include "common/scummsys.h"
+#include "common/stream.h"
namespace Common {
-enum {
- ZLIB_OK = Z_OK
-};
-
-int uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen);
+#if defined(USE_ZLIB)
-} // end of namespace Common
+/**
+ * Thin wrapper around zlib's uncompress() function. This wrapper makes
+ * it possible to uncompress data in engines without being forced to link
+ * them against zlib, thus simplifying the build system.
+ *
+ * @return true on success (i.e. Z_OK), false otherwise
+ */
+bool uncompress(byte *dst, unsigned long *dstLen, const byte *src, unsigned long srcLen);
#endif
-#endif
+/**
+ * Take an arbitrary SeekableReadStream and wrap it in a custom stream which
+ * provides transparent on-the-fly decompression. Assumes the data it
+ * retrieves from the wrapped stream to be either uncompressed or in gzip
+ * format. In the former case, the original stream is returned unmodified
+ * (and in particular, not wrapped).
+ *
+ * It is safe to call this with a NULL parameter (in this case, NULL is
+ * returned).
+ */
+Common::SeekableReadStream *wrapCompressedReadStream(Common::SeekableReadStream *toBeWrapped);
+/**
+ * Take an arbitrary WriteStream and wrap it in a custom stream which provides
+ * transparent on-the-fly compression. The compressed data is written in the
+ * gzip format, unless ZLIB support has been disabled, in which case the given
+ * stream is returned unmodified (and in particular, not wrapped).
+ *
+ * It is safe to call this with a NULL parameter (in this case, NULL is
+ * returned).
+ */
+Common::WriteStream *wrapCompressedWriteStream(Common::WriteStream *toBeWrapped);
+
+} // End of namespace Common
+
+#endif