added filesystem common code from scummvm (part 1)

This commit is contained in:
Pawel Kolodziejski 2008-07-20 13:16:56 +00:00
parent 0c1da3ed3a
commit 8c7012c803
13 changed files with 2362 additions and 1 deletions

47
common/error.h Normal file
View File

@ -0,0 +1,47 @@
/* 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_ERROR_H
#define COMMON_ERROR_H
/**
* This file contains enums with error codes commonly used.
*/
/**
* Errors used in the SaveFileManager class.
*/
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
};
#endif //COMMON_ERROR_H

542
common/file.cpp Normal file
View File

@ -0,0 +1,542 @@
/* 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/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;
String dirBuf(directory);
String fileBuf(filename);
#if !defined(__GP32__) && !defined(PALMOS_MODE)
// Add a trailing slash, if necessary.
if (!dirBuf.empty()) {
const char c = dirBuf.lastChar();
if (c != ':' && c != '/' && c != '\\')
dirBuf += '/';
}
#endif
// Append the filename to the path string
String pathBuf(dirBuf);
pathBuf += fileBuf;
//
// Try to open the file normally
//
file = fopen(pathBuf.c_str(), mode);
//
// Try again, with file name converted to upper case
//
if (!file) {
fileBuf.toUppercase();
pathBuf = dirBuf + fileBuf;
file = fopen(pathBuf.c_str(), mode);
}
//
// Try again, with file name converted to lower case
//
if (!file) {
fileBuf.toLowercase();
pathBuf = dirBuf + fileBuf;
file = fopen(pathBuf.c_str(), mode);
}
//
// Try again, with file name capitalized
//
if (!file) {
fileBuf.toLowercase();
fileBuf.setChar(toupper(fileBuf[0]),0);
pathBuf = dirBuf + fileBuf;
file = fopen(pathBuf.c_str(), mode);
}
#ifdef __amigaos4__
//
// Work around for possibility that someone uses AmigaOS "newlib" build with SmartFileSystem (blocksize 512 bytes), leading
// to buffer size being only 512 bytes. "Clib2" sets the buffer size to 8KB, resulting smooth movie playback. This forces the buffer
// to be enough also when using "newlib" compile on SFS.
//
if (file) {
setvbuf(file, NULL, _IOFBF, 8192);
}
#endif
return file;
}
void File::addDefaultDirectory(const String &directory) {
FilesystemNode dir(directory);
addDefaultDirectoryRecursive(dir, 1);
}
void File::addDefaultDirectoryRecursive(const String &directory, int level, const String &prefix) {
FilesystemNode dir(directory);
addDefaultDirectoryRecursive(dir, level, prefix);
}
void File::addDefaultDirectory(const FilesystemNode &directory) {
addDefaultDirectoryRecursive(directory, 1);
}
void File::addDefaultDirectoryRecursive(const FilesystemNode &dir, int level, const String &prefix) {
if (level <= 0)
return;
FSList fslist;
if (!dir.getChildren(fslist, FilesystemNode::kListAll)) {
// Failed listing the contents of this node, so it is either not a
// directory, or just doesn't exist at all.
return;
}
if (!_defaultDirectories)
_defaultDirectories = new StringIntMap;
// Do not add directories multiple times, unless this time they are added
// with a bigger depth.
const String &directory(dir.getPath());
if (_defaultDirectories->contains(directory) && (*_defaultDirectories)[directory] >= level)
return;
(*_defaultDirectories)[directory] = level;
if (!_filesMap)
_filesMap = new StringMap;
for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
if (file->isDirectory()) {
addDefaultDirectoryRecursive(file->getPath(), level - 1, prefix + file->getName() + "/");
} else {
String lfn(prefix);
lfn += file->getName();
lfn.toLowercase();
if (!_filesMap->contains(lfn)) {
(*_filesMap)[lfn] = file->getPath();
}
}
}
}
void File::resetDefaultDirectories() {
delete _defaultDirectories;
delete _filesMap;
_defaultDirectories = 0;
_filesMap = 0;
}
File::File()
: _handle(0), _ioFailed(false) {
}
//#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, AccessMode mode) {
assert(mode == kFileReadMode || mode == kFileWriteMode);
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 + ".")) {
// 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
}
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;
}
bool File::open(const FilesystemNode &node, AccessMode mode) {
assert(mode == kFileReadMode || mode == kFileWriteMode);
if (!node.exists()) {
warning("File::open: Trying to open a FilesystemNode which does not exist");
return false;
} else if (node.isDirectory()) {
warning("File::open: Trying to open a FilesystemNode which is a directory");
return false;
} /*else if (!node.isReadable() && mode == kFileReadMode) {
warning("File::open: Trying to open an unreadable FilesystemNode object for reading");
return false;
} else if (!node.isWritable() && mode == kFileWriteMode) {
warning("File::open: Trying to open an unwritable FilesystemNode object for writing");
return false;
}*/
String filename(node.getName());
if (_handle) {
error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str());
}
clearIOFailed();
_name.clear();
const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb";
_handle = fopen(node.getPath().c_str(), modeStr);
if (_handle == NULL) {
/* if (mode == kFileReadMode)
debug(2, "File %s not found", filename.c_str());
else
debug(2, "File %s not opened", filename.c_str());*/
return false;
}
_name = filename;
#ifdef DEBUG_FILE_REFCOUNT
warning("File::open on file '%s'", _name.c_str());
#endif
return true;
}
bool File::exists(const String &filename) {
// First try to find the file via a FilesystemNode (in case an absolute
// path was passed). This is only used to filter out directories.
FilesystemNode file(filename);
if (file.exists())
return !file.isDirectory();
// See if the file is already mapped
if (_filesMap && _filesMap->contains(filename)) {
FilesystemNode file2((*_filesMap)[filename]);
if (file2.exists())
return !file2.isDirectory();
}
// Try all default directories
if (_defaultDirectories) {
StringIntMap::const_iterator i(_defaultDirectories->begin());
for (; i != _defaultDirectories->end(); ++i) {
FilesystemNode file2(i->_key + filename);
if(file2.exists())
return !file2.isDirectory();
}
}
//Try opening the file inside the local directory as a last resort
File tmp;
return tmp.open(filename, kFileReadMode);
}
void File::close() {
if (_handle)
fclose((FILE *)_handle);
_handle = NULL;
}
bool File::isOpen() const {
return _handle != NULL;
}
bool File::ioFailed() const {
return _ioFailed != 0;
}
void File::clearIOFailed() {
_ioFailed = false;
}
bool File::eof() const {
if (_handle == NULL) {
error("File::eof: File is not open!");
return false;
}
return feof((FILE *)_handle) != 0;
}
uint32 File::pos() const {
if (_handle == NULL) {
error("File::pos: File is not open!");
return 0;
}
return ftell((FILE *)_handle);
}
uint32 File::size() const {
if (_handle == NULL) {
error("File::size: File is not open!");
return 0;
}
uint32 oldPos = ftell((FILE *)_handle);
fseek((FILE *)_handle, 0, SEEK_END);
uint32 length = ftell((FILE *)_handle);
fseek((FILE *)_handle, oldPos, SEEK_SET);
return length;
}
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);
}
uint32 File::read(void *ptr, uint32 len) {
byte *ptr2 = (byte *)ptr;
uint32 real_len;
if (_handle == NULL) {
error("File::read: File is not open!");
return 0;
}
if (len == 0)
return 0;
real_len = fread(ptr2, 1, len, (FILE *)_handle);
if (real_len < len) {
_ioFailed = true;
}
return real_len;
}
uint32 File::write(const void *ptr, uint32 len) {
if (_handle == NULL) {
error("File::write: File is not open!");
return 0;
}
if (len == 0)
return 0;
if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) {
_ioFailed = true;
}
return len;
}
} // End of namespace Common

