[Scummvm-cvs-logs] SF.net SVN: scummvm:[39864] residual/trunk

aquadran at users.sourceforge.net aquadran at users.sourceforge.net
Sun Apr 5 16:48:55 CEST 2009


Revision: 39864
          http://scummvm.svn.sourceforge.net/scummvm/?rev=39864&view=rev
Author:   aquadran
Date:     2009-04-05 14:48:54 +0000 (Sun, 05 Apr 2009)

Log Message:
-----------
update rest scummvm common code with residual, and update engine code to that changes

Modified Paths:
--------------
    residual/trunk/common/config-file.cpp
    residual/trunk/common/config-file.h
    residual/trunk/common/config-manager.cpp
    residual/trunk/common/config-manager.h
    residual/trunk/common/error.h
    residual/trunk/common/file.cpp
    residual/trunk/common/file.h
    residual/trunk/common/fs.cpp
    residual/trunk/common/fs.h
    residual/trunk/common/savefile.h
    residual/trunk/common/stream.cpp
    residual/trunk/common/stream.h
    residual/trunk/dists/msvc9/residual.vcproj
    residual/trunk/engine/backend/events/default/default-events.cpp
    residual/trunk/engine/backend/events/default/default-events.h
    residual/trunk/engine/backend/fs/abstract-fs.h
    residual/trunk/engine/backend/fs/amigaos4/amigaos4-fs-factory.cpp
    residual/trunk/engine/backend/fs/amigaos4/amigaos4-fs-factory.h
    residual/trunk/engine/backend/fs/amigaos4/amigaos4-fs.cpp
    residual/trunk/engine/backend/fs/fs-factory.h
    residual/trunk/engine/backend/fs/posix/posix-fs-factory.cpp
    residual/trunk/engine/backend/fs/posix/posix-fs-factory.h
    residual/trunk/engine/backend/fs/posix/posix-fs.cpp
    residual/trunk/engine/backend/fs/windows/windows-fs-factory.cpp
    residual/trunk/engine/backend/fs/windows/windows-fs-factory.h
    residual/trunk/engine/backend/fs/windows/windows-fs.cpp
    residual/trunk/engine/backend/platform/driver.h
    residual/trunk/engine/backend/platform/sdl/driver_gl.cpp
    residual/trunk/engine/backend/platform/sdl/driver_gl.h
    residual/trunk/engine/backend/platform/sdl/driver_sdl.cpp
    residual/trunk/engine/backend/platform/sdl/driver_sdl.h
    residual/trunk/engine/backend/platform/sdl/driver_tinygl.cpp
    residual/trunk/engine/backend/platform/sdl/driver_tinygl.h
    residual/trunk/engine/backend/saves/default/default-saves.cpp
    residual/trunk/engine/backend/saves/default/default-saves.h
    residual/trunk/engine/backend/saves/savefile.cpp
    residual/trunk/engine/backend/timer/default/default-timer.cpp
    residual/trunk/engine/backend/timer/default/default-timer.h
    residual/trunk/engine/cmd_line.cpp
    residual/trunk/engine/engine.cpp
    residual/trunk/engine/imuse/imuse_mcmp_mgr.cpp
    residual/trunk/engine/lab.cpp
    residual/trunk/engine/lab.h
    residual/trunk/engine/localize.cpp
    residual/trunk/engine/lua/liolib.cpp
    residual/trunk/engine/lua/lua.h
    residual/trunk/engine/lua.cpp
    residual/trunk/engine/main.cpp
    residual/trunk/engine/resource.cpp
    residual/trunk/engine/resource.h
    residual/trunk/engine/savegame.cpp
    residual/trunk/engine/savegame.h
    residual/trunk/engine/smush/smush.cpp
    residual/trunk/mixer/audiostream.cpp
    residual/trunk/mixer/audiostream.h
    residual/trunk/mixer/mixer.cpp
    residual/trunk/mixer/mixer.h
    residual/trunk/mixer/mixer_intern.h
    residual/trunk/mixer/rate.cpp
    residual/trunk/mixer/rate.h

Added Paths:
-----------
    residual/trunk/common/archive.cpp
    residual/trunk/common/archive.h
    residual/trunk/common/zlib.cpp
    residual/trunk/common/zlib.h
    residual/trunk/engine/backend/fs/abstract-fs.cpp
    residual/trunk/engine/backend/fs/posix/posix-fs.h
    residual/trunk/engine/backend/fs/stdiostream.cpp
    residual/trunk/engine/backend/fs/stdiostream.h
    residual/trunk/engine/backend/saves/posix/
    residual/trunk/engine/backend/saves/posix/posix-saves.cpp
    residual/trunk/engine/backend/saves/posix/posix-saves.h

