From ae257f15a24b84317b84ce0f5d19d246f641a41f Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Mon, 15 Jan 2024 05:17:26 +0000 Subject: [PATCH] Add support for json save files (#54) --- .gitignore | 1 + mm/2s2h/BenJsonConversions.hpp | 330 ++++++++++++++++++ mm/2s2h/BenPort.cpp | 192 ++++++++++ mm/2s2h/BenPort.h | 2 + mm/CMakeLists.txt | 2 +- mm/include/z64save.h | 2 +- mm/src/code/stubs.c | 3 + mm/src/code/sys_flashrom.c | 53 +-- mm/src/code/title_setup.c | 3 +- mm/src/code/z_sram_NES.c | 6 +- .../overlays/gamestates/ovl_title/z_title.c | 3 - 11 files changed, 569 insertions(+), 28 deletions(-) create mode 100644 mm/2s2h/BenJsonConversions.hpp diff --git a/.gitignore b/.gitignore index 818925571..f750e6eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ logs *.nix shipofharkinian.json +*.sav .envrc imgui.ini diff --git a/mm/2s2h/BenJsonConversions.hpp b/mm/2s2h/BenJsonConversions.hpp new file mode 100644 index 000000000..57c044c5f --- /dev/null +++ b/mm/2s2h/BenJsonConversions.hpp @@ -0,0 +1,330 @@ +#ifndef BenJsonConversions_hpp +#define BenJsonConversions_hpp + +#include "z64.h" +#include + +using json = nlohmann::json; + +void to_json(json& j, const ItemEquips& itemEquips) { + j = json{ + { "buttonItems", itemEquips.buttonItems }, + { "cButtonSlots", itemEquips.cButtonSlots }, + { "equipment", itemEquips.equipment }, + }; +} + +void from_json(const json& j, ItemEquips& itemEquips) { + j.at("equipment").get_to(itemEquips.equipment); + // buttonItems and cButtonSlots are arrays of arrays, so we need to manually parse them + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + itemEquips.buttonItems[x][y] = j.at("buttonItems")[x][y].get(); + itemEquips.cButtonSlots[x][y] = j.at("cButtonSlots")[x][y].get(); + } + } +} + +void to_json(json& j, const Inventory& inventory) { + j = json{ + { "items", inventory.items }, + { "ammo", inventory.ammo }, + { "upgrades", inventory.upgrades }, + { "questItems", inventory.questItems }, + { "dungeonItems", inventory.dungeonItems }, + { "dungeonKeys", inventory.dungeonKeys }, + { "defenseHearts", inventory.defenseHearts }, + { "strayFairies", inventory.strayFairies }, + { "dekuPlaygroundPlayerName", inventory.dekuPlaygroundPlayerName }, + }; +} + +void from_json(const json& j, Inventory& inventory) { + j.at("items").get_to(inventory.items); + j.at("ammo").get_to(inventory.ammo); + j.at("upgrades").get_to(inventory.upgrades); + j.at("questItems").get_to(inventory.questItems); + j.at("dungeonItems").get_to(inventory.dungeonItems); + j.at("dungeonKeys").get_to(inventory.dungeonKeys); + j.at("defenseHearts").get_to(inventory.defenseHearts); + j.at("strayFairies").get_to(inventory.strayFairies); + // dekuPlaygroundPlayerName is an array of char arrays, so we need to manually parse it + for (int i = 0; i < 3; i++) { + std::string name = j.at("dekuPlaygroundPlayerName")[i].get(); + for (int j = 0; j < 8; j++) { + inventory.dekuPlaygroundPlayerName[i][j] = name[j]; + } + } +} + +void to_json(json& j, const PermanentSceneFlags& permanentSceneFlags) { + j = json{ + { "chest", permanentSceneFlags.chest }, + { "switch0", permanentSceneFlags.switch0 }, + { "switch1", permanentSceneFlags.switch1 }, + { "clearedRoom", permanentSceneFlags.clearedRoom }, + { "collectible", permanentSceneFlags.collectible }, + { "unk_14", permanentSceneFlags.unk_14 }, + { "rooms", permanentSceneFlags.rooms }, + }; +} + +void from_json(const json& j, PermanentSceneFlags& permanentSceneFlags) { + j.at("chest").get_to(permanentSceneFlags.chest); + j.at("switch0").get_to(permanentSceneFlags.switch0); + j.at("switch1").get_to(permanentSceneFlags.switch1); + j.at("clearedRoom").get_to(permanentSceneFlags.clearedRoom); + j.at("collectible").get_to(permanentSceneFlags.collectible); + j.at("unk_14").get_to(permanentSceneFlags.unk_14); + j.at("rooms").get_to(permanentSceneFlags.rooms); +} + +void to_json(json& j, const SavePlayerData& savePlayerData) { + j = json{ + { "newf", savePlayerData.newf }, + { "threeDayResetCount", savePlayerData.threeDayResetCount }, + { "playerName", savePlayerData.playerName }, + { "healthCapacity", savePlayerData.healthCapacity }, + { "health", savePlayerData.health }, + { "magicLevel", savePlayerData.magicLevel }, + { "magic", savePlayerData.magic }, + { "rupees", savePlayerData.rupees }, + { "swordHealth", savePlayerData.swordHealth }, + { "tatlTimer", savePlayerData.tatlTimer }, + { "isMagicAcquired", savePlayerData.isMagicAcquired }, + { "isDoubleMagicAcquired", savePlayerData.isDoubleMagicAcquired }, + { "doubleDefense", savePlayerData.doubleDefense }, + { "unk_1F", savePlayerData.unk_1F }, + { "unk_20", savePlayerData.unk_20 }, + { "owlActivationFlags", savePlayerData.owlActivationFlags }, + { "unk_24", savePlayerData.unk_24 }, + { "savedSceneId", savePlayerData.savedSceneId }, + }; +} + +void from_json(const json& j, SavePlayerData& savePlayerData) { + // newf is an array of chars, so we need to manually parse it + std::string newf = j.at("newf").get(); + for (int i = 0; i < 6; i++) { + savePlayerData.newf[i] = newf[i]; + } + j.at("threeDayResetCount").get_to(savePlayerData.threeDayResetCount); + std::string playerName = j.at("playerName").get(); + for (int i = 0; i < 8; i++) { + savePlayerData.playerName[i] = playerName[i]; + } + j.at("healthCapacity").get_to(savePlayerData.healthCapacity); + j.at("health").get_to(savePlayerData.health); + j.at("magicLevel").get_to(savePlayerData.magicLevel); + j.at("magic").get_to(savePlayerData.magic); + j.at("rupees").get_to(savePlayerData.rupees); + j.at("swordHealth").get_to(savePlayerData.swordHealth); + j.at("tatlTimer").get_to(savePlayerData.tatlTimer); + j.at("isMagicAcquired").get_to(savePlayerData.isMagicAcquired); + j.at("isDoubleMagicAcquired").get_to(savePlayerData.isDoubleMagicAcquired); + j.at("doubleDefense").get_to(savePlayerData.doubleDefense); + j.at("unk_1F").get_to(savePlayerData.unk_1F); + j.at("unk_20").get_to(savePlayerData.unk_20); + j.at("owlActivationFlags").get_to(savePlayerData.owlActivationFlags); + j.at("unk_24").get_to(savePlayerData.unk_24); + j.at("savedSceneId").get_to(savePlayerData.savedSceneId); +} + +void to_json(json& j, const Vec3s& vec) { + j = json{ + { "x", vec.x }, + { "y", vec.y }, + { "z", vec.z }, + }; +} + +void from_json(const json& j, Vec3s& vec) { + j.at("x").get_to(vec.x); + j.at("y").get_to(vec.y); + j.at("z").get_to(vec.z); +} + +void to_json(json& j, const HorseData& horseData) { + j = json{ + { "sceneId", horseData.sceneId }, + { "pos", horseData.pos }, + { "yaw", horseData.yaw }, + }; +} + +void from_json(const json& j, HorseData& horseData) { + j.at("sceneId").get_to(horseData.sceneId); + j.at("pos").get_to(horseData.pos); + j.at("yaw").get_to(horseData.yaw); +} + +void to_json(json& j, const SaveInfo& saveInfo) { + j = json{ + { "playerData", saveInfo.playerData }, + { "equips", saveInfo.equips }, + { "inventory", saveInfo.inventory }, + { "permanentSceneFlags", saveInfo.permanentSceneFlags }, + { "unk_DF4", saveInfo.unk_DF4 }, + { "dekuPlaygroundHighScores", saveInfo.dekuPlaygroundHighScores }, + { "pictoFlags0", saveInfo.pictoFlags0 }, + { "pictoFlags1", saveInfo.pictoFlags1 }, + { "unk_E5C", saveInfo.unk_E5C }, + { "unk_E60", saveInfo.unk_E60 }, + { "unk_E64", saveInfo.unk_E64 }, + { "scenesVisible", saveInfo.scenesVisible }, + { "skullTokenCount", saveInfo.skullTokenCount }, + { "unk_EA0", saveInfo.unk_EA0 }, + { "unk_EA4", saveInfo.unk_EA4 }, + { "unk_EA8", saveInfo.unk_EA8 }, + { "stolenItems", saveInfo.stolenItems }, + { "unk_EB4", saveInfo.unk_EB4 }, + { "highScores", saveInfo.highScores }, + { "weekEventReg", saveInfo.weekEventReg }, + { "regionsVisited", saveInfo.regionsVisited }, + { "worldMapCloudVisibility", saveInfo.worldMapCloudVisibility }, + { "unk_F40", saveInfo.unk_F40 }, + { "scarecrowSpawnSongSet", saveInfo.scarecrowSpawnSongSet }, + { "scarecrowSpawnSong", saveInfo.scarecrowSpawnSong }, + { "bombersCaughtNum", saveInfo.bombersCaughtNum }, + { "bombersCaughtOrder", saveInfo.bombersCaughtOrder }, + { "lotteryCodes", saveInfo.lotteryCodes }, + { "spiderHouseMaskOrder", saveInfo.spiderHouseMaskOrder }, + { "bomberCode", saveInfo.bomberCode }, + { "horseData", saveInfo.horseData }, + { "checksum", saveInfo.checksum }, + }; +} + +void from_json(const json& j, SaveInfo& saveInfo) { + j.at("playerData").get_to(saveInfo.playerData); + j.at("equips").get_to(saveInfo.equips); + j.at("inventory").get_to(saveInfo.inventory); + j.at("permanentSceneFlags").get_to(saveInfo.permanentSceneFlags); + j.at("unk_DF4").get_to(saveInfo.unk_DF4); + j.at("dekuPlaygroundHighScores").get_to(saveInfo.dekuPlaygroundHighScores); + j.at("pictoFlags0").get_to(saveInfo.pictoFlags0); + j.at("pictoFlags1").get_to(saveInfo.pictoFlags1); + j.at("unk_E5C").get_to(saveInfo.unk_E5C); + j.at("unk_E60").get_to(saveInfo.unk_E60); + j.at("unk_E64").get_to(saveInfo.unk_E64); + j.at("scenesVisible").get_to(saveInfo.scenesVisible); + j.at("skullTokenCount").get_to(saveInfo.skullTokenCount); + j.at("unk_EA0").get_to(saveInfo.unk_EA0); + j.at("unk_EA4").get_to(saveInfo.unk_EA4); + j.at("unk_EA8").get_to(saveInfo.unk_EA8); + j.at("stolenItems").get_to(saveInfo.stolenItems); + j.at("unk_EB4").get_to(saveInfo.unk_EB4); + j.at("highScores").get_to(saveInfo.highScores); + j.at("weekEventReg").get_to(saveInfo.weekEventReg); + j.at("regionsVisited").get_to(saveInfo.regionsVisited); + j.at("worldMapCloudVisibility").get_to(saveInfo.worldMapCloudVisibility); + j.at("unk_F40").get_to(saveInfo.unk_F40); + j.at("scarecrowSpawnSongSet").get_to(saveInfo.scarecrowSpawnSongSet); + j.at("scarecrowSpawnSong").get_to(saveInfo.scarecrowSpawnSong); + j.at("bombersCaughtNum").get_to(saveInfo.bombersCaughtNum); + j.at("bombersCaughtOrder").get_to(saveInfo.bombersCaughtOrder); + // lotteryCodes is an array of arrays, so we need to manually parse it + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 3; y++) { + saveInfo.lotteryCodes[x][y] = j.at("lotteryCodes")[x][y].get(); + } + } + j.at("spiderHouseMaskOrder").get_to(saveInfo.spiderHouseMaskOrder); + j.at("bomberCode").get_to(saveInfo.bomberCode); + j.at("horseData").get_to(saveInfo.horseData); + j.at("checksum").get_to(saveInfo.checksum); +} + +void to_json(json& j, const Save& save) { + j = json{ + { "entrance", save.entrance }, + { "equippedMask", save.equippedMask }, + { "isFirstCycle", save.isFirstCycle }, + { "unk_06", save.unk_06 }, + { "linkAge", save.linkAge }, + { "cutsceneIndex", save.cutsceneIndex }, + { "time", save.time }, + { "owlSaveLocation", save.owlSaveLocation }, + { "isNight", save.isNight }, + { "timeSpeedOffset", save.timeSpeedOffset }, + { "day", save.day }, + { "eventDayCount", save.eventDayCount }, + { "playerForm", save.playerForm }, + { "snowheadCleared", save.snowheadCleared }, + { "hasTatl", save.hasTatl }, + { "isOwlSave", save.isOwlSave }, + { "saveInfo", save.saveInfo }, + }; +} + +void from_json(const json& j, Save& save) { + j.at("entrance").get_to(save.entrance); + j.at("equippedMask").get_to(save.equippedMask); + j.at("isFirstCycle").get_to(save.isFirstCycle); + j.at("unk_06").get_to(save.unk_06); + j.at("linkAge").get_to(save.linkAge); + j.at("cutsceneIndex").get_to(save.cutsceneIndex); + j.at("time").get_to(save.time); + j.at("owlSaveLocation").get_to(save.owlSaveLocation); + j.at("isNight").get_to(save.isNight); + j.at("timeSpeedOffset").get_to(save.timeSpeedOffset); + j.at("day").get_to(save.day); + j.at("eventDayCount").get_to(save.eventDayCount); + j.at("playerForm").get_to(save.playerForm); + j.at("snowheadCleared").get_to(save.snowheadCleared); + j.at("hasTatl").get_to(save.hasTatl); + j.at("isOwlSave").get_to(save.isOwlSave); + j.at("saveInfo").get_to(save.saveInfo); +} + +void to_json(json& j, const SaveContext& saveContext) { + j = json{ + { "save", saveContext.save }, + { "eventInf", saveContext.eventInf }, + { "unk_1014", saveContext.unk_1014 }, + { "bButtonStatus", saveContext.bButtonStatus }, + { "jinxTimer", saveContext.jinxTimer }, + { "rupeeAccumulator", saveContext.rupeeAccumulator }, + { "bottleTimerStates", saveContext.bottleTimerStates }, + { "bottleTimerStartOsTimes", saveContext.bottleTimerStartOsTimes }, + { "bottleTimerTimeLimits", saveContext.bottleTimerTimeLimits }, + { "bottleTimerCurTimes", saveContext.bottleTimerCurTimes }, + { "bottleTimerPausedOsTimes", saveContext.bottleTimerPausedOsTimes }, + { "pictoPhotoI5", saveContext.pictoPhotoI5 }, + }; +} + +void from_json(const json& j, SaveContext& saveContext) { + j.at("save").get_to(saveContext.save); + j.at("eventInf").get_to(saveContext.eventInf); + j.at("unk_1014").get_to(saveContext.unk_1014); + j.at("bButtonStatus").get_to(saveContext.bButtonStatus); + j.at("jinxTimer").get_to(saveContext.jinxTimer); + j.at("rupeeAccumulator").get_to(saveContext.rupeeAccumulator); + j.at("bottleTimerStates").get_to(saveContext.bottleTimerStates); + j.at("bottleTimerStartOsTimes").get_to(saveContext.bottleTimerStartOsTimes); + j.at("bottleTimerTimeLimits").get_to(saveContext.bottleTimerTimeLimits); + j.at("bottleTimerCurTimes").get_to(saveContext.bottleTimerCurTimes); + j.at("bottleTimerPausedOsTimes").get_to(saveContext.bottleTimerPausedOsTimes); + j.at("pictoPhotoI5").get_to(saveContext.pictoPhotoI5); +} + +void to_json(json& j, const SaveOptions& saveOptions) { + j = json{ + { "optionId", saveOptions.optionId }, + { "language", saveOptions.language }, + { "audioSetting", saveOptions.audioSetting }, + { "languageSetting", saveOptions.languageSetting }, + { "zTargetSetting", saveOptions.zTargetSetting }, + }; +} + +void from_json(const json& j, SaveOptions& saveOptions) { + j.at("optionId").get_to(saveOptions.optionId); + j.at("language").get_to(saveOptions.language); + j.at("audioSetting").get_to(saveOptions.audioSetting); + j.at("languageSetting").get_to(saveOptions.languageSetting); + j.at("zTargetSetting").get_to(saveOptions.zTargetSetting); +} + +#endif // BenJsonConversions_hpp diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 1c03aa311..7dc32b298 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -30,6 +30,7 @@ #include "z64.h" #include "macros.h" #include +#include #include #include @@ -51,6 +52,7 @@ CrowdControl* CrowdControl::Instance; #include #include +#include "BenJsonConversions.hpp" #include "Enhancements/controls/SohInputEditorWindow.h" @@ -1384,3 +1386,193 @@ extern "C" int Controller_ShouldRumble(size_t slot) { return 0; } + +// This entire thing is temporary until we have a more robust save system that +// supports backwards compatability, migrations, threaded saving, save sections, etc. +typedef enum FlashSlotFile { + /* -1 */ FLASH_SLOT_FILE_UNAVAILABLE = -1, + /* 0 */ FLASH_SLOT_FILE_1_NEW_CYCLE, + /* 1 */ FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP, + /* 2 */ FLASH_SLOT_FILE_2_NEW_CYCLE, + /* 3 */ FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP, + /* 4 */ FLASH_SLOT_FILE_1_OWL_SAVE, + /* 5 */ FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP, + /* 6 */ FLASH_SLOT_FILE_2_OWL_SAVE, + /* 7 */ FLASH_SLOT_FILE_2_OWL_SAVE_BACKUP, + /* 8 */ FLASH_SLOT_FILE_SRAM_HEADER, + /* 9 */ FLASH_SLOT_FILE_SRAM_HEADER_BACKUP, +} FlashSlotFile; + +#define GET_NEWF(save, index) (save.saveInfo.playerData.newf[index]) +#define IS_VALID_FILE(save) \ + ((GET_NEWF(save, 0) == 'Z') && (GET_NEWF(save, 1) == 'E') && \ + (GET_NEWF(save, 2) == 'L') && (GET_NEWF(save, 3) == 'D') && \ + (GET_NEWF(save, 4) == 'A') && (GET_NEWF(save, 5) == '3')) + +const std::filesystem::path savesFolderPath(LUS::Context::GetPathRelativeToAppDirectory("Save")); + +void WriteSaveFile(std::filesystem::path fileName, nlohmann::json j) { + const std::filesystem::path filePath = savesFolderPath / fileName; + + if (!std::filesystem::exists(savesFolderPath)) { + std::filesystem::create_directory(savesFolderPath); + } + + std::ofstream o(filePath); + o << std::setw(4) << j << std::endl; + o.close(); +} + +void DeleteSaveFile(std::filesystem::path fileName) { + const std::filesystem::path filePath = savesFolderPath / fileName; + + if (std::filesystem::exists(filePath)) { + std::filesystem::remove(filePath); + } +} + +int ReadSaveFile(std::filesystem::path fileName, nlohmann::json& j) { + const std::filesystem::path filePath = savesFolderPath / fileName; + + if (!std::filesystem::exists(filePath)) { + return -1; + } + + std::ifstream i(filePath); + i >> j; + i.close(); + return 0; +} + +extern "C" void BenSysFlashrom_WriteData(u8* saveBuffer, u32 pageNum, u32 pageCount) { + FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE; + bool isBackup = false; + for (u32 i = 0; i < ARRAY_COUNT(gFlashSaveStartPages) - 1; i++) { + if (pageNum == gFlashSaveStartPages[i]) { + flashSlotFile = static_cast(i); + break; + } + } + + if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) { + return; + } + + switch (flashSlotFile) { + case FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP: + case FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP: + isBackup = true; + // fallthrough + case FLASH_SLOT_FILE_1_NEW_CYCLE: + case FLASH_SLOT_FILE_2_NEW_CYCLE: { + Save save; + memcpy(&save, saveBuffer, sizeof(Save)); + + std::string fileName = "save_" + std::to_string(flashSlotFile) + ".sav"; + if (isBackup) fileName += ".bak"; + + if (IS_VALID_FILE(save)) { + WriteSaveFile(fileName, save); + } else { + DeleteSaveFile(fileName); + } + break; + } + case FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP: + case FLASH_SLOT_FILE_2_OWL_SAVE_BACKUP: + isBackup = true; + // fallthrough + case FLASH_SLOT_FILE_1_OWL_SAVE: + case FLASH_SLOT_FILE_2_OWL_SAVE: { + SaveContext saveContext; + memcpy(&saveContext, saveBuffer, sizeof(SaveContext)); + + std::string fileName = "save_" + std::to_string(flashSlotFile) + ".sav"; + if (isBackup) fileName += ".bak"; + + if (IS_VALID_FILE(saveContext.save)) { + WriteSaveFile(fileName, saveContext); + } else { + DeleteSaveFile(fileName); + } + break; + } + case FLASH_SLOT_FILE_SRAM_HEADER_BACKUP: + case FLASH_SLOT_FILE_SRAM_HEADER: { + SaveOptions saveOptions; + memcpy(&saveOptions, saveBuffer, sizeof(SaveOptions)); + + std::string fileName = "global.sav"; + WriteSaveFile(fileName, saveOptions); + break; + } + } +} + +extern "C" s32 BenSysFlashrom_ReadData(void* saveBuffer, u32 pageNum, u32 pageCount) { + FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE; + bool isBackup = false; + for (u32 i = 0; i < ARRAY_COUNT(gFlashSaveStartPages) - 1; i++) { + if (pageNum == gFlashSaveStartPages[i]) { + flashSlotFile = static_cast(i); + break; + } + } + + if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) { + return -1; + } + + switch (flashSlotFile) { + case FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP: + case FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP: + isBackup = true; + // fallthrough + case FLASH_SLOT_FILE_1_NEW_CYCLE: + case FLASH_SLOT_FILE_2_NEW_CYCLE: { + std::string fileName = "save_" + std::to_string(flashSlotFile) + ".sav"; + if (isBackup) fileName += ".bak"; + + nlohmann::json j; + int result = ReadSaveFile(fileName, j); + if (result != 0) return result; + + Save save = j; + + memcpy(saveBuffer, &save, sizeof(Save)); + return result; + } + case FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP: + case FLASH_SLOT_FILE_2_OWL_SAVE_BACKUP: + isBackup = true; + // fallthrough + case FLASH_SLOT_FILE_1_OWL_SAVE: + case FLASH_SLOT_FILE_2_OWL_SAVE: { + std::string fileName = "save_" + std::to_string(flashSlotFile) + ".sav"; + if (isBackup) fileName += ".bak"; + + nlohmann::json j; + int result = ReadSaveFile(fileName, j); + if (result != 0) return result; + + SaveContext saveContext = j; + + memcpy(saveBuffer, &saveContext, sizeof(SaveContext)); + return result; + } + case FLASH_SLOT_FILE_SRAM_HEADER: + case FLASH_SLOT_FILE_SRAM_HEADER_BACKUP: { + std::string fileName = "global.sav"; + + nlohmann::json j; + int result = ReadSaveFile(fileName, j); + if (result != 0) return result; + + SaveOptions saveOptions = j; + + memcpy(saveBuffer, &saveOptions, sizeof(SaveOptions)); + return 0; + break; + } + } +} diff --git a/mm/2s2h/BenPort.h b/mm/2s2h/BenPort.h index 3100786b7..f580dae25 100644 --- a/mm/2s2h/BenPort.h +++ b/mm/2s2h/BenPort.h @@ -125,6 +125,8 @@ void Overlay_DisplayText_Seconds(int seconds, const char* text); void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); void CheckTracker_OnMessageClose(); +void BenSysFlashrom_WriteData(u8* addr, u32 pageNum, u32 pageCount); +s32 BenSysFlashrom_ReadData(void* addr, u32 pageNum, u32 pageCount); int32_t GetGIID(uint32_t itemID); #endif diff --git a/mm/CMakeLists.txt b/mm/CMakeLists.txt index cf8abe628..55787a978 100644 --- a/mm/CMakeLists.txt +++ b/mm/CMakeLists.txt @@ -124,7 +124,7 @@ source_group("include" FILES ${Header_Files__include}) # }}} # m (root) -file(GLOB soh__ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "2s2h/*.c" "2s2h/*.cpp" "2s2h/*.h") +file(GLOB soh__ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "2s2h/*.c" "2s2h/*.cpp" "2s2h/*.h" "2s2h/*.hpp") source_group("2s2h" FILES ${soh__}) file(GLOB_RECURSE libultra_headers RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/mm/include/z64save.h b/mm/include/z64save.h index b0766b415..865b0650a 100644 --- a/mm/include/z64save.h +++ b/mm/include/z64save.h @@ -1685,7 +1685,7 @@ void Sram_UpdateWriteToFlashOwlSave(SramContext* sramCtx); extern u32 gSramSlotOffsets[]; extern u8 gAmmoItems[]; -extern s32 gFlashSaveStartPages[]; +extern s32 gFlashSaveStartPages[10]; extern s32 gFlashSaveNumPages[]; extern s32 gFlashSpecialSaveNumPages[]; extern s32 gFlashOwlSaveStartPages[]; diff --git a/mm/src/code/stubs.c b/mm/src/code/stubs.c index 62512c8c5..7f3733603 100644 --- a/mm/src/code/stubs.c +++ b/mm/src/code/stubs.c @@ -386,6 +386,9 @@ void* osViGetCurrentFramebuffer(void) { OSPiHandle* osFlashInit(void) { } void osFlashReadId(u32* t, u32* v) { + // We're faking these so the flashrom system will continue to work + *t = 0x11118001; // FLASH_TYPE_MAGIC + *v = 0x00C20000; // FLASH_VERSION_MX_PROTO_A } s32 osFlashSectorErase(u32 page) { } diff --git a/mm/src/code/sys_flashrom.c b/mm/src/code/sys_flashrom.c index c3eab0620..f15186acb 100644 --- a/mm/src/code/sys_flashrom.c +++ b/mm/src/code/sys_flashrom.c @@ -7,6 +7,7 @@ #include "z64thread.h" #include "sys_flashrom.h" #include "PR/os_internal_flash.h" +#include "BenPort.h" OSMesgQueue sFlashromMesgQueue; OSMesg sFlashromMesg[1]; @@ -78,6 +79,10 @@ s32 SysFlashrom_InitFlash(void) { } s32 SysFlashrom_ReadData(void* addr, u32 pageNum, u32 pageCount) { + // #region 2S2H [Port] Redirect to our own read function + return BenSysFlashrom_ReadData(addr, pageNum, pageCount); + // #endregion + OSIoMesg msg; if (!SysFlashrom_IsInit()) { @@ -201,25 +206,27 @@ s32 SysFlashrom_WriteData(void* addr, u32 pageNum, u32 pageCount) { } void SysFlashrom_ThreadEntry(void* arg) { - #if 0 FlashromRequest* req = (FlashromRequest*)arg; switch (req->requestType) { case FLASHROM_REQUEST_WRITE: req->response = SysFlashrom_WriteData(req->addr, req->pageNum, req->pageCount); - osSendMesg(&req->messageQueue, (OSMesg)req->response, OS_MESG_BLOCK); + osSendMesg(&req->messageQueue, OS_MESG_32(req->response), OS_MESG_BLOCK); break; case FLASHROM_REQUEST_READ: req->response = SysFlashrom_ReadData(req->addr, req->pageNum, req->pageCount); - osSendMesg(&req->messageQueue, (OSMesg)req->response, OS_MESG_BLOCK); + osSendMesg(&req->messageQueue, OS_MESG_32(req->response), OS_MESG_BLOCK); break; } - #endif } void SysFlashrom_WriteDataAsync(u8* addr, u32 pageNum, u32 pageCount) { - #if 0 + // #region 2S2H [Port] Redirect to our own write function + BenSysFlashrom_WriteData(addr, pageNum, pageCount); + return; + // #endregion + FlashromRequest* req = &sFlashromRequest; if (SysFlashrom_IsInit()) { req->requestType = FLASHROM_REQUEST_WRITE; @@ -233,28 +240,34 @@ void SysFlashrom_WriteDataAsync(u8* addr, u32 pageNum, u32 pageCount) { STACK_TOP(sSysFlashromStack), Z_PRIORITY_FLASHROM); osStartThread(&sSysFlashromThread); } - #endif } s32 SysFlashrom_IsBusy(void) { - return 0; - //OSMesgQueue* queue = &sFlashromRequest.messageQueue; - // - //if (!SysFlashrom_IsInit()) { - // return -1; - //} - //return MQ_IS_FULL(queue); + // #region 2S2H [Port] This is only checked in 3 places in which we always want to tell it we've "started working" + // Eventually we should probably have this call into BenPort and check if a save is in progress + return 1; + // #endregion + + OSMesgQueue* queue = &sFlashromRequest.messageQueue; + + if (!SysFlashrom_IsInit()) { + return -1; + } + return MQ_IS_FULL(queue); } s32 SysFlashrom_AwaitResult(void) { + // #region 2S2H [Port] Currently all of our save writes/reads are synchronous, so we don't need to wait for anything return 0; - //if (!SysFlashrom_IsInit()) { - // return -1; - //} - //osRecvMesg(&sFlashromRequest.messageQueue, NULL, OS_MESG_BLOCK); - //osDestroyThread(&sSysFlashromThread); - //StackCheck_Cleanup(&sSysFlashromStackInfo); - //return sFlashromRequest.response; + // #endregion + + if (!SysFlashrom_IsInit()) { + return -1; + } + osRecvMesg(&sFlashromRequest.messageQueue, NULL, OS_MESG_BLOCK); + osDestroyThread(&sSysFlashromThread); + StackCheck_Cleanup(&sSysFlashromStackInfo); + return sFlashromRequest.response; } void SysFlashrom_WriteDataSync(void* addr, u32 pageNum, u32 pageCount) { diff --git a/mm/src/code/title_setup.c b/mm/src/code/title_setup.c index 0ff2cb0df..8330df574 100644 --- a/mm/src/code/title_setup.c +++ b/mm/src/code/title_setup.c @@ -49,8 +49,7 @@ void Setup_SetRegs(void) { void Setup_InitImpl(SetupState* this) { SysFlashrom_InitFlash(); - // BENTODO: this doesn't crash but was stubbed in minibuild? probably just for debug purposes - // SaveContext_Init(); + SaveContext_Init(); Setup_SetRegs(); STOP_GAMESTATE(&this->state); diff --git a/mm/src/code/z_sram_NES.c b/mm/src/code/z_sram_NES.c index cd2681d20..4e2cae8ac 100644 --- a/mm/src/code/z_sram_NES.c +++ b/mm/src/code/z_sram_NES.c @@ -300,7 +300,7 @@ u8 gAmmoItems[ITEM_NUM_SLOTS] = { }; // Stores flash start page number -s32 gFlashSaveStartPages[] = { +s32 gFlashSaveStartPages[10] = { 0, // File 1 New Cycle Save 0x40, // File 1 New Cycle Save Backup 0x80, // File 2 New Cycle Save @@ -667,6 +667,10 @@ void Sram_IncrementDay(void) { } u16 Sram_CalcChecksum(void* data, size_t count) { + // #region 2S2H [Port] I'm not really sure what this is doing or how to port it, for now always return the same + return 1; + // #endregion + u8* dataPtr = data; u16 chkSum = 0; diff --git a/mm/src/overlays/gamestates/ovl_title/z_title.c b/mm/src/overlays/gamestates/ovl_title/z_title.c index 52b8034a7..16223bb1c 100644 --- a/mm/src/overlays/gamestates/ovl_title/z_title.c +++ b/mm/src/overlays/gamestates/ovl_title/z_title.c @@ -145,9 +145,6 @@ void ConsoleLogo_Main(GameState* thisx) { // #region 2S2H [Debug] Eventually we'll get rid of this if (CVarGetInteger("gDebugEnabled", 0)) { gSaveContext.gameMode = GAMEMODE_NORMAL; - gSaveContext.nextDayTime = NEXT_TIME_NONE; - gSaveContext.nextTransitionType = TRANS_NEXT_TYPE_DEFAULT; - gSaveContext.prevHudVisibility = HUD_VISIBILITY_ALL; SET_NEXT_GAMESTATE(&this->state, MapSelect_Init, sizeof(MapSelectState)); // #endregion } else {