122
common/file.h Normal file
View File

@ -0,0 +1,122 @@
/* 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_FILE_H
#define COMMON_FILE_H
#include "common/sys.h"
#include "common/str.h"
#include "common/stream.h"
class FilesystemNode;
namespace Common {
class File : public SeekableReadStream, public WriteStream {
protected:
/** File handle to the actual file; 0 if no file is open. */
void *_handle;
/** Status flag which tells about recent I/O failures. */
bool _ioFailed;
/** The name of this file, for debugging. */
String _name;
private:
// Disallow copying File objects. There is not strict reason for this,
// except that so far we never had real need for such a feature, and
// code that accidentally copied File objects tended to break in strange
// ways.
File(const File &f);
File &operator =(const File &f);
public:
enum AccessMode {
kFileReadMode = 1,
kFileWriteMode = 2
};
static void addDefaultDirectory(const String &directory);
static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = "");
static void addDefaultDirectory(const FilesystemNode &directory);
static void addDefaultDirectoryRecursive(const FilesystemNode &directory, int level = 4, const String &prefix = "");
static void resetDefaultDirectories();
File();
virtual ~File();
/**
* Checks if a given file exists in any of the current default paths
* (those were/are added by addDefaultDirectory and/or
* addDefaultDirectoryRecursive).
*
* @param filename: the file to check for
* @return: true if the file exists, else false
*/
static bool exists(const String &filename);
virtual bool open(const String &filename, AccessMode mode = kFileReadMode);
virtual bool open(const FilesystemNode &node, AccessMode mode = kFileReadMode);
virtual void close();
/**
* Checks if the object opened a file successfully.
*
* @return: true if any file is opened, false otherwise.
*/
bool isOpen() const;
/**
* Returns the filename of the opened file.
*
* @return: the filename
*/
const char *name() const { return _name.c_str(); }
bool ioFailed() const;
void clearIOFailed();
bool eos() const { return eof(); }
/**
* Checks for end of file.
*
* @return: true if the end of file is reached, false otherwise.
*/
virtual bool eof() const;
virtual uint32 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);
};
} // End of namespace Common
#endif

205
common/fs.cpp Normal file
View File

