WriteFilesSafely (#7299)

Log filesystem op failures, don't rename after write failed

refactor SaveContext handling, use std::lock_guard
This commit is contained in:
Philip Dubé
2026-10-03 18:10:19 +00:00
committed by GitHub
parent 4d7b885dbd
commit 9d81cb03cf
7 changed files with 255 additions and 267 deletions
+31 -31
View File
@@ -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,
@@ -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
@@ -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<RandomizerCheck> 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;
@@ -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<RandomizerHint> hints;
{
std::lock_guard<std::mutex> lock(readHintsMutex);
@@ -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());
}
+216 -218
View File
@@ -1,3 +1,16 @@
#include <ship/Context.h>
#include <fstream>
#include <filesystem>
#include <array>
#include <mutex>
#ifdef _WIN32
#include <io.h>
#elif !defined(__SWITCH__) && !defined(__WIIU__)
#include <unistd.h>
#endif
#include <spdlog/spdlog.h>
#include <libultraship/bridge/consolevariablebridge.h>
#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 <variables.h>
}
#define NOGDI // avoid various windows defines that conflict with things in z64.h
#include <spdlog/spdlog.h>
#include <ship/Context.h>
#include <fstream>
#include <filesystem>
#include <array>
#include <mutex>
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> 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<std::mutex> 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<std::mutex> 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<GameInteractor::OnSaveFile>(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<SaveContext>(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<std::mutex> 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);
}
+4 -13
View File
@@ -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<InitFunc> 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