Removed Paths:
-------------
    residual/trunk/engine/backend/saves/compressed/

Added: residual/trunk/common/archive.cpp
===================================================================
--- residual/trunk/common/archive.cpp	                        (rev 0)
+++ residual/trunk/common/archive.cpp	2009-04-05 14:48:54 UTC (rev 39864)
@@ -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


Property changes on: residual/trunk/common/archive.cpp
___________________________________________________________________
Added: svn:mime-type
   + text/plain
Added: svn:keywords
   + Date Revision Author URL Id
Added: svn:eol-style
   + native

Added: residual/trunk/common/archive.h
===================================================================
--- residual/trunk/common/archive.h	                        (rev 0)
+++ residual/trunk/common/archive.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -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<ArchiveMember> ArchiveMemberPtr;
+typedef List<ArchiveMemberPtr> 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<Node> 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<SearchManager>, 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<SingletonBaseType>;
+	SearchManager();
+};
+
+/** Shortcut for accessing the search manager. */
+#define SearchMan		Common::SearchManager::instance()
+
+} // namespace Common
+
+#endif


Property changes on: residual/trunk/common/archive.h
___________________________________________________________________
Added: svn:mime-type
   + text/plain
Added: svn:keywords
   + Date Revision Author URL Id
Added: svn:eol-style
   + native

Modified: residual/trunk/common/config-file.cpp
===================================================================
--- residual/trunk/common/config-file.cpp	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-file.cpp	2009-04-05 14:48:54 UTC (rev 39864)
@@ -60,7 +60,7 @@
 
 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 @@
 	SaveFileManager *saveFileMan = g_driver->getSavefileManager();
 	SeekableReadStream *loadFile;
 
+	assert(saveFileMan);
 	if (!(loadFile = saveFileMan->openForLoading(filename)))
 		return false;
 
@@ -79,7 +80,6 @@
 }
 
 bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
-	char buf[MAXLINELEN];
 	Section section;
 	KeyValue kv;
 	String comment;
@@ -88,22 +88,25 @@
 	// 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 @@
 			// 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 @@
 			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 @@
 				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::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 @@
 	SaveFileManager *saveFileMan = g_driver->getSavefileManager();
 	WriteStream *saveFile;
 
+	assert(saveFileMan);
 	if (!(saveFile = saveFileMan->openForSaving(filename)))
 		return false;
 
@@ -245,7 +257,7 @@
 void ConfigFile::removeSection(const String &section) {
 	assert(isValidName(section));
 	for (List<Section>::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 @@
 
 ConfigFile::Section *ConfigFile::getSection(const String &section) {
 	for (List<Section>::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 @@
 
 const ConfigFile::Section *ConfigFile::getSection(const String &section) const {
 	for (List<Section>::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 @@
 
 const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const {
 	for (List<KeyValue>::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 @@
 
 void ConfigFile::Section::setKey(const String &key, const String &value) {
 	for (List<KeyValue>::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::removeKey(const String &key) {
 	for (List<KeyValue>::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;
 		}

Modified: residual/trunk/common/config-file.h
===================================================================
--- residual/trunk/common/config-file.h	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-file.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -53,8 +53,6 @@
  */
 class ConfigFile {
 public:
-	typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet;
-
 	struct KeyValue {
 		String key;
 		String value;
@@ -120,7 +118,6 @@
 	const SectionList getSections() const { return _sections; }
 	const SectionKeyList getKeys(const String &section) const;
 
-	void listSections(StringSet &set);
 	void listKeyValues(StringMap &kv);
 
 private:

Modified: residual/trunk/common/config-manager.cpp
===================================================================
--- residual/trunk/common/config-manager.cpp	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-manager.cpp	2009-04-05 14:48:54 UTC (rev 39864)
@@ -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 @@
 
 
 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) {
-	_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) {
+	FSNode node(filename);
 	File cfg_file;
-
-	if (!cfg_file.open(filename)) {
+	if (!cfg_file.open(node)) {
 		printf("Creating configuration file: %s\n", filename.c_str());
 	} else {
-		String domain;
-		String comment;
-		int lineno = 0;
+		printf("Using configuration file: %s\n", _filename.c_str());
+		loadFromStream(cfg_file);
+	}
+}
 
-		// TODO: Detect if a domain occurs multiple times (or likewise, if
-		// a key occurs multiple times inside one domain).
+void ConfigManager::loadFromStream(SeekableReadStream &stream) {
+	String domain;
+	String comment;
+	int lineno = 0;
 
-		while (!cfg_file.eof() && !cfg_file.ioFailed()) {
-			lineno++;
+	_appDomain.clear();
+	_gameDomains.clear();
+	_transientDomain.clear();
+	_domainSaveOrder.clear();
 
-			// Read a line
-			String line;
+	// TODO: Detect if a domain occurs multiple times (or likewise, if
+	// a key occurs multiple times inside one domain).
+
+	while (!stream.eos() && !stream.ioFailed()) {
+		lineno++;
+
+		// Read a line
+		String line = stream.readLine();
+
+		if (line.size() == 0) {
+			// Do nothing
+		} else if (line[0] == '#') {
+			// Accumulate comments here. Once we encounter either the start
+			// of a new domain, or a key-value-pair, we associate the value
+			// of the 'comment' variable with that entity.
+			comment += line;
 #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++;
+				_gameDomains[domain].setDomainComment(comment);
+			}
+			comment.clear();
 
-				// Skip empty lines / lines with only whitespace
-				if (*t == 0)
-					continue;
+			_domainSaveOrder.push_back(domain);
+		} else {
+			// This line should be a line with a 'key=value' pair, or an empty one.
 
-				// 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);
-				}
+			// Skip leading whitespaces
+			const char *t = line.c_str();
+			while (isspace(*t))
+				t++;
 
-				// 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);
+			// Skip empty lines / lines with only whitespace
+			if (*t == 0)
+				continue;
 
-				// 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++;
+			// 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);
+			}
 
-				// Trim trailing spaces
-				p2 = t + strlen(t);
-				while (p2 > t && isspace(*(p2-1)))
-					p2--;
+			// 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);
 
-				String value(t, p2 - t);
+			// Extract the key/value pair
+			String key(t, p);
+			String value(p + 1);
 
-				// Finally, store the key/value pair in the active domain
-				set(key, value, domain);
+			// Trim of spaces
+			key.trim();
+			value.trim();
 
-				// Store comment
-				if (domain == kApplicationDomain) {
-					_appDomain.setKVComment(key, comment);
-				} else {
-					_gameDomains[domain].setKVComment(key, comment);
-				}
-				comment.clear();
+			// 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 @@
 	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 @@
 	// 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) {

Modified: residual/trunk/common/config-manager.h
===================================================================
--- residual/trunk/common/config-manager.h	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/config-manager.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -35,6 +35,7 @@
 namespace Common {
 
 class WriteStream;
+class SeekableReadStream;
 
 
 /**
@@ -143,19 +144,11 @@
 	bool				hasGameDomain(const String &domName) const;
 	const DomainMap &	getGameDomains() const { return _gameDomains; }
 
-/*
-	TODO: Callback/change notification system
-	typedef void (*ConfigCallback)(const ConstString &key, void *refCon);
-
-	void   registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString)
-	void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString)
-*/
-
 private:
 	friend class Singleton<SingletonBaseType>;
 	ConfigManager();
 
-	void			loadFile(const String &filename);
+	void			loadFromStream(SeekableReadStream &stream);
 	void			writeDomain(WriteStream &stream, const String &name, const Domain &domain);
 
 	Domain			_transientDomain;

Modified: residual/trunk/common/error.h
===================================================================
--- residual/trunk/common/error.h	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/error.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -26,22 +26,45 @@
 #ifndef COMMON_ERROR_H
 #define COMMON_ERROR_H
 
+namespace Common {
+
 /**
- * This file contains enums with error codes commonly used.
+ * This file contains an enum with commonly used error codes.
  */
 
+
+
 /**
- * Errors used in the SaveFileManager class.
+ * 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 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
+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

Modified: residual/trunk/common/file.cpp
===================================================================
--- residual/trunk/common/file.cpp	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/file.cpp	2009-04-05 14:48:54 UTC (rev 39864)
@@ -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 <errno.h>
 
-#if defined(MACOSX) || defined(IPHONE)
-#include "CoreFoundation/CoreFoundation.h"
-#endif
-
-#ifdef __PLAYSTATION2__
-	// for those replaced fopen/fread/etc functions
-	typedef unsigned long	uint64;
-	typedef signed long	int64;
-	#include "backends/platform/ps2/fileio.h"
-
-	#define fopen(a, b)			ps2_fopen(a, b)
-	#define fclose(a)			ps2_fclose(a)
-	#define fseek(a, b, c)			ps2_fseek(a, b, c)
-	#define ftell(a)			ps2_ftell(a)
-	#define feof(a)				ps2_feof(a)
-	#define fread(a, b, c, d)		ps2_fread(a, b, c, d)
-	#define fwrite(a, b, c, d)		ps2_fwrite(a, b, c, d)
-
-	//#define fprintf				ps2_fprintf	// used in common/util.cpp
-	//#define fflush(a)			ps2_fflush(a)	// used in common/util.cpp
-
-	//#define fgetc(a)			ps2_fgetc(a)	// not used
-	//#define fgets(a, b, c)			ps2_fgets(a, b, c)	// not used
-	//#define fputc(a, b)			ps2_fputc(a, b)	// not used
-	//#define fputs(a, b)			ps2_fputs(a, b)	// not used
-
-	//#define fsize(a)			ps2_fsize(a)	// not used -- and it is not a standard function either
-#endif
-
-#ifdef __DS__
-
-	// These functions replease the standard library functions of the same name.
-	// As this header is included after the standard one, I have the chance to #define
-	// all of these to my own code.
-	//
-	// A #define is the only way, as redefinig the functions would cause linker errors.
-
-	// These functions need to be #undef'ed, as their original definition
-	// in devkitarm is done with #includes (ugh!)
-	#undef feof
-	#undef clearerr
-	//#undef getc
-	//#undef ferror
-
-	#include "backends/fs/ds/ds-fs.h"
-
-
-	//void	std_fprintf(FILE* handle, const char* fmt, ...);	// used in common/util.cpp
-	//void	std_fflush(FILE* handle);	// used in common/util.cpp
-
-	//char*	std_fgets(char* str, int size, FILE* file);	// not used
-	//int	std_getc(FILE* handle);	// not used
-	//char*	std_getcwd(char* dir, int dunno);	// not used
-	//void	std_cwd(char* dir);	// not used
-	//int	std_ferror(FILE* handle);	// not used
-
-	// Only functions used in the ScummVM source have been defined here!
-	#define fopen(name, mode)					DS::std_fopen(name, mode)
-	#define fclose(handle)						DS::std_fclose(handle)
-	#define fread(ptr, size, items, file)		DS::std_fread(ptr, size, items, file)
-	#define fwrite(ptr, size, items, file)		DS::std_fwrite(ptr, size, items, file)
-	#define feof(handle)						DS::std_feof(handle)
-	#define ftell(handle)						DS::std_ftell(handle)
-	#define fseek(handle, offset, whence)		DS::std_fseek(handle, offset, whence)
-	#define clearerr(handle)					DS::std_clearerr(handle)
-
-	//#define printf(fmt, ...)					consolePrintf(fmt, ##__VA_ARGS__)
-
-	//#define fprintf(file, fmt, ...)				{ char str[128]; sprintf(str, fmt, ##__VA_ARGS__); DS::std_fwrite(str, strlen(str), 1, file); }
-	//#define fflush(file)						DS::std_fflush(file)	// used in common/util.cpp
-
-	//#define fgets(str, size, file)				DS::std_fgets(str, size, file)	// not used
-	//#define getc(handle)						DS::std_getc(handle)	// not used
-	//#define getcwd(dir, dunno)					DS::std_getcwd(dir, dunno)	// not used
-	//#define ferror(handle)						DS::std_ferror(handle)	// 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<String, int> StringIntMap;
-
-// The following two objects could be turned into static members of class
-// File. However, then we would be forced to #include hashmap in file.h
-// which seems to be a high price just for a simple beautification...
-static StringIntMap *_defaultDirectories;
-static StringMap *_filesMap;
-
-static FILE *fopenNoCase(const String &filename, const String &directory, const char *mode) {
-	FILE *file = 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 FSNode &dir) {
+	if (dir.exists() && dir.isDirectory())
+		SearchMan.addDirectory(dir.getPath(), dir);
 }
 
-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;
-}
-
 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");
+		warning("File::open: '%s' is a directory", node.getPath().c_str());
 		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());
 	}
 
+	SeekableReadStream *stream = node.createReadStream();
+	return open(stream, node.getPath());
+}
+
+bool File::open(SeekableReadStream *stream, const Common::String &name) {
+	assert(!_handle);
 	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());
-		return false;
+	if (stream) {
+		_handle = stream;
+		_name = name;
+	} else {
+		debug(2, "File::open: opening '%s' failed", name.c_str());
 	}
-
-	_name = filename;
-
-#ifdef DEBUG_FILE_REFCOUNT
-	warning("File::open on file '%s'", _name.c_str());
-#endif
-
-	return true;
+	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();
+bool File::exists(const String &filename) {
+	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::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;
-	}
+bool File::err() const {
+	assert(_handle);
+	return _handle->err();
+}
 
-	return feof((FILE *)_handle) != 0;
+void File::clearErr() {
+	assert(_handle);
+	_handle->clearErr();
 }
 
-uint32 File::pos() const {
-	if (_handle == NULL) {
-		error("File::pos: File is not open!");
-		return 0;
-	}
+bool File::eos() const {
+	assert(_handle);
+	return _handle->eos();
+}
 
-	return ftell((FILE *)_handle);
+int32 File::pos() const {
+	assert(_handle);
+	return _handle->pos();
 }
 
-uint32 File::size() const {
-	if (_handle == NULL) {
-		error("File::size: File is not open!");
-		return 0;
-	}
+int32 File::size() const {
+	assert(_handle);
+	return _handle->size();
+}
 
-	uint32 oldPos = ftell((FILE *)_handle);
-	fseek((FILE *)_handle, 0, SEEK_END);
-	uint32 length = ftell((FILE *)_handle);
-	fseek((FILE *)_handle, oldPos, SEEK_SET);
+bool File::seek(int32 offs, int whence) {
+	assert(_handle);
+	return _handle->seek(offs, whence);
+}
 
-	return length;
+uint32 File::read(void *ptr, uint32 len) {
+	assert(_handle);
+	return _handle->read(ptr, len);
 }
 
-void File::seek(int32 offs, int whence) {
-	if (_handle == NULL) {
-		error("File::seek: File is not open!");
-		return;
-	}
 
-	if (fseek((FILE *)_handle, offs, whence) != 0)
-		clearerr((FILE *)_handle);
+DumpFile::DumpFile() : _handle(0) {
 }
 
-uint32 File::read(void *ptr, uint32 len) {
-	byte *ptr2 = (byte *)ptr;
-	uint32 real_len;
+DumpFile::~DumpFile() {
+	close();
+}
 
-	if (_handle == NULL) {
-		error("File::read: File is not open!");
-		return 0;
-	}
+bool DumpFile::open(const String &filename) {
+	assert(!filename.empty());
+	assert(!_handle);
 
-	if (len == 0)
-		return 0;
+	FSNode node(filename);
+	return open(node);
+}
 
-	real_len = fread(ptr2, 1, len, (FILE *)_handle);
-	if (real_len < len) {
-		_ioFailed = true;
+bool DumpFile::open(const FSNode &node) {
+	assert(!_handle);
+
+	if (node.isDirectory()) {
+		warning("DumpFile::open: FSNode is a directory");
+		return false;
 	}
 
-	return real_len;
+	_handle = node.createWriteStream();
+
+	if (_handle == NULL)
+		debug(2, "File %s not found", node.getName().c_str());
+
+	return _handle != NULL;
 }
 
-uint32 File::write(const void *ptr, uint32 len) {
-	if (_handle == NULL) {
-		error("File::write: File is not open!");
-		return 0;
-	}
+void DumpFile::close() {
+	delete _handle;
+	_handle = NULL;
+}
 
-	if (len == 0)
-		return 0;
+bool DumpFile::isOpen() const {
+	return _handle != NULL;
+}
 
-	if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) {
-		_ioFailed = true;
-	}
+bool DumpFile::err() const {
+	assert(_handle);
+	return _handle->ioFailed();
+}
 
-	return len;
+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

Modified: residual/trunk/common/file.h
===================================================================
--- residual/trunk/common/file.h	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/file.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -27,46 +27,31 @@
 #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 FSNode &directory);
 
-	static void addDefaultDirectory(const FilesystemNode &directory);
-	static void addDefaultDirectoryRecursive(const FilesystemNode &directory, int level = 4, const String &prefix = "");
-
-	static void resetDefaultDirectories();
-
 	File();
 	virtual ~File();
 
@@ -75,14 +60,56 @@
 	 * (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 @@
 	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

Modified: residual/trunk/common/fs.cpp
===================================================================
--- residual/trunk/common/fs.cpp	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/fs.cpp	2009-04-05 14:48:54 UTC (rev 39864)
@@ -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<AbstractFilesystemNode>(tmp);
+	_realNode = Common::SharedPtr<AbstractFSNode>(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 @@
 
 	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;
+	}
 
-	FSList children;
-	FSList subdirs;
-	Common::String pattern = p;
+	return _realNode->createReadStream();
+}
 
-	pattern.toUppercase();
+Common::WriteStream *FSNode::createWriteStream() const {
+	if (_realNode == 0)
+		return 0;
 
-	// 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);
-		} else {
-			Common::String filename = entry->getName();
-			filename.toUppercase();
-			if (matchString(filename.c_str(), pattern.c_str())) {
-				results.push_back(*entry);
+	if (_realNode->isDirectory()) {
+		warning("FSNode::createWriteStream: FSNode is a directory");
+		return 0;
+	}
 
-				if (!exhaustive)
-					return true;	// Abort on first match if no exhaustive search was requested
+	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;
+
+	FSNode *node = lookupCache(_fileCache, name);
+	return node && node->exists();
+}
+
+ArchiveMemberPtr FSDirectory::getMember(const String &name) {
+	if (name.empty() || !_node.isDirectory())
+		return ArchiveMemberPtr();
+
+	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 {
+			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;
+}
 
-	return !results.empty();
+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 files;
 }
+
+
+}	// End of namespace Common

Modified: residual/trunk/common/fs.h
===================================================================
--- residual/trunk/common/fs.h	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/fs.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -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<FilesystemNode> {};
+class FSList : public Array<FSNode> {};
 
 /**
- * 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<AbstractFilesystemNode>	_realNode;
-	FilesystemNode(AbstractFilesystemNode *realNode);
+	SharedPtr<AbstractFSNode>	_realNode;
+	FSNode(AbstractFSNode *realNode);
 
 public:
 	/**
@@ -82,14 +72,14 @@
 	};
 
 	/**
-	 * 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 @@
 	 * 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 @@
 	 *
 	 * @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 @@
 	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.
+	 * @return pointer to the stream object, 0 in case of a failure
+	 */
+	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.
 	 *
-	 * @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 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<String, FSNode, IgnoreCase_Hash, IgnoreCase_EqualTo> 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

Modified: residual/trunk/common/savefile.h
===================================================================
--- residual/trunk/common/savefile.h	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/savefile.h	2009-04-05 14:48:54 UTC (rev 39864)
@@ -39,27 +39,14 @@
  * 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 @@
 class SaveFileManager : NonCopyable {
 
 protected:
-	SFMError _error;
+	Error _error;
 	String _errorDesc;
 
 	/**
@@ -86,7 +73,7 @@
 	 * @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 @@
 	/**
 	 * 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 @@
 
 	/**
 	 * 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;
 };

Modified: residual/trunk/common/stream.cpp
===================================================================
--- residual/trunk/common/stream.cpp	2009-04-05 13:15:33 UTC (rev 39863)
+++ residual/trunk/common/stream.cpp	2009-04-05 14:48:54 UTC (rev 39864)
@@ -43,8 +43,10 @@
 
 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 @@
 	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,94 +83,46 @@
 	}
 	// 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
+enum {
+	LF = 0x0A,
+	CR = 0x0D
+};
 
-char *SeekableReadStream::readLine(char *buf, size_t bufSize) {
-	assert(buf && bufSize > 0);
+char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
+	assert(buf != 0 && bufSize > 1);
 	char *p = buf;
 	size_t len = 0;
-	char c;
+	char c = 0;
 
-	if (buf == 0 || bufSize == 0 || eos()) {
+	// If end-of-file occurs before any characters are read, return NULL
+	// and the buffer contents remain unchanged.
+	if (eos() || err()) {
 		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.
+	// 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();
 
-	// EOF is treated as a line break, unless it was the first character
-	// that was read.
+		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;
 
-	// 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;
-}
-
-char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) {
-	assert(buf != 0 && bufSize > 1);
-	char *p = buf;
-	size_t len = 0;
-	char c = 0;
-
-	// If end-of-file occurs before any characters are read, return NULL
-	// and the buffer contents remain unchanged. 
-	if (eos() || ioFailed()) {
-		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) {
-		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 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 @@
 		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 @@
 	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 @@
 	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

Modified: residual/trunk/common/stream.h
===================================================================

@@ Diff output truncated at 100000 characters. @@

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.




More information about the Scummvm-git-logs mailing list