@ -0,0 +1,205 @@
/* 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/util.h"
#include "engine/backend/driver.h"
#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;
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++;
}
}
}
FilesystemNode::FilesystemNode() {
}
FilesystemNode::FilesystemNode(AbstractFilesystemNode *realNode)
: _realNode(realNode) {
}
FilesystemNode::FilesystemNode(const Common::String &p) {
FilesystemFactory *factory = g_driver->getFilesystemFactory();
AbstractFilesystemNode *tmp = 0;
if (p.empty() || p == ".")
tmp = factory->makeCurrentDirectoryFileNode();
else
tmp = factory->makeFileNodePath(p);
_realNode = Common::SharedPtr<AbstractFilesystemNode>(tmp);
}
bool FilesystemNode::operator<(const FilesystemNode& node) const {
if (isDirectory() != node.isDirectory())
return isDirectory();
return strcasecmp(getDisplayName().c_str(), node.getDisplayName().c_str()) < 0;
}
bool FilesystemNode::exists() const {
if (_realNode == 0)
return false;
return _realNode->exists();
}
FilesystemNode FilesystemNode::getChild(const Common::String &n) const {
if (_realNode == 0)
return *this;
assert(_realNode->isDirectory());
AbstractFilesystemNode *node = _realNode->getChild(n);
return FilesystemNode(node);
}
bool FilesystemNode::getChildren(FSList &fslist, ListMode mode, bool hidden) const {
if (!_realNode || !_realNode->isDirectory())
return false;
AbstractFSList tmp;
if (!_realNode->getChildren(tmp, mode, hidden))
return false;
fslist.clear();
for (AbstractFSList::iterator i = tmp.begin(); i != tmp.end(); ++i) {
fslist.push_back(FilesystemNode(*i));
}
return true;
}
Common::String FilesystemNode::getDisplayName() const {
assert(_realNode);
return _realNode->getDisplayName();
}
Common::String FilesystemNode::getName() const {
assert(_realNode);
return _realNode->getName();
}
FilesystemNode FilesystemNode::getParent() const {
if (_realNode == 0)
return *this;
AbstractFilesystemNode *node = _realNode->getParent();
if (node == 0) {
return *this;
} else {
return FilesystemNode(node);
}
}
Common::String FilesystemNode::getPath() const {
assert(_realNode);
return _realNode->getPath();
}
bool FilesystemNode::isDirectory() const {
if (_realNode == 0)
return false;
return _realNode->isDirectory();
}
bool FilesystemNode::isReadable() const {
if (_realNode == 0)
return false;
return _realNode->isReadable();
}
bool FilesystemNode::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())
return false;
FSList children;
FSList subdirs;
Common::String pattern = p;
pattern.toUppercase();
// 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 (!exhaustive)
return true; // Abort on first match if no exhaustive search was requested
}
}
}
// 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
}
return !results.empty();
}

228
common/fs.h Normal file
View File

@ -0,0 +1,228 @@
/* 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_FS_H
#define COMMON_FS_H
#include "common/array.h"
#include "common/ptr.h"
#include "common/str.h"
//namespace Common {
class FilesystemNode;
class AbstractFilesystemNode;
/**
* 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> {};
/**
* 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:, ...).
*
* To this end, we abstract away from paths; implementations can be based on
* paths (and it's left to them whether / or \ or : is the path separator :-);
* but it is also possible to use inodes or vrefs (MacOS 9) or anything else.
*
* NOTE: Backends still have to provide a way to extract a path from a FSIntern
*
* You may ask now: "isn't this cheating? Why do we go through all this when we use
* a path in the end anyway?!?".
* Well, for once as long as we don't provide our own file open/read/write API, we
* still have to use fopen(). Since all our targets already support fopen(), it should
* be possible to get a fopen() compatible string for any file system node.
*
* Secondly, with this abstraction layer, we still avoid a lot of complications based on
* differences in FS roots, different path separators, or even systems with no real
* paths (MacOS 9 doesn't even have the notion of a "current directory").
* And if we ever want to support devices with no FS in the classical sense (Palm...),
* we can build upon this.
*
* This class acts as a wrapper around the AbstractFilesystemNode class defined in backends/fs.
*/
class FilesystemNode {
private:
Common::SharedPtr<AbstractFilesystemNode> _realNode;
FilesystemNode(AbstractFilesystemNode *realNode);
public:
/**
* Flag to tell listDir() which kind of files to list.
*/
enum ListMode {
kListFilesOnly = 1,
kListDirectoriesOnly = 2,
kListAll = 3
};
/**
* Create a new pathless FilesystemNode. 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();
/**
* Create a new FilesystemNode 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
* directory" will be created. If that is not possible (since e.g. the
* operating system doesn't support the concept), some other directory is
* used (usually the root directory).
*/
explicit FilesystemNode(const Common::String &path);
virtual ~FilesystemNode() {}
/**
* Compare the name of this node to the name of another. Directories
* go before normal files.
*/
bool operator<(const FilesystemNode& node) const;
/**
* Indicates whether the object referred by this path exists in the filesystem or not.
*
* @return bool true if the path 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.
*/
FilesystemNode getChild(const Common::String &name) const;
/**
* Return a list of 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).
*/
virtual bool getChildren(FSList &fslist, ListMode mode = kListDirectoriesOnly, bool hidden = false) const;
/**
* Return a human readable string for this node, usable for display (e.g.
* in the GUI code). Do *not* rely on it being usable for anything else,
* like constructing paths!
*
* @return the display name
*/
virtual Common::String getDisplayName() const;
/**
* Return a string representation of the name of the file. This is 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;
/**
* 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.
*
* @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;
/**
* 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;
/**
* Indicates whether the path 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).
* That might actually be OK... but we could still add an isFile method.
* Or even replace isDirectory by a getType() method that can return values like
* kDirNodeType, kFileNodeType, kInvalidNodeType.
*/
virtual bool isDirectory() const;
/**
* Indicates whether the object referred by this path can be read from or not.
*
* If the path 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
* contents of the file.
*
* @return bool 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.
*
* If the path 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
* to the file.
*
* @return bool 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.
*
* The files in each directory are scanned first. Other than that, a depth first search
* is performed.
*
* @param results List to put the matches in.
* @param pattern Pattern of the files to look for.
* @param hidden Whether to search hidden files or not.
* @param exhaustive Whether to continue searching after one match has been found.
* @param depth How many levels to search through (-1 = search all subdirs, 0 = only the current one)
*
* @return true if matches could be found, false otherwise.
*/
virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const;
};
//} // End of namespace Common
#endif //COMMON_FS_H

