From e881a04f8645166183cfb6dbeed87cdfed4a658a Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Sun, 1 Mar 2026 19:57:43 -0600
Subject: [PATCH 01/14] YAAAA completely change up save system AUWEGBSLIUFJSAI
---
autogen/convert_functions.py | 3 +-
autogen/lua_definitions/constants.lua | 2 +-
autogen/lua_definitions/functions.lua | 7 -
docs/lua/functions-6.md | 24 ----
docs/lua/functions.md | 1 -
include/PR/os_eeprom.h | 6 +-
src/buffers/buffers.c | 2 +-
src/game/save_file.c | 190 +++-----------------------
src/game/save_file.h | 38 ++----
src/pc/configfile.c | 8 +-
src/pc/configfile.h | 4 +-
src/pc/djui/djui_panel_host_save.c | 9 +-
src/pc/fs/fs.h | 4 +-
src/pc/lua/smlua_constants_autogen.c | 2 +-
src/pc/lua/smlua_functions_autogen.c | 20 ---
src/pc/mods/mod_storage.cpp | 4 +-
src/pc/mods/mod_storage.h | 2 +-
src/pc/network/packets/packet_join.c | 10 +-
src/pc/ultra_reimplementation.c | 29 ++--
19 files changed, 79 insertions(+), 286 deletions(-)
diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index 8846db56f..e34a52c2e 100644
--- a/autogen/convert_functions.py
+++ b/autogen/convert_functions.py
@@ -89,7 +89,7 @@ override_allowed_functions = {
"src/pc/djui/djui_popup.h": [ "create" ],
"src/pc/djui/djui_language.h": [ "djui_language_get" ],
"src/pc/djui/djui_panel_menu.h": [ "djui_menu_get_rainbow_string_color" ],
- "src/game/save_file.h": [ "get_level_", "save_file_get_", "save_file_set_flags", "save_file_clear_flags", "save_file_reload", "save_file_erase_current_backup_save", "save_file_set_star_flags", "save_file_is_cannon_unlocked", "save_file_set_cannon_unlocked", "touch_coin_score_age", "save_file_set_course_coin_score", "save_file_do_save", "save_file_remove_star_flags", "save_file_erase" ],
+ "src/game/save_file.h": [ "get_level_", "save_file_get_", "save_file_set_flags", "save_file_clear_flags", "save_file_reload", "save_file_erase_current_backup_save", "save_file_set_star_flags", "save_file_is_cannon_unlocked", "save_file_set_cannon_unlocked", "save_file_set_course_coin_score", "save_file_do_save", "save_file_remove_star_flags", "save_file_erase" ],
"src/pc/lua/utils/smlua_model_utils.h": [ "smlua_model_util_get_id" ],
"src/game/object_list_processor.h": [ "set_object_respawn_info_bits" ],
"src/game/platform_displacement.h": [ "apply_platform_displacement" ],
@@ -119,6 +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/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 b1fd233f5..7ce77e0fa 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 = 4
+NUM_SAVE_FILES = 32
SAVE_FILE_A = 0 --- @type SaveFileIndex
SAVE_FILE_B = 1 --- @type SaveFileIndex
diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua
index f7b0aa63d..524e2aac0 100644
--- a/autogen/lua_definitions/functions.lua
+++ b/autogen/lua_definitions/functions.lua
@@ -9946,13 +9946,6 @@ function get_level_course_num(levelNum)
-- ...
end
---- @param fileIndex integer
---- @param courseIndex integer
---- Marks the coin score for a specific course as the newest among all save files. Adjusts the age of other scores to reflect the update. Useful for leaderboard tracking or displaying recent progress
-function touch_coin_score_age(fileIndex, courseIndex)
- -- ...
-end
-
--- @param fileIndex integer
--- @param forceSave integer
--- 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/docs/lua/functions-6.md b/docs/lua/functions-6.md
index afd96447e..d3070b04a 100644
--- a/docs/lua/functions-6.md
+++ b/docs/lua/functions-6.md
@@ -4805,30 +4805,6 @@ Gets the level number's corresponding course number
-## [touch_coin_score_age](#touch_coin_score_age)
-
-### Description
-Marks the coin score for a specific course as the newest among all save files. Adjusts the age of other scores to reflect the update. Useful for leaderboard tracking or displaying recent progress
-
-### Lua Example
-`touch_coin_score_age(fileIndex, courseIndex)`
-
-### Parameters
-| Field | Type |
-| ----- | ---- |
-| fileIndex | `integer` |
-| courseIndex | `integer` |
-
-### Returns
-- None
-
-### C Prototype
-`void touch_coin_score_age(s32 fileIndex, s32 courseIndex);`
-
-[:arrow_up_small:](#)
-
-
-
## [save_file_do_save](#save_file_do_save)
### Description
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index e2f94b46b..9804e8424 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -1779,7 +1779,6 @@
- save_file.h
- [get_level_num_from_course_num](functions-6.md#get_level_num_from_course_num)
- [get_level_course_num](functions-6.md#get_level_course_num)
- - [touch_coin_score_age](functions-6.md#touch_coin_score_age)
- [save_file_do_save](functions-6.md#save_file_do_save)
- [save_file_erase](functions-6.md#save_file_erase)
- [save_file_erase_current_backup_save](functions-6.md#save_file_erase_current_backup_save)
diff --git a/include/PR/os_eeprom.h b/include/PR/os_eeprom.h
index b3bca8144..63a9e14c9 100644
--- a/include/PR/os_eeprom.h
+++ b/include/PR/os_eeprom.h
@@ -21,7 +21,7 @@
/*---------------------------------------------------------------------*
Copyright (C) 1998 Nintendo. (Originated by SGI)
-
+
$RCSfile: os_eeprom.h,v $
$Revision: 1.1 $
$Date: 1998/10/09 08:01:06 $
@@ -94,8 +94,8 @@ extern "C" {
extern s32 osEepromProbe(OSMesgQueue *);
extern s32 osEepromRead(OSMesgQueue *, u8, u8 *);
extern s32 osEepromWrite(OSMesgQueue *, u8, u8 *);
-extern s32 osEepromLongRead(OSMesgQueue *, u8, u8 *, int);
-extern s32 osEepromLongWrite(OSMesgQueue *, u8, u8 *, int);
+extern s32 osEepromLongRead(OSMesgQueue *, u8, u8, u8 *, int);
+extern s32 osEepromLongWrite(OSMesgQueue *, u8, u8, u8 *, int);
#endif /* defined(_LANGUAGE_C) || defined(_LANGUAGE_C_PLUS_PLUS) */
diff --git a/src/buffers/buffers.c b/src/buffers/buffers.c
index 063aa7bdd..fb918d5a2 100644
--- a/src/buffers/buffers.c
+++ b/src/buffers/buffers.c
@@ -23,7 +23,7 @@ ALIGNED8 u8 gThread6Stack[0x2000];
ALIGNED8 u8 gGfxSPTaskStack[SP_DRAM_STACK_SIZE8] = { 0 };
// 0xc00 bytes for f3dex, 0x900 otherwise
ALIGNED8 u8 gGfxSPTaskYieldBuffer[OS_YIELD_DATA_SIZE] = { 0 };
-// 0x200 bytes
+// 0x80 bytes
ALIGNED8 struct SaveBuffer gSaveBuffer = { 0 };
// 0x190a0 bytes
struct GfxPool gGfxPools[GFX_NUM_POOLS] = { 0 };
diff --git a/src/game/save_file.c b/src/game/save_file.c
index 91d9350fb..0d692a9e8 100644
--- a/src/game/save_file.c
+++ b/src/game/save_file.c
@@ -20,7 +20,6 @@
#define bcopy(b1,b2,len) (memmove((b2), (b1), (len)), (void) 0)
#endif
-#define MENU_DATA_MAGIC 0x4849
#define SAVE_FILE_MAGIC 0x4441
#define INVALID_FILE_INDEX(_fi) ((u32)_fi >= NUM_SAVE_FILES)
@@ -29,13 +28,12 @@
#define INVALID_COURSE_STAR_INDEX(_ci) ((u32)_ci >= COURSE_COUNT)
#define INVALID_COURSE_COIN_INDEX(_ci) ((u32)_ci >= COURSE_STAGES_COUNT)
-STATIC_ASSERT(sizeof(struct SaveBuffer) == EEPROM_SIZE, "eeprom buffer size must match");
+STATIC_ASSERT(sizeof(struct SingleSaveFile) == EEPROM_SIZE, "eeprom buffer size must match");
extern struct SaveBuffer gSaveBuffer;
struct WarpCheckpoint gWarpCheckpoint;
-s8 gMainMenuDataModified;
s8 gSaveFileModified;
u8 gLastCompletedCourseNum = COURSE_NONE;
@@ -100,10 +98,9 @@ s8 get_level_course_num(s16 levelNum) {
return gLevelToCourseNumTable[levelNum];
}
-// This was probably used to set progress to 100% for debugging, but
-// it was removed from the release ROM.
-static void stub_save_file_1(void) {
- UNUSED s32 pad;
+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);
}
/**
@@ -114,19 +111,6 @@ static inline void bswap_signature(struct SaveBlockSignature *data) {
data->chksum = BSWAP16(data->chksum); // valid as long as the checksum is a literal sum
}
-/**
- * Byteswap all multibyte fields in a MainMenuSaveData.
- */
-static inline void bswap_menudata(struct MainMenuSaveData *data) {
- for (s32 i = 0; i < NUM_SAVE_FILES; ++i)
- data->coinScoreAges[i] = BSWAP32(data->coinScoreAges[i]);
- data->soundMode = BSWAP16(data->soundMode);
-#ifdef VERSION_EU
- data->language = BSWAP16(data->language);
-#endif
- bswap_signature(&data->signature);
-}
-
/**
* Byteswap all multibyte fields in a SaveFile.
*/
@@ -144,17 +128,17 @@ static inline void bswap_savefile(struct SaveFile *data) {
* 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.
*/
-static s32 read_eeprom_data(void *buffer, s32 size) {
+static s32 read_eeprom_data(u8 file, void *buffer, s32 size) {
s32 status = 0;
if (gEepromProbe != 0) {
s32 triesLeft = 4;
- u32 offset = (u32)((u8 *) buffer - (u8 *) &gSaveBuffer) / 8;
+ u32 offset = (u32)((u8 *) buffer - (u8 *) &gSaveBuffer.files[file]) / 8;
do {
block_until_rumble_pak_free();
triesLeft--;
- status = osEepromLongRead(&gSIEventMesgQueue, offset, buffer, size);
+ status = osEepromLongRead(&gSIEventMesgQueue, file, offset, buffer, size);
release_rumble_pak_control();
} while (triesLeft > 0 && status != 0);
}
@@ -168,7 +152,7 @@ static s32 read_eeprom_data(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(void *buffer, s32 size, const uintptr_t baseofs) {
+static s32 write_eeprom_data(u8 file, void *buffer, s32 size, const uintptr_t baseofs) {
s32 status = 1;
if (gEepromProbe != 0) {
@@ -178,7 +162,7 @@ static s32 write_eeprom_data(void *buffer, s32 size, const uintptr_t baseofs) {
do {
block_until_rumble_pak_free();
triesLeft--;
- status = osEepromLongWrite(&gSIEventMesgQueue, offset, buffer, size);
+ status = osEepromLongWrite(&gSIEventMesgQueue, file, offset, buffer, size);
release_rumble_pak_control();
} while (triesLeft > 0 && status != 0);
}
@@ -194,32 +178,16 @@ static inline s32 write_eeprom_savefile(const u32 file, const u32 slot, const u3
if (INVALID_FILE_INDEX(file)) { return 0; }
if (INVALID_SRC_SLOT(slot)) { return 0; }
// calculate the EEPROM address using the file number and slot
- const uintptr_t ofs = (u8*)&gSaveBuffer.files[file][slot] - (u8*)&gSaveBuffer;
+ const uintptr_t ofs = (u8*)&gSaveBuffer.files[file][slot] - (u8*)&gSaveBuffer.files[file];
#if IS_BIG_ENDIAN
- return write_eeprom_data(&gSaveBuffer.files[file][slot], num * sizeof(struct SaveFile), ofs);
+ return write_eeprom_data(file, &gSaveBuffer.files[file][slot], num * sizeof(struct SaveFile), ofs);
#else
// byteswap the data and then write it
struct SaveFile sf[num];
bcopy(&gSaveBuffer.files[file][slot], sf, num * sizeof(sf[0]));
for (u32 i = 0; i < num; ++i) bswap_savefile(&sf[i]);
- return write_eeprom_data(&sf, sizeof(sf), ofs);
-#endif
-}
-
-static inline s32 write_eeprom_menudata(const u32 slot, const u32 num) {
- if (INVALID_SRC_SLOT(slot)) { return 0; }
- // calculate the EEPROM address using the slot
- const uintptr_t ofs = (u8*)&gSaveBuffer.menuData[slot] - (u8*)&gSaveBuffer;
-
-#if IS_BIG_ENDIAN
- return write_eeprom_data(&gSaveBuffer.menuData[slot], num * sizeof(struct MainMenuSaveData), ofs);
-#else
- // byteswap the data and then write it
- struct MainMenuSaveData md[num];
- bcopy(&gSaveBuffer.menuData[slot], md, num * sizeof(md[0]));
- for (u32 i = 0; i < num; ++i) bswap_menudata(&md[i]);
- return write_eeprom_data(&md, sizeof(md), ofs);
+ return write_eeprom_data(file, &sf, sizeof(sf), ofs);
#endif
}
@@ -261,98 +229,6 @@ static void add_save_block_signature(void *buffer, s32 size, u16 magic) {
sig->chksum = calc_checksum(buffer, size);
}
-/**
- * Copy main menu data from one backup slot to the other slot.
- */
-UNUSED static void restore_main_menu_data(s32 srcSlot) {
- if (INVALID_SRC_SLOT(srcSlot)) { return; }
- s32 destSlot = srcSlot ^ 1;
- if (INVALID_SRC_SLOT(destSlot)) { return; }
-
- // Compute checksum on source data
- add_save_block_signature(&gSaveBuffer.menuData[srcSlot], sizeof(gSaveBuffer.menuData[srcSlot]), MENU_DATA_MAGIC);
-
- // Copy source data to destination
- bcopy(&gSaveBuffer.menuData[srcSlot], &gSaveBuffer.menuData[destSlot], sizeof(gSaveBuffer.menuData[destSlot]));
-
- // Write destination data to EEPROM
- write_eeprom_menudata(destSlot, 1);
-}
-
-static void save_main_menu_data(void) {
- if (gMainMenuDataModified) {
- // Compute checksum
- add_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC);
-
- // Back up data
- bcopy(&gSaveBuffer.menuData[0], &gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]));
-
- // Write to EEPROM
- write_eeprom_menudata(0, 2);
-
- gMainMenuDataModified = FALSE;
- }
-}
-
-UNUSED static void wipe_main_menu_data(void) {
- bzero(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]));
-
- // Set score ages for all courses to 3, 2, 1, and 0, respectively.
- gSaveBuffer.menuData[0].coinScoreAges[0] = 0x3FFFFFFF;
- gSaveBuffer.menuData[0].coinScoreAges[1] = 0x2AAAAAAA;
- gSaveBuffer.menuData[0].coinScoreAges[2] = 0x15555555;
-
- gMainMenuDataModified = TRUE;
- save_main_menu_data();
-}
-
-static s32 get_coin_score_age(s32 fileIndex, s32 courseIndex) {
- if (INVALID_FILE_INDEX(fileIndex)) { return 0; }
- return (gSaveBuffer.menuData[0].coinScoreAges[fileIndex] >> (2 * courseIndex)) & 0x3;
-}
-
-static void set_coin_score_age(s32 fileIndex, s32 courseIndex, s32 age) {
- if (INVALID_FILE_INDEX(fileIndex)) { return; }
- s32 mask = 0x3 << (2 * courseIndex);
-
- gSaveBuffer.menuData[0].coinScoreAges[fileIndex] &= ~mask;
- gSaveBuffer.menuData[0].coinScoreAges[fileIndex] |= age << (2 * courseIndex);
-}
-
-/**
- * Mark a coin score for a save file as the newest out of all save files.
- */
-void touch_coin_score_age(s32 fileIndex, s32 courseIndex) {
- if (INVALID_FILE_INDEX(fileIndex)) { return; }
- s32 i;
- u32 age;
- u32 currentAge = get_coin_score_age(fileIndex, courseIndex);
-
- if (currentAge != 0) {
- for (i = 0; i < NUM_SAVE_FILES; i++) {
- age = get_coin_score_age(i, courseIndex);
- if (age < currentAge) {
- set_coin_score_age(i, courseIndex, age + 1);
- }
- }
-
- set_coin_score_age(fileIndex, courseIndex, 0);
- gMainMenuDataModified = TRUE;
- }
-}
-
-/**
- * Mark all coin scores for a save file as new.
- */
-static void touch_high_score_ages(s32 fileIndex) {
- if (INVALID_FILE_INDEX(fileIndex)) { return; }
- s32 i;
-
- for (i = 0; i < 15; i++) {
- touch_coin_score_age(fileIndex, i);
- }
-}
-
/**
* Copy save file data from one backup slot to the other slot.
*/
@@ -380,8 +256,6 @@ UNUSED static void restore_save_file_data(s32 fileIndex, s32 srcSlot) {
static u8 save_file_need_bswap(const struct SaveBuffer *buf) {
// check all signatures just in case
for (s32 i = 0; i < 2; ++i) {
- if (buf->menuData[i].signature.magic == BSWAP16(MENU_DATA_MAGIC))
- return TRUE;
for (s32 j = 0; j < NUM_SAVE_FILES; ++j) {
if (buf->files[j][i].signature.magic == BSWAP16(SAVE_FILE_MAGIC))
return TRUE;
@@ -394,8 +268,6 @@ static u8 save_file_need_bswap(const struct SaveBuffer *buf) {
* Byteswap all multibyte fields in a SaveBuffer.
*/
static void save_file_bswap(struct SaveBuffer *buf) {
- bswap_menudata(buf->menuData + 0);
- bswap_menudata(buf->menuData + 1);
for (s32 i = 0; i < NUM_SAVE_FILES; ++i) {
bswap_savefile(buf->files[i] + 0);
bswap_savefile(buf->files[i] + 1);
@@ -426,13 +298,11 @@ void save_file_do_save(s32 fileIndex, s8 forceSave) {
gSaveFileModified = FALSE;
}
- save_main_menu_data();
}
void save_file_erase(s32 fileIndex) {
if (INVALID_FILE_INDEX(fileIndex)) { return; }
- touch_high_score_ages(fileIndex);
bzero(&gSaveBuffer.files[fileIndex][0], sizeof(gSaveBuffer.files[fileIndex][0]));
bzero(&gSaveBuffer.files[fileIndex][1], sizeof(gSaveBuffer.files[fileIndex][1]));
@@ -465,7 +335,6 @@ BAD_RETURN(s32) save_file_copy(s32 srcFileIndex, s32 destFileIndex) {
if (INVALID_FILE_INDEX(srcFileIndex)) { return; }
if (INVALID_FILE_INDEX(destFileIndex)) { return; }
- touch_high_score_ages(destFileIndex);
bcopy(&gSaveBuffer.files[srcFileIndex][0], &gSaveBuffer.files[destFileIndex][0],
sizeof(gSaveBuffer.files[destFileIndex][0]));
bcopy(&gSaveBuffer.files[srcFileIndex][1], &gSaveBuffer.files[destFileIndex][1],
@@ -478,12 +347,13 @@ BAD_RETURN(s32) save_file_copy(s32 srcFileIndex, s32 destFileIndex) {
void save_file_load_all(UNUSED u8 reload) {
//s32 file;
- gMainMenuDataModified = FALSE;
gSaveFileModified = FALSE;
bzero(&gSaveBuffer, sizeof(gSaveBuffer));
- read_eeprom_data(&gSaveBuffer, sizeof(gSaveBuffer));
+ for (int file = 0; file < NUM_SAVE_FILES; file++) {
+ read_eeprom_data(file, &gSaveBuffer.files[file], sizeof(gSaveBuffer.files[file]));
+ }
if (save_file_need_bswap(&gSaveBuffer))
save_file_bswap(&gSaveBuffer);
@@ -491,19 +361,6 @@ void save_file_load_all(UNUSED u8 reload) {
// Verify the main menu data and create a backup copy if only one of the slots is valid.
/* Disable this so the 'backup' slot can be used
s32 validSlots;
- validSlots = verify_save_block_signature(&gSaveBuffer.menuData[0], sizeof(gSaveBuffer.menuData[0]), MENU_DATA_MAGIC);
- validSlots |= verify_save_block_signature(&gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]),MENU_DATA_MAGIC) << 1;
- switch (validSlots) {
- case 0: // Neither copy is correct
- wipe_main_menu_data();
- break;
- case 1: // Slot 0 is correct and slot 1 is incorrect
- restore_main_menu_data(0);
- break;
- case 2: // Slot 1 is correct and slot 0 is incorrect
- restore_main_menu_data(1);
- break;
- }
for (file = 0; file < NUM_SAVE_FILES; file++) {
// Verify the save file and create a backup copy if only one of the slots is valid.
@@ -522,7 +379,6 @@ void save_file_load_all(UNUSED u8 reload) {
}
}
*/
- stub_save_file_1();
}
/**
@@ -557,7 +413,6 @@ void save_file_collect_star_or_key(s16 coinScore, s16 starIndex, u8 fromNetwork)
if (coinScore > save_file_get_course_coin_score(fileIndex, courseIndex)) {
gSaveBuffer.files[fileIndex][gSaveFileUsingBackupSlot].courseCoinScores[courseIndex] = coinScore;
- touch_coin_score_age(fileIndex, courseIndex);
gGotFileCoinHiScore = TRUE;
gSaveFileModified = TRUE;
@@ -595,24 +450,21 @@ s32 save_file_exists(s32 fileIndex) {
}
/**
- * Get the maximum coin score across all files for a course. The lower 16 bits
+ * Get the maximum coin score across all save files for a course. The lower 16 bits
* of the returned value are the score, and the upper 16 bits are the file number
* of the save file with this score.
*/
u32 save_file_get_max_coin_score(s32 courseIndex) {
s32 fileIndex;
s32 maxCoinScore = -1;
- s32 maxScoreAge = -1;
s32 maxScoreFileNum = 0;
for (fileIndex = 0; fileIndex < NUM_SAVE_FILES; fileIndex++) {
if (save_file_get_star_flags(fileIndex, courseIndex) != 0) {
s32 coinScore = save_file_get_course_coin_score(fileIndex, courseIndex);
- s32 scoreAge = get_coin_score_age(fileIndex, courseIndex);
- if (coinScore > maxCoinScore || (coinScore == maxCoinScore && scoreAge > maxScoreAge)) {
+ if (coinScore > maxCoinScore) {
maxCoinScore = coinScore;
- maxScoreAge = scoreAge;
maxScoreFileNum = fileIndex + 1;
}
}
@@ -805,16 +657,16 @@ s32 save_file_get_cap_pos(VEC_OUT Vec3s capPos) {
return FALSE;
}
-void save_file_set_sound_mode(u16 mode) {
- set_sound_mode(mode);
+void save_file_set_sound_mode(UNUSED u16 mode) {
+ /*set_sound_mode(mode);
gSaveBuffer.menuData[0].soundMode = mode;
gMainMenuDataModified = TRUE;
- save_main_menu_data();
+ save_main_menu_data();*/
}
u16 save_file_get_sound_mode(void) {
- return gSaveBuffer.menuData[0].soundMode;
+ return 0;
}
void save_file_move_cap_to_default_location(void) {
diff --git a/src/game/save_file.h b/src/game/save_file.h
index a5ac6a59e..806ecd779 100644
--- a/src/game/save_file.h
+++ b/src/game/save_file.h
@@ -8,8 +8,9 @@
#include "course_table.h"
-#define EEPROM_SIZE 0x200
-#define NUM_SAVE_FILES 4
+#define NUM_SAVE_FILES 32
+// size of savebuffer
+#define EEPROM_SIZE 128
struct SaveBlockSignature
{
@@ -45,33 +46,18 @@ enum SaveFileIndex {
SAVE_FILE_D
};
-struct MainMenuSaveData
+struct SingleSaveFile
{
- // 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[NUM_SAVE_FILES];
- 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[EEPROM_SIZE / 2 - SUBTRAHEND - NUM_SAVE_FILES * (4 + sizeof(struct SaveFile))];
-
- struct SaveBlockSignature signature;
+ // Each save file has two copies. If one is bad, the other is used as a backup.
+ struct SaveFile files[2];
+ // Filler to make a single save file equal the eeprom size
+ u8 filler[EEPROM_SIZE - (sizeof(struct SaveFile) * 2)];
};
struct SaveBuffer
{
- // Each of the four save files has two copies. If one is bad, the other is used as a backup.
+ // For all save files, each save has two copies. If one is bad, the other is used as a backup.
struct SaveFile files[NUM_SAVE_FILES][2];
- // The main menu data has two copies. If one is bad, the other is used as a backup.
- struct MainMenuSaveData menuData[2];
};
extern u8 gLastCompletedCourseNum;
@@ -134,11 +120,7 @@ s8 get_level_num_from_course_num(s16 courseNum);
/* |description|Gets the level number's corresponding course number|descriptionEnd| */
s8 get_level_course_num(s16 levelNum);
-/* |description|
-Marks the coin score for a specific course as the newest among all save files. Adjusts the age of other scores to reflect the update.
-Useful for leaderboard tracking or displaying recent progress
-|descriptionEnd| */
-void touch_coin_score_age(s32 fileIndex, s32 courseIndex);
+void save_file_get_dir(int fileIndex, char* outPath, size_t size);
/* |description|
Saves the current state of the game into a specified save file. Includes data verification and backup management.
diff --git a/src/pc/configfile.c b/src/pc/configfile.c
index f2267a7db..847f1d907 100644
--- a/src/pc/configfile.c
+++ b/src/pc/configfile.c
@@ -19,7 +19,6 @@
#include "network/moderator_list.h"
#include "debuglog.h"
#include "djui/djui_hud_utils.h"
-#include "game/save_file.h"
#include "pc/network/network_player.h"
#include "pc/pc_main.h"
@@ -58,12 +57,7 @@ struct FunctionConfigOption {
/*
*Config options and default values
*/
-char configSaveNames[4][MAX_SAVE_NAME_STRING] = {
- "SM64",
- "SM64",
- "SM64",
- "SM64"
-};
+char configSaveNames[NUM_SAVE_FILES][MAX_SAVE_NAME_STRING] = { 0 };
// Video/audio stuff
ConfigWindow configWindow = {
diff --git a/src/pc/configfile.h b/src/pc/configfile.h
index d537970e4..330e2c1f0 100644
--- a/src/pc/configfile.h
+++ b/src/pc/configfile.h
@@ -4,6 +4,8 @@
#include
#include
#include "game/player_palette.h"
+#include "pc/lua/smlua_autogen.h"
+#include "game/save_file.h"
#define CONFIGFILE_DEFAULT "sm64config.txt"
#define CONFIGFILE_BACKUP "sm64config-backup.txt"
@@ -43,7 +45,7 @@ enum RefreshRateMode {
RRM_MAX
};
-extern char configSaveNames[4][MAX_SAVE_NAME_STRING];
+extern char configSaveNames[32][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 aab34efad..4a68b0ba1 100644
--- a/src/pc/djui/djui_panel_host_save.c
+++ b/src/pc/djui/djui_panel_host_save.c
@@ -12,8 +12,6 @@ static struct DjuiBase* sSaveButtonCaller = NULL;
static struct DjuiButton* sSaveButtons[NUM_SAVE_FILES] = { NULL };
static s32 sButtonTag = 0;
-static char* sSaveLetters[NUM_SAVE_FILES] = { "A", "B", "C", "D" };
-
static void djui_panel_host_save_update_button(struct DjuiButton* button, int slot);
static void djui_panel_host_save_save_name_change(UNUSED struct DjuiBase* caller) {
@@ -38,7 +36,9 @@ static void djui_panel_edit_create(struct DjuiBase* caller) {
struct DjuiRect* rect1 = djui_rect_container_create(body, 32);
{
char buffer[64] = { 0 };
- djui_language_replace(DLANG(HOST_SAVE, EDIT_NAME), buffer, 64, '@', sSaveLetters[sButtonTag]);
+ char slotBuffer[4];
+ snprintf(slotBuffer, sizeof(slotBuffer), "%d", sButtonTag);
+ djui_language_replace(DLANG(HOST_SAVE, EDIT_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);
@@ -65,6 +65,9 @@ static void djui_panel_edit_create(struct DjuiBase* caller) {
static void djui_panel_host_save_update_button(struct DjuiButton* button, int slot) {
char starString[64] = { 0 };
+ if (configSaveNames[slot][0] == '\0') {
+ snprintf(configSaveNames[slot], MAX_SAVE_NAME_STRING, "SM64");
+ }
snprintf(starString, 64, "%c x%d - %s", '~' + 1, save_file_get_total_star_count(slot, 0, 24), configSaveNames[slot]);
djui_text_set_text(button->text, starString);
}
diff --git a/src/pc/fs/fs.h b/src/pc/fs/fs.h
index a5b9d06ba..c2b784538 100644
--- a/src/pc/fs/fs.h
+++ b/src/pc/fs/fs.h
@@ -8,7 +8,9 @@
#include "../platform.h"
-#define SAVE_FILENAME "sm64_save_file.bin"
+#define SAVE_FILENAME_OLD "sm64_save_file.bin"
+#define SAVE_DIRECTORY "saves/"
+#define SAVE_FILENAME "sm64coopdx_save_file_"
extern char fs_writepath[];
diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c
index c7fde7802..ba33bb751 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=4\n"
+"NUM_SAVE_FILES=32\n"
"SAVE_FILE_A=0\n"
"SAVE_FILE_B=1\n"
"SAVE_FILE_C=2\n"
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index c4b6b4f0e..e4ac40a77 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -29727,25 +29727,6 @@ int smlua_func_get_level_course_num(lua_State* L) {
return 1;
}
-int smlua_func_touch_coin_score_age(lua_State* L) {
- if (L == NULL) { return 0; }
-
- int top = lua_gettop(L);
- if (top != 2) {
- LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "touch_coin_score_age", 2, top);
- return 0;
- }
-
- s32 fileIndex = smlua_to_integer(L, 1);
- if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "touch_coin_score_age"); return 0; }
- s32 courseIndex = smlua_to_integer(L, 2);
- if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "touch_coin_score_age"); return 0; }
-
- touch_coin_score_age(fileIndex, courseIndex);
-
- return 1;
-}
-
int smlua_func_save_file_do_save(lua_State* L) {
if (L == NULL) { return 0; }
@@ -38334,7 +38315,6 @@ void smlua_bind_functions_autogen(void) {
// save_file.h
smlua_bind_function(L, "get_level_num_from_course_num", smlua_func_get_level_num_from_course_num);
smlua_bind_function(L, "get_level_course_num", smlua_func_get_level_course_num);
- smlua_bind_function(L, "touch_coin_score_age", smlua_func_touch_coin_score_age);
smlua_bind_function(L, "save_file_do_save", smlua_func_save_file_do_save);
smlua_bind_function(L, "save_file_erase", smlua_func_save_file_erase);
smlua_bind_function(L, "save_file_erase_current_backup_save", smlua_func_save_file_erase_current_backup_save);
diff --git a/src/pc/mods/mod_storage.cpp b/src/pc/mods/mod_storage.cpp
index e64a9ff2a..4e4f42a24 100644
--- a/src/pc/mods/mod_storage.cpp
+++ b/src/pc/mods/mod_storage.cpp
@@ -61,7 +61,7 @@ static bool char_valid(const char* buffer, bool isKey) {
}
static void mod_storage_get_filename(char* dest) {
- const char* path = fs_get_write_path(SAVE_DIRECTORY); // get user path
+ const char* path = fs_get_write_path(MS_SAVE_DIRECTORY); // get user path
snprintf(dest, SYS_MAX_PATH - 1, "%s/%s", path, gLuaActiveMod->relativePath); // append sav folder
strdelete(dest, ".lua"); // delete ".lua" from sav name
strcat(dest, SAVE_EXTENSION); // append SAVE_EXTENSION
@@ -88,7 +88,7 @@ static bool mod_storage_check_inputs(const char *key, const char *value, char *f
if (!char_valid(value, false)) { return false; }
// write: ensure savPath exists
- const char* savPath = fs_get_write_path(SAVE_DIRECTORY);
+ const char* savPath = fs_get_write_path(MS_SAVE_DIRECTORY);
if (!fs_sys_dir_exists(savPath)) { fs_sys_mkdir(savPath); }
}
diff --git a/src/pc/mods/mod_storage.h b/src/pc/mods/mod_storage.h
index ee41af4a4..0c1d555d0 100644
--- a/src/pc/mods/mod_storage.h
+++ b/src/pc/mods/mod_storage.h
@@ -10,7 +10,7 @@ extern "C" {
#define MAX_KEYS 4096
#define MAX_KEY_VALUE_LENGTH 1024
-#define SAVE_DIRECTORY "sav"
+#define MS_SAVE_DIRECTORY "sav"
#define SAVE_EXTENSION ".sav"
/* |description|Saves a `key` corresponding to a string `value` to mod storage|descriptionEnd| */
diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c
index 0f1602352..29b665e1c 100644
--- a/src/pc/network/packets/packet_join.c
+++ b/src/pc/network/packets/packet_join.c
@@ -28,7 +28,7 @@
#include "pc/lua/utils/smlua_misc_utils.h"
extern u8* gOverrideEeprom;
-static u8 eeprom[512] = { 0 };
+static u8 eeprom[EEPROM_SIZE] = { 0 };
static u8 sJoinRequestPlayerModel;
static struct PlayerPalette sJoinRequestPlayerPalette;
@@ -100,9 +100,9 @@ void network_send_join(struct Packet* joinRequestPacket) {
// do connection event
network_player_connected(NPT_CLIENT, globalIndex, sJoinRequestPlayerModel, &sJoinRequestPlayerPalette, sJoinRequestPlayerName, sJoinRequestDiscordId);
- fs_file_t* fp = fs_open(SAVE_FILENAME);
+ fs_file_t* fp = fs_open(SAVE_FILENAME_OLD);
if (fp != NULL) {
- fs_read(fp, eeprom, 512);
+ fs_read(fp, eeprom, EEPROM_SIZE);
fs_close(fp);
}
@@ -126,7 +126,7 @@ void network_send_join(struct Packet* joinRequestPacket) {
packet_write(&p, &gServerSettings.maxPlayers, sizeof(u8));
packet_write(&p, &gServerSettings.pauseAnywhere, sizeof(u8));
packet_write(&p, &gServerSettings.pvpType, sizeof(u8));
- packet_write(&p, eeprom, sizeof(u8) * 512);
+ packet_write(&p, eeprom, sizeof(u8) * EEPROM_SIZE);
network_send_to(globalIndex, &p);
LOG_INFO("sending join packet");
@@ -179,7 +179,7 @@ void network_receive_join(struct Packet* p) {
packet_read(p, &gServerSettings.maxPlayers, sizeof(u8));
packet_read(p, &gServerSettings.pauseAnywhere, sizeof(u8));
packet_read(p, &gServerSettings.pvpType, sizeof(u8));
- packet_read(p, eeprom, sizeof(u8) * 512);
+ packet_read(p, eeprom, sizeof(u8) * EEPROM_SIZE);
network_player_connected(NPT_SERVER, 0, 0, &DEFAULT_MARIO_PALETTE, "Player", "0");
network_player_connected(NPT_LOCAL, myGlobalIndex, configPlayerModel, &configPlayerPalette, configPlayerName, get_local_discord_id());
diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c
index 230f7e747..9f54e41b0 100644
--- a/src/pc/ultra_reimplementation.c
+++ b/src/pc/ultra_reimplementation.c
@@ -4,6 +4,7 @@
#include "macros.h"
#include "platform.h"
#include "fs/fs.h"
+#include "game/save_file.h"
u8* gOverrideEeprom = NULL;
@@ -124,20 +125,22 @@ s32 osEepromProbe(UNUSED OSMesgQueue *mq) {
return 1;
}
-s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) {
+s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) {
if (gOverrideEeprom != NULL) {
memcpy(buffer, gOverrideEeprom + address * 8, nbytes);
return 0;
}
- u8 content[512];
+ u8 content[EEPROM_SIZE];
s32 ret = -1;
- fs_file_t *fp = fs_open(SAVE_FILENAME);
+ char filePath[256];
+ save_file_get_dir(fileIndex, filePath, 256);
+ fs_file_t *fp = fs_open(filePath);
if (fp == NULL) {
return -1;
}
- if (fs_read(fp, content, 512) == 512) {
+ if (fs_read(fp, content, EEPROM_SIZE) == EEPROM_SIZE) {
memcpy(buffer, content + address * 8, nbytes);
ret = 0;
}
@@ -146,23 +149,29 @@ s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes)
return ret;
}
-s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) {
+s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) {
if (gOverrideEeprom != NULL) {
memcpy(gOverrideEeprom + address * 8, buffer, nbytes);
return 0;
}
- u8 content[512] = { 0 };
- if (address != 0 || nbytes != 512) {
- osEepromLongRead(mq, 0, content, 512);
+ u8 content[EEPROM_SIZE] = { 0 };
+ if (address != 0 || nbytes != EEPROM_SIZE) {
+ osEepromLongRead(mq, fileIndex, 0, content, EEPROM_SIZE);
}
memcpy(content + address * 8, buffer, nbytes);
- FILE *fp = fopen(fs_get_write_path(SAVE_FILENAME), "wb");
+ 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(fileIndex, filePath, 256);
+ FILE *fp = fopen(fs_get_write_path(filePath), "wb");
if (fp == NULL) {
return -1;
}
- s32 ret = fwrite(content, 1, 512, fp) == 512 ? 0 : -1;
+ s32 ret = fwrite(content, 1, EEPROM_SIZE, fp) == EEPROM_SIZE ? 0 : -1;
fclose(fp);
return ret;
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 02/14] 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);
From 99beef174f0251e4a9dfde698846cab332fc86cd Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Sun, 1 Mar 2026 23:26:22 -0600
Subject: [PATCH 03/14] syncing still not working :(((
---
src/pc/network/packets/packet_join.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c
index 29b665e1c..78d0d99c4 100644
--- a/src/pc/network/packets/packet_join.c
+++ b/src/pc/network/packets/packet_join.c
@@ -100,7 +100,9 @@ void network_send_join(struct Packet* joinRequestPacket) {
// do connection event
network_player_connected(NPT_CLIENT, globalIndex, sJoinRequestPlayerModel, &sJoinRequestPlayerPalette, sJoinRequestPlayerName, sJoinRequestDiscordId);
- fs_file_t* fp = fs_open(SAVE_FILENAME_OLD);
+ char filePath[256];
+ save_file_get_dir(gCurrSaveFileNum - 1, filePath, 256);
+ fs_file_t* fp = fs_open(fs_get_write_path(filePath));
if (fp != NULL) {
fs_read(fp, eeprom, EEPROM_SIZE);
fs_close(fp);
From 00e298db332d000280c7bb4d1a221006eeff6616 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Sun, 1 Mar 2026 23:29:09 -0600
Subject: [PATCH 04/14] [build] Now it should work (i still need to test with
another pc(weewoo))
---
src/pc/network/packets/packet_join.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c
index 78d0d99c4..300cdb40f 100644
--- a/src/pc/network/packets/packet_join.c
+++ b/src/pc/network/packets/packet_join.c
@@ -102,7 +102,7 @@ void network_send_join(struct Packet* joinRequestPacket) {
char filePath[256];
save_file_get_dir(gCurrSaveFileNum - 1, filePath, 256);
- fs_file_t* fp = fs_open(fs_get_write_path(filePath));
+ fs_file_t* fp = fs_open(filePath);
if (fp != NULL) {
fs_read(fp, eeprom, EEPROM_SIZE);
fs_close(fp);
From fc5ad1ead8b12da4928b588febd5805e9655bbcf Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Mon, 2 Mar 2026 15:58:05 -0600
Subject: [PATCH 05/14] make names stored inside save efile, some bug fixes,
still need to do syncing
---
autogen/convert_functions.py | 2 +-
src/game/save_file.c | 92 ++++++++++++++++++++++++----
src/game/save_file.h | 7 ++-
src/pc/configfile.c | 26 --------
src/pc/configfile.h | 5 +-
src/pc/djui/djui_panel_host.c | 4 +-
src/pc/djui/djui_panel_host_save.c | 92 ++++++++++++++++++++--------
src/pc/fs/fs.h | 4 +-
src/pc/mods/mod_storage.cpp | 2 +-
src/pc/mods/mod_storage.h | 2 +-
src/pc/network/packets/packet_join.c | 2 +-
src/pc/ultra_reimplementation.c | 6 +-
12 files changed, 167 insertions(+), 77 deletions(-)
diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index 0c2449f81..1168aecd2 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", "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" ],
"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/src/game/save_file.c b/src/game/save_file.c
index 78576cb36..eafc0a458 100644
--- a/src/game/save_file.c
+++ b/src/game/save_file.c
@@ -14,6 +14,8 @@
#include "macros.h"
#include "pc/network/network.h"
#include "pc/lua/utils/smlua_level_utils.h"
+#include "pc/mods/mod.h"
+#include "pc/mods/mods_utils.h"
#include "pc/utils/misc.h"
#ifndef bcopy
@@ -269,15 +271,88 @@ 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);
+/**
+ * Converts old 512 byte save files into 4 new 128 byte save files
+ */
+static void save_file_convert_old_to_new() {
+ struct LegacySaveBuffer saveBuffer = { 0 };
+ s32 status = osEepromLongReadLegacy(&gSIEventMesgQueue, 0, (void*)&saveBuffer, sizeof(saveBuffer));
+ if (status == 0) {
+ for (int i = 0; i < 4; i++)
+ write_eeprom_data(i, saveBuffer.files[i], sizeof(saveBuffer.files[i]), 0);
+ }
}
+/**
+ * Gets all save file names
+*/
+void save_file_get_all_filenames(char filenames[NUM_SAVE_FILES][MAX_SAVE_NAME_STRING]) {
+ char* directory = (char*)fs_get_write_path(SAVE_DIRECTORY);
+ if (!directory) return;
+
+ if (!fs_sys_dir_exists(directory)) return;
+ struct dirent* dir = NULL;
+
+ DIR* d = opendir(directory);
+ if (!d) { return; }
+
+ // iterate
+ char path[SYS_MAX_PATH] = { 0 };
+ while ((dir = readdir(d)) != NULL) {
+ // sanity check
+ if (!directory_sanity_check(dir, directory, path)) continue;
+ snprintf(path, SYS_MAX_PATH, "%s", dir->d_name);
+ if (strlen(path) == 0 || strlen(path) >= 256) continue;
+
+ // verify filename follows format (index)_(name)(SAVE_EXTENSION)
+ int index = 0;
+ char name[MAX_SAVE_NAME_STRING];
+ char extension[12];
+ if (sscanf(path, "%d_%31[^.]%11s", &index, name, extension) == 3) {
+ if (index < 0 || index >= NUM_SAVE_FILES) continue;
+ if (strlen(name) == 0) continue;
+ if (strcmp(extension, SAVE_EXTENSION) != 0) continue;
+ snprintf(filenames[index], 256, "%s", name);
+ }
+ }
+
+ closedir(d);
+}
+
+/**
+ * Gets save file name for index. If it does not exist, use SM64
+*/
+void save_file_get_filename_at_index(int fileIndex, char outFilename[MAX_SAVE_NAME_STRING]) {
+ char filenames[NUM_SAVE_FILES][MAX_SAVE_NAME_STRING] = { 0 };
+ save_file_get_all_filenames(filenames);
+ if (filenames[fileIndex] && filenames[fileIndex][0] != '\0') {
+ snprintf(outFilename, MAX_SAVE_NAME_STRING, "%s", filenames[fileIndex]);
+ } else {
+ snprintf(outFilename, MAX_SAVE_NAME_STRING, "%s", "SM64");
+ }
+}
+
+/**
+ * Get directory for a save file at index
+*/
+void save_file_get_dir(int fileIndex, char* outPath, size_t size, char* overrideName) {
+ char name[MAX_SAVE_NAME_STRING] = { 0 };
+ if (overrideName == NULL) {
+ save_file_get_filename_at_index(fileIndex, name);
+ } else {
+ snprintf(name, MAX_SAVE_NAME_STRING, "%s", overrideName);
+ }
+ snprintf(outPath, size, "%s%d_%s%s", SAVE_DIRECTORY, fileIndex, name, SAVE_EXTENSION);
+}
+
+/**
+ * Gets the first available index that is not being used
+*/
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);
+ save_file_get_dir(i, filePath, 256, NULL);
if (!fs_sys_file_exists(fs_get_write_path(filePath))) return i;
}
return NUM_SAVE_FILES;
@@ -317,7 +392,7 @@ void save_file_erase(s32 fileIndex) {
if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return;
char filepath[256];
- save_file_get_dir(fileIndex, filepath, 256);
+ save_file_get_dir(fileIndex, filepath, 256, NULL);
if (!fs_sys_file_exists(fs_get_write_path(filepath))) return;
remove(fs_get_write_path(filepath));
}
@@ -362,14 +437,7 @@ 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);
- }
+ save_file_convert_old_to_new();
}
bzero(&gSaveBuffer, sizeof(gSaveBuffer));
diff --git a/src/game/save_file.h b/src/game/save_file.h
index ae9ced518..ac9d9105a 100644
--- a/src/game/save_file.h
+++ b/src/game/save_file.h
@@ -9,6 +9,7 @@
#include "course_table.h"
#define NUM_SAVE_FILES 64
+#define MAX_SAVE_NAME_STRING 32
// size of savebuffer
#define EEPROM_SIZE 128
@@ -149,7 +150,11 @@ s8 get_level_num_from_course_num(s16 courseNum);
/* |description|Gets the level number's corresponding course number|descriptionEnd| */
s8 get_level_course_num(s16 levelNum);
-void save_file_get_dir(int fileIndex, char* outPath, size_t size);
+void save_file_get_all_filenames(char filenames[NUM_SAVE_FILES][MAX_SAVE_NAME_STRING]);
+
+void save_file_get_filename_at_index(int fileIndex, char outFilename[MAX_SAVE_NAME_STRING]);
+
+void save_file_get_dir(int fileIndex, char* outPath, size_t size, char* overrideName);
s32 save_file_get_first_available_index();
diff --git a/src/pc/configfile.c b/src/pc/configfile.c
index 847f1d907..f6a8f2891 100644
--- a/src/pc/configfile.c
+++ b/src/pc/configfile.c
@@ -57,7 +57,6 @@ struct FunctionConfigOption {
/*
*Config options and default values
*/
-char configSaveNames[NUM_SAVE_FILES][MAX_SAVE_NAME_STRING] = { 0 };
// Video/audio stuff
ConfigWindow configWindow = {
@@ -522,36 +521,11 @@ static void dynos_pack_write(FILE* file) {
}
}
-static void save_name_read(char** tokens, int numTokens) {
- if (numTokens < 2) { return; }
- char fullSaveName[MAX_SAVE_NAME_STRING] = { 0 };
- int index = 0;
- for (int i = 1; i < numTokens; i++) {
- if (i == 1) {
- index = atoi(tokens[i]);
- } else {
- if (i > 2) {
- strncat(fullSaveName, " ", MAX_SAVE_NAME_STRING - 1);
- }
- strncat(fullSaveName, tokens[i], MAX_SAVE_NAME_STRING - 1);
- }
-
- }
- snprintf(configSaveNames[index], MAX_SAVE_NAME_STRING, "%s", fullSaveName);
-}
-
-static void save_name_write(FILE* file) {
- for (int i = 0; i < NUM_SAVE_FILES; i++) {
- fprintf(file, "%s %d %s\n", "save-name:", i, configSaveNames[i]);
- }
-}
-
static const struct FunctionConfigOption functionOptions[] = {
{ .name = "enable-mod:", .read = enable_mod_read, .write = enable_mod_write },
{ .name = "ban:", .read = ban_read, .write = ban_write },
{ .name = "moderator:", .read = moderator_read, .write = moderator_write },
{ .name = "dynos-pack:", .read = dynos_pack_read, .write = dynos_pack_write },
- { .name = "save-name:", .read = save_name_read, .write = save_name_write }
};
// Reads an entire line from a file (excluding the newline character) and returns an allocated string
diff --git a/src/pc/configfile.h b/src/pc/configfile.h
index c5b0cccf7..c513b0d32 100644
--- a/src/pc/configfile.h
+++ b/src/pc/configfile.h
@@ -5,7 +5,7 @@
#include
#include "game/player_palette.h"
#include "pc/lua/smlua_autogen.h"
-#include "game/save_file.h"
+#include "game/area.h"
#define CONFIGFILE_DEFAULT "sm64config.txt"
#define CONFIGFILE_BACKUP "sm64config-backup.txt"
@@ -13,7 +13,6 @@
#define MAX_BINDS 3
#define MAX_VOLUME 127
#define MAX_CONFIG_STRING 64
-#define MAX_SAVE_NAME_STRING 32
#define DEFAULT_PORT 7777
#define DEFAULT_COOPNET_IP "net.coop64.us"
@@ -45,8 +44,6 @@ enum RefreshRateMode {
RRM_MAX
};
-extern char configSaveNames[64][MAX_SAVE_NAME_STRING];
-
// display settings
extern ConfigWindow configWindow;
extern ConfigStick configStick;
diff --git a/src/pc/djui/djui_panel_host.c b/src/pc/djui/djui_panel_host.c
index b1b069e4b..760b5ff9f 100644
--- a/src/pc/djui/djui_panel_host.c
+++ b/src/pc/djui/djui_panel_host.c
@@ -173,7 +173,9 @@ void djui_panel_host_create(struct DjuiBase* caller) {
djui_text_set_drop_shadow(text1, 64, 64, 64, 100);
char starString[64] = { 0 };
- snprintf(starString, 64, "%c x%d - %s", '~' + 1, save_file_get_total_star_count(configHostSaveSlot - 1, 0, 24), configSaveNames[configHostSaveSlot - 1]);
+ char saveName[MAX_SAVE_NAME_STRING] = { 0 };
+ save_file_get_filename_at_index(configHostSaveSlot - 1, saveName);
+ snprintf(starString, 64, "%c x%d - %s", '~' + 1, save_file_get_total_star_count(configHostSaveSlot - 1, 0, 24), saveName);
struct DjuiButton* button1 = djui_button_create(&rect2->base, starString, DJUI_BUTTON_STYLE_NORMAL, djui_panel_host_save_create);
djui_base_set_size(&button1->base, 0.45f, 32);
djui_base_set_alignment(&button1->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP);
diff --git a/src/pc/djui/djui_panel_host_save.c b/src/pc/djui/djui_panel_host_save.c
index 6e5093a15..7fd0d60bc 100644
--- a/src/pc/djui/djui_panel_host_save.c
+++ b/src/pc/djui/djui_panel_host_save.c
@@ -12,6 +12,7 @@ static struct DjuiPaginated* sSavePaginated = NULL;
static struct DjuiInputbox* sSaveNameInputBox = NULL;
static struct DjuiBase* sSaveButtonCaller = NULL;
static struct DjuiButton* sSaveButtons[NUM_SAVE_FILES] = { NULL };
+static char sSaveName[MAX_SAVE_NAME_STRING] = { 0 };
static s32 sButtonTag = 0;
static bool sEditing = true;
@@ -24,35 +25,74 @@ static void djui_panel_host_reload_saves() {
djui_paginated_calculate_height(sSavePaginated);
}
+static void djui_panel_host_save_update_save_name() {
+ if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return;
+ if (strstr(sSaveName, ".")) return;
+ char filePath[256];
+ save_file_get_dir(sButtonTag, filePath, 256, NULL);
+ char newFilePath[256];
+ save_file_get_dir(sButtonTag, newFilePath, 256, sSaveName);
+ if (strcmp(filePath, newFilePath) == 0) return;
+ if (!fs_sys_file_exists(fs_get_write_path(filePath))) return;
+ // write the save data of the file to a variable
+ u8 content[EEPROM_SIZE] = { 0 };
+ fs_file_t* oldFile = fs_open(filePath);
+ if (oldFile == NULL) return;
+ fs_read(oldFile, content, EEPROM_SIZE);
+ fs_close(oldFile);
+ // create a new file with the data
+ FILE* fp = fopen(fs_get_write_path(newFilePath), "wb");
+ if (fp == NULL) return;
+ bool success = fwrite(content, 1, EEPROM_SIZE, fp) == EEPROM_SIZE;
+ fclose(fp);
+ if (success) {
+ // nuke old file
+ remove(fs_get_write_path(filePath));
+ } else {
+ // uh oh! New file failed to be written to. Nuke new file
+ remove(fs_get_write_path(newFilePath));
+ }
+
+ djui_panel_host_reload_saves();
+}
+
static void djui_panel_host_save_save_name_change(UNUSED struct DjuiBase* caller) {
- snprintf(configSaveNames[sButtonTag], MAX_SAVE_NAME_STRING, "%s", sSaveNameInputBox->buffer);
+ snprintf(sSaveName, MAX_SAVE_NAME_STRING, "%s", sSaveNameInputBox->buffer);
if (strlen(sSaveNameInputBox->buffer) >= 64) {
- djui_inputbox_set_text(sSaveNameInputBox, configSaveNames[sButtonTag]);
+ djui_inputbox_set_text(sSaveNameInputBox, sSaveName);
+ }
+
+ if (strstr(sSaveName, ".")) {
+ djui_inputbox_set_text_color(sSaveNameInputBox, 255, 0, 0, 255);
+ } else {
+ djui_inputbox_set_text_color(sSaveNameInputBox, 0, 0, 0, 255);
}
}
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);
+ save_file_get_dir(sButtonTag, filePath, 256, sSaveName);
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; }
+ if (fp == NULL) return;
fwrite(content, 1, EEPROM_SIZE, fp);
+ fclose(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");
+static void djui_panel_edit_save(UNUSED struct DjuiBase* caller) {
+ if (sSaveName[0] == '\0' || strstr(sSaveName, ".")) {
+ snprintf(sSaveName, MAX_SAVE_NAME_STRING, "SM64");
+ djui_panel_menu_back(caller);
+ return;
}
+ djui_panel_host_save_update_save_name();
djui_panel_host_save_update_button(sSaveButtons[sButtonTag], sButtonTag);
- return false;
+ djui_panel_menu_back(caller);
}
static void djui_panel_edit_create(struct DjuiBase* caller) {
@@ -76,17 +116,18 @@ static void djui_panel_edit_create(struct DjuiBase* caller) {
djui_base_set_size_type(&sSaveNameInputBox->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE);
djui_base_set_size(&sSaveNameInputBox->base, 0.45f, 32);
djui_base_set_alignment(&sSaveNameInputBox->base, DJUI_HALIGN_RIGHT, DJUI_VALIGN_TOP);
- char saveName[MAX_SAVE_NAME_STRING] = { 0 };
- snprintf(saveName, MAX_SAVE_NAME_STRING, "%s", configSaveNames[sButtonTag]);
- djui_inputbox_set_text(sSaveNameInputBox, saveName);
+ save_file_get_filename_at_index(sButtonTag, sSaveName);
+ djui_inputbox_set_text(sSaveNameInputBox, sSaveName);
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);
+ if (!sEditing)
+ djui_button_create(body, DLANG(HOST_SAVE, CREATE), DJUI_BUTTON_STYLE_NORMAL, djui_panel_create_create);
+ else
+ djui_button_create(body, DLANG(HOST_SAVE, EDIT), DJUI_BUTTON_STYLE_NORMAL, djui_panel_edit_save);
djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back);
}
- panel->on_back = djui_panel_edit_back;
djui_panel_add(caller, panel, NULL);
}
@@ -104,14 +145,19 @@ static void djui_panel_host_save_edit(struct DjuiBase* caller) {
}
static void djui_panel_host_save_update_button(struct DjuiButton* button, int slot) {
+ if (!button || !button->text) return;
char starString[64] = { 0 };
- if (configSaveNames[slot][0] == '\0') {
- snprintf(configSaveNames[slot], MAX_SAVE_NAME_STRING, "SM64");
- }
- snprintf(starString, 64, "%c x%d - %s", '~' + 1, save_file_get_total_star_count(slot, 0, 24), configSaveNames[slot]);
+ char name[MAX_SAVE_NAME_STRING] = { 0 };
+ save_file_get_filename_at_index(slot, name);
+ snprintf(starString, 64, "%c x%d - %s", '~' + 1, save_file_get_total_star_count(slot, 0, 24), name);
djui_text_set_text(button->text, starString);
}
+static bool djui_panel_host_save_back(UNUSED struct DjuiBase* caller) {
+ djui_panel_host_save_update_button((struct DjuiButton*)sSaveButtonCaller, configHostSaveSlot - 1);
+ return false;
+}
+
static void djui_panel_host_save_button_click(struct DjuiBase* caller) {
configHostSaveSlot = caller->tag + 1;
djui_panel_host_save_update_button((struct DjuiButton*)sSaveButtonCaller, configHostSaveSlot - 1);
@@ -128,17 +174,14 @@ static void djui_panel_host_save_erase_yes(struct DjuiBase* caller) {
static void djui_panel_host_save_erase(struct DjuiBase* caller) {
sButtonTag = caller->tag;
- djui_panel_confirm_create(caller,
- DLANG(HOST_SAVE, ERASE_TITLE),
- DLANG(HOST_SAVE, CONFIRM),
- djui_panel_host_save_erase_yes);
+ djui_panel_confirm_create(caller, DLANG(HOST_SAVE, ERASE_TITLE), DLANG(HOST_SAVE, CONFIRM), djui_panel_host_save_erase_yes);
}
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);
+ save_file_get_dir(i, filepath, 256, NULL);
if (!fs_sys_file_exists(fs_get_write_path(filepath))) continue;
struct DjuiRect* rect1 = djui_rect_container_create(base, 32);
{
@@ -180,5 +223,6 @@ void djui_panel_host_save_create(struct DjuiBase* caller) {
djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_NORMAL, djui_panel_menu_back);
}
+ panel->on_back = djui_panel_host_save_back;
djui_panel_add(caller, panel, NULL);
}
diff --git a/src/pc/fs/fs.h b/src/pc/fs/fs.h
index c2b784538..5b2bdc445 100644
--- a/src/pc/fs/fs.h
+++ b/src/pc/fs/fs.h
@@ -8,9 +8,9 @@
#include "../platform.h"
-#define SAVE_FILENAME_OLD "sm64_save_file.bin"
+#define SAVE_FILENAME "sm64_save_file.bin"
#define SAVE_DIRECTORY "saves/"
-#define SAVE_FILENAME "sm64coopdx_save_file_"
+#define SAVE_EXTENSION ".bin"
extern char fs_writepath[];
diff --git a/src/pc/mods/mod_storage.cpp b/src/pc/mods/mod_storage.cpp
index 4e4f42a24..707c1a1e7 100644
--- a/src/pc/mods/mod_storage.cpp
+++ b/src/pc/mods/mod_storage.cpp
@@ -64,7 +64,7 @@ static void mod_storage_get_filename(char* dest) {
const char* path = fs_get_write_path(MS_SAVE_DIRECTORY); // get user path
snprintf(dest, SYS_MAX_PATH - 1, "%s/%s", path, gLuaActiveMod->relativePath); // append sav folder
strdelete(dest, ".lua"); // delete ".lua" from sav name
- strcat(dest, SAVE_EXTENSION); // append SAVE_EXTENSION
+ strcat(dest, MS_SAVE_EXTENSION); // append MS_SAVE_EXTENSION
normalize_path(dest); // fix any out of place slashes
}
diff --git a/src/pc/mods/mod_storage.h b/src/pc/mods/mod_storage.h
index 0c1d555d0..e00bd0e6e 100644
--- a/src/pc/mods/mod_storage.h
+++ b/src/pc/mods/mod_storage.h
@@ -11,7 +11,7 @@ extern "C" {
#define MAX_KEYS 4096
#define MAX_KEY_VALUE_LENGTH 1024
#define MS_SAVE_DIRECTORY "sav"
-#define SAVE_EXTENSION ".sav"
+#define MS_SAVE_EXTENSION ".sav"
/* |description|Saves a `key` corresponding to a string `value` to mod storage|descriptionEnd| */
bool mod_storage_save(const char* key, const char* value);
diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c
index 300cdb40f..8895e9af3 100644
--- a/src/pc/network/packets/packet_join.c
+++ b/src/pc/network/packets/packet_join.c
@@ -101,7 +101,7 @@ void network_send_join(struct Packet* joinRequestPacket) {
network_player_connected(NPT_CLIENT, globalIndex, sJoinRequestPlayerModel, &sJoinRequestPlayerPalette, sJoinRequestPlayerName, sJoinRequestDiscordId);
char filePath[256];
- save_file_get_dir(gCurrSaveFileNum - 1, filePath, 256);
+ save_file_get_dir(gCurrSaveFileNum - 1, filePath, 256, NULL);
fs_file_t* fp = fs_open(filePath);
if (fp != NULL) {
fs_read(fp, eeprom, EEPROM_SIZE);
diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c
index 269c099dd..9db65fef0 100644
--- a/src/pc/ultra_reimplementation.c
+++ b/src/pc/ultra_reimplementation.c
@@ -134,7 +134,7 @@ s32 osEepromLongReadLegacy(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int n
u8 content[512];
s32 ret = -1;
- fs_file_t *fp = fs_open(SAVE_FILENAME_OLD);
+ fs_file_t *fp = fs_open(SAVE_FILENAME);
if (fp == NULL) {
return -1;
}
@@ -157,7 +157,7 @@ s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffe
s32 ret = -1;
char filePath[256];
- save_file_get_dir(fileIndex, filePath, 256);
+ save_file_get_dir(fileIndex, filePath, 256, NULL);
fs_file_t *fp = fs_open(filePath);
if (fp == NULL) {
return -1;
@@ -188,7 +188,7 @@ s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buff
}
char filePath[256];
- save_file_get_dir(fileIndex, filePath, 256);
+ save_file_get_dir(fileIndex, filePath, 256, NULL);
FILE *fp = fopen(fs_get_write_path(filePath), "wb");
if (fp == NULL) {
return -1;
From e7f06691a548a6b9aa272d045308783a1166306e Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Mon, 2 Mar 2026 21:57:24 -0600
Subject: [PATCH 06/14] syncing (should) work now
---
src/pc/network/network.c | 2 +
src/pc/network/packets/packet.c | 2 +
src/pc/network/packets/packet.h | 12 ++-
src/pc/network/packets/packet_download.c | 2 +-
.../packets/packet_download_save_files.c | 97 +++++++++++++++++++
src/pc/network/packets/packet_reliable.c | 2 +
6 files changed, 114 insertions(+), 3 deletions(-)
create mode 100644 src/pc/network/packets/packet_download_save_files.c
diff --git a/src/pc/network/network.c b/src/pc/network/network.c
index 6bd1d4882..31f880991 100644
--- a/src/pc/network/network.c
+++ b/src/pc/network/network.c
@@ -239,6 +239,8 @@ bool network_allow_unknown_local_index(enum PacketType packetType) {
|| (packetType == PACKET_MOD_LIST_ENTRY)
|| (packetType == PACKET_MOD_LIST_FILE)
|| (packetType == PACKET_MOD_LIST_DONE)
+ || (packetType == PACKET_DOWNLOAD_SAVE_REQUEST)
+ || (packetType == PACKET_DOWNLOAD_SAVE_FILE)
|| (packetType == PACKET_DOWNLOAD_REQUEST)
|| (packetType == PACKET_DOWNLOAD)
|| (packetType == PACKET_KEEP_ALIVE)
diff --git a/src/pc/network/packets/packet.c b/src/pc/network/packets/packet.c
index 199a88e5a..67dd43fa6 100644
--- a/src/pc/network/packets/packet.c
+++ b/src/pc/network/packets/packet.c
@@ -95,6 +95,8 @@ void packet_process(struct Packet* p) {
case PACKET_MODERATOR: network_receive_moderator(p); break;
case PACKET_KEEP_ALIVE: network_receive_keep_alive(p); break;
case PACKET_LEAVING: network_receive_leaving(p); break;
+ case PACKET_DOWNLOAD_SAVE_REQUEST: network_receive_download_saves_request(p); break;
+ case PACKET_DOWNLOAD_SAVE_FILE: network_receive_download_save(p); break;
case PACKET_SAVE_FILE: network_receive_save_file(p); break;
case PACKET_SAVE_SET_FLAG: network_receive_save_set_flag(p); break;
case PACKET_SAVE_REMOVE_FLAG: network_receive_save_remove_flag(p); break;
diff --git a/src/pc/network/packets/packet.h b/src/pc/network/packets/packet.h
index 5ab859760..48290584f 100644
--- a/src/pc/network/packets/packet.h
+++ b/src/pc/network/packets/packet.h
@@ -31,6 +31,9 @@ enum PacketType {
PACKET_KICK,
PACKET_KEEP_ALIVE,
PACKET_LEAVING,
+ PACKET_DOWNLOAD_SAVE_REQUEST,
+ PACKET_DOWNLOAD_SAVE_FILE,
+ PACKET_FINISHED_DOWNLOAD_SAVE_FILE,
PACKET_SAVE_FILE,
PACKET_SAVE_SET_FLAG,
PACKET_SAVE_REMOVE_FLAG,
@@ -74,7 +77,7 @@ enum PacketType {
PACKET_LUA_CUSTOM,
PACKET_LUA_CUSTOM_BYTESTRING,
-
+
PACKET_COMMAND,
PACKET_MODERATOR,
@@ -256,6 +259,12 @@ void network_receive_keep_alive(struct Packet* p);
void network_send_leaving(u8 globalIndex);
void network_receive_leaving(struct Packet* p);
+// packet_download_save_files.c
+void network_send_download_save_files_request(void);
+void network_receive_download_saves_request(UNUSED struct Packet* p);
+void network_send_download_save(int chunk);
+void network_receive_download_save(struct Packet* p);
+
// packet_save_file.c
void network_send_save_file(s32 fileIndex);
void network_receive_save_file(struct Packet* p);
@@ -308,7 +317,6 @@ void network_send_area_request(struct NetworkPlayer* fromNp, struct NetworkPlaye
void network_receive_area_request(struct Packet* p);
// packet_area.c
-
void area_remove_sync_ids_add(u32 syncId);
void area_remove_sync_ids_clear(void);
void network_send_area(struct NetworkPlayer* toNp);
diff --git a/src/pc/network/packets/packet_download.c b/src/pc/network/packets/packet_download.c
index ab5aa4917..b10ab9606 100644
--- a/src/pc/network/packets/packet_download.c
+++ b/src/pc/network/packets/packet_download.c
@@ -199,7 +199,7 @@ static void network_update_offset_groups(void) {
mod->enabled = true;
}
LOG_INFO("Download complete!");
- network_send_join_request();
+ network_send_download_save_files_request();
return;
}
diff --git a/src/pc/network/packets/packet_download_save_files.c b/src/pc/network/packets/packet_download_save_files.c
new file mode 100644
index 000000000..1dafb1383
--- /dev/null
+++ b/src/pc/network/packets/packet_download_save_files.c
@@ -0,0 +1,97 @@
+#include
+#include "../network.h"
+#include "PR/os_eeprom.h"
+#include "game/save_file.h"
+#include "pc/debuglog.h"
+
+extern u8* gOverrideEeprom;
+static u8 eeprom[NUM_SAVE_FILES][EEPROM_SIZE] = { 0 };
+static int filledEepromData = 0;
+static int chunks = (NUM_SAVE_FILES * EEPROM_SIZE + (PACKET_LENGTH - 8) - 1) / (PACKET_LENGTH - 8);
+
+void network_send_download_save_files_request(void) {
+ SOFT_ASSERT(gNetworkType == NT_CLIENT);
+
+ filledEepromData = 0;
+
+ struct Packet p = { 0 };
+ packet_init(&p, PACKET_DOWNLOAD_SAVE_REQUEST, true, PLMT_NONE);
+
+ network_send_to((gNetworkPlayerServer != NULL) ? gNetworkPlayerServer->localIndex : 0, &p);
+ LOG_INFO("sending download save files request");
+}
+
+void network_receive_download_saves_request(UNUSED struct Packet* p) {
+ SOFT_ASSERT(gNetworkType == NT_SERVER);
+
+ for (int i = 0; i < chunks; i++) {
+ network_send_download_save(i);
+ }
+
+ LOG_INFO("sending save info");
+}
+
+void network_send_download_save(int chunk) {
+ SOFT_ASSERT(gNetworkType == NT_SERVER);
+
+ int startingSaveFile = chunk * (NUM_SAVE_FILES + chunks - 1) / chunks;
+ int endSaveFile = (chunk + 1) * (NUM_SAVE_FILES + chunks - 1) / chunks;
+ if (endSaveFile > NUM_SAVE_FILES) endSaveFile = NUM_SAVE_FILES;
+
+ struct Packet p = { 0 };
+ packet_init(&p, PACKET_DOWNLOAD_SAVE_FILE, true, PLMT_NONE);
+ packet_write(&p, &startingSaveFile, sizeof(startingSaveFile));
+ packet_write(&p, &endSaveFile, sizeof(endSaveFile));
+
+ for (int i = startingSaveFile; i < endSaveFile; i++) {
+ u8 content[EEPROM_SIZE] = { 0 };
+ char filePath[256];
+ save_file_get_dir(i, filePath, 256, NULL);
+ fs_file_t* fp = fs_open(filePath);
+ if (fp != NULL) {
+ fs_read(fp, content, EEPROM_SIZE);
+ fs_close(fp);
+ }
+ packet_write(&p, content, sizeof(content));
+ }
+
+ network_send_to(0, &p);
+}
+
+void network_receive_download_save(struct Packet* p) {
+ SOFT_ASSERT(gNetworkType == NT_CLIENT);
+
+ if (p->localIndex != UNKNOWN_LOCAL_INDEX) {
+ if (gNetworkPlayerServer == NULL || gNetworkPlayerServer->localIndex != p->localIndex) {
+ LOG_ERROR("Received download from known local index '%d'", p->localIndex);
+ return;
+ }
+ }
+
+ if (filledEepromData >= NUM_SAVE_FILES * EEPROM_SIZE) {
+ LOG_ERROR("Received eeprom data after eeprom was filled");
+ djui_popup_create(DLANG(NOTIF, DISCONNECT_CLOSED), 1);
+ network_shutdown(false, false, false, false);
+ return;
+ }
+
+ int startingSaveFile = 0;
+ int endSaveFile = 0;
+ packet_read(p, &startingSaveFile, sizeof(startingSaveFile));
+ packet_read(p, &endSaveFile, sizeof(endSaveFile));
+
+ for (int i = startingSaveFile; i < endSaveFile; i++) {
+ packet_read(p, &eeprom[i], sizeof(eeprom[i]));
+ filledEepromData += EEPROM_SIZE;
+ }
+
+ if (filledEepromData == NUM_SAVE_FILES * EEPROM_SIZE) {
+ filledEepromData = 0;
+ network_send_join_request();
+ } else if (filledEepromData > NUM_SAVE_FILES * EEPROM_SIZE) {
+ LOG_ERROR("Filled eeprom data too much! Should be %d, but is %d", NUM_SAVE_FILES * EEPROM_SIZE, filledEepromData);
+ djui_popup_create(DLANG(NOTIF, DISCONNECT_CLOSED), 1);
+ network_shutdown(false, false, false, false);
+ return;
+ }
+}
diff --git a/src/pc/network/packets/packet_reliable.c b/src/pc/network/packets/packet_reliable.c
index a1176a311..1486b1165 100644
--- a/src/pc/network/packets/packet_reliable.c
+++ b/src/pc/network/packets/packet_reliable.c
@@ -130,6 +130,8 @@ void network_remember_reliable(struct Packet* p) {
static float adjust_max_elapsed(enum PacketType packetType, float maxElapsed) {
switch (packetType) {
+ case PACKET_DOWNLOAD_SAVE_REQUEST:
+ case PACKET_DOWNLOAD_SAVE_FILE:
case PACKET_DOWNLOAD_REQUEST:
case PACKET_DOWNLOAD:
case PACKET_MOD_LIST_REQUEST:
From 1a44cf70534a8f2421664224de4788dcc4b82176 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Mon, 2 Mar 2026 22:17:58 -0600
Subject: [PATCH 07/14] modify `gOverrideEeprom`
---
src/pc/network/network.c | 9 +++++----
.../packets/packet_download_save_files.c | 5 ++++-
src/pc/network/packets/packet_join.c | 19 +------------------
src/pc/ultra_reimplementation.c | 15 +++++----------
4 files changed, 15 insertions(+), 33 deletions(-)
diff --git a/src/pc/network/network.c b/src/pc/network/network.c
index 31f880991..c4b581d27 100644
--- a/src/pc/network/network.c
+++ b/src/pc/network/network.c
@@ -34,6 +34,7 @@
#include "game/first_person_cam.h"
#include "game/envfx_snow.h"
#include "game/mario.h"
+#include "game/save_file.h"
#include "engine/math_util.h"
#include "engine/lighting_engine.h"
#include "src/audio/load.h"
@@ -169,8 +170,8 @@ bool network_init(enum NetworkType inNetworkType, bool reconnecting) {
dynos_behavior_hook_all_custom_behaviors();
network_player_connected(NPT_LOCAL, 0, configPlayerModel, &configPlayerPalette, configPlayerName, get_local_discord_id());
- extern u8* gOverrideEeprom;
- gOverrideEeprom = NULL;
+ extern u8* gOverrideEeprom[NUM_SAVE_FILES];
+ memset(gOverrideEeprom, 0, sizeof(gOverrideEeprom));
if (gCurrLevelNum != (s16)gLevelValues.entryLevel) {
extern s16 gChangeLevelTransition;
@@ -705,8 +706,8 @@ void network_shutdown(bool sendLeaving, bool exiting, bool popup, bool reconnect
dynos_model_clear_pool(MODEL_POOL_SESSION);
// reset other stuff
- extern u8* gOverrideEeprom;
- gOverrideEeprom = NULL;
+ extern u8* gOverrideEeprom[NUM_SAVE_FILES];
+ memset(gOverrideEeprom, 0, sizeof(gOverrideEeprom));
extern u8 gOverrideFreezeCamera;
gOverrideFreezeCamera = false;
gDjuiHudLockMouse = false;
diff --git a/src/pc/network/packets/packet_download_save_files.c b/src/pc/network/packets/packet_download_save_files.c
index 1dafb1383..9f9d738db 100644
--- a/src/pc/network/packets/packet_download_save_files.c
+++ b/src/pc/network/packets/packet_download_save_files.c
@@ -4,7 +4,7 @@
#include "game/save_file.h"
#include "pc/debuglog.h"
-extern u8* gOverrideEeprom;
+extern u8* gOverrideEeprom[NUM_SAVE_FILES];
static u8 eeprom[NUM_SAVE_FILES][EEPROM_SIZE] = { 0 };
static int filledEepromData = 0;
static int chunks = (NUM_SAVE_FILES * EEPROM_SIZE + (PACKET_LENGTH - 8) - 1) / (PACKET_LENGTH - 8);
@@ -86,6 +86,9 @@ void network_receive_download_save(struct Packet* p) {
}
if (filledEepromData == NUM_SAVE_FILES * EEPROM_SIZE) {
+ for (int i = 0; i < NUM_SAVE_FILES; i++) {
+ gOverrideEeprom[i] = eeprom[i];
+ }
filledEepromData = 0;
network_send_join_request();
} else if (filledEepromData > NUM_SAVE_FILES * EEPROM_SIZE) {
diff --git a/src/pc/network/packets/packet_join.c b/src/pc/network/packets/packet_join.c
index 8895e9af3..3cf5700c7 100644
--- a/src/pc/network/packets/packet_join.c
+++ b/src/pc/network/packets/packet_join.c
@@ -27,10 +27,7 @@
#include "pc/configfile.h"
#include "pc/lua/utils/smlua_misc_utils.h"
-extern u8* gOverrideEeprom;
-static u8 eeprom[EEPROM_SIZE] = { 0 };
-
-static u8 sJoinRequestPlayerModel;
+static u8 sJoinRequestPlayerModel;
static struct PlayerPalette sJoinRequestPlayerPalette;
static char sJoinRequestPlayerName[MAX_CONFIG_STRING];
static char sJoinRequestDiscordId[64];
@@ -40,7 +37,6 @@ void network_send_join_request(void) {
SOFT_ASSERT(gNetworkType == NT_CLIENT);
gNetworkSentJoin = true;
- gOverrideEeprom = eeprom;
struct Packet p = { 0 };
packet_init(&p, PACKET_JOIN_REQUEST, true, PLMT_NONE);
@@ -99,15 +95,6 @@ void network_send_join(struct Packet* joinRequestPacket) {
// do connection event
network_player_connected(NPT_CLIENT, globalIndex, sJoinRequestPlayerModel, &sJoinRequestPlayerPalette, sJoinRequestPlayerName, sJoinRequestDiscordId);
-
- char filePath[256];
- save_file_get_dir(gCurrSaveFileNum - 1, filePath, 256, NULL);
- fs_file_t* fp = fs_open(filePath);
- if (fp != NULL) {
- fs_read(fp, eeprom, EEPROM_SIZE);
- fs_close(fp);
- }
-
char version[MAX_VERSION_LENGTH] = { 0 };
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
LOG_INFO("sending version: %s", version);
@@ -128,7 +115,6 @@ void network_send_join(struct Packet* joinRequestPacket) {
packet_write(&p, &gServerSettings.maxPlayers, sizeof(u8));
packet_write(&p, &gServerSettings.pauseAnywhere, sizeof(u8));
packet_write(&p, &gServerSettings.pvpType, sizeof(u8));
- packet_write(&p, eeprom, sizeof(u8) * EEPROM_SIZE);
network_send_to(globalIndex, &p);
LOG_INFO("sending join packet");
@@ -142,8 +128,6 @@ void network_receive_join(struct Packet* p) {
LOG_INFO("received join packet");
gCurrentlyJoining = true;
- gOverrideEeprom = eeprom;
-
char version[MAX_VERSION_LENGTH] = { 0 };
snprintf(version, MAX_VERSION_LENGTH, "%s", get_version());
LOG_INFO("client has version: %s", version);
@@ -181,7 +165,6 @@ void network_receive_join(struct Packet* p) {
packet_read(p, &gServerSettings.maxPlayers, sizeof(u8));
packet_read(p, &gServerSettings.pauseAnywhere, sizeof(u8));
packet_read(p, &gServerSettings.pvpType, sizeof(u8));
- packet_read(p, eeprom, sizeof(u8) * EEPROM_SIZE);
network_player_connected(NPT_SERVER, 0, 0, &DEFAULT_MARIO_PALETTE, "Player", "0");
network_player_connected(NPT_LOCAL, myGlobalIndex, configPlayerModel, &configPlayerPalette, configPlayerName, get_local_discord_id());
diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c
index 9db65fef0..dccc55cd6 100644
--- a/src/pc/ultra_reimplementation.c
+++ b/src/pc/ultra_reimplementation.c
@@ -6,7 +6,7 @@
#include "fs/fs.h"
#include "game/save_file.h"
-u8* gOverrideEeprom = NULL;
+u8* gOverrideEeprom[NUM_SAVE_FILES] = { NULL };
extern OSMgrArgs piMgrArgs;
@@ -126,11 +126,6 @@ s32 osEepromProbe(UNUSED OSMesgQueue *mq) {
}
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;
@@ -148,8 +143,8 @@ s32 osEepromLongReadLegacy(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int n
}
s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) {
- if (gOverrideEeprom != NULL) {
- memcpy(buffer, gOverrideEeprom + address * 8, nbytes);
+ if (gOverrideEeprom[fileIndex] != NULL) {
+ memcpy(buffer, gOverrideEeprom[fileIndex] + address * 8, nbytes);
return 0;
}
@@ -172,8 +167,8 @@ s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffe
}
s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 fileIndex, u8 address, u8 *buffer, int nbytes) {
- if (gOverrideEeprom != NULL) {
- memcpy(gOverrideEeprom + address * 8, buffer, nbytes);
+ if (gOverrideEeprom[fileIndex] != NULL) {
+ memcpy(gOverrideEeprom[fileIndex] + address * 8, buffer, nbytes);
return 0;
}
From 93e64a11a2063e2ac7df7f5ba0501dddc6dc6c95 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Mon, 2 Mar 2026 22:19:31 -0600
Subject: [PATCH 08/14] Add german and portuguese translations
---
lang/English.ini | 6 +++---
lang/German.ini | 4 ++++
lang/Portuguese.ini | 6 +++++-
3 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/lang/English.ini b/lang/English.ini
index 175ff3822..4a6a98e9b 100644
--- a/lang/English.ini
+++ b/lang/English.ini
@@ -219,13 +219,13 @@ SAVE_TITLE = "SAVE"
ERASE_TITLE = "ERASE"
CONFIRM = "Are you sure you want to erase this save slot?"
ERASE = "Erase"
+EDIT = "Edit"
+EDIT_TITLE = "EDIT"
+EDIT_NAME = "Edit Save File @'s Name:"
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:"
[HOST_SETTINGS]
SETTINGS = "SETTINGS"
diff --git a/lang/German.ini b/lang/German.ini
index 46d0bf440..eb3ddd644 100644
--- a/lang/German.ini
+++ b/lang/German.ini
@@ -222,6 +222,10 @@ ERASE = "Löschen"
EDIT = "Bearbeiten"
EDIT_TITLE = "BEARBEITEN"
EDIT_NAME = "Bearbeite den Namen des Spielstandes @:"
+CREATE_SAVE = "Spielstand erstellen"
+CREATE = "Erstellen"
+CREATE_TITLE = "ERSTELLEN"
+CREATE_NAME = "Name von Spielstand @ festlegen:"
[HOST_SETTINGS]
SETTINGS = "EINSTELLUNGEN"
diff --git a/lang/Portuguese.ini b/lang/Portuguese.ini
index e935499a6..4035e9467 100644
--- a/lang/Portuguese.ini
+++ b/lang/Portuguese.ini
@@ -222,6 +222,10 @@ EDIT_TITLE = "EDITAR ARQUIVO"
EDIT_NAME = "Editar nome do arquivo salvo @:"
CONFIRM = "Quer mesmo limpar este arquivo?"
ERASE = "Limpar"
+CREATE_SAVE = "Criar Arquivo"
+CREATE = "Criar"
+CREATE_TITLE = "CRIAR"
+CREATE_NAME = "Nome do Arquivo @:"
[HOST_SETTINGS]
SETTINGS = "CONFIGURAÇÕES"
@@ -412,7 +416,7 @@ MUTE_FOCUS_LOSS = "Silenciar quando a janela estiver desfocada"
[LANGUAGE]
LANGUAGE = "IDIOMA"
-Czech = "Tcheco (Čeština)"
+Czech = "Tcheco (Čeština)"
Dutch = "Holandês (Nederlands)"
French = "Francês (Français)"
German = "Alemão (Deutsch)"
From a63398ccbf6ac9308002f42de63a1fcb1f9e8513 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Tue, 3 Mar 2026 11:28:32 -0600
Subject: [PATCH 09/14] Allow save files to be imported via drag and drop
---
autogen/convert_functions.py | 2 +-
include/PR/os_eeprom.h | 4 +--
src/game/save_file.c | 39 +++++++++++++++++++++++-----
src/game/save_file.h | 9 ++++++-
src/pc/configfile.c | 10 +++++++
src/pc/mods/mod_import.c | 46 +++++++++++++++++++++++++++++++++
src/pc/ultra_reimplementation.c | 16 ++++++------
7 files changed, 108 insertions(+), 18 deletions(-)
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);
From b7b7f1288d59bcbe0be3c9c5957ea4e9af4b6f1c Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Tue, 3 Mar 2026 11:30:16 -0600
Subject: [PATCH 10/14] Add english lang
---
lang/English.ini | 1 +
1 file changed, 1 insertion(+)
diff --git a/lang/English.ini b/lang/English.ini
index 4a6a98e9b..ff78d8ffd 100644
--- a/lang/English.ini
+++ b/lang/English.ini
@@ -18,6 +18,7 @@ DEBUG_FLY = "@ entered debug free-fly mode"
IMPORT_MOD_SUCCESS = "\\#a0ffa0\\Imported mod\n\\#dcdcdc\\'@'"
IMPORT_DYNOS_SUCCESS = "\\#a0ffa0\\Imported DynOS pack\n\\#dcdcdc\\'@'"
IMPORT_PALETTE_SUCCESS = "\\#a0ffa0\\Imported palette preset\n\\#dcdcdc\\'@'"
+IMPORT_SAVE_SUCCESS = "\\#a0ffa0\\Imported save\n\\#dcdcdc\\'@'"
IMPORT_FAIL = "\\#ffa0a0\\Failed to import\n\\#dcdcdc\\'@'"
IMPORT_FAIL_INGAME = "\\#ffa0a0\\Can not import while in-game"
COOPNET_CONNECTION_FAILED = "\\#ffa0a0\\Could not connect to CoopNet!"
From 74fef4cfc169bab70c520c10a24c2f8faf2b7ff3 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Tue, 3 Mar 2026 15:07:03 -0600
Subject: [PATCH 11/14] Finish german and portuguese translations
---
lang/German.ini | 1 +
lang/Portuguese.ini | 1 +
2 files changed, 2 insertions(+)
diff --git a/lang/German.ini b/lang/German.ini
index eb3ddd644..03a093e4d 100644
--- a/lang/German.ini
+++ b/lang/German.ini
@@ -18,6 +18,7 @@ DEBUG_FLY = "@ hat den Debug-Free-Fly-Modus aktiviert."
IMPORT_MOD_SUCCESS = "\\#a0ffa0\\Mod erfolgreich importiert\n\\#dcdcdc\\'@'"
IMPORT_DYNOS_SUCCESS = "\\#a0ffa0\\DynOS-Paket erfolgreich importiert\n\\#dcdcdc\\'@'"
IMPORT_PALETTE_SUCCESS = "\\#a0ffa0\\Paletten-Voreinstellung erfolgreich importiert\n\\#dcdcdc\\'@'"
+IMPORT_SAVE_SUCCESS = "\\#a0ffa0\\Spielstand importiert:\n\\#dcdcdc\\'@'"
IMPORT_FAIL = "\\#ffa0a0\\Importierung fehlgeschlagen\n\\#dcdcdc\\'@'"
IMPORT_FAIL_INGAME = "\\#ffa0a0\\Ein Fehler beim Importieren ist aufgetreten."
COOPNET_CONNECTION_FAILED = "\\#ffa0a0\\Es konnte keine Verbindung zu CoopNet hergestellt werden!"
diff --git a/lang/Portuguese.ini b/lang/Portuguese.ini
index 4035e9467..3450c92f5 100644
--- a/lang/Portuguese.ini
+++ b/lang/Portuguese.ini
@@ -18,6 +18,7 @@ DEBUG_FLY = "@ entrou no modo de voo livre de debug"
IMPORT_MOD_SUCCESS = "\\#dcdcdc\\'@'\n\\#a0ffa0\\Mod importado"
IMPORT_DYNOS_SUCCESS = "\\#dcdcdc\\'@'\n\\#a0ffa0\\Pacote DynOS importado"
IMPORT_PALETTE_SUCCESS = "\\#dcdcdc\\'@'\n\\#a0ffa0\\Paleta importada"
+IMPORT_SAVE_SUCCESS = "\\#a0ffa0\\Arquivo importado\n\\#dcdcdc\\'@'"
IMPORT_FAIL = "\\#dcdcdc\\'@'\n\\#ffa0a0\\Erro ao importar"
IMPORT_FAIL_INGAME = "\\#ffa0a0\\Não é possível importar\ndurante o jogo"
COOPNET_CONNECTION_FAILED = "\\#ffa0a0\\Não foi possível se conectar à CoopNet!"
From 422c0bc1dc64188460b379e8d4ddbd5e1f9b5303 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Tue, 10 Mar 2026 18:40:07 -0500
Subject: [PATCH 12/14] Add russian translation
---
lang/Russian.ini | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lang/Russian.ini b/lang/Russian.ini
index 52861a4cf..34e698d81 100644
--- a/lang/Russian.ini
+++ b/lang/Russian.ini
@@ -18,6 +18,7 @@ DEBUG_FLY = "@ вошел в режим полета при отладке"
IMPORT_MOD_SUCCESS = "\\#a0ffa0\\Импортирован мод\n\\#dcdcdc\\'@'"
IMPORT_DYNOS_SUCCESS = "\\#a0ffa0\\Импортированнан пакет DynOS\n\\#dcdcdc\\'@'"
IMPORT_PALETTE_SUCCESS = "\\#a0ffa0\\Импортированный предустановленный набор палитр\n\\#dcdcdc\\'@'"
+IMPORT_SAVE_SUCCESS = "\\#a0ffa0\\Импортировано сохранение\n\\#dcdcdc\\'@'"
IMPORT_FAIL = "\\#ffa0a0\\Не удалось импортировать\n\\#dcdcdc\\'@'"
IMPORT_FAIL_INGAME = "\\#ffa0a0\\Невозможно импортировать во время игры"
COOPNET_CONNECTION_FAILED = "\\#ffa0a0\\Не удалось установить соединение с CoopNet!"
@@ -221,6 +222,10 @@ ERASE = "Удалить"
EDIT = "Редактировать"
EDIT_TITLE = "РЕДАКТИРОВАТЬ"
EDIT_NAME = "Редактировать имя файла сохранения @:"
+CREATE_SAVE = "Создать сохранение"
+CREATE = "Создать"
+CREATE_TITLE = "CREATE"
+CREATE_NAME = "Изменить название сохранения @:"
[HOST_SETTINGS]
SETTINGS = "SETTINGS"
From 3842b2e2936702fe73ea731bb348c7f851cd5b4c Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Mon, 16 Mar 2026 18:45:24 -0500
Subject: [PATCH 13/14] Some ui changes
---
lang/English.ini | 1 +
src/pc/djui/djui_panel_host_save.c | 14 +++++++++-----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/lang/English.ini b/lang/English.ini
index ff78d8ffd..5a27d5f5d 100644
--- a/lang/English.ini
+++ b/lang/English.ini
@@ -223,6 +223,7 @@ ERASE = "Erase"
EDIT = "Edit"
EDIT_TITLE = "EDIT"
EDIT_NAME = "Edit Save File @'s Name:"
+APPLY = "Apply"
CREATE_SAVE = "Create Save"
CREATE = "Create"
CREATE_TITLE = "CREATE"
diff --git a/src/pc/djui/djui_panel_host_save.c b/src/pc/djui/djui_panel_host_save.c
index 7fd0d60bc..fdf0108db 100644
--- a/src/pc/djui/djui_panel_host_save.c
+++ b/src/pc/djui/djui_panel_host_save.c
@@ -121,11 +121,15 @@ 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);
- else
- djui_button_create(body, DLANG(HOST_SAVE, EDIT), DJUI_BUTTON_STYLE_NORMAL, djui_panel_edit_save);
- djui_button_create(body, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back);
+ struct DjuiRect* rect = djui_rect_container_create(body, 64);
+ {
+ djui_button_left_create(&rect->base, DLANG(MENU, BACK), DJUI_BUTTON_STYLE_BACK, djui_panel_menu_back);
+ if (!sEditing) {
+ djui_button_right_create(&rect->base, DLANG(HOST_SAVE, CREATE), DJUI_BUTTON_STYLE_NORMAL, djui_panel_create_create);
+ } else {
+ djui_button_right_create(&rect->base, DLANG(HOST_SAVE, APPLY), DJUI_BUTTON_STYLE_NORMAL, djui_panel_edit_save);
+ }
+ }
}
djui_panel_add(caller, panel, NULL);
From eae34aabe69c2c6bb9f6713f9c008f0619fa48a3 Mon Sep 17 00:00:00 2001
From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com>
Date: Tue, 17 Mar 2026 12:06:46 -0500
Subject: [PATCH 14/14] Transfer old save names to new format
---
autogen/convert_functions.py | 2 +-
src/game/save_file.c | 58 ++++++++++++++++++++++++++++--
src/game/save_file.h | 4 +++
src/pc/configfile.c | 35 +++++++++++++++++-
src/pc/configfile.h | 3 ++
src/pc/djui/djui_panel_host_save.c | 28 +--------------
src/pc/ultra_reimplementation.c | 1 +
7 files changed, 99 insertions(+), 32 deletions(-)
diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index f9cfedb77..98a3c376e 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", "save_file_get_amount_of_available_indexes", "save_file_get_first_active_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", "save_file_rename_file" ],
"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/src/game/save_file.c b/src/game/save_file.c
index a705f42fe..22a21efe7 100644
--- a/src/game/save_file.c
+++ b/src/game/save_file.c
@@ -277,9 +277,10 @@ static void save_file_bswap(struct SaveBuffer *buf) {
static void save_file_convert_old_to_new() {
struct LegacySaveBuffer saveBuffer = { 0 };
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);
+ if (status != 0) return;
+ for (int i = 0; i < 4; i++) {
+ write_eeprom_data(i, saveBuffer.files[i], sizeof(saveBuffer.files[i]), 0);
+ save_file_rename_file(i, configSaveNames[i]);
}
}
@@ -385,6 +386,45 @@ s32 save_file_get_amount_of_available_indexes() {
return count;
}
+/**
+ * Renames a specific save file. Returns false on failure
+*/
+bool save_file_rename_file(s32 fileIndex, char* name) {
+ if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return false;
+ if (strstr(name, ".")) return false;
+
+ char filePath[SYS_MAX_PATH];
+ save_file_get_dir(fileIndex, filePath, 256, NULL);
+ char newFilePath[SYS_MAX_PATH];
+ save_file_get_dir(fileIndex, newFilePath, 256, name);
+
+ if (strcmp(filePath, newFilePath) == 0) return false;
+ if (!fs_sys_file_exists(fs_get_write_path(filePath))) return false;
+
+ // write the save data of the file to a variable
+ u8 content[EEPROM_SIZE] = { 0 };
+ fs_file_t* oldFile = fs_open(filePath);
+ if (oldFile == NULL) return false;
+ fs_read(oldFile, content, EEPROM_SIZE);
+ fs_close(oldFile);
+
+ // create a new file with the data
+ FILE* fp = fopen(fs_get_write_path(newFilePath), "wb");
+ if (fp == NULL) return false;
+ bool success = fwrite(content, 1, EEPROM_SIZE, fp) == EEPROM_SIZE;
+ fclose(fp);
+ if (success) {
+ // nuke old file!
+ remove(fs_get_write_path(filePath));
+ } else {
+ // uh oh! new file failed to be written to :( nuke new file!!
+ remove(fs_get_write_path(newFilePath));
+ }
+}
+
+/**
+ * Saves file data to the disk
+*/
void save_file_do_save(s32 fileIndex, s8 forceSave) {
if (INVALID_FILE_INDEX(fileIndex)) { return; }
if (gNetworkType != NT_SERVER) {
@@ -411,6 +451,9 @@ void save_file_do_save(s32 fileIndex, s8 forceSave) {
}
}
+/**
+ * Erases save file ingame and from the disk
+*/
void save_file_erase(s32 fileIndex) {
if (INVALID_FILE_INDEX(fileIndex)) { return; }
@@ -424,6 +467,9 @@ void save_file_erase(s32 fileIndex) {
remove(fs_get_write_path(filepath));
}
+/**
+ * Reloads save file from the disk and updates mario's stars
+*/
void save_file_reload(u8 loadAll) {
gSaveFileModified = TRUE;
update_all_mario_stars();
@@ -435,6 +481,9 @@ void save_file_reload(u8 loadAll) {
}
}
+/**
+ * Erases the current backup save
+*/
void save_file_erase_current_backup_save(void) {
if (INVALID_FILE_INDEX(gCurrSaveFileNum-1)) { return; }
if (gNetworkType != NT_SERVER) { return; }
@@ -458,6 +507,9 @@ BAD_RETURN(s32) save_file_copy(s32 srcFileIndex, s32 destFileIndex) {
save_file_do_save(destFileIndex, TRUE);
}
+/**
+ * Loads save file data from disk
+*/
void save_file_load_all(UNUSED u8 reload) {
//s32 file;
diff --git a/src/game/save_file.h b/src/game/save_file.h
index 0e89c5854..8e89f4371 100644
--- a/src/game/save_file.h
+++ b/src/game/save_file.h
@@ -139,6 +139,8 @@ struct WarpCheckpoint {
/*0x04*/ u8 warpNode;
};
+struct WarpNode;
+
extern struct WarpCheckpoint gWarpCheckpoint;
extern s8 gMainMenuDataModified;
@@ -165,6 +167,8 @@ s32 save_file_get_amount_of_available_indexes();
s32 save_file_get_first_active_index();
+bool save_file_rename_file(s32 fileIndex, char* name);
+
/* |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.c b/src/pc/configfile.c
index 85c11010b..dada22dae 100644
--- a/src/pc/configfile.c
+++ b/src/pc/configfile.c
@@ -21,7 +21,6 @@
#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]))
@@ -59,6 +58,14 @@ struct FunctionConfigOption {
*Config options and default values
*/
+// still exists for compatibility reasons
+char configSaveNames[4][MAX_SAVE_NAME_STRING] = {
+ "SM64",
+ "SM64",
+ "SM64",
+ "SM64"
+};
+
// Video/audio stuff
ConfigWindow configWindow = {
.x = WAPI_WIN_CENTERPOS,
@@ -522,11 +529,37 @@ static void dynos_pack_write(FILE* file) {
}
}
+static void save_name_read(char** tokens, int numTokens) {
+ if (numTokens < 2) { return; }
+ char fullSaveName[MAX_SAVE_NAME_STRING] = { 0 };
+ int index = 0;
+ for (int i = 1; i < numTokens; i++) {
+ if (i == 1) {
+ index = atoi(tokens[i]);
+ } else {
+ if (i > 2) {
+ strncat(fullSaveName, " ", MAX_SAVE_NAME_STRING - 1);
+ }
+ strncat(fullSaveName, tokens[i], MAX_SAVE_NAME_STRING - 1);
+ }
+
+ }
+ snprintf(configSaveNames[index], MAX_SAVE_NAME_STRING, "%s", fullSaveName);
+}
+
+static void save_name_write(FILE* file) {
+ // don't write save name data anymore, this only exists for compat
+ /*for (int i = 0; i < 4; i++) {
+ fprintf(file, "%s %d %s\n", "save-name:", i, configSaveNames[i]);
+ }*/
+}
+
static const struct FunctionConfigOption functionOptions[] = {
{ .name = "enable-mod:", .read = enable_mod_read, .write = enable_mod_write },
{ .name = "ban:", .read = ban_read, .write = ban_write },
{ .name = "moderator:", .read = moderator_read, .write = moderator_write },
{ .name = "dynos-pack:", .read = dynos_pack_read, .write = dynos_pack_write },
+ { .name = "save-name:", .read = save_name_read, .write = save_name_write }
};
// Reads an entire line from a file (excluding the newline character) and returns an allocated string
diff --git a/src/pc/configfile.h b/src/pc/configfile.h
index c513b0d32..77c26fa13 100644
--- a/src/pc/configfile.h
+++ b/src/pc/configfile.h
@@ -6,6 +6,7 @@
#include "game/player_palette.h"
#include "pc/lua/smlua_autogen.h"
#include "game/area.h"
+#include "game/save_file.h"
#define CONFIGFILE_DEFAULT "sm64config.txt"
#define CONFIGFILE_BACKUP "sm64config-backup.txt"
@@ -44,6 +45,8 @@ enum RefreshRateMode {
RRM_MAX
};
+extern char configSaveNames[4][MAX_SAVE_NAME_STRING];
+
// display settings
extern ConfigWindow configWindow;
extern ConfigStick configStick;
diff --git a/src/pc/djui/djui_panel_host_save.c b/src/pc/djui/djui_panel_host_save.c
index fdf0108db..3de76980e 100644
--- a/src/pc/djui/djui_panel_host_save.c
+++ b/src/pc/djui/djui_panel_host_save.c
@@ -26,33 +26,7 @@ static void djui_panel_host_reload_saves() {
}
static void djui_panel_host_save_update_save_name() {
- if (!fs_sys_dir_exists(fs_get_write_path(SAVE_DIRECTORY))) return;
- if (strstr(sSaveName, ".")) return;
- char filePath[256];
- save_file_get_dir(sButtonTag, filePath, 256, NULL);
- char newFilePath[256];
- save_file_get_dir(sButtonTag, newFilePath, 256, sSaveName);
- if (strcmp(filePath, newFilePath) == 0) return;
- if (!fs_sys_file_exists(fs_get_write_path(filePath))) return;
- // write the save data of the file to a variable
- u8 content[EEPROM_SIZE] = { 0 };
- fs_file_t* oldFile = fs_open(filePath);
- if (oldFile == NULL) return;
- fs_read(oldFile, content, EEPROM_SIZE);
- fs_close(oldFile);
- // create a new file with the data
- FILE* fp = fopen(fs_get_write_path(newFilePath), "wb");
- if (fp == NULL) return;
- bool success = fwrite(content, 1, EEPROM_SIZE, fp) == EEPROM_SIZE;
- fclose(fp);
- if (success) {
- // nuke old file
- remove(fs_get_write_path(filePath));
- } else {
- // uh oh! New file failed to be written to. Nuke new file
- remove(fs_get_write_path(newFilePath));
- }
-
+ save_file_rename_file(sButtonTag, sSaveName);
djui_panel_host_reload_saves();
}
diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c
index ec2e1963e..c74ae6d2d 100644
--- a/src/pc/ultra_reimplementation.c
+++ b/src/pc/ultra_reimplementation.c
@@ -4,6 +4,7 @@
#include "macros.h"
#include "platform.h"
#include "fs/fs.h"
+#include "configfile.h"
#include "game/save_file.h"
u8* gOverrideEeprom[NUM_SAVE_FILES] = { NULL };