From 0db3bd8812e200149ef60d8cc38fe8689b08f5af Mon Sep 17 00:00:00 2001 From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:11:01 -0600 Subject: [PATCH] strike --- autogen/convert_functions.py | 2 +- autogen/lua_definitions/constants.lua | 2 +- include/PR/os_eeprom.h | 1 + lang/English.ini | 4 + src/game/save_file.c | 37 +++++++-- src/game/save_file.h | 33 +++++++- src/pc/configfile.h | 2 +- src/pc/djui/djui_panel_host_save.c | 106 +++++++++++++++++++------- src/pc/lua/smlua_constants_autogen.c | 2 +- src/pc/ultra_reimplementation.c | 22 ++++++ 10 files changed, 171 insertions(+), 40 deletions(-) diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py index e34a52c2e..0c2449f81 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_dir" ], + "src/game/save_file.h": [ "save_file_get_dir", "save_file_get_first_available_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/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 7ce77e0fa..4d0431b7c 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -6797,7 +6797,7 @@ METAL = CAP --- @type PlayerPart --- | `METAL` --- @type integer -NUM_SAVE_FILES = 32 +NUM_SAVE_FILES = 64 SAVE_FILE_A = 0 --- @type SaveFileIndex SAVE_FILE_B = 1 --- @type SaveFileIndex diff --git a/include/PR/os_eeprom.h b/include/PR/os_eeprom.h index 63a9e14c9..75cdfb0c2 100644 --- a/include/PR/os_eeprom.h +++ b/include/PR/os_eeprom.h @@ -94,6 +94,7 @@ 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 osEepromLongWrite(OSMesgQueue *, u8, u8, u8 *, int); diff --git a/lang/English.ini b/lang/English.ini index 14ec1d462..175ff3822 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -219,6 +219,10 @@ SAVE_TITLE = "SAVE" ERASE_TITLE = "ERASE" CONFIRM = "Are you sure you want to erase this save slot?" ERASE = "Erase" +CREATE_SAVE = "Create Save" +CREATE = "Create" +CREATE_TITLE = "CREATE" +CREATE_NAME = "Set Save File @'s Name:" EDIT = "Edit" EDIT_TITLE = "EDIT" EDIT_NAME = "Edit Save File @'s Name:" diff --git a/src/game/save_file.c b/src/game/save_file.c index 0d692a9e8..78576cb36 100644 --- a/src/game/save_file.c +++ b/src/game/save_file.c @@ -98,11 +98,6 @@ s8 get_level_course_num(s16 levelNum) { return gLevelToCourseNumTable[levelNum]; } -void save_file_get_dir(int fileIndex, char* outPath, size_t size) { - snprintf(outPath, size, "%s%s%d.bin", SAVE_DIRECTORY, SAVE_FILENAME, fileIndex); - printf("Returning filepath: %s\n", outPath); -} - /** * Byteswap all multibyte fields in a SaveBlockSignature. */ @@ -274,6 +269,20 @@ static void save_file_bswap(struct SaveBuffer *buf) { } } +void save_file_get_dir(int fileIndex, char* outPath, size_t size) { + snprintf(outPath, size, "%s%s%d.bin", SAVE_DIRECTORY, SAVE_FILENAME, fileIndex); +} + +s32 save_file_get_first_available_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); + if (!fs_sys_file_exists(fs_get_write_path(filePath))) return i; + } + return NUM_SAVE_FILES; +} + void save_file_do_save(s32 fileIndex, s8 forceSave) { if (INVALID_FILE_INDEX(fileIndex)) { return; } if (gNetworkType != NT_SERVER) { @@ -306,8 +315,11 @@ void save_file_erase(s32 fileIndex) { bzero(&gSaveBuffer.files[fileIndex][0], sizeof(gSaveBuffer.files[fileIndex][0])); bzero(&gSaveBuffer.files[fileIndex][1], sizeof(gSaveBuffer.files[fileIndex][1])); - gSaveFileModified = TRUE; - save_file_do_save(fileIndex, TRUE); + if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return; + char filepath[256]; + save_file_get_dir(fileIndex, filepath, 256); + if (!fs_sys_file_exists(fs_get_write_path(filepath))) return; + remove(fs_get_write_path(filepath)); } void save_file_reload(u8 loadAll) { @@ -349,6 +361,17 @@ void save_file_load_all(UNUSED u8 reload) { gSaveFileModified = FALSE; + if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) { + // Attempt to get and convert old save data into new + struct LegacySaveBuffer saveBuffer = { 0 }; + s32 status = osEepromLongReadLegacy(&gSIEventMesgQueue, 0, (void*)&saveBuffer, sizeof(saveBuffer)); + // 0 is success + if (status == 0) { + for (int i = 0; i < 4; i++) + write_eeprom_data(i, saveBuffer.files[i], sizeof(saveBuffer.files[i]), 0); + } + } + bzero(&gSaveBuffer, sizeof(gSaveBuffer)); for (int file = 0; file < NUM_SAVE_FILES; file++) { diff --git a/src/game/save_file.h b/src/game/save_file.h index 806ecd779..ae9ced518 100644 --- a/src/game/save_file.h +++ b/src/game/save_file.h @@ -8,7 +8,7 @@ #include "course_table.h" -#define NUM_SAVE_FILES 32 +#define NUM_SAVE_FILES 64 // size of savebuffer #define EEPROM_SIZE 128 @@ -60,6 +60,35 @@ struct SaveBuffer struct SaveFile files[NUM_SAVE_FILES][2]; }; +// Legacy save info for loading old save files +struct LegacyMainMenuSaveData +{ + // Each save file has a 2 bit "age" for each course. The higher this value, + // the older the high score is. This is used for tie-breaking when displaying + // on the high score screen. + u32 coinScoreAges[4]; + u16 soundMode; + +#ifdef VERSION_EU + u16 language; +#define SUBTRAHEND 8 +#else +#define SUBTRAHEND 6 +#endif + + // Pad to match the EEPROM size of 0x200 (10 bytes on JP/US, 8 bytes on EU) + u8 filler[512 / 2 - SUBTRAHEND - 4 * (4 + sizeof(struct SaveFile))]; + + struct SaveBlockSignature signature; +}; + +struct LegacySaveBuffer { + // Each of the four save files has two copies. If one is bad, the other is used as a backup. + struct SaveFile files[4][2]; + // The main menu data has two copies. If one is bad, the other is used as a backup. + struct LegacyMainMenuSaveData menuData[2]; +}; + extern u8 gLastCompletedCourseNum; extern u8 gLastCompletedStarNum; extern s8 sUnusedGotGlobalCoinHiScore; @@ -122,6 +151,8 @@ s8 get_level_course_num(s16 levelNum); void save_file_get_dir(int fileIndex, char* outPath, size_t size); +s32 save_file_get_first_available_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 diff --git a/src/pc/configfile.h b/src/pc/configfile.h index 330e2c1f0..c5b0cccf7 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -45,7 +45,7 @@ enum RefreshRateMode { RRM_MAX }; -extern char configSaveNames[32][MAX_SAVE_NAME_STRING]; +extern char configSaveNames[64][MAX_SAVE_NAME_STRING]; // display settings extern ConfigWindow configWindow; diff --git a/src/pc/djui/djui_panel_host_save.c b/src/pc/djui/djui_panel_host_save.c index 4a68b0ba1..6e5093a15 100644 --- a/src/pc/djui/djui_panel_host_save.c +++ b/src/pc/djui/djui_panel_host_save.c @@ -7,12 +7,22 @@ #include "game/save_file.h" #include "pc/configfile.h" +static struct DjuiFlowLayout* sSaveLayout = NULL; +static struct DjuiPaginated* sSavePaginated = NULL; static struct DjuiInputbox* sSaveNameInputBox = NULL; static struct DjuiBase* sSaveButtonCaller = NULL; static struct DjuiButton* sSaveButtons[NUM_SAVE_FILES] = { NULL }; static s32 sButtonTag = 0; +static bool sEditing = true; static void djui_panel_host_save_update_button(struct DjuiButton* button, int slot); +static void djui_panel_host_save_add_saves(struct DjuiBase* base); + +static void djui_panel_host_reload_saves() { + djui_base_destroy_children(&sSaveLayout->base); + djui_panel_host_save_add_saves(&sSaveLayout->base); + djui_paginated_calculate_height(sSavePaginated); +} static void djui_panel_host_save_save_name_change(UNUSED struct DjuiBase* caller) { snprintf(configSaveNames[sButtonTag], MAX_SAVE_NAME_STRING, "%s", sSaveNameInputBox->buffer); @@ -21,7 +31,23 @@ static void djui_panel_host_save_save_name_change(UNUSED struct DjuiBase* caller } } +static void djui_panel_create_create(struct DjuiBase* caller) { + if (sEditing) { return; } + if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) + fs_sys_mkdir(fs_get_write_path(SAVE_DIRECTORY)); + char filePath[256]; + save_file_get_dir(sButtonTag, filePath, 256); + if (fs_sys_file_exists(fs_get_write_path(filePath))) return; + u8 content[EEPROM_SIZE] = { 0 }; + FILE *fp = fopen(fs_get_write_path(filePath), "wb"); + if (fp == NULL) { return; } + fwrite(content, 1, EEPROM_SIZE, fp); + djui_panel_host_reload_saves(); + djui_panel_menu_back(caller); +} + static bool djui_panel_edit_back(UNUSED struct DjuiBase* caller) { + if (!sEditing) { return false; } if (configSaveNames[sButtonTag][0] == '\0') { snprintf(configSaveNames[sButtonTag], MAX_SAVE_NAME_STRING, "SM64"); } @@ -30,15 +56,15 @@ static bool djui_panel_edit_back(UNUSED struct DjuiBase* caller) { } static void djui_panel_edit_create(struct DjuiBase* caller) { - struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(HOST_SAVE, EDIT_TITLE), false); + struct DjuiThreePanel* panel = djui_panel_menu_create(sEditing ? DLANG(HOST_SAVE, EDIT_TITLE) : DLANG(HOST_SAVE, CREATE_TITLE), false); struct DjuiBase* body = djui_three_panel_get_body(panel); { struct DjuiRect* rect1 = djui_rect_container_create(body, 32); { char buffer[64] = { 0 }; char slotBuffer[4]; - snprintf(slotBuffer, sizeof(slotBuffer), "%d", sButtonTag); - djui_language_replace(DLANG(HOST_SAVE, EDIT_NAME), buffer, 64, '@', slotBuffer); + snprintf(slotBuffer, sizeof(slotBuffer), "%d", sButtonTag + 1); + djui_language_replace(sEditing ? DLANG(HOST_SAVE, EDIT_NAME) : DLANG(HOST_SAVE, CREATE_NAME), buffer, 64, '@', slotBuffer); struct DjuiText* text = djui_text_create(&rect1->base, buffer); djui_base_set_size_type(&text->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); djui_base_set_color(&text->base, 220, 220, 220, 255); @@ -56,6 +82,7 @@ static void djui_panel_edit_create(struct DjuiBase* caller) { djui_interactable_hook_value_change(&sSaveNameInputBox->base, djui_panel_host_save_save_name_change); } + if (!sEditing) djui_button_create(body, DLANG(HOST_SAVE, CREATE), DJUI_BUTTON_STYLE_NORMAL, djui_panel_create_create); djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back); } @@ -63,6 +90,19 @@ static void djui_panel_edit_create(struct DjuiBase* caller) { djui_panel_add(caller, panel, NULL); } +static void djui_panel_save_create(struct DjuiBase* caller) { + sEditing = false; + sButtonTag = save_file_get_first_available_index(); + if (sButtonTag >= NUM_SAVE_FILES) return; + djui_panel_edit_create(caller); +} + +static void djui_panel_host_save_edit(struct DjuiBase* caller) { + sEditing = true; + sButtonTag = caller->tag; + djui_panel_edit_create(caller); +} + static void djui_panel_host_save_update_button(struct DjuiButton* button, int slot) { char starString[64] = { 0 }; if (configSaveNames[slot][0] == '\0') { @@ -82,6 +122,7 @@ static void djui_panel_host_save_erase_yes(struct DjuiBase* caller) { save_file_erase(sButtonTag); play_character_sound(gMarioState, CHAR_SOUND_WAAAOOOW); djui_panel_host_save_update_button(sSaveButtons[sButtonTag], sButtonTag); + djui_panel_host_reload_saves(); djui_panel_menu_back(caller); } @@ -93,9 +134,33 @@ static void djui_panel_host_save_erase(struct DjuiBase* caller) { djui_panel_host_save_erase_yes); } -static void djui_panel_host_save_edit(struct DjuiBase* caller) { - sButtonTag = caller->tag; - djui_panel_edit_create(caller); +void djui_panel_host_save_add_saves(struct DjuiBase* base) { + if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return; + for (int i = 0; i < NUM_SAVE_FILES; i++) { + char filepath[256]; + save_file_get_dir(i, filepath, 256); + if (!fs_sys_file_exists(fs_get_write_path(filepath))) continue; + struct DjuiRect* rect1 = djui_rect_container_create(base, 32); + { + struct DjuiButton* button1 = djui_button_create(&rect1->base, "", DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_button_click); + djui_panel_host_save_update_button(button1, i); + djui_base_set_size(&button1->base, 0.6f, 32); + button1->base.tag = i; + sSaveButtons[i] = button1; + + struct DjuiButton* button2 = djui_button_create(&rect1->base, DLANG(HOST_SAVE, ERASE), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_erase); + button2->base.tag = i; + djui_base_set_size(&button2->base, 0.19f, 32); + djui_base_set_alignment(&button2->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP); + djui_base_set_location(&button2->base, configDjuiThemeCenter ? 127 : button1->rect->base.width.value + 98, 0); + djui_base_set_enabled(&button2->base, gDjuiInMainMenu || gCurrSaveFileNum - 1 != i); + + struct DjuiButton* button3 = djui_button_create(&rect1->base, DLANG(HOST_SAVE, EDIT), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_edit); + button3->base.tag = i; + djui_base_set_size(&button3->base, 0.19f, 32); + djui_base_set_alignment(&button3->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP); + } + } } void djui_panel_host_save_create(struct DjuiBase* caller) { @@ -104,29 +169,14 @@ void djui_panel_host_save_create(struct DjuiBase* caller) { struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(HOST_SAVE, SAVE_TITLE), false); struct DjuiBase* body = djui_three_panel_get_body(panel); { - for (int i = 0; i < NUM_SAVE_FILES; i++) { - struct DjuiRect* rect1 = djui_rect_container_create(body, 32); - { - struct DjuiButton* button1 = djui_button_create(&rect1->base, "", DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_button_click); - djui_panel_host_save_update_button(button1, i); - djui_base_set_size(&button1->base, 0.6f, 32); - button1->base.tag = i; - sSaveButtons[i] = button1; - - struct DjuiButton* button2 = djui_button_create(&rect1->base, DLANG(HOST_SAVE, ERASE), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_erase); - button2->base.tag = i; - djui_base_set_size(&button2->base, 0.19f, 32); - djui_base_set_alignment(&button2->base, DJUI_HALIGN_CENTER, DJUI_VALIGN_TOP); - djui_base_set_location(&button2->base, configDjuiThemeCenter ? 127 : button1->rect->base.width.value + 98, 0); - djui_base_set_enabled(&button2->base, gDjuiInMainMenu || gCurrSaveFileNum - 1 != i); - - struct DjuiButton* button3 = djui_button_create(&rect1->base, DLANG(HOST_SAVE, EDIT), DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_edit); - button3->base.tag = i; - djui_base_set_size(&button3->base, 0.19f, 32); - djui_base_set_alignment(&button3->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP); - } - } + struct DjuiPaginated* paginated = djui_paginated_create(body, 8); + paginated->showMaxCount = true; + sSaveLayout = paginated->layout; + djui_panel_host_save_add_saves(&paginated->layout->base); + djui_paginated_calculate_height(paginated); + sSavePaginated = paginated; + djui_button_create(body, DLANG(HOST_SAVE, CREATE_SAVE), DJUI_BUTTON_STYLE_NORMAL, djui_panel_save_create); djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_NORMAL, djui_panel_menu_back); } diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index ba33bb751..68d9e3c04 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -2997,7 +2997,7 @@ char gSmluaConstants[] = "" "EMBLEM=7\n" "PLAYER_PART_MAX=8\n" "METAL=CAP\n" -"NUM_SAVE_FILES=32\n" +"NUM_SAVE_FILES=64\n" "SAVE_FILE_A=0\n" "SAVE_FILE_B=1\n" "SAVE_FILE_C=2\n" diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c index 9f54e41b0..269c099dd 100644 --- a/src/pc/ultra_reimplementation.c +++ b/src/pc/ultra_reimplementation.c @@ -125,6 +125,28 @@ s32 osEepromProbe(UNUSED OSMesgQueue *mq) { return 1; } +s32 osEepromLongReadLegacy(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) { + if (gOverrideEeprom != NULL) { + memcpy(buffer, gOverrideEeprom + address * 8, nbytes); + return 0; + } + + u8 content[512]; + s32 ret = -1; + + fs_file_t *fp = fs_open(SAVE_FILENAME_OLD); + if (fp == NULL) { + return -1; + } + if (fs_read(fp, content, 512) == 512) { + memcpy(buffer, content + address * 8, nbytes); + ret = 0; + } + fs_close(fp); + + return ret; +} + s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) { if (gOverrideEeprom != NULL) { memcpy(buffer, gOverrideEeprom + address * 8, nbytes);