SCI32: Fix multiple bugs in kSave
1. Shift save numbers up/down for game scripts that rely on save game numbers starting from 0 to work correctly 2. Add fake file operations to support KQ7 save games 3. Hide autosave games from native save/load list to match SSCI.
This commit is contained in:
parent
2629269212
commit
fba8568484
4 changed files with 112 additions and 69 deletions
|
@ -355,7 +355,7 @@ void listSavegames(Common::Array<SavegameDesc> &saves) {
|
|||
|
||||
#ifdef ENABLE_SCI32
|
||||
const int id = strtol(filename.end() - 3, NULL, 10);
|
||||
if (id == kNewGameId) {
|
||||
if (id == kNewGameId || id == kAutoSaveId) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -41,8 +41,13 @@ enum {
|
|||
|
||||
#ifdef ENABLE_SCI32
|
||||
enum {
|
||||
kAutoSaveId = 0, ///< The save game slot number for autosaves
|
||||
kNewGameId = 999 ///< The save game slot number for a "new game" save
|
||||
kAutoSaveId = 0, ///< The save game slot number for autosaves
|
||||
kNewGameId = 999, ///< The save game slot number for a "new game" save
|
||||
|
||||
// SCI engine expects game IDs to start at 0, but slot 0 in ScummVM is
|
||||
// reserved for autosave, so non-autosave games get their IDs shifted up
|
||||
// when saving or restoring, and shifted down when enumerating save games
|
||||
kSaveIdShift = 1
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -277,7 +277,11 @@ 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]);
|
||||
|
||||
assert(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;
|
||||
}
|
||||
|
||||
int mode = argv[1].toUint16();
|
||||
bool unwrapFilename = true;
|
||||
|
@ -298,36 +302,12 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
|
|||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// See kMakeSaveCatName
|
||||
if (name == "fake.cat") {
|
||||
return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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 (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);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
// Shivers stores the name and score of save games in separate %d.SG files,
|
||||
// which are used by the save/load screen
|
||||
if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) {
|
||||
|
@ -340,6 +320,7 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
|
|||
// 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);
|
||||
|
@ -370,8 +351,73 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
|
|||
return make_reg(0, handle);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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
|
||||
|
@ -1066,13 +1112,15 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {
|
|||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
|
||||
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 (argv[0].isNull()) {
|
||||
if (isScummVMSave) {
|
||||
// ScummVM call, from a patched Game::save
|
||||
g_sci->_soundCmd->pauseAll(true);
|
||||
GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true);
|
||||
|
@ -1080,6 +1128,7 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
|
|||
g_sci->_soundCmd->pauseAll(false);
|
||||
|
||||
if (saveNo < 0) {
|
||||
// User cancelled save
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
|
@ -1094,6 +1143,8 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
|
|||
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) {
|
||||
|
@ -1102,35 +1153,10 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
|
|||
// Autosave slot 1 is a "new game" save
|
||||
saveNo = kNewGameId;
|
||||
}
|
||||
} else if (saveNo == 0) {
|
||||
// SSCI save games normally start from save number 0, but this is
|
||||
// reserved for the autosave game in ScummVM. So, any time a game tries
|
||||
// to save to slot 0 (and it isn't an autosave), it should instead go to
|
||||
// the next highest free ID
|
||||
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
|
||||
Common::StringArray saveNames = saveFileMan->listSavefiles(g_sci->getSavegamePattern());
|
||||
Common::sort(saveNames.begin(), saveNames.end());
|
||||
if (saveNames.size()) {
|
||||
int lastId = 0;
|
||||
for (int i = 0; i < (int)saveNames.size(); ++i) {
|
||||
const int id = strtol(saveNames[i].end() - 3, NULL, 10);
|
||||
if (id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (id != lastId + 1) {
|
||||
saveNo = lastId + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
++lastId;
|
||||
}
|
||||
}
|
||||
|
||||
// There was no gap, so this save goes to a brand new slot
|
||||
if (saveNo == 0) {
|
||||
saveNo = saveNames.size() + 1;
|
||||
}
|
||||
} 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();
|
||||
|
@ -1161,17 +1187,20 @@ reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) {
|
|||
}
|
||||
|
||||
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 (argv[0].isNull() && saveNo == -1) {
|
||||
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 {
|
||||
|
@ -1185,6 +1214,10 @@ reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) {
|
|||
// 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();
|
||||
|
@ -1211,8 +1244,12 @@ reg_t kCheckSaveGame32(EngineState *s, int argc, reg_t *argv) {
|
|||
Common::Array<SavegameDesc> saves;
|
||||
listSavegames(saves);
|
||||
|
||||
if ((gameName == "Autosave" || gameName == "Autosv") && saveNo == 1) {
|
||||
saveNo = kNewGameId;
|
||||
if (gameName == "Autosave" || gameName == "Autosv") {
|
||||
if (saveNo == 1) {
|
||||
saveNo = kNewGameId;
|
||||
}
|
||||
} else {
|
||||
saveNo += kSaveIdShift;
|
||||
}
|
||||
|
||||
SavegameDesc save;
|
||||
|
@ -1240,7 +1277,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
|
|||
listSavegames(saves);
|
||||
|
||||
// Normally SSCI limits to 20 games per directory, but ScummVM allows more
|
||||
// than that
|
||||
// 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);
|
||||
|
||||
|
@ -1248,7 +1285,7 @@ reg_t kGetSaveFiles32(EngineState *s, int argc, reg_t *argv) {
|
|||
const SavegameDesc &save = saves[i];
|
||||
char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i);
|
||||
Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH);
|
||||
saveIds.int16At(i) = save.id;
|
||||
saveIds.int16At(i) = save.id - kSaveIdShift;
|
||||
}
|
||||
|
||||
descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0';
|
||||
|
@ -1270,7 +1307,7 @@ reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) {
|
|||
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
|
||||
int16 saveNo = argv[2].toSint16();
|
||||
const int16 saveNo = argv[2].toSint16();
|
||||
outFileName.fromString(g_sci->getSavegameName(saveNo));
|
||||
return argv[0];
|
||||
}
|
||||
|
|
|
@ -590,11 +590,12 @@ void SciEngine::patchGameSaveRestore() {
|
|||
case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all
|
||||
case GID_HOYLE2: // gets confused, see hoyle1
|
||||
case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required
|
||||
case GID_KQ7: // has custom save/load code
|
||||
case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs
|
||||
case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs
|
||||
case GID_PHANTASMAGORIA: // has custom save/load code
|
||||
case GID_SHIVERS: // has custom save/load code
|
||||
case GID_PQSWAT: // has custom save/load code
|
||||
case GID_SHIVERS: // has custom save/load code
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue