diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 6fb434bf3..a6f1b96be 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 be47493c4..032c4197b 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -2,6 +2,9 @@ extern "C" { #include "gameplaystats.h" } +#include "soh/SaveManager.h" +#include "functions.h" +#include "macros.h" #include "ImGuiImpl.h" #include "../UIWidgets.hpp" @@ -273,6 +276,104 @@ std::string formatHexOnlyGameplayStat(uint32_t value) { return fmt::format("{:#x}", value, value); } +void LoadStatsVersion1() { + std::string buildVersion; + SaveManager::Instance->LoadData("buildVersion", buildVersion); + strncpy(gSaveContext.sohStats.buildVersion, buildVersion.c_str(), ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1); + gSaveContext.sohStats.buildVersion[ARRAY_COUNT(gSaveContext.sohStats.buildVersion) - 1] = 0; + SaveManager::Instance->LoadData("buildVersionMajor", gSaveContext.sohStats.buildVersionMajor); + SaveManager::Instance->LoadData("buildVersionMinor", gSaveContext.sohStats.buildVersionMinor); + SaveManager::Instance->LoadData("buildVersionPatch", gSaveContext.sohStats.buildVersionPatch); + + SaveManager::Instance->LoadData("heartPieces", gSaveContext.sohStats.heartPieces); + SaveManager::Instance->LoadData("heartContainers", gSaveContext.sohStats.heartContainers); + SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.dungeonKeys[i]); + }); + SaveManager::Instance->LoadData("rtaTiming", gSaveContext.sohStats.rtaTiming); + SaveManager::Instance->LoadData("fileCreatedAt", gSaveContext.sohStats.fileCreatedAt); + SaveManager::Instance->LoadData("playTimer", gSaveContext.sohStats.playTimer); + SaveManager::Instance->LoadData("pauseTimer", gSaveContext.sohStats.pauseTimer); + SaveManager::Instance->LoadArray("itemTimestamps", ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.itemTimestamp[i]); + }); + SaveManager::Instance->LoadArray("sceneTimestamps", ARRAY_COUNT(gSaveContext.sohStats.sceneTimestamps), [&](size_t i) { + SaveManager::Instance->LoadStruct("", [&]() { + int scene, room, sceneTime, roomTime, isRoom; + SaveManager::Instance->LoadData("scene", scene); + SaveManager::Instance->LoadData("room", room); + SaveManager::Instance->LoadData("sceneTime", sceneTime); + SaveManager::Instance->LoadData("roomTime", roomTime); + SaveManager::Instance->LoadData("isRoom", isRoom); + if (scene == 0 && room == 0 && sceneTime == 0 && roomTime == 0 && isRoom == 0) { + return; + } + gSaveContext.sohStats.sceneTimestamps[i].scene = scene; + gSaveContext.sohStats.sceneTimestamps[i].room = room; + gSaveContext.sohStats.sceneTimestamps[i].sceneTime = sceneTime; + gSaveContext.sohStats.sceneTimestamps[i].roomTime = roomTime; + gSaveContext.sohStats.sceneTimestamps[i].isRoom = isRoom; + }); + }); + SaveManager::Instance->LoadData("tsIdx", gSaveContext.sohStats.tsIdx); + SaveManager::Instance->LoadArray("counts", ARRAY_COUNT(gSaveContext.sohStats.count), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.count[i]); + }); + SaveManager::Instance->LoadArray("scenesDiscovered", ARRAY_COUNT(gSaveContext.sohStats.scenesDiscovered), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.scenesDiscovered[i]); + }); + SaveManager::Instance->LoadArray("entrancesDiscovered", ARRAY_COUNT(gSaveContext.sohStats.entrancesDiscovered), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.entrancesDiscovered[i]); + }); + SaveManager::Instance->LoadArray("locationsSkipped", ARRAY_COUNT(gSaveContext.sohStats.locationsSkipped), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.sohStats.locationsSkipped[i]); + }); +} + +void SaveStats(SaveContext* saveContext, int sectionID) { + SaveManager::Instance->SaveData("buildVersion", saveContext->sohStats.buildVersion); + SaveManager::Instance->SaveData("buildVersionMajor", saveContext->sohStats.buildVersionMajor); + SaveManager::Instance->SaveData("buildVersionMinor", saveContext->sohStats.buildVersionMinor); + SaveManager::Instance->SaveData("buildVersionPatch", saveContext->sohStats.buildVersionPatch); + + SaveManager::Instance->SaveData("heartPieces", saveContext->sohStats.heartPieces); + SaveManager::Instance->SaveData("heartContainers", saveContext->sohStats.heartContainers); + SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext->sohStats.dungeonKeys), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->sohStats.dungeonKeys[i]); + }); + SaveManager::Instance->SaveData("rtaTiming", saveContext->sohStats.rtaTiming); + SaveManager::Instance->SaveData("fileCreatedAt", saveContext->sohStats.fileCreatedAt); + SaveManager::Instance->SaveData("playTimer", saveContext->sohStats.playTimer); + SaveManager::Instance->SaveData("pauseTimer", saveContext->sohStats.pauseTimer); + SaveManager::Instance->SaveArray("itemTimestamps", ARRAY_COUNT(saveContext->sohStats.itemTimestamp), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->sohStats.itemTimestamp[i]); + }); + SaveManager::Instance->SaveArray("sceneTimestamps", ARRAY_COUNT(saveContext->sohStats.sceneTimestamps), [&](size_t i) { + if (saveContext->sohStats.sceneTimestamps[i].scene != 254 && saveContext->sohStats.sceneTimestamps[i].room != 254) { + SaveManager::Instance->SaveStruct("", [&]() { + SaveManager::Instance->SaveData("scene", saveContext->sohStats.sceneTimestamps[i].scene); + SaveManager::Instance->SaveData("room", saveContext->sohStats.sceneTimestamps[i].room); + SaveManager::Instance->SaveData("sceneTime", saveContext->sohStats.sceneTimestamps[i].sceneTime); + SaveManager::Instance->SaveData("roomTime", saveContext->sohStats.sceneTimestamps[i].roomTime); + SaveManager::Instance->SaveData("isRoom", saveContext->sohStats.sceneTimestamps[i].isRoom); + }); + } + }); + SaveManager::Instance->SaveData("tsIdx", saveContext->sohStats.tsIdx); + SaveManager::Instance->SaveArray("counts", ARRAY_COUNT(saveContext->sohStats.count), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->sohStats.count[i]); + }); + SaveManager::Instance->SaveArray("scenesDiscovered", ARRAY_COUNT(saveContext->sohStats.scenesDiscovered), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->sohStats.scenesDiscovered[i]); + }); + SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]); + }); + SaveManager::Instance->SaveArray("locationsSkipped", ARRAY_COUNT(saveContext->sohStats.locationsSkipped), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->sohStats.locationsSkipped[i]); + }); +} + void GameplayStatsRow(const char* label, std::string value, ImVec4 color = COLOR_WHITE) { ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::TableNextRow(); @@ -553,6 +654,47 @@ void DrawStatsTracker(bool& open) { ImGui::End(); } +void InitStats(bool isDebug) { + gSaveContext.sohStats.heartPieces = isDebug ? 8 : 0; + gSaveContext.sohStats.heartContainers = isDebug ? 8 : 0; + for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) { + gSaveContext.sohStats.dungeonKeys[dungeon] = isDebug ? 8 : 0; + } + gSaveContext.sohStats.rtaTiming = CVarGetInteger("gGameplayStats.RTATiming", 0); + gSaveContext.sohStats.fileCreatedAt = 0; + gSaveContext.sohStats.playTimer = 0; + gSaveContext.sohStats.pauseTimer = 0; + for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp); timestamp++) { + gSaveContext.sohStats.itemTimestamp[timestamp] = 0; + } + for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.sceneTimestamps); timestamp++) { + gSaveContext.sohStats.sceneTimestamps[timestamp].sceneTime = 0; + gSaveContext.sohStats.sceneTimestamps[timestamp].roomTime = 0; + gSaveContext.sohStats.sceneTimestamps[timestamp].scene = 254; + gSaveContext.sohStats.sceneTimestamps[timestamp].room = 254; + gSaveContext.sohStats.sceneTimestamps[timestamp].isRoom = 0; + } + gSaveContext.sohStats.tsIdx = 0; + for (int count = 0; count < ARRAY_COUNT(gSaveContext.sohStats.count); count++) { + gSaveContext.sohStats.count[count] = 0; + } + gSaveContext.sohStats.gameComplete = false; + for (int scenesIdx = 0; scenesIdx < ARRAY_COUNT(gSaveContext.sohStats.scenesDiscovered); scenesIdx++) { + gSaveContext.sohStats.scenesDiscovered[scenesIdx] = 0; + } + for (int entrancesIdx = 0; entrancesIdx < ARRAY_COUNT(gSaveContext.sohStats.entrancesDiscovered); entrancesIdx++) { + gSaveContext.sohStats.entrancesDiscovered[entrancesIdx] = 0; + } + for (int rc = 0; rc < ARRAY_COUNT(gSaveContext.sohStats.locationsSkipped); rc++) { + gSaveContext.sohStats.locationsSkipped[rc] = 0; + } + + strncpy(gSaveContext.sohStats.buildVersion, (const char*) gBuildVersion, sizeof(gSaveContext.sohStats.buildVersion) - 1); + gSaveContext.sohStats.buildVersion[sizeof(gSaveContext.sohStats.buildVersion) - 1] = 0; + gSaveContext.sohStats.buildVersionMajor = gBuildVersionMajor; + gSaveContext.sohStats.buildVersionMinor = gBuildVersionMinor; + gSaveContext.sohStats.buildVersionPatch = gBuildVersionPatch; +} // Entries listed here will have a timestamp shown in the stat window void SetupDisplayNames() { @@ -702,4 +844,11 @@ extern "C" void InitStatTracker() { LUS::AddWindow("Enhancements", "Gameplay Stats", DrawStatsTracker, CVarGetInteger("gGameplayStats.Enabled", 0)); SetupDisplayNames(); SetupDisplayColors(); + SaveManager::Instance->AddLoadFunction("sohStats", 1, LoadStatsVersion1); + // 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); } \ 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 3c3712afa..2ccb83627 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -769,6 +769,8 @@ void Entrance_SetSceneDiscovered(u8 sceneNum) { u32 sceneBit = 1 << (sceneNum - (idx * bitsPerIndex)); gSaveContext.sohStats.scenesDiscovered[idx] |= sceneBit; } + // Save scenesDiscovered + Save_SaveSection(SECTION_ID_SCENES); } u8 Entrance_GetIsEntranceDiscovered(u16 entranceIndex) { @@ -801,4 +803,6 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex) { } } } + // Save entrancesDiscovered + Save_SaveSection(SECTION_ID_ENTRANCES); } diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index e4e22d54c..bbc884066 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,14 +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); - AddSaveFunction("base", 3, SaveBase); + AddLoadFunction("base", 4, LoadBaseVersion4); + AddSaveFunction("base", 4, SaveBase, true, SECTION_PARENT_NONE); AddLoadFunction("randomizer", 1, LoadRandomizerVersion1); AddLoadFunction("randomizer", 2, LoadRandomizerVersion2); - AddSaveFunction("randomizer", 2, SaveRandomizer); + AddSaveFunction("randomizer", 2, SaveRandomizer, true, SECTION_PARENT_NONE); AddInitFunction(InitFileImpl); @@ -261,7 +268,7 @@ void SaveManager::LoadRandomizerVersion2() { }); } -void SaveManager::SaveRandomizer(SaveContext* saveContext) { +void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID) { if(!saveContext->n64ddFlag) return; @@ -394,6 +401,7 @@ void SaveManager::Init() { for (int fileNum = 0; fileNum < MaxFiles; fileNum++) { if (std::filesystem::exists(GetFileName(fileNum))) { LoadFile(fileNum); + saveBlock = nlohmann::json::object(); } } @@ -507,39 +515,6 @@ void SaveManager::InitFileNormal() { } gSaveContext.inventory.defenseHearts = 0; gSaveContext.inventory.gsTokens = 0; - gSaveContext.sohStats.heartPieces = 0; - gSaveContext.sohStats.heartContainers = 0; - for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) { - gSaveContext.sohStats.dungeonKeys[dungeon] = 0; - } - gSaveContext.sohStats.rtaTiming = CVarGetInteger("gGameplayStats.RTATiming", 0); - gSaveContext.sohStats.fileCreatedAt = 0; - gSaveContext.sohStats.playTimer = 0; - gSaveContext.sohStats.pauseTimer = 0; - for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.itemTimestamp); timestamp++) { - gSaveContext.sohStats.itemTimestamp[timestamp] = 0; - } - for (int timestamp = 0; timestamp < ARRAY_COUNT(gSaveContext.sohStats.sceneTimestamps); timestamp++) { - gSaveContext.sohStats.sceneTimestamps[timestamp].sceneTime = 0; - gSaveContext.sohStats.sceneTimestamps[timestamp].roomTime = 0; - gSaveContext.sohStats.sceneTimestamps[timestamp].scene = 254; - gSaveContext.sohStats.sceneTimestamps[timestamp].room = 254; - gSaveContext.sohStats.sceneTimestamps[timestamp].isRoom = 0; - } - gSaveContext.sohStats.tsIdx = 0; - for (int count = 0; count < ARRAY_COUNT(gSaveContext.sohStats.count); count++) { - gSaveContext.sohStats.count[count] = 0; - } - gSaveContext.sohStats.gameComplete = false; - for (int scenesIdx = 0; scenesIdx < ARRAY_COUNT(gSaveContext.sohStats.scenesDiscovered); scenesIdx++) { - gSaveContext.sohStats.scenesDiscovered[scenesIdx] = 0; - } - for (int entrancesIdx = 0; entrancesIdx < ARRAY_COUNT(gSaveContext.sohStats.entrancesDiscovered); entrancesIdx++) { - gSaveContext.sohStats.entrancesDiscovered[entrancesIdx] = 0; - } - for (int rc = 0; rc < ARRAY_COUNT(gSaveContext.sohStats.locationsSkipped); rc++) { - gSaveContext.sohStats.locationsSkipped[rc] = 0; - } for (int scene = 0; scene < ARRAY_COUNT(gSaveContext.sceneFlags); scene++) { gSaveContext.sceneFlags[scene].chest = 0; gSaveContext.sceneFlags[scene].swch = 0; @@ -608,12 +583,6 @@ void SaveManager::InitFileNormal() { gSaveContext.pendingSale = ITEM_NONE; gSaveContext.pendingSaleMod = MOD_NONE; - strncpy(gSaveContext.sohStats.buildVersion, (const char*) gBuildVersion, sizeof(gSaveContext.sohStats.buildVersion) - 1); - gSaveContext.sohStats.buildVersion[sizeof(gSaveContext.sohStats.buildVersion) - 1] = 0; - gSaveContext.sohStats.buildVersionMajor = gBuildVersionMajor; - gSaveContext.sohStats.buildVersionMinor = gBuildVersionMinor; - gSaveContext.sohStats.buildVersionPatch = gBuildVersionPatch; - //RANDOTODO (ADD ITEMLOCATIONS TO GSAVECONTEXT) } @@ -695,11 +664,6 @@ void SaveManager::InitFileDebug() { } gSaveContext.inventory.defenseHearts = 0; gSaveContext.inventory.gsTokens = 0; - gSaveContext.sohStats.heartPieces = 8; - gSaveContext.sohStats.heartContainers = 8; - for (int dungeon = 0; dungeon < ARRAY_COUNT(gSaveContext.sohStats.dungeonKeys); dungeon++) { - gSaveContext.sohStats.dungeonKeys[dungeon] = 8; - } gSaveContext.horseData.scene = SCENE_SPOT00; gSaveContext.horseData.pos.x = -1840; @@ -728,27 +692,53 @@ void SaveManager::InitFileDebug() { } // Threaded SaveFile takes copy of gSaveContext for local unmodified storage -void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext) { - nlohmann::json baseBlock; - baseBlock["version"] = 1; - baseBlock["sections"] = nlohmann::json::object(); - for (auto& section : sectionSaveHandlers) { - nlohmann::json& sectionBlock = baseBlock["sections"][section.first]; - sectionBlock["version"] = section.second.first; +void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) { + // Needed for first time save, hasn't changed in forever anyway + saveBlock["version"] = 1; + 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 { + 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"]; - section.second.second(saveContext); + svi.func(saveContext, sectionID); } #if defined(__SWITCH__) || defined(__WIIU__) FILE* w = fopen(GetFileName(fileNum).c_str(), "w"); - std::string json_string = baseBlock.dump(4); + std::string json_string = saveBlock.dump(4); fwrite(json_string.c_str(), sizeof(char), json_string.length(), w); fclose(w); #else std::ofstream output(GetFileName(fileNum)); - output << std::setw(4) << baseBlock << std::endl; + output << std::setw(4) << saveBlock << std::endl; #endif delete saveContext; @@ -756,20 +746,29 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext) { GameInteractor::Instance->ExecuteHooks(fileNum); } -void SaveManager::SaveFile(int fileNum, bool threaded) { +// 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, bool threaded) { 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)); if (threaded) { - smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext); + smThreadPool->push_task_back(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionID); } else { - SaveFileThreaded(fileNum, saveContext); + SaveFileThreaded(fileNum, saveContext, sectionID); } } +void SaveManager::SaveFile(int fileNum) { + SaveSection(fileNum, SECTION_ID_BASE, true); +} + void SaveManager::SaveGlobal() { nlohmann::json globalBlock; globalBlock["version"] = 1; @@ -790,7 +789,7 @@ void SaveManager::LoadFile(int fileNum) { std::ifstream input(GetFileName(fileNum)); - nlohmann::json saveBlock; + saveBlock = nlohmann::json::object(); input >> saveBlock; if (!saveBlock.contains("version")) { SPDLOG_ERROR("Save at " + GetFileName(fileNum).string() + " contains no version"); @@ -866,14 +865,22 @@ 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, index); } void SaveManager::AddPostFunction(const std::string& name, PostFunc func) { @@ -886,6 +893,15 @@ void SaveManager::AddPostFunction(const std::string& name, PostFunc func) { postHandlers[name] = func; } +// Returns -1 if section name not found +int SaveManager::GetSaveSectionID(std::string& sectionName) { + if (sectionRegistry.contains(sectionName)) { + return sectionRegistry.find(sectionName)->second; + } else { + return -1; + } +} + void SaveManager::CreateDefaultGlobal() { gSaveContext.audioSetting = 0; gSaveContext.zTargetSetting = 0; @@ -1461,7 +1477,180 @@ void SaveManager::LoadBaseVersion3() { SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams); } -void SaveManager::SaveBase(SaveContext* saveContext) { +void SaveManager::LoadBaseVersion4() { + SaveManager::Instance->LoadData("entranceIndex", gSaveContext.entranceIndex); + SaveManager::Instance->LoadData("linkAge", gSaveContext.linkAge); + SaveManager::Instance->LoadData("cutsceneIndex", gSaveContext.cutsceneIndex); + SaveManager::Instance->LoadData("dayTime", gSaveContext.dayTime); + SaveManager::Instance->LoadData("nightFlag", gSaveContext.nightFlag); + SaveManager::Instance->LoadData("totalDays", gSaveContext.totalDays); + SaveManager::Instance->LoadData("bgsDayCount", gSaveContext.bgsDayCount); + SaveManager::Instance->LoadData("deaths", gSaveContext.deaths); + SaveManager::Instance->LoadArray("playerName", ARRAY_COUNT(gSaveContext.playerName), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.playerName[i]); + }); + SaveManager::Instance->LoadData("n64ddFlag", gSaveContext.n64ddFlag); + SaveManager::Instance->LoadData("healthCapacity", gSaveContext.healthCapacity); + SaveManager::Instance->LoadData("health", gSaveContext.health); + SaveManager::Instance->LoadData("magicLevel", gSaveContext.magicLevel); + SaveManager::Instance->LoadData("magic", gSaveContext.magic); + SaveManager::Instance->LoadData("rupees", gSaveContext.rupees); + SaveManager::Instance->LoadData("swordHealth", gSaveContext.swordHealth); + SaveManager::Instance->LoadData("naviTimer", gSaveContext.naviTimer); + SaveManager::Instance->LoadData("isMagicAcquired", gSaveContext.isMagicAcquired); + SaveManager::Instance->LoadData("isDoubleMagicAcquired", gSaveContext.isDoubleMagicAcquired); + SaveManager::Instance->LoadData("isDoubleDefenseAcquired", gSaveContext.isDoubleDefenseAcquired); + SaveManager::Instance->LoadData("bgsFlag", gSaveContext.bgsFlag); + SaveManager::Instance->LoadData("ocarinaGameRoundNum", gSaveContext.ocarinaGameRoundNum); + SaveManager::Instance->LoadStruct("childEquips", []() { + SaveManager::Instance->LoadArray("buttonItems", ARRAY_COUNT(gSaveContext.childEquips.buttonItems), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.childEquips.buttonItems[i], + static_cast(ITEM_NONE)); + }); + SaveManager::Instance->LoadArray("cButtonSlots", ARRAY_COUNT(gSaveContext.childEquips.cButtonSlots), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.childEquips.cButtonSlots[i], + static_cast(SLOT_NONE)); + }); + SaveManager::Instance->LoadData("equipment", gSaveContext.childEquips.equipment); + }); + SaveManager::Instance->LoadStruct("adultEquips", []() { + SaveManager::Instance->LoadArray("buttonItems", ARRAY_COUNT(gSaveContext.adultEquips.buttonItems), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.adultEquips.buttonItems[i], + static_cast(ITEM_NONE)); + }); + SaveManager::Instance->LoadArray("cButtonSlots", ARRAY_COUNT(gSaveContext.adultEquips.cButtonSlots), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.adultEquips.cButtonSlots[i], + static_cast(SLOT_NONE)); + }); + SaveManager::Instance->LoadData("equipment", gSaveContext.adultEquips.equipment); + }); + SaveManager::Instance->LoadData("unk_54", gSaveContext.unk_54); + SaveManager::Instance->LoadData("savedSceneNum", gSaveContext.savedSceneNum); + SaveManager::Instance->LoadStruct("equips", []() { + SaveManager::Instance->LoadArray("buttonItems", ARRAY_COUNT(gSaveContext.equips.buttonItems), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.equips.buttonItems[i], static_cast(ITEM_NONE)); + }); + SaveManager::Instance->LoadArray("cButtonSlots", ARRAY_COUNT(gSaveContext.equips.cButtonSlots), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.equips.cButtonSlots[i], static_cast(SLOT_NONE)); + }); + SaveManager::Instance->LoadData("equipment", gSaveContext.equips.equipment); + }); + SaveManager::Instance->LoadStruct("inventory", []() { + SaveManager::Instance->LoadArray("items", ARRAY_COUNT(gSaveContext.inventory.items), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.inventory.items[i]); + }); + SaveManager::Instance->LoadArray("ammo", ARRAY_COUNT(gSaveContext.inventory.ammo), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.inventory.ammo[i]); + }); + SaveManager::Instance->LoadData("equipment", gSaveContext.inventory.equipment); + SaveManager::Instance->LoadData("upgrades", gSaveContext.inventory.upgrades); + SaveManager::Instance->LoadData("questItems", gSaveContext.inventory.questItems); + SaveManager::Instance->LoadArray("dungeonItems", ARRAY_COUNT(gSaveContext.inventory.dungeonItems), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.inventory.dungeonItems[i]); + }); + SaveManager::Instance->LoadArray("dungeonKeys", ARRAY_COUNT(gSaveContext.inventory.dungeonKeys), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.inventory.dungeonKeys[i]); + }); + SaveManager::Instance->LoadData("defenseHearts", gSaveContext.inventory.defenseHearts); + SaveManager::Instance->LoadData("gsTokens", gSaveContext.inventory.gsTokens); + }); + SaveManager::Instance->LoadArray("sceneFlags", ARRAY_COUNT(gSaveContext.sceneFlags), [](size_t i) { + SaveManager::Instance->LoadStruct("", [&i]() { + SaveManager::Instance->LoadData("chest", gSaveContext.sceneFlags[i].chest); + SaveManager::Instance->LoadData("swch", gSaveContext.sceneFlags[i].swch); + SaveManager::Instance->LoadData("clear", gSaveContext.sceneFlags[i].clear); + SaveManager::Instance->LoadData("collect", gSaveContext.sceneFlags[i].collect); + SaveManager::Instance->LoadData("unk", gSaveContext.sceneFlags[i].unk); + SaveManager::Instance->LoadData("rooms", gSaveContext.sceneFlags[i].rooms); + SaveManager::Instance->LoadData("floors", gSaveContext.sceneFlags[i].floors); + }); + }); + SaveManager::Instance->LoadStruct("fw", []() { + SaveManager::Instance->LoadStruct("pos", []() { + SaveManager::Instance->LoadData("x", gSaveContext.fw.pos.x); + SaveManager::Instance->LoadData("y", gSaveContext.fw.pos.y); + SaveManager::Instance->LoadData("z", gSaveContext.fw.pos.z); + }); + SaveManager::Instance->LoadData("yaw", gSaveContext.fw.yaw); + SaveManager::Instance->LoadData("playerParams", gSaveContext.fw.playerParams); + SaveManager::Instance->LoadData("entranceIndex", gSaveContext.fw.entranceIndex); + SaveManager::Instance->LoadData("roomIndex", gSaveContext.fw.roomIndex); + SaveManager::Instance->LoadData("set", gSaveContext.fw.set); + SaveManager::Instance->LoadData("tempSwchFlags", gSaveContext.fw.tempSwchFlags); + SaveManager::Instance->LoadData("tempCollectFlags", gSaveContext.fw.tempCollectFlags); + }); + SaveManager::Instance->LoadArray("gsFlags", ARRAY_COUNT(gSaveContext.gsFlags), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.gsFlags[i]); + }); + SaveManager::Instance->LoadArray("highScores", ARRAY_COUNT(gSaveContext.highScores), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.highScores[i]); + }); + SaveManager::Instance->LoadArray("eventChkInf", ARRAY_COUNT(gSaveContext.eventChkInf), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.eventChkInf[i]); + }); + SaveManager::Instance->LoadArray("itemGetInf", ARRAY_COUNT(gSaveContext.itemGetInf), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.itemGetInf[i]); + }); + SaveManager::Instance->LoadArray("infTable", ARRAY_COUNT(gSaveContext.infTable), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.infTable[i]); + }); + SaveManager::Instance->LoadData("worldMapAreaData", gSaveContext.worldMapAreaData); + SaveManager::Instance->LoadData("scarecrowLongSongSet", gSaveContext.scarecrowLongSongSet); + SaveManager::Instance->LoadArray("scarecrowLongSong", ARRAY_COUNT(gSaveContext.scarecrowLongSong), [](size_t i) { + SaveManager::Instance->LoadStruct("", [&i]() { + SaveManager::Instance->LoadData("noteIdx", gSaveContext.scarecrowLongSong[i].noteIdx); + SaveManager::Instance->LoadData("unk_01", gSaveContext.scarecrowLongSong[i].unk_01); + SaveManager::Instance->LoadData("unk_02", gSaveContext.scarecrowLongSong[i].unk_02); + SaveManager::Instance->LoadData("volume", gSaveContext.scarecrowLongSong[i].volume); + SaveManager::Instance->LoadData("vibrato", gSaveContext.scarecrowLongSong[i].vibrato); + SaveManager::Instance->LoadData("tone", gSaveContext.scarecrowLongSong[i].tone); + SaveManager::Instance->LoadData("semitone", gSaveContext.scarecrowLongSong[i].semitone); + }); + }); + SaveManager::Instance->LoadData("scarecrowSpawnSongSet", gSaveContext.scarecrowSpawnSongSet); + SaveManager::Instance->LoadArray("scarecrowSpawnSong", ARRAY_COUNT(gSaveContext.scarecrowSpawnSong), [](size_t i) { + SaveManager::Instance->LoadStruct("", [&i]() { + SaveManager::Instance->LoadData("noteIdx", gSaveContext.scarecrowSpawnSong[i].noteIdx); + SaveManager::Instance->LoadData("unk_01", gSaveContext.scarecrowSpawnSong[i].unk_01); + SaveManager::Instance->LoadData("unk_02", gSaveContext.scarecrowSpawnSong[i].unk_02); + SaveManager::Instance->LoadData("volume", gSaveContext.scarecrowSpawnSong[i].volume); + SaveManager::Instance->LoadData("vibrato", gSaveContext.scarecrowSpawnSong[i].vibrato); + SaveManager::Instance->LoadData("tone", gSaveContext.scarecrowSpawnSong[i].tone); + SaveManager::Instance->LoadData("semitone", gSaveContext.scarecrowSpawnSong[i].semitone); + }); + }); + SaveManager::Instance->LoadStruct("horseData", []() { + SaveManager::Instance->LoadData("scene", gSaveContext.horseData.scene); + SaveManager::Instance->LoadStruct("pos", []() { + SaveManager::Instance->LoadData("x", gSaveContext.horseData.pos.x); + SaveManager::Instance->LoadData("y", gSaveContext.horseData.pos.y); + SaveManager::Instance->LoadData("z", gSaveContext.horseData.pos.z); + }); + SaveManager::Instance->LoadData("angle", gSaveContext.horseData.angle); + }); + + SaveManager::Instance->LoadArray("randomizerInf", ARRAY_COUNT(gSaveContext.randomizerInf), [](size_t i) { + SaveManager::Instance->LoadData("", gSaveContext.randomizerInf[i]); + }); + SaveManager::Instance->LoadData("isMasterQuest", gSaveContext.isMasterQuest); + SaveManager::Instance->LoadStruct("backupFW", []() { + SaveManager::Instance->LoadStruct("pos", []() { + SaveManager::Instance->LoadData("x", gSaveContext.backupFW.pos.x); + SaveManager::Instance->LoadData("y", gSaveContext.backupFW.pos.y); + SaveManager::Instance->LoadData("z", gSaveContext.backupFW.pos.z); + }); + SaveManager::Instance->LoadData("yaw", gSaveContext.backupFW.yaw); + SaveManager::Instance->LoadData("playerParams", gSaveContext.backupFW.playerParams); + SaveManager::Instance->LoadData("entranceIndex", gSaveContext.backupFW.entranceIndex); + SaveManager::Instance->LoadData("roomIndex", gSaveContext.backupFW.roomIndex); + SaveManager::Instance->LoadData("set", gSaveContext.backupFW.set); + SaveManager::Instance->LoadData("tempSwchFlags", gSaveContext.backupFW.tempSwchFlags); + SaveManager::Instance->LoadData("tempCollectFlags", gSaveContext.backupFW.tempCollectFlags); + }); + SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams); +} + +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); @@ -1534,47 +1723,6 @@ void SaveManager::SaveBase(SaveContext* saveContext) { SaveManager::Instance->SaveData("defenseHearts", saveContext->inventory.defenseHearts); SaveManager::Instance->SaveData("gsTokens", saveContext->inventory.gsTokens); }); - SaveManager::Instance->SaveStruct("sohStats", [&]() { - SaveManager::Instance->SaveData("buildVersion", saveContext->sohStats.buildVersion); - SaveManager::Instance->SaveData("buildVersionMajor", saveContext->sohStats.buildVersionMajor); - SaveManager::Instance->SaveData("buildVersionMinor", saveContext->sohStats.buildVersionMinor); - SaveManager::Instance->SaveData("buildVersionPatch", saveContext->sohStats.buildVersionPatch); - - SaveManager::Instance->SaveData("heartPieces", saveContext->sohStats.heartPieces); - SaveManager::Instance->SaveData("heartContainers", saveContext->sohStats.heartContainers); - SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext->sohStats.dungeonKeys), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.dungeonKeys[i]); - }); - SaveManager::Instance->SaveData("rtaTiming", saveContext->sohStats.rtaTiming); - SaveManager::Instance->SaveData("fileCreatedAt", saveContext->sohStats.fileCreatedAt); - SaveManager::Instance->SaveData("playTimer", saveContext->sohStats.playTimer); - SaveManager::Instance->SaveData("pauseTimer", saveContext->sohStats.pauseTimer); - SaveManager::Instance->SaveArray("itemTimestamps", ARRAY_COUNT(saveContext->sohStats.itemTimestamp), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.itemTimestamp[i]); - }); - SaveManager::Instance->SaveArray("sceneTimestamps", ARRAY_COUNT(saveContext->sohStats.sceneTimestamps), [&](size_t i) { - SaveManager::Instance->SaveStruct("", [&]() { - SaveManager::Instance->SaveData("scene", saveContext->sohStats.sceneTimestamps[i].scene); - SaveManager::Instance->SaveData("room", saveContext->sohStats.sceneTimestamps[i].room); - SaveManager::Instance->SaveData("sceneTime", saveContext->sohStats.sceneTimestamps[i].sceneTime); - SaveManager::Instance->SaveData("roomTime", saveContext->sohStats.sceneTimestamps[i].roomTime); - SaveManager::Instance->SaveData("isRoom", saveContext->sohStats.sceneTimestamps[i].isRoom); - }); - }); - SaveManager::Instance->SaveData("tsIdx", saveContext->sohStats.tsIdx); - SaveManager::Instance->SaveArray("counts", ARRAY_COUNT(saveContext->sohStats.count), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.count[i]); - }); - SaveManager::Instance->SaveArray("scenesDiscovered", ARRAY_COUNT(saveContext->sohStats.scenesDiscovered), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.scenesDiscovered[i]); - }); - SaveManager::Instance->SaveArray("entrancesDiscovered", ARRAY_COUNT(saveContext->sohStats.entrancesDiscovered), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.entrancesDiscovered[i]); - }); - SaveManager::Instance->SaveArray("locationsSkipped", ARRAY_COUNT(saveContext->sohStats.locationsSkipped), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->sohStats.locationsSkipped[i]); - }); - }); SaveManager::Instance->SaveArray("sceneFlags", ARRAY_COUNT(saveContext->sceneFlags), [&](size_t i) { SaveManager::Instance->SaveStruct("", [&]() { SaveManager::Instance->SaveData("chest", saveContext->sceneFlags[i].chest); @@ -1744,8 +1892,7 @@ void SaveManager::LoadStruct(const std::string& name, LoadStructFunc func) { #if defined(__WIIU__) || defined(__SWITCH__) // std::filesystem::copy_file doesn't work properly with the Wii U's toolchain atm -int copy_file(const char* src, const char* dst) -{ +int copy_file(const char* src, const char* dst) { alignas(0x40) uint8_t buf[4096]; FILE* r = fopen(src, "r"); if (!r) { @@ -2147,7 +2294,7 @@ void SaveManager::ConvertFromUnversioned() { static SaveContext saveContextSave = gSaveContext; InitFile(false); CopyV0Save(*file, gSaveContext); - SaveFile(fileNum, false); + SaveSection(fileNum, SECTION_ID_BASE, false); InitMeta(fileNum); gSaveContext = saveContextSave; } @@ -2168,7 +2315,11 @@ extern "C" void Save_InitFile(int isDebug) { } extern "C" void Save_SaveFile(void) { - SaveManager::Instance->SaveFile(gSaveContext.fileNum, true); + SaveManager::Instance->SaveFile(gSaveContext.fileNum); +} + +extern "C" void Save_SaveSection(int sectionID) { + SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionID, true); } extern "C" void Save_SaveGlobal(void) { @@ -2184,8 +2335,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) { + SaveManager::Instance->AddSaveFunction(name, version, func, saveWithBase); } extern "C" SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum) { diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index d5c8def68..ec0a2d787 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; @@ -31,28 +32,38 @@ typedef struct { #include "thread-pool/BS_thread_pool.hpp" extern "C" { - #include "z64save.h" +#include "z64save.h" } #include class SaveManager { -public: + public: static SaveManager* Instance; static void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size); static void ReadSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size); - using InitFunc = void(*)(bool isDebug); - using LoadFunc = void(*)(); - using SaveFunc = void(*)(SaveContext* saveContext); - using PostFunc = void(*)(int version); + using InitFunc = void (*)(bool isDebug); + using LoadFunc = void (*)(); + 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, bool threaded); + void SaveFile(int fileNum); + void SaveSection(int fileNum, int sectionID, bool threaded); + int GetSaveSectionID(std::string& name); void SaveGlobal(); void LoadFile(int fileNum); bool SaveFile_Exist(int fileNum); @@ -64,10 +75,12 @@ public: // Adds a function to handling loading a section 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); + // 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, 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. + // 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); void CopyZeldaFile(int from, int to); @@ -75,8 +88,7 @@ public: bool IsRandoFile(); // Use a name of "" to save to an array. You must be in a SaveArray callback. - template - void SaveData(const std::string& name, const T& data) { + template void SaveData(const std::string& name, const T& data) { if (name == "") { assert((*currentJsonContext).is_array()); (*currentJsonContext).push_back(data); @@ -84,16 +96,16 @@ public: (*currentJsonContext)[name.c_str()] = data; } } - + // In the SaveArrayFunc func, the name must be "" to save to the array. using SaveArrayFunc = std::function; void SaveArray(const std::string& name, const size_t size, SaveArrayFunc func); - + using SaveStructFunc = std::function; void SaveStruct(const std::string& name, SaveStructFunc func); // Use a name of "" to load from an array. You must be in a LoadArray callback. - template void LoadData(const std::string& name, T& data, const T& defaultValue = T{}) { + template void LoadData(const std::string& name, T& data, const T& defaultValue = T{}) { if (name == "") { if (currentJsonArrayContext == currentJsonContext->end()) { // This array member is past the data in the json file. Therefore, default construct it @@ -120,11 +132,12 @@ public: private: std::filesystem::path GetFileName(int fileNum); + nlohmann::json saveBlock; void ConvertFromUnversioned(); void CreateDefaultGlobal(); - void SaveFileThreaded(int fileNum, SaveContext* saveContext); + void SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID); void InitMeta(int slotNum); static void InitFileImpl(bool isDebug); @@ -133,20 +146,23 @@ public: static void LoadRandomizerVersion1(); static void LoadRandomizerVersion2(); - static void SaveRandomizer(SaveContext* saveContext); + static void SaveRandomizer(SaveContext* saveContext, int sectionID); static void LoadBaseVersion1(); static void LoadBaseVersion2(); static void LoadBaseVersion3(); - static void SaveBase(SaveContext* saveContext); + static void LoadBaseVersion4(); + static void SaveBase(SaveContext* saveContext, int sectionID); std::vector initFuncs; using SectionLoadHandler = std::map; std::map sectionLoadHandlers; - using SectionSaveHandler = std::pair; - std::map sectionSaveHandlers; + int sectionIndex = SECTION_ID_MAX; + std::map coreSectionIDsByName; + std::map sectionSaveHandlers; + std::map sectionRegistry; std::map postHandlers; @@ -160,15 +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(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);