aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/engine/kfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/engine/kfile.cpp')
-rw-r--r--engines/sci/engine/kfile.cpp633
1 files changed, 455 insertions, 178 deletions
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index e8b9d0461d..6aad256664 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -51,6 +51,7 @@ 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);
+extern bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc);
/**
* Writes the cwd to the supplied address and returns the address in acc.
@@ -158,33 +159,37 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) {
}
reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) {
- if (argc > 1) {
- // SCI1.1/SCI32
- // TODO: don't know if those are right for SCI32 as well
- // Please note that sierra sci supported both calls either w/ or w/o opcode in SCI1.1
- switch (argv[1].toUint16()) {
- case 0: // return saved game size
- return make_reg(0, 0); // we return 0
-
- case 1: // return free harddisc space (shifted right somehow)
- return make_reg(0, 0x7fff); // we return maximum
-
- case 2: // same as call w/o opcode
- break;
- return make_reg(0, 1);
-
- default:
- error("kCheckFreeSpace: called with unknown sub-op %d", argv[1].toUint16());
- }
+ // A file path to test is also passed to this function as a separate
+ // argument, but we do not actually check anything, so it is unused
+
+ enum {
+ kSaveGameSize = 0,
+ kFreeDiskSpace = 1,
+ kEnoughSpaceToSave = 2
+ };
+
+ int16 subop;
+ // In SCI2.1mid, the call is moved into kFileIO and the arguments are
+ // flipped
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+ subop = argc > 0 ? argv[0].toSint16() : 2;
+ } else {
+ subop = argc > 1 ? argv[1].toSint16() : 2;
}
- Common::String path = s->_segMan->getString(argv[0]);
+ switch (subop) {
+ case kSaveGameSize:
+ return make_reg(0, 0);
- debug(3, "kCheckFreeSpace(%s)", path.c_str());
- // We simply always pretend that there is enough space. The alternative
- // would be to write a big test file, which is not nice on systems where
- // doing so is very slow.
- return make_reg(0, 1);
+ case kFreeDiskSpace: // in KiB; up to 32MiB maximum
+ return make_reg(0, 0x7fff);
+
+ case kEnoughSpaceToSave:
+ return make_reg(0, 1);
+
+ default:
+ error("kCheckFreeSpace: called with unknown sub-op %d", subop);
+ }
}
reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
@@ -224,6 +229,45 @@ reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) {
// ---- FileIO operations -----------------------------------------------------
+#ifdef ENABLE_SCI32
+static bool isSaveCatalogue(const Common::String &name) {
+ return name == "autosave.cat" || name.hasSuffix("sg.cat");
+}
+
+// SCI32 save game scripts check for, and write directly to, the save game
+// catalogue. Since ScummVM does not use these catalogues, when looking for a
+// catalogue, we instead check for save games within ScummVM that are logically
+// equivalent to the behaviour of SSCI.
+static bool saveCatalogueExists(const Common::String &name) {
+ bool exists = false;
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+
+ // There will always be one save game in some games, the "new game"
+ // game, which should be ignored when deciding if there are any save
+ // games available
+ uint numPermanentSaves;
+ switch (g_sci->getGameId()) {
+ case GID_TORIN:
+ case GID_LSL7:
+ case GID_LIGHTHOUSE:
+ numPermanentSaves = 1;
+ break;
+ default:
+ numPermanentSaves = 0;
+ break;
+ }
+
+ // Torin uses autosave.cat; LSL7 uses autosvsg.cat
+ if (name == "autosave.cat" || name == "autosvsg.cat") {
+ exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty();
+ } else {
+ exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > numPermanentSaves;
+ }
+
+ return exists;
+}
+#endif
+
reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
if (!s)
return make_reg(0, getSciVersion());
@@ -233,9 +277,13 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
Common::String name = s->_segMan->getString(argv[0]);
- // 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();
+ 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;
+ }
+
+ int mode = argv[1].toUint16();
bool unwrapFilename = true;
// SQ4 floppy prepends /\ to the filenames
@@ -253,94 +301,158 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
return SIGNAL_REG;
}
- // Torin's autosave system checks for the presence of autosave.cat
- // by opening it. Since we don't use .cat files, we instead check
- // for autosave.000 or autosave.001.
- //
- // The same logic is being followed for torinsg.cat - this shows
- // the "Open..." button when continuing a game.
- //
- // This has the added benefit of not detecting an SSCI autosave.cat
- // accompanying SSCI autosave files that we wouldn't be able to load.
- if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) {
- Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
- const Common::String pattern = (name == "autosave.cat") ? g_sci->wrapFilename("autosave.###") : g_sci->getSavegamePattern();
- bool exists = !saveFileMan->listSavefiles(pattern).empty();
- if (exists) {
- // Dummy handle. Torin only checks if this is SIGNAL_REG,
- // and calls kFileIOClose on it.
- return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
- } else {
- return SIGNAL_REG;
+#ifdef ENABLE_SCI32
+ // GK1, GK2, KQ7, LSL6hires, Phant1, PQ4, PQ:SWAT, and SQ6 read in
+ // their game version from the VERSION file
+ if (name.compareToIgnoreCase("version") == 0) {
+ unwrapFilename = false;
+
+ // LSL6hires version is in a file with an empty extension
+ if (Common::File::exists(name + ".")) {
+ name += ".";
}
}
- 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);
+ if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
+ // Shivers stores the name and score of save games in separate %d.SG
+ // files, which are used by the save/load screen
+ if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+ // Suppress creation of the SG file, since it is not necessary
+ 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 saveNo;
+ sscanf(name.c_str(), "%d.SG", &saveNo);
+ saveNo += kSaveIdShift;
+
+ SavegameDesc save;
+ fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
+
+ Common::String score;
+ if (!save.highScore) {
+ score = Common::String::format("%u", save.lowScore);
+ } else {
+ score = Common::String::format("%u%03u", save.highScore, save.lowScore);
+ }
- if (name.hasPrefix("sciAudio\\")) {
- // fan-made sciAudio extension, don't create those files and instead return a virtual handle
- return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO);
- }
+ const uint nameLength = strlen(save.name);
+ const uint size = nameLength + /* \r\n */ 2 + score.size();
+ char *buffer = (char *)malloc(size);
+ memcpy(buffer, save.name, nameLength);
+ buffer[nameLength] = '\r';
+ buffer[nameLength + 1] = '\n';
+ memcpy(buffer + nameLength + 2, score.c_str(), score.size());
-#ifdef ENABLE_SCI32
- // 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")) {
+ const uint handle = findFreeFileHandle(s);
+
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._out = nullptr;
+ s->_fileHandles[handle]._name = "";
+
+ return make_reg(0, handle);
+ }
+ } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES && name.hasSuffix(".DTA")) {
+ // MGDX stores the name and avatar ID in separate %d.DTA files, which
+ // are used by the save/load screen
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
+ // Suppress creation of the DTA file, since it is not necessary
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);
+ int saveNo;
+ sscanf(name.c_str(), "%d.DTA", &saveNo);
+ saveNo += kSaveIdShift;
- Common::Array<SavegameDesc> saves;
- listSavegames(saves);
- int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START);
+ SavegameDesc save;
+ fillSavegameDesc(g_sci->getSavegameName(saveNo), &save);
- int size = strlen(saves[savegameNr].name) + 2;
- char *buf = (char *)malloc(size);
- strcpy(buf, saves[savegameNr].name);
- buf[size - 1] = 0; // Spot description (empty)
+ const Common::String avatarId = Common::String::format("%02d", save.avatarId);
+ const uint nameLength = strlen(save.name);
+ const uint size = nameLength + /* \r\n */ 2 + avatarId.size() + 1;
+ char *buffer = (char *)malloc(size);
+ memcpy(buffer, save.name, nameLength);
+ buffer[nameLength] = '\r';
+ buffer[nameLength + 1] = '\n';
+ memcpy(buffer + nameLength + 2, avatarId.c_str(), avatarId.size() + 1);
- uint handle = findFreeFileHandle(s);
+ const uint handle = findFreeFileHandle(s);
- s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buf, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);
s->_fileHandles[handle]._out = nullptr;
s->_fileHandles[handle]._name = "";
return make_reg(0, handle);
}
+ } else if (g_sci->getGameId() == GID_KQ7) {
+ // KQ7 creates a temp.tmp file to perform an atomic rewrite of the
+ // catalogue, but since we do not create catalogues for most SCI32
+ // games, ignore the write
+ if (name == "temp.tmp") {
+ return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ }
+
+ // KQ7 tries to read out game information from catalogues directly
+ // instead of using the standard kSaveGetFiles function
+ if (name == "kq7cdsg.cat") {
+ if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) {
+ // Suppress creation of the catalogue file, since it is not necessary
+ debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());
+ return SIGNAL_REG;
+ } else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ const uint recordSize = sizeof(int16) + SCI_MAX_SAVENAME_LENGTH;
+ const uint numSaves = MIN<uint>(saves.size(), 10);
+ const uint size = numSaves * recordSize + /* terminator */ 2;
+ byte *const buffer = (byte *)malloc(size);
+
+ byte *out = buffer;
+ for (uint i = 0; i < numSaves; ++i) {
+ WRITE_UINT16(out, saves[i].id - kSaveIdShift);
+ Common::strlcpy((char *)out + sizeof(int16), saves[i].name, SCI_MAX_SAVENAME_LENGTH);
+ out += recordSize;
+ }
+ WRITE_UINT16(out, 0xFFFF);
+
+ const uint handle = findFreeFileHandle(s);
+ s->_fileHandles[handle]._in = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES);
+ s->_fileHandles[handle]._out = nullptr;
+ s->_fileHandles[handle]._name = "";
+
+ return make_reg(0, handle);
+ }
+ }
+ }
+
+ // See kMakeSaveCatName
+ if (name == "fake.cat") {
+ return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ }
+
+ if (isSaveCatalogue(name)) {
+ const bool exists = saveCatalogueExists(name);
+ if (exists) {
+ // Dummy handle is used to represent the catalogue and ignore any
+ // direct game script writes
+ return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ } else {
+ return SIGNAL_REG;
+ }
}
#endif
+ debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode);
+
+ if (name.hasPrefix("sciAudio\\")) {
+ // fan-made sciAudio extension, don't create those files and instead return a virtual handle
+ return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO);
+ }
+
// 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
@@ -363,7 +475,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
if (handle >= VIRTUALFILE_HANDLE_START) {
// it's a virtual handle? ignore it
- return SIGNAL_REG;
+ return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;
}
FileHandle *f = getFileFromHandle(s, handle);
@@ -371,7 +483,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {
f->close();
if (getSciVersion() <= SCI_VERSION_0_LATE)
return s->r_acc; // SCI0 semantics: no value returned
- return SIGNAL_REG;
+ return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;
}
if (getSciVersion() <= SCI_VERSION_0_LATE)
@@ -403,18 +515,37 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {
reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {
uint16 handle = argv[0].toUint16();
uint16 size = argv[2].toUint16();
+
+#ifdef ENABLE_SCI32
+ if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) {
+ return make_reg(0, size);
+ }
+#endif
+
char *buf = new char[size];
+ uint bytesWritten = 0;
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);
- success = true;
+ bytesWritten = f->_out->write(buf, size);
+ success = !f->_out->err();
}
delete[] buf;
+
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ if (!success) {
+ return SIGNAL_REG;
+ }
+
+ return make_reg(0, bytesWritten);
+ }
+#endif
+
if (success)
return NULL_REG;
return make_reg(0, 6); // DOS - invalid handle
@@ -444,19 +575,35 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {
int savedir_nr = saves[slotNum].id;
name = g_sci->getSavegameName(savedir_nr);
result = saveFileMan->removeSavefile(name);
+#ifdef ENABLE_SCI32
} else if (getSciVersion() >= SCI_VERSION_2) {
+ // Special case for KQ7, basically identical to the SQ4 case above,
+ // where the game hardcodes its save game names
+ if (name.hasPrefix("kq7cdsg.")) {
+ int saveNo = atoi(name.c_str() + name.size() - 3);
+ name = g_sci->getSavegameName(saveNo + kSaveIdShift);
+ }
+
// 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);
}
+#endif
} else {
const Common::String wrappedName = g_sci->wrapFilename(name);
result = saveFileMan->removeSavefile(wrappedName);
}
debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str());
+
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return make_reg(0, result);
+ }
+#endif
+
if (result)
return NULL_REG;
return make_reg(0, 2); // DOS - file not found error code
@@ -567,6 +714,12 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+ if (isSaveCatalogue(name)) {
+ return saveCatalogueExists(name) ? TRUE_REG : NULL_REG;
+ }
+#endif
+
// TODO: It may apparently be worth caching the existence of
// phantsg.dir, and possibly even keeping it open persistently
@@ -662,7 +815,7 @@ 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?
+ return s->r_acc;
}
reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) {
@@ -676,27 +829,13 @@ 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?
+ return s->r_acc;
}
-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.
-
- // Creates a new save slot, and returns if the operation was successful
-
- // 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)
-
- // We don't really use or need any of this...
-
- 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 kFileIOGetCWD(EngineState *s, int argc, reg_t *argv) {
+ SciArray &fileName = *s->_segMan->lookupArray(argv[0]);
+ fileName.fromString("C:\\SIERRA\\");
+ return argv[0];
}
reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv) {
@@ -721,6 +860,13 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) {
#endif
reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
+ // slot 0 is the ScummVM auto-save slot, which is not used by us, but is
+ // still reserved
+ enum {
+ SAVEGAMESLOT_FIRST = 1,
+ SAVEGAMESLOT_LAST = 99
+ };
+
Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
int16 virtualId = argv[1].toSint16();
int16 savegameId = -1;
@@ -736,13 +882,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
- // Torin has two sets of saves: autosave.### and torinsg.###, both with
- // their own slots and .cat file.
- // The autosave system uses autosave.000 and autosave.001.
- // It also checks the presence of autosave.cat to determine if it should
- // show the chapter selection menu on startup. (See kFileIOOpen.)
- bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave";
-
if (argv[0].isNull()) {
// Direct call, from a patched Game::save
if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull()))
@@ -761,14 +900,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
g_sci->_soundCmd->pauseAll(false); // unpause music (we can't have it paused during save)
if (savegameId < 0)
return NULL_REG;
-
- } else if (torinAutosave) {
- if (argv[2].isNull())
- error("kSaveGame: called with description being NULL");
- game_description = s->_segMan->getString(argv[2]);
- savegameId = virtualId;
-
- debug(3, "kSaveGame(%s,%d,%s,%s) [Torin autosave]", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());
} else {
// Real call from script
if (argv[2].isNull())
@@ -844,10 +975,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
Common::OutSaveFile *out;
- if (torinAutosave) {
- filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId));
- }
-
out = saveFileMan->openForSaving(filename);
if (!out) {
warning("Error opening savegame \"%s\" for writing", filename.c_str());
@@ -876,10 +1003,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId);
-
- // See comment in kSaveGame
- bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave";
-
if (argv[0].isNull()) {
// Direct call, either from launcher or from a patched Game::restore
if (savegameId == -1) {
@@ -895,7 +1018,7 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
pausedMusic = true;
}
// don't adjust ID of the saved game, it's already correct
- } else if (!torinAutosave) {
+ } else {
if (g_sci->getGameId() == GID_JONES) {
// Jones has one save slot only
savegameId = 0;
@@ -912,9 +1035,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
s->r_acc = NULL_REG; // signals success
Common::Array<SavegameDesc> saves;
- if (!torinAutosave)
- listSavegames(saves);
- if (!torinAutosave && findSavegame(saves, savegameId) == -1) {
+ listSavegames(saves);
+ if (findSavegame(saves, savegameId) == -1) {
s->r_acc = TRUE_REG;
warning("Savegame ID %d not found", savegameId);
} else {
@@ -922,10 +1044,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
Common::String filename = g_sci->getSavegameName(savegameId);
Common::SeekableReadStream *in;
- if (torinAutosave) {
- filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId));
- }
-
in = saveFileMan->openForLoading(filename);
if (in) {
// found a savegame file
@@ -998,10 +1116,6 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) {
}
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;
@@ -1010,6 +1124,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
listSavegames(saves);
uint totalSaves = MIN<uint>(saves.size(), MAX_SAVEGAME_NR);
+ Common::String game_id = s->_segMan->getString(argv[0]);
+
+ debug(3, "kGetSaveFiles(%s)", game_id.c_str());
+
reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves);
if (!slot) {
@@ -1037,46 +1155,205 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
#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
- // with game parameters. We don't have a use for this at all, as we have our own
- // savegame directory management, thus we always return an empty string.
- return argv[0];
+reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
+ const bool isScummVMSave = argv[0].isNull();
+ Common::String gameName = "";
+ int16 saveNo;
+ Common::String saveDescription;
+ Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]);
+
+ if (isScummVMSave) {
+ // ScummVM call, from a patched Game::save
+ g_sci->_soundCmd->pauseAll(true);
+ GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
+ saveNo = dialog.runModalWithCurrentTarget();
+ g_sci->_soundCmd->pauseAll(false);
+
+ if (saveNo < 0) {
+ // User cancelled save
+ return NULL_REG;
+ }
+
+ saveDescription = dialog.getResultString();
+ if (saveDescription.empty()) {
+ saveDescription = dialog.createDefaultSaveDescription(saveNo);
+ }
+ } else {
+ // Native script call
+ gameName = s->_segMan->getString(argv[0]);
+ saveNo = argv[1].toSint16();
+ saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+ }
+
+ debugC(kDebugLevelFile, "Game name %s save %d desc %s ver %s", gameName.c_str(), saveNo, saveDescription.c_str(), gameVersion.c_str());
+
+ // Auto-save system used by Torin and LSL7
+ if (gameName == "Autosave" || gameName == "Autosv") {
+ if (saveNo == 0) {
+ // Autosave slot 0 is the autosave
+ } else {
+ // Autosave slot 1 is a "new game" save
+ saveNo = kNewGameId;
+ }
+ } else if (!isScummVMSave) {
+ // ScummVM save screen will give a pre-corrected save number, but native
+ // save-load will not
+ saveNo += kSaveIdShift;
+ }
+
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ const Common::String filename = g_sci->getSavegameName(saveNo);
+ Common::OutSaveFile *saveStream = saveFileMan->openForSaving(filename);
+
+ if (saveStream == nullptr) {
+ warning("Error opening savegame \"%s\" for writing", filename.c_str());
+ return NULL_REG;
+ }
+
+ if (!gamestate_save(s, saveStream, saveDescription, gameVersion)) {
+ warning("Saving the game failed");
+ saveStream->finalize();
+ delete saveStream;
+ return NULL_REG;
+ }
+
+ saveStream->finalize();
+ if (saveStream->err()) {
+ warning("Writing the savegame failed");
+ delete saveStream;
+ return NULL_REG;
+ }
+
+ delete saveStream;
+ return TRUE_REG;
}
-reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
- // Creates a savegame name from a slot number. Used when deleting saved games.
- // Param 0: the output buffer (same as in kMakeSaveCatName)
- // Param 1: a string with game parameters, ignored
- // Param 2: the selected slot
+reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
+ const bool isScummVMRestore = argv[0].isNull();
+ Common::String gameName = "";
+ int16 saveNo = argv[1].toSint16();
+ const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
+
+ if (isScummVMRestore && saveNo == -1) {
+ // ScummVM call, either from lancher or a patched Game::restore
+ g_sci->_soundCmd->pauseAll(true);
+ GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false);
+ saveNo = dialog.runModalWithCurrentTarget();
+ g_sci->_soundCmd->pauseAll(false);
+
+ if (saveNo < 0) {
+ // User cancelled restore
+ return s->r_acc;
+ }
+ } else {
+ gameName = s->_segMan->getString(argv[0]);
+ }
- SciString *resultString = s->_segMan->lookupString(argv[0]);
- uint16 virtualId = argv[2].toUint16();
- if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END))
- error("kMakeSaveFileName: invalid savegame ID specified");
- uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START;
+ if (gameName == "Autosave" || gameName == "Autosv") {
+ if (saveNo == 0) {
+ // Autosave slot 0 is the autosave
+ } else {
+ // Autosave slot 1 is a "new game" save
+ saveNo = kNewGameId;
+ }
+ } else if (!isScummVMRestore) {
+ // ScummVM save screen will give a pre-corrected save number, but native
+ // save-load will not
+ saveNo += kSaveIdShift;
+ }
+
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ const Common::String filename = g_sci->getSavegameName(saveNo);
+ Common::SeekableReadStream *saveStream = saveFileMan->openForLoading(filename);
+
+ if (saveStream == nullptr) {
+ warning("Savegame #%d not found", saveNo);
+ return NULL_REG;
+ }
+
+ gamestate_restore(s, saveStream);
+ delete saveStream;
+
+ gamestate_afterRestoreFixUp(s, saveNo);
+ return TRUE_REG;
+}
+
+reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) {
+ const Common::String gameName = s->_segMan->getString(argv[0]);
+ int16 saveNo = argv[1].toSint16();
+ const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]);
Common::Array<SavegameDesc> saves;
listSavegames(saves);
- Common::String filename = g_sci->getSavegameName(saveSlot);
- resultString->fromString(filename);
+ if (gameName == "Autosave" || gameName == "Autosv") {
+ if (saveNo == 1) {
+ saveNo = kNewGameId;
+ }
+ } else {
+ saveNo += kSaveIdShift;
+ }
- return argv[0];
-}
+ SavegameDesc save;
+ if (!fillSavegameDesc(g_sci->getSavegameName(saveNo), &save)) {
+ return NULL_REG;
+ }
+
+ if (save.version < MINIMUM_SAVEGAME_VERSION ||
+ save.version > CURRENT_SAVEGAME_VERSION ||
+ save.gameVersion != gameVersion) {
-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)
+ return NULL_REG;
+ }
- // This function has to return something other than 0 to proceed
return TRUE_REG;
}
+reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
+ // argv[0] is gameName, used in SSCI as the name of the save game catalogue
+ // but unused here since ScummVM does not support multiple catalogues
+ SciArray &descriptions = *s->_segMan->lookupArray(argv[1]);
+ SciArray &saveIds = *s->_segMan->lookupArray(argv[2]);
+
+ Common::Array<SavegameDesc> saves;
+ listSavegames(saves);
+
+ // Normally SSCI limits to 20 games per directory, but ScummVM allows more
+ // than that with games that use the standard save-load dialogue
+ descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true);
+ saveIds.resize(saves.size() + 1, true);
+
+ for (uint i = 0; i < saves.size(); ++i) {
+ const SavegameDesc &save = saves[i];
+ char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i);
+ Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH);
+ saveIds.setFromInt16(i, save.id - kSaveIdShift);
+ }
+
+ descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0';
+ saveIds.setFromInt16(saves.size(), 0);
+
+ return make_reg(0, saves.size());
+}
+
+reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
+ // ScummVM does not use SCI catalogues for save games, but game scripts try
+ // to write out catalogues manually after a save game is deleted, so we need
+ // to be able to identify and ignore these IO operations by always giving
+ // back a fixed catalogue name and then ignoring it in kFileIO
+ SciArray &outCatName = *s->_segMan->lookupArray(argv[0]);
+ outCatName.fromString("fake.cat");
+ return argv[0];
+}
+
+reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) {
+ SciArray &outFileName = *s->_segMan->lookupArray(argv[0]);
+ // argv[1] is the game name, which is not used by ScummVM
+ const int16 saveNo = argv[2].toSint16();
+ outFileName.fromString(g_sci->getSavegameName(saveNo + kSaveIdShift));
+ return argv[0];
+}
+
#endif
} // End of namespace Sci