diff --git a/soh/include/z64save.h b/soh/include/z64save.h index a7e7ca652..97a8837fa 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -61,6 +61,15 @@ typedef struct { u8 isRoom; } SceneTimestamp; +typedef enum { // Pre-existing IDs for save sections in base code + SECTION_ID_BASE, + SECTION_ID_RANDOMIZER, + SECTION_ID_STATS, + SECTION_ID_ENTRANCES, + SECTION_ID_SCENES, + SECTION_ID_MAX +} SaveFuncIDs; + typedef struct { /* */ char buildVersion[50]; /* */ s16 buildVersionMajor; diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index 99d61ff50..b4b1c9732 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -231,8 +231,8 @@ void LoadStatsVersion1() { }); } -void SaveStats(SaveContext* saveContext, const std::string& subSection) { - if (subSection == "all") { +void SaveStats(SaveContext* saveContext, int sectionID) { + if (sectionID == SECTION_ID_BASE) { std::string buildVersion; SaveManager::Instance->LoadData("buildVersion", buildVersion); strncpy(gSaveContext.sohStats.buildVersion, buildVersion.c_str(), ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1); @@ -268,12 +268,12 @@ void SaveStats(SaveContext* saveContext, const std::string& subSection) { SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]); }); } - if (subSection == "entrances" || subSection == "all") { + if (sectionID == SECTION_ID_ENTRANCES || sectionID == SECTION_ID_BASE) { SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]); }); } - if (subSection == "scenes" || subSection == "all") { + if (sectionID == SECTION_ID_SCENES || sectionID == SECTION_ID_BASE) { SaveManager::Instance->SaveArray("scenesDiscovered", ARRAY_COUNT(saveContext->sohStats.scenesDiscovered), [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->sohStats.scenesDiscovered[i]); }); @@ -821,7 +821,10 @@ extern "C" void InitStatTracker() { SetupDisplayNames(); SetupDisplayColors(); SaveManager::Instance->AddLoadFunction("sohStats", 1, LoadStatsVersion1); - SaveManager::Instance->AddSaveFunction("sohStats", 1, SaveStats); + // Add main section save, no parent + SaveManager::Instance->AddSaveFunction("sohStats", 1, SaveStats, true, SECTION_PARENT_NONE); + // Add subsections, parent of "sohStats". Not sure how to do this without the redundant references to "SaveStats" + SaveManager::Instance->AddSaveFunction("entrances", 1, SaveStats, false, SECTION_ID_STATS); + SaveManager::Instance->AddSaveFunction("scenes", 1, SaveStats, false, SECTION_ID_STATS); SaveManager::Instance->AddInitFunction(InitStats); - SaveManager::Instance->RegisterGameSaveSection("sohStats"); } \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index 26be88a24..2ccb83627 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -769,7 +769,8 @@ void Entrance_SetSceneDiscovered(u8 sceneNum) { u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex)); gSaveContext.sohStats.scenesDiscovered[idx] |= sceneBit; } - Save_SaveSection("sohStats.scenes"); + // Save scenesDiscovered + Save_SaveSection(SECTION_ID_SCENES); } u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) { @@ -802,5 +803,6 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex) { } } } - Save_SaveSection("sohStats.entrances"); + // Save entrancesDiscovered + Save_SaveSection(SECTION_ID_ENTRANCES); } diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 60dcb8876..2f8c52759 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -17,6 +17,7 @@ #include extern "C" SaveContext gSaveContext; +using namespace std::string_literals; void SaveManager::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr, const size_t size) { @@ -46,17 +47,20 @@ std::filesystem::path SaveManager::GetFileName(int fileNum) { } SaveManager::SaveManager() { + coreSectionIDsByName["base"] = SECTION_ID_BASE; + coreSectionIDsByName["randomizer"] = SECTION_ID_RANDOMIZER; + coreSectionIDsByName["sohStats"] = SECTION_ID_STATS; + coreSectionIDsByName["entrances"] = SECTION_ID_ENTRANCES; + coreSectionIDsByName["scenes"] = SECTION_ID_SCENES; AddLoadFunction("base", 1, LoadBaseVersion1); AddLoadFunction("base", 2, LoadBaseVersion2); AddLoadFunction("base", 3, LoadBaseVersion3); AddLoadFunction("base", 4, LoadBaseVersion4); - AddSaveFunction("base", 4, SaveBase); - RegisterGameSaveSection("base"); + AddSaveFunction("base", 4, SaveBase, true, SECTION_PARENT_NONE); AddLoadFunction("randomizer", 1, LoadRandomizerVersion1); AddLoadFunction("randomizer", 2, LoadRandomizerVersion2); - AddSaveFunction("randomizer", 2, SaveRandomizer); - RegisterGameSaveSection("randomizer"); + AddSaveFunction("randomizer", 2, SaveRandomizer, true, SECTION_PARENT_NONE); AddInitFunction(InitFileImpl); @@ -86,20 +90,6 @@ SaveManager::SaveManager() { } } -// GameSaveSection functions affect the list of sections that save their data when a game save is triggered -void SaveManager::RegisterGameSaveSection(std::string section) { - if (std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), section) == gameSaveRegistry.end()) { - gameSaveRegistry.push_back(section); - } -} - -void SaveManager::UnregisterAutosaveSection(std::string section) { - auto find = std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), section); - if (find != gameSaveRegistry.end()) { - gameSaveRegistry.erase(find); - } -} - void SaveManager::LoadRandomizerVersion1() { for (int i = 0; i < ARRAY_COUNT(gSaveContext.itemLocations); i++) { SaveManager::Instance->LoadStruct("get" + std::to_string(i), [&]() { @@ -278,7 +268,7 @@ void SaveManager::LoadRandomizerVersion2() { }); } -void SaveManager::SaveRandomizer(SaveContext* saveContext, const std::string& subString) { +void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID) { if(!saveContext->n64ddFlag) return; @@ -700,40 +690,42 @@ void SaveManager::InitFileDebug() { gSaveContext.sceneFlags[5].swch = 0x40000000; } -// Threaded SaveFile takes copy of gSaveContext for local unmodified storage -void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, const std::string& sectionString) { +void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) { + // Needed for first time save, hasn't changed in forever anyway saveBlock["version"] = 1; - size_t period = sectionString.find("."); - std::string section = sectionString; - std::string subsection = ""; - if (period != std::string::npos) { - subsection = sectionString.substr(period + 1, std::string::npos); - section = sectionString.substr(0, sectionString.length() - (subsection.length() + 1)); - } - if (sectionString == "all") { - for (auto& sectionHandler : sectionSaveHandlers) { - if (std::find(gameSaveRegistry.begin(), gameSaveRegistry.end(), sectionHandler.first) != gameSaveRegistry.end()) { - nlohmann::json& sectionBlock = saveBlock["sections"][sectionHandler.first]; - sectionBlock["version"] = sectionHandler.second.first; - // If any save file is loaded for medatata, or a spoiler log is loaded (not sure which at this point), there is still data in the "randomizer" section - // This clears the randomizer data block if and only if the section being called is "randomizer" and n64ddFlag is false. - if (sectionHandler.first == "randomizer" && !gSaveContext.n64ddFlag) { - sectionBlock["data"] = nlohmann::json::object(); - } - - currentJsonContext = §ionBlock["data"]; - sectionHandler.second.second(saveContext, "all"); + if (sectionID == SECTION_ID_BASE) { + for (auto& sectionHandlerPair : sectionSaveHandlers) { + auto& saveFuncInfo = sectionHandlerPair.second; + // Don't call SaveFuncs for sections that aren't tied to game save + if (!saveFuncInfo.saveWithBase) { + continue; } + nlohmann::json& sectionBlock = saveBlock["sections"][saveFuncInfo.name]; + sectionBlock["version"] = sectionHandlerPair.second.version; + // If any save file is loaded for medatata, or a spoiler log is loaded (not sure which at this point), there is still data in the "randomizer" section + // This clears the randomizer data block if and only if the section being called is "randomizer" and n64ddFlag is false. + if (sectionHandlerPair.second.name == "randomizer" && !gSaveContext.n64ddFlag) { + sectionBlock["data"] = nlohmann::json::object(); + continue; + } + + currentJsonContext = §ionBlock["data"]; + sectionHandlerPair.second.func(saveContext, sectionID); } - } else if (sectionSaveHandlers.contains(section)) { - SectionSaveHandler handler = sectionSaveHandlers.find(section)->second; - nlohmann::json& sectionBlock = saveBlock["sections"][section]; - sectionBlock["version"] = handler.first; - currentJsonContext = §ionBlock["data"]; - handler.second(saveContext, subsection); } else { - // save function for specified section does not exist. should this error? - return; + SaveFuncInfo svi = sectionSaveHandlers.find(sectionID)->second; + auto& sectionName = svi.name; + auto sectionVersion = svi.version; + // If section has a parentSection, it is a subsection. Load parentSection version and set sectionBlock to parent string + if (svi.parentSection != -1 && svi.parentSection < sectionIndex) { + auto parentSvi = sectionSaveHandlers.find(svi.parentSection)->second; + sectionName = parentSvi.name; + sectionVersion = parentSvi.version; + } + nlohmann::json& sectionBlock = saveBlock["sections"][sectionName]; + sectionBlock["version"] = sectionVersion; + currentJsonContext = §ionBlock["data"]; + svi.func(saveContext, sectionID); } #if defined(__SWITCH__) || defined(__WIIU__) @@ -751,18 +743,24 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, const GameInteractor::Instance->ExecuteHooks(fileNum); } -void SaveManager::SaveSection(int fileNum, const std::string& sectionString) { +// SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded +void SaveManager::SaveSection(int fileNum, int sectionID) { if (fileNum == 0xFF) { return; } - // Can't think of any time the promise would be needed, so use push_task instead of submit + // Don't save a nonexistent section + if (sectionID >= sectionIndex) { + SPDLOG_ERROR("SaveSection: Section ID not registered."); + return; + } auto saveContext = new SaveContext; memcpy(saveContext, &gSaveContext, sizeof(gSaveContext)); - smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionString); + // Can't think of any time the promise would be needed, so use push_task instead of submit + smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionID); } void SaveManager::SaveFile(int fileNum) { - SaveSection(fileNum, "all"); + SaveSection(fileNum, SECTION_ID_BASE); } void SaveManager::SaveGlobal() { @@ -860,14 +858,21 @@ void SaveManager::AddLoadFunction(const std::string& name, int version, LoadFunc sectionLoadHandlers[name][version] = func; } -void SaveManager::AddSaveFunction(const std::string& name, int version, SaveFunc func) { - if (sectionSaveHandlers.contains(name)) { +void SaveManager::AddSaveFunction(const std::string& name, int version, SaveFunc func, bool saveWithBase, int parentSection = -1) { + if (sectionRegistry.contains(name)) { SPDLOG_ERROR("Adding save function for section that already has one: " + name); assert(false); return; } - - sectionSaveHandlers[name] = std::make_pair(version, func); + int index = sectionIndex; + if (coreSectionIDsByName.contains(name)) { + index = coreSectionIDsByName.find(name)->second; + } else { + sectionIndex++; + } + SaveFuncInfo sfi = { name, version, func, saveWithBase, parentSection }; + sectionSaveHandlers.emplace(index, sfi); + sectionRegistry.emplace(name); } void SaveManager::AddPostFunction(const std::string& name, PostFunc func) { @@ -1623,7 +1628,7 @@ void SaveManager::LoadBaseVersion4() { SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams); } -void SaveManager::SaveBase(SaveContext* saveContext, const std::string& subString) { +void SaveManager::SaveBase(SaveContext* saveContext, int sectionID) { SaveManager::Instance->SaveData("entranceIndex", saveContext->entranceIndex); SaveManager::Instance->SaveData("linkAge", saveContext->linkAge); SaveManager::Instance->SaveData("cutsceneIndex", saveContext->cutsceneIndex); @@ -2290,8 +2295,8 @@ extern "C" void Save_SaveFile(void) { SaveManager::Instance->SaveFile(gSaveContext.fileNum); } -extern "C" void Save_SaveSection(char* sectionString) { - SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionString); +extern "C" void Save_SaveSection(int sectionID) { + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionID); } extern "C" void Save_SaveGlobal(void) { @@ -2307,8 +2312,8 @@ extern "C" void Save_AddLoadFunction(char* name, int version, SaveManager::LoadF SaveManager::Instance->AddLoadFunction(name, version, func); } -extern "C" void Save_AddSaveFunction(char* name, int version, SaveManager::SaveFunc func) { - SaveManager::Instance->AddSaveFunction(name, version, func); +extern "C" void Save_AddSaveFunction(char* name, int version, SaveManager::SaveFunc func, bool saveWithBase, int parentSection = SECTION_PARENT_NONE) { + SaveManager::Instance->AddSaveFunction(name, version, func, saveWithBase, parentSection); } extern "C" SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum) { diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 93a1fe336..56c3692ba 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -2,6 +2,7 @@ #include +#define SECTION_PARENT_NONE -1 typedef struct { u8 valid; u16 deaths; @@ -27,6 +28,7 @@ typedef struct { #include #include #include +#include #include #include "thread-pool/BS_thread_pool.hpp" @@ -38,6 +40,7 @@ extern "C" { class SaveManager { public: + static SaveManager* Instance; static void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size); @@ -45,15 +48,23 @@ public: using InitFunc = void(*)(bool isDebug); using LoadFunc = void(*)(); - using SaveFunc = void(*)(SaveContext* saveContext, const std::string& subSection); - using PostFunc = void(*)(int version); + using SaveFunc = void(*)(SaveContext* saveContext, int sectionID); + using PostFunc = void (*)(int version); + + typedef struct { + std::string name; + int version; + SaveManager::SaveFunc func; + bool saveWithBase; + int parentSection; + } SaveFuncInfo; SaveManager(); void Init(); void InitFile(bool isDebug); void SaveFile(int fileNum); - void SaveSection(int fileNum, const std::string& sectionString); + void SaveSection(int fileNum, int sectionID); void SaveGlobal(); void LoadFile(int fileNum); bool SaveFile_Exist(int fileNum); @@ -66,7 +77,7 @@ public: void AddLoadFunction(const std::string& name, int version, LoadFunc func); // Adds a function that is called when saving. This should only be called once for each function, the version is filled in automatically. - void AddSaveFunction(const std::string& name, int version, SaveFunc func); + void AddSaveFunction(const std::string& name, int version, SaveFunc func, bool saveWithBase, int parentSection); // Adds a function to be called after loading is complete. This is to handle any cleanup required from loading old versions. void AddPostFunction(const std::string& name, PostFunc func); @@ -119,9 +130,6 @@ public: static const int MaxFiles = 3; std::array fileMetaInfo; - void RegisterGameSaveSection(std::string section); - void UnregisterAutosaveSection(std::string section); - private: std::filesystem::path GetFileName(int fileNum); nlohmann::json saveBlock; @@ -129,7 +137,7 @@ public: void ConvertFromUnversioned(); void CreateDefaultGlobal(); - void SaveFileThreaded(int fileNum, SaveContext* saveContext, const std::string& sectionString); + void SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID); void InitMeta(int slotNum); static void InitFileImpl(bool isDebug); @@ -138,23 +146,23 @@ public: static void LoadRandomizerVersion1(); static void LoadRandomizerVersion2(); - static void SaveRandomizer(SaveContext* saveContext, const std::string& subString); + static void SaveRandomizer(SaveContext* saveContext, int sectionID); static void LoadBaseVersion1(); static void LoadBaseVersion2(); static void LoadBaseVersion3(); static void LoadBaseVersion4(); - static void SaveBase(SaveContext* saveContext, const std::string& subString); + static void SaveBase(SaveContext* saveContext, int sectionID); std::vector initFuncs; using SectionLoadHandler = std::map; std::map sectionLoadHandlers; - using SectionSaveHandler = std::pair; - std::map sectionSaveHandlers; - // tracks sections to save during game saves - std::vector gameSaveRegistry; + int sectionIndex = SECTION_ID_MAX; + std::map coreSectionIDsByName; + std::map sectionSaveHandlers; + std::set sectionRegistry; std::map postHandlers; @@ -168,16 +176,16 @@ public: // TODO feature parity to the C++ interface. We need Save_AddInitFunction and Save_AddPostFunction at least typedef void (*Save_LoadFunc)(void); -typedef void (*Save_SaveFunc)(const SaveContext* saveContext); +typedef void (*Save_SaveFunc)(const SaveContext* saveContext, int sectionID); void Save_Init(void); void Save_InitFile(int isDebug); void Save_SaveFile(void); -void Save_SaveSection(char* sectionString); +void Save_SaveSection(int sectionID); void Save_SaveGlobal(void); void Save_LoadGlobal(void); void Save_AddLoadFunction(char* name, int version, Save_LoadFunc func); -void Save_AddSaveFunction(char* name, int version, Save_SaveFunc func); +void Save_AddSaveFunction(char* name, int version, Save_SaveFunc func, bool saveWithBase, int parentSection); SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum); void Save_CopyFile(int from, int to); void Save_DeleteFile(int fileNum);