diff options
| author | Garrett Cox <garrettjcox@gmail.com> | 2024-05-25 09:54:08 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-25 09:54:08 -0500 |
| commit | 5cc1798818242395ac5f3b568e8f6b1cc00af382 (patch) | |
| tree | 37fc80905ac85e546cc93c67a9f28aab9089d251 | |
| parent | e2dc6a3fc70a79bb36de0cdaf1e57485e2d7f042 (diff) | |
Rework save files organization (#445)
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | mm/2s2h/SaveManager/BinarySaveConverter.cpp | 16 | ||||
| -rw-r--r-- | mm/2s2h/SaveManager/Migrations/4.cpp | 10 | ||||
| -rw-r--r-- | mm/2s2h/SaveManager/Migrations/5.cpp | 19 | ||||
| -rw-r--r-- | mm/2s2h/SaveManager/SaveManager.cpp | 350 | ||||
| -rw-r--r-- | mm/2s2h/SaveManager/SaveManager.h | 1 |
6 files changed, 272 insertions, 125 deletions
diff --git a/.gitignore b/.gitignore index 4049f2f1a..95795f438 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ shipofharkinian.json *.sav .envrc imgui.ini +saves/* mm/libultraship/extern/Debug/ImGui.lib diff --git a/mm/2s2h/SaveManager/BinarySaveConverter.cpp b/mm/2s2h/SaveManager/BinarySaveConverter.cpp index df2bc910e..e682f409f 100644 --- a/mm/2s2h/SaveManager/BinarySaveConverter.cpp +++ b/mm/2s2h/SaveManager/BinarySaveConverter.cpp @@ -696,11 +696,11 @@ bool BinarySaveConverter_HandleFileDropped(std::string filePath) { reader->Seek(saveOffsets[0] - 0x24, Ship::SeekOffsetType::Start); BinarySaveConverter_ReadBufferToSave(&saveContext, reader); nlohmann::json j; - std::string fileName = "save_" + std::to_string(saveSlot) + ".sav"; + std::string fileName = SaveManager_GetFileName(saveSlot); - j["save"] = saveContext.save; - - SaveManager_WriteSaveFile(fileName, j); + j["newCycleSave"]["save"] = saveContext.save; + j["type"] = "2S2H_SAVE"; + j["version"] = 4; // Based on where the save was found, determine if the two possible owl save offsets are in the list of save // offsets. If they are, we can assume that the save is the owl save associated with the other file above @@ -716,14 +716,12 @@ bool BinarySaveConverter_HandleFileDropped(std::string filePath) { if (foundOwlSave) { memset(&saveContext, 0, sizeof(Legacy_SaveContext)); BinarySaveConverter_ReadBufferToSave(&saveContext, reader); - fileName = "save_" + std::to_string(saveSlot + 4) + ".sav"; - nlohmann::json j2; - - j2 = saveContext; - SaveManager_WriteSaveFile(fileName, j2); + j["owlSave"] = saveContext; } + SaveManager_WriteSaveFile(fileName, j); + if (gFileSelectState != NULL) { func_801457CC(&gFileSelectState->state, &gFileSelectState->sramCtx); if (gFileSelectState->menuMode == FS_MENU_MODE_CONFIG && gFileSelectState->configMode == CM_MAIN_MENU) { diff --git a/mm/2s2h/SaveManager/Migrations/4.cpp b/mm/2s2h/SaveManager/Migrations/4.cpp new file mode 100644 index 000000000..39ba4c34f --- /dev/null +++ b/mm/2s2h/SaveManager/Migrations/4.cpp @@ -0,0 +1,10 @@ +#include "2s2h/SaveManager/SaveManager.h" +#include "z64.h" + +// Final legacy migration, prior to merging owl saves into main save file. +void SaveManager_Migration_4(nlohmann::json& j) { + nlohmann::json rootSave; + rootSave["newCycleSave"] = j; + + j = rootSave; +} diff --git a/mm/2s2h/SaveManager/Migrations/5.cpp b/mm/2s2h/SaveManager/Migrations/5.cpp new file mode 100644 index 000000000..6fdeb7a1c --- /dev/null +++ b/mm/2s2h/SaveManager/Migrations/5.cpp @@ -0,0 +1,19 @@ +#include "2s2h/SaveManager/SaveManager.h" +#include "z64.h" + +// This is the base migration, binary saves are imported as this version. +void SaveManager_Migration_5(nlohmann::json& j) { + nlohmann::json dpadEquips; + for (int i = 0; i < EQUIP_SLOT_D_MAX; i++) { + for (int j = 0; j < EQUIP_SLOT_D_MAX; j++) { + dpadEquips["dpadItems"][i][j] = ITEM_NONE; + dpadEquips["dpadSlots"][i][j] = SLOT_NONE; + } + } + + // if shipSaveInfo doesn't exist, create it + if (!j["save"].contains("shipSaveInfo")) { + j["save"]["shipSaveInfo"]["dpadEquips"] = dpadEquips; + j["save"]["shipSaveInfo"]["pauseSaveEntrance"] = -1; + } +} diff --git a/mm/2s2h/SaveManager/SaveManager.cpp b/mm/2s2h/SaveManager/SaveManager.cpp index 933330d69..691d740b2 100644 --- a/mm/2s2h/SaveManager/SaveManager.cpp +++ b/mm/2s2h/SaveManager/SaveManager.cpp @@ -34,7 +34,7 @@ typedef enum FlashSlotFile { ((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")); +const std::filesystem::path savesFolderPath(Ship::Context::GetPathRelativeToAppDirectory("saves")); // Migrations // The idea here is that we can read in any version of the save as generic JSON, then apply migrations @@ -46,28 +46,60 @@ const std::filesystem::path savesFolderPath(Ship::Context::GetPathRelativeToAppD // - 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 = 3; +const uint32_t CURRENT_SAVE_VERSION = 5; void SaveManager_Migration_1(nlohmann::json& j); void SaveManager_Migration_2(nlohmann::json& j); void SaveManager_Migration_3(nlohmann::json& j); +void SaveManager_Migration_4(nlohmann::json& j); +void SaveManager_Migration_5(nlohmann::json& j); const std::unordered_map<uint32_t, std::function<void(nlohmann::json&)>> migrations = { + // Pre-1.0.0 Migrations, deprecated { 0, SaveManager_Migration_1 }, { 1, SaveManager_Migration_2 }, { 2, SaveManager_Migration_3 }, + { 3, SaveManager_Migration_4 }, + // Base Migration + { 4, SaveManager_Migration_5 }, }; -void SaveManager_MigrateSave(nlohmann::json& j) { +int SaveManager_MigrateSave(nlohmann::json& j) { int version = j.value("version", 0); - // BENTODO: what should we do if the version number is greater? - while (version < CURRENT_SAVE_VERSION) { - if (migrations.contains(version)) { - auto migration = migrations.at(version); - migration(j); + if (version > CURRENT_SAVE_VERSION) { + SPDLOG_ERROR("Save version is greater than current version"); + return -1; + } + + if (version >= 4 && !j.contains("newCycleSave") && !j.contains("owlSave")) { + SPDLOG_ERROR("Save file is missing newCycleSave and owlSave"); + return -1; + } + + try { + while (version < CURRENT_SAVE_VERSION) { + if (migrations.contains(version)) { + auto migration = migrations.at(version); + if (version < 4) { + migration(j); // Pre-1.0.0 Migrations, deprecated + } else { + // In the case of copying files, the owl save is copied first, so the new cycle may not exist yet. + if (j.contains("newCycleSave")) { + migration(j["newCycleSave"]); + } + // Only migrate the owl save if it exists + if (j.contains("owlSave")) { + migration(j["owlSave"]); + } + } + } + version = j["version"] = version + 1; } - version = j["version"] = version + 1; + return 0; + } catch (...) { + SPDLOG_ERROR("Failed to migrate save file"); + return -1; } } @@ -78,17 +110,21 @@ void SaveManager_WriteSaveFile(std::filesystem::path fileName, nlohmann::json j) std::filesystem::create_directory(savesFolderPath); } - std::ofstream o(filePath); - o << std::setw(4) << j << std::endl; - o.close(); + try { + std::ofstream o(filePath); + o << std::setw(4) << j << std::endl; + o.close(); + } catch (...) { SPDLOG_ERROR("Failed to write save file"); } } void SaveManager_DeleteSaveFile(std::filesystem::path fileName) { const std::filesystem::path filePath = savesFolderPath / fileName; - if (std::filesystem::exists(filePath)) { - std::filesystem::remove(filePath); - } + try { + if (std::filesystem::exists(filePath)) { + std::filesystem::remove(filePath); + } + } catch (...) { SPDLOG_ERROR("Failed to delete save file"); } } int SaveManager_ReadSaveFile(std::filesystem::path fileName, nlohmann::json& j) { @@ -98,19 +134,39 @@ int SaveManager_ReadSaveFile(std::filesystem::path fileName, nlohmann::json& j) return -1; } - std::ifstream i(filePath); - i >> j; - i.close(); - return 0; + try { + std::ifstream i(filePath); + i >> j; + i.close(); + return 0; + } catch (...) { + SPDLOG_ERROR("Failed to read save file"); + return -2; + } +} + +void SaveManager_MoveInvalidSaveFile(std::filesystem::path fileName, std::string message) { + const std::filesystem::path filePath = savesFolderPath / fileName; + const std::filesystem::path backupFilePath = + savesFolderPath / (fileName.stem().string() + "_invalid_" + std::to_string(std::time(nullptr)) + ".json"); + + try { + if (std::filesystem::exists(filePath)) { + std::filesystem::rename(filePath, backupFilePath); + } + + auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); + gui->GetGameOverlay()->TextDrawNotification(30.0f, true, message.c_str()); + } catch (...) { SPDLOG_ERROR("Failed to move invalid save file"); } } int SaveManager_GetOpenFileSlot() { - std::string fileName = "save_0.sav"; + std::string fileName = "file1.json"; if (!std::filesystem::exists(savesFolderPath / fileName)) { - return 0; + return 1; } - fileName = "save_2.sav"; + fileName = "file2.json"; if (!std::filesystem::exists(savesFolderPath / fileName)) { return 2; } @@ -118,6 +174,50 @@ int SaveManager_GetOpenFileSlot() { return -1; } +FlashSlotFile SaveManager_GetFlashSlotFileFromPages(u32 pageNum, u32 pageCount) { + FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE; + + 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<FlashSlotFile>(i); + break; + } + } + + return flashSlotFile; +} + +std::string SaveManager_GetFileName(int fileNum, bool isBackup) { + return "file" + std::to_string(fileNum) + (isBackup ? "backup" : "") + ".json"; +} + +std::string SaveManager_GetFileNameFromFlashSlotFile(FlashSlotFile flashSlotFile) { + if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) + return "invalid"; + + if (flashSlotFile == FLASH_SLOT_FILE_SRAM_HEADER || flashSlotFile == FLASH_SLOT_FILE_SRAM_HEADER_BACKUP) { + return "global.json"; + } + + bool isBackup = + flashSlotFile == FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP || flashSlotFile == FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP || + flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP || flashSlotFile == FLASH_SLOT_FILE_2_OWL_SAVE_BACKUP; + + int fileNum = + (flashSlotFile == FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP || flashSlotFile == FLASH_SLOT_FILE_1_NEW_CYCLE || + flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP || flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE) + ? 1 + : 2; + + bool isOwlSave = + (flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE || flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP || + flashSlotFile == FLASH_SLOT_FILE_2_OWL_SAVE || flashSlotFile == FLASH_SLOT_FILE_2_OWL_SAVE_BACKUP); + + return "file" + std::to_string(fileNum) + (isBackup ? "backup" : "") + ".json"; +} + bool SaveManager_HandleFileDropped(std::string filePath) { try { std::ifstream fileStream(filePath); @@ -134,13 +234,9 @@ bool SaveManager_HandleFileDropped(std::string filePath) { nlohmann::json j; try { fileStream >> j; - } catch (nlohmann::json::parse_error& e) { - SPDLOG_ERROR("Failed to parse JSON: {}", e.what()); - return false; - } + } catch (nlohmann::json::parse_error& e) { return false; } if (!j.contains("type") || j["type"] != "2S2H_SAVE") { - SPDLOG_ERROR("Invalid save file type"); return false; } @@ -152,7 +248,7 @@ bool SaveManager_HandleFileDropped(std::string filePath) { return true; } - std::string fileName = "save_" + std::to_string(saveSlot) + ".sav"; + std::string fileName = SaveManager_GetFileName(saveSlot); SaveManager_WriteSaveFile(fileName, j); @@ -178,20 +274,24 @@ bool SaveManager_HandleFileDropped(std::string filePath) { } extern "C" void SaveManager_SysFlashrom_WriteData(u8* saveBuffer, u32 pageNum, u32 pageCount) { - FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE; + FlashSlotFile flashSlotFile = SaveManager_GetFlashSlotFileFromPages(pageNum, pageCount); + std::string fileName = SaveManager_GetFileNameFromFlashSlotFile(flashSlotFile); + 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<FlashSlotFile>(i); - break; - } + + if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) { + return; + } + + if (flashSlotFile == FLASH_SLOT_FILE_SRAM_HEADER || flashSlotFile == FLASH_SLOT_FILE_SRAM_HEADER_BACKUP) { + SaveOptions saveOptions; + memcpy(&saveOptions, saveBuffer, sizeof(SaveOptions)); + + SaveManager_WriteSaveFile(fileName, saveOptions); + return; } 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; @@ -201,19 +301,32 @@ extern "C" void SaveManager_SysFlashrom_WriteData(u8* saveBuffer, u32 pageNum, u 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; + // Read the existing save file to preserve the owl save + int result = SaveManager_ReadSaveFile(fileName, j); + if (result == -2) { + SaveManager_MoveInvalidSaveFile( + fileName, + "Something went wrong trying to preserve the owl save. Original save file has been backed up."); + } else if (result == 0) { + result = SaveManager_MigrateSave(j); + + if (result != 0) { + SaveManager_MoveInvalidSaveFile( + fileName, "Failed to migrate owl save. Original save file has been backed up."); + } + } + + j["newCycleSave"]["save"] = save; j["version"] = CURRENT_SAVE_VERSION; j["type"] = "2S2H_SAVE"; SaveManager_WriteSaveFile(fileName, j); } else { + // If IS_VALID_FILE fails, we should delete the save file, even if there is an owl save in it, because + // they just deleted the new cycle save SaveManager_DeleteSaveFile(fileName); } break; @@ -227,106 +340,111 @@ extern "C" void SaveManager_SysFlashrom_WriteData(u8* saveBuffer, u32 pageNum, u SaveContext saveContext; memcpy(&saveContext, saveBuffer, offsetof(SaveContext, fileNum)); - std::string fileName = "save_" + std::to_string(flashSlotFile) + ".sav"; - if (isBackup) - fileName += ".bak"; + nlohmann::json j; + // Read the existing save file to preserve the new cycle save + int result = SaveManager_ReadSaveFile(fileName, j); + if (result == -2) { + SaveManager_MoveInvalidSaveFile(fileName, "Something went wrong trying to preserve the new cycle save. " + "Original save file has been backed up."); + } else if (result == 0) { + result = SaveManager_MigrateSave(j); + + if (result != 0) { + SaveManager_MoveInvalidSaveFile( + fileName, "Failed to migrate new cycle save. Original save file has been backed up."); + } + } if (IS_VALID_FILE(saveContext.save)) { - nlohmann::json j = saveContext; - + j["owlSave"] = saveContext; j["version"] = CURRENT_SAVE_VERSION; j["type"] = "2S2H_SAVE"; SaveManager_WriteSaveFile(fileName, j); } else { - SaveManager_DeleteSaveFile(fileName); + // If IS_VALID_FILE fails, and there is still a new cycle save present, we just want to only remove the + // owl save and write the new cycle save back + if (j.contains("newCycleSave")) { + if (j.contains("owlSave")) { + j.erase("owlSave"); + } + j["version"] = CURRENT_SAVE_VERSION; + j["type"] = "2S2H_SAVE"; + SaveManager_WriteSaveFile(fileName, j); + // If there is no new cycle save, we should just delete the save file + } 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<FlashSlotFile>(i); - break; - } + FlashSlotFile flashSlotFile = SaveManager_GetFlashSlotFileFromPages(pageNum, pageCount); + std::string fileName = SaveManager_GetFileNameFromFlashSlotFile(flashSlotFile); + + if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) { + return -1; } - switch (flashSlotFile) { - case FLASH_SLOT_FILE_UNAVAILABLE: + if (flashSlotFile == FLASH_SLOT_FILE_SRAM_HEADER || flashSlotFile == FLASH_SLOT_FILE_SRAM_HEADER_BACKUP) { + nlohmann::json j; + int result = SaveManager_ReadSaveFile(fileName, j); + if (result == -2) { + SaveManager_MoveInvalidSaveFile( + fileName, + "Something went wrong trying to read global save file, the original file has been backed up."); 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; + } else if (result != 0) { + return result; + } - SaveManager_MigrateSave(j); + SaveOptions saveOptions = j; - Save save = j["save"]; + memcpy(saveBuffer, &saveOptions, sizeof(SaveOptions)); + return 0; + } - 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"; + bool isOwlSave = + (flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE || flashSlotFile == FLASH_SLOT_FILE_1_OWL_SAVE_BACKUP || + flashSlotFile == FLASH_SLOT_FILE_2_OWL_SAVE || flashSlotFile == FLASH_SLOT_FILE_2_OWL_SAVE_BACKUP); - nlohmann::json j; - int result = SaveManager_ReadSaveFile(fileName, j); - if (result != 0) - return result; + nlohmann::json j; + int result = SaveManager_ReadSaveFile(fileName, j); + if (result == -2) { + SaveManager_MoveInvalidSaveFile( + fileName, "Something went wrong trying to read save file, the original file has been backed up."); + return -1; + } else if (result != 0) { + return result; + } - SaveManager_MigrateSave(j); + result = SaveManager_MigrateSave(j); - SaveContext saveContext = j; + if (result != 0) { + SaveManager_MoveInvalidSaveFile(fileName, "Failed to migrate save file, the original file has been backed up."); + return -1; + } - memcpy(saveBuffer, &saveContext, offsetof(SaveContext, fileNum)); - return result; + if (isOwlSave) { + if (!j.contains("owlSave")) { + return -1; } - 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; + SaveContext saveContext = j["owlSave"]; - memcpy(saveBuffer, &saveOptions, sizeof(SaveOptions)); - return 0; + memcpy(saveBuffer, &saveContext, offsetof(SaveContext, fileNum)); + return 0; + } else { + if (!j.contains("newCycleSave")) { + return -1; } + + Save save = j["newCycleSave"]["save"]; + + memcpy(saveBuffer, &save, sizeof(Save)); + return 0; } } diff --git a/mm/2s2h/SaveManager/SaveManager.h b/mm/2s2h/SaveManager/SaveManager.h index 1ac918050..31dcd6735 100644 --- a/mm/2s2h/SaveManager/SaveManager.h +++ b/mm/2s2h/SaveManager/SaveManager.h @@ -5,6 +5,7 @@ #include <libultraship/libultraship.h> #ifdef __cplusplus +std::string SaveManager_GetFileName(int fileNum, bool isBackup = false); bool SaveManager_HandleFileDropped(std::string filePath); bool BinarySaveConverter_HandleFileDropped(std::string filePath); int SaveManager_GetOpenFileSlot(); |