View File

@ -2,11 +2,14 @@ MODULE := common
MODULE_OBJS := \
debug.o \
file.o \
fs.o \
hashmap.o \
matrix3.o \
matrix4.o \
memorypool.o \
str.o \
stream.o \
mutex.o
# Include common rules

215
common/ptr.h Normal file
View File

@ -0,0 +1,215 @@
/* 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_PTR_H
#define COMMON_PTR_H
#include "common/sys.h"
namespace Common {
class SharedPtrDeletionInternal {
public:
virtual ~SharedPtrDeletionInternal() {}
};
template<class T>
class SharedPtrDeletionImpl : public SharedPtrDeletionInternal {
public:
SharedPtrDeletionImpl(T *ptr) : _ptr(ptr) {}
~SharedPtrDeletionImpl() {
// Checks if the supplied type is not just a plain
// forward definition, taken from boost::checked_delete
// This makes the user really aware what he tries to do
// when using this with an incomplete type.
typedef char completeCheck[sizeof(T) ? 1 : -1];
(void)sizeof(completeCheck);
delete _ptr;
}
private:
T *_ptr;
};
template<class T, class D>
class SharedPtrDeletionDeleterImpl : public SharedPtrDeletionInternal {
public:
SharedPtrDeletionDeleterImpl(T *ptr, D d) : _ptr(ptr), _deleter(d) {}
~SharedPtrDeletionDeleterImpl() { _deleter(_ptr); }
private:
T *_ptr;
D _deleter;
};
/**
* A simple shared pointer implementation modelled after boost.
*
* This object keeps track of the assigned pointer and automatically
* frees it when no more SharedPtr references to it exist.
*
* To achieve that the object implements an internal reference counting.
* Thus you should try to avoid using the plain pointer after assigning
* it to a SharedPtr object for the first time. If you still use the
* plain pointer be sure you do not delete it on your own. You may also
* not use the plain pointer to create a new SharedPtr object, since that
* would result in a double deletion of the pointer sooner or later.
*
* Example creation:
* Common::SharedPtr<int> pointer(new int(1));
* would create a pointer to int. Later on usage via *pointer is the same
* as for a normal pointer. If you need to access the plain pointer value
* itself later on use the get method. The class also supplies a operator
* ->, which does the same as the -> operator on a normal pointer.
*
* Be sure you are using new to initialize the pointer you want to manage.
* If you do not use new for allocating, you have to supply a deleter as
* second parameter when creating a SharedPtr object. The deleter has to
* implement operator() which takes the pointer it should free as argument.
*
* Note that you have to specify the type itself not the pointer type as
* template parameter.
*
* When creating a SharedPtr object from a normal pointer you need a real
* definition of the type you want SharedPtr to manage, a simple forward
* definition is not enough.
*
* The class has implicit upcast support, so if you got a class B derived
* from class A, you can assign a pointer to B without any problems to a
* SharedPtr object with template parameter A. The very same applies to
* assignment of a SharedPtr<B> object to a SharedPtr<A> object.
*
* There are also operators != and == to compare two SharedPtr objects
* with compatible pointers. Comparison between a SharedPtr object and
* a plain pointer is only possible via SharedPtr::get.
*/
template<class T>
class SharedPtr {
#if !((__GNUC__ == 2) && (__GNUC_MINOR__ >= 95))
template<class T2> friend class SharedPtr;
#endif
public:
typedef int RefValue;
typedef T ValueType;
typedef T *Pointer;
SharedPtr() : _refCount(0), _deletion(0), _pointer(0) {}
template<class T2> explicit SharedPtr(T2 *p) : _refCount(new RefValue(1)), _deletion(new SharedPtrDeletionImpl<T2>(p)), _pointer(p) {}
template<class T2, class D> SharedPtr(T2 *p, D d) : _refCount(new RefValue(1)), _deletion(new SharedPtrDeletionDeleterImpl<T2, D>(p, d)), _pointer(p) {}
SharedPtr(const SharedPtr &r) : _refCount(r._refCount), _deletion(r._deletion), _pointer(r._pointer) { if (_refCount) ++(*_refCount); }
template<class T2> SharedPtr(const SharedPtr<T2> &r) : _refCount(r._refCount), _deletion(r._deletion), _pointer(r._pointer) { if (_refCount) ++(*_refCount); }
~SharedPtr() { decRef(); }
SharedPtr &operator =(const SharedPtr &r) {
if (r._refCount)
++(*r._refCount);
decRef();
_refCount = r._refCount;
_deletion = r._deletion;
_pointer = r._pointer;
return *this;
}
template<class T2>
SharedPtr &operator =(const SharedPtr<T2> &r) {
if (r._refCount)
++(*r._refCount);
decRef();
_refCount = r._refCount;
_deletion = r._deletion;
_pointer = r._pointer;
return *this;
}
ValueType &operator *() const { assert(_pointer); return *_pointer; }
Pointer operator ->() const { assert(_pointer); return _pointer; }
/**
* Returns the plain pointer value. Be sure you know what you
* do if you are continuing to use that pointer.
*
* @return the pointer the SharedPtr object manages
*/
Pointer get() const { return _pointer; }
/**
* Implicit conversion operator to bool for convenience, to make
* checks like "if (sharedPtr) ..." possible.
*/
operator bool() const { return _pointer != 0; }
/**
* Checks if the SharedPtr object is the only object refering
* to the assigned pointer. This should just be used for
* debugging purposes.
*/
bool unique() const { return refCount() == 1; }
/**
* Returns the number of references to the assigned pointer.
* This should just be used for debugging purposes.
*/
RefValue refCount() const { return _refCount ? *_refCount : 0; }
#if !((__GNUC__ == 2) && (__GNUC_MINOR__ >= 95))
private:
#endif
void decRef() {
if (_refCount) {
--(*_refCount);
if (!*_refCount) {
delete _refCount;
delete _deletion;
_deletion = 0;
_refCount = 0;
_pointer = 0;
}
}
}
RefValue *_refCount;
SharedPtrDeletionInternal *_deletion;
T *_pointer;
};
} // end of namespace Common
template<class T1, class T2>
bool operator ==(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) {
return l.get() == r.get();
}
template<class T1, class T2>
bool operator !=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) {
return l.get() != r.get();
}
#endif

