diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py index 1168aecd2..f9cfedb77 100644 --- a/autogen/convert_functions.py +++ b/autogen/convert_functions.py @@ -119,7 +119,7 @@ override_disallowed_functions = { "src/pc/djui/djui_console.h": [ " djui_console_create", "djui_console_message_create", "djui_console_message_dequeue" ], "src/pc/djui/djui_chat_message.h": [ "create_from" ], "src/game/interaction.h": [ "process_interaction", "_handle_" ], - "src/game/save_file.h": [ "save_file_get_all_filenames", "save_file_get_dir", "save_file_get_first_available_index" ], + "src/game/save_file.h": [ "save_file_get_all_filenames", "save_file_get_dir", "save_file_get_first_available_index", "save_file_get_amount_of_available_indexes", "save_file_get_first_active_index" ], "src/game/sound_init.h": [ "_loop_", "thread4_", "set_sound_mode" ], "src/pc/network/network_utils.h": [ "network_get_player_text_color[^_]" ], "src/pc/network/network_player.h": [ "_init", "_connected[^_]", "_shutdown", "_disconnected", "_update", "construct_player_popup", "network_player_name_valid" ], diff --git a/include/PR/os_eeprom.h b/include/PR/os_eeprom.h index 75cdfb0c2..95b1c17dc 100644 --- a/include/PR/os_eeprom.h +++ b/include/PR/os_eeprom.h @@ -94,8 +94,8 @@ extern "C" { extern s32 osEepromProbe(OSMesgQueue *); extern s32 osEepromRead(OSMesgQueue *, u8, u8 *); extern s32 osEepromWrite(OSMesgQueue *, u8, u8 *); -extern s32 osEepromLongReadLegacy(OSMesgQueue *, u8 , u8 *, int); -extern s32 osEepromLongRead(OSMesgQueue *, u8, u8, u8 *, int); +extern s32 osEepromLongRead(OSMesgQueue *, u8 , u8 *, int, char *, size_t); +extern s32 osEepromLongReadFile(OSMesgQueue *, u8, u8, u8 *, int); extern s32 osEepromLongWrite(OSMesgQueue *, u8, u8, u8 *, int); diff --git a/src/game/save_file.c b/src/game/save_file.c index eafc0a458..a705f42fe 100644 --- a/src/game/save_file.c +++ b/src/game/save_file.c @@ -123,9 +123,9 @@ static inline void bswap_savefile(struct SaveFile *data) { * Read from EEPROM to a given address. * The EEPROM address is computed using the offset of the destination address from gSaveBuffer. * Try at most 4 times, and return 0 on success. On failure, return the status returned from - * osEepromLongRead. It also returns 0 if EEPROM isn't loaded correctly in the system. + * osEepromLongReadFile. It also returns 0 if EEPROM isn't loaded correctly in the system. */ -static s32 read_eeprom_data(u8 file, void *buffer, s32 size) { +s32 read_eeprom_data(u8 file, void *buffer, s32 size) { s32 status = 0; if (gEepromProbe != 0) { @@ -135,7 +135,7 @@ static s32 read_eeprom_data(u8 file, void *buffer, s32 size) { do { block_until_rumble_pak_free(); triesLeft--; - status = osEepromLongRead(&gSIEventMesgQueue, file, offset, buffer, size); + status = osEepromLongReadFile(&gSIEventMesgQueue, file, offset, buffer, size); release_rumble_pak_control(); } while (triesLeft > 0 && status != 0); } @@ -149,7 +149,7 @@ static s32 read_eeprom_data(u8 file, void *buffer, s32 size) { * Try at most 4 times, and return 0 on success. On failure, return the status returned from * osEepromLongWrite. Unlike read_eeprom_data, return 1 if EEPROM isn't loaded. */ -static s32 write_eeprom_data(u8 file, void *buffer, s32 size, const uintptr_t baseofs) { +s32 write_eeprom_data(u8 file, void *buffer, s32 size, const uintptr_t baseofs) { s32 status = 1; if (gEepromProbe != 0) { @@ -276,7 +276,7 @@ static void save_file_bswap(struct SaveBuffer *buf) { */ static void save_file_convert_old_to_new() { struct LegacySaveBuffer saveBuffer = { 0 }; - s32 status = osEepromLongReadLegacy(&gSIEventMesgQueue, 0, (void*)&saveBuffer, sizeof(saveBuffer)); + s32 status = osEepromLongRead(&gSIEventMesgQueue, 0, (void*)&saveBuffer, sizeof(saveBuffer), (char*)fs_get_write_path(SAVE_FILENAME), 512); if (status == 0) { for (int i = 0; i < 4; i++) write_eeprom_data(i, saveBuffer.files[i], sizeof(saveBuffer.files[i]), 0); @@ -346,7 +346,7 @@ void save_file_get_dir(int fileIndex, char* outPath, size_t size, char* override } /** - * Gets the first available index that is not being used + * Gets the first available index that is not being used. Returns the number of save files on failure */ s32 save_file_get_first_available_index() { if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return 0; @@ -358,6 +358,33 @@ s32 save_file_get_first_available_index() { return NUM_SAVE_FILES; } +/** + * Gets the first available index that is active. Returns 0 on failure +*/ +s32 save_file_get_first_active_index() { + if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return 0; + for (int i = 0; i < NUM_SAVE_FILES; i++) { + char filePath[256]; + save_file_get_dir(i, filePath, 256, NULL); + if (fs_sys_file_exists(fs_get_write_path(filePath))) return i; + } + return 0; +} + +/** + * Gets the amount of available indexes +*/ +s32 save_file_get_amount_of_available_indexes() { + if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return 0; + int count = 0; + for (int i = 0; i < NUM_SAVE_FILES; i++) { + char filePath[256]; + save_file_get_dir(i, filePath, 256, NULL); + if (!fs_sys_file_exists(fs_get_write_path(filePath))) count++; + } + return count; +} + void save_file_do_save(s32 fileIndex, s8 forceSave) { if (INVALID_FILE_INDEX(fileIndex)) { return; } if (gNetworkType != NT_SERVER) { diff --git a/src/game/save_file.h b/src/game/save_file.h index ac9d9105a..0e89c5854 100644 --- a/src/game/save_file.h +++ b/src/game/save_file.h @@ -144,6 +144,9 @@ extern struct WarpCheckpoint gWarpCheckpoint; extern s8 gMainMenuDataModified; extern s8 gSaveFileModified; +s32 read_eeprom_data(u8 file, void *buffer, s32 size); +s32 write_eeprom_data(u8 file, void *buffer, s32 size, const uintptr_t baseofs); + /* |description|Gets the course number's corresponding level number|descriptionEnd| */ s8 get_level_num_from_course_num(s16 courseNum); @@ -158,6 +161,10 @@ void save_file_get_dir(int fileIndex, char* outPath, size_t size, char* override s32 save_file_get_first_available_index(); +s32 save_file_get_amount_of_available_indexes(); + +s32 save_file_get_first_active_index(); + /* |description| Saves the current state of the game into a specified save file. Includes data verification and backup management. Useful for maintaining game progress during play or when saving manually @@ -176,7 +183,7 @@ Erases the backup data for the current save file without affecting the primary s void save_file_erase_current_backup_save(void); BAD_RETURN(s32) save_file_copy(s32 srcFileIndex, s32 destFileIndex); -void save_file_load_all(u8 reload); +void save_file_load_all(UNUSED u8 reload); /* |description| Reloads the save file data into memory, optionally resetting all save files. Marks the save file as modified. diff --git a/src/pc/configfile.c b/src/pc/configfile.c index f6a8f2891..85c11010b 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -21,6 +21,7 @@ #include "djui/djui_hud_utils.h" #include "pc/network/network_player.h" #include "pc/pc_main.h" +#include "game/save_file.h" #define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0])) @@ -757,6 +758,15 @@ NEXT_OPTION: if (configDjuiTheme >= DJUI_THEME_MAX) { configDjuiTheme = 0; } if (configDjuiScale >= 5) { configDjuiScale = 0; } + if (configHostSaveSlot >= NUM_SAVE_FILES) { + configHostSaveSlot = save_file_get_first_active_index() + 1; + } else { + char filePath[256]; + save_file_get_dir(configHostSaveSlot - 1, filePath, 256, NULL); + if (!fs_sys_file_exists(fs_get_write_path(filePath))) + configHostSaveSlot = save_file_get_first_active_index() + 1; + } + if (gCLIOpts.fullscreen == 1) { configWindow.fullscreen = true; } else if (gCLIOpts.fullscreen == 2) { diff --git a/src/pc/mods/mod_import.c b/src/pc/mods/mod_import.c index ad9de35c7..fb9417f1e 100644 --- a/src/pc/mods/mod_import.c +++ b/src/pc/mods/mod_import.c @@ -8,6 +8,8 @@ #include "pc/djui/djui_popup.h" #include "mods.h" #include "mods_utils.h" +#include "game/save_file.h" +#include "buffers/buffers.h" static bool mod_import_lua(char* src) { char dst[SYS_MAX_PATH] = { 0 }; @@ -252,10 +254,48 @@ static bool mod_import_zip(char* path, bool* isLua, bool* isDynos) { return true; } +static bool mod_import_save(char* src) { + // check to see if it's the old format first + struct LegacySaveBuffer legacySaveBuffer = { 0 }; + s32 status = osEepromLongRead(NULL, 0, (void*)&legacySaveBuffer, sizeof(legacySaveBuffer), src, 512); + if (status == 0) { + // old data is a go, write eeprom data + if (save_file_get_amount_of_available_indexes() < 4) { + LOG_ERROR("Ran out of save files slots"); + return false; + } + for (int i = 0; i < 4; i++) { + int file = save_file_get_first_available_index(); + write_eeprom_data(file, legacySaveBuffer.files[i], sizeof(legacySaveBuffer.files[i]), 0); + } + LOG_INFO("Imported save: '%s' into 4 parts", src); + save_file_load_all(TRUE); + return true; + } + + // it's not legacy, try to load it using the new format + int firstIndex = save_file_get_first_available_index(); + if (firstIndex == NUM_SAVE_FILES) { + LOG_ERROR("Ran out of save files slots"); + return false; + } + status = osEepromLongRead(NULL, 0, (void*)&gSaveBuffer.files[firstIndex], sizeof(gSaveBuffer.files[firstIndex]), src, EEPROM_SIZE); + if (status == 0) { + // data is a go, write to eeprom data + write_eeprom_data(firstIndex, gSaveBuffer.files[firstIndex], sizeof(gSaveBuffer.files[firstIndex]), 0); + LOG_INFO("Imported save: '%s'", src); + save_file_load_all(TRUE); + return true; + } + + return false; +} + bool mod_import_file(char* path) { bool isLua = false; bool isDynos = false; bool isPalette = false; + bool isSave = false; bool ret = false; if (gNetworkType != NT_NONE && !path_ends_with(path, ".ini")) { @@ -271,6 +311,9 @@ bool mod_import_file(char* path) { ret = mod_import_palette(path); } else if (path_ends_with(path, ".zip")) { ret = mod_import_zip(path, &isLua, &isDynos); + } else if (path_ends_with(path, SAVE_EXTENSION)) { + isSave = true; + ret = mod_import_save(path); } char msg[SYS_MAX_PATH] = { 0 }; @@ -288,6 +331,9 @@ bool mod_import_file(char* path) { } else if (isPalette) { djui_language_replace(DLANG(NOTIF, IMPORT_PALETTE_SUCCESS), msg, SYS_MAX_PATH, '@', basename); djui_popup_create(msg, 2); + } else if (isSave) { + djui_language_replace(DLANG(NOTIF, IMPORT_SAVE_SUCCESS), msg, SYS_MAX_PATH, '@', basename); + djui_popup_create(msg, 2); } } else { djui_language_replace(DLANG(NOTIF, IMPORT_FAIL), msg, SYS_MAX_PATH, '@', basename); diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c index dccc55cd6..ec2e1963e 100644 --- a/src/pc/ultra_reimplementation.c +++ b/src/pc/ultra_reimplementation.c @@ -125,24 +125,24 @@ s32 osEepromProbe(UNUSED OSMesgQueue *mq) { return 1; } -s32 osEepromLongReadLegacy(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) { - u8 content[512]; +s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes, char *path, size_t size) { + u8 content[size]; s32 ret = -1; - fs_file_t *fp = fs_open(SAVE_FILENAME); - if (fp == NULL) { + FILE *fp = fopen(path, "rb"); + if (!fp) { return -1; } - if (fs_read(fp, content, 512) == 512) { + if (fread(content, 1, size, fp) == size) { memcpy(buffer, content + address * 8, nbytes); ret = 0; } - fs_close(fp); + fclose(fp); return ret; } -s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) { +s32 osEepromLongReadFile(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) { if (gOverrideEeprom[fileIndex] != NULL) { memcpy(buffer, gOverrideEeprom[fileIndex] + address * 8, nbytes); return 0; @@ -174,7 +174,7 @@ s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buff u8 content[EEPROM_SIZE] = { 0 }; if (address != 0 || nbytes != EEPROM_SIZE) { - osEepromLongRead(mq, fileIndex, 0, content, EEPROM_SIZE); + osEepromLongReadFile(mq, fileIndex, 0, content, EEPROM_SIZE); } memcpy(content + address * 8, buffer, nbytes);