From 3bea667a10d823706a516caf8bd7d93d012c765d Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Thu, 27 Mar 2008 18:03:00 +0000 Subject: [PATCH] - Implemented support for --list-saves in Kyra engine - Added support for variable length savegame name field - Changed savegame identifier - Increased savegame file version svn-id: r31268 --- engines/kyra/detection.cpp | 39 ++++++++- engines/kyra/kyra.h | 26 +++++- engines/kyra/kyra_v1.h | 2 - engines/kyra/kyra_v2.h | 2 - engines/kyra/saveload.cpp | 152 ++++++++++++++++++++++++----------- engines/kyra/saveload_v1.cpp | 23 +++--- engines/kyra/saveload_v2.cpp | 54 ++----------- 7 files changed, 186 insertions(+), 112 deletions(-) diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp index 80d9ca64ec7..4a0ba0fc26f 100644 --- a/engines/kyra/detection.cpp +++ b/engines/kyra/detection.cpp @@ -29,6 +29,7 @@ #include "common/config-manager.h" #include "common/advancedDetector.h" +#include "common/savefile.h" #include "base/plugins.h" @@ -445,15 +446,17 @@ class KyraMetaEngine : public Common::AdvancedMetaEngine { public: KyraMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {} - virtual const char *getName() const { + const char *getName() const { return "Legend of Kyrandia Engine"; } - virtual const char *getCopyright() const { + const char *getCopyright() const { return "The Legend of Kyrandia (C) Westwood Studios"; } - virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; + bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; + + SaveStateList listSaves(const char *target) const; }; bool KyraMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const { @@ -489,10 +492,38 @@ bool KyraMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common break; default: res = false; - error("Kyra engine: unknown gameID"); + warning("Kyra engine: unknown gameID"); } return res; } +SaveStateList KyraMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Kyra::KyraEngine::SaveHeader header; + Common::String pattern = target; + pattern += ".???"; + + Common::StringList filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + SaveStateList saveList; + for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (Kyra::KyraEngine::readSaveHeader(in, header) == Kyra::KyraEngine::kRSHENoError) + saveList.push_back(SaveStateDescriptor(slotNum, header.description, *file)); + delete in; + } + } + } + + return saveList; +} + REGISTER_PLUGIN(KYRA, PLUGIN_TYPE_ENGINE, KyraMetaEngine); diff --git a/engines/kyra/kyra.h b/engines/kyra/kyra.h index 8bb8e159b28..fc37b2fb944 100644 --- a/engines/kyra/kyra.h +++ b/engines/kyra/kyra.h @@ -38,6 +38,8 @@ class InSaveFile; class OutSaveFile; } // end of namespace Common +class KyraMetaEngine; + namespace Kyra { struct GameFlags { @@ -101,6 +103,7 @@ class ScriptHelper; class KyraEngine : public Engine { friend class Debugger; +friend class ::KyraMetaEngine; public: KyraEngine(OSystem *system, const GameFlags &flags); virtual ~KyraEngine(); @@ -225,10 +228,27 @@ protected: static const int8 _addYPosTable[]; // save/load - virtual uint32 saveGameID() const = 0; - const char *getSavegameFilename(int num); - Common::InSaveFile *openSaveForReading(const char *filename, uint32 &version, char *saveName); + + struct SaveHeader { + Common::String description; + uint32 version; + byte gameID; + uint32 flags; + + bool originalSave; // savegame from original interpreter + bool oldHeader; // old scummvm save header + }; + + enum kReadSaveHeaderError { + kRSHENoError = 0, + kRSHEInvalidType = 1, + kRSHEInvalidVersion = 2, + kRSHEIoError = 3 + }; + static kReadSaveHeaderError readSaveHeader(Common::InSaveFile *file, SaveHeader &header); + + Common::InSaveFile *openSaveForReading(const char *filename, SaveHeader &header); Common::OutSaveFile *openSaveForWriting(const char *filename, const char *saveName) const; }; diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h index 5d4350f857f..ee31fb97ff6 100644 --- a/engines/kyra/kyra_v1.h +++ b/engines/kyra/kyra_v1.h @@ -281,8 +281,6 @@ public: void snd_voiceWaitForFinish(bool ingame = true); protected: - uint32 saveGameID() const { return 'KYRA'; } - void saveGame(const char *fileName, const char *saveName); void loadGame(const char *fileName); diff --git a/engines/kyra/kyra_v2.h b/engines/kyra/kyra_v2.h index ac6e5a36284..5a6f55ceeb3 100644 --- a/engines/kyra/kyra_v2.h +++ b/engines/kyra/kyra_v2.h @@ -1201,8 +1201,6 @@ protected: int _dbgPass; // save/load specific - uint32 saveGameID() const { return 'HOFS'; } - void saveGame(const char *fileName, const char *saveName); void loadGame(const char *fileName); }; diff --git a/engines/kyra/saveload.cpp b/engines/kyra/saveload.cpp index 7d6fcaa0c10..9347da4aeb6 100644 --- a/engines/kyra/saveload.cpp +++ b/engines/kyra/saveload.cpp @@ -29,16 +29,82 @@ #include "kyra/kyra.h" -#define CURRENT_SAVE_VERSION 8 +#define CURRENT_SAVE_VERSION 9 -#define GF_FLOPPY (1 << 0) -#define GF_TALKIE (1 << 1) +#define GF_FLOPPY (1 << 0) +#define GF_TALKIE (1 << 1) #define GF_FMTOWNS (1 << 2) namespace Kyra { -Common::InSaveFile *KyraEngine::openSaveForReading(const char *filename, uint32 &version, char *saveName) { - debugC(9, kDebugLevelMain, "KyraEngine::openSaveForReading('%s', %p, %p)", filename, (const void*)&version, saveName); +KyraEngine::kReadSaveHeaderError KyraEngine::readSaveHeader(Common::InSaveFile *in, SaveHeader &header) { + uint32 type = in->readUint32BE(); + header.originalSave = false; + header.oldHeader = false; + header.flags = 0; + + if (type == MKID_BE('KYRA') || type == MKID_BE('ARYK')) { // old Kyra1 header ID + header.gameID = GI_KYRA1; + header.oldHeader = true; + } else if (type == MKID_BE('HOFS')) { // old Kyra2 header ID + header.gameID = GI_KYRA2; + header.oldHeader = true; + } else if (type == MKID_BE('WWSV')) { + header.gameID = in->readByte(); + } else { + // try checking for original save header + const int descriptionSize[2] = { 30, 80 }; + char descriptionBuffer[81]; + + bool saveOk = false; + + for (uint i = 0; i < ARRAYSIZE(descriptionSize) && !saveOk; ++i) { + in->seek(0, SEEK_SET); + in->read(descriptionBuffer, descriptionSize[i]); + descriptionBuffer[descriptionSize[i]] = 0; + + type = in->readUint32BE(); + header.version = in->readUint16LE(); + if (type == MKID_BE('MBL3') && header.version == 100) { + saveOk = true; + header.description = descriptionBuffer; + header.gameID = GI_KYRA2; + break; + } + } + + if (saveOk) { + header.originalSave = true; + header.description = descriptionBuffer; + return kRSHENoError; + } else { + return kRSHEInvalidType; + } + } + + header.version = in->readUint32BE(); + if (header.version > CURRENT_SAVE_VERSION || (header.oldHeader && header.version > 8) || (type == MKID_BE('ARYK') && header.version > 3)) + return kRSHEInvalidVersion; + + // Versions prior to 9 are using a fixed length description field + if (header.version <= 8) { + char buffer[31]; + in->read(buffer, 31); + header.description = buffer; + } else { + header.description = ""; + for (char c = 0; (c = in->readByte()) != 0;) + header.description += c; + } + + if (header.version >= 2) + header.flags = in->readUint32BE(); + + return (in->ioFailed() ? kRSHEIoError : kRSHENoError); +} + +Common::InSaveFile *KyraEngine::openSaveForReading(const char *filename, SaveHeader &header) { + debugC(9, kDebugLevelMain, "KyraEngine::openSaveForReading('%s', -)", filename); Common::InSaveFile *in = 0; if (!(in = _saveFileMan->openForLoading(filename))) { @@ -46,52 +112,45 @@ Common::InSaveFile *KyraEngine::openSaveForReading(const char *filename, uint32 return 0; } - uint32 type = in->readUint32BE(); + kReadSaveHeaderError errorCode = KyraEngine::readSaveHeader(in, header); + if (errorCode != kRSHENoError) { + if (errorCode == kRSHEInvalidType) + warning("No ScummVM Kyra engine savefile header."); + else if (errorCode == kRSHEInvalidVersion) + warning("Savegame is not the right version (%u, '%s')", header.version, header.oldHeader ? "true" : "false"); + else if (errorCode == kRSHEIoError) + warning("Load failed ('%s').", filename); - // FIXME: The kyra savegame code used to be endian unsafe. Uncomment the - // following line to graciously handle old savegames from LE machines. - // if (type != MKID_BE('KYRA') && type != MKID_BE('ARYK')) { - if (type != MKID_BE(saveGameID())) { - warning("No ScummVM Kyra engine savefile header."); delete in; return 0; } - version = in->readUint32BE(); - if (version > CURRENT_SAVE_VERSION) { - warning("Savegame is not the right version (%u)", version); - delete in; - return 0; - } - - char saveNameBuffer[31]; - if (!saveName) - saveName = saveNameBuffer; - in->read(saveName, 31); - - if (_flags.gameID == GI_KYRA1 && version < 2) { - warning("Make sure your savefile was from this version! (too old savefile version to detect that)"); - } else { - uint32 flags = in->readUint32BE(); - if ((flags & GF_FLOPPY) && (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)) { - warning("Can not load DOS Floppy savefile for this (non DOS Floppy) gameversion"); - delete in; - return 0; - } else if ((flags & GF_TALKIE) && !(_flags.isTalkie)) { - warning("Can not load DOS CD-ROM savefile for this (non DOS CD-ROM) gameversion"); - delete in; - return 0; - } else if ((flags & GF_FMTOWNS) && !(_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)) { - warning("Can not load FM-Towns/PC98 savefile for this (non FM-Towns/PC98) gameversion"); - delete in; - return 0; + if (!header.originalSave) { + if (!header.oldHeader) { + if (header.gameID != _flags.gameID) { + warning("Trying to load game state from other game (save game: %u, running game: %u)", header.gameID, _flags.gameID); + delete in; + return 0; + } } - } - if (in->ioFailed()) { - error("Load failed ('%s', '%s').", filename, saveName); - delete in; - return 0; + if (header.version < 2) { + warning("Make sure your savefile was from this version! (too old savefile version to detect that)"); + } else { + if ((header.flags & GF_FLOPPY) && (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)) { + warning("Can not load DOS Floppy savefile for this (non DOS Floppy) gameversion"); + delete in; + return 0; + } else if ((header.flags & GF_TALKIE) && !(_flags.isTalkie)) { + warning("Can not load DOS CD-ROM savefile for this (non DOS CD-ROM) gameversion"); + delete in; + return 0; + } else if ((header.flags & GF_FMTOWNS) && !(_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)) { + warning("Can not load FM-Towns/PC98 savefile for this (non FM-Towns/PC98) gameversion"); + delete in; + return 0; + } + } } return in; @@ -109,9 +168,10 @@ Common::OutSaveFile *KyraEngine::openSaveForWriting(const char *filename, const } // Savegame version - out->writeUint32BE(saveGameID()); + out->writeUint32BE(MKID_BE('WWSV')); + out->writeByte(_flags.gameID); out->writeUint32BE(CURRENT_SAVE_VERSION); - out->write(saveName, 31); + out->write(saveName, strlen(saveName)+1); if (_flags.isTalkie) out->writeUint32BE(GF_TALKIE); else if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) diff --git a/engines/kyra/saveload_v1.cpp b/engines/kyra/saveload_v1.cpp index 37ad1fc8d36..3168d150fb7 100644 --- a/engines/kyra/saveload_v1.cpp +++ b/engines/kyra/saveload_v1.cpp @@ -38,12 +38,17 @@ namespace Kyra { void KyraEngine_v1::loadGame(const char *fileName) { debugC(9, kDebugLevelMain, "KyraEngine_v1::loadGame('%s')", fileName); - uint32 version = 0; - char saveName[31]; - Common::InSaveFile *in = openSaveForReading(fileName, version, saveName); + SaveHeader header; + Common::InSaveFile *in = openSaveForReading(fileName, header); if (!in) return; + if (header.originalSave) { + // no support for original savefile in Kyrandia 1 (yet) + delete in; + return; + } + snd_playSoundEffect(0x0A); snd_playWanderScoreViaMap(0, 1); @@ -99,7 +104,7 @@ void KyraEngine_v1::loadGame(const char *fileName) { _poisonDeathCounter = in->readByte(); _animator->_brandonDrawFrame = in->readUint16BE(); - _timer->loadDataFromFile(in, version); + _timer->loadDataFromFile(in, header.version); memset(_flagsTable, 0, sizeof(_flagsTable)); uint32 flagsSize = in->readUint32BE(); @@ -131,7 +136,7 @@ void KyraEngine_v1::loadGame(const char *fileName) { _roomTable[sceneId].needInit[i] = in->readByte(); } } - if (version >= 3) { + if (header.version >= 3) { _lastMusicCommand = in->readSint16BE(); if (_lastMusicCommand != -1) snd_playWanderScoreViaMap(_lastMusicCommand, 1); @@ -140,7 +145,7 @@ void KyraEngine_v1::loadGame(const char *fileName) { // Version 4 stored settings in the savegame. As of version 5, they are // handled by the config manager. - if (version == 4) { + if (header.version == 4) { in->readByte(); // Text speed in->readByte(); // Walk speed in->readByte(); // Music @@ -148,7 +153,7 @@ void KyraEngine_v1::loadGame(const char *fileName) { in->readByte(); // Voice } - if (version >= 7) { + if (header.version >= 7) { _curSfxFile = in->readByte(); // In the first version when this entry was introduced, @@ -200,9 +205,9 @@ void KyraEngine_v1::loadGame(const char *fileName) { setMousePos(brandonX, brandonY); if (in->ioFailed()) - error("Load failed ('%s', '%s').", fileName, saveName); + error("Load failed ('%s', '%s').", fileName, header.description.c_str()); else - debugC(1, kDebugLevelMain, "Loaded savegame '%s.'", saveName); + debugC(1, kDebugLevelMain, "Loaded savegame '%s.'", header.description.c_str()); // We didn't explicitly set the walk speed, but it's saved as part of // the _timers array, so we need to re-sync it with _configWalkspeed. diff --git a/engines/kyra/saveload_v2.cpp b/engines/kyra/saveload_v2.cpp index 0da58233ffb..44707b35aff 100644 --- a/engines/kyra/saveload_v2.cpp +++ b/engines/kyra/saveload_v2.cpp @@ -131,47 +131,11 @@ void KyraEngine_v2::saveGame(const char *fileName, const char *saveName) { void KyraEngine_v2::loadGame(const char *fileName) { debugC(9, kDebugLevelMain, "KyraEngine_v2::loadGame('%s')", fileName); - uint32 version = 0; - char saveName[31]; - Common::InSaveFile *in = openSaveForReading(fileName, version, saveName); + SaveHeader header; + Common::InSaveFile *in = openSaveForReading(fileName, header); if (!in) { - // check for original savefile - if ((in = _saveFileMan->openForLoading(fileName))) { - in->seek(0x50, SEEK_CUR); - - uint8 type[4]; - uint8 acceptedType[4] = { 0x4D, 0x42, 0x4C, 0x33 }; // 'MBL3' - in->read(type, sizeof(type)); - uint16 origVersion = in->readUint16LE(); - - debug(1, "Savegame type: '%c%c%c%c' version: %d", type[0], type[1], type[2], type[3], version); - - if (!memcmp(type, acceptedType, 4) && origVersion == 100) { - warning("Trying to load savegame from original interpreter, while this is possible, it is not officially supported."); - - in->seek(0, SEEK_SET); - - // read first 31 bytes of original description - in->read(saveName, 30); - saveName[30] = 0; - // skip last part of original description - in->seek(0x50-30, SEEK_CUR); - version = 0xF000 + origVersion; - // skip type - in->seek(4, SEEK_CUR); - // skip version - in->seek(2, SEEK_CUR); - } else { - delete in; - in = 0; - } - } - - if (!in) { - showMessageFromCCode(0x35, 0x84, 0); - snd_playSoundEffect(0x0D); - return; - } + showMessageFromCCode(0x35, 0x84, 0); + snd_playSoundEffect(0x0D); } bool setFlag1EE = (queryGameFlag(0x1EE) != 0); @@ -187,8 +151,8 @@ void KyraEngine_v2::loadGame(const char *fileName) { _screen->hideMouse(); - if (version < 0xF000) { - _timer->loadDataFromFile(in, version); + if (!header.originalSave) { + _timer->loadDataFromFile(in, header.version); uint32 flagsSize = in->readUint32BE(); assert(flagsSize <= sizeof(_flagsTable)); @@ -264,8 +228,6 @@ void KyraEngine_v2::loadGame(const char *fileName) { _sceneExit3 = in->readUint16BE(); _sceneExit4 = in->readUint16BE(); } else { - version -= 0xF000; - /*word_2AB05 = */in->readUint16LE(); _lastMusicCommand = in->readSint16LE(); _newChapterFile = in->readByte(); @@ -363,9 +325,9 @@ void KyraEngine_v2::loadGame(const char *fileName) { } if (in->ioFailed()) - error("Load failed ('%s', '%s').", fileName, saveName); + error("Load failed ('%s', '%s').", fileName, header.description.c_str()); else - debugC(1, kDebugLevelMain, "Loaded savegame '%s.'", saveName); + debugC(1, kDebugLevelMain, "Loaded savegame '%s.'", header.description.c_str()); delete in;