From adc48f2bd66d38c882b2ad9c5ce9e78700650bb5 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 11 Jan 2026 11:54:24 -0500 Subject: [PATCH 01/68] Updating text_helper to throw an error with text that is too long --- text_helper/main.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 3c16e19..6f86ea3 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -9,17 +9,19 @@ import copy import math import sys import filecmp +from pathlib import Path update = True print ("Running text_helper:") +BASE_DIR = Path(__file__).resolve().parent if (update == True): url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' - new_file_path = 'text_helper/new_text.xlsx' - old_file_path = 'text_helper/text.xlsx' - json_file_path = 'text_helper/output.json' + new_file_path = BASE_DIR / 'new_text.xlsx' + old_file_path = BASE_DIR / 'text.xlsx' + json_file_path = BASE_DIR / 'output.json' no_file = False try: @@ -27,7 +29,7 @@ if (update == True): response.raise_for_status() if response.status_code == 200: with open(new_file_path, 'wb') as file: - file.write(response.content) + file.write(response.content) print('File downloaded successfully') except requests.exceptions.ReadTimeout as errrt: if os.path.exists(old_file_path): @@ -300,11 +302,11 @@ def split_into_sentences(text: str) -> list[str]: class Languages(Enum): Japanese = 0 English = 1 - ##French = 2 - ##German = 3 - ##Italian = 4 - ##SpanishEU = 5 - ##SpanishLA = 6 + French = 2 + German = 3 + Italian = 4 + SpanishEU = 5 + SpanishLA = 6 # read by default 1st sheet of an excel file dir = os.curdir + "/text_helper" @@ -426,8 +428,10 @@ def write_text_bin_file(filename, dictionary): current_offset += len(linedata) if len(linedata) > 1024: - print(f"Error: entry '{key}' numBytes exceeds 1024 (got {len(linedata)})", file=sys.stderr) - sys.exit(1) + #print(f"Error: entry '{key}' numBytes exceeds 1024 (got {len(linedata)}). Trunacting to 1024.", file=sys.stderr) + next_key = max(mainDict[lang.name]["Errors"].keys(), default =- 1) + 1 + mainDict[lang.name]["Errors"][next_key] = f"ERROR! Contents of dialogue with identifier \"{key}\" exceeds 1024 bytes!" + linedata = linedata[:1024] num += 1 From 1055094ab9da94d7d0c0bb90aec3a35fc4ba015e Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 11 Jan 2026 15:02:16 -0500 Subject: [PATCH 02/68] Modifying text_helper to not have static text sections --- source/box_menu.cpp | 2 +- source/gameboy_colour.cpp | 6 +- source/global_frame_controller.cpp | 2 +- source/main.cpp | 10 +- source/multiboot_upload.cpp | 2 +- source/mystery_gift_builder.cpp | 2 +- source/pokedex.cpp | 4 +- source/script_array.cpp | 410 ++++++++++++++--------------- source/select_menu.cpp | 2 +- text_helper/main.py | 150 ++++------- 10 files changed, 272 insertions(+), 318 deletions(-) diff --git a/source/box_menu.cpp b/source/box_menu.cpp index e26f2f8..cbe8b0e 100644 --- a/source/box_menu.cpp +++ b/source/box_menu.cpp @@ -36,7 +36,7 @@ int Box_Menu::box_main(PokeBox* box) obj_unhide(box_select, 0); int index = 0; - PKMN_NAMES.decompress(get_compressed_pkmn_names_table()); + PKMN_NAMES.decompress(get_compressed_PKMN_NAMES_table()); while (true) { diff --git a/source/gameboy_colour.cpp b/source/gameboy_colour.cpp index d6a5984..8be2d06 100644 --- a/source/gameboy_colour.cpp +++ b/source/gameboy_colour.cpp @@ -169,7 +169,7 @@ void setup(const u16 *debug_charset) u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); ptgb_write(general_text.get_text_entry(GENERAL_connecting), true); } } @@ -228,7 +228,7 @@ byte handleIncomingByte(byte in, byte *box_data_storage, byte *curr_payload, GB_ u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); ptgb_write(general_text.get_text_entry(curr_gb_rom->version != YELLOW_ID ? GENERAL_link_success : GENERAL_link_success_yellow), true); } @@ -280,7 +280,7 @@ byte handleIncomingByte(byte in, byte *box_data_storage, byte *curr_payload, GB_ u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); ptgb_write(general_text.get_text_entry(GENERAL_transferring), true); } diff --git a/source/global_frame_controller.cpp b/source/global_frame_controller.cpp index e12927c..1a45688 100644 --- a/source/global_frame_controller.cpp +++ b/source/global_frame_controller.cpp @@ -27,7 +27,7 @@ static void __attribute__((noinline)) show_pulled_cart_error() u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); ptgb_write(general_text.get_text_entry(GENERAL_pulled_cart_error), true); } diff --git a/source/main.cpp b/source/main.cpp index c86d003..eba3e03 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -147,7 +147,7 @@ void game_load_error(void) u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); ptgb_write(general_text.get_text_entry(GENERAL_cart_load_error), true); } @@ -189,7 +189,7 @@ void first_load_message(void) u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); ptgb_write(general_text.get_text_entry(GENERAL_intro_first), true); } @@ -206,7 +206,7 @@ int credits() text_data_table credits_text_table(text_decompression_buffer); int curr_credits_num = 0; - credits_text_table.decompress(get_compressed_credits_table()); + credits_text_table.decompress(get_compressed_CREDITS_table()); bool update = true; global_next_frame(); @@ -329,7 +329,7 @@ int main_menu_loop() int return_values[NUM_MENU_OPTIONS] = {BTN_TRANSFER, BTN_POKEDEX, BTN_CREDITS}; u16 test = 0; - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); while (true) { @@ -429,7 +429,7 @@ static void __attribute__((noinline)) show_intro() text_data_table general_text(general_text_table_buffer); const u8 *text_entry; - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); text_entry = general_text.get_text_entry(GENERAL_press_start); press_start_text_length = get_string_length(text_entry); diff --git a/source/multiboot_upload.cpp b/source/multiboot_upload.cpp index 9f6e133..6712018 100644 --- a/source/multiboot_upload.cpp +++ b/source/multiboot_upload.cpp @@ -19,7 +19,7 @@ void multiboot_upload_screen() text_data_table general_text(general_text_table_buffer); LinkCableMultiboot linkCableMultiboot; - general_text.decompress(get_compressed_general_table()); + general_text.decompress(get_compressed_GENERAL_table()); multiboot_show_textbox(); ptgb_write(general_text.get_text_entry(GENERAL_send_multiboot_instructions), true); diff --git a/source/mystery_gift_builder.cpp b/source/mystery_gift_builder.cpp index 5683e6a..7651f86 100644 --- a/source/mystery_gift_builder.cpp +++ b/source/mystery_gift_builder.cpp @@ -378,7 +378,7 @@ void mystery_gift_script::build_script(PokeBox *box) // this decompresses the ZX0 compressed text table into the buffer inside of the decompressed_store union // thereby reusing the stack (=IWRAM) memory used earlier for the PokemonTables instance we used above - decompressed_text_table.decompress(get_compressed_rsefrlg_table()); + decompressed_text_table.decompress(get_compressed_RSEFRLG_table()); switch (curr_GBA_rom.gamecode) { case RUBY_ID: diff --git a/source/pokedex.cpp b/source/pokedex.cpp index 97a6ec4..129400f 100644 --- a/source/pokedex.cpp +++ b/source/pokedex.cpp @@ -41,7 +41,7 @@ static void load_text_entry_into_buffer(text_data_table& data_table, u8 *output_ static void load_general_table_text_entries(u8 *decompression_buffer, u8 *kanto_buffer, u8 *johto_buffer) { text_data_table data_table(decompression_buffer); - data_table.decompress(get_compressed_general_table()); + data_table.decompress(get_compressed_GENERAL_table()); load_text_entry_into_buffer(data_table, kanto_buffer, GENERAL_kanto_name); load_text_entry_into_buffer(data_table, johto_buffer, GENERAL_johto_name); @@ -105,7 +105,7 @@ int pokedex_loop() load_general_table_text_entries(decompression_buffer, kanto_name, johto_name); text_data_table PKMN_NAMES(decompression_buffer); - PKMN_NAMES.decompress(get_compressed_pkmn_names_table()); + PKMN_NAMES.decompress(get_compressed_PKMN_NAMES_table()); pokedex_init(); pokedex_show(); diff --git a/source/script_array.cpp b/source/script_array.cpp index f6ddcb6..f351a47 100644 --- a/source/script_array.cpp +++ b/source/script_array.cpp @@ -26,75 +26,75 @@ Box_Menu box_viewer; // Check that the conditions are set for the transfer // T_SCRIPT_START // COND_TUTORIAL_COMPLETE -// DIA_OPEN +// PTGB_DIA_OPEN // CMD_SET_TUTOR_TRUE // COND_BEAT_E4 -// DIA_E4 +// PTGB_DIA_E4 // COND_MG_ENABLED // COND_IS_FRLGE -// DIA_MG_FRLGE -// DIA_MG_RS +// PTGB_DIA_MG_FRLGE +// PTGB_DIA_MG_RS // COND_MG_OTHER_EVENT // COND_PKMN_TO_COLLECT -// DIA_MG_OTHER_EVENT -// DIA_PKMN_TO_COLLECT +// PTGB_DIA_MG_OTHER_EVENT +// PTGB_DIA_PKMN_TO_COLLECT // // Ask the user what game and language they're using -// DIA_WHAT_GAME_TRANS +// PTGB_DIA_WHAT_GAME_TRANS // CMD_GAME_MENU -// DIA_WHAT_LANG_TRANS +// PTGB_DIA_WHAT_LANG_TRANS // CMD_LANG_MENU -// DIA_ASK_QUEST +// PTGB_DIA_ASK_QUEST // CMD_SLIDE_PROF_LEFT // CMD_SLIDE_PROF_RIGHT // COND_GB_ROM_EXISTS -// DIA_NO_GB_ROM -// DIA_MENU_BACK +// PTGB_DIA_NO_GB_ROM +// PTGB_DIA_MENU_BACK // // Initiate the transfer and check for errors -// DIA_LETS_START -// DIA_START +// PTGB_DIA_LETS_START +// PTGB_DIA_START // CMD_START_LINK // COND_ERROR_TIMEOUT_ONE -// DIA_ERROR_TIME_ONE +// PTGB_DIA_ERROR_TIME_ONE // COND_ERROR_TIMEOUT_TWO -// DIA_ERROR_TIME_TWO +// PTGB_DIA_ERROR_TIME_TWO // COND_ERROR_COM_ENDED -// DIA_ERROR_COM_ENDED +// PTGB_DIA_ERROR_COM_ENDED // COND_ERROR_COLOSSEUM -// DIA_ERROR_COLOSSEUM +// PTGB_DIA_ERROR_COLOSSEUM // COND_ERROR_DISCONNECT -// DIA_ERROR_DISCONNECT +// PTGB_DIA_ERROR_DISCONNECT // // Pause the transfer and show the user their box data // CMD_IS_A_VALID_PKMN -// DIA_NO_VALID_PKMN +// PTGB_DIA_NO_VALID_PKMN // COND_SOME_INVALID_PKMN -// DIA_SOME_INVALID_PKMN +// PTGB_DIA_SOME_INVALID_PKMN // COND_CHECK_MYTHIC -// DIA_MYTHIC_CONVERT +// PTGB_DIA_MYTHIC_CONVERT // CMD_MYTHIC_MENU // COND_CHECK_MISSINGNO -// DIA_IS_MISSINGNO -// DIA_IN_BOX +// PTGB_DIA_IS_MISSINGNO +// PTGB_DIA_IN_BOX // CMD_BOX_MENU -// DIA_CANCEL +// PTGB_DIA_CANCEL // CMD_IMPORT_POKEMON // CMD_CONTINUE_LINK // CMD_CANCEL_LINK // CMD_END_MISSINGNO // // Complete the transfer and give messages based on the transfered Pokemon -// DIA_TRANS_GOOD +// PTGB_DIA_TRANS_GOOD // COND_NEW_POKEMON -// DIA_NEW_DEX -// DIA_NO_NEW_DEX +// PTGB_DIA_NEW_DEX +// PTGB_DIA_NO_NEW_DEX // COND_IS_HOENN_RS // COND_IS_HOENN_E -// DIA_SEND_FRIEND_HOENN_RS -// DIA_SEND_FRIEND_HOENN_E -// DIA_SEND_FRIEND_KANTO -// DIA_THANK +// PTGB_DIA_SEND_FRIEND_HOENN_RS +// PTGB_DIA_SEND_FRIEND_HOENN_E +// PTGB_DIA_SEND_FRIEND_KANTO +// PTGB_DIA_THANK // // Hide the dialogue and professor // CMD_END_SCRIPT @@ -104,184 +104,184 @@ Box_Menu box_viewer; // So if adding a new dia, command or condition, make sure to add it in the right position/index. // defining it this way does not generate an expensive function. It's all stored as read-only data in EWRAM. const script_obj_params transfer_script_params[SCRIPT_SIZE] = { - // DIA_OPEN + // PTGB_DIA_OPEN { - .text_entry_index = DIA_OPEN, + .text_entry_index = PTGB_DIA_OPEN, .next_if_true = CMD_SET_TUTOR_TRUE }, - // DIA_E4 + // PTGB_DIA_E4 { - .text_entry_index = DIA_E4, + .text_entry_index = PTGB_DIA_E4, .next_if_true = CMD_END_SCRIPT }, - // DIA_MG_FRLGE + // PTGB_DIA_MG_FRLGE { - .text_entry_index = DIA_MG_FRLGE, + .text_entry_index = PTGB_DIA_MG_FRLGE, .next_if_true = CMD_END_SCRIPT }, - // DIA_MG_RS + // PTGB_DIA_MG_RS { - .text_entry_index = DIA_MG_RS, + .text_entry_index = PTGB_DIA_MG_RS, .next_if_true = CMD_END_SCRIPT }, - // DIA_LETS_START + // PTGB_DIA_LETS_START { - .text_entry_index = DIA_LETS_START, - .next_if_true = DIA_START + .text_entry_index = PTGB_DIA_LETS_START, + .next_if_true = PTGB_DIA_START }, - // DIA_START + // PTGB_DIA_START { - .text_entry_index = DIA_START, + .text_entry_index = PTGB_DIA_START, .next_if_true = CMD_START_LINK }, - // DIA_TRANS_GOOD + // PTGB_DIA_TRANS_GOOD { - .text_entry_index = DIA_TRANS_GOOD, + .text_entry_index = PTGB_DIA_TRANS_GOOD, .next_if_true = COND_NEW_POKEMON }, - // DIA_NEW_DEX + // PTGB_DIA_NEW_DEX { - .text_entry_index = DIA_NEW_DEX, + .text_entry_index = PTGB_DIA_NEW_DEX, .next_if_true = COND_IS_HOENN_RS }, - // DIA_NO_NEW_DEX + // PTGB_DIA_NO_NEW_DEX { - .text_entry_index = DIA_NO_NEW_DEX, + .text_entry_index = PTGB_DIA_NO_NEW_DEX, .next_if_true = COND_IS_HOENN_RS }, - // DIA_SEND_FRIEND_KANTO + // PTGB_DIA_SEND_FRIEND_KANTO { - .text_entry_index = DIA_SEND_FRIEND_KANTO, - .next_if_true = DIA_THANK + .text_entry_index = PTGB_DIA_SEND_FRIEND_KANTO, + .next_if_true = PTGB_DIA_THANK }, - // DIA_SEND_FRIEND_HOENN_RS + // PTGB_DIA_SEND_FRIEND_HOENN_RS { - .text_entry_index = DIA_SEND_FRIEND_HOENN_RS, - .next_if_true = DIA_THANK + .text_entry_index = PTGB_DIA_SEND_FRIEND_HOENN_RS, + .next_if_true = PTGB_DIA_THANK }, - // DIA_SEND_FRIEND_HOENN_E + // PTGB_DIA_SEND_FRIEND_HOENN_E { - .text_entry_index = DIA_SEND_FRIEND_HOENN_E, - .next_if_true = DIA_THANK + .text_entry_index = PTGB_DIA_SEND_FRIEND_HOENN_E, + .next_if_true = PTGB_DIA_THANK }, - // DIA_THANK + // PTGB_DIA_THANK { - .text_entry_index = DIA_THANK, + .text_entry_index = PTGB_DIA_THANK, .next_if_true = CMD_END_SCRIPT }, - // DIA_GET_MON (unused) + // PTGB_DIA_GET_MON (unused) { - .text_entry_index = DIA_GET_MON, + .text_entry_index = PTGB_DIA_GET_MON, .next_if_true = CMD_END_SCRIPT }, - // DIA_MG_OTHER_EVENT + // PTGB_DIA_MG_OTHER_EVENT { - .text_entry_index = DIA_MG_OTHER_EVENT, - .next_if_true = DIA_ASK_QUEST + .text_entry_index = PTGB_DIA_MG_OTHER_EVENT, + .next_if_true = PTGB_DIA_ASK_QUEST }, - // DIA_PKMN_TO_COLLECT + // PTGB_DIA_PKMN_TO_COLLECT { - .text_entry_index = DIA_PKMN_TO_COLLECT, + .text_entry_index = PTGB_DIA_PKMN_TO_COLLECT, .next_if_true = CMD_END_SCRIPT }, - // DIA_NO_VALID_PKMN + // PTGB_DIA_NO_VALID_PKMN { - .text_entry_index = DIA_NO_VALID_PKMN, + .text_entry_index = PTGB_DIA_NO_VALID_PKMN, .next_if_true = CMD_CANCEL_LINK }, - // DIA_ASK_QUEST + // PTGB_DIA_ASK_QUEST { - .text_entry_index = DIA_ASK_QUEST, + .text_entry_index = PTGB_DIA_ASK_QUEST, .next_if_true = CMD_SLIDE_PROF_LEFT }, - // DIA_WHAT_GAME_TRANS + // PTGB_DIA_WHAT_GAME_TRANS { - .text_entry_index = DIA_WHAT_GAME_TRANS, + .text_entry_index = PTGB_DIA_WHAT_GAME_TRANS, .next_if_true = CMD_GAME_MENU }, - // DIA_WHAT_LANG_TRANS + // PTGB_DIA_WHAT_LANG_TRANS { - .text_entry_index = DIA_WHAT_LANG_TRANS, + .text_entry_index = PTGB_DIA_WHAT_LANG_TRANS, .next_if_true = CMD_LANG_MENU }, - // DIA_NO_GB_ROM + // PTGB_DIA_NO_GB_ROM { - .text_entry_index = DIA_NO_GB_ROM, - .next_if_true = DIA_WHAT_LANG_TRANS + .text_entry_index = PTGB_DIA_NO_GB_ROM, + .next_if_true = PTGB_DIA_WHAT_LANG_TRANS }, - // DIA_IN_BOX + // PTGB_DIA_IN_BOX { - .text_entry_index = DIA_IN_BOX, + .text_entry_index = PTGB_DIA_IN_BOX, .next_if_true = CMD_BOX_MENU }, - // DIA_MYTHIC_CONVERT + // PTGB_DIA_MYTHIC_CONVERT { - .text_entry_index = DIA_MYTHIC_CONVERT, + .text_entry_index = PTGB_DIA_MYTHIC_CONVERT, .next_if_true = CMD_MYTHIC_MENU }, - // DIA_CANCEL + // PTGB_DIA_CANCEL { - .text_entry_index = DIA_CANCEL, + .text_entry_index = PTGB_DIA_CANCEL, .next_if_true = CMD_CANCEL_LINK }, - // DIA_SOME_INVALID_PKMN + // PTGB_DIA_SOME_INVALID_PKMN { - .text_entry_index = DIA_SOME_INVALID_PKMN, + .text_entry_index = PTGB_DIA_SOME_INVALID_PKMN, .next_if_true = COND_CHECK_MYTHIC }, - // DIA_MENU_BACK + // PTGB_DIA_MENU_BACK { - .text_entry_index = DIA_MENU_BACK, + .text_entry_index = PTGB_DIA_MENU_BACK, .next_if_true = CMD_END_SCRIPT }, - // DIA_IS_MISSINGNO + // PTGB_DIA_IS_MISSINGNO { - .text_entry_index = DIA_IS_MISSINGNO, - .next_if_true = DIA_IN_BOX + .text_entry_index = PTGB_DIA_IS_MISSINGNO, + .next_if_true = PTGB_DIA_IN_BOX }, - // DIA_ERROR_COLOSSEUM + // PTGB_DIA_ERROR_COLOSSEUM { - .text_entry_index = DIA_ERROR_COLOSSEUM, - .next_if_true = DIA_START + .text_entry_index = PTGB_DIA_ERROR_COLOSSEUM, + .next_if_true = PTGB_DIA_START }, - // DIA_ERROR_COM_ENDED + // PTGB_DIA_ERROR_COM_ENDED { - .text_entry_index = DIA_ERROR_COM_ENDED, - .next_if_true = DIA_START + .text_entry_index = PTGB_DIA_ERROR_COM_ENDED, + .next_if_true = PTGB_DIA_START }, - // DIA_ERROR_DISCONNECT + // PTGB_DIA_ERROR_DISCONNECT { - .text_entry_index = DIA_ERROR_DISCONNECT, - .next_if_true = DIA_START + .text_entry_index = PTGB_DIA_ERROR_DISCONNECT, + .next_if_true = PTGB_DIA_START }, - // DIA_ERROR_TIME_ONE + // PTGB_DIA_ERROR_TIME_ONE { - .text_entry_index = DIA_ERROR_TIME_ONE, - .next_if_true = DIA_START + .text_entry_index = PTGB_DIA_ERROR_TIME_ONE, + .next_if_true = PTGB_DIA_START }, - // DIA_ERROR_TIME_TWO + // PTGB_DIA_ERROR_TIME_TWO { - .text_entry_index = DIA_ERROR_TIME_TWO, - .next_if_true = DIA_START + .text_entry_index = PTGB_DIA_ERROR_TIME_TWO, + .next_if_true = PTGB_DIA_START }, - // DIA_WHAT_LANG_EVENT + // PTGB_DIA_WHAT_LANG_EVENT { - .text_entry_index = DIA_WHAT_LANG_EVENT, + .text_entry_index = PTGB_DIA_WHAT_LANG_EVENT, .next_if_true = CMD_LANG_MENU }, - // DIA_WHAT_GAME_EVENT + // PTGB_DIA_WHAT_GAME_EVENT { - .text_entry_index = DIA_WHAT_GAME_EVENT, + .text_entry_index = PTGB_DIA_WHAT_GAME_EVENT, .next_if_true = CMD_GAME_MENU }, - // DIA_K_DEX_NOT_FULL + // PTGB_DIA_K_DEX_NOT_FULL { - .text_entry_index = DIA_K_DEX_NOT_FULL, + .text_entry_index = PTGB_DIA_K_DEX_NOT_FULL, .next_if_true = CMD_END_SCRIPT }, - // DIA_J_DEX_NOT_FULL + // PTGB_DIA_J_DEX_NOT_FULL { - .text_entry_index = DIA_J_DEX_NOT_FULL, + .text_entry_index = PTGB_DIA_J_DEX_NOT_FULL, .next_if_true = CMD_END_SCRIPT }, // T_SCRIPT_START @@ -292,7 +292,7 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { // E_SCRIPT_START { .conditional_index = CMD_SHOW_PROF, - .next_if_true = DIA_ASK_QUEST + .next_if_true = PTGB_DIA_ASK_QUEST }, // CMD_START_LINK { @@ -330,23 +330,23 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { { .conditional_index = CMD_GAME_MENU, .next_if_true = COND_GB_ROM_EXISTS, - .next_if_false = DIA_WHAT_LANG_TRANS + .next_if_false = PTGB_DIA_WHAT_LANG_TRANS }, // CMD_LANG_MENU { .conditional_index = CMD_LANG_MENU, - .next_if_true = DIA_WHAT_GAME_TRANS, - .next_if_false = DIA_CANCEL + .next_if_true = PTGB_DIA_WHAT_GAME_TRANS, + .next_if_false = PTGB_DIA_CANCEL }, // CMD_SLIDE_PROF_LEFT { .conditional_index = CMD_SLIDE_PROF_LEFT, - .next_if_true = DIA_WHAT_LANG_TRANS + .next_if_true = PTGB_DIA_WHAT_LANG_TRANS }, // CMD_SLIDE_PROF_RIGHT { .conditional_index = CMD_SLIDE_PROF_RIGHT, - .next_if_true = DIA_LETS_START + .next_if_true = PTGB_DIA_LETS_START }, // CMD_CONTINUE_LINK { @@ -357,7 +357,7 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { { .conditional_index = CMD_BOX_MENU, .next_if_true = CMD_IMPORT_POKEMON, - .next_if_false = DIA_CANCEL + .next_if_false = PTGB_DIA_CANCEL }, // CMD_MYTHIC_MENU { @@ -368,7 +368,7 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { { .conditional_index = CMD_IS_A_VALID_PKMN, .next_if_true = COND_SOME_INVALID_PKMN, - .next_if_false = DIA_NO_VALID_PKMN + .next_if_false = PTGB_DIA_NO_VALID_PKMN }, // CMD_CANCEL_LINK { @@ -378,43 +378,43 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { // CMD_END_MISSINGNO { .conditional_index = CMD_END_MISSINGNO, - .next_if_true = DIA_TRANS_GOOD + .next_if_true = PTGB_DIA_TRANS_GOOD }, // COND_ERROR_TIMEOUT_ONE { .conditional_index = COND_ERROR_TIMEOUT_ONE, .next_if_true = COND_ERROR_TIMEOUT_TWO, - .next_if_false = DIA_ERROR_TIME_ONE + .next_if_false = PTGB_DIA_ERROR_TIME_ONE }, // COND_ERROR_DISCONNECT { .conditional_index = COND_ERROR_DISCONNECT, .next_if_true = CMD_IS_A_VALID_PKMN, - .next_if_false = DIA_ERROR_DISCONNECT + .next_if_false = PTGB_DIA_ERROR_DISCONNECT }, // COND_ERROR_COM_ENDED { .conditional_index = COND_ERROR_COM_ENDED, .next_if_true = COND_ERROR_COLOSSEUM, - .next_if_false = DIA_ERROR_COM_ENDED + .next_if_false = PTGB_DIA_ERROR_COM_ENDED }, // COND_ERROR_TIMEOUT_TWO { .conditional_index = COND_ERROR_TIMEOUT_TWO, .next_if_true = COND_ERROR_COM_ENDED, - .next_if_false = DIA_ERROR_TIME_TWO + .next_if_false = PTGB_DIA_ERROR_TIME_TWO }, // COND_ERROR_COLOSSEUM { .conditional_index = COND_ERROR_COLOSSEUM, .next_if_true = COND_ERROR_DISCONNECT, - .next_if_false = DIA_ERROR_COLOSSEUM + .next_if_false = PTGB_DIA_ERROR_COLOSSEUM }, // COND_BEAT_E4 { .conditional_index = COND_BEAT_E4, .next_if_true = COND_MG_ENABLED, - .next_if_false = DIA_E4 + .next_if_false = PTGB_DIA_E4 }, // COND_MG_ENABLED { @@ -426,48 +426,48 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { { .conditional_index = COND_TUTORIAL_COMPLETE, .next_if_true = COND_BEAT_E4, - .next_if_false = DIA_OPEN + .next_if_false = PTGB_DIA_OPEN }, // COND_NEW_POKEMON { .conditional_index = COND_NEW_POKEMON, - .next_if_true = DIA_NEW_DEX, - .next_if_false = DIA_NO_NEW_DEX + .next_if_true = PTGB_DIA_NEW_DEX, + .next_if_false = PTGB_DIA_NO_NEW_DEX }, // COND_IS_HOENN_RS { .conditional_index = COND_IS_HOENN_RS, - .next_if_true = DIA_SEND_FRIEND_HOENN_RS, + .next_if_true = PTGB_DIA_SEND_FRIEND_HOENN_RS, .next_if_false = COND_IS_HOENN_E }, // COND_IS_FRLGE { .conditional_index = COND_IS_FRLGE, - .next_if_true = DIA_MG_FRLGE, - .next_if_false = DIA_MG_RS + .next_if_true = PTGB_DIA_MG_FRLGE, + .next_if_false = PTGB_DIA_MG_RS }, // COND_MG_OTHER_EVENT { .conditional_index = COND_MG_OTHER_EVENT, - .next_if_true = DIA_MG_OTHER_EVENT, + .next_if_true = PTGB_DIA_MG_OTHER_EVENT, .next_if_false = COND_PKMN_TO_COLLECT }, // COND_PKMN_TO_COLLECT { .conditional_index = COND_PKMN_TO_COLLECT, - .next_if_true = DIA_PKMN_TO_COLLECT, - .next_if_false = DIA_ASK_QUEST + .next_if_true = PTGB_DIA_PKMN_TO_COLLECT, + .next_if_false = PTGB_DIA_ASK_QUEST }, // COND_GB_ROM_EXISTS { .conditional_index = COND_GB_ROM_EXISTS, .next_if_true = CMD_SLIDE_PROF_RIGHT, - .next_if_false = DIA_NO_GB_ROM + .next_if_false = PTGB_DIA_NO_GB_ROM }, // COND_CHECK_MYTHIC { .conditional_index = COND_CHECK_MYTHIC, - .next_if_true = DIA_MYTHIC_CONVERT, + .next_if_true = PTGB_DIA_MYTHIC_CONVERT, .next_if_false = COND_CHECK_MISSINGNO }, // COND_CHECK_DEX @@ -479,26 +479,26 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { // COND_CHECK_KANTO { .conditional_index = COND_CHECK_KANTO, - .next_if_true = DIA_K_DEX_NOT_FULL, - .next_if_false = DIA_J_DEX_NOT_FULL + .next_if_true = PTGB_DIA_K_DEX_NOT_FULL, + .next_if_false = PTGB_DIA_J_DEX_NOT_FULL }, // COND_SOME_INVALID_PKMN { .conditional_index = COND_SOME_INVALID_PKMN, - .next_if_true = DIA_SOME_INVALID_PKMN, + .next_if_true = PTGB_DIA_SOME_INVALID_PKMN, .next_if_false = COND_CHECK_MYTHIC }, // COND_IS_HOENN_E { .conditional_index = COND_IS_HOENN_E, - .next_if_true = DIA_SEND_FRIEND_HOENN_E, - .next_if_false = DIA_SEND_FRIEND_KANTO + .next_if_true = PTGB_DIA_SEND_FRIEND_HOENN_E, + .next_if_false = PTGB_DIA_SEND_FRIEND_KANTO }, // COND_CHECK_MISSINGNO { .conditional_index = COND_CHECK_MISSINGNO, - .next_if_true = DIA_IS_MISSINGNO, - .next_if_false = DIA_IN_BOX + .next_if_true = PTGB_DIA_IS_MISSINGNO, + .next_if_false = PTGB_DIA_IN_BOX } }; @@ -506,24 +506,24 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { // -------- EVENTS SCRIPT -------- // Start the dialogue and show the menu // E_SCRIPT_START -// DIA_ASK_QUEST +// PTGB_DIA_ASK_QUEST // // Ask the user what game and language they're using -// DIA_WHAT_GAME_EVENT +// PTGB_DIA_WHAT_GAME_EVENT // CMD_GAME_MENU -// DIA_WHAT_LANG_EVENT +// PTGB_DIA_WHAT_LANG_EVENT // CMD_LANG_MENU -// DIA_ASK_QUEST +// PTGB_DIA_ASK_QUEST // CMD_SLIDE_PROF_LEFT // CMD_SLIDE_PROF_RIGHT // COND_GB_ROM_EXISTS -// DIA_NO_GB_ROM +// PTGB_DIA_NO_GB_ROM // // Check the player's dex // COND_CHECK_DEX // COND_CHECK_KANTO -// DIA_K_DEX_NOT_FULL -// DIA_J_DEX_NOT_FULL +// PTGB_DIA_K_DEX_NOT_FULL +// PTGB_DIA_J_DEX_NOT_FULL // // Hide the dialogue and professor // CMD_END_SCRIPT @@ -535,71 +535,71 @@ const script_obj_params transfer_script_params[SCRIPT_SIZE] = { // defining it this way does not generate an expensive function. It's all stored as read-only data in EWRAM. // Although frankly given the small amount of defined entries, we ARE wasting a lot of space with this one. (probably around 500 bytes) const script_obj_params event_script_params[SCRIPT_SIZE] = { - {}, // DIA_OPEN - {}, // DIA_E4 - {}, // DIA_MG_FRLGE - {}, // DIA_MG_RS - {}, // DIA_LETS_START - {}, // DIA_START - {}, // DIA_TRANS_GOOD - {}, // DIA_NEW_DEX - {}, // DIA_NO_NEW_DEX - {}, // DIA_SEND_FRIEND_KANTO - {}, // DIA_SEND_FRIEND_HOENN_RS - {}, // DIA_SEND_FRIEND_HOENN_E - {}, // DIA_THANK - {}, // DIA_GET_MON - {}, // DIA_MG_OTHER_EVENT - {}, // DIA_PKMN_TO_COLLECT - {}, // DIA_NO_VALID_PKMN - // DIA_ASK_QUEST + {}, // PTGB_DIA_OPEN + {}, // PTGB_DIA_E4 + {}, // PTGB_DIA_MG_FRLGE + {}, // PTGB_DIA_MG_RS + {}, // PTGB_DIA_LETS_START + {}, // PTGB_DIA_START + {}, // PTGB_DIA_TRANS_GOOD + {}, // PTGB_DIA_NEW_DEX + {}, // PTGB_DIA_NO_NEW_DEX + {}, // PTGB_DIA_SEND_FRIEND_KANTO + {}, // PTGB_DIA_SEND_FRIEND_HOENN_RS + {}, // PTGB_DIA_SEND_FRIEND_HOENN_E + {}, // PTGB_DIA_THANK + {}, // PTGB_DIA_GET_MON + {}, // PTGB_DIA_MG_OTHER_EVENT + {}, // PTGB_DIA_PKMN_TO_COLLECT + {}, // PTGB_DIA_NO_VALID_PKMN + // PTGB_DIA_ASK_QUEST { - .text_entry_index = DIA_ASK_QUEST, + .text_entry_index = PTGB_DIA_ASK_QUEST, .next_if_true = CMD_SLIDE_PROF_LEFT }, - {}, // DIA_WHAT_GAME_TRANS - {}, // DIA_WHAT_LANG_TRANS - // DIA_NO_GB_ROM + {}, // PTGB_DIA_WHAT_GAME_TRANS + {}, // PTGB_DIA_WHAT_LANG_TRANS + // PTGB_DIA_NO_GB_ROM { - .text_entry_index = DIA_NO_GB_ROM, - .next_if_true = DIA_WHAT_LANG_EVENT + .text_entry_index = PTGB_DIA_NO_GB_ROM, + .next_if_true = PTGB_DIA_WHAT_LANG_EVENT }, - {}, // DIA_IN_BOX - {}, // DIA_MYTHIC_CONVERT - {}, // DIA_CANCEL - {}, // DIA_SOME_INVALID_PKMN - {}, // DIA_MENU_BACK - {}, // DIA_IS_MISSINGNO - {}, // DIA_ERROR_COLOSSEUM - {}, // DIA_ERROR_COM_ENDED - {}, // DIA_ERROR_DISCONNECT - {}, // DIA_ERROR_TIME_ONE - {}, // DIA_ERROR_TIME_TWO - // DIA_WHAT_LANG_EVENT + {}, // PTGB_DIA_IN_BOX + {}, // PTGB_DIA_MYTHIC_CONVERT + {}, // PTGB_DIA_CANCEL + {}, // PTGB_DIA_SOME_INVALID_PKMN + {}, // PTGB_DIA_MENU_BACK + {}, // PTGB_DIA_IS_MISSINGNO + {}, // PTGB_DIA_ERROR_COLOSSEUM + {}, // PTGB_DIA_ERROR_COM_ENDED + {}, // PTGB_DIA_ERROR_DISCONNECT + {}, // PTGB_DIA_ERROR_TIME_ONE + {}, // PTGB_DIA_ERROR_TIME_TWO + // PTGB_DIA_WHAT_LANG_EVENT { - .text_entry_index = DIA_WHAT_LANG_EVENT, + .text_entry_index = PTGB_DIA_WHAT_LANG_EVENT, .next_if_true = CMD_LANG_MENU }, - // DIA_WHAT_GAME_EVENT + // PTGB_DIA_WHAT_GAME_EVENT { - .text_entry_index = DIA_WHAT_GAME_EVENT, + .text_entry_index = PTGB_DIA_WHAT_GAME_EVENT, .next_if_true = CMD_GAME_MENU }, - // DIA_K_DEX_NOT_FULL + // PTGB_DIA_K_DEX_NOT_FULL { - .text_entry_index = DIA_K_DEX_NOT_FULL, + .text_entry_index = PTGB_DIA_K_DEX_NOT_FULL, .next_if_true = CMD_END_SCRIPT }, - // DIA_J_DEX_NOT_FULL + // PTGB_DIA_J_DEX_NOT_FULL { - .text_entry_index = DIA_J_DEX_NOT_FULL, + .text_entry_index = PTGB_DIA_J_DEX_NOT_FULL, .next_if_true = CMD_END_SCRIPT }, {}, // T_SCRIPT_START // E_SCRIPT_START { .conditional_index = CMD_SHOW_PROF, - .next_if_true = DIA_ASK_QUEST + .next_if_true = PTGB_DIA_ASK_QUEST }, {}, // CMD_START_LINK {}, // CMD_IMPORT_POKEMON @@ -620,17 +620,17 @@ const script_obj_params event_script_params[SCRIPT_SIZE] = { { .conditional_index = CMD_GAME_MENU, .next_if_true = COND_GB_ROM_EXISTS, - .next_if_false = DIA_WHAT_LANG_EVENT + .next_if_false = PTGB_DIA_WHAT_LANG_EVENT }, // CMD_LANG_MENU { .conditional_index = CMD_LANG_MENU, - .next_if_true = DIA_WHAT_GAME_EVENT + .next_if_true = PTGB_DIA_WHAT_GAME_EVENT }, // CMD_SLIDE_PROF_LEFT { .conditional_index = CMD_SLIDE_PROF_LEFT, - .next_if_true = DIA_WHAT_LANG_EVENT + .next_if_true = PTGB_DIA_WHAT_LANG_EVENT }, // CMD_SLIDE_PROF_RIGHT { @@ -660,7 +660,7 @@ const script_obj_params event_script_params[SCRIPT_SIZE] = { { .conditional_index = COND_GB_ROM_EXISTS, .next_if_true = CMD_SLIDE_PROF_RIGHT, - .next_if_false = DIA_NO_GB_ROM + .next_if_false = PTGB_DIA_NO_GB_ROM }, {}, // COND_CHECK_MYTHIC // COND_CHECK_DEX @@ -672,8 +672,8 @@ const script_obj_params event_script_params[SCRIPT_SIZE] = { // COND_CHECK_KANTO { .conditional_index = COND_CHECK_KANTO, - .next_if_true = DIA_K_DEX_NOT_FULL, - .next_if_false = DIA_J_DEX_NOT_FULL + .next_if_true = PTGB_DIA_K_DEX_NOT_FULL, + .next_if_false = PTGB_DIA_J_DEX_NOT_FULL }, {}, // COND_SOME_INVALID_PKMN {}, // COND_IS_HOENN_E diff --git a/source/select_menu.cpp b/source/select_menu.cpp index fd8ae1d..3257f59 100644 --- a/source/select_menu.cpp +++ b/source/select_menu.cpp @@ -106,7 +106,7 @@ void Select_Menu::show_menu() { u8 decompression_buffer[2048]; text_data_table text_data(decompression_buffer); - text_data.decompress(get_compressed_general_table()); + text_data.decompress(get_compressed_GENERAL_table()); add_menu_box(menu_options.size(), startTileX, startTileY); for (unsigned int i = 0; i < menu_options.size(); i++) diff --git a/text_helper/main.py b/text_helper/main.py index 6f86ea3..32bc684 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -313,18 +313,6 @@ dir = os.curdir + "/text_helper" mainDict = {} -for lang in Languages: - mainDict[lang.name] = { - "PTGB": {}, - "RSEFRLG": {}, - "GB": {}, - "GENERAL": {}, - "CREDITS": {}, - "PKMN_NAMES": {}, - "Warnings" : {}, - "Errors": {}, -} - def convert_item(ogDict): line = ogDict["bytes"] numLines = ogDict["numLines"] @@ -445,13 +433,29 @@ def write_enum_to_header_file(hFile, prefix, dictionary): for key, line in dictionary.items(): hFile.write(f"#define {prefix}{key} {num}\n") num += 1 + hFile.write(f"\n#define {prefix}_LENGTH {num}\n") hFile.write("\n") return num print("Starting parse:") - currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") + +textSections = [] + +for row in currSheet.iterrows(): + currRow = row[1]["Text Section"] + if (currRow not in textSections): + textSections.append(currRow) + +for lang in Languages: + mainDict[lang.name] = {} + for section in textSections: + mainDict[lang.name][section] = {} + mainDict[lang.name]["Warnings"] = {} + mainDict[lang.name]["Errors"] = {} + + for row in currSheet.iterrows(): #print(row) for lang in Languages: @@ -467,116 +471,66 @@ for row in currSheet.iterrows(): "includeBoxBreaks": currRow.iloc[5], } +print("\tGenerating header file") # generate the header file with open (os.curdir + '/include/translated_text.h', 'w') as hFile: - hFile.write("#ifndef DIALOGUE_H\n#define DIALOGUE_H\n\n#include \n\n") + hFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n\n#ifndef TRANSLATED_TEXT_H\n#define TRANSLATED_TEXT_H\n\n#include \n\n") - # PTGB - num = write_enum_to_header_file(hFile, "", mainDict[lang.name]["PTGB"]) - hFile.write(f"\n#define DIA_SIZE {num}\n#define DIA_END DIA_SIZE\n\n") - # RSEFRLG - write_enum_to_header_file(hFile, "RSEFRLG_", mainDict[lang.name]["RSEFRLG"]) + for section in textSections: + num = write_enum_to_header_file(hFile, section + "_", mainDict[lang.name][section]) + if(section == "PTGB"): + hFile.write(f"\n#define DIA_END {num}\n") - # GENERAL - write_enum_to_header_file(hFile, "GENERAL_", mainDict[lang.name]["GENERAL"]) + hFile.write("/** Returns the LZ10 compressed " + section + " text table.*/\n") + hFile.write("const u8* get_compressed_" + section + "_table();\n\n") - # CREDITS - write_enum_to_header_file(hFile, "CREDITS_", mainDict[lang.name]["CREDITS"]) - - # PKMN_NAMES - write_enum_to_header_file(hFile, "PKMN_NAMES_", mainDict[lang.name]["PKMN_NAMES"]) - - hFile.write("/** Returns the LZ10 compressed PTGB text table.*/\n") - hFile.write("const u8* get_compressed_PTGB_table();\n\n") - hFile.write("/** Returns the LZ10 compressed RSEFRLG text table.*/\n") - hFile.write("const u8* get_compressed_rsefrlg_table();\n\n") - hFile.write("/** Returns the LZ10 compressed GENERAL text table.*/\n") - hFile.write("const u8* get_compressed_general_table();\n\n") - hFile.write("/** Returns the LZ10 compressed CREDITS text table.*/\n") - hFile.write("const u8* get_compressed_credits_table();\n\n") - hFile.write("/** Returns the LZ10 compressed PKMN_NAMES text table.*/\n") - hFile.write("const u8* get_compressed_pkmn_names_table();\n\n") + hFile.write("") hFile.write("\n#endif") hFile.close() +print("\tGenerating text tables") # now generate the text tables for lang in Languages: - # PTGB - table_file = os.curdir + '/to_compress/PTGB_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name]["PTGB"]) - - # RSEFRLG - table_file = os.curdir + '/to_compress/RSEFRLG_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name]["RSEFRLG"]) - - # GENERAL - table_file = os.curdir + '/to_compress/GENERAL_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name]["GENERAL"]) - - # CREDITS - table_file = os.curdir + '/to_compress/CREDITS_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name]["CREDITS"]) - - # PKMN_NAMES - table_file = os.curdir + '/to_compress/PKMN_NAMES_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name]["PKMN_NAMES"]) + for section in textSections: + table_file = os.curdir + '/to_compress/' + section + '_' + lang.name.lower() + '.bin' + write_text_bin_file(table_file, mainDict[lang.name][section]) +print("\tGenerating cpp file") # now generate the cpp file. with open(os.curdir + '/source/translated_text.cpp', 'w') as cppFile: - cppFile.write("#include \"translated_text.h\"\n#include \"debug_mode.h\"\n") + cppFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n#include \"translated_text.h\"\n#include \"debug_mode.h\"\n") # generate includes for each language for lang in Languages: - for cat in mainDict[lang.name]: - if cat in {"PTGB", "RSEFRLG", "GENERAL", "CREDITS", "PKMN_NAMES"}: - cppFile.write("#include \"" + cat.upper() + "_" + lang.name.lower() + "_lz10_bin.h\"\n") + for section in textSections: + cppFile.write("#include \"" + section.upper() + "_" + lang.name.lower() + "_lz10_bin.h\"\n") for lang in Languages: cppFile.write(f"\n#if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") - # PTGB - cppFile.write("const u8* get_compressed_PTGB_table()\n") - cppFile.write("{\n") - cppFile.write("\treturn PTGB_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("}\n\n") - # RSEFRLG - cppFile.write("const u8* get_compressed_rsefrlg_table()\n") - cppFile.write("{\n") - cppFile.write("\treturn RSEFRLG_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("}\n\n") - # GENERAL - cppFile.write("const u8* get_compressed_general_table()\n") - cppFile.write("{\n") - cppFile.write("\treturn GENERAL_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("}\n\n") - # CREDITS - cppFile.write("const u8* get_compressed_credits_table()\n") - cppFile.write("{\n") - cppFile.write("\treturn CREDITS_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("}\n\n") - # PKMN_NAMES - cppFile.write("const u8* get_compressed_pkmn_names_table()\n") - cppFile.write("{\n") - cppFile.write("\treturn PKMN_NAMES_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("}\n\n") + for section in textSections: + cppFile.write("const u8* get_compressed_" + section + "_table()\n") + cppFile.write("{\n") + cppFile.write("\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") + cppFile.write("}\n\n") cppFile.write(f"#endif\n\n\n") +print("\tOutputting json file") for lang in Languages: - for cat in mainDict[lang.name]: - if cat in {"PTGB", "RSEFRLG", "GENERAL", "CREDITS", "PKMN_NAMES"}: - for item in mainDict[lang.name][cat]: - string = mainDict[lang.name][cat][item]["bytes"].split(" ") - outText = "" - if lang == Languages.Japanese: - arr = jpnCharArray - else: - arr = engCharArray - for byte in string: - byte = engCharArray[int(byte, 16)] - outText += chr(byte) - mainDict[lang.name][cat][item]["text"] = outText + for section in textSections: + for item in mainDict[lang.name][section]: + string = mainDict[lang.name][section][item]["bytes"].split(" ") + outText = "" + if lang == Languages.Japanese: + arr = jpnCharArray + else: + arr = engCharArray + for byte in string: + byte = engCharArray[int(byte, 16)] + outText += chr(byte) + mainDict[lang.name][section][item]["text"] = outText with open(dir + '/output.json', 'w') as jsonFile: jsonFile.write(json.dumps(mainDict)) From 2482bb486b96a8dea53ad272eb2b4628aa9b3abe Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 11 Jan 2026 17:09:30 -0500 Subject: [PATCH 03/68] Modifying decompression function to be based on indexes --- source/box_menu.cpp | 2 +- source/gameboy_colour.cpp | 6 +++--- source/global_frame_controller.cpp | 2 +- source/main.cpp | 10 ++++----- source/multiboot_upload.cpp | 2 +- source/mystery_gift_builder.cpp | 2 +- source/pokedex.cpp | 4 ++-- source/select_menu.cpp | 2 +- source/text_engine.cpp | 2 +- text_helper/main.py | 33 ++++++++++++++++++++++-------- 10 files changed, 40 insertions(+), 25 deletions(-) diff --git a/source/box_menu.cpp b/source/box_menu.cpp index cbe8b0e..22e5265 100644 --- a/source/box_menu.cpp +++ b/source/box_menu.cpp @@ -36,7 +36,7 @@ int Box_Menu::box_main(PokeBox* box) obj_unhide(box_select, 0); int index = 0; - PKMN_NAMES.decompress(get_compressed_PKMN_NAMES_table()); + PKMN_NAMES.decompress(get_compressed_text_table(PKMN_NAMES_INDEX)); while (true) { diff --git a/source/gameboy_colour.cpp b/source/gameboy_colour.cpp index 8be2d06..4db3084 100644 --- a/source/gameboy_colour.cpp +++ b/source/gameboy_colour.cpp @@ -169,7 +169,7 @@ void setup(const u16 *debug_charset) u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); ptgb_write(general_text.get_text_entry(GENERAL_connecting), true); } } @@ -228,7 +228,7 @@ byte handleIncomingByte(byte in, byte *box_data_storage, byte *curr_payload, GB_ u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); ptgb_write(general_text.get_text_entry(curr_gb_rom->version != YELLOW_ID ? GENERAL_link_success : GENERAL_link_success_yellow), true); } @@ -280,7 +280,7 @@ byte handleIncomingByte(byte in, byte *box_data_storage, byte *curr_payload, GB_ u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); ptgb_write(general_text.get_text_entry(GENERAL_transferring), true); } diff --git a/source/global_frame_controller.cpp b/source/global_frame_controller.cpp index 1a45688..f6626b8 100644 --- a/source/global_frame_controller.cpp +++ b/source/global_frame_controller.cpp @@ -27,7 +27,7 @@ static void __attribute__((noinline)) show_pulled_cart_error() u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); ptgb_write(general_text.get_text_entry(GENERAL_pulled_cart_error), true); } diff --git a/source/main.cpp b/source/main.cpp index eba3e03..dc547b7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -147,7 +147,7 @@ void game_load_error(void) u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); ptgb_write(general_text.get_text_entry(GENERAL_cart_load_error), true); } @@ -189,7 +189,7 @@ void first_load_message(void) u8 general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); ptgb_write(general_text.get_text_entry(GENERAL_intro_first), true); } @@ -206,7 +206,7 @@ int credits() text_data_table credits_text_table(text_decompression_buffer); int curr_credits_num = 0; - credits_text_table.decompress(get_compressed_CREDITS_table()); + credits_text_table.decompress(get_compressed_text_table(CREDITS_INDEX)); bool update = true; global_next_frame(); @@ -329,7 +329,7 @@ int main_menu_loop() int return_values[NUM_MENU_OPTIONS] = {BTN_TRANSFER, BTN_POKEDEX, BTN_CREDITS}; u16 test = 0; - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); while (true) { @@ -429,7 +429,7 @@ static void __attribute__((noinline)) show_intro() text_data_table general_text(general_text_table_buffer); const u8 *text_entry; - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); text_entry = general_text.get_text_entry(GENERAL_press_start); press_start_text_length = get_string_length(text_entry); diff --git a/source/multiboot_upload.cpp b/source/multiboot_upload.cpp index 6712018..a4b5ab9 100644 --- a/source/multiboot_upload.cpp +++ b/source/multiboot_upload.cpp @@ -19,7 +19,7 @@ void multiboot_upload_screen() text_data_table general_text(general_text_table_buffer); LinkCableMultiboot linkCableMultiboot; - general_text.decompress(get_compressed_GENERAL_table()); + general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); multiboot_show_textbox(); ptgb_write(general_text.get_text_entry(GENERAL_send_multiboot_instructions), true); diff --git a/source/mystery_gift_builder.cpp b/source/mystery_gift_builder.cpp index 7651f86..820b167 100644 --- a/source/mystery_gift_builder.cpp +++ b/source/mystery_gift_builder.cpp @@ -378,7 +378,7 @@ void mystery_gift_script::build_script(PokeBox *box) // this decompresses the ZX0 compressed text table into the buffer inside of the decompressed_store union // thereby reusing the stack (=IWRAM) memory used earlier for the PokemonTables instance we used above - decompressed_text_table.decompress(get_compressed_RSEFRLG_table()); + decompressed_text_table.decompress(get_compressed_text_table(RSEFRLG_INDEX)); switch (curr_GBA_rom.gamecode) { case RUBY_ID: diff --git a/source/pokedex.cpp b/source/pokedex.cpp index 129400f..92c4166 100644 --- a/source/pokedex.cpp +++ b/source/pokedex.cpp @@ -41,7 +41,7 @@ static void load_text_entry_into_buffer(text_data_table& data_table, u8 *output_ static void load_general_table_text_entries(u8 *decompression_buffer, u8 *kanto_buffer, u8 *johto_buffer) { text_data_table data_table(decompression_buffer); - data_table.decompress(get_compressed_GENERAL_table()); + data_table.decompress(get_compressed_text_table(GENERAL_INDEX)); load_text_entry_into_buffer(data_table, kanto_buffer, GENERAL_kanto_name); load_text_entry_into_buffer(data_table, johto_buffer, GENERAL_johto_name); @@ -105,7 +105,7 @@ int pokedex_loop() load_general_table_text_entries(decompression_buffer, kanto_name, johto_name); text_data_table PKMN_NAMES(decompression_buffer); - PKMN_NAMES.decompress(get_compressed_PKMN_NAMES_table()); + PKMN_NAMES.decompress(get_compressed_text_table(PKMN_NAMES_INDEX)); pokedex_init(); pokedex_show(); diff --git a/source/select_menu.cpp b/source/select_menu.cpp index 3257f59..2f51ffe 100644 --- a/source/select_menu.cpp +++ b/source/select_menu.cpp @@ -106,7 +106,7 @@ void Select_Menu::show_menu() { u8 decompression_buffer[2048]; text_data_table text_data(decompression_buffer); - text_data.decompress(get_compressed_GENERAL_table()); + text_data.decompress(get_compressed_text_table(GENERAL_INDEX)); add_menu_box(menu_options.size(), startTileX, startTileY); for (unsigned int i = 0; i < menu_options.size(); i++) diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 51d94be..4a00b7e 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -35,7 +35,7 @@ static __attribute__((noinline)) const u8* read_dialogue_text_entry(uint8_t inde text_data_table dialogue_table(text_decompression_buffer); - dialogue_table.decompress(get_compressed_PTGB_table()); + dialogue_table.decompress(get_compressed_text_table(PTGB_INDEX)); text_entry = dialogue_table.get_text_entry(index); memcpy(output_buffer, text_entry, dialogue_table.get_text_entry_size(index)); diff --git a/text_helper/main.py b/text_helper/main.py index 32bc684..9f540d3 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -433,7 +433,7 @@ def write_enum_to_header_file(hFile, prefix, dictionary): for key, line in dictionary.items(): hFile.write(f"#define {prefix}{key} {num}\n") num += 1 - hFile.write(f"\n#define {prefix}_LENGTH {num}\n") + hFile.write(f"\n#define {prefix}LENGTH {num}\n") hFile.write("\n") return num @@ -476,16 +476,26 @@ print("\tGenerating header file") with open (os.curdir + '/include/translated_text.h', 'w') as hFile: hFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n\n#ifndef TRANSLATED_TEXT_H\n#define TRANSLATED_TEXT_H\n\n#include \n\n") - + sectionEnds = [] + index = 0 for section in textSections: num = write_enum_to_header_file(hFile, section + "_", mainDict[lang.name][section]) + hFile.write("#define " + section + "_INDEX " + str(index)) if(section == "PTGB"): hFile.write(f"\n#define DIA_END {num}\n") hFile.write("/** Returns the LZ10 compressed " + section + " text table.*/\n") - hFile.write("const u8* get_compressed_" + section + "_table();\n\n") + sectionEnds.append(num) + index += 1 + + hFile.write("#define NUM_TEXT_SECTIONS " + str(index) + "\n") + hFile.write("const int text_section_lengths[] = {\n") + for end in sectionEnds: + hFile.write("\t" + str(end) + ",\n") + hFile.write("};\n\n") + + hFile.write("const u8* get_compressed_text_table(int table_index);\n") - hFile.write("") hFile.write("\n#endif") hFile.close() @@ -508,12 +518,17 @@ with open(os.curdir + '/source/translated_text.cpp', 'w') as cppFile: for lang in Languages: cppFile.write(f"\n#if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") + cppFile.write("const u8* get_compressed_text_table(int table_index)\n") + cppFile.write("{\n") + cppFile.write("\tswitch (table_index)\n\t{\n") for section in textSections: - cppFile.write("const u8* get_compressed_" + section + "_table()\n") - cppFile.write("{\n") - cppFile.write("\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("}\n\n") - + cppFile.write("\tcase(" + section + "_INDEX):\n") + if(section == "PTGB"): + cppFile.write("\tdefault:\n") + cppFile.write("\t\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") + cppFile.write("\t\tbreak;\n") + cppFile.write("\t}\n") + cppFile.write("}\n\n") cppFile.write(f"#endif\n\n\n") From 2309f7c493de721ffc515408dd157c634fdaaf40 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 11 Jan 2026 17:33:02 -0500 Subject: [PATCH 04/68] Adding text debug menu --- Makefile | 2 +- PCCS | 2 +- include/button_menu.h | 1 + include/debug_mode.h | 5 +- include/text_engine.h | 4 + source/main.cpp | 28 ++++-- source/text_engine.cpp | 190 ++++++++++++++++++++++++++++++++--------- 7 files changed, 182 insertions(+), 50 deletions(-) diff --git a/Makefile b/Makefile index b7ddae6..efce3bb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # Build configuration (set to either 'debug' or 'release') -BUILD_TYPE := release +BUILD_TYPE := debug #--------------------------------------------------------------------------------- .SUFFIXES: diff --git a/PCCS b/PCCS index 1c98a39..db430d8 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit 1c98a391c2261c8141c8917160b2040514a0c2ee +Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 diff --git a/include/button_menu.h b/include/button_menu.h index cd923a6..78b8250 100644 --- a/include/button_menu.h +++ b/include/button_menu.h @@ -12,6 +12,7 @@ #define BTN_EVENTS 3 #define BTN_CREDITS 4 #define BTN_OPENING 5 +#define BTN_TEXT_DEBUG 6 #define BUTTON_CANCEL UINT8_MAX diff --git a/include/debug_mode.h b/include/debug_mode.h index 1b11c61..a3005a6 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -2,9 +2,9 @@ #define DEBUG_MODE_H #define VERSION "v1.2.1" -#define PTGB_BUILD_LANGUAGE 2 +#define PTGB_BUILD_LANGUAGE ENGLISH -#define DEBUG_MODE false +#define DEBUG_MODE true #define PRINT_LINK_DATA (false && DEBUG_MODE) #define INSTANT_TEXT_SPEED (false && DEBUG_MODE) @@ -24,6 +24,7 @@ #define PAYLOAD_EXPORT_TEST (false && DEBUG_MODE) #define DONT_TRANSFER_POKEMON_AT_INDEX_X (false && DEBUG_MODE) #define POKEMON_INDEX_TO_SKIP 5 +#define ENABLE_TEXT_DEBUG_SCREEN (true && DEBUG_MODE) #define DEBUG_GAME EMERALD_ID #define DEBUG_VERS VERS_1_0 diff --git a/include/text_engine.h b/include/text_engine.h index e733c04..1b3915b 100644 --- a/include/text_engine.h +++ b/include/text_engine.h @@ -15,6 +15,10 @@ #define INK_ROM_COLOR 14 #define INK_DARK_GREY 13 +#define SCRIPT_DEBUG 0 +#define SCRIPT_TRANSFER 1 +#define SCRIPT_EVENT 2 + void init_text_engine(); int text_loop(int script); int text_next_obj_id(script_obj current_line); diff --git a/source/main.cpp b/source/main.cpp index dc547b7..5f08e1d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -317,16 +317,22 @@ int credits() } }; -#define NUM_MENU_OPTIONS 3 - int main_menu_loop() { +#if ENABLE_TEXT_DEBUG_SCREEN +#define NUM_MENU_OPTIONS 4 + const uint8_t menu_options[NUM_MENU_OPTIONS] = {GENERAL_option_transfer, GENERAL_option_dreamdex, GENERAL_option_credits, GENERAL_option_text_debug}; + int return_values[NUM_MENU_OPTIONS] = {BTN_TRANSFER, BTN_POKEDEX, BTN_CREDITS, BTN_TEXT_DEBUG}; +#else +#define NUM_MENU_OPTIONS 3 + const uint8_t menu_options[NUM_MENU_OPTIONS] = {GENERAL_option_transfer, GENERAL_option_dreamdex, GENERAL_option_credits}; + int return_values[NUM_MENU_OPTIONS] = {BTN_TRANSFER, BTN_POKEDEX, BTN_CREDITS}; +#endif + uint8_t general_text_table_buffer[2048]; text_data_table general_text(general_text_table_buffer); bool update = true; - const uint8_t menu_options[NUM_MENU_OPTIONS] = {GENERAL_option_transfer, GENERAL_option_dreamdex, GENERAL_option_credits}; const uint8_t *text_entry; - int return_values[NUM_MENU_OPTIONS] = {BTN_TRANSFER, BTN_POKEDEX, BTN_CREDITS}; u16 test = 0; general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); @@ -380,8 +386,8 @@ int main_menu_loop() } } -// Legal mumbo jumbo -static void show_legal_text(const u8* intro_text) +// Legal stuff +static void show_legal_text(const u8 *intro_text) { tte_set_margins(8, 8, H_MAX - 8, V_MAX - 8); tte_set_pos(8, 8); @@ -535,7 +541,7 @@ int main(void) tte_set_ink(INK_DARK_GREY); obj_hide_multi(ptgb_logo_l, 2); load_flex_background(BG_FENNEL, 3); - text_loop(BTN_TRANSFER); + text_loop(SCRIPT_TRANSFER); break; case (BTN_POKEDEX): if (get_tutorial_flag()) @@ -560,7 +566,13 @@ int main(void) break; case (BTN_EVENTS): obj_hide_multi(ptgb_logo_l, 2); - text_loop(BTN_EVENTS); + text_loop(SCRIPT_EVENT); + break; + case (BTN_TEXT_DEBUG): + tte_set_ink(INK_DARK_GREY); + REG_BG1CNT = (REG_BG1CNT & ~BG_PRIO_MASK) | BG_PRIO(3); + obj_hide_multi(ptgb_logo_l, 2); + text_loop(SCRIPT_DEBUG); break; default: global_next_frame(); diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 4a00b7e..2a8efb7 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -11,6 +11,7 @@ #include "latin_short.h" #include "japanese_small.h" #include "text_data_table.h" +#include "background_engine.h" #define TEXT_CBB 0 #define TEXT_SBB 10 @@ -28,7 +29,7 @@ bool text_exit; // Doing it this way does mean that we need to completely restart decompression whenever we switch from dialog entry. // but given that it requires user input to do so, I believe it's worth it and not time-critical. // attribute noinline was used to make sure the compiler doesn't inline this code back into text_loop() -static __attribute__((noinline)) const u8* read_dialogue_text_entry(uint8_t index, u8 *output_buffer) +static __attribute__((noinline)) const u8 *read_dialogue_text_entry(uint8_t index, u8 *output_buffer) { u8 text_decompression_buffer[6144]; const u8 *text_entry; @@ -43,6 +44,21 @@ static __attribute__((noinline)) const u8* read_dialogue_text_entry(uint8_t inde return output_buffer; } +static __attribute__((noinline)) const u8 *read_dialogue_text_entry(uint8_t index, uint8_t text_section, u8 *output_buffer) +{ + u8 text_decompression_buffer[6144]; + const u8 *text_entry; + + text_data_table dialogue_table(text_decompression_buffer); + + dialogue_table.decompress(get_compressed_text_table(text_section)); + + text_entry = dialogue_table.get_text_entry(index); + memcpy(output_buffer, text_entry, dialogue_table.get_text_entry_size(index)); + + return output_buffer; +} + void init_text_engine() { // Load the TTE @@ -82,51 +98,148 @@ int text_loop(int script) u8 diag_entry_text_buffer[1024]; switch (script) { - case BTN_TRANSFER: + case SCRIPT_DEBUG: + curr_line = transfer_script_params[0]; + break; + + case SCRIPT_TRANSFER: curr_line = transfer_script_params[T_SCRIPT_START]; break; - case BTN_EVENTS: + case SCRIPT_EVENT: curr_line = event_script_params[E_SCRIPT_START]; break; } curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; - REG_BG1CNT = (REG_BG1CNT && !BG_PRIO_MASK) | BG_PRIO(2); // Show Fennel - show_text_box(); // tte_set_margins(LEFT, TOP, RIGHT, BOTTOM); - while (true) // This loops through all the connected script objects + if (script != SCRIPT_DEBUG) { - if (curr_text != NULL && curr_text[char_index] != 0xFF && curr_text[char_index] != 0xFB) + REG_BG1CNT = (REG_BG1CNT && !BG_PRIO_MASK) | BG_PRIO(2); // Show Fennel + show_text_box(); + while (true) // This loops through all the connected script objects { - tte_set_pos(LEFT, TOP); - tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); - ptgb_write(curr_text, char_index); + if (curr_text != NULL && curr_text[char_index] != 0xFF && curr_text[char_index] != 0xFB) + { + tte_set_pos(LEFT, TOP); + tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); + ptgb_write(curr_text, false); + } + + wait_for_user_to_continue(false); + + line_char_index = 0; + switch (script) + { + case BTN_TRANSFER: + curr_line = transfer_script_params[text_next_obj_id(curr_line)]; + break; + case BTN_EVENTS: + curr_line = event_script_params[text_next_obj_id(curr_line)]; + break; + } + + curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; + char_index = 0; + + if (text_exit) + { + hide_text_box(); + tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); + text_exit = false; + return 0; + } } + } + else // Debug script loop + { + u16 debug_charset[256]; + load_localized_charset(debug_charset, 3, ENGLISH); - wait_for_user_to_continue(false); - - line_char_index = 0; - switch (script) + int text_section = 0; + int text_identifier = 0; + while (true) { - case BTN_TRANSFER: - curr_line = transfer_script_params[text_next_obj_id(curr_line)]; - break; - case BTN_EVENTS: - curr_line = event_script_params[text_next_obj_id(curr_line)]; - break; - } + bool exit = false; + bool update_text = true; + key_poll(); + while (!exit) + { + if (key_hit(KEY_LEFT)) + { + text_identifier = (text_identifier + (text_section_lengths[text_section] - 1)) % text_section_lengths[text_section]; + update_text = true; + } + else if (key_hit(KEY_RIGHT)) + { + text_identifier = (text_identifier + 1) % text_section_lengths[text_section]; + update_text = true; + } + else if (key_hit(KEY_UP)) + { + text_section = (text_section + 1) % NUM_TEXT_SECTIONS; + update_text = true; + } + else if (key_hit(KEY_DOWN)) + { + text_section = (text_section + (NUM_TEXT_SECTIONS - 1)) % NUM_TEXT_SECTIONS; + update_text = true; + } + else if (key_hit(KEY_START)) + { + exit = true; + } + if (update_text) + { + if (text_identifier > text_section_lengths[text_section]) + { + text_identifier = text_section_lengths[text_section]; + } + if (text_section > NUM_TEXT_SECTIONS) + { + text_section = NUM_TEXT_SECTIONS; + } + tte_set_pos(0, 0); + tte_erase_rect(0, 0, 240, 160); + ptgb_write_debug(debug_charset, "(", true); + ptgb_write_debug(debug_charset, ptgb::to_string(text_section), true); + ptgb_write_debug(debug_charset, ", ", true); + ptgb_write_debug(debug_charset, ptgb::to_string(text_identifier), true); + ptgb_write_debug(debug_charset, ")", true); + update_text = false; + } + global_next_frame(); + } - curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; - char_index = 0; + line_char_index = 0; + curr_text = read_dialogue_text_entry(text_identifier, text_section, diag_entry_text_buffer); + char_index = 0; - if (text_exit) - { + if (curr_text != NULL && curr_text[char_index] != 0xFF && curr_text[char_index] != 0xFB) + { + if (text_section == PTGB_INDEX) + { + reset_textbox(); + } + else + { + create_textbox(4, 1, 160, 80, true); + } + show_text_box(); + tte_erase_rect(0, 0, 240, 160); + ptgb_write(curr_text, true); + } + + wait_for_user_to_continue(false); + update_text = true; hide_text_box(); - tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); - text_exit = false; - return 0; + + if (text_exit) + { + text_exit = false; + return 0; + } } } } @@ -184,15 +297,15 @@ int ptgb_write(const byte *text, bool instant, int length) TFont *font; int num = 0; -/* - if (curr_text[char_index] == 0xFB) // This will need to be moved - { - line_char_index += char_index; - line_char_index++; - // Low key kinda scuffed, but it works to split the string - curr_text = &curr_line.get_text()[line_char_index]; - } -*/ + /* + if (curr_text[char_index] == 0xFB) // This will need to be moved + { + line_char_index += char_index; + line_char_index++; + // Low key kinda scuffed, but it works to split the string + curr_text = &curr_line.get_text()[line_char_index]; + } + */ while ((ch = *str) != 0xFF && num < length) { if (get_frame_count() % 2 == 0 || key_held(KEY_B) || key_held(KEY_A) || instant) @@ -253,7 +366,7 @@ int ptgb_write(const byte *text, bool instant, int length) return 0; // str - text; } // This is mostly used for debug stuff, I shouldn't rely it on it much. -int ptgb_write_debug(const u16* charset, const char *text, bool instant) +int ptgb_write_debug(const u16 *charset, const char *text, bool instant) { byte temp_holding[256]; int i; @@ -296,6 +409,7 @@ void wait_for_user_to_continue(bool clear_text) fennel_speak(0); } } + key_poll(); while (!(key_hit(KEY_A) || key_hit(KEY_B) || curr_text == NULL)) { global_next_frame(); From 5462c1a715fac9c144ff655e590c2e752898ddf0 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 13 Jan 2026 14:52:27 -0500 Subject: [PATCH 05/68] Updating log warnings & errors --- text_helper/main.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 9f540d3..985f6a0 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -142,24 +142,26 @@ charConversionList = [ ["'", "’"], ] +def logWarningError(type, text): + nType = type + "s" + nText = type + ": " + text + if nText not in mainDict[lang.name][nType].values(): + mainDict[lang.name][nType][max(mainDict[lang.name][nType].keys(), default =- 1) + 1] = nText + #print(nText) + def convertByte(incoming, array): for pair in charConversionList: if incoming == ord(pair[0]): incoming = ord(pair[1]) - #print(f"Warning! {pair[0]} found, replacing with {pair[1]} !") - next_key = max(mainDict[lang.name]["Warnings"].keys(), default =- 1) + 1 - mainDict[lang.name]["Warnings"][next_key] = f"Warning! {pair[0]} found, replacing with {pair[1]} !" + logWarningError("Warning", f"Character {pair[0]} was used but is not in character table. Replaced with {pair[1]} .") index = 0 for val in array: if val == incoming: return index - index += 1 - #print(f"Error! No match found for char [ {chr(incoming)} ]!") - - next_key = max(mainDict[lang.name]["Errors"].keys(), default =- 1) + 1 - mainDict[lang.name]["Errors"][next_key] = f"Error! No match found for char [ {chr(incoming)} ]!" + index += 1 + logWarningError("Error", f"No match found for char [ {chr(incoming)} ]!") return 0 def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): @@ -215,9 +217,7 @@ def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): # Test if the word is too long in general elif (wordLength > pixelsInLine): - #print(f"ERROR: Word {word} exceeds alloted length") - next_key = max(mainDict[lang.name]["Errors"].keys(), default =- 1) + 1 - mainDict[lang.name]["Errors"][next_key] = f"ERROR: Word {word} exceeds alloted length" + logWarningError("Error", f"Word {word} exceeds alloted length") currWordIndex += 1 # Test if adding the word will go over our alloted space @@ -347,14 +347,10 @@ def convert_item(ogDict): escapeCount += 1 #print(index) if not include_box_breaks: - #print(f"ERROR! Made a line break when disabled, sentence \"{outStr}\" is too long!") - next_key = max(mainDict[lang.name]["Errors"].keys(), default =- 1) + 1 - mainDict[lang.name]["Errors"][next_key] = f"ERROR! Made a line break when disabled, sentence \"{outStr}\" is too long!" + logWarningError("Error", f"Made a line break when disabled, sentence \"{outStr}\" is too long!") if escapeCount == 100: - #print(f"ERROR! Sentence \"{out}\" is too long!") - next_key = max(mainDict[lang.name]["Errors"].keys(), default =- 1) + 1 - mainDict[lang.name]["Errors"][next_key] = f"ERROR! Sentence \"{out}\" is too long!" + logWarningError("Error", f"Sentence \"{out}\" is too long!") # Some cases that should be fixed exitLoop = False @@ -416,9 +412,7 @@ def write_text_bin_file(filename, dictionary): current_offset += len(linedata) if len(linedata) > 1024: - #print(f"Error: entry '{key}' numBytes exceeds 1024 (got {len(linedata)}). Trunacting to 1024.", file=sys.stderr) - next_key = max(mainDict[lang.name]["Errors"].keys(), default =- 1) + 1 - mainDict[lang.name]["Errors"][next_key] = f"ERROR! Contents of dialogue with identifier \"{key}\" exceeds 1024 bytes!" + logWarningError("Error", f"Contents of dialogue with identifier \"{key}\" exceeds 1024 bytes!") linedata = linedata[:1024] num += 1 From f50d42f61fb1a7e47c7311a951b48a260f638ec4 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 13 Jan 2026 20:24:03 -0500 Subject: [PATCH 06/68] Updating accent characters --- graphics/unused graphics/latin_short.png | Bin 1360 -> 1361 bytes include/debug_mode.h | 2 +- source/latin_short.s | 4 +- text_helper/main.py | 95 +++++++++++++---------- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/graphics/unused graphics/latin_short.png b/graphics/unused graphics/latin_short.png index 20259436b849b9c0149665476d4a471eae76c999..75ec62c95fa15811f5fce198ec5a140237163cb3 100644 GIT binary patch delta 1281 zcmV+c1^)Wb3egIXV1L|6L_t(|ob6i)a_lM$L^qJ`f7x#Ytp(e7t9td5&=XI_TO(v7 zB*3L?Al|-Y+v@*epSKV7<7q!!zulpKh3bFF`#4{Q{d7Kpo%TLzsn}FH;oVlk@p!$J z>YuXPO3(rH;8*VmYAGdcmjqE%RO7V-{qH>iy;0)AuRyeK;(sF>y<~9v=8a7_kRBA0 zoXina2r@r1but^1@Mw)=Ev8PUikwWj^eSkZ=T4BF4rLP`B;Y1Nw6>juFxkVCgGA85 z&N}&|3ebTbC$sl+A5oQg;H+xLRb`mshhp~N?hH&H zPD0?^Q-5Jzfo6^GPP?3sX8sd`S>|zvfMyoFZ1ay{n5lXUjE#?z4!7Zh>O%6OZ`f{< zN!7Mh8WPj^y#AD024h`2Nhz<_5RKOOu5kg9Q zr{S_e0&0tS{A463cL>6mB1(KjP+WpG?%Bsx#iNy`gMLldfn`7_!;55#lQ8mzs*SPs zj({MOxbmWI&L5>+SW>BxAWFF#QowVzvBj|NBDIe*f`wlJoeCg^DY)I=O;Xi(h|N~8 zz<(_-6NG~)_TRGLRyr4?3e3DG2+cdM-0K7?MRB*Fr+W!lh7@m{E;HD#K_nv;aicpO z2r%=;I)vsh+bqW&3Ap$gL=aGUP=zQ#yP5_9o)aY7?@EmI-aW=o;E!_-3;zrFa{Jt+ z#b2WzKqnM>t2A9^-Fr22IcUJC&pagHD}RC#d`5Wb0cXqt+%mFx7(GE3AAl@GbQmpkR-aaejHV>CDm@fDLPM1BvjW3xPKF9 zjD*sYUV@dDCV`O=^va0PLe8*zr@@nvNWjrKf}rI>qYfZJF#;N+z=c1HZ%b;7W%JKOX-9)v0N#UikG*00000NkvXXu0mjfR)uh@ delta 1280 zcmV+b1^@cd3eXCWV1L_5L_t(|ob6i+lH?!=g&V;AFS`*0fuzl^YHEAN*OQ*k?}+czL&W#EG#Vi zO?Vbc(y3@fF5Oe<8A4!MV(me&<;}Epi!FUd?1{KW&VPE-7Jll@R0-^N0~&$sV}qyA z9|^n)?Up75W6Ot?K*?HFh53j8J>YJ6f|ZN1*9a`x=1&AB1!`0zANo=ntuEY!7TH*LVz<1UbgwiP-d!b1G)D=Vbb9i#-KVQKj<5_o4666 z65JPo0~*175kl#YBzUcZF-Q3HDG1W^#KJF!_9MG&;W|7NeLNJ#&?gq2N|3+qLu^KW zJrv=s1YY>N0O<8};EQ&!u&}VO@YNt!o8@5M>6`OR!}~@y6*ggZ&yrbW-MFbRP!- z+`Mfaf;vo{<+vgN!B~T61PC{(5GBy7u@KOlAlZIbVyyS>F}?!5oO4+CFJPUCH{V@z z-TMLSghFqXrpvs0Z)VO14LJ3ghXiy+0Dr+}gqI%djGIF9O#9`~VSdsiz$`%`=;xS+ zF?G{CuYw%KA$Fhpv%8N8C@8VkJFaa`1o1J(-|;c(b?EzGeaFH=Lp2oZEGS$d;IXG` z??%h74fc=#Gu`+tw~A^}C`Xat%IRUMcF-~?Esz=c1HcS~yh-V=gZ zV?#@Co)dKQmAIE6**%5V6aDq15G!auYVT;3cbMb?82&F3r1wBgf+GUUGmIcQ9vjVY z9Yg}JLRUZ~u+>dw(uu(0fG*_l33G>8k|(kpRN1Ya)TYU3^5q{rt2BaWq$N90Wps qp$1?%z<6(~`+(jK&UCo&_4o%Yu4&y^RdD 1023: + newStr = newStr[:1023] + logWarningError("Warning", f"String {newStr} exceeds character limit of 1023 and has been truncated.") + exitLoop = (newStr == outStr) outStr = newStr @@ -408,13 +422,10 @@ def write_text_bin_file(filename, dictionary): index[num * 2] = (current_offset & 0xFF) index[num * 2 + 1] = (current_offset >> 8) & 0xFF linedata = bytes.fromhex(dictionary[key]['bytes']) + bindata.extend(linedata) current_offset += len(linedata) - if len(linedata) > 1024: - logWarningError("Error", f"Contents of dialogue with identifier \"{key}\" exceeds 1024 bytes!") - linedata = linedata[:1024] - num += 1 # Write the index and bindata to the file @@ -537,11 +548,11 @@ for lang in Languages: else: arr = engCharArray for byte in string: - byte = engCharArray[int(byte, 16)] + byte = arr[int(byte, 16)] outText += chr(byte) mainDict[lang.name][section][item]["text"] = outText with open(dir + '/output.json', 'w') as jsonFile: jsonFile.write(json.dumps(mainDict)) -print("Parse finished!\n") +print("Parse finished!\n") \ No newline at end of file From b50b3e8cd61b392532a4e530b2cad88451d0b88b Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 15 Jan 2026 13:42:15 -0500 Subject: [PATCH 07/68] Update main.py for extra sheet column --- text_helper/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_helper/main.py b/text_helper/main.py index 913f531..0b0690f 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -467,7 +467,7 @@ for row in currSheet.iterrows(): currRow = row[1] #print(currRow) offset = lang.value - if (pd.isna(currRow.iloc[7 + lang.value])): + if (pd.isna(currRow.iloc[8 + lang.value])): offset = Languages.English.value mainDict[lang.name][currRow.iloc[0]][currRow.iloc[1]] = {"bytes": currRow.iloc[7 + offset], "numLines": currRow.iloc[2], From a56596afcda99af258785b0690e3b1e94a27c934 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 15 Jan 2026 13:55:07 -0500 Subject: [PATCH 08/68] Fixing main.py --- PCCS | 2 +- text_helper/main.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/PCCS b/PCCS index db430d8..1c98a39 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 +Subproject commit 1c98a391c2261c8141c8917160b2040514a0c2ee diff --git a/text_helper/main.py b/text_helper/main.py index 0b0690f..0d65e7d 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -15,6 +15,7 @@ update = True print ("Running text_helper:") BASE_DIR = Path(__file__).resolve().parent +FIRST_TRANSLATION_COL_INDEX = 8 if update: @@ -467,9 +468,9 @@ for row in currSheet.iterrows(): currRow = row[1] #print(currRow) offset = lang.value - if (pd.isna(currRow.iloc[8 + lang.value])): + if (pd.isna(currRow.iloc[FIRST_TRANSLATION_COL_INDEX + lang.value])): offset = Languages.English.value - mainDict[lang.name][currRow.iloc[0]][currRow.iloc[1]] = {"bytes": currRow.iloc[7 + offset], + mainDict[lang.name][currRow.iloc[0]][currRow.iloc[1]] = {"bytes": currRow.iloc[FIRST_TRANSLATION_COL_INDEX + offset], "numLines": currRow.iloc[2], "pixelsPerChar": currRow.iloc[3], "pixelsInLine" : currRow.iloc[4], From 1acbe99f93099a989e6e4a63c500f74764b73958 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 19 Jan 2026 15:32:53 -0500 Subject: [PATCH 09/68] Updating debug ROM to first check if real ROM has been inserted --- include/debug_mode.h | 4 ++-- include/rom_data.h | 2 +- source/main.cpp | 20 ++++++++++++++------ source/rom_data.cpp | 4 ++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/include/debug_mode.h b/include/debug_mode.h index 20a90fa..eedf682 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -12,12 +12,12 @@ #define IGNORE_GAME_PAK_SPRITES (false && DEBUG_MODE) #define IGNORE_LINK_CABLE (false && DEBUG_MODE) #define IGNORE_MG_E4_FLAGS (true && DEBUG_MODE) -#define IGNORE_UNRECEIVED_PKMN (true && DEBUG_MODE) +#define IGNORE_UNRECEIVED_PKMN (false && DEBUG_MODE) #define FORCE_TUTORIAL (false && DEBUG_MODE) #define DONT_REMOVE_PKMN (false && DEBUG_MODE) #define DONT_HIDE_INVALID_PKMN (false && DEBUG_MODE) #define IGNORE_DEX_COMPLETION (false && DEBUG_MODE) -#define FORCE_ALL_CAUGHT (false && DEBUG_MODE) +#define FORCE_ALL_CAUGHT (true && DEBUG_MODE) #define WRITE_CABLE_DATA_TO_SAVE (false && DEBUG_MODE) #define SHOW_DATA_PACKETS (false && DEBUG_MODE) #define DISPLAY_CONTROL_CHAR (false && DEBUG_MODE) diff --git a/include/rom_data.h b/include/rom_data.h index 98611a7..24c8bd2 100644 --- a/include/rom_data.h +++ b/include/rom_data.h @@ -25,7 +25,7 @@ class rom_data { public: rom_data(); - bool load_rom(); + bool load_rom(bool debug); bool is_hoenn(); bool is_ruby_sapphire(); void print_rom_info(); diff --git a/source/main.cpp b/source/main.cpp index 5f08e1d..70bc1fe 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -448,7 +448,7 @@ static void __attribute__((noinline)) show_intro() REG_BG1CNT = REG_BG1CNT | BG_PRIO(3); key_poll(); // Reset the keys - curr_GBA_rom.load_rom(); + curr_GBA_rom.load_rom(false); obj_set_pos(ptgb_logo_l, 56, 12); obj_set_pos(ptgb_logo_r, 56 + 64, 12); @@ -494,12 +494,20 @@ int main(void) REG_BLDALPHA = BLDA_BUILD(0b10000, 0); // Reset fade // Check if the game has been loaded correctly. - while (!curr_GBA_rom.load_rom()) + bool debug = false; + while (!curr_GBA_rom.load_rom(debug)) { - obj_hide_multi(ptgb_logo_l, 2); - global_next_frame(); - game_load_error(); - // initalization_script(); + if (IGNORE_GAME_PAK) + { + debug = true; + } + else + { + obj_hide_multi(ptgb_logo_l, 2); + global_next_frame(); + game_load_error(); + // initalization_script(); + } } // Initalize memory and save data after loading the game diff --git a/source/rom_data.cpp b/source/rom_data.cpp index ceceb81..8788373 100644 --- a/source/rom_data.cpp +++ b/source/rom_data.cpp @@ -16,14 +16,14 @@ extern rom_data curr_GBA_rom; rom_data::rom_data() {} -bool rom_data::load_rom() +bool rom_data::load_rom(bool debug) { u8 rom_list_buffer[2048]; u32 rom_list_size; const u8 *compressed_rom_list; const u8 *cur; - if (IGNORE_GAME_PAK) + if (debug) { gamecode = DEBUG_GAME; version = DEBUG_VERS; From 6220dc1a07525ab47a9395312095876797771b0c Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 20 Jan 2026 14:09:00 -0500 Subject: [PATCH 10/68] Updating text helper --- text_helper/main.py | 179 ++++++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 81 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 0d65e7d..3a21303 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -11,68 +11,6 @@ import sys import filecmp from pathlib import Path -update = True - -print ("Running text_helper:") -BASE_DIR = Path(__file__).resolve().parent -FIRST_TRANSLATION_COL_INDEX = 8 - -if update: - - url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' - new_file_path = BASE_DIR / 'new_text.xlsx' - old_file_path = BASE_DIR / 'text.xlsx' - json_file_path = BASE_DIR / 'output.json' - - offline = False - - # ---- Attempt download ---- - try: - response = requests.get(url, timeout=5) - response.raise_for_status() - with open(new_file_path, 'wb') as f: - f.write(response.content) - print("File downloaded successfully") - - except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError): - if old_file_path.exists(): - print("No internet. Using cached xlsx.") - offline = True - else: - print("ERROR: No internet and no cached xlsx. Cannot continue.") - sys.exit(1) - - # ---- Decision logic ---- - if offline: - # XML exists (guaranteed here) - if json_file_path.exists(): - print("Offline mode: trusting cached XML + JSON. Skipping parse.\n") - sys.exit(0) - else: - print("Offline mode: XML present but JSON missing. Rebuilding.") - - else: - # Online mode - if old_file_path.exists(): - new_df = pd.read_excel(new_file_path, sheet_name="Translations") - old_df = pd.read_excel(old_file_path, sheet_name="Translations") - - if new_df.equals(old_df): - print("Downloaded file is identical.") - new_file_path.unlink() - if json_file_path.exists(): - print("Skipping parse.\n") - sys.exit(0) - else: - print("JSON missing - forcing rebuild.") - else: - old_file_path.unlink() - new_file_path.rename(old_file_path) - - else: - print("No cached xlsx - forcing rebuild.") - new_file_path.rename(old_file_path) - engCharArray = [ 0x20, 0xC0, 0xC1, 0xC2, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0x20, 0xCE, 0xCF, 0xD2, 0xD3, 0xD4, @@ -86,7 +24,7 @@ engCharArray = [ 0x20, 0x20, 0x20, 0x20, 0x1D49, 0x3C, 0x3E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2B3, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x21, 0x3F, 0x2E, 0x2D, 0x30FB, -0x2026, 0x201C, 0x201D, 0x2018, 0x2019, 0x2642, 0x2640, 0x20, 0x2C, 0xD7, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45, +0x2025, 0x201C, 0x201D, 0x2018, 0x2019, 0x2642, 0x2640, 0x20, 0x2C, 0xD7, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x25B6, @@ -119,7 +57,7 @@ jpnCharArray = [ 0x305E, 0x3060, 0x3062, 0x3065, 0x3067, 0x3069, 0x3070, 0x3073, 0x3076, 0x3079, 0x307C, 0x3071, 0x3074, 0x3077, 0x307A, 0x307D, 0x3063, 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, 0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, -0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA, 0x20, 0x30EC, 0x30ED, 0x30EF, 0x30F2, 0x30F3, 0x30A1, +0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA, 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F2, 0x30F3, 0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30E3, 0x30E5, 0x30E7, 0x30AC, 0x30AE, 0x30B0, 0x30B2, 0x30B4, 0x30B6, 0x30B8, 0x30BA, 0x30BC, 0x30BE, 0x30C0, 0x30C2, 0x30C5, 0x30C7, 0x30C9, 0x30D0, 0x30D3, 0x30D6, 0x30D9, 0x30DC, 0x30D1, 0x30D4, 0x30D7, 0x30DA, 0x30DD, 0x30C3, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xFF01, 0xFF1F, 0x3002, 0x30FC, 0x30FB, @@ -153,12 +91,20 @@ charConversionList = [ ["'", "’"], ] +escapeCharConversionList = [ + ["{SCL}", "Ş"], + ["{CLR}", "ȼ"], + ["{NEW}", "Ň"], + ["{END}", "ƞ"], +] + + def logWarningError(type, text): nType = type + "s" nText = type + ": " + text if nText not in mainDict[lang.name][nType].values(): mainDict[lang.name][nType][max(mainDict[lang.name][nType].keys(), default =- 1) + 1] = nText - #print(nText) + print(nText) def convertByte(incoming, array): for pair in charConversionList: @@ -166,7 +112,6 @@ def convertByte(incoming, array): incoming = ord(pair[1]) logWarningError("Warning", f"Character {pair[0]} was used but is not in character table. Replaced with {pair[1]} .") - index = 0 for val in array: if val == incoming: @@ -178,13 +123,21 @@ def convertByte(incoming, array): def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): # If we can optimize this to remove the spaces, it could save a few bytes. + splitChars = [' ', '、'] outStr = "" currLine = "" lineCount = 0 currWordIndex = 0 lineLength = 0 spaceLength = 0 + for char in splitChars: + sentence.replace(char, " ") words = sentence.split() + for word in words: + for pair in escapeCharConversionList: + word.replace(pair[0], pair[1]) + + while(currWordIndex < len(words)): word = words[currWordIndex] wordLength = 0 @@ -228,7 +181,7 @@ def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): # Test if the word is too long in general elif (wordLength > pixelsInLine): - logWarningError("Error", f"Word {word} exceeds alloted length") + logWarningError("Error", f"Word {word} exceeds alloted length ({pixelsInLine} pixels)") currWordIndex += 1 # Test if adding the word will go over our alloted space @@ -250,18 +203,18 @@ def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): outStr += currLine return lineLength + offset, lineCount, outStr -# -*- coding: utf-8 -*- -import re -alphabets= r"([A-Za-z])" -prefixes = r"(Mr|St|Mrs|Ms|Dr)[.]" -suffixes = r"(Inc|Ltd|Jr|Sr|Co)" -starters = r"(Mr|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|He\s|She\s|It\s|They\s|Their\s|Our\s|We\s|But\s|However\s|That\s|This\s|Wherever)" -acronyms = r"([A-Z][.][A-Z][.](?:[A-Z][.])?)" -websites = r"[.](com|net|org|io|gov|edu|me)" -digits = r"([0-9])" -multiple_dots = r'\.{2,}' - def split_into_sentences(text: str) -> list[str]: + # -*- coding: utf-8 -*- + import re + alphabets= r"([A-Za-z])" + prefixes = r"(Mr|St|Mrs|Ms|Dr)[.]" + suffixes = r"(Inc|Ltd|Jr|Sr|Co)" + starters = r"(Mr|Mrs|Ms|Dr|Prof|Capt|Cpt|Lt|He\s|She\s|It\s|They\s|Their\s|Our\s|We\s|But\s|However\s|That\s|This\s|Wherever)" + acronyms = r"([A-Z][.][A-Z][.](?:[A-Z][.])?)" + websites = r"[.](com|net|org|io|gov|edu|me)" + digits = r"([0-9])" + multiple_dots = r'\.{2,}' + """ Split the text into sentences. @@ -319,9 +272,6 @@ class Languages(Enum): SpanishEU = 5 SpanishLA = 6 -# read by default 1st sheet of an excel file -dir = os.curdir + "/text_helper" - mainDict = {} def convert_item(ogDict): @@ -443,6 +393,73 @@ def write_enum_to_header_file(hFile, prefix, dictionary): hFile.write("\n") return num +# Main +update = True + +print ("Running text_helper:") +BASE_DIR = Path(__file__).resolve().parent +FIRST_TRANSLATION_COL_INDEX = 8 + +# read by default 1st sheet of an excel file +dir = os.curdir + "/text_helper" + +if update: + + url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' + new_file_path = BASE_DIR / 'new_text.xlsx' + old_file_path = BASE_DIR / 'text.xlsx' + json_file_path = BASE_DIR / 'output.json' + + offline = False + + # ---- Attempt download ---- + try: + response = requests.get(url, timeout=5) + response.raise_for_status() + with open(new_file_path, 'wb') as f: + f.write(response.content) + print("File downloaded successfully") + + except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError): + if old_file_path.exists(): + print("No internet. Using cached xlsx.") + offline = True + else: + print("ERROR: No internet and no cached xlsx. Cannot continue.") + sys.exit(1) + + # ---- Decision logic ---- + if offline: + # XML exists (guaranteed here) + if json_file_path.exists(): + print("Offline mode: trusting cached XML + JSON. Skipping parse.\n") + sys.exit(0) + else: + print("Offline mode: XML present but JSON missing. Rebuilding.") + + else: + # Online mode + if old_file_path.exists(): + new_df = pd.read_excel(new_file_path, sheet_name="Translations") + old_df = pd.read_excel(old_file_path, sheet_name="Translations") + + if new_df.equals(old_df): + print("Downloaded file is identical.") + new_file_path.unlink() + if json_file_path.exists(): + print("Skipping parse.\n") + sys.exit(0) + else: + print("JSON missing - forcing rebuild.") + else: + old_file_path.unlink() + new_file_path.rename(old_file_path) + + else: + print("No cached xlsx - forcing rebuild.") + new_file_path.rename(old_file_path) + + print("Starting parse:") currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") From 8a1f82a1f522659bf12ce0cc7356141c696c8b51 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 20 Jan 2026 14:10:27 -0500 Subject: [PATCH 11/68] Updating PCCS --- PCCS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PCCS b/PCCS index 1c98a39..db430d8 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit 1c98a391c2261c8141c8917160b2040514a0c2ee +Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 From a51cdb0f14248556ea62a1272d0ec18c2f1c9048 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 21 Jan 2026 13:43:02 -0500 Subject: [PATCH 12/68] Updating escape characters --- text_helper/main.py | 49 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 3a21303..7e6a5d0 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -91,11 +91,28 @@ charConversionList = [ ["'", "’"], ] -escapeCharConversionList = [ - ["{SCL}", "Ş"], - ["{CLR}", "ȼ"], - ["{NEW}", "Ň"], - ["{END}", "ƞ"], +itlEscapeCharConversionList = [ + ["{SCL}", [0xFA]], + ["{CLR}", [0xFB]], + ["{DEF}", [0xFC, 0x01, 0x02]], + ["{FEM}", [0xFC, 0x01, 0x04]], + ["{FPC}", [0xFC, 0x01, 0x06]], + ["{MLE}", [0xFC, 0x01, 0x08]], + ["{PLR}", [0xFD, 0x01]], + ["{NEW}", [0xFE]], + ["{END}", [0xFF]], +] + +jpnEscapeCharConversionList = [ + ["{SCL}", [0xFA]], + ["{CLR}", [0xFB]], + ["{DEF}", [0xFC, 0x06, 0x02]], + ["{FEM}", [0xFC, 0x06, 0x03]], # ??? + ["{MLE}", [0xFC, 0x06, 0x04]], + ["{FPC}", [0xFC, 0x06, 0x05]], + ["{PLR}", [0xFD, 0x01]], + ["{NEW}", [0xFE]], + ["{END}", [0xFF]], ] @@ -122,7 +139,6 @@ def convertByte(incoming, array): def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): # If we can optimize this to remove the spaces, it could save a few bytes. - splitChars = [' ', '、'] outStr = "" currLine = "" @@ -132,10 +148,7 @@ def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): spaceLength = 0 for char in splitChars: sentence.replace(char, " ") - words = sentence.split() - for word in words: - for pair in escapeCharConversionList: - word.replace(pair[0], pair[1]) + words = sentence.split() while(currWordIndex < len(words)): @@ -280,6 +293,22 @@ def convert_item(ogDict): pixelsPerChar = ogDict["pixelsPerChar"] pixelsInLine = ogDict["pixelsInLine"] include_box_breaks = ogDict["includeBoxBreaks"] + + if lang == "Japanese": + arr = jpnCharArray + list = jpnEscapeCharConversionList + else: + arr = engCharArray + list = itlEscapeCharConversionList + for pair in list: + if pair[0] in line: + escapeString = "" + for char in pair[1]: + escapeString += chr(arr[char]) + #print(f"Replacing {pair[0]} with {escapeString}!") + line = line.replace(pair[0], escapeString) + #print(line) + split_sents = split_into_sentences(line) index = 0 outStr = "" From 13459812fa71680370310b56fd189bd3379ba7db Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 21 Jan 2026 14:03:10 -0500 Subject: [PATCH 13/68] Fixing Japanese if statement --- text_helper/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_helper/main.py b/text_helper/main.py index 7e6a5d0..2941d2b 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -294,7 +294,7 @@ def convert_item(ogDict): pixelsInLine = ogDict["pixelsInLine"] include_box_breaks = ogDict["includeBoxBreaks"] - if lang == "Japanese": + if lang == Languages.Japanese: arr = jpnCharArray list = jpnEscapeCharConversionList else: From dc586d941f362b976e575868da4894de75be983f Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 21 Jan 2026 14:16:43 -0500 Subject: [PATCH 14/68] Removing space after JPN period --- text_helper/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/text_helper/main.py b/text_helper/main.py index 2941d2b..a1118dd 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -213,6 +213,7 @@ def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): lineLength = 0 offset = 0 + currLine = currLine.replace("。 ", "。") # Get rid of the space after the Japanese peroid outStr += currLine return lineLength + offset, lineCount, outStr From 63a54a7c68cc9aadcdee2f8618912b811f65d5c2 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 21 Jan 2026 17:25:24 -0500 Subject: [PATCH 15/68] Reorganizing main.py --- text_helper/main.py | 553 ++++++++++++++++++++++---------------------- 1 file changed, 279 insertions(+), 274 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index a1118dd..a12d383 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -1,16 +1,38 @@ -# import pandas lib as pd import pandas as pd import os from enum import Enum import json import requests from collections import defaultdict -import copy -import math import sys -import filecmp from pathlib import Path +class Languages(Enum): + Japanese = 0 + English = 1 + French = 2 + German = 3 + Italian = 4 + SpanishEU = 5 + SpanishLA = 6 + +FIRST_TRANSLATION_COL_INDEX = 8 +BASE_DIR = Path(__file__).resolve().parent + +# read by default 1st sheet of an excel file +dir = os.curdir + "/text_helper" + +url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' +new_file_path = BASE_DIR / 'new_text.xlsx' +old_file_path = BASE_DIR / 'text.xlsx' +json_file_path = BASE_DIR / 'output.json' + +mainDict = {} +textSections = [] +charArrays = { + "International": {}, + "Japanese": {}, +} engCharArray = [ 0x20, 0xC0, 0xC1, 0xC2, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0x20, 0xCE, 0xCF, 0xD2, 0xD3, 0xD4, @@ -31,24 +53,6 @@ engCharArray = [ 0x3A, 0xC4, 0xD6, 0xDC, 0xE4, 0xF6, 0xFC, 0x2A, 0x20, 0x20, 0x15E, 0x23C, 0x206, 0x1B2, 0x147, 0x19E, ] -engCharWidthArray = [ - 0x4, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, -0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, -0x6, 0x6, 0x6, 0x6, 0x6, 0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x9, 0x6, 0x6, 0x0, -0x0, 0x0, 0x0, 0x0, 0xA, 0x8, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x6, 0x6, 0x4, 0x8, 0x8, 0x8, 0x7, 0x8, 0x8, 0x4, 0x6, 0x6, 0x4, 0x4, 0x0, 0x0, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7, 0x2, 0x3, 0x4, -0x5, 0x5, 0x6, 0x7, 0x5, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x6, 0x3, 0x6, 0x3, -0x6, 0x6, 0x6, 0x3, 0x3, 0x6, 0x6, 0x6, 0x3, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, -0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, -0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x5, 0x6, -0x4, 0x6, 0x6, 0x6, 0x6, 0x6, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x8, -0x3, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, ] - jpnCharArray = [ 0x20, 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F, 0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, 0x305F, 0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D, 0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, @@ -86,23 +90,29 @@ jpnCharWidthArray = [ 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, ] +engCharWidthArray = [ + 0x4, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, +0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, +0x6, 0x6, 0x6, 0x6, 0x6, 0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x9, 0x6, 0x6, 0x0, +0x0, 0x0, 0x0, 0x0, 0xA, 0x8, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +0x6, 0x6, 0x4, 0x8, 0x8, 0x8, 0x7, 0x8, 0x8, 0x4, 0x6, 0x6, 0x4, 0x4, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, +0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7, 0x2, 0x3, 0x4, +0x5, 0x5, 0x6, 0x7, 0x5, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x6, 0x3, 0x6, 0x3, +0x6, 0x6, 0x6, 0x3, 0x3, 0x6, 0x6, 0x6, 0x3, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, +0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, +0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x5, 0x6, +0x4, 0x6, 0x6, 0x6, 0x6, 0x6, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x8, +0x3, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, ] + charConversionList = [ # replaces the first char in the list with the latter ["'", "’"], ] -itlEscapeCharConversionList = [ - ["{SCL}", [0xFA]], - ["{CLR}", [0xFB]], - ["{DEF}", [0xFC, 0x01, 0x02]], - ["{FEM}", [0xFC, 0x01, 0x04]], - ["{FPC}", [0xFC, 0x01, 0x06]], - ["{MLE}", [0xFC, 0x01, 0x08]], - ["{PLR}", [0xFD, 0x01]], - ["{NEW}", [0xFE]], - ["{END}", [0xFF]], -] - jpnEscapeCharConversionList = [ ["{SCL}", [0xFA]], ["{CLR}", [0xFB]], @@ -115,107 +125,17 @@ jpnEscapeCharConversionList = [ ["{END}", [0xFF]], ] - -def logWarningError(type, text): - nType = type + "s" - nText = type + ": " + text - if nText not in mainDict[lang.name][nType].values(): - mainDict[lang.name][nType][max(mainDict[lang.name][nType].keys(), default =- 1) + 1] = nText - print(nText) - -def convertByte(incoming, array): - for pair in charConversionList: - if incoming == ord(pair[0]): - incoming = ord(pair[1]) - logWarningError("Warning", f"Character {pair[0]} was used but is not in character table. Replaced with {pair[1]} .") - - index = 0 - for val in array: - if val == incoming: - return index - index += 1 - logWarningError("Error", f"No match found for char [ {chr(incoming)} ]!") - return 0 - -def SplitSentenceIntoLines(sentence, offset, pixelsPerChar, pixelsInLine): - # If we can optimize this to remove the spaces, it could save a few bytes. - splitChars = [' ', '、'] - outStr = "" - currLine = "" - lineCount = 0 - currWordIndex = 0 - lineLength = 0 - spaceLength = 0 - for char in splitChars: - sentence.replace(char, " ") - words = sentence.split() - - - while(currWordIndex < len(words)): - word = words[currWordIndex] - wordLength = 0 - # print(word) - - # Figure out the length of the word in pixels - for char in word: - if (pixelsPerChar == "Variable"): - if(lang == Languages.Japanese): - wordLength += jpnCharWidthArray[convertByte(ord(char), jpnCharArray)] - spaceLength = jpnCharWidthArray[convertByte(ord(' '), jpnCharArray)] - else: - wordLength += engCharWidthArray[convertByte(ord(char), engCharArray)] - spaceLength = engCharWidthArray[convertByte(ord(' '), engCharArray)] - - elif (pixelsPerChar == "Default"): - if (lang == Languages.Japanese): - wordLength += 8 - spaceLength = 8 - - else: - wordLength += 6 - spaceLength = 6 - - # See if the whole sentence is a newline - if (sentence == "Ň"): - outStr += "Ň" - currLine = "" - lineCount += 1 - offset = 0 - lineLength = 0 - currWordIndex += 1 - - # See if the sentence is a new box - elif(sentence == "Ş" or sentence == "ȼ"): - outStr += sentence - currLine = "" - offset = 0 - lineLength = 0 - currWordIndex += 1 - - # Test if the word is too long in general - elif (wordLength > pixelsInLine): - logWarningError("Error", f"Word {word} exceeds alloted length ({pixelsInLine} pixels)") - currWordIndex += 1 - - # Test if adding the word will go over our alloted space - elif ((wordLength + lineLength + offset) <= pixelsInLine): - # If not, add the word and increase the index - currLine += (word + " ") - lineLength += (wordLength + spaceLength) - currWordIndex += 1 - - # We need to move to the next line - else: - # Every line should already have a space at the end of it. Remove it here - outStr += (currLine[:-1] + "Ň") - currLine = "" - lineCount += 1 - lineLength = 0 - offset = 0 - - currLine = currLine.replace("。 ", "。") # Get rid of the space after the Japanese peroid - outStr += currLine - return lineLength + offset, lineCount, outStr +itlEscapeCharConversionList = [ + ["{SCL}", [0xFA]], + ["{CLR}", [0xFB]], + ["{DEF}", [0xFC, 0x01, 0x02]], + ["{FEM}", [0xFC, 0x01, 0x04]], + ["{FPC}", [0xFC, 0x01, 0x06]], + ["{MLE}", [0xFC, 0x01, 0x08]], + ["{PLR}", [0xFD, 0x01]], + ["{NEW}", [0xFE]], + ["{END}", [0xFF]], +] def split_into_sentences(text: str) -> list[str]: # -*- coding: utf-8 -*- @@ -276,19 +196,109 @@ def split_into_sentences(text: str) -> list[str]: sentences = [s.strip() for s in sentences] if sentences and not sentences[-1]: sentences = sentences[:-1] return sentences + +def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lang): + # If we can optimize this to remove the spaces, it could save a few bytes. + splitChars = [' ', '、'] + outStr = "" + currLine = "" + lineCount = 0 + currWordIndex = 0 + lineLength = 0 + spaceLength = 0 + for char in splitChars: + sentence.replace(char, " ") + words = sentence.split() -class Languages(Enum): - Japanese = 0 - English = 1 - French = 2 - German = 3 - Italian = 4 - SpanishEU = 5 - SpanishLA = 6 -mainDict = {} + while(currWordIndex < len(words)): + word = words[currWordIndex] + wordLength = 0 + # print(word) + + # Figure out the length of the word in pixels + for char in word: + if (pixelsPerChar == "Variable"): + if(lang == Languages.Japanese): + wordLength += jpnCharWidthArray[convert_char_to_byte(ord(char), jpnCharArray, lang)] + spaceLength = jpnCharWidthArray[convert_char_to_byte(ord(' '), jpnCharArray, lang)] + else: + wordLength += engCharWidthArray[convert_char_to_byte(ord(char), engCharArray, lang)] + spaceLength = engCharWidthArray[convert_char_to_byte(ord(' '), engCharArray, lang)] + + elif (pixelsPerChar == "Default"): + if (lang == Languages.Japanese): + wordLength += 8 + spaceLength = 8 + + else: + wordLength += 6 + spaceLength = 6 + + # See if the whole sentence is a newline + if (sentence == "Ň"): + outStr += "Ň" + currLine = "" + lineCount += 1 + offset = 0 + lineLength = 0 + currWordIndex += 1 + + # See if the sentence is a new box + elif(sentence == "Ş" or sentence == "ȼ"): + outStr += sentence + currLine = "" + offset = 0 + lineLength = 0 + currWordIndex += 1 + + # Test if the word is too long in general + elif (wordLength > pixelsInLine): + log_warning_error(lang, "Error", f"Word {word} exceeds alloted length ({pixelsInLine} pixels)") + currWordIndex += 1 + + # Test if adding the word will go over our alloted space + elif ((wordLength + lineLength + offset) <= pixelsInLine): + # If not, add the word and increase the index + currLine += (word + " ") + lineLength += (wordLength + spaceLength) + currWordIndex += 1 + + # We need to move to the next line + else: + # Every line should already have a space at the end of it. Remove it here + outStr += (currLine[:-1] + "Ň") + currLine = "" + lineCount += 1 + lineLength = 0 + offset = 0 + + currLine = currLine.replace("。 ", "。") # Get rid of the space after the Japanese peroid + outStr += currLine + return lineLength + offset, lineCount, outStr -def convert_item(ogDict): +def convert_char_to_byte(incoming, array, lang): + for pair in charConversionList: + if incoming == ord(pair[0]): + incoming = ord(pair[1]) + log_warning_error(lang, "Warning", f"Character {pair[0]} was used but is not in character table. Replaced with {pair[1]} .") + + index = 0 + for val in array: + if val == incoming: + return index + index += 1 + log_warning_error(lang, "Error", f"No match found for char [ {chr(incoming)} ]!") + return 0 + +def log_warning_error(lang, type, text): + nType = type + "s" + nText = type + ": " + text + if nText not in mainDict[lang.name][nType].values(): + mainDict[lang.name][nType][max(mainDict[lang.name][nType].keys(), default =- 1) + 1] = nText + print(nText) + +def convert_item(ogDict, lang): line = ogDict["bytes"] numLines = ogDict["numLines"] pixelsPerChar = ogDict["pixelsPerChar"] @@ -317,7 +327,7 @@ def convert_item(ogDict): offset = 0 escapeCount = 0 while index < len(split_sents) and escapeCount < 100: - offset, recievedLine, out = SplitSentenceIntoLines(split_sents[index], offset, pixelsPerChar, pixelsInLine) + offset, recievedLine, out = split_sentence_into_lines(split_sents[index], offset, pixelsPerChar, pixelsInLine, lang) currLine += recievedLine if (out == "ȼ"): @@ -338,10 +348,10 @@ def convert_item(ogDict): escapeCount += 1 #print(index) if not include_box_breaks: - logWarningError("Error", f"Made a line break when disabled, sentence \"{outStr}\" is too long!") + log_warning_error(lang, "Error", f"Made a line break when disabled, sentence \"{outStr}\" is too long!") if escapeCount == 100: - logWarningError("Error", f"Sentence \"{out}\" is too long!") + log_warning_error(lang, "Error", f"Sentence \"{out}\" is too long!") # Some cases that should be fixed exitLoop = False @@ -362,7 +372,7 @@ def convert_item(ogDict): if len(newStr) > 1023: newStr = newStr[:1023] - logWarningError("Warning", f"String {newStr} exceeds character limit of 1023 and has been truncated.") + log_warning_error(lang, "Warning", f"String {newStr} exceeds character limit of 1023 and has been truncated.") exitLoop = (newStr == outStr) outStr = newStr @@ -373,16 +383,16 @@ def convert_item(ogDict): else: arr = engCharArray for char in outStr[:-1]: - byteStr += f"{convertByte(ord(char), arr):02x} " + byteStr += f"{convert_char_to_byte(ord(char), arr, lang):02x} " if (len(outStr) > 0 and outStr[-1] != ' '): # Check if the last char is a space - byteStr += f"{convertByte(ord(outStr[-1]), arr):02x} " + byteStr += f"{convert_char_to_byte(ord(outStr[-1]), arr, lang):02x} " byteStr += "ff" ogDict["bytes"] = byteStr return ogDict -def write_text_bin_file(filename, dictionary): +def write_text_bin_file(filename, dictionary, lang): with open(filename, 'wb') as binFile: # Let the first byte indicate the number of entries dict_size = len(dictionary) @@ -398,7 +408,7 @@ def write_text_bin_file(filename, dictionary): # Append every line's binary data to bindata # keep an index of the binary offset within bindata at which each line starts for key, line in dictionary.items(): - dictionary[key] = convert_item(line) + dictionary[key] = convert_item(line, lang) # store the offset of the line in the index as a 16 bit little endian value index[num * 2] = (current_offset & 0xFF) index[num * 2 + 1] = (current_offset >> 8) & 0xFF @@ -423,25 +433,8 @@ def write_enum_to_header_file(hFile, prefix, dictionary): hFile.write("\n") return num -# Main -update = True - -print ("Running text_helper:") -BASE_DIR = Path(__file__).resolve().parent -FIRST_TRANSLATION_COL_INDEX = 8 - -# read by default 1st sheet of an excel file -dir = os.curdir + "/text_helper" - -if update: - - url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' - new_file_path = BASE_DIR / 'new_text.xlsx' - old_file_path = BASE_DIR / 'text.xlsx' - json_file_path = BASE_DIR / 'output.json' - +def download_xlsx_file(): offline = False - # ---- Attempt download ---- try: response = requests.get(url, timeout=5) @@ -489,118 +482,130 @@ if update: print("No cached xlsx - forcing rebuild.") new_file_path.rename(old_file_path) +def transfer_xlsx_to_dict(): + #print("\tGetting character arrays") + #currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") + + #for arr in charArrays: -print("Starting parse:") -currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") -textSections = [] + print("\tGetting string data") + currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") -for row in currSheet.iterrows(): - currRow = row[1]["Text Section"] - if (currRow not in textSections): - textSections.append(currRow) + for row in currSheet.iterrows(): + currRow = row[1]["Text Section"] + if (currRow not in textSections): + textSections.append(currRow) -for lang in Languages: - mainDict[lang.name] = {} - for section in textSections: - mainDict[lang.name][section] = {} - mainDict[lang.name]["Warnings"] = {} - mainDict[lang.name]["Errors"] = {} - - -for row in currSheet.iterrows(): - #print(row) - for lang in Languages: - currRow = row[1] - #print(currRow) - offset = lang.value - if (pd.isna(currRow.iloc[FIRST_TRANSLATION_COL_INDEX + lang.value])): - offset = Languages.English.value - mainDict[lang.name][currRow.iloc[0]][currRow.iloc[1]] = {"bytes": currRow.iloc[FIRST_TRANSLATION_COL_INDEX + offset], - "numLines": currRow.iloc[2], - "pixelsPerChar": currRow.iloc[3], - "pixelsInLine" : currRow.iloc[4], - "includeBoxBreaks": currRow.iloc[5], - } - -print("\tGenerating header file") -# generate the header file -with open (os.curdir + '/include/translated_text.h', 'w') as hFile: - hFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n\n#ifndef TRANSLATED_TEXT_H\n#define TRANSLATED_TEXT_H\n\n#include \n\n") - - sectionEnds = [] - index = 0 - for section in textSections: - num = write_enum_to_header_file(hFile, section + "_", mainDict[lang.name][section]) - hFile.write("#define " + section + "_INDEX " + str(index)) - if(section == "PTGB"): - hFile.write(f"\n#define DIA_END {num}\n") - - hFile.write("/** Returns the LZ10 compressed " + section + " text table.*/\n") - sectionEnds.append(num) - index += 1 - - hFile.write("#define NUM_TEXT_SECTIONS " + str(index) + "\n") - hFile.write("const int text_section_lengths[] = {\n") - for end in sectionEnds: - hFile.write("\t" + str(end) + ",\n") - hFile.write("};\n\n") - - hFile.write("const u8* get_compressed_text_table(int table_index);\n") - - - hFile.write("\n#endif") - hFile.close() - -print("\tGenerating text tables") -# now generate the text tables -for lang in Languages: - for section in textSections: - table_file = os.curdir + '/to_compress/' + section + '_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name][section]) - -print("\tGenerating cpp file") -# now generate the cpp file. -with open(os.curdir + '/source/translated_text.cpp', 'w') as cppFile: - cppFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n#include \"translated_text.h\"\n#include \"debug_mode.h\"\n") - # generate includes for each language for lang in Languages: + mainDict[lang.name] = {} for section in textSections: - cppFile.write("#include \"" + section.upper() + "_" + lang.name.lower() + "_lz10_bin.h\"\n") + mainDict[lang.name][section] = {} + mainDict[lang.name]["Warnings"] = {} + mainDict[lang.name]["Errors"] = {} - for lang in Languages: - cppFile.write(f"\n#if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") - cppFile.write("const u8* get_compressed_text_table(int table_index)\n") - cppFile.write("{\n") - cppFile.write("\tswitch (table_index)\n\t{\n") + for row in currSheet.iterrows(): + #print(row) + for lang in Languages: + currRow = row[1] + #print(currRow) + offset = lang.value + if (pd.isna(currRow.iloc[FIRST_TRANSLATION_COL_INDEX + lang.value])): + offset = Languages.English.value + mainDict[lang.name][currRow.iloc[0]][currRow.iloc[1]] = {"bytes": currRow.iloc[FIRST_TRANSLATION_COL_INDEX + offset], + "numLines": currRow.iloc[2], + "pixelsPerChar": currRow.iloc[3], + "pixelsInLine" : currRow.iloc[4], + "includeBoxBreaks": currRow.iloc[5], + } + +def generate_header_file(): + print("\tGenerating header file") + with open (os.curdir + '/include/translated_text.h', 'w') as hFile: + hFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n\n#ifndef TRANSLATED_TEXT_H\n#define TRANSLATED_TEXT_H\n\n#include \n\n") + + sectionEnds = [] + index = 0 + lang = Languages.English # This doesn't matter, it just needs to be there. They're all the same for section in textSections: - cppFile.write("\tcase(" + section + "_INDEX):\n") + num = write_enum_to_header_file(hFile, section + "_", mainDict[lang.name][section]) + hFile.write("#define " + section + "_INDEX " + str(index)) if(section == "PTGB"): - cppFile.write("\tdefault:\n") - cppFile.write("\t\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("\t\tbreak;\n") - cppFile.write("\t}\n") - cppFile.write("}\n\n") - cppFile.write(f"#endif\n\n\n") + hFile.write(f"\n#define DIA_END {num}\n") + + hFile.write("/** Returns the LZ10 compressed " + section + " text table.*/\n") + sectionEnds.append(num) + index += 1 + + hFile.write("#define NUM_TEXT_SECTIONS " + str(index) + "\n") + hFile.write("const int text_section_lengths[] = {\n") + for end in sectionEnds: + hFile.write("\t" + str(end) + ",\n") + hFile.write("};\n\n") + + hFile.write("const u8* get_compressed_text_table(int table_index);\n") -print("\tOutputting json file") -for lang in Languages: - for section in textSections: - for item in mainDict[lang.name][section]: - string = mainDict[lang.name][section][item]["bytes"].split(" ") - outText = "" - if lang == Languages.Japanese: - arr = jpnCharArray - else: - arr = engCharArray - for byte in string: - byte = arr[int(byte, 16)] - outText += chr(byte) - mainDict[lang.name][section][item]["text"] = outText - -with open(dir + '/output.json', 'w') as jsonFile: - jsonFile.write(json.dumps(mainDict)) - -print("Parse finished!\n") \ No newline at end of file + hFile.write("\n#endif") + hFile.close() + +def generate_text_tables(): + print("\tGenerating text tables") + for lang in Languages: + for section in textSections: + table_file = os.curdir + '/to_compress/' + section + '_' + lang.name.lower() + '.bin' + write_text_bin_file(table_file, mainDict[lang.name][section], lang) + +def generate_cpp_file(): + print("\tGenerating cpp file") + with open(os.curdir + '/source/translated_text.cpp', 'w') as cppFile: + cppFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n#include \"translated_text.h\"\n#include \"debug_mode.h\"\n") + # generate includes for each language + for lang in Languages: + for section in textSections: + cppFile.write("#include \"" + section.upper() + "_" + lang.name.lower() + "_lz10_bin.h\"\n") + + for lang in Languages: + cppFile.write(f"\n#if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") + cppFile.write("const u8* get_compressed_text_table(int table_index)\n") + cppFile.write("{\n") + cppFile.write("\tswitch (table_index)\n\t{\n") + for section in textSections: + cppFile.write("\tcase(" + section + "_INDEX):\n") + if(section == "PTGB"): + cppFile.write("\tdefault:\n") + cppFile.write("\t\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") + cppFile.write("\t\tbreak;\n") + cppFile.write("\t}\n") + cppFile.write("}\n\n") + cppFile.write(f"#endif\n\n\n") + +def output_json_file(): + print("\tOutputting json file") + for lang in Languages: + for section in textSections: + for item in mainDict[lang.name][section]: + string = mainDict[lang.name][section][item]["bytes"].split(" ") + outText = "" + if lang == Languages.Japanese: + arr = jpnCharArray + else: + arr = engCharArray + for byte in string: + byte = arr[int(byte, 16)] + outText += chr(byte) + mainDict[lang.name][section][item]["text"] = outText + + with open(dir + '/output.json', 'w') as jsonFile: + jsonFile.write(json.dumps(mainDict)) + +# Main +print("Running text_helper:") +download_xlsx_file() +transfer_xlsx_to_dict() +generate_header_file() +generate_text_tables() +generate_cpp_file() +output_json_file() +print("text_helper finished!\n") \ No newline at end of file From 63d57f063dcee255ea8983540e300838c1bbbe48 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 22 Jan 2026 16:42:53 -0500 Subject: [PATCH 16/68] Pulling character arrays from sheet --- text_helper/main.py | 110 ++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 64 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index a12d383..d101af2 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -6,6 +6,7 @@ import requests from collections import defaultdict import sys from pathlib import Path +import hashlib class Languages(Enum): Japanese = 0 @@ -30,47 +31,18 @@ json_file_path = BASE_DIR / 'output.json' mainDict = {} textSections = [] charArrays = { - "International": {}, - "Japanese": {}, + "International": [0] * 0x100, + "Japanese": [0] * 0x100, +} +charArrayOfLanguage = { + Languages.Japanese: charArrays["Japanese"], + Languages.English: charArrays["International"], + Languages.French: charArrays["International"], + Languages.German: charArrays["International"], + Languages.Italian: charArrays["International"], + Languages.SpanishEU: charArrays["International"], + Languages.SpanishLA: charArrays["International"], } - -engCharArray = [ -0x20, 0xC0, 0xC1, 0xC2, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0x20, 0xCE, 0xCF, 0xD2, 0xD3, 0xD4, -0x152, 0xD9, 0xDA, 0xDB, 0xD1, 0xDF, 0xE0, 0xE1, 0x20, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0x20, -0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0x153, 0xF9, 0xFA, 0xFB, 0xF1, 0xBA, 0xAA, 0x1D49, 0x26, 0x2B, 0x20, -0x20, 0x20, 0x20, 0x20, 0x20, 0x3D, 0x3B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -0x25AF, 0xBF, 0xA1, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xCD, 0x25, 0x28, 0x29, 0x20, 0x20, -0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xE2, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xED, -0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2B07, 0x2B05, 0x27A1, 0x20, 0x20, 0x20, -0x20, 0x20, 0x20, 0x20, 0x1D49, 0x3C, 0x3E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -0x2B3, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x21, 0x3F, 0x2E, 0x2D, 0x30FB, -0x2025, 0x201C, 0x201D, 0x2018, 0x2019, 0x2642, 0x2640, 0x20, 0x2C, 0xD7, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45, -0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, -0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, -0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x25B6, -0x3A, 0xC4, 0xD6, 0xDC, 0xE4, 0xF6, 0xFC, 0x2A, 0x20, 0x20, 0x15E, 0x23C, 0x206, 0x1B2, 0x147, 0x19E, -] - -jpnCharArray = [ -0x20, 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F, 0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, -0x305F, 0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D, 0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, -0x307F, 0x3080, 0x3081, 0x3082, 0x3084, 0x3086, 0x3088, 0x3089, 0x308A, 0x308B, 0x308C, 0x308D, 0x308F, 0x3092, 0x3093, 0x3041, -0x3043, 0x3045, 0x3047, 0x3049, 0x3083, 0x3085, 0x3087, 0x304C, 0x304E, 0x3050, 0x3052, 0x3054, 0x3056, 0x3058, 0x305A, 0x305C, -0x305E, 0x3060, 0x3062, 0x3065, 0x3067, 0x3069, 0x3070, 0x3073, 0x3076, 0x3079, 0x307C, 0x3071, 0x3074, 0x3077, 0x307A, 0x307D, -0x3063, 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, -0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, -0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA, 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F2, 0x30F3, 0x30A1, -0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30E3, 0x30E5, 0x30E7, 0x30AC, 0x30AE, 0x30B0, 0x30B2, 0x30B4, 0x30B6, 0x30B8, 0x30BA, 0x30BC, -0x30BE, 0x30C0, 0x30C2, 0x30C5, 0x30C7, 0x30C9, 0x30D0, 0x30D3, 0x30D6, 0x30D9, 0x30DC, 0x30D1, 0x30D4, 0x30D7, 0x30DA, 0x30DD, -0x30C3, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xFF01, 0xFF1F, 0x3002, 0x30FC, 0x30FB, -0x30FB, 0x300E, 0x300F, 0x300C, 0x300D, 0x2642, 0x2640, 0x5186, 0x2E, 0xD7, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45, -0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, -0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, -0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x25B6, -0x3A, 0xC4, 0xD6, 0xDC, 0xE4, 0xF6, 0xFC, 0x2A, 0x20, 0x20, 0x15E, 0x23C, 0x206, 0x1B2, 0x147, 0x19E, -] jpnCharWidthArray = [ 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, @@ -220,11 +192,11 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lan for char in word: if (pixelsPerChar == "Variable"): if(lang == Languages.Japanese): - wordLength += jpnCharWidthArray[convert_char_to_byte(ord(char), jpnCharArray, lang)] - spaceLength = jpnCharWidthArray[convert_char_to_byte(ord(' '), jpnCharArray, lang)] + wordLength += jpnCharWidthArray[convert_char_to_byte(ord(char), charArrayOfLanguage[lang], lang)] + spaceLength = jpnCharWidthArray[convert_char_to_byte(ord(' '), charArrayOfLanguage[lang], lang)] else: - wordLength += engCharWidthArray[convert_char_to_byte(ord(char), engCharArray, lang)] - spaceLength = engCharWidthArray[convert_char_to_byte(ord(' '), engCharArray, lang)] + wordLength += engCharWidthArray[convert_char_to_byte(ord(char), charArrayOfLanguage[lang], lang)] + spaceLength = engCharWidthArray[convert_char_to_byte(ord(' '), charArrayOfLanguage[lang], lang)] elif (pixelsPerChar == "Default"): if (lang == Languages.Japanese): @@ -285,7 +257,7 @@ def convert_char_to_byte(incoming, array, lang): index = 0 for val in array: - if val == incoming: + if str(val) == chr(incoming): return index index += 1 log_warning_error(lang, "Error", f"No match found for char [ {chr(incoming)} ]!") @@ -298,6 +270,16 @@ def log_warning_error(lang, type, text): mainDict[lang.name][nType][max(mainDict[lang.name][nType].keys(), default =- 1) + 1] = nText print(nText) +def hash_excel(path): + sheets = pd.read_excel(path, sheet_name=None) + h = hashlib.sha256() + for name in sorted(sheets): + h.update(name.encode()) + h.update(pd.util.hash_pandas_object( + sheets[name], index=True + ).values) + return h.digest() + def convert_item(ogDict, lang): line = ogDict["bytes"] numLines = ogDict["numLines"] @@ -306,16 +288,16 @@ def convert_item(ogDict, lang): include_box_breaks = ogDict["includeBoxBreaks"] if lang == Languages.Japanese: - arr = jpnCharArray + arr = charArrayOfLanguage[lang] list = jpnEscapeCharConversionList else: - arr = engCharArray + arr = charArrayOfLanguage[lang] list = itlEscapeCharConversionList for pair in list: if pair[0] in line: escapeString = "" for char in pair[1]: - escapeString += chr(arr[char]) + escapeString += arr[char] #print(f"Replacing {pair[0]} with {escapeString}!") line = line.replace(pair[0], escapeString) #print(line) @@ -378,10 +360,7 @@ def convert_item(ogDict, lang): outStr = newStr byteStr = "" - if lang == Languages.Japanese: - arr = jpnCharArray - else: - arr = engCharArray + arr = charArrayOfLanguage[lang] for char in outStr[:-1]: byteStr += f"{convert_char_to_byte(ord(char), arr, lang):02x} " if (len(outStr) > 0 and outStr[-1] != ' '): # Check if the last char is a space @@ -463,10 +442,8 @@ def download_xlsx_file(): else: # Online mode if old_file_path.exists(): - new_df = pd.read_excel(new_file_path, sheet_name="Translations") - old_df = pd.read_excel(old_file_path, sheet_name="Translations") - if new_df.equals(old_df): + if hash_excel(new_file_path) == hash_excel(old_file_path): print("Downloaded file is identical.") new_file_path.unlink() if json_file_path.exists(): @@ -483,10 +460,18 @@ def download_xlsx_file(): new_file_path.rename(old_file_path) def transfer_xlsx_to_dict(): - #print("\tGetting character arrays") - #currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") - - #for arr in charArrays: + print("\tGetting character arrays") + currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Character Arrays", header=None) + offset = 0 + for key, value in charArrays.items(): + for r in range(16): + for c in range(16): + val = currSheet.iloc[r + 1, c + offset] + if pd.isna(val): + val = " " + value[r * 0x10 + c] = val + # print(charArrays[key]) + offset += 16 @@ -588,13 +573,10 @@ def output_json_file(): for item in mainDict[lang.name][section]: string = mainDict[lang.name][section][item]["bytes"].split(" ") outText = "" - if lang == Languages.Japanese: - arr = jpnCharArray - else: - arr = engCharArray + arr = charArrayOfLanguage[lang] for byte in string: byte = arr[int(byte, 16)] - outText += chr(byte) + outText += str(byte) mainDict[lang.name][section][item]["text"] = outText with open(dir + '/output.json', 'w') as jsonFile: From 4bf29b1d3b46b7c33ab8ff946c7b0c39d0f0b718 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Fri, 23 Jan 2026 10:14:02 -0500 Subject: [PATCH 17/68] Fixing Japanese periods --- text_helper/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index d101af2..03ef573 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -315,7 +315,8 @@ def convert_item(ogDict, lang): if (out == "ȼ"): offset = 0 currLine = 0 - outStr = outStr[:-1] + if outStr and outStr[-1] == " ": + outStr = outStr[:-1] outStr += "ȼ" index += 1 elif (currLine < numLines): @@ -323,7 +324,8 @@ def convert_item(ogDict, lang): index += 1 outStr += out else: - outStr = outStr[:-1] + if outStr and outStr[-1] == " ": + outStr = outStr[:-1] outStr += "ȼ" # new textbox character offset = 0 currLine = 0 From 48c1ae7b1494a2eeddee242bcfa8308a68467700 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 26 Jan 2026 11:47:28 -0500 Subject: [PATCH 18/68] Adding font generator --- .gitignore | 2 + font_helper/main.py | 160 ++++++++++++++++++++ graphics/latin_normal.png | Bin 0 -> 3077 bytes graphics/unused graphics/latin_normal.png | Bin 10115 -> 0 bytes graphics/unused graphics/latin_short.png | Bin 1361 -> 1455 bytes include/latin_short.h | 21 --- source/japanese_small.s | 163 --------------------- source/latin_short.s | 163 --------------------- source/text_engine.cpp | 14 +- text_helper/main.py | 169 +++++++++++++++++++++- 10 files changed, 335 insertions(+), 357 deletions(-) create mode 100644 font_helper/main.py create mode 100644 graphics/latin_normal.png delete mode 100644 graphics/unused graphics/latin_normal.png delete mode 100644 include/latin_short.h delete mode 100644 source/japanese_small.s delete mode 100644 source/latin_short.s diff --git a/.gitignore b/.gitignore index 371a6c5..55d9a35 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ include/translated_text.h source/translated_text.cpp text_helper/output.json text_helper/text.xlsx +include/latin_normal.h +source/latin_normal.c diff --git a/font_helper/main.py b/font_helper/main.py new file mode 100644 index 0000000..8265056 --- /dev/null +++ b/font_helper/main.py @@ -0,0 +1,160 @@ +import os +import math +from PIL import Image +import numpy as np + +# THIS PROGRAM ASSUMES THAT THE FIRST TWO COLORS IN THE PALETTE ARE THE BACKGROUND AND CELL BACKGROUND! + +dir = os.curdir +BACKGROUND_PAL_INDEX = 0 +CELL_PAL_INDEX = 1 + +class Font: + def __init__(self, fileName, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight): + self.fileName = fileName + self.numColors = numColors + self.numChars = numChars + self.numCharsX = numCharsX + self.numCharsY = numCharsY + self.cellWidth = cellWidth + self.cellHeight = cellHeight + + self.bpp = int(math.log(numColors, 2)) + 1 + self.numWords = self.numChars * self.cellWidth * self.cellHeight * self.bpp // (8 * 4) + self.numBytes = self.numWords * 4 + + self.charWordTable = [0] * self.numWords + self.charWidthTable = [0] * self.numBytes + + +def build_h(myFont): + with open(dir + "/include/" + myFont.fileName + ".h", 'w') as f: + f.write(f''' +#ifndef __{myFont.fileName.upper()}__ +#define __{myFont.fileName.upper()}__ + +extern const TFont {myFont.fileName}Font; + +#define {myFont.fileName}GlyphsLen {myFont.numBytes} +extern const unsigned int {myFont.fileName}Glyphs[{myFont.numBytes}]; + +#define {myFont.fileName}WidthsLen {myFont.numChars} +extern const unsigned char {myFont.fileName}Widths[{myFont.numChars}]; + +#endif +''') + f.close() + +def build_c(myFont): + with open(dir + "/source/" + myFont.fileName + ".c", 'w') as f: + f.write(f'''typedef struct TFont +{{ + const void *data; //!< Character data. + const unsigned char *widths; //!< Width table for vwf. + const unsigned char *heights; //!< Height table for vhf. + unsigned short charOffset; //!< Character offset. + unsigned short charnumChars; //!< Character numChars. + unsigned char charW; //!< Character width (fwf). + unsigned char charH; //!< Character height. + unsigned char cellW; //!< Glyph cell width. + unsigned char cellH; //!< Glyph cell height. + unsigned short cellSize; //!< Cell-size (bytes). + unsigned char bpp; //!< Font bitdepth; + unsigned char extra; //!< Padding. Free to use. +}} TFont;\n +''') + + f.write(f'const unsigned int {myFont.fileName}Glyphs[{myFont.numWords}] __attribute__((aligned(4)))=\n{{\n\t') + + for i in range(myFont.numWords): + f.write(f'{myFont.charWordTable[i]:#010x},') + if (i == myFont.numWords - 1): + f.write("\n};\n\n") + elif (i % 64 == 63): + f.write("\n\n\t") + elif (i % 8 == 7): + f.write("\n\t") + + f.write(f'const unsigned char {myFont.fileName}Widths[{myFont.numChars}] __attribute__((aligned(4)))=\n{{\n\t') + + for i in range(myFont.numChars): + f.write(f'{myFont.charWidthTable[i]:#04x}, ') + if (i == myFont.numChars - 1): + f.write("\n};\n\n") + elif (i % 128 == 127): + f.write("\n\n\t") + elif (i % 16 == 15): + f.write("\n\t") + + f.write(f'''const TFont {myFont.fileName}Font= +{{ + {myFont.fileName}Glyphs, + {myFont.fileName}Widths, + 0, // All heights are the same + 0, // Character offset, is set to zero + {myFont.numChars}, + {myFont.cellWidth}, {myFont.cellHeight}, + {myFont.cellWidth}, {myFont.cellHeight}, + {myFont.numBytes // myFont.numChars}, + {myFont.bpp}, + 0, // Padding, left blank +}}; + ''') + + f.close() + +def generate_tables(myFont): + img = Image.open(f'{dir}/graphics/{myFont.fileName}.png') + if (img.mode != "P"): + print("Error: Image file does not contain a palette") + exit() + pixels = img.load() + + bitsPerWord = 32 + pixelsPerTileX = 8 + pixelsPerTileY = 8 + tilesPerCharX = myFont.cellWidth // pixelsPerTileX + tilesPerCharY = myFont.cellHeight // pixelsPerTileY + charsPerChartX = myFont.numCharsX + charsPerChartY = myFont.numCharsY + + globalX = 0 + globalY = 0 + bitTotal = 0 + + for charY in range(charsPerChartY): + for charX in range(charsPerChartX): + for tileY in range(tilesPerCharY): + for tileX in range(tilesPerCharX): + for pixelY in range(pixelsPerTileY): + for pixelX in range(pixelsPerTileX): + + arrayIndex = bitTotal // bitsPerWord + bitIndex = bitTotal % bitsPerWord + + globalX = pixelX + (tileX * pixelsPerTileX) + (charX * tilesPerCharX * pixelsPerTileX) + globalY = pixelY + (tileY * pixelsPerTileY) + (charY * tilesPerCharY * pixelsPerTileY) + + val = (pixels[globalX, globalY] - 2) & ((1 << myFont.bpp) - 1) + myFont.charWordTable[arrayIndex] |= val << bitIndex + + #print(f'globalX: {globalX}, globalY: {globalY}, arrayIndex:{arrayIndex}, bitIndex:{bitIndex}, val:{val}') + bitTotal += myFont.bpp + + #print(f'{charX, charY}') + + myFont.charWidthTable[(charY * charsPerChartX) + charX] = (tilesPerCharX * pixelsPerTileX) + for x in range(tilesPerCharX * pixelsPerTileX): + globalX = x + (charX * tilesPerCharX * pixelsPerTileX) + globalY = 0 + (charY * tilesPerCharY * pixelsPerTileY) + #print(f'x: {globalX}, y: {globalY}') + if (pixels[globalX, globalY] == BACKGROUND_PAL_INDEX): + myFont.charWidthTable[(charY * charsPerChartX) + charX] = x + break + +# Main: +latin_normal = Font("latin_normal", 1, 256, 16, 16, 16, 16) +generate_tables(latin_normal) +build_h(latin_normal) +build_c(latin_normal) + diff --git a/graphics/latin_normal.png b/graphics/latin_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..e3f323b60e9cc91492d554866bb17aaee0fb6eee GIT binary patch literal 3077 zcmbtW`8U*m`+mJ=CX;0pLP(^9EXg|dNhV9y#8^HtJ|sdTOV)T-(xNn#N*TK(Vy4Kx zOe7=46haz1^RZN z(v2BDWzgYyaL5Meu5qhnV8;_du8k`AoTN{Pnc3>PQf%?z&njD`{LE)41X^i)UE~qP9_RW3u_Pkk}cci`yBry~?x%?{q;xCQ%tL)7Eljy6A zbL2IGzma-fQGw`MEAPzD%7|mE80BIAC-cOHVJ&!Dv{j!~bD1pj<&~E0FxDhq0_U_L zG~^6_x(J5!=^qJ70?esuSD4~;1Q7-<*WR%VXdoPRS7D<-aD+6jJe4QwZGx`=`}t|g zR=!XgIplUP@tSMAlo4= zlJLy83Hx-QLW%ooIA=UfG;5N7YIP%ID^)jajtw^F!hiEK{5@?ES?j{p%lZaXPC;RO zcW*Sy#1YlKXSNuilS=9z1bCAWv`0TKq6_jTxq9#~JhM)M2dCS&OQAI5TL@#}WU>r^ zf^~5D3D~j8m|zil+aYDJ8`Pp@*{=b|rQH)VBp+)4#G7ZgCxur?iBPaE-CvIGsz}0~ zWN+4iuDctMY#5L1sUzOFS|c>4b}2sbDH5>ZDE>Gpkook!1A6dQEJl{A8WF6%&PD&@ zfnq6pRrtEJM`)O_(!Y*_Z2SMe9ZNfncLA##`_Jg5HO^J7>DN-Y3Ckl2NXFRXpPwN0 zX)=}@|9Nm@_O(<5>llLk>ThFu*vEFIq6zWV--dr;G!|xJ^K@X>doN#C5`_yuSnXq8 zhiUMWPYns5ln#b)Pp`N-n%>m#P0=R;dj-Wdq&8%2nErcJ4` zyD+D9;2F23ys(Y{-Fh`H@l}i-+9?Xx>|^M|hEdIjIjiAGPf{Z-&G z6e~3}=X1(MfK{&S&JH?UY~@F|_ya@ikU`B)0WSMSL;jbi;PI^P0>|X23hqgDX>6P( zuF8H8H?qKvDREpI+ZVjSFG!!n6QMl4XL1E17s?P9J1HPT+9?(_w zxH7ofc@*Rw$wAOb&>eXr6gkN7*MZ@u;Jau4xhowAT76zSRM2)o6gQPvuSR(6I@p5t zb22^O>p?wlt#^C9x(Spn^Abi0U-ry|V?c*sB)d*SZS{KdU=X`JNuF{OVtTF`9eerm zyblW3nbiZ2&O=!(9VSoQ{lF26H=) zxtBGzS?wa$?F>w=!Vkp-lf{J!F7_5Cc%)xa9(H;{?G<7P%v#U3fkWm3aC*_epmDm=JGQ^+dsvvuaI%ip6N3c5!9Pb zB@u4NmtEWMu0B#lJR@Xn@R!^9*Q}oAt#C8t5ANFC{pWfbG`01fw6KG9uHjC%chk56 zR+>l>+lm2OalUQClYlja+nNO)Un%*Qg?^qvZ$4#;Bg^g#Ejlx$>EEwLn;eYUl7oLe znePFNwHi>Z?9k(*`g3k54f%a|qaE8H9qO$$Yz+6n2j_VC9WrF7<%FZ2rR<|OSTd{p+d;?-HKnAKN@WYZOu^#b6+#i+--1Ladzs($jhfzv)UY1#c-@1%#W+~b4G zt{I}S#9OxI2?kADz`a{~NFzM{+7|H8wCp_9ogj{kc{~xOsT5 z*P%n|51K`iZV8~KI5F@i(ov*EyE>qhDc-e7HQ>_*@Tc2h-*VJNaTms5r8`uo2(e35 z!lxVUpR@JCiJ_W#&ay`NrEbR~PIL|K;YPv#&a(eR+W$-zzsIgvbPQC^1b`nK_bT8T z$t*VvrTr9Rq!aPcy~$vqxbUuyIK#`5ND&^Zo5)|XBcn{>4Wmd=^;aK-3U?qGQ6J>t z&w5(2(7f-8V%oHI!^?B9VLCZ-KUh?Y=E?f-TsRKVBb4&zURX3LLvKF|Ed@sDF;+34 z^YF@vss#n(5TVCb#gHG>%}o7WeW6w@m6ozl5SfwEd~T`VZf9*VB0?{dXFIdk!T_10Ba z$RZvW3&ohfdGLcUZ+x>4h$>venwZed$a=%CW30>ihtB8bX$e_MC@x?!KNd#=3j_1F zX}-YhtXb+wR&G>s+dF)QVlIN5E+aqvAXz5G`!{T%hwhvkKfV|KG!nj%DGzK5e9rD? z<6ihx7Ga%oP-~%vxWBp7XnVSzdd%DAsrCjF+*E?Tn(R6UuWrP2)VJn~CI9ZNi%DCW zpFF<4Yjl%yfW>>|Fa6zxO=sfne}PRKJbl_o*1MftcI`jM|GW0G@d~9iuaK)+a-P^jdv^>L44&iGR zTS|Qpf7FLAjXC2%O?8nB`-bVn-W%p53TWjogc{y`G;7n9ZQ+x&W_WW4F)KaOe~D$j zoOqHAP6>Z%SG(R_l%HuQJKzK|X69xUA0D*UYqV1fZwxE$?cfGWs~0oG!mGHrxNb|& zkc7FG8cDf`g$zD8lSb)Th>MGxsgXUOMLM^Bx5I(pETzFDhnZwgMFd`M%T3nZW6pKq jyXo_`tb1yP+&1^|ExSX2ept^xlL4+wJbPbDe9`w;N*qtP^2QHLKqeh>f}3)d}& z{{YrCFZl!}0f0VZ^-Zp55EmwfK{G3Sd&#P0A2Aee_3Ehxd&=`j;HlcHZjskiPlp`13E@TYr0?{zyhiO5N4*Y}QLH81TVn$QCH| z^^mV}N|1|5VOBRqqBNr71w?e**X z&J%_X#1eS;7O9wy%NS1n(|GIadx*skaoue|}n`PRzenHR&GP`%er5$5dvx zg;tsr2P)ehmKeXozK3vE%&DXmx z#+LvE-D7fUdG^r9v6Q%KEkhW@jmi+zal6vTV2R2Y!*2ITu3Ay-KN6fP&ofWpAt9%8 zm68eO25W_t*H{xl3?D-%Bc*tBx4)BGbnmGeiHC^%X3wF~D#2iEdoah3`=E~ISKmlw zPoJb8X#D4Lv-(`1r;F~Oxo(Gg&45kDdWPFUS4r$q)qa-~oeWXnBtHK|SH@1}qP}oTVMl+#;KwpI;#z11ZXy-j7OiA$RRY)-VSr1d9epj5uP9U2R9Ss}iv69ydCC z^egNkLdU)@q55_@ZrZo-GWCaIUyb9^(CUgh+ebvASZ1nMR)ne;oYrB9X{i+T1NY!x zwXE!E`by`*%Tcd`|12BvmQeZatdsUAS8D4a%V=5-h!EGlD zB;A(w|gR$=3~slLS;jbe+R$~MwH@8r{p#088{Xjbg7`u3fta^u=__+9Rme!}{k zxMi)x8AGT14@8Vm1e>wKmbRC&s`#xY9L`}Ur`uTQuc?t^@LLs5GZMTS{3N=N`{{=I zr7u&4DJJiooUnf1$|Io!O-Do16hF6upQ;rc^`9grl)E)}cg*X)=eOdHC73Lwe?^97 zLA`cHXq>yH2kVAo432hFue%?Jk0c6^R}{MmqChU!#QW~JwPIT^MkryUGbCpuwKYs8 z^DKF6h~GjqBdA5=R2ov+UA1=`+eX&aks3r7npZ;P{OS@GW2_R=A?X6>+EKH%aYahJu|f&Y%}~&YFvgXaAI)OvSZ4|N3!W# zQ`fDQ#L`jbsJaOC(Uz4e2nuA5*1ykvvQ#{%|GRp}w{i2%9;iCvLVIT)mp(7Ks*XM0 z+Z{STpH=X3%sjdMCCrV!#OLUF>|?VIxIo`VjpW4X-yf`vW3#W?5gMIWw*CHyU({Y~ zqlcdP(#6X>Wj}y{r7I5=O)c~|_zrAk<+a?D{W&_{j0j>w*LHMu*!qgwkn=hAr4v1 zdEU90lcCQYf(n8ee~JIUnAJ;K=}06S?^)D2_bvaEXr6SV&OP9|g-$N__6M%It6zp* zG%uB}QJL84;E@VMmGJAJtIAqr4Xkh@cMIplZ`;`z zw3UX66OigN+Kk~+-&tU3hfLSM*pPEHVlEaZh}WyBe7#?{W4#e->HL92!WD*9F`DJ7 z()0ZG`E_Y7qs5~Fu6djy1lD~PPDvq$IrtxnynM~Qgg=*}@7s%r?>2nwK4NOQ z6873Zyt*Pp@j>LOWQZj4jn%av?a6bbo6MJYTbdQ##LTt;r4L`rRV%qFweifyfaRcy z?VOL_Ee-2~ck+UkTMjgCa@Tcv_pJ;7i0`B-z_q*nfNxD0Th$EECsrQ>dM76Hnvx}BexFG^PBXoLVTBr8=wRxoKjN{kNH?GX#GaU8yC>y_?@}hi4mB#FO zI3RfjQR5_Sq7}Wt6t)5{GE@hL$hzzMPp#+z&+_bnhkJB^6WcU_f8?qE=U#jOk8q3s z`)B`9N|r*f?%>DCg*oW}B-5HRbor~Mp}T=~TzktIj(K1>nM9}T( z>e2|6&vg<%&J%0*)6=h+bfAL0dWj2NF?99MC5^E1Qozj6FX955t>e10NylDH)h>#d z33XP@*9tR6XSf(h`dT+vW;EpvE?nHb6d9!2lbm5iJU*B}uoJUYtgr03y^+w)GeZU*NV z(fEt^2XXf!(;`uCr&8uhU6gsg_q57uiNAD*9QOtA@1gY=w3%990T$L!wg=WP49;s8 zW@C;W0q)0k)P|68RROk~F_2M}dNMC9YSCakkfXqDcqY<-SNa%ag14rwj2JSv&vdlHrYkn&6z@ezW;%1&?FRa+H9h~ z&G}3T*XPTwd7SqtLBXs$%<>TwB$(T6^ad=B-cEF%y@J%uTB%tEA5yKd1FqdMVI%CThzZe>{*>!<=OE&BlPBMTo6o3zdh)h27G&@9i!diPolvy>J$mDkF-v?dD*;j1_MIWc;p^ zXOGRi8SkPCxP7@!jr4$60L=r_iz|m97TD48^%f@8&vVtW3|;MoFfKS9tH$Z%I-2Sx zCi+3sdbivhL2nZv%kAuH0_OT@^dy@Gf4X2*X^iQv3SC1ISGhlne0iNOt%~%9*H6=R zC&U%L1eGm)_ti)+y21^Ju0dCNZQjux56)0osQo6z9K|JUdXaf#ngMS#!B6O#TTn7eWZr| zr<;n=pV}8sxBjpx+5#IIwX&;JdhBhR>mEJLjxQS?d^bAx(aJUQ{O+wET^sJtYkK%8 zCN>-A6u+_cQ5`(`U41*LIO-!{cO|q)z2Vxt=0i*5;mW;BKO6z1f9Ye353aw^T+0Ek zBEafivHG^GLl4H7Ia~F47U~|omVBZu%iUF&(^SfWZ;Rl=C%LUb6*Q%aRSo8m^ z2meh?u2Mj@_19X3U#3JV$R#CZ?cjWzZW2``yj|tb7~kmQpAMhGeEEAqD%uiVBT zz9qp|q#Rv7v)GCr!dvp{LUD*~x%4cwc33^Z@WjXT_A<9XS>Dbdj00zW5xEYJuWVFF_<#ickhTm(UbL0@+jvy)= zx37JNtJd?d$8*u{R3={T<)#*&?c5SwIkY#y^oqrvw4eK;XRcRAFXJM09~R_ylJa>Z z5y$SS1vYPhP1?w!2rXfgCx_ch^szTOhtHcCQuYs*Rascw2JA&JCihr7NeNDV)*uGE z3v+@-K%>TeDub&1J}?k9U$?Thu+*J*y0t3ce+nVil|bBk&YGzSa^r{3rp394%2XHf zH`lLc2A`Vv-0boe`#GT(7;$t7Y%X3|_e2j!6sj0~K53rh@R)YhOmwoOBZAzd_8o`V z>Y#$8U)LJW60Ufw`t*gUnG|-T(&f60QzI`1ljn;QXOHOKgQ|(N9>;y^SKDsZq%g^j zr)&j9ed;-$bE!E0UT)?4m!wLQ&G}RIlK7z{|z-eZ{0V4!hnL zb~=m>h}tzyRJdh&L;n?))U>MiuQD3=WIflVS^nk3@dGhg+rAq$J}*D=xY4-_bm$S% zE*K}Zt-Ug2ESN(Oq0;lgSii7mQIUrz%f&Ry$dRid>4V9r_$P~;oJ+?z>+nz5Mls4` zpSDV^GPAV6IxEVah_T8NihU~-@(_-#%J`YSDac*j%fihKf|dd4V-4jNF%rTS;w?FC zN_M(nvF?4zn6%U+{2g3wt5+VW z4RDA(K!JqvnPZuibb({6l6BNs#itvQ z@?#CxCC5lJszq6px+R1uL@1BZ-#At&A6IATFVj6Yf-FKn=F;kesec)Z&P+gx7R5z9cTtUldlrCOh{fyw58nHA(-og&rF7IA8lJb z9El13wK~ucx_V;U%7D(xI>^aI))M_d_*i*UUPwmLoj3n=aEi++S1UsWgXqVyYfKMJQv$v2bLGGS4okp~5Hc%!<}XZTw*|$1^*vVCI#KeE-jHffK25IAC`f zRX%){pf^~4D1Q23{6VOt$@BL{1c)T!Oga4SSYw)M&nFV{sA}(g{8j$MmDhLeb@6qf z-QE#1OtDw|M0~G|UAaF_G!MMcbzNcKNo! z-=kTnNJT5h$B-Z}P`|WHxh;Qz{q86<(9b9~4qMZv_Vdpd1Uk{NFn&+Ef|lKMV0vA<2d zsOqc9l!Uqn6=i~f7A06U59gf2w^<8f3#oCi9Atsrs${d!?dNvh@ckl(;jjUM9Wx~A_xXiGls`tJYWju(di3oJ_$es;fIHR^@w_a$$YARRw z8)kMGkd0@5mZ75*IE(o`nh#s2yqGSRB#Q~_V5ZsS)AA`)Pd~4+jtkqM88$!myVNSB zFQ$5OQK++s`pjvEl?Y6x+xQZ0;v9)q1n}3)Z5Uy^+OUxP&?q})8QLqO?i^Y(T!=du zCU-y7m(v{F?bX*W&%5(AP^r%jB~~szs>#}sf|d+h(Nuk|-RM~OgZsL@`5hZcpi(}F zd@f4Tz6pCL>EeaKq3Cw$*+FE+*uo7h&ucla9$@d_S=;+oc#djh>u{F1_(rs(eN8nmk=yq`m&Qd8~BNv23 zF7g*2EPP+i8ZW5G9?Du^a8(fu6h^aXVmK@|s_aHLYU;eIr^eWpB{$$chzw2M(#dHe0^2L4mWYfK56KF=C+7fS@?rzai*%Ud-En( zH`UpE=&x#bkAWVa(7S_7GR;YqeP5WA*L75pXuq&v2~Ct)XP4LHyE^%l%L)%KSs<3b z@R>Z>yDD->9j-$>Ch4kdTGll z9G-w73=F}4@*-J^L`&M5BhRudQIP%;uc@=udHQC8ZsJFznt<`YmdMgh|MB?th7 z%UehXn~BCL8TO&iM)S+6YUa9VzfhXr7}{dcqxf?DjG>^pUA|)Y@C!vCfu4*;FL-&5 zx>7kMCK)=cRHt8ORML>vVqcn$_2j8KTI>}hVzS8Gj}|lH9ASehZIL)OZ7@f?zQ$Q2 z{9ve)KTz#I%i799@l=fVCw}G^S0TMD(qJ5i^NK@7u}bTBg)-25f+sc#oZN30?WIv@ zVH}J^a_V*olBLhkR>Psi5q7QQ(w<@xv&`Jpdp3|m*%;B=qqG(L!`yHzD1+gnsB0?Je@8Myl2thlKGA(Uqt;_k-HU_N+yqo`nTsWY}-YYOs#En!%jOW zVPPnB`WWn631=QBdmo)i>i2afr;$pYd+**{8p15N9#GG(o&V%A-$H~QU7k*sHJGBu zn#A5?O8=Fj>)yS6z7BWNs*>A}6%wdkGsx_TYqN1=6+))L4n*!}#APFe6mkTHa4$%| zvs9Jtz7w=A`nNE`EjT7I+GGfRZ8b(WMI8{FoGphXVqU#P-*~N+lF~4)Sw)#$dan7>wt9^g#N#d%3H`$Nt z&c01B9ASuj6D~kPcHKSPUO6CrCRz;%cdOj&t|Ho)s;LgvaB*vlzVlJN3{h!~5slCR z7p?3$A@ur{ey}kmqwtR7(V0Ak2A?k1$7%s-X>0t^*dz1Y#u9zil>36vzDm9Nxy@=jRck_Ula@2Ze_YlmZ`tB!KY~c{v zsq3JZ#QJ&0cSwmRNGB=xZiGGNT{UpCz#g7Y`-ME_ZH!!20HiGfmrScoM@*ax-V~m^ zWvUk6xKP3M!%KJFUiNGj!Y#r+&u1WR=)Wpx7RivvcBqipdH5Y(jb zrFvBT_Z0NwOsoMeBs4TM(wgp4>6UK%()Eli7`mbLpBXeNl`8iz$|YUt3A%->yjn$l zwFNm=+amF8N04xoO@%{lVBmJSlWu%2&KQ}m1>}YrcwT^HeW>cHB68h+=(nw}8-w^e z;UcqX7euE30B_3G0Aqdq|26FSZ{hy`0yO>qz5h2*^Z%#Xzo;fzoWz8N3jTOziZO!4 z)>YCbL6Aa2&JETb7_-s9n!73YT}QLF_hAL z+LEYy@A-PSb+<})JdXT%tQjK5#L`#T%UUD#6z#L2=21qOaK5W@W#Lm$tRUI>)sUHh zWb3a#xxfmpN4eo)>zacHGN*R*`i9(h@cdb}d@@3YTu?81Vvi3Ik>id*qxsY^aHQFM zt{&tt>X5DRWiTB+o%|zd$0uZ=*@NXK1xc>n|HpMOxoBGQ)}(^_(K+3=Q2)qCvNJ32 z9ifr+#&FtE|I#4|p1#tg#&p5Lb0yqaJ*RIYP~#Ja)g{%TP3wA$;OKgOf^ii(k)oEr^h0@X9W4w zk2-Muj}h(z!R4>Pg~v?e$U-kY58LdU#Y?NX%-vR4hjTeOCYh#3l8qlHAU>V+?AsQ+ zLXVL7N65mw6#C@Mux8`?VqCf@fX!qn%CW%ce`34;B^VsO*kLs9xn>9F!=_@^TGqL= zpQ%e)1fHqjU9=K}6Xs3Ic8F>^ubh5x85*?5%TtaV%J*>R#J_U|^R=}1=p*`&)UyMm z4)O?MM?!un{nlpl$W;*GN1Ma4&=r$>QiO}=s92U)O6OgnpJTDag3lae%Rce0_dZTE=cK_h>)X-v-~SjvA`@E8y%rs^l8iJuMKuN(f_FpIwk zf&AIBk4u23S9$v#$M3&MmtC`nN2-=l-9Feal@7hyNAdQ1CD!@fsS7JupjVNKswjdR zI;I@;#oFe6Z%nb%y5AkysK$Mi8d8(^+wj}aN~#I1*aP)t3Lv`FZ!_)*pd3F`B_@&V z7Uyx9{xbg|kTqg3yv4hp-3_?Tk$EKt*cw%<$pzkv507yh&JC#y!JNz^;~-5dhx+Ea zc8l>O;bSprTn4}JzSi#Qoy}?X{cIshK7H^&kRM8cXx|oCCiPe0+jIv#Y)D{4JHeWG zIx(a>Uvpc;zu*#s8nVLKUv13wc1GQyA9Lc^nJM7x;R?~)(lN-p)%5ezOs%zHn5Uez z>S3Y(#yf*GX?iCxMq`R4jc30EsWvzk*Jdte{Pu)q ziH-oTE4;T!br~;xO@@B_jY)FUE~LiqbW!wh^g0ShPGMzJFEqj;HX@x;BgteQ{!iE> z7?VvS7C7y(tB&o1Cy`~}q=S)yMM&?8t-P%=r0Fx`$@eCxV~Z}!yum^>(|NWymp6RW zX~ugrJdYDL{h@91^A}QsItnwCoCZbR08x%~>QR03bgHE9{3l)wr`N`y?ru?Daf1z z_3|uMRO?Gr1m2ekP$Xr+onjGNs?u|FYID#+S^CqUr^w+s7C*g^7+TLOB*l8)cMS46 z1vkZIF&7IWXivS;*skxsXT-ZZ3qw6+JBG4gQnK9t0-qwCTNLw{+*!4`4XVqyUh~XJ zrxfNQJs`9szx1O2VKN)TTQbeHFJin}jw@(|5fo(NI-0X5RI&Vhfu!EQ#;_36Q|#`X zV_zK)9j-t$Tv9t!*Q{{Z75q;3EJ diff --git a/graphics/unused graphics/latin_short.png b/graphics/unused graphics/latin_short.png index 75ec62c95fa15811f5fce198ec5a140237163cb3..52e40dfda3984b6ae1d501c9e1ff4e6df8d627bb 100644 GIT binary patch delta 1395 zcmV-(1&sR93a<;0DF!$=IRF3ukufNL003npK41U<1v*JYK~#90?VF31>naF_;SKQp zUv@|2S0Jgi$yu}7v$T_GbMX_bAYMx4NW#ZII*$6D&iQzWT@UAWpIQ$6D^lMnp5uN= z=eSQLC59M_NddJ~94a00=_u~Hf1Xh4c$L#pTpgeu@VWrhQc5`90=%eD5NXd5%j7oPF~_BX&?rubj-K3MzzjKQeVPBut#GF>5h%GE_y5#$hUG zn`Z+=rz6h9t9e8TxJfYF%h_MRY7LOZ&;H4*E?XzRPyuzI*HP6IAEA{PooXvrnDsz~ zqfUC=%)Z7?(gyq#ptlu1?9my28wLA*5oA;+Gg@eEqQg4rDrm^(y}8$bg6ahaSXH4* z;5`jgP%*u|Gnu&U7}u*Eh{Zw)kgSmdAbX^$!W=+TKs^_tsRZR6;3Pq+1BO4)Bdb8Z z4j!fr_WQ6mb4*<2DKLE_4fxiKWJXE21#oGWHVaYuJ(rUE$g)MP^12$j-HedrD z0QxADwNq&kxpq&fXFyRL`wZ&i;QDr?qg$-QXAv8Sx5&9=I>OJEnKJ&Z9WnMApad0` z04eQ3Xf?ggi?Ca!DwPu&e?~za_!rIv8t&cfSQqVR_<`W;ZgHO{2Y`_L=zy^F3 zfSLL{Qr~BT=-$2iX99q9a;Oaj`sOx4NQwVpx-3Z`Z8Oif=>cU0Q2bn4%kUAP3<)qL z(RMnFXrbg& zUPH_+x_uX+eY^rZNgMDr09^_Y9``J922dMD~cU!>*x42CZ4yN>X9Sti*+JrWS z3e0>4xaRG5?sWj$?q|3Ky=^LB8(&bKq9VxZUFAf?fuD*0ou0BOoc`tDCAw5U55%98ntZ#V=Tbg3eBKO37^s9DnMf- zlwLf*S!rn!beM%iy4{$ZN6K~&5C$V2pb~hI3u7#QlV6R+~Z*k7hk&9w3-c5GTPE0OJe* zyyL-aMy~@8=qgwRWB_(I!K4EKaexUqe1h|tCW$D8s7I6!QF{w}#rWl@6AAF8p; z=OLzI4n=nj@EZw)$8bNZ06@vAWfh{>78^^OFYUqfDxkkKfPi&PBoHnkGWP%-Tktf0 z1H_C2fEmk)21AATyDjqp)()l;yuP)THsBio{{Y0G)`TJ!uXq3e002ovPDHLkV1k#r Bak&5h delta 1301 zcmV+w1?u{*3(*RYDF*-l|2Q}}kufNL000R{cRK(81l&nPK~#90?OO?Q>?#aIH<0ds z*>41`1>1P5di9gg6HmrlBV;5bz@=;;-o9kp>i=P%w-5E>X+K=Q-JySl>VL`mIA4bS zbUuQe_C9K<*i<^<-B!Z!c)gYCpR(IZ&;j(|SMLaFDJ5-}1W{B}zy3y;0)A zuRyeK;v*ZqWN`cDjZHX^9u$(C%n?)wGCwkPG8>cdXpLhnrcS1coJ_g&DrlSMPLQ1r zWfLDH;3h$|ww;79*~62AM9{&`I{Bmu(19K&v-fi!QI(jTYActRb%Vm86VuHL=e_Xr zVQqysdrZc55;TlX##?A@qM;RkodykqlL3K9J@BF#ngr3)pb9Exw0D*+Zrl8LwFBz0 z&_FO)BPW8M$pJ=Rh9XOfGpsyc>>BM-Kzvh zw&je#NAdLLPT&$)wvHJ49zjV}xJA%z=`jXTR-$ggI|7Ukf}p1(nm9-s9#MkUUUanN zee6K5^1VbLYuw_8V)o#F?hH&HPD0?^Q(<0#W{vMoyPS_^{u6>(=5dFBW){3`^N(Se zsd@~IjgONKx8Z~8Lh_<-*lv!7Mh8Wb16i zW6QV>4c8=y4i2?cz!tX=LP~t6;j%#jYKwXNWF#ne2*Q{mN_<36T!J?4*~eAIqm`zE zeofbbWk4vyi)4$FF!F|~jj{HQfFP5&@}h3eAEjMbQmK(3O1T?Sz;m{-#jx%owU0A` zgO;Xi(h|N~8z%4Hmgo7#e-?HFVIv1o0%)BQE%{#B$>jWxAakrqS zdkI*E6mOg^GuW>|BqJ4ZqdOf4F!RPbgyt~YEXN%QxcC}G5Kwtgg(yM0ng#-%6C~U3 zN{sd1J;qPqk8=(S{|oqX``o3)U!xyDClq?CG+k!hdo^=^IcUJC&pagHD}oYyMtJD~ zXUr6;XWB1^4)c?cfU*RMpq~?QW9p`PT?IW#E_R>$!`;UO9F#cLJFaa`1o1J(-|;cF z>&W&C`i_N#iqTNeS#Y>QA->nv-g(O(8|)zgWjg<@x3NvP+m(+Av~71YCGkFxB)YVI z996I-)o#CkDLPM1BvjW3xD#lMgwm50ve;hg+GgLOKR=j3xZi=BcwO)3A*`e+{-1|J%`s5?e(M(E3|$T?`RG0FzE%w z{a+*?j3-c#;D`Wu1_+|#!Dxo*AQD&=x&lT5tZssTNhbot0bNM<3BhNg5O}GD--F!# z0_s}B@^I2C^@nxRzg&z%%p8iY8sG~Fh&|mOs{n#ar;)|(u`#u`rv$
|@P00HZo zNI+atWF85mpP$wsj^?%-oj}QN)Sw^-@b|X559saSN{0(S9{&N=scEZT`1MTy0000< LMNUKnu0mjfim7$^ diff --git a/include/latin_short.h b/include/latin_short.h deleted file mode 100644 index 6e61434..0000000 --- a/include/latin_short.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "debug_mode.h" -#include "pokemon_data.h" - -#if PTGB_BUILD_LANGUAGE != JPN_ID - -//{{BLOCK(latin_short) - -#ifndef __LATIN_SHORT__ -#define __LATIN_SHORT__ - -extern const TFont latin_shortFont; - -#define latin_shortGlyphsLen 4096 -extern const unsigned int latin_shortGlyphs[1024]; -#define BUILD_FONT &latin_shortFont - -#endif // __LATIN_SHORT__ - -//}}BLOCK(latin_short) - -#endif \ No newline at end of file diff --git a/source/japanese_small.s b/source/japanese_small.s deleted file mode 100644 index 3238a8c..0000000 --- a/source/japanese_small.s +++ /dev/null @@ -1,163 +0,0 @@ - -@{{BLOCK(japanese_small) - - .section .rodata - .align 2 - .global japanese_smallFont -japanese_smallFont: - .word japanese_smallGlyphs, 0, 0 - .hword 0, 256 - .byte 8, 10 - .byte 8, 16 - .hword 16 - .byte 1, 0 - - .section .rodata - .align 2 - .global japanese_smallGlyphs @ 4096 unsigned chars -japanese_smallGlyphs: - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x3F040000,0x49553E04,0x00000026,0x00000000 - .word 0x21000000,0x0A414141,0x00000004,0x00000000,0x001E0000,0x1020201E,0x0000000C,0x00000000 - .word 0x001E0000,0x120C101F,0x00000061,0x00000000,0x5F440000,0x45453E04,0x00000026,0x00000000 - .word 0x4F220000,0x10111252,0x0000000C,0x00000000,0x3F080000,0x01107E08,0x0000003E,0x00000000 - .word 0x08100000,0x08040204,0x00000010,0x00000000,0x79200000,0x21212121,0x00000012,0x00000000 - .word 0x1F000000,0x01000020,0x0000003E,0x00000000,0x7F080000,0x01013E10,0x0000003E,0x00000000 - .word 0x02020000,0x42420202,0x0000003C,0x00000000,0x7F200000,0x203C223C,0x00000018,0x00000000 - .word 0x22220000,0x0212227F,0x0000003C,0x00000000,0x081E0000,0x020C3F04,0x0000001C,0x00000000 - - .word 0x3F040000,0x09027A02,0x00000071,0x00000000,0x3F040000,0x40423E02,0x00000038,0x00000000 - .word 0x3F000000,0x20404040,0x0000001C,0x00000000,0x307F0000,0x08040408,0x00000030,0x00000000 - .word 0x34040000,0x0101010E,0x0000007E,0x00000000,0x4F240000,0x623C2102,0x0000001C,0x00000000 - .word 0x01790000,0x05050101,0x00000079,0x00000000,0x12120000,0x55654B3E,0x00000032,0x00000000 - .word 0x37040000,0x5566444C,0x00000074,0x00000000,0x2A1C0000,0x49494949,0x00000026,0x00000000 - .word 0x79210000,0x65392121,0x00000019,0x00000000,0x240F0000,0x11312262,0x0000000E,0x00000000 - .word 0x101C0000,0x51511208,0x0000000C,0x00000000,0x04000000,0x4020110A,0x00000000,0x00000000 - .word 0x21790000,0x65392179,0x00000019,0x00000000,0x207C0000,0x623C207C,0x0000003C,0x00000000 - - .word 0x280E0000,0x25653E28,0x00000012,0x00000000,0x5F440000,0x46450506,0x0000003C,0x00000000 - .word 0x1A120000,0x4D495536,0x00000032,0x00000000,0x1F040000,0x44041F04,0x00000038,0x00000000 - .word 0x3F120000,0x04324252,0x00000004,0x00000000,0x3D080000,0x3C515153,0x00000008,0x00000000 - .word 0x38080000,0x093E0808,0x00000006,0x00000000,0x011C0000,0x2020231D,0x0000001E,0x00000000 - .word 0x22220000,0x10202222,0x0000000C,0x00000000,0x081E0000,0x524C413E,0x0000003C,0x00000000 - .word 0x37040000,0x2526444C,0x00000044,0x00000000,0x183E0000,0x4040423C,0x00000038,0x00000000 - .word 0x37040000,0x4546444C,0x00000024,0x00000000,0x3E040000,0x04143A0C,0x00000038,0x00000000 - .word 0x04040000,0x11120E04,0x00000061,0x00000000,0x04000000,0x351E143F,0x0000002E,0x00000000 - - .word 0x00000000,0x0A212111,0x00000004,0x00000000,0x1E000000,0x10201E00,0x0000000C,0x00000000 - .word 0x0E000000,0x1E081F00,0x00000031,0x00000000,0x24000000,0x251E042F,0x00000016,0x00000000 - .word 0x0A000000,0x14222B1E,0x00000004,0x00000000,0x08000000,0x1C292B1D,0x00000008,0x00000000 - .word 0x08000000,0x190E0818,0x0000000E,0x00000000,0x2F025050,0x10115252,0x0000000C,0x00000000 - .word 0x3F085050,0x01107E08,0x0000003E,0x00000000,0x08100000,0x08045254,0x00000010,0x00000000 - .word 0x79205050,0x21212121,0x00000012,0x00000000,0x1F005050,0x01000020,0x0000003E,0x00000000 - .word 0x7F085050,0x01013E10,0x0000003E,0x00000000,0x52020000,0x42420252,0x0000003C,0x00000000 - .word 0x7F205050,0x203C223C,0x00000018,0x00000000,0x22225050,0x0212227F,0x0000003C,0x00000000 - - .word 0x080E5050,0x020C3F04,0x0000001C,0x00000000,0x3F045050,0x09027A02,0x00000071,0x00000000 - .word 0x3F045050,0x40423E02,0x00000038,0x00000000,0x3F005050,0x20404040,0x0000001C,0x00000000 - .word 0x307F5050,0x08040408,0x00000030,0x00000000,0x34045050,0x0101010E,0x0000007E,0x00000000 - .word 0x79215050,0x65392121,0x00000019,0x00000000,0x240F5050,0x11312262,0x0000000E,0x00000000 - .word 0x100C5050,0x51511208,0x0000000C,0x00000000,0x54500000,0x4020110A,0x00000000,0x00000000 - .word 0x21795050,0x65392179,0x00000019,0x00000000,0x79216060,0x65392121,0x00000019,0x00000000 - .word 0x240F6060,0x11312262,0x0000000E,0x00000000,0x101C6060,0x51511208,0x0000000C,0x00000000 - .word 0x34300000,0x4020110A,0x00000000,0x00000000,0x21396060,0x65392179,0x00000019,0x00000000 - - .word 0x00000000,0x0810100F,0x00000006,0x00000000,0x407F0000,0x08083848,0x00000006,0x00000000 - .word 0x10200000,0x08080B0C,0x00000008,0x00000000,0x7F080000,0x20404141,0x00000018,0x00000000 - .word 0x3E000000,0x08080808,0x0000007F,0x00000000,0x7F100000,0x11121418,0x00000018,0x00000000 - .word 0x7E040000,0x42444444,0x00000031,0x00000000,0x7F080000,0x087F0808,0x00000008,0x00000000 - .word 0x223E0000,0x10202021,0x0000000E,0x00000000,0x7E020000,0x10202122,0x0000000C,0x00000000 - .word 0x203F0000,0x20202020,0x0000003F,0x00000000,0x7F220000,0x20202222,0x0000001C,0x00000000 - .word 0x400E0000,0x2040404E,0x0000001E,0x00000000,0x203F0000,0x24181020,0x00000043,0x00000000 - .word 0x7F020000,0x02022242,0x0000003C,0x00000000,0x41410000,0x10202042,0x0000000C,0x00000000 - - .word 0x427E0000,0x2040714E,0x0000001C,0x00000000,0x1E300000,0x08107F10,0x00000006,0x00000000 - .word 0x4A4A0000,0x2040404A,0x0000001C,0x00000000,0x003E0000,0x1010107F,0x0000000C,0x00000000 - .word 0x04040000,0x0404443C,0x00000004,0x00000000,0x7F100000,0x08101010,0x00000006,0x00000000 - .word 0x3E000000,0x00000000,0x0000007F,0x00000000,0x203F0000,0x14081420,0x00000023,0x00000000 - .word 0x7F080000,0x4B2C1820,0x00000008,0x00000000,0x20200000,0x0C101020,0x00000003,0x00000000 - .word 0x12000000,0x41422222,0x00000041,0x00000000,0x31010000,0x0101010F,0x0000007E,0x00000000 - .word 0x407F0000,0x10202040,0x0000000C,0x00000000,0x04000000,0x4020110A,0x00000000,0x00000000 - .word 0x7F080000,0x4A4A2A08,0x00000049,0x00000000,0x407F0000,0x08142240,0x00000010,0x00000000 - - .word 0x001E0000,0x03001C02,0x0000003C,0x00000000,0x04040000,0x41222204,0x0000007F,0x00000000 - .word 0x24200000,0x4C301028,0x00000003,0x00000000,0x043F0000,0x04047F04,0x00000078,0x00000000 - .word 0x7F040000,0x04142444,0x00000004,0x00000000,0x101E0000,0x7F101010,0x00000000,0x00000000 - .word 0x407F0000,0x40407E40,0x0000007F,0x00000000,0x003E0000,0x2040407F,0x0000001C,0x00000000 - .word 0x22220000,0x10202222,0x0000000C,0x00000000,0x0A0A0000,0x4A0A0A0A,0x00000039,0x00000000 - .word 0x02020000,0x1A224202,0x00000006,0x00000000,0x417F0000,0x7F414141,0x00000000,0x00000000 - .word 0x417F0000,0x20404041,0x0000001C,0x00000000,0x407F0000,0x20407E40,0x0000001E,0x00000000 - .word 0x40470000,0x20404040,0x0000001F,0x00000000,0x00000000,0x0C14101F,0x00000002,0x00000000 - - .word 0x00000000,0x080E0810,0x00000008,0x00000000,0x00000000,0x08111F04,0x00000006,0x00000000 - .word 0x00000000,0x04040E00,0x0000001F,0x00000000,0x00000000,0x0A0C1F08,0x00000009,0x00000000 - .word 0x00000000,0x0A121F02,0x00000002,0x00000000,0x00000000,0x08080E00,0x0000001F,0x00000000 - .word 0x00000000,0x080F080F,0x0000000F,0x00000000,0x7E045050,0x42444444,0x00000031,0x00000000 - .word 0x7F085050,0x087F0808,0x00000008,0x00000000,0x223E5050,0x10202021,0x0000000E,0x00000000 - .word 0x7E025050,0x10202122,0x0000000C,0x00000000,0x203F5050,0x20202020,0x0000003F,0x00000000 - .word 0x7F225050,0x20202222,0x0000001C,0x00000000,0x400E5050,0x2040404E,0x0000001E,0x00000000 - .word 0x203F5050,0x24181020,0x00000043,0x00000000,0x7F025050,0x02022242,0x0000003C,0x00000000 - - .word 0x41015050,0x10202042,0x0000000C,0x00000000,0x427E5050,0x2040714E,0x0000001C,0x00000000 - .word 0x1E305050,0x08107F10,0x00000006,0x00000000,0x4A0A5050,0x2040404A,0x0000001C,0x00000000 - .word 0x003E5050,0x1010107F,0x0000000C,0x00000000,0x04045050,0x0404443C,0x00000004,0x00000000 - .word 0x12005050,0x41422222,0x00000041,0x00000000,0x31015050,0x0101010F,0x0000007E,0x00000000 - .word 0x407F5050,0x10202040,0x0000000C,0x00000000,0x54500000,0x4020110A,0x00000000,0x00000000 - .word 0x7F085050,0x4A4A2A08,0x00000049,0x00000000,0x12606000,0x41422222,0x00000041,0x00000000 - .word 0x19616000,0x01010107,0x0000007E,0x00000000,0x7F606000,0x10202040,0x0000000C,0x00000000 - .word 0x34300000,0x4020110A,0x00000000,0x00000000,0x7F686000,0x4A4A2A08,0x00000049,0x00000000 - - .word 0x00000000,0x08101515,0x00000006,0x00000000,0x1C000000,0x26636332,0x0000001C,0x00000000 - .word 0x18000000,0x1818181C,0x0000007E,0x00000000,0x3E000000,0x071E7063,0x0000007F,0x00000000 - .word 0x7E000000,0x63601C30,0x0000003E,0x00000000,0x38000000,0x7F33363C,0x00000030,0x00000000 - .word 0x3F000000,0x63603F03,0x0000003E,0x00000000,0x3E000000,0x63633F03,0x0000003E,0x00000000 - .word 0x7F000000,0x0C183063,0x0000000C,0x00000000,0x3E000000,0x63633E63,0x0000003E,0x00000000 - .word 0x3E000000,0x607E6363,0x0000003E,0x00000000,0x3C3C0000,0x1800183C,0x00000018,0x00000000 - .word 0x673E0000,0x18001873,0x00000018,0x00000000,0x00000000,0x0A0E0000,0x0000000E,0x00000000 - .word 0x00000000,0x00003F00,0x00000000,0x00000000,0x00000000,0x00181800,0x00000000,0x00000000 - - .word 0x00000000,0x00220000,0x00000000,0x00000000,0x447C0000,0x1C141474,0x00000000,0x00000000 - .word 0x1C000000,0x11171414,0x0000001F,0x00000000,0x10700000,0x00001010,0x00000000,0x00000000 - .word 0x00000000,0x04040400,0x00000007,0x00000000,0x2A1C0000,0x22221C08,0x0000001C,0x00000000 - .word 0x221C0000,0x3E081C22,0x00000808,0x00000000,0x497F0000,0x41417F49,0x00000061,0x00000000 - .word 0x00000000,0x0C0C0000,0x00000000,0x00000000,0x22000000,0x22140814,0x00000000,0x00000000 - .word 0x20400000,0x02040810,0x00000001,0x00000000,0x361C0000,0x637F6363,0x00000063,0x00000000 - .word 0x633F0000,0x63633F63,0x0000003F,0x00000000,0x673E0000,0x67030303,0x0000003E,0x00000000 - .word 0x331F0000,0x33636363,0x0000001F,0x00000000,0x037F0000,0x03033F03,0x0000007F,0x00000000 - - .word 0x037F0000,0x03033F03,0x00000003,0x00000000,0x077E0000,0x67637303,0x0000007E,0x00000000 - .word 0x63630000,0x63637F63,0x00000063,0x00000000,0x187E0000,0x18181818,0x0000007E,0x00000000 - .word 0x60600000,0x63636060,0x0000003E,0x00000000,0x63630000,0x63331F33,0x00000063,0x00000000 - .word 0x06060000,0x06060606,0x0000007E,0x00000000,0x63410000,0x636B7F77,0x00000063,0x00000000 - .word 0x67630000,0x737B7F6F,0x00000063,0x00000000,0x633E0000,0x63636363,0x0000003E,0x00000000 - .word 0x633F0000,0x033F6363,0x00000003,0x00000000,0x633E0000,0x3B636363,0x0000006E,0x00000000 - .word 0x633F0000,0x3B1F7363,0x00000073,0x00000000,0x331E0000,0x63603E03,0x0000003E,0x00000000 - .word 0x187E0000,0x18181818,0x00000018,0x00000000,0x63630000,0x63636363,0x0000003E,0x00000000 - - .word 0x63630000,0x1C3E7763,0x00000008,0x00000000,0x63630000,0x777F7F6B,0x00000063,0x00000000 - .word 0x63630000,0x63361C36,0x00000063,0x00000000,0x66660000,0x18183C66,0x00000018,0x00000000 - .word 0x707F0000,0x070E1C38,0x0000007F,0x00000000,0x00000000,0x667E603C,0x0000007C,0x00000000 - .word 0x06000000,0x66663E06,0x0000003E,0x00000000,0x00000000,0x6606663C,0x0000003C,0x00000000 - .word 0x60000000,0x66667C60,0x0000007C,0x00000000,0x00000000,0x067E663C,0x0000003C,0x00000000 - .word 0x38000000,0x0C0C3E0C,0x0000000C,0x00000000,0x7C000000,0x607C6666,0x0000003E,0x00000000 - .word 0x06000000,0x66663E06,0x00000066,0x00000000,0x18000000,0x18181800,0x00000018,0x00000000 - .word 0x30000000,0x33303000,0x0000001E,0x00000000,0x06000000,0x361E3666,0x00000066,0x00000000 - - .word 0x0E000000,0x0C0C0C0C,0x0000000C,0x00000000,0x00000000,0x6B6B6B3F,0x00000063,0x00000000 - .word 0x00000000,0x6666663E,0x00000066,0x00000000,0x00000000,0x6666663C,0x0000003C,0x00000000 - .word 0x00000000,0x3E66663C,0x00000006,0x00000000,0x00000000,0x7C66663C,0x00000060,0x00000000 - .word 0x00000000,0x0C0C1C6C,0x0000000C,0x00000000,0x00000000,0x303E063C,0x0000001E,0x00000000 - .word 0x0C000000,0x0C0C0C1E,0x0000001C,0x00000000,0x00000000,0x66666666,0x0000007C,0x00000000 - .word 0x00000000,0x36666666,0x0000001C,0x00000000,0x00000000,0x6B6B6B63,0x00000037,0x00000000 - .word 0x00000000,0x361C3663,0x00000063,0x00000000,0x00000000,0x607C6666,0x0000003C,0x00000000 - .word 0x00000000,0x0C18307E,0x0000007E,0x00000000,0x0E060000,0x0E1E3E1E,0x00000006,0x00000000 - - .word 0x00000000,0x08000008,0x00000000,0x00000000,0x00660000,0x7F63361C,0x00000063,0x00000000 - .word 0x00660000,0x6363633E,0x0000003E,0x00000000,0x00660000,0x63636363,0x0000003E,0x00000000 - .word 0x00660000,0x667E603C,0x0000007C,0x00000000,0x00660000,0x6666663C,0x0000003C,0x00000000 - .word 0x00660000,0x66666666,0x0000007C,0x00000000,0xFFFFFE7C,0xC68339EF,0x0000007C,0x00000000 - .word 0x01010000,0x00010101,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - -@}}BLOCK(japanese_small) diff --git a/source/latin_short.s b/source/latin_short.s deleted file mode 100644 index 8bdf1bb..0000000 --- a/source/latin_short.s +++ /dev/null @@ -1,163 +0,0 @@ - -@{{BLOCK(latin_short) - - .section .rodata - .align 2 - .global latin_shortFont -latin_shortFont: - .word latin_shortGlyphs, 0, 0 - .hword 0, 256 - .byte 6, 10 - .byte 8, 16 - .hword 16 - .byte 1, 0 - - .section .rodata - .align 2 - .global latin_shortGlyphs @ 4096 unsigned chars -latin_shortGlyphs: - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x0E000806,0x111F1111,0x00000011,0x00000000 - .word 0x0E00020C,0x111F1111,0x00000011,0x00000000,0x0E000A04,0x111F1111,0x00000011,0x00000000 - .word 0x01110E00,0x040E1101,0x00000002,0x00000000,0x1F000806,0x01010F01,0x0000001F,0x00000000 - .word 0x1F00020C,0x01010F01,0x0000001F,0x00000000,0x1F000A04,0x01010F01,0x0000001F,0x00000000 - .word 0x1F000011,0x01010F01,0x0000001F,0x00000000,0x1F000806,0x04040404,0x0000001F,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x1F000A04,0x04040404,0x0000001F,0x00000000 - .word 0x1F000011,0x04040404,0x0000001F,0x00000000,0x0E000806,0x11111111,0x0000000E,0x00000000 - .word 0x0E00020C,0x11111111,0x0000000E,0x00000000,0x0E000A04,0x11111111,0x0000000E,0x00000000 - - .word 0x76000000,0x19117119,0x00000076,0x00000000,0x11000806,0x11111111,0x0000000E,0x00000000 - .word 0x1100020C,0x11111111,0x0000000E,0x00000000,0x11000A04,0x11111111,0x0000000E,0x00000000 - .word 0x11001926,0x11191513,0x00000011,0x00000000,0x09090700,0x09110907,0x00000001,0x00000000 - .word 0x00080600,0x1911111E,0x00000016,0x00000000,0x00020C00,0x1911111E,0x00000016,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x110E0000,0x040E1101,0x00000002,0x00000000 - .word 0x00080600,0x011F110E,0x0000001E,0x00000000,0x00020C00,0x011F110E,0x0000001E,0x00000000 - .word 0x000A0400,0x011F110E,0x0000001E,0x00000000,0x00001100,0x011F110E,0x0000001E,0x00000000 - .word 0x00080600,0x04040404,0x00000004,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x000A0400,0x04040404,0x00000004,0x00000000,0x00001100,0x04040404,0x00000004,0x00000000 - .word 0x00080600,0x1111110E,0x0000000E,0x00000000,0x00020C00,0x1111110E,0x0000000E,0x00000000 - .word 0x000A0400,0x1111110E,0x0000000E,0x00000000,0x00000000,0x09794936,0x00000076,0x00000000 - .word 0x00080600,0x09090909,0x00000016,0x00000000,0x00020C00,0x09090909,0x00000016,0x00000000 - .word 0x000A0400,0x09090909,0x00000016,0x00000000,0x00192600,0x1212120E,0x00000012,0x00000000 - .word 0x1111110E,0x0000000E,0x00000000,0x00000000,0x0909090E,0x00000016,0x00000000,0x00000000 - .word 0x212F69A6,0x0000002E,0x00000000,0x00000000,0x08141408,0x12121428,0x0000002C,0x00000000 - .word 0x00000000,0x083E0808,0x00000008,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x11010100,0x47A1A111,0x00000000,0x00000000,0x013E0000,0x00003E01,0x00000000,0x00000000 - .word 0x00060600,0x04060600,0x00000002,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x1111111F,0x11111111,0x0000001F,0x00000000,0x04000404,0x11111106,0x0000000E,0x00000000 - .word 0x04000404,0x04040404,0x00000004,0x00000000,0x57555507,0x50515131,0x00000050,0x00000000 - .word 0x59494F09,0x49696959,0x00000048,0x00000000,0xBF858587,0x28A9A9A9,0x00000038,0x00000000 - .word 0x39021222,0x280A3A2A,0x00000038,0x00000000,0x95951300,0x95959593,0x00000073,0x00000000 - .word 0x8AAA9100,0xAA8A8A8A,0x00000091,0x00000000,0x02040400,0x04020101,0x00000004,0x00000000 - .word 0x1F00020C,0x04040404,0x0000001F,0x00000000,0x15252700,0x74080817,0x00725254,0x00000000 - .word 0x02020204,0x02020202,0x00000004,0x00000000,0x04040402,0x04040404,0x00000002,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x000A0400,0x1911111E,0x00000016,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00020C00,0x04040404,0x00000004,0x00000000 - - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0xFE7C3810,0x7C7C7CFF,0x00000000,0x00000000 - .word 0x7C7C7C00,0x387CFEFF,0x00000010,0x00000000,0xFEFC1810,0x18FCFEFF,0x00000010,0x00000000 - .word 0xFE7E3010,0x307EFEFE,0x00000010,0x00000000,0x00000000,0x00000001,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x010F0906,0x0000000E,0x00000000,0x00000000,0x04081020,0x10080402,0x00000020,0x00000000 - .word 0x10080402,0x04081020,0x00000002,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - - .word 0x09794B35,0x00000071,0x00000000,0x00000000,0x1111110E,0x11111111,0x0000000E,0x00000000 - .word 0x04040604,0x04040404,0x0000000E,0x00000000,0x1011110E,0x01020408,0x0000001F,0x00000000 - .word 0x1010110E,0x1110100C,0x0000000E,0x00000000,0x090A0A0C,0x081F0909,0x00000008,0x00000000 - .word 0x0101011F,0x1110100F,0x0000000E,0x00000000,0x0101110E,0x1111110F,0x0000000E,0x00000000 - .word 0x1010101F,0x04040808,0x00000004,0x00000000,0x1111110E,0x1111110E,0x0000000E,0x00000000 - .word 0x1111110E,0x1110101E,0x0000000E,0x00000000,0x04040404,0x04000404,0x00000004,0x00000000 - .word 0x1111110E,0x0400040C,0x00000004,0x00000000,0x00000000,0x06000000,0x00000006,0x00000000 - .word 0x00000000,0x0000001F,0x00000000,0x00000000,0x00000000,0x00000606,0x00000000,0x00000000 - - .word 0x00000000,0x00000000,0x00150000,0x00000000,0x1B1B0912,0x00000000,0x00000000,0x00000000 - .word 0x09121B1B,0x00000000,0x00000000,0x00000000,0x03030102,0x00000000,0x00000000,0x00000000 - .word 0x01020303,0x00000000,0x00000000,0x00000000,0x04150E04,0x1111110E,0x0000000E,0x00000000 - .word 0x1111110E,0x041F040E,0x00000004,0x00000000,0x1C24241C,0x3E043E04,0x00000004,0x00000000 - .word 0x00000000,0x04060600,0x00000002,0x00000000,0x42000000,0x24181824,0x00000042,0x00000000 - .word 0x08081000,0x02020404,0x00000001,0x00000000,0x11110E00,0x11111F11,0x00000011,0x00000000 - .word 0x11110F00,0x1111110F,0x0000000F,0x00000000,0x01110E00,0x11010101,0x0000000E,0x00000000 - .word 0x11110F00,0x11111111,0x0000000F,0x00000000,0x01011F00,0x0101010F,0x0000001F,0x00000000 - - .word 0x01011F00,0x0101010F,0x00000001,0x00000000,0x11110E00,0x11111901,0x0000000E,0x00000000 - .word 0x11111100,0x1111111F,0x00000011,0x00000000,0x04041F00,0x04040404,0x0000001F,0x00000000 - .word 0x10101000,0x11101010,0x0000000E,0x00000000,0x05091100,0x09050303,0x00000011,0x00000000 - .word 0x01010100,0x01010101,0x0000001F,0x00000000,0x1B1B1100,0x11111515,0x00000011,0x00000000 - .word 0x13131100,0x11191915,0x00000011,0x00000000,0x11110E00,0x11111111,0x0000000E,0x00000000 - .word 0x11110F00,0x01010F11,0x00000001,0x00000000,0x11110E00,0x19171111,0x0000002E,0x00000000 - .word 0x11110F00,0x11090F11,0x00000011,0x00000000,0x01110E00,0x11100806,0x0000000E,0x00000000 - .word 0x04041F00,0x04040404,0x00000004,0x00000000,0x11111100,0x11111111,0x0000000E,0x00000000 - - .word 0x11111100,0x040A0A11,0x00000004,0x00000000,0x15111100,0x1B1B1515,0x00000011,0x00000000 - .word 0x0A111100,0x110A0404,0x00000011,0x00000000,0x0A111100,0x0404040A,0x00000004,0x00000000 - .word 0x08101F00,0x01020404,0x0000001F,0x00000000,0x00000000,0x1911111E,0x00000016,0x00000000 - .word 0x01010100,0x1111110F,0x0000000F,0x00000000,0x00000000,0x1101110E,0x0000000E,0x00000000 - .word 0x10101000,0x1111111E,0x0000001E,0x00000000,0x00000000,0x011F110E,0x0000001E,0x00000000 - .word 0x02020C00,0x0202020F,0x00000002,0x00000000,0x00000000,0x101E111E,0x0000000E,0x00000000 - .word 0x01010100,0x1111110F,0x00000011,0x00000000,0x00000200,0x02020202,0x00000002,0x00000000 - .word 0x00000800,0x09090808,0x00000006,0x00000000,0x01010100,0x05030509,0x00000009,0x00000000 - - .word 0x04040600,0x04040404,0x00000004,0x00000000,0x00000000,0x1515150F,0x00000015,0x00000000 - .word 0x00000000,0x09090907,0x00000009,0x00000000,0x00000000,0x1111110E,0x0000000E,0x00000000 - .word 0x00000000,0x0F11110F,0x00000001,0x00000000,0x00000000,0x1E11111E,0x00000010,0x00000000 - .word 0x00000000,0x0101030D,0x00000001,0x00000000,0x00000000,0x0806010F,0x0000000F,0x00000000 - .word 0x02020000,0x0202020F,0x0000000E,0x00000000,0x00000000,0x09090909,0x00000016,0x00000000 - .word 0x00000000,0x0A0A1111,0x00000004,0x00000000,0x00000000,0x15151515,0x0000000A,0x00000000 - .word 0x00000000,0x0A040A11,0x00000011,0x00000000,0x00000000,0x101E1111,0x0000000F,0x00000000 - .word 0x00000000,0x0204081F,0x0000001F,0x00000000,0x1E0E0602,0x060E1E3E,0x00000002,0x00000000 - - .word 0x06060000,0x06060000,0x00000000,0x00000000,0x0E000011,0x111F1111,0x00000011,0x00000000 - .word 0x0E000011,0x11111111,0x0000000E,0x00000000,0x11000011,0x11111111,0x0000000E,0x00000000 - .word 0x00001100,0x1911111E,0x00000016,0x00000000,0x00001100,0x1111110E,0x0000000E,0x00000000 - .word 0x00001100,0x09090909,0x00000016,0x00000000,0x3F3F3E3C,0x0603392F,0x0000003C,0x00000000 - .word 0x07070301,0x03060407,0x00000001,0x00000000,0x10020702,0x0E041038,0x00000004,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - .word 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000 - -@}}BLOCK(latin_short) diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 2a8efb7..01fd39f 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -8,7 +8,8 @@ #include "debug_mode.h" #include "button_menu.h" #include "sprite_data.h" -#include "latin_short.h" +//#include "latin_short.h" +#include "latin_normal.h" #include "japanese_small.h" #include "text_data_table.h" #include "background_engine.h" @@ -64,16 +65,17 @@ void init_text_engine() // Load the TTE // tte_init_se(3, BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB) | BG_PRIO(0), 0, CLR_WHITE, 14, &japanese_smallFont, NULL); - tte_init_chr4c(3, // BG 0 - BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB), // Charblock 0; screenblock 31 + tte_init_chr4c(3, // BG 3 + BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB), // Charblock 0; screenblock 10 0xF000, // Screen-entry offset bytes2word( // Color attributes: - 15, // Text color - 0, // Shadow color + 13, // Text color + 15, // Shadow color 0, // Paper 0), // Special CLR_WHITE, // White text - BUILD_FONT, // Custom font + //&latin_shortFont, // Custom font + &latin_normalFont, // Custom font NULL // Use default chr4 renderer ); tte_init_con(); diff --git a/text_helper/main.py b/text_helper/main.py index 03ef573..8d1481f 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -7,6 +7,9 @@ from collections import defaultdict import sys from pathlib import Path import hashlib +import math +from PIL import Image +import numpy as np class Languages(Enum): Japanese = 0 @@ -21,7 +24,7 @@ FIRST_TRANSLATION_COL_INDEX = 8 BASE_DIR = Path(__file__).resolve().parent # read by default 1st sheet of an excel file -dir = os.curdir + "/text_helper" +textDir = os.curdir + "/text_helper" url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' new_file_path = BASE_DIR / 'new_text.xlsx' @@ -463,7 +466,7 @@ def download_xlsx_file(): def transfer_xlsx_to_dict(): print("\tGetting character arrays") - currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Character Arrays", header=None) + currSheet = pd.read_excel(textDir + "/text.xlsx", sheet_name="Character Arrays", header=None) offset = 0 for key, value in charArrays.items(): for r in range(16): @@ -478,7 +481,7 @@ def transfer_xlsx_to_dict(): print("\tGetting string data") - currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") + currSheet = pd.read_excel(textDir + "/text.xlsx", sheet_name="Translations") for row in currSheet.iterrows(): currRow = row[1]["Text Section"] @@ -581,11 +584,169 @@ def output_json_file(): outText += str(byte) mainDict[lang.name][section][item]["text"] = outText - with open(dir + '/output.json', 'w') as jsonFile: + with open(textDir + '/output.json', 'w') as jsonFile: jsonFile.write(json.dumps(mainDict)) + +# This is for the font generation +fontDir = os.curdir +BACKGROUND_PAL_INDEX = 0 +CELL_PAL_INDEX = 1 + +class Font: + def __init__(self, fileName, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight): + self.fileName = fileName + self.numColors = numColors + self.numChars = numChars + self.numCharsX = numCharsX + self.numCharsY = numCharsY + self.cellWidth = cellWidth + self.cellHeight = cellHeight + + self.bpp = int(math.log(numColors, 2)) + 1 + self.numWords = self.numChars * self.cellWidth * self.cellHeight * self.bpp // (8 * 4) + self.numBytes = self.numWords * 4 + + self.charWordTable = [0] * self.numWords + self.charWidthTable = [0] * self.numBytes + +def build_h(myFont): + with open(fontDir + "/include/" + myFont.fileName + ".h", 'w') as f: + f.write(f''' +#ifndef __{myFont.fileName.upper()}__ +#define __{myFont.fileName.upper()}__ + +extern const TFont {myFont.fileName}Font; + +#define {myFont.fileName}GlyphsLen {myFont.numBytes} +extern const unsigned int {myFont.fileName}Glyphs[{myFont.numBytes}]; + +#define {myFont.fileName}WidthsLen {myFont.numChars} +extern const unsigned char {myFont.fileName}Widths[{myFont.numChars}]; + +#endif +''') + f.close() + +def build_c(myFont): + with open(fontDir + "/source/" + myFont.fileName + ".c", 'w') as f: + f.write(f'''typedef struct TFont +{{ + const void *data; //!< Character data. + const unsigned char *widths; //!< Width table for vwf. + const unsigned char *heights; //!< Height table for vhf. + unsigned short charOffset; //!< Character offset. + unsigned short charnumChars; //!< Character numChars. + unsigned char charW; //!< Character width (fwf). + unsigned char charH; //!< Character height. + unsigned char cellW; //!< Glyph cell width. + unsigned char cellH; //!< Glyph cell height. + unsigned short cellSize; //!< Cell-size (bytes). + unsigned char bpp; //!< Font bitdepth; + unsigned char extra; //!< Padding. Free to use. +}} TFont;\n +''') + + f.write(f'const unsigned int {myFont.fileName}Glyphs[{myFont.numWords}] __attribute__((aligned(4)))=\n{{\n\t') + + for i in range(myFont.numWords): + f.write(f'{myFont.charWordTable[i]:#010x},') + if (i == myFont.numWords - 1): + f.write("\n};\n\n") + elif (i % 64 == 63): + f.write("\n\n\t") + elif (i % 8 == 7): + f.write("\n\t") + + f.write(f'const unsigned char {myFont.fileName}Widths[{myFont.numChars}] __attribute__((aligned(4)))=\n{{\n\t') + + for i in range(myFont.numChars): + f.write(f'{myFont.charWidthTable[i]:#04x}, ') + if (i == myFont.numChars - 1): + f.write("\n};\n\n") + elif (i % 128 == 127): + f.write("\n\n\t") + elif (i % 16 == 15): + f.write("\n\t") + + f.write(f'''const TFont {myFont.fileName}Font= +{{ + {myFont.fileName}Glyphs, + {myFont.fileName}Widths, + 0, // All heights are the same + 0, // Character offset, is set to zero + {myFont.numChars}, + {myFont.cellWidth}, {myFont.cellHeight}, + {myFont.cellWidth}, {myFont.cellHeight}, + {myFont.numBytes // myFont.numChars}, + {myFont.bpp}, + 0, // Padding, left blank +}}; + ''') + + f.close() + +def generate_tables(myFont): + img = Image.open(f'{fontDir}/graphics/{myFont.fileName}.png') + if (img.mode != "P"): + print("Error: Image file does not contain a palette") + exit() + pixels = img.load() + + bitsPerWord = 32 + pixelsPerTileX = 8 + pixelsPerTileY = 8 + tilesPerCharX = myFont.cellWidth // pixelsPerTileX + tilesPerCharY = myFont.cellHeight // pixelsPerTileY + charsPerChartX = myFont.numCharsX + charsPerChartY = myFont.numCharsY + + globalX = 0 + globalY = 0 + bitTotal = 0 + + for charY in range(charsPerChartY): + for charX in range(charsPerChartX): + for tileY in range(tilesPerCharY): + for tileX in range(tilesPerCharX): + for pixelY in range(pixelsPerTileY): + for pixelX in range(pixelsPerTileX): + + arrayIndex = bitTotal // bitsPerWord + bitIndex = bitTotal % bitsPerWord + + globalX = pixelX + (tileX * pixelsPerTileX) + (charX * tilesPerCharX * pixelsPerTileX) + globalY = pixelY + (tileY * pixelsPerTileY) + (charY * tilesPerCharY * pixelsPerTileY) + + val = (pixels[globalX, globalY] - 2) & ((1 << myFont.bpp) - 1) + myFont.charWordTable[arrayIndex] |= val << bitIndex + + #print(f'globalX: {globalX}, globalY: {globalY}, arrayIndex:{arrayIndex}, bitIndex:{bitIndex}, val:{val}') + bitTotal += myFont.bpp + + #print(f'{charX, charY}') + + myFont.charWidthTable[(charY * charsPerChartX) + charX] = (tilesPerCharX * pixelsPerTileX) + for x in range(tilesPerCharX * pixelsPerTileX): + globalX = x + (charX * tilesPerCharX * pixelsPerTileX) + globalY = 0 + (charY * tilesPerCharY * pixelsPerTileY) + #print(f'x: {globalX}, y: {globalY}') + if (pixels[globalX, globalY] == BACKGROUND_PAL_INDEX): + myFont.charWidthTable[(charY * charsPerChartX) + charX] = x + break + +def generate_font(): + latin_normal = Font("latin_normal", 1, 256, 16, 16, 16, 16) + generate_tables(latin_normal) + build_h(latin_normal) + build_c(latin_normal) + + + # Main print("Running text_helper:") +print("\tGenerating font") +generate_font() download_xlsx_file() transfer_xlsx_to_dict() generate_header_file() From 26b89d17fcc06f87324254d89cdd55ecb03f8267 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 27 Jan 2026 09:29:28 -0500 Subject: [PATCH 19/68] Updating font generation --- Makefile | 2 +- building_on_windows.txt | 2 +- font_helper/main.py | 160 --------------- graphics/latin_normal.png | Bin 3077 -> 0 bytes graphics/unused graphics/japanese_normal.png | Bin 9919 -> 0 bytes include/debug_mode.h | 2 +- include/japanese_normal.h | 19 ++ include/japanese_small.h | 21 -- include/latin_normal.h | 15 +- source/japanese_normal.c | 198 ++++++++++++++++++ source/text_engine.cpp | 6 +- text_helper/fonts/japanese_normal.png | Bin 0 -> 2932 bytes text_helper/fonts/latin_normal.png | Bin 0 -> 3080 bytes text_helper/main.py | 203 +++++++++---------- 14 files changed, 323 insertions(+), 305 deletions(-) delete mode 100644 font_helper/main.py delete mode 100644 graphics/latin_normal.png delete mode 100644 graphics/unused graphics/japanese_normal.png create mode 100644 include/japanese_normal.h delete mode 100644 include/japanese_small.h create mode 100644 source/japanese_normal.c create mode 100644 text_helper/fonts/japanese_normal.png create mode 100644 text_helper/fonts/latin_normal.png diff --git a/Makefile b/Makefile index efce3bb..8cf1eca 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +166,7 @@ generate_data: @echo "----------------------------------------------------------------" @echo @tools/payload-generator/payload-generator to_compress - @python3 text_helper/main.py + @python text_helper/main.py @echo "Compressing bin files!" @echo -n "[" @find to_compress -name "*.bin" -print0 | xargs -0 -n1 ./compress_lz10.sh diff --git a/building_on_windows.txt b/building_on_windows.txt index c359334..6d5a432 100644 --- a/building_on_windows.txt +++ b/building_on_windows.txt @@ -19,4 +19,4 @@ break-system-packages = true pacman -S openssl-devel export CMAKE_TLS_VERIFY=0 -pip install pandas requests openpyxl \ No newline at end of file +pip install pandas requests openpyxl pypng \ No newline at end of file diff --git a/font_helper/main.py b/font_helper/main.py deleted file mode 100644 index 8265056..0000000 --- a/font_helper/main.py +++ /dev/null @@ -1,160 +0,0 @@ -import os -import math -from PIL import Image -import numpy as np - -# THIS PROGRAM ASSUMES THAT THE FIRST TWO COLORS IN THE PALETTE ARE THE BACKGROUND AND CELL BACKGROUND! - -dir = os.curdir -BACKGROUND_PAL_INDEX = 0 -CELL_PAL_INDEX = 1 - -class Font: - def __init__(self, fileName, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight): - self.fileName = fileName - self.numColors = numColors - self.numChars = numChars - self.numCharsX = numCharsX - self.numCharsY = numCharsY - self.cellWidth = cellWidth - self.cellHeight = cellHeight - - self.bpp = int(math.log(numColors, 2)) + 1 - self.numWords = self.numChars * self.cellWidth * self.cellHeight * self.bpp // (8 * 4) - self.numBytes = self.numWords * 4 - - self.charWordTable = [0] * self.numWords - self.charWidthTable = [0] * self.numBytes - - -def build_h(myFont): - with open(dir + "/include/" + myFont.fileName + ".h", 'w') as f: - f.write(f''' -#ifndef __{myFont.fileName.upper()}__ -#define __{myFont.fileName.upper()}__ - -extern const TFont {myFont.fileName}Font; - -#define {myFont.fileName}GlyphsLen {myFont.numBytes} -extern const unsigned int {myFont.fileName}Glyphs[{myFont.numBytes}]; - -#define {myFont.fileName}WidthsLen {myFont.numChars} -extern const unsigned char {myFont.fileName}Widths[{myFont.numChars}]; - -#endif -''') - f.close() - -def build_c(myFont): - with open(dir + "/source/" + myFont.fileName + ".c", 'w') as f: - f.write(f'''typedef struct TFont -{{ - const void *data; //!< Character data. - const unsigned char *widths; //!< Width table for vwf. - const unsigned char *heights; //!< Height table for vhf. - unsigned short charOffset; //!< Character offset. - unsigned short charnumChars; //!< Character numChars. - unsigned char charW; //!< Character width (fwf). - unsigned char charH; //!< Character height. - unsigned char cellW; //!< Glyph cell width. - unsigned char cellH; //!< Glyph cell height. - unsigned short cellSize; //!< Cell-size (bytes). - unsigned char bpp; //!< Font bitdepth; - unsigned char extra; //!< Padding. Free to use. -}} TFont;\n -''') - - f.write(f'const unsigned int {myFont.fileName}Glyphs[{myFont.numWords}] __attribute__((aligned(4)))=\n{{\n\t') - - for i in range(myFont.numWords): - f.write(f'{myFont.charWordTable[i]:#010x},') - if (i == myFont.numWords - 1): - f.write("\n};\n\n") - elif (i % 64 == 63): - f.write("\n\n\t") - elif (i % 8 == 7): - f.write("\n\t") - - f.write(f'const unsigned char {myFont.fileName}Widths[{myFont.numChars}] __attribute__((aligned(4)))=\n{{\n\t') - - for i in range(myFont.numChars): - f.write(f'{myFont.charWidthTable[i]:#04x}, ') - if (i == myFont.numChars - 1): - f.write("\n};\n\n") - elif (i % 128 == 127): - f.write("\n\n\t") - elif (i % 16 == 15): - f.write("\n\t") - - f.write(f'''const TFont {myFont.fileName}Font= -{{ - {myFont.fileName}Glyphs, - {myFont.fileName}Widths, - 0, // All heights are the same - 0, // Character offset, is set to zero - {myFont.numChars}, - {myFont.cellWidth}, {myFont.cellHeight}, - {myFont.cellWidth}, {myFont.cellHeight}, - {myFont.numBytes // myFont.numChars}, - {myFont.bpp}, - 0, // Padding, left blank -}}; - ''') - - f.close() - -def generate_tables(myFont): - img = Image.open(f'{dir}/graphics/{myFont.fileName}.png') - if (img.mode != "P"): - print("Error: Image file does not contain a palette") - exit() - pixels = img.load() - - bitsPerWord = 32 - pixelsPerTileX = 8 - pixelsPerTileY = 8 - tilesPerCharX = myFont.cellWidth // pixelsPerTileX - tilesPerCharY = myFont.cellHeight // pixelsPerTileY - charsPerChartX = myFont.numCharsX - charsPerChartY = myFont.numCharsY - - globalX = 0 - globalY = 0 - bitTotal = 0 - - for charY in range(charsPerChartY): - for charX in range(charsPerChartX): - for tileY in range(tilesPerCharY): - for tileX in range(tilesPerCharX): - for pixelY in range(pixelsPerTileY): - for pixelX in range(pixelsPerTileX): - - arrayIndex = bitTotal // bitsPerWord - bitIndex = bitTotal % bitsPerWord - - globalX = pixelX + (tileX * pixelsPerTileX) + (charX * tilesPerCharX * pixelsPerTileX) - globalY = pixelY + (tileY * pixelsPerTileY) + (charY * tilesPerCharY * pixelsPerTileY) - - val = (pixels[globalX, globalY] - 2) & ((1 << myFont.bpp) - 1) - myFont.charWordTable[arrayIndex] |= val << bitIndex - - #print(f'globalX: {globalX}, globalY: {globalY}, arrayIndex:{arrayIndex}, bitIndex:{bitIndex}, val:{val}') - bitTotal += myFont.bpp - - #print(f'{charX, charY}') - - myFont.charWidthTable[(charY * charsPerChartX) + charX] = (tilesPerCharX * pixelsPerTileX) - for x in range(tilesPerCharX * pixelsPerTileX): - globalX = x + (charX * tilesPerCharX * pixelsPerTileX) - globalY = 0 + (charY * tilesPerCharY * pixelsPerTileY) - #print(f'x: {globalX}, y: {globalY}') - if (pixels[globalX, globalY] == BACKGROUND_PAL_INDEX): - myFont.charWidthTable[(charY * charsPerChartX) + charX] = x - break - -# Main: -latin_normal = Font("latin_normal", 1, 256, 16, 16, 16, 16) -generate_tables(latin_normal) -build_h(latin_normal) -build_c(latin_normal) - diff --git a/graphics/latin_normal.png b/graphics/latin_normal.png deleted file mode 100644 index e3f323b60e9cc91492d554866bb17aaee0fb6eee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3077 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 GiTy7;R7W@f diff --git a/include/debug_mode.h b/include/debug_mode.h index eedf682..045cb88 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -2,7 +2,7 @@ #define DEBUG_MODE_H #define VERSION "v1.2.1" -#define PTGB_BUILD_LANGUAGE 2 +#define PTGB_BUILD_LANGUAGE 1 #define DEBUG_MODE true diff --git a/include/japanese_normal.h b/include/japanese_normal.h new file mode 100644 index 0000000..f621375 --- /dev/null +++ b/include/japanese_normal.h @@ -0,0 +1,19 @@ +#include "debug_mode.h" +#include "pokemon_data.h" + +#if PTGB_BUILD_LANGUAGE == 1 + +#ifndef __JAPANESE_NORMAL__ +#define __JAPANESE_NORMAL__ + +extern const TFont japanese_normalFont; + +#define japanese_normalGlyphsLen 4096 +extern const unsigned int japanese_normalGlyphs[4096]; + +#define japanese_normalWidthsLen 256 +extern const unsigned char japanese_normalWidths[256]; + +#endif + +#endif diff --git a/include/japanese_small.h b/include/japanese_small.h deleted file mode 100644 index 5529479..0000000 --- a/include/japanese_small.h +++ /dev/null @@ -1,21 +0,0 @@ -#include "debug_mode.h" -#include "pokemon_data.h" - -#if PTGB_BUILD_LANGUAGE == JPN_ID - -//{{BLOCK(japanese_small) - -#ifndef __JAPANESE_SMALL__ -#define __JAPANESE_SMALL__ - -extern const TFont japanese_smallFont; - -#define japanese_smallGlyphsLen 4096 -extern const unsigned int japanese_smallGlyphs[1024]; -#define BUILD_FONT &japanese_smallFont - -#endif // __JAPANESE_SMALL__ - -//}}BLOCK(japanese_small) - -#endif \ No newline at end of file diff --git a/include/latin_normal.h b/include/latin_normal.h index c6bdd7a..a9601b4 100644 --- a/include/latin_normal.h +++ b/include/latin_normal.h @@ -1,14 +1,19 @@ +#include "debug_mode.h" +#include "pokemon_data.h" -//{{BLOCK(latin_normal) +#if PTGB_BUILD_LANGUAGE != 1 #ifndef __LATIN_NORMAL__ #define __LATIN_NORMAL__ extern const TFont latin_normalFont; -#define latin_normalGlyphsLen 4096 -extern const unsigned int latin_normalGlyphs[1024]; +#define latin_normalGlyphsLen 8192 +extern const unsigned int latin_normalGlyphs[8192]; -#endif // __LATIN_NORMAL__ +#define latin_normalWidthsLen 256 +extern const unsigned char latin_normalWidths[256]; -//}}BLOCK(latin_normal) +#endif + +#endif diff --git a/source/japanese_normal.c b/source/japanese_normal.c new file mode 100644 index 0000000..4c7bb2e --- /dev/null +++ b/source/japanese_normal.c @@ -0,0 +1,198 @@ +typedef struct TFont +{ + const void *data; //!< Character data. + const unsigned char *widths; //!< Width table for vwf. + const unsigned char *heights; //!< Height table for vhf. + unsigned short charOffset; //!< Character offset. + unsigned short charnumChars; //!< Character numChars. + unsigned char charW; //!< Character width (fwf). + unsigned char charH; //!< Character height. + unsigned char cellW; //!< Glyph cell width. + unsigned char cellH; //!< Glyph cell height. + unsigned short cellSize; //!< Cell-size (bytes). + unsigned char bpp; //!< Font bitdepth; + unsigned char extra; //!< Padding. Free to use. +} TFont; + +const unsigned int japanese_normalGlyphs[1024] __attribute__((aligned(4)))= +{ + 0x00000000,0x00000000,0x00000000,0x00000000,0x04000000,0x3e24043f,0x264d5565,0x00000000, + 0x00000000,0x41412101,0x06094141,0x00000000,0x1c000000,0x40413e00,0x1c204040,0x00000000, + 0x0e000000,0x10103f00,0x61120c08,0x00000000,0x24000000,0x3e04445f,0x36454545,0x00000000, + 0x22000000,0x22225f42,0x18212122,0x00000000,0x08000000,0x207e103e,0x3c020240,0x00000000, + 0x20000000,0x02040810,0x20100804,0x00000000,0x20000000,0x21217d21,0x12212121,0x00000000, + 0x00000000,0x0000201e,0x3c020000,0x00000000,0x10000000,0x20207e10,0x3c020240,0x00000000, + 0x02000000,0x02020202,0x38440202,0x00000000,0x20000000,0x2438207f,0x18203824,0x00000000, + 0x22000000,0x22227f22,0x3c020222,0x00000000,0x3e000000,0x087f0810,0x38040404,0x00000000, + + 0x02000000,0x017a023f,0x79050101,0x00000000,0x02000000,0x3a02023f,0x1c204046,0x00000000, + 0x00000000,0x40201f00,0x1c204040,0x00000000,0x3f000000,0x02040810,0x38040202,0x00000000, + 0x02000000,0x020e3202,0x3e010101,0x00000000,0x02000000,0x2102422f,0x18246439,0x00000000, + 0x00000000,0x01014139,0x72090101,0x00000000,0x12000000,0x4d563e12,0x32555569,0x00000000, + 0x02000000,0x42463a07,0x324a4a73,0x00000000,0x00000000,0x49494a3c,0x26494949,0x00000000, + 0x21000000,0x2121217d,0x19256539,0x00000000,0x07000000,0x21611202,0x0c122121,0x00000000, + 0x1e000000,0x08040808,0x0c515252,0x00000000,0x00000000,0x11120a04,0x40402021,0x00000000, + 0x7d000000,0x21217d21,0x19256539,0x00000000,0x08000000,0x083f083f,0x0629190e,0x00000000, + + 0x0e000000,0x3e282808,0x10262969,0x00000000,0x04000000,0x4624041f,0x38464505,0x00000000, + 0x12000000,0x55563e12,0x264d4d55,0x00000000,0x04000000,0x3e04043e,0x38440404,0x00000000, + 0x12000000,0x42573a12,0x04042442,0x00000000,0x10000000,0x5151533d,0x08103c51,0x00000000, + 0x10000000,0x10107010,0x0e51311e,0x00000000,0x18000000,0x463a0222,0x1c204040,0x00000000, + 0x42000000,0x44424242,0x18204040,0x00000000,0x3e000000,0x221c0810,0x3c524c41,0x00000000, + 0x02000000,0x22261a07,0x42222223,0x00000000,0x3e000000,0x221c0810,0x1c204041,0x00000000, + 0x02000000,0x42463a07,0x32424243,0x00000000,0x08000000,0x196e043f,0x7c020214,0x00000000, + 0x04000000,0x06020204,0x3149494a,0x00000000,0x00000000,0x083e0800,0x2c5a6a3c,0x00000000, + + 0x00000000,0x21110000,0x06012121,0x00000000,0x00000000,0x1c001c00,0x0c102022,0x00000000, + 0x00000000,0x1f000e00,0x310a0408,0x00000000,0x00000000,0x445f2400,0x1625251e,0x00000000, + 0x00000000,0x2a1f1200,0x0212222a,0x00000000,0x00000000,0x2b1d0800,0x04182929,0x00000000, + 0x00000000,0x08380800,0x0629190e,0x00000000,0x52500000,0x22225f22,0x18212122,0x00000000, + 0x54500000,0x107f083f,0x1e010120,0x00000000,0x20000000,0x52540810,0x20100804,0x00000000, + 0x50500000,0x21217d21,0x12212121,0x00000000,0x50500000,0x0000300e,0x3c020000,0x00000000, + 0x58500000,0x10103f08,0x1e010120,0x00000000,0x52000000,0x02020252,0x38440202,0x00000000, + 0x50500000,0x2438207f,0x18203824,0x00000000,0x52500000,0x22227f22,0x3c020222,0x00000000, + + 0x5e500000,0x087f0810,0x38040404,0x00000000,0x52500000,0x017a023f,0x79050101,0x00000000, + 0x52500000,0x3a02023f,0x1c204046,0x00000000,0x50500000,0x40201f00,0x1c204040,0x00000000, + 0x3f000000,0x52540810,0x38040202,0x00000000,0x52500000,0x020e3202,0x3e010101,0x00000000, + 0x51500000,0x21217d21,0x19256539,0x00000000,0x57500000,0x21611202,0x0c122121,0x00000000, + 0x5e500000,0x08040808,0x0c515252,0x00000000,0x50000000,0x11120a54,0x40402021,0x00000000, + 0x51500000,0x217d217d,0x19256539,0x00000000,0x51200000,0x21217d21,0x19256539,0x00000000, + 0xa7400000,0x21611242,0x0c122121,0x00000000,0xae400000,0x08040850,0x0c515252,0x00000000, + 0x40000000,0x11124aa4,0x40402021,0x00000000,0xa1400000,0x217d215d,0x19256539,0x00000000, + + 0x00000000,0x1e000000,0x0c102020,0x00000000,0x00000000,0x4840407f,0x04080828,0x00000000, + 0x20000000,0x17181020,0x10101010,0x00000000,0x08000000,0x41417f08,0x18204040,0x00000000, + 0x00000000,0x0808083e,0x7f080808,0x00000000,0x20000000,0x30307f20,0x20232428,0x00000000, + 0x04000000,0x44447f04,0x30424444,0x00000000,0x08000000,0x08087f08,0x0808087f,0x00000000, + 0x02000000,0x2222223e,0x0c102020,0x00000000,0x02000000,0x21227e02,0x0c102020,0x00000000, + 0x00000000,0x2020203e,0x3e202020,0x00000000,0x22000000,0x22227f22,0x0c102020,0x00000000, + 0x00000000,0x20272007,0x0f102020,0x00000000,0x00000000,0x1020203e,0x43442810,0x00000000, + 0x02000000,0x22223f02,0x3c020212,0x00000000,0x00000000,0x40444242,0x0c102020,0x00000000, + + 0x02000000,0x7946427e,0x1c204040,0x00000000,0x20000000,0x7f10101e,0x0c101010,0x00000000, + 0x00000000,0x40494949,0x1c204040,0x00000000,0x3e000000,0x107f0000,0x04081010,0x00000000, + 0x02000000,0x0e020202,0x02020232,0x00000000,0x10000000,0x10107f10,0x06081010,0x00000000, + 0x00000000,0x0000003e,0x007f0000,0x00000000,0x00000000,0x2620203f,0x47281018,0x00000000, + 0x08000000,0x1820407f,0x0808482f,0x00000000,0x00000000,0x20202020,0x030c1010,0x00000000, + 0x00000000,0x22222202,0x41414242,0x00000000,0x01000000,0x013f0101,0x3e010101,0x00000000, + 0x00000000,0x2020203f,0x06081010,0x00000000,0x00000000,0x11120a04,0x40402021,0x00000000, + 0x08000000,0x2a087f08,0x0849492a,0x00000000,0x00000000,0x2240407f,0x20100814,0x00000000, + + 0x0e000000,0x300e0030,0x38060000,0x00000000,0x08000000,0x24040408,0x7f414222,0x00000000, + 0x20000000,0x30282620,0x07485020,0x00000000,0x00000000,0x3f04043f,0x78040404,0x00000000, + 0x04000000,0x44447f04,0x04040424,0x00000000,0x00000000,0x1010101e,0x7f101010,0x00000000, + 0x00000000,0x3e20203f,0x3f202020,0x00000000,0x1e000000,0x203f0000,0x0c102020,0x00000000, + 0x22000000,0x22222222,0x18202020,0x00000000,0x0a000000,0x0a0a0a0a,0x19294a4a,0x00000000, + 0x02000000,0x02020202,0x0e122202,0x00000000,0x00000000,0x2121213f,0x3f212121,0x00000000, + 0x00000000,0x4041417f,0x18204040,0x00000000,0x00000000,0x3e20203f,0x0e102020,0x00000000, + 0x00000000,0x40404403,0x0f102040,0x00000000,0x00000000,0x203f0000,0x02040414,0x00000000, + + 0x00000000,0x20200000,0x10101618,0x00000000,0x00000000,0x3e080000,0x0c102022,0x00000000, + 0x00000000,0x3e000000,0x3e080808,0x00000000,0x00000000,0x3e100000,0x18121418,0x00000000, + 0x00000000,0x3f040000,0x04041424,0x00000000,0x00000000,0x1c000000,0x3e101010,0x00000000, + 0x00000000,0x3e000000,0x3e203c20,0x00000000,0x54500000,0x44447f04,0x30424444,0x00000000, + 0x58500000,0x08087f08,0x0808087f,0x00000000,0x52500000,0x2222223e,0x0c102020,0x00000000, + 0x52500000,0x21227e02,0x0c102020,0x00000000,0x50500000,0x2020203e,0x3e202020,0x00000000, + 0x52500000,0x22227f22,0x0c102020,0x00000000,0x50500000,0x20270007,0x0f102020,0x00000000, + 0x50500000,0x1020203e,0x43442810,0x00000000,0x2a280000,0x22223f02,0x3c020212,0x00000000, + + 0x50500000,0x20222101,0x06081010,0x00000000,0x52500000,0x7946423e,0x1c204040,0x00000000, + 0x28280000,0x7f10101e,0x0c101010,0x00000000,0x50500000,0x40494909,0x1c204040,0x00000000, + 0x5e500000,0x107f0000,0x04081010,0x00000000,0x52500000,0x0e020202,0x02020232,0x00000000, + 0x50500000,0x22222200,0x41414242,0x00000000,0x51500000,0x013f0101,0x3e010101,0x00000000, + 0x50500000,0x2020203f,0x06081010,0x00000000,0x50000000,0x11120a54,0x40402021,0x00000000, + 0x58500000,0x2a087f08,0x0849492a,0x00000000,0xa0400000,0x22222240,0x41414242,0x00000000, + 0xa1400000,0x013f0141,0x3e010101,0x00000000,0xa0400000,0x2020205f,0x06081010,0x00000000, + 0xa0400000,0x11120a44,0x40402021,0x00000000,0xa8400000,0x2a083f48,0x0849492a,0x00000000, + + 0x00000000,0x0a000000,0x1820202a,0x00000000,0x1c000000,0x22222222,0x1c222222,0x00000000, + 0x08000000,0x0808080c,0x1c080808,0x00000000,0x1c000000,0x10202222,0x3e020408,0x00000000, + 0x1c000000,0x18202222,0x1c222220,0x00000000,0x18000000,0x12121414,0x10103e12,0x00000000, + 0x3e000000,0x1e020202,0x1c222020,0x00000000,0x1c000000,0x1e020222,0x1c222222,0x00000000, + 0x3e000000,0x10202020,0x08080810,0x00000000,0x1c000000,0x1c222222,0x1c222222,0x00000000, + 0x1c000000,0x3c222222,0x1c222020,0x00000000,0x18000000,0x183c3c3c,0x18000018,0x00000000, + 0x3c000000,0x18306666,0x18000018,0x00000000,0x00000000,0x00000000,0x06090906,0x00000000, + 0x00000000,0x3e000000,0x00000000,0x00000000,0x00000000,0x0c000000,0x0000000c,0x00000000, + + 0x00000000,0x22000000,0x00000000,0x00000000,0x427e0000,0x0e0a0a7a,0x00000000,0x00000000, + 0x00000000,0x38000000,0x212f2828,0x0000003f,0x023e0000,0x00020202,0x00000000,0x00000000, + 0x00000000,0x00000000,0x10101010,0x0000001f,0x08000000,0x1c082a1c,0x1c222222,0x00000000, + 0x1c000000,0x1c222222,0x08083e08,0x00000000,0x00000000,0x49497f00,0x6141417f,0x00000000, + 0x00000000,0x00000000,0x06060000,0x00000000,0x00000000,0x22000000,0x22140814,0x00000000, + 0x20200000,0x08081010,0x02020404,0x00000000,0x1c000000,0x22222222,0x2222223e,0x00000000, + 0x1e000000,0x1e222222,0x1e222222,0x00000000,0x1c000000,0x02022222,0x1c222202,0x00000000, + 0x1e000000,0x22222222,0x1e222222,0x00000000,0x3e000000,0x3e020202,0x3e020202,0x00000000, + + 0x3e000000,0x1e020202,0x02020202,0x00000000,0x1c000000,0x02022222,0x1c222232,0x00000000, + 0x22000000,0x3e222222,0x22222222,0x00000000,0x1c000000,0x08080808,0x1c080808,0x00000000, + 0x20000000,0x20202020,0x1c222222,0x00000000,0x22000000,0x060a1222,0x2222120a,0x00000000, + 0x02000000,0x02020202,0x3e020202,0x00000000,0x41000000,0x55636341,0x41494955,0x00000000, + 0x22000000,0x2a262622,0x2232322a,0x00000000,0x1c000000,0x22222222,0x1c222222,0x00000000, + 0x1e000000,0x1e222222,0x02020202,0x00000000,0x1c000000,0x22222222,0x5c263a22,0x00000000, + 0x1e000000,0x1e222222,0x22222212,0x00000000,0x1c000000,0x1c022222,0x1c222220,0x00000000, + 0x3e000000,0x08080808,0x08080808,0x00000000,0x22000000,0x22222222,0x1c222222,0x00000000, + + 0x22000000,0x14222222,0x08081414,0x00000000,0x41000000,0x55554949,0x41416363,0x00000000, + 0x22000000,0x08141422,0x22221414,0x00000000,0x22000000,0x14142222,0x08080808,0x00000000, + 0x3e000000,0x08101020,0x3e020404,0x00000000,0x00000000,0x20201c00,0x3c22223c,0x00000000, + 0x00000000,0x02020202,0x1e22221e,0x00000000,0x00000000,0x02221c00,0x1c220202,0x00000000, + 0x00000000,0x20202020,0x3c22223c,0x00000000,0x00000000,0x221c0000,0x1c22023e,0x00000000, + 0x00000000,0x04040418,0x0404041e,0x00000000,0x00000000,0x22223c00,0x1c22203c,0x00000000, + 0x00000000,0x1e020202,0x22222222,0x00000000,0x00000000,0x00080800,0x08080808,0x00000000, + 0x00000000,0x10001010,0x0c121210,0x00000000,0x02000000,0x0a122202,0x22120a06,0x00000000, + + 0x00000000,0x0808080c,0x08080808,0x00000000,0x00000000,0x49493f00,0x49494949,0x00000000, + 0x00000000,0x22221e00,0x22222222,0x00000000,0x00000000,0x22221c00,0x1c222222,0x00000000, + 0x00000000,0x22221e00,0x0202021e,0x00000000,0x00000000,0x22223c00,0x2020203c,0x00000000, + 0x00000000,0x0c340400,0x04040404,0x00000000,0x00000000,0x02221c00,0x1c22201c,0x00000000, + 0x00000000,0x041e0404,0x18040404,0x00000000,0x00000000,0x22222200,0x3c222222,0x00000000, + 0x00000000,0x22222200,0x08081414,0x00000000,0x00000000,0x49414100,0x22365549,0x00000000, + 0x00000000,0x14224100,0x41221408,0x00000000,0x00000000,0x22222200,0x1c20203c,0x00000000, + 0x00000000,0x20407e00,0x7e040810,0x00000000,0x06000000,0x3e3e1e0e,0x00060e1e,0x00000000, + + 0x00000000,0x00000c0c,0x000c0c00,0x00000000,0x00120000,0x2222221c,0x2222223e,0x00000000, + 0x00120000,0x2222221c,0x1c222222,0x00000000,0x00120000,0x22222222,0x1c222222,0x00000000, + 0x00120000,0x20201c00,0x3c22223c,0x00000000,0x00120000,0x22221c00,0x1c222222,0x00000000, + 0x00120000,0x22222200,0x3c222222,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, +}; + +const unsigned char japanese_normalWidths[256] __attribute__((aligned(4)))= +{ + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, +}; + +const TFont japanese_normalFont= +{ + japanese_normalGlyphs, + japanese_normalWidths, + 0, // All heights are the same + 0, // Character offset, is set to zero + 256, + 8, 16, + 8, 16, + 16, + 1, + 0, // Padding, left blank +}; + \ No newline at end of file diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 01fd39f..088db00 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -8,9 +8,8 @@ #include "debug_mode.h" #include "button_menu.h" #include "sprite_data.h" -//#include "latin_short.h" #include "latin_normal.h" -#include "japanese_small.h" +#include "japanese_normal.h" #include "text_data_table.h" #include "background_engine.h" @@ -74,8 +73,7 @@ void init_text_engine() 0, // Paper 0), // Special CLR_WHITE, // White text - //&latin_shortFont, // Custom font - &latin_normalFont, // Custom font + &japanese_normalFont, // Custom font NULL // Use default chr4 renderer ); tte_init_con(); diff --git a/text_helper/fonts/japanese_normal.png b/text_helper/fonts/japanese_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..68747f397f8c4812272b3b44e3c0680ad4336f03 GIT binary patch literal 2932 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 J_x>y~?!P}LRZ;)| literal 0 HcmV?d00001 diff --git a/text_helper/fonts/latin_normal.png b/text_helper/fonts/latin_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..bbb026197e8e7766f4e43299248db75d646d2d4b GIT binary patch literal 3080 zcmbtW`9IYA9{zl1hB1Sg8e3s(r%X~3M+{= zBcTGK>6q<~fv|}C{&t6mWm@$f0dCl!Mr*~k7rc2@aKVY1>hzx54Y^ABR6*pEUzu?F zO4ynf&AEVj3Ujk0X6QmSv?!|f4({u1J?)s%DGces)5nFWr8vc~9qBzfBbRk**d=AK z#Vsw^2*Up{zZ8JH*n#pGm*0)0o(S{}Qf_+%oVQL*>X%E+dL2mi>YdBzG@$+QfFVH3 zhYs6YT%$~}0tQQCq$z-cH=F$h_iVGGwqW(>U=Z-ZFwkugmBivP@ItRRP@yQI+}bb+4R$DFsLs*jJR1i?U64MxG$3U2FSq!}Az ztzr{dg?~DGr%=foD$>cejxd$y*h>{2=VE9#GMusGpuI(0*0sGAU=cwp4FtnZWq;rX zb^tfQghb&iU+Ei`+TsIh9qwc`m$eT-{+d9VHY5z1zXro6&mrtfOZ7-iN}WW1J=RP^ z00z{}fmOk^+bQbe=(|QQZ6^a;c>1B)OPn!o%-C{hbxGL2Hf%4w+L)7!`s}4aHC)r( zsl~(zf&-!;kV*hJR-x;Q$q+FxN?6hMGVmMe^cAFQ=fKnxL;}~pp;r(d=ssW2?ebj$ z!m(7NP?N7hVatt<2H|q;Q67N`0T>3vS&>$F-wW*JTJC?fB-{xQ>Z;m@Wf4Ea%p8zhv5s{3KJ&3j#Yl-3G)H9KtbwbtJ@4=+<=2wp%`|}v$Nb?e0 z0dxp9C+~^VkHGrELee}00F1^1+;j;b-?m-|)yDWCIayV^m(G9fw5x-8K^<#^j_|l> z(_}hRiuNqP5X5u<4|7kEd+#PdX7CPsF%WQM)%_a$;GC7^o&3ZHJa733jgSqUvouMQEnm@8=yjckB)>V%HaekIr=C`JiRto9jid~+@?%yYN`ZW;mgT?y$Hmlt4- z0C&CYd0i%<8#5~L=S4)MAZwLh+)|9pY>Uku=TD3!Xw$6jt@=(ThTp#?0m#D7Kgs z8JzhUuR=KM9=wlSY4G*h*6XzBx)O-f4=vO>H;z-w)@tU4ZoP?FQ3o3{^@Q&~e#FNE z?*acBV}z6Ti81uKM0Yp0nSx6<_!+lgspIcIw?I;$XbQ0qD+O=1X#Y;Box6N$Of2z3 z>F=cQsMVSAQQn-`jfKFY2B`w{((~!#`9Um<(Og#5PlcNVAPqt-zXKoqf}a2fuXyEw zQ;5qHR3sJEe*B%#5Eiqn1p6ZD`lPc^)AwL)>#~Gi*7KJc1rhsc+b@QOivfR?s&i8&*|r@Z1i6b+Yt^5~Z`MBmFf=!-i`|>lrn(&Tqnes)pRVnKmj~M&(K(FU zkp{#_DN^vk>1RcE-D-<_OYia(K!Q$Xte>l&Z@lsla#7l!d-9TEYZv(_L`YA9a;ETy zNIfr4W_q8xWL^~`_7Yg5mP!ov6P}wj3`QTO@p|5_q4M^}nF(j4teD?jh*_2eXp$R@ zF4Gm?=mX)VW7hZinAyv_O@$Y2>U)6j*u|$ zvPu}eC>Igb(oyxg_cD z40kAu>Iy}vtlT<$c0JrAC~PEo(Pcdkn*+DaO2dGv_jtXRvEJ%Fk3M^puB8;X?D0{m$85Lv>X! zg}rx-ym07*-6ncuLS2UO&3Mnm1Ts}fgOE;1x_?V%%O^QmSxE(cu>NaTl_M5hoiMxA zB|^)8Ak>wA`&V|rpOwvGo(`aVCEMfV-%ED_?66FV80+a zgOg_bOX>24IeIUXERjZ()YgSJ`)wzA4}8`WP+91ihOv)fN@-k=#_-eXm^e3bNN&9j z@|G?ceA=gx6ejesDIILWo-1Ha2N=|D8GE?X!ou0gkGBm72Na=S`l7^7mk*7C|GwM1fbUA_dKE76~&#*=R@)t$0T zT-*+E{*9|PVh=5frL5?Vu-QowQe2TA#a5Y1#Oc;+zX?h|nkP7HC${^Bb}z+#o0PZw iO1Da>+`zwd+rS=#nV|80xS{{lcykjQ;}Ros^uGZOb$fgO literal 0 HcmV?d00001 diff --git a/text_helper/main.py b/text_helper/main.py index 8d1481f..0fc3613 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -8,8 +8,8 @@ import sys from pathlib import Path import hashlib import math -from PIL import Image import numpy as np +import png class Languages(Enum): Japanese = 0 @@ -31,87 +31,6 @@ new_file_path = BASE_DIR / 'new_text.xlsx' old_file_path = BASE_DIR / 'text.xlsx' json_file_path = BASE_DIR / 'output.json' -mainDict = {} -textSections = [] -charArrays = { - "International": [0] * 0x100, - "Japanese": [0] * 0x100, -} -charArrayOfLanguage = { - Languages.Japanese: charArrays["Japanese"], - Languages.English: charArrays["International"], - Languages.French: charArrays["International"], - Languages.German: charArrays["International"], - Languages.Italian: charArrays["International"], - Languages.SpanishEU: charArrays["International"], - Languages.SpanishLA: charArrays["International"], -} - -jpnCharWidthArray = [ - 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, -0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, ] - -engCharWidthArray = [ - 0x4, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, -0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, -0x6, 0x6, 0x6, 0x6, 0x6, 0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x9, 0x6, 0x6, 0x0, -0x0, 0x0, 0x0, 0x0, 0xA, 0x8, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x6, 0x6, 0x4, 0x8, 0x8, 0x8, 0x7, 0x8, 0x8, 0x4, 0x6, 0x6, 0x4, 0x4, 0x0, 0x0, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7, 0x2, 0x3, 0x4, -0x5, 0x5, 0x6, 0x7, 0x5, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -0x8, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x6, 0x3, 0x6, 0x3, -0x6, 0x6, 0x6, 0x3, 0x3, 0x6, 0x6, 0x6, 0x3, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, -0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, -0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x5, 0x6, -0x4, 0x6, 0x6, 0x6, 0x6, 0x6, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x8, -0x3, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, ] - -charConversionList = [ - # replaces the first char in the list with the latter - ["'", "’"], -] - -jpnEscapeCharConversionList = [ - ["{SCL}", [0xFA]], - ["{CLR}", [0xFB]], - ["{DEF}", [0xFC, 0x06, 0x02]], - ["{FEM}", [0xFC, 0x06, 0x03]], # ??? - ["{MLE}", [0xFC, 0x06, 0x04]], - ["{FPC}", [0xFC, 0x06, 0x05]], - ["{PLR}", [0xFD, 0x01]], - ["{NEW}", [0xFE]], - ["{END}", [0xFF]], -] - -itlEscapeCharConversionList = [ - ["{SCL}", [0xFA]], - ["{CLR}", [0xFB]], - ["{DEF}", [0xFC, 0x01, 0x02]], - ["{FEM}", [0xFC, 0x01, 0x04]], - ["{FPC}", [0xFC, 0x01, 0x06]], - ["{MLE}", [0xFC, 0x01, 0x08]], - ["{PLR}", [0xFD, 0x01]], - ["{NEW}", [0xFE]], - ["{END}", [0xFF]], -] - def split_into_sentences(text: str) -> list[str]: # -*- coding: utf-8 -*- import re @@ -194,13 +113,8 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lan # Figure out the length of the word in pixels for char in word: if (pixelsPerChar == "Variable"): - if(lang == Languages.Japanese): - wordLength += jpnCharWidthArray[convert_char_to_byte(ord(char), charArrayOfLanguage[lang], lang)] - spaceLength = jpnCharWidthArray[convert_char_to_byte(ord(' '), charArrayOfLanguage[lang], lang)] - else: - wordLength += engCharWidthArray[convert_char_to_byte(ord(char), charArrayOfLanguage[lang], lang)] - spaceLength = engCharWidthArray[convert_char_to_byte(ord(' '), charArrayOfLanguage[lang], lang)] - + wordLength += charArrayOfLanguage[lang]["font"].charWidthTable[convert_char_to_byte(ord(char), charArrayOfLanguage[lang]["array"], lang)] + spaceLength = charArrayOfLanguage[lang]["font"].charWidthTable[convert_char_to_byte(ord(' '), charArrayOfLanguage[lang]["array"], lang)] elif (pixelsPerChar == "Default"): if (lang == Languages.Japanese): wordLength += 8 @@ -259,6 +173,7 @@ def convert_char_to_byte(incoming, array, lang): log_warning_error(lang, "Warning", f"Character {pair[0]} was used but is not in character table. Replaced with {pair[1]} .") index = 0 + #print(array) for val in array: if str(val) == chr(incoming): return index @@ -290,12 +205,9 @@ def convert_item(ogDict, lang): pixelsInLine = ogDict["pixelsInLine"] include_box_breaks = ogDict["includeBoxBreaks"] - if lang == Languages.Japanese: - arr = charArrayOfLanguage[lang] - list = jpnEscapeCharConversionList - else: - arr = charArrayOfLanguage[lang] - list = itlEscapeCharConversionList + arr = charArrayOfLanguage[lang]["array"] + list = charArrayOfLanguage[lang]["escape"] + for pair in list: if pair[0] in line: escapeString = "" @@ -365,7 +277,7 @@ def convert_item(ogDict, lang): outStr = newStr byteStr = "" - arr = charArrayOfLanguage[lang] + arr = charArrayOfLanguage[lang]["array"] for char in outStr[:-1]: byteStr += f"{convert_char_to_byte(ord(char), arr, lang):02x} " if (len(outStr) > 0 and outStr[-1] != ' '): # Check if the last char is a space @@ -474,7 +386,7 @@ def transfer_xlsx_to_dict(): val = currSheet.iloc[r + 1, c + offset] if pd.isna(val): val = " " - value[r * 0x10 + c] = val + value["array"][r * 0x10 + c] = val # print(charArrays[key]) offset += 16 @@ -578,7 +490,7 @@ def output_json_file(): for item in mainDict[lang.name][section]: string = mainDict[lang.name][section][item]["bytes"].split(" ") outText = "" - arr = charArrayOfLanguage[lang] + arr = charArrayOfLanguage[lang]["array"] for byte in string: byte = arr[int(byte, 16)] outText += str(byte) @@ -594,14 +506,17 @@ BACKGROUND_PAL_INDEX = 0 CELL_PAL_INDEX = 1 class Font: - def __init__(self, fileName, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight): + def __init__(self, fileName, conditional, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight, charWidth, charHeight): self.fileName = fileName + self.conditional = conditional self.numColors = numColors self.numChars = numChars self.numCharsX = numCharsX self.numCharsY = numCharsY self.cellWidth = cellWidth self.cellHeight = cellHeight + self.charWidth = charWidth + self.charHeight = charHeight self.bpp = int(math.log(numColors, 2)) + 1 self.numWords = self.numChars * self.cellWidth * self.cellHeight * self.bpp // (8 * 4) @@ -612,7 +527,11 @@ class Font: def build_h(myFont): with open(fontDir + "/include/" + myFont.fileName + ".h", 'w') as f: - f.write(f''' + f.write(f'''#include "debug_mode.h" +#include "pokemon_data.h" + +#if {myFont.conditional} + #ifndef __{myFont.fileName.upper()}__ #define __{myFont.fileName.upper()}__ @@ -624,6 +543,8 @@ extern const unsigned int {myFont.fileName}Glyphs[{myFont.numBytes}]; #define {myFont.fileName}WidthsLen {myFont.numChars} extern const unsigned char {myFont.fileName}Widths[{myFont.numChars}]; +#endif + #endif ''') f.close() @@ -676,7 +597,7 @@ def build_c(myFont): 0, // All heights are the same 0, // Character offset, is set to zero {myFont.numChars}, - {myFont.cellWidth}, {myFont.cellHeight}, + {myFont.charWidth}, {myFont.charHeight}, {myFont.cellWidth}, {myFont.cellHeight}, {myFont.numBytes // myFont.numChars}, {myFont.bpp}, @@ -687,11 +608,15 @@ def build_c(myFont): f.close() def generate_tables(myFont): - img = Image.open(f'{fontDir}/graphics/{myFont.fileName}.png') - if (img.mode != "P"): + reader = png.Reader(f'{fontDir}/text_helper/fonts/{myFont.fileName}.png') + png_info = reader.read()[3] + palette = png_info.get('palette') + if (palette is None): print("Error: Image file does not contain a palette") exit() - pixels = img.load() + + width, height, rows, info = reader.read() + pixels = list(rows) bitsPerWord = 32 pixelsPerTileX = 8 @@ -707,8 +632,8 @@ def generate_tables(myFont): for charY in range(charsPerChartY): for charX in range(charsPerChartX): - for tileY in range(tilesPerCharY): - for tileX in range(tilesPerCharX): + for tileX in range(tilesPerCharX): # Tiles go from top to bottom, then left to right + for tileY in range(tilesPerCharY): for pixelY in range(pixelsPerTileY): for pixelX in range(pixelsPerTileX): @@ -718,7 +643,10 @@ def generate_tables(myFont): globalX = pixelX + (tileX * pixelsPerTileX) + (charX * tilesPerCharX * pixelsPerTileX) globalY = pixelY + (tileY * pixelsPerTileY) + (charY * tilesPerCharY * pixelsPerTileY) - val = (pixels[globalX, globalY] - 2) & ((1 << myFont.bpp) - 1) + val = (pixels[globalY][globalX] - 1) + if val < 0: + val = 0 + val &= myFont.bpp myFont.charWordTable[arrayIndex] |= val << bitIndex #print(f'globalX: {globalX}, globalY: {globalY}, arrayIndex:{arrayIndex}, bitIndex:{bitIndex}, val:{val}') @@ -731,17 +659,68 @@ def generate_tables(myFont): globalX = x + (charX * tilesPerCharX * pixelsPerTileX) globalY = 0 + (charY * tilesPerCharY * pixelsPerTileY) #print(f'x: {globalX}, y: {globalY}') - if (pixels[globalX, globalY] == BACKGROUND_PAL_INDEX): + if (pixels[globalY][globalX] == BACKGROUND_PAL_INDEX): myFont.charWidthTable[(charY * charsPerChartX) + charX] = x break def generate_font(): - latin_normal = Font("latin_normal", 1, 256, 16, 16, 16, 16) - generate_tables(latin_normal) - build_h(latin_normal) - build_c(latin_normal) + for font in fonts.values(): + generate_tables(font) + build_h(font) + build_c(font) +mainDict = {} +textSections = [] +fonts = { + "International": Font("latin_normal", "PTGB_BUILD_LANGUAGE != 1", 1, 256, 16, 16, 16, 16, 16, 14), + "Japanese": Font("japanese_normal", "PTGB_BUILD_LANGUAGE == 1", 1, 256, 16, 16, 8, 16, 8, 16), +} +charArrays = { + "International": { + "array": [0] * 0x100, + "font": fonts["International"], + "escape": [ + ["{SCL}", [0xFA]], + ["{CLR}", [0xFB]], + ["{DEF}", [0xFC, 0x01, 0x02]], + ["{FEM}", [0xFC, 0x01, 0x04]], + ["{FPC}", [0xFC, 0x01, 0x06]], + ["{MLE}", [0xFC, 0x01, 0x08]], + ["{PLR}", [0xFD, 0x01]], + ["{NEW}", [0xFE]], + ["{END}", [0xFF]], + ] + }, + "Japanese": { + "array": [0] * 0x100, + "font": fonts["Japanese"], + "escape": [ + ["{SCL}", [0xFA]], + ["{CLR}", [0xFB]], + ["{DEF}", [0xFC, 0x06, 0x02]], + ["{FEM}", [0xFC, 0x06, 0x03]], # ??? + ["{MLE}", [0xFC, 0x06, 0x04]], + ["{FPC}", [0xFC, 0x06, 0x05]], + ["{PLR}", [0xFD, 0x01]], + ["{NEW}", [0xFE]], + ["{END}", [0xFF]], + ] + }, +} +charArrayOfLanguage = { + Languages.Japanese: charArrays["Japanese"], + Languages.English: charArrays["International"], + Languages.French: charArrays["International"], + Languages.German: charArrays["International"], + Languages.Italian: charArrays["International"], + Languages.SpanishEU: charArrays["International"], + Languages.SpanishLA: charArrays["International"], +} +charConversionList = [ + # replaces the first char in the list with the latter + ["'", "’"], +] # Main print("Running text_helper:") From a64bc8c6a05fba9897cf4f7e2de479631d4ad683 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 27 Jan 2026 10:34:06 -0500 Subject: [PATCH 20/68] Combining fonts into one file --- .gitignore | 3 +- include/japanese_normal.h | 19 ---- include/latin_normal.h | 19 ---- source/japanese_normal.c | 198 --------------------------------- source/text_engine.cpp | 16 ++- text_helper/main.py | 223 ++++++++++++++++---------------------- 6 files changed, 110 insertions(+), 368 deletions(-) delete mode 100644 include/japanese_normal.h delete mode 100644 include/latin_normal.h delete mode 100644 source/japanese_normal.c diff --git a/.gitignore b/.gitignore index 55d9a35..6537d39 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,4 @@ include/translated_text.h source/translated_text.cpp text_helper/output.json text_helper/text.xlsx -include/latin_normal.h -source/latin_normal.c +include/fonts.h diff --git a/include/japanese_normal.h b/include/japanese_normal.h deleted file mode 100644 index f621375..0000000 --- a/include/japanese_normal.h +++ /dev/null @@ -1,19 +0,0 @@ -#include "debug_mode.h" -#include "pokemon_data.h" - -#if PTGB_BUILD_LANGUAGE == 1 - -#ifndef __JAPANESE_NORMAL__ -#define __JAPANESE_NORMAL__ - -extern const TFont japanese_normalFont; - -#define japanese_normalGlyphsLen 4096 -extern const unsigned int japanese_normalGlyphs[4096]; - -#define japanese_normalWidthsLen 256 -extern const unsigned char japanese_normalWidths[256]; - -#endif - -#endif diff --git a/include/latin_normal.h b/include/latin_normal.h deleted file mode 100644 index a9601b4..0000000 --- a/include/latin_normal.h +++ /dev/null @@ -1,19 +0,0 @@ -#include "debug_mode.h" -#include "pokemon_data.h" - -#if PTGB_BUILD_LANGUAGE != 1 - -#ifndef __LATIN_NORMAL__ -#define __LATIN_NORMAL__ - -extern const TFont latin_normalFont; - -#define latin_normalGlyphsLen 8192 -extern const unsigned int latin_normalGlyphs[8192]; - -#define latin_normalWidthsLen 256 -extern const unsigned char latin_normalWidths[256]; - -#endif - -#endif diff --git a/source/japanese_normal.c b/source/japanese_normal.c deleted file mode 100644 index 4c7bb2e..0000000 --- a/source/japanese_normal.c +++ /dev/null @@ -1,198 +0,0 @@ -typedef struct TFont -{ - const void *data; //!< Character data. - const unsigned char *widths; //!< Width table for vwf. - const unsigned char *heights; //!< Height table for vhf. - unsigned short charOffset; //!< Character offset. - unsigned short charnumChars; //!< Character numChars. - unsigned char charW; //!< Character width (fwf). - unsigned char charH; //!< Character height. - unsigned char cellW; //!< Glyph cell width. - unsigned char cellH; //!< Glyph cell height. - unsigned short cellSize; //!< Cell-size (bytes). - unsigned char bpp; //!< Font bitdepth; - unsigned char extra; //!< Padding. Free to use. -} TFont; - -const unsigned int japanese_normalGlyphs[1024] __attribute__((aligned(4)))= -{ - 0x00000000,0x00000000,0x00000000,0x00000000,0x04000000,0x3e24043f,0x264d5565,0x00000000, - 0x00000000,0x41412101,0x06094141,0x00000000,0x1c000000,0x40413e00,0x1c204040,0x00000000, - 0x0e000000,0x10103f00,0x61120c08,0x00000000,0x24000000,0x3e04445f,0x36454545,0x00000000, - 0x22000000,0x22225f42,0x18212122,0x00000000,0x08000000,0x207e103e,0x3c020240,0x00000000, - 0x20000000,0x02040810,0x20100804,0x00000000,0x20000000,0x21217d21,0x12212121,0x00000000, - 0x00000000,0x0000201e,0x3c020000,0x00000000,0x10000000,0x20207e10,0x3c020240,0x00000000, - 0x02000000,0x02020202,0x38440202,0x00000000,0x20000000,0x2438207f,0x18203824,0x00000000, - 0x22000000,0x22227f22,0x3c020222,0x00000000,0x3e000000,0x087f0810,0x38040404,0x00000000, - - 0x02000000,0x017a023f,0x79050101,0x00000000,0x02000000,0x3a02023f,0x1c204046,0x00000000, - 0x00000000,0x40201f00,0x1c204040,0x00000000,0x3f000000,0x02040810,0x38040202,0x00000000, - 0x02000000,0x020e3202,0x3e010101,0x00000000,0x02000000,0x2102422f,0x18246439,0x00000000, - 0x00000000,0x01014139,0x72090101,0x00000000,0x12000000,0x4d563e12,0x32555569,0x00000000, - 0x02000000,0x42463a07,0x324a4a73,0x00000000,0x00000000,0x49494a3c,0x26494949,0x00000000, - 0x21000000,0x2121217d,0x19256539,0x00000000,0x07000000,0x21611202,0x0c122121,0x00000000, - 0x1e000000,0x08040808,0x0c515252,0x00000000,0x00000000,0x11120a04,0x40402021,0x00000000, - 0x7d000000,0x21217d21,0x19256539,0x00000000,0x08000000,0x083f083f,0x0629190e,0x00000000, - - 0x0e000000,0x3e282808,0x10262969,0x00000000,0x04000000,0x4624041f,0x38464505,0x00000000, - 0x12000000,0x55563e12,0x264d4d55,0x00000000,0x04000000,0x3e04043e,0x38440404,0x00000000, - 0x12000000,0x42573a12,0x04042442,0x00000000,0x10000000,0x5151533d,0x08103c51,0x00000000, - 0x10000000,0x10107010,0x0e51311e,0x00000000,0x18000000,0x463a0222,0x1c204040,0x00000000, - 0x42000000,0x44424242,0x18204040,0x00000000,0x3e000000,0x221c0810,0x3c524c41,0x00000000, - 0x02000000,0x22261a07,0x42222223,0x00000000,0x3e000000,0x221c0810,0x1c204041,0x00000000, - 0x02000000,0x42463a07,0x32424243,0x00000000,0x08000000,0x196e043f,0x7c020214,0x00000000, - 0x04000000,0x06020204,0x3149494a,0x00000000,0x00000000,0x083e0800,0x2c5a6a3c,0x00000000, - - 0x00000000,0x21110000,0x06012121,0x00000000,0x00000000,0x1c001c00,0x0c102022,0x00000000, - 0x00000000,0x1f000e00,0x310a0408,0x00000000,0x00000000,0x445f2400,0x1625251e,0x00000000, - 0x00000000,0x2a1f1200,0x0212222a,0x00000000,0x00000000,0x2b1d0800,0x04182929,0x00000000, - 0x00000000,0x08380800,0x0629190e,0x00000000,0x52500000,0x22225f22,0x18212122,0x00000000, - 0x54500000,0x107f083f,0x1e010120,0x00000000,0x20000000,0x52540810,0x20100804,0x00000000, - 0x50500000,0x21217d21,0x12212121,0x00000000,0x50500000,0x0000300e,0x3c020000,0x00000000, - 0x58500000,0x10103f08,0x1e010120,0x00000000,0x52000000,0x02020252,0x38440202,0x00000000, - 0x50500000,0x2438207f,0x18203824,0x00000000,0x52500000,0x22227f22,0x3c020222,0x00000000, - - 0x5e500000,0x087f0810,0x38040404,0x00000000,0x52500000,0x017a023f,0x79050101,0x00000000, - 0x52500000,0x3a02023f,0x1c204046,0x00000000,0x50500000,0x40201f00,0x1c204040,0x00000000, - 0x3f000000,0x52540810,0x38040202,0x00000000,0x52500000,0x020e3202,0x3e010101,0x00000000, - 0x51500000,0x21217d21,0x19256539,0x00000000,0x57500000,0x21611202,0x0c122121,0x00000000, - 0x5e500000,0x08040808,0x0c515252,0x00000000,0x50000000,0x11120a54,0x40402021,0x00000000, - 0x51500000,0x217d217d,0x19256539,0x00000000,0x51200000,0x21217d21,0x19256539,0x00000000, - 0xa7400000,0x21611242,0x0c122121,0x00000000,0xae400000,0x08040850,0x0c515252,0x00000000, - 0x40000000,0x11124aa4,0x40402021,0x00000000,0xa1400000,0x217d215d,0x19256539,0x00000000, - - 0x00000000,0x1e000000,0x0c102020,0x00000000,0x00000000,0x4840407f,0x04080828,0x00000000, - 0x20000000,0x17181020,0x10101010,0x00000000,0x08000000,0x41417f08,0x18204040,0x00000000, - 0x00000000,0x0808083e,0x7f080808,0x00000000,0x20000000,0x30307f20,0x20232428,0x00000000, - 0x04000000,0x44447f04,0x30424444,0x00000000,0x08000000,0x08087f08,0x0808087f,0x00000000, - 0x02000000,0x2222223e,0x0c102020,0x00000000,0x02000000,0x21227e02,0x0c102020,0x00000000, - 0x00000000,0x2020203e,0x3e202020,0x00000000,0x22000000,0x22227f22,0x0c102020,0x00000000, - 0x00000000,0x20272007,0x0f102020,0x00000000,0x00000000,0x1020203e,0x43442810,0x00000000, - 0x02000000,0x22223f02,0x3c020212,0x00000000,0x00000000,0x40444242,0x0c102020,0x00000000, - - 0x02000000,0x7946427e,0x1c204040,0x00000000,0x20000000,0x7f10101e,0x0c101010,0x00000000, - 0x00000000,0x40494949,0x1c204040,0x00000000,0x3e000000,0x107f0000,0x04081010,0x00000000, - 0x02000000,0x0e020202,0x02020232,0x00000000,0x10000000,0x10107f10,0x06081010,0x00000000, - 0x00000000,0x0000003e,0x007f0000,0x00000000,0x00000000,0x2620203f,0x47281018,0x00000000, - 0x08000000,0x1820407f,0x0808482f,0x00000000,0x00000000,0x20202020,0x030c1010,0x00000000, - 0x00000000,0x22222202,0x41414242,0x00000000,0x01000000,0x013f0101,0x3e010101,0x00000000, - 0x00000000,0x2020203f,0x06081010,0x00000000,0x00000000,0x11120a04,0x40402021,0x00000000, - 0x08000000,0x2a087f08,0x0849492a,0x00000000,0x00000000,0x2240407f,0x20100814,0x00000000, - - 0x0e000000,0x300e0030,0x38060000,0x00000000,0x08000000,0x24040408,0x7f414222,0x00000000, - 0x20000000,0x30282620,0x07485020,0x00000000,0x00000000,0x3f04043f,0x78040404,0x00000000, - 0x04000000,0x44447f04,0x04040424,0x00000000,0x00000000,0x1010101e,0x7f101010,0x00000000, - 0x00000000,0x3e20203f,0x3f202020,0x00000000,0x1e000000,0x203f0000,0x0c102020,0x00000000, - 0x22000000,0x22222222,0x18202020,0x00000000,0x0a000000,0x0a0a0a0a,0x19294a4a,0x00000000, - 0x02000000,0x02020202,0x0e122202,0x00000000,0x00000000,0x2121213f,0x3f212121,0x00000000, - 0x00000000,0x4041417f,0x18204040,0x00000000,0x00000000,0x3e20203f,0x0e102020,0x00000000, - 0x00000000,0x40404403,0x0f102040,0x00000000,0x00000000,0x203f0000,0x02040414,0x00000000, - - 0x00000000,0x20200000,0x10101618,0x00000000,0x00000000,0x3e080000,0x0c102022,0x00000000, - 0x00000000,0x3e000000,0x3e080808,0x00000000,0x00000000,0x3e100000,0x18121418,0x00000000, - 0x00000000,0x3f040000,0x04041424,0x00000000,0x00000000,0x1c000000,0x3e101010,0x00000000, - 0x00000000,0x3e000000,0x3e203c20,0x00000000,0x54500000,0x44447f04,0x30424444,0x00000000, - 0x58500000,0x08087f08,0x0808087f,0x00000000,0x52500000,0x2222223e,0x0c102020,0x00000000, - 0x52500000,0x21227e02,0x0c102020,0x00000000,0x50500000,0x2020203e,0x3e202020,0x00000000, - 0x52500000,0x22227f22,0x0c102020,0x00000000,0x50500000,0x20270007,0x0f102020,0x00000000, - 0x50500000,0x1020203e,0x43442810,0x00000000,0x2a280000,0x22223f02,0x3c020212,0x00000000, - - 0x50500000,0x20222101,0x06081010,0x00000000,0x52500000,0x7946423e,0x1c204040,0x00000000, - 0x28280000,0x7f10101e,0x0c101010,0x00000000,0x50500000,0x40494909,0x1c204040,0x00000000, - 0x5e500000,0x107f0000,0x04081010,0x00000000,0x52500000,0x0e020202,0x02020232,0x00000000, - 0x50500000,0x22222200,0x41414242,0x00000000,0x51500000,0x013f0101,0x3e010101,0x00000000, - 0x50500000,0x2020203f,0x06081010,0x00000000,0x50000000,0x11120a54,0x40402021,0x00000000, - 0x58500000,0x2a087f08,0x0849492a,0x00000000,0xa0400000,0x22222240,0x41414242,0x00000000, - 0xa1400000,0x013f0141,0x3e010101,0x00000000,0xa0400000,0x2020205f,0x06081010,0x00000000, - 0xa0400000,0x11120a44,0x40402021,0x00000000,0xa8400000,0x2a083f48,0x0849492a,0x00000000, - - 0x00000000,0x0a000000,0x1820202a,0x00000000,0x1c000000,0x22222222,0x1c222222,0x00000000, - 0x08000000,0x0808080c,0x1c080808,0x00000000,0x1c000000,0x10202222,0x3e020408,0x00000000, - 0x1c000000,0x18202222,0x1c222220,0x00000000,0x18000000,0x12121414,0x10103e12,0x00000000, - 0x3e000000,0x1e020202,0x1c222020,0x00000000,0x1c000000,0x1e020222,0x1c222222,0x00000000, - 0x3e000000,0x10202020,0x08080810,0x00000000,0x1c000000,0x1c222222,0x1c222222,0x00000000, - 0x1c000000,0x3c222222,0x1c222020,0x00000000,0x18000000,0x183c3c3c,0x18000018,0x00000000, - 0x3c000000,0x18306666,0x18000018,0x00000000,0x00000000,0x00000000,0x06090906,0x00000000, - 0x00000000,0x3e000000,0x00000000,0x00000000,0x00000000,0x0c000000,0x0000000c,0x00000000, - - 0x00000000,0x22000000,0x00000000,0x00000000,0x427e0000,0x0e0a0a7a,0x00000000,0x00000000, - 0x00000000,0x38000000,0x212f2828,0x0000003f,0x023e0000,0x00020202,0x00000000,0x00000000, - 0x00000000,0x00000000,0x10101010,0x0000001f,0x08000000,0x1c082a1c,0x1c222222,0x00000000, - 0x1c000000,0x1c222222,0x08083e08,0x00000000,0x00000000,0x49497f00,0x6141417f,0x00000000, - 0x00000000,0x00000000,0x06060000,0x00000000,0x00000000,0x22000000,0x22140814,0x00000000, - 0x20200000,0x08081010,0x02020404,0x00000000,0x1c000000,0x22222222,0x2222223e,0x00000000, - 0x1e000000,0x1e222222,0x1e222222,0x00000000,0x1c000000,0x02022222,0x1c222202,0x00000000, - 0x1e000000,0x22222222,0x1e222222,0x00000000,0x3e000000,0x3e020202,0x3e020202,0x00000000, - - 0x3e000000,0x1e020202,0x02020202,0x00000000,0x1c000000,0x02022222,0x1c222232,0x00000000, - 0x22000000,0x3e222222,0x22222222,0x00000000,0x1c000000,0x08080808,0x1c080808,0x00000000, - 0x20000000,0x20202020,0x1c222222,0x00000000,0x22000000,0x060a1222,0x2222120a,0x00000000, - 0x02000000,0x02020202,0x3e020202,0x00000000,0x41000000,0x55636341,0x41494955,0x00000000, - 0x22000000,0x2a262622,0x2232322a,0x00000000,0x1c000000,0x22222222,0x1c222222,0x00000000, - 0x1e000000,0x1e222222,0x02020202,0x00000000,0x1c000000,0x22222222,0x5c263a22,0x00000000, - 0x1e000000,0x1e222222,0x22222212,0x00000000,0x1c000000,0x1c022222,0x1c222220,0x00000000, - 0x3e000000,0x08080808,0x08080808,0x00000000,0x22000000,0x22222222,0x1c222222,0x00000000, - - 0x22000000,0x14222222,0x08081414,0x00000000,0x41000000,0x55554949,0x41416363,0x00000000, - 0x22000000,0x08141422,0x22221414,0x00000000,0x22000000,0x14142222,0x08080808,0x00000000, - 0x3e000000,0x08101020,0x3e020404,0x00000000,0x00000000,0x20201c00,0x3c22223c,0x00000000, - 0x00000000,0x02020202,0x1e22221e,0x00000000,0x00000000,0x02221c00,0x1c220202,0x00000000, - 0x00000000,0x20202020,0x3c22223c,0x00000000,0x00000000,0x221c0000,0x1c22023e,0x00000000, - 0x00000000,0x04040418,0x0404041e,0x00000000,0x00000000,0x22223c00,0x1c22203c,0x00000000, - 0x00000000,0x1e020202,0x22222222,0x00000000,0x00000000,0x00080800,0x08080808,0x00000000, - 0x00000000,0x10001010,0x0c121210,0x00000000,0x02000000,0x0a122202,0x22120a06,0x00000000, - - 0x00000000,0x0808080c,0x08080808,0x00000000,0x00000000,0x49493f00,0x49494949,0x00000000, - 0x00000000,0x22221e00,0x22222222,0x00000000,0x00000000,0x22221c00,0x1c222222,0x00000000, - 0x00000000,0x22221e00,0x0202021e,0x00000000,0x00000000,0x22223c00,0x2020203c,0x00000000, - 0x00000000,0x0c340400,0x04040404,0x00000000,0x00000000,0x02221c00,0x1c22201c,0x00000000, - 0x00000000,0x041e0404,0x18040404,0x00000000,0x00000000,0x22222200,0x3c222222,0x00000000, - 0x00000000,0x22222200,0x08081414,0x00000000,0x00000000,0x49414100,0x22365549,0x00000000, - 0x00000000,0x14224100,0x41221408,0x00000000,0x00000000,0x22222200,0x1c20203c,0x00000000, - 0x00000000,0x20407e00,0x7e040810,0x00000000,0x06000000,0x3e3e1e0e,0x00060e1e,0x00000000, - - 0x00000000,0x00000c0c,0x000c0c00,0x00000000,0x00120000,0x2222221c,0x2222223e,0x00000000, - 0x00120000,0x2222221c,0x1c222222,0x00000000,0x00120000,0x22222222,0x1c222222,0x00000000, - 0x00120000,0x20201c00,0x3c22223c,0x00000000,0x00120000,0x22221c00,0x1c222222,0x00000000, - 0x00120000,0x22222200,0x3c222222,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, - 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, - 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, - 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, - 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, -}; - -const unsigned char japanese_normalWidths[256] __attribute__((aligned(4)))= -{ - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, -}; - -const TFont japanese_normalFont= -{ - japanese_normalGlyphs, - japanese_normalWidths, - 0, // All heights are the same - 0, // Character offset, is set to zero - 256, - 8, 16, - 8, 16, - 16, - 1, - 0, // Padding, left blank -}; - \ No newline at end of file diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 088db00..80b18ea 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -8,8 +8,7 @@ #include "debug_mode.h" #include "button_menu.h" #include "sprite_data.h" -#include "latin_normal.h" -#include "japanese_normal.h" +#include "fonts.h" #include "text_data_table.h" #include "background_engine.h" @@ -59,11 +58,22 @@ static __attribute__((noinline)) const u8 *read_dialogue_text_entry(uint8_t inde return output_buffer; } +// This will have to be changed to be dynamic to support nicknamed Pokemon that are in a different language than the current build. +// Maybe combine Japanese and Latin into one larger font? + +#if PTGB_BUILD_LANGUAGE == 1 + #define BUILD_FONT &japanese_normalFont +#else + #define BUILD_FONT &latin_normalFont +#endif + void init_text_engine() { // Load the TTE // tte_init_se(3, BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB) | BG_PRIO(0), 0, CLR_WHITE, 14, &japanese_smallFont, NULL); + + tte_init_chr4c(3, // BG 3 BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB), // Charblock 0; screenblock 10 0xF000, // Screen-entry offset @@ -73,7 +83,7 @@ void init_text_engine() 0, // Paper 0), // Special CLR_WHITE, // White text - &japanese_normalFont, // Custom font + BUILD_FONT, // Custom font NULL // Use default chr4 renderer ); tte_init_con(); diff --git a/text_helper/main.py b/text_helper/main.py index 0fc3613..c1d8a12 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -330,6 +330,7 @@ def write_enum_to_header_file(hFile, prefix, dictionary): return num def download_xlsx_file(): + print("Downloading xlsx file") offline = False # ---- Attempt download ---- try: @@ -361,19 +362,19 @@ def download_xlsx_file(): if old_file_path.exists(): if hash_excel(new_file_path) == hash_excel(old_file_path): - print("Downloaded file is identical.") + print("Downloaded file is identical") new_file_path.unlink() if json_file_path.exists(): - print("Skipping parse.\n") + print("Skipping parse\n") sys.exit(0) else: - print("JSON missing - forcing rebuild.") + print("JSON missing - forcing rebuild") else: old_file_path.unlink() new_file_path.rename(old_file_path) else: - print("No cached xlsx - forcing rebuild.") + print("No cached xlsx - forcing rebuild") new_file_path.rename(old_file_path) def transfer_xlsx_to_dict(): @@ -506,9 +507,8 @@ BACKGROUND_PAL_INDEX = 0 CELL_PAL_INDEX = 1 class Font: - def __init__(self, fileName, conditional, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight, charWidth, charHeight): + def __init__(self, fileName, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight, charWidth, charHeight): self.fileName = fileName - self.conditional = conditional self.numColors = numColors self.numChars = numChars self.numCharsX = numCharsX @@ -525,155 +525,124 @@ class Font: self.charWordTable = [0] * self.numWords self.charWidthTable = [0] * self.numBytes -def build_h(myFont): - with open(fontDir + "/include/" + myFont.fileName + ".h", 'w') as f: +def build_h(): + print("Building font.h") + with open(fontDir + "/include/fonts.h", 'w') as f: f.write(f'''#include "debug_mode.h" #include "pokemon_data.h" -#if {myFont.conditional} +#ifndef __FONTS_H__ +#define __FONTS_H__''') -#ifndef __{myFont.fileName.upper()}__ -#define __{myFont.fileName.upper()}__ - -extern const TFont {myFont.fileName}Font; + for myFont in fonts.values(): + f.write(f'''\n #define {myFont.fileName}GlyphsLen {myFont.numBytes} -extern const unsigned int {myFont.fileName}Glyphs[{myFont.numBytes}]; +#define {myFont.fileName}WidthsLen {myFont.numChars}''') + + f.write(f'\n\nconst unsigned int {myFont.fileName}Glyphs[{myFont.numWords}] __attribute__((aligned(4)))=\n{{\n\t') -#define {myFont.fileName}WidthsLen {myFont.numChars} -extern const unsigned char {myFont.fileName}Widths[{myFont.numChars}]; + for i in range(myFont.numWords): + f.write(f'{myFont.charWordTable[i]:#010x},') + if (i == myFont.numWords - 1): + f.write("\n};\n\n") + elif (i % 64 == 63): + f.write("\n\n\t") + elif (i % 8 == 7): + f.write("\n\t") -#endif + f.write(f'const unsigned char {myFont.fileName}Widths[{myFont.numChars}] __attribute__((aligned(4)))=\n{{\n\t') -#endif -''') - f.close() + for i in range(myFont.numChars): + f.write(f'{myFont.charWidthTable[i]:#04x}, ') + if (i == myFont.numChars - 1): + f.write("\n};\n\n") + elif (i % 128 == 127): + f.write("\n\n\t") + elif (i % 16 == 15): + f.write("\n\t") -def build_c(myFont): - with open(fontDir + "/source/" + myFont.fileName + ".c", 'w') as f: - f.write(f'''typedef struct TFont + f.write(f'''const TFont {myFont.fileName}Font= {{ - const void *data; //!< Character data. - const unsigned char *widths; //!< Width table for vwf. - const unsigned char *heights; //!< Height table for vhf. - unsigned short charOffset; //!< Character offset. - unsigned short charnumChars; //!< Character numChars. - unsigned char charW; //!< Character width (fwf). - unsigned char charH; //!< Character height. - unsigned char cellW; //!< Glyph cell width. - unsigned char cellH; //!< Glyph cell height. - unsigned short cellSize; //!< Cell-size (bytes). - unsigned char bpp; //!< Font bitdepth; - unsigned char extra; //!< Padding. Free to use. -}} TFont;\n -''') - - f.write(f'const unsigned int {myFont.fileName}Glyphs[{myFont.numWords}] __attribute__((aligned(4)))=\n{{\n\t') - - for i in range(myFont.numWords): - f.write(f'{myFont.charWordTable[i]:#010x},') - if (i == myFont.numWords - 1): - f.write("\n};\n\n") - elif (i % 64 == 63): - f.write("\n\n\t") - elif (i % 8 == 7): - f.write("\n\t") - - f.write(f'const unsigned char {myFont.fileName}Widths[{myFont.numChars}] __attribute__((aligned(4)))=\n{{\n\t') - - for i in range(myFont.numChars): - f.write(f'{myFont.charWidthTable[i]:#04x}, ') - if (i == myFont.numChars - 1): - f.write("\n};\n\n") - elif (i % 128 == 127): - f.write("\n\n\t") - elif (i % 16 == 15): - f.write("\n\t") - - f.write(f'''const TFont {myFont.fileName}Font= -{{ - {myFont.fileName}Glyphs, + {myFont.fileName}Glyphs, {myFont.fileName}Widths, 0, // All heights are the same - 0, // Character offset, is set to zero + 0, // Character offset, is set to zero {myFont.numChars}, - {myFont.charWidth}, {myFont.charHeight}, - {myFont.cellWidth}, {myFont.cellHeight}, + {myFont.charWidth}, {myFont.charHeight}, + {myFont.cellWidth}, {myFont.cellHeight}, {myFont.numBytes // myFont.numChars}, - {myFont.bpp}, + {myFont.bpp}, 0, // Padding, left blank -}}; - ''') +}};''') - f.close() + f.write('\n\n#endif') + f.close() -def generate_tables(myFont): - reader = png.Reader(f'{fontDir}/text_helper/fonts/{myFont.fileName}.png') - png_info = reader.read()[3] - palette = png_info.get('palette') - if (palette is None): - print("Error: Image file does not contain a palette") - exit() +def generate_tables(): + print("Generating font tables") + for myFont in fonts.values(): + print(f'\t{myFont.fileName}') + reader = png.Reader(f'{fontDir}/text_helper/fonts/{myFont.fileName}.png') + png_info = reader.read()[3] + palette = png_info.get('palette') + if (palette is None): + print("Error: Image file does not contain a palette") + exit() - width, height, rows, info = reader.read() - pixels = list(rows) + width, height, rows, info = reader.read() + pixels = list(rows) - bitsPerWord = 32 - pixelsPerTileX = 8 - pixelsPerTileY = 8 - tilesPerCharX = myFont.cellWidth // pixelsPerTileX - tilesPerCharY = myFont.cellHeight // pixelsPerTileY - charsPerChartX = myFont.numCharsX - charsPerChartY = myFont.numCharsY + bitsPerWord = 32 + pixelsPerTileX = 8 + pixelsPerTileY = 8 + tilesPerCharX = myFont.cellWidth // pixelsPerTileX + tilesPerCharY = myFont.cellHeight // pixelsPerTileY + charsPerChartX = myFont.numCharsX + charsPerChartY = myFont.numCharsY - globalX = 0 - globalY = 0 - bitTotal = 0 + globalX = 0 + globalY = 0 + bitTotal = 0 - for charY in range(charsPerChartY): - for charX in range(charsPerChartX): - for tileX in range(tilesPerCharX): # Tiles go from top to bottom, then left to right - for tileY in range(tilesPerCharY): - for pixelY in range(pixelsPerTileY): - for pixelX in range(pixelsPerTileX): - - arrayIndex = bitTotal // bitsPerWord - bitIndex = bitTotal % bitsPerWord + for charY in range(charsPerChartY): + for charX in range(charsPerChartX): + for tileX in range(tilesPerCharX): # Tiles go from top to bottom, then left to right + for tileY in range(tilesPerCharY): + for pixelY in range(pixelsPerTileY): + for pixelX in range(pixelsPerTileX): + + arrayIndex = bitTotal // bitsPerWord + bitIndex = bitTotal % bitsPerWord - globalX = pixelX + (tileX * pixelsPerTileX) + (charX * tilesPerCharX * pixelsPerTileX) - globalY = pixelY + (tileY * pixelsPerTileY) + (charY * tilesPerCharY * pixelsPerTileY) + globalX = pixelX + (tileX * pixelsPerTileX) + (charX * tilesPerCharX * pixelsPerTileX) + globalY = pixelY + (tileY * pixelsPerTileY) + (charY * tilesPerCharY * pixelsPerTileY) - val = (pixels[globalY][globalX] - 1) - if val < 0: - val = 0 - val &= myFont.bpp - myFont.charWordTable[arrayIndex] |= val << bitIndex + val = (pixels[globalY][globalX] - 1) + if val < 0: + val = 0 + val &= myFont.bpp + myFont.charWordTable[arrayIndex] |= val << bitIndex - #print(f'globalX: {globalX}, globalY: {globalY}, arrayIndex:{arrayIndex}, bitIndex:{bitIndex}, val:{val}') - bitTotal += myFont.bpp - - #print(f'{charX, charY}') + #print(f'globalX: {globalX}, globalY: {globalY}, arrayIndex:{arrayIndex}, bitIndex:{bitIndex}, val:{val}') + bitTotal += myFont.bpp + + #print(f'{charX, charY}') - myFont.charWidthTable[(charY * charsPerChartX) + charX] = (tilesPerCharX * pixelsPerTileX) - for x in range(tilesPerCharX * pixelsPerTileX): - globalX = x + (charX * tilesPerCharX * pixelsPerTileX) - globalY = 0 + (charY * tilesPerCharY * pixelsPerTileY) - #print(f'x: {globalX}, y: {globalY}') - if (pixels[globalY][globalX] == BACKGROUND_PAL_INDEX): - myFont.charWidthTable[(charY * charsPerChartX) + charX] = x - break - -def generate_font(): - for font in fonts.values(): - generate_tables(font) - build_h(font) - build_c(font) + myFont.charWidthTable[(charY * charsPerChartX) + charX] = (tilesPerCharX * pixelsPerTileX) + for x in range(tilesPerCharX * pixelsPerTileX): + globalX = x + (charX * tilesPerCharX * pixelsPerTileX) + globalY = 0 + (charY * tilesPerCharY * pixelsPerTileY) + #print(f'x: {globalX}, y: {globalY}') + if (pixels[globalY][globalX] == BACKGROUND_PAL_INDEX): + myFont.charWidthTable[(charY * charsPerChartX) + charX] = x + break mainDict = {} textSections = [] fonts = { - "International": Font("latin_normal", "PTGB_BUILD_LANGUAGE != 1", 1, 256, 16, 16, 16, 16, 16, 14), - "Japanese": Font("japanese_normal", "PTGB_BUILD_LANGUAGE == 1", 1, 256, 16, 16, 8, 16, 8, 16), + "International": Font("latin_normal", 1, 256, 16, 16, 16, 16, 16, 14), + "Japanese": Font("japanese_normal", 1, 256, 16, 16, 8, 16, 8, 16), } charArrays = { "International": { @@ -724,8 +693,8 @@ charConversionList = [ # Main print("Running text_helper:") -print("\tGenerating font") -generate_font() +generate_tables() +build_h() download_xlsx_file() transfer_xlsx_to_dict() generate_header_file() From 304880f7d0a0457fc01148e1e9970d678a5241c6 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 27 Jan 2026 22:26:38 -0500 Subject: [PATCH 21/68] QoL before adding scrolling --- text_helper/main.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index c1d8a12..46d31f6 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -134,7 +134,7 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lan currWordIndex += 1 # See if the sentence is a new box - elif(sentence == "Ş" or sentence == "ȼ"): + elif(sentence == "ȼ"): outStr += sentence currLine = "" offset = 0 @@ -365,7 +365,10 @@ def download_xlsx_file(): print("Downloaded file is identical") new_file_path.unlink() if json_file_path.exists(): - print("Skipping parse\n") + print("Skipping parse") + if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(f'{textDir}/text.xlsx'): + print("\t...but the python file is new, so we're doing it anyway!") + return sys.exit(0) else: print("JSON missing - forcing rebuild") @@ -641,7 +644,7 @@ def generate_tables(): mainDict = {} textSections = [] fonts = { - "International": Font("latin_normal", 1, 256, 16, 16, 16, 16, 16, 14), + "International": Font("latin_normal", 1, 256, 16, 16, 16, 16, 16, 16), "Japanese": Font("japanese_normal", 1, 256, 16, 16, 8, 16, 8, 16), } charArrays = { From aff5709717a283736ee67916ba8d2360afcbc55f Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 28 Jan 2026 17:34:30 -0500 Subject: [PATCH 22/68] Fully adding scroll support --- include/debug_mode.h | 4 +-- include/text_engine.h | 1 + source/text_engine.cpp | 42 +++++++++++++++++++--- text_helper/fonts/latin_normal.png | Bin 3080 -> 3071 bytes text_helper/main.py | 54 +++++++++++++++++++++++------ 5 files changed, 84 insertions(+), 17 deletions(-) diff --git a/include/debug_mode.h b/include/debug_mode.h index 045cb88..2992aed 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -2,7 +2,7 @@ #define DEBUG_MODE_H #define VERSION "v1.2.1" -#define PTGB_BUILD_LANGUAGE 1 +#define PTGB_BUILD_LANGUAGE 2 #define DEBUG_MODE true @@ -10,7 +10,7 @@ #define INSTANT_TEXT_SPEED (false && DEBUG_MODE) #define IGNORE_GAME_PAK (true && DEBUG_MODE) #define IGNORE_GAME_PAK_SPRITES (false && DEBUG_MODE) -#define IGNORE_LINK_CABLE (false && DEBUG_MODE) +#define IGNORE_LINK_CABLE (true && DEBUG_MODE) #define IGNORE_MG_E4_FLAGS (true && DEBUG_MODE) #define IGNORE_UNRECEIVED_PKMN (false && DEBUG_MODE) #define FORCE_TUTORIAL (false && DEBUG_MODE) diff --git a/include/text_engine.h b/include/text_engine.h index 1b3915b..a4274a1 100644 --- a/include/text_engine.h +++ b/include/text_engine.h @@ -30,5 +30,6 @@ int ptgb_write(const byte *text, bool instant); int ptgb_write(const byte *text, bool instant, int length); int ptgb_write_debug(const u16* charset, const char *text, bool instant); void wait_for_user_to_continue(bool clear_text); +void scroll_text(const byte *text, bool instant, int length, int curr_index, TTC *tc); #endif \ No newline at end of file diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 80b18ea..60c57ee 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -62,9 +62,9 @@ static __attribute__((noinline)) const u8 *read_dialogue_text_entry(uint8_t inde // Maybe combine Japanese and Latin into one larger font? #if PTGB_BUILD_LANGUAGE == 1 - #define BUILD_FONT &japanese_normalFont +#define BUILD_FONT &japanese_normalFont #else - #define BUILD_FONT &latin_normalFont +#define BUILD_FONT &latin_normalFont #endif void init_text_engine() @@ -134,6 +134,7 @@ int text_loop(int script) { tte_set_pos(LEFT, TOP); tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); + REG_BG3VOFS = 0; ptgb_write(curr_text, false); } @@ -238,12 +239,14 @@ int text_loop(int script) } show_text_box(); tte_erase_rect(0, 0, 240, 160); - ptgb_write(curr_text, true); + ptgb_write(curr_text, false); } wait_for_user_to_continue(false); update_text = true; hide_text_box(); + REG_BG3VOFS = 0; + if (text_exit) { @@ -323,6 +326,16 @@ int ptgb_write(const byte *text, bool instant, int length) str++; switch (ch) { + case 0xFA: + if (DISPLAY_CONTROL_CHAR) + { + tc->drawgProc(0x79); + } + wait_for_user_to_continue(false); + scroll_text(text, instant, length, num, tc); + tc->cursorY += tc->font->charH; + tc->cursorX = tc->marginLeft; + break; case 0xFB: if (DISPLAY_CONTROL_CHAR) { @@ -428,5 +441,26 @@ void wait_for_user_to_continue(bool clear_text) { tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); tte_set_pos(LEFT, TOP); + REG_BG3VOFS = 0; } -} \ No newline at end of file +} + +void scroll_text(const byte *text, bool instant, int length, int curr_index, TTC *tc) +{ + for (int i = 1; i <= tc->font->charH; i++) + { + REG_BG3VOFS = i; + tte_erase_rect(LEFT, TOP - tc->font->charH, RIGHT, TOP + i); + global_next_frame(); + } + //wait_for_user_to_continue(true); + // Remove current text + //tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); + // Write old text offset up one line + //tte_set_pos(LEFT, TOP - tc->font->charH); + //ptgb_write(text, true, curr_index); + // Remove text that went outside of the box + tte_erase_rect(LEFT, TOP - tc->font->charH, RIGHT, TOP); + // Prepare to continue + //tte_set_pos(LEFT, TOP); +} diff --git a/text_helper/fonts/latin_normal.png b/text_helper/fonts/latin_normal.png index bbb026197e8e7766f4e43299248db75d646d2d4b..c9312f93708fc0bbcdf6d3f57f18360cc433855a 100644 GIT binary patch delta 2935 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 delta 2972 zcmaKu=|9wa1BSo9nPJStOpUEDwo|535~mo%GD( zY^oa*WV_D}t|UI0zFna10~P9L*+!VF@Ezp~4hwM1>*=m|YS4~C0r%>Taxjl#mIQ)9 zm(sroeOo{fXGWp(7cch=%5R7Oqn2>2O2FL-Aet^vU=C~pjbDS2<7ZHgB_)QG2K9C_ z&5%1)AAke3GhkVKLm)`oexZut{hXnzg_m5k!wHhE^4a{b$Yo;>)!exyaAn zIt-H)gRL6eU2(8m3IsApfWR$qe?A@}4TecedfvwVL+yUzEWK=mcARA3>No5X$`jk; z2Rhxp%R&UMW+Z0(Whi3tqqA|ia%-e#plSe)4e^&0RNr+2M`gI#@KWO|Tsdg29xDPl z5?28mrP~dOF}oo)R7$6k(cq?wsrJm#hh~;2O4g6yN-3(pCKpQ~90j57nmu?f`7^@8 z2`!LrDhb9vPQ^&V{d53!%?5oGw2VwErmYzYn4#02GsNRWL5 zW-#nLY>WW|%U>#IGxi}2OK7^>`pg1{_<(&;0`h9`lJ^CxslTMyam%Z7UD^IZ58tnoZOp;KJDU?E9X z-UcDdjDM@6c9qCHum_&JnvNf1Z_se!Ct$GKaDVA0S zV-#s%BliT;-VcuULwNS984sL((MNz=UrJe9mvP9HT#C96;)d=AsQvt`wD#*g z!f&avauqSULfCR9U1RqW-0kv4xE~yTWAs}eBE+u>L3@I9L$gSy`dotSdg#sUX*F*ZX67T`$?<7YQ-89aH+dAb#Y|J5qc@35$ z%_E?m%ORaIDq`FraKSKZ)__Cm#0|^-eI5mibC*R$O-1O8mYAGT(b!0w9@FODvfp@| zl$Y(x&(v|9&ft#!S3Vh?LQHpPL|gP&k2_i;+h* zTD5+s*UVfxJ|Z3ezT{6zc;xcbC_F5jk-k0`c+faSj8$?rZ8R^4i!+_cto*5Zg9H>n zsP%W?OPuo;;}g{`KX3_gn}7=CBU=x@l^DR|7S$17q}(637wG!!scBx6HOzeWB0WE1 z7gKN#Dh>Lwz8*E(mm%GvdjKDO^xD>b?pB-}4`OzJn|>S@>4VZN0cFe(>0-TDuV=MM9H$woxBJF$^NOqK5cg8LUL4EtIeyWyGFtitu*EDC!^XQv&n)WuqxLg}U2j$}xx4OK zY)emGvb-}Fy{H7R6b}STu_N}QFGQG(UftC2EFVM70oS7(h=QSqC{DnBMCqJ}1 zE0cq;i2EgQ;mu^5x|XY{)lRD=qgCpo1zJ&sELM4htzAL^P41H4e3QC_4mP@j7r3pMFWyp*NlFm5(bBb>e zRmOQ}m%!C+$Xcf&sY)TIuHdPC?-_exy{-4=TA2>{N5G8p1Bsk2*T*qJ*<>AMSzcmU zr7vS06Z!TCTivSMoc=Au0abRT0!>l)rFTX3)@d!*;z+#@jN)!6Wv0*i{m|JBrGRW7 z8P1N)!?9~;7#VhwEgQc^`9f#VwcGHbif>q`{E!Q}Y32HImr1w+{e-a2!WAyXcm=zs zVf@YYLyvnZ7R)s+IBuQpHqq7u6Zm_FsB?RdI;>-d#)Ec1SYw9!PZL-TIvu@OMR{w3iboTgvi0_V2>&z*yz& z`^KniwMG)Z{z{FX=r0r(^B68{8BxlUkqVa0wMtTjU@u49NtRtSO!uXQ`#x)x!JTEZA z!g(5ro5rNwYS3@JjJoF$kb|9C?7`vtvF!S)N-;7=_Iz)qmQba!#9+cYett8=^$&s8kRvoNoxEf)#N#DI$O|NY6xn1f zkSCk3{vodLXqM!(8Q= numLines) and include_scrolling): + breakChar = 'Ş' + else: + breakChar = outStr[outIndex] + outBox += split + breakChar + outIndex += 1 + i += 1 + newStr += f'{outBox}ȼ' + newStr = newStr[:-1] # remove the last ȼ + # A space right before a newline just takes up space newStr = newStr.replace(" Ň", "Ň") # Newlines shouldn't happen right after a new textbox @@ -269,6 +296,7 @@ def convert_item(ogDict, lang): # Nor should a new scroll be after a new textbox newStr = newStr.replace("ȼŞ", "ȼ") + if len(newStr) > 1023: newStr = newStr[:1023] log_warning_error(lang, "Warning", f"String {newStr} exceeds character limit of 1023 and has been truncated.") @@ -352,7 +380,10 @@ def download_xlsx_file(): if offline: # XML exists (guaranteed here) if json_file_path.exists(): - print("Offline mode: trusting cached XML + JSON. Skipping parse.\n") + print("Offline mode: trusting cached XML + JSON. Skipping parse.") + if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(f'{textDir}/output.json'): + print("\t...but the python file is new, so we're doing it anyway!") + return sys.exit(0) else: print("Offline mode: XML present but JSON missing. Rebuilding.") @@ -366,7 +397,7 @@ def download_xlsx_file(): new_file_path.unlink() if json_file_path.exists(): print("Skipping parse") - if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(f'{textDir}/text.xlsx'): + if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(f'{textDir}/output.json'): print("\t...but the python file is new, so we're doing it anyway!") return sys.exit(0) @@ -424,6 +455,7 @@ def transfer_xlsx_to_dict(): "pixelsPerChar": currRow.iloc[3], "pixelsInLine" : currRow.iloc[4], "includeBoxBreaks": currRow.iloc[5], + "includeScrolling": currRow.iloc[6], } def generate_header_file(): From d042e18768a554717705b1fde941b3463230ba67 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 28 Jan 2026 18:00:37 -0500 Subject: [PATCH 23/68] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8cf1eca..efce3bb 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +166,7 @@ generate_data: @echo "----------------------------------------------------------------" @echo @tools/payload-generator/payload-generator to_compress - @python text_helper/main.py + @python3 text_helper/main.py @echo "Compressing bin files!" @echo -n "[" @find to_compress -name "*.bin" -print0 | xargs -0 -n1 ./compress_lz10.sh From 84dfe45866e5ba7667909f7120e4da722bfb9704 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 28 Jan 2026 21:52:55 -0500 Subject: [PATCH 24/68] Adding Japanese comma and debug text options --- include/debug_mode.h | 2 +- include/text_engine.h | 2 +- source/text_engine.cpp | 28 ++++++++++---------------- text_helper/fonts/japanese_normal.png | Bin 2932 -> 2929 bytes text_helper/main.py | 2 +- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/include/debug_mode.h b/include/debug_mode.h index 2992aed..c5d6b4b 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -2,7 +2,7 @@ #define DEBUG_MODE_H #define VERSION "v1.2.1" -#define PTGB_BUILD_LANGUAGE 2 +#define PTGB_BUILD_LANGUAGE 1 #define DEBUG_MODE true diff --git a/include/text_engine.h b/include/text_engine.h index a4274a1..fd993f5 100644 --- a/include/text_engine.h +++ b/include/text_engine.h @@ -30,6 +30,6 @@ int ptgb_write(const byte *text, bool instant); int ptgb_write(const byte *text, bool instant, int length); int ptgb_write_debug(const u16* charset, const char *text, bool instant); void wait_for_user_to_continue(bool clear_text); -void scroll_text(const byte *text, bool instant, int length, int curr_index, TTC *tc); +void scroll_text(bool instant, TTC *tc); #endif \ No newline at end of file diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 60c57ee..98437e4 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -72,8 +72,6 @@ void init_text_engine() // Load the TTE // tte_init_se(3, BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB) | BG_PRIO(0), 0, CLR_WHITE, 14, &japanese_smallFont, NULL); - - tte_init_chr4c(3, // BG 3 BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB), // Charblock 0; screenblock 10 0xF000, // Screen-entry offset @@ -174,6 +172,7 @@ int text_loop(int script) { bool exit = false; bool update_text = true; + bool instant_text = false; key_poll(); while (!exit) { @@ -197,8 +196,9 @@ int text_loop(int script) text_section = (text_section + (NUM_TEXT_SECTIONS - 1)) % NUM_TEXT_SECTIONS; update_text = true; } - else if (key_hit(KEY_START)) + else if (key_hit(KEY_START) || key_hit(KEY_SELECT)) { + instant_text = key_hit(KEY_START); // instant with start, not with select exit = true; } if (update_text) @@ -239,7 +239,7 @@ int text_loop(int script) } show_text_box(); tte_erase_rect(0, 0, 240, 160); - ptgb_write(curr_text, false); + ptgb_write(curr_text, instant_text); } wait_for_user_to_continue(false); @@ -247,7 +247,6 @@ int text_loop(int script) hide_text_box(); REG_BG3VOFS = 0; - if (text_exit) { text_exit = false; @@ -332,7 +331,7 @@ int ptgb_write(const byte *text, bool instant, int length) tc->drawgProc(0x79); } wait_for_user_to_continue(false); - scroll_text(text, instant, length, num, tc); + scroll_text(instant, tc); tc->cursorY += tc->font->charH; tc->cursorX = tc->marginLeft; break; @@ -445,22 +444,17 @@ void wait_for_user_to_continue(bool clear_text) } } -void scroll_text(const byte *text, bool instant, int length, int curr_index, TTC *tc) +void scroll_text(bool instant, TTC *tc) { for (int i = 1; i <= tc->font->charH; i++) { REG_BG3VOFS = i; tte_erase_rect(LEFT, TOP - tc->font->charH, RIGHT, TOP + i); - global_next_frame(); + if (!instant) + { + global_next_frame(); + } } - //wait_for_user_to_continue(true); - // Remove current text - //tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); - // Write old text offset up one line - //tte_set_pos(LEFT, TOP - tc->font->charH); - //ptgb_write(text, true, curr_index); - // Remove text that went outside of the box + // Remove text that went outside of the box tte_erase_rect(LEFT, TOP - tc->font->charH, RIGHT, TOP); - // Prepare to continue - //tte_set_pos(LEFT, TOP); } diff --git a/text_helper/fonts/japanese_normal.png b/text_helper/fonts/japanese_normal.png index 68747f397f8c4812272b3b44e3c0680ad4336f03..2d00f15af950cd6fb0a16b931ae88e630195bf9e 100644 GIT binary patch delta 2862 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 diff --git a/text_helper/main.py b/text_helper/main.py index b3d7eff..2bd25b1 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -93,7 +93,7 @@ def split_into_sentences(text: str) -> list[str]: def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lang): # If we can optimize this to remove the spaces, it could save a few bytes. - splitChars = [' ', '、'] + splitChars = [' '] outStr = "" currLine = "" lineCount = 0 From 5ce9a0c47074797b84f9ad59a09f39bfa5cc483f Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 29 Jan 2026 21:20:42 -0500 Subject: [PATCH 25/68] Maintaining spaces after punctuation --- text_helper/main.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 2bd25b1..796e1bf 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -92,16 +92,13 @@ def split_into_sentences(text: str) -> list[str]: return sentences def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lang): - # If we can optimize this to remove the spaces, it could save a few bytes. - splitChars = [' '] outStr = "" currLine = "" lineCount = 0 currWordIndex = 0 lineLength = 0 spaceLength = 0 - for char in splitChars: - sentence = sentence.replace(char, " ") + words = sentence.split() @@ -152,8 +149,13 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lan # Test if adding the word will go over our alloted space elif ((wordLength + lineLength + offset) <= pixelsInLine): # If not, add the word and increase the index - currLine += (word + " ") - lineLength += (wordLength + spaceLength) + if (currWordIndex == (len(words) - 1)): + # Don't add a space to the end of the sentence. + currLine += word + lineLength += wordLength + else: + currLine += (word + " ") + lineLength += (wordLength + spaceLength) currWordIndex += 1 # We need to move to the next line @@ -181,7 +183,8 @@ def convert_char_to_byte(incoming, array, lang): if str(val) == chr(incoming): return index index += 1 - log_warning_error(lang, "Error", f"No match found for char [ {chr(incoming)} ]!") + if chr(incoming) != '_': + log_warning_error(lang, "Error", f"No match found for char [ {chr(incoming)} ]!") return 0 def log_warning_error(lang, type, text): @@ -221,6 +224,12 @@ def convert_item(ogDict, lang): line = line.replace(pair[0], escapeString) #print(line) + # Change all the punctuation marks followed by spaces into being followed by _ . + # These will end up being replaced by spaces anyway in the end (but ignore the error) + puncts = ['.'] + for punct in puncts: + line = line.replace(punct + " ", punct + "_") + split_sents = split_into_sentences(line) index = 0 outStr = "" From 894105fde9bbda3172756e59a19e7e539eeebeb6 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 29 Jan 2026 22:20:56 -0500 Subject: [PATCH 26/68] Adding other punctuations --- text_helper/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 796e1bf..52cab84 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -167,7 +167,6 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lan lineLength = 0 offset = 0 - currLine = currLine.replace("。 ", "。") # Get rid of the space after the Japanese peroid outStr += currLine return lineLength + offset, lineCount, outStr @@ -226,7 +225,7 @@ def convert_item(ogDict, lang): # Change all the punctuation marks followed by spaces into being followed by _ . # These will end up being replaced by spaces anyway in the end (but ignore the error) - puncts = ['.'] + puncts = ['.', '?', '!', '。', '!', '?'] for punct in puncts: line = line.replace(punct + " ", punct + "_") From 5447b395696691bd60684d1724c33af4746ce94a Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 29 Jan 2026 22:42:06 -0500 Subject: [PATCH 27/68] Updating dex menu and selections --- graphics/menu_bars.png | Bin 225 -> 229 bytes include/global_frame_controller.h | 1 + source/global_frame_controller.cpp | 14 +++++++++++++- source/main.cpp | 9 ++++----- source/pokedex.cpp | 13 ++++++------- source/script_var.cpp | 2 +- text_helper/fonts/japanese_normal.png | Bin 2929 -> 3552 bytes text_helper/fonts/latin_normal.png | Bin 3071 -> 3099 bytes text_helper/main.py | 2 +- 9 files changed, 26 insertions(+), 15 deletions(-) diff --git a/graphics/menu_bars.png b/graphics/menu_bars.png index d5f13a874fe7277593fba0fffa840515f9953613..a14932bad5c63395aed04d2c64fd1ceb2a8b23e8 100644 GIT binary patch delta 127 zcmaFJ_>^%%Zbh=Ei(^Q|oVQmTc^MRVSPn)VaMHc9-<;=z%1(hakvG|^8K9t{`DJNf zKLaBZ3x|NhgS`w4kF>=U92!6ZN0=Ep)<=Mu90CITFdY!OVSUbRXCZ^dKKvU&0-mmZ JF6*2UngGgfzZb`hSi(^Q|oVS-9c^MRVSPn)VaMHc9-`q!lZ;~YI@0(f-Fz~?j^6Le1 zj7%&X0w3ZTjvUn&P;h8qVEo9?(H#$FF+F00sfE#ZwlE%HKRa`>f#VjCkf*Dk%Q~lo FCID>6BZdF~ diff --git a/include/global_frame_controller.h b/include/global_frame_controller.h index 8ab0b7a..d92514f 100644 --- a/include/global_frame_controller.h +++ b/include/global_frame_controller.h @@ -31,6 +31,7 @@ void set_treecko(bool val); bool get_treecko_enabled(); u32 fnv1a_hash(unsigned char *data, size_t length); int get_string_length(const byte *str); +int get_string_char_count(const byte *str); void convert_int_to_ptgb_str(int val, byte str[]); void convert_int_to_ptgb_str(int val, byte str[], int min_length); diff --git a/source/global_frame_controller.cpp b/source/global_frame_controller.cpp index f6626b8..081e63c 100644 --- a/source/global_frame_controller.cpp +++ b/source/global_frame_controller.cpp @@ -267,7 +267,7 @@ bool get_treecko_enabled() return treecko_enabled; } -int get_string_length(const byte *str) +int get_string_char_count(const byte *str) { int size = 0; while (str[size] != 0xFF) @@ -277,6 +277,18 @@ int get_string_length(const byte *str) return size; } +int get_string_length(const byte *str) +{ + int size = 0; + int length = 0; + while (str[size] != 0xFF) + { + length += tte_get_glyph_width(str[size]); + size++; + } + return length; +} + void convert_int_to_ptgb_str(int val, byte str[], int min_length) { int div = 1; diff --git a/source/main.cpp b/source/main.cpp index 70bc1fe..a411d29 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -345,10 +345,9 @@ int main_menu_loop() for (int i = 0; i < NUM_MENU_OPTIONS; i++) { text_entry = general_text.get_text_entry(menu_options[i]); - int size = get_string_length(text_entry); - int char_width = (PTGB_BUILD_LANGUAGE == JPN_ID ? 8 : 6); - int x = ((240 - (size * char_width)) / 2); - tte_set_pos(x, ((i * 17) + 80)); + int string_length = get_string_length(text_entry); + int x = ((240 - string_length) / 2); + tte_set_pos(x, ((i * (16 + 10)) + 70)); if (i == curr_selection) { tte_set_ink(INK_WHITE); @@ -438,7 +437,7 @@ static void __attribute__((noinline)) show_intro() general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); text_entry = general_text.get_text_entry(GENERAL_press_start); - press_start_text_length = get_string_length(text_entry); + press_start_text_length = get_string_char_count(text_entry); memcpy(press_start_text, text_entry, press_start_text_length + 1); text_entry = general_text.get_text_entry(GENERAL_intro_legal); diff --git a/source/pokedex.cpp b/source/pokedex.cpp index 92c4166..fa65e60 100644 --- a/source/pokedex.cpp +++ b/source/pokedex.cpp @@ -116,7 +116,7 @@ int pokedex_loop() // TODO: For some reason there is screen tearing here. Probably not noticable on console, // but it should be removed at some point - tte_set_pos(8, 148); + tte_set_pos(8, 146); ptgb_write(kanto_name, true); convert_int_to_ptgb_str(kanto_dex_num, temp_string, 3); ptgb_write(temp_string, true); @@ -126,7 +126,7 @@ int pokedex_loop() convert_int_to_ptgb_str(mew_caught ? 151 : 150, temp_string, 3); ptgb_write(temp_string, true); - tte_set_pos(128, 148); + tte_set_pos(128, 146); ptgb_write(johto_name, true); convert_int_to_ptgb_str(johto_dex_num, temp_string, 3); ptgb_write(temp_string, true); @@ -216,18 +216,17 @@ int pokedex_loop() if (is_caught(dex_shift + i + 1 + mythic_skip)) { - tte_set_pos(dex_x_cord + (3 * 8 / 2), (i * 8 * 2) + 32); + tte_set_pos(dex_x_cord + (3 * 8 / 2), (i * 8 * 2) + 28); temp_string[0] = 0xF7; - temp_string[1] = 0xF8; - temp_string[2] = 0xFF; + temp_string[1] = 0xFF; ptgb_write(temp_string, true); } - tte_set_pos(dex_x_cord + (3 * 8), (i * 8 * 2) + 32); + tte_set_pos(dex_x_cord + (3 * 8), (i * 8 * 2) + 28); convert_int_to_ptgb_str(dex_shift + i + 1 + mythic_skip, temp_string, 3); ptgb_write(temp_string, true); - tte_set_pos(dex_x_cord + (7 * 8), (i * 8 * 2) + 32); + tte_set_pos(dex_x_cord + (7 * 8), (i * 8 * 2) + 28); ptgb_write(is_caught(dex_shift + i + 1 + mythic_skip) ? PKMN_NAMES.get_text_entry(dex_shift + i + 1 + mythic_skip) : undiscovered_text, true); } diff --git a/source/script_var.cpp b/source/script_var.cpp index 3b8d802..c1381bc 100644 --- a/source/script_var.cpp +++ b/source/script_var.cpp @@ -140,7 +140,7 @@ u32 xse_var::get_loc_in_sec30() void textbox_var::set_text(const byte nText[]) { text = nText; - text_length = get_string_length(nText); + text_length = get_string_char_count(nText); } void textbox_var::set_start() diff --git a/text_helper/fonts/japanese_normal.png b/text_helper/fonts/japanese_normal.png index 2d00f15af950cd6fb0a16b931ae88e630195bf9e..34ac4fe03d67731c9159671f9085b9057e7ba86d 100644 GIT binary patch literal 3552 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 Hu(|&Mc4{JX literal 2929 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 M-Bh>snoHb&00zZK!2kdN diff --git a/text_helper/fonts/latin_normal.png b/text_helper/fonts/latin_normal.png index c9312f93708fc0bbcdf6d3f57f18360cc433855a..aec6e5aa1d5db684f2cdb3983dc6d1431137a539 100644 GIT binary patch delta 2974 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 diff --git a/text_helper/main.py b/text_helper/main.py index 52cab84..49e9dd0 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -685,7 +685,7 @@ mainDict = {} textSections = [] fonts = { "International": Font("latin_normal", 1, 256, 16, 16, 16, 16, 16, 16), - "Japanese": Font("japanese_normal", 1, 256, 16, 16, 8, 16, 8, 16), + "Japanese": Font("japanese_normal", 1, 256, 16, 16, 16, 16, 16, 16), } charArrays = { "International": { From 5536c5690b3da39f7813f78a3f6b7016268505ec Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 29 Jan 2026 23:57:21 -0500 Subject: [PATCH 28/68] Fixing full width spaces --- text_helper/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 49e9dd0..84ed21d 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -225,9 +225,11 @@ def convert_item(ogDict, lang): # Change all the punctuation marks followed by spaces into being followed by _ . # These will end up being replaced by spaces anyway in the end (but ignore the error) + spaces = [' ', ' '] puncts = ['.', '?', '!', '。', '!', '?'] - for punct in puncts: - line = line.replace(punct + " ", punct + "_") + for space in spaces: + for punct in puncts: + line = line.replace(punct + space, punct + "_") split_sents = split_into_sentences(line) index = 0 From bc780f6028f0c1c10a612a2c61de0ceb8a3d20ed Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Fri, 30 Jan 2026 15:20:22 -0500 Subject: [PATCH 29/68] Testing bot update --- text_helper/fonts/japanese_normal.png | Bin 3552 -> 3554 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/text_helper/fonts/japanese_normal.png b/text_helper/fonts/japanese_normal.png index 34ac4fe03d67731c9159671f9085b9057e7ba86d..5f21cf9b177e14d0f803d3cc2513fba53cb79875 100644 GIT binary patch literal 3554 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 A(*OVf literal 3552 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 Hu(|&Mc4{JX From 7def9f6b01fca0500bef6d8081e89d6325597694 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Fri, 30 Jan 2026 15:23:20 -0500 Subject: [PATCH 30/68] Undoing test --- text_helper/fonts/japanese_normal.png | Bin 3554 -> 3552 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/text_helper/fonts/japanese_normal.png b/text_helper/fonts/japanese_normal.png index 5f21cf9b177e14d0f803d3cc2513fba53cb79875..34ac4fe03d67731c9159671f9085b9057e7ba86d 100644 GIT binary patch literal 3552 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 Hu(|&Mc4{JX literal 3554 zcmd5<`#02G7eC**7?&C2HsumcOn6L~M5c%#p3ntPlBO`JH-j)lp)llDgbKOe(&Un; z$b|SRQ^+L}lT1WKA!_ot^Xh%xKj8iG-D|J4_u6N#z4ke4e?I4&D|UzW;lvfi0RT8_ zD~rDXfYx0EKq1#nUR6cQx}n$|aIj42SX&n=mAW4IUqlf%5WQa0+uFj+At-w)$J=dE zOEjhq{SKN8zg}}!P0EGjXA*LEPweUg>jV=sHPJ)M@_FH#^TMZXh8%z3QH&Pia_Wuo zz*^;`ZWO5)H0_gFFjg0vBgYA7A5f1*MDzQ_22PN< zD9y!W0eoQQlUm$WcUdaTO@`wRvOzWo!V#s}H<|qaRbtqr2~IoGG|f#ga19`Tkv4Ki z)3j8T@y+55S8j~Gr#(76;3aQ1RnKu4+((hD4=0^vi_E)2J@EF!*Qx#=18o+=B$=gW z9fC7La{KL-#7mvYQnL%aX*-Mji>)y7e;V9VBadUtb@vol^$PV5$e0~XKkq<%R-o8s zR%f<#<`~?=qO9mj+i9z3w#D-UtibCngHxg|`(bgCS0sp9%q`c}c125(V8D=>(YWd< zpXc=cLz1tp=3Z6y=SQ44#=vO=IF*gW8anxmRf~y|7z?WGo#oDX13!{-lf3bv!+?@E zECU9zpI?5Ex>uHQ-u&%aT4r&f$=!k+$DzawdK0}fu4ITnojWxNzGy>a-;6{!8HyP1qK{<%eIOw2YY#=;s~ zLoN*64lzyU{pg86n)LNTz*`DvR&N3KOjKX5EjGWtSbj7sH)*sfx$tmlGsobpO!k@8 zrG3vtm~2-pWABcBgj_BEJIebwQIl!wnm%Nx!_z7n7EI=&K+VcY zMI3>7v$&SSe_TBKSWSaK(-PIo6wn?#diU#ToKiZHWYA;>qzGGR4s{}tz;X~?k(l5% zZ86C=EOusl8|F1nB165ZWGhBRP&pf?Vuy_73~9)EY<|yBT}VS4M%vN#`GV=R$Lp@* z8s{vYbt!+7-j}hIr17-rG*4b0azhE~#e>2!t{I`$z?&(X{K+((E_B=;uSntu+~_=< zG10ym84!10h^E!pwQ(b^DryyvK*Wf$OBiiVl)T|Ec&voycigHUS@OFq41#?cCes?@ zl+M0H?i4CxrI<^qK9$ONe!F`}h@nsA@6L@Pg1p~=wBX% zqtZqfHraPVZ8WsbX)6iU8<$msp8E;%R8D24&3&v=jyVq{d5ToFx??ShpErF8q8#ip zaZ804>(kDq*Ws#XRS@v8^O+5Mi`}`Xs*Y1HB7j^gS-RgN!TRfhWrZ$CPm|$`*kLgB zM_f)K;uvm)mH<0ZKx+EDVQrr|o7dXh2UhBXO)7T&qBjuA}p znW4Zde#ob$r9ynRgvtQb#3K#fKOz$LtS^k$`!cK&<05BBU3Nl~3KC=6RD$dWVaJX; zoYz9EXH_zM+wXEx(cMtOwqH;HuU_4U74&dEGe{PmIQ3ZIH6qd&tm(rR2~vRRwnG(T zn890p(mHRFifSOxukBmB-ihx#-1lpk6Q7q@-qjm_r%ZmSLo%{A zB>Z3tE=gP-G5!Y|w;D_X2>&cEiS70K&9LxATNbuam*aWg>C@cwEoZUME8~rCqD+fzhz4yIC)T^RsytfFe1JcZh}3~1 zM$00`e7n)>{rMuTgrTp;f`5u3yE&^b7h-scW!K4FxGw1YVF$+xpt->DEa{R0OvS*2 zn-`D?2rygSq6k7?Z0e6Zz0%l+){sfQgK9&^6KI}2ZA%UBFqK4PC6{(GNGdm6k^LR1 zx%j7;T_UpN;BCv47m%)Ru=3c(>CIwF`H?;;-=sCX4ja;Q%d-~@mIp|ldByaI4bRx2ea$W-% z+j_C}tKZBb)ev!^yTr8qyw2gk(aGCc_Gt6v(SM21lS3OiK)- z7@@=B;BO6^ohmN~j6LR_2vLcK|GYz(>^Wr1P4tnn=^AKoVL7!=MRGpbtR(Dqbm}&3 zmj9C72`7u-d$CW5hXgtY>Y+p5m!RPqw>u~siFi)4y}}W2VP}K_b!JDntGzTX1t*q3 zOI&O~(PVWM)NK(Ci$lHCU#ZJrBWM+Wfhe6dmmhpMB%7Wmwfp0HOrgkp2N%91YqXZ{Y^TGh|C!#F;L=!$mjk>7)C?nPRt$)PqX*C9 zl1EksP!q!N2E+FFCD#v_(qF>Iy0PX1dBban{3l(szZLep!}Qd`RX+U24=Zm`%+1E) zGgiBE>l>jkQ<&T%!tHe0W)-Fk!uXpS$TL&GWT8F>&Y?@@_EtFPJ9xQX3YP({f+(3ra`&qnT%S-rE>|>Q&F*kT`t(lj0Y&9K)s0m zX;Brd#6=^=kFzF_Ly|Mj9=~z>|EBX-o&+Wsb2qmdBANwZ4a&rSkz8E+wa?%VJWITP zBp7AHO9hY1k}RzGY1?b>+(SO=o5ju{tp-9=fJH#kfGE*1`Mbxdy5-J}K3+_ozM;&&9E!>tpcd z;Xj^uf#j&bXP(FHA^4%}D))LNq?iw_(%JEXwG9fRoREGa?zh?Eu|KB4t1|zm#4lS# zpXhze=oEK&GR_aLOB9J!-ocml#HWkBn9z7TkuqnR?Ro1=+on$2Kppx6i+JlP+z!oB zLC7?kL8$(c1={WPGJTccR=){rbOL%Q=9+9&r8R+ef#v8A;GL<3*U%Rvj_$WMWEOFE zX_TNO(G_Dz86%DFp>7EAX}q`JPT6x~-Q%IJP_D3D=d}GojyS)5ZCC zj=c0xSTJ5qOlc8>!j_IzyQ~;X-c3U0mI9QGh8*37QJcXG_4@X&%`nHriXjYq?hI$# z0%-%mCYWRagds)b7+g~$erx+a-?V;DEZcq!FUZozzkS~y{QHAuZF$JzzBwcE-x0wk A(*OVf From 8b846a223bd04bbe229580f98f1e1d521ce80009 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 1 Feb 2026 19:44:05 -0500 Subject: [PATCH 31/68] Adjusting text box width --- graphics/textBoxBG.png | Bin 410 -> 406 bytes include/text_engine.h | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/textBoxBG.png b/graphics/textBoxBG.png index 3c1f9b9f73a508f08b386fcb8289e21c030043e5..a8d42934ed9570d89be5b90261747518462ccfbb 100644 GIT binary patch delta 236 zcmbQmJdJrmE~CiAJd?c2!s}-bt=Y|aIB_Tc-?vsZ@!z|jf2k{f@opM>Oq%@V^9`3D z&9LKtpUSjty1Vvib;193^E@Utl$w4#^~v)IV|K~JhTN+c!R(_k_sjRq+Q&5MztyZG z>)CQ1_r=ZeSK@rZ&7LD4BQn81X{tZVL`R9dhTk@Gq%7PR^A|S$5LvPDe)#k`&8GY2 zte$;9=vkU3^zca>j)g+oBWp@D&s3C5_`UhtF6^|S7*Yr9jvT j&97jqyYFY%+xqXEt$v-&GecMoGXR07tDnm{r-UW|@X?J}k_)b^8$Upw@_l|g*<-_Qkqe~adn$?}*)BKhABNlhF Date: Mon, 2 Feb 2026 12:09:33 -0500 Subject: [PATCH 32/68] Fixing scrolling --- source/background_engine.cpp | 2 +- source/text_engine.cpp | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/source/background_engine.cpp b/source/background_engine.cpp index cf39cb4..93ad621 100644 --- a/source/background_engine.cpp +++ b/source/background_engine.cpp @@ -36,7 +36,7 @@ void reset_textbox() { tte_erase_rect(0, 0, H_MAX, V_MAX); reload_textbox_background(); - tte_set_pos(1 * 8, 14 * 8); + tte_set_pos(LEFT, TOP); tte_set_margins(LEFT, TOP, RIGHT, BOTTOM); } diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 98437e4..8047cf0 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -132,7 +132,6 @@ int text_loop(int script) { tte_set_pos(LEFT, TOP); tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); - REG_BG3VOFS = 0; ptgb_write(curr_text, false); } @@ -245,7 +244,6 @@ int text_loop(int script) wait_for_user_to_continue(false); update_text = true; hide_text_box(); - REG_BG3VOFS = 0; if (text_exit) { @@ -440,7 +438,6 @@ void wait_for_user_to_continue(bool clear_text) { tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); tte_set_pos(LEFT, TOP); - REG_BG3VOFS = 0; } } @@ -455,6 +452,15 @@ void scroll_text(bool instant, TTC *tc) global_next_frame(); } } - // Remove text that went outside of the box - tte_erase_rect(LEFT, TOP - tc->font->charH, RIGHT, TOP); + REG_BG3VOFS = 0; + + // The map starts at tile 0 in the top left, increases by 1 as you go down, and then loops back at the top. + for (int i = 0; i < 30; i++) + { + tonccpy(&tile_mem[TEXT_CBB][14 + (i * 20)], &tile_mem[TEXT_CBB][16 + (i * 20)], 2 * 2 * 32); + } + + // Remove text that went outside of the box and set the position + tte_erase_rect(LEFT, TOP + tc->font->charH, RIGHT, BOTTOM); + tte_set_pos(LEFT, BOTTOM - (8 + (2 * tc->font->charH))); // The newline will trigger after this and move it down a line } From ede6995a9431d51c4a38f5193efc32998a7dbe19 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 2 Feb 2026 14:55:37 -0500 Subject: [PATCH 33/68] Fixing extra spaces in strings --- text_helper/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/text_helper/main.py b/text_helper/main.py index 84ed21d..7028c1e 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -185,7 +185,7 @@ def convert_char_to_byte(incoming, array, lang): if chr(incoming) != '_': log_warning_error(lang, "Error", f"No match found for char [ {chr(incoming)} ]!") return 0 - + def log_warning_error(lang, type, text): nType = type + "s" nText = type + ": " + text @@ -295,6 +295,16 @@ def convert_item(ogDict, lang): # A space right before a newline just takes up space newStr = newStr.replace(" Ň", "Ň") + newStr = newStr.replace("_Ň", "Ň") + # A space after a newline looks weird + newStr = newStr.replace("Ň ", "Ň") + newStr = newStr.replace("Ň_", "Ň") + # Same thing with a scroll + newStr = newStr.replace("Ş ", "Ş") + newStr = newStr.replace("Ş_", "Ş") + # Same thing with a new textbox + newStr = newStr.replace("ȼ ", "ȼ") + newStr = newStr.replace("ȼ_", "ȼ") # Newlines shouldn't happen right after a new textbox newStr = newStr.replace("ȼŇ", "ȼ") # Nor should newlines be right before a new textbox From 1862e4be83df97af09f7e630325d8d288ac8be20 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 2 Feb 2026 21:53:48 -0500 Subject: [PATCH 34/68] Updating text generator --- text_helper/main.py | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/text_helper/main.py b/text_helper/main.py index 7028c1e..ec5d0a5 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -244,24 +244,27 @@ def convert_item(ogDict, lang): if (out == "ȼ"): offset = 0 currLine = 0 - if outStr and outStr[-1] == " ": + if (outStr and (outStr[-1] == " ") or (outStr[-1] == 'Ň') or (outStr[-1] == 'Ş')): outStr = outStr[:-1] outStr += "ȼ" index += 1 elif (currLine < (numLines + int(include_scrolling))): #print(split_sents[index]) index += 1 + if ((outStr and out and outStr[-1] == 'ȼ') and ((out[0] == ' ') or (out[0] == 'Ň') or (out[0] == 'Ş'))): + out = out[:-1] outStr += out else: - if outStr and outStr[-1] == " ": + if not include_box_breaks: + log_warning_error(lang, "Error", f"Attempted to make a new text box when disabled, sentence \"{outStr}\" is too long!") + elif (outStr and ((outStr[-1] == " ") or (outStr[-1] == 'Ň') or (outStr[-1] == 'Ş'))): outStr = outStr[:-1] outStr += "ȼ" # new textbox character offset = 0 currLine = 0 escapeCount += 1 - #print(index) - if not include_box_breaks: - log_warning_error(lang, "Error", f"Made a line break when disabled, sentence \"{outStr}\" is too long!") + #print(index) + if escapeCount == 100: log_warning_error(lang, "Error", f"Sentence \"{out}\" is too long!") @@ -274,6 +277,9 @@ def convert_item(ogDict, lang): splitBoxes = outStr.split('ȼ') outIndex = 0 for box in splitBoxes: + if box and ((box[0] == " ") or (box[0] == "_")): + box = box[1:] + outIndex += 1 # Make sure both kinds of newlines are being accounted for box = box.replace('Ş', 'Ň') splitLines = box.split('Ň') @@ -290,33 +296,10 @@ def convert_item(ogDict, lang): outBox += split + breakChar outIndex += 1 i += 1 - newStr += f'{outBox}ȼ' + if (outBox and (outBox[-1] != 'ȼ') and (outBox[-1] != 'Ň')): + newStr += f'{outBox}ȼ' newStr = newStr[:-1] # remove the last ȼ - # A space right before a newline just takes up space - newStr = newStr.replace(" Ň", "Ň") - newStr = newStr.replace("_Ň", "Ň") - # A space after a newline looks weird - newStr = newStr.replace("Ň ", "Ň") - newStr = newStr.replace("Ň_", "Ň") - # Same thing with a scroll - newStr = newStr.replace("Ş ", "Ş") - newStr = newStr.replace("Ş_", "Ş") - # Same thing with a new textbox - newStr = newStr.replace("ȼ ", "ȼ") - newStr = newStr.replace("ȼ_", "ȼ") - # Newlines shouldn't happen right after a new textbox - newStr = newStr.replace("ȼŇ", "ȼ") - # Nor should newlines be right before a new textbox - newStr = newStr.replace("Ňȼ", "ȼ") - # Nor should a new textbox be after a new textbox - newStr = newStr.replace("ȼȼ", "ȼ") - # Nor should a new scroll be after a new textbox - newStr = newStr.replace("Şȼ", "Ş") - # Nor should a new scroll be after a new textbox - newStr = newStr.replace("ȼŞ", "ȼ") - - if len(newStr) > 1023: newStr = newStr[:1023] log_warning_error(lang, "Warning", f"String {newStr} exceeds character limit of 1023 and has been truncated.") From e257bafb2383bd7f929c690bbe9b155c7d0810eb Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 2 Feb 2026 22:25:07 -0500 Subject: [PATCH 35/68] Disabling error printout and giving the legal screen more space --- source/main.cpp | 2 +- text_helper/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/main.cpp b/source/main.cpp index a411d29..e9ec96a 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -214,7 +214,7 @@ int credits() { if (update) { - create_textbox(4, 1, 160, 80, true); + create_textbox(1, 1, 200, 120, true); show_text_box(); ptgb_write(credits_text_table.get_text_entry(curr_credits_num), true); update = false; diff --git a/text_helper/main.py b/text_helper/main.py index ec5d0a5..527ef56 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -191,7 +191,7 @@ def log_warning_error(lang, type, text): nText = type + ": " + text if nText not in mainDict[lang.name][nType].values(): mainDict[lang.name][nType][max(mainDict[lang.name][nType].keys(), default =- 1) + 1] = nText - print(nText) + #print(nText) def hash_excel(path): sheets = pd.read_excel(path, sheet_name=None) From 210add3f6271db861ee31d05341f433026371b43 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 2 Feb 2026 22:42:31 -0500 Subject: [PATCH 36/68] Making the real changes this time --- source/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/main.cpp b/source/main.cpp index e9ec96a..627d3da 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -388,8 +388,8 @@ int main_menu_loop() // Legal stuff static void show_legal_text(const u8 *intro_text) { - tte_set_margins(8, 8, H_MAX - 8, V_MAX - 8); - tte_set_pos(8, 8); + tte_set_margins(0, 0, H_MAX, V_MAX); + tte_set_pos(0, 0); tte_set_ink(INK_ROM_COLOR); ptgb_write(intro_text, true); bool wait = true; From cdc653a46e18178932ff7e1fb889714065525072 Mon Sep 17 00:00:00 2001 From: Adri1 Date: Wed, 4 Feb 2026 13:04:56 +0100 Subject: [PATCH 37/68] =?UTF-8?q?Menu=20bars=20adjustments=20;=20POK=C3=A9?= =?UTF-8?q?DEx=20to=20DREAM=20DEX=20in=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphics/dexBG.png | Bin 438 -> 410 bytes graphics/menu_bars.png | Bin 229 -> 232 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/graphics/dexBG.png b/graphics/dexBG.png index 77ab9d9926d09c411549eb62139b6881666c8c7b..399a0a2cafd3a3e7bb9aa0c802b4b06e86f72575 100644 GIT binary patch delta 310 zcmdnSJd1fkZoP!3i(^Q|oVRxZxeh7F9RDc0Ncc~?>;wHT_e9O!FdbYV{Dy09`NKKE zN>vks#J~PkbWVL^yxi{Gxzwj7r%m6VTN9J|uHiq+wreem7Ms2@`T8PiJ;&+{jYsRZ zUB9CIHF5buov9q{&u+{v+mKakaW1>5*0}cNtPSZQXRL013_W5ZSzj38@%lpSW&gXg z!+&gCwfvIj?}(BMrH_}@>iGZOA*7RZB);ZOP*VSqUre`?Zp!YOu*Z`zkL|sAQ~OGr zIop5eM<3k&so=!Vh<67MkOlOCy6n04|F8a){_>QZz=WAkWf&8H;KBO#_wrBQtZcla Q9m)U%p00i_>zopr06Vs=%>V!Z delta 338 zcmbQmyp4H6ZoP@8i(^Q|oVRz*<}xb^v^~^S5g}s>}~gdS~BtV9sT7+Q%^JU>=A#rby~)n+3xcXOI(lITCsKQ zhK{I-m)3ggPkQgYXVKS9;%4u))-8;_EM-37@eilRm)90di4yL=RbM42`&g%bw{AuH z%G5PhKkwWQWbB;vI?ZBP{>J%QH=ngtOQyW}U3O#ZvsG6Y9V< diff --git a/graphics/menu_bars.png b/graphics/menu_bars.png index a14932bad5c63395aed04d2c64fd1ceb2a8b23e8..fc91ebeba7391950eef9d726498fc1ec9fe8b5c9 100644 GIT binary patch delta 94 zcmaFL_=0glE@RrnJdL0S301LIlpPuv7@1f&1U|$uIMld+SPCGaj|>ezI$#{8M~n<8 b;KNkjZ(JrN6VE^%%E@Se Date: Wed, 4 Feb 2026 21:28:50 -0500 Subject: [PATCH 38/68] File cleanup --- payload_builder copy.cpp | 996 --------------------------------------- vbalink.ini | Bin 1 -> 0 bytes 2 files changed, 996 deletions(-) delete mode 100644 payload_builder copy.cpp delete mode 100644 vbalink.ini diff --git a/payload_builder copy.cpp b/payload_builder copy.cpp deleted file mode 100644 index 3cf5357..0000000 --- a/payload_builder copy.cpp +++ /dev/null @@ -1,996 +0,0 @@ -#include "payloads/payload_builder.h" -#include "gb_rom_values/base_gb_rom_struct.h" -#include "payloads/z80_asm.h" -#include "../../../include/debug_mode.h" -#include - -#define DATA_LOC (SHOW_DATA_PACKETS ? curr_rom.transferStringLocation : curr_rom.wEnemyMonSpecies) -#define DEBUG_PAYLOADS (false && DEBUG_MODE) - -void init_payload(byte *payload_buffer, const GB_ROM &curr_rom, int type, bool debug) -{ - if (DEBUG_PAYLOADS) - { - debug = true; - } - - (void)type; - /* 10 RNG bytes - 8 Preamble bytes - 418 / 441 Party bytes - 7 Preamble bytes - 194 Patch list bytes (last 2 are unused) - - 637 / 660 total bytes - */ - if ((curr_rom.generation == 1 && curr_rom.version != YELLOW_ID)) - { - std::vector jump_vector; - std::vector var_vector; - - z80_asm_handler z80_rng_seed(0x0A, curr_rom.wSerialOtherGameboyRandomNumberListBlock + 8); - z80_asm_handler z80_payload(0x1AA, curr_rom.wSerialEnemyDataBlock); - z80_asm_handler z80_patchlist(0xC9, curr_rom.wSerialEnemyMonsPatchList); - - z80_jump asm_start(&jump_vector); - z80_jump save_box(&jump_vector); - z80_jump remove_array_loop(&jump_vector); - z80_jump packet_loop(&jump_vector); - z80_jump fe_bypass(&jump_vector); - z80_jump send_packet_loop(&jump_vector); - - z80_variable array_counter(&var_vector, 1, 0x00); // 1 byte to store the current array counter - z80_variable removal_array(&var_vector); // 40 byte storage for list of Pokemon to remove, plus a permanent array terminator - - byte removal_array_data[41]; - for (int i = 0; i < 41; i++) - { - if (debug) - { - removal_array_data[i] = (i < 30 ? (29 - i) : 0xFF); - } - else - { - removal_array_data[i] = (i < 30 ? 0xFD : 0xFF); - } - } - removal_array.load_data(41, removal_array_data); - z80_variable transfer_wait_string(&var_vector, 13, // SENDING DATA - 0x92, 0x84, 0x8D, 0x83, 0x88, 0x8D, 0x86, 0x7F, 0x83, 0x80, 0x93, 0x80, 0x50); - z80_variable custom_name(&var_vector, 11, // FENNEL - 0x85, 0x84, 0x8D, 0x8D, 0x84, 0x8B, 0x50, 0x50, 0x50, 0x50, 0x50); - - // RNG Seed - // Location of the entrance vector - z80_rng_seed.index = 5; // Set the entrance vector - z80_rng_seed.JP(asm_start.place_direct_jump(&z80_rng_seed) | T_U16); - - // Preamble - // At 0x00, 0x07 in length - // Must be filled with 0xFD - for (int i = 0; i < 7; i++) - { - z80_payload.add_byte(0xFD); - } - - // Rival name - // At 0x07, 0x0B in length - // Set to stored name - z80_payload.index = 0x07; - custom_name.insert_variable(&z80_payload); - - // Number of Pokemon - // At 0x12, 0x01 in length - // Does not need to be set - z80_payload.index = 0x12; - z80_payload.add_byte(0x06); - - // Pokemon list - // At 0x13, can be up to 0x1A2 / 0x1B9 bytes in length. - // Calculate the number of Pokemon names that need to be printed, - // and add them to the list. Then terminate the list. - z80_payload.index = 0x13; - int distance = curr_rom.stack_overwrite_location - curr_rom.print_string_start; - distance /= 20; // Automatically truncated, so it won't overshoot - - for (int i = 0; i < distance; i++) - { - z80_payload.add_byte(curr_rom.short_pkmn_name); - } - z80_payload.add_byte(curr_rom.pointer_pkmn_name); - z80_payload.add_byte(0xFF); - - // Patchlist preamble - // At 0x1B4 / 0x1D7, 0x07 in length - // Set as five 0xFD and two 0xFF - - for (int i = 0; i < 7; i++) - { - z80_patchlist.add_byte(i < 5 ? 0xFD : 0xFF); - } - - // Patchlist - // At 0x1BB / 0x1DE, 0xC2 in length (0xC4, but the last 2 are unused) - // Fill with custom code - asm_start.set_start(&z80_patchlist); - - /* Write transferring message to screen: */ - // call ClearScreen - z80_patchlist.CALL(curr_rom.clearScreen | T_U16); - z80_patchlist.LD(HL, curr_rom.textBorderUppLeft | T_U16); - z80_patchlist.LD(C, curr_rom.textBorderWidth | T_U8); - z80_patchlist.LD(B, curr_rom.textBorderHeight | T_U8); - z80_patchlist.CALL(curr_rom.CableClub_TextBoxBorder | T_U16); - z80_patchlist.LD(HL, curr_rom.transferStringLocation | T_U16); - z80_patchlist.LD(DE, transfer_wait_string.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.CALL(curr_rom.placeString | T_U16); - - /* Build the packet */ - // HL is the current data pointer - // DE is the destination pointer - // A is the checksum - // B is the 0xFE flag byte - // C is the counter - send_packet_loop.set_start(&z80_patchlist); - - z80_patchlist.LD(HL, (DATA_LOC + PACKET_SIZE + 3) | T_U16); // Load the next data location into HL - z80_patchlist.LD(A, HLI_PTR); - z80_patchlist.LD(H, HL_PTR); - z80_patchlist.LD(L, A); - z80_patchlist.LD(DE, (DATA_LOC + 2) | T_U16); // Enemy Pokemon data, should be unused - z80_patchlist.XOR(A, A); // Clear the register - z80_patchlist.LD(B, A); // Clear B as well - z80_patchlist.LD(C, A); // Clear C as well - z80_patchlist.PUSH(AF); - packet_loop.set_start(&z80_patchlist); - z80_patchlist.LD(B, 0x00 | T_U8); // Reset the flag byte - z80_patchlist.POP(AF); - z80_patchlist.ADD(A, HL_PTR); // Add the current data to the checksum - z80_patchlist.PUSH(AF); - z80_patchlist.LD(A, 0xFE); - z80_patchlist.CP(A, HL_PTR); // Compare the current data to 0xFE - z80_patchlist.LD(A, HLI_PTR); // Load HL's data into A for modification (if need be) - - // If HL's data is 0xFE - z80_patchlist.JR(NZ_F, fe_bypass.place_relative_jump(&z80_patchlist) | T_I8); - z80_patchlist.DEC(A); - z80_patchlist.INC(B); // Set flag - - fe_bypass.set_start(&z80_patchlist); - z80_patchlist.LD(DE_PTR, A); // Place the data in - z80_patchlist.INC(DE); - z80_patchlist.PUSH(AF); - z80_patchlist.LD(A, B); - z80_patchlist.LD(DE_PTR, A); // Place the flag in as well - z80_patchlist.POP(AF); - z80_patchlist.INC(DE); - z80_patchlist.INC(C); - z80_patchlist.LD(A, DATA_PER_PACKET - 1); - z80_patchlist.CP(A, C); - z80_patchlist.JR(NC_F, packet_loop.place_relative_jump(&z80_patchlist) | T_I8); // If all the data has been set, send the rest of the data - z80_patchlist.POP(AF); - z80_patchlist.RES(7 | T_BIT, A); // Reset bit 7 of the checksum, guaranteeing that it will never be 0xFE - z80_patchlist.LD(DE_PTR, A); - z80_patchlist.INC(DE); - z80_patchlist.LD(A, H); - z80_patchlist.LD(DE_PTR, A); - z80_patchlist.INC(DE); - z80_patchlist.LD(A, L); - z80_patchlist.LD(DE_PTR, A); - - // z80_patchlist.LD(HL, curr_rom.garbageDataLocation | T_U16); - - /* Transfer box data packet: */ - z80_patchlist.LD(A, (debug ? 0x02 : 0x01) | T_U8); // Make sure GB is the slave, master if debug - z80_patchlist.LDH((curr_rom.hSerialConnectionStatus & 0xFF) | T_U8, A); // Since hSerialConnectionStatus is at 0xFFxx we can use this method instead - z80_patchlist.LD(HL, DATA_LOC | T_U16); - z80_patchlist.LD(HL_PTR, 0xFD | T_U8); // set the start of the data to 0xFD so Serial_ExchangeBytes is happy - z80_patchlist.INC(HL); - z80_patchlist.LD(HL_PTR, 0x00 | T_U8); // add a 0x00 after the 0xFD to prevent further 0xFDs from being interpreted as part of the preamble - z80_patchlist.DEC(HL); // Reset HL back so it points to 0xFD - z80_patchlist.LD(DE, (DATA_LOC + PACKET_SIZE) | T_U16); // location to put stored data - z80_patchlist.LD(BC, PACKET_SIZE | T_U16); - if (debug) // Don't call serialExchangeBytes if debug is enabled - { - z80_patchlist.index += 3; - } - else - { - z80_patchlist.CALL(curr_rom.Serial_ExchangeBytes | T_U16); - } - - z80_patchlist.LD(A, (DATA_LOC + PACKET_SIZE + 3 + 1) | T_U16); - z80_patchlist.CP(A, 0xFF); - z80_patchlist.JR(NZ_F, send_packet_loop.place_relative_jump(&z80_patchlist) | T_I8); - - /* Recieve the Pokemon to remove */ - z80_patchlist.LD(HL, curr_rom.hSerialConnectionStatus | T_U16); // This can also be shortened - z80_patchlist.LD(HL_PTR, (debug ? 0x02 : 0x01) | T_U8); // Make sure GB is the slave, master if debug - z80_patchlist.LD(HL, curr_rom.garbageDataLocation | T_U16); - z80_patchlist.LD(DE, removal_array.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.LD(BC, 0x001E | T_U16); // Preamble does *not* count - if (debug) // Don't add in the Serial_ExchangeBytes call if in debug - { - z80_patchlist.index += 3; - } - else - { - z80_patchlist.CALL(curr_rom.Serial_ExchangeBytes | T_U16); - } - - /* Remove the transfered Pokemon */ - z80_patchlist.LD(HL, curr_rom.wRemoveMonFromBox | T_U16); - z80_patchlist.LD(HL_PTR, 0x01 | T_U8); // != 0x00 specifies the current box - remove_array_loop.set_start(&z80_patchlist); - z80_patchlist.LD(A, array_counter.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.LD(E, A); - z80_patchlist.LD(D, 0x00 | T_U8); - z80_patchlist.LD(HL, removal_array.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.ADD(HL, DE); - z80_patchlist.INC(A); - z80_patchlist.LD(array_counter.place_ptr(&z80_patchlist) | T_U16, A); - z80_patchlist.LD(A, curr_rom.wBoxCount | T_U16); - z80_patchlist.LD(B, A); - z80_patchlist.LD(A, HL_PTR); - z80_patchlist.CP(A, 0xFF | T_U8); - z80_patchlist.JR(Z_F, save_box.place_relative_jump(&z80_patchlist) | T_I8); - z80_patchlist.CP(A, B); - z80_patchlist.JR(NC_F, remove_array_loop.place_relative_jump(&z80_patchlist) | T_I8); - z80_patchlist.LD(HL, curr_rom.wWhichPokemon | T_U16); - z80_patchlist.LD(HL_PTR, A); - if (DONT_REMOVE_PKMN) - { - z80_patchlist.index += 3; - } - else - { - z80_patchlist.CALL(curr_rom._RemovePokemon | T_U16); - } - z80_patchlist.JR((remove_array_loop.place_relative_jump(&z80_patchlist) & 0xFF) | T_I8); - - /* Save the current box */ - save_box.set_start(&z80_patchlist); - z80_patchlist.LD(B, (curr_rom.SaveSAVtoSRAM1 >> 16) | T_U8); // Load ROM Bank - z80_patchlist.LD(HL, curr_rom.SaveSAVtoSRAM1 | T_U16); - z80_patchlist.CALL(curr_rom.Bankswitch | T_U16); - z80_patchlist.LD(B, (curr_rom.SaveSAVtoSRAM1 >> 16) | T_U8); // Load ROM Bank - z80_patchlist.LD(HL, curr_rom.SaveSAVtoSRAM2 | T_U16); // TODO: We probably don't have to load the ROM bank twice - z80_patchlist.CALL(curr_rom.Bankswitch | T_U16); - z80_patchlist.JP(curr_rom.SoftReset | T_U16); - - // z80_patchlist.index += 5; - - array_counter.insert_variable(&z80_patchlist); - removal_array.insert_variable(&z80_payload); - transfer_wait_string.insert_variable(&z80_patchlist); - - // This payload works by placing Pokemon ID 0xFC's name in the stack, and causing a return to CD8E, - // which is part of the RNG seed. From there we can jump anywhere- and we choose to jump to D887, - // which is the rival's name. This code fixes the stack and jumps to the patchlist, which is where - // our final code is. - - // Update all the pointers - for (unsigned int i = 0; i < var_vector.size(); i++) - { - var_vector.at(i)->update_ptrs(); - } - for (unsigned int i = 0; i < jump_vector.size(); i++) - { - jump_vector.at(i)->update_jumps(); - } - - // Combine the vectors into the full payload - u8 *cur_out = payload_buffer; - memcpy(cur_out, z80_rng_seed.data_vector.data(), z80_rng_seed.data_vector.size()); - cur_out += z80_rng_seed.data_vector.size(); - memcpy(cur_out, z80_payload.data_vector.data(), z80_payload.data_vector.size()); - cur_out += z80_payload.data_vector.size(); - memcpy(cur_out, z80_patchlist.data_vector.data(), z80_patchlist.data_vector.size()); - cur_out += z80_patchlist.data_vector.size(); - - return; - } - - else if ((curr_rom.generation == 1 && curr_rom.version == YELLOW_ID)) - { - std::vector jump_vector; - std::vector var_vector; - - z80_asm_handler z80_rng_seed(0x0A, curr_rom.wSerialOtherGameboyRandomNumberListBlock + 8); - z80_asm_handler z80_payload(0x1AA, curr_rom.wSerialEnemyDataBlock - 8); // Subtracting 8 is because the data is shifted after patching, removing part of the enemy name. May change depending on language - z80_asm_handler z80_patchlist(0xC9, curr_rom.wSerialEnemyMonsPatchList); - - z80_jump asm_start(&jump_vector); - z80_jump save_box(&jump_vector); - z80_jump remove_array_loop(&jump_vector); - z80_jump packet_loop(&jump_vector); - z80_jump fe_bypass(&jump_vector); - z80_jump send_packet_loop(&jump_vector); - z80_jump skip_enemy_write(&jump_vector); - - z80_jump test_loop(&jump_vector); - - z80_variable array_counter(&var_vector, 1, 0x00); // 1 byte to store the current array counter - z80_variable removal_array(&var_vector); // 40 byte storage for list of Pokemon to remove, plus a permanent array terminator - - byte removal_array_data[41]; - for (int i = 0; i < 41; i++) - { - if (debug) - { - removal_array_data[i] = (i < 30 ? (29 - i) : 0xFF); - } - else - { - removal_array_data[i] = (i < 30 ? 0xFD : 0xFF); - } - } - removal_array.load_data(41, removal_array_data); - z80_variable transfer_wait_string(&var_vector, 13, // SENDING DATA - 0x92, 0x84, 0x8D, 0x83, 0x88, 0x8D, 0x86, 0x7F, 0x83, 0x80, 0x93, 0x80, 0x50); - z80_variable custom_name(&var_vector, 11, // FENNEL - 0x85, 0x84, 0x8D, 0x8D, 0x84, 0x8B, 0x50, 0x50, 0x50, 0x50, 0x50); - - // RNG Seed: - // at 0x00, 0x0A in length - // Can be empty - - // Preamble - // At 0x00, 0x07 in length - // Must be filled with 0xFD - for (int i = 0; i < 7; i++) - { - z80_payload.add_byte(0xFD); - } - - // Rival name - // At 0x07, 0x0B in length - // Set to stored name - z80_payload.index = 0x07; - custom_name.insert_variable(&z80_payload); - - // Number of Pokemon - // At 0x12, 0x01 in length - // 2 or higher, 0xE8 for JPN - z80_payload.index = 0x12; - z80_payload.add_byte(0x02); - - // Pokemon list - // At 0x13, is usually 6 bytes and 1 terminator - for (int i = 0; i < 7; i++) - { - z80_payload.add_byte(i < 6 ? i + 1 : 0xFF); - } - - // Pokemon data - // Specific for each language - - // Mon #1 HP - z80_payload.add_bytes(2, - 0x00, 0x00); - - // Mon #2 data - z80_payload.index = 0x46; - z80_payload.add_bytes(9, - 0x4E, 0x4E, 0x4E, 0x00, 0x01, 0x80, 0x80, 0xD4, 0xCF); - - z80_payload.add_bytes(2, asm_start.place_pointer(&z80_payload), 0x00); // These values must not have any control characters in them. - - z80_payload.add_bytes(33, - 0x4E, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x4E, 0x50, 0x00, 0x00, - 0x00, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x59); - - // Mon #2 name - z80_payload.index = 0x16F; - for (int i = 0; i < 11; i++) - { - z80_payload.add_byte(0x4E); - } - - // Now that the ACE has been set up, we can use the space between Mon #2's data and Mon #2's name to put our code. - // Since this space is larger than the patchlist, we will be able to fit everything in it - - // Patchlist preamble - // At 0x1B4 0x07 in length - // Set as five 0xFD, the 0xFF will be added as part of the patch list. - for (int i = 0; i < 5; i++) - { - z80_patchlist.add_byte(0xFD); - } - - z80_payload.index = 0x72; - - array_counter.insert_variable(&z80_payload); - removal_array.insert_variable(&z80_payload); - transfer_wait_string.insert_variable(&z80_payload); - - asm_start.set_start(&z80_payload); - - z80_payload.CALL(0x2233 | T_U16); // Stop the battle music - - /* Write transferring message to screen: */ - // call ClearScreen - z80_payload.CALL(curr_rom.clearScreen | T_U16); - z80_payload.LD(HL, curr_rom.textBorderUppLeft | T_U16); - z80_payload.LD(C, curr_rom.textBorderWidth | T_U8); - z80_payload.LD(B, curr_rom.textBorderHeight | T_U8); - z80_payload.CALL(curr_rom.CableClub_TextBoxBorder | T_U16); - z80_payload.LD(HL, curr_rom.transferStringLocation | T_U16); - z80_payload.LD(DE, transfer_wait_string.place_ptr(&z80_payload) | T_U16); - z80_payload.CALL(curr_rom.placeString | T_U16); - z80_payload.JR(skip_enemy_write.place_relative_jump(&z80_payload) | T_I8); // This is a simple way to avoid the enemy name placement. Might have to modify for other languages - - z80_payload.index = 0xCA; - - skip_enemy_write.set_start(&z80_payload); - - // Reload the save data to remove any data that was overwritten during exploit entry - z80_payload.LD(B, (curr_rom.LoadCurrentBoxData >> 16) | T_U8); - z80_payload.LD(HL, curr_rom.LoadCurrentBoxData | T_U16); - z80_payload.CALL(curr_rom.Bankswitch | T_U16); - if (!debug) - { - /* Build the packet */ - // HL is the current data pointer - // DE is the destination pointer - // A is the checksum - // B is the 0xFE flag byte - // C is the counter - send_packet_loop.set_start(&z80_payload); - - z80_payload.LD(HL, (DATA_LOC + PACKET_SIZE + 3) | T_U16); // Load the next data location into HL - z80_payload.LD(A, HLI_PTR); - z80_payload.LD(H, HL_PTR); - z80_payload.LD(L, A); - z80_payload.LD(DE, (DATA_LOC + 2) | T_U16); // Enemy Pokemon data, should be unused - z80_payload.XOR(A, A); // Clear the register - z80_payload.LD(B, A); // Clear B as well - z80_payload.LD(C, A); // Clear C as well - z80_payload.PUSH(AF); - packet_loop.set_start(&z80_payload); - z80_payload.LD(B, 0x00 | T_U8); // Reset the flag byte - z80_payload.POP(AF); - z80_payload.ADD(A, HL_PTR); // Add the current data to the checksum - z80_payload.PUSH(AF); - z80_payload.LD(A, 0xFE); - z80_payload.CP(A, HL_PTR); // Compare the current data to 0xFE - z80_payload.LD(A, HLI_PTR); // Load HL's data into A for modification (if need be) - - // If HL's data is 0xFE - z80_payload.JR(NZ_F, fe_bypass.place_relative_jump(&z80_payload) | T_I8); - z80_payload.DEC(A); - z80_payload.INC(B); // Set flag - - fe_bypass.set_start(&z80_payload); - z80_payload.LD(DE_PTR, A); // Place the data in - z80_payload.INC(DE); - z80_payload.PUSH(AF); - z80_payload.LD(A, B); - z80_payload.LD(DE_PTR, A); // Place the flag in as well - z80_payload.POP(AF); - z80_payload.INC(DE); - z80_payload.INC(C); - z80_payload.LD(A, DATA_PER_PACKET - 1); - z80_payload.CP(A, C); - z80_payload.JR(NC_F, packet_loop.place_relative_jump(&z80_payload) | T_I8); // If all the data has been set, send the rest of the data - z80_payload.POP(AF); - z80_payload.RES(7 | T_BIT, A); // Reset bit 7 of the checksum, guaranteeing that it will never be 0xFE - z80_payload.LD(DE_PTR, A); - z80_payload.INC(DE); - z80_payload.LD(A, H); - - z80_payload.LD(DE_PTR, A); - z80_payload.INC(DE); - z80_payload.LD(A, L); - z80_payload.LD(DE_PTR, A); - - /* Transfer box data packet: */ - z80_payload.LD(A, (debug ? 0x02 : 0x01) | T_U8); // Make sure GB is the slave, master if debug - z80_payload.LDH((curr_rom.hSerialConnectionStatus & 0xFF) | T_U8, A); // Since hSerialConnectionStatus is at 0xFFxx we can use this method instead - z80_payload.LD(HL, DATA_LOC | T_U16); - z80_payload.LD(HL_PTR, 0xFD | T_U8); // set the start of the data to 0xFD so Serial_ExchangeBytes is happy - z80_payload.INC(HL); - z80_payload.LD(HL_PTR, 0x00 | T_U8); // add a 0x00 after the 0xFD to prevent further 0xFDs from being interpreted as part of the preamble - z80_payload.DEC(HL); // Reset HL back so it points to 0xFD - z80_payload.LD(DE, (DATA_LOC + PACKET_SIZE) | T_U16); // location to put stored data - z80_payload.LD(BC, PACKET_SIZE | T_U16); - if (debug) // Don't call serialExchangeBytes if debug is enabled - { - z80_payload.index += 3; - } - else - { - z80_payload.CALL(curr_rom.Serial_ExchangeBytes | T_U16); - } - - z80_payload.LD(A, (DATA_LOC + PACKET_SIZE + 3 + 1) | T_U16); - z80_payload.CP(A, 0xFF); - if (debug && false) // Don't compare the "recieved" data if in debug - { - z80_payload.index += 2; - } - else - { - z80_payload.JR(NZ_F, send_packet_loop.place_relative_jump(&z80_payload) | T_I8); - } - } - /* Recieve the Pokemon to remove */ - z80_payload.LD(HL, curr_rom.hSerialConnectionStatus | T_U16); // This can also be shortened - z80_payload.LD(HL_PTR, (debug ? 0x02 : 0x01) | T_U8); // Make sure GB is the slave, master if debug - z80_payload.LD(HL, curr_rom.garbageDataLocation | T_U16); - z80_payload.LD(DE, removal_array.place_ptr(&z80_payload) | T_U16); - z80_payload.LD(BC, 0x001E | T_U16); // Preamble does *not* count - if (debug) // Don't add in the Serial_ExchangeBytes call if in debug - { - z80_payload.index += 3; - } - else - { - z80_payload.CALL(curr_rom.Serial_ExchangeBytes | T_U16); - } - - /* Remove the transfered Pokemon */ - z80_payload.LD(HL, curr_rom.wRemoveMonFromBox | T_U16); - z80_payload.LD(HL_PTR, 0x01 | T_U8); // != 0x00 specifies the current box - remove_array_loop.set_start(&z80_payload); - z80_payload.LD(A, array_counter.place_ptr(&z80_payload) | T_U16); - z80_payload.LD(E, A); - z80_payload.LD(D, 0x00 | T_U8); - z80_payload.LD(HL, removal_array.place_ptr(&z80_payload) | T_U16); - z80_payload.ADD(HL, DE); - z80_payload.INC(A); - z80_payload.LD(array_counter.place_ptr(&z80_payload) | T_U16, A); - z80_payload.LD(A, curr_rom.wBoxCount | T_U16); - z80_payload.LD(B, A); - z80_payload.LD(A, HL_PTR); - z80_payload.CP(A, 0xFF | T_U8); - z80_payload.JR(Z_F, save_box.place_relative_jump(&z80_payload) | T_I8); - z80_payload.CP(A, B); - z80_payload.JR(NC_F, remove_array_loop.place_relative_jump(&z80_payload) | T_I8); - z80_payload.LD(HL, curr_rom.wWhichPokemon | T_U16); - z80_payload.LD(HL_PTR, A); - if (DONT_REMOVE_PKMN) - { - z80_payload.index += 3; - } - else - { - z80_payload.LD(B, (curr_rom._RemovePokemon >> 16) | T_U8); // Load ROM Bank - z80_payload.LD(HL, curr_rom._RemovePokemon | T_U16); - z80_payload.CALL(curr_rom.Bankswitch | T_U16); - } - z80_payload.JR((remove_array_loop.place_relative_jump(&z80_payload) & 0xFF) | T_I8); - - // We need to move this in order to not corrupt the 4E's - z80_payload.JR((save_box.place_relative_jump(&z80_payload) & 0xFF) | T_I8); - z80_payload.index = 0x17A; - - /* Save the current box */ - save_box.set_start(&z80_payload); - z80_payload.LD(B, (curr_rom.SaveSAVtoSRAM1 >> 16) | T_U8); // Load ROM Bank - z80_payload.LD(HL, curr_rom.SaveSAVtoSRAM1 | T_U16); - z80_payload.CALL(curr_rom.Bankswitch | T_U16); - z80_payload.CALL(curr_rom.SaveSAVtoSRAM2 | T_U16); // The bank is already loaded, we don't have to load it again - z80_payload.JP(curr_rom.SoftReset | T_U16); - - // z80_payload.index += 5; - - // Patchlist - // At 0x1BB / 0x1DE, 0xC2 in length (0xC4, but the last 2 are unused) - z80_patchlist.generate_patchlist(&z80_payload); - - // This payload works by placing Pokemon ID 0xFC's name in the stack, and causing a return to CD8E, - // which is part of the RNG seed. From there we can jump anywhere- and we choose to jump to D887, - // which is the rival's name. This code fixes the stack and jumps to the patchlist, which is where - // our final code is. - - // Update all the pointers - for (unsigned int i = 0; i < var_vector.size(); i++) - { - var_vector.at(i)->update_ptrs(); - } - for (unsigned int i = 0; i < jump_vector.size(); i++) - { - jump_vector.at(i)->update_jumps(); - } - - // Combine the vectors into the full payload - u8 *cur_out = payload_buffer; - memcpy(cur_out, z80_rng_seed.data_vector.data(), z80_rng_seed.data_vector.size()); - cur_out += z80_rng_seed.data_vector.size(); - memcpy(cur_out, z80_payload.data_vector.data(), z80_payload.data_vector.size()); - cur_out += z80_payload.data_vector.size(); - memcpy(cur_out, z80_patchlist.data_vector.data(), z80_patchlist.data_vector.size()); - cur_out += z80_patchlist.data_vector.size(); - - return; - - /* - else if (type == EVENT) - { - // ld a, $03 - payload_storage[offset++] = 0x3A; - payload_storage[offset++] = 0x03; - - // call OpenSRAM - payload_storage[offset++] = 0xC3; - payload_storage[offset++] = (curr_rom.OpenSRAM >> 0) & 0xFF; - payload_storage[offset++] = (curr_rom.OpenSRAM >> 8) & 0xFF; - - // ld de, $BEB0 - payload_storage[offset++] = 0x11; - payload_storage[offset++] = 0xB0; - payload_storage[offset++] = 0xBE; - - // ld bc, $150 - payload_storage[offset++] = 0x01; - payload_storage[offset++] = 0x50; - payload_storage[offset++] = 0x01; - - // ld hl, ??? - payload_storage[offset++] = 0x21; - payload_storage[offset++] = 0x00; - payload_storage[offset++] = 0x00; - - // CUSTOM EVENT SCRIPT: - // Start with the table data - int init_offset = offset; - insert_ext_copy_cmd(&offset, 0x11410E, 0xD930, 18); // Copy text pointers - insert_ext_copy_cmd(&offset, 0x00C120, 0xC140, 10); // - insert_ext_copy_cmd(&offset, 0x00C220, 0xC240, 16); // - byte temp[] = {0xD0, 0x0A}; - insert_int_copy_cmd(&offset, 0xD4E9, 2, temp); // - byte temp1[] = {0x15, 0x05}; - insert_int_copy_cmd(&offset, 0xD517, 2, temp1); // - byte temp2[] = {0x30, 0xD9}; - insert_int_copy_cmd(&offset, 0xD36B, 2, temp2); // - byte temp3[] = {0x0B, 0xF0, 0xFF}; - insert_int_copy_cmd(&offset, 0xD5D3, 3, temp3); // - byte temp4[] = {0x05}; - insert_int_copy_cmd(&offset, 0xD4E0, 1, temp4); // - byte temp5[] = {0x44, 0xD9, 0x08, 0x21, 0x54, 0xD9, 0xCD, 0x68, 0x31, 0x21, 0xB0, 0xC1, 0x00, 0x00, 0xC3, 0xD2, 0x23, 0x01, 0x00, 0x5E, 0xD8, 0xD8, 0xC9, 0xD8, 0xC9, 0xD8, 0xC9, 0xD8, 0xC9, 0x00, 0x8C, 0xB8, 0xB4, 0xB4, 0xE7, 0x50, 0x08, 0x3E, 0x83, 0xCD, 0x8B, 0x11, 0xCD, 0x3E, 0x37, 0xC3, 0xD2, 0x23}; - insert_int_copy_cmd(&offset, 0xD942, 48, temp5); // - payload_storage[offset++] = 0xFF; - - - // call CloseSRAM - payload_storage[offset++] = 0xC3; - payload_storage[offset++] = (curr_rom.CloseSRAM >> 0) & 0xFF; - payload_storage[offset++] = (curr_rom.CloseSRAM >> 8) & 0xFF; - - // call SoftReset - payload_storage[offset++] = 0xC3; - payload_storage[offset++] = (curr_rom.SoftReset >> 0) & 0xFF; - payload_storage[offset++] = (curr_rom.SoftReset >> 8) & 0xFF; - - }*/ - } - - else if (curr_rom.generation == 2) - { - std::vector jump_vector; - std::vector var_vector; - - z80_asm_handler z80_rng_seed(0x0A, curr_rom.wSerialOtherGameboyRandomNumberListBlock); - z80_asm_handler z80_payload(0x1CD, curr_rom.wSerialEnemyDataBlock); // wOTPartyData - z80_asm_handler z80_patchlist(0xC9, curr_rom.wSerialEnemyMonsPatchList); // wOTPatchLists - - /* - Initally the entire wLinkData is copied into the data section at D26B. - then, CopyBytes is called four times within link.skip_mail: - 1. It copies 0xB bytes from wLinkData to wOTPlayerName (D26B) - 2. It copies 0x8 bytes from where the previous one ended (D280) to wOTPartyCount - 3. It copies 0x2 bytes from where the previous one ended (D276) to wOTPlayerID - 4. It copies 0x1A4 bytes from where the previous one ended (D288) to wOTPartyMons - - 8 bytes aren't overwritten when copied over (D278-D28F), which is where the weird offset comes from. These bytes are index 0xE through 0x15 in the original data - By having 9 bytes of 0xFD, the offset ends up working out correctly. - */ - - z80_jump asm_start(&jump_vector); - z80_jump save_box(&jump_vector); - z80_jump remove_array_loop(&jump_vector); - z80_jump jump_to_party(&jump_vector); - z80_jump jump_to_payload(&jump_vector); - z80_jump packet_loop(&jump_vector); - z80_jump fe_bypass(&jump_vector); - z80_jump send_packet_loop(&jump_vector); - - z80_variable array_counter(&var_vector, 1, 0x00); // 1 byte to store the current array counter - z80_variable removal_array(&var_vector); // 40 byte storage for list of Pokemon to remove, plus a permanent array terminator - byte removal_array_data[41]; - for (int i = 0; i < 41; i++) - { - if (debug) - { - removal_array_data[i] = (i < 30 ? (29 - i) : 0xFF); - } - else - { - removal_array_data[i] = (i < 30 ? 0xFD : 0xFF); - } - } - removal_array.load_data(41, removal_array_data); - z80_variable transfer_wait_string(&var_vector, 13, // SENDING DATA - 0x92, 0x84, 0x8D, 0x83, 0x88, 0x8D, 0x86, 0x7F, 0x83, 0x80, 0x93, 0x80, 0x50); - z80_variable custom_name(&var_vector, 11, // FENNEL - 0x85, 0x84, 0x8D, 0x8D, 0x84, 0x8B, 0x50, 0x50, 0x50, 0x50, 0x50); - - // RNG Seed: - // at 0x00, 0x0A in length - // Does not need to be set - - // Preamble - // At 0x00, 0x09 in length - // Must be filled with 0xFD - for (int i = 0; i < 9; i++) - { - z80_payload.add_byte(0xFD); - } - - // Preamble/Rival name - // At 0x00, 0x1C5 in length - // Set to stored name - int distance = curr_rom.stack_overwrite_location - curr_rom.print_string_start; - int remainder = distance % 40; - distance /= 40; // Automatically truncated, so it won't overshoot - distance -= 8; // There will be 8 extra bytes due to how the copy functions work. - - for (int i = 0; i < distance; i++) - { - z80_payload.add_byte(curr_rom.short_pkmn_name); - } - for (int i = 0; i < remainder; i++) - { - z80_payload.add_byte(0x80); - } - - z80_payload.index -= 1; // Prep for the direct jump, since it usually has to jump forward one for the ASM call - jump_to_payload.place_direct_jump(&z80_payload); - z80_payload.index += 3; - z80_payload.add_byte(0x50); // String terminator - - // Saving the box overwrites our code, so we need to move it here. - jump_to_party.set_start(&z80_payload); - z80_payload.LD(A, curr_rom.SaveBox >> 16 | T_U8); - z80_payload.RST(0x10); // Bank switch - z80_payload.CALL(curr_rom.SaveBox | T_U16); - - z80_payload.JP(curr_rom.SoftReset | T_U16); - - // Patchlist preamble - // At 0x1B4 / 0x1D7, 0x07 in length - // Set as five 0xFD and two 0xFF - - for (int i = 0; i < 7; i++) - { - z80_patchlist.add_byte(i < 5 ? 0xFD : 0xFF); - } - - // Patchlist - // At 0x1DE, 194 in length - // Fill with custom code - - // Write transferring message to screen: - // call ClearScreen - jump_to_payload.set_start(&z80_patchlist); - z80_patchlist.CALL(curr_rom.clearScreen | T_U16); - - z80_patchlist.LD(HL, curr_rom.textBorderUppLeft | T_U16); - z80_patchlist.LD(C, curr_rom.textBorderWidth | T_U8); - z80_patchlist.LD(B, curr_rom.textBorderHeight | T_U8); - z80_patchlist.LD(A, curr_rom.CableClub_TextBoxBorder >> 16 | T_U8); - z80_patchlist.RST(0x10); // Bank switch - z80_patchlist.CALL(curr_rom.CableClub_TextBoxBorder | T_U16); - z80_patchlist.LD(HL, curr_rom.transferStringLocation | T_U16); - z80_patchlist.LD(DE, transfer_wait_string.place_ptr(&z80_patchlist) | T_U16); - - // call PlaceString - z80_patchlist.CALL(curr_rom.placeString | T_U16); - - // call OpenSRAM - z80_patchlist.LD(A, 0x01 | T_U8); - z80_patchlist.CALL(curr_rom.OpenSRAM | T_U16); - - /* Build the packet */ - // HL is the current data pointer - // DE is the destination pointer - // A is the checksum - // B is the 0xFE flag byte - // C is the counter - send_packet_loop.set_start(&z80_patchlist); - - z80_patchlist.LD(HL, (DATA_LOC + PACKET_SIZE + 3) | T_U16); // Load the next data location into HL - z80_patchlist.LD(A, HLI_PTR); - z80_patchlist.LD(H, HL_PTR); - z80_patchlist.LD(L, A); - - z80_patchlist.LD(DE, (DATA_LOC + 2) | T_U16); // Enemy Pokemon data, should be unused - z80_patchlist.XOR(A, A); // Clear the register - z80_patchlist.LD(B, A); // Clear B as well - z80_patchlist.LD(C, A); // Clear C as well - z80_patchlist.PUSH(AF); - packet_loop.set_start(&z80_patchlist); - z80_patchlist.LD(B, 0x00 | T_U8); // Reset the flag byte - z80_patchlist.POP(AF); - z80_patchlist.ADD(A, HL_PTR); // Add the current data to the checksum - z80_patchlist.PUSH(AF); - z80_patchlist.LD(A, 0xFE); - z80_patchlist.CP(A, HL_PTR); // Compare the current data to 0xFE - z80_patchlist.LD(A, HLI_PTR); // Load HL's data into A for modification (if need be) - - // If HL's data is 0xFE - z80_patchlist.JR(NZ_F, fe_bypass.place_relative_jump(&z80_patchlist) | T_I8); - z80_patchlist.DEC(A); - z80_patchlist.INC(B); // Set flag - - fe_bypass.set_start(&z80_patchlist); - z80_patchlist.LD(DE_PTR, A); // Place the data in - z80_patchlist.INC(DE); - z80_patchlist.PUSH(AF); - z80_patchlist.LD(A, B); - z80_patchlist.LD(DE_PTR, A); // Place the flag in as well - z80_patchlist.POP(AF); - z80_patchlist.INC(DE); - z80_patchlist.INC(C); - z80_patchlist.LD(A, DATA_PER_PACKET - 1); - z80_patchlist.CP(A, C); - z80_patchlist.JR(NC_F, packet_loop.place_relative_jump(&z80_patchlist) | T_I8); // If all the data has been set, send the rest of the data - z80_patchlist.POP(AF); - z80_patchlist.RES(7 | T_BIT, A); // Reset bit 7 of the checksum, guaranteeing that it will never be 0xFE - z80_patchlist.LD(DE_PTR, A); - z80_patchlist.INC(DE); - z80_patchlist.LD(A, H); - z80_patchlist.LD(DE_PTR, A); - z80_patchlist.INC(DE); - z80_patchlist.LD(A, L); - z80_patchlist.LD(DE_PTR, A); - - /* Transfer box data packet: */ - z80_patchlist.LD(A, (debug ? 0x02 : 0x01) | T_U8); // Make sure GB is the slave, master if debug - z80_patchlist.LDH((curr_rom.hSerialConnectionStatus & 0xFF) | T_U8, A); // Since hSerialConnectionStatus is at 0xFFxx we can use this method instead - z80_patchlist.LD(HL, DATA_LOC | T_U16); - z80_patchlist.LD(HL_PTR, 0xFD | T_U8); // set the start of the data to 0xFD so Serial_ExchangeBytes is happy - z80_patchlist.INC(HL); - z80_patchlist.LD(HL_PTR, 0x00 | T_U8); // add a 0x00 after the 0xFD to prevent further 0xFDs from being interpreted as part of the preamble - z80_patchlist.DEC(HL); // Reset HL back so it points to 0xFD - z80_patchlist.LD(DE, (DATA_LOC + PACKET_SIZE) | T_U16); // location to put stored data - z80_patchlist.LD(BC, PACKET_SIZE | T_U16); - if (debug) // Don't call serialExchangeBytes if debug is enabled - { - z80_patchlist.index += 3; - } - else - { - z80_patchlist.CALL(curr_rom.Serial_ExchangeBytes | T_U16); - } - - z80_patchlist.LD(A, (DATA_LOC + PACKET_SIZE + 3 + 1) | T_U16); - z80_patchlist.CP(A, 0xFF); - z80_patchlist.JR(NZ_F, send_packet_loop.place_relative_jump(&z80_patchlist) | T_I8); - - // Recieve the Pokemon to remove - z80_patchlist.LD(HL, curr_rom.hSerialConnectionStatus | T_U16); // This can also be shortened - z80_patchlist.LD(HL_PTR, (debug ? 0x02 : 0x01) | T_U8); // Make sure GB is the slave, master if debug - z80_patchlist.LD(HL, curr_rom.garbageDataLocation | T_U16); - z80_patchlist.LD(DE, removal_array.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.LD(BC, 0x001E | T_U16); // Preamble does *not* count - if (debug) // Don't add in the Serial_ExchangeBytes call if in debug - { - z80_patchlist.index += 3; - } - else - { - z80_patchlist.CALL(curr_rom.Serial_ExchangeBytes | T_U16); - } - - // Remove the transfered Pokemon - z80_patchlist.LD(HL, curr_rom.wRemoveMonFromBox | T_U16); - z80_patchlist.LD(HL_PTR, 0x01 | T_U8); // != 0x00 specifies the current box - remove_array_loop.set_start(&z80_patchlist); - z80_patchlist.LD(A, array_counter.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.LD(E, A); - z80_patchlist.LD(D, 0x00 | T_U8); - z80_patchlist.LD(HL, removal_array.place_ptr(&z80_patchlist) | T_U16); - z80_patchlist.ADD(HL, DE); - z80_patchlist.INC(A); - z80_patchlist.LD(array_counter.place_ptr(&z80_patchlist) | T_U16, A); - z80_patchlist.LD(A, curr_rom.wBoxCount | T_U16); - z80_patchlist.LD(B, A); - z80_patchlist.LD(A, HL_PTR); - z80_patchlist.CP(A, 0xFF | T_U8); - z80_patchlist.JR(Z_F, save_box.place_relative_jump(&z80_patchlist) | T_I8); - z80_patchlist.CP(A, B); - z80_patchlist.JR(NC_F, remove_array_loop.place_relative_jump(&z80_patchlist) | T_I8); - z80_patchlist.LD(HL, curr_rom.wWhichPokemon | T_U16); - z80_patchlist.LD(HL_PTR, A); - if (DONT_REMOVE_PKMN) - { - z80_patchlist.index += 6; - } - else - { - z80_patchlist.LD(A, curr_rom._RemovePokemon >> 16 | T_U8); - z80_patchlist.RST(0x10); // Bank switch - z80_patchlist.CALL(curr_rom._RemovePokemon | T_U16); - } - z80_patchlist.JR(remove_array_loop.place_relative_jump(&z80_patchlist) | T_I8); - - save_box.set_start(&z80_patchlist); - z80_patchlist.JP(jump_to_party.place_direct_jump(&z80_patchlist) | T_U16); // jp pkmn list (because saving the box overwrites the data) - - array_counter.insert_variable(&z80_patchlist); - removal_array.insert_variable(&z80_payload); - transfer_wait_string.insert_variable(&z80_patchlist); - - // This payload works by placing Pokemon ID 0xFC's name in the stack, and causing a return to CD8E, - // which is part of the RNG seed. From there we can jump anywhere- and we choose to jump to D887, - // which is the rival's name. This code fixes the stack and jumps to the patchlist, which is where - // our final code is. - - // Update all the pointers - for (unsigned int i = 0; i < var_vector.size(); i++) - { - var_vector.at(i)->update_ptrs(); - } - for (unsigned int i = 0; i < jump_vector.size(); i++) - { - jump_vector.at(i)->update_jumps(); - } - - // Combine the vectors into the full payload - u8 *cur_out = payload_buffer; - memcpy(cur_out, z80_rng_seed.data_vector.data(), z80_rng_seed.data_vector.size()); - cur_out += z80_rng_seed.data_vector.size(); - memcpy(cur_out, z80_payload.data_vector.data(), z80_payload.data_vector.size()); - cur_out += z80_payload.data_vector.size(); - memcpy(cur_out, z80_patchlist.data_vector.data(), z80_patchlist.data_vector.size()); - cur_out += z80_patchlist.data_vector.size(); - - return; - - // This payload works by placing Pokemon ID 0xFC's name in the stack, and causing a return to CD8E, - // which is part of the RNG seed. From there we can jump anywhere- and we choose to jump to D887, - // which is the rival's name. This code fixes the stack and jumps to the patchlist, which is where - // our final code is. - } - memset(payload_buffer, 0x00, PAYLOAD_SIZE); -}; - -#if PAYLOAD_EXPORT_TEST -#include -#include "gb_rom_values/gb_rom_values.h" -int main() // Rename to "main" to send the payload to test_payload.txt -{ - byte buffer[672] = {0}; - freopen("test_payload.txt", "w", stdout); - printf("\n"); - init_payload(buffer, gb_rom_values_eng[RED_ID], TRANSFER, true); - - if (true) - { - for (int i = 0; i < 0x2A0; i++) - { - printf("0x%02X, ", (unsigned int)buffer[i]); - if (i % 0x10 == 0xF) - { - printf("\n# 0x%X\n", i + 1); - } - } - return 0; - } - else - { - for (int i = 0; i < 0x150; i++) - { - printf("%02X ", (unsigned int)buffer[i + ((0x10 * 28) + 9)]); - if (i % 0x10 == 0xF) - { - printf("\n"); - } - } - return 0; - } -} -#endif \ No newline at end of file diff --git a/vbalink.ini b/vbalink.ini deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 From 5e7e9b4df7f881d74a3555a38788933b0c267692 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 5 Feb 2026 11:15:09 -0500 Subject: [PATCH 39/68] Updating makefile --- Makefile | 42 +++++++++++++++++++++++++++++++++++++----- PCCS | 2 +- include/debug_mode.h | 13 ++++++++++--- source/main.cpp | 2 +- 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index efce3bb..6f6b87c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,34 @@ -# Build configuration (set to either 'debug' or 'release') -BUILD_TYPE := debug +BUILD_LANGS := japanese english french german italian spanish-eu spanish-la +BUILD_TYPES := release debug + +# defaults +BUILD_LANG ?= english +BUILD_TYPE ?= release +GIT_VERSION ?= $(shell git describe --tags --abbrev=0) +GIT_FULL ?= $(shell git describe --tags --always --dirty 2>/dev/null) + +CMD_GOALS := $(filter-out build,$(MAKECMDGOALS)) + +LANG_INDEX := $(shell echo $(BUILD_LANGS) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_LANG) | awk '{print $$1}') +TYPE_INDEX := $(shell echo $(BUILD_TYPES) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_TYPE) | awk '{print $$1}') + +CPPFLAGS += -DPTGB_BUILD_LANGUAGE=$(LANG_INDEX) +CPPFLAGS += -DDEBUG_MODE=$(TYPE_INDEX) +CPPFLAGS += -DBUILD_INFO=\"$(GIT_FULL)\" + +CFLAGS += $(CPPFLAGS) +CXXFLAGS += $(CPPFLAGS) + +# detect language +ifneq ($(filter $(BUILD_LANGS),$(CMD_GOALS)),) +BUILD_LANG := $(filter $(BUILD_LANGS),$(CMD_GOALS)) +endif + +# detect build type +ifneq ($(filter $(BUILD_TYPES),$(CMD_GOALS)),) +BUILD_TYPE := $(filter $(BUILD_TYPES),$(CMD_GOALS)) +endif + #--------------------------------------------------------------------------------- .SUFFIXES: @@ -33,7 +62,7 @@ LIBPCCS := $(CURDIR)/PCCS # the makefile is found # #--------------------------------------------------------------------------------- -TARGET := $(notdir $(CURDIR))_mb +TARGET := $(notdir $(CURDIR))_v$(GIT_VERSION)_$(BUILD_LANG) BUILD := build SOURCES := source INCLUDES := include PCCS/lib/include @@ -46,12 +75,12 @@ GRAPHICS := graphics #--------------------------------------------------------------------------------- ARCH := -mthumb -mthumb-interwork -CFLAGS := -Wall -O2\ +CFLAGS += -Wall -O2\ -mcpu=arm7tdmi -mtune=arm7tdmi -masm-syntax-unified\ $(ARCH) CFLAGS += $(INCLUDE) -ffunction-sections -fdata-sections -Os -Wall -mthumb -mcpu=arm7tdmi -mtune=arm7tdmi -fstack-usage -CXXFLAGS := $(CFLAGS) -g0 -fno-rtti -fno-exceptions -fdata-sections -ffunction-sections -std=c++20 -Wno-volatile -D_GLIBCXX_USE_CXX20_ABI=0 -fstack-usage +CXXFLAGS += $(CFLAGS) -g0 -fno-rtti -fno-exceptions -fdata-sections -ffunction-sections -std=c++20 -Wno-volatile -D_GLIBCXX_USE_CXX20_ABI=0 -fstack-usage ifeq ($(BUILD_TYPE), debug) CFLAGS += -g -DDEBUG @@ -149,6 +178,9 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) all: $(BUILD) generate_data: + @echo "----------------------------------------------------------------" + @echo "Building v$(GIT_VERSION) with parameters: $(BUILD_LANG), $(BUILD_TYPE)" + @echo "----------------------------------------------------------------" mkdir -p data mkdir -p to_compress @env - \ diff --git a/PCCS b/PCCS index db430d8..1c98a39 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 +Subproject commit 1c98a391c2261c8141c8917160b2040514a0c2ee diff --git a/include/debug_mode.h b/include/debug_mode.h index c5d6b4b..d5928be 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -1,10 +1,17 @@ #ifndef DEBUG_MODE_H #define DEBUG_MODE_H -#define VERSION "v1.2.1" -#define PTGB_BUILD_LANGUAGE 1 +#ifndef BUILD_INFO +#define BUILD_INFO "NaN" +#endif -#define DEBUG_MODE true +#ifndef PTGB_BUILD_LANGUAGE +#define PTGB_BUILD_LANGUAGE 1 +#endif + +#ifndef DEBUG_MODE +#define DEBUG_MODE 0 +#endif #define PRINT_LINK_DATA (false && DEBUG_MODE) #define INSTANT_TEXT_SPEED (false && DEBUG_MODE) diff --git a/source/main.cpp b/source/main.cpp index 627d3da..62f853d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -296,7 +296,7 @@ int credits() ptgb_write_debug(charset, hexBuffer, true); ptgb_write_debug(charset, "\n", true); - ptgb_write_debug(charset, VERSION, true); + ptgb_write_debug(charset, BUILD_INFO, true); if (get_treecko_enabled()) { ptgb_write_debug(charset, ".T", true); From e1c3d1a4c2367b1d7c75508e85c2077e2976490c Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 5 Feb 2026 23:37:20 -0500 Subject: [PATCH 40/68] Updating make files to use debug mode --- Makefile | 24 ++++++++----------- include/debug_mode.h | 2 ++ text_helper/main.py | 18 +++++++++----- tools/payload-generator/Makefile | 24 ++++++++++++++++++- .../src/payloads/payload_builder.cpp | 1 - 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 6f6b87c..bd4b2ea 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,18 @@ BUILD_TYPES := release debug # defaults BUILD_LANG ?= english BUILD_TYPE ?= release -GIT_VERSION ?= $(shell git describe --tags --abbrev=0) -GIT_FULL ?= $(shell git describe --tags --always --dirty 2>/dev/null) +GIT_SUFFIX := $(shell git describe --tags --long --dirty | sed -E 's/^[^-]+-([0-9]+)-g[0-9a-f]+(-dirty)?$$/\1/') +GIT_FULL := $(shell git describe --tags --always --dirty 2>/dev/null) + +ifeq ($(GIT_SUFFIX),0) +GIT_VERSION = $(shell git describe --tags --abbrev=0) +else +GIT_VERSION = $(shell git describe --tags --abbrev=0)b +endif CMD_GOALS := $(filter-out build,$(MAKECMDGOALS)) -LANG_INDEX := $(shell echo $(BUILD_LANGS) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_LANG) | awk '{print $$1}') +LANG_INDEX := $(shell echo $(BUILD_LANGS) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_LANG) | awk '{print $$1 + 1}') TYPE_INDEX := $(shell echo $(BUILD_TYPES) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_TYPE) | awk '{print $$1}') CPPFLAGS += -DPTGB_BUILD_LANGUAGE=$(LANG_INDEX) @@ -19,16 +25,6 @@ CPPFLAGS += -DBUILD_INFO=\"$(GIT_FULL)\" CFLAGS += $(CPPFLAGS) CXXFLAGS += $(CPPFLAGS) -# detect language -ifneq ($(filter $(BUILD_LANGS),$(CMD_GOALS)),) -BUILD_LANG := $(filter $(BUILD_LANGS),$(CMD_GOALS)) -endif - -# detect build type -ifneq ($(filter $(BUILD_TYPES),$(CMD_GOALS)),) -BUILD_TYPE := $(filter $(BUILD_TYPES),$(CMD_GOALS)) -endif - #--------------------------------------------------------------------------------- .SUFFIXES: @@ -193,7 +189,7 @@ generate_data: CXXFLAGS= \ LDFLAGS= \ AR=ar \ - $(MAKE) -C tools/payload-generator + $(MAKE) -C tools/payload-generator BUILD_LANG=$(BUILD_LANG) BUILD_TYPE=$(BUILD_TYPE) @echo @echo "----------------------------------------------------------------" @echo diff --git a/include/debug_mode.h b/include/debug_mode.h index d5928be..5c14c87 100644 --- a/include/debug_mode.h +++ b/include/debug_mode.h @@ -32,6 +32,8 @@ #define DONT_TRANSFER_POKEMON_AT_INDEX_X (false && DEBUG_MODE) #define POKEMON_INDEX_TO_SKIP 5 #define ENABLE_TEXT_DEBUG_SCREEN (true && DEBUG_MODE) +#define DEBUG_PAYLOADS (false && DEBUG_MODE) + #define DEBUG_GAME EMERALD_ID #define DEBUG_VERS VERS_1_0 diff --git a/text_helper/main.py b/text_helper/main.py index 527ef56..bf5313d 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -507,9 +507,11 @@ def generate_cpp_file(): for section in textSections: cppFile.write("#include \"" + section.upper() + "_" + lang.name.lower() + "_lz10_bin.h\"\n") - for lang in Languages: - cppFile.write(f"\n#if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") - cppFile.write("const u8* get_compressed_text_table(int table_index)\n") + + cppFile.write("\nconst u8* get_compressed_text_table(int table_index)\n") + + for i, lang in enumerate(Languages): + cppFile.write(f"\n#{"el" if i > 0 else ""}if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") cppFile.write("{\n") cppFile.write("\tswitch (table_index)\n\t{\n") for section in textSections: @@ -519,8 +521,8 @@ def generate_cpp_file(): cppFile.write("\t\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") cppFile.write("\t\tbreak;\n") cppFile.write("\t}\n") - cppFile.write("}\n\n") - cppFile.write(f"#endif\n\n\n") + cppFile.write("}\n") + cppFile.write(f"#else\n#error \"Unsupported PTGB_BUILD_LANGUAGE\"\n#endif") def output_json_file(): print("\tOutputting json file") @@ -566,7 +568,11 @@ class Font: def build_h(): print("Building font.h") with open(fontDir + "/include/fonts.h", 'w') as f: - f.write(f'''#include "debug_mode.h" + f.write(f'''#ifndef PTGB_BUILD_LANGUAGE +#error "PTGB_BUILD_LANGUAGE not defined" +#endif + +#include "debug_mode.h" #include "pokemon_data.h" #ifndef __FONTS_H__ diff --git a/tools/payload-generator/Makefile b/tools/payload-generator/Makefile index a5dcb98..a1de7be 100644 --- a/tools/payload-generator/Makefile +++ b/tools/payload-generator/Makefile @@ -1,5 +1,27 @@ + +BUILD_LANGS := japanese english french german italian spanish-eu spanish-la +BUILD_TYPES := release debug + +# defaults +BUILD_LANG ?= english +BUILD_TYPE ?= release +GIT_VERSION ?= $(shell git describe --tags --abbrev=0) +GIT_FULL ?= $(shell git describe --tags --always --dirty 2>/dev/null) + +CMD_GOALS := $(filter-out build,$(MAKECMDGOALS)) + +LANG_INDEX := $(shell echo $(BUILD_LANGS) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_LANG) | awk '{print $$1 + 1}') +TYPE_INDEX := $(shell echo $(BUILD_TYPES) | tr ' ' '\n' | nl -v0 | grep -w $(BUILD_TYPE) | awk '{print $$1}') + +CPPFLAGS += -DPTGB_BUILD_LANGUAGE=$(LANG_INDEX) +CPPFLAGS += -DDEBUG_MODE=$(TYPE_INDEX) +CPPFLAGS += -DBUILD_INFO=\"$(GIT_FULL)\" + +CFLAGS += $(CPPFLAGS) +CXXFLAGS += $(CPPFLAGS) + # # Compiler flags -CXXFLAGS := -std=c++20 -fno-rtti -fno-exceptions -fno-unwind-tables -Wall -Wextra -I $(CURDIR)/include -g +CXXFLAGS += -std=c++20 -fno-rtti -fno-exceptions -fno-unwind-tables -Wall -Wextra -I $(CURDIR)/include -g # Source files directory SRC_DIR := ./src diff --git a/tools/payload-generator/src/payloads/payload_builder.cpp b/tools/payload-generator/src/payloads/payload_builder.cpp index 4fbe2cf..f1ee5c5 100644 --- a/tools/payload-generator/src/payloads/payload_builder.cpp +++ b/tools/payload-generator/src/payloads/payload_builder.cpp @@ -5,7 +5,6 @@ #include #define DATA_LOC (SHOW_DATA_PACKETS ? curr_rom.transferStringLocation : curr_rom.wEnemyMonSpecies) -#define DEBUG_PAYLOADS (false && DEBUG_MODE) void init_payload(byte *payload_buffer, const GB_ROM &curr_rom, int type, bool debug) { From 0f725431ce053ce79839e5e29fbf88bb821700de Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Fri, 6 Feb 2026 08:50:44 -0500 Subject: [PATCH 41/68] Update Makefile Target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bd4b2ea..72b2c7e 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ LIBPCCS := $(CURDIR)/PCCS # the makefile is found # #--------------------------------------------------------------------------------- -TARGET := $(notdir $(CURDIR))_v$(GIT_VERSION)_$(BUILD_LANG) +TARGET := $(notdir $(CURDIR))_mb BUILD := build SOURCES := source INCLUDES := include PCCS/lib/include From e70caddaf4fca86af65ee27909beee86868520c5 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Fri, 6 Feb 2026 17:00:30 -0500 Subject: [PATCH 42/68] Adding center text option --- Makefile | 7 +++++-- text_helper/main.py | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 72b2c7e..f35c16f 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ export DEPSDIR := $(CURDIR)/$(BUILD) CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) translated_text.cpp SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) @@ -160,7 +160,7 @@ export OFILES_GRAPHICS := $(PNGFILES:.png=.o) export OFILES := $(OFILES_SOURCES) $(OFILES_GRAPHICS) -export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(PNGFILES:.png=.h) +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(PNGFILES:.png=.h) $(CURDIR)/include/translated_text.h $(CURDIR)/include/fonts.h export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ @@ -226,6 +226,9 @@ clean: @$(MAKE) -C PCCS clean @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba data/ to_compress/ @rm -f text_helper/output.json + @rm -f include/fonts.h + @rm -f include/translated_text.h + @rm -f source/translated_text.cpp diff --git a/text_helper/main.py b/text_helper/main.py index bf5313d..30458f4 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -20,7 +20,7 @@ class Languages(Enum): SpanishEU = 5 SpanishLA = 6 -FIRST_TRANSLATION_COL_INDEX = 9 +FIRST_TRANSLATION_COL_INDEX = 10 BASE_DIR = Path(__file__).resolve().parent # read by default 1st sheet of an excel file @@ -210,6 +210,7 @@ def convert_item(ogDict, lang): pixelsInLine = ogDict["pixelsInLine"] include_box_breaks = ogDict["includeBoxBreaks"] include_scrolling = ogDict["includeScrolling"] + center_text = ogDict["centerText"] arr = charArrayOfLanguage[lang]["array"] escape_list = charArrayOfLanguage[lang]["escape"] @@ -459,6 +460,7 @@ def transfer_xlsx_to_dict(): "pixelsInLine" : currRow.iloc[4], "includeBoxBreaks": currRow.iloc[5], "includeScrolling": currRow.iloc[6], + "centerText": currRow.iloc[7] } def generate_header_file(): From 7625d796ab397b49969d06ddeead01b31306e4ab Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 8 Feb 2026 12:11:33 -0500 Subject: [PATCH 43/68] Organizing makefile --- Makefile | 43 ++++++++++++----- .../text_helper}/fonts/japanese_normal.png | Bin .../text_helper}/fonts/latin_normal.png | Bin {text_helper => tools/text_helper}/main.py | 44 ++++++++++++------ tools/text_helper/text.xlsx | Bin 0 -> 73740 bytes 5 files changed, 62 insertions(+), 25 deletions(-) rename {text_helper => tools/text_helper}/fonts/japanese_normal.png (100%) rename {text_helper => tools/text_helper}/fonts/latin_normal.png (100%) rename {text_helper => tools/text_helper}/main.py (95%) mode change 100755 => 100644 create mode 100644 tools/text_helper/text.xlsx diff --git a/Makefile b/Makefile index f35c16f..6384ab9 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,7 @@ LIBPCCS := $(CURDIR)/PCCS #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR))_mb BUILD := build +GENERATED_DIR := $(BUILD)/generated SOURCES := source INCLUDES := include PCCS/lib/include DATA := data @@ -124,14 +125,15 @@ ifneq ($(BUILD),$(notdir $(CURDIR))) export OUTPUT := $(CURDIR)/$(TARGET) -export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ +export VPATH := $(CURDIR)/$(GENERATED_DIR) \ + $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) export DEPSDIR := $(CURDIR)/$(BUILD) CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) translated_text.cpp +CPPFILES := $(sort $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) translated_text.cpp) SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) @@ -160,25 +162,45 @@ export OFILES_GRAPHICS := $(PNGFILES:.png=.o) export OFILES := $(OFILES_SOURCES) $(OFILES_GRAPHICS) -export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(PNGFILES:.png=.h) $(CURDIR)/include/translated_text.h $(CURDIR)/include/fonts.h +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(PNGFILES:.png=.h) \ + $(CURDIR)/$(GENERATED_DIR)/translated_text.h \ + $(CURDIR)/$(GENERATED_DIR)/fonts.h export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(GENERATED_DIR) \ -I$(CURDIR)/$(BUILD) \ -I$(CURDIR)/tools/payload-generator/include export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) -.PHONY: $(BUILD) generate_data clean +.PHONY: $(BUILD) generate_data clean text_generated data to_compress generated_dir all: $(BUILD) -generate_data: +TEXT_GENERATED := $(CURDIR)/$(GENERATED_DIR)/translated_text.h \ + $(CURDIR)/$(GENERATED_DIR)/translated_text.cpp \ + $(CURDIR)/$(GENERATED_DIR)/fonts.h \ + $(CURDIR)/$(GENERATED_DIR)/output.json + +text_generated: $(TEXT_GENERATED) + +$(TEXT_GENERATED): tools/text_helper/main.py | data to_compress generated_dir + @PTGB_GEN_DIR="$(CURDIR)/$(GENERATED_DIR)" python3 tools/text_helper/main.py + +data: + @mkdir -p $@ + +to_compress: + @mkdir -p $@ + +generated_dir: + @mkdir -p $(GENERATED_DIR) + +generate_data: data to_compress text_generated @echo "----------------------------------------------------------------" @echo "Building v$(GIT_VERSION) with parameters: $(BUILD_LANG), $(BUILD_TYPE)" @echo "----------------------------------------------------------------" - mkdir -p data - mkdir -p to_compress @env - \ PATH="$(PATH)" \ TMPDIR=/tmp TMP=/tmp TEMP=/tmp \ @@ -194,7 +216,6 @@ generate_data: @echo "----------------------------------------------------------------" @echo @tools/payload-generator/payload-generator to_compress - @python3 text_helper/main.py @echo "Compressing bin files!" @echo -n "[" @find to_compress -name "*.bin" -print0 | xargs -0 -n1 ./compress_lz10.sh @@ -225,10 +246,8 @@ clean: @$(MAKE) -C loader clean @$(MAKE) -C PCCS clean @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba data/ to_compress/ - @rm -f text_helper/output.json - @rm -f include/fonts.h - @rm -f include/translated_text.h - @rm -f source/translated_text.cpp + @rm -f tools/text_helper/output.json + @rm -rf $(GENERATED_DIR) diff --git a/text_helper/fonts/japanese_normal.png b/tools/text_helper/fonts/japanese_normal.png similarity index 100% rename from text_helper/fonts/japanese_normal.png rename to tools/text_helper/fonts/japanese_normal.png diff --git a/text_helper/fonts/latin_normal.png b/tools/text_helper/fonts/latin_normal.png similarity index 100% rename from text_helper/fonts/latin_normal.png rename to tools/text_helper/fonts/latin_normal.png diff --git a/text_helper/main.py b/tools/text_helper/main.py old mode 100755 new mode 100644 similarity index 95% rename from text_helper/main.py rename to tools/text_helper/main.py index 30458f4..d617c3a --- a/text_helper/main.py +++ b/tools/text_helper/main.py @@ -24,12 +24,26 @@ FIRST_TRANSLATION_COL_INDEX = 10 BASE_DIR = Path(__file__).resolve().parent # read by default 1st sheet of an excel file -textDir = os.curdir + "/text_helper" +textDir = str(BASE_DIR) + +gen_dir_env = os.environ.get("PTGB_GEN_DIR") +if gen_dir_env: + GEN_DIR = Path(gen_dir_env) + GEN_DIR.mkdir(parents=True, exist_ok=True) + TRANSLATED_H_PATH = GEN_DIR / "translated_text.h" + TRANSLATED_CPP_PATH = GEN_DIR / "translated_text.cpp" + FONTS_H_PATH = GEN_DIR / "fonts.h" + OUTPUT_JSON_PATH = GEN_DIR / "output.json" +else: + TRANSLATED_H_PATH = Path(os.curdir) / "include/translated_text.h" + TRANSLATED_CPP_PATH = Path(os.curdir) / "source/translated_text.cpp" + FONTS_H_PATH = Path(os.curdir) / "include/fonts.h" + OUTPUT_JSON_PATH = BASE_DIR / "output.json" url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' new_file_path = BASE_DIR / 'new_text.xlsx' old_file_path = BASE_DIR / 'text.xlsx' -json_file_path = BASE_DIR / 'output.json' +json_file_path = OUTPUT_JSON_PATH def split_into_sentences(text: str) -> list[str]: # -*- coding: utf-8 -*- @@ -91,7 +105,7 @@ def split_into_sentences(text: str) -> list[str]: if sentences and not sentences[-1]: sentences = sentences[:-1] return sentences -def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lang): +def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, isCentered, lang): outStr = "" currLine = "" lineCount = 0 @@ -166,7 +180,11 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, lan lineCount += 1 lineLength = 0 offset = 0 - + if (isCentered): + count = (pixelsInLine - lineLength) // 2 + for i in range(count): + currLine = " " + currLine + lineLength += spaceLength outStr += currLine return lineLength + offset, lineCount, outStr @@ -239,7 +257,7 @@ def convert_item(ogDict, lang): offset = 0 escapeCount = 0 while index < len(split_sents) and escapeCount < 100: - offset, recievedLine, out = split_sentence_into_lines(split_sents[index], offset, pixelsPerChar, pixelsInLine, lang) + offset, recievedLine, out = split_sentence_into_lines(split_sents[index], offset, pixelsPerChar, pixelsInLine, center_text, lang) currLine += recievedLine if (out == "ȼ"): @@ -385,7 +403,7 @@ def download_xlsx_file(): # XML exists (guaranteed here) if json_file_path.exists(): print("Offline mode: trusting cached XML + JSON. Skipping parse.") - if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(f'{textDir}/output.json'): + if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(OUTPUT_JSON_PATH): print("\t...but the python file is new, so we're doing it anyway!") return sys.exit(0) @@ -401,7 +419,7 @@ def download_xlsx_file(): new_file_path.unlink() if json_file_path.exists(): print("Skipping parse") - if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(f'{textDir}/output.json'): + if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(OUTPUT_JSON_PATH): print("\t...but the python file is new, so we're doing it anyway!") return sys.exit(0) @@ -465,7 +483,7 @@ def transfer_xlsx_to_dict(): def generate_header_file(): print("\tGenerating header file") - with open (os.curdir + '/include/translated_text.h', 'w') as hFile: + with open(TRANSLATED_H_PATH, 'w') as hFile: hFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n\n#ifndef TRANSLATED_TEXT_H\n#define TRANSLATED_TEXT_H\n\n#include \n\n") sectionEnds = [] @@ -502,7 +520,7 @@ def generate_text_tables(): def generate_cpp_file(): print("\tGenerating cpp file") - with open(os.curdir + '/source/translated_text.cpp', 'w') as cppFile: + with open(TRANSLATED_CPP_PATH, 'w') as cppFile: cppFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n#include \"translated_text.h\"\n#include \"debug_mode.h\"\n") # generate includes for each language for lang in Languages: @@ -539,7 +557,7 @@ def output_json_file(): outText += str(byte) mainDict[lang.name][section][item]["text"] = outText - with open(textDir + '/output.json', 'w') as jsonFile: + with open(OUTPUT_JSON_PATH, 'w') as jsonFile: jsonFile.write(json.dumps(mainDict)) @@ -569,7 +587,7 @@ class Font: def build_h(): print("Building font.h") - with open(fontDir + "/include/fonts.h", 'w') as f: + with open(FONTS_H_PATH, 'w') as f: f.write(f'''#ifndef PTGB_BUILD_LANGUAGE #error "PTGB_BUILD_LANGUAGE not defined" #endif @@ -629,7 +647,7 @@ def generate_tables(): print("Generating font tables") for myFont in fonts.values(): print(f'\t{myFont.fileName}') - reader = png.Reader(f'{fontDir}/text_helper/fonts/{myFont.fileName}.png') + reader = png.Reader(f'{BASE_DIR}/fonts/{myFont.fileName}.png') png_info = reader.read()[3] palette = png_info.get('palette') if (palette is None): @@ -747,4 +765,4 @@ generate_header_file() generate_text_tables() generate_cpp_file() output_json_file() -print("text_helper finished!\n") \ No newline at end of file +print("text_helper finished!\n") diff --git a/tools/text_helper/text.xlsx b/tools/text_helper/text.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e02c4e552d9c014f7ea8a80f759d3d9fd9b609ae GIT binary patch literal 73740 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 Date: Sun, 8 Feb 2026 18:03:51 -0500 Subject: [PATCH 44/68] Optimizing makefile --- .gitignore | 8 +------ Makefile | 64 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 6537d39..8e84241 100644 --- a/.gitignore +++ b/.gitignore @@ -9,10 +9,4 @@ to_compress/ *.zip *.exe .vscode/ -source/test_payload.txt -loader/data/multiboot_rom.bin -include/translated_text.h -source/translated_text.cpp -text_helper/output.json -text_helper/text.xlsx -include/fonts.h + diff --git a/Makefile b/Makefile index 6384ab9..f36e9ef 100644 --- a/Makefile +++ b/Makefile @@ -174,18 +174,24 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) -.PHONY: $(BUILD) generate_data clean text_generated data to_compress generated_dir +.PHONY: clean + +GENERATE_STAMP := $(BUILD)/.generate_data.$(BUILD_LANG).$(BUILD_TYPE).stamp +BUILD_STAMP := $(BUILD)/.build.$(BUILD_LANG).$(BUILD_TYPE).stamp + +TEXT_HELPER_INPUTS := $(shell find tools/text_helper -type f \( -name "*.py" -o -name "*.xlsx" -o -name "*.png" \)) +PAYLOAD_GEN_INPUTS := $(shell find tools/payload-generator/src tools/payload-generator/include -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \)) -all: $(BUILD) +all: $(BUILD_STAMP) TEXT_GENERATED := $(CURDIR)/$(GENERATED_DIR)/translated_text.h \ $(CURDIR)/$(GENERATED_DIR)/translated_text.cpp \ $(CURDIR)/$(GENERATED_DIR)/fonts.h \ $(CURDIR)/$(GENERATED_DIR)/output.json -text_generated: $(TEXT_GENERATED) +text_generated: $(TEXT_GENERATED) -$(TEXT_GENERATED): tools/text_helper/main.py | data to_compress generated_dir +$(TEXT_GENERATED): $(TEXT_HELPER_INPUTS) | data to_compress generated_dir @PTGB_GEN_DIR="$(CURDIR)/$(GENERATED_DIR)" python3 tools/text_helper/main.py data: @@ -197,12 +203,14 @@ to_compress: generated_dir: @mkdir -p $(GENERATED_DIR) -generate_data: data to_compress text_generated - @echo "----------------------------------------------------------------" - @echo "Building v$(GIT_VERSION) with parameters: $(BUILD_LANG), $(BUILD_TYPE)" - @echo "----------------------------------------------------------------" - @env - \ - PATH="$(PATH)" \ +generate_data: $(GENERATE_STAMP) + +$(GENERATE_STAMP): text_generated $(PAYLOAD_GEN_INPUTS) compress_lz10.sh | data to_compress generated_dir + @echo "----------------------------------------------------------------" + @echo "Building v$(GIT_VERSION) with parameters: $(BUILD_LANG), $(BUILD_TYPE)" + @echo "----------------------------------------------------------------" + @env - \ + PATH="$(PATH)" \ TMPDIR=/tmp TMP=/tmp TEMP=/tmp \ SYSTEMROOT="$(SYSTEMROOT)" \ CC=cc \ @@ -220,24 +228,28 @@ generate_data: data to_compress text_generated @echo -n "[" @find to_compress -name "*.bin" -print0 | xargs -0 -n1 ./compress_lz10.sh @echo "]" - @echo "Compressing finished!" - @echo - @echo "----------------------------------------------------------------" - @echo + @echo "Compressing finished!" + @echo + @echo "----------------------------------------------------------------" + @echo + @touch $@ #--------------------------------------------------------------------------------- -$(BUILD): generate_data - @[ -d $@ ] || mkdir -p $@ - @$(MAKE) -C PCCS \ - CC="$(CC)" \ - CXX="$(CXX)" \ - CFLAGS="$(CFLAGS)" \ - CXXFLAGS="$(CXXFLAGS)" \ - LDFLAGS="$(LDFLAGS)" - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile - @mkdir -p loader/data - @cp $(TARGET).gba loader/data/multiboot_rom.bin - @$(MAKE) -C loader +$(BUILD_STAMP): generate_data | $(BUILD) + @$(MAKE) -C PCCS \ + CC="$(CC)" \ + CXX="$(CXX)" \ + CFLAGS="$(CFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" \ + LDFLAGS="$(LDFLAGS)" + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + @mkdir -p loader/data + @cp $(TARGET).gba loader/data/multiboot_rom.bin + @$(MAKE) -C loader + @touch $@ + +$(BUILD): + @mkdir -p $@ #--------------------------------------------------------------------------------- clean: From 11fc7784b9445b3972c4bf53458c27868a2616b2 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 8 Feb 2026 18:04:16 -0500 Subject: [PATCH 45/68] Updating PCCS --- PCCS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PCCS b/PCCS index 1c98a39..3f07bba 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit 1c98a391c2261c8141c8917160b2040514a0c2ee +Subproject commit 3f07bba2004626344a77549b1f9bbc66c715f357 From 6bfc6b81952bb89a8c27a1306b1145823551e0b9 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 9 Feb 2026 11:37:06 -0500 Subject: [PATCH 46/68] Updating PCCS --- PCCS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PCCS b/PCCS index 3f07bba..db430d8 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit 3f07bba2004626344a77549b1f9bbc66c715f357 +Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 From 37aa2a867402ff8749766e7fc7261cd665046dda Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Mon, 9 Feb 2026 23:35:51 -0500 Subject: [PATCH 47/68] Adding a release file xlsx to the text builder --- Makefile | 80 +++++++++++++++---------------- tools/text_helper/main.py | 91 ++++++++++++++++++++---------------- tools/text_helper/text.xlsx | Bin 73740 -> 0 bytes 3 files changed, 91 insertions(+), 80 deletions(-) delete mode 100644 tools/text_helper/text.xlsx diff --git a/Makefile b/Makefile index f36e9ef..5d3f4cd 100644 --- a/Makefile +++ b/Makefile @@ -174,25 +174,25 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) -.PHONY: clean - -GENERATE_STAMP := $(BUILD)/.generate_data.$(BUILD_LANG).$(BUILD_TYPE).stamp -BUILD_STAMP := $(BUILD)/.build.$(BUILD_LANG).$(BUILD_TYPE).stamp - -TEXT_HELPER_INPUTS := $(shell find tools/text_helper -type f \( -name "*.py" -o -name "*.xlsx" -o -name "*.png" \)) -PAYLOAD_GEN_INPUTS := $(shell find tools/payload-generator/src tools/payload-generator/include -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \)) +.PHONY: clean -all: $(BUILD_STAMP) +GENERATE_STAMP := $(BUILD)/.generate_data.$(BUILD_LANG).$(BUILD_TYPE).stamp +BUILD_STAMP := $(BUILD)/.build.$(BUILD_LANG).$(BUILD_TYPE).stamp + +TEXT_HELPER_INPUTS := $(shell find tools/text_helper -type f \( -name "*.py" -o -name "*.xlsx" -o -name "*.png" \)) +PAYLOAD_GEN_INPUTS := $(shell find tools/payload-generator/src tools/payload-generator/include -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \)) + +all: $(BUILD_STAMP) TEXT_GENERATED := $(CURDIR)/$(GENERATED_DIR)/translated_text.h \ $(CURDIR)/$(GENERATED_DIR)/translated_text.cpp \ $(CURDIR)/$(GENERATED_DIR)/fonts.h \ $(CURDIR)/$(GENERATED_DIR)/output.json -text_generated: $(TEXT_GENERATED) +text_generated: $(TEXT_GENERATED) -$(TEXT_GENERATED): $(TEXT_HELPER_INPUTS) | data to_compress generated_dir - @PTGB_GEN_DIR="$(CURDIR)/$(GENERATED_DIR)" python3 tools/text_helper/main.py +$(TEXT_GENERATED): $(TEXT_HELPER_INPUTS) | data to_compress generated_dir + @PTGB_GEN_DIR="$(CURDIR)/$(GENERATED_DIR)" python3 tools/text_helper/main.py $(BUILD_LANG) $(BUILD_TYPE) data: @mkdir -p $@ @@ -203,14 +203,14 @@ to_compress: generated_dir: @mkdir -p $(GENERATED_DIR) -generate_data: $(GENERATE_STAMP) - -$(GENERATE_STAMP): text_generated $(PAYLOAD_GEN_INPUTS) compress_lz10.sh | data to_compress generated_dir - @echo "----------------------------------------------------------------" - @echo "Building v$(GIT_VERSION) with parameters: $(BUILD_LANG), $(BUILD_TYPE)" - @echo "----------------------------------------------------------------" - @env - \ - PATH="$(PATH)" \ +generate_data: $(GENERATE_STAMP) + +$(GENERATE_STAMP): text_generated $(PAYLOAD_GEN_INPUTS) compress_lz10.sh | data to_compress generated_dir + @echo "----------------------------------------------------------------" + @echo "Building v$(GIT_VERSION) with parameters: $(BUILD_LANG), $(BUILD_TYPE)" + @echo "----------------------------------------------------------------" + @env - \ + PATH="$(PATH)" \ TMPDIR=/tmp TMP=/tmp TEMP=/tmp \ SYSTEMROOT="$(SYSTEMROOT)" \ CC=cc \ @@ -228,28 +228,28 @@ $(GENERATE_STAMP): text_generated $(PAYLOAD_GEN_INPUTS) compress_lz10.sh | data @echo -n "[" @find to_compress -name "*.bin" -print0 | xargs -0 -n1 ./compress_lz10.sh @echo "]" - @echo "Compressing finished!" - @echo - @echo "----------------------------------------------------------------" - @echo - @touch $@ + @echo "Compressing finished!" + @echo + @echo "----------------------------------------------------------------" + @echo + @touch $@ #--------------------------------------------------------------------------------- -$(BUILD_STAMP): generate_data | $(BUILD) - @$(MAKE) -C PCCS \ - CC="$(CC)" \ - CXX="$(CXX)" \ - CFLAGS="$(CFLAGS)" \ - CXXFLAGS="$(CXXFLAGS)" \ - LDFLAGS="$(LDFLAGS)" - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile - @mkdir -p loader/data - @cp $(TARGET).gba loader/data/multiboot_rom.bin - @$(MAKE) -C loader - @touch $@ - -$(BUILD): - @mkdir -p $@ +$(BUILD_STAMP): generate_data | $(BUILD) + @$(MAKE) -C PCCS \ + CC="$(CC)" \ + CXX="$(CXX)" \ + CFLAGS="$(CFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" \ + LDFLAGS="$(LDFLAGS)" + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + @mkdir -p loader/data + @cp $(TARGET).gba loader/data/multiboot_rom.bin + @$(MAKE) -C loader + @touch $@ + +$(BUILD): + @mkdir -p $@ #--------------------------------------------------------------------------------- clean: @@ -258,7 +258,7 @@ clean: @$(MAKE) -C loader clean @$(MAKE) -C PCCS clean @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba data/ to_compress/ - @rm -f tools/text_helper/output.json + @rm -f tools/text_helper/build @rm -rf $(GENERATED_DIR) diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index d617c3a..78fb1bb 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -10,6 +10,7 @@ import hashlib import math import numpy as np import png +import shutil class Languages(Enum): Japanese = 0 @@ -21,29 +22,29 @@ class Languages(Enum): SpanishLA = 6 FIRST_TRANSLATION_COL_INDEX = 10 + BASE_DIR = Path(__file__).resolve().parent - -# read by default 1st sheet of an excel file -textDir = str(BASE_DIR) - -gen_dir_env = os.environ.get("PTGB_GEN_DIR") -if gen_dir_env: - GEN_DIR = Path(gen_dir_env) - GEN_DIR.mkdir(parents=True, exist_ok=True) - TRANSLATED_H_PATH = GEN_DIR / "translated_text.h" - TRANSLATED_CPP_PATH = GEN_DIR / "translated_text.cpp" - FONTS_H_PATH = GEN_DIR / "fonts.h" - OUTPUT_JSON_PATH = GEN_DIR / "output.json" -else: - TRANSLATED_H_PATH = Path(os.curdir) / "include/translated_text.h" - TRANSLATED_CPP_PATH = Path(os.curdir) / "source/translated_text.cpp" - FONTS_H_PATH = Path(os.curdir) / "include/fonts.h" - OUTPUT_JSON_PATH = BASE_DIR / "output.json" +BUILD_DIR = BASE_DIR / "build" +GEN_DIR = BASE_DIR.parent.parent / "build" / "generated" +GEN_DIR.mkdir(parents=True, exist_ok=True) +TRANSLATED_H_PATH = GEN_DIR / "translated_text.h" +TRANSLATED_CPP_PATH = GEN_DIR / "translated_text.cpp" +FONTS_H_PATH = GEN_DIR / "fonts.h" +OUTPUT_JSON_PATH = BUILD_DIR / "output.json" +THIS_SCRIPT_PATH = BASE_DIR / "main.py" url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' -new_file_path = BASE_DIR / 'new_text.xlsx' -old_file_path = BASE_DIR / 'text.xlsx' -json_file_path = OUTPUT_JSON_PATH +new_file_path = BUILD_DIR / 'new_text.xlsx' +old_file_path = BUILD_DIR / 'text.xlsx' +release_file_path = BASE_DIR / 'release.xlsx' +json_file_path = BUILD_DIR / "output.json" + +if len(sys.argv) >= 3: + BUILD_LANG = sys.argv[1] + BUILD_TYPE = sys.argv[2] +else: + BUILD_LANG = "" # Not implemented yet + BUILD_TYPE = "debug" def split_into_sentences(text: str) -> list[str]: # -*- coding: utf-8 -*- @@ -380,30 +381,35 @@ def write_enum_to_header_file(hFile, prefix, dictionary): return num def download_xlsx_file(): - print("Downloading xlsx file") - offline = False - # ---- Attempt download ---- - try: - response = requests.get(url, timeout=5) - response.raise_for_status() - with open(new_file_path, 'wb') as f: - f.write(response.content) - print("File downloaded successfully") + if os.path.isfile(release_file_path): + print('Release file found. Using that instead!') + shutil.copy(release_file_path, new_file_path) + offline = False + else: + print("Downloading xlsx file") + offline = False + # ---- Attempt download ---- + try: + response = requests.get(url, timeout=5) + response.raise_for_status() + with open(new_file_path, 'wb') as f: + f.write(response.content) + print("File downloaded successfully") - except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError): - if old_file_path.exists(): - print("No internet. Using cached xlsx.") - offline = True - else: - print("ERROR: No internet and no cached xlsx. Cannot continue.") - sys.exit(1) + except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectionError): + if old_file_path.exists(): + print("No internet. Using cached xlsx.") + offline = True + else: + print("ERROR: No internet and no cached xlsx. Cannot continue.") + sys.exit(1) # ---- Decision logic ---- if offline: # XML exists (guaranteed here) if json_file_path.exists(): print("Offline mode: trusting cached XML + JSON. Skipping parse.") - if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(OUTPUT_JSON_PATH): + if os.path.getmtime(THIS_SCRIPT_PATH) > os.path.getmtime(OUTPUT_JSON_PATH): print("\t...but the python file is new, so we're doing it anyway!") return sys.exit(0) @@ -419,7 +425,7 @@ def download_xlsx_file(): new_file_path.unlink() if json_file_path.exists(): print("Skipping parse") - if os.path.getmtime(f'{textDir}/main.py') > os.path.getmtime(OUTPUT_JSON_PATH): + if os.path.getmtime(THIS_SCRIPT_PATH) > os.path.getmtime(OUTPUT_JSON_PATH): print("\t...but the python file is new, so we're doing it anyway!") return sys.exit(0) @@ -435,7 +441,7 @@ def download_xlsx_file(): def transfer_xlsx_to_dict(): print("\tGetting character arrays") - currSheet = pd.read_excel(textDir + "/text.xlsx", sheet_name="Character Arrays", header=None) + currSheet = pd.read_excel(old_file_path, sheet_name="Character Arrays", header=None) offset = 0 for key, value in charArrays.items(): for r in range(16): @@ -450,7 +456,7 @@ def transfer_xlsx_to_dict(): print("\tGetting string data") - currSheet = pd.read_excel(textDir + "/text.xlsx", sheet_name="Translations") + currSheet = pd.read_excel(old_file_path, sheet_name="Translations") for row in currSheet.iterrows(): currRow = row[1]["Text Section"] @@ -480,6 +486,10 @@ def transfer_xlsx_to_dict(): "includeScrolling": currRow.iloc[6], "centerText": currRow.iloc[7] } +def test_if_release(): + if (BUILD_TYPE == 'release'): + print("\tis release. Saving text file as release.xlsx") + shutil.copy(old_file_path, release_file_path) def generate_header_file(): print("\tGenerating header file") @@ -760,6 +770,7 @@ print("Running text_helper:") generate_tables() build_h() download_xlsx_file() +test_if_release() transfer_xlsx_to_dict() generate_header_file() generate_text_tables() diff --git a/tools/text_helper/text.xlsx b/tools/text_helper/text.xlsx deleted file mode 100644 index e02c4e552d9c014f7ea8a80f759d3d9fd9b609ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73740 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 Date: Wed, 11 Feb 2026 08:22:49 -0500 Subject: [PATCH 48/68] Adding legal message padding --- source/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/main.cpp b/source/main.cpp index 62f853d..9b24c73 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -388,8 +388,8 @@ int main_menu_loop() // Legal stuff static void show_legal_text(const u8 *intro_text) { - tte_set_margins(0, 0, H_MAX, V_MAX); - tte_set_pos(0, 0); + tte_set_margins(8, 0, H_MAX - 8, V_MAX); + tte_set_pos(8, 0); tte_set_ink(INK_ROM_COLOR); ptgb_write(intro_text, true); bool wait = true; From f390c3692a3997a8ab76a5398d9dec0d6e9d844f Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 11 Feb 2026 10:05:25 -0500 Subject: [PATCH 49/68] Fixing sheet download issues --- tools/text_helper/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index 78fb1bb..5a87ae8 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -26,6 +26,7 @@ FIRST_TRANSLATION_COL_INDEX = 10 BASE_DIR = Path(__file__).resolve().parent BUILD_DIR = BASE_DIR / "build" GEN_DIR = BASE_DIR.parent.parent / "build" / "generated" +BUILD_DIR.mkdir(parents=True, exist_ok=True) GEN_DIR.mkdir(parents=True, exist_ok=True) TRANSLATED_H_PATH = GEN_DIR / "translated_text.h" TRANSLATED_CPP_PATH = GEN_DIR / "translated_text.cpp" From e03a020c458c97a84a28e62079c61189f7f1479d Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 11 Feb 2026 10:19:12 -0500 Subject: [PATCH 50/68] Updating makefile clean function --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5d3f4cd..0f97845 100644 --- a/Makefile +++ b/Makefile @@ -258,8 +258,8 @@ clean: @$(MAKE) -C loader clean @$(MAKE) -C PCCS clean @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba data/ to_compress/ - @rm -f tools/text_helper/build - @rm -rf $(GENERATED_DIR) + @rm -fr tools/text_helper/build + @rm -fr $(GENERATED_DIR) From a4af5e3b420217e26399aa88f21e9b9c5869c0fe Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 11 Feb 2026 20:28:27 -0500 Subject: [PATCH 51/68] Adding _ escape character --- tools/text_helper/fonts/japanese_normal.png | Bin 3552 -> 3553 bytes tools/text_helper/fonts/latin_normal.png | Bin 3099 -> 3108 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/text_helper/fonts/japanese_normal.png b/tools/text_helper/fonts/japanese_normal.png index 34ac4fe03d67731c9159671f9085b9057e7ba86d..eefe567ebe9312a2cf48f7df3ae16b4f377ee64b 100644 GIT binary patch literal 3553 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 Hu(|&Mc4{JX diff --git a/tools/text_helper/fonts/latin_normal.png b/tools/text_helper/fonts/latin_normal.png index aec6e5aa1d5db684f2cdb3983dc6d1431137a539..afcc3fa9a829c026625c0bca244d9d1604a5591e 100644 GIT binary patch delta 2976 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 JPDHLkV1kVlX@md( From 8d869927dade1cb93cb4932a5c056c4e75f57b17 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Wed, 11 Feb 2026 21:00:42 -0500 Subject: [PATCH 52/68] Adding in {LVL} escape sequence --- source/text_engine.cpp | 10 ++++++++++ tools/text_helper/fonts/latin_normal.png | Bin 3108 -> 3111 bytes tools/text_helper/main.py | 13 +++++++++++++ 3 files changed, 23 insertions(+) diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 8047cf0..da63cd7 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -340,6 +340,16 @@ int ptgb_write(const byte *text, bool instant, int length) } wait_for_user_to_continue(true); break; + case 0xFC: + if (DISPLAY_CONTROL_CHAR) + { + tc->drawgProc(0xB9); + } + else + { + tc->cursorX += tc->font->widths[0xFC]; + } + break; case 0xFE: if (DISPLAY_CONTROL_CHAR) { diff --git a/tools/text_helper/fonts/latin_normal.png b/tools/text_helper/fonts/latin_normal.png index afcc3fa9a829c026625c0bca244d9d1604a5591e..19e74b7712ec50b58d352c1f6f4d42b97ac4bc17 100644 GIT binary patch delta 2975 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 VO?glRQSSf%002ovPDHLkV1l*;pw$2X delta 2984 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 et#LP%agH3sSFI Date: Wed, 11 Feb 2026 21:04:29 -0500 Subject: [PATCH 53/68] Updating legal message margins --- source/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/main.cpp b/source/main.cpp index 9b24c73..dda44c2 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -388,8 +388,8 @@ int main_menu_loop() // Legal stuff static void show_legal_text(const u8 *intro_text) { - tte_set_margins(8, 0, H_MAX - 8, V_MAX); - tte_set_pos(8, 0); + tte_set_margins(4, 0, H_MAX - 4, V_MAX); + tte_set_pos(4, 0); tte_set_ink(INK_ROM_COLOR); ptgb_write(intro_text, true); bool wait = true; From 28872528669402a76dae81e17071bec66db68e7f Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Thu, 12 Feb 2026 22:42:07 -0500 Subject: [PATCH 54/68] Updating character sheets and adding centering --- tools/text_helper/fonts/japanese_normal.png | Bin 3553 -> 3566 bytes tools/text_helper/fonts/latin_normal.png | Bin 3111 -> 3327 bytes tools/text_helper/main.py | 97 ++++++++++++++------ 3 files changed, 68 insertions(+), 29 deletions(-) diff --git a/tools/text_helper/fonts/japanese_normal.png b/tools/text_helper/fonts/japanese_normal.png index eefe567ebe9312a2cf48f7df3ae16b4f377ee64b..016373abdf4392d418b6106e32d9724a05ca78bd 100644 GIT binary patch literal 3566 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 ajnY5r<4t)`1X1q*0000 list[str]: text = text.replace("Ň", "Ň") # Split newlines into their own sentences text = text.replace("ȼ", "ȼ") # Split new boxes into their own sentences text = text.replace("Ş", "Ş") # Split new boxes into their own sentences + text = text.replace("Ω", "Ω") # Split centering into their own sentences + text = text.replace("ɑ", "ɑ") # Split centering into their own sentences + sentences = text.split("") sentences = [s.strip() for s in sentences] if sentences and not sentences[-1]: sentences = sentences[:-1] return sentences -def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, isCentered, lang): +def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, centered, lang): outStr = "" currLine = "" lineCount = 0 @@ -115,7 +119,7 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, isC lineLength = 0 spaceLength = 0 - words = sentence.split() + words = sentence.split() while(currWordIndex < len(words)): @@ -125,9 +129,11 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, isC # Figure out the length of the word in pixels for char in word: + if (char == PURPOSEFUL_SPACE_CHAR): + char = " " if (pixelsPerChar == "Variable"): wordLength += charArrayOfLanguage[lang]["font"].charWidthTable[convert_char_to_byte(ord(char), charArrayOfLanguage[lang]["array"], lang)] - spaceLength = charArrayOfLanguage[lang]["font"].charWidthTable[convert_char_to_byte(ord(' '), charArrayOfLanguage[lang]["array"], lang)] + spaceLength = charArrayOfLanguage[lang]["font"].charWidthTable[0] elif (pixelsPerChar == "Default"): if (lang == Languages.Japanese): wordLength += 8 @@ -148,6 +154,20 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, isC offset = 0 lineLength = 0 currWordIndex += 1 + + # See if the whole sentence is a center character + elif (sentence == "ɑ" or sentence == "Ω"): + if (sentence == "ɑ"): + centered = True + outStr += "Ň" + else: + centered = False + outStr += "Ň" + currLine = "" + lineCount += 1 + offset = 0 + lineLength = 0 + currWordIndex += 1 # See if the sentence is a new box elif(sentence == "ȼ"): @@ -182,13 +202,13 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, isC lineCount += 1 lineLength = 0 offset = 0 - if (isCentered): - count = (pixelsInLine - lineLength) // 2 + if (centered and (len(words) > 0) and words[0] != 'ɑ'): + count = ((pixelsInLine - lineLength) // 2) for i in range(count): - currLine = " " + currLine - lineLength += spaceLength + currLine = "_" + currLine + lineLength += 1 # This character should *always* be one pixel wide outStr += currLine - return lineLength + offset, lineCount, outStr + return lineLength + offset, lineCount, outStr, centered def convert_char_to_byte(incoming, array, lang): for pair in charConversionList: @@ -230,7 +250,6 @@ def convert_item(ogDict, lang): pixelsInLine = ogDict["pixelsInLine"] include_box_breaks = ogDict["includeBoxBreaks"] include_scrolling = ogDict["includeScrolling"] - center_text = ogDict["centerText"] arr = charArrayOfLanguage[lang]["array"] escape_list = charArrayOfLanguage[lang]["escape"] @@ -243,27 +262,39 @@ def convert_item(ogDict, lang): #print(f"Replacing {pair[0]} with {escapeString}!") line = line.replace(pair[0], escapeString) #print(line) + + # Special case for the centering escape character + line = line.replace("{CTR}", 'ɑ') + line = line.replace("{nCTR}", 'Ω') - # Special case for the Level values + # Special case for the values that change by language. Probably should be built into the lang struct eventually if (lang == Languages.English): - line = line.replace("{LVL}", 0x30) + line = line.replace("{LVL}", arr[0x30]) + line = line.replace("{PP}", arr[0x60]) + line = line.replace("{NO}", arr[0x70]) elif (lang == Languages.French): - line = line.replace("{LVL}", 0x31) + line = line.replace("{LVL}", arr[0x31]) + line = line.replace("{PP}", arr[0x60]) + line = line.replace("{NO}", arr[0x71]) elif (lang == Languages.German): - line = line.replace("{LVL}", 0x32) + line = line.replace("{LVL}", arr[0x32]) + line = line.replace("{PP}", arr[0x61]) + line = line.replace("{NO}", arr[0x72]) elif (lang == Languages.Italian): - line = line.replace("{LVL}", 0x33) + line = line.replace("{LVL}", arr[0x33]) + line = line.replace("{PP}", arr[0x60]) + line = line.replace("{NO}", arr[0x71]) elif (lang == Languages.SpanishEU or lang == Languages.SpanishLA): - line = line.replace("{LVL}", 0x34) + line = line.replace("{LVL}", arr[0x34]) + line = line.replace("{PP}", arr[0x60]) + line = line.replace("{NO}", arr[0x72]) - - # Change all the punctuation marks followed by spaces into being followed by _ . - # These will end up being replaced by spaces anyway in the end (but ignore the error) + # Change all the punctuation marks followed by spaces into being followed by | temporarily spaces = [' ', ' '] puncts = ['.', '?', '!', '。', '!', '?'] for space in spaces: for punct in puncts: - line = line.replace(punct + space, punct + "_") + line = line.replace(punct + space, punct + PURPOSEFUL_SPACE_CHAR) split_sents = split_into_sentences(line) index = 0 @@ -271,27 +302,31 @@ def convert_item(ogDict, lang): currLine = 0 offset = 0 escapeCount = 0 + centered = False while index < len(split_sents) and escapeCount < 100: - offset, recievedLine, out = split_sentence_into_lines(split_sents[index], offset, pixelsPerChar, pixelsInLine, center_text, lang) + offset, recievedLine, out, centered = split_sentence_into_lines(split_sents[index], offset, pixelsPerChar, pixelsInLine, centered, lang) currLine += recievedLine if (out == "ȼ"): offset = 0 currLine = 0 - if (outStr and (outStr[-1] == " ") or (outStr[-1] == 'Ň') or (outStr[-1] == 'Ş')): + # This tests if the character before the new box is a space, newline, or scroll + if outStr and (outStr[-1] in (" ", "Ň", "Ş")): outStr = outStr[:-1] outStr += "ȼ" index += 1 elif (currLine < (numLines + int(include_scrolling))): #print(split_sents[index]) index += 1 - if ((outStr and out and outStr[-1] == 'ȼ') and ((out[0] == ' ') or (out[0] == 'Ň') or (out[0] == 'Ş'))): - out = out[:-1] + # This tests if the character after the new box is a space, newline, or scroll + if outStr and out and outStr[-1] == 'ȼ' and out[0] in (" ", "Ň", "Ş"): + out = out[1:] outStr += out else: if not include_box_breaks: log_warning_error(lang, "Error", f"Attempted to make a new text box when disabled, sentence \"{outStr}\" is too long!") - elif (outStr and ((outStr[-1] == " ") or (outStr[-1] == 'Ň') or (outStr[-1] == 'Ş'))): + # This tests if the character before the new box is a space, newline, or scroll(?) + elif outStr and (outStr[-1] in (" ", "Ň", "Ş")): outStr = outStr[:-1] outStr += "ȼ" # new textbox character offset = 0 @@ -302,7 +337,10 @@ def convert_item(ogDict, lang): if escapeCount == 100: log_warning_error(lang, "Error", f"Sentence \"{out}\" is too long!") - + + # It's safe to swap the purposeful spaces back + outStr = outStr.replace(PURPOSEFUL_SPACE_CHAR, " ") + # Some cases that should be fixed exitLoop = False while(not exitLoop): @@ -311,7 +349,7 @@ def convert_item(ogDict, lang): splitBoxes = outStr.split('ȼ') outIndex = 0 for box in splitBoxes: - if box and ((box[0] == " ") or (box[0] == "_")): + if box and ((box[0] == " ")): box = box[1:] outIndex += 1 # Make sure both kinds of newlines are being accounted for @@ -330,7 +368,9 @@ def convert_item(ogDict, lang): outBox += split + breakChar outIndex += 1 i += 1 - if (outBox and (outBox[-1] != 'ȼ') and (outBox[-1] != 'Ň')): + if (outBox and (outBox[:-1] == 'ȼ') or (outBox[:-1] == 'Ň')): + newStr += f'{outBox[:-1]}ȼ' + elif (outBox): newStr += f'{outBox}ȼ' newStr = newStr[:-1] # remove the last ȼ @@ -498,7 +538,6 @@ def transfer_xlsx_to_dict(): "pixelsInLine" : currRow.iloc[4], "includeBoxBreaks": currRow.iloc[5], "includeScrolling": currRow.iloc[6], - "centerText": currRow.iloc[7] } def test_if_release(): if (BUILD_TYPE == 'release'): From 3526a96831671537610846d99610bcb6b90659b5 Mon Sep 17 00:00:00 2001 From: Peter Dubois Date: Fri, 13 Feb 2026 13:38:53 +0900 Subject: [PATCH 55/68] Updating some glyphs in Japanese font --- tools/text_helper/fonts/japanese_normal.png | Bin 3566 -> 4528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/text_helper/fonts/japanese_normal.png b/tools/text_helper/fonts/japanese_normal.png index 016373abdf4392d418b6106e32d9724a05ca78bd..5aeacc26284f0f09de783f5f7e957f18f9f21ab3 100644 GIT binary patch literal 4528 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 literal 3566 zcmcJS`9IX_`^WED%oxmA%5ugwwiFc#V+do*h-{H1%b*2yGL&Uxc@Gkj6qQqGNYP0m zyDVd0lVeMWDU4+jag+!j=X}3^z~`6G>zC_(T#x&4{q($^_v22qwKm7{;rRdnSPP1& zJpi!XBOD+RyXA4&tB&2GZfoUWmi~5UH##~xyN&-QcG`+76`_hFX%U@e%jPAAEZZrZ=gzWIX$A)Kjx*i5Q6@k@G;SwdF+A&X z`_$#XrmJ3 zjr)M@LE0F=UxeWp2nlg1i9lW>Pv?b1YKX%$9Uj6mk zt*;f=TAvj?3CYAxY5aL^mQ~C2&$xS3R|(&O=+Mt%w>*pl?P#JbjJa`3)X_32(ZT5V z-~QGU9)c1C#4p;7qwtG1abXx|i|DEU)l%v+=!TrVR;`WBzbt%nV~Y9?&A&4x0lK#GH!SsbTl!!58_R&1u9i@ zBQ;=s5z9}`qyYKUyh#m*W3<7={3dHT=i7L zNRkojjlfEC_2%0h&t2g zzftAH{@sL1 z%HB)}!ZEEJ*v9c?1bLqQ#+p*^x8^nO`_W}<773#~@_+sYksfOZ=fKdb-&iN#X?V}D zHfk%bEPsq24fljY9w9-KazjTm5xSg`&t$Ooj?=-S?u@kl)tfG!fa)4Vm4r|_?3XLJ zJrJ)|ayc&;2=B1q{h1v0nCnNenmgN5c%N6|@4C8*+<@B#WUHaMwb1l`NdI*a?Be`+ zGdV^pIEfo zwFnz!u7$k#H0|gCv(F~50-J=6%$bWI+o#R>+d~9ioRG9;e**(On!^Fv2$iME^z^R$cf?mVd+iinA;ootTPhnNEfR%9+7_=bDG-A4SbL(>q5>1uPqx(&CdR43O_(xZ3g7T zdk4f?u2+~^Qk*(P!Cg#fF?uga@xk(;5gtf*4^l|BZ*WFVu@nQa*dD<`1;ra-jnkl! z>z$^t{an;jxm1>5NEY#IzSG-Yy%KIQ0`M$B6s#=5gzdX4e!e3~&&OWZ<#2g(@~X4@hEp4j&!QF+7&kHnp_y$N zjV#?}w|X|?rI?HgbZ&x5lr6Sf(ned|4)}lQ4aTd-yCugT30D32?WRVdr)F$^p0Fw`2|-GF4dRq+8k9?&-NCC0fX0OMn=F1}Z% zNXZ1zG=*4?<6h^JpwSNT2|{#&liHInE6_gf4<5mlH1$*_%;3AUK|Rn4y!iJywJy z>|a#{7Za=3DL8ln_Gz>g)Zayilo1p!Ixp2>t+*MQy@--(iag*6_KBGq9Nt_&?miWKZ@>9t)P z{w!6Mj_8Fkrc@hOE9YW%&EkO~{M=-|D_zCJ_@3H%(%@A+bt358-0x(90u0PJMf?_nN-Bnxe@)6-&%)YiA(1fQw4sM7jv5&DOS7yG{eK! z9J~Ch64n<9lB(_C`)A1b0bgMy5_|E`t0&Rl{zaIK(3uWu8+T+>6^O`ffBSV3ypa?4+;)2IH4Qd4=i~Xv zIZOO34D^dKZblDpc_Kc_+#pUX_7x{7D~s48gzJKxJEE z)}O^Sx; zf>47SNh2RLiZb!1&{i{NJtKDZfnR0(U#Dr5p@|bCQ>W|snG4RW_b*_n$B>YjZkJrf zBkvKR9--ublyBn}VIx^njX?WCCH z%IV7VV5m4)zj+HFe>9l@`5N=zk%ZyYuYNvne)YZ$*1>tf$lQA%<17YxnN!^@`VlO{eh)(=HdDa^d{-g!RRpG_mbWoa5<-a$GSf- zRK)LXRdRi4BO0;NIp3i_sI^h{b=LHq=I0@P_j^A5a+uAPwk*pSoKTO=9i7_n0ELCa z;d{W9>Ez)t6uTpkYr!KS`5h~%EYu|D2x?+W<CM1M{d`dx^7(Gpw9X z<%Wd7pwANzaL5b#fwh7~+^B@;27*6W;$zK4!rWAH%n{I5uWIMICTJ-x)$3e2uA<0s zOcb034)HJg Date: Fri, 13 Feb 2026 21:25:31 -0500 Subject: [PATCH 56/68] Compressing underscores with escape characters --- source/main.cpp | 4 ++-- source/text_engine.cpp | 9 +++++++-- tools/text_helper/main.py | 21 ++++++++++++++++----- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/source/main.cpp b/source/main.cpp index dda44c2..93fdefd 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -467,9 +467,9 @@ static void __attribute__((noinline)) show_intro() { fade = abs(((get_frame_count() / 6) % 24) - 12); global_next_frame(); - start_pressed = key_hit(KEY_START) | key_hit(KEY_A); + start_pressed = key_hit(KEY_START) | key_hit(KEY_A) | true; REG_BLDALPHA = BLDA_BUILD(0b10000, fade); - }; + } } int main(void) diff --git a/source/text_engine.cpp b/source/text_engine.cpp index da63cd7..65fe827 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -341,13 +341,18 @@ int ptgb_write(const byte *text, bool instant, int length) wait_for_user_to_continue(true); break; case 0xFC: + ch = *str; + str++; + num += 1; if (DISPLAY_CONTROL_CHAR) { - tc->drawgProc(0xB9); + for (uint i = 0; i < ch; i++){ + tc->drawgProc(0xB9); + } } else { - tc->cursorX += tc->font->widths[0xFC]; + tc->cursorX += tc->font->widths[0xFC] * ch; } break; case 0xFE: diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index 344b5ca..6e8005b 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -204,9 +204,8 @@ def split_sentence_into_lines(sentence, offset, pixelsPerChar, pixelsInLine, cen offset = 0 if (centered and (len(words) > 0) and words[0] != 'ɑ'): count = ((pixelsInLine - lineLength) // 2) - for i in range(count): - currLine = "_" + currLine - lineLength += 1 # This character should *always* be one pixel wide + currLine = f'_[{count}]{currLine}' + lineLength += count outStr += currLine return lineLength + offset, lineCount, outStr, centered @@ -383,8 +382,20 @@ def convert_item(ogDict, lang): byteStr = "" arr = charArrayOfLanguage[lang]["array"] - for char in outStr[:-1]: - byteStr += f"{convert_char_to_byte(ord(char), arr, lang):02x} " + i = 0 + while i < len(outStr[:-1]): + char = outStr[i] + if (char == '['): + val = '' + i += 1 + while outStr[i] != ']': + val = val + outStr[i] + i += 1 + num = int(val) + byteStr += f"{num:02x} " + else: + byteStr += f"{convert_char_to_byte(ord(char), arr, lang):02x} " + i += 1 if (len(outStr) > 0 and outStr[-1] != ' '): # Check if the last char is a space byteStr += f"{convert_char_to_byte(ord(outStr[-1]), arr, lang):02x} " From e583853ea905fd15db482c10b657c5c7b80113fb Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Fri, 13 Feb 2026 22:16:27 -0500 Subject: [PATCH 57/68] Adding check for bin file sizes --- tools/text_helper/main.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index 6e8005b..b9318ea 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -404,7 +404,16 @@ def convert_item(ogDict, lang): ogDict["bytes"] = byteStr return ogDict -def write_text_bin_file(filename, dictionary, lang): +def write_text_bin_file(filename, dictionary, lang, section): + MAX_BIN_SIZES = { + "PTGB": 6144, + "RSEFRLG": 3444, + "GB": 9999, + "GENERAL": 2048, + "CREDITS": 2048, + "PKMN_NAMES": 3072, + } + with open(filename, 'wb') as binFile: # Let the first byte indicate the number of entries dict_size = len(dictionary) @@ -434,6 +443,9 @@ def write_text_bin_file(filename, dictionary, lang): # Write the index and bindata to the file binFile.write(index) binFile.write(bindata) + binFile.seek(0, os.SEEK_END) + if binFile.tell() > MAX_BIN_SIZES[section]: + log_warning_error(lang, "Error", f'Section {section} exceeds the max binary file size by {binFile.tell() - MAX_BIN_SIZES[section]} bytes!') binFile.close() def write_enum_to_header_file(hFile, prefix, dictionary): @@ -590,7 +602,7 @@ def generate_text_tables(): for lang in Languages: for section in textSections: table_file = os.curdir + '/to_compress/' + section + '_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name][section], lang) + write_text_bin_file(table_file, mainDict[lang.name][section], lang, section) def generate_cpp_file(): print("\tGenerating cpp file") From 38454168fd1f6cbabc38071da05441a416c6e9f0 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Sun, 15 Feb 2026 22:43:00 -0500 Subject: [PATCH 58/68] Adding space escape character --- tools/text_helper/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index b9318ea..28694c9 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -805,6 +805,7 @@ charArrays = { ["{FEM}", [0xFC, 0x01, 0x04]], ["{FPC}", [0xFC, 0x01, 0x06]], ["{MLE}", [0xFC, 0x01, 0x08]], + ["{SPA}", [0xFC]], ["{PLR}", [0xFD, 0x01]], ["{NEW}", [0xFE]], ["{END}", [0xFF]], @@ -819,6 +820,7 @@ charArrays = { ["{DEF}", [0xFC, 0x06, 0x02]], ["{FEM}", [0xFC, 0x06, 0x03]], # ??? ["{MLE}", [0xFC, 0x06, 0x04]], + ["{SPA}", [0xFC]], ["{FPC}", [0xFC, 0x06, 0x05]], ["{PLR}", [0xFD, 0x01]], ["{NEW}", [0xFE]], From b806100da16137c36870c11b8362495baf137f26 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 17 Feb 2026 16:42:48 -0500 Subject: [PATCH 59/68] Fixing Spanish makefile names --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0f97845..2967c69 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -BUILD_LANGS := japanese english french german italian spanish-eu spanish-la +BUILD_LANGS := japanese english french german italian spanisheu spanishla BUILD_TYPES := release debug # defaults From 335e1dfe7f9565372fe504700012ccbc3ff976e7 Mon Sep 17 00:00:00 2001 From: The Gears of Progress Date: Tue, 17 Feb 2026 17:40:35 -0500 Subject: [PATCH 60/68] Updating Makefile and Dockerfile --- Dockerfile | 2 +- Makefile | 2 +- source/main.cpp | 2 +- tools/payload-generator/Makefile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f7e23b..9c5ffca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,4 @@ ARG GROUP_ID ENV DEBIAN_FRONTEND="noninteractive" -RUN apt update && apt install -y build-essential python3-pip && pip install pandas requests openpyxl --break-system-packages +RUN apt update && apt install -y build-essential python3-pip python3-png && pip install pandas requests openpyxl --break-system-packages \ No newline at end of file diff --git a/Makefile b/Makefile index 2967c69..b959db5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -BUILD_LANGS := japanese english french german italian spanisheu spanishla +BUILD_LANGS := japanese english french german italian spanishEU spanishLA BUILD_TYPES := release debug # defaults diff --git a/source/main.cpp b/source/main.cpp index 93fdefd..9c69ab7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -467,7 +467,7 @@ static void __attribute__((noinline)) show_intro() { fade = abs(((get_frame_count() / 6) % 24) - 12); global_next_frame(); - start_pressed = key_hit(KEY_START) | key_hit(KEY_A) | true; + start_pressed = key_hit(KEY_START) | key_hit(KEY_A); REG_BLDALPHA = BLDA_BUILD(0b10000, fade); } } diff --git a/tools/payload-generator/Makefile b/tools/payload-generator/Makefile index a1de7be..bf978a4 100644 --- a/tools/payload-generator/Makefile +++ b/tools/payload-generator/Makefile @@ -1,5 +1,5 @@ -BUILD_LANGS := japanese english french german italian spanish-eu spanish-la +BUILD_LANGS := japanese english french german italian spanishEU spanishLA BUILD_TYPES := release debug # defaults From d0cfed60188ad3b1610889f5adfd462e1f979b8f Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Thu, 19 Feb 2026 21:49:43 +0100 Subject: [PATCH 61/68] Fix some build issues Problem 1: syntax error in text_helper Fix 1: single quotes instead of double quotes Problem 2: the ground shifted beneath our feet with the devkitpro/devkitARM base image again. The --break-system-packages option no longer exists on pip install. Fix 2: Use a specific tag of the devkitpro/devkitARM docker image to ensure this won't happen again. --- Dockerfile | 4 ++-- tools/text_helper/main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9c5ffca..24e4f93 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM devkitpro/devkitarm +FROM devkitpro/devkitarm:20251117 LABEL author="Poke Transporter GB" @@ -9,4 +9,4 @@ ARG GROUP_ID ENV DEBIAN_FRONTEND="noninteractive" -RUN apt update && apt install -y build-essential python3-pip python3-png && pip install pandas requests openpyxl --break-system-packages \ No newline at end of file +RUN apt update && apt install -y build-essential python3-pip python3-png && pip install pandas requests openpyxl --break-system-packages diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index 28694c9..f88e388 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -617,7 +617,7 @@ def generate_cpp_file(): cppFile.write("\nconst u8* get_compressed_text_table(int table_index)\n") for i, lang in enumerate(Languages): - cppFile.write(f"\n#{"el" if i > 0 else ""}if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") + cppFile.write(f"\n#{'el' if i > 0 else ''}if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") cppFile.write("{\n") cppFile.write("\tswitch (table_index)\n\t{\n") for section in textSections: From 48af7e2fd20816c5b37fb3f59d442c037c59698d Mon Sep 17 00:00:00 2001 From: GearsProgress Date: Thu, 19 Feb 2026 17:21:15 -0500 Subject: [PATCH 62/68] Updating gitignore and PCCS --- .gitignore | 2 +- PCCS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8e84241..38c42b5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ to_compress/ *.zip *.exe .vscode/ - +.venv/ diff --git a/PCCS b/PCCS index db430d8..a1a2458 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 +Subproject commit a1a2458970e62c6247fad88f641c6100285168fa From 51ab2487adadcd6443e72d48cd62aae058e9619d Mon Sep 17 00:00:00 2001 From: GearsProgress Date: Thu, 19 Feb 2026 17:23:16 -0500 Subject: [PATCH 63/68] Updating gitignore (again) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 38c42b5..ecb78a3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ to_compress/ *.exe .vscode/ .venv/ +tools/payload-generator/payload-generator \ No newline at end of file From cf2e09d102f733c291269d95e679e7e95c51040a Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 23 Feb 2026 20:52:14 +0100 Subject: [PATCH 64/68] Fix the text-debug-screen branch --- Makefile | 10 +--------- tools/text_helper/main.py | 6 +++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index b959db5..52d2814 100644 --- a/Makefile +++ b/Makefile @@ -179,19 +179,11 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) GENERATE_STAMP := $(BUILD)/.generate_data.$(BUILD_LANG).$(BUILD_TYPE).stamp BUILD_STAMP := $(BUILD)/.build.$(BUILD_LANG).$(BUILD_TYPE).stamp -TEXT_HELPER_INPUTS := $(shell find tools/text_helper -type f \( -name "*.py" -o -name "*.xlsx" -o -name "*.png" \)) PAYLOAD_GEN_INPUTS := $(shell find tools/payload-generator/src tools/payload-generator/include -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \)) all: $(BUILD_STAMP) -TEXT_GENERATED := $(CURDIR)/$(GENERATED_DIR)/translated_text.h \ - $(CURDIR)/$(GENERATED_DIR)/translated_text.cpp \ - $(CURDIR)/$(GENERATED_DIR)/fonts.h \ - $(CURDIR)/$(GENERATED_DIR)/output.json - -text_generated: $(TEXT_GENERATED) - -$(TEXT_GENERATED): $(TEXT_HELPER_INPUTS) | data to_compress generated_dir +text_generated: to_compress generated_dir data @PTGB_GEN_DIR="$(CURDIR)/$(GENERATED_DIR)" python3 tools/text_helper/main.py $(BUILD_LANG) $(BUILD_TYPE) data: diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index f88e388..1bfda05 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -498,7 +498,7 @@ def download_xlsx_file(): if old_file_path.exists(): if hash_excel(new_file_path) == hash_excel(old_file_path): - print("Downloaded file is identical") + print("Downloaded file is identical. Deleting new file: " + str(new_file_path)) new_file_path.unlink() if json_file_path.exists(): print("Skipping parse") @@ -513,7 +513,7 @@ def download_xlsx_file(): new_file_path.rename(old_file_path) else: - print("No cached xlsx - forcing rebuild") + print("No cached xlsx - forcing rebuild: " + str(new_file_path) + " renamed to " + str(old_file_path)) new_file_path.rename(old_file_path) def transfer_xlsx_to_dict(): @@ -564,7 +564,7 @@ def transfer_xlsx_to_dict(): } def test_if_release(): if (BUILD_TYPE == 'release'): - print("\tis release. Saving text file as release.xlsx") + print("\tis release. Saving text file as release.xlsx. Copying " + str(old_file_path) + " to " + str(release_file_path)) shutil.copy(old_file_path, release_file_path) def generate_header_file(): From be3c250e3e014d09d526e1406eea8e250a54f7df Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 23 Feb 2026 20:54:41 +0100 Subject: [PATCH 65/68] Revert debug logs --- tools/text_helper/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index 1bfda05..d1ba73b 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -498,7 +498,7 @@ def download_xlsx_file(): if old_file_path.exists(): if hash_excel(new_file_path) == hash_excel(old_file_path): - print("Downloaded file is identical. Deleting new file: " + str(new_file_path)) + print("Downloaded file is identical.") new_file_path.unlink() if json_file_path.exists(): print("Skipping parse") @@ -513,7 +513,7 @@ def download_xlsx_file(): new_file_path.rename(old_file_path) else: - print("No cached xlsx - forcing rebuild: " + str(new_file_path) + " renamed to " + str(old_file_path)) + print("No cached xlsx - forcing rebuild") new_file_path.rename(old_file_path) def transfer_xlsx_to_dict(): @@ -564,7 +564,7 @@ def transfer_xlsx_to_dict(): } def test_if_release(): if (BUILD_TYPE == 'release'): - print("\tis release. Saving text file as release.xlsx. Copying " + str(old_file_path) + " to " + str(release_file_path)) + print("\tis release. Saving text file as release.xlsx") shutil.copy(old_file_path, release_file_path) def generate_header_file(): From 2e257922274990132662854cec11f506ebe26e67 Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 23 Feb 2026 20:55:18 +0100 Subject: [PATCH 66/68] More revert log --- tools/text_helper/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index d1ba73b..f88e388 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -498,7 +498,7 @@ def download_xlsx_file(): if old_file_path.exists(): if hash_excel(new_file_path) == hash_excel(old_file_path): - print("Downloaded file is identical.") + print("Downloaded file is identical") new_file_path.unlink() if json_file_path.exists(): print("Skipping parse") From 5651194a150d45a31e1597096d60b74b07793d1b Mon Sep 17 00:00:00 2001 From: GearsProgress Date: Mon, 23 Feb 2026 22:59:54 -0500 Subject: [PATCH 67/68] Updating PCCS --- PCCS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PCCS b/PCCS index a1a2458..db430d8 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit a1a2458970e62c6247fad88f641c6100285168fa +Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 From bd7979bbc56b00b4f5a15207e252b4da756b032c Mon Sep 17 00:00:00 2001 From: GearsProgress Date: Mon, 23 Feb 2026 23:04:30 -0500 Subject: [PATCH 68/68] Updating PCCS (again) --- PCCS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PCCS b/PCCS index db430d8..6858e4b 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit db430d84c27bf196ab61e07d4b4588ddc093da08 +Subproject commit 6858e4ba23f4553f06c41db77959467afba2e77e