aboutsummaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/advancedDetector.cpp230
-rw-r--r--common/advancedDetector.h7
-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.cpp376
-rw-r--r--common/config-manager.h12
-rw-r--r--common/events.h19
-rw-r--r--common/file.cpp435
-rw-r--r--common/file.h50
-rw-r--r--common/fs.cpp41
-rw-r--r--common/fs.h97
-rw-r--r--common/func.h119
-rw-r--r--common/hash-str.h4
-rw-r--r--common/hashmap.cpp116
-rw-r--r--common/hashmap.h282
-rw-r--r--common/iff_container.h16
-rw-r--r--common/keyboard.h2
-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.h51
-rw-r--r--common/savefile.h17
-rw-r--r--common/str.cpp265
-rw-r--r--common/str.h109
-rw-r--r--common/stream.cpp94
-rw-r--r--common/stream.h211
-rw-r--r--common/system.cpp62
-rw-r--r--common/system.h32
-rw-r--r--common/unarj.cpp355
-rw-r--r--common/unarj.h83
-rw-r--r--common/unzip.cpp537
-rw-r--r--common/unzip.h290
-rw-r--r--common/util.cpp50
-rw-r--r--common/util.h31
-rw-r--r--common/xmlparser.cpp71
-rw-r--r--common/xmlparser.h240
-rw-r--r--common/zlib.cpp308
-rw-r--r--common/zlib.h57
43 files changed, 3283 insertions, 2272 deletions
diff --git a/common/advancedDetector.cpp b/common/advancedDetector.cpp
index e328cf7787..1b0db4755a 100644
--- a/common/advancedDetector.cpp
+++ b/common/advancedDetector.cpp
@@ -34,7 +34,11 @@
namespace Common {
-using namespace AdvancedDetector;
+/**
+ * A list of pointers to ADGameDescription structs (or subclasses thereof).
+ */
+typedef Array<const ADGameDescription*> ADGameDescList;
+
/**
* Detect games in specified directory.
@@ -78,18 +82,20 @@ 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));
warning("Target upgraded from %s to %s", o->from, o->to);
+ // WORKAROUND: Fix for bug #1719463: "DETECTOR: Launching
+ // undefined target adds launcher entry"
if (ConfMan.hasKey("id_came_from_command_line")) {
warning("Target came from command line. Skipping save");
} else {
@@ -282,10 +288,10 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c
return kNoError;
}
-typedef HashMap<String, bool> StringSet;
-typedef HashMap<String, int32> IntMap;
+typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet;
+typedef HashMap<String, int32, IgnoreCase_Hash, IgnoreCase_EqualTo> IntMap;
-static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) {
+static void reportUnknown(const StringMap &filesMD5, const IntMap &filesSize) {
// TODO: This message should be cleaned up / made more specific.
// For example, we should specify at least which engine triggered this.
//
@@ -301,62 +307,64 @@ static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) {
printf("\n");
}
+static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams &params);
+
static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &params, Language language, Platform platform, const Common::String extra) {
- StringSet filesList;
+ StringMap allFiles;
+ StringSet detectFiles;
StringMap filesMD5;
IntMap filesSize;
- IntMap allFiles;
-
- File testFile;
-
- String tstr;
-
- uint i;
- char md5str[32+1];
- bool fileMissing;
const ADGameFileDescription *fileDesc;
const ADGameDescription *g;
const byte *descPtr;
debug(3, "Starting detection");
- // First we compose list of files which we need MD5s for
- for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) {
- g = (const ADGameDescription *)descPtr;
-
- for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
- tstr = String(fileDesc->fileName);
- tstr.toLowercase();
- filesList[tstr] = true;
- }
- }
-
- // Get the information of the existing files
+ // First we compose an efficient to query set of all files in fslist.
+ // Includes nifty stuff like removing trailing dots and ignoring case.
for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
- if (file->isDirectory()) continue;
- tstr = file->getName();
- tstr.toLowercase();
+ if (file->isDirectory())
+ continue;
+
+ String tstr = file->getName();
// Strip any trailing dot
if (tstr.lastChar() == '.')
tstr.deleteLastChar();
- allFiles[tstr] = true;
+ allFiles[tstr] = file->getPath(); // Record the presence of this file
+ }
- debug(3, "+ %s", tstr.c_str());
+ // Compute the set of files for which we need MD5s for. I.e. files which are
+ // included in some ADGameDescription *and* present in fslist.
+ for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) {
+ g = (const ADGameDescription *)descPtr;
- if (!filesList.contains(tstr)) continue;
+ for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
+ String tstr = fileDesc->fileName;
+ if (allFiles.contains(tstr))
+ detectFiles[tstr] = true;
+ }
+ }
+
+ // Get the information for all detection files, if they exist
+ for (StringSet::const_iterator file = detectFiles.begin(); file != detectFiles.end(); ++file) {
+ String fname = file->_key;
- if (!md5_file_string(*file, md5str, params.md5Bytes))
+ debug(3, "+ %s", fname.c_str());
+
+ char md5str[32+1];
+ if (!md5_file_string(allFiles[fname].c_str(), md5str, params.md5Bytes))
continue;
- filesMD5[tstr] = md5str;
+ filesMD5[fname] = md5str;
- debug(3, "> %s: %s", tstr.c_str(), md5str);
+ debug(3, "> %s: %s", fname.c_str(), md5str);
- if (testFile.open(file->getPath())) {
- filesSize[tstr] = (int32)testFile.size();
+ File testFile;
+ if (testFile.open(allFiles[fname])) {
+ filesSize[fname] = (int32)testFile.size();
testFile.close();
}
}
@@ -366,9 +374,10 @@ static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &p
int maxFilesMatched = 0;
// MD5 based matching
+ uint i;
for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize, ++i) {
g = (const ADGameDescription *)descPtr;
- fileMissing = false;
+ bool fileMissing = false;
// Do not even bother to look at entries which do not have matching
// language and platform (if specified).
@@ -377,32 +386,28 @@ static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &p
continue;
}
- if ((params.flags & kADFlagUseExtraAsHint) && extra != "" && g->extra != extra)
+ if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra)
continue;
// Try to match all files for this game
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
- tstr = fileDesc->fileName;
- tstr.toLowercase();
+ String tstr = fileDesc->fileName;
if (!filesMD5.contains(tstr)) {
fileMissing = true;
break;
}
- if (fileDesc->md5 != NULL) {
- if (fileDesc->md5 != filesMD5[tstr]) {
- debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str());
- fileMissing = true;
- break;
- }
+
+ if (fileDesc->md5 != NULL && fileDesc->md5 != filesMD5[tstr]) {
+ debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str());
+ fileMissing = true;
+ break;
}
- if (fileDesc->fileSize != -1) {
- if (fileDesc->fileSize != filesSize[tstr]) {
- debug(3, "Size Mismatch. Skipping");
- fileMissing = true;
- break;
- }
+ if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSize[tstr]) {
+ debug(3, "Size Mismatch. Skipping");
+ fileMissing = true;
+ break;
}
debug(3, "Matched file: %s", tstr.c_str());
@@ -440,85 +445,68 @@ static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &p
}
}
- // We've found a match
- if (!matched.empty())
- return matched;
-
- if (!filesMD5.empty())
- reportUnknown(filesMD5, filesSize);
-
- // Filename based fallback
- if (params.fileBasedFallback != 0) {
- const ADFileBasedFallback *ptr = params.fileBasedFallback;
- const char* const* filenames = 0;
-
- // First we create list of files required for detection.
- // The filenames can be different than the MD5 based match ones.
- for (; ptr->desc; ptr++) {
- filenames = ptr->filenames;
- for (; *filenames; filenames++) {
- tstr = String(*filenames);
- tstr.toLowercase();
-
- if (!allFiles.contains(tstr)) {
- if (testFile.open(tstr) || testFile.open(tstr + ".")) {
- allFiles[tstr] = true;
- testFile.close();
- }
- }
- }
- }
-
- // Then we perform the actual filename matching. If there are
- // several matches, only the one with the maximum numbers of
- // files is considered.
- int maxNumMatchedFiles = 0;
- const ADGameDescription *matchedDesc = 0;
-
- ptr = params.fileBasedFallback;
+ // We didn't find a match
+ if (matched.empty()) {
+ if (!filesMD5.empty())
+ reportUnknown(filesMD5, filesSize);
+
+ // Filename based fallback
+ if (params.fileBasedFallback != 0)
+ matched = detectGameFilebased(allFiles, params);
+ }
- for (; ptr->desc; ptr++) {
- const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc;
- int numMatchedFiles = 0;
- fileMissing = false;
+ return matched;
+}
- filenames = ptr->filenames;
- for (; *filenames; filenames++) {
- if (fileMissing) {
- continue;
- }
+/**
+ * Check for each ADFileBasedFallback record whether all files listed
+ * in it are present. If multiple pass this test, we pick the one with
+ * the maximal number of matching files. In case of a tie, the entry
+ * coming first in the list is chosen.
+ */
+static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams &params) {
+ const ADFileBasedFallback *ptr;
+ const char* const* filenames;
- tstr = String(*filenames);
- tstr.toLowercase();
+ int maxNumMatchedFiles = 0;
+ const ADGameDescription *matchedDesc = 0;
- debug(3, "++ %s", *filenames);
- if (!allFiles.contains(tstr)) {
- fileMissing = true;
- continue;
- }
+ for (ptr = params.fileBasedFallback; ptr->desc; ++ptr) {
+ const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc;
+ int numMatchedFiles = 0;
+ bool fileMissing = false;
- numMatchedFiles++;
+ for (filenames = ptr->filenames; *filenames; ++filenames) {
+ debug(3, "++ %s", *filenames);
+ if (!allFiles.contains(*filenames)) {
+ fileMissing = true;
+ break;
}
- if (!fileMissing)
- debug(4, "Matched: %s", agdesc->gameid);
+ numMatchedFiles++;
+ }
- if (!fileMissing && numMatchedFiles > maxNumMatchedFiles) {
+ if (!fileMissing) {
+ debug(4, "Matched: %s", agdesc->gameid);
+
+ if (numMatchedFiles > maxNumMatchedFiles) {
matchedDesc = agdesc;
maxNumMatchedFiles = numMatchedFiles;
-
+
debug(4, "and overriden");
}
}
+ }
- if (matchedDesc) { // We got a match
- matched.push_back(matchedDesc);
- if (params.flags & kADFlagPrintWarningOnFileBasedFallback) {
- printf("Your game version has been detected using filename matching as a\n");
- printf("variant of %s.\n", matchedDesc->gameid);
- printf("If this is an original and unmodified version, please report any\n");
- printf("information previously printed by ScummVM to the team.\n");
- }
+ ADGameDescList matched;
+
+ if (matchedDesc) { // We got a match
+ matched.push_back(matchedDesc);
+ if (params.flags & kADFlagPrintWarningOnFileBasedFallback) {
+ printf("Your game version has been detected using filename matching as a\n");
+ printf("variant of %s.\n", matchedDesc->gameid);
+ printf("If this is an original and unmodified version, please report any\n");
+ printf("information previously printed by ScummVM to the team.\n");
}
}
diff --git a/common/advancedDetector.h b/common/advancedDetector.h
index 48b9e213d7..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 {
@@ -69,11 +67,6 @@ struct ADGameDescription {
};
/**
- * A list of pointers to ADGameDescription structs (or subclasses thereof).
- */
-typedef Array<const ADGameDescription*> ADGameDescList;
-
-/**
* End marker for a table of ADGameDescription structs. Use this to
* terminate a list to be passed to the AdvancedDetector API.
*/
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 044474a927..b741757cc5 100644
--- a/common/config-manager.cpp
+++ b/common/config-manager.cpp
@@ -23,38 +23,13 @@
*
*/
-#if defined(WIN32)
-#include <windows.h>
-// winnt.h defines ARRAYSIZE, but we want our own one...
-#undef ARRAYSIZE
-#endif
-
#include "common/config-manager.h"
#include "common/file.h"
#include "common/util.h"
+#include "common/system.h"
DECLARE_SINGLETON(Common::ConfigManager);
-#ifdef __PLAYSTATION2__
-#include "backends/platform/ps2/systemps2.h"
-#endif
-
-#ifdef IPHONE
-#include "backends/platform/iphone/osys_iphone.h"
-#endif
-
-#if defined(UNIX)
-#ifdef MACOSX
-#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences"
-#else
-#define DEFAULT_CONFIG_FILE ".scummvmrc"
-#endif
-#else
-#define DEFAULT_CONFIG_FILE "scummvm.ini"
-#endif
-
-#define MAXLINELEN 256
-
static bool isValidDomainName(const Common::String &domName) {
const char *p = domName.c_str();
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
@@ -85,238 +60,175 @@ ConfigManager::ConfigManager()
void ConfigManager::loadDefaultConfigFile() {
- char configFile[MAXPATHLEN];
- // GP2X is Linux based but Home dir can be read only so do not use it and put the config in the executable dir.
- // On the iPhone, the home dir of the user when you launch the app from the Springboard, is /. Which we don't want.
-#if defined(UNIX) && !defined(GP2X) && !defined(IPHONE)
- const char *home = getenv("HOME");
- if (home != NULL && strlen(home) < MAXPATHLEN)
- snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE);
- else
- strcpy(configFile, DEFAULT_CONFIG_FILE);
-#else
- #if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__)
- OSVERSIONINFO win32OsVersion;
- ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO));
- win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- GetVersionEx(&win32OsVersion);
- // Check for non-9X version of Windows.
- if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) {
- // Use the Application Data directory of the user profile.
- if (win32OsVersion.dwMajorVersion >= 5) {
- if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile)))
- error("Unable to access application data directory");
- } else {
- if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile)))
- error("Unable to access user profile directory");
+ // Open the default config file
+ SeekableReadStream *stream = g_system->openConfigFileForReading();
+ _filename.clear(); // clear the filename to indicate that we are using the default config file
- strcat(configFile, "\\Application Data");
- CreateDirectory(configFile, NULL);
- }
+ // ... load it, if available ...
+ if (stream)
+ loadFromStream(*stream);
+
+ // ... and close it again.
+ delete stream;
- strcat(configFile, "\\ScummVM");
- CreateDirectory(configFile, NULL);
- strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
-
- if (fopen(configFile, "r") == NULL) {
- // Check windows directory
- char oldConfigFile[MAXPATHLEN];
- GetWindowsDirectory(oldConfigFile, MAXPATHLEN);
- strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE);
- if (fopen(oldConfigFile, "r")) {
- printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n");
- printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n");
- printf("file from the old default location:\n");
- printf("%s\n", oldConfigFile);
- printf("to the new default location:\n");
- printf("%s\n\n", configFile);
- strcpy(configFile, oldConfigFile);
- }
- }
- } else {
- // Check windows directory
- GetWindowsDirectory(configFile, MAXPATHLEN);
- strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
- }
-
- #elif defined(PALMOS_MODE)
- strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
- #elif defined(IPHONE)
- strcpy(configFile, OSystem_IPHONE::getConfigPath());
- #elif defined(__PLAYSTATION2__)
- ((OSystem_PS2*)g_system)->makeConfigPath(configFile);
- #elif defined(__PSP__)
- strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
- #elif defined (__SYMBIAN32__)
- strcpy(configFile, Symbian::GetExecutablePath());
- strcat(configFile, DEFAULT_CONFIG_FILE);
- #else
- strcpy(configFile, DEFAULT_CONFIG_FILE);
- #endif
-#endif
-
- loadConfigFile(configFile);
flushToDisk();
}
void ConfigManager::loadConfigFile(const String &filename) {
- _appDomain.clear();
- _gameDomains.clear();
- _transientDomain.clear();
-
_filename = filename;
- _domainSaveOrder.clear();
- loadFile(_filename);
- printf("Using configuration file: %s\n", _filename.c_str());
-}
-void ConfigManager::loadFile(const String &filename) {
File cfg_file;
-
if (!cfg_file.open(filename)) {
printf("Creating configuration file: %s\n", filename.c_str());
} else {
- String domain;
- String comment;
- int lineno = 0;
-
- // TODO: Detect if a domain occurs multiple times (or likewise, if
- // a key occurs multiple times inside one domain).
-
- while (!cfg_file.eof() && !cfg_file.ioFailed()) {
- lineno++;
-
- // Read a line
- String line;
- while (line.lastChar() != '\n') {
- char buf[MAXLINELEN];
- if (!cfg_file.readLine_NEW(buf, MAXLINELEN))
- break;
- line += buf;
+ printf("Using configuration file: %s\n", _filename.c_str());
+ loadFromStream(cfg_file);
+ }
+}
+
+void ConfigManager::loadFromStream(SeekableReadStream &stream) {
+ String domain;
+ String comment;
+ int lineno = 0;
+
+ _appDomain.clear();
+ _gameDomains.clear();
+ _transientDomain.clear();
+ _domainSaveOrder.clear();
+
+ // TODO: Detect if a domain occurs multiple times (or likewise, if
+ // a key occurs multiple times inside one domain).
+
+ while (!stream.eos() && !stream.ioFailed()) {
+ lineno++;
+
+ // 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 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;
+ // Get the domain name, and check whether it's valid (that
+ // is, verify that it only consists of alphanumerics,
+ // dashes and underscores).
+ while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
+ p++;
+
+ if (*p == '\0')
+ error("Config file buggy: missing ] in line %d", lineno);
+ else if (*p != ']')
+ error("Config file buggy: Invalid character '%c' occured in section name in line %d", *p, lineno);
+
+ domain = String(line.c_str() + 1, p);
+
+ // Store domain comment
+ if (domain == kApplicationDomain) {
+ _appDomain.setDomainComment(comment);
+ } else {
+ _gameDomains[domain].setDomainComment(comment);
}
+ comment.clear();
- if (line.size() == 0) {
- // Do nothing
- } else if (line[0] == '#') {
- // Accumulate comments here. Once we encounter either the start
- // of a new domain, or a key-value-pair, we associate the value
- // of the 'comment' variable with that entity.
- comment += line;
- } else if (line[0] == '[') {
- // It's a new domain which begins here.
- const char *p = line.c_str() + 1;
- // Get the domain name, and check whether it's valid (that
- // is, verify that it only consists of alphanumerics,
- // dashes and underscores).
- while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
- p++;
-
- switch (*p) {
- case '\0':
- error("Config file buggy: missing ] in line %d", lineno);
- break;
- case ']':
- domain = String(line.c_str() + 1, p - (line.c_str() + 1));
- //domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes
- break;
- default:
- error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno);
- }
-
- // Store domain comment
- if (domain == kApplicationDomain) {
- _appDomain.setDomainComment(comment);
- } else {
- _gameDomains[domain].setDomainComment(comment);
- }
- comment.clear();
-
- _domainSaveOrder.push_back(domain);
+ _domainSaveOrder.push_back(domain);
+ } else {
+ // 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;
+
+ // If no domain has been set, this config file is invalid!
+ if (domain.empty()) {
+ error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
+ }
+
+ // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
+ const char *p = strchr(t, '=');
+ if (!p)
+ error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
+
+ // Extract the key/value pair
+ String key(t, p);
+ String value(p + 1);
+
+ // Trim of spaces
+ key.trim();
+ value.trim();
+
+ // Finally, store the key/value pair in the active domain
+ set(key, value, domain);
+
+ // Store comment
+ if (domain == kApplicationDomain) {
+ _appDomain.setKVComment(key, comment);
} else {
- // 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;
-
- // If no domain has been set, this config file is invalid!
- if (domain.empty()) {
- error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
- }
-
- // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
- const char *p = strchr(t, '=');
- if (!p)
- error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
-
- // Trim spaces before the '=' to obtain the key
- const char *p2 = p;
- while (p2 > t && isspace(*(p2-1)))
- p2--;
- String key(t, p2 - t);
-
- // Skip spaces after the '='
- t = p + 1;
- while (isspace(*t))
- t++;
-
- // Trim trailing spaces
- p2 = t + strlen(t);
- while (p2 > t && isspace(*(p2-1)))
- p2--;
-
- String value(t, p2 - t);
-
- // Finally, store the key/value pair in the active domain
- set(key, value, domain);
-
- // Store comment
- if (domain == kApplicationDomain) {
- _appDomain.setKVComment(key, comment);
- } else {
- _gameDomains[domain].setKVComment(key, comment);
- }
- comment.clear();
+ _gameDomains[domain].setKVComment(key, comment);
}
+ comment.clear();
}
}
}
void ConfigManager::flushToDisk() {
#ifndef __DC__
- DumpFile cfg_file;
+ WriteStream *stream;
- if (!cfg_file.open(_filename)) {
- warning("Unable to write configuration file: %s", _filename.c_str());
+ if (_filename.empty()) {
+ // Write to the default config file
+ stream = g_system->openConfigFileForWriting();
+ if (!stream) // If writing to the config file is not possible, do nothing
+ return;
} else {
- // First write the domains in _domainSaveOrder, in that order.
- // Note: It's possible for _domainSaveOrder to list domains which
- // are not present anymore.
- StringList::const_iterator i;
- for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
- if (kApplicationDomain == *i) {
- writeDomain(cfg_file, *i, _appDomain);
- } else if (_gameDomains.contains(*i)) {
- writeDomain(cfg_file, *i, _gameDomains[*i]);
- }
+ DumpFile *dump = new DumpFile();
+ assert(dump);
+
+ if (!dump->open(_filename)) {
+ warning("Unable to write configuration file: %s", _filename.c_str());
+ delete dump;
+ return;
}
+
+ stream = dump;
+ }
- DomainMap::const_iterator d;
+ // First write the domains in _domainSaveOrder, in that order.
+ // Note: It's possible for _domainSaveOrder to list domains which
+ // are not present anymore.
+ StringList::const_iterator i;
+ for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
+ if (kApplicationDomain == *i) {
+ writeDomain(*stream, *i, _appDomain);
+ } else if (_gameDomains.contains(*i)) {
+ writeDomain(*stream, *i, _gameDomains[*i]);
+ }
+ }
+ DomainMap::const_iterator d;
- // Now write the domains which haven't been written yet
- if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
- writeDomain(cfg_file, kApplicationDomain, _appDomain);
- for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
- if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
- writeDomain(cfg_file, d->_key, d->_value);
- }
+
+ // Now write the domains which haven't been written yet
+ if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
+ writeDomain(*stream, kApplicationDomain, _appDomain);
+ for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
+ if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
+ writeDomain(*stream, d->_key, d->_value);
}
+
+ delete stream;
+
#endif // !__DC__
}
@@ -324,6 +236,12 @@ void ConfigManager::writeDomain(WriteStream &stream, const String &name, const D
if (domain.empty())
return; // Don't bother writing empty domains.
+ // WORKAROUND: Fix for bug #1972625 "ALL: On-the-fly targets are
+ // written to the config file": Do not save domains that came from
+ // the command line
+ if (domain.contains("id_came_from_command_line"))
+ return;
+
String comment;
// Write domain comment (if any)
diff --git a/common/config-manager.h b/common/config-manager.h
index bebb59b539..9e5b88a073 100644
--- a/common/config-manager.h
+++ b/common/config-manager.h
@@ -36,7 +36,7 @@
namespace Common {
class WriteStream;
-
+class SeekableReadStream;
/**
* The (singleton) configuration manager, used to query & set configuration
@@ -144,19 +144,11 @@ public:
bool hasGameDomain(const String &domName) const;
const DomainMap & getGameDomains() const { return _gameDomains; }
-/*
- TODO: Callback/change notification system
- typedef void (*ConfigCallback)(const ConstString &key, void *refCon);
-
- void registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString)
- void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString)
-*/
-
private:
friend class Singleton<SingletonBaseType>;
ConfigManager();
- void loadFile(const String &filename);
+ void loadFromStream(SeekableReadStream &stream);
void writeDomain(WriteStream &stream, const String &name, const Domain &domain);
Domain _transientDomain;
diff --git a/common/events.h b/common/events.h
index 1a20ee4e9e..645d9e4aed 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,
/**
@@ -178,6 +182,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;
@@ -187,6 +202,10 @@ public:
// replacing it by a generic getScreenChangeID method here
virtual Common::Keymapper *getKeymapper() = 0;
+
+protected:
+
+ Common::Queue<Common::Event> artificialEventQueue;
};
} // End of namespace Common
diff --git a/common/file.cpp b/common/file.cpp
index 5b465b5e01..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);
- }
+static Common::SearchSet *s_searchSet = 0;
- //
- // 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
-
- 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");
- return false;
- } /*else if (!node.isReadable() && mode == kFileReadMode) {
- warning("File::open: Trying to open an unreadable FilesystemNode object for reading");
+ warning("File::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;
- }*/
+ }
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,20 +217,28 @@ bool DumpFile::open(const String &filename) {
assert(!filename.empty());
assert(!_handle);
- String fname(filename);
- fname.toLowercase();
-
- _handle = fopenNoCase(filename, "", "wb");
+ FilesystemNode node(filename);
+ return open(node);
+}
+
+bool DumpFile::open(const FilesystemNode &node) {
+ assert(!_handle);
+
+ if (node.isDirectory()) {
+ warning("DumpFile::open: FilesystemNode is a directory");
+ return false;
+ }
+
+ _handle = node.openForWriting();
if (_handle == NULL)
- debug(2, "Failed to open '%s' for writing", filename.c_str());
+ debug(2, "File %s not found", node.getName().c_str());
return _handle != NULL;
}
void DumpFile::close() {
- if (_handle)
- fclose((FILE *)_handle);
+ delete _handle;
_handle = NULL;
}
@@ -504,29 +246,24 @@ bool DumpFile::isOpen() const {
return _handle != NULL;
}
-bool DumpFile::ioFailed() const {
+bool DumpFile::err() const {
assert(_handle);
- return ferror((FILE *)_handle) != 0;
+ return _handle->ioFailed();
}
-void DumpFile::clearIOFailed() {
+void DumpFile::clearErr() {
assert(_handle);
- clearerr((FILE *)_handle);
+ _handle->clearIOFailed();
}
-bool DumpFile::eof() const {
+uint32 DumpFile::write(const void *ptr, uint32 len) {
assert(_handle);
- return feof((FILE *)_handle) != 0;
+ return _handle->write(ptr, len);
}
-uint32 DumpFile::write(const void *ptr, uint32 len) {
+bool DumpFile::flush() {
assert(_handle);
-
- if (len == 0)
- return 0;
-
- return (uint32)fwrite(ptr, 1, len, (FILE *)_handle);
+ return _handle->flush();
}
-
} // End of namespace Common
diff --git a/common/file.h b/common/file.h
index 3c2520b07c..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,14 +110,14 @@ 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();
virtual ~DumpFile();
virtual bool open(const String &filename);
- //virtual bool open(const FilesystemNode &node);
+ virtual bool open(const FilesystemNode &node);
virtual void close();
@@ -136,22 +128,14 @@ public:
*/
bool isOpen() const;
+ bool err() const;
+ void clearErr();
- 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;
+ virtual uint32 write(const void *dataPtr, uint32 dataSize);
- uint32 write(const void *dataPtr, uint32 dataSize);
+ virtual bool flush();
};
-
} // End of namespace Common
#endif
diff --git a/common/fs.cpp b/common/fs.cpp
index 7d803dacd4..4d31ac09fa 100644
--- a/common/fs.cpp
+++ b/common/fs.cpp
@@ -27,6 +27,8 @@
#include "backends/fs/abstract-fs.h"
#include "backends/fs/fs-factory.h"
+namespace Common {
+
FilesystemNode::FilesystemNode() {
}
@@ -49,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 {
@@ -60,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);
}
@@ -152,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)
@@ -170,3 +172,32 @@ bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool h
return !results.empty();
}
+
+Common::SeekableReadStream *FilesystemNode::openForReading() const {
+ if (_realNode == 0)
+ return 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 _realNode->openForReading();
+}
+
+Common::WriteStream *FilesystemNode::openForWriting() const {
+ if (_realNode == 0)
+ return 0;
+
+ if (_realNode->isDirectory()) {
+ warning("FilesystemNode::openForWriting: FilesystemNode is a directory");
+ return 0;
+ }
+
+ return _realNode->openForWriting();
+}
+
+} // End of namespace Common
diff --git a/common/fs.h b/common/fs.h
index ed7355cc00..c5f7ca6b4c 100644
--- a/common/fs.h
+++ b/common/fs.h
@@ -29,10 +29,13 @@
#include "common/ptr.h"
#include "common/str.h"
-//namespace Common {
+class AbstractFilesystemNode;
+
+namespace Common {
class FilesystemNode;
-class AbstractFilesystemNode;
+class SeekableReadStream;
+class WriteStream;
/**
* List of multiple file system nodes. E.g. the contents of a given directory.
@@ -49,22 +52,6 @@ class FSList : public Common::Array<FilesystemNode> {};
* To this end, we abstract away from paths; implementations can be based on
* paths (and it's left to them whether / or \ or : is the path separator :-);
* but it is also possible to use inodes or vrefs (MacOS 9) or anything else.
- *
- * NOTE: Backends still have to provide a way to extract a path from a FSIntern
- *
- * You may ask now: "isn't this cheating? Why do we go through all this when we use
- * a path in the end anyway?!?".
- * Well, for once as long as we don't provide our own file open/read/write API, we
- * still have to use fopen(). Since all our targets already support fopen(), it should
- * be possible to get a fopen() compatible string for any file system node.
- *
- * Secondly, with this abstraction layer, we still avoid a lot of complications based on
- * differences in FS roots, different path separators, or even systems with no real
- * paths (MacOS 9 doesn't even have the notion of a "current directory").
- * And if we ever want to support devices with no FS in the classical sense (Palm...),
- * we can build upon this.
- *
- * This class acts as a wrapper around the AbstractFilesystemNode class defined in backends/fs.
*/
class FilesystemNode {
private:
@@ -108,24 +95,36 @@ public:
bool operator<(const FilesystemNode& node) const;
/**
- * Indicates whether the object referred by this path exists in the filesystem or not.
+ * Indicates whether the object referred by this node exists in the filesystem or not.
*
- * @return bool true if the path exists, false otherwise.
+ * @return bool true if the node exists, false otherwise.
*/
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;
@@ -149,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'.
@@ -168,7 +168,7 @@ public:
FilesystemNode getParent() const;
/**
- * Indicates whether the path refers to a directory or not.
+ * Indicates whether the node refers to a directory or not.
*
* @todo Currently we assume that a node that is not a directory
* automatically is a file (ignoring things like symlinks or pipes).
@@ -179,28 +179,28 @@ public:
virtual bool isDirectory() const;
/**
- * Indicates whether the object referred by this path can be read from or not.
+ * Indicates whether the object referred by this node can be read from or not.
*
- * If the path refers to a directory, readability implies being able to read
+ * If the node refers to a directory, readability implies being able to read
* and list the directory entries.
*
- * If the path refers to a file, readability implies being able to read the
+ * If the node refers to a file, readability implies being able to read the
* contents of the file.
*
- * @return bool true if the object can be read, false otherwise.
+ * @return true if the object can be read, false otherwise.
*/
virtual bool isReadable() const;
/**
- * Indicates whether the object referred by this path can be written to or not.
+ * Indicates whether the object referred by this node can be written to or not.
*
- * If the path refers to a directory, writability implies being able to modify
+ * If the node refers to a directory, writability implies being able to modify
* the directory entry (i.e. rename the directory, remove it or write files inside of it).
*
- * If the path refers to a file, writability implies being able to write data
+ * If the node refers to a file, writability implies being able to write data
* to the file.
*
- * @return bool true if the object can be written to, false otherwise.
+ * @return true if the object can be written to, false otherwise.
*/
virtual bool isWritable() const;
@@ -221,8 +221,27 @@ public:
* @return true if matches could be found, false otherwise.
*/
virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const;
+
+
+ /**
+ * Creates a SeekableReadStream instance corresponding to the file
+ * referred by this node. This assumes that the node actually refers
+ * to a readable file. If this is not the case, 0 is returned.
+ *
+ * @return pointer to the stream object, 0 in case of a failure
+ */
+ virtual Common::SeekableReadStream *openForReading() const;
+
+ /**
+ * Creates a WriteStream instance corresponding to the file
+ * referred by this node. This assumes that the node actually refers
+ * to a readable file. If this is not the case, 0 is returned.
+ *
+ * @return pointer to the stream object, 0 in case of a failure
+ */
+ virtual Common::WriteStream *openForWriting() const;
};
-//} // End of namespace Common
+} // End of namespace Common
#endif //COMMON_FS_H
diff --git a/common/func.h b/common/func.h
index 1c045b9e5d..6aa5b76ed4 100644
--- a/common/func.h
+++ b/common/func.h
@@ -78,7 +78,7 @@ private:
Op _op;
typename Op::FirstArgumentType _arg1;
public:
- Binder1st(const Op &op, const typename Op::FirstArgumentType &arg1) : _op(op), _arg1(arg1) {}
+ Binder1st(const Op &op, typename Op::FirstArgumentType arg1) : _op(op), _arg1(arg1) {}
typename Op::ResultType operator()(typename Op::SecondArgumentType v) const {
return _op(_arg1, v);
@@ -89,8 +89,8 @@ public:
* Transforms a binary function object into an unary function object.
* To achieve that the first parameter is bound to the passed value t.
*/
-template<class Op, class T>
-inline Binder1st<Op> bind1st(const Op &op, const T &t) {
+template<class Op>
+inline Binder1st<Op> bind1st(const Op &op, typename Op::FirstArgumentType t) {
return Binder1st<Op>(op, t);
}
@@ -100,7 +100,7 @@ private:
Op _op;
typename Op::SecondArgumentType _arg2;
public:
- Binder2nd(const Op &op, const typename Op::SecondArgumentType &arg2) : _op(op), _arg2(arg2) {}
+ Binder2nd(const Op &op, typename Op::SecondArgumentType arg2) : _op(op), _arg2(arg2) {}
typename Op::ResultType operator()(typename Op::FirstArgumentType v) const {
return _op(v, _arg2);
@@ -109,10 +109,10 @@ public:
/**
* Transforms a binary function object into an unary function object.
- * To achieve that the second parameter is bound to the passed value t.
+ * To achieve that the first parameter is bound to the passed value t.
*/
-template<class Op, class T>
-inline Binder2nd<Op> bind2nd(const Op &op, const T &t) {
+template<class Op>
+inline Binder2nd<Op> bind2nd(const Op &op, typename Op::SecondArgumentType t) {
return Binder2nd<Op>(op, t);
}
@@ -159,7 +159,7 @@ inline PointerToBinaryFunc<Arg1, Arg2, Result> ptr_fun(Result (*func)(Arg1, Arg2
}
template<class Result, class T>
-class MemFunc0 : public UnaryFunction<T*, Result> {
+class MemFunc0 : public UnaryFunction<T *, Result> {
private:
Result (T::*_func)();
public:
@@ -179,7 +179,7 @@ public:
typedef Result (T::*FuncType)() const;
ConstMemFunc0(const FuncType &func) : _func(func) {}
- Result operator()(T *v) const {
+ Result operator()(const T *v) const {
return (v->*_func)();
}
};
@@ -205,7 +205,7 @@ public:
typedef Result (T::*FuncType)(Arg) const;
ConstMemFunc1(const FuncType &func) : _func(func) {}
- Result operator()(T *v1, Arg v2) const {
+ Result operator()(const T *v1, Arg v2) const {
return (v1->*_func)(v2);
}
};
@@ -252,6 +252,105 @@ inline ConstMemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg) const) {
return ConstMemFunc1<Result, Arg, T>(f);
}
+template<class Result, class T>
+class MemFuncRef0 : public UnaryFunction<T &, Result> {
+private:
+ Result (T::*_func)();
+public:
+ typedef Result (T::*FuncType)();
+
+ MemFuncRef0(const FuncType &func) : _func(func) {}
+ Result operator()(T &v) const {
+ return (v.*_func)();
+ }
+};
+
+template<class Result, class T>
+class ConstMemFuncRef0 : public UnaryFunction<T &, Result> {
+private:
+ Result (T::*_func)() const;
+public:
+ typedef Result (T::*FuncType)() const;
+
+ ConstMemFuncRef0(const FuncType &func) : _func(func) {}
+ Result operator()(const T &v) const {
+ return (v.*_func)();
+ }
+};
+
+template<class Result, class Arg, class T>
+class MemFuncRef1 : public BinaryFunction<T &, Arg, Result> {
+private:
+ Result (T::*_func)(Arg);
+public:
+ typedef Result (T::*FuncType)(Arg);
+
+ MemFuncRef1(const FuncType &func) : _func(func) {}
+ Result operator()(T &v1, Arg v2) const {
+ return (v1.*_func)(v2);
+ }
+};
+
+template<class Result, class Arg, class T>
+class ConstMemFuncRef1 : public BinaryFunction<T &, Arg, Result> {
+private:
+ Result (T::*_func)(Arg) const;
+public:
+ typedef Result (T::*FuncType)(Arg) const;
+
+ ConstMemFuncRef1(const FuncType &func) : _func(func) {}
+ Result operator()(const T &v1, Arg v2) const {
+ return (v1.*_func)(v2);
+ }
+};
+
+/**
+ * Creates a unary function object from a class member function pointer.
+ * The parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ */
+template<class Result, class T>
+inline MemFuncRef0<Result, T> mem_fun_ref(Result (T::*f)()) {
+ return MemFuncRef0<Result, T>(f);
+}
+
+/**
+ * Creates a unary function object from a class member function pointer.
+ * The parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ */
+template<class Result, class T>
+inline ConstMemFuncRef0<Result, T> mem_fun_Ref(Result (T::*f)() const) {
+ return ConstMemFuncRef0<Result, T>(f);
+}
+
+/**
+ * Creates a binary function object from a class member function pointer.
+ * The first parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ * The second one is the parameter passed to the member function.
+ */
+template<class Result, class Arg, class T>
+inline MemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg)) {
+ return MemFuncRef1<Result, Arg, T>(f);
+}
+
+/**
+ * Creates a binary function object from a class member function pointer.
+ * The first parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ * The second one is the parameter passed to the member function.
+ */
+template<class Result, class Arg, class T>
+inline ConstMemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg) const) {
+ return ConstMemFuncRef1<Result, Arg, T>(f);
+}
+
// functor code
/**
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 ab6e737d74..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
@@ -58,29 +34,22 @@
#include "common/str.h"
#include "common/util.h"
-// FIXME: Since this define is very system dependant,
-// it should be moved to the appropriate H file instead.
-// Portdefs might be a good location for example
-#if !defined(__SYMBIAN32__)
#define USE_HASHMAP_MEMORY_POOL
-#endif
-
#ifdef USE_HASHMAP_MEMORY_POOL
#include "common/memorypool.h"
// FIXME: we sadly can't assume standard C++ to be present
// on every system we support, so we should get rid of this.
// The solution should be to write a simple placement new
// on our own.
+
+// Symbian does not have <new> but the new operator
+#if !defined(__SYMBIAN32__)
#include <new>
#endif
+#endif
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).
@@ -115,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();
@@ -138,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;
@@ -154,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;
@@ -176,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;
}
@@ -197,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;
@@ -225,7 +210,7 @@ public:
// Remove the previous content and ...
clear();
- delete[] _arr;
+ delete[] _storage;
// ... copy the new stuff.
assign(map);
return *this;
@@ -244,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();
@@ -260,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();
@@ -272,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();
}
@@ -287,7 +272,7 @@ public:
// TODO: insert() method?
bool empty() const {
- return (_nele == 0);
+ return (_size == 0);
}
};
@@ -299,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;
@@ -323,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);
}
@@ -335,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
}
/**
@@ -351,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++;
@@ -450,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;
@@ -460,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);
}
}
@@ -478,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>
@@ -494,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;
}
@@ -510,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 a0ae941a08..6a4445728f 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/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
index cb29c59058..be6df0148a 100644
--- a/common/queue.h
+++ b/common/queue.h
@@ -31,39 +31,62 @@
namespace Common {
/**
- * Variable size Queue class, implemented using our Array class.
+ * Variable size Queue class, implemented using our List class.
*/
template<class T>
class Queue {
-protected:
- List<T> _queue;
public:
- Queue<T>() {}
- Queue<T>(const List<T> &queueContent) : _queue(queueContent) {}
+ 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 _queue.empty();
+ return _impl.empty();
}
+
void clear() {
- _queue.clear();
+ _impl.clear();
}
+
void push(const T &x) {
- _queue.push_back(x);
+ _impl.push_back(x);
}
- T back() const {
- return _queue.reverse_begin().operator*();
+
+ T &front() {
+ return *_impl.begin();
}
- T front() const {
- return _queue.begin().operator*();
+
+ 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();
- _queue.pop_front();
+ _impl.pop_front();
return tmp;
}
+
int size() const {
- return _queue.size();
+ return _impl.size();
}
+
+private:
+ List<T> _impl;
};
} // End of namespace Common
diff --git a/common/savefile.h b/common/savefile.h
index f30ddfc160..d44f946d48 100644
--- a/common/savefile.h
+++ b/common/savefile.h
@@ -39,27 +39,14 @@ namespace Common {
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-class InSaveFile : public SeekableReadStream {};
+typedef SeekableReadStream InSaveFile;
/**
* A class which allows game engines to save game state data.
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-class OutSaveFile : public WriteStream {
-public:
- /**
- * Close this savefile, to be called right before destruction of this
- * savefile. The idea is that this ways, I/O errors that occur
- * during closing/flushing of the file can still be handled by the
- * game engine.
- *
- * By default, this just flushes the stream.
- */
- virtual void finalize() {
- flush();
- }
-};
+typedef WriteStream OutSaveFile;
/**
diff --git a/common/str.cpp b/common/str.cpp
index 5f8d4ffb7e..6c48738533 100644
--- a/common/str.cpp
+++ b/common/str.cpp
@@ -26,6 +26,13 @@
#include "common/hash-str.h"
#include "common/util.h"
+#include "common/memorypool.h"
+
+#if !defined(__SYMBIAN32__)
+#include <new>
+#endif
+
+
namespace Common {
#if !(defined(PALMOS_ARM) || defined(PALMOS_DEBUG) || defined(__GP32__))
@@ -34,28 +41,27 @@ 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;
+
+MemoryPool *g_refCountPool = 0; // FIXME: This is never freed right now
+
+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 +73,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,28 +89,30 @@ 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) {
if (str.isStorageIntern()) {
// String in internal storage: just copy it
memcpy(_storage, str._storage, _builtinCapacity);
+ _str = _storage;
} else {
// String in external storage: use refcount mechanism
str.incRefCount();
_extern._refCount = str._extern._refCount;
_extern._capacity = str._extern._capacity;
+ _str = str._str;
}
assert(_str != 0);
}
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 +120,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 +137,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 +145,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;
}
@@ -182,7 +190,11 @@ void String::ensureCapacity(uint32 new_len, bool keep_old) {
void String::incRefCount() const {
assert(!isStorageIntern());
if (_extern._refCount == 0) {
- _extern._refCount = new int(2);
+ if (g_refCountPool == 0)
+ g_refCountPool = new MemoryPool(sizeof(int));
+
+ _extern._refCount = (int *)g_refCountPool->malloc();
+ *_extern._refCount = 2;
} else {
++(*_extern._refCount);
}
@@ -198,7 +210,10 @@ void String::decRefCount(int *oldRefCount) {
if (!oldRefCount || *oldRefCount <= 0) {
// The ref count reached zero, so we free the string storage
// and the ref count storage.
- delete oldRefCount;
+ if (oldRefCount) {
+ assert(g_refCountPool);
+ g_refCountPool->free(oldRefCount);
+ }
free(_str);
// Even though _str points to a freed memory block now,
@@ -210,7 +225,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 +236,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 +255,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 +264,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 +308,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 +330,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 +405,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 +547,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..772718a087 100644
--- a/common/str.h
+++ b/common/str.h
@@ -54,14 +54,14 @@ protected:
* than 8 makes no sense, since that's the size of member _extern
* (on 32 bit machines; 12 bytes on systems with 64bit pointers).
*/
- static const uint32 _builtinCapacity = 32;
+ static const uint32 _builtinCapacity = 32 - sizeof(uint32) - sizeof(char*);
/**
* Length of the string. Stored to avoid having to call strlen
* 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 c19db3a9a5..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,26 @@ 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
+ * stream instance is deleted. The goal here is to enable calling
+ * code to detect and handle I/O errors which might occur when
+ * closing (and this flushing, if buffered) the stream.
+ *
+ * After this method has been called, no further writes may be
+ * performed on the stream. Calling err() is allowed.
+ *
+ * By default, this just flushes the stream.
+ */
+ virtual void finalize() {
+ flush();
+ }
+
// The remaining methods all have default implementations; subclasses
// need not (and should not) overload them.
@@ -135,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;
@@ -151,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.
+
+ /**
+ * DEPRECATED
+ * Default implementation for backward compatibility
+ */
+ virtual bool ioFailed() { return (eos() || err()); }
/**
- * Read am unsigned byte from the stream and return it.
+ * 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;
@@ -169,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;
@@ -182,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();
@@ -195,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();
@@ -208,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();
@@ -221,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();
@@ -234,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();
@@ -245,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();
@@ -256,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();
@@ -267,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();
@@ -277,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);
@@ -287,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();
};
/**
@@ -353,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),
- _disposeParentStream(disposeParentStream) {
+ _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);
};
@@ -381,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);
};
/**
@@ -437,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);
};
@@ -451,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);
};
@@ -471,6 +548,7 @@ private:
uint32 _pos;
byte _encbyte;
bool _disposeMemory;
+ bool _eos;
public:
@@ -485,7 +563,8 @@ public:
_size(dataSize),
_pos(0),
_encbyte(0),
- _disposeMemory(disposeMemory) {}
+ _disposeMemory(disposeMemory),
+ _eos(false) {}
~MemoryReadStream() {
if (_disposeMemory)
@@ -496,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);
};
@@ -553,14 +634,13 @@ public:
return dataSize;
}
- bool eos() const { return _pos == _bufSize; }
uint32 pos() const { return _pos; }
uint32 size() const { return _bufSize; }
};
-/**
+/**
* A sort of hybrid between MemoryWriteStream and Array classes. A stream
- * that grows as it's written to.
+ * that grows as it's written to.
*/
class MemoryWriteStreamDynamic : public Common::WriteStream {
private:
@@ -607,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 8d528258f4..d9bc027e91 100644
--- a/common/system.cpp
+++ b/common/system.cpp
@@ -28,12 +28,9 @@
#include "common/config-manager.h"
#include "common/system.h"
-#include "common/timer.h"
-#include "common/util.h"
#include "graphics/colormasks.h"
#include "gui/message.h"
-#include "sound/mixer.h"
OSystem *g_system = 0;
@@ -118,6 +115,63 @@ 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();
}
+
+
+/*
+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.
+*/
+
+
+#ifdef __PLAYSTATION2__
+#include "backends/platform/ps2/systemps2.h"
+#endif
+
+#ifdef IPHONE
+#include "backends/platform/iphone/osys_iphone.h"
+#endif
+
+
+#if defined(UNIX)
+#define DEFAULT_CONFIG_FILE ".scummvmrc"
+#else
+#define DEFAULT_CONFIG_FILE "scummvm.ini"
+#endif
+
+static Common::String getDefaultConfigFileName() {
+ char configFile[MAXPATHLEN];
+#if defined(PALMOS_MODE)
+ strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
+#elif defined(IPHONE)
+ strcpy(configFile, OSystem_IPHONE::getConfigPath());
+#elif defined(__PLAYSTATION2__)
+ ((OSystem_PS2*)g_system)->makeConfigPath(configFile);
+#elif defined(__PSP__)
+ strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
+#else
+ strcpy(configFile, DEFAULT_CONFIG_FILE);
+#endif
+
+ return configFile;
+}
+
+Common::SeekableReadStream *OSystem::openConfigFileForReading() {
+ Common::FilesystemNode file(getDefaultConfigFileName());
+ return file.openForReading();
+}
+
+Common::WriteStream *OSystem::openConfigFileForWriting() {
+#ifdef __DC__
+ return 0;
+#else
+ Common::FilesystemNode file(getDefaultConfigFileName());
+ return file.openForWriting();
+#endif
+}
diff --git a/common/system.h b/common/system.h
index b895a5cfba..cb9dbedad7 100644
--- a/common/system.h
+++ b/common/system.h
@@ -43,7 +43,10 @@ namespace Common {
struct Event;
class EventManager;
class SaveFileManager;
+ class SearchSet;
class TimerManager;
+ class SeekableReadStream;
+ class WriteStream;
}
class FilesystemFactory;
@@ -900,10 +903,37 @@ public:
/**
* Returns the FilesystemFactory object, depending on the current architecture.
*
- * @return FilesystemFactory* The specific factory for the current architecture.
+ * @return the FSNode factory for the current architecture
*/
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.
+ */
+ virtual Common::SeekableReadStream *openConfigFileForReading();
+
+ /**
+ * Open the default config file for writing, by returning a suitable
+ * WriteStream instance. It is the callers responsiblity to delete
+ * the stream after use.
+ *
+ * May return 0 to indicate that writing to config file is not possible.
+ */
+ virtual Common::WriteStream *openConfigFileForWriting();
/**
* Return String which is used for backend-specific addition to theme
diff --git a/common/unarj.cpp b/common/unarj.cpp
index da88c11fc9..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;
}
}
@@ -75,7 +76,7 @@ static uint32 GetCRC(byte *data, int len) {
return CRC ^ 0xFFFFFFFF;
}
-ArjFile::ArjFile() {
+ArjFile::ArjFile() : _uncompressedData(NULL) {
InitCRC();
_isOpen = false;
_fallBack = false;
@@ -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.comment, (const char *)&headData[header.firstHdrSize + strlen(header.filename) + 1], COMMENT_MAX);
+ strncpy(header.filename, (const char *)&headData[header.firstHdrSize], ARJ_FILENAME_MAX);
- /* if extheadersize == 0 then no CRC */
- /* otherwise read extheader data and read 4 bytes for CRC */
+ strncpy(header.comment, (const char *)&headData[header.firstHdrSize + strlen(header.filename) + 1], ARJ_COMMENT_MAX);
- 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();
@@ -256,6 +264,11 @@ bool ArjFile::open(const Common::String &filename) {
_compsize = hdr->compSize;
_origsize = hdr->origSize;
+ // FIXME: This hotfix prevents Drascula from leaking memory.
+ // As far as sanity checks go this is not bad, but the engine should be fixed.
+ if (_uncompressedData)
+ free(_uncompressedData);
+
_uncompressedData = (byte *)malloc(_origsize);
_outstream = new MemoryWriteStream(_uncompressedData, _origsize);
@@ -315,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++)
@@ -410,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];
@@ -425,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;
@@ -440,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++;
@@ -458,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;
@@ -469,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) {
@@ -554,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;
@@ -618,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;
@@ -639,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.cpp b/common/unzip.cpp
index 93fb60f41c..054200e7a2 100644
--- a/common/unzip.cpp
+++ b/common/unzip.cpp
@@ -28,14 +28,51 @@
Read unzip.h for more info
*/
+/* unzip.h -- IO for uncompress .zip files using zlib
+ Version 0.15 beta, Mar 19th, 1998,
+
+ Copyright (C) 1998 Gilles Vollant
+
+ This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g
+ WinZip, InfoZip tools and compatible.
+ Encryption and multi volume ZipFile (span) are not supported.
+ Old compressions used by old PKZip 1.x are not supported
+
+ THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE
+ CAN CHANGE IN FUTURE VERSION !!
+ I WAIT FEEDBACK at mail info@winimage.com
+ Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution
+
+ Condition of use and distribution are the same than zlib :
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+
+*/
+/* for more info about .ZIP format, see
+ ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip
+ PkWare has also a specification at :
+ ftp://ftp.pkware.com/probdesc.zip */
+
+
#include "common/scummsys.h"
#ifdef USE_ZLIB
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
#ifdef __SYMBIAN32__
#include <zlib\zlib.h>
#else
@@ -45,24 +82,213 @@
#include "common/unzip.h"
#include "common/file.h"
-#ifdef STDC
-# include <stddef.h>
-# include <string.h>
-# include <stdlib.h>
-#endif
-#ifdef NO_ERRNO_H
- extern int errno;
+#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+ from (void*) without cast */
+typedef struct TagunzFile__ { int unused; } unzFile__;
+typedef unzFile__ *unzFile;
#else
-# include <errno.h>
+typedef voidp unzFile;
#endif
-#ifndef local
-# define local static
-#endif
-/* compile with -Dlocal if your debugger can't find static symbols */
+#define UNZ_OK (0)
+#define UNZ_END_OF_LIST_OF_FILE (-100)
+#define UNZ_ERRNO (Z_ERRNO)
+#define UNZ_EOF (0)
+#define UNZ_PARAMERROR (-102)
+#define UNZ_BADZIPFILE (-103)
+#define UNZ_INTERNALERROR (-104)
+#define UNZ_CRCERROR (-105)
+
+/* tm_unz contain date/time info */
+typedef struct {
+ uInt tm_sec; /* seconds after the minute - [0,59] */
+ uInt tm_min; /* minutes after the hour - [0,59] */
+ uInt tm_hour; /* hours since midnight - [0,23] */
+ uInt tm_mday; /* day of the month - [1,31] */
+ uInt tm_mon; /* months since January - [0,11] */
+ uInt tm_year; /* years - [1980..2044] */
+} tm_unz;
+
+/* unz_global_info structure contain global data about the ZIPfile
+ These data comes from the end of central dir */
+typedef struct {
+ uLong number_entry; /* total number of entries in
+ the central dir on this disk */
+ uLong size_comment; /* size of the global comment of the zipfile */
+} unz_global_info;
+
+
+/* unz_file_info contain information about a file in the zipfile */
+typedef struct {
+ uLong version; /* version made by 2 bytes */
+ uLong version_needed; /* version needed to extract 2 bytes */
+ uLong flag; /* general purpose bit flag 2 bytes */
+ uLong compression_method; /* compression method 2 bytes */
+ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */
+ uLong crc; /* crc-32 4 bytes */
+ uLong compressed_size; /* compressed size 4 bytes */
+ uLong uncompressed_size; /* uncompressed size 4 bytes */
+ uLong size_filename; /* filename length 2 bytes */
+ uLong size_file_extra; /* extra field length 2 bytes */
+ uLong size_file_comment; /* file comment length 2 bytes */
+
+ uLong disk_num_start; /* disk number start 2 bytes */
+ uLong internal_fa; /* internal file attributes 2 bytes */
+ uLong external_fa; /* external file attributes 4 bytes */
+
+ tm_unz tmu_date;
+} unz_file_info;
+
+int unzStringFileNameCompare(const char* fileName1,
+ const char* fileName2,
+ int iCaseSensitivity);
+/*
+ Compare two filename (fileName1,fileName2).
+ If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
+ If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
+ or strcasecmp)
+ If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
+ (like 1 on Unix, 2 on Windows)
+*/
+
+
+unzFile unzOpen(const char *path);
+/*
+ Open a Zip file. path contain the full pathname (by example,
+ on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer
+ "zlib/zlib111.zip".
+ If the zipfile cannot be opened (file don't exist or in not valid), the
+ return value is NULL.
+ Else, the return value is a unzFile Handle, usable with other function
+ of this unzip package.
+*/
+
+int unzClose(unzFile file);
+/*
+ Close a ZipFile opened with unzipOpen.
+ If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
+ these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
+ return UNZ_OK if there is no problem. */
+
+int unzGetGlobalInfo(unzFile file,
+ unz_global_info *pglobal_info);
+/*
+ Write info about the ZipFile in the *pglobal_info structure.
+ No preparation of the structure is needed
+ return UNZ_OK if there is no problem. */
+
+
+int unzGetGlobalComment(unzFile file, char *szComment, uLong uSizeBuf);
+/*
+ Get the global comment string of the ZipFile, in the szComment buffer.
+ uSizeBuf is the size of the szComment buffer.
+ return the number of byte copied or an error code <0
+*/
+
+
+/***************************************************************************/
+/* Unzip package allow you browse the directory of the zipfile */
+
+int unzGoToFirstFile(unzFile file);
+/*
+ Set the current file of the zipfile to the first file.
+ return UNZ_OK if there is no problem
+*/
+
+int unzGoToNextFile(unzFile file);
+/*
+ Set the current file of the zipfile to the next file.
+ return UNZ_OK if there is no problem
+ return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
+*/
+
+int unzLocateFile(unzFile file, const char *szFileName, int iCaseSensitivity);
+/*
+ Try locate the file szFileName in the zipfile.
+ For the iCaseSensitivity signification, see unzStringFileNameCompare
+
+ return value :
+ UNZ_OK if the file is found. It becomes the current file.
+ UNZ_END_OF_LIST_OF_FILE if the file is not found
+*/
+
+
+int unzGetCurrentFileInfo(unzFile file,
+ unz_file_info *pfile_info,
+ char *szFileName,
+ uLong fileNameBufferSize,
+ void *extraField,
+ uLong extraFieldBufferSize,
+ char *szComment,
+ uLong commentBufferSize);
+/*
+ Get Info about the current file
+ if pfile_info!=NULL, the *pfile_info structure will contain somes info about
+ the current file
+ if szFileName!=NULL, the filemane string will be copied in szFileName
+ (fileNameBufferSize is the size of the buffer)
+ if extraField!=NULL, the extra field information will be copied in extraField
+ (extraFieldBufferSize is the size of the buffer).
+ This is the Central-header version of the extra field
+ if szComment!=NULL, the comment string of the file will be copied in szComment
+ (commentBufferSize is the size of the buffer)
+*/
+
+/***************************************************************************/
+/* for reading the content of the current zipfile, you can open it, read data
+ from it, and close it (you can close it before reading all the file)
+ */
+
+int unzOpenCurrentFile(unzFile file);
+/*
+ Open for reading data the current file in the zipfile.
+ If there is no error, the return value is UNZ_OK.
+*/
+
+int unzCloseCurrentFile(unzFile file);
+/*
+ Close the file in zip opened with unzOpenCurrentFile
+ Return UNZ_CRCERROR if all the file was read but the CRC is not good
+*/
+
+
+int unzReadCurrentFile(unzFile file, voidp buf, unsigned len);
+/*
+ Read bytes from the current file (opened by unzOpenCurrentFile)
+ buf contain buffer where data must be copied
+ len the size of buf.
+
+ return the number of byte copied if somes bytes are copied
+ return 0 if the end of file was reached
+ return <0 with error code if there is an error
+ (UNZ_ERRNO for IO error, or zLib error for uncompress error)
+*/
+
+z_off_t unztell(unzFile file);
+/*
+ Give the current position in uncompressed data
+*/
+
+int unzeof(unzFile file);
+/*
+ return 1 if the end of file was reached, 0 elsewhere
+*/
+
+int unzGetLocalExtrafield(unzFile file, voidp buf, unsigned len);
+/*
+ Read extra field from the current file (opened by unzOpenCurrentFile)
+ This is the local-header version of the extra field (sometimes, there is
+ more info in the local-header version than in the central-header)
+ if buf==NULL, it return the size of the local extra field
+ if buf!=NULL, len is the size of the buffer, the extra header is copied in
+ buf.
+ the return value is the number of bytes copied in buf, or (if <0)
+ the error code
+*/
#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \
!defined(CASESENSITIVITYDEFAULT_NO)
@@ -78,45 +304,22 @@
#define UNZ_MAXFILENAMEINZIP (256)
#endif
-#ifndef ALLOC
-# define ALLOC(size) (malloc(size))
-#endif
-#ifndef TRYFREE
-# define TRYFREE(p) {if (p) free(p);}
-#endif
-
#define SIZECENTRALDIRITEM (0x2e)
#define SIZEZIPLOCALHEADER (0x1e)
-/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */
-
-#ifndef SEEK_CUR
-#define SEEK_CUR 1
-#endif
-
-#ifndef SEEK_END
-#define SEEK_END 2
-#endif
-
-#ifndef SEEK_SET
-#define SEEK_SET 0
-#endif
-
const char unz_copyright[] =
" unzip 0.15 Copyright 1998 Gilles Vollant ";
/* unz_file_info_interntal contain internal info about a file in zipfile*/
-typedef struct unz_file_info_internal_s
-{
+typedef struct {
uLong offset_curfile;/* relative offset of local header 4 bytes */
} unz_file_info_internal;
/* file_in_zip_read_info_s contain internal information about a file in zipfile,
when reading and decompress it */
-typedef struct
-{
+typedef struct {
char *read_buffer; /* internal buffer for compressed data */
z_stream stream; /* zLib stream structure for inflate */
@@ -139,8 +342,7 @@ typedef struct
/* unz_s contain internal information about the zipfile
*/
-typedef struct
-{
+typedef struct {
Common::File file; /* io structore of the zipfile */
unz_global_info gi; /* public global information */
uLong byte_before_the_zipfile; /* byte before the zipfile, (>0 for sfx)*/
@@ -166,14 +368,11 @@ typedef struct
*/
-/*local int unzlocal_getByte(Common::File &fin, int *pi)
-{
+/*static int unzlocal_getByte(Common::File &fin, int *pi) {
unsigned char c = fin.readByte();
*pi = (int)c;
return UNZ_OK;
- }
- else
- {
+ } else {
if (fin.ioFailed())
return UNZ_ERRNO;
else
@@ -185,37 +384,14 @@ typedef struct
/* ===========================================================================
Reads a long in LSB order from the given gz_stream. Sets
*/
-local int unzlocal_getShort (Common::File &fin, uLong *pX)
-{
+static int unzlocal_getShort(Common::File &fin, uLong *pX) {
*pX = fin.readUint16LE();
- return UNZ_OK;
+ return fin.ioFailed() ? UNZ_ERRNO : UNZ_OK;
}
-local int unzlocal_getLong (Common::File &fin, uLong *pX)
-{
+static int unzlocal_getLong(Common::File &fin, uLong *pX) {
*pX = fin.readUint32LE();
- return UNZ_OK;
-}
-
-/* My own strcmpi / strcasecmp */
-local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2) {
- for (;;)
- {
- char c1=*(fileName1++);
- char c2=*(fileName2++);
- if ((c1>='a') && (c1<='z'))
- c1 -= 0x20;
- if ((c2>='a') && (c2<='z'))
- c2 -= 0x20;
- if (c1=='\0')
- return ((c2=='\0') ? 0 : -1);
- if (c2=='\0')
- return 1;
- if (c1<c2)
- return -1;
- if (c1>c2)
- return 1;
- }
+ return fin.ioFailed() ? UNZ_ERRNO : UNZ_OK;
}
@@ -225,10 +401,6 @@ local int strcmpcasenosensitive_internal (const char* fileName1, const char* fil
#define CASESENSITIVITYDEFAULTVALUE 1
#endif
-#ifndef STRCMPCASENOSENTIVEFUNCTION
-#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal
-#endif
-
/*
Compare two filename (fileName1,fileName2).
If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
@@ -238,15 +410,14 @@ local int strcmpcasenosensitive_internal (const char* fileName1, const char* fil
(like 1 on Unix, 2 on Windows)
*/
-extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity)
-{
+int unzStringFileNameCompare(const char* fileName1, const char* fileName2, int iCaseSensitivity) {
if (iCaseSensitivity==0)
iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE;
if (iCaseSensitivity==1)
return strcmp(fileName1,fileName2);
- return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2);
+ return scumm_stricmp(fileName1,fileName2);
}
#define BUFREADCOMMENT (0x400)
@@ -255,8 +426,7 @@ extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, const char*
Locate the Central directory of a zipfile (at the end, just before
the global comment)
*/
-local uLong unzlocal_SearchCentralDir(Common::File &fin)
-{
+static uLong unzlocal_SearchCentralDir(Common::File &fin) {
unsigned char* buf;
uLong uSizeFile;
uLong uBackRead;
@@ -270,13 +440,12 @@ local uLong unzlocal_SearchCentralDir(Common::File &fin)
if (uMaxBack>uSizeFile)
uMaxBack = uSizeFile;
- buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+ buf = (unsigned char*)malloc(BUFREADCOMMENT+4);
if (buf==NULL)
return 0;
uBackRead = 4;
- while (uBackRead<uMaxBack)
- {
+ while (uBackRead<uMaxBack) {
uLong uReadSize,uReadPos ;
int i;
if (uBackRead+BUFREADCOMMENT>uMaxBack)
@@ -305,7 +474,7 @@ local uLong unzlocal_SearchCentralDir(Common::File &fin)
if (uPosFound!=0)
break;
}
- TRYFREE(buf);
+ free(buf);
return uPosFound;
}
@@ -318,8 +487,7 @@ local uLong unzlocal_SearchCentralDir(Common::File &fin)
Else, the return value is a unzFile Handle, usable with other function
of this unzip package.
*/
-extern unzFile ZEXPORT unzOpen (const char *path)
-{
+unzFile unzOpen(const char *path) {
unz_s *us = new unz_s;
uLong central_pos,uL;
@@ -408,8 +576,7 @@ extern unzFile ZEXPORT unzOpen (const char *path)
If there is files inside the .Zip opened with unzipOpenCurrentFile (see later),
these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
return UNZ_OK if there is no problem. */
-extern int ZEXPORT unzClose (unzFile file)
-{
+int unzClose(unzFile file) {
unz_s* s;
if (file==NULL)
return UNZ_PARAMERROR;
@@ -428,8 +595,7 @@ extern int ZEXPORT unzClose (unzFile file)
Write info about the ZipFile in the *pglobal_info structure.
No preparation of the structure is needed
return UNZ_OK if there is no problem. */
-extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info)
-{
+int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info) {
unz_s* s;
if (file==NULL)
return UNZ_PARAMERROR;
@@ -442,8 +608,7 @@ extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info
/*
Translate date/time from Dos format to tm_unz (readable more easilty)
*/
-local void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm)
-{
+static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) {
uLong uDate;
uDate = (uLong)(ulDosDate>>16);
ptm->tm_mday = (uInt)(uDate&0x1f) ;
@@ -458,7 +623,7 @@ local void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm)
/*
Get Info about the current file in the zipfile, with internal only info
*/
-local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file,
+static int unzlocal_GetCurrentFileInfoInternal(unzFile file,
unz_file_info *pfile_info,
unz_file_info_internal
*pfile_info_internal,
@@ -467,9 +632,9 @@ local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file,
void *extraField,
uLong extraFieldBufferSize,
char *szComment,
- uLong commentBufferSize));
+ uLong commentBufferSize);
-local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
+static int unzlocal_GetCurrentFileInfoInternal(unzFile file,
unz_file_info *pfile_info,
unz_file_info_internal *pfile_info_internal,
char *szFileName, uLong fileNameBufferSize,
@@ -492,8 +657,7 @@ local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
/* we check the magic */
- if (err==UNZ_OK)
- {
+ if (err==UNZ_OK) {
if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK)
err=UNZ_ERRNO;
else if (uMagic!=0x02014b50)
@@ -548,15 +712,12 @@ local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
err=UNZ_ERRNO;
lSeek+=file_info.size_filename;
- if ((err==UNZ_OK) && (szFileName!=NULL))
- {
+ if ((err==UNZ_OK) && (szFileName!=NULL)) {
uLong uSizeRead ;
- if (file_info.size_filename<fileNameBufferSize)
- {
+ if (file_info.size_filename<fileNameBufferSize) {
*(szFileName+file_info.size_filename)='\0';
uSizeRead = file_info.size_filename;
- }
- else
+ } else
uSizeRead = fileNameBufferSize;
if ((file_info.size_filename>0) && (fileNameBufferSize>0))
@@ -566,16 +727,14 @@ local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
}
- if ((err==UNZ_OK) && (extraField!=NULL))
- {
+ if ((err==UNZ_OK) && (extraField!=NULL)) {
uLong uSizeRead ;
if (file_info.size_file_extra<extraFieldBufferSize)
uSizeRead = file_info.size_file_extra;
else
uSizeRead = extraFieldBufferSize;
- if (lSeek!=0)
- {
+ if (lSeek!=0) {
s->file.seek(lSeek, SEEK_CUR);
if (s->file.ioFailed())
lSeek=0;
@@ -591,19 +750,15 @@ local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
lSeek+=file_info.size_file_extra;
- if ((err==UNZ_OK) && (szComment!=NULL))
- {
+ if ((err==UNZ_OK) && (szComment!=NULL)) {
uLong uSizeRead ;
- if (file_info.size_file_comment<commentBufferSize)
- {
+ if (file_info.size_file_comment<commentBufferSize) {
*(szComment+file_info.size_file_comment)='\0';
uSizeRead = file_info.size_file_comment;
- }
- else
+ } else
uSizeRead = commentBufferSize;
- if (lSeek!=0)
- {
+ if (lSeek!=0) {
s->file.seek(lSeek, SEEK_CUR);
if (s->file.ioFailed())
lSeek=0;
@@ -614,8 +769,7 @@ local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
if (s->file.read(szComment,(uInt)uSizeRead)!=uSizeRead)
err=UNZ_ERRNO;
lSeek+=file_info.size_file_comment - uSizeRead;
- }
- else
+ } else
lSeek+=file_info.size_file_comment;
if ((err==UNZ_OK) && (pfile_info!=NULL))
@@ -634,7 +788,7 @@ local int unzlocal_GetCurrentFileInfoInternal (unzFile file,
No preparation of the structure is needed
return UNZ_OK if there is no problem.
*/
-extern int ZEXPORT unzGetCurrentFileInfo (unzFile file,
+int unzGetCurrentFileInfo(unzFile file,
unz_file_info *pfile_info,
char *szFileName, uLong fileNameBufferSize,
void *extraField, uLong extraFieldBufferSize,
@@ -650,8 +804,7 @@ extern int ZEXPORT unzGetCurrentFileInfo (unzFile file,
Set the current file of the zipfile to the first file.
return UNZ_OK if there is no problem
*/
-extern int ZEXPORT unzGoToFirstFile (unzFile file)
-{
+int unzGoToFirstFile(unzFile file) {
int err=UNZ_OK;
unz_s* s;
if (file==NULL)
@@ -672,8 +825,7 @@ extern int ZEXPORT unzGoToFirstFile (unzFile file)
return UNZ_OK if there is no problem
return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
*/
-extern int ZEXPORT unzGoToNextFile (unzFile file)
-{
+int unzGoToNextFile(unzFile file) {
unz_s* s;
int err;
@@ -704,8 +856,7 @@ extern int ZEXPORT unzGoToNextFile (unzFile file)
UNZ_OK if the file is found. It becomes the current file.
UNZ_END_OF_LIST_OF_FILE if the file is not found
*/
-extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity)
-{
+int unzLocateFile(unzFile file, const char *szFileName, int iCaseSensitivity) {
unz_s* s;
int err;
@@ -729,8 +880,7 @@ extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCas
err = unzGoToFirstFile(file);
- while (err == UNZ_OK)
- {
+ while (err == UNZ_OK) {
char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1];
unzGetCurrentFileInfo(file,NULL,
szCurrentFileName,sizeof(szCurrentFileName)-1,
@@ -754,10 +904,9 @@ extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCas
store in *piSizeVar the size of extra info in local header
(filename and size of extra field data)
*/
-local int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar,
+static int unzlocal_CheckCurrentFileCoherencyHeader(unz_s* s, uInt* piSizeVar,
uLong *poffset_local_extrafield,
- uInt *psize_local_extrafield)
-{
+ uInt *psize_local_extrafield) {
uLong uMagic,uData,uFlags;
uLong size_filename;
uLong size_extra_field;
@@ -773,8 +922,7 @@ local int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar,
return UNZ_ERRNO;
- if (err==UNZ_OK)
- {
+ if (err==UNZ_OK) {
if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK)
err=UNZ_ERRNO;
else if (uMagic!=0x04034b50)
@@ -843,8 +991,7 @@ local int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar,
Open for reading data the current file in the zipfile.
If there is no error and the file is opened, the return value is UNZ_OK.
*/
-extern int ZEXPORT unzOpenCurrentFile (unzFile file)
-{
+int unzOpenCurrentFile (unzFile file) {
int err=UNZ_OK;
int Store;
uInt iSizeVar;
@@ -866,19 +1013,19 @@ extern int ZEXPORT unzOpenCurrentFile (unzFile file)
&offset_local_extrafield,&size_local_extrafield)!=UNZ_OK)
return UNZ_BADZIPFILE;
- pfile_in_zip_read_info = (file_in_zip_read_info_s*) ALLOC(sizeof(file_in_zip_read_info_s));
+ pfile_in_zip_read_info = (file_in_zip_read_info_s*) malloc(sizeof(file_in_zip_read_info_s));
if (pfile_in_zip_read_info==NULL)
return UNZ_INTERNALERROR;
- pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE);
+ pfile_in_zip_read_info->read_buffer=(char*)malloc(UNZ_BUFSIZE);
pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield;
pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield;
pfile_in_zip_read_info->pos_local_extrafield=0;
if (pfile_in_zip_read_info->read_buffer==NULL)
{
- TRYFREE(pfile_in_zip_read_info);
+ free(pfile_in_zip_read_info);
return UNZ_INTERNALERROR;
}
@@ -938,8 +1085,7 @@ extern int ZEXPORT unzOpenCurrentFile (unzFile file)
return <0 with error code if there is an error
(UNZ_ERRNO for IO error, or zLib error for uncompress error)
*/
-extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len)
-{
+int unzReadCurrentFile(unzFile file, voidp buf, unsigned len) {
int err=UNZ_OK;
uInt iRead = 0;
unz_s* s;
@@ -1051,8 +1197,7 @@ extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len)
/*
Give the current position in uncompressed data
*/
-extern z_off_t ZEXPORT unztell (unzFile file)
-{
+z_off_t unztell(unzFile file) {
unz_s* s;
file_in_zip_read_info_s* pfile_in_zip_read_info;
if (file==NULL)
@@ -1070,8 +1215,7 @@ extern z_off_t ZEXPORT unztell (unzFile file)
/*
return 1 if the end of file was reached, 0 elsewhere
*/
-extern int ZEXPORT unzeof (unzFile file)
-{
+int unzeof(unzFile file) {
unz_s* s;
file_in_zip_read_info_s* pfile_in_zip_read_info;
if (file==NULL)
@@ -1102,8 +1246,7 @@ extern int ZEXPORT unzeof (unzFile file)
the return value is the number of bytes copied in buf, or (if <0)
the error code
*/
-extern int ZEXPORT unzGetLocalExtrafield (unzFile file,voidp buf,unsigned len)
-{
+int unzGetLocalExtrafield(unzFile file, voidp buf, unsigned len) {
unz_s* s;
file_in_zip_read_info_s* pfile_in_zip_read_info;
uInt read_now;
@@ -1146,8 +1289,7 @@ extern int ZEXPORT unzGetLocalExtrafield (unzFile file,voidp buf,unsigned len)
Close the file in zip opened with unzipOpenCurrentFile
Return UNZ_CRCERROR if all the file was read but the CRC is not good
*/
-extern int ZEXPORT unzCloseCurrentFile (unzFile file)
-{
+int unzCloseCurrentFile(unzFile file) {
int err=UNZ_OK;
unz_s* s;
@@ -1167,13 +1309,13 @@ extern int ZEXPORT unzCloseCurrentFile (unzFile file)
}
- TRYFREE(pfile_in_zip_read_info->read_buffer);
+ free(pfile_in_zip_read_info->read_buffer);
pfile_in_zip_read_info->read_buffer = NULL;
if (pfile_in_zip_read_info->stream_initialised)
inflateEnd(&pfile_in_zip_read_info->stream);
pfile_in_zip_read_info->stream_initialised = 0;
- TRYFREE(pfile_in_zip_read_info);
+ free(pfile_in_zip_read_info);
s->pfile_in_zip_read=NULL;
@@ -1186,8 +1328,7 @@ extern int ZEXPORT unzCloseCurrentFile (unzFile file)
uSizeBuf is the size of the szComment buffer.
return the number of byte copied or an error code <0
*/
-extern int ZEXPORT unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf)
-{
+int unzGetGlobalComment(unzFile file, char *szComment, uLong uSizeBuf) {
unz_s* s;
uLong uReadThis ;
if (file==NULL)
@@ -1213,4 +1354,92 @@ extern int ZEXPORT unzGetGlobalComment (unzFile file, char *szComment, uLong uSi
return (int)uReadThis;
}
+
+namespace Common {
+
+
+/*
+class ZipArchiveMember : public ArchiveMember {
+ unzFile _zipFile;
+
+public:
+ ZipArchiveMember(FilesystemNode &node) : _node(node) {
+ }
+
+ String getName() const {
+ ...
+ }
+
+ SeekableReadStream *open() {
+ ...
+ }
+};
+*/
+
+ZipArchive::ZipArchive(const Common::String &name) {
+ _zipFile = unzOpen(name.c_str());
+}
+
+ZipArchive::~ZipArchive() {
+ unzClose(_zipFile);
+}
+
+bool ZipArchive::isOpen() const {
+ return _zipFile != 0;
+}
+
+bool ZipArchive::hasFile(const Common::String &name) {
+ return (_zipFile && unzLocateFile(_zipFile, name.c_str(), 2) == UNZ_OK);
+}
+
+int ZipArchive::getAllNames(Common::StringList &list) {
+ return 0;
+}
+
+/*
+int ZipArchive::listMembers(Common::ArchiveMemberList &list) {
+ if (!_zipFile)
+ return 0;
+
+ int matches = 0;
+ int err = unzGoToFirstFile(_zipFile);
+
+ while (err == UNZ_OK) {
+ char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1];
+ unzGetCurrentFileInfo(_zipFile, NULL,
+ szCurrentFileName, sizeof(szCurrentFileName)-1,
+ NULL, 0, NULL, 0);
+
+ szCurrentFileName
+ matches++;
+ err = unzGoToNextFile(file);
+ }
+ return 0;
+}
+*/
+
+Common::SeekableReadStream *ZipArchive::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.
+}
+
+} // End of namespace Common
+
+
#endif
diff --git a/common/unzip.h b/common/unzip.h
index 2d888fe5b1..93afd0b05b 100644
--- a/common/unzip.h
+++ b/common/unzip.h
@@ -22,286 +22,34 @@
* $Id$
*/
-/* unzip.h -- IO for uncompress .zip files using zlib
- Version 0.15 beta, Mar 19th, 1998,
-
- Copyright (C) 1998 Gilles Vollant
-
- This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g
- WinZip, InfoZip tools and compatible.
- Encryption and multi volume ZipFile (span) are not supported.
- Old compressions used by old PKZip 1.x are not supported
-
- THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE
- CAN CHANGE IN FUTURE VERSION !!
- I WAIT FEEDBACK at mail info@winimage.com
- Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution
-
- Condition of use and distribution are the same than zlib :
-
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
-
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
-
-
-*/
-/* for more info about .ZIP format, see
- ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip
- PkWare has also a specification at :
- ftp://ftp.pkware.com/probdesc.zip */
-
-#ifndef _unz_H
-#define _unz_H
-
-#include "common/scummsys.h"
+#ifndef COMMON_UNZIP_H
+#define COMMON_UNZIP_H
#ifdef USE_ZLIB
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifdef __SYMBIAN32__
-#include <zlib\zlib.h>
-#else
-#include <zlib.h>
-#endif
-
-#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
-/* like the STRICT of WIN32, we define a pointer that cannot be converted
- from (void*) without cast */
-typedef struct TagunzFile__ { int unused; } unzFile__;
-typedef unzFile__ *unzFile;
-#else
-typedef voidp unzFile;
-#endif
-
-
-#define UNZ_OK (0)
-#define UNZ_END_OF_LIST_OF_FILE (-100)
-#define UNZ_ERRNO (Z_ERRNO)
-#define UNZ_EOF (0)
-#define UNZ_PARAMERROR (-102)
-#define UNZ_BADZIPFILE (-103)
-#define UNZ_INTERNALERROR (-104)
-#define UNZ_CRCERROR (-105)
-
-/* tm_unz contain date/time info */
-typedef struct tm_unz_s
-{
- uInt tm_sec; /* seconds after the minute - [0,59] */
- uInt tm_min; /* minutes after the hour - [0,59] */
- uInt tm_hour; /* hours since midnight - [0,23] */
- uInt tm_mday; /* day of the month - [1,31] */
- uInt tm_mon; /* months since January - [0,11] */
- uInt tm_year; /* years - [1980..2044] */
-} tm_unz;
-
-/* unz_global_info structure contain global data about the ZIPfile
- These data comes from the end of central dir */
-typedef struct unz_global_info_s
-{
- uLong number_entry; /* total number of entries in
- the central dir on this disk */
- uLong size_comment; /* size of the global comment of the zipfile */
-} unz_global_info;
-
-
-/* unz_file_info contain information about a file in the zipfile */
-typedef struct unz_file_info_s
-{
- uLong version; /* version made by 2 bytes */
- uLong version_needed; /* version needed to extract 2 bytes */
- uLong flag; /* general purpose bit flag 2 bytes */
- uLong compression_method; /* compression method 2 bytes */
- uLong dosDate; /* last mod file date in Dos fmt 4 bytes */
- uLong crc; /* crc-32 4 bytes */
- uLong compressed_size; /* compressed size 4 bytes */
- uLong uncompressed_size; /* uncompressed size 4 bytes */
- uLong size_filename; /* filename length 2 bytes */
- uLong size_file_extra; /* extra field length 2 bytes */
- uLong size_file_comment; /* file comment length 2 bytes */
-
- uLong disk_num_start; /* disk number start 2 bytes */
- uLong internal_fa; /* internal file attributes 2 bytes */
- uLong external_fa; /* external file attributes 4 bytes */
-
- tm_unz tmu_date;
-} unz_file_info;
-
-extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1,
- const char* fileName2,
- int iCaseSensitivity));
-/*
- Compare two filename (fileName1,fileName2).
- If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
- If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
- or strcasecmp)
- If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
- (like 1 on Unix, 2 on Windows)
-*/
-
-
-extern unzFile ZEXPORT unzOpen OF((const char *path));
-/*
- Open a Zip file. path contain the full pathname (by example,
- on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer
- "zlib/zlib111.zip".
- If the zipfile cannot be opened (file don't exist or in not valid), the
- return value is NULL.
- Else, the return value is a unzFile Handle, usable with other function
- of this unzip package.
-*/
-
-extern int ZEXPORT unzClose OF((unzFile file));
-/*
- Close a ZipFile opened with unzipOpen.
- If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
- these files MUST be closed with unzipCloseCurrentFile before call unzipClose.
- return UNZ_OK if there is no problem. */
-
-extern int ZEXPORT unzGetGlobalInfo OF((unzFile file,
- unz_global_info *pglobal_info));
-/*
- Write info about the ZipFile in the *pglobal_info structure.
- No preparation of the structure is needed
- return UNZ_OK if there is no problem. */
-
-
-extern int ZEXPORT unzGetGlobalComment OF((unzFile file,
- char *szComment,
- uLong uSizeBuf));
-/*
- Get the global comment string of the ZipFile, in the szComment buffer.
- uSizeBuf is the size of the szComment buffer.
- return the number of byte copied or an error code <0
-*/
-
-
-/***************************************************************************/
-/* Unzip package allow you browse the directory of the zipfile */
-
-extern int ZEXPORT unzGoToFirstFile OF((unzFile file));
-/*
- Set the current file of the zipfile to the first file.
- return UNZ_OK if there is no problem
-*/
-
-extern int ZEXPORT unzGoToNextFile OF((unzFile file));
-/*
- Set the current file of the zipfile to the next file.
- return UNZ_OK if there is no problem
- return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
-*/
-
-extern int ZEXPORT unzLocateFile OF((unzFile file,
- const char *szFileName,
- int iCaseSensitivity));
-/*
- Try locate the file szFileName in the zipfile.
- For the iCaseSensitivity signification, see unzStringFileNameCompare
-
- return value :
- UNZ_OK if the file is found. It becomes the current file.
- UNZ_END_OF_LIST_OF_FILE if the file is not found
-*/
-
-
-extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file,
- unz_file_info *pfile_info,
- char *szFileName,
- uLong fileNameBufferSize,
- void *extraField,
- uLong extraFieldBufferSize,
- char *szComment,
- uLong commentBufferSize));
-/*
- Get Info about the current file
- if pfile_info!=NULL, the *pfile_info structure will contain somes info about
- the current file
- if szFileName!=NULL, the filemane string will be copied in szFileName
- (fileNameBufferSize is the size of the buffer)
- if extraField!=NULL, the extra field information will be copied in extraField
- (extraFieldBufferSize is the size of the buffer).
- This is the Central-header version of the extra field
- if szComment!=NULL, the comment string of the file will be copied in szComment
- (commentBufferSize is the size of the buffer)
-*/
-
-/***************************************************************************/
-/* for reading the content of the current zipfile, you can open it, read data
- from it, and close it (you can close it before reading all the file)
- */
-
-extern int ZEXPORT unzOpenCurrentFile OF((unzFile file));
-/*
- Open for reading data the current file in the zipfile.
- If there is no error, the return value is UNZ_OK.
-*/
-
-extern int ZEXPORT unzCloseCurrentFile OF((unzFile file));
-/*
- Close the file in zip opened with unzOpenCurrentFile
- Return UNZ_CRCERROR if all the file was read but the CRC is not good
-*/
-
-
-extern int ZEXPORT unzReadCurrentFile OF((unzFile file,
- voidp buf,
- unsigned len));
-/*
- Read bytes from the current file (opened by unzOpenCurrentFile)
- buf contain buffer where data must be copied
- len the size of buf.
-
- return the number of byte copied if somes bytes are copied
- return 0 if the end of file was reached
- return <0 with error code if there is an error
- (UNZ_ERRNO for IO error, or zLib error for uncompress error)
-*/
+#include "common/scummsys.h"
+#include "common/archive.h"
-extern z_off_t ZEXPORT unztell OF((unzFile file));
-/*
- Give the current position in uncompressed data
-*/
+typedef void *unzFile;
-extern int ZEXPORT unzeof OF((unzFile file));
-/*
- return 1 if the end of file was reached, 0 elsewhere
-*/
+namespace Common {
-extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file,
- voidp buf,
- unsigned len));
-/*
- Read extra field from the current file (opened by unzOpenCurrentFile)
- This is the local-header version of the extra field (sometimes, there is
- more info in the local-header version than in the central-header)
+class ZipArchive : public Archive {
+ void *_zipFile;
- if buf==NULL, it return the size of the local extra field
+public:
+ ZipArchive(const String &name);
+ ~ZipArchive();
+
+ bool isOpen() const;
- if buf!=NULL, len is the size of the buffer, the extra header is copied in
- buf.
- the return value is the number of bytes copied in buf, or (if <0)
- the error code
-*/
+ virtual bool hasFile(const String &name);
+ virtual int getAllNames(StringList &list); // FIXME: This one is not (yet?) implemented
+ virtual Common::SeekableReadStream *openFile(const Common::String &name);
+};
-#ifdef __cplusplus
-}
-#endif
+} // End of namespace Common
-#endif
+#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..573004c437 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:
@@ -128,20 +106,20 @@ public:
/**
* Generates a random unsigned integer in the interval [0, max].
* @param max the upper bound
- * @return a random number in the interval [0, max].
+ * @return a random number in the interval [0, max]
*/
uint getRandomNumber(uint max);
/**
- * Generates a random unsigned integer in the interval [0, 1].
+ * Generates a random bit, i.e. either 0 or 1.
* Identical to getRandomNumber(1), but faster, hopefully.
- * @return a random number in the interval [0, max].
+ * @return a random bit, either 0 or 1
*/
uint getRandomBit(void);
/**
* Generates a random unsigned integer in the interval [min, max].
* @param min the lower bound
* @param max the upper bound
- * @return a random number in the interval [min, max].
+ * @return a random number in the interval [min, max]
*/
uint getRandomNumberRng(uint min, uint max);
};
@@ -210,6 +188,7 @@ enum Platform {
kPlatformApple2GS,
kPlatformPC98,
+ kPlatformWii,
kPlatformUnknown = -1
};
diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp
index 900f2f81ab..b93a5205be 100644
--- a/common/xmlparser.cpp
+++ b/common/xmlparser.cpp
@@ -37,7 +37,8 @@ using namespace Graphics;
bool XMLParser::parserError(const char *errorString, ...) {
_state = kParserError;
- int pos = _pos;
+ int original_pos = _stream->pos();
+ int pos = original_pos;
int lineCount = 1;
int lineStart = 0;
@@ -46,18 +47,21 @@ bool XMLParser::parserError(const char *errorString, ...) {
lineCount = 0;
} else {
do {
- if (_text[pos] == '\n' || _text[pos] == '\r') {
+ if (_char == '\n' || _char == '\r') {
lineCount++;
if (lineStart == 0)
lineStart = MAX(pos + 1, _pos - 60);
}
- } while (pos-- > 0);
+
+ _stream->seek(-1, SEEK_CUR);
+
+ } while (_stream->pos() > 0);
}
char lineStr[70];
- _text.stream()->seek(lineStart, SEEK_SET);
- _text.stream()->readLine(lineStr, 70);
+ _stream->seek(original_pos - 35, SEEK_SET);
+ _stream->readLine_NEW(lineStr, 70);
for (int i = 0; i < 70; ++i)
if (lineStr[i] == '\n')
@@ -70,7 +74,7 @@ bool XMLParser::parserError(const char *errorString, ...) {
printf("%s%s%s\n", startFull ? "" : "...", lineStr, endFull ? "" : "...");
- int cursor = MIN(_pos - lineStart, 70);
+ int cursor = 35;
if (!startFull)
cursor += 3;
@@ -102,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 {
@@ -149,15 +154,20 @@ bool XMLParser::parseKeyValue(Common::String keyName) {
_token.clear();
char stringStart;
- if (_text[_pos] == '"' || _text[_pos] == '\'') {
- stringStart = _text[_pos++];
+ if (_char == '"' || _char == '\'') {
+ stringStart = _char;
+ _char = _stream->readByte();
- while (_text[_pos] && _text[_pos] != stringStart)
- _token += _text[_pos++];
+ while (_char && _char != stringStart) {
+ _token += _char;
+ _char = _stream->readByte();
+ }
- if (_text[_pos++] == 0)
+ if (_char == 0)
return false;
+ _char = _stream->readByte();
+
} else if (!parseToken()) {
return false;
}
@@ -185,7 +195,7 @@ bool XMLParser::closeKey() {
bool XMLParser::parse() {
- if (_text.ready() == false)
+ if (_stream == 0)
return parserError("XML stream not ready for reading.");
if (_XMLkeys == 0)
@@ -202,8 +212,10 @@ bool XMLParser::parse() {
_state = kParserNeedKey;
_pos = 0;
_activeKey.clear();
+
+ _char = _stream->readByte();
- while (_text[_pos] && _state != kParserError) {
+ while (_char && _state != kParserError) {
if (skipSpaces())
continue;
@@ -212,18 +224,18 @@ bool XMLParser::parse() {
switch (_state) {
case kParserNeedKey:
- if (_text[_pos++] != '<') {
+ if (_char != '<') {
parserError("Parser expecting key start.");
break;
}
- if (_text[_pos] == 0) {
+ if ((_char = _stream->readByte()) == 0) {
parserError("Unexpected end of file.");
break;
}
- if (_text[_pos] == '/' && _text[_pos + 1] != '*') {
- _pos++;
+ if (_char == '/') { // FIXME: What if it's a comment start
+ _char = _stream->readByte();
activeClosure = true;
}
@@ -262,19 +274,25 @@ bool XMLParser::parse() {
activeClosure = false;
- if (_text[_pos++] != '>')
+ if (_char != '>')
parserError("Invalid syntax in key closure.");
else
_state = kParserNeedKey;
+ _char = _stream->readByte();
break;
}
- selfClosure = (_text[_pos] == '/');
+ selfClosure = false;
+
+ if (_char == '/') { // FIXME: comment start?
+ selfClosure = true;
+ _char = _stream->readByte();
+ }
- if ((selfClosure && _text[_pos + 1] == '>') || _text[_pos] == '>') {
+ if (_char == '>') {
if (parseActiveKey(selfClosure)) {
- _pos += selfClosure ? 2 : 1;
+ _char = _stream->readByte();
_state = kParserNeedKey;
}
break;
@@ -288,11 +306,12 @@ bool XMLParser::parse() {
break;
case kParserNeedPropertyOperator:
- if (_text[_pos++] != '=')
+ if (_char != '=')
parserError("Syntax error after key name.");
else
_state = kParserNeedPropertyValue;
+ _char = _stream->readByte();
break;
case kParserNeedPropertyValue:
diff --git a/common/xmlparser.h b/common/xmlparser.h
index 4d1c8fc85d..dcbfc60c2f 100644
--- a/common/xmlparser.h
+++ b/common/xmlparser.h
@@ -40,145 +40,12 @@
namespace Common {
-/***********************************************
- **** XMLParser.cpp/h -- Generic XML Parser ****
- ***********************************************
+/*
+ XMLParser.cpp/h -- Generic XML Parser
+ =====================================
- This is a simple implementation of a generic parser which is able to
- interpret a subset of the XML language.
-
- The XMLParser class is virtual, and must be derived into a child class,
- called a Custom Parser Class, which will manage the parsed data for your
- specific needs.
-
- Custom Parser Classes have two basic requirements:
- They must inherit directly the XMLParser class, and they must define the
- parsing layout of the XML file.
-
- Declaring the XML layout is done with the help of the CUSTOM_XML_PARSER()
- macro: this macro must appear once inside the Custom Parser Class
- declaration, and takes a single parameter, the name of the Custom Parser
- Class.
-
- The macro must be followed by the actual layout of the XML files to be
- parsed, and closed with the PARSER_END() macro. The layout of XML files
- is defined by the use of 3 helper macros: XML_KEY(), KEY_END() and
- XML_PROP().
-
- Here's a sample of its usage:
-
- =========== =========== =========== =========== =========== ===========
-
- CUSTOM_XML_PARSER(ThemeParser) {
- XML_KEY(render_info)
- XML_KEY(palette)
- XML_KEY(color)
- XML_PROP(name, true)
- XML_PROP(rgb, true)
- KEY_END()
- KEY_END()
-
- XML_KEY(fonts)
- XML_KEY(font)
- XML_PROP(id, true)
- XML_PROP(type, true)
- XML_PROP(color, true)
- KEY_END()
- KEY_END()
-
- XML_KEY(defaults)
- XML_PROP(stroke, false)
- XML_PROP(shadow, false)
- XML_PROP(factor, false)
- XML_PROP(fg_color, false)
- XML_PROP(bg_color, false)
- XML_PROP(gradient_start, false)
- XML_PROP(gradient_end, false)
- XML_PROP(gradient_factor, false)
- XML_PROP(fill, false)
- KEY_END()
- KEY_END()
- } PARSER_END()
-
- =========== =========== =========== =========== =========== ===========
-
- The XML_KEY() macro takes a single argument, the name of the expected key.
- Inside the scope of each key, you may define properties for the given key
- with the XML_PROP() macro, which takes as parameters the name of the
- property and whether it's optional or required. You might also define the
- contained children keys, using the XML_KEY() macro again.
- The scope of a XML key is closed with the KEY_END() macro.
-
- Keys which may contain any kind of Property names may be defined with the
- XML_PROP_ANY() macro instead of the XML_PROP() macro. This macro takes no
- arguments.
-
- As an example, the following XML layout:
-
- XML_KEY(palette)
- XML_KEY(color)
- XML_PROP(name, true)
- XML_PROP(rgb, true)
- XML_PROP(optional_param, false)
- KEY_END()
- KEY_END()
-
- will expect to parse a syntax like this:
-
- <palette>
- <color name = "red" rgb = "255, 0, 0" />
- <color name = "blue" rgb = "0, 0, 255" optional_param = "565" />
- </palette>
-
- Once a layout has been defined, everytime a XML node (that is, a key and
- all its properties) has been parsed, a specific callback funcion is called,
- which should take care of managing the parsed data for the node.
-
- Callback functions must be explicitly declared with the following syntax:
-
- bool parserCallback_KEYNAME(ParserNode *node);
-
- A callback function is needed for each key that can be parsed, since they
- are called automatically; the function will receive a pointer to the XML
- Node that has been parsed. This XML Node has the following properties:
-
- - It's assured to be expected in the layout of the XML file (i.e.
- has the proper position and depth in the XML tree).
-
- - It's assured to contain all the required Properties that have
- been declared in the XML layout.
-
- - It's assured to contain NO unexpected properties (i.e. properties
- which haven't been declared in the XML layout).
-
- Further validation of the Node's data may be performed inside the callback
- function. Once the node has been validated and its data has been parsed/
- managed, the callback function is expected to return true.
-
- If the data in the XML Node is corrupted or there was a problem when
- parsing it, the callback function is expected to return false or,
- preferably, to throw a parserError() using the following syntax:
-
- return parserError("There was a problem in key '%s'.", arg1, ...);
-
- Also, note that the XML parser doesn't take into account the actual order
- of the keys and properties in the XML layout definition, only its layout
- and relationships.
-
- Lastly, when defining your own Custom XML Parser, further customization
- may be accomplished _optionally_ by overloading several virtual functions
- of the XMLParser class.
-
- Check the API documentation of the following functions for more info:
-
- virtual bool closedKeyCallback(ParserNode *node);
- virtual bool skipComments();
- virtual bool isValidNameChar(char c);
- virtual void cleanup();
-
- Check the sample implementation of the GUI::ThemeParser custom parser
- for a working sample of a Custom XML Parser.
-
+ External documentation available at:
+ http://www.smartlikearoboc.com/scummvm_doc/xmlparser_doc.html
*/
#define XML_KEY(keyName) {\
@@ -226,43 +93,6 @@ namespace Common {
#define PARSER_END() layout.clear(); }
-class XMLStream {
-protected:
- SeekableReadStream *_stream;
- int _pos;
-
-public:
- XMLStream() : _stream(0), _pos(0) {}
-
- ~XMLStream() {
- delete _stream;
- }
-
- SeekableReadStream *stream() {
- return _stream;
- }
-
- char operator [](int idx) {
- assert(_stream && idx >= 0);
-
- if (_pos + 1 != idx)
- _stream->seek(idx, SEEK_SET);
-
- _pos = idx;
-
- return _stream->readByte();
- }
-
- void loadStream(SeekableReadStream *s) {
- delete _stream;
- _stream = s;
- }
-
- bool ready() {
- return _stream != 0;
- }
-};
-
/**
* The base XMLParser class implements generic functionality for parsing
* XML-like files.
@@ -278,13 +108,14 @@ public:
/**
* Parser constructor.
*/
- XMLParser() : _XMLkeys(0) {}
+ XMLParser() : _XMLkeys(0), _stream(0) {}
virtual ~XMLParser() {
while (!_activeKey.empty())
delete _activeKey.pop();
delete _XMLkeys;
+ delete _stream;
for (Common::List<XMLKeyLayout*>::iterator i = _layoutList.begin();
i != _layoutList.end(); ++i)
@@ -352,7 +183,7 @@ public:
}
_fileName = filename;
- _text.loadStream(f);
+ _stream = f;
return true;
}
@@ -365,7 +196,7 @@ public:
}
_fileName = node.getName();
- _text.loadStream(f);
+ _stream = f;
return true;
}
@@ -381,13 +212,13 @@ public:
* no longer needed by the parser.
*/
bool loadBuffer(const byte *buffer, uint32 size, bool disposable = false) {
- _text.loadStream(new MemoryReadStream(buffer, size, disposable));
+ _stream = new MemoryReadStream(buffer, size, disposable);
_fileName = "Memory Stream";
return true;
}
- bool loadStream(MemoryReadStream *stream) {
- _text.loadStream(stream);
+ bool loadStream(SeekableReadStream *stream) {
+ _stream = stream;
_fileName = "Compressed File Stream";
return true;
}
@@ -492,11 +323,11 @@ protected:
* Skips spaces/whitelines etc. Returns true if any spaces were skipped.
*/
bool skipSpaces() {
- if (!isspace(_text[_pos]))
+ if (!isspace(_char))
return false;
- while (_text[_pos] && isspace(_text[_pos]))
- _pos++;
+ while (_char && isspace(_char))
+ _char = _stream->readByte();
return true;
}
@@ -508,14 +339,31 @@ protected:
* or to change the commenting syntax.
*/
virtual bool skipComments() {
- if (_text[_pos] == '/' && _text[_pos + 1] == '*') {
- _pos += 2;
- while (_text[_pos++]) {
- if (_text[_pos - 2] == '*' && _text[_pos - 1] == '/')
+ char endComment1 = 0, endComment2 = 0;
+
+ if (_char == '/') {
+ _char = _stream->readByte();
+
+ if (_char != '*') {
+ _stream->seek(-1, SEEK_CUR);
+ _char = '/';
+ return false;
+ }
+
+ _char = _stream->readByte();
+
+ while (_char) {
+ endComment1 = endComment2;
+ endComment2 = _char;
+ _char = _stream->readByte();
+
+ if (endComment1 == '*' && endComment2 == '/')
break;
- if (_text[_pos] == 0)
+
+ if (_char == 0)
parserError("Comment has no closure.");
}
+ _char = _stream->readByte();
return true;
}
@@ -527,7 +375,7 @@ protected:
* Overload this if you want to support keys with strange characters
* in their name.
*/
- virtual bool isValidNameChar(char c) {
+ virtual inline bool isValidNameChar(char c) {
return isalnum(c) || c == '_';
}
@@ -537,10 +385,13 @@ protected:
*/
bool parseToken() {
_token.clear();
- while (isValidNameChar(_text[_pos]))
- _token += _text[_pos++];
- return isspace(_text[_pos]) != 0 || _text[_pos] == '>' || _text[_pos] == '=' || _text[_pos] == '/';
+ while (isValidNameChar(_char)) {
+ _token += _char;
+ _char = _stream->readByte();
+ }
+
+ return isspace(_char) != 0 || _char == '>' || _char == '=' || _char == '/';
}
/**
@@ -599,7 +450,8 @@ protected:
private:
int _pos; /** Current position on the XML buffer. */
- XMLStream _text; /** Buffer with the text being parsed */
+ char _char;
+ SeekableReadStream *_stream;
Common::String _fileName;
ParserState _state; /** Internal state of the parser */
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