aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sci/console.cpp21
-rw-r--r--engines/sci/engine/file.cpp308
-rw-r--r--engines/sci/engine/file.h66
-rw-r--r--engines/sci/engine/kernel.h3
-rw-r--r--engines/sci/engine/kfile.cpp343
-rw-r--r--engines/sci/engine/state.h43
6 files changed, 401 insertions, 383 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 5b5301b468..6a44972a4b 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -1216,6 +1216,27 @@ bool Console::cmdRestartGame(int argc, const char **argv) {
return Cmd_Exit(0, 0);
}
+// The scripts get IDs ranging from 100->199, because the scripts require us to assign unique ids THAT EVEN STAY BETWEEN
+// SAVES and the scripts also use "saves-count + 1" to create a new savedgame slot.
+// SCI1.1 actually recycles ids, in that case we will currently get "0".
+// This behavior is required especially for LSL6. In this game, it's possible to quick save. The scripts will use
+// the last-used id for that feature. If we don't assign sticky ids, the feature will overwrite different saves all the
+// time. And sadly we can't just use the actual filename ids directly, because of the creation method for new slots.
+
+extern void listSavegames(Common::Array<SavegameDesc> &saves);
+
+bool Console::cmdListSaves(int argc, const char **argv) {
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ for (uint i = 0; i < saves.size(); i++) {
+ Common::String filename = g_sci->getSavegameName(saves[i].id);
+ DebugPrintf("%s: '%s'\n", filename.c_str(), saves[i].name);
+ }
+
+ return true;
+}
+
bool Console::cmdClassTable(int argc, const char **argv) {
DebugPrintf("Available classes (parse a parameter to filter the table by a specific class):\n");
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 8876f3c46c..66f54d67df 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -25,9 +25,315 @@
#include "sci/sci.h"
#include "sci/engine/file.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/savegame.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
namespace Sci {
+/*
+ * Note on how file I/O is implemented: In ScummVM, one can not create/write
+ * arbitrary data files, simply because many of our target platforms do not
+ * support this. The only files one can create are savestates. But SCI has an
+ * opcode to create and write to seemingly 'arbitrary' files. This is mainly
+ * used in LSL3 for LARRY3.DRV (which is a game data file, not a driver, used
+ * for persisting the results of the "age quiz" across restarts) and in LSL5
+ * for MEMORY.DRV (which is again a game data file and contains the game's
+ * password, XOR encrypted).
+ * To implement that opcode, we combine the SaveFileManager with regular file
+ * code, similarly to how the SCUMM HE engine does it.
+ *
+ * To handle opening a file called "foobar", what we do is this: First, we
+ * create an 'augmented file name', by prepending the game target and a dash,
+ * so if we running game target sq1sci, the name becomes "sq1sci-foobar".
+ * Next, we check if such a file is known to the SaveFileManager. If so, we
+ * we use that for reading/writing, delete it, whatever.
+ *
+ * If no such file is present but we were only asked to *read* the file,
+ * we fallback to looking for a regular file called "foobar", and open that
+ * for reading only.
+ */
+
+reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename) {
+ Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH);
+ Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName;
+ Common::SeekableReadStream *inFile = 0;
+ Common::WriteStream *outFile = 0;
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+
+ if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+ // Try to open file, abort if not possible
+ inFile = saveFileMan->openForLoading(wrappedName);
+ // If no matching savestate exists: fall back to reading from a regular
+ // file
+ if (!inFile)
+ inFile = SearchMan.createReadStreamForMember(englishName);
+
+ if (!inFile)
+ debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str());
+ } else if (mode == _K_FILE_MODE_CREATE) {
+ // Create the file, destroying any content it might have had
+ outFile = saveFileMan->openForSaving(wrappedName);
+ if (!outFile)
+ debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str());
+ } else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) {
+ // Try to open file, create it if it doesn't exist
+ outFile = saveFileMan->openForSaving(wrappedName);
+ if (!outFile)
+ debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str());
+
+ // QfG1 opens the character export file with _K_FILE_MODE_CREATE first,
+ // closes it immediately and opens it again with this here. Perhaps
+ // other games use this for read access as well. I guess changing this
+ // whole code into using virtual files and writing them after close
+ // would be more appropriate.
+ } else {
+ error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str());
+ }
+
+ if (!inFile && !outFile) { // Failed
+ debugC(kDebugLevelFile, " -> file_open() failed");
+ return SIGNAL_REG;
+ }
+
+ // Find a free file handle
+ uint handle = 1; // Ignore _fileHandles[0]
+ while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
+ handle++;
+
+ if (handle == s->_fileHandles.size()) {
+ // Hit size limit => Allocate more space
+ s->_fileHandles.resize(s->_fileHandles.size() + 1);
+ }
+
+ s->_fileHandles[handle]._in = inFile;
+ s->_fileHandles[handle]._out = outFile;
+ s->_fileHandles[handle]._name = englishName;
+
+ debugC(kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle);
+ return make_reg(0, handle);
+}
+
+FileHandle *getFileFromHandle(EngineState *s, uint handle) {
+ if (handle == 0 || handle == VIRTUALFILE_HANDLE) {
+ error("Attempt to use invalid file handle (%d)", handle);
+ return 0;
+ }
+
+ if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) {
+ warning("Attempt to use invalid/unused file handle %d", handle);
+ return 0;
+ }
+
+ return &s->_fileHandles[handle];
+}
+
+int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (!f)
+ return 0;
+
+ if (!f->_in) {
+ error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
+ return 0;
+ }
+ int readBytes = 0;
+ if (maxsize > 1) {
+ memset(dest, 0, maxsize);
+ f->_in->readLine(dest, maxsize);
+ readBytes = strlen(dest); // FIXME: sierra sci returned byte count and didn't react on NUL characters
+ // The returned string must not have an ending LF
+ if (readBytes > 0) {
+ if (dest[readBytes - 1] == 0x0A)
+ dest[readBytes - 1] = 0;
+ }
+ } else {
+ *dest = 0;
+ }
+
+ debugC(kDebugLevelFile, " -> FGets'ed \"%s\"", dest);
+ return readBytes;
+}
+
+static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) {
+ if (l.date != r.date)
+ return (l.date > r.date);
+ return (l.time > r.time);
+}
+
+// Create a sorted array containing all found savedgames
+void listSavegames(Common::Array<SavegameDesc> &saves) {
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+
+ // Load all saves
+ Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
+
+ for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
+ Common::String filename = *iter;
+ Common::SeekableReadStream *in;
+ if ((in = saveFileMan->openForLoading(filename))) {
+ SavegameMetadata meta;
+ if (!get_savegame_metadata(in, &meta) || meta.name.empty()) {
+ // invalid
+ delete in;
+ continue;
+ }
+ delete in;
+
+ SavegameDesc desc;
+ desc.id = strtol(filename.end() - 3, NULL, 10);
+ desc.date = meta.saveDate;
+ // We need to fix date in here, because we save DDMMYYYY instead of
+ // YYYYMMDD, so sorting wouldn't work
+ desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24);
+ desc.time = meta.saveTime;
+ desc.version = meta.version;
+
+ if (meta.name.lastChar() == '\n')
+ meta.name.deleteLastChar();
+
+ Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH);
+
+ debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id);
+
+ saves.push_back(desc);
+ }
+ }
+
+ // Sort the list by creation date of the saves
+ Common::sort(saves.begin(), saves.end(), _savegame_sort_byDate);
+}
+
+// Find a savedgame according to virtualId and return the position within our array
+int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) {
+ for (uint saveNr = 0; saveNr < saves.size(); saveNr++) {
+ if (saves[saveNr].id == savegameId)
+ return saveNr;
+ }
+ return -1;
+}
+
+
+FileHandle::FileHandle() : _in(0), _out(0) {
+}
+
+FileHandle::~FileHandle() {
+ close();
+}
+
+void FileHandle::close() {
+ delete _in;
+ delete _out;
+ _in = 0;
+ _out = 0;
+ _name.clear();
+}
+
+bool FileHandle::isOpen() const {
+ return _in || _out;
+}
+
+
+void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) {
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask);
+ if (!foundFiles.empty()) {
+ _files.push_back(title);
+ _virtualFiles.push_back("");
+ Common::StringArray::iterator it;
+ Common::StringArray::iterator it_end = foundFiles.end();
+
+ for (it = foundFiles.begin(); it != it_end; it++) {
+ Common::String regularFilename = *it;
+ Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1);
+
+ Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename);
+ int32 testfileSize = testfile->size();
+ delete testfile;
+ if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game.
+ continue; // and we dont want to have those in the list
+ // We need to remove the prefix for display purposes
+ _files.push_back(wrappedFilename);
+ // but remember the actual name as well
+ _virtualFiles.push_back(regularFilename);
+ }
+ }
+}
+
+Common::String DirSeeker::getVirtualFilename(uint fileNumber) {
+ if (fileNumber >= _virtualFiles.size())
+ error("invalid virtual filename access");
+ return _virtualFiles[fileNumber];
+}
+
+reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) {
+ // Verify that we are given a valid buffer
+ if (!buffer.segment) {
+ error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str());
+ return NULL_REG;
+ }
+ _outbuffer = buffer;
+ _files.clear();
+ _virtualFiles.clear();
+
+ int QfGImport = g_sci->inQfGImportRoom();
+ if (QfGImport) {
+ _files.clear();
+ addAsVirtualFiles("-QfG1-", "qfg1-*");
+ addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*");
+ if (QfGImport > 2)
+ addAsVirtualFiles("-QfG2-", "qfg2-*");
+ if (QfGImport > 3)
+ addAsVirtualFiles("-QfG3-", "qfg3-*");
+
+ if (QfGImport == 3) {
+ // QfG3 sorts the filelisting itself, we can't let that happen otherwise our
+ // virtual list would go out-of-sync
+ reg_t savedHeros = segMan->findObjectByName("savedHeros");
+ if (!savedHeros.isNull())
+ writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0);
+ }
+
+ } else {
+ // Prefix the mask
+ const Common::String wrappedMask = g_sci->wrapFilename(mask);
+
+ // Obtain a list of all files matching the given mask
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ _files = saveFileMan->listSavefiles(wrappedMask);
+ }
+
+ // Reset the list iterator and write the first match to the output buffer,
+ // if any.
+ _iter = _files.begin();
+ return nextFile(segMan);
+}
+
+reg_t DirSeeker::nextFile(SegManager *segMan) {
+ if (_iter == _files.end()) {
+ return NULL_REG;
+ }
+
+ Common::String string;
+
+ if (_virtualFiles.empty()) {
+ // Strip the prefix, if we don't got a virtual filelisting
+ const Common::String wrappedString = *_iter;
+ string = g_sci->unwrapFilename(wrappedString);
+ } else {
+ string = *_iter;
+ }
+ if (string.size() > 12)
+ string = Common::String(string.c_str(), 12);
+ segMan->strcpy(_outbuffer, string.c_str());
+
+ // Return the result and advance the list iterator :)
+ ++_iter;
+ return _outbuffer;
+}
+
+
#ifdef ENABLE_SCI32
VirtualIndexFile::VirtualIndexFile(Common::String fileName) : _fileName(fileName), _changed(false) {
@@ -111,7 +417,7 @@ bool VirtualIndexFile::seek(int32 offset, int whence) {
_ptr += offset;
break;
case SEEK_SET:
- assert(offset < _bufferSize);
+ assert(offset < (int32)_bufferSize);
_ptr = _buffer + offset;
break;
case SEEK_END:
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
index e7b1090c91..896dcd2a42 100644
--- a/engines/sci/engine/file.h
+++ b/engines/sci/engine/file.h
@@ -23,10 +23,74 @@
#ifndef SCI_ENGINE_FILE_H
#define SCI_ENGINE_FILE_H
-#include "common/scummsys.h"
+#include "common/str-array.h"
+#include "common/stream.h"
namespace Sci {
+enum {
+ _K_FILE_MODE_OPEN_OR_CREATE = 0,
+ _K_FILE_MODE_OPEN_OR_FAIL = 1,
+ _K_FILE_MODE_CREATE = 2
+};
+
+/* Maximum length of a savegame name (including terminator character). */
+#define SCI_MAX_SAVENAME_LENGTH 0x24
+
+enum {
+ MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */
+};
+
+#define VIRTUALFILE_HANDLE 200
+#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir"
+
+struct SavegameDesc {
+ int16 id;
+ int virtualId; // straight numbered, according to id but w/o gaps
+ int date;
+ int time;
+ int version;
+ char name[SCI_MAX_SAVENAME_LENGTH];
+};
+
+class FileHandle {
+public:
+ Common::String _name;
+ Common::SeekableReadStream *_in;
+ Common::WriteStream *_out;
+
+public:
+ FileHandle();
+ ~FileHandle();
+
+ void close();
+ bool isOpen() const;
+};
+
+
+class DirSeeker {
+protected:
+ reg_t _outbuffer;
+ Common::StringArray _files;
+ Common::StringArray _virtualFiles;
+ Common::StringArray::const_iterator _iter;
+
+public:
+ DirSeeker() {
+ _outbuffer = NULL_REG;
+ _iter = _files.begin();
+ }
+
+ reg_t firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan);
+ reg_t nextFile(SegManager *segMan);
+
+ Common::String getVirtualFilename(uint fileNumber);
+
+private:
+ void addAsVirtualFiles(Common::String title, Common::String fileMask);
+};
+
+
#ifdef ENABLE_SCI32
/**
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index d9fdbefbc2..25ca082a9a 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -276,9 +276,6 @@ private:
const Common::String _invalid;
};
-/* Maximum length of a savegame name (including terminator character). */
-#define SCI_MAX_SAVENAME_LENGTH 0x24
-
/******************** Kernel functions ********************/
reg_t kStrLen(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index dddbc2710e..36e5273b06 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -41,125 +41,11 @@
namespace Sci {
-struct SavegameDesc {
- int16 id;
- int virtualId; // straight numbered, according to id but w/o gaps
- int date;
- int time;
- int version;
- char name[SCI_MAX_SAVENAME_LENGTH];
-};
-
-/*
- * Note on how file I/O is implemented: In ScummVM, one can not create/write
- * arbitrary data files, simply because many of our target platforms do not
- * support this. The only files one can create are savestates. But SCI has an
- * opcode to create and write to seemingly 'arbitrary' files. This is mainly
- * used in LSL3 for LARRY3.DRV (which is a game data file, not a driver, used
- * for persisting the results of the "age quiz" across restarts) and in LSL5
- * for MEMORY.DRV (which is again a game data file and contains the game's
- * password, XOR encrypted).
- * To implement that opcode, we combine the SaveFileManager with regular file
- * code, similarly to how the SCUMM HE engine does it.
- *
- * To handle opening a file called "foobar", what we do is this: First, we
- * create an 'augmented file name', by prepending the game target and a dash,
- * so if we running game target sq1sci, the name becomes "sq1sci-foobar".
- * Next, we check if such a file is known to the SaveFileManager. If so, we
- * we use that for reading/writing, delete it, whatever.
- *
- * If no such file is present but we were only asked to *read* the file,
- * we fallback to looking for a regular file called "foobar", and open that
- * for reading only.
- */
-
-FileHandle::FileHandle() : _in(0), _out(0) {
-}
-
-FileHandle::~FileHandle() {
- close();
-}
-
-void FileHandle::close() {
- delete _in;
- delete _out;
- _in = 0;
- _out = 0;
- _name.clear();
-}
-
-bool FileHandle::isOpen() const {
- return _in || _out;
-}
-
-enum {
- _K_FILE_MODE_OPEN_OR_CREATE = 0,
- _K_FILE_MODE_OPEN_OR_FAIL = 1,
- _K_FILE_MODE_CREATE = 2
-};
-
-#define VIRTUALFILE_HANDLE 200
-#define PHANTASMAGORIA_SAVEGAME_INDEX "phantsg.dir"
-
-reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename) {
- Common::String englishName = g_sci->getSciLanguageString(filename, K_LANG_ENGLISH);
- Common::String wrappedName = unwrapFilename ? g_sci->wrapFilename(englishName) : englishName;
- Common::SeekableReadStream *inFile = 0;
- Common::WriteStream *outFile = 0;
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
-
- if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
- // Try to open file, abort if not possible
- inFile = saveFileMan->openForLoading(wrappedName);
- // If no matching savestate exists: fall back to reading from a regular
- // file
- if (!inFile)
- inFile = SearchMan.createReadStreamForMember(englishName);
-
- if (!inFile)
- debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_OPEN_OR_FAIL): failed to open file '%s'", englishName.c_str());
- } else if (mode == _K_FILE_MODE_CREATE) {
- // Create the file, destroying any content it might have had
- outFile = saveFileMan->openForSaving(wrappedName);
- if (!outFile)
- debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str());
- } else if (mode == _K_FILE_MODE_OPEN_OR_CREATE) {
- // Try to open file, create it if it doesn't exist
- outFile = saveFileMan->openForSaving(wrappedName);
- if (!outFile)
- debugC(kDebugLevelFile, " -> file_open(_K_FILE_MODE_CREATE): failed to create file '%s'", englishName.c_str());
-
- // QfG1 opens the character export file with _K_FILE_MODE_CREATE first,
- // closes it immediately and opens it again with this here. Perhaps
- // other games use this for read access as well. I guess changing this
- // whole code into using virtual files and writing them after close
- // would be more appropriate.
- } else {
- error("file_open: unsupported mode %d (filename '%s')", mode, englishName.c_str());
- }
-
- if (!inFile && !outFile) { // Failed
- debugC(kDebugLevelFile, " -> file_open() failed");
- return SIGNAL_REG;
- }
-
- // Find a free file handle
- uint handle = 1; // Ignore _fileHandles[0]
- while ((handle < s->_fileHandles.size()) && s->_fileHandles[handle].isOpen())
- handle++;
-
- if (handle == s->_fileHandles.size()) {
- // Hit size limit => Allocate more space
- s->_fileHandles.resize(s->_fileHandles.size() + 1);
- }
-
- s->_fileHandles[handle]._in = inFile;
- s->_fileHandles[handle]._out = outFile;
- s->_fileHandles[handle]._name = englishName;
-
- debugC(kDebugLevelFile, " -> opened file '%s' with handle %d", englishName.c_str(), handle);
- return make_reg(0, handle);
-}
+extern reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool unwrapFilename);
+extern FileHandle *getFileFromHandle(EngineState *s, uint handle);
+extern int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle);
+extern void listSavegames(Common::Array<SavegameDesc> &saves);
+extern int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId);
reg_t kFOpen(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
@@ -169,20 +55,6 @@ reg_t kFOpen(EngineState *s, int argc, reg_t *argv) {
return file_open(s, name, mode, true);
}
-static FileHandle *getFileFromHandle(EngineState *s, uint handle) {
- if (handle == 0 || handle == VIRTUALFILE_HANDLE) {
- error("Attempt to use invalid file handle (%d)", handle);
- return 0;
- }
-
- if ((handle >= s->_fileHandles.size()) || !s->_fileHandles[handle].isOpen()) {
- warning("Attempt to use invalid/unused file handle %d", handle);
- return 0;
- }
-
- return &s->_fileHandles[handle];
-}
-
reg_t kFClose(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelFile, "kFClose(%d)", argv[0].toUint16());
if (argv[0] != SIGNAL_REG) {
@@ -204,33 +76,6 @@ reg_t kFPuts(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-static int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle) {
- FileHandle *f = getFileFromHandle(s, handle);
- if (!f)
- return 0;
-
- if (!f->_in) {
- error("fgets_wrapper: Trying to read from file '%s' opened for writing", f->_name.c_str());
- return 0;
- }
- int readBytes = 0;
- if (maxsize > 1) {
- memset(dest, 0, maxsize);
- f->_in->readLine(dest, maxsize);
- readBytes = strlen(dest); // FIXME: sierra sci returned byte count and didn't react on NUL characters
- // The returned string must not have an ending LF
- if (readBytes > 0) {
- if (dest[readBytes - 1] == 0x0A)
- dest[readBytes - 1] = 0;
- }
- } else {
- *dest = 0;
- }
-
- debugC(kDebugLevelFile, " -> FGets'ed \"%s\"", dest);
- return readBytes;
-}
-
reg_t kFGets(EngineState *s, int argc, reg_t *argv) {
int maxsize = argv[1].toUint16();
char *buf = new char[maxsize];
@@ -257,9 +102,6 @@ reg_t kGetCWD(EngineState *s, int argc, reg_t *argv) {
return argv[0];
}
-static void listSavegames(Common::Array<SavegameDesc> &saves);
-static int findSavegame(Common::Array<SavegameDesc> &saves, int16 saveId);
-
enum {
K_DEVICE_INFO_GET_DEVICE = 0,
K_DEVICE_INFO_GET_CURRENT_DEVICE = 1,
@@ -393,83 +235,6 @@ reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, 1);
}
-static bool _savegame_sort_byDate(const SavegameDesc &l, const SavegameDesc &r) {
- if (l.date != r.date)
- return (l.date > r.date);
- return (l.time > r.time);
-}
-
-// Create a sorted array containing all found savedgames
-static void listSavegames(Common::Array<SavegameDesc> &saves) {
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
-
- // Load all saves
- Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
-
- for (Common::StringArray::const_iterator iter = saveNames.begin(); iter != saveNames.end(); ++iter) {
- Common::String filename = *iter;
- Common::SeekableReadStream *in;
- if ((in = saveFileMan->openForLoading(filename))) {
- SavegameMetadata meta;
- if (!get_savegame_metadata(in, &meta) || meta.name.empty()) {
- // invalid
- delete in;
- continue;
- }
- delete in;
-
- SavegameDesc desc;
- desc.id = strtol(filename.end() - 3, NULL, 10);
- desc.date = meta.saveDate;
- // We need to fix date in here, because we save DDMMYYYY instead of
- // YYYYMMDD, so sorting wouldn't work
- desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24);
- desc.time = meta.saveTime;
- desc.version = meta.version;
-
- if (meta.name.lastChar() == '\n')
- meta.name.deleteLastChar();
-
- Common::strlcpy(desc.name, meta.name.c_str(), SCI_MAX_SAVENAME_LENGTH);
-
- debug(3, "Savegame in file %s ok, id %d", filename.c_str(), desc.id);
-
- saves.push_back(desc);
- }
- }
-
- // Sort the list by creation date of the saves
- Common::sort(saves.begin(), saves.end(), _savegame_sort_byDate);
-}
-
-// Find a savedgame according to virtualId and return the position within our array
-static int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) {
- for (uint saveNr = 0; saveNr < saves.size(); saveNr++) {
- if (saves[saveNr].id == savegameId)
- return saveNr;
- }
- return -1;
-}
-
-// The scripts get IDs ranging from 100->199, because the scripts require us to assign unique ids THAT EVEN STAY BETWEEN
-// SAVES and the scripts also use "saves-count + 1" to create a new savedgame slot.
-// SCI1.1 actually recycles ids, in that case we will currently get "0".
-// This behavior is required especially for LSL6. In this game, it's possible to quick save. The scripts will use
-// the last-used id for that feature. If we don't assign sticky ids, the feature will overwrite different saves all the
-// time. And sadly we can't just use the actual filename ids directly, because of the creation method for new slots.
-
-bool Console::cmdListSaves(int argc, const char **argv) {
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
-
- for (uint i = 0; i < saves.size(); i++) {
- Common::String filename = g_sci->getSavegameName(saves[i].id);
- DebugPrintf("%s: '%s'\n", filename.c_str(), saves[i].name);
- }
-
- return true;
-}
-
reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
Common::String game_id = s->_segMan->getString(argv[0]);
uint16 virtualId = argv[1].toUint16();
@@ -989,104 +754,6 @@ reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) {
return SIGNAL_REG;
}
-void DirSeeker::addAsVirtualFiles(Common::String title, Common::String fileMask) {
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
- Common::StringArray foundFiles = saveFileMan->listSavefiles(fileMask);
- if (!foundFiles.empty()) {
- _files.push_back(title);
- _virtualFiles.push_back("");
- Common::StringArray::iterator it;
- Common::StringArray::iterator it_end = foundFiles.end();
-
- for (it = foundFiles.begin(); it != it_end; it++) {
- Common::String regularFilename = *it;
- Common::String wrappedFilename = Common::String(regularFilename.c_str() + fileMask.size() - 1);
-
- Common::SeekableReadStream *testfile = saveFileMan->openForLoading(regularFilename);
- int32 testfileSize = testfile->size();
- delete testfile;
- if (testfileSize > 1024) // check, if larger than 1k. in that case its a saved game.
- continue; // and we dont want to have those in the list
- // We need to remove the prefix for display purposes
- _files.push_back(wrappedFilename);
- // but remember the actual name as well
- _virtualFiles.push_back(regularFilename);
- }
- }
-}
-
-Common::String DirSeeker::getVirtualFilename(uint fileNumber) {
- if (fileNumber >= _virtualFiles.size())
- error("invalid virtual filename access");
- return _virtualFiles[fileNumber];
-}
-
-reg_t DirSeeker::firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan) {
- // Verify that we are given a valid buffer
- if (!buffer.segment) {
- error("DirSeeker::firstFile('%s') invoked with invalid buffer", mask.c_str());
- return NULL_REG;
- }
- _outbuffer = buffer;
- _files.clear();
- _virtualFiles.clear();
-
- int QfGImport = g_sci->inQfGImportRoom();
- if (QfGImport) {
- _files.clear();
- addAsVirtualFiles("-QfG1-", "qfg1-*");
- addAsVirtualFiles("-QfG1VGA-", "qfg1vga-*");
- if (QfGImport > 2)
- addAsVirtualFiles("-QfG2-", "qfg2-*");
- if (QfGImport > 3)
- addAsVirtualFiles("-QfG3-", "qfg3-*");
-
- if (QfGImport == 3) {
- // QfG3 sorts the filelisting itself, we can't let that happen otherwise our
- // virtual list would go out-of-sync
- reg_t savedHeros = segMan->findObjectByName("savedHeros");
- if (!savedHeros.isNull())
- writeSelectorValue(segMan, savedHeros, SELECTOR(sort), 0);
- }
-
- } else {
- // Prefix the mask
- const Common::String wrappedMask = g_sci->wrapFilename(mask);
-
- // Obtain a list of all files matching the given mask
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
- _files = saveFileMan->listSavefiles(wrappedMask);
- }
-
- // Reset the list iterator and write the first match to the output buffer,
- // if any.
- _iter = _files.begin();
- return nextFile(segMan);
-}
-
-reg_t DirSeeker::nextFile(SegManager *segMan) {
- if (_iter == _files.end()) {
- return NULL_REG;
- }
-
- Common::String string;
-
- if (_virtualFiles.empty()) {
- // Strip the prefix, if we don't got a virtual filelisting
- const Common::String wrappedString = *_iter;
- string = g_sci->unwrapFilename(wrappedString);
- } else {
- string = *_iter;
- }
- if (string.size() > 12)
- string = Common::String(string.c_str(), 12);
- segMan->strcpy(_outbuffer, string.c_str());
-
- // Return the result and advance the list iterator :)
- ++_iter;
- return _outbuffer;
-}
-
reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv) {
Common::String mask = s->_segMan->getString(argv[0]);
reg_t buf = argv[1];
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index 6f1f6a6bda..78a8a5b0a2 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -34,6 +34,7 @@ class WriteStream;
}
#include "sci/sci.h"
+#include "sci/engine/file.h"
#include "sci/engine/seg_manager.h"
#include "sci/parser/vocabulary.h"
@@ -42,6 +43,8 @@ class WriteStream;
namespace Sci {
+class FileHandle;
+class DirSeeker;
class EventManager;
class MessageState;
class SoundCommandParser;
@@ -54,32 +57,6 @@ enum AbortGameState {
kAbortQuitGame = 3
};
-class DirSeeker {
-protected:
- reg_t _outbuffer;
- Common::StringArray _files;
- Common::StringArray _virtualFiles;
- Common::StringArray::const_iterator _iter;
-
-public:
- DirSeeker() {
- _outbuffer = NULL_REG;
- _iter = _files.begin();
- }
-
- reg_t firstFile(const Common::String &mask, reg_t buffer, SegManager *segMan);
- reg_t nextFile(SegManager *segMan);
-
- Common::String getVirtualFilename(uint fileNumber);
-
-private:
- void addAsVirtualFiles(Common::String title, Common::String fileMask);
-};
-
-enum {
- MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */
-};
-
// We assume that scripts give us savegameId 0->99 for creating a new save slot
// and savegameId 100->199 for existing save slots ffs. kfile.cpp
enum {
@@ -93,20 +70,6 @@ enum {
GAMEISRESTARTING_RESTORE = 2
};
-class FileHandle {
-public:
- Common::String _name;
- Common::SeekableReadStream *_in;
- Common::WriteStream *_out;
-
-public:
- FileHandle();
- ~FileHandle();
-
- void close();
- bool isOpen() const;
-};
-
enum VideoFlags {
kNone = 0,
kDoubled = 1 << 0,