diff options
author | Christopher Page | 2008-08-04 22:34:07 +0000 |
---|---|---|
committer | Christopher Page | 2008-08-04 22:34:07 +0000 |
commit | 4198ee962399305a4a158b1f43224c00e2e04a1b (patch) | |
tree | eea375b5cb471509df2e2c5d9123d963a62403c6 /common | |
parent | a51f45407659bba43254b466d20b6af2e8f17ffd (diff) | |
parent | 4f5479ee744ac6b419cdf7ec1e96fbf7c83d36ef (diff) | |
download | scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.gz scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.bz2 scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.zip |
Merged revisions 33188-33189,33191-33193,33196,33198,33202-33203,33206,33210,33212,33218-33220,33222,33224-33226,33229-33243,33246,33248-33250,33252,33258-33261,33263,33266,33270,33272-33283,33285,33287-33290,33295-33298,33321,33325-33330,33332-33335,33337-33340,33342,33345,33347,33349-33350,33352-33357,33359-33367,33369-33371,33373,33375-33377,33379-33380,33383-33385,33387-33389,33392-33394,33400-33402,33404-33405,33407-33410,33412-33416,33418-33419,33425-33427,33432,33436-33438,33444,33446,33452-33453,33455-33459,33463-33464,33466-33471,33473-33474,33478,33490,33492,33495-33496,33509-33512,33518-33519,33522-33527,33529-33530,33537,33541,33544,33546,33550,33552-33554,33556,33558,33561-33562,33565,33568,33570,33574,33576,33578-33581,33584-33587,33590,33596,33604-33611,33614-33615,33617-33618,33620-33621 via svnmerge from
https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk
svn-id: r33624
Diffstat (limited to 'common')
-rw-r--r-- | common/advancedDetector.cpp | 274 | ||||
-rw-r--r-- | common/advancedDetector.h | 5 | ||||
-rw-r--r-- | common/algorithm.h | 54 | ||||
-rw-r--r-- | common/config-file.cpp | 6 | ||||
-rw-r--r-- | common/config-manager.cpp | 389 | ||||
-rw-r--r-- | common/config-manager.h | 12 | ||||
-rw-r--r-- | common/file.cpp | 197 | ||||
-rw-r--r-- | common/file.h | 69 | ||||
-rw-r--r-- | common/fs.cpp | 41 | ||||
-rw-r--r-- | common/fs.h | 67 | ||||
-rw-r--r-- | common/func.h | 248 | ||||
-rw-r--r-- | common/hashmap.h | 4 | ||||
-rw-r--r-- | common/ptr.h | 22 | ||||
-rw-r--r-- | common/rect.h | 4 | ||||
-rw-r--r-- | common/savefile.h | 17 | ||||
-rw-r--r-- | common/str.cpp | 152 | ||||
-rw-r--r-- | common/str.h | 3 | ||||
-rw-r--r-- | common/stream.cpp | 79 | ||||
-rw-r--r-- | common/stream.h | 70 | ||||
-rw-r--r-- | common/system.cpp | 71 | ||||
-rw-r--r-- | common/system.h | 19 | ||||
-rw-r--r-- | common/unarj.cpp | 2 | ||||
-rw-r--r-- | common/unarj.h | 2 |
23 files changed, 1163 insertions, 644 deletions
diff --git a/common/advancedDetector.cpp b/common/advancedDetector.cpp index 4387bd199e..522b24163e 100644 --- a/common/advancedDetector.cpp +++ b/common/advancedDetector.cpp @@ -34,7 +34,11 @@ namespace Common { -using namespace AdvancedDetector; +/** + * A list of pointers to ADGameDescription structs (or subclasses thereof). + */ +typedef Array<const ADGameDescription*> ADGameDescList; + /** * Detect games in specified directory. @@ -48,7 +52,7 @@ using namespace AdvancedDetector; * @param platform restrict results to specified platform only * @return list of ADGameDescription (or subclass) pointers corresponding to matched games */ -static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra); +static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra); /** @@ -90,6 +94,8 @@ static void upgradeTargetIfNecessary(const Common::ADParams ¶ms) { warning("Target upgraded from %s to %s", o->from, o->to); + // WORKAROUND: Fix for bug #1719463: "DETECTOR: Launching + // undefined target adds launcher entry" if (ConfMan.hasKey("id_came_from_command_line")) { warning("Target came from command line. Skipping save"); } else { @@ -194,7 +200,7 @@ static void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription * } GameList AdvancedMetaEngine::detectGames(const FSList &fslist) const { - ADGameDescList matches = detectGame(&fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); + ADGameDescList matches = detectGame(fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); GameList detectedGames; // Use fallback detector if there were no matches by other means @@ -233,7 +239,21 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c Common::String gameid = ConfMan.get("gameid"); - ADGameDescList matches = detectGame(0, params, language, platform, extra); + Common::String path; + if (ConfMan.hasKey("path")) { + path = ConfMan.get("path"); + } else { + path = "."; + warning("No path was provided. Assuming the data files are in the current directory"); + } + FilesystemNode dir(path); + FSList files; + if (!dir.isDirectory() || !dir.getChildren(files, FilesystemNode::kListAll)) { + warning("Game data path does not exist or is not a directory (%s)", path.c_str()); + return kNoGameDataFoundError; + } + + ADGameDescList matches = detectGame(files, params, language, platform, extra); if (params.singleid == NULL) { for (uint i = 0; i < matches.size(); i++) { @@ -268,10 +288,10 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c return kNoError; } -typedef HashMap<String, bool> StringSet; -typedef HashMap<String, int32> IntMap; +typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet; +typedef HashMap<String, int32, IgnoreCase_Hash, IgnoreCase_EqualTo> IntMap; -static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) { +static void reportUnknown(const StringMap &filesMD5, const IntMap &filesSize) { // TODO: This message should be cleaned up / made more specific. // For example, we should specify at least which engine triggered this. // @@ -287,96 +307,77 @@ static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) { printf("\n"); } -static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra) { - StringSet filesList; +static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams ¶ms); +static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra) { + StringMap allFiles; + + StringSet detectFiles; StringMap filesMD5; IntMap filesSize; - IntMap allFiles; - - File testFile; - String tstr; - - uint i; - char md5str[32+1]; - - bool fileMissing; const ADGameFileDescription *fileDesc; const ADGameDescription *g; const byte *descPtr; debug(3, "Starting detection"); - // First we compose list of files which we need MD5s for + // First we compose an efficient to query set of all files in fslist. + // Includes nifty stuff like removing trailing dots and ignoring case. + for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + if (file->isDirectory()) + continue; + + String tstr = file->getName(); + + // Strip any trailing dot + if (tstr.lastChar() == '.') + tstr.deleteLastChar(); + + allFiles[tstr] = file->getPath(); // Record the presence of this file + } + + // Compute the set of files for which we need MD5s for. I.e. files which are + // included in some ADGameDescription *and* present in fslist. for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { - tstr = String(fileDesc->fileName); - tstr.toLowercase(); - filesList[tstr] = true; + String tstr = fileDesc->fileName; + if (allFiles.contains(tstr)) + detectFiles[tstr] = true; } } - // TODO/FIXME: Fingolfin says: It's not good that we have two different code paths here, - // one using a FSList, one using File::open, as that will lead to discrepancies and subtle - // problems caused by those. - if (fslist != 0) { - // Get the information of the existing files - for (FSList::const_iterator file = fslist->begin(); file != fslist->end(); ++file) { - if (file->isDirectory()) continue; - tstr = file->getName(); - tstr.toLowercase(); - - // Strip any trailing dot - if (tstr.lastChar() == '.') - tstr.deleteLastChar(); - - allFiles[tstr] = true; - - debug(3, "+ %s", tstr.c_str()); - - if (!filesList.contains(tstr)) continue; + // Get the information for all detection files, if they exist + for (StringSet::const_iterator file = detectFiles.begin(); file != detectFiles.end(); ++file) { + String fname = file->_key; - if (!md5_file_string(*file, md5str, params.md5Bytes)) - continue; - filesMD5[tstr] = md5str; + debug(3, "+ %s", fname.c_str()); - debug(3, "> %s: %s", tstr.c_str(), md5str); + char md5str[32+1]; + if (!md5_file_string(allFiles[fname].c_str(), md5str, params.md5Bytes)) + continue; + filesMD5[fname] = md5str; - if (testFile.open(file->getPath())) { - filesSize[tstr] = (int32)testFile.size(); - testFile.close(); - } - } - } else { - // Get the information of the requested files - for (StringSet::const_iterator file = filesList.begin(); file != filesList.end(); ++file) { - tstr = file->_key; + debug(3, "> %s: %s", fname.c_str(), md5str); - debug(3, "+ %s", tstr.c_str()); - if (!filesMD5.contains(tstr)) { - if (testFile.open(tstr) || testFile.open(tstr + ".")) { - filesSize[tstr] = (int32)testFile.size(); - testFile.close(); - - if (md5_file_string(file->_key.c_str(), md5str, params.md5Bytes)) { - filesMD5[tstr] = md5str; - debug(3, "> %s: %s", tstr.c_str(), md5str); - } - } - } + File testFile; + if (testFile.open(allFiles[fname])) { + filesSize[fname] = (int32)testFile.size(); + testFile.close(); } } + ADGameDescList matched; int maxFilesMatched = 0; // MD5 based matching + uint i; for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize, ++i) { g = (const ADGameDescription *)descPtr; - fileMissing = false; + bool fileMissing = false; // Do not even bother to look at entries which do not have matching // language and platform (if specified). @@ -385,32 +386,28 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p continue; } - if ((params.flags & kADFlagUseExtraAsHint) && extra != "" && g->extra != extra) + if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra) continue; // Try to match all files for this game for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { - tstr = fileDesc->fileName; - tstr.toLowercase(); + String tstr = fileDesc->fileName; if (!filesMD5.contains(tstr)) { fileMissing = true; break; } - if (fileDesc->md5 != NULL) { - if (fileDesc->md5 != filesMD5[tstr]) { - debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str()); - fileMissing = true; - break; - } + + if (fileDesc->md5 != NULL && fileDesc->md5 != filesMD5[tstr]) { + debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str()); + fileMissing = true; + break; } - if (fileDesc->fileSize != -1) { - if (fileDesc->fileSize != filesSize[tstr]) { - debug(3, "Size Mismatch. Skipping"); - fileMissing = true; - break; - } + if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSize[tstr]) { + debug(3, "Size Mismatch. Skipping"); + fileMissing = true; + break; } debug(3, "Matched file: %s", tstr.c_str()); @@ -448,85 +445,68 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p } } - // We've found a match - if (!matched.empty()) - return matched; - - if (!filesMD5.empty()) - reportUnknown(filesMD5, filesSize); - - // Filename based fallback - if (params.fileBasedFallback != 0) { - const ADFileBasedFallback *ptr = params.fileBasedFallback; - const char* const* filenames = 0; - - // First we create list of files required for detection. - // The filenames can be different than the MD5 based match ones. - for (; ptr->desc; ptr++) { - filenames = ptr->filenames; - for (; *filenames; filenames++) { - tstr = String(*filenames); - tstr.toLowercase(); - - if (!allFiles.contains(tstr)) { - if (testFile.open(tstr) || testFile.open(tstr + ".")) { - allFiles[tstr] = true; - testFile.close(); - } - } - } - } - - // Then we perform the actual filename matching. If there are - // several matches, only the one with the maximum numbers of - // files is considered. - int maxNumMatchedFiles = 0; - const ADGameDescription *matchedDesc = 0; - - ptr = params.fileBasedFallback; + // We didn't find a match + if (matched.empty()) { + if (!filesMD5.empty()) + reportUnknown(filesMD5, filesSize); + + // Filename based fallback + if (params.fileBasedFallback != 0) + matched = detectGameFilebased(allFiles, params); + } - for (; ptr->desc; ptr++) { - const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc; - int numMatchedFiles = 0; - fileMissing = false; + return matched; +} - filenames = ptr->filenames; - for (; *filenames; filenames++) { - if (fileMissing) { - continue; - } +/** + * Check for each ADFileBasedFallback record whether all files listed + * in it are present. If multiple pass this test, we pick the one with + * the maximal number of matching files. In case of a tie, the entry + * coming first in the list is chosen. + */ +static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams ¶ms) { + const ADFileBasedFallback *ptr; + const char* const* filenames; - tstr = String(*filenames); - tstr.toLowercase(); + int maxNumMatchedFiles = 0; + const ADGameDescription *matchedDesc = 0; - debug(3, "++ %s", *filenames); - if (!allFiles.contains(tstr)) { - fileMissing = true; - continue; - } + for (ptr = params.fileBasedFallback; ptr->desc; ++ptr) { + const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc; + int numMatchedFiles = 0; + bool fileMissing = false; - numMatchedFiles++; + for (filenames = ptr->filenames; *filenames; ++filenames) { + debug(3, "++ %s", *filenames); + if (!allFiles.contains(*filenames)) { + fileMissing = true; + break; } - if (!fileMissing) - debug(4, "Matched: %s", agdesc->gameid); + numMatchedFiles++; + } - if (!fileMissing && numMatchedFiles > maxNumMatchedFiles) { + if (!fileMissing) { + debug(4, "Matched: %s", agdesc->gameid); + + if (numMatchedFiles > maxNumMatchedFiles) { matchedDesc = agdesc; maxNumMatchedFiles = numMatchedFiles; - + debug(4, "and overriden"); } } + } - if (matchedDesc) { // We got a match - matched.push_back(matchedDesc); - if (params.flags & kADFlagPrintWarningOnFileBasedFallback) { - printf("Your game version has been detected using filename matching as a\n"); - printf("variant of %s.\n", matchedDesc->gameid); - printf("If this is an original and unmodified version, please report any\n"); - printf("information previously printed by ScummVM to the team.\n"); - } + ADGameDescList matched; + + if (matchedDesc) { // We got a match + matched.push_back(matchedDesc); + if (params.flags & kADFlagPrintWarningOnFileBasedFallback) { + printf("Your game version has been detected using filename matching as a\n"); + printf("variant of %s.\n", matchedDesc->gameid); + printf("If this is an original and unmodified version, please report any\n"); + printf("information previously printed by ScummVM to the team.\n"); } } diff --git a/common/advancedDetector.h b/common/advancedDetector.h index 48b9e213d7..40f5823d1b 100644 --- a/common/advancedDetector.h +++ b/common/advancedDetector.h @@ -69,11 +69,6 @@ struct ADGameDescription { }; /** - * A list of pointers to ADGameDescription structs (or subclasses thereof). - */ -typedef Array<const ADGameDescription*> ADGameDescList; - -/** * End marker for a table of ADGameDescription structs. Use this to * terminate a list to be passed to the AdvancedDetector API. */ diff --git a/common/algorithm.h b/common/algorithm.h index beae34245f..3b6c63d55c 100644 --- a/common/algorithm.h +++ b/common/algorithm.h @@ -29,6 +29,11 @@ namespace Common { +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + */ template<class In, class Out> Out copy(In first, In last, Out dst) { while (first != last) @@ -36,6 +41,13 @@ Out copy(In first, In last, Out dst) { return dst; } +/** + * Copies data from the range [first, last) to [dst - (last - first), dst). + * It requires the range [dst - (last - first), dst) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy copy_backward copies the data from the end to the beginning. + */ template<class In, class Out> Out copy_backward(In first, In last, Out dst) { while (first != last) @@ -43,6 +55,15 @@ Out copy_backward(In first, In last, Out dst) { return dst; } +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy or copy_backward it does not copy all data. It only copies + * a data element when operator() of the op parameter returns true for the + * passed data element. + */ template<class In, class Out, class Op> Out copy_if(In first, In last, Out dst, Op op) { while (first != last) { @@ -76,6 +97,9 @@ char *set_to(char *first, char *last, Value val) { return last; } +/** + * Sets all elements in the range [first, last) to val. + */ template<class In, class Value> In set_to(In first, In last, Value val) { while (first != last) @@ -83,6 +107,10 @@ In set_to(In first, In last, Value val) { return first; } +/** + * Finds the first data value in the range [first, last) matching v. + * For data comperance it uses operator == of the data elements. + */ template<class In, class T> In find(In first, In last, const T &v) { while (first != last) { @@ -93,6 +121,10 @@ In find(In first, In last, const T &v) { return last; } +/** + * Finds the first data value in the range [first, last) for which + * the specified predicate p returns true. + */ template<class In, class Pred> In find_if(In first, In last, Pred p) { while (first != last) { @@ -103,15 +135,22 @@ In find_if(In first, In last, Pred p) { return last; } +/** + * Applies the function f on all elements of the range [first, last). + * The processing order is from beginning to end. + */ template<class In, class Op> Op for_each(In first, In last, Op f) { while (first != last) f(*first++); return f; } -// Simple sort function, modeled after std::sort. -// Use it like this: sort(container.begin(), container.end()). -// Also work on plain old int arrays etc. +/** + * Simple sort function, modeled after std::sort. + * Use it like this: sort(container.begin(), container.end()). + * Also works on plain old i.e. int arrays etc. For comperance + * operator < is used. + */ template<class T> void sort(T first, T last) { if (first == last) @@ -131,8 +170,13 @@ void sort(T first, T last) { } } -// Using this with: Common::Less from common/func.h -// will give the same results as the function above. +/** + * Simple sort function, modeled after std::sort. + * It compares data with the given comparator object comp. + * + * Note: Using this with: Common::Less from common/func.h + * will give the same results as the plain sort function. + */ template<class T, class StrictWeakOrdering> void sort(T first, T last, StrictWeakOrdering comp) { if (first == last) diff --git a/common/config-file.cpp b/common/config-file.cpp index fe827e32dc..9f54c9ddde 100644 --- a/common/config-file.cpp +++ b/common/config-file.cpp @@ -58,7 +58,7 @@ void ConfigFile::clear() { bool ConfigFile::loadFromFile(const String &filename) { File file; - if (file.open(filename, File::kFileReadMode)) + if (file.open(filename)) return loadFromStream(file); else return false; @@ -171,8 +171,8 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { } bool ConfigFile::saveToFile(const String &filename) { - File file; - if (file.open(filename, File::kFileWriteMode)) + DumpFile file; + if (file.open(filename)) return saveToStream(file); else return false; diff --git a/common/config-manager.cpp b/common/config-manager.cpp index 59855cf6c9..a424c4f6c7 100644 --- a/common/config-manager.cpp +++ b/common/config-manager.cpp @@ -23,38 +23,13 @@ * */ -#if defined(WIN32) -#include <windows.h> -// winnt.h defines ARRAYSIZE, but we want our own one... -#undef ARRAYSIZE -#endif - #include "common/config-manager.h" #include "common/file.h" #include "common/util.h" +#include "common/system.h" DECLARE_SINGLETON(Common::ConfigManager); -#ifdef __PLAYSTATION2__ -#include "backends/platform/ps2/systemps2.h" -#endif - -#ifdef IPHONE -#include "backends/platform/iphone/osys_iphone.h" -#endif - -#if defined(UNIX) -#ifdef MACOSX -#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences" -#else -#define DEFAULT_CONFIG_FILE ".scummvmrc" -#endif -#else -#define DEFAULT_CONFIG_FILE "scummvm.ini" -#endif - -#define MAXLINELEN 256 - static bool isValidDomainName(const Common::String &domName) { const char *p = domName.c_str(); while (*p && (isalnum(*p) || *p == '-' || *p == '_')) @@ -85,242 +60,180 @@ ConfigManager::ConfigManager() void ConfigManager::loadDefaultConfigFile() { - char configFile[MAXPATHLEN]; - // GP2X is Linux based but Home dir can be read only so do not use it and put the config in the executable dir. - // On the iPhone, the home dir of the user when you launch the app from the Springboard, is /. Which we don't want. -#if defined(UNIX) && !defined(GP2X) && !defined(IPHONE) - const char *home = getenv("HOME"); - if (home != NULL && strlen(home) < MAXPATHLEN) - snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE); - else - strcpy(configFile, DEFAULT_CONFIG_FILE); -#else - #if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__) - OSVERSIONINFO win32OsVersion; - ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO)); - win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&win32OsVersion); - // Check for non-9X version of Windows. - if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) { - // Use the Application Data directory of the user profile. - if (win32OsVersion.dwMajorVersion >= 5) { - if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile))) - error("Unable to access application data directory"); - } else { - if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile))) - error("Unable to access user profile directory"); + // Open the default config file + SeekableReadStream *stream = g_system->openConfigFileForReading(); + _filename.clear(); // clear the filename to indicate that we are using the default config file - strcat(configFile, "\\Application Data"); - CreateDirectory(configFile, NULL); - } + // ... load it ... + assert(stream); + loadFromStream(*stream); + + // ... and close it again. + delete stream; - strcat(configFile, "\\ScummVM"); - CreateDirectory(configFile, NULL); - strcat(configFile, "\\" DEFAULT_CONFIG_FILE); - - if (fopen(configFile, "r") == NULL) { - // Check windows directory - char oldConfigFile[MAXPATHLEN]; - GetWindowsDirectory(oldConfigFile, MAXPATHLEN); - strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE); - if (fopen(oldConfigFile, "r")) { - printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n"); - printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n"); - printf("file from the old default location:\n"); - printf("%s\n", oldConfigFile); - printf("to the new default location:\n"); - printf("%s\n\n", configFile); - strcpy(configFile, oldConfigFile); - } - } - } else { - // Check windows directory - GetWindowsDirectory(configFile, MAXPATHLEN); - strcat(configFile, "\\" DEFAULT_CONFIG_FILE); - } - - #elif defined(PALMOS_MODE) - strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE); - #elif defined(IPHONE) - strcpy(configFile, OSystem_IPHONE::getConfigPath()); - #elif defined(__PLAYSTATION2__) - ((OSystem_PS2*)g_system)->makeConfigPath(configFile); - #elif defined(__PSP__) - strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE); - #elif defined (__SYMBIAN32__) - strcpy(configFile, Symbian::GetExecutablePath()); - strcat(configFile, DEFAULT_CONFIG_FILE); - #else - strcpy(configFile, DEFAULT_CONFIG_FILE); - #endif -#endif - - loadConfigFile(configFile); flushToDisk(); } void ConfigManager::loadConfigFile(const String &filename) { - _appDomain.clear(); - _gameDomains.clear(); - _transientDomain.clear(); - _filename = filename; - _domainSaveOrder.clear(); - loadFile(_filename); - printf("Using configuration file: %s\n", _filename.c_str()); -} -void ConfigManager::loadFile(const String &filename) { File cfg_file; - if (!cfg_file.open(filename)) { printf("Creating configuration file: %s\n", filename.c_str()); } else { - String domain; - String comment; - int lineno = 0; - - // TODO: Detect if a domain occurs multiple times (or likewise, if - // a key occurs multiple times inside one domain). - - while (!cfg_file.eof() && !cfg_file.ioFailed()) { - lineno++; - - // Read a line - String line; - while (line.lastChar() != '\n') { - char buf[MAXLINELEN]; - if (!cfg_file.readLine_NEW(buf, MAXLINELEN)) - break; - line += buf; + printf("Using configuration file: %s\n", _filename.c_str()); + loadFromStream(cfg_file); + } +} + +void ConfigManager::loadFromStream(SeekableReadStream &stream) { + String domain; + String comment; + int lineno = 0; + + _appDomain.clear(); + _gameDomains.clear(); + _transientDomain.clear(); + _domainSaveOrder.clear(); + + // TODO: Detect if a domain occurs multiple times (or likewise, if + // a key occurs multiple times inside one domain). + + while (!stream.eos() && !stream.ioFailed()) { + lineno++; + + // Read a line + String line; + while (line.lastChar() != '\n') { + char buf[256]; + if (!stream.readLine_NEW(buf, 256)) + break; + line += buf; + } + + 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' occured in section name in line %d", *p, lineno); + + domain = String(line.c_str() + 1, p); + + // Store domain comment + if (domain == kApplicationDomain) { + _appDomain.setDomainComment(comment); + } else { + _gameDomains[domain].setDomainComment(comment); + } + comment.clear(); + + _domainSaveOrder.push_back(domain); + } else { + // This line should be a line with a 'key=value' pair, or an empty one. + + // Skip leading whitespaces + const char *t = line.c_str(); + while (isspace(*t)) + t++; + + // Skip empty lines / lines with only whitespace + if (*t == 0) + continue; + + // If no domain has been set, this config file is invalid! + if (domain.empty()) { + error("Config file buggy: Key/value pair found outside a domain in line %d", lineno); } - if (line.size() == 0) { - // Do nothing - } else if (line[0] == '#') { - // Accumulate comments here. Once we encounter either the start - // of a new domain, or a key-value-pair, we associate the value - // of the 'comment' variable with that entity. - comment += line; - } else if (line[0] == '[') { - // It's a new domain which begins here. - const char *p = line.c_str() + 1; - // Get the domain name, and check whether it's valid (that - // is, verify that it only consists of alphanumerics, - // dashes and underscores). - while (*p && (isalnum(*p) || *p == '-' || *p == '_')) - p++; - - switch (*p) { - case '\0': - error("Config file buggy: missing ] in line %d", lineno); - break; - case ']': - domain = String(line.c_str() + 1, p - (line.c_str() + 1)); - //domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes - break; - default: - error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno); - } - - // Store domain comment - if (domain == kApplicationDomain) { - _appDomain.setDomainComment(comment); - } else { - _gameDomains[domain].setDomainComment(comment); - } - comment.clear(); - - _domainSaveOrder.push_back(domain); + // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. + const char *p = strchr(t, '='); + if (!p) + error("Config file buggy: Junk found in line line %d: '%s'", lineno, t); + + // Extract the key/value pair + String key(t, p); + String value(p + 1); + + // Trim of spaces + key.trim(); + value.trim(); + + // Finally, store the key/value pair in the active domain + set(key, value, domain); + + // Store comment + if (domain == kApplicationDomain) { + _appDomain.setKVComment(key, comment); } else { - // This line should be a line with a 'key=value' pair, or an empty one. - - // Skip leading whitespaces - const char *t = line.c_str(); - while (isspace(*t)) - t++; - - // Skip empty lines / lines with only whitespace - if (*t == 0) - continue; - - // If no domain has been set, this config file is invalid! - if (domain.empty()) { - error("Config file buggy: Key/value pair found outside a domain in line %d", lineno); - } - - // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. - const char *p = strchr(t, '='); - if (!p) - error("Config file buggy: Junk found in line line %d: '%s'", lineno, t); - - // Trim spaces before the '=' to obtain the key - const char *p2 = p; - while (p2 > t && isspace(*(p2-1))) - p2--; - String key(t, p2 - t); - - // Skip spaces after the '=' - t = p + 1; - while (isspace(*t)) - t++; - - // Trim trailing spaces - p2 = t + strlen(t); - while (p2 > t && isspace(*(p2-1))) - p2--; - - String value(t, p2 - t); - - // Finally, store the key/value pair in the active domain - set(key, value, domain); - - // Store comment - if (domain == kApplicationDomain) { - _appDomain.setKVComment(key, comment); - } else { - _gameDomains[domain].setKVComment(key, comment); - } - comment.clear(); + _gameDomains[domain].setKVComment(key, comment); } + comment.clear(); } } } void ConfigManager::flushToDisk() { #ifndef __DC__ - 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 + stream = g_system->openConfigFileForWriting(); + if (!stream) // If writing to the config file is not possible, do nothing + return; } else { - // First write the domains in _domainSaveOrder, in that order. - // Note: It's possible for _domainSaveOrder to list domains which - // are not present anymore. - StringList::const_iterator i; - for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) { - if (kApplicationDomain == *i) { - writeDomain(cfg_file, *i, _appDomain); - } else if (_gameDomains.contains(*i)) { - writeDomain(cfg_file, *i, _gameDomains[*i]); - } + DumpFile *dump = new DumpFile(); + assert(dump); + + if (!dump->open(_filename)) { + warning("Unable to write configuration file: %s", _filename.c_str()); + delete dump; + return; } + + stream = dump; + } - DomainMap::const_iterator d; + // First write the domains in _domainSaveOrder, in that order. + // Note: It's possible for _domainSaveOrder to list domains which + // are not present anymore. + StringList::const_iterator i; + for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) { + if (kApplicationDomain == *i) { + writeDomain(*stream, *i, _appDomain); + } else if (_gameDomains.contains(*i)) { + writeDomain(*stream, *i, _gameDomains[*i]); + } + } + DomainMap::const_iterator d; - // Now write the domains which haven't been written yet - if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end()) - writeDomain(cfg_file, kApplicationDomain, _appDomain); - for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) { - if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end()) - writeDomain(cfg_file, d->_key, d->_value); - } + + // Now write the domains which haven't been written yet + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end()) + writeDomain(*stream, kApplicationDomain, _appDomain); + for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) { + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end()) + writeDomain(*stream, d->_key, d->_value); } + + delete stream; + #endif // !__DC__ } @@ -328,6 +241,12 @@ void ConfigManager::writeDomain(WriteStream &stream, const String &name, const D if (domain.empty()) return; // Don't bother writing empty domains. + // WORKAROUND: Fix for bug #1972625 "ALL: On-the-fly targets are + // written to the config file": Do not save domains that came from + // the command line + if (domain.contains("id_came_from_command_line")) + return; + String comment; // Write domain comment (if any) @@ -642,6 +561,10 @@ void ConfigManager::addGameDomain(const String &domName) { // the given name already exists? _gameDomains[domName]; + + // Add it to the _domainSaveOrder, if it's not already in there + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end()) + _domainSaveOrder.push_back(domName); } void ConfigManager::removeGameDomain(const String &domName) { diff --git a/common/config-manager.h b/common/config-manager.h index bebb59b539..9e5b88a073 100644 --- a/common/config-manager.h +++ b/common/config-manager.h @@ -36,7 +36,7 @@ namespace Common { class WriteStream; - +class SeekableReadStream; /** * The (singleton) configuration manager, used to query & set configuration @@ -144,19 +144,11 @@ public: bool hasGameDomain(const String &domName) const; const DomainMap & getGameDomains() const { return _gameDomains; } -/* - TODO: Callback/change notification system - typedef void (*ConfigCallback)(const ConstString &key, void *refCon); - - void registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString) - void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString) -*/ - private: friend class Singleton<SingletonBaseType>; ConfigManager(); - void loadFile(const String &filename); + void loadFromStream(SeekableReadStream &stream); void writeDomain(WriteStream &stream, const String &name, const Domain &domain); Domain _transientDomain; diff --git a/common/file.cpp b/common/file.cpp index a1ea1aff77..fb837b9499 100644 --- a/common/file.cpp +++ b/common/file.cpp @@ -104,7 +104,7 @@ //#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 + #define ferror(handle) DS::std_ferror(handle) #endif @@ -273,26 +273,14 @@ 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()); - } +bool File::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); _name.clear(); clearIOFailed(); @@ -300,32 +288,29 @@ bool File::open(const String &filename, AccessMode mode) { 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)) { + if (_filesMap && _filesMap->contains(fname)) { fname = (*_filesMap)[fname]; debug(3, "Opening hashed: %s", fname.c_str()); - _handle = fopen(fname.c_str(), modeStr); + _handle = fopen(fname.c_str(), "rb"); } 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); + _handle = fopen(fname.c_str(), "rb"); } 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); + _handle = fopenNoCase(filename, x->_key, "rb"); } } // Last resort: try the current directory if (_handle == NULL) - _handle = fopenNoCase(filename, "", modeStr); + _handle = fopenNoCase(filename, "", "rb"); // Last last (really) resort: try looking inside the application bundle on Mac OS X for the lowercase file. #if defined(MACOSX) || defined(IPHONE) @@ -335,7 +320,7 @@ bool File::open(const String &filename, AccessMode mode) { if (fileUrl) { UInt8 buf[256]; if (CFURLGetFileSystemRepresentation(fileUrl, false, (UInt8 *)buf, 256)) { - _handle = fopen((char *)buf, modeStr); + _handle = fopen((char *)buf, "rb"); } CFRelease(fileUrl); } @@ -345,26 +330,15 @@ bool File::open(const String &filename, AccessMode mode) { } - 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 (_handle == NULL) + debug(2, "File %s not opened", filename.c_str()); + else + _name = filename; - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif - - return true; + return _handle != NULL; } -bool File::open(const FilesystemNode &node, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); +bool File::open(const FilesystemNode &node) { if (!node.exists()) { warning("File::open: Trying to open a FilesystemNode which does not exist"); @@ -389,25 +363,14 @@ bool File::open(const FilesystemNode &node, AccessMode mode) { clearIOFailed(); _name.clear(); - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; - - _handle = fopen(node.getPath().c_str(), modeStr); + _handle = fopen(node.getPath().c_str(), "rb"); - 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 + if (_handle == NULL) + debug(2, "File %s not found", filename.c_str()); + else + _name = filename; - return true; + return _handle != NULL; } bool File::exists(const String &filename) { @@ -438,7 +401,7 @@ bool File::exists(const String &filename) { //Try opening the file inside the local directory as a last resort File tmp; - return tmp.open(filename, kFileReadMode); + return tmp.open(filename); } void File::close() { @@ -462,28 +425,19 @@ void File::clearIOFailed() { } bool File::eof() const { - if (_handle == NULL) { - error("File::eof: File is not open!"); - return false; - } + assert(_handle); return feof((FILE *)_handle) != 0; } uint32 File::pos() const { - if (_handle == NULL) { - error("File::pos: File is not open!"); - return 0; - } + assert(_handle); return ftell((FILE *)_handle); } uint32 File::size() const { - if (_handle == NULL) { - error("File::size: File is not open!"); - return 0; - } + assert(_handle); uint32 oldPos = ftell((FILE *)_handle); fseek((FILE *)_handle, 0, SEEK_END); @@ -494,10 +448,7 @@ uint32 File::size() const { } void File::seek(int32 offs, int whence) { - if (_handle == NULL) { - error("File::seek: File is not open!"); - return; - } + assert(_handle); if (fseek((FILE *)_handle, offs, whence) != 0) clearerr((FILE *)_handle); @@ -507,10 +458,7 @@ 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; - } + assert(_handle); if (len == 0) return 0; @@ -523,20 +471,91 @@ uint32 File::read(void *ptr, uint32 len) { return real_len; } -uint32 File::write(const void *ptr, uint32 len) { - if (_handle == NULL) { - error("File::write: File is not open!"); - return 0; - } + +DumpFile::DumpFile() : _handle(0) { +} + +DumpFile::~DumpFile() { + close(); +} + +bool DumpFile::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); + + String fname(filename); + fname.toLowercase(); + + _handle = fopenNoCase(filename, "", "wb"); + + if (_handle == NULL) + debug(2, "Failed to open '%s' for writing", filename.c_str()); + + return _handle != NULL; +} + +bool DumpFile::open(const FilesystemNode &node) { + assert(!_handle); + + 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; + }*/ + + _handle = fopen(node.getPath().c_str(), "wb"); + + if (_handle == NULL) + debug(2, "File %s not found", node.getName().c_str()); + + return _handle != NULL; +} + +void DumpFile::close() { + if (_handle) + fclose((FILE *)_handle); + _handle = NULL; +} + +bool DumpFile::isOpen() const { + return _handle != NULL; +} + +bool DumpFile::ioFailed() const { + assert(_handle); + return ferror((FILE *)_handle) != 0; +} + +void DumpFile::clearIOFailed() { + assert(_handle); + clearerr((FILE *)_handle); +} + +bool DumpFile::eof() const { + assert(_handle); + return feof((FILE *)_handle) != 0; +} + +uint32 DumpFile::write(const void *ptr, uint32 len) { + assert(_handle); if (len == 0) return 0; - if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) { - _ioFailed = true; - } + return (uint32)fwrite(ptr, 1, len, (FILE *)_handle); +} - return len; +void DumpFile::flush() { + assert(_handle); + // TODO: Should check the return value of fflush, and if it is non-zero, + // check errno and set an error flag. + fflush((FILE *)_handle); } + } // End of namespace Common diff --git a/common/file.h b/common/file.h index 8a69318128..3adeb6ff36 100644 --- a/common/file.h +++ b/common/file.h @@ -27,6 +27,7 @@ #define COMMON_FILE_H #include "common/scummsys.h" +#include "common/noncopyable.h" #include "common/str.h" #include "common/stream.h" @@ -34,7 +35,10 @@ class FilesystemNode; namespace Common { -class File : public SeekableReadStream, public WriteStream { +/** + * 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; @@ -45,19 +49,7 @@ protected: /** 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 = ""); @@ -80,8 +72,8 @@ public: */ 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 bool open(const String &filename); + virtual bool open(const FilesystemNode &node); virtual void close(); @@ -114,9 +106,54 @@ public: 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); }; + +/** + * 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. */ + void *_handle; + +public: + DumpFile(); + virtual ~DumpFile(); + + virtual bool open(const String &filename); + virtual bool open(const FilesystemNode &node); + + virtual void close(); + + /** + * Checks if the object opened a file successfully. + * + * @return: true if any file is opened, false otherwise. + */ + bool isOpen() const; + + + bool ioFailed() const; + void clearIOFailed(); + bool eos() const { return eof(); } + + /** + * Checks for end of file. + * + * @return: true if the end of file is reached, false otherwise. + */ + virtual bool eof() const; + + virtual uint32 write(const void *dataPtr, uint32 dataSize); + + virtual void flush(); +}; + + } // End of namespace Common #endif diff --git a/common/fs.cpp b/common/fs.cpp index 7d803dacd4..3f585c6038 100644 --- a/common/fs.cpp +++ b/common/fs.cpp @@ -23,10 +23,13 @@ */ #include "common/util.h" +#include "common/file.h" #include "common/system.h" #include "backends/fs/abstract-fs.h" #include "backends/fs/fs-factory.h" +//namespace Common { + FilesystemNode::FilesystemNode() { } @@ -170,3 +173,41 @@ bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool h return !results.empty(); } + +Common::SeekableReadStream *FilesystemNode::openForReading() { + if (_realNode == 0) + return 0; +#if 0 + return _realNode->openForReading(); +#else + // FIXME: Until we support openForReading in AbstractFilesystemNode, + // we just use Common::File. + Common::File *confFile = new Common::File(); + assert(confFile); + if (!confFile->open(*this)) { + delete confFile; + confFile = 0; + } + return confFile; +#endif +} + +Common::WriteStream *FilesystemNode::openForWriting() { + if (_realNode == 0) + return 0; +#if 0 + return _realNode->openForWriting(); +#else + // FIXME: Until we support openForWriting in AbstractFilesystemNode, + // we just use Common::DumpFile. + Common::DumpFile *confFile = new Common::DumpFile(); + assert(confFile); + if (!confFile->open(*this)) { + delete confFile; + confFile = 0; + } + return confFile; +#endif +} + +//} // End of namespace Common diff --git a/common/fs.h b/common/fs.h index ed7355cc00..972e0d86af 100644 --- a/common/fs.h +++ b/common/fs.h @@ -29,10 +29,18 @@ #include "common/ptr.h" #include "common/str.h" +class AbstractFilesystemNode; + +namespace Common { + class SeekableReadStream; + class WriteStream; +} + //namespace Common { class FilesystemNode; -class AbstractFilesystemNode; +//class SeekableReadStream; +//class WriteStream; /** * List of multiple file system nodes. E.g. the contents of a given directory. @@ -49,22 +57,6 @@ class FSList : public Common::Array<FilesystemNode> {}; * To this end, we abstract away from paths; implementations can be based on * paths (and it's left to them whether / or \ or : is the path separator :-); * but it is also possible to use inodes or vrefs (MacOS 9) or anything else. - * - * NOTE: Backends still have to provide a way to extract a path from a FSIntern - * - * You may ask now: "isn't this cheating? Why do we go through all this when we use - * a path in the end anyway?!?". - * Well, for once as long as we don't provide our own file open/read/write API, we - * still have to use fopen(). Since all our targets already support fopen(), it should - * be possible to get a fopen() compatible string for any file system node. - * - * Secondly, with this abstraction layer, we still avoid a lot of complications based on - * differences in FS roots, different path separators, or even systems with no real - * paths (MacOS 9 doesn't even have the notion of a "current directory"). - * And if we ever want to support devices with no FS in the classical sense (Palm...), - * we can build upon this. - * - * This class acts as a wrapper around the AbstractFilesystemNode class defined in backends/fs. */ class FilesystemNode { private: @@ -108,9 +100,9 @@ public: bool operator<(const FilesystemNode& node) const; /** - * Indicates whether the object referred by this path exists in the filesystem or not. + * Indicates whether the object referred by this node exists in the filesystem or not. * - * @return bool true if the path exists, false otherwise. + * @return bool true if the node exists, false otherwise. */ virtual bool exists() const; @@ -168,7 +160,7 @@ public: FilesystemNode getParent() const; /** - * Indicates whether the path refers to a directory or not. + * Indicates whether the node refers to a directory or not. * * @todo Currently we assume that a node that is not a directory * automatically is a file (ignoring things like symlinks or pipes). @@ -179,28 +171,28 @@ public: virtual bool isDirectory() const; /** - * Indicates whether the object referred by this path can be read from or not. + * Indicates whether the object referred by this node can be read from or not. * - * If the path refers to a directory, readability implies being able to read + * If the node refers to a directory, readability implies being able to read * and list the directory entries. * - * If the path refers to a file, readability implies being able to read the + * If the node refers to a file, readability implies being able to read the * contents of the file. * - * @return bool true if the object can be read, false otherwise. + * @return true if the object can be read, false otherwise. */ virtual bool isReadable() const; /** - * Indicates whether the object referred by this path can be written to or not. + * Indicates whether the object referred by this node can be written to or not. * - * If the path refers to a directory, writability implies being able to modify + * If the node refers to a directory, writability implies being able to modify * the directory entry (i.e. rename the directory, remove it or write files inside of it). * - * If the path refers to a file, writability implies being able to write data + * If the node refers to a file, writability implies being able to write data * to the file. * - * @return bool true if the object can be written to, false otherwise. + * @return true if the object can be written to, false otherwise. */ virtual bool isWritable() const; @@ -221,6 +213,25 @@ public: * @return true if matches could be found, false otherwise. */ virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const; + + + /** + * Creates a SeekableReadStream instance corresponding to the file + * referred by this node. This assumes that the node actually refers + * to a readable file. If this is not the case, 0 is returned. + * + * @return pointer to the stream object, 0 in case of a failure + */ + virtual Common::SeekableReadStream *openForReading(); + + /** + * Creates a WriteStream instance corresponding to the file + * referred by this node. This assumes that the node actually refers + * to a readable file. If this is not the case, 0 is returned. + * + * @return pointer to the stream object, 0 in case of a failure + */ + virtual Common::WriteStream *openForWriting(); }; //} // End of namespace Common diff --git a/common/func.h b/common/func.h index 95df96123a..6aa5b76ed4 100644 --- a/common/func.h +++ b/common/func.h @@ -29,12 +29,18 @@ namespace Common { +/** + * Generic unary function. + */ template<class Arg, class Result> struct UnaryFunction { typedef Arg ArgumenType; typedef Result ResultType; }; +/** + * Generic binary function. + */ template<class Arg1, class Arg2, class Result> struct BinaryFunction { typedef Arg1 FirstArgumentType; @@ -42,16 +48,25 @@ struct BinaryFunction { typedef Result ResultType; }; +/** + * Predicate to check for equallity of two data elements. + */ template<class T> struct EqualTo : public BinaryFunction<T, T, bool> { bool operator()(const T &x, const T &y) const { return x == y; } }; +/** + * Predicate to check for x being less than y. + */ template<class T> struct Less : public BinaryFunction<T, T, bool> { bool operator()(const T &x, const T &y) const { return x < y; } }; +/** + * Predicate to check for x being greater than y. + */ template<class T> struct Greater : public BinaryFunction<T, T, bool> { bool operator()(const T &x, const T &y) const { return x > y; } @@ -63,15 +78,19 @@ private: Op _op; typename Op::FirstArgumentType _arg1; public: - Binder1st(const Op &op, const typename Op::FirstArgumentType &arg1) : _op(op), _arg1(arg1) {} + Binder1st(const Op &op, typename Op::FirstArgumentType arg1) : _op(op), _arg1(arg1) {} typename Op::ResultType operator()(typename Op::SecondArgumentType v) const { return _op(_arg1, v); } }; -template<class Op, class T> -inline Binder1st<Op> bind1st(const Op &op, const T &t) { +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template<class Op> +inline Binder1st<Op> bind1st(const Op &op, typename Op::FirstArgumentType t) { return Binder1st<Op>(op, t); } @@ -81,15 +100,19 @@ private: Op _op; typename Op::SecondArgumentType _arg2; public: - Binder2nd(const Op &op, const typename Op::SecondArgumentType &arg2) : _op(op), _arg2(arg2) {} + Binder2nd(const Op &op, typename Op::SecondArgumentType arg2) : _op(op), _arg2(arg2) {} typename Op::ResultType operator()(typename Op::FirstArgumentType v) const { return _op(v, _arg2); } }; -template<class Op, class T> -inline Binder2nd<Op> bind2nd(const Op &op, const T &t) { +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template<class Op> +inline Binder2nd<Op> bind2nd(const Op &op, typename Op::SecondArgumentType t) { return Binder2nd<Op>(op, t); } @@ -119,18 +142,24 @@ public: } }; +/** + * Creates an unary function object from a function pointer. + */ template<class Arg, class Result> inline PointerToUnaryFunc<Arg, Result> ptr_fun(Result (*func)(Arg)) { return PointerToUnaryFunc<Arg, Result>(func); } +/** + * Creates an binary function object from a function pointer. + */ template<class Arg1, class Arg2, class Result> inline PointerToBinaryFunc<Arg1, Arg2, Result> ptr_fun(Result (*func)(Arg1, Arg2)) { return PointerToBinaryFunc<Arg1, Arg2, Result>(func); } template<class Result, class T> -class MemFunc0 : public UnaryFunction<T*, Result> { +class MemFunc0 : public UnaryFunction<T *, Result> { private: Result (T::*_func)(); public: @@ -143,20 +172,20 @@ public: }; template<class Result, class T> -class ConstMemFunc0 : public UnaryFunction<T*, Result> { +class ConstMemFunc0 : public UnaryFunction<T *, Result> { private: Result (T::*_func)() const; public: typedef Result (T::*FuncType)() const; ConstMemFunc0(const FuncType &func) : _func(func) {} - Result operator()(T *v) const { + Result operator()(const T *v) const { return (v->*_func)(); } }; template<class Result, class Arg, class T> -class MemFunc1 : public BinaryFunction<T*, Arg, Result> { +class MemFunc1 : public BinaryFunction<T *, Arg, Result> { private: Result (T::*_func)(Arg); public: @@ -169,40 +198,166 @@ public: }; template<class Result, class Arg, class T> -class ConstMemFunc1 : public BinaryFunction<T*, Arg, Result> { +class ConstMemFunc1 : public BinaryFunction<T *, Arg, Result> { private: Result (T::*_func)(Arg) const; public: typedef Result (T::*FuncType)(Arg) const; ConstMemFunc1(const FuncType &func) : _func(func) {} - Result operator()(T *v1, Arg v2) const { + Result operator()(const T *v1, Arg v2) const { return (v1->*_func)(v2); } }; +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ template<class Result, class T> inline MemFunc0<Result, T> mem_fun(Result (T::*f)()) { return MemFunc0<Result, T>(f); } +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ template<class Result, class T> inline ConstMemFunc0<Result, T> mem_fun(Result (T::*f)() const) { return ConstMemFunc0<Result, T>(f); } +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ template<class Result, class Arg, class T> inline MemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg)) { return MemFunc1<Result, Arg, T>(f); } +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ template<class Result, class Arg, class T> inline ConstMemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg) const) { return ConstMemFunc1<Result, Arg, T>(f); } +template<class Result, class T> +class MemFuncRef0 : public UnaryFunction<T &, Result> { +private: + Result (T::*_func)(); +public: + typedef Result (T::*FuncType)(); + + MemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(T &v) const { + return (v.*_func)(); + } +}; + +template<class Result, class T> +class ConstMemFuncRef0 : public UnaryFunction<T &, Result> { +private: + Result (T::*_func)() const; +public: + typedef Result (T::*FuncType)() const; + + ConstMemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(const T &v) const { + return (v.*_func)(); + } +}; + +template<class Result, class Arg, class T> +class MemFuncRef1 : public BinaryFunction<T &, Arg, Result> { +private: + Result (T::*_func)(Arg); +public: + typedef Result (T::*FuncType)(Arg); + + MemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +template<class Result, class Arg, class T> +class ConstMemFuncRef1 : public BinaryFunction<T &, Arg, Result> { +private: + Result (T::*_func)(Arg) const; +public: + typedef Result (T::*FuncType)(Arg) const; + + ConstMemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(const T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template<class Result, class T> +inline MemFuncRef0<Result, T> mem_fun_ref(Result (T::*f)()) { + return MemFuncRef0<Result, T>(f); +} + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template<class Result, class T> +inline ConstMemFuncRef0<Result, T> mem_fun_Ref(Result (T::*f)() const) { + return ConstMemFuncRef0<Result, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline MemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg)) { + return MemFuncRef1<Result, Arg, T>(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template<class Result, class Arg, class T> +inline ConstMemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg) const) { + return ConstMemFuncRef1<Result, Arg, T>(f); +} + // functor code +/** + * Generic functor object for function objects without parameters. + * + * @see Functor1 + */ template<class Res> struct Functor0 { virtual ~Functor0() {} @@ -211,6 +366,18 @@ struct Functor0 { virtual Res operator()() const = 0; }; +/** + * Functor object for a class member function without parameter. + * + * Example creation: + * + * Foo bar; + * Functor0Men<void, Foo> myFunctor(&bar, &Foo::myFunc); + * + * Example usage: + * + * myFunctor(); + */ template<class Res, class T> class Functor0Mem : public Functor0<Res> { public: @@ -218,7 +385,7 @@ public: Functor0Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()() const { return (_t->*_func)(); } @@ -227,6 +394,38 @@ private: const FuncType _func; }; +/** + * Generic functor object for unary function objects. + * + * A typical usage for an unary function object is for executing opcodes + * in a script interpreter. To achieve that one can create an Common::Array + * object with 'Functor1<Arg, Res> *' as type. Now after the right engine version + * has been determined and the opcode table to use is found one could easily + * add the opcode implementations like this: + * + * Common::Array<Functor1<ScriptState, void> *> opcodeTable; + * opcodeTable[0] = new Functor1Mem<ScriptState, void, MyEngine_v1>(&myEngine, &MyEngine_v1::o1_foo); + * opcodeTable[1] = new Functor1Mem<ScriptState, void, MyEngine_v2>(&myEngine, &MyEngine_v2::o2_foo); + * // unimplemented/unused opcode + * opcodeTable[2] = 0; + * etc. + * + * This makes it easy to add member functions of different classes as + * opcode functions to the function table. Since with the generic + * Functor1<ScriptState, void> object the only requirement for an + * function to be used is 'ScriptState' as argument and 'void' as return + * value. + * + * Now for calling the opcodes one has simple to do: + * if (opcodeTable[opcodeNum] && opcodeTable[opcodeNum]->isValid()) + * (*opcodeTable[opcodeNum])(scriptState); + * else + * warning("Unimplemented opcode %d", opcodeNum); + * + * If you want to see an real world example check the kyra engine. + * Files: engines/kyra/script.cpp and .h and engine/kyra/script_*.cpp + * are interesting for that matter. + */ template<class Arg, class Res> struct Functor1 : public Common::UnaryFunction<Arg, Res> { virtual ~Functor1() {} @@ -235,6 +434,13 @@ struct Functor1 : public Common::UnaryFunction<Arg, Res> { virtual Res operator()(Arg) const = 0; }; +/** + * Functor object for an unary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take one parameter though. + * + * @see Functor0Men + */ template<class Arg, class Res, class T> class Functor1Mem : public Functor1<Arg, Res> { public: @@ -242,7 +448,7 @@ public: Functor1Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()(Arg v1) const { return (_t->*_func)(v1); } @@ -251,6 +457,11 @@ private: const FuncType _func; }; +/** + * Generic functor object for binary function objects. + * + * @see Functor1 + */ template<class Arg1, class Arg2, class Res> struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> { virtual ~Functor2() {} @@ -259,6 +470,13 @@ struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> { virtual Res operator()(Arg1, Arg2) const = 0; }; +/** + * Functor object for a binary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take two parameter though. + * + * @see Functor0Men + */ template<class Arg1, class Arg2, class Res, class T> class Functor2Mem : public Functor2<Arg1, Arg2, Res> { public: @@ -266,7 +484,7 @@ public: Functor2Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()(Arg1 v1, Arg2 v2) const { return (_t->*_func)(v1, v2); } diff --git a/common/hashmap.h b/common/hashmap.h index 1bae44e98e..69f367de97 100644 --- a/common/hashmap.h +++ b/common/hashmap.h @@ -65,8 +65,12 @@ // on every system we support, so we should get rid of this. // The solution should be to write a simple placement new // on our own. + +// Symbian does not have <new> but the new operator +#if !defined(__SYMBIAN32__) #include <new> #endif +#endif namespace Common { diff --git a/common/ptr.h b/common/ptr.h index eea3c39882..c6fcaa4f75 100644 --- a/common/ptr.h +++ b/common/ptr.h @@ -121,7 +121,7 @@ public: ~SharedPtr() { decRef(); } - SharedPtr &operator =(const SharedPtr &r) { + SharedPtr &operator=(const SharedPtr &r) { if (r._refCount) ++(*r._refCount); decRef(); @@ -134,7 +134,7 @@ public: } template<class T2> - SharedPtr &operator =(const SharedPtr<T2> &r) { + SharedPtr &operator=(const SharedPtr<T2> &r) { if (r._refCount) ++(*r._refCount); decRef(); @@ -146,8 +146,8 @@ public: return *this; } - ValueType &operator *() const { assert(_pointer); return *_pointer; } - Pointer operator ->() const { assert(_pointer); return _pointer; } + 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 @@ -171,6 +171,16 @@ public: bool unique() const { return refCount() == 1; } /** + * Resets the SharedPtr object to a NULL pointer. + */ + void reset() { + decRef(); + _deletion = 0; + _refCount = 0; + _pointer = 0; + } + + /** * Returns the number of references to the assigned pointer. * This should just be used for debugging purposes. */ @@ -199,12 +209,12 @@ private: } // end of namespace Common template<class T1, class T2> -bool operator ==(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) { +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) { +bool operator!=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) { return l.get() != r.get(); } diff --git a/common/rect.h b/common/rect.h index f71124434a..dcf1c8b421 100644 --- a/common/rect.h +++ b/common/rect.h @@ -177,6 +177,10 @@ struct Rect { clip(Rect(0, 0, maxw, maxh)); } + bool isEmpty() const { + return (left >= right || top >= bottom); + } + bool isValidRect() const { return (left <= right && top <= bottom); } diff --git a/common/savefile.h b/common/savefile.h index f30ddfc160..d44f946d48 100644 --- a/common/savefile.h +++ b/common/savefile.h @@ -39,27 +39,14 @@ namespace Common { * That typically means "save games", but also includes things like the * IQ points in Indy3. */ -class InSaveFile : public SeekableReadStream {}; +typedef SeekableReadStream InSaveFile; /** * A class which allows game engines to save game state data. * That typically means "save games", but also includes things like the * IQ points in Indy3. */ -class OutSaveFile : public WriteStream { -public: - /** - * Close this savefile, to be called right before destruction of this - * savefile. The idea is that this ways, I/O errors that occur - * during closing/flushing of the file can still be handled by the - * game engine. - * - * By default, this just flushes the stream. - */ - virtual void finalize() { - flush(); - } -}; +typedef WriteStream OutSaveFile; /** diff --git a/common/str.cpp b/common/str.cpp index a2e6e0c66d..5f8d4ffb7e 100644 --- a/common/str.cpp +++ b/common/str.cpp @@ -111,6 +111,74 @@ String::~String() { decRefCount(_extern._refCount); } +void String::makeUnique() { + ensureCapacity(_len, true); +} + +/** + * Ensure that enough storage is available to store at least new_len + * characters plus a null byte. In addition, if we currently share + * the storage with another string, unshare it, so that we can safely + * write to the storage. + */ +void String::ensureCapacity(uint32 new_len, bool keep_old) { + bool isShared; + uint32 curCapacity, newCapacity; + char *newStorage; + int *oldRefCount = _extern._refCount; + + if (isStorageIntern()) { + isShared = false; + curCapacity = _builtinCapacity - 1; + } else { + isShared = (oldRefCount && *oldRefCount > 1); + curCapacity = _extern._capacity; + } + + // Special case: If there is enough space, and we do not share + // the storage, then there is nothing to do. + if (!isShared && new_len <= curCapacity) + return; + + if (isShared && new_len <= _builtinCapacity - 1) { + // We share the storage, but there is enough internal storage: Use that. + newStorage = _storage; + newCapacity = _builtinCapacity - 1; + } else { + // We need to allocate storage on the heap! + + // Compute a suitable new capacity limit + newCapacity = computeCapacity(new_len); + + // Allocate new storage + newStorage = (char *)malloc(newCapacity+1); + assert(newStorage); + } + + // Copy old data if needed, elsewise reset the new storage. + if (keep_old) { + assert(_len <= newCapacity); + memcpy(newStorage, _str, _len + 1); + } else { + _len = 0; + newStorage[0] = 0; + } + + // Release hold on the old storage ... + decRefCount(oldRefCount); + + // ... in favor of the new storage + _str = newStorage; + + if (!isStorageIntern()) { + // Set the ref count & capacity if we use an external storage. + // It is important to do this *after* copying any old content, + // else we would override data that has not yet been copied! + _extern._refCount = 0; + _extern._capacity = newCapacity; + } +} + void String::incRefCount() const { assert(!isStorageIntern()); if (_extern._refCount == 0) { @@ -170,7 +238,8 @@ String &String::operator =(const String &str) { } String& String::operator =(char c) { - ensureCapacity(1, false); + decRefCount(_extern._refCount); + _str = _storage; _len = 1; _str[0] = c; _str[1] = 0; @@ -253,10 +322,7 @@ void String::deleteLastChar() { void String::deleteChar(uint32 p) { assert(p < _len); - // Call ensureCapacity to make sure we actually *own* the storage - // to which _str points to -- we wouldn't want to modify a storage - // which other string objects are sharing, after all. - ensureCapacity(_len, true); + makeUnique(); while (p++ < _len) _str[p-1] = _str[p]; _len--; @@ -273,7 +339,7 @@ void String::clear() { void String::setChar(char c, uint32 p) { assert(p <= _len); - ensureCapacity(_len, true); + makeUnique(); _str[p] = c; } @@ -288,78 +354,36 @@ void String::insertChar(char c, uint32 p) { } void String::toLowercase() { - ensureCapacity(_len, true); + makeUnique(); for (uint32 i = 0; i < _len; ++i) _str[i] = tolower(_str[i]); } void String::toUppercase() { - ensureCapacity(_len, true); + makeUnique(); for (uint32 i = 0; i < _len; ++i) _str[i] = toupper(_str[i]); } -/** - * Ensure that enough storage is available to store at least new_len - * characters plus a null byte. In addition, if we currently share - * the storage with another string, unshare it, so that we can safely - * write to the storage. - */ -void String::ensureCapacity(uint32 new_len, bool keep_old) { - bool isShared; - uint32 curCapacity, newCapacity; - char *newStorage; - int *oldRefCount = _extern._refCount; - - if (isStorageIntern()) { - isShared = false; - curCapacity = _builtinCapacity - 1; - } else { - isShared = (oldRefCount && *oldRefCount > 1); - curCapacity = _extern._capacity; - } - - // Special case: If there is enough space, and we do not share - // the storage, then there is nothing to do. - if (!isShared && new_len <= curCapacity) +void String::trim() { + if (_len == 0) return; - if (isShared && new_len <= _builtinCapacity - 1) { - // We share the storage, but there is enough internal storage: Use that. - newStorage = _storage; - newCapacity = _builtinCapacity - 1; - } else { - // We need to allocate storage on the heap! - - // Compute a suitable new capacity limit - newCapacity = computeCapacity(new_len); + makeUnique(); - // Allocate new storage - newStorage = (char *)malloc(newCapacity+1); - assert(newStorage); - } - - // Copy old data if needed, elsewise reset the new storage. - if (keep_old) { - assert(_len <= newCapacity); - memcpy(newStorage, _str, _len + 1); - } else { - _len = 0; - newStorage[0] = 0; - } - - // Release hold on the old storage ... - decRefCount(oldRefCount); + // Trim trailing whitespace + while (_len >= 1 && isspace(_str[_len-1])) + _len--; + _str[_len] = 0; - // ... in favor of the new storage - _str = newStorage; + // Trim leading whitespace + char *t = _str; + while (isspace(*t)) + t++; - if (!isStorageIntern()) { - // Set the ref count & capacity if we use an external storage. - // It is important to do this *after* copying any old content, - // else we would override data that has not yet been copied! - _extern._refCount = 0; - _extern._capacity = newCapacity; + if (t != _str) { + _len -= t - _str; + memmove(_str, t, _len + 1); } } diff --git a/common/str.h b/common/str.h index ae9cb992b6..3479fee8e4 100644 --- a/common/str.h +++ b/common/str.h @@ -177,6 +177,8 @@ public: void toLowercase(); void toUppercase(); + void trim(); + uint hash() const; public: @@ -200,6 +202,7 @@ public: } protected: + void makeUnique(); void ensureCapacity(uint32 new_len, bool keep_old); void incRefCount() const; void decRefCount(int *oldRefCount); diff --git a/common/stream.cpp b/common/stream.cpp index 61166fd451..e06cc28415 100644 --- a/common/stream.cpp +++ b/common/stream.cpp @@ -242,4 +242,83 @@ void SeekableSubReadStream::seek(int32 offset, int whence) { _parentStream->seek(_pos); } +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) { +} + +void 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); + } +} + } // End of namespace Common diff --git a/common/stream.h b/common/stream.h index 4cf5fae114..01a946e685 100644 --- a/common/stream.h +++ b/common/stream.h @@ -78,6 +78,22 @@ public: */ virtual void flush() {} + /** + * Finalize and close this stream. To be called right before this + * stream instance is deleted. The goal here is to enable calling + * code to detect and handle I/O errors which might occur when + * closing (and this flushing, if buffered) the stream. + * + * After this method has been called, no further writes may be + * peformed on the stream. Calling ioFailed() is allowed. + * + * By default, this just flushes the stream. + */ + virtual void finalize() { + flush(); + } + + // The remaining methods all have default implementations; subclasses // need not (and should not) overload them. @@ -350,15 +366,17 @@ public: class SubReadStream : virtual public ReadStream { protected: ReadStream *_parentStream; + bool _disposeParentStream; uint32 _pos; uint32 _end; - bool _disposeParentStream; public: SubReadStream(ReadStream *parentStream, uint32 end, bool disposeParentStream = false) : _parentStream(parentStream), + _disposeParentStream(disposeParentStream), _pos(0), - _end(end), - _disposeParentStream(disposeParentStream) {} + _end(end) { + assert(parentStream); + } ~SubReadStream() { if (_disposeParentStream) delete _parentStream; } @@ -414,6 +432,48 @@ public: } }; +/** + * Wrapper class which adds buffering to any given ReadStream. + * Users can specify how big the buffer should be, and whether the + * wrapped stream should be disposed when the wrapper is disposed. + */ +class BufferedReadStream : virtual public ReadStream { +protected: + ReadStream *_parentStream; + bool _disposeParentStream; + byte *_buf; + uint32 _pos; + uint32 _bufSize; + uint32 _realBufSize; + +public: + BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + ~BufferedReadStream(); + + virtual bool eos() const { return (_pos == _bufSize) && _parentStream->eos(); } + virtual bool ioFailed() const { return _parentStream->ioFailed(); } + virtual void clearIOFailed() { _parentStream->clearIOFailed(); } + + virtual uint32 read(void *dataPtr, uint32 dataSize); +}; + +/** + * Wrapper class which adds buffering to any given SeekableReadStream. + * @see BufferedReadStream + */ +class BufferedSeekableReadStream : public BufferedReadStream, public SeekableReadStream { +protected: + SeekableReadStream *_parentStream; +public: + BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + + virtual uint32 pos() const { return _parentStream->pos() - (_bufSize - _pos); } + virtual uint32 size() const { return _parentStream->size(); } + + virtual void seek(int32 offset, int whence = SEEK_SET); +}; + + /** * Simple memory based 'stream', which implements the ReadStream interface for @@ -514,9 +574,9 @@ public: uint32 size() const { return _bufSize; } }; -/** +/** * A sort of hybrid between MemoryWriteStream and Array classes. A stream - * that grows as it's written to. + * that grows as it's written to. */ class MemoryWriteStreamDynamic : public Common::WriteStream { private: diff --git a/common/system.cpp b/common/system.cpp index 8d528258f4..e3f81a69b6 100644 --- a/common/system.cpp +++ b/common/system.cpp @@ -29,6 +29,10 @@ #include "common/config-manager.h" #include "common/system.h" #include "common/timer.h" +#if defined(WIN32) && defined(ARRAYSIZE) +// winnt.h defines ARRAYSIZE, but we want our own one... - this is needed before including util.h +#undef ARRAYSIZE +#endif #include "common/util.h" #include "graphics/colormasks.h" @@ -121,3 +125,70 @@ void OSystem::clearScreen() { memset(screen->pixels, 0, screen->h * screen->pitch); unlockScreen(); } + + +/* +FIXME: The config file loading code below needs to be cleaned up. + Port specific variants should be pushed into the respective ports. + + Ideally, the default OSystem::openConfigFileForReading/Writing methods + should be removed completely. +*/ + +#include "common/file.h" + +#ifdef __PLAYSTATION2__ +#include "backends/platform/ps2/systemps2.h" +#endif + +#ifdef IPHONE +#include "backends/platform/iphone/osys_iphone.h" +#endif + + +#if defined(UNIX) +#define DEFAULT_CONFIG_FILE ".scummvmrc" +#else +#define DEFAULT_CONFIG_FILE "scummvm.ini" +#endif + +static Common::String getDefaultConfigFileName() { + char configFile[MAXPATHLEN]; +#if defined(PALMOS_MODE) + strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE); +#elif defined(IPHONE) + strcpy(configFile, OSystem_IPHONE::getConfigPath()); +#elif defined(__PLAYSTATION2__) + ((OSystem_PS2*)g_system)->makeConfigPath(configFile); +#elif defined(__PSP__) + strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE); +#else + strcpy(configFile, DEFAULT_CONFIG_FILE); +#endif + + return configFile; +} + +Common::SeekableReadStream *OSystem::openConfigFileForReading() { + Common::File *confFile = new Common::File(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +} + +Common::WriteStream *OSystem::openConfigFileForWriting() { +#ifdef __DC__ + return 0; +#else + Common::DumpFile *confFile = new Common::DumpFile(); + assert(confFile); + if (!confFile->open(getDefaultConfigFileName())) { + delete confFile; + confFile = 0; + } + return confFile; +#endif +} diff --git a/common/system.h b/common/system.h index b895a5cfba..501d0802fd 100644 --- a/common/system.h +++ b/common/system.h @@ -44,6 +44,8 @@ namespace Common { class EventManager; class SaveFileManager; class TimerManager; + class SeekableReadStream; + class WriteStream; } class FilesystemFactory; @@ -900,10 +902,25 @@ public: /** * Returns the FilesystemFactory object, depending on the current architecture. * - * @return FilesystemFactory* The specific factory for the current architecture. + * @return the FSNode factory for the current architecture */ virtual FilesystemFactory *getFilesystemFactory() = 0; + /** + * Open the default config file for reading, by returning a suitable + * ReadStream instance. It is the callers responsiblity to delete + * the stream after use. + */ + virtual Common::SeekableReadStream *openConfigFileForReading(); + + /** + * Open the default config file for writing, by returning a suitable + * WriteStream instance. It is the callers responsiblity to delete + * the stream after use. + * + * May return 0 to indicate that writing to config file is not possible. + */ + virtual Common::WriteStream *openConfigFileForWriting(); /** * Return String which is used for backend-specific addition to theme diff --git a/common/unarj.cpp b/common/unarj.cpp index f3ac20c285..da88c11fc9 100644 --- a/common/unarj.cpp +++ b/common/unarj.cpp @@ -231,7 +231,7 @@ ArjHeader *ArjFile::readHeader() { } -bool ArjFile::open(const Common::String &filename, AccessMode mode) { +bool ArjFile::open(const Common::String &filename) { if (_isOpen) error("Attempt to open another instance of archive"); diff --git a/common/unarj.h b/common/unarj.h index b015999671..c8965968f6 100644 --- a/common/unarj.h +++ b/common/unarj.h @@ -110,7 +110,7 @@ public: void registerArchive(const String &filename); - bool open(const Common::String &filename, AccessMode mode = kFileReadMode); + bool open(const Common::String &filename); void close(); uint32 read(void *dataPtr, uint32 dataSize); |