diff --git a/Changelog.md b/Changelog.md index 29c63352..59467d18 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ - Ambient: Fix missing Battle ID for battles triggered using field opcodes - Ambient: Fix Battle ID detection for random encounters in Field +- Modding: Allow modding card names hardcoded in exe ( https://github.com/julianxhokaxhiu/FFNx/pull/739 ) # 1.20.3 diff --git a/src/exe_data.cpp b/src/exe_data.cpp index c2f7f599..b46e3545 100644 --- a/src/exe_data.cpp +++ b/src/exe_data.cpp @@ -26,13 +26,24 @@ #include "saveload.h" uint8_t *ff8_exe_scan_texts = nullptr; +uint8_t *ff8_exe_card_names = nullptr; + +bool ff8_get_exe_path(const char *name, char *target_filename) +{ + snprintf(target_filename, MAX_PATH, "%s/exe/%s.msd", direct_mode_path.c_str(), name); + normalize_path(target_filename); + + return fileExists(target_filename); +} bool ff8_get_battle_scan_texts_filename(char *filename) { - snprintf(filename, MAX_PATH, "%s/exe/battle_scans.msd", direct_mode_path.c_str()); - normalize_path(filename); + return ff8_get_exe_path("battle_scans", filename); +} - return fileExists(filename); +bool ff8_get_card_names_filename(char *filename) +{ + return ff8_get_exe_path("card_names", filename); } void ff8_dump_battle_scan_texts() @@ -76,19 +87,49 @@ void ff8_dump_battle_scan_texts() fclose(f); } -uint8_t *ff8_override_battle_scans() +void ff8_dump_card_names() { - if (ff8_exe_scan_texts != nullptr) { - return ff8_exe_scan_texts; + uint8_t *data = *(uint8_t **)ff8_externals.card_name_positions; + uint16_t *positions = *(uint16_t **)ff8_externals.card_name_positions; + constexpr int text_count = 110; + uint16_t old_data_offset = (1 + text_count) * sizeof(uint16_t), new_data_offset = text_count * sizeof(uint32_t); + uint32_t offsets_rel_to_start[text_count] = {}; + int higher_offset = 0; + + for (int i = 0; i < text_count; ++i) { + offsets_rel_to_start[i] = positions[i + 1] - old_data_offset + new_data_offset; + if (positions[i + 1] > higher_offset) { + higher_offset = positions[i + 1]; + } + } + + for (int i = higher_offset; i < higher_offset + 1024; ++i) { + if (data[i] == '\0') { + higher_offset = i + 1; + break; + } } char filename[MAX_PATH] = {}; - if (! ff8_get_battle_scan_texts_filename(filename)) { - if (trace_all || trace_direct) ffnx_warning("Direct file not found %s\n", filename); + if (ff8_get_card_names_filename(filename)) { + ffnx_warning("Save exe file skipped because the file [ %s ] already exists.\n", filename); - return nullptr; + return; } + FILE *f = fopen(filename, "wb"); + + if (f == nullptr) { + return; + } + + fwrite(offsets_rel_to_start, new_data_offset, 1, f); + fwrite(data + old_data_offset, higher_offset - old_data_offset, 1, f); + fclose(f); +} + +uint8_t *ff8_open_msd(char *filename) +{ if (trace_all || trace_direct) ffnx_info("Direct file using %s\n", filename); FILE *f = fopen(filename, "rb"); @@ -101,18 +142,54 @@ uint8_t *ff8_override_battle_scans() long file_size = ftell(f); fseek(f, 0, SEEK_SET); - ff8_exe_scan_texts = (uint8_t *)driver_malloc(file_size); // Allocated once, never freed + uint8_t *target_data = (uint8_t *)driver_malloc(file_size); // Allocated once, never freed - if (ff8_exe_scan_texts == nullptr) { + if (target_data == nullptr) { return nullptr; } - fread(ff8_exe_scan_texts, file_size, 1, f); + fread(target_data, file_size, 1, f); fclose(f); + return target_data; +} + +uint8_t *ff8_override_battle_scans() +{ + if (ff8_exe_scan_texts != nullptr) { + return ff8_exe_scan_texts; + } + + char filename[MAX_PATH] = {}; + if (! ff8_get_battle_scan_texts_filename(filename)) { + if (trace_all || trace_direct) ffnx_warning("Direct file not found %s\n", filename); + + return nullptr; + } + + ff8_exe_scan_texts = ff8_open_msd(filename); + return ff8_exe_scan_texts; } +uint8_t *ff8_override_card_names() +{ + if (ff8_exe_card_names != nullptr) { + return ff8_exe_card_names; + } + + char filename[MAX_PATH] = {}; + if (! ff8_get_card_names_filename(filename)) { + if (trace_all || trace_direct) ffnx_warning("Direct file not found %s\n", filename); + + return nullptr; + } + + ff8_exe_card_names = ff8_open_msd(filename); + + return ff8_exe_card_names; +} + uint8_t *ff8_battle_get_scan_text(uint8_t target_id) { uint8_t *direct_data_msd = ff8_override_battle_scans(); @@ -129,6 +206,26 @@ uint8_t *ff8_battle_get_scan_text(uint8_t target_id) return ((uint8_t*(*)(uint8_t))ff8_externals.scan_get_text_sub_B687C0)(target_id); } +char *ff8_get_card_name(int32_t card_id) +{ + if (card_id >= 110) { + return nullptr; + } + + uint8_t *direct_data_msd = ff8_override_card_names(); + if (direct_data_msd != nullptr) { + uint32_t *positions = (uint32_t *)direct_data_msd; + + if (trace_all) ffnx_trace("%s: get card name card_id=%d\n", __func__, card_id); + + return (char *)direct_data_msd + positions[card_id]; + } + + uint16_t *positions = *(uint16_t **)ff8_externals.card_name_positions; + + return *(char **)ff8_externals.card_name_positions + positions[card_id + 1]; +} + void dump_exe_data() { char dirname[MAX_PATH] = {}; @@ -140,6 +237,7 @@ void dump_exe_data() if (ff8) { ff8_dump_battle_scan_texts(); + ff8_dump_card_names(); } } @@ -153,5 +251,6 @@ void exe_data_init() if (ff8) { replace_call(ff8_externals.sub_84F8D0 + 0x88, ff8_battle_get_scan_text); + replace_function(ff8_externals.get_card_name, ff8_get_card_name); } } diff --git a/src/ff8.h b/src/ff8.h index 0fec9e52..39476b63 100644 --- a/src/ff8.h +++ b/src/ff8.h @@ -1117,6 +1117,9 @@ struct ff8_externals uint32_t sub_4B3140; uint32_t sub_4BDB30; ff8_menu_callback *menu_callbacks; + uint32_t menu_cards_render; + uint32_t sub_4EFC00; + uint32_t sub_4EFCD0; uint32_t menu_config_controller; uint32_t menu_config_render; uint32_t menu_config_render_submenu; @@ -1517,6 +1520,8 @@ struct ff8_externals double *time_volume_change_related_1A78BE0; uint32_t* game_mode_obj_1D9CF88; uint32_t field_vars_stack_1CFE9B8; + uint32_t get_card_name; + uint32_t card_name_positions; }; void ff8gl_field_78(struct ff8_polygon_set *polygon_set, struct ff8_game_obj *game_object); diff --git a/src/ff8_data.cpp b/src/ff8_data.cpp index f8c23982..1d6900c8 100644 --- a/src/ff8_data.cpp +++ b/src/ff8_data.cpp @@ -404,6 +404,9 @@ void ff8_find_externals() ff8_externals.sub_4B3140 = get_relative_call(ff8_externals.sub_4B3310, 0xC8); ff8_externals.sub_4BDB30 = get_relative_call(ff8_externals.sub_4B3140, 0x4); ff8_externals.menu_callbacks = (ff8_menu_callback *)get_absolute_value(ff8_externals.sub_4BDB30, 0x11); + ff8_externals.menu_cards_render = get_absolute_value(uint32_t(ff8_externals.menu_callbacks[7].func), 0x5); + ff8_externals.sub_4EFC00 = get_relative_call(ff8_externals.menu_cards_render, 0x2B6); + ff8_externals.sub_4EFCD0 = get_absolute_value(ff8_externals.sub_4EFC00, 0xB0); ff8_externals.menu_config_render = get_absolute_value(uint32_t(ff8_externals.menu_callbacks[8].func), 0x3); ff8_externals.menu_config_render_submenu = get_relative_call(ff8_externals.menu_config_render, 0x101); ff8_externals.menu_config_controller = get_absolute_value(uint32_t(ff8_externals.menu_callbacks[8].func), 0x8); @@ -826,6 +829,8 @@ void ff8_find_externals() ff8_externals.battle_entities_1D27BCB = get_absolute_value(ff8_externals.scan_get_text_sub_B687C0, 0x18); ff8_externals.scan_text_positions = get_absolute_value(ff8_externals.scan_get_text_sub_B687C0, 0x20); ff8_externals.scan_text_data = get_absolute_value(ff8_externals.scan_get_text_sub_B687C0, 0x27); + ff8_externals.get_card_name = get_relative_call(ff8_externals.sub_4EFCD0, 0x89); + ff8_externals.card_name_positions = get_absolute_value(ff8_externals.get_card_name, 0xB); ff8_externals.fps_limiter = get_relative_call(ff8_externals.field_main_loop, 0x261); if (JP_VERSION)