From 3044593da01c055ad274b2ae552b8a285ce4fdb0 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 19 Apr 2009 01:01:54 +0000 Subject: SCUMM: Moved o5_saveLoadGame and o5_saveLoadVars to ScummEngine_v4 (the highest SCUMM version to implement these opcodes. Actually, our code was bugged in so far as we only ever invoked o5_saveLoadGame in V3 games, never in V4 games (but this properly never mattered ;) svn-id: r40014 --- engines/scumm/script_v4.cpp | 319 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 3 deletions(-) (limited to 'engines/scumm/script_v4.cpp') diff --git a/engines/scumm/script_v4.cpp b/engines/scumm/script_v4.cpp index 08df1e4653..b8964c4194 100644 --- a/engines/scumm/script_v4.cpp +++ b/engines/scumm/script_v4.cpp @@ -47,13 +47,19 @@ void ScummEngine_v4::setupOpcodes() { OPCODE(0xdc, o4_oldRoomEffect); OPCODE(0x0f, o4_ifState); - OPCODE(0x2f, o4_ifNotState); OPCODE(0x4f, o4_ifState); - OPCODE(0x6f, o4_ifNotState); OPCODE(0x8f, o4_ifState); - OPCODE(0xaf, o4_ifNotState); OPCODE(0xcf, o4_ifState); + + OPCODE(0x2f, o4_ifNotState); + OPCODE(0x6f, o4_ifNotState); + OPCODE(0xaf, o4_ifNotState); OPCODE(0xef, o4_ifNotState); + + OPCODE(0xa7, o4_saveLoadVars); + + OPCODE(0x22, o4_saveLoadGame); + OPCODE(0xa2, o4_saveLoadGame); } void ScummEngine_v4::o4_ifState() { @@ -171,4 +177,311 @@ void ScummEngine_v4::o4_oldRoomEffect() { } } +void ScummEngine_v4::o4_saveLoadVars() { + if (fetchScriptByte() == 1) + saveVars(); + else + loadVars(); +} + +enum StringIds { + // The string IDs used by Indy3 to store the episode resp. series IQ points. + // Note that we save the episode IQ points but load the series IQ points, + // which matches the original Indy3 save/load code. See also the notes + // on Feature Request #1666521. + STRINGID_IQ_EPISODE = 7, + STRINGID_IQ_SERIES = 9, + // The string IDs of the first savegame name, used as an offset to determine + // the IDs of all savenames. + // Loom is the only game whose savenames start with a different ID. + STRINGID_SAVENAME1 = 10, + STRINGID_SAVENAME1_LOOM = 9 +}; + +void ScummEngine_v4::saveVars() { + int a, b; + + while ((_opcode = fetchScriptByte()) != 0) { + switch (_opcode & 0x1F) { + case 0x01: // write a range of variables + getResultPos(); + a = _resultVarNumber; + getResultPos(); + b = _resultVarNumber; + debug(0, "stub saveVars: vars %d -> %d", a, b); + break; + case 0x02: // write a range of string variables + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + + if (a == STRINGID_IQ_EPISODE && b == STRINGID_IQ_EPISODE) { + if (_game.id == GID_INDY3) { + saveIQPoints(); + } + break; + } + // FIXME: changing savegame-names not supported + break; + case 0x03: // open file + a = resStrLen(_scriptPointer); + strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a); + _saveLoadVarsFilename[a] = '\0'; + _scriptPointer += a + 1; + break; + case 0x04: + return; + case 0x1F: // close file + _saveLoadVarsFilename[0] = '\0'; + return; + } + } +} + +void ScummEngine_v4::loadVars() { + int a, b; + + while ((_opcode = fetchScriptByte()) != 0) { + switch (_opcode & 0x1F) { + case 0x01: // read a range of variables + getResultPos(); + a = _resultVarNumber; + getResultPos(); + b = _resultVarNumber; + debug(0, "stub loadVars: vars %d -> %d", a, b); + break; + case 0x02: // read a range of string variables + a = getVarOrDirectByte(PARAM_1); + b = getVarOrDirectByte(PARAM_2); + + int slot; + int slotSize; + byte* slotContent; + int savegameId; + bool avail_saves[100]; + + if (a == STRINGID_IQ_SERIES && b == STRINGID_IQ_SERIES) { + // Zak256 loads the IQ script-slot but does not use it -> ignore it + if (_game.id == GID_INDY3) { + byte *ptr = getResourceAddress(rtString, STRINGID_IQ_SERIES); + if (ptr) { + int size = getResourceSize(rtString, STRINGID_IQ_SERIES); + loadIQPoints(ptr, size); + } + } + break; + } + + listSavegames(avail_saves, ARRAYSIZE(avail_saves)); + for (slot = a; slot <= b; ++slot) { + slotSize = getResourceSize(rtString, slot); + slotContent = getResourceAddress(rtString, slot); + + // load savegame names + savegameId = slot - a + 1; + Common::String name; + if (avail_saves[savegameId] && getSavegameName(savegameId, name)) { + int pos; + const char *ptr = name.c_str(); + // slotContent ends with {'\0','@'} -> max. length = slotSize-2 + for (pos = 0; pos < slotSize - 2; ++pos) { + if (!ptr[pos]) + break; + // replace special characters + if (ptr[pos] >= 32 && ptr[pos] <= 122 && ptr[pos] != 64) + slotContent[pos] = ptr[pos]; + else + slotContent[pos] = '_'; + } + slotContent[pos] = '\0'; + } else { + slotContent[0] = '\0'; + } + } + break; + case 0x03: // open file + a = resStrLen(_scriptPointer); + strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a); + _saveLoadVarsFilename[a] = '\0'; + _scriptPointer += a + 1; + break; + case 0x04: + return; + case 0x1F: // close file + _saveLoadVarsFilename[0] = '\0'; + return; + } + } +} + +/** + * IQ Point calculation for Indy3. + * The scripts that perform this task are + * - script-9 (save/load dialog initialization, loads room 14), + * - room-14-204 (load series IQ string), + * - room-14-205 (save series IQ string), + * - room-14-206 (calculate series IQ string). + * Unfortunately script-9 contains lots of GUI stuff so calling this script + * directly is not possible. The other scripts depend on script-9. + */ +void ScummEngine_v4::updateIQPoints() { + int seriesIQ; + // IQString[0..72] corresponds to each puzzle's IQ. + // IQString[73] indicates that the IQ-file was loaded successfully and is always 0 when + // the IQ is calculated, hence it will be ignored here. + const int NUM_PUZZLES = 73; + byte seriesIQString[NUM_PUZZLES]; + byte *episodeIQString; + int episodeIQStringSize; + + // load string with IQ points given per puzzle in any savegame + // IMPORTANT: the resource string STRINGID_IQ_SERIES is only valid while + // the original save/load dialog is executed, so do not use it here. + memset(seriesIQString, 0, sizeof(seriesIQString)); + loadIQPoints(seriesIQString, sizeof(seriesIQString)); + + // string with IQ points given per puzzle in current savegame + episodeIQString = getResourceAddress(rtString, STRINGID_IQ_EPISODE); + if (!episodeIQString) + return; + episodeIQStringSize = getResourceSize(rtString, STRINGID_IQ_EPISODE); + if (episodeIQStringSize < NUM_PUZZLES) + return; + + // merge episode and series IQ strings and calculate series IQ + seriesIQ = 0; + // iterate over puzzles + for (int i = 0; i < NUM_PUZZLES ; ++i) { + byte puzzleIQ = seriesIQString[i]; + // if puzzle is solved copy points to episode string + if (puzzleIQ > 0) + episodeIQString[i] = puzzleIQ; + // add puzzle's IQ-points to series IQ + seriesIQ += episodeIQString[i]; + } + _scummVars[245] = seriesIQ; + + // save series IQ string + saveIQPoints(); +} + +void ScummEngine_v4::saveIQPoints() { + // save Indy3 IQ-points + Common::OutSaveFile *file; + Common::String filename = _targetName + ".iq"; + + file = _saveFileMan->openForSaving(filename.c_str()); + if (file != NULL) { + byte *ptr = getResourceAddress(rtString, STRINGID_IQ_EPISODE); + if (ptr) { + int size = getResourceSize(rtString, STRINGID_IQ_EPISODE); + file->write(ptr, size); + } + delete file; + } +} + +void ScummEngine_v4::loadIQPoints(byte *ptr, int size) { + // load Indy3 IQ-points + Common::InSaveFile *file; + Common::String filename = _targetName + ".iq"; + + file = _saveFileMan->openForLoading(filename.c_str()); + if (file != NULL) { + byte *tmp = (byte*)malloc(size); + int nread = file->read(tmp, size); + if (nread == size) { + memcpy(ptr, tmp, size); + } + free(tmp); + delete file; + } +} + +void ScummEngine_v4::o4_saveLoadGame() { + getResultPos(); + byte a = getVarOrDirectByte(PARAM_1); + byte slot = a & 0x1F; + byte result = 0; + + // Slot numbers in older games start with 0, in newer games with 1 + if (_game.version <= 2) + slot++; + + if ((_game.id == GID_MANIAC) && (_game.version <= 1)) { + // Convert older load/save screen + // 1 Load + // 2 Save + slot = 1; + if (a == 1) + _opcode = 0x40; + else if ((a == 2) || (_game.platform == Common::kPlatformNES)) + _opcode = 0x80; + } else { + _opcode = a & 0xE0; + } + + switch (_opcode) { + case 0x00: // num slots available + result = 100; + break; + case 0x20: // drive + if (_game.version <= 3) { + // 0 = ??? + // [1,2] = disk drive [A:,B:] + // 3 = hard drive + result = 3; + } else { + // set current drive + result = 1; + } + break; + case 0x40: // load + if (loadState(slot, false)) + result = 3; // sucess + else + result = 5; // failed to load + break; + case 0x80: // save + if (_game.version <= 3) { + char name[32]; + if (_game.version <= 2) { + // use generic name + sprintf(name, "Game %c", 'A'+slot-1); + } else { + // use name entered by the user + char* ptr; + int firstSlot = (_game.id == GID_LOOM) ? STRINGID_SAVENAME1_LOOM : STRINGID_SAVENAME1; + ptr = (char*)getStringAddress(slot + firstSlot - 1); + strncpy(name, ptr, sizeof(name)); + } + + if (savePreparedSavegame(slot, name)) + result = 0; + else + result = 2; + } else { + result = 2; // failed to save + } + break; + case 0xC0: // test if save exists + { + Common::InSaveFile *file; + bool avail_saves[100]; + + listSavegames(avail_saves, ARRAYSIZE(avail_saves)); + Common::String filename = makeSavegameName(slot, false); + if (avail_saves[slot] && (file = _saveFileMan->openForLoading(filename.c_str()))) { + result = 6; // save file exists + delete file; + } else + result = 7; // save file does not exist + } + break; + default: + error("o4_saveLoadGame: unknown subopcode %d", _opcode); + } + + setResult(result); +} + } // End of namespace Scumm -- cgit v1.2.3