163
common/savefile.h Normal file
View File

@ -0,0 +1,163 @@
/* 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_SAVEFILE_H
#define COMMON_SAVEFILE_H
#include "common/noncopyable.h"
#include "common/sys.h"
#include "common/stream.h"
#include "common/str.h"
#include "common/error.h"
namespace Common {
/**
* A class which allows game engines to load game state data.
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
class InSaveFile : public SeekableReadStream {};
/**
* 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();
}
};
/**
* The SaveFileManager is serving as a factory for InSaveFile
* and OutSaveFile objects.
*
* Engines and other code should use SaveFiles whenever they need to
* store data which they need to be able to retrieve again later on --
* i.e. typically save states, but also configuration files and similar
* things.
*
* While not declared as a singleton, it is effectively used as such,
* with OSystem::getSavefileManager returning a pointer to the single
* SaveFileManager instances to be used.
*/
class SaveFileManager : NonCopyable {
protected:
SFMError _error;
String _errorDesc;
/**
* Set some information about the last error which occurred .
* @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; }
public:
virtual ~SaveFileManager() {}
/**
* Clears the last set error code and string.
*/
virtual void clearError() { _error = SFM_NO_ERROR; _errorDesc = ""; }
/**
* Returns the last occurred error code. If none occurred, returns SFM_NO_ERROR.
*
* @return A SFMError indicating the type of the last error.
*/
virtual SFMError getError() { return _error; }
/**
* Returns the last occurred error description. If none occurred, returns 0.
*
* @return A string describing the last error.
*/
virtual String getErrorDesc() { return _errorDesc; }
/**
* Returns the last occurred error description. If none occurred, returns 0.
* Also clears the last error state and description.
*
* @return A string describing the last error.
*/
virtual String popErrorDesc();
/**
* Open the savefile with the specified name in the given directory for saving.
* @param name the name of the savefile
* @return pointer to an OutSaveFile, or NULL if an error occured.
*/
virtual OutSaveFile *openForSaving(const char *name) = 0;
/**
* Open the file with the specified name in the given directory for loading.
* @param name the name of the savefile
* @return pointer to an InSaveFile, or NULL if an error occured.
*/
virtual InSaveFile *openForLoading(const char *name) = 0;
/**
* Removes the given savefile from the system.
* @param name the name of the savefile to be removed.
* @return true if no error occurred, false otherwise.
*/
virtual bool removeSavefile(const char *name) = 0;
/**
* Renames the given savefile.
* @param oldName Old name.
* @param newName New name.
* @return true if no error occurred. false otherwise.
*/
virtual bool renameSavefile(const char *oldName, const char *newName);
/**
* 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()
* 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
*/
virtual Common::StringList listSavefiles(const char *pattern) = 0;
};
} // End of namespace Common
#endif

98
common/singleton.h Normal file
View File

@ -0,0 +1,98 @@
/* 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_SINGLETON_H
#define COMMON_SINGLETON_H
#include "common/noncopyable.h"
namespace Common {
/**
* Generic template base class for implementing the singleton design pattern.
*/
template<class T>
class Singleton : NonCopyable {
private:
Singleton<T>(const Singleton<T> &);
Singleton<T> &operator=(const Singleton<T> &);
static T *_singleton;
/**
* The default object factory used by the template class Singleton.
* By specialising this template function, one can make a singleton use a
* custom object factory. For example, to support encapsulation, your
* singleton class might be pure virtual (or "abstract" in Java terminology),
* and you specialise makeInstance to return an instance of a subclass.
*/
//template <class T>
#if defined (_WIN32_WCE) || defined (_MSC_VER) || defined (__WINS__)
//FIXME evc4 and msvc7 doesn't like it as private member
public:
#endif
static T *makeInstance() {
return new T();
}
static void destroyInstance() {
delete _singleton;
_singleton = 0;
}
public:
static T& instance() {
// TODO: We aren't thread safe. For now we ignore it since the
// only thing using this singleton template is the config manager,
// and that is first instantiated long before any threads.
// TODO: We don't leak, but the destruction order is nevertheless
// semi-random. If we use multiple singletons, the destruction
// order might become an issue. There are various approaches
// to solve that problem, but for now this is sufficient
if (!_singleton)
_singleton = T::makeInstance();
return *_singleton;
}
static void destroy() {
T::destroyInstance();
}
protected:
Singleton<T>() { }
#ifdef __SYMBIAN32__
virtual ~Singleton() { }
#else
virtual ~Singleton<T>() { }
#endif
typedef T SingletonBaseType;
};
#define DECLARE_SINGLETON(T) template<> T *Common::Singleton<T>::_singleton = 0
} // End of namespace Common
#endif

190
common/stream.cpp Normal file
View File

