Allow save files to be imported via drag and drop

This commit is contained in:
EmeraldLockdown 2026-03-03 11:28:32 -06:00
parent 93e64a11a2
commit a63398ccbf
7 changed files with 108 additions and 18 deletions

View file

@ -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" ],

View file

@ -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);

View file

@ -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) {

View file

@ -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.

View file

@ -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) {

View file

@ -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);

View file

@ -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);