From 045613af95b84b6a57526125da35b949d0f431ea Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Wed, 13 Jun 2012 12:54:40 +0300 Subject: SCI: Shuffle the kernel functions inside kfile.cpp This puts them in the order that they are defined in the kernel tables --- engines/sci/engine/kfile.cpp | 1144 +++++++++++++++++++++--------------------- 1 file changed, 580 insertions(+), 564 deletions(-) diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 052332957b..445a019368 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -152,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 @@ -194,292 +182,187 @@ reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { return make_reg(0, 1); } -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); +reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { + Common::String path = s->_segMan->getString(argv[0]); - Common::Array saves; - listSavegames(saves); + debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.offset); - // 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; + // Always return true + return make_reg(0, 1); +} - // 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; +#ifdef ENABLE_SCI32 - // Check for compatible savegame version - int ver = saves[savegameNr].version; - if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION) - return NULL_REG; +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()); + } - // Otherwise we assume the savegame is OK - return TRUE_REG; + return NULL_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()); +#endif - // 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; +// ---- FileIO operations ----------------------------------------------------- - Common::Array saves; - listSavegames(saves); - uint totalSaves = MIN(saves.size(), MAX_SAVEGAME_NR); +reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves); +reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { + Common::String name = s->_segMan->getString(argv[0]); - if (!slot) { - warning("kGetSaveFiles: %04X:%04X invalid or too small to hold slot data", PRINT_REG(argv[2])); - totalSaves = 0; +#ifdef ENABLE_SCI32 + if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { + if (s->_virtualIndexFile) { + return make_reg(0, VIRTUALFILE_HANDLE); + } else { + Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH); + Common::String wrappedName = g_sci->wrapFilename(englishName); + if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) { + s->_virtualIndexFile = new VirtualIndexFile(wrappedName); + return make_reg(0, VIRTUALFILE_HANDLE); + } + } } +#endif - const uint bufSize = (totalSaves * SCI_MAX_SAVENAME_LENGTH) + 1; - char *saveNames = new char[bufSize]; - char *saveNamePtr = saveNames; + // SCI32 can call K_FILEIO_OPEN with only one argument. It seems to + // just be checking if it exists. + int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16(); + bool unwrapFilename = true; - 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; + // SQ4 floppy prepends /\ to the filenames + if (name.hasPrefix("/\\")) { + name.deleteChar(0); + name.deleteChar(0); } - *saveNamePtr = 0; // Terminate list + // SQ4 floppy attempts to update the savegame index file sq4sg.dir when + // deleting saved games. We don't use an index file for saving or loading, + // so just stop the game from modifying the file here in order to avoid + // having it saved in the ScummVM save directory. + if (name == "sq4sg.dir") { + debugC(kDebugLevelFile, "Not opening unused file sq4sg.dir"); + return SIGNAL_REG; + } - s->_segMan->memcpy(argv[1], (byte *)saveNames, bufSize); - delete[] saveNames; + if (name.empty()) { + // Happens many times during KQ1 (e.g. when typing something) + debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); + return SIGNAL_REG; + } + debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); - return make_reg(0, totalSaves); + // 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 + // already destroyed when we get here. That's why we need to remember + // selection via kDrawControl. + name = s->_dirseeker.getVirtualFilename(s->_chosenQfGImportItem); + unwrapFilename = false; + } + + return file_open(s, name, mode, unwrapFilename); } -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; +reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { + debugC(kDebugLevelFile, "kFileIO(close): %d", argv[0].toUint16()); - if (argc > 3) - version = s->_segMan->getString(argv[3]); + if (argv[0] == SIGNAL_REG) + return s->r_acc; + + uint16 handle = argv[0].toUint16(); - // 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; +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) { + s->_virtualIndexFile->close(); + return SIGNAL_REG; } +#endif - 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()); + 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; + } - Common::Array saves; - listSavegames(saves); + if (getSciVersion() <= SCI_VERSION_0_LATE) + return s->r_acc; // SCI0 semantics: no value returned + return NULL_REG; +} - 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"); - } +reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) { + 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); - // Save in case caller wants to overwrite last newly created save - s->_lastSaveVirtualId = virtualId; - s->_lastSaveNewId = savegameId; +#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 - s->r_acc = NULL_REG; + // 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); - Common::String filename = g_sci->getSavegameName(savegameId); - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - Common::OutSaveFile *out; + delete[] buf; + return make_reg(0, bytesRead); +} - 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 +reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { + 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); - Common::Array saves; - listSavegames(saves); - if (findSavegame(saves, savegameId) == -1) { - s->r_acc = TRUE_REG; - warning("Savegame ID %d not found", savegameId); +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) { + s->_virtualIndexFile->write(buf, size); + success = true; } 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); +#endif + FileHandle *f = getFileFromHandle(s, handle); + if (f) { + f->_out->write(buf, size); + success = true; } +#ifdef ENABLE_SCI32 } +#endif - if (!s->r_acc.isNull()) { - // no success? - if (pausedMusic) - g_sci->_soundCmd->pauseAll(false); // unpause music - } - - return s->r_acc; -} - -reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { - Common::String path = s->_segMan->getString(argv[0]); - - debug(3, "kValidPath(%s) -> %d", path.c_str(), s->r_acc.offset); - - // Always return true - return make_reg(0, 1); -} - -reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { - if (!s) - return make_reg(0, getSciVersion()); - error("not supposed to call this"); + delete[] buf; + if (success) + return NULL_REG; + return make_reg(0, 6); // DOS - invalid handle } -reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { +reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); - -#ifdef ENABLE_SCI32 - if (name == PHANTASMAGORIA_SAVEGAME_INDEX) { - if (s->_virtualIndexFile) { - return make_reg(0, VIRTUALFILE_HANDLE); - } else { - Common::String englishName = g_sci->getSciLanguageString(name, K_LANG_ENGLISH); - Common::String wrappedName = g_sci->wrapFilename(englishName); - if (!g_sci->getSaveFileManager()->listSavefiles(wrappedName).empty()) { - s->_virtualIndexFile = new VirtualIndexFile(wrappedName); - return make_reg(0, VIRTUALFILE_HANDLE); - } - } - } -#endif - - // SCI32 can call K_FILEIO_OPEN with only one argument. It seems to - // just be checking if it exists. - int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16(); - bool unwrapFilename = true; + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + bool result; // SQ4 floppy prepends /\ to the filenames if (name.hasPrefix("/\\")) { @@ -487,407 +370,546 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { name.deleteChar(0); } - // SQ4 floppy attempts to update the savegame index file sq4sg.dir when - // deleting saved games. We don't use an index file for saving or loading, - // so just stop the game from modifying the file here in order to avoid - // having it saved in the ScummVM save directory. - if (name == "sq4sg.dir") { - debugC(kDebugLevelFile, "Not opening unused file sq4sg.dir"); - return SIGNAL_REG; - } - - if (name.empty()) { - // Happens many times during KQ1 (e.g. when typing something) - debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); - return SIGNAL_REG; - } - debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); + // Special case for SQ4 floppy: This game has hardcoded names for all of + // its savegames, and they are all named "sq4sg.xxx", where xxx is the + // slot. We just take the slot number here, and delete the appropriate + // save game. + if (name.hasPrefix("sq4sg.")) { + // Special handling for SQ4... get the slot number and construct the + // save game name. + int slotNum = atoi(name.c_str() + name.size() - 3); + Common::Array saves; + listSavegames(saves); + int savedir_nr = saves[slotNum].id; + name = g_sci->getSavegameName(savedir_nr); + result = saveFileMan->removeSavefile(name); + } else if (getSciVersion() >= SCI_VERSION_2) { + // 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); + } - // 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 - // already destroyed when we get here. That's why we need to remember - // selection via kDrawControl. - name = s->_dirseeker.getVirtualFilename(s->_chosenQfGImportItem); - unwrapFilename = false; +#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); } - return file_open(s, name, mode, unwrapFilename); + debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); + if (result) + return NULL_REG; + return make_reg(0, 2); // DOS - file not found error code } -reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) { - debugC(kDebugLevelFile, "kFileIO(close): %d", argv[0].toUint16()); +reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) { + uint16 maxsize = argv[1].toUint16(); + char *buf = new char[maxsize]; + uint16 handle = argv[2].toUint16(); + debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize); + uint32 bytesRead; - if (argv[0] == SIGNAL_REG) - return s->r_acc; - - uint16 handle = argv[0].toUint16(); +#ifdef ENABLE_SCI32 + if (handle == VIRTUALFILE_HANDLE) + bytesRead = s->_virtualIndexFile->readLine(buf, maxsize); + else +#endif + bytesRead = fgets_wrapper(s, buf, maxsize, handle); + + s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); + delete[] buf; + return bytesRead ? argv[0] : NULL_REG; +} + +reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { + int handle = argv[0].toUint16(); + Common::String str = s->_segMan->getString(argv[1]); + debugC(kDebugLevelFile, "kFileIO(writeString): %d", handle); #ifdef ENABLE_SCI32 if (handle == VIRTUALFILE_HANDLE) { - s->_virtualIndexFile->close(); - return SIGNAL_REG; + s->_virtualIndexFile->write(str.c_str(), str.size()); + return NULL_REG; } #endif FileHandle *f = getFileFromHandle(s, handle); + if (f) { - f->close(); + f->_out->write(str.c_str(), str.size()); if (getSciVersion() <= SCI_VERSION_0_LATE) return s->r_acc; // SCI0 semantics: no value returned - return SIGNAL_REG; + return NULL_REG; } if (getSciVersion() <= SCI_VERSION_0_LATE) return s->r_acc; // SCI0 semantics: no value returned - return NULL_REG; + return make_reg(0, 6); // DOS - invalid handle } -reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) { +reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) { 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); + uint16 offset = ABS(argv[1].toSint16()); // can be negative + uint16 whence = argv[2].toUint16(); + debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); #ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) { - bytesRead = s->_virtualIndexFile->read(buf, size); - } else { + if (handle == VIRTUALFILE_HANDLE) + return make_reg(0, s->_virtualIndexFile->seek(offset, whence)); #endif - FileHandle *f = getFileFromHandle(s, handle); - if (f) - bytesRead = f->_in->read(buf, size); -#ifdef ENABLE_SCI32 + + FileHandle *f = getFileFromHandle(s, handle); + + 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; + } + + 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); } -#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); + return SIGNAL_REG; +} - delete[] buf; - return make_reg(0, bytesRead); +reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv) { + Common::String mask = s->_segMan->getString(argv[0]); + reg_t buf = argv[1]; + int attr = argv[2].toUint16(); // We won't use this, Win32 might, though... + debugC(kDebugLevelFile, "kFileIO(findFirst): %s, 0x%x", mask.c_str(), attr); + + // We remove ".*". mask will get prefixed, so we will return all additional files for that gameid + if (mask == "*.*") + mask = "*"; + return s->_dirseeker.firstFile(mask, buf, s->_segMan); } -reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { - 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); +reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) { + debugC(kDebugLevelFile, "kFileIO(findNext)"); + return s->_dirseeker.nextFile(s->_segMan); +} + +reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { + Common::String name = s->_segMan->getString(argv[0]); #ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) { - s->_virtualIndexFile->write(buf, size); - success = true; - } else { + // 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 - FileHandle *f = getFileFromHandle(s, handle); - if (f) { - f->_out->write(buf, size); - success = true; - } -#ifdef ENABLE_SCI32 + + bool exists = false; + + // Check for regular file + exists = Common::File::exists(name); + + // Check for a savegame with the name + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + if (!exists) + exists = !saveFileMan->listSavefiles(name).empty(); + + // Try searching for the file prepending "target-" + const Common::String wrappedName = g_sci->wrapFilename(name); + if (!exists) { + exists = !saveFileMan->listSavefiles(wrappedName).empty(); } -#endif - delete[] buf; - if (success) - return NULL_REG; - return make_reg(0, 6); // DOS - invalid handle -} + // SCI2+ debug mode + if (DebugMan.isDebugChannelEnabled(kDebugLevelDebugMode)) { + if (!exists && name == "1.scr") // PQ4 + exists = true; + if (!exists && name == "18.scr") // QFG4 + exists = true; + if (!exists && name == "99.scr") // GK1, KQ7 + exists = true; + if (!exists && name == "classes") // GK2, SQ6, LSL7 + exists = true; + } -reg_t kFileIOReadString(EngineState *s, int argc, reg_t *argv) { - uint16 maxsize = argv[1].toUint16(); - char *buf = new char[maxsize]; - uint16 handle = argv[2].toUint16(); - debugC(kDebugLevelFile, "kFileIO(readString): %d, %d", handle, maxsize); - uint32 bytesRead; + // Special case for non-English versions of LSL5: The English version of + // LSL5 calls kFileIO(), case K_FILEIO_OPEN for reading to check if + // memory.drv exists (which is where the game's password is stored). If + // it's not found, it calls kFileIO() again, case K_FILEIO_OPEN for + // writing and creates a new file. Non-English versions call kFileIO(), + // case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be + // found. We create a default memory.drv file with no password, so that + // the game can continue. + if (!exists && name == "memory.drv") { + // Create a new file, and write the bytes for the empty password + // string inside + byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 }; + Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName); + for (int i = 0; i < 10; i++) + outFile->writeByte(defaultContent[i]); + outFile->finalize(); + exists = !outFile->err(); // check whether we managed to create the file. + delete outFile; + } -#ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) - bytesRead = s->_virtualIndexFile->readLine(buf, maxsize); - else -#endif - bytesRead = fgets_wrapper(s, buf, maxsize, handle); + // Special case for KQ6 Mac: The game checks for two video files to see + // if they exist before it plays them. Since we support multiple naming + // schemes for resource fork files, we also need to support that here in + // case someone has a "HalfDome.bin" file, etc. + if (!exists && g_sci->getGameId() == GID_KQ6 && g_sci->getPlatform() == Common::kPlatformMacintosh && + (name == "HalfDome" || name == "Kq6Movie")) + exists = Common::MacResManager::exists(name); - s->_segMan->memcpy(argv[0], (const byte*)buf, maxsize); - delete[] buf; - return bytesRead ? argv[0] : NULL_REG; + debugC(kDebugLevelFile, "kFileIO(fileExists) %s -> %d", name.c_str(), exists); + return make_reg(0, exists); } -reg_t kFileIOWriteString(EngineState *s, int argc, reg_t *argv) { - int handle = argv[0].toUint16(); - Common::String str = s->_segMan->getString(argv[1]); - debugC(kDebugLevelFile, "kFileIO(writeString): %d", handle); +reg_t kFileIORename(EngineState *s, int argc, reg_t *argv) { + Common::String oldName = s->_segMan->getString(argv[0]); + Common::String newName = s->_segMan->getString(argv[1]); + + // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 + // returns -1 on fail. We just return -1 for all versions. + if (g_sci->getSaveFileManager()->renameSavefile(oldName, newName)) + return NULL_REG; + else + return SIGNAL_REG; +} #ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) { - s->_virtualIndexFile->write(str.c_str(), str.size()); +reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv) { + // Read the byte into the low byte of the accumulator + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (!f) return NULL_REG; - } -#endif + return make_reg(0, (s->r_acc.toUint16() & 0xff00) | f->_in->readByte()); +} - FileHandle *f = getFileFromHandle(s, handle); +reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) { + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (f) + f->_out->writeByte(argv[1].toUint16() & 0xff); + return s->r_acc; // FIXME: does this really not return anything? +} - 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 +reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) { + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (!f) return NULL_REG; - } + return make_reg(0, f->_in->readUint16LE()); +} - 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 kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) { + FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + if (f) + f->_out->writeUint16LE(argv[1].toUint16()); + return s->r_acc; // FIXME: does this really not return anything? } -reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { - Common::String name = s->_segMan->getString(argv[0]); - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - bool result; +reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) { + // Used in Shivers when the user enters his name on the guest book + // in the beginning to start the game. - // SQ4 floppy prepends /\ to the filenames - if (name.hasPrefix("/\\")) { - name.deleteChar(0); - name.deleteChar(0); - } + // Creates a new save slot, and returns if the operation was successful - // Special case for SQ4 floppy: This game has hardcoded names for all of - // its savegames, and they are all named "sq4sg.xxx", where xxx is the - // slot. We just take the slot number here, and delete the appropriate - // save game. - if (name.hasPrefix("sq4sg.")) { - // Special handling for SQ4... get the slot number and construct the - // save game name. - int slotNum = atoi(name.c_str() + name.size() - 3); - Common::Array saves; - listSavegames(saves); - int savedir_nr = saves[slotNum].id; - name = g_sci->getSavegameName(savedir_nr); - result = saveFileMan->removeSavefile(name); - } else if (getSciVersion() >= SCI_VERSION_2) { - // 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); - } + // Argument 0 denotes the save slot as a negative integer, 2 means "0" + // Argument 1 is a string, with the file name, obtained from kSave(5). + // The interpreter checks if it can be written to (by checking for free + // disk space and write permissions) -#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); - } + // We don't really use or need any of this... - debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); - if (result) - return NULL_REG; - return make_reg(0, 2); // DOS - file not found error code + uint16 saveSlot = argv[0].toUint16(); + char* fileName = s->_segMan->lookupString(argv[1])->getRawData(); + warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName); + + return TRUE_REG; // slot creation was successful } -reg_t kFileIOSeek(EngineState *s, int argc, reg_t *argv) { - uint16 handle = argv[0].toUint16(); - uint16 offset = ABS(argv[1].toSint16()); // can be negative - uint16 whence = argv[2].toUint16(); - debugC(kDebugLevelFile, "kFileIO(seek): %d, %d, %d", handle, offset, whence); +#endif + +// ---- Save operations ------------------------------------------------------- #ifdef ENABLE_SCI32 - if (handle == VIRTUALFILE_HANDLE) - return make_reg(0, s->_virtualIndexFile->seek(offset, whence)); + +reg_t kSave(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + #endif - FileHandle *f = getFileFromHandle(s, handle); +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 (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; + 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 + 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; - 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); + } 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 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; } - return SIGNAL_REG; -} + s->r_acc = NULL_REG; -reg_t kFileIOFindFirst(EngineState *s, int argc, reg_t *argv) { - Common::String mask = s->_segMan->getString(argv[0]); - reg_t buf = argv[1]; - int attr = argv[2].toUint16(); // We won't use this, Win32 might, though... - debugC(kDebugLevelFile, "kFileIO(findFirst): %s, 0x%x", mask.c_str(), attr); + 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; + } - // We remove ".*". mask will get prefixed, so we will return all additional files for that gameid - if (mask == "*.*") - mask = "*"; - return s->_dirseeker.firstFile(mask, buf, s->_segMan); + return s->r_acc; } -reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) { - debugC(kDebugLevelFile, "kFileIO(findNext)"); - return s->_dirseeker.nextFile(s->_segMan); -} +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; -reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { - Common::String name = s->_segMan->getString(argv[0]); + debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); -#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 + 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; + } - bool exists = false; + s->r_acc = NULL_REG; // signals success - // Check for regular file - exists = Common::File::exists(name); + Common::Array 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; - // Check for a savegame with the name - Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - if (!exists) - exists = !saveFileMan->listSavefiles(name).empty(); + in = saveFileMan->openForLoading(filename); + if (in) { + // found a savegame file - // Try searching for the file prepending "target-" - const Common::String wrappedName = g_sci->wrapFilename(name); - if (!exists) { - exists = !saveFileMan->listSavefiles(wrappedName).empty(); - } + gamestate_restore(s, in); + delete in; - // SCI2+ debug mode - if (DebugMan.isDebugChannelEnabled(kDebugLevelDebugMode)) { - if (!exists && name == "1.scr") // PQ4 - exists = true; - if (!exists && name == "18.scr") // QFG4 - exists = true; - if (!exists && name == "99.scr") // GK1, KQ7 - exists = true; - if (!exists && name == "classes") // GK2, SQ6, LSL7 - exists = true; + 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); + } } - // Special case for non-English versions of LSL5: The English version of - // LSL5 calls kFileIO(), case K_FILEIO_OPEN for reading to check if - // memory.drv exists (which is where the game's password is stored). If - // it's not found, it calls kFileIO() again, case K_FILEIO_OPEN for - // writing and creates a new file. Non-English versions call kFileIO(), - // case K_FILEIO_FILE_EXISTS instead, and fail if memory.drv can't be - // found. We create a default memory.drv file with no password, so that - // the game can continue. - if (!exists && name == "memory.drv") { - // Create a new file, and write the bytes for the empty password - // string inside - byte defaultContent[] = { 0xE9, 0xE9, 0xEB, 0xE1, 0x0D, 0x0A, 0x31, 0x30, 0x30, 0x30 }; - Common::WriteStream *outFile = saveFileMan->openForSaving(wrappedName); - for (int i = 0; i < 10; i++) - outFile->writeByte(defaultContent[i]); - outFile->finalize(); - exists = !outFile->err(); // check whether we managed to create the file. - delete outFile; + if (!s->r_acc.isNull()) { + // no success? + if (pausedMusic) + g_sci->_soundCmd->pauseAll(false); // unpause music } - // Special case for KQ6 Mac: The game checks for two video files to see - // if they exist before it plays them. Since we support multiple naming - // schemes for resource fork files, we also need to support that here in - // case someone has a "HalfDome.bin" file, etc. - if (!exists && g_sci->getGameId() == GID_KQ6 && g_sci->getPlatform() == Common::kPlatformMacintosh && - (name == "HalfDome" || name == "Kq6Movie")) - exists = Common::MacResManager::exists(name); + return s->r_acc; +} - debugC(kDebugLevelFile, "kFileIO(fileExists) %s -> %d", name.c_str(), exists); - return make_reg(0, exists); +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 kFileIORename(EngineState *s, int argc, reg_t *argv) { - Common::String oldName = s->_segMan->getString(argv[0]); - Common::String newName = s->_segMan->getString(argv[1]); +reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { + Common::String game_id = s->_segMan->getString(argv[0]); + uint16 virtualId = argv[1].toUint16(); - // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 - // returns -1 on fail. We just return -1 for all versions. - if (g_sci->getSaveFileManager()->renameSavefile(oldName, newName)) - return NULL_REG; - else - return SIGNAL_REG; -} + debug(3, "kCheckSaveGame(%s, %d)", game_id.c_str(), virtualId); -#ifdef ENABLE_SCI32 -reg_t kFileIOReadByte(EngineState *s, int argc, reg_t *argv) { - // Read the byte into the low byte of the accumulator - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); - if (!f) + Common::Array 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; - return make_reg(0, (s->r_acc.toUint16() & 0xff00) | f->_in->readByte()); -} -reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) { - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); - if (f) - f->_out->writeByte(argv[1].toUint16() & 0xff); - return s->r_acc; // FIXME: does this really not return anything? -} + // 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; -reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) { - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); - if (!f) + // Check for compatible savegame version + int ver = saves[savegameNr].version; + if (ver < MINIMUM_SAVEGAME_VERSION || ver > CURRENT_SAVEGAME_VERSION) return NULL_REG; - return make_reg(0, f->_in->readUint16LE()); -} -reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) { - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); - if (f) - f->_out->writeUint16LE(argv[1].toUint16()); - return s->r_acc; // FIXME: does this really not return anything? + // Otherwise we assume the savegame is OK + return TRUE_REG; } -reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) { - // Used in Shivers when the user enters his name on the guest book - // in the beginning to start the game. +reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { + Common::String game_id = s->_segMan->getString(argv[0]); - // Creates a new save slot, and returns if the operation was successful + debug(3, "kGetSaveFiles(%s)", game_id.c_str()); - // Argument 0 denotes the save slot as a negative integer, 2 means "0" - // Argument 1 is a string, with the file name, obtained from kSave(5). - // The interpreter checks if it can be written to (by checking for free - // disk space and write permissions) + // 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; - // We don't really use or need any of this... + Common::Array saves; + listSavegames(saves); + uint totalSaves = MIN(saves.size(), MAX_SAVEGAME_NR); - uint16 saveSlot = argv[0].toUint16(); - char* fileName = s->_segMan->lookupString(argv[1])->getRawData(); - warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName); + reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves); - return TRUE_REG; // slot creation was successful -} + if (!slot) { + warning("kGetSaveFiles: %04X:%04X invalid or too small to hold slot data", PRINT_REG(argv[2])); + totalSaves = 0; + } -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()); + 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; } - return NULL_REG; + *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 @@ -928,12 +950,6 @@ reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -reg_t kSave(EngineState *s, int argc, reg_t *argv) { - if (!s) - return make_reg(0, getSciVersion()); - error("not supposed to call this"); -} - #endif } // End of namespace Sci -- cgit v1.2.3