From 9d81cb03cf93c6b351fb39ad19c3e5bb7d0e1c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= <159546+serprex@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:10:19 +0000 Subject: [PATCH] WriteFilesSafely (#7299) Log filesystem op failures, don't rename after write failed refactor SaveContext handling, use std::lock_guard --- soh/soh/Enhancements/gameplaystats.cpp | 62 +-- .../Enhancements/randomizer/location_access.h | 1 - .../randomizer/randomizer_check_tracker.cpp | 4 +- .../randomizer/randomizer_hint_tracker.cpp | 2 +- .../randomizer/randomizer_item_tracker.cpp | 2 +- soh/soh/SaveManager.cpp | 434 +++++++++--------- soh/soh/SaveManager.h | 17 +- 7 files changed, 255 insertions(+), 267 deletions(-) diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index d814025b0f..71ae03c92d 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -327,48 +327,48 @@ void LoadStatsVersion1() { [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.ship.stats.entrancesDiscovered[i]); }); } -void SaveStats(SaveContext* saveContext, int sectionID, bool fullSave) { - SaveManager::Instance->SaveData("buildVersion", saveContext->ship.stats.buildVersion); - SaveManager::Instance->SaveData("buildVersionMajor", saveContext->ship.stats.buildVersionMajor); - SaveManager::Instance->SaveData("buildVersionMinor", saveContext->ship.stats.buildVersionMinor); - SaveManager::Instance->SaveData("buildVersionPatch", saveContext->ship.stats.buildVersionPatch); +void SaveStats(const SaveContext& saveContext, int sectionID, bool fullSave) { + SaveManager::Instance->SaveData("buildVersion", saveContext.ship.stats.buildVersion); + SaveManager::Instance->SaveData("buildVersionMajor", saveContext.ship.stats.buildVersionMajor); + SaveManager::Instance->SaveData("buildVersionMinor", saveContext.ship.stats.buildVersionMinor); + SaveManager::Instance->SaveData("buildVersionPatch", saveContext.ship.stats.buildVersionPatch); - SaveManager::Instance->SaveData("heartPieces", saveContext->ship.stats.heartPieces); - SaveManager::Instance->SaveData("heartContainers", saveContext->ship.stats.heartContainers); - SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext->ship.stats.dungeonKeys), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->ship.stats.dungeonKeys[i]); + SaveManager::Instance->SaveData("heartPieces", saveContext.ship.stats.heartPieces); + SaveManager::Instance->SaveData("heartContainers", saveContext.ship.stats.heartContainers); + SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext.ship.stats.dungeonKeys), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.ship.stats.dungeonKeys[i]); }); - SaveManager::Instance->SaveData("rtaTiming", saveContext->ship.stats.rtaTiming); - SaveManager::Instance->SaveData("firstInput", saveContext->ship.stats.firstInput); - SaveManager::Instance->SaveData("fileCreatedAt", saveContext->ship.stats.fileCreatedAt); - SaveManager::Instance->SaveData("playTimer", saveContext->ship.stats.playTimer); - SaveManager::Instance->SaveData("pauseTimer", saveContext->ship.stats.pauseTimer); + SaveManager::Instance->SaveData("rtaTiming", saveContext.ship.stats.rtaTiming); + SaveManager::Instance->SaveData("firstInput", saveContext.ship.stats.firstInput); + SaveManager::Instance->SaveData("fileCreatedAt", saveContext.ship.stats.fileCreatedAt); + SaveManager::Instance->SaveData("playTimer", saveContext.ship.stats.playTimer); + SaveManager::Instance->SaveData("pauseTimer", saveContext.ship.stats.pauseTimer); SaveManager::Instance->SaveArray( - "itemTimestamps", ARRAY_COUNT(saveContext->ship.stats.itemTimestamp), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->ship.stats.itemTimestamp[i]); }); + "itemTimestamps", ARRAY_COUNT(saveContext.ship.stats.itemTimestamp), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.ship.stats.itemTimestamp[i]); }); SaveManager::Instance->SaveArray( - "sceneTimestamps", ARRAY_COUNT(saveContext->ship.stats.sceneTimestamps), [&](size_t i) { - if (saveContext->ship.stats.sceneTimestamps[i].scene != 254 && - saveContext->ship.stats.sceneTimestamps[i].room != 254) { + "sceneTimestamps", ARRAY_COUNT(saveContext.ship.stats.sceneTimestamps), [&](size_t i) { + if (saveContext.ship.stats.sceneTimestamps[i].scene != 254 && + saveContext.ship.stats.sceneTimestamps[i].room != 254) { SaveManager::Instance->SaveStruct("", [&]() { - SaveManager::Instance->SaveData("scene", saveContext->ship.stats.sceneTimestamps[i].scene); - SaveManager::Instance->SaveData("room", saveContext->ship.stats.sceneTimestamps[i].room); - SaveManager::Instance->SaveData("sceneTime", saveContext->ship.stats.sceneTimestamps[i].sceneTime); - SaveManager::Instance->SaveData("roomTime", saveContext->ship.stats.sceneTimestamps[i].roomTime); - SaveManager::Instance->SaveData("isRoom", saveContext->ship.stats.sceneTimestamps[i].isRoom); + SaveManager::Instance->SaveData("scene", saveContext.ship.stats.sceneTimestamps[i].scene); + SaveManager::Instance->SaveData("room", saveContext.ship.stats.sceneTimestamps[i].room); + SaveManager::Instance->SaveData("sceneTime", saveContext.ship.stats.sceneTimestamps[i].sceneTime); + SaveManager::Instance->SaveData("roomTime", saveContext.ship.stats.sceneTimestamps[i].roomTime); + SaveManager::Instance->SaveData("isRoom", saveContext.ship.stats.sceneTimestamps[i].isRoom); }); } }); - SaveManager::Instance->SaveData("tsIdx", saveContext->ship.stats.tsIdx); - SaveManager::Instance->SaveArray("counts", ARRAY_COUNT(saveContext->ship.stats.count), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->ship.stats.count[i]); + SaveManager::Instance->SaveData("tsIdx", saveContext.ship.stats.tsIdx); + SaveManager::Instance->SaveArray("counts", ARRAY_COUNT(saveContext.ship.stats.count), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.ship.stats.count[i]); }); SaveManager::Instance->SaveArray( - "scenesDiscovered", ARRAY_COUNT(saveContext->ship.stats.scenesDiscovered), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->ship.stats.scenesDiscovered[i]); }); + "scenesDiscovered", ARRAY_COUNT(saveContext.ship.stats.scenesDiscovered), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.ship.stats.scenesDiscovered[i]); }); SaveManager::Instance->SaveArray( - "entrancesDiscovered", ARRAY_COUNT(saveContext->ship.stats.entrancesDiscovered), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->ship.stats.entrancesDiscovered[i]); }); + "entrancesDiscovered", ARRAY_COUNT(saveContext.ship.stats.entrancesDiscovered), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.ship.stats.entrancesDiscovered[i]); }); } void GameplayStatsRow(const char* label, const std::string& value, ImVec4 color = COLOR_WHITE, diff --git a/soh/soh/Enhancements/randomizer/location_access.h b/soh/soh/Enhancements/randomizer/location_access.h index af993dab2b..d08ecb1b8f 100644 --- a/soh/soh/Enhancements/randomizer/location_access.h +++ b/soh/soh/Enhancements/randomizer/location_access.h @@ -8,7 +8,6 @@ #include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/Enhancements/randomizer/SeedContext.h" #include "soh/Enhancements/randomizer/logic.h" -#include "soh/Enhancements/randomizer/dungeon.h" #define TIME_PASSES true #define TIME_DOESNT_PASS false diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 67ba9cc99b..5f9c36bcd5 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1145,7 +1145,7 @@ void InitTrackerData(bool isDebug) { areasSpoiled = 0; } -void SaveTrackerData(SaveContext* saveContext, int sectionID, bool fullSave) { +void SaveTrackerData(const SaveContext& saveContext, int sectionID, bool fullSave) { bool updateOrdering = false; std::vector checkCount; for (int i = RC_UNKNOWN_CHECK; i < RC_MAX; i++) { @@ -1182,7 +1182,7 @@ void SaveTrackerData(SaveContext* saveContext, int sectionID, bool fullSave) { } } -void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) { +void SaveFile(const SaveContext& saveContext, int sectionID, bool fullSave) { SaveTrackerData(saveContext, sectionID, fullSave); if (fullSave) { recalculateAvailable = true; diff --git a/soh/soh/Enhancements/randomizer/randomizer_hint_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_hint_tracker.cpp index dfea62c033..8143b2eac1 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_hint_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_hint_tracker.cpp @@ -135,7 +135,7 @@ void InitHintTrackerData(bool isDebug) { hintTextCache.clear(); } -void SaveHintTrackerData(SaveContext* saveContext, int sectionID, bool fullSave) { +void SaveHintTrackerData(const SaveContext& saveContext, int sectionID, bool fullSave) { std::vector hints; { std::lock_guard lock(readHintsMutex); diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index bac5a1893d..e1e5892e1b 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -1766,7 +1766,7 @@ void ItemTrackerInitFile(bool isDebug) { itemTrackerNotes.push_back(0); } -void ItemTrackerSaveFile(SaveContext* saveContext, int sectionID, bool fullSave) { +void ItemTrackerSaveFile(const SaveContext& saveContext, int sectionID, bool fullSave) { SaveManager::Instance->SaveData("personalNotes", std::string(std::begin(itemTrackerNotes), std::end(itemTrackerNotes)).c_str()); } diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 593afc448e..0c695efab0 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -1,3 +1,16 @@ +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#elif !defined(__SWITCH__) && !defined(__WIIU__) +#include +#endif +#include +#include + #include "SaveManager.h" #include "OTRGlobals.h" #include "Enhancements/game-interactor/GameInteractor.h" @@ -10,7 +23,6 @@ #include "soh/Enhancements/randomizer/settings.h" #include "ResourceManagerHelpers.h" #include "soh/SohGui/SohGui.hpp" -#include "soh/SohGui/UIWidgets.hpp" extern "C" { #include "z64.h" @@ -19,18 +31,76 @@ extern "C" { #include } -#define NOGDI // avoid various windows defines that conflict with things in z64.h -#include -#include - -#include -#include -#include -#include - extern "C" SaveContext gSaveContext; using namespace std::string_literals; +#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) { + alignas(0x40) uint8_t buf[4096]; + FILE* r = fopen(src, "r"); + if (!r) { + return -1; + } + FILE* w = fopen(dst, "w"); + if (!w) { + return -2; + } + + size_t res; + while ((res = fread(buf, 1, sizeof(buf), r)) > 0) { + if (fwrite(buf, 1, res, w) != res) { + break; + } + } + + fclose(r); + fclose(w); + return res >= 0 ? 0 : res; +} +#endif + +// Write to temp file and only swap once fully on disk +static bool WriteFileSafely(const std::filesystem::path& fileName, const std::filesystem::path& tempFile, + const std::string& contents) { +#ifdef _WIN32 + FILE* w = _wfopen(tempFile.c_str(), L"wb"); +#else + FILE* w = fopen(tempFile.c_str(), "wb"); +#endif + bool written = + w != nullptr && fwrite(contents.c_str(), 1, contents.length(), w) == contents.length() && fflush(w) == 0; + // Push data from OS cache to disk. Without this system crash after rename can leave file at full size but zeroed +#ifdef _WIN32 + written = written && _commit(_fileno(w)) == 0; +#elif !defined(__SWITCH__) && !defined(__WIIU__) + written = written && fsync(fileno(w)) == 0; +#endif + if (w != nullptr) { + written = fclose(w) == 0 && written; + } + + std::error_code ec; + if (!written) { + SPDLOG_ERROR("Failed to write {}, keeping previous {}", tempFile.string(), fileName.string()); + std::filesystem::remove(tempFile, ec); + return false; + } +#if defined(__SWITCH__) || defined(__WIIU__) + std::filesystem::remove(fileName, ec); + copy_file(tempFile.c_str(), fileName.c_str()); + std::filesystem::remove(tempFile, ec); +#else + std::filesystem::rename(tempFile, fileName, ec); + if (ec) { + SPDLOG_ERROR("Failed to replace {}: {}", fileName.string(), ec.message()); + std::filesystem::remove(tempFile, ec); + return false; + } +#endif + return true; +} + void SaveManager::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr, const size_t size) { std::ofstream saveFile = std::ofstream(savePath, std::fstream::in | std::fstream::out | std::fstream::binary); @@ -295,8 +365,8 @@ void SaveManager::LoadRandomizer() { }); } -void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool fullSave) { - if (saveContext->ship.quest.id != QUEST_RANDOMIZER) { +void SaveManager::SaveRandomizer(const SaveContext& saveContext, int sectionID, bool fullSave) { + if (saveContext.ship.quest.id != QUEST_RANDOMIZER) { return; } @@ -426,8 +496,8 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f }); SaveManager::Instance->SaveData("triforcePiecesCollected", - saveContext->ship.quest.data.randomizer.triforcePiecesCollected); - SaveManager::Instance->SaveData("bombchuUpgradeLevel", saveContext->ship.quest.data.randomizer.bombchuUpgradeLevel); + saveContext.ship.quest.data.randomizer.triforcePiecesCollected); + SaveManager::Instance->SaveData("bombchuUpgradeLevel", saveContext.ship.quest.data.randomizer.bombchuUpgradeLevel); SaveManager::Instance->SaveData("silverShadowBlades", gSaveContext.ship.quest.data.randomizer.silverShadowBlades); SaveManager::Instance->SaveData("silverShadowPit", gSaveContext.ship.quest.data.randomizer.silverShadowPit); SaveManager::Instance->SaveData("silverShadowSpikes", gSaveContext.ship.quest.data.randomizer.silverShadowSpikes); @@ -457,7 +527,7 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f SaveManager::Instance->SaveData("silverMqGanonWater", gSaveContext.ship.quest.data.randomizer.silverMqGanonWater); SaveManager::Instance->SaveData("silverMqGanonShadow", gSaveContext.ship.quest.data.randomizer.silverMqGanonShadow); - SaveManager::Instance->SaveData("pendingIceTrapCount", saveContext->ship.pendingIceTrapCount); + SaveManager::Instance->SaveData("pendingIceTrapCount", saveContext.ship.pendingIceTrapCount); std::shared_ptr randomizer = OTRGlobals::Instance->gRandomizer; @@ -594,9 +664,7 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { sections.erase("randomizer"); metaSaveBlock["fileType"] = FILE_TYPE_SAVE_VANILLA; std::lock_guard guard(saveMtx); - std::ofstream output(fileName); - output << metaSaveBlock.dump(1); - output.close(); + WriteFileSafely(fileName, GetFileTempName(fileNum), metaSaveBlock.dump(1)); } else { nlohmann::json& statsBlock = sections["sohStats"]["data"]; s16 major = statsBlock.value("buildVersionMajor", 0); @@ -1221,36 +1289,10 @@ void SaveManager::InitFileMaxed() { Flags_SetRandomizerInf(RAND_INF_OBTAINED_ROCS_FEATHER); } -#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) { - alignas(0x40) uint8_t buf[4096]; - FILE* r = fopen(src, "r"); - if (!r) { - return -1; - } - FILE* w = fopen(dst, "w"); - if (!w) { - return -2; - } - - size_t res; - while ((res = fread(buf, 1, sizeof(buf), r)) > 0) { - if (fwrite(buf, 1, res, w) != res) { - break; - } - } - - fclose(r); - fclose(w); - return res >= 0 ? 0 : res; -} -#endif - // Threaded SaveFile takes copy of gSaveContext for local unmodified storage -void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID) { - saveMtx.lock(); +void SaveManager::SaveFileThreaded(int fileNum, const SaveContext& saveContext, int sectionID) { + std::lock_guard guard(saveMtx); SPDLOG_INFO("Save File - fileNum: {}", fileNum); // Needed for first time save, hasn't changed in forever anyway saveBlock["version"] = 1; @@ -1292,41 +1334,13 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se svi.func(saveContext, sectionID, false); } - std::filesystem::path fileName = GetFileName(fileNum); - std::filesystem::path tempFile = GetFileTempName(fileNum); - - if (std::filesystem::exists(tempFile)) { - std::filesystem::remove(tempFile); + if (!WriteFileSafely(GetFileName(fileNum), GetFileTempName(fileNum), saveBlock.dump(1))) { + return; } -#if defined(__SWITCH__) || defined(__WIIU__) - FILE* w = fopen(tempFile.c_str(), "w"); - std::string json_string = saveBlock.dump(1); - fwrite(json_string.c_str(), sizeof(char), json_string.length(), w); - fclose(w); -#else - std::ofstream output(tempFile); - output << std::setw(1) << saveBlock << std::endl; - output.close(); -#endif - -#if defined(__SWITCH__) || defined(__WIIU__) - if (std::filesystem::exists(fileName)) { - std::filesystem::remove(fileName); - } - copy_file(tempFile.c_str(), fileName.c_str()); - if (std::filesystem::exists(tempFile)) { - std::filesystem::remove(tempFile); - } -#else - std::filesystem::rename(tempFile, fileName); -#endif - - delete saveContext; InitMeta(fileNum); GameInteractor::Instance->ExecuteHooks(fileNum, sectionID); SPDLOG_INFO("Save File Finish - fileNum: {}", fileNum); - saveMtx.unlock(); } // SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to @@ -1341,12 +1355,13 @@ void SaveManager::SaveSection(int fileNum, int sectionID, bool threaded) { SPDLOG_ERROR("SaveSection: Section ID not registered."); return; } - auto saveContext = new SaveContext; - memcpy(saveContext, &gSaveContext, sizeof(gSaveContext)); + + auto saveContext = std::make_shared(gSaveContext); if (threaded) { - smThreadPool->detach_task(std::bind(&SaveManager::SaveFileThreaded, this, fileNum, saveContext, sectionID)); + smThreadPool->detach_task( + [this, fileNum, saveContext, sectionID] { SaveFileThreaded(fileNum, *saveContext, sectionID); }); } else { - SaveFileThreaded(fileNum, saveContext, sectionID); + SaveFileThreaded(fileNum, *saveContext, sectionID); } } @@ -1364,12 +1379,11 @@ void SaveManager::SaveGlobal() { const std::filesystem::path sSavePath(Ship::Context::GetPathRelativeToAppDirectory("Save")); const std::filesystem::path sGlobalPath = sSavePath / std::string("global.sav"); - std::ofstream output(sGlobalPath); - output << std::setw(1) << globalBlock << std::endl; + WriteFileSafely(sGlobalPath, sSavePath / std::string("global.temp"), globalBlock.dump(1)); } void SaveManager::LoadFile(int fileNum) { - saveMtx.lock(); + std::lock_guard guard(saveMtx); SPDLOG_INFO("Load File - fileNum: {}", fileNum); std::filesystem::path fileName = GetFileName(fileNum); assert(std::filesystem::exists(fileName)); @@ -1445,7 +1459,6 @@ void SaveManager::LoadFile(int fileNum) { ".\nSave file corruption is suspected.\n" + "The file has been renamed to prevent further issues."); } - saveMtx.unlock(); } void SaveManager::ThreadPoolWait() { @@ -2265,170 +2278,167 @@ void SaveManager::LoadBaseVersion4() { SaveManager::Instance->LoadData("maskMemory", gSaveContext.ship.maskMemory); } -void SaveManager::SaveBase(SaveContext* saveContext, int sectionID, bool fullSave) { - SaveManager::Instance->SaveData("entranceIndex", saveContext->entranceIndex); - SaveManager::Instance->SaveData("linkAge", saveContext->linkAge); - SaveManager::Instance->SaveData("cutsceneIndex", saveContext->cutsceneIndex); - SaveManager::Instance->SaveData("dayTime", saveContext->dayTime); - SaveManager::Instance->SaveData("nightFlag", saveContext->nightFlag); - SaveManager::Instance->SaveData("totalDays", saveContext->totalDays); - SaveManager::Instance->SaveData("bgsDayCount", saveContext->bgsDayCount); - SaveManager::Instance->SaveData("deaths", saveContext->deaths); - SaveManager::Instance->SaveArray("playerName", ARRAY_COUNT(saveContext->playerName), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->playerName[i]); - }); - SaveManager::Instance->SaveData("healthCapacity", saveContext->healthCapacity); - SaveManager::Instance->SaveData("health", saveContext->health); - SaveManager::Instance->SaveData("magicLevel", saveContext->magicLevel); - SaveManager::Instance->SaveData("magic", saveContext->magic); - SaveManager::Instance->SaveData("rupees", saveContext->rupees); - SaveManager::Instance->SaveData("swordHealth", saveContext->swordHealth); - SaveManager::Instance->SaveData("naviTimer", saveContext->naviTimer); - SaveManager::Instance->SaveData("isMagicAcquired", saveContext->isMagicAcquired); - SaveManager::Instance->SaveData("isDoubleMagicAcquired", saveContext->isDoubleMagicAcquired); - SaveManager::Instance->SaveData("isDoubleDefenseAcquired", saveContext->isDoubleDefenseAcquired); - SaveManager::Instance->SaveData("bgsFlag", saveContext->bgsFlag); - SaveManager::Instance->SaveData("ocarinaGameRoundNum", saveContext->ocarinaGameRoundNum); +void SaveManager::SaveBase(const SaveContext& saveContext, int sectionID, bool fullSave) { + SaveManager::Instance->SaveData("entranceIndex", saveContext.entranceIndex); + SaveManager::Instance->SaveData("linkAge", saveContext.linkAge); + SaveManager::Instance->SaveData("cutsceneIndex", saveContext.cutsceneIndex); + SaveManager::Instance->SaveData("dayTime", saveContext.dayTime); + SaveManager::Instance->SaveData("nightFlag", saveContext.nightFlag); + SaveManager::Instance->SaveData("totalDays", saveContext.totalDays); + SaveManager::Instance->SaveData("bgsDayCount", saveContext.bgsDayCount); + SaveManager::Instance->SaveData("deaths", saveContext.deaths); + SaveManager::Instance->SaveArray("playerName", ARRAY_COUNT(saveContext.playerName), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.playerName[i]); }); + SaveManager::Instance->SaveData("healthCapacity", saveContext.healthCapacity); + SaveManager::Instance->SaveData("health", saveContext.health); + SaveManager::Instance->SaveData("magicLevel", saveContext.magicLevel); + SaveManager::Instance->SaveData("magic", saveContext.magic); + SaveManager::Instance->SaveData("rupees", saveContext.rupees); + SaveManager::Instance->SaveData("swordHealth", saveContext.swordHealth); + SaveManager::Instance->SaveData("naviTimer", saveContext.naviTimer); + SaveManager::Instance->SaveData("isMagicAcquired", saveContext.isMagicAcquired); + SaveManager::Instance->SaveData("isDoubleMagicAcquired", saveContext.isDoubleMagicAcquired); + SaveManager::Instance->SaveData("isDoubleDefenseAcquired", saveContext.isDoubleDefenseAcquired); + SaveManager::Instance->SaveData("bgsFlag", saveContext.bgsFlag); + SaveManager::Instance->SaveData("ocarinaGameRoundNum", saveContext.ocarinaGameRoundNum); SaveManager::Instance->SaveStruct("childEquips", [&]() { SaveManager::Instance->SaveArray( - "buttonItems", ARRAY_COUNT(saveContext->childEquips.buttonItems), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->childEquips.buttonItems[i]); }); + "buttonItems", ARRAY_COUNT(saveContext.childEquips.buttonItems), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.childEquips.buttonItems[i]); }); SaveManager::Instance->SaveArray( - "cButtonSlots", ARRAY_COUNT(saveContext->childEquips.cButtonSlots), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->childEquips.cButtonSlots[i]); }); - SaveManager::Instance->SaveData("equipment", saveContext->childEquips.equipment); + "cButtonSlots", ARRAY_COUNT(saveContext.childEquips.cButtonSlots), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.childEquips.cButtonSlots[i]); }); + SaveManager::Instance->SaveData("equipment", saveContext.childEquips.equipment); }); SaveManager::Instance->SaveStruct("adultEquips", [&]() { SaveManager::Instance->SaveArray( - "buttonItems", ARRAY_COUNT(saveContext->adultEquips.buttonItems), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->adultEquips.buttonItems[i]); }); + "buttonItems", ARRAY_COUNT(saveContext.adultEquips.buttonItems), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.adultEquips.buttonItems[i]); }); SaveManager::Instance->SaveArray( - "cButtonSlots", ARRAY_COUNT(saveContext->adultEquips.cButtonSlots), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->adultEquips.cButtonSlots[i]); }); - SaveManager::Instance->SaveData("equipment", saveContext->adultEquips.equipment); + "cButtonSlots", ARRAY_COUNT(saveContext.adultEquips.cButtonSlots), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.adultEquips.cButtonSlots[i]); }); + SaveManager::Instance->SaveData("equipment", saveContext.adultEquips.equipment); }); - SaveManager::Instance->SaveData("unk_54", saveContext->unk_54); - SaveManager::Instance->SaveData("savedSceneNum", saveContext->savedSceneNum); + SaveManager::Instance->SaveData("unk_54", saveContext.unk_54); + SaveManager::Instance->SaveData("savedSceneNum", saveContext.savedSceneNum); SaveManager::Instance->SaveStruct("equips", [&]() { - SaveManager::Instance->SaveArray("buttonItems", ARRAY_COUNT(saveContext->equips.buttonItems), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->equips.buttonItems[i]); + SaveManager::Instance->SaveArray("buttonItems", ARRAY_COUNT(saveContext.equips.buttonItems), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.equips.buttonItems[i]); }); - SaveManager::Instance->SaveArray("cButtonSlots", ARRAY_COUNT(saveContext->equips.cButtonSlots), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->equips.cButtonSlots[i]); + SaveManager::Instance->SaveArray("cButtonSlots", ARRAY_COUNT(saveContext.equips.cButtonSlots), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.equips.cButtonSlots[i]); }); - SaveManager::Instance->SaveData("equipment", saveContext->equips.equipment); + SaveManager::Instance->SaveData("equipment", saveContext.equips.equipment); }); SaveManager::Instance->SaveStruct("inventory", [&]() { - SaveManager::Instance->SaveArray("items", ARRAY_COUNT(saveContext->inventory.items), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->inventory.items[i]); + SaveManager::Instance->SaveArray("items", ARRAY_COUNT(saveContext.inventory.items), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.inventory.items[i]); }); - SaveManager::Instance->SaveArray("ammo", ARRAY_COUNT(saveContext->inventory.ammo), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->inventory.ammo[i]); + SaveManager::Instance->SaveArray("ammo", ARRAY_COUNT(saveContext.inventory.ammo), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.inventory.ammo[i]); }); - SaveManager::Instance->SaveData("equipment", saveContext->inventory.equipment); - SaveManager::Instance->SaveData("upgrades", saveContext->inventory.upgrades); - SaveManager::Instance->SaveData("questItems", saveContext->inventory.questItems); + SaveManager::Instance->SaveData("equipment", saveContext.inventory.equipment); + SaveManager::Instance->SaveData("upgrades", saveContext.inventory.upgrades); + SaveManager::Instance->SaveData("questItems", saveContext.inventory.questItems); SaveManager::Instance->SaveArray( - "dungeonItems", ARRAY_COUNT(saveContext->inventory.dungeonItems), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->inventory.dungeonItems[i]); }); - SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext->inventory.dungeonKeys), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->inventory.dungeonKeys[i]); + "dungeonItems", ARRAY_COUNT(saveContext.inventory.dungeonItems), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.inventory.dungeonItems[i]); }); + SaveManager::Instance->SaveArray("dungeonKeys", ARRAY_COUNT(saveContext.inventory.dungeonKeys), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.inventory.dungeonKeys[i]); }); - SaveManager::Instance->SaveData("defenseHearts", saveContext->inventory.defenseHearts); - SaveManager::Instance->SaveData("gsTokens", saveContext->inventory.gsTokens); + SaveManager::Instance->SaveData("defenseHearts", saveContext.inventory.defenseHearts); + SaveManager::Instance->SaveData("gsTokens", saveContext.inventory.gsTokens); }); - SaveManager::Instance->SaveArray("sceneFlags", ARRAY_COUNT(saveContext->sceneFlags), [&](size_t i) { + SaveManager::Instance->SaveArray("sceneFlags", ARRAY_COUNT(saveContext.sceneFlags), [&](size_t i) { SaveManager::Instance->SaveStruct("", [&]() { - SaveManager::Instance->SaveData("chest", saveContext->sceneFlags[i].chest); - SaveManager::Instance->SaveData("swch", saveContext->sceneFlags[i].swch); - SaveManager::Instance->SaveData("clear", saveContext->sceneFlags[i].clear); - SaveManager::Instance->SaveData("collect", saveContext->sceneFlags[i].collect); - SaveManager::Instance->SaveData("unk", saveContext->sceneFlags[i].unk); - SaveManager::Instance->SaveData("rooms", saveContext->sceneFlags[i].rooms); - SaveManager::Instance->SaveData("floors", saveContext->sceneFlags[i].floors); + SaveManager::Instance->SaveData("chest", saveContext.sceneFlags[i].chest); + SaveManager::Instance->SaveData("swch", saveContext.sceneFlags[i].swch); + SaveManager::Instance->SaveData("clear", saveContext.sceneFlags[i].clear); + SaveManager::Instance->SaveData("collect", saveContext.sceneFlags[i].collect); + SaveManager::Instance->SaveData("unk", saveContext.sceneFlags[i].unk); + SaveManager::Instance->SaveData("rooms", saveContext.sceneFlags[i].rooms); + SaveManager::Instance->SaveData("floors", saveContext.sceneFlags[i].floors); }); }); SaveManager::Instance->SaveStruct("fw", [&]() { SaveManager::Instance->SaveStruct("pos", [&]() { - SaveManager::Instance->SaveData("x", saveContext->fw.pos.x); - SaveManager::Instance->SaveData("y", saveContext->fw.pos.y); - SaveManager::Instance->SaveData("z", saveContext->fw.pos.z); + SaveManager::Instance->SaveData("x", saveContext.fw.pos.x); + SaveManager::Instance->SaveData("y", saveContext.fw.pos.y); + SaveManager::Instance->SaveData("z", saveContext.fw.pos.z); }); - SaveManager::Instance->SaveData("yaw", saveContext->fw.yaw); - SaveManager::Instance->SaveData("playerParams", saveContext->fw.playerParams); - SaveManager::Instance->SaveData("entranceIndex", saveContext->fw.entranceIndex); - SaveManager::Instance->SaveData("roomIndex", saveContext->fw.roomIndex); - SaveManager::Instance->SaveData("set", saveContext->fw.set); - SaveManager::Instance->SaveData("tempSwchFlags", saveContext->fw.tempSwchFlags); - SaveManager::Instance->SaveData("tempCollectFlags", saveContext->fw.tempCollectFlags); + SaveManager::Instance->SaveData("yaw", saveContext.fw.yaw); + SaveManager::Instance->SaveData("playerParams", saveContext.fw.playerParams); + SaveManager::Instance->SaveData("entranceIndex", saveContext.fw.entranceIndex); + SaveManager::Instance->SaveData("roomIndex", saveContext.fw.roomIndex); + SaveManager::Instance->SaveData("set", saveContext.fw.set); + SaveManager::Instance->SaveData("tempSwchFlags", saveContext.fw.tempSwchFlags); + SaveManager::Instance->SaveData("tempCollectFlags", saveContext.fw.tempCollectFlags); }); - SaveManager::Instance->SaveArray("gsFlags", ARRAY_COUNT(saveContext->gsFlags), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->gsFlags[i]); }); - SaveManager::Instance->SaveArray("highScores", ARRAY_COUNT(saveContext->highScores), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->highScores[i]); + SaveManager::Instance->SaveArray("gsFlags", ARRAY_COUNT(saveContext.gsFlags), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.gsFlags[i]); }); + SaveManager::Instance->SaveArray("highScores", ARRAY_COUNT(saveContext.highScores), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.highScores[i]); }); + SaveManager::Instance->SaveArray("eventChkInf", ARRAY_COUNT(saveContext.eventChkInf), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.eventChkInf[i]); }); - SaveManager::Instance->SaveArray("eventChkInf", ARRAY_COUNT(saveContext->eventChkInf), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->eventChkInf[i]); - }); - SaveManager::Instance->SaveArray("itemGetInf", ARRAY_COUNT(saveContext->itemGetInf), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->itemGetInf[i]); - }); - SaveManager::Instance->SaveArray("infTable", ARRAY_COUNT(saveContext->infTable), - [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->infTable[i]); }); - SaveManager::Instance->SaveData("worldMapAreaData", saveContext->worldMapAreaData); - SaveManager::Instance->SaveData("scarecrowLongSongSet", saveContext->scarecrowLongSongSet); - SaveManager::Instance->SaveArray("scarecrowLongSong", ARRAY_COUNT(saveContext->scarecrowLongSong), [&](size_t i) { + SaveManager::Instance->SaveArray("itemGetInf", ARRAY_COUNT(saveContext.itemGetInf), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.itemGetInf[i]); }); + SaveManager::Instance->SaveArray("infTable", ARRAY_COUNT(saveContext.infTable), + [&](size_t i) { SaveManager::Instance->SaveData("", saveContext.infTable[i]); }); + SaveManager::Instance->SaveData("worldMapAreaData", saveContext.worldMapAreaData); + SaveManager::Instance->SaveData("scarecrowLongSongSet", saveContext.scarecrowLongSongSet); + SaveManager::Instance->SaveArray("scarecrowLongSong", ARRAY_COUNT(saveContext.scarecrowLongSong), [&](size_t i) { SaveManager::Instance->SaveStruct("", [&]() { - SaveManager::Instance->SaveData("noteIdx", saveContext->scarecrowLongSong[i].pitch); - SaveManager::Instance->SaveData("unk_02", saveContext->scarecrowLongSong[i].length); - SaveManager::Instance->SaveData("volume", saveContext->scarecrowLongSong[i].volume); - SaveManager::Instance->SaveData("vibrato", saveContext->scarecrowLongSong[i].vibrato); - SaveManager::Instance->SaveData("tone", saveContext->scarecrowLongSong[i].bend); - SaveManager::Instance->SaveData("semitone", saveContext->scarecrowLongSong[i].bFlat4Flag); + SaveManager::Instance->SaveData("noteIdx", saveContext.scarecrowLongSong[i].pitch); + SaveManager::Instance->SaveData("unk_02", saveContext.scarecrowLongSong[i].length); + SaveManager::Instance->SaveData("volume", saveContext.scarecrowLongSong[i].volume); + SaveManager::Instance->SaveData("vibrato", saveContext.scarecrowLongSong[i].vibrato); + SaveManager::Instance->SaveData("tone", saveContext.scarecrowLongSong[i].bend); + SaveManager::Instance->SaveData("semitone", saveContext.scarecrowLongSong[i].bFlat4Flag); }); }); - SaveManager::Instance->SaveData("scarecrowSpawnSongSet", saveContext->scarecrowSpawnSongSet); - SaveManager::Instance->SaveArray("scarecrowSpawnSong", ARRAY_COUNT(saveContext->scarecrowSpawnSong), [&](size_t i) { + SaveManager::Instance->SaveData("scarecrowSpawnSongSet", saveContext.scarecrowSpawnSongSet); + SaveManager::Instance->SaveArray("scarecrowSpawnSong", ARRAY_COUNT(saveContext.scarecrowSpawnSong), [&](size_t i) { SaveManager::Instance->SaveStruct("", [&]() { - SaveManager::Instance->SaveData("noteIdx", saveContext->scarecrowSpawnSong[i].pitch); - SaveManager::Instance->SaveData("unk_02", saveContext->scarecrowSpawnSong[i].length); - SaveManager::Instance->SaveData("volume", saveContext->scarecrowSpawnSong[i].volume); - SaveManager::Instance->SaveData("vibrato", saveContext->scarecrowSpawnSong[i].vibrato); - SaveManager::Instance->SaveData("tone", saveContext->scarecrowSpawnSong[i].bend); - SaveManager::Instance->SaveData("semitone", saveContext->scarecrowSpawnSong[i].bFlat4Flag); + SaveManager::Instance->SaveData("noteIdx", saveContext.scarecrowSpawnSong[i].pitch); + SaveManager::Instance->SaveData("unk_02", saveContext.scarecrowSpawnSong[i].length); + SaveManager::Instance->SaveData("volume", saveContext.scarecrowSpawnSong[i].volume); + SaveManager::Instance->SaveData("vibrato", saveContext.scarecrowSpawnSong[i].vibrato); + SaveManager::Instance->SaveData("tone", saveContext.scarecrowSpawnSong[i].bend); + SaveManager::Instance->SaveData("semitone", saveContext.scarecrowSpawnSong[i].bFlat4Flag); }); }); SaveManager::Instance->SaveStruct("horseData", [&]() { - SaveManager::Instance->SaveData("scene", saveContext->horseData.scene); + SaveManager::Instance->SaveData("scene", saveContext.horseData.scene); SaveManager::Instance->SaveStruct("pos", [&]() { - SaveManager::Instance->SaveData("x", saveContext->horseData.pos.x); - SaveManager::Instance->SaveData("y", saveContext->horseData.pos.y); - SaveManager::Instance->SaveData("z", saveContext->horseData.pos.z); + SaveManager::Instance->SaveData("x", saveContext.horseData.pos.x); + SaveManager::Instance->SaveData("y", saveContext.horseData.pos.y); + SaveManager::Instance->SaveData("z", saveContext.horseData.pos.z); }); - SaveManager::Instance->SaveData("angle", saveContext->horseData.angle); + SaveManager::Instance->SaveData("angle", saveContext.horseData.angle); }); - SaveManager::Instance->SaveArray("randomizerInf", ARRAY_COUNT(saveContext->ship.randomizerInf), [&](size_t i) { - SaveManager::Instance->SaveData("", saveContext->ship.randomizerInf[i]); + SaveManager::Instance->SaveArray("randomizerInf", ARRAY_COUNT(saveContext.ship.randomizerInf), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext.ship.randomizerInf[i]); }); - SaveManager::Instance->SaveData("isMasterQuest", saveContext->ship.quest.id == QUEST_MASTER); + SaveManager::Instance->SaveData("isMasterQuest", saveContext.ship.quest.id == QUEST_MASTER); SaveManager::Instance->SaveStruct("backupFW", [&]() { SaveManager::Instance->SaveStruct("pos", [&]() { - SaveManager::Instance->SaveData("x", saveContext->ship.backupFW.pos.x); - SaveManager::Instance->SaveData("y", saveContext->ship.backupFW.pos.y); - SaveManager::Instance->SaveData("z", saveContext->ship.backupFW.pos.z); + SaveManager::Instance->SaveData("x", saveContext.ship.backupFW.pos.x); + SaveManager::Instance->SaveData("y", saveContext.ship.backupFW.pos.y); + SaveManager::Instance->SaveData("z", saveContext.ship.backupFW.pos.z); }); - SaveManager::Instance->SaveData("yaw", saveContext->ship.backupFW.yaw); - SaveManager::Instance->SaveData("playerParams", saveContext->ship.backupFW.playerParams); - SaveManager::Instance->SaveData("entranceIndex", saveContext->ship.backupFW.entranceIndex); - SaveManager::Instance->SaveData("roomIndex", saveContext->ship.backupFW.roomIndex); - SaveManager::Instance->SaveData("set", saveContext->ship.backupFW.set); - SaveManager::Instance->SaveData("tempSwchFlags", saveContext->ship.backupFW.tempSwchFlags); - SaveManager::Instance->SaveData("tempCollectFlags", saveContext->ship.backupFW.tempCollectFlags); + SaveManager::Instance->SaveData("yaw", saveContext.ship.backupFW.yaw); + SaveManager::Instance->SaveData("playerParams", saveContext.ship.backupFW.playerParams); + SaveManager::Instance->SaveData("entranceIndex", saveContext.ship.backupFW.entranceIndex); + SaveManager::Instance->SaveData("roomIndex", saveContext.ship.backupFW.roomIndex); + SaveManager::Instance->SaveData("set", saveContext.ship.backupFW.set); + SaveManager::Instance->SaveData("tempSwchFlags", saveContext.ship.backupFW.tempSwchFlags); + SaveManager::Instance->SaveData("tempCollectFlags", saveContext.ship.backupFW.tempCollectFlags); }); - SaveManager::Instance->SaveData("dogParams", saveContext->dogParams); - SaveManager::Instance->SaveData("filenameLanguage", saveContext->ship.filenameLanguage); - SaveManager::Instance->SaveData("maskMemory", saveContext->ship.maskMemory); + SaveManager::Instance->SaveData("dogParams", saveContext.dogParams); + SaveManager::Instance->SaveData("filenameLanguage", saveContext.ship.filenameLanguage); + SaveManager::Instance->SaveData("maskMemory", saveContext.ship.maskMemory); } // Load a string into a char array based on size and ensuring it is null terminated when overflowed @@ -2917,14 +2927,6 @@ extern "C" void Save_LoadFile(void) { SaveManager::Instance->LoadFile(gSaveContext.fileNum); } -extern "C" void Save_AddLoadFunction(char* name, int version, SaveManager::LoadFunc func) { - SaveManager::Instance->AddLoadFunction(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) { return &SaveManager::Instance->fileMetaInfo[fileNum]; } @@ -2936,7 +2938,3 @@ extern "C" void Save_CopyFile(int from, int to) { extern "C" void Save_DeleteFile(int fileNum) { SaveManager::Instance->DeleteZeldaFile(fileNum); } - -extern "C" u32 Save_Exist(int fileNum) { - return SaveManager::Instance->SaveFile_Exist(fileNum); -} diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 06e8bd5851..095cf7e6d8 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -66,7 +66,7 @@ class SaveManager { using InitFunc = void (*)(bool isDebug); using LoadFunc = void (*)(); - using SaveFunc = void (*)(SaveContext* saveContext, int sectionID, bool fullSave); + using SaveFunc = void (*)(const SaveContext& saveContext, int sectionID, bool fullSave); using PostFunc = void (*)(int version); typedef struct { @@ -160,7 +160,7 @@ class SaveManager { void ConvertFromUnversioned(); void CreateDefaultGlobal(); - void SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID); + void SaveFileThreaded(int fileNum, const SaveContext& saveContext, int sectionID); void InitMeta(int slotNum); void StartupCheckAndInitMeta(int slotNum); @@ -170,13 +170,13 @@ class SaveManager { static void InitFileMaxed(); static void LoadRandomizer(); - static void SaveRandomizer(SaveContext* saveContext, int sectionID, bool fullSave); + static void SaveRandomizer(const SaveContext& saveContext, int sectionID, bool fullSave); static void LoadBaseVersion1(); static void LoadBaseVersion2(); static void LoadBaseVersion3(); static void LoadBaseVersion4(); - static void SaveBase(SaveContext* saveContext, int sectionID, bool fullSave); + static void SaveBase(const SaveContext& saveContext, int sectionID, bool fullSave); std::vector initFuncs; @@ -198,21 +198,12 @@ class SaveManager { #else -// 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, 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, bool saveWithBase, int parentSection); SaveFileMetaInfo* Save_GetSaveMetaInfo(int fileNum); void Save_CopyFile(int from, int to); void Save_DeleteFile(int fileNum); -bool Save_Exist(int fileNum); #endif