diff --git a/soh/soh/Enhancements/Presets/Presets.cpp b/soh/soh/Enhancements/Presets/Presets.cpp index 267f093ab..7671ee18e 100644 --- a/soh/soh/Enhancements/Presets/Presets.cpp +++ b/soh/soh/Enhancements/Presets/Presets.cpp @@ -251,6 +251,7 @@ void SavePreset(std::string& presetName) { fs::create_directory(presetFolder); } presets[presetName].presetValues["presetName"] = presetName; + presets[presetName].presetValues["fileType"] = FILE_TYPE_PRESET; std::ofstream file( fmt::format("{}/{}.json", Ship::Context::GetInstance()->LocateFileAcrossAppDirs("presets"), presetName)); file << presets[presetName].presetValues.dump(4); diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index b3e71e8f0..85225e2b1 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -343,6 +343,7 @@ const char* SpoilerLog_Write() { jsonData.clear(); jsonData["version"] = (char*)gBuildVersion; + jsonData["fileType"] = FILE_TYPE_SPOILER; jsonData["git_branch"] = (char*)gGitBranch; jsonData["git_commit"] = (char*)gGitCommitHash; jsonData["seed"] = ctx->GetSeedString(); diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index c17ab0a54..ab6350758 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -156,6 +156,10 @@ SaveManager::SaveManager() { } void SaveManager::LoadRandomizer() { + if (gSaveContext.ship.quest.id != QUEST_RANDOMIZER) { + return; + } + auto randoContext = Rando::Context::GetInstance(); SaveManager::Instance->LoadArray("itemLocations", RC_MAX, [&](size_t i) { SaveManager::Instance->LoadStruct("", [&]() { @@ -255,9 +259,10 @@ void SaveManager::LoadRandomizer() { } void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool fullSave) { - - if (saveContext->ship.quest.id != QUEST_RANDOMIZER) + if (saveContext->ship.quest.id != QUEST_RANDOMIZER) { return; + } + auto randoContext = Rando::Context::GetInstance(); SaveManager::Instance->SaveArray("itemLocations", RC_MAX, [&](size_t i) { @@ -455,11 +460,122 @@ void SaveManager::Init() { // Load files to initialize metadata for (int fileNum = 0; fileNum < MaxFiles; fileNum++) { if (std::filesystem::exists(GetFileName(fileNum))) { - LoadFile(fileNum); - saveBlock = nlohmann::json::object(); - OTRGlobals::Instance->gRandoContext->ClearItemLocations(); + StartupCheckAndInitMeta(fileNum); } } + saveBlock = nlohmann::json::object(); +} + +void SaveManager::StartupCheckAndInitMeta(int fileNum) { + saveMtx.lock(); + SPDLOG_INFO("Init Meta - fileNum: {}", fileNum); + std::filesystem::path fileName = GetFileName(fileNum); + + std::ifstream input(fileName); + + bool deleteRando = false; + nlohmann::json metaSaveBlock = nlohmann::json::object(); + input >> metaSaveBlock; + input.close(); + saveMtx.unlock(); + if (!metaSaveBlock.contains("version")) { + SPDLOG_ERROR("Save at " + fileName.string() + " contains no version"); + assert(false); + return; + } + if (metaSaveBlock["sections"].contains("randomizer")) { + if (!metaSaveBlock.contains("fileType") || metaSaveBlock["fileType"] == FILE_TYPE_SAVE_VANILLA) { + SohGui::RegisterPopup( + "Loading old file", + "The file in slot " + std::to_string(fileNum + 1) + + " appears to contain randomizer data, but is a very old format or is empty.\n" + + "The randomizer data has been removed, and this file will be treated as a vanilla " + "file.\nIf this was a vanilla file, it still is, and you shouldn't see this " + "message again.\n" + + "If this was a randomizer file, the file will not work, and should be deleted."); + metaSaveBlock["sections"].erase(metaSaveBlock["sections"].find("randomizer")); + metaSaveBlock["fileType"] = FILE_TYPE_SAVE_VANILLA; + saveMtx.lock(); + std::ofstream output(GetFileName(fileNum)); + output << metaSaveBlock.dump(1); + output.close(); + saveMtx.unlock(); + } + s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + // block loading outdated rando save + if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { + std::string newFileName = + Ship::Context::GetPathRelativeToAppDirectory("Save") + + ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); +#if defined(__SWITCH__) || defined(__WIIU__) + copy_file(fileName.c_str(), newFileName.c_str()); + std::filesystem::remove(fileName); +#else + std::filesystem::rename(fileName, newFileName); +#endif + SohGui::RegisterPopup("Outdated Randomizer Save", + "The SoH version in the file in slot " + std::to_string(fileNum + 1) + + " does not match the currently running version.\n" + + "Non-matching rando saves are unsupported, and the file has been renamed to\n" + + " " + newFileName + "\n" + + "If this was not in error, the file should be deleted."); + return; + } + } + bool isRando = metaSaveBlock["fileType"] == FILE_TYPE_SAVE_RANDO; + + fileMetaInfo[fileNum].valid = true; + nlohmann::json& baseBlock = metaSaveBlock["sections"]["base"]["data"]; + fileMetaInfo[fileNum].deaths = baseBlock["deaths"]; + for (int i = 0; i < ARRAY_COUNT(fileMetaInfo[fileNum].playerName); i++) { + fileMetaInfo[fileNum].playerName[i] = baseBlock["playerName"][i]; + } + fileMetaInfo[fileNum].healthCapacity = baseBlock["healthCapacity"]; + fileMetaInfo[fileNum].questItems = baseBlock["inventory"]["questItems"]; + for (int i = 0; i < ARRAY_COUNT(fileMetaInfo[fileNum].inventoryItems); i++) { + fileMetaInfo[fileNum].inventoryItems[i] = baseBlock["inventory"]["items"][i]; + } + fileMetaInfo[fileNum].equipment = baseBlock["inventory"]["equipment"]; + fileMetaInfo[fileNum].upgrades = baseBlock["inventory"]["upgrades"]; + fileMetaInfo[fileNum].isMagicAcquired = baseBlock["isMagicAcquired"]; + fileMetaInfo[fileNum].isDoubleMagicAcquired = baseBlock["isDoubleMagicAcquired"]; + fileMetaInfo[fileNum].rupees = baseBlock["rupees"]; + fileMetaInfo[fileNum].gsTokens = baseBlock["inventory"]["gsTokens"]; + fileMetaInfo[fileNum].isDoubleDefenseAcquired = baseBlock["isDoubleDefenseAcquired"]; + fileMetaInfo[fileNum].gregFound = false; + fileMetaInfo[fileNum].filenameLanguage = baseBlock["filenameLanguage"]; + fileMetaInfo[fileNum].hasWallet = !isRando; + fileMetaInfo[fileNum].defense = baseBlock["inventory"]["defenseHearts"]; + fileMetaInfo[fileNum].health = baseBlock["health"]; + + fileMetaInfo[fileNum].requiresOriginal = !baseBlock["isMasterQuest"]; + fileMetaInfo[fileNum].requiresMasterQuest = baseBlock["isMasterQuest"]; + + fileMetaInfo[fileNum].randoSave = isRando; + if (isRando) { + nlohmann::json& randoBlock = metaSaveBlock["sections"]["randomizer"]["data"]; + + for (int i = 0; i < ARRAY_COUNT(fileMetaInfo[fileNum].seedHash); i++) { + fileMetaInfo[fileNum].seedHash[i] = randoBlock["seed"][i]; + } + fileMetaInfo[fileNum].gregFound = + (int16_t)baseBlock["randomizerInf"][RAND_INF_GREG_FOUND >> 4] & (1 << (RAND_INF_GREG_FOUND & 0xF)); + fileMetaInfo[fileNum].hasWallet = + (int16_t)baseBlock["randomizerInf"][RAND_INF_HAS_WALLET >> 4] & (1 << (RAND_INF_HAS_WALLET & 0xF)); + fileMetaInfo[fileNum].requiresMasterQuest = randoBlock["masterQuestDungeonCount"] > 0; + // If the file is not marked as Master Quest, it could still theoretically be a rando save with all 12 MQ + // dungeons, in which case we don't actually require a vanilla OTR. + fileMetaInfo[fileNum].requiresOriginal = randoBlock["masterQuestDungeonCount"] < 12; + } + + fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, + metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], + ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); } void SaveManager::InitMeta(int fileNum) { @@ -539,7 +655,6 @@ void SaveManager::InitFileNormal() { gSaveContext.ship.filenameLanguage = (gSaveContext.language == LANGUAGE_JPN) ? NAME_LANGUAGE_NTSC_JPN : NAME_LANGUAGE_NTSC_ENG; } - gSaveContext.n64ddFlag = 0; gSaveContext.healthCapacity = 0x30; gSaveContext.health = 0x30; gSaveContext.magicLevel = 0; @@ -715,7 +830,6 @@ void SaveManager::InitFileDebug() { gSaveContext.ship.filenameLanguage = (gSaveContext.language == LANGUAGE_JPN) ? NAME_LANGUAGE_NTSC_JPN : NAME_LANGUAGE_NTSC_ENG; } - gSaveContext.n64ddFlag = 0; gSaveContext.healthCapacity = 0xE0; gSaveContext.health = 0xE0; gSaveContext.magicLevel = 0; @@ -836,7 +950,6 @@ void SaveManager::InitFileMaxed() { gSaveContext.ship.filenameLanguage = (gSaveContext.language == LANGUAGE_JPN) ? NAME_LANGUAGE_NTSC_JPN : NAME_LANGUAGE_NTSC_ENG; } - gSaveContext.n64ddFlag = 0; gSaveContext.healthCapacity = 0x140; gSaveContext.health = 0x140; gSaveContext.magicLevel = 2; @@ -1004,6 +1117,11 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se SPDLOG_INFO("Save File - fileNum: {}", fileNum); // Needed for first time save, hasn't changed in forever anyway saveBlock["version"] = 1; + if (IS_RANDO) { + saveBlock["fileType"] = FILE_TYPE_SAVE_RANDO; + } else { + saveBlock["fileType"] = FILE_TYPE_SAVE_VANILLA; + } if (sectionID == SECTION_ID_BASE) { for (auto& sectionHandlerPair : sectionSaveHandlers) { auto& saveFuncInfo = sectionHandlerPair.second; @@ -1123,62 +1241,20 @@ void SaveManager::LoadFile(int fileNum) { std::ifstream input(fileName); try { - bool deleteRando = false; saveBlock = nlohmann::json::object(); input >> saveBlock; + input.close(); if (!saveBlock.contains("version")) { SPDLOG_ERROR("Save at " + fileName.string() + " contains no version"); assert(false); } + if (saveBlock.contains("fileType") && saveBlock["fileType"] == FILE_TYPE_SAVE_RANDO) { + gSaveContext.ship.quest.id = QUEST_RANDOMIZER; + } switch (saveBlock["version"].get()) { case 1: for (auto& block : saveBlock["sections"].items()) { - bool oldVanilla = - block.value()["data"].empty() || block.value()["data"].contains("aat0") || - block.value()["data"]["entrances"].empty() || - SohUtils::IsStringEmpty(saveBlock["sections"]["sohStats"]["data"]["buildVersion"]); std::string sectionName = block.key(); - if (sectionName == "randomizer") { - bool hasStats = saveBlock["sections"].contains("sohStats"); - if (oldVanilla || !hasStats) { // Vanilla "rando" data - SohGui::RegisterPopup( - "Loading old file", - "The file in slot " + std::to_string(fileNum + 1) + - " appears to contain randomizer data, but is a very old format or is empty.\n" + - "The randomizer data has been removed, and this file will be treated as a vanilla " - "file.\nIf this was a vanilla file, it still is, and you shouldn't see this " - "message again.\n" + - "If this was a randomizer file, the file will not work, and should be deleted."); - deleteRando = true; - continue; - } - s16 major = saveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - s16 minor = saveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - s16 patch = saveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - // block loading outdated rando save - if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && - patch == gBuildVersionPatch)) { - input.close(); - std::string newFileName = Ship::Context::GetPathRelativeToAppDirectory("Save") + - ("/file" + std::to_string(fileNum + 1) + "-" + - std::to_string(GetUnixTimestamp()) + ".bak"); -#if defined(__SWITCH__) || defined(__WIIU__) - copy_file(fileName.c_str(), newFileName.c_str()); - std::filesystem::remove(fileName); -#else - std::filesystem::rename(fileName, newFileName); -#endif - SohGui::RegisterPopup( - "Outdated Randomizer Save", - "The SoH version in the file in slot " + std::to_string(fileNum + 1) + - " does not match the currently running version.\n" + - "Non-matching rando saves are unsupported, and the file has been renamed to\n" + - " " + newFileName + "\n" + - "If this was not in error, the file should be deleted."); - saveMtx.unlock(); - return; - } - } int sectionVersion = block.value()["version"]; if (sectionName == "randomizer" && sectionVersion != 1) { sectionVersion = 1; @@ -1214,12 +1290,6 @@ void SaveManager::LoadFile(int fileNum) { assert(false); break; } - input.close(); - if (deleteRando) { - saveBlock["sections"].erase(saveBlock["sections"].find("randomizer")); - SaveFile(fileNum); - deleteRando = false; - } InitMeta(fileNum); GameInteractor::Instance->ExecuteHooks(fileNum); } catch (const std::exception& e) { @@ -1335,11 +1405,6 @@ void SaveManager::LoadBaseVersion1() { SaveManager::Instance->LoadData("deaths", gSaveContext.deaths); SaveManager::Instance->LoadArray("playerName", ARRAY_COUNT(gSaveContext.playerName), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.playerName[i]); }); - int isRando = 0; - SaveManager::Instance->LoadData("n64ddFlag", isRando); - if (isRando) { - gSaveContext.ship.quest.id = QUEST_RANDOMIZER; - } SaveManager::Instance->LoadData("healthCapacity", gSaveContext.healthCapacity); SaveManager::Instance->LoadData("health", gSaveContext.health); SaveManager::Instance->LoadData("magicLevel", gSaveContext.magicLevel); @@ -1479,11 +1544,6 @@ void SaveManager::LoadBaseVersion2() { SaveManager::Instance->LoadData("deaths", gSaveContext.deaths); SaveManager::Instance->LoadArray("playerName", ARRAY_COUNT(gSaveContext.playerName), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.playerName[i]); }); - int isRando = 0; - SaveManager::Instance->LoadData("n64ddFlag", isRando); - if (isRando) { - gSaveContext.ship.quest.id = QUEST_RANDOMIZER; - } SaveManager::Instance->LoadData("healthCapacity", gSaveContext.healthCapacity); SaveManager::Instance->LoadData("health", gSaveContext.health); SaveManager::Instance->LoadData("magicLevel", gSaveContext.magicLevel); @@ -1695,11 +1755,6 @@ void SaveManager::LoadBaseVersion3() { SaveManager::Instance->LoadData("deaths", gSaveContext.deaths); SaveManager::Instance->LoadArray("playerName", ARRAY_COUNT(gSaveContext.playerName), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.playerName[i]); }); - int isRando = 0; - SaveManager::Instance->LoadData("n64ddFlag", isRando); - if (isRando) { - gSaveContext.ship.quest.id = QUEST_RANDOMIZER; - } SaveManager::Instance->LoadData("healthCapacity", gSaveContext.healthCapacity); SaveManager::Instance->LoadData("health", gSaveContext.health); SaveManager::Instance->LoadData("magicLevel", gSaveContext.magicLevel); @@ -1915,11 +1970,6 @@ void SaveManager::LoadBaseVersion4() { SaveManager::Instance->LoadData("deaths", gSaveContext.deaths); SaveManager::Instance->LoadArray("playerName", ARRAY_COUNT(gSaveContext.playerName), [](size_t i) { SaveManager::Instance->LoadData("", gSaveContext.playerName[i]); }); - int isRando = 0; - SaveManager::Instance->LoadData("n64ddFlag", isRando); - if (isRando) { - gSaveContext.ship.quest.id = QUEST_RANDOMIZER; - } SaveManager::Instance->LoadData("healthCapacity", gSaveContext.healthCapacity); SaveManager::Instance->LoadData("health", gSaveContext.health); SaveManager::Instance->LoadData("magicLevel", gSaveContext.magicLevel); @@ -2098,7 +2148,6 @@ void SaveManager::SaveBase(SaveContext* saveContext, int sectionID, bool fullSav SaveManager::Instance->SaveArray("playerName", ARRAY_COUNT(saveContext->playerName), [&](size_t i) { SaveManager::Instance->SaveData("", saveContext->playerName[i]); }); - SaveManager::Instance->SaveData("n64ddFlag", saveContext->ship.quest.id == QUEST_RANDOMIZER); SaveManager::Instance->SaveData("healthCapacity", saveContext->healthCapacity); SaveManager::Instance->SaveData("health", saveContext->health); SaveManager::Instance->SaveData("magicLevel", saveContext->magicLevel); diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 6d7ef3b33..42c608844 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -161,6 +161,7 @@ class SaveManager { void SaveFileThreaded(int fileNum, SaveContext* saveContext, int sectionID); void InitMeta(int slotNum); + void StartupCheckAndInitMeta(int slotNum); static void InitFileImpl(bool isDebug); static void InitFileNormal(); static void InitFileDebug(); diff --git a/soh/soh/util.h b/soh/soh/util.h index 9a31d4be4..f704076b4 100644 --- a/soh/soh/util.h +++ b/soh/soh/util.h @@ -2,6 +2,8 @@ #include #include +typedef enum FileType { FILE_TYPE_SAVE_VANILLA, FILE_TYPE_SAVE_RANDO, FILE_TYPE_PRESET, FILE_TYPE_SPOILER } FileType; + namespace SohUtils { const std::string& GetSceneName(int32_t scene);