diff options
-rw-r--r-- | engines/tucker/detection.cpp | 75 | ||||
-rw-r--r-- | engines/tucker/saveload.cpp | 258 | ||||
-rw-r--r-- | engines/tucker/tucker.cpp | 10 | ||||
-rw-r--r-- | engines/tucker/tucker.h | 37 |
4 files changed, 324 insertions, 56 deletions
diff --git a/engines/tucker/detection.cpp b/engines/tucker/detection.cpp index 2447e15d6b..7d07edabd3 100644 --- a/engines/tucker/detection.cpp +++ b/engines/tucker/detection.cpp @@ -25,8 +25,8 @@ #include "common/savefile.h" #include "common/system.h" #include "common/fs.h" - #include "base/plugins.h" +#include "graphics/thumbnail.h" #include "tucker/tucker.h" @@ -132,6 +132,10 @@ public: case kSupportsListSaves: case kSupportsLoadingDuringStartup: case kSupportsDeleteSave: + case kSavesSupportMetaInfo: + case kSavesSupportThumbnail: + case kSavesSupportCreationDate: + case kSavesSupportPlayTime: return true; default: return false; @@ -162,26 +166,24 @@ public: virtual SaveStateList listSaves(const char *target) const { Common::String pattern = Tucker::generateGameStateFileName(target, 0, true); Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles(pattern); - bool slotsTable[Tucker::kLastSaveSlot + 1]; - memset(slotsTable, 0, sizeof(slotsTable)); + Tucker::TuckerEngine::SavegameHeader header; SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { int slot; const char *ext = strrchr(file->c_str(), '.'); if (ext && (slot = atoi(ext + 1)) >= 0 && slot <= Tucker::kLastSaveSlot) { Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file); if (in) { - slotsTable[slot] = true; + if (Tucker::TuckerEngine::readSavegameHeader(in, header, false) == Tucker::TuckerEngine::kSavegameNoError) { + saveList.push_back(SaveStateDescriptor(slot, header.description)); + } + delete in; } } } - for (int slot = 0; slot <= Tucker::kLastSaveSlot; ++slot) { - if (slotsTable[slot]) { - Common::String description = Common::String::format("savegm.%02d", slot); - saveList.push_back(SaveStateDescriptor(slot, description)); - } - } + // Sort saves based on slot number. Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; @@ -195,6 +197,59 @@ public: Common::String filename = Tucker::generateGameStateFileName(target, slot); g_system->getSavefileManager()->removeSavefile(filename); } + + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const { + Common::String fileName = Common::String::format("%s.%d", target, slot); + Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(fileName); + + if (!file) { + return SaveStateDescriptor(); + } + + Tucker::TuckerEngine::SavegameHeader header; + Tucker::TuckerEngine::SavegameError savegameError = Tucker::TuckerEngine::readSavegameHeader(file, header, true); + if (savegameError) { + delete file; + return SaveStateDescriptor(); + } + + SaveStateDescriptor desc(slot, header.description); + + if (slot == Tucker::kAutoSaveSlot) { + bool autosaveAllowed = Tucker::TuckerEngine::isAutosaveAllowed(target); + desc.setDeletableFlag(!autosaveAllowed); + desc.setWriteProtectedFlag(autosaveAllowed); + } + + if (header.version >= 2) { + // creation/play time + if (header.saveDate) { + int day = (header.saveDate >> 24) & 0xFF; + int month = (header.saveDate >> 16) & 0xFF; + int year = header.saveDate & 0xFFFF; + desc.setSaveDate(year, month, day); + } + + if (header.saveTime) { + int hour = (header.saveTime >> 16) & 0xFF; + int minutes = (header.saveTime >> 8) & 0xFF; + desc.setSaveTime(hour, minutes); + } + + if (header.playTime) { + desc.setPlayTime(header.playTime * 1000); + } + + // thumbnail + if (header.thumbnail) { + desc.setThumbnail(header.thumbnail); + } + } + + delete file; + return desc; + } + }; #if PLUGIN_ENABLED_DYNAMIC(TUCKER) diff --git a/engines/tucker/saveload.cpp b/engines/tucker/saveload.cpp index 08a4dfad82..417804104b 100644 --- a/engines/tucker/saveload.cpp +++ b/engines/tucker/saveload.cpp @@ -21,20 +21,29 @@ */ #include "common/savefile.h" +#include "common/system.h" #include "common/textconsole.h" +#include "graphics/thumbnail.h" #include "tucker/tucker.h" namespace Tucker { +#define kSavegameSignature MKTAG('T', 'C', 'K', 'R') + enum { - kCurrentGameStateVersion = 1 + kSavegameVersionCurrent = 2, + kSavegameVersionMinimum = 1 +}; + +enum SavegameFlag { + kSavegameFlagAutosave = 1 << 0, }; Common::String generateGameStateFileName(const char *target, int slot, bool prefixOnly) { Common::String name(target); if (prefixOnly) { - name += ".*"; + name += ".#*"; } else { name += Common::String::format(".%d", slot); } @@ -50,7 +59,7 @@ static void saveOrLoadInt(Common::ReadStream &stream, int &i) { } template<class S> -void TuckerEngine::saveOrLoadGameStateData(S &s) { +TuckerEngine::SavegameError TuckerEngine::saveOrLoadGameStateData(S &s) { for (int i = 0; i < kFlagsTableSize; ++i) { saveOrLoadInt(s, _flagsTable[i]); } @@ -71,59 +80,224 @@ void TuckerEngine::saveOrLoadGameStateData(S &s) { saveOrLoadInt(s, _yPosCurrent); saveOrLoadInt(s, _inventoryObjectsCount); saveOrLoadInt(s, _inventoryObjectsOffset); + + return s.err() ? kSavegameIoError : kSavegameNoError; +} + +Common::Error TuckerEngine::loadGameState(int slot) { + Common::String fileName = generateGameStateFileName(_targetName.c_str(), slot); + Common::InSaveFile *file = _saveFileMan->openForLoading(fileName); + + if (!file) { + return Common::kReadingFailed; + } + + SavegameHeader header; + SavegameError savegameError = readSavegameHeader(file, header); + + if (!savegameError) { + savegameError = saveOrLoadGameStateData(*file); + } + + if (savegameError) { + switch (savegameError) { + case kSavegameInvalidTypeError: + warning("Invalid savegame '%s' (does not look like a ScummVM Tucker-engine savegame)", fileName.c_str()); + break; + + case kSavegameInvalidVersionError: + warning("Invalid savegame '%s' (expected savegame version v%i-v%i, got v%i)", + fileName.c_str(), kSavegameVersionMinimum, kSavegameVersionCurrent, header.version); + break; + + default: + warning("Failed to load savegame '%s'", fileName.c_str()); + break; + } + + delete file; + return Common::kReadingFailed; + } + + g_engine->setTotalPlayTime(header.playTime * 1000); + + _nextLocationNum = _locationNum; + setBlackPalette(); + loadBudSpr(); + _forceRedrawPanelItems = true; + + delete file; + return Common::kNoError; +} + + +TuckerEngine::SavegameError TuckerEngine::readSavegameHeader(const char *target, int slot, SavegameHeader &header) { + Common::String fileName = generateGameStateFileName(target, slot); + Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(fileName); + + if (!file) { + return kSavegameNotFoundError; + } + + SavegameError savegameError = readSavegameHeader(file, header); + + delete file; + return savegameError; } -Common::Error TuckerEngine::loadGameState(int num) { - Common::Error ret = Common::kNoError; - Common::String gameStateFileName = generateGameStateFileName(_targetName.c_str(), num); - Common::InSaveFile *f = _saveFileMan->openForLoading(gameStateFileName); - if (f) { - uint16 version = f->readUint16LE(); - if (version < kCurrentGameStateVersion) { - warning("Unsupported gamestate version %d (slot %d)", version, num); +TuckerEngine::SavegameError TuckerEngine::readSavegameHeader(Common::InSaveFile *file, SavegameHeader &header, bool loadThumbnail) { + header.version = -1; + header.flags = 0; + header.description.clear(); + header.saveDate = 0; + header.saveTime = 0; + header.playTime = 0; + header.thumbnail = nullptr; + + if (file->readUint32BE() == kSavegameSignature) { + header.version = file->readUint16LE(); + } else { + // possibly an old, headerless savegame + + file->seek(0, SEEK_SET); + + header.version = file->readUint16LE(); + // old savegames are always version 1 + if (header.version != 1) { + return kSavegameInvalidTypeError; + } + + file->skip(2); + } + + if (header.version > kSavegameVersionCurrent) { + return kSavegameInvalidVersionError; + } + + if (header.version >= 2) { + // savegame flags + header.flags = file->readUint32LE(); + + char ch; + while ((ch = (char)file->readByte()) != '\0') + header.description += ch; + + header.saveDate = file->readUint32LE(); + header.saveTime = file->readUint32LE(); + header.playTime = file->readUint32LE(); + + if (loadThumbnail) { + header.thumbnail = Graphics::loadThumbnail(*file); } else { - f->skip(2); - saveOrLoadGameStateData(*f); - if (f->err() || f->eos()) { - warning("Can't read file '%s'", gameStateFileName.c_str()); - ret = Common::kReadingFailed; - } else { - _nextLocationNum = _locationNum; - setBlackPalette(); - loadBudSpr(); - _forceRedrawPanelItems = true; - } + Graphics::skipThumbnail(*file); } - delete f; } - return ret; -} - -Common::Error TuckerEngine::saveGameState(int num, const Common::String &description) { - Common::Error ret = Common::kNoError; - Common::String gameStateFileName = generateGameStateFileName(_targetName.c_str(), num); - Common::OutSaveFile *f = _saveFileMan->openForSaving(gameStateFileName); - if (f) { - f->writeUint16LE(kCurrentGameStateVersion); - f->writeUint16LE(0); - saveOrLoadGameStateData(*f); - f->finalize(); - if (f->err()) { - warning("Can't write file '%s'", gameStateFileName.c_str()); - ret = Common::kWritingFailed; + + return ((file->err() || file->eos()) ? kSavegameIoError : kSavegameNoError); +} + +TuckerEngine::SavegameError TuckerEngine::writeSavegameHeader(Common::OutSaveFile *file, SavegameHeader &header) { + // Tucker savegame signature + file->writeUint32BE(kSavegameSignature); + + // version information + file->writeUint16LE(kSavegameVersionCurrent); + + // savegame flags + file->writeUint32LE(header.flags); + + // savegame name + file->writeString(header.description); + file->writeByte(0); + + // creation/play time + TimeDate curTime; + _system->getTimeAndDate(curTime); + header.saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF); + header.saveTime = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min) & 0xFF) << 8) | ((curTime.tm_sec) & 0xFF); + header.playTime = g_engine->getTotalPlayTime() / 1000; + file->writeUint32LE(header.saveDate); + file->writeUint32LE(header.saveTime); + file->writeUint32LE(header.playTime); + + // thumbnail + Graphics::saveThumbnail(*file); + + return (file->err() ? kSavegameIoError : kSavegameNoError); +} + +Common::Error TuckerEngine::saveGameState(int slot, const Common::String &description) { + return writeSavegame(slot, description, false); +} + +Common::Error TuckerEngine::writeSavegame(int slot, const Common::String &description, bool autosave) { + Common::String fileName = generateGameStateFileName(_targetName.c_str(), slot); + Common::OutSaveFile *file = _saveFileMan->openForSaving(fileName); + SavegameHeader header; + SavegameError savegameError = kSavegameNoError; + + if (!file) + savegameError = kSavegameIoError; + + if (!savegameError) { + // savegame flags + if (autosave) + header.flags |= kSavegameFlagAutosave; + + // description + header.description = description; + + savegameError = writeSavegameHeader(file, header); + } + + if (!savegameError) + savegameError = saveOrLoadGameStateData(*file); + + if (!savegameError) + file->finalize(); + + delete file; + + if (savegameError) { + warning("Error writing savegame '%s'", fileName.c_str()); + return Common::kWritingFailed; + } + + return Common::kNoError; +} + +bool TuckerEngine::isAutosaveAllowed() { + return isAutosaveAllowed(_targetName.c_str()); +} + +bool TuckerEngine::isAutosaveAllowed(const char *target) { + SavegameHeader savegameHeader; + SavegameError savegameError = readSavegameHeader(target, kAutoSaveSlot, savegameHeader); + return (savegameError == kSavegameNotFoundError || (savegameHeader.flags & kSavegameFlagAutosave)); +} + +void TuckerEngine::writeAutosave() { + if (canSaveGameStateCurrently()) { + if (!isAutosaveAllowed()) { + warning("Refusing to overwrite non-autosave savegame in slot %i, skipping autosave", kAutoSaveSlot); + return; } - delete f; + + writeSavegame(kAutoSaveSlot, "Autosave", true); + _lastSaveTime = _system->getMillis(); } - return ret; } +bool TuckerEngine::canLoadOrSave() const { + return !_player && _cursorState != kCursorStateDisabledHidden; +} bool TuckerEngine::canLoadGameStateCurrently() { - return !_player && _cursorState != kCursorStateDisabledHidden; + return canLoadOrSave(); } bool TuckerEngine::canSaveGameStateCurrently() { - return !_player && _cursorState != kCursorStateDisabledHidden; + return canLoadOrSave(); } bool TuckerEngine::existsSavegame() { diff --git a/engines/tucker/tucker.cpp b/engines/tucker/tucker.cpp index 309e0f2bed..56247a126d 100644 --- a/engines/tucker/tucker.cpp +++ b/engines/tucker/tucker.cpp @@ -351,6 +351,8 @@ void TuckerEngine::resetVariables() { _updateLocationFlag = false; _updateLocation70StringLen = 0; memset(_updateLocation70String, 0, sizeof(_updateLocation70String)); + + _lastSaveTime = _system->getMillis(); } void TuckerEngine::mainLoop() { @@ -616,7 +618,15 @@ void TuckerEngine::mainLoop() { handleCreditsSequence(); _quitGame = true; } + + if (shouldPerformAutoSave(_lastSaveTime)) { + writeAutosave(); + } } while (!_quitGame && _flagsTable[100] == 0); + + // auto save on quit + writeAutosave(); + if (_flagsTable[100] == 1) { handleCongratulationsSequence(); } diff --git a/engines/tucker/tucker.h b/engines/tucker/tucker.h index a4cee743d4..cd12939443 100644 --- a/engines/tucker/tucker.h +++ b/engines/tucker/tucker.h @@ -28,6 +28,7 @@ #include "common/endian.h" #include "common/events.h" #include "common/random.h" +#include "common/savefile.h" #include "common/stream.h" #include "video/flic_decoder.h" @@ -254,7 +255,8 @@ enum { kStartupLocationGame = 1, kDefaultCharSpeechSoundCounter = 1, kMaxSoundVolume = 127, - kLastSaveSlot = 99 + kLastSaveSlot = 99, + kAutoSaveSlot = kLastSaveSlot }; enum InputKey { @@ -334,6 +336,24 @@ public: kMaxDirtyRects = 32 }; + struct SavegameHeader { + uint16 version; + uint32 flags; + Common::String description; + uint32 saveDate; + uint32 saveTime; + uint32 playTime; + Graphics::Surface *thumbnail; + }; + + enum SavegameError { + kSavegameNoError = 0, + kSavegameInvalidTypeError, + kSavegameInvalidVersionError, + kSavegameNotFoundError, + kSavegameIoError + }; + TuckerEngine(OSystem *system, Common::Language language, uint32 flags); virtual ~TuckerEngine(); @@ -341,6 +361,10 @@ public: virtual bool hasFeature(EngineFeature f) const; GUI::Debugger *getDebugger() { return _console; } + static SavegameError readSavegameHeader(Common::InSaveFile *file, SavegameHeader &header, bool loadThumbnail = false); + static SavegameError readSavegameHeader(const char *target, int slot, SavegameHeader &header); + bool isAutosaveAllowed(); + static bool isAutosaveAllowed(const char *target); protected: int getRandomNumber(); @@ -627,9 +651,13 @@ protected: void updateSprite_locationNum81_1(int i); void updateSprite_locationNum82(int i); - template<class S> void saveOrLoadGameStateData(S &s); - virtual Common::Error loadGameState(int num); - virtual Common::Error saveGameState(int num, const Common::String &description); + template<class S> SavegameError saveOrLoadGameStateData(S &s); + virtual Common::Error loadGameState(int slot); + virtual Common::Error saveGameState(int slot, const Common::String &description); + Common::Error writeSavegame(int slot, const Common::String &description, bool autosave = false); + SavegameError writeSavegameHeader(Common::OutSaveFile *file, SavegameHeader &header); + void writeAutosave(); + bool canLoadOrSave() const; virtual bool canLoadGameStateCurrently(); virtual bool canSaveGameStateCurrently(); virtual bool existsSavegame(); @@ -679,6 +707,7 @@ protected: Common::Language _gameLang; uint32 _gameFlags; int _startSlot; + uint32 _lastSaveTime; bool _quitGame; bool _fastMode; |