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;