aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStrangerke2012-06-13 11:29:16 +0200
committerStrangerke2012-06-13 11:29:16 +0200
commit9bc0591825c167f135f993a9269d564d3728ff66 (patch)
tree964be665dcc166c154af6a48ed5dc101d106f67b
parent2e7b16a8bdb6ad1cf51046d57eb0f5406ee2cc13 (diff)
parent93024c073be6d93c9369a83e4b4468bb0aaeeadd (diff)
downloadscummvm-rg350-9bc0591825c167f135f993a9269d564d3728ff66.tar.gz
scummvm-rg350-9bc0591825c167f135f993a9269d564d3728ff66.tar.bz2
scummvm-rg350-9bc0591825c167f135f993a9269d564d3728ff66.zip
Merge branch 'master' of github.com:scummvm/scummvm
-rw-r--r--engines/sci/console.cpp21
-rw-r--r--engines/sci/engine/file.cpp445
-rw-r--r--engines/sci/engine/file.h139
-rw-r--r--engines/sci/engine/kernel.h8
-rw-r--r--engines/sci/engine/kernel_tables.h24
-rw-r--r--engines/sci/engine/kfile.cpp626
-rw-r--r--engines/sci/engine/kmisc.cpp4
-rw-r--r--engines/sci/engine/kvideo.cpp1
-rw-r--r--engines/sci/engine/state.cpp12
-rw-r--r--engines/sci/engine/state.h48
-rw-r--r--engines/sci/engine/workarounds.cpp1
-rw-r--r--engines/sci/module.mk1
12 files changed, 818 insertions, 512 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
new file mode 100644
index 0000000000..66f54d67df
--- /dev/null
+++ b/engines/sci/engine/file.cpp
@@ -0,0 +1,445 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/savefile.h"
+#include "common/stream.h"
+
+#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) {
+ Common::SeekableReadStream *inFile = g_sci->getSaveFileManager()->openForLoading(fileName);
+
+ _bufferSize = inFile->size();
+ _buffer = new char[_bufferSize];
+ inFile->read(_buffer, _bufferSize);
+ _ptr = _buffer;
+ delete inFile;
+}
+
+VirtualIndexFile::~VirtualIndexFile() {
+ close();
+
+ _bufferSize = 0;
+ delete[] _buffer;
+ _buffer = 0;
+}
+
+uint32 VirtualIndexFile::read(char *buffer, uint32 size) {
+ uint32 curPos = _ptr - _buffer;
+ uint32 finalSize = MIN<uint32>(size, _bufferSize - curPos);
+ char *localPtr = buffer;
+
+ for (uint32 i = 0; i < finalSize; i++)
+ *localPtr++ = *_ptr++;
+
+ return finalSize;
+}
+
+uint32 VirtualIndexFile::write(const char *buffer, uint32 size) {
+ _changed = true;
+ uint32 curPos = _ptr - _buffer;
+
+ // Check if the buffer needs to be resized
+ if (curPos + size >= _bufferSize) {
+ _bufferSize = curPos + size + 1;
+ char *tmp = _buffer;
+ _buffer = new char[_bufferSize];
+ _ptr = _buffer + curPos;
+ memcpy(_buffer, tmp, _bufferSize);
+ delete[] tmp;
+ }
+
+ for (uint32 i = 0; i < size; i++)
+ *_ptr++ = *buffer++;
+
+ return size;
+}
+
+uint32 VirtualIndexFile::readLine(char *buffer, uint32 size) {
+ uint32 startPos = _ptr - _buffer;
+ uint32 bytesRead = 0;
+ char *localPtr = buffer;
+
+ // This is not a full-blown implementation of readLine, but it
+ // suffices for Phantasmagoria
+ while (startPos + bytesRead < size) {
+ bytesRead++;
+
+ if (*_ptr == 0 || *_ptr == 0x0A) {
+ _ptr++;
+ *localPtr = 0;
+ return bytesRead;
+ } else {
+ *localPtr++ = *_ptr++;
+ }
+ }
+
+ return bytesRead;
+}
+
+bool VirtualIndexFile::seek(int32 offset, int whence) {
+ uint32 startPos = _ptr - _buffer;
+ assert(offset >= 0);
+
+ switch (whence) {
+ case SEEK_CUR:
+ assert(startPos + offset < _bufferSize);
+ _ptr += offset;
+ break;
+ case SEEK_SET:
+ assert(offset < (int32)_bufferSize);
+ _ptr = _buffer + offset;
+ break;
+ case SEEK_END:
+ assert(_bufferSize - offset >= 0);
+ _ptr = _buffer + (_bufferSize - offset);
+ break;
+ }
+
+ return true;
+}
+
+void VirtualIndexFile::close() {
+ if (_changed) {
+ Common::WriteStream *outFile = g_sci->getSaveFileManager()->openForSaving(_fileName);
+ outFile->write(_buffer, _bufferSize);
+ delete outFile;
+ }
+
+ // Maintain the buffer, and seek to the beginning of it
+ _ptr = _buffer;
+}
+
+#endif
+
+} // End of namespace Sci
diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h
new file mode 100644
index 0000000000..896dcd2a42
--- /dev/null
+++ b/engines/sci/engine/file.h
@@ -0,0 +1,139 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_ENGINE_FILE_H
+#define SCI_ENGINE_FILE_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
+
+/**
+ * An implementation of a virtual file that supports basic read and write
+ * operations simultaneously.
+ *
+ * This class has been initially implemented for Phantasmagoria, which has its
+ * own custom save/load code. The load code keeps checking for the existence
+ * of the save index file and keeps closing and reopening it for each save
+ * slot. This is notoriously slow and clumsy, and introduces noticeable delays,
+ * especially for non-desktop systems. Also, its game scripts request to open
+ * the index file for reading and writing with the same parameters
+ * (SaveManager::setCurrentSave and SaveManager::getCurrentSave). Moreover,
+ * the game scripts reopen the index file for writing in order to update it
+ * and seek within it. We do not support seeking in writeable streams, and the
+ * fact that our saved games are ZIP files makes this operation even more
+ * expensive. Finally, the savegame index file is supposed to be expanded when
+ * a new save slot is added.
+ * For the aforementioned reasons, this class has been implemented, which offers
+ * the basic functionality needed by the game scripts in Phantasmagoria.
+ */
+class VirtualIndexFile {
+public:
+ VirtualIndexFile(Common::String fileName);
+ ~VirtualIndexFile();
+
+ uint32 read(char *buffer, uint32 size);
+ uint32 readLine(char *buffer, uint32 size);
+ uint32 write(const char *buffer, uint32 size);
+ bool seek(int32 offset, int whence);
+ void close();
+
+private:
+ char *_buffer;
+ uint32 _bufferSize;
+ char *_ptr;
+
+ Common::String _fileName;
+ bool _changed;
+};
+
+#endif
+
+} // End of namespace Sci
+
+#endif // SCI_ENGINE_FILE_H
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index d9fdbefbc2..677b790f93 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);
@@ -326,10 +323,6 @@ reg_t kTimesCot(EngineState *s, int argc, reg_t *argv);
reg_t kCosDiv(EngineState *s, int argc, reg_t *argv);
reg_t kSinDiv(EngineState *s, int argc, reg_t *argv);
reg_t kValidPath(EngineState *s, int argc, reg_t *argv);
-reg_t kFOpen(EngineState *s, int argc, reg_t *argv);
-reg_t kFPuts(EngineState *s, int argc, reg_t *argv);
-reg_t kFGets(EngineState *s, int argc, reg_t *argv);
-reg_t kFClose(EngineState *s, int argc, reg_t *argv);
reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv);
reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv);
reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv);
@@ -464,6 +457,7 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv);
// SCI2.1 Kernel Functions
reg_t kText(EngineState *s, int argc, reg_t *argv);
reg_t kSave(EngineState *s, int argc, reg_t *argv);
+reg_t kAutoSave(EngineState *s, int argc, reg_t *argv);
reg_t kList(EngineState *s, int argc, reg_t *argv);
reg_t kRobot(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 9b765283ec..6965a5da45 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -246,6 +246,20 @@ static const SciKernelMapSubEntry kFileIO_subops[] = {
};
#ifdef ENABLE_SCI32
+
+static const SciKernelMapSubEntry kSave_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(SaveGame), "[r0]i[r0](r)", NULL },
+ { SIG_SCI32, 1, MAP_CALL(RestoreGame), "[r0]i[r0]", NULL },
+ { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r*)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(CheckSaveGame), ".*", NULL },
+ // Subop 4 hasn't been encountered yet
+ { SIG_SCI32, 5, MAP_CALL(GetSaveFiles), "rrr", NULL },
+ { SIG_SCI32, 6, MAP_CALL(MakeSaveCatName), "rr", NULL },
+ { SIG_SCI32, 7, MAP_CALL(MakeSaveFileName), "rri", NULL },
+ { SIG_SCI32, 8, MAP_CALL(AutoSave), "[o0]", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kList_subops[] = {
{ SIG_SCI21, 0, MAP_CALL(NewList), "", NULL },
@@ -338,10 +352,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(EditControl), SIG_EVERYWHERE, "[o0][o0]", NULL, NULL },
{ MAP_CALL(Empty), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(EmptyList), SIG_EVERYWHERE, "l", NULL, NULL },
- { MAP_CALL(FClose), SIG_EVERYWHERE, "i", NULL, NULL },
- { MAP_CALL(FGets), SIG_EVERYWHERE, "rii", NULL, NULL },
- { MAP_CALL(FOpen), SIG_EVERYWHERE, "ri", NULL, NULL },
- { MAP_CALL(FPuts), SIG_EVERYWHERE, "ir", NULL, NULL },
+ { "FClose", kFileIOClose, SIG_EVERYWHERE, "i", NULL, NULL },
+ { "FGets", kFileIOReadString, SIG_EVERYWHERE, "rii", NULL, NULL },
+ { "FOpen", kFileIOOpen, SIG_EVERYWHERE, "ri", NULL, NULL },
+ { "FPuts", kFileIOWriteString, SIG_EVERYWHERE, "ir", NULL, NULL },
{ MAP_CALL(FileIO), SIG_EVERYWHERE, "i(.*)", kFileIO_subops, NULL },
{ MAP_CALL(FindKey), SIG_EVERYWHERE, "l.", NULL, kFindKey_workarounds },
{ MAP_CALL(FirstNode), SIG_EVERYWHERE, "[l0]", NULL, NULL },
@@ -555,7 +569,7 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL },
{ MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
- { MAP_CALL(Save), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL },
{ MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL },
{ MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL },
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 42f8b8832c..052332957b 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -33,6 +33,7 @@
#include "gui/saveload.h"
#include "sci/sci.h"
+#include "sci/engine/file.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/savegame.h"
@@ -40,209 +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
-};
-
-
-
-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);
-}
-
-reg_t kFOpen(EngineState *s, int argc, reg_t *argv) {
- Common::String name = s->_segMan->getString(argv[0]);
- int mode = argv[1].toUint16();
-
- debugC(kDebugLevelFile, "kFOpen(%s,0x%x)", name.c_str(), mode);
- return file_open(s, name, mode, true);
-}
-
-static FileHandle *getFileFromHandle(EngineState *s, uint handle) {
- if (handle == 0) {
- error("Attempt to use file handle 0");
- 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) {
- FileHandle *f = getFileFromHandle(s, argv[0].toUint16());
- if (f)
- f->close();
- }
- return s->r_acc;
-}
-
-reg_t kFPuts(EngineState *s, int argc, reg_t *argv) {
- int handle = argv[0].toUint16();
- Common::String data = s->_segMan->getString(argv[1]);
-
- FileHandle *f = getFileFromHandle(s, handle);
- if (f)
- f->_out->write(data.c_str(), data.size());
-
- 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];
- int handle = argv[2].toUint16();
-
- debugC(kDebugLevelFile, "kFGets(%d, %d)", handle, maxsize);
- int readBytes = fgets_wrapper(s, buf, maxsize, handle);
- s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize);
- delete[] buf;
- return readBytes ? argv[0] : NULL_REG;
-}
+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);
/**
* Writes the cwd to the supplied address and returns the address in acc.
@@ -258,9 +61,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,
@@ -394,83 +194,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();
@@ -738,6 +461,21 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
+#ifdef ENABLE_SCI32
+ if (name == PHANTASMAGORIA_SAVEGAME_INDEX) {
+ if (s->_virtualIndexFile) {
+ return make_reg(0, VIRTUALFILE_HANDLE);
+ } else {
+ Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH);
+ Common::String wrappedName = g_sci->wrapFilename(englishName);
+ if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) {
+ s->_virtualIndexFile = new VirtualIndexFile(wrappedName);
+ return make_reg(0, VIRTUALFILE_HANDLE);
+ }
+ }
+ }
+#endif
+
// SCI32 can call K_FILEIO_OPEN with only one argument. It seems to
// just be checking if it exists.
int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16();
@@ -780,48 +518,82 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
debugC(kDebugLevelFile, "kFileIO(close): %d", argv[0].toUint16());
- FileHandle *f = getFileFromHandle(s, argv[0].toUint16());
+ if (argv[0] == SIGNAL_REG)
+ return s->r_acc;
+
+ uint16 handle = argv[0].toUint16();
+
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE) {
+ s->_virtualIndexFile->close();
+ return SIGNAL_REG;
+ }
+#endif
+
+ FileHandle *f = getFileFromHandle(s, handle);
if (f) {
f->close();
+ if (getSciVersion() <= SCI_VERSION_0_LATE)
+ return s->r_acc; // SCI0 semantics: no value returned
return SIGNAL_REG;
}
+
+ if (getSciVersion() <= SCI_VERSION_0_LATE)
+ return s->r_acc; // SCI0 semantics: no value returned
return NULL_REG;
}
reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {
- int handle = argv[0].toUint16();
- int size = argv[2].toUint16();
+ uint16 handle = argv[0].toUint16();
+ uint16 size = argv[2].toUint16();
int bytesRead = 0;
char *buf = new char[size];
debugC(kDebugLevelFile, "kFileIO(readRaw): %d, %d", handle, size);
- FileHandle *f = getFileFromHandle(s, handle);
- if (f) {
- bytesRead = f->_in->read(buf, size);
- // TODO: What happens if less bytes are read than what has
- // been requested? (i.e. if bytesRead is non-zero, but still
- // less than size)
- if (bytesRead > 0)
- s->_segMan->memcpy(argv[1], (const byte*)buf, size);
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE) {
+ bytesRead = s->_virtualIndexFile->read(buf, size);
+ } else {
+#endif
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f)
+ bytesRead = f->_in->read(buf, size);
+#ifdef ENABLE_SCI32
}
+#endif
+
+ // TODO: What happens if less bytes are read than what has
+ // been requested? (i.e. if bytesRead is non-zero, but still
+ // less than size)
+ if (bytesRead > 0)
+ s->_segMan->memcpy(argv[1], (const byte*)buf, size);
delete[] buf;
return make_reg(0, bytesRead);
}
reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {
- int handle = argv[0].toUint16();
- int size = argv[2].toUint16();
+ uint16 handle = argv[0].toUint16();
+ uint16 size = argv[2].toUint16();
char *buf = new char[size];
bool success = false;
s->_segMan->memcpy((byte *)buf, argv[1], size);
debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size);
- FileHandle *f = getFileFromHandle(s, handle);
- if (f) {
- f->_out->write(buf, size);
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE) {
+ s->_virtualIndexFile->write(buf, size);
success = true;
+ } else {
+#endif
+ FileHandle *f = getFileFromHandle(s, handle);
+ if (f) {
+ f->_out->write(buf, size);
+ success = true;
+ }
+#ifdef ENABLE_SCI32
}
+#endif
delete[] buf;
if (success)
@@ -829,6 +601,51 @@ reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, 6); // DOS - invalid handle
}
+reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) {
+ uint16 maxsize = argv[1].toUint16();
+ char *buf = new char[maxsize];
+ uint16 handle = argv[2].toUint16();
+ debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize);
+ uint32 bytesRead;
+
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE)
+ bytesRead = s->_virtualIndexFile->readLine(buf, maxsize);
+ else
+#endif
+ bytesRead = fgets_wrapper(s, buf, maxsize, handle);
+
+ s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize);
+ delete[] buf;
+ return bytesRead ? argv[0] : NULL_REG;
+}
+
+reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
+ int handle = argv[0].toUint16();
+ Common::String str = s->_segMan->getString(argv[1]);
+ debugC(kDebugLevelFile, "kFileIO(writeString): %d", handle);
+
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE) {
+ s->_virtualIndexFile->write(str.c_str(), str.size());
+ return NULL_REG;
+ }
+#endif
+
+ FileHandle *f = getFileFromHandle(s, handle);
+
+ if (f) {
+ f->_out->write(str.c_str(), str.size());
+ if (getSciVersion() <= SCI_VERSION_0_LATE)
+ return s->r_acc; // SCI0 semantics: no value returned
+ return NULL_REG;
+ }
+
+ if (getSciVersion() <= SCI_VERSION_0_LATE)
+ return s->r_acc; // SCI0 semantics: no value returned
+ return make_reg(0, 6); // DOS - invalid handle
+}
+
reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
@@ -854,9 +671,19 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
name = g_sci->getSavegameName(savedir_nr);
result = saveFileMan->removeSavefile(name);
} else if (getSciVersion() >= SCI_VERSION_2) {
- // We don't need to wrap the filename in SCI32 games, as it's already
- // constructed here
+ // The file name may be already wrapped, so check both cases
result = saveFileMan->removeSavefile(name);
+ if (!result) {
+ const Common::String wrappedName = g_sci->wrapFilename(name);
+ result = saveFileMan->removeSavefile(wrappedName);
+ }
+
+#ifdef ENABLE_SCI32
+ if (name == PHANTASMAGORIA_SAVEGAME_INDEX) {
+ delete s->_virtualIndexFile;
+ s->_virtualIndexFile = 0;
+ }
+#endif
} else {
const Common::String wrappedName = g_sci->wrapFilename(name);
result = saveFileMan->removeSavefile(wrappedName);
@@ -868,143 +695,34 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, 2); // DOS - file not found error code
}
-reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) {
- int size = argv[1].toUint16();
- char *buf = new char[size];
- int handle = argv[2].toUint16();
- debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, size);
-
- int readBytes = fgets_wrapper(s, buf, size, handle);
- s->_segMan->memcpy(argv[0], (const byte*)buf, size);
- delete[] buf;
- return readBytes ? argv[0] : NULL_REG;
-}
-
-reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
- int handle = argv[0].toUint16();
- Common::String str = s->_segMan->getString(argv[1]);
- debugC(kDebugLevelFile, "kFileIO(writeString): %d", handle);
-
- FileHandle *f = getFileFromHandle(s, handle);
-
- if (f) {
- f->_out->write(str.c_str(), str.size());
- return NULL_REG;
- }
-
- return make_reg(0, 6); // DOS - invalid handle
-}
-
reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) {
- int handle = argv[0].toUint16();
- int offset = argv[1].toUint16();
- int whence = argv[2].toUint16();
+ uint16 handle = argv[0].toUint16();
+ uint16 offset = ABS<int16>(argv[1].toSint16()); // can be negative
+ uint16 whence = argv[2].toUint16();
debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence);
- FileHandle *f = getFileFromHandle(s, handle);
-
- if (f)
- s->r_acc = make_reg(0, f->_in->seek(offset, whence));
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE)
+ return make_reg(0, s->_virtualIndexFile->seek(offset, whence));
+#endif
- return SIGNAL_REG;
-}
+ FileHandle *f = getFileFromHandle(s, handle);
-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);
+ if (f && f->_in) {
+ // Backward seeking isn't supported in zip file streams, thus adapt the
+ // parameters accordingly if games ask for such a seek mode. A known
+ // case where this is requested is the save file manager in Phantasmagoria
+ if (whence == SEEK_END) {
+ whence = SEEK_SET;
+ offset = f->_in->size() - offset;
}
- }
-}
-
-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;
+ return make_reg(0, f->_in->seek(offset, whence));
+ } else if (f && f->_out) {
+ error("kFileIOSeek: Unsupported seek operation on a writeable stream (offset: %d, whence: %d)", offset, whence);
}
- _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;
+ return SIGNAL_REG;
}
reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv) {
@@ -1027,6 +745,14 @@ reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
+#ifdef ENABLE_SCI32
+ // Cache the file existence result for the Phantasmagoria
+ // save index file, as the game scripts keep checking for
+ // its existence.
+ if (name == PHANTASMAGORIA_SAVEGAME_INDEX && s->_virtualIndexFile)
+ return TRUE_REG;
+#endif
+
bool exists = false;
// Check for regular file
@@ -1191,35 +917,21 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
return argv[0];
}
+reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) {
+ // TODO
+ // This is a timer callback, with 1 parameter: the timer object
+ // (e.g. "timers").
+ // It's used for auto-saving (i.e. save every X minutes, by checking
+ // the elapsed time from the timer object)
+
+ // This function has to return something other than 0 to proceed
+ return s->r_acc;
+}
+
reg_t kSave(EngineState *s, int argc, reg_t *argv) {
- switch (argv[0].toUint16()) {
- case 0:
- return kSaveGame(s, argc - 1,argv + 1);
- case 1:
- return kRestoreGame(s, argc - 1,argv + 1);
- case 2:
- return kGetSaveDir(s, argc - 1, argv + 1);
- case 3:
- return kCheckSaveGame(s, argc - 1, argv + 1);
- case 5:
- return kGetSaveFiles(s, argc - 1, argv + 1);
- case 6:
- return kMakeSaveCatName(s, argc - 1, argv + 1);
- case 7:
- return kMakeSaveFileName(s, argc - 1, argv + 1);
- case 8:
- // TODO
- // This is a timer callback, with 1 parameter: the timer object
- // (e.g. "timers").
- // It's used for auto-saving (i.e. save every X minutes, by checking
- // the elapsed time from the timer object)
-
- // This function has to return something other than 0 to proceed
- return s->r_acc;
- default:
- kStub(s, argc, argv);
- return NULL_REG;
- }
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
}
#endif
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 1cbaf0708d..47c891eefd 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -384,6 +384,10 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) {
} else if (setting == "language") {
Common::String languageId = Common::String::format("%d", g_sci->getSciLanguage());
s->_segMan->strcpy(data, languageId.c_str());
+ } else if (setting == "torindebug") {
+ // Used to enable the debug mode in Torin's Passage (French).
+ // If true, the debug mode is enabled.
+ s->_segMan->strcpy(data, "");
} else {
error("GetConfig: Unknown configuration setting %s", setting.c_str());
}
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index c9cf652013..f176a13721 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -259,6 +259,7 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
warning("kRobot(%d)", subop);
break;
case 8: // sync
+ //if (false) { // debug: automatically skip all robot videos
if ((uint32)g_sci->_robotDecoder->getCurFrame() != g_sci->_robotDecoder->getFrameCount() - 1) {
writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
} else {
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index 28818cddef..237c6b54a6 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -26,6 +26,7 @@
#include "sci/debug.h" // for g_debug_sleeptime_factor
#include "sci/event.h"
+#include "sci/engine/file.h"
#include "sci/engine/kernel.h"
#include "sci/engine/state.h"
#include "sci/engine/selector.h"
@@ -68,21 +69,26 @@ static const uint16 s_halfWidthSJISMap[256] = {
};
EngineState::EngineState(SegManager *segMan)
-: _segMan(segMan), _dirseeker() {
+: _segMan(segMan),
+#ifdef ENABLE_SCI32
+ _virtualIndexFile(0),
+#endif
+ _dirseeker() {
reset(false);
}
EngineState::~EngineState() {
delete _msgState;
+#ifdef ENABLE_SCI32
+ delete _virtualIndexFile;
+#endif
}
void EngineState::reset(bool isRestoring) {
if (!isRestoring) {
_memorySegmentSize = 0;
-
_fileHandles.resize(5);
-
abortScriptProcessing = kAbortNone;
}
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index dcffe6dbb3..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,9 +43,12 @@ class WriteStream;
namespace Sci {
+class FileHandle;
+class DirSeeker;
class EventManager;
class MessageState;
class SoundCommandParser;
+class VirtualIndexFile;
enum AbortGameState {
kAbortNone = 0,
@@ -53,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 {
@@ -92,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,
@@ -163,6 +127,10 @@ public:
int16 _lastSaveVirtualId; // last virtual id fed to kSaveGame, if no kGetSaveFiles was called inbetween
int16 _lastSaveNewId; // last newly created filename-id by kSaveGame
+#ifdef ENABLE_SCI32
+ VirtualIndexFile *_virtualIndexFile;
+#endif
+
uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms
bool _cursorWorkaroundActive; // ffs. GfxCursor::setPosition()
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index c1d4a3d9f9..ecb1e4c2d5 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -167,6 +167,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_SQ6, -1, 0, 0, "SQ6", "init", -1, 2, { WORKAROUND_FAKE, 0 } }, // Demo and full version: called when the game starts (demo: room 0, full: room 100)
{ GID_SQ6, 100, 64950, 0, "View", "handleEvent", -1, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu
{ GID_SQ6, -1, 64964, 0, "DPath", "init", -1, 1, { WORKAROUND_FAKE, 0 } }, // during the game
+ { GID_TORIN, -1, 64017, 0, "oFlags", "clear", -1, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
SCI_WORKAROUNDENTRY_TERMINATOR
};
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index b6d5837b31..6b6058c819 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -10,6 +10,7 @@ MODULE_OBJS := \
sci.o \
util.o \
engine/features.o \
+ engine/file.o \
engine/gc.o \
engine/kernel.o \
engine/kevent.o \