From 630623c997038989089ed29b08062767ef5748e7 Mon Sep 17 00:00:00 2001 From: grisenti Date: Tue, 23 Aug 2022 15:52:22 +0200 Subject: [PATCH] HPL1: use standard naming scheme for saves --- engines/hpl1/engine/system/SerializeClass.cpp | 15 +++--- engines/hpl1/hpl1.cpp | 48 +++++++++++++++++++ engines/hpl1/hpl1.h | 9 ++++ engines/hpl1/metaengine.cpp | 20 -------- engines/hpl1/metaengine.h | 2 - engines/hpl1/penumbra-overture/DeathMenu.cpp | 4 +- engines/hpl1/penumbra-overture/MainMenu.cpp | 26 +++++----- .../hpl1/penumbra-overture/SaveHandler.cpp | 14 +++--- 8 files changed, 85 insertions(+), 53 deletions(-) diff --git a/engines/hpl1/engine/system/SerializeClass.cpp b/engines/hpl1/engine/system/SerializeClass.cpp index eb1fe29a138..822b82b46fb 100644 --- a/engines/hpl1/engine/system/SerializeClass.cpp +++ b/engines/hpl1/engine/system/SerializeClass.cpp @@ -195,22 +195,23 @@ bool cSerializeClass::SaveToFile(iSerializable *apData, const tWString &asFile, glTabs = 0; // FIXME: string types - Common::String filename(cString::To8Char(asFile).c_str()); + Common::String saveDesc(cString::To8Char(asFile).c_str()); + Common::String filename(Hpl1::g_engine->createSaveFile(saveDesc)); TiXmlDocument pXmlDoc; // Create root TiXmlElement XmlRoot(asRoot.c_str()); TiXmlElement *pRootElem = static_cast(pXmlDoc.InsertEndChild(XmlRoot)); Common::ScopedPtr savefile(g_engine->getSaveFileManager()->openForSaving(filename)); if (!savefile) { - Hpl1::logError(Hpl1::kDebugSaves, "could't open file %s for saving\n", asFile.c_str()); + Hpl1::logError(Hpl1::kDebugSaves, "could't open file %s for saving\n", filename.c_str()); return false; } SaveToElement(apData, "", pRootElem); if (!pXmlDoc.SaveFile(*savefile)) { - Hpl1::logError(Hpl1::kDebugSaves, "couldn't save to file '%s'\n", asFile.c_str()); + Hpl1::logError(Hpl1::kDebugSaves, "couldn't save to file '%s'\n", filename.c_str()); return false; } - g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), "", filename.contains("auto")); + g_engine->getMetaEngine()->appendExtendedSave(savefile.get(), g_engine->getTotalPlayTime(), saveDesc, filename.contains("auto")); return true; } @@ -279,15 +280,15 @@ bool cSerializeClass::LoadFromFile(iSerializable *apData, const tWString &asFile // Load document TiXmlDocument pXmlDoc; // FIXME: string types - Common::String filename(cString::To8Char(asFile).c_str()); + Common::String filename(Hpl1::g_engine->mapInternalSaveToFile(cString::To8Char(asFile).c_str())); Common::ScopedPtr saveFile(g_engine->getSaveFileManager()->openForLoading(filename)); if (!saveFile) { Hpl1::logError(Hpl1::kDebugSaves | Hpl1::kDebugResourceLoading, "save file %s could not be opened\n", filename.c_str()); return false; } ExtendedSavegameHeader header; - if (MetaEngine::readSavegameHeader(saveFile.get(), &header)) { - Hpl1::logError(Hpl1::kDebugResourceLoading | Hpl1::kDebugSaves, "couldn't load heaer from save file %s\n", filename.c_str()); + if (!MetaEngine::readSavegameHeader(saveFile.get(), &header)) { + Hpl1::logError(Hpl1::kDebugResourceLoading | Hpl1::kDebugSaves, "couldn't load header from save file %s\n", filename.c_str()); return false; } g_engine->setTotalPlayTime(header.playtime); diff --git a/engines/hpl1/hpl1.cpp b/engines/hpl1/hpl1.cpp index a88b0abd057..00d09cdfe31 100644 --- a/engines/hpl1/hpl1.cpp +++ b/engines/hpl1/hpl1.cpp @@ -30,6 +30,7 @@ #include "graphics/palette.h" #include "hpl1/console.h" #include "hpl1/detection.h" +#include "hpl1/debug.h" extern int hplMain(const hpl::tString &asCommandLine); @@ -54,11 +55,58 @@ Common::String Hpl1Engine::getGameId() const { return _gameDescription->gameId; } +static void initSaves(const char *target, Common::BitArray &slots, Common::HashMap &savemap) { + slots.set_size(g_engine->getMetaEngine()->getMaximumSaveSlot()); + SaveStateList saves = g_engine->getMetaEngine()->listSaves(target); + for (auto &s : saves) { + savemap.setVal(s.getDescription(), s.getSaveSlot()); + slots.set(s.getSaveSlot()); + } +} + Common::Error Hpl1Engine::run() { + initSaves(_targetName.c_str(), _saveSlots, _internalSaves); hplMain(""); return Common::kNoError; } +static int freeSaveSlot(Common::BitArray &slots, const int size) { + for (int i = 0; i < size; ++i) { + if (!slots.get(i)) + return i; + } + return -1; +} + +Common::String Hpl1Engine::createSaveFile(const Common::String &internalName) { + const int freeSlot = freeSaveSlot(_saveSlots, getMetaEngine()->getMaximumSaveSlot()); + if (freeSlot == -1) { + warning("game out of save slots"); + return ""; + } + _saveSlots.set(freeSlot); + _internalSaves.setVal(internalName, freeSlot); + return getSaveStateName(freeSlot); +} + +Common::String Hpl1Engine::mapInternalSaveToFile(const Common::String &internalName) { + const int slot = _internalSaves.getValOrDefault(internalName, -1); + if (slot == -1) { + logError(Hpl1::kDebugLevelError, "trying to map invalid save name: %s\n", internalName.c_str()); + return ""; + } + return getSaveStateName(slot); +} + +Common::StringArray Hpl1Engine::listInternalSaves(const Common::String &pattern) { + Common::StringArray saves; + for(auto &kv : _internalSaves) { + if (kv._key.matchString(pattern)) + saves.push_back(kv._key); + } + return saves; +} + Common::Error Hpl1Engine::syncGame(Common::Serializer &s) { // The Serializer has methods isLoading() and isSaving() // if you need to specific steps; for example setting diff --git a/engines/hpl1/hpl1.h b/engines/hpl1/hpl1.h index 5fbaf15e7bf..7c30b7d14c4 100644 --- a/engines/hpl1/hpl1.h +++ b/engines/hpl1/hpl1.h @@ -33,6 +33,7 @@ #include "engines/engine.h" #include "engines/savestate.h" #include "graphics/screen.h" +#include "common/bitarray.h" #include "hpl1/detection.h" @@ -44,6 +45,8 @@ class Hpl1Engine : public Engine { private: const ADGameDescription *_gameDescription; Common::RandomSource _randomSource; + Common::HashMap _internalSaves; + Common::BitArray _saveSlots; protected: // Engine APIs @@ -84,6 +87,12 @@ public: return true; } + Common::String createSaveFile(const Common::String &internalName); + + Common::String mapInternalSaveToFile(const Common::String &internalName); + + Common::StringArray listInternalSaves(const Common::String &pattern); + /** * Uses a serializer to allow implementing savegame * loading and saving using a single method diff --git a/engines/hpl1/metaengine.cpp b/engines/hpl1/metaengine.cpp index 6e529acc96c..7510071b30b 100644 --- a/engines/hpl1/metaengine.cpp +++ b/engines/hpl1/metaengine.cpp @@ -59,26 +59,6 @@ void Hpl1MetaEngine::getSavegameThumbnail(Graphics::Surface &thumbnail) { scaledScreen->free(); } -static Common::U32String formatSave(const Common::String &filename) { - const int begin = filename.findFirstOf('.') + 1; - const int len = filename.findLastOf('_') - begin; - Common::String name = filename.substr(begin, len); - Common::replace(name.begin(), name.end(), '.', ':'); - Common::replace(name.begin(), name.end(), '_', ' '); - return name; -} - -SaveStateList Hpl1MetaEngine::listSaves(const char *target) const { - SaveStateList saveList; - Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("hpl1-po-????.*"); - int i = 0; - for (auto &save : filenames) { - if (!save.contains("favourite")) - saveList.push_back(SaveStateDescriptor(this, ++i, formatSave(save))); - } - return saveList; -} - Common::Action *createKeyBoardAction(const char *id, const Common::U32String &desc, const char *defaultMap, const Common::KeyState &key) { Common::Action *act = new Common::Action(id, desc); act->setKeyEvent(key); diff --git a/engines/hpl1/metaengine.h b/engines/hpl1/metaengine.h index 39fe205a3c6..bb90ddbfa00 100644 --- a/engines/hpl1/metaengine.h +++ b/engines/hpl1/metaengine.h @@ -37,8 +37,6 @@ public: */ bool hasFeature(MetaEngineFeature f) const override; - SaveStateList listSaves(const char *target) const override; - void getSavegameThumbnail(Graphics::Surface &thumbnail) override; Common::Array initKeymaps(const char *target) const override; diff --git a/engines/hpl1/penumbra-overture/DeathMenu.cpp b/engines/hpl1/penumbra-overture/DeathMenu.cpp index d0b5160d76a..5eaf2ed04f7 100644 --- a/engines/hpl1/penumbra-overture/DeathMenu.cpp +++ b/engines/hpl1/penumbra-overture/DeathMenu.cpp @@ -104,7 +104,7 @@ void cDeathMenuButton::OnMouseOver(bool abOver) { //----------------------------------------------------------------------- void cDeathMenuButton_Continue::OnMouseDown() { - tWString save = mpInit->mpSaveHandler->GetLatest(_W("hpl1-po-????.*.sav")); + tWString save = mpInit->mpSaveHandler->GetLatest(_W("????:*")); if (save != _W("")) mpInit->mpSaveHandler->LoadGameFromFile(save); } @@ -301,7 +301,7 @@ void cDeathMenu::SetActive(bool abX) { STLDeleteAll(mlstButtons); // Continue - tWString latestSave = mpInit->mpSaveHandler->GetLatest(_W("hpl1-po-????.*.sav")); + tWString latestSave = mpInit->mpSaveHandler->GetLatest(_W("????:*")); if (latestSave != _W("")) { mlstButtons.push_back(hplNew(cDeathMenuButton_Continue, (mpInit, cVector2f(400, 290), kTranslate("DeathMenu", "Continue")))); } diff --git a/engines/hpl1/penumbra-overture/MainMenu.cpp b/engines/hpl1/penumbra-overture/MainMenu.cpp index 37cc3a2aeb1..bcd4885e453 100644 --- a/engines/hpl1/penumbra-overture/MainMenu.cpp +++ b/engines/hpl1/penumbra-overture/MainMenu.cpp @@ -634,7 +634,7 @@ void cMainMenuWidget_Continue::OnMouseDown(eMButton aButton) { mpInit->mpMainMenu->SetActive(false); - tWString latestSave = mpInit->mpSaveHandler->GetLatest(_W("hpl1-po-????.*.sav")); + tWString latestSave = mpInit->mpSaveHandler->GetLatest(_W("????:*")); if (latestSave != _W("")) mpInit->mpSaveHandler->LoadGameFromFile(latestSave); } @@ -784,9 +784,11 @@ public: return; tWString originalName = gvSaveGameFileVec[mlNum][lSelected]; - tWString newName = _W("save-favorite.") + cString::SubW(originalName, originalName.find_first_of('.') + 1); + tWString newName = _W("favorite-") + cString::SubW(originalName, originalName.find_first_of('.') + 1); Hpl1::logInfo(Hpl1::kDebugSaves, "adding save %s to favourites\n", cString::To8Char(newName).c_str()); - g_engine->getSaveFileManager()->copySavefile(cString::To8Char(originalName).c_str(), cString::To8Char(newName).c_str()); + Common::String originalFile(Hpl1::g_engine->mapInternalSaveToFile(cString::To8Char(originalName).c_str())); + Common::String newFile(Hpl1::g_engine->createSaveFile(cString::To8Char(newName).c_str())); + g_engine->getSaveFileManager()->copySavefile(originalFile, newFile); mpInit->mpMainMenu->UpdateWidgets(); } @@ -2480,7 +2482,7 @@ void cMainMenu::CreateWidgets() { AddWidgetToState(eMainMenuState_Start, hplNew(cMainMenuWidget_Resume, (mpInit, vPos, kTranslate("MainMenu", "Resume")))); vPos.y += 60; } else { - tWString latestSave = mpInit->mpSaveHandler->GetLatest(_W("hpl1-po-????.*.sav")); + tWString latestSave = mpInit->mpSaveHandler->GetLatest(_W("????:*")); if (latestSave != _W("")) { AddWidgetToState(eMainMenuState_Start, hplNew(cMainMenuWidget_MainButton, (mpInit, vPos, kTranslate("MainMenu", "Continue"), eMainMenuState_Continue))); @@ -2577,18 +2579,18 @@ void cMainMenu::CreateWidgets() { vPos.y += 46 + 30; vPos.x += 15; - tWString sDir = _W("hpl1-po-spot"); + tWString sDir = _W("spot:"); if (i == 1) - sDir = _W("hpl1-po-auto"); + sDir = _W("auto:"); else if (i == 2) - sDir = _W("hpl1-po-favorite"); + sDir = _W("favorite:"); gpSaveGameList[i] = hplNew(cMainMenuWidget_SaveGameList, ( mpInit, vPos, cVector2f(355, 170), 15, sDir, (int)i)); AddWidgetToState(state, gpSaveGameList[i]); tTempFileAndDataSet setTempFiles; - Common::StringArray saves = g_engine->getSaveFileManager()->listSavefiles( cString::To8Char(sDir).c_str() + Common::String("*.sav")); + Common::StringArray saves = Hpl1::g_engine->listInternalSaves(cString::To8Char(sDir).c_str() + Common::String("*")); for (auto &s : saves) { tWString sFile = cString::To16Char(s.c_str()); cDate date = cSaveHandler::parseDate(s); @@ -2605,13 +2607,7 @@ void cMainMenu::CreateWidgets() { gvSaveGameFileVec[i].push_back(sFile); - sFile = cString::SetFileExtW(sFile, _W("")); - sFile = cString::SubW(sFile, sFile.find_first_of(_W(".")) + 1); - sFile = cString::SubW(sFile, 0, (int)sFile.length() - 3); - sFile = cString::ReplaceCharToW(sFile, _W("_"), _W(" ")); - sFile = cString::ReplaceCharToW(sFile, _W("."), _W(":")); - - // TODO: PROBLEM!!! + sFile = cString::SubW(sFile, sFile.find_first_of(_W(":")) + 1); gpSaveGameList[i]->AddEntry(sFile); // gpSaveGameList[i]->AddEntry(sFile); } diff --git a/engines/hpl1/penumbra-overture/SaveHandler.cpp b/engines/hpl1/penumbra-overture/SaveHandler.cpp index eba9743f91a..5acfa5e3b82 100644 --- a/engines/hpl1/penumbra-overture/SaveHandler.cpp +++ b/engines/hpl1/penumbra-overture/SaveHandler.cpp @@ -610,7 +610,7 @@ void cSaveHandler::AutoSave(const tWString &asDir, int alMaxSaves) { sMapName = cString::ReplaceCharToW(sMapName, _W(":"), _W(" ")); cDate date = mpInit->mpGame->GetSystem()->GetLowLevel()->getDate(); wchar_t sTemp[512]; - swprintf(sTemp, 512, _W("hpl1-po-%ls.%ls %d-%02d-%02d_%02d.%02d.%02d_%02d.sav"), + swprintf(sTemp, 512, _W("%ls: %ls %d-%02d-%02d %02d:%02d:%02d"), asDir.c_str(), sMapName.c_str(), date.year, @@ -618,8 +618,7 @@ void cSaveHandler::AutoSave(const tWString &asDir, int alMaxSaves) { date.month_day, date.hours, date.minutes, - date.seconds, - cMath::RandRectl(0, 99)); + date.seconds); tWString sFile = sTemp; SaveGameToFile(sFile); @@ -629,7 +628,7 @@ void cSaveHandler::AutoSave(const tWString &asDir, int alMaxSaves) { //----------------------------------------------------------------------- void cSaveHandler::AutoLoad(const tWString &asDir) { - tWString latestSave = GetLatest(_W("hpl1-po-") + asDir + _W(".*.sav")); + tWString latestSave = GetLatest( asDir + _W(":*")); LoadGameFromFile(latestSave); mpInit->mpGame->ResetLogicTimer(); } @@ -700,14 +699,15 @@ void cSaveHandler::DeleteOldestIfMax(const tWString &asDir, const tWString &asMa cDate cSaveHandler::parseDate(const Common::String &saveFile) { cDate date; - Common::String strDate = saveFile.substr(saveFile.findLastOf(" ")); - sscanf(strDate.c_str(), "%d-%d-%d_%d.%d.%d.sav", &date.year, &date.month, &date.month_day, &date.hours, &date.minutes, &date.seconds); + auto firstDigit = Common::find_if(saveFile.begin(), saveFile.end(), Common::isDigit); + Common::String strDate = saveFile.substr(Common::distance(saveFile.begin(), firstDigit)); + sscanf(strDate.c_str(), "%d-%d-%d %d:%d:%d", &date.year, &date.month, &date.month_day, &date.hours, &date.minutes, &date.seconds); return date; } tWString cSaveHandler::GetLatest(const tWString &asMask) { // FIXME: string types - Common::StringArray saves = g_engine->getSaveFileManager()->listSavefiles(cString::To8Char(asMask).c_str()); + Common::StringArray saves = Hpl1::g_engine->listInternalSaves(cString::To8Char(asMask).c_str()); if (saves.empty()) return _W(""); cDate latestDate = parseDate(saves.front());