@ -0,0 +1,190 @@
/* 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/stream.h"
#include "common/str.h"
#include "common/util.h"
namespace Common {
void WriteStream::writeString(const String &str) {
write(str.c_str(), str.size());
}
MemoryReadStream *ReadStream::readStream(uint32 dataSize) {
void *buf = malloc(dataSize);
dataSize = read(buf, dataSize);
assert(dataSize > 0);
return new MemoryReadStream((byte *)buf, dataSize, true);
}
uint32 MemoryReadStream::read(void *dataPtr, uint32 dataSize) {
// Read at most as many bytes as are still available...
if (dataSize > _size - _pos)
dataSize = _size - _pos;
memcpy(dataPtr, _ptr, dataSize);
if (_encbyte) {
byte *p = (byte *)dataPtr;
byte *end = p + dataSize;
while (p < end)
*p++ ^= _encbyte;
}
_ptr += dataSize;
_pos += dataSize;
return dataSize;
}
void 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;
// Fall through
case SEEK_SET:
_ptr = _ptrOrig + offs;
_pos = offs;
break;
case SEEK_CUR:
_ptr += offs;
_pos += offs;
break;
}
// Post-Condition
assert(_pos <= _size);
}
#define LF 0x0A
#define CR 0x0D
char *SeekableReadStream::readLine(char *buf, size_t bufSize) {
assert(buf && bufSize > 0);
char *p = buf;
size_t len = 0;
char c;
if (buf == 0 || bufSize == 0 || eos()) {
return 0;
}
// We don't include the newline character(s) in the buffer, and we
// always terminate it - we never read more than len-1 characters.
// EOF is treated as a line break, unless it was the first character
// that was read.
// 0 is treated as a line break, even though it should never occur in
// a text file.
// DOS and Windows use CRLF line breaks
// Unix and OS X use LF line breaks
// Macintosh before OS X uses CR line breaks
c = readByte();
if (eos() || ioFailed()) {
return 0;
}
while (!eos() && len + 1 < bufSize) {
if (ioFailed())
return 0;
if (c == 0 || c == LF)
break;
if (c == CR) {
c = readByte();
if (c != LF && !eos())
seek(-1, SEEK_CUR);
break;
}
*p++ = c;
len++;
c = readByte();
}
// This should fix a bug while using readLine with Common::File
// it seems that it sets the eos flag after an invalid read
// and at the same time the ioFailed flag
// the config file parser fails out of that reason for the new themes
if (eos()) {
clearIOFailed();
}
*p = 0;
return buf;
}
uint32 SubReadStream::read(void *dataPtr, uint32 dataSize) {
dataSize = MIN(dataSize, _end - _pos);
dataSize = _parentStream->read(dataPtr, dataSize);
_pos += dataSize;
return dataSize;
}
SeekableSubReadStream::SeekableSubReadStream(SeekableReadStream *parentStream, uint32 begin, uint32 end, bool disposeParentStream)
: SubReadStream(parentStream, end, disposeParentStream),
_parentStream(parentStream),
_begin(begin) {
assert(_begin <= _end);
_pos = _begin;
_parentStream->seek(_pos);
}
void SeekableSubReadStream::seek(int32 offset, int whence) {
assert(_pos >= _begin);
assert(_pos <= _end);
switch(whence) {
case SEEK_END:
offset = size() - offset;
// fallthrough
case SEEK_SET:
_pos = _begin + offset;
break;
case SEEK_CUR:
_pos += offset;
}
assert(_pos >= _begin);
assert(_pos <= _end);
_parentStream->seek(_pos);
}
} // End of namespace Common

548
common/stream.h Normal file
View File

@ -0,0 +1,548 @@
/* 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_STREAM_H
#define COMMON_STREAM_H
#include "common/sys.h"
namespace Common {
class String;
class MemoryReadStream;
/**
* Virtual base class for both ReadStream and WriteStream.
*/
class Stream {
public:
virtual ~Stream() {}
/**
* Returns true if any I/O failure occurred.
* This flag is never cleared automatically. In order to clear it,
* client code has to call clearIOFailed() explicitly.
*
* @todo Instead of returning a plain bool, maybe we should define
* a list of error codes which can be returned here.
*/
virtual bool ioFailed() const { return false; }
/**
* Reset the I/O error status.
*/
virtual void clearIOFailed() {}
};
/**
* Generic interface for a writable data stream.
*/
class WriteStream : virtual public Stream {
public:
/**
* Write data into the stream. Subclasses must implement this
* method; all other write methods are implemented using it.
*
* @param dataPtr pointer to the data to be written
* @param dataSize number of bytes to be written
* @return the number of bytes which were actually written.
*/
virtual uint32 write(const void *dataPtr, uint32 dataSize) = 0;
/**
* Commit any buffered data to the underlying channel or
* storage medium; unbuffered streams can use the default
* implementation.
*/
virtual void flush() {}
// The remaining methods all have default implementations; subclasses
// need not (and should not) overload them.
void writeByte(byte value) {
write(&value, 1);
}
void writeSByte(int8 value) {
write(&value, 1);
}
void writeUint16LE(uint16 value) {
writeByte((byte)(value & 0xff));
writeByte((byte)(value >> 8));
}
void writeUint32LE(uint32 value) {
writeUint16LE((uint16)(value & 0xffff));
writeUint16LE((uint16)(value >> 16));
}
void writeUint16BE(uint16 value) {
writeByte((byte)(value >> 8));
writeByte((byte)(value & 0xff));
}
void writeUint32BE(uint32 value) {
writeUint16BE((uint16)(value >> 16));
writeUint16BE((uint16)(value & 0xffff));
}
void writeSint16LE(int16 value) {
writeUint16LE((uint16)value);
}
void writeSint32LE(int32 value) {
writeUint32LE((uint32)value);
}
void writeSint16BE(int16 value) {
writeUint16BE((uint16)value);
}
void writeSint32BE(int32 value) {
writeUint32BE((uint32)value);
}
void writeString(const String &str);
};
/**
* Generic interface for a readable data stream.
*/
class ReadStream : virtual public Stream {
public:
/**
* Returns true if the end of the stream has been reached.
*/
virtual bool eos() const = 0;
/**
* Read data from the stream. Subclasses must implement this
* method; all other read methods are implemented using it.
*
* @param dataPtr pointer to a buffer into which the data is read
* @param dataSize number of bytes to be read
* @return the number of bytes which were actually read.
*/
virtual uint32 read(void *dataPtr, uint32 dataSize) = 0;
// The remaining methods all have default implementations; subclasses
// need not (and should not) overload them.
/**
* Read am unsigned byte from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
byte readByte() {
byte b = 0;
read(&b, 1);
return b;
}
/**
* Read a signed byte from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
int8 readSByte() {
int8 b = 0;
read(&b, 1);
return b;
}
/**
* Read an unsigned 16-bit word stored in little endian (LSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
uint16 readUint16LE() {
uint16 a = readByte();
uint16 b = readByte();
return a | (b << 8);
}
/**
* Read an unsigned 32-bit word stored in little endian (LSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
uint32 readUint32LE() {
uint32 a = readUint16LE();
uint32 b = readUint16LE();
return (b << 16) | a;
}
/**
* Read an unsigned 16-bit word stored in big endian (MSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
uint16 readUint16BE() {
uint16 b = readByte();
uint16 a = readByte();
return a | (b << 8);
}
/**
* Read an unsigned 32-bit word stored in big endian (MSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
uint32 readUint32BE() {
uint32 b = readUint16BE();
uint32 a = readUint16BE();
return (b << 16) | a;
}
/**
* Read a signed 16-bit word stored in little endian (LSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
int16 readSint16LE() {
return (int16)readUint16LE();
}
/**
* Read a signed 32-bit word stored in little endian (LSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
int32 readSint32LE() {
return (int32)readUint32LE();
}
/**
* Read a signed 16-bit word stored in big endian (MSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
int16 readSint16BE() {
return (int16)readUint16BE();
}
/**
* Read a signed 32-bit word stored in big endian (MSB first) order
* from the stream and return it.
* Performs no error checking. The return value is undefined
* if a read error occurred (for which client code can check by
* calling ioFailed()).
*/
int32 readSint32BE() {
return (int32)readUint32BE();
}
/**
* Read the specified amount of data into a malloc'ed buffer
* which then is wrapped into a MemoryReadStream.
* The returned stream might contain less data than requested,
* if reading more failed.
*/
MemoryReadStream *readStream(uint32 dataSize);
};
/**
* Interface for a seekable & readable data stream.
*
* @todo We really need better error handling here!
* Like seek should somehow indicate whether it failed.
*/
class SeekableReadStream : virtual public ReadStream {
public:
virtual uint32 pos() const = 0;
virtual uint32 size() const = 0;
virtual void seek(int32 offset, int whence = SEEK_SET) = 0;
void skip(uint32 offset) { seek(offset, SEEK_CUR); }
/**
* Read one line of text from a CR or CR/LF terminated plain text file.
* This method is a rough analog of the (f)gets function.
*
* @param buf the buffer to store into
* @param bufSize the size of the buffer
* @return a pointer to the read string, or NULL if an error occurred
* @note The line terminator (CR or CR/LF) is stripped and not inserted
* into the buffer.
*/
virtual char *readLine(char *buf, size_t bufSize);
};
/**
* SubReadStream provides access to a ReadStream restricted to the range
* [currentPosition, currentPosition+end).
* Manipulating the parent stream directly /will/ mess up a substream.
* Likewise, manipulating two substreams of a parent stream will cause them to
* step on each others toes.
*/
class SubReadStream : virtual public ReadStream {
protected:
ReadStream *_parentStream;
uint32 _pos;
uint32 _end;
bool _disposeParentStream;
public:
SubReadStream(ReadStream *parentStream, uint32 end, bool disposeParentStream = false)
: _parentStream(parentStream),
_pos(0),
_end(end),
_disposeParentStream(disposeParentStream) {}
~SubReadStream() {
if (_disposeParentStream) delete _parentStream;
}
virtual bool eos() const { return _pos == _end; }
virtual uint32 read(void *dataPtr, uint32 dataSize);
};
/*
* SeekableSubReadStream provides access to a SeekableReadStream restricted to
* the range [begin, end).
* The same caveats apply to SeekableSubReadStream as do to SeekableReadStream.
*/
class SeekableSubReadStream : public SubReadStream, public SeekableReadStream {
protected:
SeekableReadStream *_parentStream;
uint32 _begin;
public:
SeekableSubReadStream(SeekableReadStream *parentStream, uint32 begin, uint32 end, bool disposeParentStream = false);
virtual uint32 pos() const { return _pos - _begin; }
virtual uint32 size() const { return _end - _begin; }
virtual void seek(int32 offset, int whence = SEEK_SET);
};
/**
* This is a wrapper around SeekableSubReadStream, but it adds non-endian
* read methods whose endianness is set on the stream creation.
*/
class SeekableSubReadStreamEndian : public SeekableSubReadStream {
public:
bool _bigEndian;
SeekableSubReadStreamEndian(SeekableReadStream *parentStream, uint32 begin, uint32 end, bool bigEndian = false, bool disposeParentStream = false)
: SeekableSubReadStream(parentStream, begin, end, disposeParentStream), _bigEndian(bigEndian) {
}
inline uint16 readUint16() {
return (_bigEndian) ? readUint16BE() : readUint16LE();
}
inline uint32 readUint32() {
return (_bigEndian) ? readUint32BE() : readUint32LE();
}
inline int16 readSint16() {
return (int16)readUint16();
}
inline int32 readSint32() {
return (int32)readUint32();
}
};
/**
* Simple memory based 'stream', which implements the ReadStream interface for
* a plain memory block.
*/
class MemoryReadStream : public SeekableReadStream {
private:
const byte * const _ptrOrig;
const byte *_ptr;
const uint32 _size;
uint32 _pos;
byte _encbyte;
bool _disposeMemory;
public:
/**
* This constructor takes a pointer to a memory buffer and a length, and
* wraps it. If disposeMemory is true, the MemoryReadStream takes ownership
* of the buffer and hence free's it when destructed.
*/
MemoryReadStream(const byte *dataPtr, uint32 dataSize, bool disposeMemory = false) :
_ptrOrig(dataPtr),
_ptr(dataPtr),
_size(dataSize),
_pos(0),
_encbyte(0),
_disposeMemory(disposeMemory) {}
~MemoryReadStream() {
if (_disposeMemory)
free(const_cast<byte *>(_ptrOrig));
}
void setEnc(byte value) { _encbyte = value; }
uint32 read(void *dataPtr, uint32 dataSize);
bool eos() const { return _pos == _size; }
uint32 pos() const { return _pos; }
uint32 size() const { return _size; }
void seek(int32 offs, int whence = SEEK_SET);
};
/**
* This is a wrapper around MemoryReadStream, but it adds non-endian
* read methods whose endianness is set on the stream creation.
*/
class MemoryReadStreamEndian : public Common::MemoryReadStream {
private:
public:
bool _bigEndian;
MemoryReadStreamEndian(const byte *buf, uint32 len, bool bigEndian = false) : MemoryReadStream(buf, len), _bigEndian(bigEndian) {}
inline uint16 readUint16() {
return (_bigEndian) ? readUint16BE() : readUint16LE();
}
inline uint32 readUint32() {
return (_bigEndian) ? readUint32BE() : readUint32LE();
}
inline int16 readSint16() {
return (int16)readUint16();
}
inline int32 readSint32() {
return (int32)readUint32();
}
};
/**
* Simple memory based 'stream', which implements the WriteStream interface for
* a plain memory block.
*/
class MemoryWriteStream : public WriteStream {
private:
byte *_ptr;
const uint32 _bufSize;
uint32 _pos;
public:
MemoryWriteStream(byte *buf, uint32 len) : _ptr(buf), _bufSize(len), _pos(0) {}
uint32 write(const void *dataPtr, uint32 dataSize) {
// Write at most as many bytes as are still available...
if (dataSize > _bufSize - _pos)
dataSize = _bufSize - _pos;
memcpy(_ptr, dataPtr, dataSize);
_ptr += dataSize;
_pos += dataSize;
return dataSize;
}
bool eos() const { return _pos == _bufSize; }
uint32 pos() const { return _pos; }
uint32 size() const { return _bufSize; }
};
/**
* A sort of hybrid between MemoryWriteStream and Array classes. A stream
* that grows as it's written to.
*/
class MemoryWriteStreamDynamic : public Common::WriteStream {
private:
uint32 _capacity;
uint32 _size;
byte *_ptr;
byte *_data;
uint32 _pos;
bool _disposeMemory;
void ensureCapacity(uint32 new_len) {
if (new_len <= _capacity)
return;
byte *old_data = _data;
_capacity = new_len + 32;
_data = new byte[_capacity];
_ptr = _data + _pos;
if (old_data) {
// Copy old data
memcpy(_data, old_data, _size);
delete[] old_data;
}
_size = new_len;
}
public:
MemoryWriteStreamDynamic(bool disposeMemory = false) : _capacity(0), _size(0), _ptr(0), _data(0), _pos(0), _disposeMemory(disposeMemory) {}
~MemoryWriteStreamDynamic() {
if (_disposeMemory)
delete[] _data;
}
uint32 write(const void *dataPtr, uint32 dataSize) {
ensureCapacity(_pos + dataSize);
memcpy(_ptr, dataPtr, dataSize);
_ptr += dataSize;
_pos += dataSize;
if (_pos > _size)
_size = _pos;
return dataSize;
}
bool eos() const { return false; }
uint32 pos() const { return _pos; }
uint32 size() const { return _size; }
byte *getData() { return _data; }
};
} // End of namespace Common
#endif

View File

@ -46,6 +46,7 @@ extern "C" int residual_main(int argc, char *argv[]);
#pragma once
#pragma warning( disable : 4068 ) // turn off "unknown pragma" warning
#pragma warning( disable : 4244 ) // turn off "conversion type" warning
#pragma warning( disable : 4250 ) // turn off "inherits via dominance" warning
#endif
#ifndef LOCAL_PI

View File

@ -56,5 +56,4 @@ template<typename T> inline void SWAP(T &a, T &b) { T tmp = a; a = b; b = tmp; }
#define round(x) ((x > 0.0) ? floor((x) + 0.5) : ceil((x) - 0.5))
#endif
#endif