diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp index 28e36277f..1bd24dee7 100644 --- a/mm/2s2h/BenPort.cpp +++ b/mm/2s2h/BenPort.cpp @@ -52,7 +52,6 @@ CrowdControl* CrowdControl::Instance; #include #include -#include "BenJsonConversions.hpp" #include "Enhancements/GameInteractor/GameInteractor.h" #include "Enhancements/Enhancements.h" @@ -1592,192 +1591,3 @@ 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(Ship::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++) { - // Verify that the requested pages align with expected values - if (pageNum == (u32)gFlashSaveStartPages[i] && - (pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) { - flashSlotFile = static_cast(i); - break; - } - } - - switch (flashSlotFile) { - case FLASH_SLOT_FILE_UNAVAILABLE: - return; - 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, offsetof(SaveContext, fileNum)); - - 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++) { - // Verify that the requested pages align with expected values - if (pageNum == (u32)gFlashSaveStartPages[i] && - (pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) { - flashSlotFile = static_cast(i); - break; - } - } - - switch (flashSlotFile) { - case FLASH_SLOT_FILE_UNAVAILABLE: - return -1; - 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, offsetof(SaveContext, fileNum)); - 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; - } - } -} diff --git a/mm/2s2h/BenPort.h b/mm/2s2h/BenPort.h index 2252ca2d4..a691d9472 100644 --- a/mm/2s2h/BenPort.h +++ b/mm/2s2h/BenPort.h @@ -135,8 +135,6 @@ void Gfx_RegisterBlendedTexture(const char* name, u8* mask, u8* replacement); void Gfx_UnregisterBlendedTexture(const char* name); void Gfx_TextureCacheDelete(const uint8_t* texAddr); 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/2s2h/SaveManager/Migrations/1.cpp b/mm/2s2h/SaveManager/Migrations/1.cpp new file mode 100644 index 000000000..742c00232 --- /dev/null +++ b/mm/2s2h/SaveManager/Migrations/1.cpp @@ -0,0 +1,13 @@ +#include "2s2h/SaveManager/SaveManager.h" + +// To better support future migrations, we always store the `Save` at the `save` key to be consistent across normal saves and owl saves +void SaveManager_Migration_1(nlohmann::json& j) { + bool isOwlSave = j.contains("save"); + + if (!isOwlSave) { + nlohmann::json saveContext; + saveContext["save"] = j; + + j = saveContext; + } +} diff --git a/mm/2s2h/SaveManager/SaveManager.cpp b/mm/2s2h/SaveManager/SaveManager.cpp new file mode 100644 index 000000000..c4fc229de --- /dev/null +++ b/mm/2s2h/SaveManager/SaveManager.cpp @@ -0,0 +1,240 @@ +#include "SaveManager.h" + +#include +#include +#include +#include + + +#include "macros.h" +#include "BenJsonConversions.hpp" + +// 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(Ship::Context::GetPathRelativeToAppDirectory("Save")); + +// Migrations +// The idea here is that we can read in any version of the save as generic JSON, then apply migrations +// to the JSON to ensure it's in the correct shape for the current to_json/from_json helpers to convert +// it to the current struct that the game uses. +// +// To add a new migration: +// - Increment CURRENT_SAVE_VERSION +// - Create the migration file in the Migrations folder with the name `{CURRENT_SAVE_VERSION}.cpp` +// - Add the migration function definition below and add it to the `migrations` map with the key being the previous version +const uint32_t CURRENT_SAVE_VERSION = 1; + +void SaveManager_Migration_1(nlohmann::json& j); + +std::map> migrations = { + { 0, SaveManager_Migration_1 }, +}; + +void SaveManager_MigrateSave(nlohmann::json& j) { + int version = j.value("version", 0); + while (version != CURRENT_SAVE_VERSION) { + if (migrations.contains(version)) { + auto migration = migrations[version]; + migration(j); + } + version = j["version"] = version + 1; + } +} + +void SaveManager_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 SaveManager_DeleteSaveFile(std::filesystem::path fileName) { + const std::filesystem::path filePath = savesFolderPath / fileName; + + if (std::filesystem::exists(filePath)) { + std::filesystem::remove(filePath); + } +} + +int SaveManager_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 SaveManager_SysFlashrom_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++) { + // Verify that the requested pages align with expected values + if (pageNum == (u32)gFlashSaveStartPages[i] && + (pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) { + flashSlotFile = static_cast(i); + break; + } + } + + switch (flashSlotFile) { + case FLASH_SLOT_FILE_UNAVAILABLE: + return; + 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)) { + nlohmann::json j; + + j["save"] = save; + j["version"] = CURRENT_SAVE_VERSION; + + SaveManager_WriteSaveFile(fileName, j); + } else { + SaveManager_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, offsetof(SaveContext, fileNum)); + + std::string fileName = "save_" + std::to_string(flashSlotFile) + ".sav"; + if (isBackup) fileName += ".bak"; + + if (IS_VALID_FILE(saveContext.save)) { + nlohmann::json j = saveContext; + + j["version"] = CURRENT_SAVE_VERSION; + + SaveManager_WriteSaveFile(fileName, j); + } else { + SaveManager_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"; + SaveManager_WriteSaveFile(fileName, saveOptions); + break; + } + } +} + +extern "C" s32 SaveManager_SysFlashrom_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++) { + // Verify that the requested pages align with expected values + if (pageNum == (u32)gFlashSaveStartPages[i] && + (pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) { + flashSlotFile = static_cast(i); + break; + } + } + + switch (flashSlotFile) { + case FLASH_SLOT_FILE_UNAVAILABLE: + return -1; + 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 = SaveManager_ReadSaveFile(fileName, j); + if (result != 0) return result; + + SaveManager_MigrateSave(j); + + Save save = j["save"]; + + 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 = SaveManager_ReadSaveFile(fileName, j); + if (result != 0) return result; + + SaveManager_MigrateSave(j); + + SaveContext saveContext = j; + + memcpy(saveBuffer, &saveContext, offsetof(SaveContext, fileNum)); + 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 = SaveManager_ReadSaveFile(fileName, j); + if (result != 0) return result; + + SaveOptions saveOptions = j; + + memcpy(saveBuffer, &saveOptions, sizeof(SaveOptions)); + return 0; + } + } +} \ No newline at end of file diff --git a/mm/2s2h/SaveManager/SaveManager.h b/mm/2s2h/SaveManager/SaveManager.h new file mode 100644 index 000000000..6bac65ab8 --- /dev/null +++ b/mm/2s2h/SaveManager/SaveManager.h @@ -0,0 +1,12 @@ + +#ifndef SAVE_MANAGER_H +#define SAVE_MANAGER_H + +#include + +#ifndef __cplusplus +void SaveManager_SysFlashrom_WriteData(u8* addr, u32 pageNum, u32 pageCount); +s32 SaveManager_SysFlashrom_ReadData(void* addr, u32 pageNum, u32 pageCount); +#endif + +#endif // SAVE_MANAGER_H diff --git a/mm/CMakeLists.txt b/mm/CMakeLists.txt index 321ee3177..d814aa874 100644 --- a/mm/CMakeLists.txt +++ b/mm/CMakeLists.txt @@ -164,6 +164,13 @@ file(GLOB_RECURSE soh__BenGui RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "2s2h/BenGui/*.hpp" ) +file(GLOB_RECURSE soh__SaveManager RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "2s2h/SaveManager/*.c" + "2s2h/SaveManager/*.cpp" + "2s2h/SaveManager/*.h" + "2s2h/SaveManager/*.hpp" +) + #list(REMOVE_ITEM soh__Enhancements "soh/Enhancements/gamecommand.h") #list(FILTER soh__Enhancements EXCLUDE REGEX "soh/Enhancements/gfx.*") @@ -276,6 +283,7 @@ set(ALL_FILES # ${soh__config} ${soh__Enhancements} ${soh__BenGui} + ${soh__SaveManager} ${soh__Extractor} ${soh__Resource} ${src__} diff --git a/mm/src/code/sys_flashrom.c b/mm/src/code/sys_flashrom.c index f15186acb..4ee13271f 100644 --- a/mm/src/code/sys_flashrom.c +++ b/mm/src/code/sys_flashrom.c @@ -7,7 +7,7 @@ #include "z64thread.h" #include "sys_flashrom.h" #include "PR/os_internal_flash.h" -#include "BenPort.h" +#include "2s2h/SaveManager/SaveManager.h" OSMesgQueue sFlashromMesgQueue; OSMesg sFlashromMesg[1]; @@ -80,7 +80,7 @@ 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); + return SaveManager_SysFlashrom_ReadData(addr, pageNum, pageCount); // #endregion OSIoMesg msg; @@ -223,7 +223,7 @@ void SysFlashrom_ThreadEntry(void* arg) { void SysFlashrom_WriteDataAsync(u8* addr, u32 pageNum, u32 pageCount) { // #region 2S2H [Port] Redirect to our own write function - BenSysFlashrom_WriteData(addr, pageNum, pageCount); + SaveManager_SysFlashrom_WriteData(addr, pageNum, pageCount); return; // #endregion