diff options
Diffstat (limited to 'common')
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 ¶ms) { 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 ¶ms); + static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, 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 ¶ms) { + 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 §ion) { 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 §ion) cons ConfigFile::Section *ConfigFile::getSection(const String §ion) { 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 §ion) { const ConfigFile::Section *ConfigFile::getSection(const String §ion) 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 |