diff --git a/common/archive.cpp b/common/archive.cpp new file mode 100644 index 00000000000..7b7c2fad8ae --- /dev/null +++ b/common/archive.cpp @@ -0,0 +1,247 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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" + +#include "engine/backend/platform/driver.h" + +namespace Common { + +GenericArchiveMember::GenericArchiveMember(String name, Archive *parent) + : _parent(parent), _name(name) { +} + +String GenericArchiveMember::getName() const { + return _name; +} + +SeekableReadStream *GenericArchiveMember::createReadStream() const { + return _parent->createReadStreamForMember(_name); +} + + +int Archive::listMatchingMembers(ArchiveMemberList &list, const String &pattern) { + // Get all "names" (TODO: "files" ?) + ArchiveMemberList allNames; + listMembers(allNames); + + int matches = 0; + + // need to match lowercase key + String lowercasePattern = pattern; + lowercasePattern.toLowercase(); + + ArchiveMemberList::iterator it = allNames.begin(); + for ( ; it != allNames.end(); ++it) { + if ((*it)->getName().matchString(lowercasePattern, true)) { + list.push_back(*it); + matches++; + } + } + + return matches; +} + + + +SearchSet::ArchiveNodeList::iterator SearchSet::find(const String &name) { + ArchiveNodeList::iterator it = _list.begin(); + for ( ; it != _list.end(); ++it) { + if (it->_name == name) + break; + } + return it; +} + +SearchSet::ArchiveNodeList::const_iterator SearchSet::find(const String &name) const { + ArchiveNodeList::const_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) { + ArchiveNodeList::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, Archive *archive, int priority, bool autoFree) { + if (find(name) == _list.end()) { + Node node(priority, name, archive, autoFree); + insert(node); + } else { + if (autoFree) + delete archive; + warning("SearchSet::add: archive '%s' already present", name.c_str()); + } + +} + +void SearchSet::addDirectory(const String &name, const String &directory, int priority, int depth) { + FSNode dir(directory); + addDirectory(name, dir, priority, depth); +} + +void SearchSet::addDirectory(const String &name, const FSNode &dir, int priority, int depth) { + if (!dir.exists() || !dir.isDirectory()) + return; + + add(name, new FSDirectory(dir, depth), priority); +} + + +void SearchSet::remove(const String &name) { + ArchiveNodeList::iterator it = find(name); + if (it != _list.end()) { + if (it->_autoFree) + delete it->_arc; + _list.erase(it); + } +} + +bool SearchSet::hasArchive(const String &name) const { + return (find(name) != _list.end()); +} + +void SearchSet::clear() { + for (ArchiveNodeList::iterator i = _list.begin(); i != _list.end(); ++i) { + if (i->_autoFree) + delete i->_arc; + } + + _list.clear(); +} + +void SearchSet::setPriority(const String &name, int priority) { + ArchiveNodeList::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; + + ArchiveNodeList::iterator it = _list.begin(); + for ( ; it != _list.end(); ++it) { + if (it->_arc->hasFile(name)) + return true; + } + + return false; +} + +int SearchSet::listMatchingMembers(ArchiveMemberList &list, const String &pattern) { + int matches = 0; + + ArchiveNodeList::iterator it = _list.begin(); + for ( ; it != _list.end(); ++it) + matches += it->_arc->listMatchingMembers(list, pattern); + + return matches; +} + +int SearchSet::listMembers(ArchiveMemberList &list) { + int matches = 0; + + ArchiveNodeList::iterator it = _list.begin(); + for ( ; it != _list.end(); ++it) + matches += it->_arc->listMembers(list); + + return matches; +} + +ArchiveMemberPtr SearchSet::getMember(const String &name) { + if (name.empty()) + return ArchiveMemberPtr(); + + ArchiveNodeList::iterator it = _list.begin(); + for ( ; it != _list.end(); ++it) { + if (it->_arc->hasFile(name)) + return it->_arc->getMember(name); + } + + return ArchiveMemberPtr(); +} + +SeekableReadStream *SearchSet::createReadStreamForMember(const String &name) const { + if (name.empty()) + return 0; + + ArchiveNodeList::const_iterator it = _list.begin(); + for ( ; it != _list.end(); ++it) { + SeekableReadStream *stream = it->_arc->createReadStreamForMember(name); + if (stream) + return stream; + } + + return 0; +} + + +DECLARE_SINGLETON(SearchManager); + +SearchManager::SearchManager() { + clear(); // Force a reset +} + +void SearchManager::clear() { + SearchSet::clear(); + + // Always keep system specific archives in the SearchManager. + // But we give them a lower priority than the default priority (which is 0), + // so that archives added by client code are searched first. + if (g_driver) + g_driver->addSysArchivesToSearchSet(*this, -1); + + // Add the current dir as a very last resort. + // See also bug #2137680. + addDirectory(".", ".", -2); +} + +} // namespace Common diff --git a/common/archive.h b/common/archive.h new file mode 100644 index 00000000000..fd462f54654 --- /dev/null +++ b/common/archive.h @@ -0,0 +1,224 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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_ARCHIVE_H +#define COMMON_ARCHIVE_H + +#include "common/str.h" +#include "common/hash-str.h" +#include "common/list.h" +#include "common/ptr.h" +#include "common/singleton.h" + +namespace Common { + +class FSNode; +class SeekableReadStream; + + +/** + * ArchiveMember is an abstract interface to represent elements inside + * implementations of Archive. + * + * Archive subclasses must provide their own implementation of ArchiveMember, + * and use it when serving calls to listMembers() and listMatchingMembers(). + * Alternatively, the GenericArchiveMember below can be used. + */ +class ArchiveMember { +public: + virtual ~ArchiveMember() { } + virtual SeekableReadStream *createReadStream() const = 0; + virtual String getName() const = 0; + virtual String getDisplayName() const { return getName(); } +}; + +typedef SharedPtr ArchiveMemberPtr; +typedef List ArchiveMemberList; + +class Archive; + +/** + * Simple ArchiveMember implementation which allows + * creation of ArchiveMember compatible objects via + * a simple Archive and name pair. + * + * Note that GenericArchiveMember objects will not + * be working anymore after the 'parent' object + * is destroyed. + */ +class GenericArchiveMember : public ArchiveMember { + Archive *_parent; + String _name; +public: + GenericArchiveMember(String name, Archive *parent); + String getName() const; + SeekableReadStream *createReadStream() const; +}; + + +/** + * 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 createReadStreamForMember. + * Must not remove elements from the list. + * + * @return the number of names added to list + */ + virtual int listMatchingMembers(ArchiveMemberList &list, const String &pattern); + + /** + * Add all the names present in the Archive to list. Returned + * names can be used as parameters to createReadStreamForMember. + * Must not remove elements from the list. + * + * @return the number of names added to list + */ + virtual int listMembers(ArchiveMemberList &list) = 0; + + /** + * Returns a ArchiveMember representation of the given file. + */ + virtual ArchiveMemberPtr getMember(const String &name) = 0; + + /** + * Create a stream bound to a member in the archive. If no member with the + * specified name exists, then 0 is returned. + * @return the newly created input stream + */ + virtual SeekableReadStream *createReadStreamForMember(const String &name) const = 0; +}; + + +/** + * 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 { + int _priority; + String _name; + Archive *_arc; + bool _autoFree; + Node(int priority, const String &name, Archive *arc, bool autoFree) + : _priority(priority), _name(name), _arc(arc), _autoFree(autoFree) { + } + }; + typedef List ArchiveNodeList; + ArchiveNodeList _list; + + ArchiveNodeList::iterator find(const String &name); + ArchiveNodeList::const_iterator find(const String &name) const; + + // Add an archive keeping the list sorted by ascending priorities. + void insert(const Node& node); + +public: + virtual ~SearchSet() { clear(); } + + /** + * Add a new archive to the searchable set. + */ + void add(const String& name, Archive *arch, int priority = 0, bool autoFree = true); + + /** + * Create and add a FSDirectory by name + */ + void addDirectory(const String &name, const String &directory, int priority = 0, int depth = 1); + + /** + * Create and add a FSDirectory by FSNode + */ + void addDirectory(const String &name, const FSNode &directory, int priority = 0, int depth = 1); + + /** + * 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. + */ + virtual void clear(); + + /** + * Change the order of searches. + */ + void setPriority(const String& name, int priority); + + virtual bool hasFile(const String &name); + virtual int listMatchingMembers(ArchiveMemberList &list, const String &pattern); + virtual int listMembers(ArchiveMemberList &list); + + virtual ArchiveMemberPtr getMember(const String &name); + + /** + * Implements createReadStreamForMember from Archive base class. The current policy is + * opening the first file encountered that matches the name. + */ + virtual SeekableReadStream *createReadStreamForMember(const String &name) const; +}; + + +class SearchManager : public Singleton, public SearchSet { +public: + + /** + * Resets the search manager to the default list of search paths (system + * specific dirs + current dir). + */ + virtual void clear(); + +private: + friend class Common::Singleton; + SearchManager(); +}; + +/** Shortcut for accessing the search manager. */ +#define SearchMan Common::SearchManager::instance() + +} // namespace Common + +#endif diff --git a/common/config-file.cpp b/common/config-file.cpp index af0ca978608..76a006cd6e5 100644 --- a/common/config-file.cpp +++ b/common/config-file.cpp @@ -60,7 +60,7 @@ void ConfigFile::clear() { bool ConfigFile::loadFromFile(const String &filename) { File file; - if (file.open(filename, File::kFileReadMode)) + if (file.open(filename)) return loadFromStream(file); else return false; @@ -70,6 +70,7 @@ bool ConfigFile::loadFromSaveFile(const char *filename) { SaveFileManager *saveFileMan = g_driver->getSavefileManager(); SeekableReadStream *loadFile; + assert(saveFileMan); if (!(loadFile = saveFileMan->openForLoading(filename))) return false; @@ -79,7 +80,6 @@ bool ConfigFile::loadFromSaveFile(const char *filename) { } bool ConfigFile::loadFromStream(SeekableReadStream &stream) { - char buf[MAXLINELEN]; Section section; KeyValue kv; String comment; @@ -88,22 +88,25 @@ 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; #ifdef _WIN32 comment += "\r\n"; #else comment += "\n"; #endif - } 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". // @@ -111,15 +114,15 @@ 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; #ifdef _WIN32 comment += "\r\n"; #else comment += "\n"; #endif - } 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). @@ -131,23 +134,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)); + // This line should be a line with a 'key=value' pair, or an empty one. - // Skip empty lines + // Skip leading whitespaces + const char *t = line.c_str(); + while (isspace(*t)) + t++; + + // Skip empty lines / lines with only whitespace if (*t == 0) continue; @@ -156,14 +161,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); - kv.key = rtrim(t); - kv.value = ltrim(p + 1); + // Extract the key/value pair + kv.key = String(t, p); + kv.value = String(p + 1); + + // Trim of spaces + kv.key.trim(); + kv.value.trim(); + + // Store comment kv.comment = comment; comment.clear(); @@ -181,8 +192,8 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { } bool ConfigFile::saveToFile(const String &filename) { - File file; - if (file.open(filename, File::kFileWriteMode)) + DumpFile file; + if (file.open(filename)) return saveToStream(file); else return false; @@ -192,6 +203,7 @@ bool ConfigFile::saveToSaveFile(const char *filename) { SaveFileManager *saveFileMan = g_driver->getSavefileManager(); WriteStream *saveFile; + assert(saveFileMan); if (!(saveFile = saveFileMan->openForSaving(filename))) return false; @@ -245,7 +257,7 @@ bool ConfigFile::saveToStream(WriteStream &stream) { void ConfigFile::removeSection(const String §ion) { assert(isValidName(section)); for (List
::iterator i = _sections.begin(); i != _sections.end(); ++i) { - if (!strcasecmp(section.c_str(), i->name.c_str())) { + if (section.equalsIgnoreCase(i->name)) { _sections.erase(i); return; } @@ -338,7 +350,7 @@ const ConfigFile::SectionKeyList ConfigFile::getKeys(const String §ion) cons ConfigFile::Section *ConfigFile::getSection(const String §ion) { for (List
::iterator i = _sections.begin(); i != _sections.end(); ++i) { - if (!strcasecmp(section.c_str(), i->name.c_str())) { + if (section.equalsIgnoreCase(i->name)) { return &(*i); } } @@ -347,7 +359,7 @@ ConfigFile::Section *ConfigFile::getSection(const String §ion) { const ConfigFile::Section *ConfigFile::getSection(const String §ion) const { for (List
::const_iterator i = _sections.begin(); i != _sections.end(); ++i) { - if (!strcasecmp(section.c_str(), i->name.c_str())) { + if (section.equalsIgnoreCase(i->name)) { return &(*i); } } @@ -360,7 +372,7 @@ bool ConfigFile::Section::hasKey(const String &key) const { const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const { for (List::const_iterator i = keys.begin(); i != keys.end(); ++i) { - if (!strcasecmp(key.c_str(), i->key.c_str())) { + if (key.equalsIgnoreCase(i->key)) { return &(*i); } } @@ -369,7 +381,7 @@ const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const void ConfigFile::Section::setKey(const String &key, const String &value) { for (List::iterator i = keys.begin(); i != keys.end(); ++i) { - if (!strcasecmp(key.c_str(), i->key.c_str())) { + if (key.equalsIgnoreCase(i->key)) { i->value = value; return; } @@ -383,7 +395,7 @@ void ConfigFile::Section::setKey(const String &key, const String &value) { void ConfigFile::Section::removeKey(const String &key) { for (List::iterator i = keys.begin(); i != keys.end(); ++i) { - if (!strcasecmp(key.c_str(), i->key.c_str())) { + if (key.equalsIgnoreCase(i->key)) { keys.erase(i); return; } diff --git a/common/config-file.h b/common/config-file.h index 21bec845b8c..584aeeec894 100644 --- a/common/config-file.h +++ b/common/config-file.h @@ -53,8 +53,6 @@ namespace Common { */ class ConfigFile { public: - typedef HashMap StringSet; - struct KeyValue { String key; String value; @@ -120,7 +118,6 @@ public: const SectionList getSections() const { return _sections; } const SectionKeyList getKeys(const String §ion) const; - void listSections(StringSet &set); void listKeyValues(StringMap &kv); private: diff --git a/common/config-manager.cpp b/common/config-manager.cpp index 1b59b635be4..1729b22f9d2 100644 --- a/common/config-manager.cpp +++ b/common/config-manager.cpp @@ -31,30 +31,13 @@ #include "common/config-manager.h" #include "common/file.h" +#include "common/fs.h" #include "common/util.h" +#include "engine/backend/platform/driver.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/Residual Preferences" -#else -#define DEFAULT_CONFIG_FILE ".residualrc" -#endif -#else -#define DEFAULT_CONFIG_FILE "residual.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,230 +68,182 @@ 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 + assert(g_driver); + SeekableReadStream *stream = g_driver->createConfigReadStream(); + _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); - strcat(configFile, "\\Residual"); - CreateDirectory(configFile, NULL); - strcat(configFile, "\\" DEFAULT_CONFIG_FILE); - } else { - // Check windows directory - GetWindowsDirectory(configFile, MAXPATHLEN); - strcat(configFile, "\\" DEFAULT_CONFIG_FILE); - } + // ... and close it again. + delete stream; - #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) { + _filename = filename; + + FSNode node(filename); + File cfg_file; + if (!cfg_file.open(node)) { + printf("Creating configuration file: %s\n", filename.c_str()); + } else { + 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(); - - _filename = filename; _domainSaveOrder.clear(); - loadFile(_filename); - printf("Using configuration file: %s\n", _filename.c_str()); -} -void ConfigManager::loadFile(const String &filename) { - File cfg_file; + // TODO: Detect if a domain occurs multiple times (or likewise, if + // a key occurs multiple times inside one domain). - if (!cfg_file.open(filename)) { - printf("Creating configuration file: %s\n", filename.c_str()); - } else { - String domain; - String comment; - int lineno = 0; + while (!stream.eos() && !stream.ioFailed()) { + lineno++; - // TODO: Detect if a domain occurs multiple times (or likewise, if - // a key occurs multiple times inside one domain). + // Read a line + String line = stream.readLine(); - while (!cfg_file.eof() && !cfg_file.ioFailed()) { - lineno++; - - // Read a line - String line; + 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; #ifdef _WIN32 - while (line.lastChar() != '\r' && line.lastChar() != '\n') { + comment += "\r\n"; #else - while (line.lastChar() != '\n') { + comment += "\n"; #endif - char buf[MAXLINELEN]; - if (!cfg_file.readLine_NEW(buf, MAXLINELEN)) - break; - line += buf; - } + } 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 (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++; + if (*p == '\0') + error("Config file buggy: missing ] in line %d", lineno); + else if (*p != ']') + error("Config file buggy: Invalid character '%c' occurred in section name in line %d", *p, lineno); - 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); - } + domain = String(line.c_str() + 1, p); - // Store domain comment - if (domain == kApplicationDomain) { - _appDomain.setDomainComment(comment); - } else { - _gameDomains[domain].setDomainComment(comment); - } - comment.clear(); - - _domainSaveOrder.push_back(domain); + // Store domain comment + if (domain == kApplicationDomain) { + _appDomain.setDomainComment(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].setDomainComment(comment); } + comment.clear(); + + _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 { + _gameDomains[domain].setKVComment(key, comment); + } + comment.clear(); } } } void ConfigManager::flushToDisk() { #ifndef __DC__ - File cfg_file; + WriteStream *stream; -// TODO -// if (!willwrite) -// return; - - if (!cfg_file.open(_filename, File::kFileWriteMode)) { - warning("Unable to write configuration file: %s", _filename.c_str()); + if (_filename.empty()) { + // Write to the default config file + assert(g_driver); + stream = g_driver->createConfigWriteStream(); + 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; } - DomainMap::const_iterator d; + stream = dump; + } - - // 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); + // 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(*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__ } @@ -316,6 +251,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) @@ -646,6 +587,10 @@ void ConfigManager::addGameDomain(const String &domName) { // the given name already exists? _gameDomains[domName]; + + // Add it to the _domainSaveOrder, if it's not already in there + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end()) + _domainSaveOrder.push_back(domName); } void ConfigManager::removeGameDomain(const String &domName) { diff --git a/common/config-manager.h b/common/config-manager.h index 1fbdf00c12f..182b7724923 100644 --- a/common/config-manager.h +++ b/common/config-manager.h @@ -35,6 +35,7 @@ namespace Common { class WriteStream; +class SeekableReadStream; /** @@ -143,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; 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/error.h b/common/error.h index be025bbabae..cc7e58a156a 100644 --- a/common/error.h +++ b/common/error.h @@ -26,22 +26,45 @@ #ifndef COMMON_ERROR_H #define COMMON_ERROR_H -/** - * This file contains enums with error codes commonly used. - */ +namespace Common { /** - * Errors used in the SaveFileManager class. + * This file contains an enum with commonly used error codes. */ -enum SFMError { - SFM_NO_ERROR, //Default state, indicates no error has been recorded - SFM_DIR_ACCESS, //stat(), mkdir()::EACCES: Search or write permission denied - SFM_DIR_LINKMAX, //mkdir()::EMLINK: The link count of the parent directory would exceed {LINK_MAX} - SFM_DIR_LOOP, //stat(), mkdir()::ELOOP: Too many symbolic links encountered while traversing the path - SFM_DIR_NAMETOOLONG, //stat(), mkdir()::ENAMETOOLONG: The path name is too long - SFM_DIR_NOENT, //stat(), mkdir()::ENOENT: A component of the path path does not exist, or the path is an empty string - SFM_DIR_NOTDIR, //stat(), mkdir()::ENOTDIR: A component of the path prefix is not a directory - SFM_DIR_ROFS //mkdir()::EROFS: The parent directory resides on a read-only file system + + + +/** + * Error codes which may be reported by plugins under various circumstances. + * + * @todo Clarify the names; add more codes, resp. verify all existing ones are acutally useful. + * Also, try to avoid overlap. + * @todo Maybe introduce a naming convention? E.g. k-NOUN/ACTION-CONDITION-Error, so + * kPathInvalidError would be correct, but these would not be: kInvalidPath, + * kPathInvalid, kPathIsInvalid, kInvalidPathError + */ +enum Error { + kNoError = 0, //!< No error occured + kInvalidPathError, //!< Engine initialization: Invalid game path was passed + kNoGameDataFoundError, //!< Engine initialization: No game data was found in the specified location + kUnsupportedGameidError, //!< Engine initialization: Gameid not supported by this (Meta)Engine + + + kReadPermissionDenied, //!< Unable to read data due to missing read permission + kWritePermissionDenied, //!< Unable to write data due to missing write permission + + // The following three overlap a bit with kInvalidPathError and each other. Which to keep? + kPathDoesNotExist, //!< The specified path does not exist + kPathNotDirectory, //!< The specified path does not point to a directory + kPathNotFile, //!< The specified path does not point to a file + + kCreatingFileFailed, + kReadingFailed, //!< Failed creating a (savestate) file + kWritingFailed, //!< Failure to write data -- disk full? + + kUnknownError //!< Catch-all error, used if no other error code matches }; +} // End of namespace Common + #endif //COMMON_ERROR_H diff --git a/common/file.cpp b/common/file.cpp index 113f32af597..514740a182e 100644 --- a/common/file.cpp +++ b/common/file.cpp @@ -23,439 +23,98 @@ * */ +#include "common/archive.h" +#include "common/debug.h" #include "common/file.h" #include "common/fs.h" -#include "common/hashmap.h" #include "common/util.h" -#include "common/debug.h" -#include "common/hash-str.h" -#include - -#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) // not used - -#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 namespace Common { -typedef HashMap 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 = NULL; - String dirBuf(directory); - String fileBuf(filename); - - if (fileBuf == "(stdin)") { - return stdin; - } else if (fileBuf == "(stdout)") { - return stdout; - } else if (fileBuf == "(stderr)") { - return stderr; - } - if (file) - return file; - - -#if !defined(__GP32__) && !defined(PALMOS_MODE) - // Add a trailing slash, if necessary. - if (!dirBuf.empty()) { - const char c = dirBuf.lastChar(); - if (c != ':' && c != '/' && c != '\\') - dirBuf += '/'; - } -#endif - - // Append the filename to the path string - String pathBuf(dirBuf); - pathBuf += fileBuf; - - // - // Try to open the file normally - // - file = fopen(pathBuf.c_str(), mode); - - // - // Try again, with file name converted to upper case - // - if (!file) { - fileBuf.toUppercase(); - pathBuf = dirBuf + fileBuf; - file = fopen(pathBuf.c_str(), mode); - } - - // - // Try again, with file name converted to lower case - // - if (!file) { - fileBuf.toLowercase(); - pathBuf = dirBuf + fileBuf; - file = fopen(pathBuf.c_str(), mode); - } - - // - // Try again, with file name capitalized - // - if (!file) { - fileBuf.toLowercase(); - fileBuf.setChar(toupper(fileBuf[0]),0); - pathBuf = dirBuf + fileBuf; - file = fopen(pathBuf.c_str(), mode); - } - -#ifdef __amigaos4__ - // - // Work around for possibility that someone uses AmigaOS "newlib" build with SmartFileSystem (blocksize 512 bytes), leading - // to buffer size being only 512 bytes. "Clib2" sets the buffer size to 8KB, resulting smooth movie playback. This forces the buffer - // to be enough also when using "newlib" compile on SFS. - // - if (file) { - setvbuf(file, NULL, _IOFBF, 8192); - } -#endif - - return file; -} - void File::addDefaultDirectory(const String &directory) { - FilesystemNode dir(directory); - addDefaultDirectoryRecursive(dir, 1); + FSNode dir(directory); + addDefaultDirectory(dir); } -void File::addDefaultDirectoryRecursive(const String &directory, int level, const String &prefix) { - FilesystemNode dir(directory); - addDefaultDirectoryRecursive(dir, level, prefix); -} - -void File::addDefaultDirectory(const FilesystemNode &directory) { - addDefaultDirectoryRecursive(directory, 1); -} - -void File::addDefaultDirectoryRecursive(const FilesystemNode &dir, int level, const String &prefix) { - if (level <= 0) - 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 (!_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(); - } - } - } -} - -void File::resetDefaultDirectories() { - delete _defaultDirectories; - delete _filesMap; - - _defaultDirectories = 0; - _filesMap = 0; +void File::addDefaultDirectory(const FSNode &dir) { + if (dir.exists() && dir.isDirectory()) + SearchMan.addDirectory(dir.getPath(), dir); } File::File() - : _handle(0), _ioFailed(false) { + : _handle(0) { } -//#define DEBUG_FILE_REFCOUNT - File::~File() { -#ifdef DEBUG_FILE_REFCOUNT - warning("File::~File on file '%s'", _name.c_str()); -#endif close(); } +bool File::open(const String &filename) { + return open(filename, SearchMan); +} -bool File::open(const String &filename, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); +bool File::open(const String &filename, Archive &archive) { + assert(!filename.empty()); + assert(!_handle); - if (filename.empty()) { - error("File::open: No filename was specified"); - } - - if (_handle) { - error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str()); - } - - _name.clear(); clearIOFailed(); - String fname(filename); - fname.toLowercase(); - - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; - if (mode == kFileWriteMode) { - _handle = fopenNoCase(filename, "", modeStr); - } else if (_filesMap && _filesMap->contains(fname)) { - fname = (*_filesMap)[fname]; - debug(3, "Opening hashed: %s", fname.c_str()); - _handle = fopen(fname.c_str(), modeStr); - } else if (_filesMap && _filesMap->contains(fname + ".")) { + SeekableReadStream *stream = 0; + + if ((stream = archive.createReadStreamForMember(filename))) { + debug(3, "Opening hashed: %s", filename.c_str()); + } else if ((stream = archive.createReadStreamForMember(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(), modeStr); - } 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, modeStr); - } - } - - // Last resort: try the current directory - if (_handle == NULL) - _handle = fopenNoCase(filename, "", modeStr); - - // 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, modeStr); - } - CFRelease(fileUrl); - } - CFRelease(cfFileName); - } -#endif - + debug(3, "Opening hashed: %s.", filename.c_str()); } - if (_handle == NULL) { - if (mode == kFileReadMode) - debug(2, "File %s not found", filename.c_str()); - else - debug(2, "File %s not opened", filename.c_str()); - return false; - } - - - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif - - return true; + return open(stream, filename); } -bool File::open(const FilesystemNode &node, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); +bool File::open(const FSNode &node) { + assert(!_handle); if (!node.exists()) { - warning("File::open: Trying to open a FilesystemNode which does not exist"); + warning("File::open: '%s' does not exist", node.getPath().c_str()); 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"); - 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()); - - if (_handle) { - error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str()); - } - - clearIOFailed(); - _name.clear(); - - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; - - _handle = fopen(node.getPath().c_str(), modeStr); - - if (_handle == NULL) { - if (mode == kFileReadMode) - debug(2, "File %s not found", filename.c_str()); - else - debug(2, "File %s not opened", filename.c_str()); + warning("File::open: '%s' is a directory", node.getPath().c_str()); return false; } - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif - - return true; + SeekableReadStream *stream = node.createReadStream(); + return open(stream, node.getPath()); } +bool File::open(SeekableReadStream *stream, const Common::String &name) { + assert(!_handle); + clearIOFailed(); + + if (stream) { + _handle = stream; + _name = name; + } else { + debug(2, "File::open: opening '%s' failed", name.c_str()); + } + return _handle != NULL; +} + + 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(); + if (SearchMan.hasFile(filename)) { + return true; + } else if (SearchMan.hasFile(filename + ".")) { + // WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails" + // sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot) + return true; } - // 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(); - } - } - - //Try opening the file inside the local directory as a last resort - File tmp; - return tmp.open(filename, kFileReadMode); + return false; } void File::close() { - if (_handle && !(_handle == stdin || _handle == stdout || _handle == stderr)) - fclose((FILE *)_handle); + delete _handle; _handle = NULL; } @@ -465,90 +124,108 @@ 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 { - if (_handle == NULL) { - error("File::eof: File is not open!"); - return false; - } - - return feof((FILE *)_handle) != 0; +bool File::err() const { + assert(_handle); + return _handle->err(); } -uint32 File::pos() const { - if (_handle == NULL) { - error("File::pos: File is not open!"); - return 0; - } - - return ftell((FILE *)_handle); +void File::clearErr() { + assert(_handle); + _handle->clearErr(); } -uint32 File::size() const { - if (_handle == NULL) { - error("File::size: File is not open!"); - return 0; - } - - uint32 oldPos = ftell((FILE *)_handle); - fseek((FILE *)_handle, 0, SEEK_END); - uint32 length = ftell((FILE *)_handle); - fseek((FILE *)_handle, oldPos, SEEK_SET); - - return length; +bool File::eos() const { + assert(_handle); + return _handle->eos(); } -void File::seek(int32 offs, int whence) { - if (_handle == NULL) { - error("File::seek: File is not open!"); - return; - } +int32 File::pos() const { + assert(_handle); + return _handle->pos(); +} - if (fseek((FILE *)_handle, offs, whence) != 0) - clearerr((FILE *)_handle); +int32 File::size() const { + assert(_handle); + return _handle->size(); +} + +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; - - if (_handle == NULL) { - error("File::read: File is not open!"); - return 0; - } - - if (len == 0) - return 0; - - real_len = fread(ptr2, 1, len, (FILE *)_handle); - if (real_len < len) { - _ioFailed = true; - } - - return real_len; + assert(_handle); + return _handle->read(ptr, len); } -uint32 File::write(const void *ptr, uint32 len) { - if (_handle == NULL) { - error("File::write: File is not open!"); - return 0; + +DumpFile::DumpFile() : _handle(0) { +} + +DumpFile::~DumpFile() { + close(); +} + +bool DumpFile::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); + + FSNode node(filename); + return open(node); +} + +bool DumpFile::open(const FSNode &node) { + assert(!_handle); + + if (node.isDirectory()) { + warning("DumpFile::open: FSNode is a directory"); + return false; } - if (len == 0) - return 0; + _handle = node.createWriteStream(); - if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) { - _ioFailed = true; - } + if (_handle == NULL) + debug(2, "File %s not found", node.getName().c_str()); - return len; + return _handle != NULL; +} + +void DumpFile::close() { + delete _handle; + _handle = NULL; +} + +bool DumpFile::isOpen() const { + return _handle != NULL; +} + +bool DumpFile::err() const { + assert(_handle); + return _handle->ioFailed(); +} + +void DumpFile::clearErr() { + assert(_handle); + _handle->clearIOFailed(); +} + +uint32 DumpFile::write(const void *ptr, uint32 len) { + assert(_handle); + return _handle->write(ptr, len); +} + +bool DumpFile::flush() { + assert(_handle); + return _handle->flush(); } } // End of namespace Common diff --git a/common/file.h b/common/file.h index 29b09520f40..e0f83edf584 100644 --- a/common/file.h +++ b/common/file.h @@ -27,45 +27,30 @@ #define COMMON_FILE_H #include "common/sys.h" +#include "common/noncopyable.h" #include "common/str.h" #include "common/stream.h" -class FilesystemNode; - namespace Common { -class File : public SeekableReadStream, public WriteStream { +class Archive; +class FSNode; + +/** + * 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; + SeekableReadStream *_handle; - /** Status flag which tells about recent I/O failures. */ - bool _ioFailed; - - /** The name of this file, for debugging. */ + /** The name of this file, kept for debugging purposes. */ String _name; -private: - // Disallow copying File objects. There is not strict reason for this, - // except that so far we never had real need for such a feature, and - // code that accidentally copied File objects tended to break in strange - // ways. - File(const File &f); - File &operator =(const File &f); - public: - enum AccessMode { - kFileReadMode = 1, - kFileWriteMode = 2 - }; static void addDefaultDirectory(const String &directory); - static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = ""); - - static void addDefaultDirectory(const FilesystemNode &directory); - static void addDefaultDirectoryRecursive(const FilesystemNode &directory, int level = 4, const String &prefix = ""); - - static void resetDefaultDirectories(); + static void addDefaultDirectory(const FSNode &directory); File(); virtual ~File(); @@ -75,14 +60,56 @@ public: * (those were/are added by addDefaultDirectory and/or * addDefaultDirectoryRecursive). * - * @param filename: the file to check for - * @return: true if the file exists, else false + * @param filename the file to check for + * @return true if the file exists, false otherwise */ static bool exists(const String &filename); - virtual bool open(const String &filename, AccessMode mode = kFileReadMode); - virtual bool open(const FilesystemNode &node, AccessMode mode = kFileReadMode); + /** + * Try to open the file with the given filename, by searching SearchMan. + * @note Must not be called if this file already is open (i.e. if isOpen returns true). + * + * @param filename the name of the file to open + * @return true if file was opened successfully, false otherwise + */ + virtual bool open(const String &filename); + /** + * Try to open the file with the given filename from within the given archive. + * @note Must not be called if this file already is open (i.e. if isOpen returns true). + * + * @param filename the name of the file to open + * @param archive the archive in which to search for the file + * @return true if file was opened successfully, false otherwise + */ + virtual bool open(const String &filename, Archive &archive); + + /** + * Try to open the file corresponding to the give node. Will check whether the + * node actually refers to an existing file (and not a directory), and handle + * those cases gracefully. + * @note Must not be called if this file already is open (i.e. if isOpen returns true). + * + * @param filename the name of the file to open + * @param archive the archive in which to search for the file + * @return true if file was opened successfully, false otherwise + */ + virtual bool open(const FSNode &node); + + /** + * Try to 'open' the given stream. That is, we just wrap around it, and if stream + * is a NULL pointer, we gracefully treat this as if opening failed. + * @note Must not be called if this file already is open (i.e. if isOpen returns true). + * + * @param stream a pointer to a SeekableReadStream, or 0 + * @param name a string describing the 'file' corresponding to stream + * @return true if stream was non-zero, false otherwise + */ + virtual bool open(SeekableReadStream *stream, const Common::String &name); + + /** + * Close the file, if open. + */ virtual void close(); /** @@ -93,28 +120,58 @@ public: bool isOpen() const; /** - * Returns the filename of the opened file. + * Returns the filename of the opened file for debugging purposes. * * @return: the filename */ - const char *name() const { return _name.c_str(); } + const char *getName() const { return _name.c_str(); } bool ioFailed() const; void clearIOFailed(); - bool eos() const { return eof(); } + bool err() const; + void clearErr(); + bool eos() const; + + virtual int32 pos() const; + virtual int32 size() const; + bool seek(int32 offs, int whence = SEEK_SET); + uint32 read(void *dataPtr, uint32 dataSize); +}; + + +/** + * TODO: document this class + * + * Some design ideas: + * - automatically drop all files into dumps/ dir? Might not be desired in all cases + */ +class DumpFile : public WriteStream, public NonCopyable { +protected: + /** File handle to the actual file; 0 if no file is open. */ + WriteStream *_handle; + +public: + DumpFile(); + virtual ~DumpFile(); + + virtual bool open(const String &filename); + virtual bool open(const FSNode &node); + + virtual void close(); /** - * Checks for end of file. + * Checks if the object opened a file successfully. * - * @return: true if the end of file is reached, false otherwise. + * @return: true if any file is opened, false otherwise. */ - virtual bool eof() const; + bool isOpen() const; - virtual uint32 pos() const; - virtual uint32 size() const; - void seek(int32 offs, int whence = SEEK_SET); - uint32 read(void *dataPtr, uint32 dataSize); - uint32 write(const void *dataPtr, uint32 dataSize); + bool err() const; + void clearErr(); + + virtual uint32 write(const void *dataPtr, uint32 dataSize); + + virtual bool flush(); }; } // End of namespace Common diff --git a/common/fs.cpp b/common/fs.cpp index fcc12a3e89a..f6216a36028 100644 --- a/common/fs.cpp +++ b/common/fs.cpp @@ -8,18 +8,19 @@ * 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/util.h" @@ -27,81 +28,51 @@ #include "engine/backend/fs/abstract-fs.h" #include "engine/backend/fs/fs-factory.h" -static bool matchString(const char *str, const char *pat) { - const char *p = 0; - const char *q = 0; +namespace Common { - 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++; - } - } +FSNode::FSNode() { } -FilesystemNode::FilesystemNode() { -} - -FilesystemNode::FilesystemNode(AbstractFilesystemNode *realNode) +FSNode::FSNode(AbstractFSNode *realNode) : _realNode(realNode) { } -FilesystemNode::FilesystemNode(const Common::String &p) { +FSNode::FSNode(const Common::String &p) { + assert(g_driver); FilesystemFactory *factory = g_driver->getFilesystemFactory(); - AbstractFilesystemNode *tmp = 0; - + AbstractFSNode *tmp = 0; + if (p.empty() || p == ".") tmp = factory->makeCurrentDirectoryFileNode(); else tmp = factory->makeFileNodePath(p); - _realNode = Common::SharedPtr(tmp); + _realNode = Common::SharedPtr(tmp); } -bool FilesystemNode::operator<(const FilesystemNode& node) const { +bool FSNode::operator<(const FSNode& node) const { if (isDirectory() != node.isDirectory()) return isDirectory(); - return strcasecmp(getDisplayName().c_str(), node.getDisplayName().c_str()) < 0; + return getDisplayName().compareToIgnoreCase(node.getDisplayName()) < 0; } -bool FilesystemNode::exists() const { +bool FSNode::exists() const { if (_realNode == 0) return false; return _realNode->exists(); } -FilesystemNode FilesystemNode::getChild(const Common::String &n) const { - if (_realNode == 0) - return *this; +FSNode FSNode::getChild(const Common::String &n) const { + // If this node is invalid or not a directory, return an invalid node + if (_realNode == 0 || !_realNode->isDirectory()) + return FSNode(); - assert(_realNode->isDirectory()); - AbstractFilesystemNode *node = _realNode->getChild(n); - return FilesystemNode(node); + AbstractFSNode *node = _realNode->getChild(n); + return FSNode(node); } -bool FilesystemNode::getChildren(FSList &fslist, ListMode mode, bool hidden) const { +bool FSNode::getChildren(FSList &fslist, ListMode mode, bool hidden) const { if (!_realNode || !_realNode->isDirectory()) return false; @@ -112,94 +83,264 @@ bool FilesystemNode::getChildren(FSList &fslist, ListMode mode, bool hidden) con fslist.clear(); for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) { - fslist.push_back(FilesystemNode(*i)); + fslist.push_back(FSNode(*i)); } return true; } -Common::String FilesystemNode::getDisplayName() const { +Common::String FSNode::getDisplayName() const { assert(_realNode); return _realNode->getDisplayName(); } -Common::String FilesystemNode::getName() const { +Common::String FSNode::getName() const { assert(_realNode); return _realNode->getName(); } -FilesystemNode FilesystemNode::getParent() const { +FSNode FSNode::getParent() const { if (_realNode == 0) return *this; - AbstractFilesystemNode *node = _realNode->getParent(); + AbstractFSNode *node = _realNode->getParent(); if (node == 0) { return *this; } else { - return FilesystemNode(node); + return FSNode(node); } } -Common::String FilesystemNode::getPath() const { +Common::String FSNode::getPath() const { assert(_realNode); return _realNode->getPath(); } -bool FilesystemNode::isDirectory() const { +bool FSNode::isDirectory() const { if (_realNode == 0) return false; return _realNode->isDirectory(); } -bool FilesystemNode::isReadable() const { +bool FSNode::isReadable() const { if (_realNode == 0) return false; return _realNode->isReadable(); } -bool FilesystemNode::isWritable() const { +bool FSNode::isWritable() const { if (_realNode == 0) return false; return _realNode->isWritable(); } -bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool hidden, bool exhaustive, int depth) const { - if (!isDirectory()) +Common::SeekableReadStream *FSNode::createReadStream() const { + if (_realNode == 0) + return 0; + + if (!_realNode->exists()) { + warning("FSNode::createReadStream: FSNode does not exist"); + return false; + } else if (_realNode->isDirectory()) { + warning("FSNode::createReadStream: FSNode is a directory"); + return false; + } + + return _realNode->createReadStream(); +} + +Common::WriteStream *FSNode::createWriteStream() const { + if (_realNode == 0) + return 0; + + if (_realNode->isDirectory()) { + warning("FSNode::createWriteStream: FSNode is a directory"); + return 0; + } + + return _realNode->createWriteStream(); +} + +FSDirectory::FSDirectory(const FSNode &node, int depth) + : _node(node), _cached(false), _depth(depth) { +} + +FSDirectory::FSDirectory(const String &prefix, const FSNode &node, int depth) + : _node(node), _cached(false), _depth(depth) { + + setPrefix(prefix); +} + +FSDirectory::FSDirectory(const String &name, int depth) + : _node(name), _cached(false), _depth(depth) { +} + +FSDirectory::FSDirectory(const String &prefix, const String &name, int depth) + : _node(name), _cached(false), _depth(depth) { + + setPrefix(prefix); +} + +FSDirectory::~FSDirectory() { +} + +void FSDirectory::setPrefix(const String &prefix) { + _prefix = prefix; + + if (!_prefix.empty() && !_prefix.hasSuffix("/")) + _prefix += "/"; +} + +FSNode FSDirectory::getFSNode() const { + return _node; +} + +FSNode *FSDirectory::lookupCache(NodeCache &cache, const String &name) const { + // make caching as lazy as possible + if (!name.empty()) { + ensureCached(); + + if (cache.contains(name)) + return &cache[name]; + } + + return 0; +} + +bool FSDirectory::hasFile(const String &name) { + if (name.empty() || !_node.isDirectory()) return false; - FSList children; - FSList subdirs; - Common::String pattern = p; + FSNode *node = lookupCache(_fileCache, name); + return node && node->exists(); +} - pattern.toUppercase(); +ArchiveMemberPtr FSDirectory::getMember(const String &name) { + if (name.empty() || !_node.isDirectory()) + return ArchiveMemberPtr(); - // First match all files on this level - getChildren(children, FilesystemNode::kListAll, hidden); - for (FSList::iterator entry = children.begin(); entry != children.end(); ++entry) { - if (entry->isDirectory()) { - if (depth != 0) - subdirs.push_back(*entry); + FSNode *node = lookupCache(_fileCache, name); + + if (!node || !node->exists()) { + warning("FSDirectory::getMember: FSNode does not exist"); + return ArchiveMemberPtr(); + } else if (node->isDirectory()) { + warning("FSDirectory::getMember: FSNode is a directory"); + return ArchiveMemberPtr(); + } + + return ArchiveMemberPtr(new FSNode(*node)); +} + +SeekableReadStream *FSDirectory::createReadStreamForMember(const String &name) const { + if (name.empty() || !_node.isDirectory()) + return 0; + + FSNode *node = lookupCache(_fileCache, name); + if (!node) + return 0; + SeekableReadStream *stream = node->createReadStream(); + if (!stream) + warning("FSDirectory::createReadStreamForMember: Can't create stream for file '%s'", name.c_str()); + + return stream; +} + +FSDirectory *FSDirectory::getSubDirectory(const String &name, int depth) { + return getSubDirectory(String::emptyString, name, depth); +} + +FSDirectory *FSDirectory::getSubDirectory(const String &prefix, const String &name, int depth) { + if (name.empty() || !_node.isDirectory()) + return 0; + + FSNode *node = lookupCache(_subDirCache, name); + if (!node) + return 0; + + return new FSDirectory(prefix, *node, depth); +} + +void FSDirectory::cacheDirectoryRecursive(FSNode node, int depth, const String& prefix) const { + if (depth <= 0) + return; + + FSList list; + node.getChildren(list, FSNode::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 { - Common::String filename = entry->getName(); - filename.toUppercase(); - if (matchString(filename.c_str(), pattern.c_str())) { - results.push_back(*entry); - - if (!exhaustive) - return true; // Abort on first match if no exhaustive search was requested + if (_fileCache.contains(lowercaseName)) { + warning("FSDirectory::cacheDirectory: name clash when building cache, ignoring file '%s'", name.c_str()); + } else { + _fileCache[lowercaseName] = *it; } } } - // Now scan all subdirs - for (FSList::iterator child = subdirs.begin(); child != subdirs.end(); ++child) { - child->lookupFile(results, pattern, hidden, exhaustive, depth - 1); - if (!exhaustive && !results.empty()) - return true; // Abort on first match if no exhaustive search was requested +} + +void FSDirectory::ensureCached() const { + if (_cached) + return; + cacheDirectoryRecursive(_node, _depth, _prefix); + _cached = true; +} + +int FSDirectory::listMatchingMembers(ArchiveMemberList &list, const String &pattern) { + if (!_node.isDirectory()) + return 0; + + // Cache dir data + ensureCached(); + + String lowercasePattern(pattern); + lowercasePattern.toLowercase(); + + int matches = 0; + NodeCache::iterator it = _fileCache.begin(); + for ( ; it != _fileCache.end(); ++it) { + if (it->_key.matchString(lowercasePattern, true)) { + list.push_back(ArchiveMemberPtr(new FSNode(it->_value))); + matches++; + } + } + return matches; +} + +int FSDirectory::listMembers(ArchiveMemberList &list) { + if (!_node.isDirectory()) + return 0; + + // Cache dir data + ensureCached(); + + int files = 0; + for (NodeCache::iterator it = _fileCache.begin(); it != _fileCache.end(); ++it) { + list.push_back(ArchiveMemberPtr(new FSNode(it->_value))); + ++files; } - return !results.empty(); + return files; } + + +} // End of namespace Common diff --git a/common/fs.h b/common/fs.h index 89ea85cda78..fa7834bfd47 100644 --- a/common/fs.h +++ b/common/fs.h @@ -8,68 +8,58 @@ * 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_FS_H #define COMMON_FS_H #include "common/array.h" +#include "common/archive.h" #include "common/ptr.h" #include "common/str.h" -//namespace Common { +class AbstractFSNode; -class FilesystemNode; -class AbstractFilesystemNode; +namespace Common { + +class FSNode; +class SeekableReadStream; +class WriteStream; /** * List of multiple file system nodes. E.g. the contents of a given directory. * This is subclass instead of just a typedef so that we can use forward * declarations of it in other places. */ -class FSList : public Common::Array {}; +class FSList : public Array {}; /** - * FilesystemNode provides an abstraction for file paths, allowing for portable - * file system browsing. To this ends, multiple or single roots have to be supported - * (compare Unix with a single root, Windows with multiple roots C:, D:, ...). + * FSNode, short for "File System Node", provides an abstraction for file + * paths, allowing for portable file system browsing. This means for example, + * that multiple or single roots have to be supported (compare Unix with a + * single root, Windows with multiple roots C:, D:, ...). * * 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 { +class FSNode : public ArchiveMember { private: - Common::SharedPtr _realNode; - FilesystemNode(AbstractFilesystemNode *realNode); + SharedPtr _realNode; + FSNode(AbstractFSNode *realNode); public: /** @@ -82,14 +72,14 @@ public: }; /** - * Create a new pathless FilesystemNode. Since there's no path associated + * Create a new pathless FSNode. Since there's no path associated * with this node, path-related operations (i.e. exists(), isDirectory(), * getPath()) will always return false or raise an assertion. */ - FilesystemNode(); + FSNode(); /** - * Create a new FilesystemNode referring to the specified path. This is + * Create a new FSNode referring to the specified path. This is * the counterpart to the path() method. * * If path is empty or equals ".", then a node representing the "current @@ -97,35 +87,47 @@ public: * operating system doesn't support the concept), some other directory is * used (usually the root directory). */ - explicit FilesystemNode(const Common::String &path); + explicit FSNode(const String &path); - virtual ~FilesystemNode() {} + virtual ~FSNode() {} /** * Compare the name of this node to the name of another. Directories * go before normal files. */ - bool operator<(const FilesystemNode& node) const; + bool operator<(const FSNode& 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 createWriteStream() method. + * + * @todo If createWriteStream() (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; + FSNode getChild(const 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; @@ -136,39 +138,40 @@ public: * * @return the display name */ - virtual Common::String getDisplayName() const; + virtual String getDisplayName() const; /** - * Return a string representation of the name of the file. This is can be + * Return a string representation of the name of the file. This can be * used e.g. by detection code that relies on matching the name of a given * file. But it is *not* suitable for use with fopen / File::open, nor * should it be archived. * * @return the file name */ - virtual Common::String getName() const; + virtual 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'. * * @return the 'path' represented by this filesystem node */ - virtual Common::String getPath() const; + virtual String getPath() const; /** * Get the parent node of this node. If this node has no parent node, * then it returns a duplicate of this node. */ - FilesystemNode getParent() const; + FSNode 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,50 +182,162 @@ 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; /** - * Searches recursively for files matching the specified pattern inside this directory and - * all its subdirectories. It is safe to call this method for non-directories, in this case - * it will just return false. + * 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. * - * The files in each directory are scanned first. Other than that, a depth first search - * is performed. - * - * @param results List to put the matches in. - * @param pattern Pattern of the files to look for. - * @param hidden Whether to search hidden files or not. - * @param exhaustive Whether to continue searching after one match has been found. - * @param depth How many levels to search through (-1 = search all subdirs, 0 = only the current one) - * - * @return true if matches could be found, false otherwise. + * @return pointer to the stream object, 0 in case of a failure */ - virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const; + virtual SeekableReadStream *createReadStream() 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 WriteStream *createWriteStream() const; }; -//} // End of namespace Common +/** + * FSDirectory models a directory tree from the filesystem and allows users + * to access it through the Archive interface. Searching is case-insensitive, + * since the intended goal is supporting retrieval of game data. + * + * FSDirectory can represent a single directory, or a tree with specified depth, + * depending on the value passed to the 'depth' parameter in the constructors. + * Filenames are cached with their relative path, with elements separated by + * slashes, e.g.: + * + * c:\my\data\file.ext + * + * would be cached as 'data/file.ext' if FSDirectory was created on 'c:/my' with + * depth > 1. If depth was 1, then the 'data' subdirectory would have been + * ignored, instead. + * Again, only SLASHES are used as separators independently from the + * underlying file system. + * + * Relative paths can be specified when calling matching functions like createReadStreamForMember(), + * hasFile(), listMatchingMembers() and listMembers(). Please see the function + * specific comments for more information. + * + * Client code can customize cache by using the constructors with the 'prefix' + * parameter. In this case, the prefix is prepended to each entry in the cache, + * and effectively treated as a 'virtual' parent subdirectory. FSDirectory adds + * a trailing slash to prefix if needed. Following on with the previous example + * and using 'your' as prefix, the cache entry would have been 'your/data/file.ext'. + * + */ +class FSDirectory : public Archive { + FSNode _node; + + String _prefix; // string that is prepended to each cache item key + void setPrefix(const String &prefix); + + // Caches are case insensitive, clashes are dealt with when creating + // Key is stored in lowercase. + typedef HashMap NodeCache; + mutable NodeCache _fileCache, _subDirCache; + mutable bool _cached; + mutable int _depth; + + // look for a match + FSNode *lookupCache(NodeCache &cache, const String &name) const; + + // cache management + void cacheDirectoryRecursive(FSNode node, int depth, const String& prefix) const; + + // fill cache if not already cached + void ensureCached() const; + +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 if the node is not a + * valid directory. + */ + FSDirectory(const String &name, int depth = 1); + FSDirectory(const FSNode &node, int depth = 1); + + /** + * Create a FSDirectory representing a tree with the specified depth. The parameter + * prefix is prepended to the keys in the cache. See class comment. + */ + FSDirectory(const String &prefix, const String &name, int depth = 1); + FSDirectory(const String &prefix, const FSNode &node, int depth = 1); + + virtual ~FSDirectory(); + + /** + * This return the underlying FSNode of the FSDirectory. + */ + FSNode getFSNode() const; + + /** + * Create a new FSDirectory pointing to a sub directory of the instance. See class comment + * for an explanation of the prefix parameter. + * @return a new FSDirectory instance + */ + FSDirectory *getSubDirectory(const String &name, int depth = 1); + FSDirectory *getSubDirectory(const String &prefix, const String &name, int depth = 1); + + /** + * Checks for existence in the cache. A full match of relative path and filename is needed + * for success. + */ + virtual bool hasFile(const String &name); + + /** + * Returns a list of matching file names. Pattern can use GLOB wildcards. + */ + virtual int listMatchingMembers(ArchiveMemberList &list, const String &pattern); + + /** + * Returns a list of all the files in the cache. + */ + virtual int listMembers(ArchiveMemberList &list); + + /** + * Get a ArchiveMember representation of the specified file. A full match of relative + * path and filename is needed for success. + */ + virtual ArchiveMemberPtr getMember(const String &name); + + /** + * Open the specified file. A full match of relative path and filename is needed + * for success. + */ + virtual SeekableReadStream *createReadStreamForMember(const String &name) const; +}; + + +} // End of namespace Common #endif //COMMON_FS_H diff --git a/common/savefile.h b/common/savefile.h index 8f8b09f6f6a..03caf4b887c 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; /** @@ -78,7 +65,7 @@ public: class SaveFileManager : NonCopyable { protected: - SFMError _error; + Error _error; String _errorDesc; /** @@ -86,7 +73,7 @@ protected: * @param error Code identifying the last error. * @param errorDesc String describing the last error. */ - virtual void setError(SFMError error, const String &errorDesc) { _error = error; _errorDesc = errorDesc; } + virtual void setError(Error error, const String &errorDesc) { _error = error; _errorDesc = errorDesc; } public: virtual ~SaveFileManager() {} @@ -94,14 +81,14 @@ public: /** * Clears the last set error code and string. */ - virtual void clearError() { _error = SFM_NO_ERROR; _errorDesc = ""; } + virtual void clearError() { _error = kNoError; _errorDesc.clear(); } /** - * Returns the last occurred error code. If none occurred, returns SFM_NO_ERROR. + * Returns the last occurred error code. If none occurred, returns kNoError. * - * @return A SFMError indicating the type of the last error. + * @return A value indicating the type of the last error. */ - virtual SFMError getError() { return _error; } + virtual Error getError() { return _error; } /** * Returns the last occurred error description. If none occurred, returns 0. @@ -149,11 +136,11 @@ public: /** * Request a list of available savegames with a given DOS-style pattern, - * also known as "glob" in the UNIX world. Refer to the Common::match() + * also known as "glob" in the UNIX world. Refer to the Common::matchString() * function to learn about the precise pattern format. * @param pattern Pattern to match. Wildcards like * or ? are available. * @return list of strings for all present file names. - * @see Common::match + * @see Common::matchString() */ virtual Common::StringList listSavefiles(const char *pattern) = 0; }; diff --git a/common/stream.cpp b/common/stream.cpp index 8bf3b3510f5..c1640b97e34 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,14 +62,14 @@ 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) { case SEEK_END: // SEEK_END works just like SEEK_SET, only 'reversed', // i.e. from the end. - offs = _size - offs; + offs = _size + offs; // Fall through case SEEK_SET: _ptr = _ptrOrig + offs; @@ -81,72 +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) { - assert(buf && bufSize > 0); - char *p = buf; - size_t len = 0; - char c; - - if (buf == 0 || bufSize == 0 || eos()) { - return 0; - } - - // We don't include the newline character(s) in the buffer, and we - // always terminate it - we never read more than len-1 characters. - - // EOF is treated as a line break, unless it was the first character - // that was read. - - // 0 is treated as a line break, even though it should never occur in - // a text file. - - // DOS and Windows use CRLF line breaks - // Unix and OS X use LF line breaks - // Macintosh before OS X uses CR line breaks - - - c = readByte(); - if (eos() || ioFailed()) { - return 0; - } - - while (!eos() && len + 1 < bufSize) { - - if (ioFailed()) - return 0; - - if (c == 0 || c == LF) - break; - - if (c == CR) { - c = readByte(); - if (c != LF && !eos()) - seek(-1, SEEK_CUR); - break; - } - - *p++ = c; - len++; - - c = readByte(); - } - - // 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(); - } - - *p = 0; - return buf; -} +enum { + LF = 0x0A, + CR = 0x0D +}; char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) { assert(buf != 0 && bufSize > 1); @@ -155,20 +101,28 @@ char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) { char c = 0; // If end-of-file occurs before any characters are read, return NULL - // and the buffer contents remain unchanged. - if (eos() || ioFailed()) { + // and the buffer contents remain unchanged. + 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,35 +132,57 @@ 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; } - + *p++ = c; 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,15 +195,16 @@ 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); switch(whence) { case SEEK_END: - offset = size() - offset; + offset = size() + offset; // fallthrough case SEEK_SET: _pos = _begin + offset; @@ -239,7 +216,91 @@ 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) + : _parentStream(parentStream), + _disposeParentStream(disposeParentStream), + _pos(0), + _bufSize(0), + _realBufSize(bufSize) { + + assert(parentStream); + _buf = new byte[bufSize]; + assert(_buf); +} + +BufferedReadStream::~BufferedReadStream() { + if (_disposeParentStream) + delete _parentStream; + delete _buf; +} + +uint32 BufferedReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 alreadyRead = 0; + const uint32 bufBytesLeft = _bufSize - _pos; + + // Check whether the data left in the buffer suffices.... + if (dataSize > bufBytesLeft) { + // Nope, we need to read more data + + // First, flush the buffer, if it is non-empty + if (0 < bufBytesLeft) { + memcpy(dataPtr, _buf + _pos, bufBytesLeft); + _pos = _bufSize; + alreadyRead += bufBytesLeft; + dataPtr = (byte *)dataPtr + bufBytesLeft; + dataSize -= bufBytesLeft; + } + + // At this point the buffer is empty. Now if the read request + // exceeds the buffer size, just satisfy it directly. + if (dataSize > _bufSize) + return alreadyRead + _parentStream->read(dataPtr, dataSize); + + // Refill the buffer. + // If we didn't read as many bytes as requested, the reason + // is EOF or an error. In that case we truncate the buffer + // size, as well as the number of bytes we are going to + // return to the caller. + _bufSize = _parentStream->read(_buf, _realBufSize); + _pos = 0; + if (dataSize > _bufSize) + dataSize = _bufSize; + } + + // Satisfy the request from the buffer + memcpy(dataPtr, _buf + _pos, dataSize); + _pos += dataSize; + return alreadyRead + dataSize; +} + +BufferedSeekableReadStream::BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream) + : BufferedReadStream(parentStream, bufSize, disposeParentStream), + _parentStream(parentStream) { +} + +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 + // since they are rarely used, it seems not worth the effort. + if (whence == SEEK_CUR && (int)_pos + offset >= 0 && _pos + offset <= _bufSize) { + _pos += offset; + } else { + // Seek was not local enough, so we reset the buffer and + // just seeks normally in the parent stream. + if (whence == SEEK_CUR) + offset -= (_bufSize - _pos); + _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 cd7a35f108d..bb6b0f1791f 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. @@ -125,6 +154,10 @@ public: writeUint32BE((uint32)value); } + /** + * Write the given string to the stream. + * This writes str.size() characters, but no terminating zero byte. + */ void writeString(const String &str); }; @@ -135,7 +168,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 +186,19 @@ public: // The remaining methods all have default implementations; subclasses - // need not (and should not) overload them. + // in general should not overload them. /** - * Read am unsigned byte from the stream and return it. + * DEPRECATED + * Default implementation for backward compatibility + */ + virtual bool ioFailed() { return (eos() || err()); } + + /** + * Read an unsigned byte from the stream and return it. * Performs no error checking. The return value is undefined * if a read error occurred (for which client code can check by - * calling ioFailed()). + * calling err() and eos() ). */ byte readByte() { byte b = 0; @@ -169,7 +210,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 +223,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 +236,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 +249,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 +262,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 +275,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 +286,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 +297,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 +308,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 +318,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 +330,84 @@ 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); } + /** + * Obtains the current value of the stream position indicator of the + * stream. + * + * @return the current position indicator, or -1 if an error occurred. + */ + virtual int32 pos() const = 0; /** - * 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 total size of the stream, measured in bytes. + * If this value is unknown or can not be computed, -1 is returned. * - * @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. - * - * @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 - * - * @note The line terminator (CR or CR/LF) is stripped and not inserted - * into the buffer. + * @return the size of the stream, or -1 if an error occurred */ - virtual char *readLine(char *buf, size_t bufSize); + 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. + * + * @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); } /** * 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(); }; /** @@ -350,20 +420,26 @@ public: class SubReadStream : virtual public ReadStream { protected: ReadStream *_parentStream; + bool _disposeParentStream; uint32 _pos; uint32 _end; - bool _disposeParentStream; + 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); }; @@ -379,10 +455,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); }; /** @@ -414,6 +490,50 @@ public: } }; +/** + * Wrapper class which adds buffering to any given ReadStream. + * Users can specify how big the buffer should be, and whether the + * wrapped stream should be disposed when the wrapper is disposed. + */ +class BufferedReadStream : virtual public ReadStream { +protected: + ReadStream *_parentStream; + bool _disposeParentStream; + byte *_buf; + uint32 _pos; + uint32 _bufSize; + uint32 _realBufSize; + +public: + BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + ~BufferedReadStream(); + + 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); +}; + +/** + * Wrapper class which adds buffering to any given SeekableReadStream. + * @see BufferedReadStream + */ +class BufferedSeekableReadStream : public BufferedReadStream, public SeekableReadStream { +protected: + SeekableReadStream *_parentStream; +public: + BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + + virtual int32 pos() const { return _parentStream->pos() - (_bufSize - _pos); } + virtual int32 size() const { return _parentStream->size(); } + + virtual bool seek(int32 offset, int whence = SEEK_SET); +}; + + /** * Simple memory based 'stream', which implements the ReadStream interface for @@ -427,6 +547,7 @@ private: uint32 _pos; byte _encbyte; bool _disposeMemory; + bool _eos; public: @@ -441,7 +562,8 @@ public: _size(dataSize), _pos(0), _encbyte(0), - _disposeMemory(disposeMemory) {} + _disposeMemory(disposeMemory), + _eos(false) {} ~MemoryReadStream() { if (_disposeMemory) @@ -452,11 +574,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; } - void seek(int32 offs, int whence = SEEK_SET); + int32 pos() const { return _pos; } + int32 size() const { return _size; } + + bool seek(int32 offs, int whence = SEEK_SET); }; @@ -509,14 +633,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: @@ -563,7 +686,6 @@ public: return dataSize; } - bool eos() const { return false; } uint32 pos() const { return _pos; } uint32 size() const { return _size; } diff --git a/engine/backend/saves/compressed/compressed-saves.cpp b/common/zlib.cpp similarity index 71% rename from engine/backend/saves/compressed/compressed-saves.cpp rename to common/zlib.cpp index 066f3dbc146..23bb34dc388 100644 --- a/engine/backend/saves/compressed/compressed-saves.cpp +++ b/common/zlib.cpp @@ -23,24 +23,36 @@ * */ -#include "common/savefile.h" +#include "common/zlib.h" #include "common/util.h" -#include "common/debug.h" -#include "engine/backend/saves/compressed/compressed-saves.h" #if defined(USE_ZLIB) -#include + #ifdef __SYMBIAN32__ + #include + #else + #include + #endif -#if ZLIB_VERNUM < 0x1204 -#error Version 1.2.0.4 or newer of zlib is required for this code + #if ZLIB_VERNUM < 0x1204 + #error Version 1.2.0.4 or newer of zlib is required for this code + #endif #endif + +namespace Common { + +#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); +} + /** * A simple wrapper class which can be used to wrap around an arbitrary - * other InSaveFile and will then provide on-the-fly decompression support. + * other SeekableReadStream and will then provide on-the-fly decompression support. * Assumes the compressed data to be in gzip format. */ -class CompressedInSaveFile : public Common::InSaveFile { +class GZipReadStream : public Common::SeekableReadStream { protected: enum { BUFSIZE = 16384 // 1 << MAX_WBITS @@ -48,22 +60,23 @@ protected: byte _buf[BUFSIZE]; - Common::InSaveFile *_wrapped; + Common::SeekableReadStream *_wrapped; z_stream _stream; int _zlibErr; uint32 _pos; uint32 _origSize; + bool _eos; public: - CompressedInSaveFile(Common::InSaveFile *w) : _wrapped(w) { + 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 once more + // Verify file header is correct w->seek(0, SEEK_SET); uint16 header = w->readUint16BE(); assert(header == 0x1F8B || @@ -79,6 +92,7 @@ public: } _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 @@ -94,13 +108,16 @@ public: _stream.avail_in = 0; } - ~CompressedInSaveFile() { + ~GZipReadStream() { inflateEnd(&_stream); delete _wrapped; } - bool ioFailed() const { return (_zlibErr != Z_OK) && (_zlibErr != Z_STREAM_END); } - void clearIOFailed() { /* errors here are not recoverable! */ } + 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; @@ -119,66 +136,78 @@ public: // 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 (_zlibErr == Z_STREAM_END); - //return _pos == _origSize; + return _eos; } - uint32 pos() const { + int32 pos() const { return _pos; } - uint32 size() const { + int32 size() const { return _origSize; } - void seek(int32 offset, int whence = SEEK_SET) { + bool seek(int32 offset, int whence = SEEK_SET) { int32 newPos = 0; + assert(whence != SEEK_END); // SEEK_END not supported switch(whence) { - case SEEK_END: - newPos = size() - offset; - break; 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; - if (offset < 0) - error("Backward seeking not supported in compressed savefiles"); - - // We could implement backward seeking, but it is tricky to do efficiently. - // A simple solution would be to restart the whole decompression from the - // start of the file. Or we could decompress the whole file in one go - // in the constructor, and wrap it into a MemoryReadStream -- but that - // would be rather wasteful. As long as we don't need it, I'd rather not - // implement this at all. -- Fingolfin - // 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 (!ioFailed() && offset > 0) { + 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 OutSaveFile and will then provide on-the-fly compression support. + * other WriteStream and will then provide on-the-fly compression support. * The compressed data is written in the gzip format. */ -class CompressedOutSaveFile : public Common::OutSaveFile { +class GZipWriteStream : public Common::WriteStream { protected: enum { BUFSIZE = 16384 // 1 << MAX_WBITS }; byte _buf[BUFSIZE]; - Common::OutSaveFile *_wrapped; + Common::WriteStream *_wrapped; z_stream _stream; int _zlibErr; @@ -198,7 +227,7 @@ protected: } public: - CompressedOutSaveFile(Common::OutSaveFile *w) : _wrapped(w) { + GZipWriteStream(Common::WriteStream *w) : _wrapped(w) { assert(w != 0); _stream.zalloc = Z_NULL; _stream.zfree = Z_NULL; @@ -213,7 +242,7 @@ public: Z_DEFLATED, MAX_WBITS + 16, 8, - Z_DEFAULT_STRATEGY); + Z_DEFAULT_STRATEGY); assert(_zlibErr == Z_OK); _stream.next_out = _buf; @@ -222,20 +251,21 @@ public: _stream.next_in = 0; } - ~CompressedOutSaveFile() { + ~GZipWriteStream() { finalize(); deflateEnd(&_stream); delete _wrapped; } - bool ioFailed() const { - return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->ioFailed(); + bool err() const { + // CHECKME: does Z_STREAM_END make sense here? + return (_zlibErr != Z_OK && _zlibErr != Z_STREAM_END) || _wrapped->err(); } - void clearIOFailed() { + void clearErr() { // Note: we don't reset the _zlibErr here, as it is not - // clear in general ho - _wrapped->clearIOFailed(); + // clear in general how + _wrapped->clearErr(); } void finalize() { @@ -259,7 +289,7 @@ public: } uint32 write(const void *dataPtr, uint32 dataSize) { - if (ioFailed()) + if (err()) return 0; // Hook in the new data ... @@ -277,7 +307,7 @@ public: #endif // USE_ZLIB -Common::InSaveFile *wrapInSaveFile(Common::InSaveFile *toBeWrapped) { +Common::SeekableReadStream *wrapCompressedReadStream(Common::SeekableReadStream *toBeWrapped) { #if defined(USE_ZLIB) if (toBeWrapped) { uint16 header = toBeWrapped->readUint16BE(); @@ -286,16 +316,19 @@ Common::InSaveFile *wrapInSaveFile(Common::InSaveFile *toBeWrapped) { header % 31 == 0)); toBeWrapped->seek(-2, SEEK_CUR); if (isCompressed) - return new CompressedInSaveFile(toBeWrapped); + return new GZipReadStream(toBeWrapped); } #endif return toBeWrapped; } -Common::OutSaveFile *wrapOutSaveFile(Common::OutSaveFile *toBeWrapped) { +Common::WriteStream *wrapCompressedWriteStream(Common::WriteStream *toBeWrapped) { #if defined(USE_ZLIB) if (toBeWrapped) - return new CompressedOutSaveFile(toBeWrapped); + return new GZipWriteStream(toBeWrapped); #endif return toBeWrapped; } + + +} // End of namespace Common diff --git a/common/zlib.h b/common/zlib.h new file mode 100644 index 00000000000..20071b502f6 --- /dev/null +++ b/common/zlib.h @@ -0,0 +1,72 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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_ZLIB_H +#define COMMON_ZLIB_H + +#include "common/sys.h" +#include "common/stream.h" + +namespace Common { + +#if defined(USE_ZLIB) + +/** + * 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 + +/** + * 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 diff --git a/dists/msvc9/residual.vcproj b/dists/msvc9/residual.vcproj index 6a95a2c7fa0..207deb8a784 100644 --- a/dists/msvc9/residual.vcproj +++ b/dists/msvc9/residual.vcproj @@ -196,6 +196,14 @@ RelativePath="..\..\common\algorithm.h" > + + + + @@ -280,6 +288,10 @@ RelativePath="..\..\common\list.h" > + + @@ -304,6 +316,10 @@ RelativePath="..\..\common\ptr.h" > + + @@ -348,6 +364,14 @@ RelativePath="..\..\common\util.h" > + + + + + + @@ -994,6 +1022,14 @@ RelativePath="..\..\engine\backend\fs\fs-factory.h" > + + + + @@ -1014,18 +1050,6 @@ RelativePath="..\..\engine\backend\saves\savefile.cpp" > - - - - - - diff --git a/engine/backend/events/default/default-events.cpp b/engine/backend/events/default/default-events.cpp index 4d0fdccea49..67ee818cc3e 100644 --- a/engine/backend/events/default/default-events.cpp +++ b/engine/backend/events/default/default-events.cpp @@ -8,25 +8,25 @@ * 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$ - * */ #if !defined(DISABLE_DEFAULT_EVENTMANAGER) #include "common/config-manager.h" #include "engine/backend/events/default/default-events.h" +#include "engine/backend/platform/driver.h" #define RECORD_SIGNATURE 0x54455354 #define RECORD_VERSION 1 @@ -85,11 +85,13 @@ void writeRecord(Common::OutSaveFile *outFile, uint32 diff, Common::Event &event } } -DefaultEventManager::DefaultEventManager(Driver *boss) : +DefaultEventManager::DefaultEventManager(EventProvider *boss) : _boss(boss), _buttonState(0), _modifierState(0), - _shouldQuit(false) { + _shouldQuit(false), + _shouldRTL(false), + _confirmExitDialogActive(false) { assert(_boss); @@ -97,8 +99,8 @@ DefaultEventManager::DefaultEventManager(Driver *boss) : _recordTimeFile = NULL; _playbackFile = NULL; _playbackTimeFile = NULL; - _timeMutex = _boss->createMutex(); - _recorderMutex = _boss->createMutex(); + _timeMutex = g_driver->createMutex(); + _recorderMutex = g_driver->createMutex(); _eventCount = 0; _lastEventCount = 0; @@ -135,8 +137,8 @@ DefaultEventManager::DefaultEventManager(Driver *boss) : if (_recordMode == kRecorderRecord) { _recordCount = 0; _recordTimeCount = 0; - _recordFile = _boss->getSavefileManager()->openForSaving(_recordTempFileName.c_str()); - _recordTimeFile = _boss->getSavefileManager()->openForSaving(_recordTimeFileName.c_str()); + _recordFile = g_driver->getSavefileManager()->openForSaving(_recordTempFileName.c_str()); + _recordTimeFile = g_driver->getSavefileManager()->openForSaving(_recordTimeFileName.c_str()); _recordSubtitles = ConfMan.getBool("subtitles"); } @@ -146,8 +148,8 @@ DefaultEventManager::DefaultEventManager(Driver *boss) : if (_recordMode == kRecorderPlayback) { _playbackCount = 0; _playbackTimeCount = 0; - _playbackFile = _boss->getSavefileManager()->openForLoading(_recordFileName.c_str()); - _playbackTimeFile = _boss->getSavefileManager()->openForLoading(_recordTimeFileName.c_str()); + _playbackFile = g_driver->getSavefileManager()->openForLoading(_recordFileName.c_str()); + _playbackTimeFile = g_driver->getSavefileManager()->openForLoading(_recordTimeFileName.c_str()); if (!_playbackFile) { warning("Cannot open playback file %s. Playback was switched off", _recordFileName.c_str()); @@ -187,14 +189,25 @@ DefaultEventManager::DefaultEventManager(Driver *boss) : _hasPlaybackEvent = false; } + +#ifdef ENABLE_VKEYBD + _vk = new Common::VirtualKeyboard(); +#endif +#ifdef ENABLE_KEYMAPPER + _keymapper = new Common::Keymapper(this); + _remap = false; +#endif } DefaultEventManager::~DefaultEventManager() { - _boss->lockMutex(_timeMutex); - _boss->lockMutex(_recorderMutex); + g_driver->lockMutex(_timeMutex); + g_driver->lockMutex(_recorderMutex); _recordMode = kPassthrough; - _boss->unlockMutex(_timeMutex); - _boss->unlockMutex(_recorderMutex); + g_driver->unlockMutex(_timeMutex); + g_driver->unlockMutex(_recorderMutex); + + if (!artificialEventQueue.empty()) + artificialEventQueue.clear(); if (_playbackFile != NULL) { delete _playbackFile; @@ -209,9 +222,9 @@ DefaultEventManager::~DefaultEventManager() { _recordTimeFile->finalize(); delete _recordTimeFile; - _playbackFile = _boss->getSavefileManager()->openForLoading(_recordTempFileName.c_str()); + _playbackFile = g_driver->getSavefileManager()->openForLoading(_recordTempFileName.c_str()); - _recordFile = _boss->getSavefileManager()->openForSaving(_recordFileName.c_str()); + _recordFile = g_driver->getSavefileManager()->openForSaving(_recordFileName.c_str()); _recordFile->writeUint32LE(RECORD_SIGNATURE); _recordFile->writeUint32LE(RECORD_VERSION); @@ -241,8 +254,11 @@ DefaultEventManager::~DefaultEventManager() { //TODO: remove recordTempFileName'ed file } - _boss->deleteMutex(_timeMutex); - _boss->deleteMutex(_recorderMutex); + g_driver->deleteMutex(_timeMutex); + g_driver->deleteMutex(_recorderMutex); +} + +void DefaultEventManager::init() { } bool DefaultEventManager::playback(Common::Event &event) { @@ -265,7 +281,7 @@ bool DefaultEventManager::playback(Common::Event &event) { case Common::EVENT_RBUTTONUP: case Common::EVENT_WHEELUP: case Common::EVENT_WHEELDOWN: - _boss->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y); + g_driver->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y); break; default: break; @@ -313,7 +329,7 @@ void DefaultEventManager::processMillis(uint32 &millis) { return; } - _boss->lockMutex(_timeMutex); + g_driver->lockMutex(_timeMutex); if (_recordMode == kRecorderRecord) { //Simple RLE compression d = millis - _lastMillis; @@ -338,18 +354,38 @@ void DefaultEventManager::processMillis(uint32 &millis) { } _lastMillis = millis; - _boss->unlockMutex(_timeMutex); + g_driver->unlockMutex(_timeMutex); } bool DefaultEventManager::pollEvent(Common::Event &event) { - uint32 time = _boss->getMillis(); + uint32 time = g_driver->getMillis(); bool result; - result = _boss->pollEvent(event); + if (!artificialEventQueue.empty()) { + event = artificialEventQueue.pop(); + result = true; + } else { + result = _boss->pollEvent(event); + +#ifdef ENABLE_KEYMAPPER + if (result) { + // send key press events to keymapper + if (event.type == Common::EVENT_KEYDOWN) { + if (_keymapper->mapKeyDown(event.kbd)) { + result = false; + } + } else if (event.type == Common::EVENT_KEYUP) { + if (_keymapper->mapKeyUp(event.kbd)) { + result = false; + } + } + } +#endif + } if (_recordMode != kPassthrough) { - _boss->lockMutex(_recorderMutex); + g_driver->lockMutex(_recorderMutex); _eventCount++; if (_recordMode == kRecorderPlayback) { @@ -363,7 +399,7 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { } } } - _boss->unlockMutex(_recorderMutex); + g_driver->unlockMutex(_recorderMutex); } if (result) { @@ -371,7 +407,6 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { switch (event.type) { case Common::EVENT_KEYDOWN: _modifierState = event.kbd.flags; - // init continuous event stream // not done on PalmOS because keyboard is emulated and keyup is not generated #if !defined(PALMOS_MODE) @@ -381,6 +416,7 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { _keyRepeatTime = time + kKeyRepeatInitialDelay; #endif break; + case Common::EVENT_KEYUP: _modifierState = event.kbd.flags; if (event.kbd.keycode == _currentKeyDown.keycode) { @@ -397,6 +433,7 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { _mousePos = event.mouse; _buttonState |= LBUTTON; break; + case Common::EVENT_LBUTTONUP: _mousePos = event.mouse; _buttonState &= ~LBUTTON; @@ -406,13 +443,14 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { _mousePos = event.mouse; _buttonState |= RBUTTON; break; + case Common::EVENT_RBUTTONUP: _mousePos = event.mouse; _buttonState &= ~RBUTTON; break; case Common::EVENT_QUIT: - _shouldQuit = true; + _shouldQuit = true; break; default: @@ -435,4 +473,14 @@ bool DefaultEventManager::pollEvent(Common::Event &event) { return result; } +void DefaultEventManager::pushEvent(const Common::Event &event) { + + // If already received an EVENT_QUIT, don't add another one + if (event.type == Common::EVENT_QUIT) { + if (!_shouldQuit) + artificialEventQueue.push(event); + } else + artificialEventQueue.push(event); +} + #endif // !defined(DISABLE_DEFAULT_EVENTMANAGER) diff --git a/engine/backend/events/default/default-events.h b/engine/backend/events/default/default-events.h index d4d759da427..144709b3c9b 100644 --- a/engine/backend/events/default/default-events.h +++ b/engine/backend/events/default/default-events.h @@ -28,28 +28,41 @@ #include "common/events.h" #include "common/savefile.h" +#include "common/mutex.h" +#include "common/queue.h" -#include "engine/backend/platform/driver.h" - -/* -At some point we will remove pollEvent from OSystem and change -DefaultEventManager to use a "boss" derived from this class: class EventProvider { -public +public: + virtual ~EventProvider() {} + /** + * Get the next event in the event queue. + * @param event point to an Common::Event struct, which will be filled with the event data. + * @return true if an event was retrieved. + */ virtual bool pollEvent(Common::Event &event) = 0; }; -Backends which wish to use the DefaultEventManager then simply can -use a subclass of EventProvider. -*/ class DefaultEventManager : public Common::EventManager { - Driver *_boss; + EventProvider *_boss; + +#ifdef ENABLE_VKEYBD + Common::VirtualKeyboard *_vk; +#endif + +#ifdef ENABLE_KEYMAPPER + Common::Keymapper *_keymapper; + bool _remap; +#endif + + Common::Queue _artificialEventQueue; Common::Point _mousePos; int _buttonState; int _modifierState; bool _shouldQuit; + bool _shouldRTL; + bool _confirmExitDialogActive; class RandomSourceRecord { public: @@ -105,10 +118,12 @@ class DefaultEventManager : public Common::EventManager { void record(Common::Event &event); bool playback(Common::Event &event); public: - DefaultEventManager(Driver *boss); + DefaultEventManager(EventProvider *boss); ~DefaultEventManager(); + virtual void init(); virtual bool pollEvent(Common::Event &event); + virtual void pushEvent(const Common::Event &event); virtual void registerRandomSource(Common::RandomSource &rnd, const char *name); virtual void processMillis(uint32 &millis); @@ -116,6 +131,12 @@ public: virtual int getButtonState() const { return _buttonState; } virtual int getModifierState() const { return _modifierState; } virtual int shouldQuit() const { return _shouldQuit; } + virtual int shouldRTL() const { return _shouldRTL; } + virtual void resetRTL() { _shouldRTL = false; } + +#ifdef ENABLE_KEYMAPPER + virtual Common::Keymapper *getKeymapper() { return _keymapper; } +#endif }; #endif diff --git a/engine/backend/fs/abstract-fs.cpp b/engine/backend/fs/abstract-fs.cpp new file mode 100644 index 00000000000..c594e68e66a --- /dev/null +++ b/engine/backend/fs/abstract-fs.cpp @@ -0,0 +1,40 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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 "engine/backend/fs/abstract-fs.h" + +const char *AbstractFSNode::lastPathComponent(const Common::String &str, const char sep) { + // TODO: Get rid of this eventually! Use Common::lastPathComponent instead + if(str.empty()) + return ""; + + const char *start = str.c_str(); + const char *cur = start + str.size() - 2; + + while (cur >= start && *cur != sep) { + --cur; + } + + return cur + 1; +} diff --git a/engine/backend/fs/abstract-fs.h b/engine/backend/fs/abstract-fs.h index db2c8c81e17..2f80a7b7cb9 100644 --- a/engine/backend/fs/abstract-fs.h +++ b/engine/backend/fs/abstract-fs.h @@ -29,54 +29,66 @@ #include "common/str.h" #include "common/fs.h" -class AbstractFilesystemNode; +class AbstractFSNode; -typedef Common::Array AbstractFSList; +typedef Common::Array AbstractFSList; /** * Abstract file system node. Private subclasses implement the actual * functionality. * - * Most of the methods correspond directly to methods in class FilesystemNode, + * Most of the methods correspond directly to methods in class FSNode, * so if they are not documented here, look there for more information about * the semantics. */ -class AbstractFilesystemNode { +class AbstractFSNode { protected: - friend class FilesystemNode; - typedef Common::String String; - typedef FilesystemNode::ListMode ListMode; + friend class Common::FSNode; + typedef Common::FSNode::ListMode ListMode; /** - * Returns the child node with the given name. If no child with this name - * exists, returns 0. When called on a non-directory node, it should - * handle this gracefully by returning 0. + * Returns the child node with the given name. When called on a non-directory + * node, it should handle this gracefully by returning 0. + * When called with a name not matching any of the files/dirs contained in this + * directory, a valid node should be returned, which returns 'false' upon calling + * the exists() method. The idea is that this node can then still can be used to + * create a new file via the createWriteStream() method. * * Example: * Calling getChild() for a node with path "/foo/bar" using name="file.txt", * would produce a new node with "/foo/bar/file.txt" as path. * - * @note This function will append a separator char (\ or /) to the end of the - * path if needed. - * * @note Handling calls on non-dir nodes gracefully makes it possible to * switch to a lazy type detection scheme in the future. * * @param name String containing the name of the child to create a new node. */ - virtual AbstractFilesystemNode *getChild(const String &name) const = 0; + virtual AbstractFSNode *getChild(const Common::String &name) const = 0; /** * The parent node of this directory. * The parent of the root is the root itself. */ - virtual AbstractFilesystemNode *getParent() const = 0; + virtual AbstractFSNode *getParent() const = 0; + + /** + * Returns the last component of a given path. + * + * Examples: + * /foo/bar.txt would return /bar.txt + * /foo/bar/ would return /bar/ + * + * @param str String containing the path. + * @param sep character used to separate path components + * @return Pointer to the first char of the last component inside str. + */ + static const char *lastPathComponent(const Common::String &str, const char sep); public: /** * Destructor. */ - virtual ~AbstractFilesystemNode() {} + virtual ~AbstractFSNode() {} /* * Indicates whether the object referred by this path exists in the filesystem or not. @@ -100,10 +112,10 @@ public: * * @note By default, this method returns the value of getName(). */ - virtual String getDisplayName() const { return getName(); } + virtual Common::String getDisplayName() const { return getName(); } /** - * Returns the last component of the path pointed by this FilesystemNode. + * Returns the last component of the path pointed by this FSNode. * * Examples (POSIX): * /foo/bar.txt would return /bar.txt @@ -111,12 +123,12 @@ public: * * @note This method is very architecture dependent, please check the concrete implementation for more information. */ - virtual String getName() const = 0; + virtual Common::String getName() const = 0; /** * Returns the 'path' of the current node, usable in fopen(). */ - virtual String getPath() const = 0; + virtual Common::String getPath() const = 0; /** * Indicates whether this path refers to a directory or not. @@ -149,9 +161,26 @@ public: */ virtual bool isWritable() const = 0; - /* TODO: - bool isFile(); - */ + + /** + * 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 *createReadStream() = 0; + + /** + * 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 *createWriteStream() = 0; }; + + #endif //BACKENDS_ABSTRACT_FS_H diff --git a/engine/backend/fs/amigaos4/amigaos4-fs-factory.cpp b/engine/backend/fs/amigaos4/amigaos4-fs-factory.cpp index 8ac9eaf0635..e8e30ae4281 100644 --- a/engine/backend/fs/amigaos4/amigaos4-fs-factory.cpp +++ b/engine/backend/fs/amigaos4/amigaos4-fs-factory.cpp @@ -26,17 +26,15 @@ #include "engine/backend/fs/amigaos4/amigaos4-fs-factory.h" #include "engine/backend/fs/amigaos4/amigaos4-fs.cpp" -DECLARE_SINGLETON(AmigaOSFilesystemFactory); - -AbstractFilesystemNode *AmigaOSFilesystemFactory::makeRootFileNode() const { +AbstractFSNode *AmigaOSFilesystemFactory::makeRootFileNode() const { return new AmigaOSFilesystemNode(); } -AbstractFilesystemNode *AmigaOSFilesystemFactory::makeCurrentDirectoryFileNode() const { +AbstractFSNode *AmigaOSFilesystemFactory::makeCurrentDirectoryFileNode() const { return new AmigaOSFilesystemNode(); } -AbstractFilesystemNode *AmigaOSFilesystemFactory::makeFileNodePath(const String &path) const { +AbstractFSNode *AmigaOSFilesystemFactory::makeFileNodePath(const Common::String &path) const { return new AmigaOSFilesystemNode(path); } #endif diff --git a/engine/backend/fs/amigaos4/amigaos4-fs-factory.h b/engine/backend/fs/amigaos4/amigaos4-fs-factory.h index e7c0c32e5f4..d64790df603 100644 --- a/engine/backend/fs/amigaos4/amigaos4-fs-factory.h +++ b/engine/backend/fs/amigaos4/amigaos4-fs-factory.h @@ -25,7 +25,6 @@ #ifndef AMIGAOS_FILESYSTEM_FACTORY_H #define AMIGAOS_FILESYSTEM_FACTORY_H -#include "common/singleton.h" #include "engine/backend/fs/fs-factory.h" /** @@ -33,19 +32,11 @@ * * Parts of this class are documented in the base interface class, FilesystemFactory. */ -class AmigaOSFilesystemFactory : public FilesystemFactory, public Common::Singleton { +class AmigaOSFilesystemFactory : public FilesystemFactory { public: - typedef Common::String String; - - virtual AbstractFilesystemNode *makeRootFileNode() const; - virtual AbstractFilesystemNode *makeCurrentDirectoryFileNode() const; - virtual AbstractFilesystemNode *makeFileNodePath(const String &path) const; - -protected: - AmigaOSFilesystemFactory() {}; - -private: - friend class Common::Singleton; + virtual AbstractFSNode *makeRootFileNode() const; + virtual AbstractFSNode *makeCurrentDirectoryFileNode() const; + virtual AbstractFSNode *makeFileNodePath(const Common::String &path) const; }; #endif /*AMIGAOS_FILESYSTEM_FACTORY_H*/ diff --git a/engine/backend/fs/amigaos4/amigaos4-fs.cpp b/engine/backend/fs/amigaos4/amigaos4-fs.cpp index 7f854400b14..af9807ce1d5 100644 --- a/engine/backend/fs/amigaos4/amigaos4-fs.cpp +++ b/engine/backend/fs/amigaos4/amigaos4-fs.cpp @@ -35,9 +35,10 @@ #include #endif -#include "common/util.h" #include "common/debug.h" +#include "common/util.h" #include "engine/backend/fs/abstract-fs.h" +#include "engine/backend/fs/stdiostream.h" #define ENTER() /* debug(6, "Enter") */ #define LEAVE() /* debug(6, "Leave") */ @@ -47,18 +48,18 @@ const uint32 kExAllBufferSize = 40960; // TODO: is this okay for sure? /** * Implementation of the ScummVM file system API. * - * Parts of this class are documented in the base interface class, AbstractFilesystemNode. + * Parts of this class are documented in the base interface class, AbstractFSNode. */ -class AmigaOSFilesystemNode : public AbstractFilesystemNode { +class AmigaOSFilesystemNode : public AbstractFSNode { protected: BPTR _pFileLock; - String _sDisplayName; - String _sPath; + Common::String _sDisplayName; + Common::String _sPath; bool _bIsDirectory; bool _bIsValid; /** - * Obtain the FileInfoBlock protection value for this FilesystemNode, + * Obtain the FileInfoBlock protection value for this FSNode, * as defined in the header. * * @return -1 if there were errors, 0 or a positive integer otherwise. @@ -74,9 +75,9 @@ public: /** * Creates a AmigaOSFilesystemNode for a given path. * - * @param path String with the path the new node should point to. + * @param path Common::String with the path the new node should point to. */ - AmigaOSFilesystemNode(const String &p); + AmigaOSFilesystemNode(const Common::String &p); /** * FIXME: document this constructor. @@ -96,16 +97,19 @@ public: virtual ~AmigaOSFilesystemNode(); virtual bool exists() const; - virtual String getDisplayName() const { return _sDisplayName; }; - virtual String getName() const { return _sDisplayName; }; - virtual String getPath() const { return _sPath; }; + virtual Common::String getDisplayName() const { return _sDisplayName; }; + virtual Common::String getName() const { return _sDisplayName; }; + virtual Common::String getPath() const { return _sPath; }; virtual bool isDirectory() const { return _bIsDirectory; }; virtual bool isReadable() const; virtual bool isWritable() const; - virtual AbstractFilesystemNode *getChild(const String &n) const; + virtual AbstractFSNode *getChild(const Common::String &n) const; virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const; - virtual AbstractFilesystemNode *getParent() const; + virtual AbstractFSNode *getParent() const; + + virtual Common::SeekableReadStream *createReadStream(); + virtual Common::WriteStream *createWriteStream(); /** * Creates a list with all the volumes present in the root node. @@ -116,14 +120,14 @@ public: /** * Returns the last component of a given path. * - * @param str String containing the path. + * @param str Common::String containing the path. * @return Pointer to the first char of the last component inside str. */ const char *lastPathComponent(const Common::String &str) { int offset = str.size(); if (offset <= 0) { - //debug(6, "Bad offset"); + debug(6, "Bad offset"); return 0; } @@ -148,7 +152,7 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode() { LEAVE(); } -AmigaOSFilesystemNode::AmigaOSFilesystemNode(const String &p) { +AmigaOSFilesystemNode::AmigaOSFilesystemNode(const Common::String &p) { ENTER(); int offset = p.size(); @@ -156,18 +160,18 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(const String &p) { //assert(offset > 0); if (offset <= 0) { - //debug(6, "Bad offset"); + debug(6, "Bad offset"); return; } _sPath = p; - _sDisplayName = lastPathComponent(_sPath); + _sDisplayName = ::lastPathComponent(_sPath); _pFileLock = 0; _bIsDirectory = false; struct FileInfoBlock *fib = (struct FileInfoBlock *)IDOS->AllocDosObject(DOS_FIB, NULL); if (!fib) { - //debug(6, "FileInfoBlock is NULL"); + debug(6, "FileInfoBlock is NULL"); LEAVE(); return; } @@ -215,7 +219,7 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(BPTR pLock, const char *pDisplayNam if (IDOS->IoErr() != ERROR_LINE_TOO_LONG) { _bIsValid = false; - //debug(6, "IoErr() != ERROR_LINE_TOO_LONG"); + debug(6, "IoErr() != ERROR_LINE_TOO_LONG"); LEAVE(); delete[] n; return; @@ -230,7 +234,7 @@ AmigaOSFilesystemNode::AmigaOSFilesystemNode(BPTR pLock, const char *pDisplayNam struct FileInfoBlock *fib = (struct FileInfoBlock *)IDOS->AllocDosObject(DOS_FIB, NULL); if (!fib) { - //debug(6, "FileInfoBlock is NULL"); + debug(6, "FileInfoBlock is NULL"); LEAVE(); return; } @@ -282,7 +286,7 @@ bool AmigaOSFilesystemNode::exists() const { struct FileInfoBlock *fib = (struct FileInfoBlock *)IDOS->AllocDosObject(DOS_FIB, NULL); if (!fib) { - //debug(6, "FileInfoBlock is NULL"); + debug(6, "FileInfoBlock is NULL"); LEAVE(); return false; } @@ -299,27 +303,19 @@ bool AmigaOSFilesystemNode::exists() const { return nodeExists; } -AbstractFilesystemNode *AmigaOSFilesystemNode::getChild(const String &n) const { +AbstractFSNode *AmigaOSFilesystemNode::getChild(const Common::String &n) const { ENTER(); if (!_bIsDirectory) { - //debug(6, "Not a directory"); + debug(6, "Not a directory"); return 0; } - String newPath(_sPath); + Common::String newPath(_sPath); if (_sPath.lastChar() != '/') newPath += '/'; newPath += n; - BPTR lock = IDOS->Lock(newPath.c_str(), SHARED_LOCK); - - if (!lock) { - //debug(6, "Bad path"); - return 0; - } - - IDOS->UnLock(lock); LEAVE(); return new AmigaOSFilesystemNode(newPath); @@ -331,19 +327,19 @@ bool AmigaOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b //TODO: honor the hidden flag if (!_bIsValid) { - //debug(6, "Invalid node"); + debug(6, "Invalid node"); LEAVE(); return false; // Empty list } if (!_bIsDirectory) { - //debug(6, "Not a directory"); + debug(6, "Not a directory"); LEAVE(); return false; // Empty list } if (_pFileLock == 0) { - //debug(6, "Root node"); + debug(6, "Root node"); LEAVE(); myList = listVolumes(); return true; @@ -368,17 +364,17 @@ bool AmigaOSFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b struct ExAllData *ead = data; do { - if ((mode == FilesystemNode::kListAll) || - (EAD_IS_DRAWER(ead) && (mode == FilesystemNode::kListDirectoriesOnly)) || - (EAD_IS_FILE(ead) && (mode == FilesystemNode::kListFilesOnly))) { - String full_path = _sPath; + if ((mode == Common::FSNode::kListAll) || + (EAD_IS_DRAWER(ead) && (mode == Common::FSNode::kListDirectoriesOnly)) || + (EAD_IS_FILE(ead) && (mode == Common::FSNode::kListFilesOnly))) { + Common::String full_path = _sPath; full_path += (char*)ead->ed_Name; BPTR lock = IDOS->Lock((STRPTR)full_path.c_str(), SHARED_LOCK); if (lock) { AmigaOSFilesystemNode *entry = new AmigaOSFilesystemNode(lock, (char *)ead->ed_Name); if (entry) { - //FIXME: since the isValid() function is no longer part of the AbstractFilesystemNode + //FIXME: since the isValid() function is no longer part of the AbstractFSNode // specification, the following call had to be changed: // if (entry->isValid()) // Please verify that the logic of the code remains coherent. Also, remember @@ -412,7 +408,7 @@ int AmigaOSFilesystemNode::getFibProtection() const { int fibProt = -1; struct FileInfoBlock *fib = (struct FileInfoBlock *)IDOS->AllocDosObject(DOS_FIB, NULL); if (!fib) { - //debug(6, "FileInfoBlock is NULL"); + debug(6, "FileInfoBlock is NULL"); LEAVE(); return fibProt; } @@ -430,17 +426,17 @@ int AmigaOSFilesystemNode::getFibProtection() const { return fibProt; } -AbstractFilesystemNode *AmigaOSFilesystemNode::getParent() const { +AbstractFSNode *AmigaOSFilesystemNode::getParent() const { ENTER(); if (!_bIsDirectory) { - //debug(6, "Not a directory"); + debug(6, "Not a directory"); LEAVE(); return 0; } if (_pFileLock == 0) { - //debug(6, "Root node"); + debug(6, "Root node"); LEAVE(); return new AmigaOSFilesystemNode(*this); } @@ -502,7 +498,7 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const { struct DosList *dosList = IDOS->LockDosList(kLockFlags); if (!dosList) { - //debug(6, "Cannot lock the DOS list"); + debug(6, "Cannot lock the DOS list"); LEAVE(); return myList; } @@ -540,7 +536,7 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const { AmigaOSFilesystemNode *entry = new AmigaOSFilesystemNode(volumeLock, buffer); if (entry) { - //FIXME: since the isValid() function is no longer part of the AbstractFilesystemNode + //FIXME: since the isValid() function is no longer part of the AbstractFSNode // specification, the following call had to be changed: // if (entry->isValid()) // Please verify that the logic of the code remains coherent. Also, remember @@ -566,4 +562,12 @@ AbstractFSList AmigaOSFilesystemNode::listVolumes() const { return myList; } +Common::SeekableReadStream *AmigaOSFilesystemNode::createReadStream() { + return StdioStream::makeFromPath(getPath().c_str(), false); +} + +Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() { + return StdioStream::makeFromPath(getPath().c_str(), true); +} + #endif //defined(__amigaos4__) diff --git a/engine/backend/fs/fs-factory.h b/engine/backend/fs/fs-factory.h index 1e2065f158c..42407e259de 100644 --- a/engine/backend/fs/fs-factory.h +++ b/engine/backend/fs/fs-factory.h @@ -29,7 +29,7 @@ #include "engine/backend/fs/abstract-fs.h" /** - * Creates concrete FilesystemNode objects depending on the current architecture. + * Creates concrete FSNode objects depending on the current architecture. */ class FilesystemFactory { public: @@ -44,7 +44,7 @@ public: * emulate it or simply return some "sensible" default directory node, * e.g. the same value as getRoot() returns. */ - virtual AbstractFilesystemNode *makeCurrentDirectoryFileNode() const = 0; + virtual AbstractFSNode *makeCurrentDirectoryFileNode() const = 0; /** * Construct a node based on a path; the path is in the same format as it @@ -54,9 +54,9 @@ public: * identical to oldNode. Hence, we can use the "path" value for persistent * storage e.g. in the config file. * - * @param path The path string to create a FilesystemNode for. + * @param path The path string to create a FSNode for. */ - virtual AbstractFilesystemNode *makeFileNodePath(const Common::String &path) const = 0; + virtual AbstractFSNode *makeFileNodePath(const Common::String &path) const = 0; /** * Returns a special node representing the filesystem root. @@ -65,7 +65,7 @@ public: * On Unix, this will be simply the node for / (the root directory). * On Windows, it will be a special node which "contains" all drives (C:, D:, E:). */ - virtual AbstractFilesystemNode *makeRootFileNode() const = 0; + virtual AbstractFSNode *makeRootFileNode() const = 0; }; #endif /*FILESYSTEM_FACTORY_H*/ diff --git a/engine/backend/fs/posix/posix-fs-factory.cpp b/engine/backend/fs/posix/posix-fs-factory.cpp index af5a22e96a1..2d356f669c3 100644 --- a/engine/backend/fs/posix/posix-fs-factory.cpp +++ b/engine/backend/fs/posix/posix-fs-factory.cpp @@ -26,19 +26,17 @@ #include "engine/backend/fs/posix/posix-fs-factory.h" #include "engine/backend/fs/posix/posix-fs.cpp" -DECLARE_SINGLETON(POSIXFilesystemFactory); - -AbstractFilesystemNode *POSIXFilesystemFactory::makeRootFileNode() const { - return new POSIXFilesystemNode(); +AbstractFSNode *POSIXFilesystemFactory::makeRootFileNode() const { + return new POSIXFilesystemNode("/"); } -AbstractFilesystemNode *POSIXFilesystemFactory::makeCurrentDirectoryFileNode() const { +AbstractFSNode *POSIXFilesystemFactory::makeCurrentDirectoryFileNode() const { char buf[MAXPATHLEN]; - getcwd(buf, MAXPATHLEN); - return new POSIXFilesystemNode(buf, true); + return getcwd(buf, MAXPATHLEN) ? new POSIXFilesystemNode(buf) : NULL; } -AbstractFilesystemNode *POSIXFilesystemFactory::makeFileNodePath(const String &path) const { - return new POSIXFilesystemNode(path, true); +AbstractFSNode *POSIXFilesystemFactory::makeFileNodePath(const Common::String &path) const { + assert(!path.empty()); + return new POSIXFilesystemNode(path); } #endif diff --git a/engine/backend/fs/posix/posix-fs-factory.h b/engine/backend/fs/posix/posix-fs-factory.h index 0d01e8e6063..e43063b097d 100644 --- a/engine/backend/fs/posix/posix-fs-factory.h +++ b/engine/backend/fs/posix/posix-fs-factory.h @@ -25,7 +25,6 @@ #ifndef POSIX_FILESYSTEM_FACTORY_H #define POSIX_FILESYSTEM_FACTORY_H -#include "common/singleton.h" #include "engine/backend/fs/fs-factory.h" /** @@ -33,19 +32,10 @@ * * Parts of this class are documented in the base interface class, FilesystemFactory. */ -class POSIXFilesystemFactory : public FilesystemFactory, public Common::Singleton { -public: - typedef Common::String String; - - virtual AbstractFilesystemNode *makeRootFileNode() const; - virtual AbstractFilesystemNode *makeCurrentDirectoryFileNode() const; - virtual AbstractFilesystemNode *makeFileNodePath(const String &path) const; - -protected: - POSIXFilesystemFactory() {}; - -private: - friend class Common::Singleton; +class POSIXFilesystemFactory : public FilesystemFactory { + virtual AbstractFSNode *makeRootFileNode() const; + virtual AbstractFSNode *makeCurrentDirectoryFileNode() const; + virtual AbstractFSNode *makeFileNodePath(const Common::String &path) const; }; #endif /*POSIX_FILESYSTEM_FACTORY_H*/ diff --git a/engine/backend/fs/posix/posix-fs.cpp b/engine/backend/fs/posix/posix-fs.cpp index 43b69bc388d..4500fbf5999 100644 --- a/engine/backend/fs/posix/posix-fs.cpp +++ b/engine/backend/fs/posix/posix-fs.cpp @@ -25,84 +25,19 @@ #if defined(UNIX) #include "engine/backend/fs/abstract-fs.h" +#include "engine/backend/fs/stdiostream.h" +#include "common/algorithm.h" -#ifdef MACOSX -#include -#endif #include #include #include #include -#include -/** - * Implementation of the ScummVM file system API based on POSIX. - * - * Parts of this class are documented in the base interface class, AbstractFilesystemNode. - */ -class POSIXFilesystemNode : public AbstractFilesystemNode { -protected: - String _displayName; - String _path; - bool _isDirectory; - bool _isValid; +#ifdef __OS2__ +#define INCL_DOS +#include +#endif -public: - /** - * Creates a POSIXFilesystemNode with the root node as path. - */ - POSIXFilesystemNode(); - - /** - * Creates a POSIXFilesystemNode for a given path. - * - * @param path String with the path the new node should point to. - * @param verify true if the isValid and isDirectory flags should be verified during the construction. - */ - POSIXFilesystemNode(const String &path, bool verify); - - virtual bool exists() const { return access(_path.c_str(), F_OK) == 0; } - virtual String getDisplayName() const { return _displayName; } - virtual String getName() const { return _displayName; } - virtual String getPath() const { return _path; } - virtual bool isDirectory() const { return _isDirectory; } - virtual bool isReadable() const { return access(_path.c_str(), R_OK) == 0; } - virtual bool isWritable() const { return access(_path.c_str(), W_OK) == 0; } - - virtual AbstractFilesystemNode *getChild(const String &n) const; - virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const; - virtual AbstractFilesystemNode *getParent() const; - -private: - /** - * Tests and sets the _isValid and _isDirectory flags, using the stat() function. - */ - virtual void setFlags(); -}; - -/** - * Returns the last component of a given path. - * - * Examples: - * /foo/bar.txt would return /bar.txt - * /foo/bar/ would return /bar/ - * - * @param str String containing the path. - * @return Pointer to the first char of the last component inside str. - */ -const char *lastPathComponent(const Common::String &str) { - if(str.empty()) - return ""; - - const char *start = str.c_str(); - const char *cur = start + str.size() - 2; - - while (cur >= start && *cur != '/') { - --cur; - } - - return cur + 1; -} void POSIXFilesystemNode::setFlags() { struct stat st; @@ -111,15 +46,7 @@ void POSIXFilesystemNode::setFlags() { _isDirectory = _isValid ? S_ISDIR(st.st_mode) : false; } -POSIXFilesystemNode::POSIXFilesystemNode() { - // The root dir. - _path = "/"; - _displayName = _path; - _isValid = true; - _isDirectory = true; -} - -POSIXFilesystemNode::POSIXFilesystemNode(const String &p, bool verify) { +POSIXFilesystemNode::POSIXFilesystemNode(const Common::String &p) { assert(p.size() > 0); // Expand "~/" to the value of the HOME env variable @@ -135,29 +62,84 @@ POSIXFilesystemNode::POSIXFilesystemNode(const String &p, bool verify) { _path = p; } - _displayName = lastPathComponent(_path); +#ifdef __OS2__ + // On OS/2, 'X:/' is a root of drive X, so we should not remove that last + // slash. + if (!(_path.size() == 3 && _path.hasSuffix(":/"))) +#endif + // Normalize the path (that is, remove unneeded slashes etc.) + _path = Common::normalizePath(_path, '/'); + _displayName = Common::lastPathComponent(_path, '/'); - if (verify) { - setFlags(); + // TODO: should we turn relative paths into absolute ones? + // Pro: Ensures the "getParent" works correctly even for relative dirs. + // Contra: The user may wish to use (and keep!) relative paths in his + // config file, and converting relative to absolute paths may hurt him... + // + // An alternative approach would be to change getParent() to work correctly + // if "_path" is the empty string. +#if 0 + if (!_path.hasPrefix("/")) { + char buf[MAXPATHLEN+1]; + getcwd(buf, MAXPATHLEN); + strcat(buf, "/"); + _path = buf + _path; } +#endif + // TODO: Should we enforce that the path is absolute at this point? + //assert(_path.hasPrefix("/")); + + setFlags(); } -AbstractFilesystemNode *POSIXFilesystemNode::getChild(const String &n) const { - // FIXME: Pretty lame implementation! We do no error checking to speak - // of, do not check if this is a special node, etc. +AbstractFSNode *POSIXFilesystemNode::getChild(const Common::String &n) const { + assert(!_path.empty()); assert(_isDirectory); - String newPath(_path); + // Make sure the string contains no slashes + assert(!n.contains('/')); + + // We assume here that _path is already normalized (hence don't bother to call + // Common::normalizePath on the final path). + Common::String newPath(_path); if (_path.lastChar() != '/') newPath += '/'; newPath += n; - return new POSIXFilesystemNode(newPath, true); + return makeNode(newPath); } bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, bool hidden) const { assert(_isDirectory); +#ifdef __OS2__ + if (_path == "/") { + // Special case for the root dir: List all DOS drives + ULONG ulDrvNum; + ULONG ulDrvMap; + + DosQueryCurrentDisk(&ulDrvNum, &ulDrvMap); + + for (int i = 0; i < 26; i++) { + if (ulDrvMap & 1) { + char drive_root[] = "A:/"; + drive_root[0] += i; + + POSIXFilesystemNode *entry = new POSIXFilesystemNode(); + entry->_isDirectory = true; + entry->_isValid = true; + entry->_path = drive_root; + entry->_displayName = "[" + Common::String(drive_root, 2) + "]"; + myList.push_back(entry); + } + + ulDrvMap >>= 1; + } + + return true; + } +#endif + DIR *dirp = opendir(_path.c_str()); struct dirent *dp; @@ -175,12 +157,12 @@ bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, boo continue; } - String newPath(_path); - if (newPath.lastChar() != '/') - newPath += '/'; - newPath += dp->d_name; - - POSIXFilesystemNode entry(newPath, false); + // Start with a clone of this node, with the correct path set + POSIXFilesystemNode entry(*this); + entry._displayName = dp->d_name; + if (_path.lastChar() != '/') + entry._path += '/'; + entry._path += entry._displayName; #if defined(SYSTEM_NOT_SUPPORTING_D_TYPE) /* TODO: d_type is not part of POSIX, so it might not be supported @@ -215,13 +197,10 @@ bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, boo continue; // Honor the chosen mode - if ((mode == FilesystemNode::kListFilesOnly && entry._isDirectory) || - (mode == FilesystemNode::kListDirectoriesOnly && !entry._isDirectory)) + if ((mode == Common::FSNode::kListFilesOnly && entry._isDirectory) || + (mode == Common::FSNode::kListDirectoriesOnly && !entry._isDirectory)) continue; - if (entry._isDirectory) - entry._path += "/"; - myList.push_back(new POSIXFilesystemNode(entry)); } closedir(dirp); @@ -229,14 +208,41 @@ bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, boo return true; } -AbstractFilesystemNode *POSIXFilesystemNode::getParent() const { +AbstractFSNode *POSIXFilesystemNode::getParent() const { if (_path == "/") - return 0; + return 0; // The filesystem root has no parent + +#ifdef __OS2__ + if (_path.size() == 3 && _path.hasSuffix(":/")) + // This is a root directory of a drive + return makeNode("/"); // return a virtual root for a list of drives +#endif const char *start = _path.c_str(); - const char *end = lastPathComponent(_path); + const char *end = start + _path.size(); - return new POSIXFilesystemNode(String(start, end - start), true); + // Strip of the last component. We make use of the fact that at this + // point, _path is guaranteed to be normalized + while (end > start && *(end-1) != '/') + end--; + + if (end == start) { + // This only happens if we were called with a relative path, for which + // there simply is no parent. + // TODO: We could also resolve this by assuming that the parent is the + // current working directory, and returning a node referring to that. + return 0; + } + + return makeNode(Common::String(start, end)); +} + +Common::SeekableReadStream *POSIXFilesystemNode::createReadStream() { + return StdioStream::makeFromPath(getPath().c_str(), false); +} + +Common::WriteStream *POSIXFilesystemNode::createWriteStream() { + return StdioStream::makeFromPath(getPath().c_str(), true); } #endif //#if defined(UNIX) diff --git a/engine/backend/fs/posix/posix-fs.h b/engine/backend/fs/posix/posix-fs.h new file mode 100644 index 00000000000..94e825d62f2 --- /dev/null +++ b/engine/backend/fs/posix/posix-fs.h @@ -0,0 +1,86 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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 POSIX_FILESYSTEM_H +#define POSIX_FILESYSTEM_H + +#include "engine/backend/fs/abstract-fs.h" + +#ifdef MACOSX +#include +#endif +#include + +/** + * Implementation of the ScummVM file system API based on POSIX. + * + * Parts of this class are documented in the base interface class, AbstractFSNode. + */ +class POSIXFilesystemNode : public AbstractFSNode { +protected: + Common::String _displayName; + Common::String _path; + bool _isDirectory; + bool _isValid; + + virtual AbstractFSNode *makeNode(const Common::String &path) const { + return new POSIXFilesystemNode(path); + } + + /** + * Plain constructor, for internal use only (hence protected). + */ + POSIXFilesystemNode() : _isDirectory(false), _isValid(false) {} + +public: + /** + * Creates a POSIXFilesystemNode for a given path. + * + * @param path the path the new node should point to. + */ + POSIXFilesystemNode(const Common::String &path); + + virtual bool exists() const { return access(_path.c_str(), F_OK) == 0; } + virtual Common::String getDisplayName() const { return _displayName; } + virtual Common::String getName() const { return _displayName; } + virtual Common::String getPath() const { return _path; } + virtual bool isDirectory() const { return _isDirectory; } + virtual bool isReadable() const { return access(_path.c_str(), R_OK) == 0; } + virtual bool isWritable() const { return access(_path.c_str(), W_OK) == 0; } + + virtual AbstractFSNode *getChild(const Common::String &n) const; + virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const; + virtual AbstractFSNode *getParent() const; + + virtual Common::SeekableReadStream *createReadStream(); + virtual Common::WriteStream *createWriteStream(); + +private: + /** + * Tests and sets the _isValid and _isDirectory flags, using the stat() function. + */ + virtual void setFlags(); +}; + +#endif /*POSIX_FILESYSTEM_H*/ diff --git a/engine/backend/fs/stdiostream.cpp b/engine/backend/fs/stdiostream.cpp new file mode 100644 index 00000000000..64d5d74246f --- /dev/null +++ b/engine/backend/fs/stdiostream.cpp @@ -0,0 +1,168 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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 "engine/backend/fs/stdiostream.h" + +#include + +#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 ferror(a) ps2_ferror(a) + #define clearerr(a) ps2_clearerr(a) + + //#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 replace 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" + + + // 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 fflush(file) DS::std_fflush(file) + #undef ferror + #define ferror(handle) DS::std_ferror(handle) + +#endif + +StdioStream::StdioStream(void *handle) : _handle(handle) { + assert(handle); +} + +StdioStream::~StdioStream() { + fclose((FILE *)_handle); +} + +bool StdioStream::err() const { + return ferror((FILE *)_handle) != 0; +} + +void StdioStream::clearErr() { + clearerr((FILE *)_handle); +} + +bool StdioStream::eos() const { + return feof((FILE *)_handle) != 0; +} + +int32 StdioStream::pos() const { + return ftell((FILE *)_handle); +} + +int32 StdioStream::size() const { + int32 oldPos = ftell((FILE *)_handle); + fseek((FILE *)_handle, 0, SEEK_END); + int32 length = ftell((FILE *)_handle); + fseek((FILE *)_handle, oldPos, SEEK_SET); + + return length; +} + +bool StdioStream::seek(int32 offs, int whence) { + return fseek((FILE *)_handle, offs, whence) == 0; +} + +uint32 StdioStream::read(void *ptr, uint32 len) { + return fread((byte *)ptr, 1, len, (FILE *)_handle); +} + +uint32 StdioStream::write(const void *ptr, uint32 len) { + return fwrite(ptr, 1, len, (FILE *)_handle); +} + +bool StdioStream::flush() { + return fflush((FILE *)_handle) == 0; +} + +StdioStream *StdioStream::makeFromPath(const Common::String &path, bool writeMode) { + FILE *handle = fopen(path.c_str(), writeMode ? "wb" : "rb"); + +#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 (handle && !writeMode) { + setvbuf(handle, NULL, _IOFBF, 8192); + } +#endif + +#if defined(__WII__) + // disable newlib's buffering, the device libraries handle caching + if (handle) + setvbuf(handle, NULL, _IONBF, 0); +#endif + + if (handle) + return new StdioStream(handle); + return 0; +} diff --git a/engine/backend/fs/stdiostream.h b/engine/backend/fs/stdiostream.h new file mode 100644 index 00000000000..1fb3669204f --- /dev/null +++ b/engine/backend/fs/stdiostream.h @@ -0,0 +1,61 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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 BACKENDS_FS_STDIOSTREAM_H +#define BACKENDS_FS_STDIOSTREAM_H + +#include "common/sys.h" +#include "common/noncopyable.h" +#include "common/stream.h" +#include "common/str.h" + +class StdioStream : public Common::SeekableReadStream, public Common::WriteStream, public Common::NonCopyable { +protected: + /** File handle to the actual file. */ + void *_handle; + +public: + /** + * Given a path, invokes fopen on that path and wrap the result in a + * StdioStream instance. + */ + static StdioStream *makeFromPath(const Common::String &path, bool writeMode); + + StdioStream(void *handle); + virtual ~StdioStream(); + + bool err() const; + void clearErr(); + bool eos() const; + + virtual uint32 write(const void *dataPtr, uint32 dataSize); + virtual bool flush(); + + virtual int32 pos() const; + virtual int32 size() const; + bool seek(int32 offs, int whence = SEEK_SET); + uint32 read(void *dataPtr, uint32 dataSize); +}; + +#endif diff --git a/engine/backend/fs/windows/windows-fs-factory.cpp b/engine/backend/fs/windows/windows-fs-factory.cpp index 212fdace6e5..7229c37fe5e 100644 --- a/engine/backend/fs/windows/windows-fs-factory.cpp +++ b/engine/backend/fs/windows/windows-fs-factory.cpp @@ -26,17 +26,15 @@ #include "engine/backend/fs/windows/windows-fs-factory.h" #include "engine/backend/fs/windows/windows-fs.cpp" -DECLARE_SINGLETON(WindowsFilesystemFactory); - -AbstractFilesystemNode *WindowsFilesystemFactory::makeRootFileNode() const { +AbstractFSNode *WindowsFilesystemFactory::makeRootFileNode() const { return new WindowsFilesystemNode(); } -AbstractFilesystemNode *WindowsFilesystemFactory::makeCurrentDirectoryFileNode() const { +AbstractFSNode *WindowsFilesystemFactory::makeCurrentDirectoryFileNode() const { return new WindowsFilesystemNode("", true); } -AbstractFilesystemNode *WindowsFilesystemFactory::makeFileNodePath(const String &path) const { +AbstractFSNode *WindowsFilesystemFactory::makeFileNodePath(const Common::String &path) const { return new WindowsFilesystemNode(path, false); } #endif diff --git a/engine/backend/fs/windows/windows-fs-factory.h b/engine/backend/fs/windows/windows-fs-factory.h index cc3cdd1caa4..698a45c15b9 100644 --- a/engine/backend/fs/windows/windows-fs-factory.h +++ b/engine/backend/fs/windows/windows-fs-factory.h @@ -25,7 +25,6 @@ #ifndef WINDOWS_FILESYSTEM_FACTORY_H #define WINDOWS_FILESYSTEM_FACTORY_H -#include "common/singleton.h" #include "engine/backend/fs/fs-factory.h" /** @@ -33,19 +32,11 @@ * * Parts of this class are documented in the base interface class, FilesystemFactory. */ -class WindowsFilesystemFactory : public FilesystemFactory, public Common::Singleton { +class WindowsFilesystemFactory : public FilesystemFactory { public: - typedef Common::String String; - - virtual AbstractFilesystemNode *makeRootFileNode() const; - virtual AbstractFilesystemNode *makeCurrentDirectoryFileNode() const; - virtual AbstractFilesystemNode *makeFileNodePath(const String &path) const; - -protected: - WindowsFilesystemFactory() {}; - -private: - friend class Common::Singleton; + virtual AbstractFSNode *makeRootFileNode() const; + virtual AbstractFSNode *makeCurrentDirectoryFileNode() const; + virtual AbstractFSNode *makeFileNodePath(const Common::String &path) const; }; #endif /*WINDOWS_FILESYSTEM_FACTORY_H*/ diff --git a/engine/backend/fs/windows/windows-fs.cpp b/engine/backend/fs/windows/windows-fs.cpp index a72830d9563..eed2fc6cc0d 100644 --- a/engine/backend/fs/windows/windows-fs.cpp +++ b/engine/backend/fs/windows/windows-fs.cpp @@ -24,24 +24,20 @@ #ifdef WIN32 -#ifdef ARRAYSIZE +#if defined(ARRAYSIZE) #undef ARRAYSIZE #endif -#ifdef _WIN32_WCE #include // winnt.h defines ARRAYSIZE, but we want our own one... #undef ARRAYSIZE +#ifdef _WIN32_WCE #undef GetCurrentDirectory #endif #include "engine/backend/fs/abstract-fs.h" +#include "engine/backend/fs/stdiostream.h" #include #include #include -#ifndef _WIN32_WCE -#include -// winnt.h defines ARRAYSIZE, but we want our own one... -#undef ARRAYSIZE -#endif #include // F_OK, R_OK and W_OK are not defined under MSVC, so we define them here @@ -62,12 +58,12 @@ /** * Implementation of the ScummVM file system API based on Windows API. * - * Parts of this class are documented in the base interface class, AbstractFilesystemNode. + * Parts of this class are documented in the base interface class, AbstractFSNode. */ -class WindowsFilesystemNode : public AbstractFilesystemNode { +class WindowsFilesystemNode : public AbstractFSNode { protected: - String _displayName; - String _path; + Common::String _displayName; + Common::String _path; bool _isDirectory; bool _isPseudoRoot; bool _isValid; @@ -89,22 +85,25 @@ public: * path=c:\foo\bar.txt, currentDir=true -> current directory * path=NULL, currentDir=true -> current directory * - * @param path String with the path the new node should point to. + * @param path Common::String with the path the new node should point to. * @param currentDir if true, the path parameter will be ignored and the resulting node will point to the current directory. */ - WindowsFilesystemNode(const String &path, const bool currentDir); + WindowsFilesystemNode(const Common::String &path, const bool currentDir); virtual bool exists() const { return _access(_path.c_str(), F_OK) == 0; } - virtual String getDisplayName() const { return _displayName; } - virtual String getName() const { return _displayName; } - virtual String getPath() const { return _path; } + virtual Common::String getDisplayName() const { return _displayName; } + virtual Common::String getName() const { return _displayName; } + virtual Common::String getPath() const { return _path; } virtual bool isDirectory() const { return _isDirectory; } virtual bool isReadable() const { return _access(_path.c_str(), R_OK) == 0; } virtual bool isWritable() const { return _access(_path.c_str(), W_OK) == 0; } - virtual AbstractFilesystemNode *getChild(const String &n) const; + virtual AbstractFSNode *getChild(const Common::String &n) const; virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const; - virtual AbstractFilesystemNode *getParent() const; + virtual AbstractFSNode *getParent() const; + + virtual Common::SeekableReadStream *createReadStream(); + virtual Common::WriteStream *createWriteStream(); private: /** @@ -113,7 +112,7 @@ private: * * @param list List to put the file entry node in. * @param mode Mode to use while adding the file entry to the list. - * @param base String with the directory being listed. + * @param base Common::String with the directory being listed. * @param find_data Describes a file that the FindFirstFile, FindFirstFileEx, or FindNextFile functions find. */ static void addFile(AbstractFSList &list, ListMode mode, const char *base, WIN32_FIND_DATA* find_data); @@ -121,7 +120,7 @@ private: /** * Converts a Unicode string to Ascii format. * - * @param str String to convert from Unicode to Ascii. + * @param str Common::String to convert from Unicode to Ascii. * @return str in Ascii format. */ static char *toAscii(TCHAR *str); @@ -129,36 +128,12 @@ private: /** * Converts an Ascii string to Unicode format. * - * @param str String to convert from Ascii to Unicode. + * @param str Common::String to convert from Ascii to Unicode. * @return str in Unicode format. */ static const TCHAR* toUnicode(const char *str); }; -/** - * Returns the last component of a given path. - * - * Examples: - * c:\foo\bar.txt would return "\bar.txt" - * c:\foo\bar\ would return "\bar\" - * - * @param str Path to obtain the last component from. - * @return Pointer to the first char of the last component inside str. - */ -const char *lastPathComponent(const Common::String &str) { - if(str.empty()) - return ""; - - const char *start = str.c_str(); - const char *cur = start + str.size() - 2; - - while (cur >= start && *cur != '\\') { - --cur; - } - - return cur + 1; -} - void WindowsFilesystemNode::addFile(AbstractFSList &list, ListMode mode, const char *base, WIN32_FIND_DATA* find_data) { WindowsFilesystemNode entry; char *asciiName = toAscii(find_data->cFileName); @@ -170,8 +145,8 @@ void WindowsFilesystemNode::addFile(AbstractFSList &list, ListMode mode, const c isDirectory = (find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? true : false); - if ((!isDirectory && mode == FilesystemNode::kListDirectoriesOnly) || - (isDirectory && mode == FilesystemNode::kListFilesOnly)) + if ((!isDirectory && mode == Common::FSNode::kListDirectoriesOnly) || + (isDirectory && mode == Common::FSNode::kListFilesOnly)) return; entry._isDirectory = isDirectory; @@ -222,18 +197,17 @@ WindowsFilesystemNode::WindowsFilesystemNode() { #endif } -WindowsFilesystemNode::WindowsFilesystemNode(const String &p, const bool currentDir) { +WindowsFilesystemNode::WindowsFilesystemNode(const Common::String &p, const bool currentDir) { if (currentDir) { char path[MAX_PATH]; GetCurrentDirectory(MAX_PATH, path); _path = path; - } - else { + } else { assert(p.size() > 0); _path = p; } - _displayName = lastPathComponent(_path); + _displayName = lastPathComponent(_path, '\\'); // Check whether it is a directory, and whether the file actually exists DWORD fileAttribs = GetFileAttributes(toUnicode(_path.c_str())); @@ -245,26 +219,24 @@ WindowsFilesystemNode::WindowsFilesystemNode(const String &p, const bool current _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0); _isValid = true; // Add a trailing slash, if necessary. - if (_path.lastChar() != '\\') { + if (_isDirectory && _path.lastChar() != '\\') { _path += '\\'; } } _isPseudoRoot = false; } -AbstractFilesystemNode *WindowsFilesystemNode::getChild(const String &n) const { +AbstractFSNode *WindowsFilesystemNode::getChild(const Common::String &n) const { assert(_isDirectory); - String newPath(_path); + // Make sure the string contains no slashes + assert(!n.contains('/')); + + Common::String newPath(_path); if (_path.lastChar() != '\\') newPath += '\\'; newPath += n; - // Check whether the directory actually exists - DWORD fileAttribs = GetFileAttributes(toUnicode(newPath.c_str())); - if (fileAttribs == INVALID_FILE_ATTRIBUTES) - return 0; - return new WindowsFilesystemNode(newPath, false); } @@ -319,7 +291,7 @@ bool WindowsFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, b return true; } -AbstractFilesystemNode *WindowsFilesystemNode::getParent() const { +AbstractFSNode *WindowsFilesystemNode::getParent() const { assert(_isValid || _isPseudoRoot); if (_isPseudoRoot) @@ -328,17 +300,25 @@ AbstractFilesystemNode *WindowsFilesystemNode::getParent() const { WindowsFilesystemNode *p = new WindowsFilesystemNode(); if (_path.size() > 3) { const char *start = _path.c_str(); - const char *end = lastPathComponent(_path); + const char *end = lastPathComponent(_path, '\\'); p = new WindowsFilesystemNode(); - p->_path = String(start, end - start); + p->_path = Common::String(start, end - start); p->_isValid = true; p->_isDirectory = true; - p->_displayName = lastPathComponent(p->_path); + p->_displayName = lastPathComponent(p->_path, '\\'); p->_isPseudoRoot = false; } return p; } +Common::SeekableReadStream *WindowsFilesystemNode::createReadStream() { + return StdioStream::makeFromPath(getPath().c_str(), false); +} + +Common::WriteStream *WindowsFilesystemNode::createWriteStream() { + return StdioStream::makeFromPath(getPath().c_str(), true); +} + #endif //#ifdef WIN32 diff --git a/engine/backend/platform/driver.h b/engine/backend/platform/driver.h index 27d001dc8f4..d4dda4395ff 100644 --- a/engine/backend/platform/driver.h +++ b/engine/backend/platform/driver.h @@ -29,6 +29,7 @@ #include "common/sys.h" #include "common/mutex.h" #include "common/events.h" +#include "common/archive.h" #include "engine/color.h" #include "engine/model.h" @@ -71,6 +72,7 @@ public: }; virtual void init() = 0; + virtual void setupScreen(int screenW, int screenH, bool fullscreen = false) = 0; virtual void toggleFullscreenMode() = 0; @@ -136,22 +138,21 @@ public: virtual const char *getVideoDeviceName() = 0; - /** @name Events and Time */ + /** @name Mouse */ //@{ - typedef unsigned int (*TimerProc)(unsigned int interval, void *param); - - virtual void getTimeAndDate(struct tm &t) const = 0; - + /** + * Move ("warp") the mouse cursor to the specified position in virtual + * screen coordinates. + * @param x the new x position of the mouse + * @param y the new y position of the mouse + */ virtual void warpMouse(int x, int y) = 0; - friend class DefaultEventManager; - /** - * Get the next event in the event queue. - * @param event point to an Event struct, which will be filled with the event data. - * @return true if an event was retrieved. - */ - virtual bool pollEvent(Common::Event &event) = 0; + //@} + + /** @name Events and Time */ + //@{ /** Get the number of milliseconds since the program was started. */ virtual uint32 getMillis() = 0; @@ -159,8 +160,25 @@ public: /** Delay/sleep for the specified amount of milliseconds. */ virtual void delayMillis(uint msecs) = 0; + /** + * Get the current time and date, in the local timezone. + * Corresponds on many systems to the combination of time() + * and localtime(). + */ + virtual void getTimeAndDate(struct tm &t) const = 0; + + /** + * Return the timer manager singleton. For more information, refer + * to the TimerManager documentation. + */ virtual Common::TimerManager *getTimerManager() = 0; + /** + * Return the event manager singleton. For more information, refer + * to the EventManager documentation. + */ + virtual Common::EventManager *getEventManager() = 0; + //@} /** @@ -209,8 +227,11 @@ public: /** @name Sound */ //@{ - virtual void setupMixer() = 0; + /** + * Return the audio mixer. For more information, refer to the + * Audio::Mixer documentation. + */ virtual Audio::Mixer *getMixer() = 0; //@} @@ -219,14 +240,49 @@ public: //@{ /** Quit (exit) the application. */ virtual void quit() = 0; + + /** + * Return the SaveFileManager, used to store and load savestates + * and other modifiable persistent game data. For more information, + * refer to the SaveFileManager documentation. + */ + virtual Common::SaveFileManager *getSavefileManager() = 0; + /** * 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; - virtual Common::SaveFileManager *getSavefileManager() = 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, int 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 *createConfigReadStream() = 0; + + /** + * 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 *createConfigWriteStream() = 0; + //@} protected: diff --git a/engine/backend/platform/sdl/driver_gl.cpp b/engine/backend/platform/sdl/driver_gl.cpp index b1b29b85e1f..f8e80751638 100644 --- a/engine/backend/platform/sdl/driver_gl.cpp +++ b/engine/backend/platform/sdl/driver_gl.cpp @@ -31,8 +31,18 @@ #include "engine/backend/platform/sdl/driver_gl.h" -// Constructor. Should create the driver and open screens, etc. -DriverGL::DriverGL(int screenW, int screenH, int screenBPP, bool fullscreen) { +DriverGL::DriverGL() { + _storedDisplay = NULL; + _emergFont = NULL; +} + +DriverGL::~DriverGL() { + delete[] _storedDisplay; + if (_emergFont && glIsList(_emergFont)) + glDeleteLists(_emergFont, 128); +} + +void DriverGL::setupScreen(int screenW, int screenH, bool fullscreen) { char GLDriver[1024]; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -46,11 +56,11 @@ DriverGL::DriverGL(int screenW, int screenH, int screenBPP, bool fullscreen) { uint32 flags = SDL_OPENGL; if (fullscreen) flags |= SDL_FULLSCREEN; - if (SDL_SetVideoMode(screenW, screenH, screenBPP, flags) == 0) + if (SDL_SetVideoMode(screenW, screenH, 24, flags) == 0) error("Could not initialize video"); _screenWidth = screenW; _screenHeight = screenH; - _screenBPP = screenBPP; + _screenBPP = 24; _isFullscreen = fullscreen; int flag; SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &flag); @@ -86,12 +96,6 @@ DriverGL::DriverGL(int screenW, int screenH, int screenBPP, bool fullscreen) { glPolygonOffset(-6.0, -6.0); } -DriverGL::~DriverGL() { - delete[] _storedDisplay; - if (glIsList(_emergFont)) - glDeleteLists(_emergFont, 128); -} - void DriverGL::toggleFullscreenMode() { warning("Switching on fly to Fullscreen mode is not allowed"); // switching to fullscreen mode it cause lost of texture diff --git a/engine/backend/platform/sdl/driver_gl.h b/engine/backend/platform/sdl/driver_gl.h index 1cc25cb60e9..abd91009c84 100644 --- a/engine/backend/platform/sdl/driver_gl.h +++ b/engine/backend/platform/sdl/driver_gl.h @@ -42,9 +42,11 @@ class DriverGL : public DriverSDL { public: - DriverGL(int screenW, int screenH, int screenBPP, bool fullscreen = false); + DriverGL(); virtual ~DriverGL(); + void setupScreen(int screenW, int screenH, bool fullscreen = false); + void setupCamera(float fov, float nclip, float fclip, float roll); void positionCamera(Vector3d pos, Vector3d interest); diff --git a/engine/backend/platform/sdl/driver_sdl.cpp b/engine/backend/platform/sdl/driver_sdl.cpp index fa206b0538c..19fcc58c349 100644 --- a/engine/backend/platform/sdl/driver_sdl.cpp +++ b/engine/backend/platform/sdl/driver_sdl.cpp @@ -23,13 +23,25 @@ * */ +#include "common/archive.h" +#include "common/config-manager.h" #include "common/debug.h" - -#include "mixer/mixer_intern.h" +#include "common/events.h" +#include "common/util.h" #include "engine/backend/platform/sdl/driver_sdl.h" + +#ifdef UNIX + #include "engine/backend/saves/posix/posix-saves.h" +#else + #include "engine/backend/saves/default/default-saves.h" +#endif +#include "engine/backend/timer/default/default-timer.h" +#include "mixer/mixer_intern.h" + #include "engine/backend/timer/default/default-timer.h" #include "engine/backend/saves/default/default-saves.h" +#include "engine/backend/events/default/default-events.h" #ifdef _WIN32 #include @@ -51,9 +63,19 @@ #endif -// NOTE: This is not a complete driver, it needs to be subclassed -// to provide rendering functionality. +#if defined(UNIX) +#ifdef MACOSX +#define DEFAULT_CONFIG_FILE "Library/Preferences/Residual Preferences" +#else +#define DEFAULT_CONFIG_FILE ".residualrc" +#endif +#else +#define DEFAULT_CONFIG_FILE "residual.ini" +#endif +#if defined(MACOSX) || defined(IPHONE) +#include "CoreFoundation/CoreFoundation.h" +#endif // FIXME move joystick defines out and replace with confile file options // we should really allow users to map any key to a joystick button @@ -74,8 +96,8 @@ #define JOY_BUT_SPACE 4 #define JOY_BUT_F5 5 -// numupper provides conversion between number keys and their "upper case" -const char numupper[] = {')', '!', '@', '#', '$', '%', '^', '&', '*', '('}; + + static int mapKey(SDLKey key, SDLMod mod, Uint16 unicode) { if (key >= SDLK_F1 && key <= SDLK_F9) { @@ -86,10 +108,8 @@ static int mapKey(SDLKey key, SDLMod mod, Uint16 unicode) { return key; } else if (unicode) { return unicode; - } else if (key >= 'a' && key <= 'z' && mod & KMOD_SHIFT) { + } else if (key >= 'a' && key <= 'z' && (mod & KMOD_SHIFT)) { return key & ~0x20; - } else if (key >= SDLK_0 && key <= SDLK_9 && mod & KMOD_SHIFT) { - return numupper[key - SDLK_0]; } else if (key >= SDLK_NUMLOCK && key <= SDLK_EURO) { return 0; } @@ -408,51 +428,182 @@ bool DriverSDL::pollEvent(Common::Event &event) { } bool DriverSDL::remapKey(SDL_Event &ev, Common::Event &event) { +#ifdef LINUPY + // On Yopy map the End button to quit + if ((ev.key.keysym.sym == 293)) { + event.type = Common::EVENT_QUIT; + return true; + } + // Map menu key to f5 (scumm menu) + if (ev.key.keysym.sym == 306) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_F5; + event.kbd.ascii = mapKey(SDLK_F5, ev.key.keysym.mod, 0); + return true; + } + // Map action key to action + if (ev.key.keysym.sym == 291) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_TAB; + event.kbd.ascii = mapKey(SDLK_TAB, ev.key.keysym.mod, 0); + return true; + } + // Map OK key to skip cinematic + if (ev.key.keysym.sym == 292) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_ESCAPE; + event.kbd.ascii = mapKey(SDLK_ESCAPE, ev.key.keysym.mod, 0); + return true; + } +#endif +#ifdef QTOPIA + // Quit on fn+backspace on zaurus + if (ev.key.keysym.sym == 127) { + event.type = Common::EVENT_QUIT; + return true; + } + + // Map menu key (f11) to f5 (scumm menu) + if (ev.key.keysym.sym == SDLK_F11) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_F5; + event.kbd.ascii = mapKey(SDLK_F5, ev.key.keysym.mod, 0); + } + // Nap center (space) to tab (default action ) + // I wanted to map the calendar button but the calendar comes up + // + else if (ev.key.keysym.sym == SDLK_SPACE) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_TAB; + event.kbd.ascii = mapKey(SDLK_TAB, ev.key.keysym.mod, 0); + } + // Since we stole space (pause) above we'll rebind it to the tab key on the keyboard + else if (ev.key.keysym.sym == SDLK_TAB) { + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_SPACE; + event.kbd.ascii = mapKey(SDLK_SPACE, ev.key.keysym.mod, 0); + } else { + // Let the events fall through if we didn't change them, this may not be the best way to + // set it up, but i'm not sure how sdl would like it if we let if fall through then redid it though. + // and yes i have an huge terminal size so i dont wrap soon enough. + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = ev.key.keysym.sym; + event.kbd.ascii = mapKey(ev.key.keysym.sym, ev.key.keysym.mod, ev.key.keysym.unicode); + } +#endif return false; } +static Common::EventManager *s_eventManager = 0; + +Common::EventManager *DriverSDL::getEventManager() { + // FIXME/TODO: Eventually this method should be turned into an abstract one, + // to force backends to implement this conciously (even if they + // end up returning the default event manager anyway). + if (!s_eventManager) + s_eventManager = new DefaultEventManager(this); + return s_eventManager; +} + static Uint32 timer_handler(Uint32 interval, void *param) { ((DefaultTimerManager *)param)->handler(); return interval; } -Common::TimerManager *DriverSDL::getTimerManager() { - assert(_timer); - return _timer; -} - -void DriverSDL::getTimeAndDate(struct tm &t) const { - time_t curTime = time(0); - t = *localtime(&curTime); -} - DriverSDL::DriverSDL() { _mixer = NULL; _timer = NULL; _savefile = NULL; + _timerID = 0; +#ifdef MIXER_DOUBLE_BUFFERING + _soundMutex = NULL; + _soundCond = NULL; + _soundThread = NULL; + _soundThreadIsRunning = false; + _soundThreadShouldQuit = false; +#endif + _samplesPerSec = 0; + + #if defined(__amigaos4__) + _fsFactory = new AmigaOSFilesystemFactory(); + #elif defined(UNIX) + _fsFactory = new POSIXFilesystemFactory(); + #elif defined(WIN32) + _fsFactory = new WindowsFilesystemFactory(); + #elif defined(__SYMBIAN32__) + // Do nothing since its handled by the Symbian SDL inheritance + #else + #error Unknown and unsupported FS backend + #endif } DriverSDL::~DriverSDL() { SDL_RemoveTimer(_timerID); - SDL_CloseAudio(); + closeMixer(); - delete _mixer; - delete _timer; - delete _savefile; + delete _fsFactory; } void DriverSDL::init() { - _timer = new DefaultTimerManager(); - _timerID = SDL_AddTimer(10, &timer_handler, _timer); + int joystick_num = ConfMan.getInt("joystick_num"); + uint32 sdlFlags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER; - if (!_savefile) { - _savefile = new DefaultSaveFileManager(); + if (ConfMan.hasKey("disable_sdl_parachute")) + sdlFlags |= SDL_INIT_NOPARACHUTE; + +#ifdef _WIN32_WCE + if (ConfMan.hasKey("use_GDI") && ConfMan.getBool("use_GDI")) { + SDL_VideoInit("windib", 0); + sdlFlags ^= SDL_INIT_VIDEO; } +#endif + + if (joystick_num > -1) + sdlFlags |= SDL_INIT_JOYSTICK; + + if (SDL_Init(sdlFlags) == -1) { + error("Could not initialize SDL: %s", SDL_GetError()); + } + + SDL_ShowCursor(SDL_DISABLE); + + // Enable unicode support if possible + SDL_EnableUNICODE(1); #if !defined(MACOSX) setupIcon(); #endif + + // Create the savefile manager, if none exists yet (we check for this to + // allow subclasses to provide their own). + if (!_savefile) { +#ifdef UNIX + _savefile = new POSIXSaveFileManager(); +#else + _savefile = new DefaultSaveFileManager(); +#endif + } + + // Create and hook up the mixer, if none exists yet (we check for this to + // allow subclasses to provide their own). + if (_mixer == 0) { + setupMixer(); + } + + // Create and hook up the timer manager, if none exists yet (we check for + // this to allow subclasses to provide their own). + if (_timer == NULL) { + // Note: We could implement a custom SDLTimerManager by using + // SDL_AddTimer. That might yield better timer resolution, but it would + // also change the semantics of a timer: Right now, ScummVM timers + // *never* run in parallel, due to the way they are implemented. If we + // switched to SDL_AddTimer, each timer might run in a separate thread. + // However, not all our code is prepared for that, so we can't just + // switch. Still, it's a potential future change to keep in mind. + _timer = new DefaultTimerManager(); + _timerID = SDL_AddTimer(10, &timer_handler, _timer); + } } const char *DriverSDL::getVideoDeviceName() { @@ -460,108 +611,23 @@ const char *DriverSDL::getVideoDeviceName() { } uint32 DriverSDL::getMillis() { - return SDL_GetTicks(); + uint32 millis = SDL_GetTicks(); + getEventManager()->processMillis(millis); + return millis; } void DriverSDL::delayMillis(uint msecs) { SDL_Delay(msecs); } -Common::MutexRef DriverSDL::createMutex() { - return (MutexRef)SDL_CreateMutex(); +void DriverSDL::getTimeAndDate(struct tm &t) const { + time_t curTime = time(0); + t = *localtime(&curTime); } -void DriverSDL::lockMutex(MutexRef mutex) { - SDL_mutexP((SDL_mutex *)mutex); -} - -void DriverSDL::unlockMutex(MutexRef mutex) { - SDL_mutexV((SDL_mutex *)mutex); -} - -void DriverSDL::deleteMutex(MutexRef mutex) { - SDL_DestroyMutex((SDL_mutex *)mutex); -} - -void DriverSDL::mixCallback(void *sys, byte *samples, int len) { - DriverSDL *this_ = (DriverSDL *)sys; - assert(this_); - - if (this_->_mixer) - this_->_mixer->mixCallback(samples, len); -} - -void DriverSDL::setupMixer() { - SDL_AudioSpec desired; - SDL_AudioSpec obtained; - - // Determine the desired output sampling frequency. - _samplesPerSec = 0; - - if (_samplesPerSec <= 0) - _samplesPerSec = SAMPLES_PER_SEC; - - // Determine the sample buffer size. We want it to store enough data for - // about 1/16th of a second. Note that it must be a power of two. - // So e.g. at 22050 Hz, we request a sample buffer size of 2048. - int samples = 8192; - while (16 * samples >= _samplesPerSec) { - samples >>= 1; - } - - memset(&desired, 0, sizeof(desired)); - desired.freq = _samplesPerSec; - desired.format = AUDIO_S16SYS; - desired.channels = 2; - desired.samples = (uint16)samples; - desired.callback = mixCallback; - desired.userdata = this; - - // Create the mixer instance - assert(!_mixer); - _mixer = new Audio::MixerImpl(); - assert(_mixer); - - if (SDL_OpenAudio(&desired, &obtained) != 0) { - warning("Could not open audio device: %s", SDL_GetError()); - _samplesPerSec = 0; - _mixer->setReady(false); - } else { - _samplesPerSec = obtained.freq; - _mixer->setOutputRate(_samplesPerSec); - _mixer->setReady(true); - SDL_PauseAudio(0); - } -} - -Audio::Mixer *DriverSDL::getMixer() { - assert(_mixer); - return _mixer; -} - -/* This function sends the SDL signal to - * go ahead and exit the game - */ -void DriverSDL::quit() { - SDL_Event event; - - event.type = SDL_QUIT; - if (SDL_PushEvent(&event) != 0) - error("Unable to push exit event!"); -} - -FilesystemFactory *DriverSDL::getFilesystemFactory() { - #if defined(__amigaos4__) - return &AmigaOSFilesystemFactory::instance(); - #elif defined(UNIX) - return &POSIXFilesystemFactory::instance(); - #elif defined(WIN32) - return &WindowsFilesystemFactory::instance(); - #elif defined(__SYMBIAN32__) - // Do nothing since its handled by the Symbian SDL inheritance - #else - #error Unknown and unsupported backend in Driver_SDL::getFilesystemFactory - #endif +Common::TimerManager *DriverSDL::getTimerManager() { + assert(_timer); + return _timer; } Common::SaveFileManager *DriverSDL::getSavefileManager() { @@ -569,6 +635,130 @@ Common::SaveFileManager *DriverSDL::getSavefileManager() { return _savefile; } +FilesystemFactory *DriverSDL::getFilesystemFactory() { + assert(_fsFactory); + return _fsFactory; +} + +void DriverSDL::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { + +#ifdef DATA_PATH + // Add the global DATA_PATH to the directory search list + // FIXME: We use depth = 4 for now, to match the old code. May want to change that + Common::FSNode dataNode(DATA_PATH); + if (dataNode.exists() && dataNode.isDirectory()) { + s.add(DATA_PATH, new Common::FSDirectory(dataNode, 4), priority); + } +#endif + +#ifdef MACOSX + // Get URL of the Resource directory of the .app bundle + CFURLRef fileUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); + if (fileUrl) { + // Try to convert the URL to an absolute path + UInt8 buf[MAXPATHLEN]; + if (CFURLGetFileSystemRepresentation(fileUrl, true, buf, sizeof(buf))) { + // Success: Add it to the search path + Common::String bundlePath((const char *)buf); + s.add("__OSX_BUNDLE__", new Common::FSDirectory(bundlePath), priority); + } + CFRelease(fileUrl); + } + +#endif + +} + + +static Common::String getDefaultConfigFileName() { + char configFile[MAXPATHLEN]; +#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"); + + strcat(configFile, "\\Application Data"); + CreateDirectory(configFile, NULL); + } + + strcat(configFile, "\\Residual"); + CreateDirectory(configFile, NULL); + strcat(configFile, "\\" DEFAULT_CONFIG_FILE); + + FILE *tmp = NULL; + if ((tmp = fopen(configFile, "r")) == NULL) { + // Check windows directory + char oldConfigFile[MAXPATHLEN]; + GetWindowsDirectory(oldConfigFile, MAXPATHLEN); + strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE); + if ((tmp = fopen(oldConfigFile, "r"))) { + strcpy(configFile, oldConfigFile); + + fclose(tmp); + } + } else { + fclose(tmp); + } + } else { + // Check windows directory + GetWindowsDirectory(configFile, MAXPATHLEN); + strcat(configFile, "\\" DEFAULT_CONFIG_FILE); + } +#elif defined(UNIX) + // On UNIX type systems, by default we store the config file inside + // to the HOME directory of the user. + // + // 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. + 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 + strcpy(configFile, DEFAULT_CONFIG_FILE); +#endif + + return configFile; +} + +Common::SeekableReadStream *DriverSDL::createConfigReadStream() { + Common::FSNode file(getDefaultConfigFileName()); + return file.createReadStream(); +} + +Common::WriteStream *DriverSDL::createConfigWriteStream() { + Common::FSNode file(getDefaultConfigFileName()); + return file.createWriteStream(); +} + +void DriverSDL::quit() { + SDL_ShowCursor(SDL_ENABLE); + + SDL_RemoveTimer(_timerID); + closeMixer(); + + delete _savefile; + delete _timer; + + SDL_Quit(); + + delete getEventManager(); +} + void DriverSDL::setupIcon() { int w, h, ncols, nbytes, i; unsigned int rgba[256], icon[32 * 32]; @@ -615,6 +805,213 @@ void DriverSDL::setupIcon() { SDL_FreeSurface(sdl_surf); } +Common::MutexRef DriverSDL::createMutex() { + return (MutexRef)SDL_CreateMutex(); +} + +void DriverSDL::lockMutex(MutexRef mutex) { + SDL_mutexP((SDL_mutex *)mutex); +} + +void DriverSDL::unlockMutex(MutexRef mutex) { + SDL_mutexV((SDL_mutex *)mutex); +} + +void DriverSDL::deleteMutex(MutexRef mutex) { + SDL_DestroyMutex((SDL_mutex *)mutex); +} + + +#pragma mark - +#pragma mark --- Audio --- +#pragma mark - + +#ifdef MIXER_DOUBLE_BUFFERING + +void DriverSDL::mixerProducerThread() { + byte nextSoundBuffer; + + SDL_LockMutex(_soundMutex); + while (true) { + // Wait till we are allowed to produce data + SDL_CondWait(_soundCond, _soundMutex); + + if (_soundThreadShouldQuit) + break; + + // Generate samples and put them into the next buffer + nextSoundBuffer = _activeSoundBuf ^ 1; + _mixer->mixCallback(_soundBuffers[nextSoundBuffer], _soundBufSize); + + // Swap buffers + _activeSoundBuf = nextSoundBuffer; + } + SDL_UnlockMutex(_soundMutex); +} + +int SDLCALL DriverSDL::mixerProducerThreadEntry(void *arg) { + DriverSDL *this_ = (DriverSDL *)arg; + assert(this_); + this_->mixerProducerThread(); + return 0; +} + +void DriverSDL::initThreadedMixer(Audio::MixerImpl *mixer, uint bufSize) { + _soundThreadIsRunning = false; + _soundThreadShouldQuit = false; + + // Create mutex and condition variable + _soundMutex = SDL_CreateMutex(); + _soundCond = SDL_CreateCond(); + + // Create two sound buffers + _activeSoundBuf = 0; + _soundBufSize = bufSize; + _soundBuffers[0] = (byte *)calloc(1, bufSize); + _soundBuffers[1] = (byte *)calloc(1, bufSize); + + _soundThreadIsRunning = true; + + // Finally start the thread + _soundThread = SDL_CreateThread(mixerProducerThreadEntry, this); +} + +void DriverSDL::deinitThreadedMixer() { + // Kill thread?? _soundThread + + if (_soundThreadIsRunning) { + // Signal the producer thread to end, and wait for it to actually finish. + _soundThreadShouldQuit = true; + SDL_CondBroadcast(_soundCond); + SDL_WaitThread(_soundThread, NULL); + + // Kill the mutex & cond variables. + // Attention: AT this point, the mixer callback must not be running + // anymore, else we will crash! + SDL_DestroyMutex(_soundMutex); + SDL_DestroyCond(_soundCond); + + _soundThreadIsRunning = false; + + free(_soundBuffers[0]); + free(_soundBuffers[1]); + } +} + + +void DriverSDL::mixCallback(void *arg, byte *samples, int len) { + DriverSDL *this_ = (DriverSDL *)arg; + assert(this_); + assert(this_->_mixer); + + assert((int)this_->_soundBufSize == len); + + // Lock mutex, to ensure our data is not overwritten by the producer thread + SDL_LockMutex(this_->_soundMutex); + + // Copy data from the current sound buffer + memcpy(samples, this_->_soundBuffers[this_->_activeSoundBuf], len); + + // Unlock mutex and wake up the produced thread + SDL_UnlockMutex(this_->_soundMutex); + SDL_CondSignal(this_->_soundCond); +} + +#else + +void DriverSDL::mixCallback(void *sys, byte *samples, int len) { + DriverSDL *this_ = (DriverSDL *)sys; + assert(this_); + + if (this_->_mixer) + this_->_mixer->mixCallback(samples, len); +} + +#endif + +void DriverSDL::setupMixer() { + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + + // Determine the desired output sampling frequency. + _samplesPerSec = 0; + + if (ConfMan.hasKey("output_rate")) + _samplesPerSec = ConfMan.getInt("output_rate"); + if (_samplesPerSec <= 0) + _samplesPerSec = SAMPLES_PER_SEC; + + // Determine the sample buffer size. We want it to store enough data for + // about 1/16th of a second. Note that it must be a power of two. + // So e.g. at 22050 Hz, we request a sample buffer size of 2048. + int samples = 8192; + while (16 * samples >= _samplesPerSec) { + samples >>= 1; + } + + memset(&desired, 0, sizeof(desired)); + desired.freq = _samplesPerSec; + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.samples = (uint16)samples; + desired.callback = mixCallback; + desired.userdata = this; + + // Create the mixer instance + assert(!_mixer); + _mixer = new Audio::MixerImpl(this); + assert(_mixer); + + if (SDL_OpenAudio(&desired, &obtained) != 0) { + warning("Could not open audio device: %s", SDL_GetError()); + _samplesPerSec = 0; + _mixer->setReady(false); + } else { + // Note: This should be the obtained output rate, but it seems that at + // least on some platforms SDL will lie and claim it did get the rate + // even if it didn't. Probably only happens for "weird" rates, though. + _samplesPerSec = obtained.freq; + debug(1, "Output sample rate: %d Hz", _samplesPerSec); + + // Tell the mixer that we are ready and start the sound processing + _mixer->setOutputRate(_samplesPerSec); + _mixer->setReady(true); + +#ifdef MIXER_DOUBLE_BUFFERING + initThreadedMixer(_mixer, obtained.samples * 4); +#endif + + // start the sound system + SDL_PauseAudio(0); + } +} + +void DriverSDL::closeMixer() { + if (_mixer) + _mixer->setReady(false); + + SDL_CloseAudio(); + + delete _mixer; + _mixer = 0; + +#ifdef MIXER_DOUBLE_BUFFERING + deinitThreadedMixer(); +#endif + +} + +Audio::Mixer *DriverSDL::getMixer() { + assert(_mixer); + return _mixer; +} + +#if defined(__SYMBIAN32__) +#include "SymbianOs.h" +#endif + +#if !defined(__MAEMO__) && !defined(_WIN32_WCE) + #if defined (WIN32) int __stdcall WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpCmdLine*/, int /*iShowCmd*/) { SDL_SetModuleHandle(GetModuleHandle(NULL)); @@ -623,7 +1020,55 @@ int __stdcall WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpC #endif int main(int argc, char *argv[]) { + +#if defined(__SYMBIAN32__) + // + // Set up redirects for stdout/stderr under Windows and Symbian. + // Code copied from SDL_main. + // + + // Symbian does not like any output to the console through any *print* function + char STDOUT_FILE[256], STDERR_FILE[256]; // shhh, don't tell anybody :) + strcpy(STDOUT_FILE, Symbian::GetExecutablePath()); + strcpy(STDERR_FILE, Symbian::GetExecutablePath()); + strcat(STDOUT_FILE, "residual.stdout.txt"); + strcat(STDERR_FILE, "residual.stderr.txt"); + + /* Flush the output in case anything is queued */ + fclose(stdout); + fclose(stderr); + + /* Redirect standard input and standard output */ + FILE *newfp = freopen(STDOUT_FILE, "w", stdout); + if (newfp == NULL) { /* This happens on NT */ +#if !defined(stdout) + stdout = fopen(STDOUT_FILE, "w"); +#else + newfp = fopen(STDOUT_FILE, "w"); + if (newfp) { + *stdout = *newfp; + } +#endif + } + newfp = freopen(STDERR_FILE, "w", stderr); + if (newfp == NULL) { /* This happens on NT */ +#if !defined(stderr) + stderr = fopen(STDERR_FILE, "w"); +#else + newfp = fopen(STDERR_FILE, "w"); + if (newfp) { + *stderr = *newfp; + } +#endif + } + setbuf(stderr, NULL); /* No buffering */ + +#endif // defined(__SYMBIAN32__) + + int res = residual_main(argc, argv); return res; } + +#endif diff --git a/engine/backend/platform/sdl/driver_sdl.h b/engine/backend/platform/sdl/driver_sdl.h index 803a2c5d170..edb69f65d97 100644 --- a/engine/backend/platform/sdl/driver_sdl.h +++ b/engine/backend/platform/sdl/driver_sdl.h @@ -34,6 +34,7 @@ #include "engine/bitmap.h" #include "engine/vector3d.h" #include "engine/backend/platform/driver.h" +#include "engine/backend/events/default/default-events.h" #include @@ -47,7 +48,7 @@ namespace Audio { class Mixer; } -class DriverSDL : public Driver { +class DriverSDL : public Driver, EventProvider { public: DriverSDL(); virtual ~DriverSDL(); @@ -63,27 +64,59 @@ public: uint32 getMillis(); void delayMillis(uint msecs); Common::TimerManager *getTimerManager(); + Common::EventManager *getEventManager(); void getTimeAndDate(struct tm &t) const; + // Set function that generates samples + virtual void setupMixer(); + static void mixCallback(void *s, byte *samples, int len); + + virtual void closeMixer(); + + virtual Audio::Mixer *getMixer(); + + MutexRef createMutex(); void lockMutex(MutexRef mutex); void unlockMutex(MutexRef mutex); void deleteMutex(MutexRef mutex); - void setupMixer(); - static void mixCallback(void *s, byte *samples, int len); - Audio::Mixer *getMixer(); - void quit(); - FilesystemFactory *getFilesystemFactory(); - Common::SaveFileManager *getSavefileManager(); + + virtual Common::SaveFileManager *getSavefileManager(); + virtual FilesystemFactory *getFilesystemFactory(); + virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); + + virtual Common::SeekableReadStream *createConfigReadStream(); + virtual Common::WriteStream *createConfigWriteStream(); private: int _samplesPerSec; + +#ifdef MIXER_DOUBLE_BUFFERING + SDL_mutex *_soundMutex; + SDL_cond *_soundCond; + SDL_Thread *_soundThread; + bool _soundThreadIsRunning; + bool _soundThreadShouldQuit; + + byte _activeSoundBuf; + uint _soundBufSize; + byte *_soundBuffers[2]; + + void mixerProducerThread(); + static int SDLCALL mixerProducerThreadEntry(void *arg); + void initThreadedMixer(Audio::MixerImpl *mixer, uint bufSize); + void deinitThreadedMixer(); +#endif + + FilesystemFactory *_fsFactory; Common::SaveFileManager *_savefile; - Common::TimerManager *_timer; + Audio::MixerImpl *_mixer; + SDL_TimerID _timerID; + Common::TimerManager *_timer; virtual void fillMouseEvent(Common::Event &event, int x, int y); @@ -101,10 +134,6 @@ private: void handleKbdMouse(); bool remapKey(SDL_Event &ev, Common::Event &event); - -protected: - - Audio::MixerImpl *_mixer; }; #endif diff --git a/engine/backend/platform/sdl/driver_tinygl.cpp b/engine/backend/platform/sdl/driver_tinygl.cpp index b578a0631e3..4325d9e6f88 100644 --- a/engine/backend/platform/sdl/driver_tinygl.cpp +++ b/engine/backend/platform/sdl/driver_tinygl.cpp @@ -136,18 +136,31 @@ TGLint tgluProject(TGLfloat objx, TGLfloat objy, TGLfloat objz, const TGLfloat m return TGL_TRUE; } -DriverTinyGL::DriverTinyGL(int screenW, int screenH, int screenBPP, bool fullscreen) { +DriverTinyGL::DriverTinyGL() { + _zb = NULL; + _storedDisplay = NULL; +} + +DriverTinyGL::~DriverTinyGL() { + delete[] _storedDisplay; + if (_zb) { + tglClose(); + ZB_close(_zb); + } +} + +void DriverTinyGL::setupScreen(int screenW, int screenH, bool fullscreen) { uint32 flags = SDL_HWSURFACE; if (fullscreen) flags |= SDL_FULLSCREEN; - _screen = SDL_SetVideoMode(screenW, screenH, screenBPP, flags); + _screen = SDL_SetVideoMode(screenW, screenH, 16, flags); if (_screen == NULL) error("Could not initialize video"); _screenWidth = screenW; _screenHeight = screenH; - _screenBPP = screenBPP; + _screenBPP = 15; _isFullscreen = fullscreen; SDL_WM_SetCaption("Residual: Software 3D Renderer", "Residual"); @@ -164,12 +177,6 @@ DriverTinyGL::DriverTinyGL(int screenW, int screenH, int screenBPP, bool fullscr tglLightModelfv(TGL_LIGHT_MODEL_AMBIENT, ambientSource); } -DriverTinyGL::~DriverTinyGL() { - delete[] _storedDisplay; - tglClose(); - ZB_close(_zb); -} - void DriverTinyGL::toggleFullscreenMode() { // We used to use SDL_WM_ToggleFullScreen() to switch to fullscreen // mode, but since that was deemed too buggy for ScummVM it's probably diff --git a/engine/backend/platform/sdl/driver_tinygl.h b/engine/backend/platform/sdl/driver_tinygl.h index 406ee311f42..7afa6ca0470 100644 --- a/engine/backend/platform/sdl/driver_tinygl.h +++ b/engine/backend/platform/sdl/driver_tinygl.h @@ -43,9 +43,11 @@ class DriverTinyGL : public DriverSDL { public: - DriverTinyGL(int screenW, int screenH, int screenBPP, bool fullscreen = false); + DriverTinyGL(); virtual ~DriverTinyGL(); + void setupScreen(int screenW, int screenH, bool fullscreen = false); + void setupCamera(float fov, float nclip, float fclip, float roll); void positionCamera(Vector3d pos, Vector3d interest); diff --git a/engine/backend/saves/default/default-saves.cpp b/engine/backend/saves/default/default-saves.cpp index 04cacf5da3e..143bf0627a1 100644 --- a/engine/backend/saves/default/default-saves.cpp +++ b/engine/backend/saves/default/default-saves.cpp @@ -8,261 +8,123 @@ * 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$ - * */ #if !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) +#include "engine/backend/saves/default/default-saves.h" + #include "common/savefile.h" #include "common/util.h" -#include "common/debug.h" #include "common/fs.h" -#include "common/file.h" +#include "common/archive.h" #include "common/config-manager.h" -#include "engine/backend/saves/default/default-saves.h" -#include "engine/backend/saves/compressed/compressed-saves.h" +#include "common/zlib.h" -#include -#include -#include +#include // for removeSavefile() -#if defined(UNIX) || defined(__SYMBIAN32__) -#include -#endif -#ifdef _WIN32 -#include -#endif +DefaultSaveFileManager::DefaultSaveFileManager() { +} -class StdioSaveFile : public Common::InSaveFile, public Common::OutSaveFile { -private: - FILE *fh; -public: - StdioSaveFile(const char *filename, bool saveOrLoad) { - fh = ::fopen(filename, (saveOrLoad? "wb" : "rb")); +DefaultSaveFileManager::DefaultSaveFileManager(const Common::String &defaultSavepath) { + ConfMan.registerDefault("savepath", defaultSavepath); +} + + +void DefaultSaveFileManager::checkPath(const Common::FSNode &dir) { + clearError(); + if (!dir.exists()) { + setError(Common::kPathDoesNotExist, "The savepath '"+dir.getPath()+"' does not exist"); + } else if (!dir.isDirectory()) { + setError(Common::kPathNotDirectory, "The savepath '"+dir.getPath()+"' is not a directory"); } - ~StdioSaveFile() { - if (fh) - ::fclose(fh); - } - - bool eos() const { return feof(fh) != 0; } - bool ioFailed() const { return ferror(fh) != 0; } - void clearIOFailed() { clearerr(fh); } - - bool isOpen() const { return fh != 0; } - - uint32 read(void *dataPtr, uint32 dataSize) { - assert(fh); - return fread(dataPtr, 1, dataSize, fh); - } - uint32 write(const void *dataPtr, uint32 dataSize) { - assert(fh); - return fwrite(dataPtr, 1, dataSize, fh); - } - - uint32 pos() const { - assert(fh); - return ftell(fh); - } - uint32 size() const { - assert(fh); - uint32 oldPos = ftell(fh); - fseek(fh, 0, SEEK_END); - uint32 length = ftell(fh); - fseek(fh, oldPos, SEEK_SET); - return length; - } - - void seek(int32 offs, int whence = SEEK_SET) { - assert(fh); - fseek(fh, offs, whence); - } -}; - -static void join_paths(const char *filename, const char *directory, char *buf, int bufsize) { - buf[bufsize - 1] = '\0'; - strncpy(buf, directory, bufsize - 1); - -#ifdef WIN32 - // Fix for Win98 issue related with game directory pointing to root drive ex. "c:\" - if ((buf[0] != 0) && (buf[1] == ':') && (buf[2] == '\\') && (buf[3] == 0)) { - buf[2] = 0; - } -#endif - - const int dirLen = strlen(buf); - - if (dirLen > 0) { -#if defined(__MORPHOS__) || defined(__amigaos4__) - if (buf[dirLen - 1] != ':' && buf[dirLen - 1] != '/') -#endif - -#if !defined(__GP32__) - strncat(buf, "/", bufsize - 1); // prevent double / -#endif - } - strncat(buf, filename, bufsize - 1); } Common::StringList DefaultSaveFileManager::listSavefiles(const char *pattern) { - FilesystemNode savePath(getSavePath()); - FSList savefiles; + Common::FSNode savePath(getSavePath()); + checkPath(savePath); + if (getError() != Common::kNoError) + return Common::StringList(); + + Common::FSDirectory dir(savePath); + Common::ArchiveMemberList savefiles; Common::StringList results; Common::String search(pattern); - if (savePath.lookupFile(savefiles, search, false, true, 0)) { - for (FSList::const_iterator file = savefiles.begin(); file != savefiles.end(); ++file) { - results.push_back(file->getName()); + if (dir.listMatchingMembers(savefiles, search) > 0) { + for (Common::ArchiveMemberList::const_iterator file = savefiles.begin(); file != savefiles.end(); ++file) { + results.push_back((*file)->getName()); } } return results; } -void DefaultSaveFileManager::checkPath(const Common::String &path) { - clearError(); - -#if defined(UNIX) || defined(__SYMBIAN32__) - struct stat sb; - - // Check whether the dir exists - if (stat(path.c_str(), &sb) == -1) { - // The dir does not exist, or stat failed for some other reason. - // If the problem was that the path pointed to nothing, try - // to create the dir (ENOENT case). - switch (errno) { - case EACCES: - setError(SFM_DIR_ACCESS, "Search or write permission denied: "+path); - break; -#if !defined(__SYMBIAN32__) - case ELOOP: - setError(SFM_DIR_LOOP, "Too many symbolic links encountered while traversing the path: "+path); - break; -#endif - case ENAMETOOLONG: - setError(SFM_DIR_NAMETOOLONG, "The path name is too long: "+path); - break; - case ENOENT: - if (mkdir(path.c_str(), 0755) != 0) { - // mkdir could fail for various reasons: The parent dir doesn't exist, - // or is not writeable, the path could be completly bogus, etc. - warning("mkdir for '%s' failed!", path.c_str()); - perror("mkdir"); - - switch (errno) { - case EACCES: - setError(SFM_DIR_ACCESS, "Search or write permission denied: "+path); - break; - case EMLINK: - setError(SFM_DIR_LINKMAX, "The link count of the parent directory would exceed {LINK_MAX}: "+path); - break; -#if !defined(__SYMBIAN32__) - case ELOOP: - setError(SFM_DIR_LOOP, "Too many symbolic links encountered while traversing the path: "+path); - break; -#endif - case ENAMETOOLONG: - setError(SFM_DIR_NAMETOOLONG, "The path name is too long: "+path); - break; - case ENOENT: - setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+path); - break; - case ENOTDIR: - setError(SFM_DIR_NOTDIR, "A component of the path prefix is not a directory: "+path); - break; - case EROFS: - setError(SFM_DIR_ROFS, "The parent directory resides on a read-only file system:"+path); - break; - } - } - break; - case ENOTDIR: - setError(SFM_DIR_NOTDIR, "A component of the path prefix is not a directory: "+path); - break; - } - } else { - // So stat() succeeded. But is the path actually pointing to a directory? - if (!S_ISDIR(sb.st_mode)) { - setError(SFM_DIR_NOTDIR, "The given savepath is not a directory: "+path); - } - } -#elif defined(_WIN32) - FilesystemNode node(path); - if (node.exists() && node.isDirectory()) - return; - if (_mkdir(path.c_str()) == 0) - return; -#endif -} - Common::InSaveFile *DefaultSaveFileManager::openForLoading(const char *filename) { // Ensure that the savepath is valid. If not, generate an appropriate error. - char buf[256]; - Common::String savePath = getSavePath(); + Common::FSNode savePath(getSavePath()); checkPath(savePath); - - if (getError() == SFM_NO_ERROR) { - join_paths(filename, savePath.c_str(), buf, sizeof(buf)); - StdioSaveFile *sf = new StdioSaveFile(buf, false); - - if (!sf->isOpen()) { - delete sf; - sf = 0; - } - - return wrapInSaveFile(sf); - } else { + if (getError() != Common::kNoError) return 0; - } + + Common::FSNode file = savePath.getChild(filename); + if (!file.exists()) + return 0; + + // Open the file for reading + Common::SeekableReadStream *sf = file.createReadStream(); + + return Common::wrapCompressedReadStream(sf); } Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const char *filename) { // Ensure that the savepath is valid. If not, generate an appropriate error. - char buf[256]; - Common::String savePath = getSavePath(); + Common::FSNode savePath(getSavePath()); checkPath(savePath); - - if (getError() == SFM_NO_ERROR) { - join_paths(filename, savePath.c_str(), buf, sizeof(buf)); - StdioSaveFile *sf = new StdioSaveFile(buf, true); - - if (!sf->isOpen()) { - delete sf; - sf = 0; - } - - return wrapOutSaveFile(sf); - } else { + if (getError() != Common::kNoError) return 0; - } + + Common::FSNode file = savePath.getChild(filename); + + // Open the file for saving + Common::WriteStream *sf = file.createWriteStream(); + + return Common::wrapCompressedWriteStream(sf); } bool DefaultSaveFileManager::removeSavefile(const char *filename) { - char buf[256]; clearError(); - Common::String filenameStr; - join_paths(filename, getSavePath().c_str(), buf, sizeof(buf)); - if (remove(buf) != 0) { + Common::FSNode savePath(getSavePath()); + checkPath(savePath); + if (getError() != Common::kNoError) + return false; + + Common::FSNode file = savePath.getChild(filename); + + // FIXME: remove does not exist on all systems. If your port fails to + // compile because of this, please let us know (scummvm-devel or Fingolfin). + // There is a nicely portable workaround, too: Make this method overloadable. + if (remove(file.getPath().c_str()) != 0) { #ifndef _WIN32_WCE if (errno == EACCES) - setError(SFM_DIR_ACCESS, "Search or write permission denied: "+filenameStr); + setError(Common::kWritePermissionDenied, "Search or write permission denied: "+file.getName()); if (errno == ENOENT) - setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+filenameStr); + setError(Common::kPathDoesNotExist, "removeSavefile: '"+file.getName()+"' does not exist or path is invalid"); #endif return false; } else { diff --git a/engine/backend/saves/default/default-saves.h b/engine/backend/saves/default/default-saves.h index f44b1814480..cc9cb8589cb 100644 --- a/engine/backend/saves/default/default-saves.h +++ b/engine/backend/saves/default/default-saves.h @@ -8,32 +8,36 @@ * 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$ - * */ #if !defined(BACKEND_SAVES_DEFAULT_H) && !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) #define BACKEND_SAVES_DEFAULT_H +#include "common/sys.h" #include "common/savefile.h" #include "common/str.h" +#include "common/fs.h" /** * Provides a default savefile manager implementation for common platforms. */ class DefaultSaveFileManager : public Common::SaveFileManager { public: + DefaultSaveFileManager(); + DefaultSaveFileManager(const Common::String &defaultSavepath); + virtual Common::StringList listSavefiles(const char *pattern); virtual Common::InSaveFile *openForLoading(const char *filename); virtual Common::OutSaveFile *openForSaving(const char *filename); @@ -51,7 +55,7 @@ protected: * Checks the given path for read access, existence, etc. * Sets the internal error and error message accordingly. */ - void checkPath(const Common::String &path); + virtual void checkPath(const Common::FSNode &dir); }; #endif diff --git a/engine/backend/saves/posix/posix-saves.cpp b/engine/backend/saves/posix/posix-saves.cpp new file mode 100644 index 00000000000..b3ab0247741 --- /dev/null +++ b/engine/backend/saves/posix/posix-saves.cpp @@ -0,0 +1,127 @@ +/* Residual - Virtual machine to run LucasArts' 3D adventure games + * + * Residual is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the AUTHORS + * 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$ + */ + +#if defined(UNIX) && !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) + +#include "engine/backend/saves/posix/posix-saves.h" + +#include "common/config-manager.h" +#include "common/savefile.h" + +#include +#include +#include +#include + + +#ifdef MACOSX +#define DEFAULT_SAVE_PATH "Documents/ScummVM Savegames" +#else +#define DEFAULT_SAVE_PATH ".scummvm" +#endif + +POSIXSaveFileManager::POSIXSaveFileManager() { + // Register default savepath based on HOME + Common::String savePath; + const char *home = getenv("HOME"); + if (home && *home && strlen(home) < MAXPATHLEN) { + savePath = home; + savePath += "/" DEFAULT_SAVE_PATH; + ConfMan.registerDefault("savepath", savePath); + } +} +/* +POSIXSaveFileManager::POSIXSaveFileManager(const Common::String &defaultSavepath) + : DefaultSaveFileManager(defaultSavepath) { +} +*/ + +#if defined(UNIX) +void POSIXSaveFileManager::checkPath(const Common::FSNode &dir) { + const Common::String path = dir.getPath(); + clearError(); + + struct stat sb; + + // Check whether the dir exists + if (stat(path.c_str(), &sb) == -1) { + // The dir does not exist, or stat failed for some other reason. + // If the problem was that the path pointed to nothing, try + // to create the dir (ENOENT case). + switch (errno) { + case EACCES: + setError(Common::kWritePermissionDenied, "Search or write permission denied: "+path); + break; + case ELOOP: + setError(Common::kUnknownError, "Too many symbolic links encountered while traversing the path: "+path); + break; + case ENAMETOOLONG: + setError(Common::kUnknownError, "The path name is too long: "+path); + break; + case ENOENT: + if (mkdir(path.c_str(), 0755) != 0) { + // mkdir could fail for various reasons: The parent dir doesn't exist, + // or is not writeable, the path could be completly bogus, etc. + warning("mkdir for '%s' failed!", path.c_str()); + perror("mkdir"); + + switch (errno) { + case EACCES: + setError(Common::kWritePermissionDenied, "Search or write permission denied: "+path); + break; + case EMLINK: + setError(Common::kUnknownError, "The link count of the parent directory would exceed {LINK_MAX}: "+path); + break; + case ELOOP: + setError(Common::kUnknownError, "Too many symbolic links encountered while traversing the path: "+path); + break; + case ENAMETOOLONG: + setError(Common::kUnknownError, "The path name is too long: "+path); + break; + case ENOENT: + setError(Common::kPathDoesNotExist, "A component of the path does not exist, or the path is an empty string: "+path); + break; + case ENOTDIR: + setError(Common::kPathDoesNotExist, "A component of the path prefix is not a directory: "+path); + break; + case EROFS: + setError(Common::kWritePermissionDenied, "The parent directory resides on a read-only file system:"+path); + break; + } + } + break; + case ENOTDIR: + setError(Common::kPathDoesNotExist, "A component of the path prefix is not a directory: "+path); + break; + } + } else { + // So stat() succeeded. But is the path actually pointing to a directory? + if (!S_ISDIR(sb.st_mode)) { + setError(Common::kPathDoesNotExist, "The given savepath is not a directory: "+path); + } + } +} +#endif + +#endif diff --git a/engine/backend/saves/compressed/compressed-saves.h b/engine/backend/saves/posix/posix-saves.h similarity index 51% rename from engine/backend/saves/compressed/compressed-saves.h rename to engine/backend/saves/posix/posix-saves.h index 625b321d43f..49c95ae3514 100644 --- a/engine/backend/saves/compressed/compressed-saves.h +++ b/engine/backend/saves/posix/posix-saves.h @@ -8,46 +8,45 @@ * 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 BACKEND_SAVES_COMPRESSED_H -#define BACKEND_SAVES_COMPRESSED_H +#if !defined(BACKEND_POSIX_SAVES_H) && !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) +#define BACKEND_POSIX_SAVES_H -#include "common/savefile.h" +#include "engine/backend/saves/default/default-saves.h" +#if defined(UNIX) /** - * Take an arbitrary InSaveFile and wrap it in a high level InSaveFile which - * provides transparent on-the-fly decompression support. - * Assumes the data it retrieves from the wrapped savefile to be either - * uncompressed or in gzip format. In the former case, the original - * savefile is returned unmodified (and in particular, not wrapped). - * - * It is safe to call this with a NULL parameter (in this case, NULL is - * returned). + * Customization of the DefaultSaveFileManager for POSIX platforms. + * The only two differences are that the default constructor sets + * up the savepath based on HOME, and that checkPath tries to + * create the savedir, if missing, via the mkdir() syscall. */ -Common::InSaveFile *wrapInSaveFile(Common::InSaveFile *toBeWrapped); +class POSIXSaveFileManager : public DefaultSaveFileManager { +public: + POSIXSaveFileManager(); +// POSIXSaveFileManager(const Common::String &defaultSavepath); -/** - * Take an arbitrary OutSaveFile and wrap it in a high level OutSaveFile which - * provides transparent on-the-fly compression support. - * The compressed data is written in the gzip format. - * - * It is safe to call this with a NULL parameter (in this case, NULL is - * returned). - */ -Common::OutSaveFile *wrapOutSaveFile(Common::OutSaveFile *toBeWrapped); +protected: + /** + * Checks the given path for read access, existence, etc. + * In addition, tries to create a missing savedir, if possible. + * Sets the internal error and error message accordingly. + */ + virtual void checkPath(const Common::FSNode &dir); +}; +#endif #endif diff --git a/engine/backend/saves/savefile.cpp b/engine/backend/saves/savefile.cpp index 72610fe098d..ec98264672f 100644 --- a/engine/backend/saves/savefile.cpp +++ b/engine/backend/saves/savefile.cpp @@ -8,19 +8,18 @@ * 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/util.h" diff --git a/engine/backend/timer/default/default-timer.cpp b/engine/backend/timer/default/default-timer.cpp index 3b6eccdf4b6..981eba81a52 100644 --- a/engine/backend/timer/default/default-timer.cpp +++ b/engine/backend/timer/default/default-timer.cpp @@ -8,25 +8,25 @@ * 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/sys.h" - -#include "engine/backend/platform/driver.h" #include "engine/backend/timer/default/default-timer.h" +#include "common/util.h" +#include "engine/backend/platform/driver.h" + struct TimerSlot { Common::TimerManager::TimerProc callback; @@ -93,8 +93,7 @@ void DefaultTimerManager::handler() { _head->next = slot->next; // Update the fire time and reinsert the TimerSlot into the priority - // queue. Has to be done before the timer callback is invoked, in case - // the callback wants to remove itself. + // queue. assert(slot->interval > 0); slot->nextFireTime += (slot->interval / 1000); slot->nextFireTimeMicro += (slot->interval % 1000); diff --git a/engine/backend/timer/default/default-timer.h b/engine/backend/timer/default/default-timer.h index 2fc0c245abc..a22b37cc30c 100644 --- a/engine/backend/timer/default/default-timer.h +++ b/engine/backend/timer/default/default-timer.h @@ -8,19 +8,18 @@ * 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 BACKENDS_TIMER_DEFAULT_H @@ -30,13 +29,12 @@ #include "common/mutex.h" struct TimerSlot; - + class DefaultTimerManager : public Common::TimerManager { private: Common::Mutex _mutex; void *_timerHandler; TimerSlot *_head; - public: DefaultTimerManager(); @@ -44,7 +42,9 @@ public: bool installTimerProc(TimerProc proc, int32 interval, void *refCon); void removeTimerProc(TimerProc proc); - // Timer callback, to be invoked at regular time intervals by the backend. + /** + * Timer callback, to be invoked at regular time intervals by the backend. + */ void handler(); }; diff --git a/engine/cmd_line.cpp b/engine/cmd_line.cpp index 2ac0c78da67..7a5eac54ebb 100644 --- a/engine/cmd_line.cpp +++ b/engine/cmd_line.cpp @@ -31,22 +31,6 @@ #include "engine/version.h" #include "engine/backend/platform/driver.h" -#ifdef IPHONE -#include "engine/backend/platform/iphone/osys_iphone.h" -#endif - -#ifdef UNIX -#ifdef MACOSX -#define DEFAULT_SAVE_PATH "Documents/Residual Savegames" -#else -#define DEFAULT_SAVE_PATH ".residual" -#endif -#elif defined(__SYMBIAN32__) -#define DEFAULT_SAVE_PATH "Residual" -#elif defined(PALMOS_MODE) -#define DEFAULT_SAVE_PATH "/PALM/Programs/Residual/Saved" -#endif - #define DETECTOR_TESTING_HACK static const char USAGE_STRING[] = @@ -142,27 +126,6 @@ void registerDefaults() { ConfMan.registerDefault("disable_sdl_parachute", false); - // Register default savepath -#ifdef DEFAULT_SAVE_PATH - char savePath[MAXPATHLEN]; -#if defined(UNIX) && !defined(IPHONE) - const char *home = getenv("HOME"); - if (home && *home && strlen(home) < MAXPATHLEN) { - snprintf(savePath, MAXPATHLEN, "%s/%s", home, DEFAULT_SAVE_PATH); - ConfMan.registerDefault("savepath", savePath); - } -#elif defined(__SYMBIAN32__) - strcpy(savePath, Symbian::GetExecutablePath()); - strcat(savePath, DEFAULT_SAVE_PATH); - ConfMan.registerDefault("savepath", savePath); -#elif defined (IPHONE) - ConfMan.registerDefault("savepath", OSystem_IPHONE::getSavePath()); - -#elif defined(PALMOS_MODE) - ConfMan.registerDefault("savepath", DEFAULT_SAVE_PATH); -#endif -#endif // #ifdef DEFAULT_SAVE_PATH - ConfMan.registerDefault("record_mode", "none"); ConfMan.registerDefault("record_file_name", "record.bin"); ConfMan.registerDefault("record_temp_file_name", "record.tmp"); @@ -330,7 +293,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, char **ar END_OPTION DO_OPTION('p', "path") - FilesystemNode path(option); + Common::FSNode path(option); if (!path.exists()) { usage("Non-existent game path '%s'", option); } else if (!path.isReadable()) { @@ -342,7 +305,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, char **ar END_OPTION DO_LONG_OPTION("savepath") - FilesystemNode path(option); + Common::FSNode path(option); if (!path.exists()) { usage("Non-existent savegames path '%s'", option); } else if (!path.isWritable()) { @@ -351,7 +314,7 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, char **ar END_OPTION DO_LONG_OPTION("extrapath") - FilesystemNode path(option); + Common::FSNode path(option); if (!path.exists()) { usage("Non-existent extra path '%s'", option); } else if (!path.isReadable()) { @@ -389,7 +352,7 @@ unknownOption: } } - return Common::String::emptyString; + return Common::String(); } /** List all supported game IDs, i.e. all games which any loaded plugin supports. */ @@ -439,7 +402,7 @@ bool processSettings(Common::String &command, Common::StringMap &settings) { if (!settings.contains("savepath")) { const char *dir = getenv("RESIDUAL_SAVEPATH"); if (dir && *dir && strlen(dir) < MAXPATHLEN) { - FilesystemNode saveDir(dir); + Common::FSNode saveDir(dir); if (!saveDir.exists()) { warning("Non-existent RESIDUAL_SAVEPATH save path. It will be ignored."); } else if (!saveDir.isWritable()) { diff --git a/engine/engine.cpp b/engine/engine.cpp index 28f70bdaca3..d7cc69f8f8d 100644 --- a/engine/engine.cpp +++ b/engine/engine.cpp @@ -218,9 +218,7 @@ extern Imuse *g_imuse; int g_imuseState = -1; int g_flags = 0; -FilesystemNode *g_fsdir; -FSList *g_fslist; -FSList::const_iterator g_findfile; +extern Common::StringList::const_iterator g_filesiter; // hack for access current upated actor to allow access position of actor to sound costume component Actor *g_currentUpdatedActor = NULL; @@ -249,8 +247,7 @@ Engine::Engine() : sprintf(buf, "%d", 1000 / _speedLimitMs); g_registry->set("engine_speed", buf); _refreshDrawNeeded = true; - g_fslist = NULL; - g_fsdir = NULL; + g_filesiter = NULL; _savedState = NULL; _fps[0] = 0; @@ -603,7 +600,7 @@ void Engine::mainLoop() { // Process events Common::Event event; - while (g_driver->pollEvent(event)) { + while (g_driver->getEventManager()->pollEvent(event)) { // Handle any button operations if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_KEYUP) handleButton(event.type, event.kbd.keycode, event.kbd.flags, event.kbd.ascii); diff --git a/engine/imuse/imuse_mcmp_mgr.cpp b/engine/imuse/imuse_mcmp_mgr.cpp index 6c64a9a845c..2ca02cc7164 100644 --- a/engine/imuse/imuse_mcmp_mgr.cpp +++ b/engine/imuse/imuse_mcmp_mgr.cpp @@ -53,7 +53,7 @@ McmpMgr::~McmpMgr() { } bool McmpMgr::openSound(const char *filename, byte **resPtr, int &offsetData) { - _file = g_resourceloader->openNewStream(filename); + _file = g_resourceloader->openNewStreamFile(filename); if (!_file || !_file->isOpen()) { warning("McmpMgr::openSound() Can't open sound MCMP file: %s", filename); diff --git a/engine/lab.cpp b/engine/lab.cpp index 244cc1b58aa..a35b2e9932a 100644 --- a/engine/lab.cpp +++ b/engine/lab.cpp @@ -91,7 +91,22 @@ Block *Lab::getFileBlock(const char *filename) const { return new Block(data, i->second.len); } -Common::File *Lab::openNewStream(const char *filename) const { +LuaFile *Lab::openNewStreamLua(const char *filename) const { + FileMapType::const_iterator i = findFilename(filename); + if (i == _fileMap.end()) + return NULL; + + Common::File *file = new Common::File(); + file->open(_labFileName.c_str()); + file->seek(i->second.offset, SEEK_SET); + + LuaFile *filehandle = new LuaFile(); + filehandle->_file = file; + + return filehandle; +} + +Common::File *Lab::openNewStreamFile(const char *filename) const { Common::File *file; FileMapType::const_iterator i = findFilename(filename); if (i == _fileMap.end()) diff --git a/engine/lab.h b/engine/lab.h index 52da7067ab7..b72bdb5e2e8 100644 --- a/engine/lab.h +++ b/engine/lab.h @@ -32,6 +32,8 @@ #include "common/file.h" +#include "engine/lua/lua.h" + class Block { public: Block(const char *data, int len) : _data(data), _len(len) {} @@ -56,7 +58,8 @@ public: void close(); bool fileExists(const char *filename) const; Block *getFileBlock(const char *filename) const; - Common::File *openNewStream(const char *filename) const; + Common::File *openNewStreamFile(const char *filename) const; + LuaFile *openNewStreamLua(const char *filename) const; int fileLength(const char *filename) const; ~Lab() { close(); } diff --git a/engine/localize.cpp b/engine/localize.cpp index 6cda6442cb0..149799aa2b5 100644 --- a/engine/localize.cpp +++ b/engine/localize.cpp @@ -37,16 +37,13 @@ Localizer *g_localizer = NULL; Localizer::Localizer() { Common::File f; - const char *namesToTry[] = { "/GRIM.TAB", "/Grim.tab", "/grim.tab" }; + const char *namesToTry[] = { "GRIM.TAB", "Grim.tab", "grim.tab" }; if (g_flags & GF_DEMO) return; for (unsigned i = 0; i < sizeof(namesToTry) / sizeof(namesToTry[0]); i++) { - const char *datadir = g_registry->get("GrimDataDir", "."); - std::string fname = (datadir != NULL ? datadir : "."); - fname += namesToTry[i]; - f.open(fname.c_str()); + f.open(namesToTry[i]); if (f.isOpen()) break; } diff --git a/engine/lua.cpp b/engine/lua.cpp index af541883cbf..91613c34e38 100644 --- a/engine/lua.cpp +++ b/engine/lua.cpp @@ -64,9 +64,8 @@ extern Imuse *g_imuse; -extern FilesystemNode *g_fsdir; -extern FSList *g_fslist; -extern FSList::const_iterator g_findfile; +Common::StringList g_listfiles; +Common::StringList::const_iterator g_filesiter; #define strmatch(src, dst) (strlen(src) == strlen(dst) && strcmp(src, dst) == 0) #define DEBUG_FUNCTION() debugFunction("Function", __FUNCTION__) @@ -1527,20 +1526,21 @@ static void TextFileGetLine() { char textBuf[512]; textBuf[0] = 0; const char *filename; - Common::File file; + Common::SeekableReadStream *file; DEBUG_FUNCTION(); filename = luaL_check_string(1); - file.open(filename); - if (!file.isOpen()) { + Common::SaveFileManager *saveFileMan = g_driver->getSavefileManager(); + file = saveFileMan->openForLoading(filename); + if (!file) { lua_pushnil(); return; } int pos = check_int(2); - file.seek(pos, SEEK_SET); - file.readLine(textBuf, 512); - file.close(); + file->seek(pos, SEEK_SET); + file->readLine_NEW(textBuf, 512); + delete file; lua_pushstring(textBuf); } @@ -1548,12 +1548,13 @@ static void TextFileGetLine() { static void TextFileGetLineCount() { char textBuf[512]; const char *filename; - Common::File file; - + Common::SeekableReadStream *file; + DEBUG_FUNCTION(); filename = luaL_check_string(1); - file.open(filename); - if (!file.isOpen()) { + Common::SaveFileManager *saveFileMan = g_driver->getSavefileManager(); + file = saveFileMan->openForLoading(filename); + if (!file) { lua_pushnil(); return; } @@ -1562,17 +1563,17 @@ static void TextFileGetLineCount() { int line = 0; for (;;) { - if (file.eof()) + if (file->eos()) break; lua_pushobject(result); lua_pushnumber(line); - int pos = file.pos(); + int pos = file->pos(); lua_pushnumber(pos); lua_settable(); - file.readLine(textBuf, 512); + file->readLine_NEW(textBuf, 512); line++; } - file.close(); + delete file; lua_pushobject(result); lua_pushstring("count"); @@ -2103,7 +2104,7 @@ static void RestoreIMuse() { g_imuse->resetState(); g_imuse->restoreState(savedIMuse); delete savedIMuse; - g_saveFileMan->removeSavefile("grim.tmp"); + g_driver->getSavefileManager()->removeSavefile("grim.tmp"); } static void SetSoundPosition() { @@ -2152,45 +2153,36 @@ static void PlaySoundAt() { static void FileFindDispose() { DEBUG_FUNCTION(); - delete g_fslist; - g_fslist = NULL; - - delete g_fsdir; - g_fsdir = NULL; + if (g_filesiter) + g_filesiter->begin(); + g_listfiles.clear(); } static void luaFileFindNext() { - if (g_findfile != g_fslist->end()) { - lua_pushstring(g_findfile->getName().c_str()); - g_findfile++; - } else { + if (g_filesiter == g_listfiles.end()) { lua_pushnil(); FileFindDispose(); + } else { + lua_pushstring(g_filesiter->c_str()); + g_filesiter++; } } static void luaFileFindFirst() { - const char *path, *extension; + const char *extension; DEBUG_FUNCTION(); extension = luaL_check_string(1); - lua_getparam(2); // overrinding below devel external gama data path to savepath + lua_getparam(2); FileFindDispose(); - path = ConfMan.get("savepath").c_str(); -#ifdef _WIN32_WCE - if (path.empty()) - path = ConfMan.get("path").c_str(); -#endif + Common::SaveFileManager *saveFileMan = g_driver->getSavefileManager(); + g_listfiles = saveFileMan->listSavefiles(extension); + g_filesiter = g_listfiles.begin(); - g_fslist = new FSList(); - g_fsdir = new FilesystemNode(path); - g_fsdir->lookupFile(*g_fslist, extension, false, true, 0); - if (g_fslist->empty()) + if (g_filesiter == g_listfiles.end()) lua_pushnil(); - g_findfile = g_fslist->begin(); - luaFileFindNext(); } @@ -2346,7 +2338,8 @@ static void CleanBuffer() { static void Exit() { DEBUG_FUNCTION(); - g_driver->quit(); + + exit(0); } /* Check for an existing object by a certain name @@ -3078,11 +3071,11 @@ static void Save() { } static void lua_remove() { - if (g_saveFileMan->removeSavefile(luaL_check_string(1))) + if (g_driver->getSavefileManager()->removeSavefile(luaL_check_string(1))) lua_pushuserdata(NULL); else { lua_pushnil(); - lua_pushstring(g_saveFileMan->getErrorDesc().c_str()); + lua_pushstring(g_driver->getSavefileManager()->getErrorDesc().c_str()); } } diff --git a/engine/lua/liolib.cpp b/engine/lua/liolib.cpp index 42cbdd41253..a6cf930feaa 100644 --- a/engine/lua/liolib.cpp +++ b/engine/lua/liolib.cpp @@ -15,6 +15,7 @@ #include "engine/resource.h" #include "engine/cmd_line.h" +#include "engine/engine.h" #include "engine/savegame.h" #include "engine/backend/platform/driver.h" @@ -34,55 +35,67 @@ #define FINPUT "_INPUT" #define FOUTPUT "_OUTPUT" -Common::File *g_fin; -Common::File *g_fout; -Common::File *g_stdin; -Common::File *g_stdout; -Common::File *g_stderr; +LuaFile *g_fin; +LuaFile *g_fout; +LuaFile *g_stdin; +LuaFile *g_stdout; +LuaFile *g_stderr; - -extern Common::SaveFileManager *g_saveFileMan; - -static void checkPath(const Common::String &path) { - FilesystemNode node(path); - if (node.exists() && node.isDirectory()) - return; -#if defined(UNIX) || defined(__SYMBIAN32__) - if (mkdir(path.c_str(), 0755) == 0) - return; -#elif defined(_WIN32) - if (_mkdir(path.c_str()) == 0) - return; -#endif - warning("Can't creating missing director for save path: %s", path.c_str()); +LuaFile::LuaFile() : _in(NULL), _out(NULL), _file(NULL), _stdin(false), _stdout(false), _stderr(false) { } -static void join_paths(const char *filename, const char *directory, - char *buf, int bufsize) { - buf[bufsize - 1] = '\0'; - strncpy(buf, directory, bufsize - 1); - -#ifdef WIN32 - // Fix for Win98 issue related with game directory pointing to root drive ex. "c:\" - if ((buf[0] != 0) && (buf[1] == ':') && (buf[2] == '\\') && (buf[3] == 0)) { - buf[2] = 0; - } -#endif - - const int dirLen = strlen(buf); - - if (dirLen > 0) { -#if defined(__MORPHOS__) || defined(__amigaos4__) - if (buf[dirLen - 1] != ':' && buf[dirLen - 1] != '/') -#endif - -#if !defined(__GP32__) - strncat(buf, "/", bufsize - 1); // prevent double / -#endif - } - strncat(buf, filename, bufsize - 1); +LuaFile::~LuaFile() { + close(); } +void LuaFile::close() { + delete _in; + delete _out; + delete _file; + _in = NULL; + _out = NULL; + _file = NULL; + _stdin = _stdout = _stderr = false; +} + +bool LuaFile::isOpen() const { + return _in || _out || _stdin || stdout || stderr; +} + +uint32 LuaFile::read(void *buf, uint32 len) { + if (_stdin) { + return fread(buf, len, 1, stdin); + } else if (_in) { + return _in->read(buf, len); + } else if (_file) { + return _file->read(buf, len); + } else + assert(0); + return 0; +} + +uint32 LuaFile::write(const char *buf, uint32 len) { + if (_stdout) { + return fwrite(buf, len, 1, stdout); + } else if (_stderr) { + return fwrite(buf, len, 1, stderr); + } else if (_out) { + return _out->write(buf, len); + } else + assert(0); + return 0; +} + +void LuaFile::seek(int32 pos, int whence) { + if (_stdin) { + fseek(stdin, pos, whence); + } else if (_in) { + _in->seek(pos, whence); + } else if (_file) { + _file->seek(pos, whence); + } else + assert(0); +} static int32 gettag(int32 i) { return (int32)lua_getnumber(lua_getparam(i)); @@ -106,35 +119,35 @@ static int32 ishandler(lua_Object f) { else return 0; } -static Common::File *getfile(const char *name) { +static LuaFile *getfile(const char *name) { lua_Object f = lua_getglobal(name); if (!ishandler(f)) luaL_verror("global variable `%.50s' is not a file handle", name); - return (Common::File *)lua_getuserdata(f); + return (LuaFile *)lua_getuserdata(f); } -static Common::File *getfileparam(const char *name, int32 *arg) { +static LuaFile *getfileparam(const char *name, int32 *arg) { lua_Object f = lua_getparam(*arg); if (ishandler(f)) { (*arg)++; - return (Common::File *)lua_getuserdata(f); + return (LuaFile *)lua_getuserdata(f); } else return getfile(name); } static void closefile(const char *name) { - Common::File *f = getfile(name); + LuaFile *f = getfile(name); f->close(); lua_pushobject(lua_getglobal(name)); lua_settag(gettag(CLOSEDTAG)); } -static void setfile(Common::File *f, const char *name, int32 tag) { +static void setfile(LuaFile *f, const char *name, int32 tag) { lua_pushusertag(f, tag); lua_setglobal(name); } -static void setreturn(Common::File *f, const char *name) { +static void setreturn(LuaFile *f, const char *name) { int32 tag = gettag(IOTAG); setfile(f, name, tag); lua_pushusertag(f, tag); @@ -146,7 +159,7 @@ static void io_readfrom() { closefile(FINPUT); setreturn(g_fin, FINPUT); } else if (lua_tag(f) == gettag(IOTAG)) { - Common::File *current = (Common::File *)lua_getuserdata(f); + LuaFile *current = (LuaFile *)lua_getuserdata(f); if (!current) { pushresult(0); return; @@ -154,22 +167,18 @@ static void io_readfrom() { setreturn(current, FINPUT); } else { const char *s = luaL_check_string(FIRSTARG); - Common::File *current = new Common::File(); - Common::String dir = ConfMan.get("savepath"); -#ifdef _WIN32_WCE - if (dir.empty()) - dir = ConfMan.get("path"); -#endif - checkPath(dir); - char buf[256]; - join_paths(s, dir.c_str(), buf, sizeof(buf)); - if (current->exists(buf)) - current->open(buf); - if (!current->isOpen()) { - delete current; - current = g_resourceloader->openNewStream(s); + LuaFile *current; + Common::SeekableReadStream *inFile = NULL; + Common::SaveFileManager *saveFileMan = g_driver->getSavefileManager(); + inFile = saveFileMan->openForLoading(s); + if (!inFile) + current = g_resourceloader->openNewStreamLua(s); + else { + current = new LuaFile(); + current->_in = inFile; } if (!current) { + delete current; pushresult(0); return; } @@ -183,7 +192,7 @@ static void io_writeto() { closefile(FOUTPUT); setreturn(g_fout, FOUTPUT); } else if (lua_tag(f) == gettag(IOTAG)) { - Common::File *current = (Common::File *)lua_getuserdata(f); + LuaFile *current = (LuaFile *)lua_getuserdata(f); if (!current->isOpen()) { pushresult(0); return; @@ -191,66 +200,56 @@ static void io_writeto() { setreturn(current, FOUTPUT); } else { const char *s = luaL_check_string(FIRSTARG); - Common::File *current = new Common::File(); - Common::String dir = ConfMan.get("savepath"); -#ifdef _WIN32_WCE - if (dir.empty()) - dir = ConfMan.get("path"); -#endif - checkPath(dir); - char buf[256]; - join_paths(s, dir.c_str(), buf, sizeof(buf)); - current->open(buf, Common::File::kFileWriteMode); - if (!current->isOpen()) { - delete current; + LuaFile *current; + Common::WriteStream *outFile = NULL; + Common::SaveFileManager *saveFileMan = g_driver->getSavefileManager(); + outFile = saveFileMan->openForSaving(s); + if (!outFile) { pushresult(0); return; } + current = new LuaFile(); + current->_out = outFile; setreturn(current, FOUTPUT); } } static void io_appendto() { const char *s = luaL_check_string(FIRSTARG); - Common::File file; - Common::String dir = ConfMan.get("savepath"); -#ifdef _WIN32_WCE - if (dir.empty()) - dir = ConfMan.get("path"); -#endif - checkPath(dir); - char path[256]; - join_paths(s, dir.c_str(), path, sizeof(path)); - file.open(path); - if (!file.isOpen()) { + Common::SeekableReadStream *inFile = NULL; + Common::SaveFileManager *saveFileMan = g_driver->getSavefileManager(); + inFile = saveFileMan->openForLoading(s); + if (!inFile) { pushresult(0); return; } - int size = file.size(); + int size = inFile->size(); byte *buf = new byte[size]; - file.read(buf, size); - file.close(); + inFile->read(buf, size); + delete inFile; - Common::File *fp = new Common::File(); - fp->open(path, Common::File::kFileWriteMode); - if (fp->isOpen()) { - fp->write(buf, size); - setreturn(fp, FOUTPUT); - } else { - delete fp; + Common::WriteStream *outFile = NULL; + outFile = saveFileMan->openForSaving(s); + if (!outFile) pushresult(0); + else { + outFile->write(buf, size); + LuaFile *current = new LuaFile(); + current->_out = outFile; + setreturn(current, FOUTPUT); } delete[] buf; } #define NEED_OTHER (EOF - 1) // just some flag different from EOF -static void read_until(Common::File *f, int32 lim) { +static void read_until(LuaFile *f, int32 lim) { int32 l = 0; int8 c; if (f->read(&c, 1) == 0) c = EOF; + for (; c != EOF && c != lim; ) { luaL_addchar(c); l++; @@ -263,7 +262,7 @@ static void read_until(Common::File *f, int32 lim) { static void io_read() { int32 arg = FIRSTARG; - Common::File *f = (Common::File *)getfileparam(FINPUT, &arg); + LuaFile *f = (LuaFile *)getfileparam(FINPUT, &arg); const char *p = luaL_opt_string(arg, NULL); luaL_resetbuffer(); if (!p) // default: read a line @@ -325,7 +324,7 @@ static void io_read() { } } break_while: - if (c >= 0) // not EOF nor NEED_OTHER? + if (c >= 0) // not EOF nor NEED_OTHER? f->seek(-1, SEEK_CUR); if (l > 0 || *p == 0) // read something or did not fail? lua_pushlstring(luaL_buffer(), l); @@ -334,7 +333,7 @@ break_while: static void io_write() { int32 arg = FIRSTARG; - Common::File *f = (Common::File *)getfileparam(FOUTPUT, &arg); + LuaFile *f = (LuaFile *)getfileparam(FOUTPUT, &arg); int32 status = 1; const char *s; int32 l; @@ -435,36 +434,25 @@ static void openwithtags() { lua_setglobal(iolibtag[i].name); } - g_fin = new Common::File(); - g_fin->open("(stdin)"); - if (!g_fin->isOpen()) - delete g_fin; - else - setfile(g_fin, FINPUT, iotag); - g_fout = new Common::File(); - g_fout->open("(stdout)", Common::File::kFileWriteMode); - if (!g_fout->isOpen()) - delete g_fout; - else - setfile(g_fout, FOUTPUT, iotag); - g_stdin = new Common::File(); - g_stdin->open("(stdin)"); - if (!g_stdin->isOpen()) - delete g_stdin; - else - setfile(g_stdin, "_STDIN", iotag); - g_stdout = new Common::File(); - g_stdout->open("(stdout)", Common::File::kFileWriteMode); - if (!g_stdout->isOpen()) - delete g_stdout; - else - setfile(g_stdout, "_STDOUT", iotag); - g_stderr = new Common::File(); - g_stderr->open("(stderr)", Common::File::kFileWriteMode); - if (!g_stderr->isOpen()) - delete g_stderr; - else - setfile(g_stderr, "_STDERR", iotag); + g_fin = new LuaFile(); + g_fin->_stdin = true; + setfile(g_fin, FINPUT, iotag); + + g_fout = new LuaFile(); + g_fout->_stdout = true; + setfile(g_fout, FOUTPUT, iotag); + + g_stdin = new LuaFile(); + g_stdin->_stdin = true; + setfile(g_stdin, "_STDIN", iotag); + + g_stdout = new LuaFile(); + g_stdout->_stdout = true; + setfile(g_stdout, "_STDOUT", iotag); + + g_stderr = new LuaFile(); + g_stderr->_stderr = true; + setfile(g_stderr, "_STDERR", iotag); } void lua_iolibopen() { diff --git a/engine/lua/lua.h b/engine/lua/lua.h index cbedecaf984..b94b24af013 100644 --- a/engine/lua/lua.h +++ b/engine/lua/lua.h @@ -8,6 +8,7 @@ */ #include "common/sys.h" +#include "common/str.h" #ifndef lua_h #define lua_h @@ -34,6 +35,32 @@ struct PointerId { PointerId makeIdFromPointer(void *ptr); void *makePointerFromId(PointerId ptr); +namespace Common { + class String; + class SeekableReadStream; + class WriteStream; + class File; +} + +class LuaFile { +public: + Common::String _name; + Common::SeekableReadStream *_in; + Common::WriteStream *_out; + Common::File *_file; + bool _stdin, _stdout, _stderr; + +public: + LuaFile(); + ~LuaFile(); + + void close(); + bool isOpen() const; + uint32 read(void *buf, uint32 len); + uint32 write(const char *buf, uint32 len); + void seek(int32 pos, int whence = 0); +}; + typedef void (*SaveStream)(void *, int32); typedef void (*SaveSint32)(int32); typedef void (*SaveUint32)(uint32); diff --git a/engine/main.cpp b/engine/main.cpp index dbd250b9031..53ef09d179f 100644 --- a/engine/main.cpp +++ b/engine/main.cpp @@ -25,6 +25,7 @@ #include "common/sys.h" #include "common/timer.h" +#include "common/fs.h" #include "engine/bitmap.h" #include "engine/resource.h" @@ -65,6 +66,9 @@ extern "C" int residual_main(int argc, char *argv[]) { Common::StringMap settings; command = parseCommandLine(settings, argc, argv); + // init temporary to allow read config file + g_driver = new DriverTinyGL(); + // Load the config file (possibly overriden via command line): if (settings.contains("config")) { ConfMan.loadConfigFile(settings["config"]); @@ -101,17 +105,20 @@ extern "C" int residual_main(int argc, char *argv[]) { ZBUFFER_GLOBAL = (tolower(g_registry->get("gl_zbuffer", "FALSE")[0]) == 't'); bool fullscreen = (tolower(g_registry->get("fullscreen", "FALSE")[0]) == 't'); - if (SDL_Init(SDL_INIT_EVERYTHING) < 0) - return 1; + delete g_driver; // delete temporary created driver + if (TINYGL_GLOBAL) + g_driver = new DriverTinyGL(); + else + g_driver = new DriverGL(); + + g_driver->init(); + g_driver->setupScreen(640, 480, fullscreen); atexit(quit); - if (TINYGL_GLOBAL) - g_driver = new DriverTinyGL(640, 480, 16, fullscreen); - else - g_driver = new DriverGL(640, 480, 24, fullscreen); - g_driver->init(); - g_driver->setupMixer(); + Common::FSNode dir(g_registry->get("GrimDataDir", ".")); + SearchMan.addDirectory(dir.getPath(), dir, 0, 1); + g_driver->getMixer()->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, 127); g_driver->getMixer()->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, Audio::Mixer::kMaxMixerVolume); g_driver->getMixer()->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, Audio::Mixer::kMaxMixerVolume); @@ -121,7 +128,6 @@ extern "C" int residual_main(int argc, char *argv[]) { g_localizer = new Localizer(); g_smush = new Smush(); g_imuse = new Imuse(20); - g_saveFileMan = g_driver->getSavefileManager(); Bitmap *splash_bm = NULL; if (!(g_flags & GF_DEMO)) @@ -184,8 +190,8 @@ void quit() { g_engine = NULL; delete g_resourceloader; g_resourceloader = NULL; + if (g_driver) + g_driver->quit(); delete g_driver; g_driver = NULL; - - SDL_Quit(); } diff --git a/engine/resource.cpp b/engine/resource.cpp index d808a8dc94c..076be926435 100644 --- a/engine/resource.cpp +++ b/engine/resource.cpp @@ -48,22 +48,18 @@ static void makeLower(std::string& s) { ResourceLoader *g_resourceloader = NULL; ResourceLoader::ResourceLoader() { - const char *directory = g_registry->get("GrimDataDir", "."); int lab_counter = 0; - FSList *fslist; - FilesystemNode *fsdir; + Lab *l; + Common::ArchiveMemberList files; - fslist = new FSList(); - fsdir = new FilesystemNode(directory); - fsdir->lookupFile(*fslist, "*.lab", false, true, 0); - if (fslist->empty()) + SearchMan.listMatchingMembers(files, "*.lab"); + + if (files.empty()) error("Cannot find game data - check configuration file"); - Lab *l; - - for (FSList::const_iterator findfile = fslist->begin(); findfile != fslist->end(); ++findfile) { - Common::String filename(findfile->getName()); - l = new Lab(findfile->getPath().c_str()); + for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { + const Common::String filename = (*x)->getName(); + l = new Lab(filename.c_str()); if (l->isOpen()) { if (filename == "005.lab") _labs.push_front(l); @@ -78,10 +74,13 @@ ResourceLoader::ResourceLoader() { } } - fsdir->lookupFile(*fslist, "*.mus", false, true, 0); - for (FSList::const_iterator findfile = fslist->begin(); findfile != fslist->end(); ++findfile) { - Common::String filename(findfile->getName()); - l = new Lab(findfile->getPath().c_str()); + files.clear(); + + SearchMan.listMatchingMembers(files, "*.mus"); + + for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { + const Common::String filename = (*x)->getName(); + l = new Lab(filename.c_str()); if (l->isOpen()) { _labs.push_back(l); lab_counter++; @@ -89,8 +88,6 @@ ResourceLoader::ResourceLoader() { delete l; } } - delete fsdir; - delete fslist; } ResourceLoader::~ResourceLoader() { @@ -118,13 +115,22 @@ Block *ResourceLoader::getFileBlock(const char *filename) const { return l->getFileBlock(filename); } -Common::File *ResourceLoader::openNewStream(const char *filename) const { +LuaFile *ResourceLoader::openNewStreamLua(const char *filename) const { const Lab *l = findFile(filename); if (!l) return NULL; else - return l->openNewStream(filename); + return l->openNewStreamLua(filename); +} + +Common::File *ResourceLoader::openNewStreamFile(const char *filename) const { + const Lab *l = findFile(filename); + + if (!l) + return NULL; + else + return l->openNewStreamFile(filename); } int ResourceLoader::fileLength(const char *filename) const { diff --git a/engine/resource.h b/engine/resource.h index 1ed94130fc4..430b1257b0e 100644 --- a/engine/resource.h +++ b/engine/resource.h @@ -26,6 +26,8 @@ #ifndef RESOURCE_H #define RESOURCE_H +#include "common/archive.h" + #include "engine/lab.h" #include @@ -96,7 +98,8 @@ class ResourceLoader { public: bool fileExists(const char *filename) const; Block *getFileBlock(const char *filename) const; - Common::File *openNewStream(const char *filename) const; + Common::File *openNewStreamFile(const char *filename) const; + LuaFile *openNewStreamLua(const char *filename) const; int fileLength(const char *filename) const; bool exportResource(const char *filename); @@ -122,6 +125,8 @@ private: typedef std::map CacheType; CacheType _cache; + Common::SearchSet _files; + // Shut up pointless g++ warning friend class dummy; }; diff --git a/engine/savegame.cpp b/engine/savegame.cpp index 7061dbc5a8d..ca4cadf989f 100644 --- a/engine/savegame.cpp +++ b/engine/savegame.cpp @@ -28,8 +28,7 @@ #include "common/endian.h" #include "engine/savegame.h" - -Common::SaveFileManager *g_saveFileMan; +#include "engine/backend/platform/driver.h" #define SAVEGAME_HEADERTAG 'RSAV' #define SAVEGAME_FOOTERTAG 'ESAV' @@ -39,7 +38,7 @@ Common::SaveFileManager *g_saveFileMan; SaveGame::SaveGame(const char *filename, bool saving) : _saving(saving), _currentSection(0) { if (_saving) { - _outSaveFile = g_saveFileMan->openForSaving(filename); + _outSaveFile = g_driver->getSavefileManager()->openForSaving(filename); if (!_outSaveFile) { warning("SaveGame::SaveGame() Error creating savegame file"); return; @@ -49,7 +48,7 @@ SaveGame::SaveGame(const char *filename, bool saving) : } else { uint32 tag, version; - _inSaveFile = g_saveFileMan->openForLoading(filename); + _inSaveFile = g_driver->getSavefileManager()->openForLoading(filename); if (!_inSaveFile) { warning("SaveGame::SaveGame() Error opening savegame file"); return; @@ -66,10 +65,11 @@ SaveGame::~SaveGame() { _outSaveFile->writeUint32BE(SAVEGAME_FOOTERTAG); _outSaveFile->finalize(); if (_outSaveFile->ioFailed()) - warning("SaveGame::~SaveGame()Can't write file. (Disk full?)"); + warning("SaveGame::~SaveGame() Can't write file. (Disk full?)"); delete _outSaveFile; - } else + } else { delete _inSaveFile; + } } uint32 SaveGame::beginSection(uint32 sectionTag) { diff --git a/engine/savegame.h b/engine/savegame.h index 0b85ac2e06f..b05306d8ec7 100644 --- a/engine/savegame.h +++ b/engine/savegame.h @@ -33,13 +33,12 @@ #endif #include "common/debug.h" +#include "common/savefile.h" #include "engine/lua.h" namespace Common { class SaveFileManager; - class InSaveFile; - class OutSaveFile; } extern Common::SaveFileManager *g_saveFileMan; diff --git a/engine/smush/smush.cpp b/engine/smush/smush.cpp index 9d488b82fe8..909bdcb208d 100644 --- a/engine/smush/smush.cpp +++ b/engine/smush/smush.cpp @@ -398,7 +398,7 @@ bool zlibFile::open(const char *filename) { if (!filename || *filename == 0) return false; - _handle = g_resourceloader->openNewStream(filename); + _handle = g_resourceloader->openNewStreamFile(filename); if (!_handle->isOpen()) { if (debugLevel == DEBUG_SMUSH || debugLevel == DEBUG_WARN || debugLevel == DEBUG_ALL) warning("zlibFile::open() zlibFile %s not found", filename); diff --git a/mixer/audiostream.cpp b/mixer/audiostream.cpp index 3ae212ff866..e7219693873 100644 --- a/mixer/audiostream.cpp +++ b/mixer/audiostream.cpp @@ -23,15 +23,17 @@ * */ -#include "common/sys.h" +#include "common/debug.h" #include "common/endian.h" +#include "common/file.h" #include "common/list.h" #include "common/util.h" -#include "engine/backend/platform/driver.h" - #include "mixer/audiostream.h" #include "mixer/mixer.h" +//#include "sound/mp3.h" +//#include "sound/vorbis.h" +//#include "sound/flac.h" // This used to be an inline template function, but @@ -45,6 +47,48 @@ namespace Audio { +struct StreamFileFormat { + /** Decodername */ + const char* decoderName; + const char* fileExtension; + /** + * Pointer to a function which tries to open a file of type StreamFormat. + * Return NULL in case of an error (invalid/nonexisting file). + */ + AudioStream* (*openStreamFile)(Common::SeekableReadStream *stream, bool disposeAfterUse, + uint32 startTime, uint32 duration, uint numLoops); +}; + +static const StreamFileFormat STREAM_FILEFORMATS[] = { + /* decoderName, fileExt, openStreamFuntion */ + + { NULL, NULL, NULL } // Terminator +}; + +AudioStream* AudioStream::openStreamFile(const Common::String &basename, uint32 startTime, uint32 duration, uint numLoops) { + AudioStream* stream = NULL; + Common::File *fileHandle = new Common::File(); + + for (int i = 0; i < ARRAYSIZE(STREAM_FILEFORMATS)-1 && stream == NULL; ++i) { + Common::String filename = basename + STREAM_FILEFORMATS[i].fileExtension; + fileHandle->open(filename); + if (fileHandle->isOpen()) { + // Create the stream object + stream = STREAM_FILEFORMATS[i].openStreamFile(fileHandle, true, startTime, duration, numLoops); + fileHandle = 0; + break; + } + } + + delete fileHandle; + + if (stream == NULL) { + debug(1, "AudioStream: Could not open compressed AudioFile %s", basename.c_str()); + } + + return stream; +} + #pragma mark - #pragma mark --- LinearMemoryStream --- #pragma mark - @@ -80,20 +124,10 @@ public: LinearMemoryStream(int rate, const byte *ptr, uint len, uint loopOffset, uint loopLen, bool autoFreeMemory) : _ptr(ptr), _end(ptr+len), _loopPtr(0), _loopEnd(0), _rate(rate), _playtime(calculatePlayTime(rate, len / (is16Bit ? 2 : 1) / (stereo ? 2 : 1))) { - // Verify the buffer sizes are sane - if (is16Bit && stereo) { - assert((len & 3) == 0 && (loopLen & 3) == 0); - } else if (is16Bit || stereo) { - assert((len & 1) == 0 && (loopLen & 1) == 0); - } - if (loopLen) { _loopPtr = _ptr + loopOffset; _loopEnd = _loopPtr + loopLen; } - if (stereo) { // Stereo requires even sized data - assert(len % 2 == 0); - } _origPtr = autoFreeMemory ? ptr : 0; } @@ -106,7 +140,6 @@ public: bool endOfData() const { return _ptr >= _end; } int getRate() const { return _rate; } - int32 getTotalPlayTime() const { return _playtime; } }; template @@ -169,6 +202,13 @@ AudioStream *makeLinearInputStream(const byte *ptr, uint32 len, int rate, byte f loopLen = loopEnd - loopStart; } + // Verify the buffer sizes are sane + if (is16Bit && isStereo) { + assert((len & 3) == 0 && (loopLen & 3) == 0); + } else if (is16Bit || isStereo) { + assert((len & 1) == 0 && (loopLen & 1) == 0); + } + if (isStereo) { if (isUnsigned) { MAKE_LINEAR(true, true); @@ -197,8 +237,7 @@ struct Buffer { /** * Wrapped memory stream. */ -template -class AppendableMemoryStream : public AppendableAudioStream { +class BaseAppendableMemoryStream : public AppendableAudioStream { protected: // A mutex to avoid access problems (causing e.g. corruption of @@ -215,28 +254,38 @@ protected: inline bool eosIntern() const { return _bufferQueue.empty(); }; public: - AppendableMemoryStream(int rate); - ~AppendableMemoryStream(); - int readBuffer(int16 *buffer, const int numSamples); + BaseAppendableMemoryStream(int rate); + ~BaseAppendableMemoryStream(); - bool isStereo() const { return stereo; } bool endOfStream() const { return _finalized && eosIntern(); } bool endOfData() const { return eosIntern(); } int getRate() const { return _rate; } - void queueBuffer(byte *data, uint32 size); void finish() { _finalized = true; } + + void queueBuffer(byte *data, uint32 size); }; +/** + * Wrapped memory stream. + */ template -AppendableMemoryStream::AppendableMemoryStream(int rate) +class AppendableMemoryStream : public BaseAppendableMemoryStream { +public: + AppendableMemoryStream(int rate) : BaseAppendableMemoryStream(rate) {} + + bool isStereo() const { return stereo; } + + int readBuffer(int16 *buffer, const int numSamples); +}; + +BaseAppendableMemoryStream::BaseAppendableMemoryStream(int rate) : _finalized(false), _rate(rate), _pos(0) { } -template -AppendableMemoryStream::~AppendableMemoryStream() { +BaseAppendableMemoryStream::~BaseAppendableMemoryStream() { // Clear the queue Common::List::iterator iter; for (iter = _bufferQueue.begin(); iter != _bufferQueue.end(); ++iter) @@ -270,20 +319,20 @@ int AppendableMemoryStream::readBuffer(int16 } while (--len); } - return numSamples-samples; + return numSamples - samples; } -template -void AppendableMemoryStream::queueBuffer(byte *data, uint32 size) { +void BaseAppendableMemoryStream::queueBuffer(byte *data, uint32 size) { Common::StackLock lock(_mutex); +/* // Verify the buffer size is sane if (is16Bit && stereo) { assert((size & 3) == 0); } else if (is16Bit || stereo) { assert((size & 1) == 0); } - +*/ // Verify that the stream has not yet been finalized (by a call to finish()) assert(!_finalized); diff --git a/mixer/audiostream.h b/mixer/audiostream.h index 5b14946dcb2..cfaba5fefa6 100644 --- a/mixer/audiostream.h +++ b/mixer/audiostream.h @@ -23,9 +23,10 @@ * */ -#ifndef AUDIOSTREAM_H -#define AUDIOSTREAM_H +#ifndef SOUND_AUDIOSTREAM_H +#define SOUND_AUDIOSTREAM_H +#include "common/util.h" #include "common/sys.h" @@ -58,6 +59,9 @@ public: /** Is this a stereo stream? */ virtual bool isStereo() const = 0; + /** Sample rate of the stream. */ + virtual int getRate() const = 0; + /** * End of data reached? If this returns true, it means that at this * time there is no data available in the stream. However there may be @@ -77,8 +81,32 @@ public: */ virtual bool endOfStream() const { return endOfData(); } - /** Sample rate of the stream. */ - virtual int getRate() const = 0; + /** + * Tries to load a file by trying all available formats. + * In case of an error, the file handle will be closed, but deleting + * it is still the responsibilty of the caller. + * @param basename a filename without an extension + * @param startTime the (optional) time offset in milliseconds from which to start playback + * @param duration the (optional) time in milliseconds specifying how long to play + * @param numLoops how often the data shall be looped (0 = infinite) + * @return an Audiostream ready to use in case of success; + * NULL in case of an error (e.g. invalid/nonexisting file) + */ + static AudioStream* openStreamFile(const Common::String &basename, uint32 startTime = 0, uint32 duration = 0, uint numLoops = 1); + + enum { + kUnknownPlayTime = -1 + }; + + /** + * Returns total playtime of the AudioStream object. + * Note that this does not require to return an playtime, if the + * playtime of the AudioStream is unknown it returns 'kUnknownPlayTime'. + * @see kUnknownPlayTime + * + * @return playtime in milliseconds + */ + virtual int32 getTotalPlayTime() const { return kUnknownPlayTime; } }; /** diff --git a/mixer/mixer.cpp b/mixer/mixer.cpp index 59bfc88e805..6cea519e7f3 100644 --- a/mixer/mixer.cpp +++ b/mixer/mixer.cpp @@ -32,6 +32,7 @@ #include "engine/backend/platform/driver.h" + namespace Audio { #pragma mark - @@ -57,6 +58,8 @@ private: uint32 _samplesConsumed; uint32 _samplesDecoded; uint32 _mixerTimeStamp; + uint32 _pauseStartTime; + uint32 _pauseTime; protected: RateConverter *_converter; @@ -81,6 +84,11 @@ public: _pauseLevel++; else if (_pauseLevel > 0) _pauseLevel--; + + if (_pauseLevel > 0) + _pauseStartTime = g_driver->getMillis(); + else + _pauseTime += (g_driver->getMillis() - _pauseStartTime); } bool isPaused() { return _pauseLevel != 0; @@ -103,8 +111,8 @@ public: #pragma mark - -MixerImpl::MixerImpl() - : _sampleRate(0), _mixerReady(false), _handleSeed(0) { +MixerImpl::MixerImpl(Driver *system) + : _syst(system), _sampleRate(0), _mixerReady(false), _handleSeed(0) { int i; @@ -208,7 +216,7 @@ void MixerImpl::mixCallback(byte *samples, uint len) { assert(samples); Common::StackLock lock(_mutex); - + int16 *buf = (int16 *)samples; len >>= 2; @@ -382,7 +390,8 @@ Channel::Channel(Mixer *mixer, Mixer::SoundType type, AudioStream *input, bool autofreeStream, bool reverseStereo, int id, bool permanent) : _type(type), _mixer(mixer), _autofreeStream(autofreeStream), _volume(Mixer::kMaxChannelVolume), _balance(0), _pauseLevel(0), _id(id), _samplesConsumed(0), - _samplesDecoded(0), _mixerTimeStamp(0), _converter(0), _input(input), _permanent(permanent) { + _samplesDecoded(0), _mixerTimeStamp(0), _pauseStartTime(0), _pauseTime(0), _converter(0), + _input(input), _permanent(permanent) { assert(mixer); assert(input); @@ -432,6 +441,7 @@ void Channel::mix(int16 *data, uint len) { _samplesConsumed = _samplesDecoded; _mixerTimeStamp = g_driver->getMillis(); + _pauseTime = 0; _converter->flow(*_input, data, len, vol_l, vol_r); @@ -451,7 +461,7 @@ uint32 Channel::getElapsedTime() { uint32 seconds = _samplesConsumed / rate; uint32 milliseconds = (1000 * (_samplesConsumed % rate)) / rate; - uint32 delta = g_driver->getMillis() - _mixerTimeStamp; + uint32 delta = g_driver->getMillis() - _mixerTimeStamp - _pauseTime; // In theory it would seem like a good idea to limit the approximation // so that it never exceeds the theoretical upper bound set by @@ -459,7 +469,6 @@ uint32 Channel::getElapsedTime() { // the Broken Sword cutscenes noticeably jerkier. I guess the mixer // isn't invoked at the regular intervals that I first imagined. - // FIXME: This won't work very well if the sound is paused. return 1000 * seconds + milliseconds + delta; } diff --git a/mixer/mixer.h b/mixer/mixer.h index 1e52e6cd86c..261c5065f22 100644 --- a/mixer/mixer.h +++ b/mixer/mixer.h @@ -30,6 +30,9 @@ #include "common/mutex.h" +class Driver; + + namespace Audio { class AudioStream; @@ -139,7 +142,7 @@ public: * Start playing the given audio input stream. * * Note that the sound id assigned below is unique. At most one stream - * with a given idea can play at any given time. Trying to play a sound + * with a given id can play at any given time. Trying to play a sound * with an id that is already in use causes the new sound to be not played. * * @param type the type (voice/sfx/music) of the stream diff --git a/mixer/mixer_intern.h b/mixer/mixer_intern.h index 7857eaf4ed8..2fcd7b4295b 100644 --- a/mixer/mixer_intern.h +++ b/mixer/mixer_intern.h @@ -32,12 +32,32 @@ namespace Audio { +/** + * The (default) implementation of the ScummVM audio mixing subsystem. + * + * Backends are responsible for allocating (and later releasing) an instance + * of this class, which engines can access via OSystem::getMixer(). + * + * Initialisation of instances of this class usually happens as follows: + * 1) Creat a new Audio::MixerImpl instance. + * 2) Set the hardware output sample rate via the setSampleRate() method. + * 3) Hook up the mixCallback() in a suitable audio processing thread/callback. + * 4) Change the mixer into ready mode via setReady(true). + * 5) Start audio processing (e.g. by resuming the audio thread, if applicable). + * + * In the future, we might make it possible for backends to provide + * (partial) alternative implementations of the mixer, e.g. to make + * better use of native sound mixing support on low-end devices. + * + * @see OSystem::getMixer() + */ class MixerImpl : public Mixer { private: enum { NUM_CHANNELS = 32 }; + Driver *_syst; Common::Mutex _mutex; uint _sampleRate; @@ -49,7 +69,7 @@ private: public: - MixerImpl(); + MixerImpl(Driver *system); ~MixerImpl(); virtual bool isReady() const { return _mixerReady; } @@ -115,7 +135,7 @@ public: * setOutputRate() has been called). */ void setReady(bool ready); - + /** * Set the output sample rate. * diff --git a/mixer/rate.cpp b/mixer/rate.cpp index 26f2ec73bbf..58cd58448dd 100644 --- a/mixer/rate.cpp +++ b/mixer/rate.cpp @@ -1,8 +1,6 @@ /* Residual - Virtual machine to run LucasArts' 3D adventure games - * - * Residual is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the AUTHORS - * file distributed with this source distribution. + * + * Copyright (C) 2003-2008 The ScummVM-Residual Team (www.scummvm.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/mixer/rate.h b/mixer/rate.h index dfb8f094a10..59fd0d356b7 100644 --- a/mixer/rate.h +++ b/mixer/rate.h @@ -1,4 +1,5 @@ /* Residual - Virtual machine to run LucasArts' 3D adventure games + * * Copyright (C) 2003-2008 The ScummVM-Residual Team (www.scummvm.org) * * This program is free software; you can redistribute it and/or