aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine
diff options
context:
space:
mode:
authorMatthew Hoops2012-06-16 01:37:40 -0400
committerMatthew Hoops2012-06-16 01:43:32 -0400
commit625f6cc71657e95e0361edefa333a38910c1aca5 (patch)
treeae5ef8582ede4d62e56253467ea144db2341b353 /engines/sci/engine
parentf02b696573fe4281e4890d71b74671804a5ebf41 (diff)
parent5230a0d61795e2855625a43d60dc3bc2ed83fc3d (diff)
downloadscummvm-rg350-625f6cc71657e95e0361edefa333a38910c1aca5.tar.gz
scummvm-rg350-625f6cc71657e95e0361edefa333a38910c1aca5.tar.bz2
scummvm-rg350-625f6cc71657e95e0361edefa333a38910c1aca5.zip
Merge remote branch 'upstream/master' into pegasus
Diffstat (limited to 'engines/sci/engine')
-rw-r--r--engines/sci/engine/file.cpp451
-rw-r--r--engines/sci/engine/file.h140
-rw-r--r--engines/sci/engine/kernel.cpp2
-rw-r--r--engines/sci/engine/kernel.h11
-rw-r--r--engines/sci/engine/kernel_tables.h32
-rw-r--r--engines/sci/engine/kfile.cpp1209
-rw-r--r--engines/sci/engine/kgraphics32.cpp163
-rw-r--r--engines/sci/engine/klists.cpp19
-rw-r--r--engines/sci/engine/kmisc.cpp35
-rw-r--r--engines/sci/engine/ksound.cpp8
-rw-r--r--engines/sci/engine/kstring.cpp14
-rw-r--r--engines/sci/engine/kvideo.cpp1
-rw-r--r--engines/sci/engine/object.cpp11
-rw-r--r--engines/sci/engine/object.h4
-rw-r--r--engines/sci/engine/savegame.cpp11
-rw-r--r--engines/sci/engine/script.cpp79
-rw-r--r--engines/sci/engine/script.h6
-rw-r--r--engines/sci/engine/seg_manager.cpp9
-rw-r--r--engines/sci/engine/seg_manager.h2
-rw-r--r--engines/sci/engine/state.cpp12
-rw-r--r--engines/sci/engine/state.h48
-rw-r--r--engines/sci/engine/vm.cpp4
-rw-r--r--engines/sci/engine/vm.h2
-rw-r--r--engines/sci/engine/workarounds.cpp1
24 files changed, 1330 insertions, 944 deletions
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
new file mode 100644
index 0000000000..c44963f457
--- /dev/null
+++ b/engines/sci/engine/file.cpp
@@ -0,0 +1,451 @@
+/* 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(uint32 initialSize) : _changed(false) {
+ _bufferSize = initialSize;
+ _buffer = new char[_bufferSize];
+ _ptr = _buffer;
+}
+
+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((int32)_bufferSize - offset >= 0);
+ _ptr = _buffer + (_bufferSize - offset);
+ break;
+ }
+
+ return true;
+}
+
+void VirtualIndexFile::close() {
+ if (_changed && !_fileName.empty()) {
+ 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..1c8e302d15
--- /dev/null
+++ b/engines/sci/engine/file.h
@@ -0,0 +1,140 @@
+/* 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 initialSize);
+ ~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.cpp b/engines/sci/engine/kernel.cpp
index c99bc4fe47..3a33c928e7 100644
--- a/engines/sci/engine/kernel.cpp
+++ b/engines/sci/engine/kernel.cpp
@@ -376,7 +376,7 @@ uint16 Kernel::findRegType(reg_t reg) {
case SEG_TYPE_SCRIPT:
if (reg.offset <= (*(Script *)mobj).getBufSize() &&
reg.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET &&
- RAW_IS_OBJECT((*(Script *)mobj).getBuf(reg.offset)) ) {
+ (*(Script *)mobj).offsetIsObject(reg.offset)) {
result |= ((Script *)mobj)->getObject(reg.offset) ? SIG_TYPE_OBJECT : SIG_TYPE_REFERENCE;
} else
result |= SIG_TYPE_REFERENCE;
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 664c97f7b5..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);
@@ -482,6 +476,9 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv);
reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv);
reg_t kFont(EngineState *s, int argc, reg_t *argv);
reg_t kBitmap(EngineState *s, int argc, reg_t *argv);
+reg_t kAddLine(EngineState *s, int argc, reg_t *argv);
+reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv);
+reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv);
// SCI3 Kernel functions
reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 4ddf0534ea..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 },
@@ -564,10 +578,13 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL },
{ MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL },
{ MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL },
- { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "io(.*)", NULL, NULL },
{ MAP_CALL(SetFontRes), SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
{ MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL },
+ { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "roiiiiiiiii", NULL, NULL },
+ { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "ro", NULL, NULL },
// SCI2.1 Empty Functions
@@ -613,9 +630,6 @@ static SciKernelMapEntry s_kernelMap[] = {
// SCI2.1 unmapped functions - TODO!
// MovePlaneItems - used by SQ6 to scroll through the inventory via the up/down buttons
- // AddLine - used by Torin's Passage to highlight the chapter buttons
- // DeleteLine - used by Torin's Passage to delete the highlight from the chapter buttons
- // UpdateLine - used by LSL6
// SetPalStyleRange - 2 integer parameters, start and end. All styles from start-end
// (inclusive) are set to 0
// MorphOn - used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270)
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 8d1b078697..7a2f161829 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,
@@ -352,18 +152,6 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
-reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv) {
-#ifdef ENABLE_SCI32
- // SCI32 uses a parameter here. It is used to modify a string, stored in a
- // global variable, so that game scripts store the save directory. We
- // don't really set a save game directory, thus not setting the string to
- // anything is the correct thing to do here.
- //if (argc > 0)
- // warning("kGetSaveDir called with %d parameter(s): %04x:%04x", argc, PRINT_REG(argv[0]));
-#endif
- return s->_segMan->getSaveDirPtr();
-}
-
reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) {
if (argc > 1) {
// SCI1.1/SCI32
@@ -394,354 +182,36 @@ 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();
-
- debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), virtualId);
-
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
-
- // we allow 0 (happens in QfG2 when trying to restore from an empty saved game list) and return false in that case
- if (virtualId == 0)
- return NULL_REG;
-
- // Find saved-game
- if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
- error("kCheckSaveGame: called with invalid savegameId");
- uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
- int savegameNr = findSavegame(saves, savegameId);
- if (savegameNr == -1)
- return NULL_REG;
-
- // Check for compatible savegame version
- int ver = saves[savegameNr].version;
- if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION)
- return NULL_REG;
-
- // Otherwise we assume the savegame is OK
- return TRUE_REG;
-}
-
-reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id = s->_segMan->getString(argv[0]);
-
- debug(3, "kGetSaveFiles(%s)", game_id.c_str());
-
- // Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really
- // mean new slot instead of overwriting the old one
- s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START;
-
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
- uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR);
-
- reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves);
-
- if (!slot) {
- warning("kGetSaveFiles: %04X:%04X invalid or too small to hold slot data", PRINT_REG(argv[2]));
- totalSaves = 0;
- }
-
- const uint bufSize = (totalSaves * SCI_MAX_SAVENAME_LENGTH) + 1;
- char *saveNames = new char[bufSize];
- char *saveNamePtr = saveNames;
-
- for (uint i = 0; i < totalSaves; i++) {
- *slot++ = make_reg(0, saves[i].id + SAVEGAMEID_OFFICIALRANGE_START); // Store the virtual savegame ID ffs. see above
- strcpy(saveNamePtr, saves[i].name);
- saveNamePtr += SCI_MAX_SAVENAME_LENGTH;
- }
-
- *saveNamePtr = 0; // Terminate list
-
- s->_segMan->memcpy(argv[1], (byte *)saveNames, bufSize);
- delete[] saveNames;
-
- return make_reg(0, totalSaves);
-}
-
-reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id;
- int16 virtualId = argv[1].toSint16();
- int16 savegameId = -1;
- Common::String game_description;
- Common::String version;
-
- if (argc > 3)
- version = s->_segMan->getString(argv[3]);
-
- // We check here, we don't want to delete a users save in case we are within a kernel function
- if (s->executionStackBase) {
- warning("kSaveGame - won't save from within kernel function");
- return NULL_REG;
- }
-
- if (argv[0].isNull()) {
- // Direct call, from a patched Game::save
- if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull()))
- error("kSaveGame: assumed patched call isn't accurate");
-
- // we are supposed to show a dialog for the user and let him choose where to save
- g_sci->_soundCmd->pauseAll(true); // pause music
- const EnginePlugin *plugin = NULL;
- EngineMan.findGame(g_sci->getGameIdStr(), &plugin);
- GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"));
- dialog->setSaveMode(true);
- savegameId = dialog->runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName());
- game_description = dialog->getResultString();
- if (game_description.empty()) {
- // create our own description for the saved game, the user didnt enter it
- #if defined(USE_SAVEGAME_TIMESTAMP)
- TimeDate curTime;
- g_system->getTimeAndDate(curTime);
- curTime.tm_year += 1900; // fixup year
- curTime.tm_mon++; // fixup month
- game_description = Common::String::format("%04d.%02d.%02d / %02d:%02d:%02d", curTime.tm_year, curTime.tm_mon, curTime.tm_mday, curTime.tm_hour, curTime.tm_min, curTime.tm_sec);
- #else
- game_description = Common::String::format("Save %d", savegameId + 1);
- #endif
- }
- delete dialog;
- g_sci->_soundCmd->pauseAll(false); // unpause music ( we can't have it paused during save)
- if (savegameId < 0)
- return NULL_REG;
-
- } else {
- // Real call from script
- game_id = s->_segMan->getString(argv[0]);
- if (argv[2].isNull())
- error("kSaveGame: called with description being NULL");
- game_description = s->_segMan->getString(argv[2]);
-
- debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());
-
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
-
- if ((virtualId >= SAVEGAMEID_OFFICIALRANGE_START) && (virtualId <= SAVEGAMEID_OFFICIALRANGE_END)) {
- // savegameId is an actual Id, so search for it just to make sure
- savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
- if (findSavegame(saves, savegameId) == -1)
- return NULL_REG;
- } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {
- // virtualId is low, we assume that scripts expect us to create new slot
- if (virtualId == s->_lastSaveVirtualId) {
- // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
- savegameId = s->_lastSaveNewId;
- } else {
- uint savegameNr;
- // savegameId is in lower range, scripts expect us to create a new slot
- for (savegameId = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; savegameId++) {
- for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
- if (savegameId == saves[savegameNr].id)
- break;
- }
- if (savegameNr == saves.size())
- break;
- }
- if (savegameId == SAVEGAMEID_OFFICIALRANGE_START)
- error("kSavegame: no more savegame slots available");
- }
- } else {
- error("kSaveGame: invalid savegameId used");
- }
-
- // Save in case caller wants to overwrite last newly created save
- s->_lastSaveVirtualId = virtualId;
- s->_lastSaveNewId = savegameId;
- }
-
- s->r_acc = NULL_REG;
-
- Common::String filename = g_sci->getSavegameName(savegameId);
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
- Common::OutSaveFile *out;
-
- out = saveFileMan->openForSaving(filename);
- if (!out) {
- warning("Error opening savegame \"%s\" for writing", filename.c_str());
- } else {
- if (!gamestate_save(s, out, game_description, version)) {
- warning("Saving the game failed");
- } else {
- s->r_acc = TRUE_REG; // save successful
- }
+reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
+ Common::String path = s->_segMan->getString(argv[0]);
- out->finalize();
- if (out->err()) {
- warning("Writing the savegame failed");
- s->r_acc = NULL_REG; // write failure
- }
- delete out;
- }
+ debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.offset);
- return s->r_acc;
+ // Always return true
+ return make_reg(0, 1);
}
-reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
- int16 savegameId = argv[1].toSint16();
- bool pausedMusic = false;
-
- debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId);
-
- if (argv[0].isNull()) {
- // Direct call, either from launcher or from a patched Game::restore
- if (savegameId == -1) {
- // we are supposed to show a dialog for the user and let him choose a saved game
- g_sci->_soundCmd->pauseAll(true); // pause music
- const EnginePlugin *plugin = NULL;
- EngineMan.findGame(g_sci->getGameIdStr(), &plugin);
- GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"));
- dialog->setSaveMode(false);
- savegameId = dialog->runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName());
- delete dialog;
- if (savegameId < 0) {
- g_sci->_soundCmd->pauseAll(false); // unpause music
- return s->r_acc;
- }
- pausedMusic = true;
- }
- // don't adjust ID of the saved game, it's already correct
- } else {
- if (argv[2].isNull())
- error("kRestoreGame: called with parameter 2 being NULL");
- // Real call from script, we need to adjust ID
- if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) {
- warning("Savegame ID %d is not allowed", savegameId);
- return TRUE_REG;
- }
- savegameId -= SAVEGAMEID_OFFICIALRANGE_START;
- }
-
- s->r_acc = NULL_REG; // signals success
-
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
- if (findSavegame(saves, savegameId) == -1) {
- s->r_acc = TRUE_REG;
- warning("Savegame ID %d not found", savegameId);
- } else {
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
- Common::String filename = g_sci->getSavegameName(savegameId);
- Common::SeekableReadStream *in;
-
- in = saveFileMan->openForLoading(filename);
- if (in) {
- // found a savegame file
-
- gamestate_restore(s, in);
- delete in;
-
- if (g_sci->getGameId() == GID_MOTHERGOOSE256) {
- // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for
- // saving a previously restored game.
- // We set the current savedgame-id directly and remove the script
- // code concerning this via script patch.
- s->variables[VAR_GLOBAL][0xB3].offset = SAVEGAMEID_OFFICIALRANGE_START + savegameId;
- }
- } else {
- s->r_acc = TRUE_REG;
- warning("Savegame #%d not found", savegameId);
- }
- }
+#ifdef ENABLE_SCI32
- if (!s->r_acc.isNull()) {
- // no success?
- if (pausedMusic)
- g_sci->_soundCmd->pauseAll(false); // unpause music
+reg_t kCD(EngineState *s, int argc, reg_t *argv) {
+ // TODO: Stub
+ switch (argv[0].toUint16()) {
+ case 0:
+ // Return whether the contents of disc argv[1] is available.
+ return TRUE_REG;
+ case 1:
+ // Return the current CD number
+ return make_reg(0, 1);
+ default:
+ warning("CD(%d)", argv[0].toUint16());
}
- return s->r_acc;
+ return NULL_REG;
}
-reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
- Common::String path = s->_segMan->getString(argv[0]);
+#endif
- debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.offset);
-
- // Always return true
- return make_reg(0, 1);
-}
+// ---- FileIO operations -----------------------------------------------------
reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
if (!s)
@@ -779,6 +249,73 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
}
debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
+#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);
+ }
+ }
+ }
+
+ // Shivers is trying to store savegame descriptions and current spots in
+ // separate .SG files, which are hardcoded in the scripts.
+ // Essentially, there is a normal save file, created by the executable
+ // and an extra hardcoded save file, created by the game scripts, probably
+ // because they didn't want to modify the save/load code to add the extra
+ // information.
+ // Each slot in the book then has two strings, the save description and a
+ // description of the current spot that the player is at. Currently, the
+ // spot strings are always empty (probably related to the unimplemented
+ // kString subop 14, which gets called right before this call).
+ // For now, we don't allow the creation of these files, which means that
+ // all the spot descriptions next to each slot description will be empty
+ // (they are empty anyway). Until a viable solution is found to handle these
+ // extra files and until the spot description strings are initialized
+ // correctly, we resort to virtual files in order to make the load screen
+ // useable. Without this code it is unusable, as the extra information is
+ // always saved to 0.SG for some reason, but on restore the correct file is
+ // used. Perhaps the virtual ID is not taken into account when saving.
+ //
+ // Future TODO: maintain spot descriptions and show them too, ideally without
+ // having to return to this logic of extra hardcoded files.
+ if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
+ if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+ // Game scripts are trying to create a file with the save
+ // description, stop them here
+ debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
+ return SIGNAL_REG;
+ } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+ // Create a virtual file containing the save game description
+ // and slot number, as the game scripts expect.
+ int slotNumber;
+ sscanf(name.c_str(), "%d.SG", &slotNumber);
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+ int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START);
+
+ if (!s->_virtualIndexFile) {
+ // Make the virtual file buffer big enough to avoid having it grow dynamically.
+ // 50 bytes should be more than enough.
+ s->_virtualIndexFile = new VirtualIndexFile(50);
+ }
+
+ s->_virtualIndexFile->seek(0, SEEK_SET);
+ s->_virtualIndexFile->write(saves[savegameNr].name, strlen(saves[savegameNr].name));
+ s->_virtualIndexFile->write("\0", 1);
+ s->_virtualIndexFile->write("\0", 1); // Spot description (empty)
+ s->_virtualIndexFile->seek(0, SEEK_SET);
+ return make_reg(0, VIRTUALFILE_HANDLE);
+ }
+ }
+#endif
+
// QFG import rooms get a virtual filelisting instead of an actual one
if (g_sci->inQfGImportRoom()) {
// We need to find out what the user actually selected, "savedHeroes" is
@@ -794,48 +331,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)
@@ -868,9 +439,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);
@@ -883,15 +464,22 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
}
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);
+ 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;
- int readBytes = fgets_wrapper(s, buf, size, handle);
- s->_segMan->memcpy(argv[0], (const byte*)buf, size);
+#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 readBytes ? argv[0] : NULL_REG;
+ return bytesRead ? argv[0] : NULL_REG;
}
reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
@@ -899,126 +487,55 @@ reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) {
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 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));
-
- 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);
- }
- }
-}
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE)
+ return make_reg(0, s->_virtualIndexFile->seek(offset, whence));
+#endif
-Common::String DirSeeker::getVirtualFilename(uint fileNumber) {
- if (fileNumber >= _virtualFiles.size())
- error("invalid virtual filename access");
- return _virtualFiles[fileNumber];
-}
+ FileHandle *f = getFileFromHandle(s, handle);
-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);
+ 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;
}
- } 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;
+ 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);
}
- 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) {
@@ -1041,6 +558,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
@@ -1163,19 +688,283 @@ reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) {
return TRUE_REG; // slot creation was successful
}
-reg_t kCD(EngineState *s, int argc, reg_t *argv) {
- // TODO: Stub
- switch (argv[0].toUint16()) {
- case 0:
- // Return whether the contents of disc argv[1] is available.
- return TRUE_REG;
- default:
- warning("CD(%d)", argv[0].toUint16());
+#endif
+
+// ---- Save operations -------------------------------------------------------
+
+#ifdef ENABLE_SCI32
+
+reg_t kSave(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+#endif
+
+reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
+ Common::String game_id;
+ int16 virtualId = argv[1].toSint16();
+ int16 savegameId = -1;
+ Common::String game_description;
+ Common::String version;
+
+ if (argc > 3)
+ version = s->_segMan->getString(argv[3]);
+
+ // We check here, we don't want to delete a users save in case we are within a kernel function
+ if (s->executionStackBase) {
+ warning("kSaveGame - won't save from within kernel function");
+ return NULL_REG;
}
- return NULL_REG;
+ if (argv[0].isNull()) {
+ // Direct call, from a patched Game::save
+ if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull()))
+ error("kSaveGame: assumed patched call isn't accurate");
+
+ // we are supposed to show a dialog for the user and let him choose where to save
+ g_sci->_soundCmd->pauseAll(true); // pause music
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
+ savegameId = dialog->runModalWithCurrentTarget();
+ game_description = dialog->getResultString();
+ if (game_description.empty()) {
+ // create our own description for the saved game, the user didnt enter it
+ game_description = dialog->createDefaultSaveDescription(savegameId);
+ }
+ delete dialog;
+ g_sci->_soundCmd->pauseAll(false); // unpause music ( we can't have it paused during save)
+ if (savegameId < 0)
+ return NULL_REG;
+
+ } else {
+ // Real call from script
+ game_id = s->_segMan->getString(argv[0]);
+ if (argv[2].isNull())
+ error("kSaveGame: called with description being NULL");
+ game_description = s->_segMan->getString(argv[2]);
+
+ debug(3, "kSaveGame(%s,%d,%s,%s)", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ if ((virtualId >= SAVEGAMEID_OFFICIALRANGE_START) && (virtualId <= SAVEGAMEID_OFFICIALRANGE_END)) {
+ // savegameId is an actual Id, so search for it just to make sure
+ savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
+ if (findSavegame(saves, savegameId) == -1)
+ return NULL_REG;
+ } else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {
+ // virtualId is low, we assume that scripts expect us to create new slot
+ if (virtualId == s->_lastSaveVirtualId) {
+ // if last virtual id is the same as this one, we assume that caller wants to overwrite last save
+ savegameId = s->_lastSaveNewId;
+ } else {
+ uint savegameNr;
+ // savegameId is in lower range, scripts expect us to create a new slot
+ for (savegameId = 0; savegameId < SAVEGAMEID_OFFICIALRANGE_START; savegameId++) {
+ for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) {
+ if (savegameId == saves[savegameNr].id)
+ break;
+ }
+ if (savegameNr == saves.size())
+ break;
+ }
+ if (savegameId == SAVEGAMEID_OFFICIALRANGE_START)
+ error("kSavegame: no more savegame slots available");
+ }
+ } else {
+ error("kSaveGame: invalid savegameId used");
+ }
+
+ // Save in case caller wants to overwrite last newly created save
+ s->_lastSaveVirtualId = virtualId;
+ s->_lastSaveNewId = savegameId;
+ }
+
+ s->r_acc = NULL_REG;
+
+ Common::String filename = g_sci->getSavegameName(savegameId);
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ Common::OutSaveFile *out;
+
+ out = saveFileMan->openForSaving(filename);
+ if (!out) {
+ warning("Error opening savegame \"%s\" for writing", filename.c_str());
+ } else {
+ if (!gamestate_save(s, out, game_description, version)) {
+ warning("Saving the game failed");
+ } else {
+ s->r_acc = TRUE_REG; // save successful
+ }
+
+ out->finalize();
+ if (out->err()) {
+ warning("Writing the savegame failed");
+ s->r_acc = NULL_REG; // write failure
+ }
+ delete out;
+ }
+
+ return s->r_acc;
}
+reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
+ Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
+ int16 savegameId = argv[1].toSint16();
+ bool pausedMusic = false;
+
+ debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId);
+
+ if (argv[0].isNull()) {
+ // Direct call, either from launcher or from a patched Game::restore
+ if (savegameId == -1) {
+ // we are supposed to show a dialog for the user and let him choose a saved game
+ g_sci->_soundCmd->pauseAll(true); // pause music
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
+ savegameId = dialog->runModalWithCurrentTarget();
+ delete dialog;
+ if (savegameId < 0) {
+ g_sci->_soundCmd->pauseAll(false); // unpause music
+ return s->r_acc;
+ }
+ pausedMusic = true;
+ }
+ // don't adjust ID of the saved game, it's already correct
+ } else {
+ if (argv[2].isNull())
+ error("kRestoreGame: called with parameter 2 being NULL");
+ // Real call from script, we need to adjust ID
+ if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) {
+ warning("Savegame ID %d is not allowed", savegameId);
+ return TRUE_REG;
+ }
+ savegameId -= SAVEGAMEID_OFFICIALRANGE_START;
+ }
+
+ s->r_acc = NULL_REG; // signals success
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+ if (findSavegame(saves, savegameId) == -1) {
+ s->r_acc = TRUE_REG;
+ warning("Savegame ID %d not found", savegameId);
+ } else {
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ Common::String filename = g_sci->getSavegameName(savegameId);
+ Common::SeekableReadStream *in;
+
+ in = saveFileMan->openForLoading(filename);
+ if (in) {
+ // found a savegame file
+
+ gamestate_restore(s, in);
+ delete in;
+
+ if (g_sci->getGameId() == GID_MOTHERGOOSE256) {
+ // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for
+ // saving a previously restored game.
+ // We set the current savedgame-id directly and remove the script
+ // code concerning this via script patch.
+ s->variables[VAR_GLOBAL][0xB3].offset = SAVEGAMEID_OFFICIALRANGE_START + savegameId;
+ }
+ } else {
+ s->r_acc = TRUE_REG;
+ warning("Savegame #%d not found", savegameId);
+ }
+ }
+
+ if (!s->r_acc.isNull()) {
+ // no success?
+ if (pausedMusic)
+ g_sci->_soundCmd->pauseAll(false); // unpause music
+ }
+
+ return s->r_acc;
+}
+
+reg_t kGetSaveDir(EngineState *s, int argc, reg_t *argv) {
+#ifdef ENABLE_SCI32
+ // SCI32 uses a parameter here. It is used to modify a string, stored in a
+ // global variable, so that game scripts store the save directory. We
+ // don't really set a save game directory, thus not setting the string to
+ // anything is the correct thing to do here.
+ //if (argc > 0)
+ // warning("kGetSaveDir called with %d parameter(s): %04x:%04x", argc, PRINT_REG(argv[0]));
+#endif
+ return s->_segMan->getSaveDirPtr();
+}
+
+reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
+ Common::String game_id = s->_segMan->getString(argv[0]);
+ uint16 virtualId = argv[1].toUint16();
+
+ debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), virtualId);
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ // we allow 0 (happens in QfG2 when trying to restore from an empty saved game list) and return false in that case
+ if (virtualId == 0)
+ return NULL_REG;
+
+ // Find saved-game
+ if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
+ error("kCheckSaveGame: called with invalid savegameId");
+ uint savegameId = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
+ int savegameNr = findSavegame(saves, savegameId);
+ if (savegameNr == -1)
+ return NULL_REG;
+
+ // Check for compatible savegame version
+ int ver = saves[savegameNr].version;
+ if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION)
+ return NULL_REG;
+
+ // Otherwise we assume the savegame is OK
+ return TRUE_REG;
+}
+
+reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
+ Common::String game_id = s->_segMan->getString(argv[0]);
+
+ debug(3, "kGetSaveFiles(%s)", game_id.c_str());
+
+ // Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really
+ // mean new slot instead of overwriting the old one
+ s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START;
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+ uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR);
+
+ reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves);
+
+ if (!slot) {
+ warning("kGetSaveFiles: %04X:%04X invalid or too small to hold slot data", PRINT_REG(argv[2]));
+ totalSaves = 0;
+ }
+
+ const uint bufSize = (totalSaves * SCI_MAX_SAVENAME_LENGTH) + 1;
+ char *saveNames = new char[bufSize];
+ char *saveNamePtr = saveNames;
+
+ for (uint i = 0; i < totalSaves; i++) {
+ *slot++ = make_reg(0, saves[i].id + SAVEGAMEID_OFFICIALRANGE_START); // Store the virtual savegame ID ffs. see above
+ strcpy(saveNamePtr, saves[i].name);
+ saveNamePtr += SCI_MAX_SAVENAME_LENGTH;
+ }
+
+ *saveNamePtr = 0; // Terminate list
+
+ s->_segMan->memcpy(argv[1], (byte *)saveNames, bufSize);
+ delete[] saveNames;
+
+ return make_reg(0, totalSaves);
+}
+
+#ifdef ENABLE_SCI32
+
reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
// Normally, this creates the name of the save catalogue/directory to save into.
// First parameter is the string to save the result into. Second is a string
@@ -1205,35 +994,15 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
return argv[0];
}
-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;
- }
+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;
}
#endif
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 2bb8288cb7..377c05935a 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -241,6 +241,7 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
//int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out
//uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts
//uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out
+#if 0
int16 divisions;
// If the game has the pFadeArray selector, another parameter is used here,
@@ -252,7 +253,7 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
} else {
divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?)
}
-
+#endif
if (showStyle > 15) {
warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj));
return s->r_acc;
@@ -308,103 +309,91 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
}
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
- // Used by Phantasmagoria 1 and SQ6. In SQ6, it is used for the messages
- // shown in the scroll window at the bottom of the screen.
-
- // TODO: This is all a stub/skeleton, thus we're invoking kStub() for now
- kStub(s, argc, argv);
-
- switch (argv[0].toUint16()) {
+ // Used by SQ6 and LSL6 hires for the text area in the bottom of the
+ // screen. The relevant scripts also exist in Phantasmagoria 1, but they're
+ // unused. This is always called by scripts 64906 (ScrollerWindow) and
+ // 64907 (ScrollableWindow).
+
+ reg_t kWindow = argv[1];
+ uint16 op = argv[0].toUint16();
+ switch (op) {
case 0: // Init
- // 2 parameters
- // argv[1] points to the scroll object (e.g. textScroller in SQ6)
- // argv[2] is an integer (e.g. 0x32)
- break;
- case 1: // Show message
+ g_sci->_gfxFrameout->initScrollText(argv[2].toUint16()); // maxItems
+ g_sci->_gfxFrameout->clearScrollTexts();
+ return argv[1]; // kWindow
+ case 1: // Show message, called by ScrollableWindow::addString
+ case 14: // Modify message, called by ScrollableWindow::modifyString
// 5 or 6 parameters
// Seems to be called with 5 parameters when the narrator speaks, and
// with 6 when Roger speaks
- // argv[1] unknown (usually 0)
- // argv[2] the text to show
- // argv[3] a small integer (e.g. 0x32)
- // argv[4] a small integer (e.g. 0x54)
- // argv[5] optional, unknown (usually 0)
- warning("kScrollWindow: '%s'", s->_segMan->getString(argv[2]).c_str());
- break;
- case 2: // Clear
- // 2 parameters
- // TODO
+ {
+ Common::String text = s->_segMan->getString(argv[2]);
+ uint16 x = 0;//argv[3].toUint16(); // TODO: can't be x (values are all wrong)
+ uint16 y = 0;//argv[4].toUint16(); // TODO: can't be y (values are all wrong)
+ // TODO: argv[5] is an optional unknown parameter (an integer set to 0)
+ g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14));
+ }
break;
- case 3: // Page up
- // 2 parameters
- // TODO
+ case 2: // Clear, called by ScrollableWindow::erase
+ g_sci->_gfxFrameout->clearScrollTexts();
break;
- case 4: // Page down
- // 2 parameters
+ case 3: // Page up, called by ScrollableWindow::scrollTo
// TODO
+ kStub(s, argc, argv);
break;
- case 5: // Up arrow
- // 2 parameters
+ case 4: // Page down, called by ScrollableWindow::scrollTo
// TODO
+ kStub(s, argc, argv);
break;
- case 6: // Down arrow
- // 2 parameters
- // TODO
+ case 5: // Up arrow, called by ScrollableWindow::scrollTo
+ g_sci->_gfxFrameout->prevScrollText();
break;
- case 7: // Home
- // 2 parameters
- // TODO
+ case 6: // Down arrow, called by ScrollableWindow::scrollTo
+ g_sci->_gfxFrameout->nextScrollText();
break;
- case 8: // End
- // 2 parameters
- // TODO
+ case 7: // Home, called by ScrollableWindow::scrollTo
+ g_sci->_gfxFrameout->firstScrollText();
break;
- case 9: // Resize
- // 3 parameters
- // TODO
+ case 8: // End, called by ScrollableWindow::scrollTo
+ g_sci->_gfxFrameout->lastScrollText();
break;
- case 10: // Where
- // 3 parameters
- // TODO
- break;
- case 11: // Go
- // 4 parameters
- // TODO
- break;
- case 12: // Insert
- // 7 parameters
+ case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize
// TODO
+ kStub(s, argc, argv);
break;
- case 13: // Delete
- // 3 parameters
+ case 10: // Where, called by ScrollableWindow::where
// TODO
+ // argv[2] is an unknown integer
+ kStub(s, argc, argv);
break;
- case 14: // Modify
- // 7 or 8 parameters
+ case 11: // Go, called by ScrollableWindow::scrollTo
+ // 2 extra parameters here
// TODO
+ kStub(s, argc, argv);
break;
- case 15: // Hide
- // 2 parameters
+ case 12: // Insert, called by ScrollableWindow::insertString
+ // 3 extra parameters here
// TODO
+ kStub(s, argc, argv);
break;
- case 16: // Show
- // 2 parameters
- // TODO
+ // case 13 (Delete) is handled below
+ // case 14 (Modify) is handled above
+ case 15: // Hide, called by ScrollableWindow::hide
+ g_sci->_gfxFrameout->toggleScrollText(false);
break;
- case 17: // Destroy
- // 2 parameters
- // TODO
+ case 16: // Show, called by ScrollableWindow::show
+ g_sci->_gfxFrameout->toggleScrollText(true);
break;
- case 18: // Text
- // 2 parameters
- // TODO
+ case 17: // Destroy, called by ScrollableWindow::dispose
+ g_sci->_gfxFrameout->clearScrollTexts();
break;
- case 19: // Reconstruct
- // 3 parameters
- // TODO
+ case 13: // Delete, unused
+ case 18: // Text, unused
+ case 19: // Reconstruct, unused
+ error("kScrollWindow: Unused subop %d invoked", op);
break;
default:
- error("kScrollWindow: unknown subop %d", argv[0].toUint16());
+ error("kScrollWindow: unknown subop %d", op);
break;
}
@@ -617,6 +606,38 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
return s->r_acc;
}
+reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
+ reg_t plane = argv[0];
+ Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16());
+ Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16());
+ // argv[5] is unknown (a number, usually 200)
+ byte color = (byte)argv[6].toUint16();
+ byte priority = (byte)argv[7].toUint16();
+ byte control = (byte)argv[8].toUint16();
+ // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
+ return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
+}
+
+reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
+ reg_t hunkId = argv[0];
+ reg_t plane = argv[1];
+ Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16());
+ Common::Point endPoint(argv[4].toUint16(), argv[5].toUint16());
+ // argv[6] is unknown (a number, usually 200)
+ byte color = (byte)argv[7].toUint16();
+ byte priority = (byte)argv[8].toUint16();
+ byte control = (byte)argv[9].toUint16();
+ // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
+ g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
+ return s->r_acc;
+}
+reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) {
+ reg_t hunkId = argv[0];
+ reg_t plane = argv[1];
+ g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
+ return s->r_acc;
+}
+
#endif
} // End of namespace Sci
diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp
index 2a33df26bc..5a608af034 100644
--- a/engines/sci/engine/klists.cpp
+++ b/engines/sci/engine/klists.cpp
@@ -575,8 +575,11 @@ reg_t kListFirstTrue(EngineState *s, int argc, reg_t *argv) {
// First, check if the target selector is a variable
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
- // Can this happen with variable selectors?
- error("kListFirstTrue: Attempted to access a variable selector");
+ // If it's a variable selector, check its value.
+ // Example: script 64893 in Torin, MenuHandler::isHilited checks
+ // all children for variable selector 0x03ba (bHilited).
+ if (!readSelector(s->_segMan, curObject, slc).isNull())
+ return curObject;
} else {
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
@@ -609,16 +612,16 @@ reg_t kListAllTrue(EngineState *s, int argc, reg_t *argv) {
// First, check if the target selector is a variable
if (lookupSelector(s->_segMan, curObject, slc, &address, NULL) == kSelectorVariable) {
- // Can this happen with variable selectors?
- error("kListAllTrue: Attempted to access a variable selector");
+ // If it's a variable selector, check its value
+ s->r_acc = readSelector(s->_segMan, curObject, slc);
} else {
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
-
- // Check if the result isn't true
- if (s->r_acc.isNull())
- break;
}
+ // Check if the result isn't true
+ if (s->r_acc.isNull())
+ break;
+
curNode = s->_segMan->lookupNode(nextNode);
}
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 2be9432521..2e80764d01 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -371,6 +371,8 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) {
// Anything below that makes Phantasmagoria awfully sluggish, so we're
// setting everything to 500, which makes the game playable.
+ setting.toLowercase();
+
if (setting == "videospeed") {
s->_segMan->strcpy(data, "500");
} else if (setting == "cpu") {
@@ -379,6 +381,19 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) {
s->_segMan->strcpy(data, "586");
} else if (setting == "cpuspeed") {
s->_segMan->strcpy(data, "500");
+ } 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 if (setting == "leakdump") {
+ // An unknown setting in LSL7. Likely used for debugging.
+ s->_segMan->strcpy(data, "");
+ } else if (setting == "startroom") {
+ // Debug setting in LSL7, specifies the room to start from.
+ s->_segMan->strcpy(data, "");
} else {
error("GetConfig: Unknown configuration setting %s", setting.c_str());
}
@@ -386,20 +401,22 @@ reg_t kGetConfig(EngineState *s, int argc, reg_t *argv) {
return argv[1];
}
+// Likely modelled after the Windows 3.1 function GetPrivateProfileInt:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ms724345%28v=vs.85%29.aspx
reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv) {
Common::String category = s->_segMan->getString(argv[0]); // always "config"
- if (category != "config")
- error("GetSierraProfileInt: category isn't 'config', it's '%s'", category.c_str());
-
+ category.toLowercase();
Common::String setting = s->_segMan->getString(argv[1]);
- if (setting != "videospeed")
- error("GetSierraProfileInt: setting isn't 'videospeed', it's '%s'", setting.c_str());
+ setting.toLowercase();
+ // The third parameter is the default value returned if the configuration key is missing
- // The game scripts pass 425 as the third parameter for some unknown reason,
- // as after the call they compare the result to 425 anyway...
+ if (category == "config" && setting == "videospeed") {
+ // We return the same fake value for videospeed as with kGetConfig
+ return make_reg(0, 500);
+ }
- // We return the same fake value for videospeed as with kGetConfig
- return make_reg(0, 500);
+ warning("kGetSierraProfileInt: Returning default value %d for unknown setting %s.%s", argv[2].toSint16(), category.c_str(), setting.c_str());
+ return argv[2];
}
#endif
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index c469f775f9..b378b4d58b 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -140,8 +140,12 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
((argv[3].toUint16() & 0xff) << 16) |
((argv[4].toUint16() & 0xff) << 8) |
(argv[5].toUint16() & 0xff);
- if (argc == 8)
- warning("kDoAudio: Play called with SQ6 extra parameters");
+ if (argc == 8) {
+ // argv[6] is always 1
+ // argv[7] is the contents of global 229 (0xE5)
+ warning("kDoAudio: Play called with SCI2.1 extra parameters: %04x:%04x and %04x:%04x",
+ PRINT_REG(argv[6]), PRINT_REG(argv[7]));
+ }
} else {
warning("kDoAudio: Play called with an unknown number of parameters (%d)", argc);
return NULL_REG;
diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp
index fe8d631497..33b8c15e9f 100644
--- a/engines/sci/engine/kstring.cpp
+++ b/engines/sci/engine/kstring.cpp
@@ -653,10 +653,16 @@ reg_t kString(EngineState *s, int argc, reg_t *argv) {
case 1: // Size
return make_reg(0, s->_segMan->getString(argv[1]).size());
case 2: { // At (return value at an index)
- if (argv[1].segment == s->_segMan->getStringSegmentId())
- return make_reg(0, s->_segMan->lookupString(argv[1])->getRawData()[argv[2].toUint16()]);
-
- return make_reg(0, s->_segMan->getString(argv[1])[argv[2].toUint16()]);
+ // Note that values are put in bytes to avoid sign extension
+ if (argv[1].segment == s->_segMan->getStringSegmentId()) {
+ SciString *string = s->_segMan->lookupString(argv[1]);
+ byte val = string->getRawData()[argv[2].toUint16()];
+ return make_reg(0, val);
+ } else {
+ Common::String string = s->_segMan->getString(argv[1]);
+ byte val = string[argv[2].toUint16()];
+ return make_reg(0, val);
+ }
}
case 3: { // Atput (put value at an index)
SciString *string = s->_segMan->lookupString(argv[1]);
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/object.cpp b/engines/sci/engine/object.cpp
index 78e216cdb5..de028392ea 100644
--- a/engines/sci/engine/object.cpp
+++ b/engines/sci/engine/object.cpp
@@ -112,7 +112,7 @@ bool Object::relocateSci0Sci21(SegmentId segment, int location, size_t scriptSiz
return relocateBlock(_variables, getPos().offset, segment, location, scriptSize);
}
-bool Object::relocateSci3(SegmentId segment, int location, int offset, size_t scriptSize) {
+bool Object::relocateSci3(SegmentId segment, uint32 location, int offset, size_t scriptSize) {
assert(_propertyOffsetsSci3);
for (uint i = 0; i < _variables.size(); ++i) {
@@ -153,7 +153,7 @@ void Object::initSpecies(SegManager *segMan, reg_t addr) {
if (speciesOffset == 0xffff) // -1
setSpeciesSelector(NULL_REG); // no species
else
- setSpeciesSelector(segMan->getClassAddress(speciesOffset, SCRIPT_GET_LOCK, addr));
+ setSpeciesSelector(segMan->getClassAddress(speciesOffset, SCRIPT_GET_LOCK, addr.segment));
}
void Object::initSuperClass(SegManager *segMan, reg_t addr) {
@@ -162,7 +162,7 @@ void Object::initSuperClass(SegManager *segMan, reg_t addr) {
if (superClassOffset == 0xffff) // -1
setSuperClassSelector(NULL_REG); // no superclass
else
- setSuperClassSelector(segMan->getClassAddress(superClassOffset, SCRIPT_GET_LOCK, addr));
+ setSuperClassSelector(segMan->getClassAddress(superClassOffset, SCRIPT_GET_LOCK, addr.segment));
}
bool Object::initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass) {
@@ -286,7 +286,7 @@ void Object::initSelectorsSci3(const byte *buf) {
_variables.resize(properties);
uint16 *propertyIds = (uint16 *)malloc(sizeof(uint16) * properties);
// uint16 *methodOffsets = (uint16 *)malloc(sizeof(uint16) * 2 * methods);
- uint16 *propertyOffsets = (uint16 *)malloc(sizeof(uint16) * properties);
+ uint32 *propertyOffsets = (uint32 *)malloc(sizeof(uint32) * properties);
int propertyCounter = 0;
int methodCounter = 0;
@@ -314,7 +314,8 @@ void Object::initSelectorsSci3(const byte *buf) {
WRITE_SCI11ENDIAN_UINT16(&propertyIds[propertyCounter],
groupBaseId + bit);
_variables[propertyCounter] = make_reg(0, value);
- propertyOffsets[propertyCounter] = (seeker + bit * 2) - buf;
+ uint32 propertyOffset = (seeker + bit * 2) - buf;
+ propertyOffsets[propertyCounter] = propertyOffset;
++propertyCounter;
} else if (value != 0xffff) { // Method
_baseMethod.push_back(groupBaseId + bit);
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index 0ca16b48a2..e8deafa8bd 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -223,7 +223,7 @@ public:
}
bool relocateSci0Sci21(SegmentId segment, int location, size_t scriptSize);
- bool relocateSci3(SegmentId segment, int location, int offset, size_t scriptSize);
+ bool relocateSci3(SegmentId segment, uint32 location, int offset, size_t scriptSize);
int propertyOffsetToId(SegManager *segMan, int propertyOffset) const;
@@ -238,7 +238,7 @@ private:
const byte *_baseObj; /**< base + object offset within base */
const uint16 *_baseVars; /**< Pointer to the varselector area for this object */
Common::Array<uint16> _baseMethod; /**< Pointer to the method selector area for this object */
- uint16 *_propertyOffsetsSci3; /**< This is used to enable relocation of property valuesa in SCI3 */
+ uint32 *_propertyOffsetsSci3; /**< This is used to enable relocation of property valuesa in SCI3 */
Common::Array<reg_t> _variables;
uint16 _methodCount;
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index 404bea799d..cabe5f468a 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -189,7 +189,7 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
assert(mobj);
- // Let the object sync custom data
+ // Let the object sync custom data. Scripts are loaded at this point.
mobj->saveLoadWithSerializer(s);
if (type == SEG_TYPE_SCRIPT) {
@@ -200,9 +200,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
// Hook the script up in the script->segment map
_scriptSegMap[scr->getScriptNumber()] = i;
- // Now, load the script itself
- scr->load(g_sci->getResMan());
-
ObjMap objects = scr->getObjectMap();
for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it)
it->_value.syncBaseObject(scr->getBuf(it->_value.getPos().offset));
@@ -467,7 +464,7 @@ void Script::syncStringHeap(Common::Serializer &s) {
break;
} while (1);
- } else {
+ } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1){
// Strings in SCI1.1 come after the object instances
byte *buf = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2;
@@ -477,6 +474,8 @@ void Script::syncStringHeap(Common::Serializer &s) {
// Now, sync everything till the end of the buffer
s.syncBytes(buf, _heapSize - (buf - _heapStart));
+ } else if (getSciVersion() == SCI_VERSION_3) {
+ warning("TODO: syncStringHeap(): Implement SCI3 variant");
}
}
@@ -484,7 +483,7 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) {
s.syncAsSint32LE(_nr);
if (s.isLoading())
- init(_nr, g_sci->getResMan());
+ load(_nr, g_sci->getResMan());
s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _bufSize
s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _scriptSize
s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _heapSize
diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp
index 8b26969f4a..d018872b43 100644
--- a/engines/sci/engine/script.cpp
+++ b/engines/sci/engine/script.cpp
@@ -32,58 +32,48 @@
namespace Sci {
-Script::Script() : SegmentObj(SEG_TYPE_SCRIPT) {
+Script::Script() : SegmentObj(SEG_TYPE_SCRIPT), _buf(NULL) {
+ freeScript();
+}
+
+Script::~Script() {
+ freeScript();
+}
+
+void Script::freeScript() {
_nr = 0;
+
+ free(_buf);
_buf = NULL;
_bufSize = 0;
_scriptSize = 0;
+ _heapStart = NULL;
_heapSize = 0;
- _synonyms = NULL;
- _heapStart = NULL;
_exportTable = NULL;
+ _numExports = 0;
+ _synonyms = NULL;
+ _numSynonyms = 0;
_localsOffset = 0;
_localsSegment = 0;
_localsBlock = NULL;
_localsCount = 0;
+ _lockers = 1;
_markedAsDeleted = false;
+ _objects.clear();
}
-Script::~Script() {
+void Script::load(int script_nr, ResourceManager *resMan) {
freeScript();
-}
-
-void Script::freeScript() {
- free(_buf);
- _buf = NULL;
- _bufSize = 0;
-
- _objects.clear();
-}
-void Script::init(int script_nr, ResourceManager *resMan) {
Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, script_nr), 0);
-
if (!script)
error("Script %d not found", script_nr);
- _localsOffset = 0;
- _localsBlock = NULL;
- _localsCount = 0;
-
- _markedAsDeleted = false;
-
_nr = script_nr;
- _buf = 0;
- _heapStart = 0;
-
- _scriptSize = script->size;
- _bufSize = script->size;
- _heapSize = 0;
-
- _lockers = 1;
+ _bufSize = _scriptSize = script->size;
if (getSciVersion() == SCI_VERSION_0_EARLY) {
_bufSize += READ_LE_UINT16(script->data) * 2;
@@ -115,16 +105,18 @@ void Script::init(int script_nr, ResourceManager *resMan) {
// scheme. We need an overlaying mechanism, or a mechanism to split script parts
// in different segments to handle these. For now, simply stop when such a script
// is found.
+ //
+ // Known large SCI 3 scripts are:
+ // Lighthouse: 9, 220, 270, 351, 360, 490, 760, 765, 800
+ // LSL7: 240, 511, 550
+ // Phantasmagoria 2: none (hooray!)
+ // RAMA: 70
+ //
// TODO: Remove this once such a mechanism is in place
if (script->size > 65535)
error("TODO: SCI script %d is over 64KB - it's %d bytes long. This can't "
"be handled at the moment, thus stopping", script_nr, script->size);
}
-}
-
-void Script::load(ResourceManager *resMan) {
- Resource *script = resMan->findResource(ResourceId(kResourceTypeScript, _nr), 0);
- assert(script != 0);
uint extraLocalsWorkaround = 0;
if (g_sci->getGameId() == GID_FANMADE && _nr == 1 && script->size == 11140) {
@@ -156,11 +148,6 @@ void Script::load(ResourceManager *resMan) {
memcpy(_heapStart, heap->data, heap->size);
}
- _exportTable = 0;
- _numExports = 0;
- _synonyms = 0;
- _numSynonyms = 0;
-
if (getSciVersion() <= SCI_VERSION_1_LATE) {
_exportTable = (const uint16 *)findBlockSCI0(SCI_OBJ_EXPORTS);
if (_exportTable) {
@@ -212,7 +199,7 @@ void Script::load(ResourceManager *resMan) {
_localsOffset = 0;
if (_localsOffset + _localsCount * 2 + 1 >= (int)_bufSize) {
- error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, _bufSize);
+ error("Locals extend beyond end of script: offset %04x, count %d vs size %d", _localsOffset, _localsCount, (int)_bufSize);
//_localsCount = (_bufSize - _localsOffset) >> 1;
}
}
@@ -553,7 +540,7 @@ void Script::initializeClasses(SegManager *segMan) {
uint16 marker;
bool isClass = false;
- uint16 classpos;
+ uint32 classpos;
int16 species = 0;
while (true) {
@@ -666,7 +653,7 @@ void Script::initializeObjectsSci11(SegManager *segMan, SegmentId segmentId) {
// Copy base from species class, as we need its selector IDs
obj->setSuperClassSelector(
- segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG));
+ segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, 0));
// If object is instance, get -propDict- from class and set it for this
// object. This is needed for ::isMemberOf() to work.
@@ -699,7 +686,7 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) {
reg_t reg = make_reg(segmentId, seeker - _buf);
Object *obj = scriptObjInit(reg);
- obj->setSuperClassSelector(segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, NULL_REG));
+ obj->setSuperClassSelector(segMan->getClassAddress(obj->getSuperClassSelector().offset, SCRIPT_GET_LOCK, 0));
seeker += READ_SCI11ENDIAN_UINT16(seeker + 2);
}
@@ -738,7 +725,7 @@ Common::Array<reg_t> Script::listAllDeallocatable(SegmentId segId) const {
Common::Array<reg_t> Script::listAllOutgoingReferences(reg_t addr) const {
Common::Array<reg_t> tmp;
- if (addr.offset <= _bufSize && addr.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && RAW_IS_OBJECT(_buf + addr.offset)) {
+ if (addr.offset <= _bufSize && addr.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET && offsetIsObject(addr.offset)) {
const Object *obj = getObject(addr.offset);
if (obj) {
// Note all local variables, if we have a local variable environment
@@ -774,4 +761,8 @@ Common::Array<reg_t> Script::listObjectReferences() const {
return tmp;
}
+bool Script::offsetIsObject(uint16 offset) const {
+ return (READ_SCI11ENDIAN_UINT16((const byte *)_buf + offset + SCRIPT_OBJECT_MAGIC_OFFSET) == SCRIPT_OBJECT_MAGIC_NUMBER);
+}
+
} // End of namespace Sci
diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h
index 1ebae3b7a8..0c99f13235 100644
--- a/engines/sci/engine/script.h
+++ b/engines/sci/engine/script.h
@@ -57,7 +57,7 @@ private:
int _lockers; /**< Number of classes and objects that require this script */
size_t _scriptSize;
size_t _heapSize;
- uint16 _bufSize;
+ size_t _bufSize;
const uint16 *_exportTable; /**< Abs. offset of the export table or 0 if not present */
uint16 _numExports; /**< Number of entries in the exports table */
@@ -89,14 +89,14 @@ public:
void syncLocalsBlock(SegManager *segMan);
ObjMap &getObjectMap() { return _objects; }
const ObjMap &getObjectMap() const { return _objects; }
+ bool offsetIsObject(uint16 offset) const;
public:
Script();
~Script();
void freeScript();
- void init(int script_nr, ResourceManager *resMan);
- void load(ResourceManager *resMan);
+ void load(int script_nr, ResourceManager *resMan);
void matchSignatureAndPatch(uint16 scriptNr, byte *scriptData, const uint32 scriptSize);
int32 findSignature(const SciScriptSignature *signature, const byte *scriptData, const uint32 scriptSize);
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index cc127c8dbc..ac02022ee7 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -227,7 +227,7 @@ Object *SegManager::getObject(reg_t pos) const {
} else if (mobj->getType() == SEG_TYPE_SCRIPT) {
Script *scr = (Script *)mobj;
if (pos.offset <= scr->getBufSize() && pos.offset >= -SCRIPT_OBJECT_MAGIC_OFFSET
- && RAW_IS_OBJECT(scr->getBuf(pos.offset))) {
+ && scr->offsetIsObject(pos.offset)) {
obj = scr->getObject(pos.offset);
}
}
@@ -939,7 +939,7 @@ void SegManager::createClassTable() {
_resMan->unlockResource(vocab996);
}
-reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller) {
+reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment) {
if (classnr == 0xffff)
return NULL_REG;
@@ -956,7 +956,7 @@ reg_t SegManager::getClassAddress(int classnr, ScriptLoadType lock, reg_t caller
return NULL_REG;
}
} else
- if (caller.segment != the_class->reg.segment)
+ if (callerSegment != the_class->reg.segment)
getScript(the_class->reg.segment)->incrementLockers();
return the_class->reg;
@@ -977,8 +977,7 @@ int SegManager::instantiateScript(int scriptNum) {
scr = allocateScript(scriptNum, &segmentId);
}
- scr->init(scriptNum, _resMan);
- scr->load(_resMan);
+ scr->load(scriptNum, _resMan);
scr->initializeLocals(this);
scr->initializeClasses(this);
scr->initializeObjects(this, segmentId);
diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h
index 62e711e686..356a1b04a7 100644
--- a/engines/sci/engine/seg_manager.h
+++ b/engines/sci/engine/seg_manager.h
@@ -125,7 +125,7 @@ private:
public:
// TODO: document this
- reg_t getClassAddress(int classnr, ScriptLoadType lock, reg_t caller);
+ reg_t getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment);
/**
* Return a pointer to the specified script.
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/vm.cpp b/engines/sci/engine/vm.cpp
index 162dce9fcc..7dc397c11e 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -996,7 +996,7 @@ void run_vm(EngineState *s) {
case op_class: // 0x28 (40)
// Get class address
s->r_acc = s->_segMan->getClassAddress((unsigned)opparams[0], SCRIPT_GET_LOCK,
- s->xs->addr.pc);
+ s->xs->addr.pc.segment);
break;
case 0x29: // (41)
@@ -1021,7 +1021,7 @@ void run_vm(EngineState *s) {
case op_super: // 0x2b (43)
// Send to any class
- r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc);
+ r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc.segment);
if (!r_temp.isPointer())
error("[VM]: Invalid superclass in object");
diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h
index cdd9b9a06e..67b9dd44eb 100644
--- a/engines/sci/engine/vm.h
+++ b/engines/sci/engine/vm.h
@@ -61,8 +61,6 @@ struct Class {
reg_t reg; ///< offset; script-relative offset, segment: 0 if not instantiated
};
-#define RAW_IS_OBJECT(datablock) (READ_SCI11ENDIAN_UINT16(((const byte *) datablock) + SCRIPT_OBJECT_MAGIC_OFFSET) == SCRIPT_OBJECT_MAGIC_NUMBER)
-
// A reference to an object's variable.
// The object is stored as a reg_t, the variable as an index into _variables
struct ObjVarRef {
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
};