From 26fd1e2dd36299ff7d279575daf44d751297d95d Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Wed, 21 May 2025 12:21:06 +0200 Subject: [PATCH] Add compression for the text data, output stack usage .su files and rework script_array Add a binary table format and convert the text entries into this format in text_helper/main.py. It then gets compressed with zx0. The new text_data_table and streamed_data_table classes exist to read the various entries from this binary table. streamed_data_table specifically exists to use a decompression buffer that is smaller than the actual binary table. But it requires a decompression buffer that is still larger than ZX0_DEFAULT_WINDOW_SIZE (default: 2048 bytes) and will only be able to decompress in chunks of ( - ) bytes Try to keep the binary text tables sufficiently small though, because since zx0 doesn't actually support random access, getting to the last entry is significantly more expensive than reading the first one. And unless you use streamed_data_table, it also requires bytes of stack space, therefore IWRAM to decompress them. I also had to rework script_array because it can no longer reference the strings directly. Instead we now reference the DIA_* "enum" values. We also no longer store an array of script_obj instances, because these were getting stored in IWRAM since they're non-const global variables originally. Instead we now have const arrays of script_obj_params structs, which should end up in .rodata -> therefore EWRAM. Right now, script_obj only supports the PTGB text table (originally the dialogue array). But if the need arises to support other tables as well, I'd consider adding a separate enum to script_obj_params to indicate the specific table. The compilation process will also output .su files in the build folder from now on. These files indicate the stack frame size for every function in every compilation unit, so be sure to check them from time to time. Note that they will only show the stack consumption for that specific function. So to get the worst case stack consumption, you need to manually add all the functions in a certain stack flow. --- Dockerfile | 2 +- Makefile | 12 +- include/mystery_gift_builder.h | 3 +- include/script_array.h | 5 +- include/script_obj.h | 29 +- include/script_var.h | 3 +- include/select_menu.h | 4 +- include/text_data_table.h | 83 ++ include/zx0_decompressor.h | 18 +- source/box_menu.cpp | 12 +- source/global_frame_controller.cpp | 17 +- source/main.cpp | 267 +++++-- source/multiboot_upload.cpp | 14 +- source/mystery_gift_builder.cpp | 174 +++-- source/mystery_gift_injector.cpp | 56 +- source/pokedex.cpp | 46 +- source/script_array.cpp | 824 ++++++++++++++++---- source/script_obj.cpp | 55 +- source/script_var.cpp | 25 +- source/select_menu.cpp | 10 +- source/text_data_table.cpp | 154 ++++ source/text_engine.cpp | 28 +- source/zx0_decompressor.cpp | 51 +- text_helper/main.py | 218 ++++-- tools/data-generator/include/common.h | 2 +- tools/data-generator/include/pokemon_data.h | 2 +- tools/data-generator/src/common.cpp | 15 +- tools/data-generator/src/main.cpp | 7 +- tools/data-generator/src/pokemon_data.cpp | 26 +- 29 files changed, 1628 insertions(+), 534 deletions(-) create mode 100644 include/text_data_table.h create mode 100644 source/text_data_table.cpp mode change 100644 => 100755 text_helper/main.py diff --git a/Dockerfile b/Dockerfile index 4dcb580..1d15c84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,4 @@ ARG GROUP_ID ENV DEBIAN_FRONTEND="noninteractive" -RUN apt update && apt install -y build-essential +RUN apt update && apt install -y build-essential python3-pip && pip install pandas requests openpyxl diff --git a/Makefile b/Makefile index 096e075..3a640aa 100644 --- a/Makefile +++ b/Makefile @@ -45,8 +45,8 @@ CFLAGS := -Wall -O2\ -mcpu=arm7tdmi -mtune=arm7tdmi -masm-syntax-unified\ $(ARCH) -CFLAGS += $(INCLUDE) -ffunction-sections -fdata-sections -Os -Wall -mthumb -mcpu=arm7tdmi -mtune=arm7tdmi -CXXFLAGS := $(CFLAGS) -g0 -fno-rtti -fno-exceptions -fdata-sections -ffunction-sections -std=c++20 -Wno-volatile -D_GLIBCXX_USE_CXX20_ABI=0 +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 ifeq ($(BUILD_TYPE), debug) CFLAGS += -g -DDEBUG @@ -144,10 +144,12 @@ all: $(BUILD) generate_data: mkdir -p data + mkdir -p to_compress @env -i PATH=$(PATH) $(MAKE) -C tools/compressZX0 @env -i PATH=$(PATH) $(MAKE) -C tools/data-generator - @cd tools/data-generator && ./data-generator - @find tools/data-generator -name *.bin | xargs -i tools/compressZX0/compressZX0 {} data/ + @tools/data-generator/data-generator to_compress + @python3 text_helper/main.py + @find to_compress -name "*.bin" | xargs -i tools/compressZX0/compressZX0 {} data/ #--------------------------------------------------------------------------------- $(BUILD): generate_data @@ -163,7 +165,7 @@ clean: @$(MAKE) -C tools/compressZX0 clean @$(MAKE) -C tools/data-generator clean @$(MAKE) -C loader clean - @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba data/ + @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba data/ to_compress/ #--------------------------------------------------------------------------------- diff --git a/include/mystery_gift_builder.h b/include/mystery_gift_builder.h index 25ecdf0..92c1bc9 100644 --- a/include/mystery_gift_builder.h +++ b/include/mystery_gift_builder.h @@ -256,7 +256,6 @@ #define CPU_SET_32BIT 0x04000000 class mystery_gift_script { - PokemonTables &data_tables; int curr_mg_index; int curr_section30_index; u8 mg_script[MG_SCRIPT_SIZE] = {}; @@ -265,7 +264,7 @@ class mystery_gift_script u8 four_align_value = 0; public: - mystery_gift_script(PokemonTables &data_tables); + mystery_gift_script(); void build_script(Pokemon_Party &incoming_box_data); //void build_script_old(Pokemon_Party &incoming_box_data); u8 get_script_value_at(int index); diff --git a/include/script_array.h b/include/script_array.h index 77bbe4f..3a30e83 100644 --- a/include/script_array.h +++ b/include/script_array.h @@ -59,11 +59,10 @@ #define SCRIPT_SIZE COND_END -extern script_obj transfer_script[]; -extern script_obj event_script[]; +extern const script_obj_params transfer_script_params[]; +extern const script_obj_params event_script_params[]; extern rom_data curr_rom; -void populate_script(); void populate_lang_menu(); void populate_game_menu(int lang); bool run_conditional(int index); diff --git a/include/script_obj.h b/include/script_obj.h index fb3f7a6..2476ed3 100644 --- a/include/script_obj.h +++ b/include/script_obj.h @@ -1,28 +1,29 @@ #ifndef SCRIPT_OBJ_H #define SCRIPT_OBJ_H -#include -#include "pokemon_party.h" +#include +typedef struct +{ + u16 text_entry_index; + u16 conditional_index; + u16 next_if_true; + u16 next_if_false; +} script_obj_params; class script_obj { public: script_obj(); - script_obj(const byte* nText, uint16_t nNext); // For dialogue - script_obj(uint16_t nRun, uint16_t nNext); // For commands - script_obj(uint16_t nRun, uint16_t nNext_if_true, uint16_t nNext_if_false); // for conditionals + script_obj(const script_obj_params ¶ms); - const byte* get_text(); - uint16_t get_true_index(); - uint16_t get_false_index(); - uint16_t get_cond_id(); + bool has_text() const; + u8 get_text_entry_index() const; + u16 get_true_index() const; + u16 get_false_index() const; + u16 get_cond_id() const; private: - const byte* text; - bool has_text = false; - uint16_t next_index; - uint16_t conditional_index; - uint16_t next_false_index; + script_obj_params params_; }; #endif \ No newline at end of file diff --git a/include/script_var.h b/include/script_var.h index 043f390..4be13ca 100644 --- a/include/script_var.h +++ b/include/script_var.h @@ -49,9 +49,8 @@ class textbox_var : public xse_var public: using xse_var::xse_var; void set_text(const byte nText[]); - void insert_text(const u16 *charset, u8 mg_array[]); + void insert_text(const u16 *charset, u8 mg_array[], bool should_set_virtual_start = false); void set_start(); - void insert_virtual_text(const u16 *charset, u8 mg_array[]); void set_virtual_start(); const byte *text; int text_length; diff --git a/include/select_menu.h b/include/select_menu.h index 5f94547..7d6e2c3 100644 --- a/include/select_menu.h +++ b/include/select_menu.h @@ -16,11 +16,11 @@ public: void hide_menu(); void show_menu(); void clear_options(); - void add_option(const byte *option, u8 return_value); + void add_option(const u8 option, u8 return_value); void set_lang(u8 nLang); private: - ptgb::vector menu_options; + ptgb::vector menu_options; ptgb::vector return_values; u16 curr_selection; bool cancel_enabled; diff --git a/include/text_data_table.h b/include/text_data_table.h new file mode 100644 index 0000000..f93fe1f --- /dev/null +++ b/include/text_data_table.h @@ -0,0 +1,83 @@ +#ifndef _TEXT_DATA_TABLE_H +#define _TEXT_DATA_TABLE_H + +#include + +/** + * This class fully decompresses a text table in the specified decompression_buffer + * and then gives you utility functions to retrieve the text entries + * + * But it requires a buffer large enough to contain the entire decompressed table. + * It will also completely decompress the table, which may not what you want. + * + * If you want to use a text table in a streamed manner (use smaller decompression buffer and only decompress what's needed) + * consider using streamed_text_data_table instead. + */ +class text_data_table +{ +public: + text_data_table(uint8_t *decompression_buffer); + + /** + * This function will start the full decompression for the specified compressed_table + * and stores it in the decompression_buffer_ + */ + void decompress(const uint8_t *compressed_table); + + /** + * Returns the number of text entries in the decompression_buffer_ + */ + uint16_t get_number_of_text_entries() const; + + /** + * This function returns a pointer to a text entry in the decompression_buffer + */ + const uint8_t* get_text_entry(uint8_t index) const; +private: + uint8_t *decompression_buffer_; +}; + +/** + * This class is an alternative to translated_text table. + * It provides the same functionality, yet in a streamed manner. + * + * This allows you to use a decompression_buffer that is smaller than the fully decoded text table and only decompress until + * you have what you need. + * + * To make sure we have access to the table index at all times, you need to specify a buffer to hold the table index as well. + * + * REQUIREMENT: decompression_buffer needs to be larger than the zx0 window size!! + */ +class streamed_text_data_table +{ +public: + streamed_text_data_table(uint8_t *decompression_buffer, uint32_t decompression_buffer_size, uint8_t *index_buffer); + + /** + * This function sets up the zx0 decompressor and decompresses the index into the index_buffer + */ + void decompress(const uint8_t *compressed_table); + + /** + * Returns the number of text entries in the decompression_buffer_ + */ + uint16_t get_number_of_text_entries() const; + + /** + * This function returns a pointer to a text entry in the decompression_buffer + */ + const uint8_t* get_text_entry(uint8_t index); +private: + uint8_t* get_window_start() const; + uint8_t* get_window_end() const; + uint16_t get_current_zx0_window_size() const; + + const uint8_t *compressed_table_; + uint8_t *decompression_buffer_; + uint32_t decompression_buffer_size_; + uint8_t *index_buffer_; + mutable uint16_t bytes_decompressed_; + uint16_t last_chunk_size_; +}; + +#endif \ No newline at end of file diff --git a/include/zx0_decompressor.h b/include/zx0_decompressor.h index 9409637..7a812b1 100644 --- a/include/zx0_decompressor.h +++ b/include/zx0_decompressor.h @@ -3,6 +3,8 @@ #include +#define ZX0_DEFAULT_WINDOW_SIZE 2048 + // The ZX0 decompressor offers functionality to decompress data // compressed with the ZX0 algorithm (see tools/compressZX0) // This algorithm was invented by Einar Saukas @@ -25,9 +27,23 @@ extern "C" /** * @brief This function copies of decompressed data into the specified - * It will trigger decompression on the go (streaming basis) + * It will append to the existing output_buffer you set earlier with zx0_decompressor_start() */ void zx0_decompressor_read(uint32_t num_bytes); + + /** + * @brief this function does a partial decompress into output_buffer. + * HOWEVER: zx0 decompression requires you to use previously decompressed bytes to decompress the current ones. + * In order to accomplish this, output_buffer MUST NOT point to the start of the buffer!! + * Instead it should refer to a point within the buffer with previously decoded bytes available before it, + * with up to the bytes available before that point. + * (So if you decoded >= window size, bytes should be available before output_buffer. + * If not, before output_buffer should have ) + * + * This function is intended as a way to read data in a "streamed" way into a smaller buffer that is smaller than the actual decompressed file size. + * NOTE: when used in a loop, you should manually move the decompressed data from the previous iteration to the front of the buffer (output_buffer + window_size - num_bytes) + */ + void zx0_decompressor_read_partial(uint8_t *output_buffer, uint16_t num_bytes); } #endif \ No newline at end of file diff --git a/source/box_menu.cpp b/source/box_menu.cpp index 30ffff6..1792533 100644 --- a/source/box_menu.cpp +++ b/source/box_menu.cpp @@ -9,11 +9,16 @@ #include "pokemon_data.h" #include "text_engine.h" #include "translated_text.h" +#include "text_data_table.h" +#include "zx0_decompressor.h" Box_Menu::Box_Menu() {}; int Box_Menu::box_main(Pokemon_Party party_data) { + u8 names_decompression_buffer[3072]; + text_data_table PKMN_NAMES(names_decompression_buffer); + tte_erase_screen(); load_flex_background(BG_BOX, 2); REG_BG1VOFS = 0; @@ -32,6 +37,9 @@ int Box_Menu::box_main(Pokemon_Party party_data) bool update_pos = true; obj_unhide(box_select, 0); int index = 0; + + PKMN_NAMES.decompress(get_compressed_pkmn_names_table()); + while (true) { if (get_frame_count() % 20 == 0) @@ -140,12 +148,12 @@ int Box_Menu::box_main(Pokemon_Party party_data) tte_set_pos(14, 98); if (curr_pkmn.is_missingno) { - ptgb_write(PKMN_NAMES[0], true); + ptgb_write(PKMN_NAMES.get_text_entry(0), true); } else { - ptgb_write(PKMN_NAMES[curr_pkmn.dex_number], true); + ptgb_write(PKMN_NAMES.get_text_entry(curr_pkmn.dex_number), true); } tte_set_pos(6, 108); val[0] = 0xC6; // L diff --git a/source/global_frame_controller.cpp b/source/global_frame_controller.cpp index d8197cf..ba3e86e 100644 --- a/source/global_frame_controller.cpp +++ b/source/global_frame_controller.cpp @@ -7,6 +7,7 @@ #include "text_engine.h" #include "sprite_data.h" #include "string.h" +#include "text_data_table.h" int global_frame_count = 0; bool rand_enabled = true; @@ -17,6 +18,18 @@ int fennel_blink_state = 0; bool missingno_enabled = false; bool treecko_enabled = false; +// split off from global_next_frame to limit the stack usage of the general_text_table_buffer to +// the execution of this function +// the noinline attribute prevents the compiler from inlining this function back into the global_next_frame function +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()); + ptgb_write(general_text.get_text_entry(GENERAL_pulled_cart_error), true); +} + void global_next_frame() { key_poll(); @@ -41,7 +54,9 @@ void global_next_frame() tte_set_pos(40, 24); create_textbox(4, 1, 160, 80, true); obj_hide_multi(ptgb_logo_l, num_sprites); - ptgb_write(pulled_cart_error, true); + + show_pulled_cart_error(); + oam_copy(oam_mem, obj_buffer, num_sprites); while (true) { diff --git a/source/main.cpp b/source/main.cpp index e1812cd..df625d0 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -29,6 +29,7 @@ #include "multiboot_upload.h" #include "rom_data.h" #include "libraries/Pokemon-Gen3-to-Gen-X/include/save.h" +#include "text_data_table.h" /* @@ -134,8 +135,7 @@ void initalization_script(void) oam_init(obj_buffer, 128); load_graphics(); - // Prepare dialouge - populate_script(); + // Prepare text engine for dialogue init_text_engine(); // Set the random seed @@ -153,7 +153,15 @@ void game_load_error(void) REG_BG2CNT = (REG_BG2CNT & ~BG_PRIO_MASK) | BG_PRIO(1); create_textbox(4, 1, 152, 100, true); - ptgb_write(cart_load_error, true); + + { + u8 general_text_table_buffer[2048]; + text_data_table general_text(general_text_table_buffer); + + general_text.decompress(get_compressed_general_table()); + ptgb_write(general_text.get_text_entry(GENERAL_cart_load_error), true); + } + key_poll(); do { @@ -187,7 +195,15 @@ void first_load_message(void) tte_set_margins(8, 8, H_MAX - 8, V_MAX); tte_set_pos(8, 8); tte_set_ink(INK_ROM_COLOR); - ptgb_write(intro_first, true); + + { + u8 general_text_table_buffer[2048]; + text_data_table general_text(general_text_table_buffer); + + general_text.decompress(get_compressed_general_table()); + ptgb_write(general_text.get_text_entry(GENERAL_intro_first), true); + } + while (!key_hit(KEY_A)) { global_next_frame(); @@ -195,34 +211,64 @@ void first_load_message(void) tte_erase_rect(0, 0, H_MAX, V_MAX); } +#define TIMER_ENABLE 0x80 +#define TIMER_CASCADE 0x4 +#define TIMER_FREQ_1 0x0 // 16.78 MHz +#define TIMER_FREQ_64 0x1 // 262,144 Hz +#define TIMER_FREQ_256 0x2 // 65,536 Hz +#define TIMER_FREQ_1024 0x3 // 16,384 Hz + +int test_decompress() +{ + uint16_t charset[256]; + + // Reset both timers + REG_TM0CNT = 0; + REG_TM1CNT = 0; + REG_TM0D = 0; + REG_TM1D = 0; + + // Set up TIMER0: count with no prescaler + REG_TM0CNT = TIMER_ENABLE | TIMER_FREQ_1; + // Set up TIMER1: cascade mode (increment when TIMER0 overflows) + REG_TM1CNT = TIMER_ENABLE | TIMER_CASCADE; + + load_localized_charset(charset, 3, ENG_ID); + + // Read combined 32-bit timer value + const u32 ticks = ((u32)REG_TM1D << 16) | REG_TM0D; + + // Stop timers + REG_TM0CNT = 0; + REG_TM1CNT = 0; + + create_textbox(4, 1, 160, 80, true); + + ptgb_write_debug(charset, "Test results:\n\nDecompress: ", true); + ptgb_write_debug(charset, ptgb::to_string(ticks * 1000 / 16777), true); + ptgb_write_debug(charset, " usec\n", true); + + + while (true) + { + if (key_hit(KEY_B)) + { + hide_text_box(); + reset_textbox(); + return 0; + } + global_next_frame(); + } + return 0; +} + int credits() { - char hexBuffer[16]; -#define CREDITS_ARRAY_SIZE 19 + u8 text_decompression_buffer[2048]; + text_data_table credits_text_table(text_decompression_buffer); int curr_credits_num = 0; - const byte *credits_array[CREDITS_ARRAY_SIZE] = { - credits_page_1, - credits_page_2, - credits_page_3, - credits_page_4, - credits_page_5, - credits_page_6, - credits_page_7, - credits_page_8, - credits_page_9, - credits_page_10, - credits_page_11, - credits_page_12, - credits_page_13, - credits_page_14, - credits_page_15, - credits_page_16, - credits_page_17, - credits_page_18, - credits_page_19, - // Add translators - }; + credits_text_table.decompress(get_compressed_credits_table()); bool update = true; global_next_frame(); @@ -232,7 +278,7 @@ int credits() { create_textbox(4, 1, 160, 80, true); show_text_box(); - ptgb_write(credits_array[curr_credits_num], true); + ptgb_write(credits_text_table.get_text_entry(curr_credits_num), true); update = false; } @@ -247,13 +293,19 @@ int credits() curr_credits_num--; update = true; } - if (key_hit(KEY_RIGHT) && curr_credits_num < (CREDITS_ARRAY_SIZE - 1)) + if (key_hit(KEY_RIGHT) && curr_credits_num < (credits_text_table.get_number_of_text_entries() - 1)) { curr_credits_num++; update = true; } + if(key_hit(KEY_SELECT)) + { + return test_decompress(); + } + #if 0 if (ENABLE_DEBUG_SCREEN && key_hit(KEY_SELECT)) { + char hexBuffer[16]; uint16_t charset[256]; load_localized_charset(charset, 3, ENG_ID); if (key_held(KEY_UP) && key_held(KEY_L) && key_held(KEY_R)) @@ -327,6 +379,7 @@ int credits() global_next_frame(); } } + #endif global_next_frame(); } }; @@ -335,10 +388,16 @@ int credits() int main_menu_loop() { + uint8_t general_text_table_buffer[2048]; + text_data_table general_text(general_text_table_buffer); bool update = true; - const byte *menu_options[NUM_MENU_OPTIONS] = {option_transfer, option_dreamdex, option_credits}; + 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_general_table()); + while (true) { if (update) @@ -346,7 +405,8 @@ int main_menu_loop() tte_erase_rect(0, 80, 240, 160); for (int i = 0; i < NUM_MENU_OPTIONS; i++) { - int size = get_string_length(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)); @@ -358,7 +418,7 @@ int main_menu_loop() { tte_set_ink(INK_ROM_COLOR); } - ptgb_write(menu_options[i], true); + ptgb_write(text_entry, true); test++; } } @@ -387,6 +447,93 @@ int main_menu_loop() } } +// Legal mumbo jumbo +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_ink(INK_ROM_COLOR); + ptgb_write(intro_text, true); + bool wait = true; + while (wait) + { + global_next_frame(); + if (key_hit(KEY_A)) + { + wait = false; + } + } +} + +// Gears of Progress +static void show_gears_of_progress() +{ + tte_erase_rect(0, 0, 240, 160); + REG_BG1VOFS = 0; + delay_counter = 0; + while (delay_counter < (15 * 60)) + { + global_next_frame(); + delay_counter++; + if (key_hit(KEY_A)) + { + delay_counter = (15 * 60); + } + } +} + +// split off from the main function in order to keep the scope of the variables limited to the execution of this function +// otherwise they stick around for the entire runtime of the program +// the attribute noinline is used to prevent the compiler from inlining this function back into the main function +// this decision was based on the output of build/main.su after adding the -fstack-usage compile flag +static void __attribute__((noinline)) show_intro() +{ + bool start_pressed = false; + u8 general_text_table_buffer[2048]; + u8 press_start_text[32]; + u8 press_start_text_length; + + text_data_table general_text(general_text_table_buffer); + const u8 *text_entry; + + 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); + memcpy(press_start_text, text_entry, press_start_text_length + 1); + text_entry = general_text.get_text_entry(GENERAL_intro_legal); + + show_legal_text(text_entry); + show_gears_of_progress(); + + REG_BG1CNT = REG_BG1CNT | BG_PRIO(3); + + key_poll(); // Reset the keys + curr_rom.load_rom(); + + obj_set_pos(ptgb_logo_l, 56, 12); + obj_set_pos(ptgb_logo_r, 56 + 64, 12); + obj_unhide_multi(ptgb_logo_l, 1, 2); + + REG_BLDCNT = BLD_BUILD(BLD_BG3, BLD_BG0, 1); + + int char_width = (PTGB_BUILD_LANGUAGE == JPN_ID ? 8 : 6); + int x = ((240 - (press_start_text_length * char_width)) / 2); + tte_set_pos(x, 12 * 8); + + tte_set_ink(INK_DARK_GREY); + ptgb_write(press_start_text, true); + + int fade = 0; + while (!start_pressed) + { + fade = abs(((get_frame_count() / 6) % 24) - 12); + global_next_frame(); + start_pressed = key_hit(KEY_START) | key_hit(KEY_A); + REG_BLDALPHA = BLDA_BUILD(0b10000, fade); + }; +} + int main(void) { initalization_script(); @@ -400,60 +547,8 @@ int main(void) first_load_message(); }*/ - // Legal mumbo jumbo - tte_set_margins(8, 8, H_MAX - 8, V_MAX - 8); - tte_set_pos(8, 8); - tte_set_ink(INK_ROM_COLOR); - ptgb_write(intro_legal, true); - bool wait = true; - while (wait) - { - global_next_frame(); - if (key_hit(KEY_A)) - { - wait = false; - } - } + show_intro(); - // Gears of Progress - tte_erase_rect(0, 0, 240, 160); - REG_BG1VOFS = 0; - delay_counter = 0; - while (delay_counter < (15 * 60)) - { - global_next_frame(); - delay_counter++; - if (key_hit(KEY_A)) - { - delay_counter = (15 * 60); - } - } - REG_BG1CNT = REG_BG1CNT | BG_PRIO(3); - - key_poll(); // Reset the keys - curr_rom.load_rom(); - - obj_set_pos(ptgb_logo_l, 56, 12); - obj_set_pos(ptgb_logo_r, 56 + 64, 12); - obj_unhide_multi(ptgb_logo_l, 1, 2); - bool start_pressed = false; - REG_BLDCNT = BLD_BUILD(BLD_BG3, BLD_BG0, 1); - - int size = get_string_length(press_start); - int char_width = (PTGB_BUILD_LANGUAGE == JPN_ID ? 8 : 6); - int x = ((240 - (size * char_width)) / 2); - tte_set_pos(x, 12 * 8); - - tte_set_ink(INK_DARK_GREY); - ptgb_write(press_start, true); - int fade = 0; - while (!start_pressed) - { - fade = abs(((get_frame_count() / 6) % 24) - 12); - global_next_frame(); - start_pressed = key_hit(KEY_START) | key_hit(KEY_A); - REG_BLDALPHA = BLDA_BUILD(0b10000, fade); - }; key_poll(); tte_erase_rect(0, 0, H_MAX, V_MAX); REG_BLDALPHA = BLDA_BUILD(0b10000, 0); // Reset fade diff --git a/source/multiboot_upload.cpp b/source/multiboot_upload.cpp index 18d766c..4497ad5 100644 --- a/source/multiboot_upload.cpp +++ b/source/multiboot_upload.cpp @@ -4,6 +4,7 @@ #include "background_engine.h" #include "libraries/gba-link-connection/LinkCableMultiboot.hpp" #include "text_engine.h" +#include "text_data_table.h" static void multiboot_show_textbox() { @@ -13,9 +14,14 @@ static void multiboot_show_textbox() void multiboot_upload_screen() { + u8 general_text_table_buffer[2048]; + text_data_table general_text(general_text_table_buffer); LinkCableMultiboot linkCableMultiboot; + + general_text.decompress(get_compressed_general_table()); + multiboot_show_textbox(); - ptgb_write(send_multiboot_instructions, true); + ptgb_write(general_text.get_text_entry(GENERAL_send_multiboot_instructions), true); // wait for key press do @@ -31,7 +37,7 @@ void multiboot_upload_screen() // start upload multiboot_show_textbox(); - ptgb_write(send_multiboot_wait, true); + ptgb_write(general_text.get_text_entry(GENERAL_send_multiboot_wait), true); global_next_frame(); const u32 romSize = 256 * 1024; // EWRAM = 256 KB @@ -49,11 +55,11 @@ void multiboot_upload_screen() multiboot_show_textbox(); if (multibootResult == LinkCableMultiboot::Result::SUCCESS) { - ptgb_write(send_multiboot_success, true); + ptgb_write(general_text.get_text_entry(GENERAL_send_multiboot_success), true); } else { - ptgb_write(send_multiboot_failure, true); + ptgb_write(general_text.get_text_entry(GENERAL_send_multiboot_failure), true); } // wait for keypress again. diff --git a/source/mystery_gift_builder.cpp b/source/mystery_gift_builder.cpp index 7e43bf3..322cc9a 100644 --- a/source/mystery_gift_builder.cpp +++ b/source/mystery_gift_builder.cpp @@ -5,6 +5,7 @@ #include "pokemon_data.h" #include "rom_data.h" #include "translated_text.h" +#include "text_data_table.h" #define MG_SCRIPT false #define S30_SCRIPT true @@ -40,8 +41,45 @@ int var_script_ptr_low = (VAR_ID_START + 0x01); int var_script_ptr_high = (VAR_ID_START + 0x02); int var_call_return_1 = (VAR_ID_START + 0x03); -mystery_gift_script::mystery_gift_script(PokemonTables &data_tables) - : data_tables(data_tables) +// the below structs and union are a scheme to reuse stack (IWRAM) memory of the +// PokemonTables instance for storing decompressed text data once it's no longer needed. +// The reason is that depending on the optimization level (-O0 specifically), the compiler may not do so automatically +// if you'd use anonymous scopes for that purpose. +// Having a union forces this behaviour. + +// this struct is used to hold the PokemonTables data +// within the decompressed_store union +struct decompressed_data_tables +{ + // This is about 3,4 KB + PokemonTables data; +}; + +// this struct is used to hold decompressed text data +// within the decompressed_store union +struct decompressed_text_data +{ + // the buffer is specifically chosen to be -at least- the size of PokemonTables + // to ensure that writing to gen3_charset_eng FROM the PokemonTables instance of the + // union doesn't overwrite said instance during the copy + u8 buffer[sizeof(PokemonTables)]; + u16 gen3_charset[256]; +}; + +// This union is used to hold the PokemonTables data on the stack when it's needed, +// and reclaim the memory for decompressed text data when it's not. +union decompressed_data_storage_union +{ + decompressed_data_tables tables; + decompressed_text_data text; + + // constructor and destructor are needed to make the compiler stop complaining + // about the decompressed_data_tables struct not being trivial + decompressed_data_storage_union(){} + ~decompressed_data_storage_union(){} +}; + +mystery_gift_script::mystery_gift_script() { curr_mg_index = NPC_LOCATION_OFFSET; curr_section30_index = 0; @@ -64,6 +102,8 @@ mystery_gift_script::mystery_gift_script(PokemonTables &data_tables) void mystery_gift_script::build_script(Pokemon_Party &incoming_box_data) { + decompressed_data_storage_union decompressed_store; + text_data_table decompressed_text_table(decompressed_store.text.buffer); ptgb::vector mg_variable_list; ptgb::vector sec30_variable_list; @@ -141,51 +181,6 @@ void mystery_gift_script::build_script(Pokemon_Party &incoming_box_data) } } - // Ş = Wait for button and scroll text - // ȼ = Wait for button and clear text - // Ȇ = Escape character - // À = Change text color - // Ç = Red - // É = Green - // Ë = Blue - // Ʋ = Variable escape sequence - // À = Player name - // Ň = New line - // ƞ = string terminator - - switch (curr_rom.gamecode) - { - case RUBY_ID: - textGreet.set_text(dia_textGreet_rse); - textMoveBox.set_text(dia_textMoveBox_rs); - textWeHere.set_text(dia_textWeHere_r); - break; - case SAPPHIRE_ID: - textGreet.set_text(dia_textGreet_rse); - textMoveBox.set_text(dia_textMoveBox_rs); - textWeHere.set_text(dia_textWeHere_s); - break; - case FIRERED_ID: - case LEAFGREEN_ID: - textGreet.set_text(dia_textGreet_frlg); - textMoveBox.set_text(dia_textMoveBox_frlg); - textWeHere.set_text(dia_textWeHere_frlg); - break; - case EMERALD_ID: - textGreet.set_text(dia_textGreet_rse); - textMoveBox.set_text(dia_textMoveBox_e); - textWeHere.set_text(dia_textWeHere_e); - break; - } - textReceived.set_text(dia_textRecieved); - textYouMustBe.set_text(first_time ? dia_textYouMustBe_first : dia_textYouMustBe_second); - textIAm.set_text(first_time ? dia_textIAm_first : dia_textIAm_second); - textPCConvo.set_text(dia_textPCConvo); // ȼDon’t worry ƲÀ,Ňyou won’t have to do a thing!"); - textPCThanks.set_text(dia_textPCThanks); - textThank.set_text(dia_textThank); - textPCFull.set_text(dia_textPCFull); - textLookerFull.set_text(dia_textLookerFull); - const int movementSlowSpinArray[16] = { MOVEMENT_ACTION_FACE_LEFT, MOVEMENT_ACTION_DELAY_8, @@ -330,9 +325,12 @@ void mystery_gift_script::build_script(Pokemon_Party &incoming_box_data) int dex_nums[MAX_PKMN_IN_BOX] = {}; + // placement new is required to run the constructor of PokemonTables for the decompressed_store's instance + // it won't get called automatically because it's part of the union (and neither will the destructor) + new (&decompressed_store.tables.data) PokemonTables(); for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the Pokemon data { - Pokemon curr_pkmn = incoming_box_data.get_converted_pkmn(data_tables, i); + Pokemon curr_pkmn = incoming_box_data.get_converted_pkmn(decompressed_store.tables.data, i); if (curr_pkmn.get_validity()) { for (int curr_byte = 0; curr_byte < POKEMON_SIZE; curr_byte++) @@ -347,6 +345,14 @@ void mystery_gift_script::build_script(Pokemon_Party &incoming_box_data) curr_section30_index += POKEMON_SIZE; } } + // the PokemonTables instance is no longer needed, but we do need to keep the english gen3 charset around + // for our insert_text() calls + decompressed_store.tables.data.load_gen3_charset(ENG_ID); + // we specifically defined the decompressed_text_data struct to ensure the memcpy shouldn't overlap + memcpy(decompressed_store.text.gen3_charset, decompressed_store.tables.data.gen3_charset, sizeof(decompressed_store.text.gen3_charset)); + // calling the destructor is nothing more than a formality for our PokemonTables class, + // but let's do it anyway for the sake of being explicit after having used placement new + decompressed_store.tables.data.~PokemonTables(); for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the dex numbers { @@ -355,16 +361,63 @@ void mystery_gift_script::build_script(Pokemon_Party &incoming_box_data) } // insert text - data_tables.load_gen3_charset(ENG_ID); - textThank.insert_text(data_tables.gen3_charset, save_section_30); - textPCFull.insert_text(data_tables.gen3_charset, save_section_30); - textWeHere.insert_text(data_tables.gen3_charset, save_section_30); - textPCConvo.insert_text(data_tables.gen3_charset, save_section_30); - textPCThanks.insert_text(data_tables.gen3_charset, save_section_30); - textLookerFull.insert_text(data_tables.gen3_charset, save_section_30); - textMoveBox.insert_text(data_tables.gen3_charset, save_section_30); - textReceived.insert_text(data_tables.gen3_charset, save_section_30); + // Ş = Wait for button and scroll text + // ȼ = Wait for button and clear text + // Ȇ = Escape character + // À = Change text color + // Ç = Red + // É = Green + // Ë = Blue + // Ʋ = Variable escape sequence + // À = Player name + // Ň = New line + // ƞ = string terminator + + // 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()); + switch (curr_rom.gamecode) + { + case RUBY_ID: + textGreet.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textGreet_rse)); + textMoveBox.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textMoveBox_rs)); + textWeHere.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textWeHere_r)); + break; + case SAPPHIRE_ID: + textGreet.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textGreet_rse)); + textMoveBox.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textMoveBox_rs)); + textWeHere.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textWeHere_s)); + break; + case FIRERED_ID: + case LEAFGREEN_ID: + textGreet.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textGreet_frlg)); + textMoveBox.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textMoveBox_frlg)); + textWeHere.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textWeHere_frlg)); + break; + case EMERALD_ID: + textGreet.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textGreet_rse)); + textMoveBox.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textMoveBox_e)); + textWeHere.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textWeHere_e)); + break; + } + textReceived.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textRecieved)); + textYouMustBe.set_text(decompressed_text_table.get_text_entry(first_time ? RSEFRLG_dia_textYouMustBe_first : RSEFRLG_dia_textYouMustBe_second)); + textIAm.set_text(decompressed_text_table.get_text_entry(first_time ? RSEFRLG_dia_textIAm_first : RSEFRLG_dia_textIAm_second)); + textPCConvo.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textPCConvo)); // ȼDon’t worry ƲÀ,Ňyou won’t have to do a thing!"); + textPCThanks.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textPCThanks)); + textThank.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textThank)); + textPCFull.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textPCFull)); + textLookerFull.set_text(decompressed_text_table.get_text_entry(RSEFRLG_dia_textLookerFull)); + + textThank.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textPCFull.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textWeHere.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textPCConvo.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textPCThanks.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textLookerFull.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textMoveBox.insert_text(decompressed_store.text.gen3_charset, save_section_30); + textReceived.insert_text(decompressed_store.text.gen3_charset, save_section_30); movementSlowSpin.insert_movement(save_section_30); movementFastSpin.insert_movement(save_section_30); @@ -786,9 +839,10 @@ void mystery_gift_script::build_script(Pokemon_Party &incoming_box_data) add_word(flashBuffer_ptr.place_word()); add_word(readFlashSector_ptr.place_word()); - textGreet.insert_virtual_text(data_tables.gen3_charset, mg_script); - textYouMustBe.insert_virtual_text(data_tables.gen3_charset, mg_script); - textIAm.insert_virtual_text(data_tables.gen3_charset, mg_script); + constexpr bool should_set_virtual_start = true; + textGreet.insert_text(decompressed_store.text.gen3_charset, mg_script, should_set_virtual_start); + textYouMustBe.insert_text(decompressed_store.text.gen3_charset, mg_script, should_set_virtual_start); + textIAm.insert_text(decompressed_store.text.gen3_charset, mg_script, should_set_virtual_start); for (unsigned int i = 0; i < mg_variable_list.size(); i++) // Fill all the refrences for script variables in the mg { diff --git a/source/mystery_gift_injector.cpp b/source/mystery_gift_injector.cpp index 7517b5a..d1c0e28 100644 --- a/source/mystery_gift_injector.cpp +++ b/source/mystery_gift_injector.cpp @@ -14,10 +14,37 @@ static u8 em_wonder_card[0x14E] = { static u8 frlg_wonder_card[0x14E] = { 0x67, 0x18, 0x00, 0x00, 0xBA, 0xB4, 0xBE, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0xCA, 0xCC, 0xC9, 0xC0, 0xBF, 0xCD, 0xCD, 0xC9, 0xCC, 0x00, 0xC0, 0xBF, 0xC8, 0xC8, 0xBF, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xE6, 0xD5, 0xE2, 0xE7, 0xDA, 0xD9, 0xE6, 0x00, 0xBD, 0xD9, 0xE6, 0xE8, 0xDD, 0xDA, 0xDD, 0xD7, 0xD5, 0xE8, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xDD, 0xE7, 0xDD, 0xE8, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xDC, 0xE3, 0xE9, 0xE7, 0xD9, 0x00, 0xE7, 0xE3, 0xE9, 0xE8, 0xDC, 0x00, 0xE3, 0xDA, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0x00, 0x00, 0x00, 0x00, 0xBD, 0xBF, 0xC8, 0xCE, 0xBF, 0xCC, 0x00, 0xE3, 0xE2, 0x00, 0xCD, 0xD9, 0xEA, 0xD9, 0xE2, 0x00, 0xC3, 0xE7, 0xE0, 0xD5, 0xE2, 0xD8, 0x00, 0xE8, 0xE3, 0x00, 0xE6, 0xD9, 0xD7, 0xDD, 0xD9, 0xEA, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2, 0xE7, 0xDA, 0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xE3, 0x00, 0xE2, 0xE3, 0xE8, 0x00, 0xE8, 0xE3, 0xE7, 0xE7, 0x00, 0xE8, 0xDC, 0xDD, 0xE7, 0x00, 0xBF, 0xEC, 0xD7, 0xDC, 0xD5, 0xE2, 0xDB, 0xD9, 0x00, 0xBD, 0xD5, 0xE6, 0xD8, 0x00, 0xD6, 0xD9, 0xDA, 0xE3, 0xE6, 0xD9, 0x00, 0x00, 0x00, 0xE6, 0xD9, 0xD7, 0xD9, 0xDD, 0xEA, 0xDD, 0xE2, 0xDB, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2, 0xE7, 0xDA, 0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // checksum -bool inject_mystery(Pokemon_Party &incoming_box_data) +static void __attribute__((noinline)) handle_old_event(Pokemon_Party &incoming_box_data, int &curr_index, int *dex_nums) { PokemonTables data_tables; - mystery_gift_script script(data_tables); + for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the Pokemon data + { + Pokemon curr_pkmn = incoming_box_data.get_converted_pkmn(data_tables, i); + if (curr_pkmn.get_validity()) + { + + for (int curr_byte = 0; curr_byte < POKEMON_SIZE; curr_byte++) + { + global_memory_buffer[curr_index] = curr_pkmn.get_gen_3_data(curr_byte); + curr_index++; + } + dex_nums[i] = curr_pkmn.get_dex_number(); + } + else + { + curr_index += POKEMON_SIZE; + } + } + for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the dex numbers + { + global_memory_buffer[curr_index] = dex_nums[i]; + curr_index++; + } +} + +bool inject_mystery(Pokemon_Party &incoming_box_data) +{ + mystery_gift_script script; if (ENABLE_OLD_EVENT) { // script.build_script_old(incoming_box_data); @@ -80,31 +107,10 @@ bool inject_mystery(Pokemon_Party &incoming_box_data) copy_save_to_ram(0x1E000, &global_memory_buffer[0], 0x1000); int curr_index = 0; int dex_nums[MAX_PKMN_IN_BOX] = {}; + if (ENABLE_OLD_EVENT) { - for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the Pokemon data - { - Pokemon curr_pkmn = incoming_box_data.get_converted_pkmn(data_tables, i); - if (curr_pkmn.get_validity()) - { - - for (int curr_byte = 0; curr_byte < POKEMON_SIZE; curr_byte++) - { - global_memory_buffer[curr_index] = curr_pkmn.get_gen_3_data(curr_byte); - curr_index++; - } - dex_nums[i] = curr_pkmn.get_dex_number(); - } - else - { - curr_index += POKEMON_SIZE; - } - } - for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the dex numbers - { - global_memory_buffer[curr_index] = dex_nums[i]; - curr_index++; - } + handle_old_event(incoming_box_data, curr_index, dex_nums); } else { diff --git a/source/pokedex.cpp b/source/pokedex.cpp index 221b68b..88738af 100644 --- a/source/pokedex.cpp +++ b/source/pokedex.cpp @@ -1,5 +1,4 @@ #include -#include #include #include "pokedex.h" @@ -12,6 +11,7 @@ #include "translated_text.h" #include "text_engine.h" #include "zx0_decompressor.h" +#include "text_data_table.h" #include "TYPES_zx0_bin.h" Dex dex_array[DEX_MAX]; @@ -30,6 +30,24 @@ bool mew_caught; bool celebi_caught; bool missingno_caught = false; +static void load_text_entry_into_buffer(text_data_table& data_table, u8 *output_buffer, u8 entry_index) +{ + const u8 *entry = data_table.get_text_entry(entry_index); + const u8 *entry_end = (const u8*)strchr((const char*)entry, 0xFF); + + // copy the text_entry including the 0xFF at the end + memcpy(output_buffer, entry, entry_end + 1 - entry); +} + +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()); + + load_text_entry_into_buffer(data_table, kanto_buffer, GENERAL_kanto_name); + load_text_entry_into_buffer(data_table, johto_buffer, GENERAL_johto_name); +} + void pokedex_init() { for (int i = 0; i < DEX_MAX; i++) @@ -71,18 +89,33 @@ void pokedex_init() obj_hide(down_arrow); } +#include "gen_3_charsets_zx0_bin.h" +#include "libstd_replacements.h" + int pokedex_loop() { - u8 TYPES[POKEMON_ARRAY_SIZE * 2]; + u8 TYPES[POKEMON_ARRAY_SIZE][2]; + u8 kanto_name[12]; + u8 johto_name[12]; + u8 decompression_buffer[3072]; + u16 charset[256]; - zx0_decompressor_start((uint8_t*)TYPES, TYPES_zx0_bin); + zx0_decompressor_start((u8*)TYPES, TYPES_zx0_bin); zx0_decompressor_read(zx0_decompressor_get_decompressed_size()); + zx0_decompressor_start((u8*)charset, gen_3_charsets_zx0_bin); + zx0_decompressor_read(zx0_decompressor_get_decompressed_size()); + + 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()); + pokedex_init(); pokedex_show(); bool update = true; - byte undiscovered_text[] = {0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xFF}; + const byte undiscovered_text[] = {0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xFF}; byte temp_string[4] = {}; // Should never be longer than 4 characters (including endline) // TODO: For some reason there is screen tearing here. Probably not noticable on console, // but it should be removed at some point @@ -199,13 +232,14 @@ int pokedex_loop() ptgb_write(temp_string, true); tte_set_pos(dex_x_cord + (7 * 8), (i * 8 * 2) + 32); - ptgb_write(is_caught(dex_shift + i + 1 + mythic_skip) ? PKMN_NAMES[dex_shift + i + 1 + mythic_skip] : undiscovered_text, true); + ptgb_write(is_caught(dex_shift + i + 1 + mythic_skip) ? PKMN_NAMES.get_text_entry(dex_shift + i + 1 + mythic_skip) : undiscovered_text, true); + } global_next_frame(); // This is a bit silly, but it works. Makes the types one frame off from the text, but that's 'fine' // Eventually it could be optimized to move the labels around, but this honestly makes the most sense. Less code but one frame different for (int i = 0; i < DEX_MAX; i++) { - load_type_sprites(TYPES, dex_shift + i + 1 + mythic_skip, i, is_caught(dex_shift + i + 1 + mythic_skip)); + load_type_sprites((const u8*)TYPES, dex_shift + i + 1 + mythic_skip, i, is_caught(dex_shift + i + 1 + mythic_skip)); } update = false; } diff --git a/source/script_array.cpp b/source/script_array.cpp index 3735b80..055ffb8 100644 --- a/source/script_array.cpp +++ b/source/script_array.cpp @@ -14,132 +14,681 @@ #include "translated_text.h" int last_error; -Pokemon_Party party_data = Pokemon_Party(); +Pokemon_Party party_data; Select_Menu langs(false, LANG_MENU, 18, 0); Select_Menu games(false, CART_MENU, 18, 0); Box_Menu box_viewer; -script_obj transfer_script[SCRIPT_SIZE]; -script_obj event_script[SCRIPT_SIZE]; +// For documentation purposes, here's an overview of the categories of the transfer script: +// -------- TRANSFER SCRIPT -------- +// +// Check that the conditions are set for the transfer +// T_SCRIPT_START +// COND_TUTORIAL_COMPLETE +// DIA_OPEN +// CMD_SET_TUTOR_TRUE +// COND_BEAT_E4 +// DIA_E4 +// COND_MG_ENABLED +// COND_IS_FRLGE +// DIA_MG_FRLGE +// DIA_MG_RS +// COND_MG_OTHER_EVENT +// COND_PKMN_TO_COLLECT +// DIA_MG_OTHER_EVENT +// DIA_PKMN_TO_COLLECT +// +// Ask the user what game and language they're using +// DIA_WHAT_GAME_TRANS +// CMD_GAME_MENU +// DIA_WHAT_LANG_TRANS +// CMD_LANG_MENU +// DIA_ASK_QUEST +// CMD_SLIDE_PROF_LEFT +// CMD_SLIDE_PROF_RIGHT +// COND_GB_ROM_EXISTS +// DIA_NO_GB_ROM +// DIA_MENU_BACK +// +// Initiate the transfer and check for errors +// DIA_LETS_START +// DIA_START +// CMD_START_LINK +// COND_ERROR_TIMEOUT_ONE +// DIA_ERROR_TIME_ONE +// COND_ERROR_TIMEOUT_TWO +// DIA_ERROR_TIME_TWO +// COND_ERROR_COM_ENDED +// DIA_ERROR_COM_ENDED +// COND_ERROR_COLOSSEUM +// DIA_ERROR_COLOSSEUM +// COND_ERROR_DISCONNECT +// DIA_ERROR_DISCONNECT +// +// Pause the transfer and show the user their box data +// CMD_LOAD_SIMP +// DIA_NO_VALID_PKMN +// COND_SOME_INVALID_PKMN +// DIA_SOME_INVALID_PKMN +// COND_CHECK_MYTHIC +// DIA_MYTHIC_CONVERT +// CMD_MYTHIC_MENU +// COND_CHECK_MISSINGNO +// DIA_IS_MISSINGNO +// DIA_IN_BOX +// CMD_BOX_MENU +// 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 +// COND_NEW_POKEMON +// DIA_NEW_DEX +// 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 +// +// Hide the dialogue and professor +// CMD_END_SCRIPT +// CMD_BACK_TO_MENU -void populate_script() -{ - // -------- TRANSFER SCRIPT -------- - // Check that the conditions are set for the transfer - transfer_script[T_SCRIPT_START] = script_obj(CMD_SHOW_PROF, COND_TUTORIAL_COMPLETE); - transfer_script[COND_TUTORIAL_COMPLETE] = script_obj(COND_TUTORIAL_COMPLETE, COND_BEAT_E4, DIA_OPEN); - transfer_script[DIA_OPEN] = script_obj(dialogue[DIA_OPEN], CMD_SET_TUTOR_TRUE); - transfer_script[CMD_SET_TUTOR_TRUE] = script_obj(CMD_SET_TUTOR_TRUE, CMD_END_SCRIPT); - transfer_script[COND_BEAT_E4] = script_obj(COND_BEAT_E4, COND_MG_ENABLED, DIA_E4); - transfer_script[DIA_E4] = script_obj(dialogue[DIA_E4], CMD_END_SCRIPT); - transfer_script[COND_MG_ENABLED] = script_obj(COND_MG_ENABLED, COND_MG_OTHER_EVENT, COND_IS_FRLGE); - transfer_script[COND_IS_FRLGE] = script_obj(COND_IS_FRLGE, DIA_MG_FRLGE, DIA_MG_RS); - transfer_script[DIA_MG_FRLGE] = script_obj(dialogue[DIA_MG_FRLGE], CMD_END_SCRIPT); - transfer_script[DIA_MG_RS] = script_obj(dialogue[DIA_MG_RS], CMD_END_SCRIPT); - transfer_script[COND_MG_OTHER_EVENT] = script_obj(COND_MG_OTHER_EVENT, DIA_MG_OTHER_EVENT, COND_PKMN_TO_COLLECT); - transfer_script[COND_PKMN_TO_COLLECT] = script_obj(COND_PKMN_TO_COLLECT, DIA_PKMN_TO_COLLECT, DIA_ASK_QUEST); - transfer_script[DIA_MG_OTHER_EVENT] = script_obj(dialogue[DIA_MG_OTHER_EVENT], DIA_ASK_QUEST); - transfer_script[DIA_PKMN_TO_COLLECT] = script_obj(dialogue[DIA_PKMN_TO_COLLECT], CMD_END_SCRIPT); +// The array below is exactly in order of the DIA, commands and condition int values +// 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 + { + .text_entry_index = DIA_OPEN, + .next_if_true = CMD_SET_TUTOR_TRUE + }, + // DIA_E4 + { + .text_entry_index = DIA_E4, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_MG_FRLGE + { + .text_entry_index = DIA_MG_FRLGE, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_MG_RS + { + .text_entry_index = DIA_MG_RS, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_LETS_START + { + .text_entry_index = DIA_LETS_START, + .next_if_true = DIA_START + }, + // DIA_START + { + .text_entry_index = DIA_START, + .next_if_true = CMD_START_LINK + }, + // DIA_TRANS_GOOD + { + .text_entry_index = DIA_TRANS_GOOD, + .next_if_true = COND_NEW_POKEMON + }, + // DIA_NEW_DEX + { + .text_entry_index = DIA_NEW_DEX, + .next_if_true = COND_IS_HOENN_RS + }, + // DIA_NO_NEW_DEX + { + .text_entry_index = DIA_NO_NEW_DEX, + .next_if_true = COND_IS_HOENN_RS + }, + // DIA_SEND_FRIEND_KANTO + { + .text_entry_index = DIA_SEND_FRIEND_KANTO, + .next_if_true = DIA_THANK + }, + // DIA_SEND_FRIEND_HOENN_RS + { + .text_entry_index = DIA_SEND_FRIEND_HOENN_RS, + .next_if_true = DIA_THANK + }, + // DIA_SEND_FRIEND_HOENN_E + { + .text_entry_index = DIA_SEND_FRIEND_HOENN_E, + .next_if_true = DIA_THANK + }, + // DIA_THANK + { + .text_entry_index = DIA_THANK, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_GET_MON (unused) + { + .text_entry_index = DIA_GET_MON, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_MG_OTHER_EVENT + { + .text_entry_index = DIA_MG_OTHER_EVENT, + .next_if_true = DIA_ASK_QUEST + }, + // DIA_PKMN_TO_COLLECT + { + .text_entry_index = DIA_PKMN_TO_COLLECT, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_NO_VALID_PKMN + { + .text_entry_index = DIA_NO_VALID_PKMN, + .next_if_true = CMD_CANCEL_LINK + }, + // DIA_ASK_QUEST + { + .text_entry_index = DIA_ASK_QUEST, + .next_if_true = CMD_SLIDE_PROF_LEFT + }, + // DIA_WHAT_GAME_TRANS + { + .text_entry_index = DIA_WHAT_GAME_TRANS, + .next_if_true = CMD_GAME_MENU + }, + // DIA_WHAT_LANG_TRANS + { + .text_entry_index = DIA_WHAT_LANG_TRANS, + .next_if_true = CMD_LANG_MENU + }, + // DIA_NO_GB_ROM + { + .text_entry_index = DIA_NO_GB_ROM, + .next_if_true = DIA_WHAT_LANG_TRANS + }, + // DIA_IN_BOX + { + .text_entry_index = DIA_IN_BOX, + .next_if_true = CMD_BOX_MENU + }, + // DIA_MYTHIC_CONVERT + { + .text_entry_index = DIA_MYTHIC_CONVERT, + .next_if_true = CMD_MYTHIC_MENU + }, + // DIA_CANCEL + { + .text_entry_index = DIA_CANCEL, + .next_if_true = CMD_CANCEL_LINK + }, + // DIA_SOME_INVALID_PKMN + { + .text_entry_index = DIA_SOME_INVALID_PKMN, + .next_if_true = COND_CHECK_MYTHIC + }, + // DIA_MENU_BACK + { + .text_entry_index = DIA_MENU_BACK, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_IS_MISSINGNO + { + .text_entry_index = DIA_IS_MISSINGNO, + .next_if_true = DIA_IN_BOX + }, + // DIA_ERROR_COLOSSEUM + { + .text_entry_index = DIA_ERROR_COLOSSEUM, + .next_if_true = DIA_START + }, + // DIA_ERROR_COM_ENDED + { + .text_entry_index = DIA_ERROR_COM_ENDED, + .next_if_true = DIA_START + }, + // DIA_ERROR_DISCONNECT + { + .text_entry_index = DIA_ERROR_DISCONNECT, + .next_if_true = DIA_START + }, + // DIA_ERROR_TIME_ONE + { + .text_entry_index = DIA_ERROR_TIME_ONE, + .next_if_true = DIA_START + }, + // DIA_ERROR_TIME_TWO + { + .text_entry_index = DIA_ERROR_TIME_TWO, + .next_if_true = DIA_START + }, + // DIA_WHAT_LANG_EVENT + { + .text_entry_index = DIA_WHAT_LANG_EVENT, + .next_if_true = CMD_LANG_MENU + }, + // DIA_WHAT_GAME_EVENT + { + .text_entry_index = DIA_WHAT_GAME_EVENT, + .next_if_true = CMD_GAME_MENU + }, + // DIA_K_DEX_NOT_FULL + { + .text_entry_index = DIA_K_DEX_NOT_FULL, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_J_DEX_NOT_FULL + { + .text_entry_index = DIA_J_DEX_NOT_FULL, + .next_if_true = CMD_END_SCRIPT + }, + // T_SCRIPT_START + { + .conditional_index = CMD_SHOW_PROF, + .next_if_true = COND_TUTORIAL_COMPLETE + }, + // E_SCRIPT_START + { + .conditional_index = CMD_SHOW_PROF, + .next_if_true = DIA_ASK_QUEST + }, + // CMD_START_LINK + { + .conditional_index = CMD_START_LINK, + .next_if_true = COND_ERROR_TIMEOUT_ONE + }, + // CMD_IMPORT_POKEMON + { + .conditional_index = CMD_IMPORT_POKEMON, + .next_if_true = CMD_CONTINUE_LINK + }, + // CMD_BACK_TO_MENU + { + .conditional_index = CMD_BACK_TO_MENU, + .next_if_true = T_SCRIPT_START + }, + // CMD_SHOW_PROF (unused in this manner (not used in a next_if_true). Therefore this is a dummy entry) + // we even need to define such unused values to keep the array element positions in order + { + }, + // CMD_HIDE_PROF (unused in this manner (not used in a next_if_true). Therefore this is a dummy entry) + { + }, + // CMD_SET_TUTOR_TRUE + { + .conditional_index = CMD_SET_TUTOR_TRUE, + .next_if_true = CMD_END_SCRIPT + }, + // CMD_END_SCRIPT + { + .conditional_index = CMD_END_SCRIPT, + .next_if_true = CMD_BACK_TO_MENU + }, + // CMD_GAME_MENU + { + .conditional_index = CMD_GAME_MENU, + .next_if_true = COND_GB_ROM_EXISTS, + .next_if_false = DIA_WHAT_LANG_TRANS + }, + // CMD_LANG_MENU + { + .conditional_index = CMD_LANG_MENU, + .next_if_true = DIA_WHAT_GAME_TRANS + }, + // CMD_SLIDE_PROF_LEFT + { + .conditional_index = CMD_SLIDE_PROF_LEFT, + .next_if_true = DIA_WHAT_LANG_TRANS + }, + // CMD_SLIDE_PROF_RIGHT + { + .conditional_index = CMD_SLIDE_PROF_RIGHT, + .next_if_true = DIA_LETS_START + }, + // CMD_CONTINUE_LINK + { + .conditional_index = CMD_CONTINUE_LINK, + .next_if_true = CMD_END_MISSINGNO + }, + // CMD_BOX_MENU + { + .conditional_index = CMD_BOX_MENU, + .next_if_true = CMD_IMPORT_POKEMON, + .next_if_false = DIA_CANCEL + }, + // CMD_MYTHIC_MENU + { + .conditional_index = CMD_MYTHIC_MENU, + .next_if_true = COND_CHECK_MISSINGNO + }, + // CMD_LOAD_SIMP + { + .conditional_index = CMD_LOAD_SIMP, + .next_if_true = COND_SOME_INVALID_PKMN, + .next_if_false = DIA_NO_VALID_PKMN + }, + // CMD_CANCEL_LINK + { + .conditional_index = CMD_CANCEL_LINK, + .next_if_true = CMD_END_SCRIPT + }, + // CMD_END_MISSINGNO + { + .conditional_index = CMD_END_MISSINGNO, + .next_if_true = 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 + }, + // COND_ERROR_DISCONNECT + { + .conditional_index = COND_ERROR_DISCONNECT, + .next_if_true = CMD_LOAD_SIMP, + .next_if_false = 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 + }, + // COND_ERROR_TIMEOUT_TWO + { + .conditional_index = COND_ERROR_TIMEOUT_TWO, + .next_if_true = COND_ERROR_COM_ENDED, + .next_if_false = DIA_ERROR_TIME_TWO + }, + // COND_ERROR_COLOSSEUM + { + .conditional_index = COND_ERROR_COLOSSEUM, + .next_if_true = COND_ERROR_DISCONNECT, + .next_if_false = DIA_ERROR_COLOSSEUM + }, + // COND_BEAT_E4 + { + .conditional_index = COND_BEAT_E4, + .next_if_true = COND_MG_ENABLED, + .next_if_false = DIA_E4 + }, + // COND_MG_ENABLED + { + .conditional_index = COND_MG_ENABLED, + .next_if_true = COND_MG_OTHER_EVENT, + .next_if_false = COND_IS_FRLGE + }, + // COND_TUTORIAL_COMPLETE + { + .conditional_index = COND_TUTORIAL_COMPLETE, + .next_if_true = COND_BEAT_E4, + .next_if_false = DIA_OPEN + }, + // COND_NEW_POKEMON + { + .conditional_index = COND_NEW_POKEMON, + .next_if_true = DIA_NEW_DEX, + .next_if_false = DIA_NO_NEW_DEX + }, + // COND_IS_HOENN_RS + { + .conditional_index = COND_IS_HOENN_RS, + .next_if_true = DIA_SEND_FRIEND_HOENN_RS, + .next_if_false = DIA_SEND_FRIEND_KANTO + }, + // COND_IS_FRLGE + { + .conditional_index = COND_IS_FRLGE, + .next_if_true = DIA_MG_FRLGE, + .next_if_false = DIA_MG_RS + }, + // COND_MG_OTHER_EVENT + { + .conditional_index = COND_MG_OTHER_EVENT, + .next_if_true = 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 + }, + // COND_GB_ROM_EXISTS + { + .conditional_index = COND_GB_ROM_EXISTS, + .next_if_true = CMD_SLIDE_PROF_RIGHT, + .next_if_false = DIA_NO_GB_ROM + }, + // COND_CHECK_MYTHIC + { + .conditional_index = COND_CHECK_MYTHIC, + .next_if_true = DIA_MYTHIC_CONVERT, + .next_if_false = COND_CHECK_MISSINGNO + }, + // COND_CHECK_DEX + { + .conditional_index = COND_CHECK_DEX, + .next_if_true = 0, + .next_if_false = COND_CHECK_KANTO + }, + // COND_CHECK_KANTO + { + .conditional_index = COND_CHECK_KANTO, + .next_if_true = DIA_K_DEX_NOT_FULL, + .next_if_false = DIA_J_DEX_NOT_FULL + }, + // COND_SOME_INVALID_PKMN + { + .conditional_index = COND_SOME_INVALID_PKMN, + .next_if_true = 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 + }, + // COND_CHECK_MISSINGNO + { + .conditional_index = COND_CHECK_MISSINGNO, + .next_if_true = DIA_IS_MISSINGNO, + .next_if_false = DIA_IN_BOX + } +}; - // Ask the user what game and language they're using - transfer_script[DIA_WHAT_GAME_TRANS] = script_obj(dialogue[DIA_WHAT_GAME_TRANS], CMD_GAME_MENU); - transfer_script[CMD_GAME_MENU] = script_obj(CMD_GAME_MENU, COND_GB_ROM_EXISTS, DIA_WHAT_LANG_TRANS); - transfer_script[DIA_WHAT_LANG_TRANS] = script_obj(dialogue[DIA_WHAT_LANG_TRANS], CMD_LANG_MENU); - transfer_script[CMD_LANG_MENU] = script_obj(CMD_LANG_MENU, DIA_WHAT_GAME_TRANS, DIA_MENU_BACK); - transfer_script[DIA_ASK_QUEST] = script_obj(dialogue[DIA_ASK_QUEST], CMD_SLIDE_PROF_LEFT); - transfer_script[CMD_SLIDE_PROF_LEFT] = script_obj(CMD_SLIDE_PROF_LEFT, DIA_WHAT_LANG_TRANS); - transfer_script[CMD_SLIDE_PROF_RIGHT] = script_obj(CMD_SLIDE_PROF_RIGHT, DIA_LETS_START); - transfer_script[COND_GB_ROM_EXISTS] = script_obj(COND_GB_ROM_EXISTS, CMD_SLIDE_PROF_RIGHT, DIA_NO_GB_ROM); - transfer_script[DIA_NO_GB_ROM] = script_obj(dialogue[DIA_NO_GB_ROM], DIA_WHAT_LANG_TRANS); - transfer_script[DIA_MENU_BACK] = script_obj(dialogue[DIA_MENU_BACK], CMD_END_SCRIPT); +// For documentation purposes, here's an overview of the categories of the events script: +// -------- EVENTS SCRIPT -------- +// Start the dialogue and show the menu +// E_SCRIPT_START +// DIA_ASK_QUEST +// +// Ask the user what game and language they're using +// DIA_WHAT_GAME_EVENT +// CMD_GAME_MENU +// DIA_WHAT_LANG_EVENT +// CMD_LANG_MENU +// DIA_ASK_QUEST +// CMD_SLIDE_PROF_LEFT +// CMD_SLIDE_PROF_RIGHT +// COND_GB_ROM_EXISTS +// 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 +// +// Hide the dialogue and professor +// CMD_END_SCRIPT +// CMD_BACK_TO_MENU - // Initiate the transfer and check for errors - transfer_script[DIA_LETS_START] = script_obj(dialogue[DIA_LETS_START], DIA_START); - transfer_script[DIA_START] = script_obj(dialogue[DIA_START], CMD_START_LINK); - transfer_script[CMD_START_LINK] = script_obj(CMD_START_LINK, COND_ERROR_TIMEOUT_ONE); - transfer_script[COND_ERROR_TIMEOUT_ONE] = script_obj(COND_ERROR_TIMEOUT_ONE, COND_ERROR_TIMEOUT_TWO, DIA_ERROR_TIME_ONE); - transfer_script[DIA_ERROR_TIME_ONE] = script_obj(dialogue[DIA_ERROR_TIME_ONE], DIA_START); - transfer_script[COND_ERROR_TIMEOUT_TWO] = script_obj(COND_ERROR_TIMEOUT_TWO, COND_ERROR_COM_ENDED, DIA_ERROR_TIME_TWO); - transfer_script[DIA_ERROR_TIME_TWO] = script_obj(dialogue[DIA_ERROR_TIME_TWO], DIA_START); - transfer_script[COND_ERROR_COM_ENDED] = script_obj(COND_ERROR_COM_ENDED, COND_ERROR_COLOSSEUM, DIA_ERROR_COM_ENDED); - transfer_script[DIA_ERROR_COM_ENDED] = script_obj(dialogue[DIA_ERROR_COM_ENDED], DIA_START); - transfer_script[COND_ERROR_COLOSSEUM] = script_obj(COND_ERROR_COLOSSEUM, COND_ERROR_DISCONNECT, DIA_ERROR_COLOSSEUM); - transfer_script[DIA_ERROR_COLOSSEUM] = script_obj(dialogue[DIA_ERROR_COLOSSEUM], DIA_START); - transfer_script[COND_ERROR_DISCONNECT] = script_obj(COND_ERROR_DISCONNECT, CMD_LOAD_SIMP, DIA_ERROR_DISCONNECT); - transfer_script[DIA_ERROR_DISCONNECT] = script_obj(dialogue[DIA_ERROR_DISCONNECT], DIA_START); - - // Pause the transfer and show the user their box data - transfer_script[CMD_LOAD_SIMP] = script_obj(CMD_LOAD_SIMP, COND_SOME_INVALID_PKMN, DIA_NO_VALID_PKMN); - transfer_script[DIA_NO_VALID_PKMN] = script_obj(dialogue[DIA_NO_VALID_PKMN], CMD_CANCEL_LINK); - transfer_script[COND_SOME_INVALID_PKMN] = script_obj(COND_SOME_INVALID_PKMN, DIA_SOME_INVALID_PKMN, COND_CHECK_MYTHIC); - transfer_script[DIA_SOME_INVALID_PKMN] = script_obj(dialogue[DIA_SOME_INVALID_PKMN], COND_CHECK_MYTHIC); - transfer_script[COND_CHECK_MYTHIC] = script_obj(COND_CHECK_MYTHIC, DIA_MYTHIC_CONVERT, COND_CHECK_MISSINGNO); - transfer_script[DIA_MYTHIC_CONVERT] = script_obj(dialogue[DIA_MYTHIC_CONVERT], CMD_MYTHIC_MENU); - transfer_script[CMD_MYTHIC_MENU] = script_obj(CMD_MYTHIC_MENU, COND_CHECK_MISSINGNO); - transfer_script[COND_CHECK_MISSINGNO] = script_obj(COND_CHECK_MISSINGNO, DIA_IS_MISSINGNO, DIA_IN_BOX); - transfer_script[DIA_IS_MISSINGNO] = script_obj(dialogue[DIA_IS_MISSINGNO], DIA_IN_BOX); - transfer_script[DIA_IN_BOX] = script_obj(dialogue[DIA_IN_BOX], CMD_BOX_MENU); - transfer_script[CMD_BOX_MENU] = script_obj(CMD_BOX_MENU, CMD_IMPORT_POKEMON, DIA_CANCEL); - transfer_script[DIA_CANCEL] = script_obj(dialogue[DIA_CANCEL], CMD_CANCEL_LINK); - transfer_script[CMD_IMPORT_POKEMON] = script_obj(CMD_IMPORT_POKEMON, CMD_CONTINUE_LINK); - transfer_script[CMD_CONTINUE_LINK] = script_obj(CMD_CONTINUE_LINK, CMD_END_MISSINGNO); - transfer_script[CMD_CANCEL_LINK] = script_obj(CMD_CANCEL_LINK, CMD_END_SCRIPT); - transfer_script[CMD_END_MISSINGNO] = script_obj(CMD_END_MISSINGNO, DIA_TRANS_GOOD); - - // Complete the transfer and give messages based on the transfered Pokemon - transfer_script[DIA_TRANS_GOOD] = script_obj(dialogue[DIA_TRANS_GOOD], COND_NEW_POKEMON); - transfer_script[COND_NEW_POKEMON] = script_obj(COND_NEW_POKEMON, DIA_NEW_DEX, DIA_NO_NEW_DEX); - transfer_script[DIA_NEW_DEX] = script_obj(dialogue[DIA_NEW_DEX], COND_IS_HOENN_RS); - transfer_script[DIA_NO_NEW_DEX] = script_obj(dialogue[DIA_NO_NEW_DEX], COND_IS_HOENN_RS); - transfer_script[COND_IS_HOENN_RS] = script_obj(COND_IS_HOENN_RS, DIA_SEND_FRIEND_HOENN_RS, COND_IS_HOENN_E); - transfer_script[COND_IS_HOENN_E] = script_obj(COND_IS_HOENN_E, DIA_SEND_FRIEND_HOENN_E, DIA_SEND_FRIEND_KANTO); - transfer_script[DIA_SEND_FRIEND_HOENN_RS] = script_obj(dialogue[DIA_SEND_FRIEND_HOENN_RS], DIA_THANK); - transfer_script[DIA_SEND_FRIEND_HOENN_E] = script_obj(dialogue[DIA_SEND_FRIEND_HOENN_E], DIA_THANK); - transfer_script[DIA_SEND_FRIEND_KANTO] = script_obj(dialogue[DIA_SEND_FRIEND_KANTO], DIA_THANK); - transfer_script[DIA_THANK] = script_obj(dialogue[DIA_THANK], CMD_END_SCRIPT); - - // Hide the dialouge and professor - transfer_script[CMD_END_SCRIPT] = script_obj(CMD_END_SCRIPT, CMD_BACK_TO_MENU); - transfer_script[CMD_BACK_TO_MENU] = script_obj(CMD_BACK_TO_MENU, T_SCRIPT_START); - - // -------- EVENTS SCRIPT -------- - // Start the dialogue and show the menu - event_script[E_SCRIPT_START] = script_obj(CMD_SHOW_PROF, DIA_ASK_QUEST); - event_script[DIA_ASK_QUEST] = script_obj(dialogue[DIA_ASK_QUEST], CMD_SLIDE_PROF_LEFT); - - // Ask the user what game and language they're using - event_script[DIA_WHAT_GAME_EVENT] = script_obj(dialogue[DIA_WHAT_GAME_EVENT], CMD_GAME_MENU); - event_script[CMD_GAME_MENU] = script_obj(CMD_GAME_MENU, COND_GB_ROM_EXISTS, DIA_WHAT_LANG_EVENT); - event_script[DIA_WHAT_LANG_EVENT] = script_obj(dialogue[DIA_WHAT_LANG_EVENT], CMD_LANG_MENU); - event_script[CMD_LANG_MENU] = script_obj(CMD_LANG_MENU, DIA_WHAT_GAME_EVENT); - event_script[DIA_ASK_QUEST] = script_obj(dialogue[DIA_ASK_QUEST], CMD_SLIDE_PROF_LEFT); - event_script[CMD_SLIDE_PROF_LEFT] = script_obj(CMD_SLIDE_PROF_LEFT, DIA_WHAT_LANG_EVENT); - event_script[CMD_SLIDE_PROF_RIGHT] = script_obj(CMD_SLIDE_PROF_RIGHT, COND_CHECK_DEX); - event_script[COND_GB_ROM_EXISTS] = script_obj(COND_GB_ROM_EXISTS, CMD_SLIDE_PROF_RIGHT, DIA_NO_GB_ROM); - event_script[DIA_NO_GB_ROM] = script_obj(dialogue[DIA_NO_GB_ROM], DIA_WHAT_LANG_EVENT); - - // Check the player's dex - event_script[COND_CHECK_DEX] = script_obj(COND_CHECK_DEX, 0, COND_CHECK_KANTO); - event_script[COND_CHECK_KANTO] = script_obj(COND_CHECK_KANTO, DIA_K_DEX_NOT_FULL, DIA_J_DEX_NOT_FULL); - event_script[DIA_K_DEX_NOT_FULL] = script_obj(dialogue[DIA_K_DEX_NOT_FULL], CMD_END_SCRIPT); - event_script[DIA_J_DEX_NOT_FULL] = script_obj(dialogue[DIA_J_DEX_NOT_FULL], CMD_END_SCRIPT); - - // Hide the dialouge and professor - event_script[CMD_END_SCRIPT] = script_obj(CMD_END_SCRIPT, CMD_BACK_TO_MENU); - event_script[CMD_BACK_TO_MENU] = script_obj(CMD_BACK_TO_MENU, T_SCRIPT_START); +// The array below is exactly in order of the DIA, commands and condition int values +// So if adding a new dia, command or condition, make sure to add it in the right position/index. +// because things are in order, it also means we have to define every possible entry even if we don't use it. +// 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 + { + .text_entry_index = DIA_ASK_QUEST, + .next_if_true = CMD_SLIDE_PROF_LEFT + }, + {}, // DIA_WHAT_GAME_TRANS + {}, // DIA_WHAT_LANG_TRANS + // DIA_NO_GB_ROM + { + .text_entry_index = DIA_NO_GB_ROM, + .next_if_true = 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 + { + .text_entry_index = DIA_WHAT_LANG_EVENT, + .next_if_true = CMD_LANG_MENU + }, + // DIA_WHAT_GAME_EVENT + { + .text_entry_index = DIA_WHAT_GAME_EVENT, + .next_if_true = CMD_GAME_MENU + }, + // DIA_K_DEX_NOT_FULL + { + .text_entry_index = DIA_K_DEX_NOT_FULL, + .next_if_true = CMD_END_SCRIPT + }, + // DIA_J_DEX_NOT_FULL + { + .text_entry_index = 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 + }, + {}, // CMD_START_LINK + {}, // CMD_IMPORT_POKEMON + // CMD_BACK_TO_MENU + { + .conditional_index = CMD_BACK_TO_MENU, + .next_if_true = T_SCRIPT_START + }, + {}, // CMD_SHOW_PROF + {}, // CMD_HIDE_PROF + {}, // CMD_SET_TUTOR_TRUE + // CMD_END_SCRIPT + { + .conditional_index = CMD_END_SCRIPT, + .next_if_true = CMD_BACK_TO_MENU + }, + // CMD_GAME_MENU + { + .conditional_index = CMD_GAME_MENU, + .next_if_true = COND_GB_ROM_EXISTS, + .next_if_false = DIA_WHAT_LANG_EVENT + }, + // CMD_LANG_MENU + { + .conditional_index = CMD_LANG_MENU, + .next_if_true = DIA_WHAT_GAME_EVENT + }, + // CMD_SLIDE_PROF_LEFT + { + .conditional_index = CMD_SLIDE_PROF_LEFT, + .next_if_true = DIA_WHAT_LANG_EVENT + }, + // CMD_SLIDE_PROF_RIGHT + { + .conditional_index = CMD_SLIDE_PROF_RIGHT, + .next_if_true = COND_CHECK_DEX + }, + {}, // CMD_CONTINUE_LINK + {}, // CMD_BOX_MENU + {}, // CMD_MYTHIC_MENU + {}, // CMD_LOAD_SIMP + {}, // CMD_CANCEL_LINK + {}, // CMD_END_MISSINGNO + {}, // COND_ERROR_TIMEOUT_ONE + {}, // COND_ERROR_DISCONNECT + {}, // COND_ERROR_COM_ENDED + {}, // COND_ERROR_TIMEOUT_TWO + {}, // COND_ERROR_COLOSSEUM + {}, // COND_BEAT_E4 + {}, // COND_MG_ENABLED + {}, // COND_TUTORIAL_COMPLETE + {}, // COND_NEW_POKEMON + {}, // COND_IS_HOENN_RS + {}, // COND_IS_FRLGE + {}, // COND_MG_OTHER_EVENT + {}, // COND_PKMN_TO_COLLECT + // COND_GB_ROM_EXISTS + { + .conditional_index = COND_GB_ROM_EXISTS, + .next_if_true = CMD_SLIDE_PROF_RIGHT, + .next_if_false = DIA_NO_GB_ROM + }, + {}, // COND_CHECK_MYTHIC + // COND_CHECK_DEX + { + .conditional_index = COND_CHECK_DEX, + .next_if_true = 0, + .next_if_false = COND_CHECK_KANTO + }, + // COND_CHECK_KANTO + { + .conditional_index = COND_CHECK_KANTO, + .next_if_true = DIA_K_DEX_NOT_FULL, + .next_if_false = DIA_J_DEX_NOT_FULL + }, + {}, // COND_SOME_INVALID_PKMN + {}, // COND_IS_HOENN_E + {}, // COND_CHECK_MISSINGNO }; void populate_lang_menu() { - langs.add_option(option_english, ENG_ID); - langs.add_option(option_japanese, JPN_ID); - langs.add_option(option_spanish, SPA_ID); - langs.add_option(option_french, FRE_ID); - langs.add_option(option_german, GER_ID); - langs.add_option(option_italian, ITA_ID); - langs.add_option(option_korean, KOR_ID); - langs.add_option(option_cancel, UINT8_MAX); + langs.add_option(GENERAL_option_english, ENG_ID); + langs.add_option(GENERAL_option_japanese, JPN_ID); + langs.add_option(GENERAL_option_spanish, SPA_ID); + langs.add_option(GENERAL_option_french, FRE_ID); + langs.add_option(GENERAL_option_german, GER_ID); + langs.add_option(GENERAL_option_italian, ITA_ID); + langs.add_option(GENERAL_option_korean, KOR_ID); + langs.add_option(GENERAL_option_cancel, UINT8_MAX); } void populate_game_menu(int lang) @@ -147,34 +696,40 @@ void populate_game_menu(int lang) switch (lang) { case (JPN_ID): - games.add_option(option_red, RED_ID); - games.add_option(option_green, GREEN_ID); - games.add_option(option_blue, BLUE_ID); - games.add_option(option_yellow, YELLOW_ID); - games.add_option(option_gold, GOLD_ID); - games.add_option(option_silver, SILVER_ID); - games.add_option(option_crystal, CRYSTAL_ID); - games.add_option(option_cancel, UINT8_MAX); + games.add_option(GENERAL_option_red, RED_ID); + games.add_option(GENERAL_option_green, GREEN_ID); + games.add_option(GENERAL_option_blue, BLUE_ID); + games.add_option(GENERAL_option_yellow, YELLOW_ID); + games.add_option(GENERAL_option_gold, GOLD_ID); + games.add_option(GENERAL_option_silver, SILVER_ID); + games.add_option(GENERAL_option_crystal, CRYSTAL_ID); + games.add_option(GENERAL_option_cancel, UINT8_MAX); break; case (KOR_ID): - games.add_option(option_gold, GOLD_ID); - games.add_option(option_silver, SILVER_ID); - games.add_option(option_cancel, UINT8_MAX); + games.add_option(GENERAL_option_gold, GOLD_ID); + games.add_option(GENERAL_option_silver, SILVER_ID); + games.add_option(GENERAL_option_cancel, UINT8_MAX); break; default: - games.add_option(option_red, RED_ID); - games.add_option(option_blue, BLUE_ID); - games.add_option(option_yellow, YELLOW_ID); - games.add_option(option_gold, GOLD_ID); - games.add_option(option_silver, SILVER_ID); - games.add_option(option_crystal, CRYSTAL_ID); - games.add_option(option_cancel, UINT8_MAX); + games.add_option(GENERAL_option_red, RED_ID); + games.add_option(GENERAL_option_blue, BLUE_ID); + games.add_option(GENERAL_option_yellow, YELLOW_ID); + games.add_option(GENERAL_option_gold, GOLD_ID); + games.add_option(GENERAL_option_silver, SILVER_ID); + games.add_option(GENERAL_option_crystal, CRYSTAL_ID); + games.add_option(GENERAL_option_cancel, UINT8_MAX); break; } } +static bool load_simple_party_data() +{ + PokemonTables data_tables; + return party_data.fill_simple_pkmn_array(data_tables); +} + bool run_conditional(int index) { // Here is most of the logic that drives what lines show up where. It's probably not the best way to code it, but it works @@ -348,10 +903,7 @@ bool run_conditional(int index) return true; case CMD_LOAD_SIMP: - { - PokemonTables data_tables; - return party_data.fill_simple_pkmn_array(data_tables); - } + return load_simple_party_data(); case CMD_CANCEL_LINK: party_data.continue_link(true); return true; diff --git a/source/script_obj.cpp b/source/script_obj.cpp index 852fce8..3f3f01f 100644 --- a/source/script_obj.cpp +++ b/source/script_obj.cpp @@ -1,53 +1,40 @@ -#include -#include #include "script_obj.h" -#include "pokemon.h" -#include "pokemon_party.h" -#include "script_array.h" -#include "translated_text.h" -script_obj::script_obj(){}; - -script_obj::script_obj(const byte* nText, uint16_t nNext) +script_obj::script_obj() + : params_({0}) +{ +}; + +script_obj::script_obj(const script_obj_params ¶ms) + : params_(params) { - text = nText; - next_index = nNext; - conditional_index = 0; - next_false_index = 0; } -script_obj::script_obj(uint16_t nRun, uint16_t nNext) +bool script_obj::has_text() const { - text = nullptr; - next_index = nNext; - conditional_index = nRun; - next_false_index = nNext; + // So the thing is: when conditional_index is set, its value will always be higher than 0 + // because the way these integer defines have been set up. + // So that means that if conditional_index IS 0, then we must have a text entry index defined instead! + // We can't base this check on the text_entry_index itself since unfortunately its value being zero can be legit. + return (params_.conditional_index == 0); } -script_obj::script_obj(uint16_t nRun, uint16_t nNext_if_true, uint16_t nNext_if_false) +u8 script_obj::get_text_entry_index() const { - text = nullptr; - next_index = nNext_if_true; - conditional_index = nRun; - next_false_index = nNext_if_false; + return params_.text_entry_index; } -const byte* script_obj::get_text() +u16 script_obj::get_true_index() const { - return text; + return params_.next_if_true; } -uint16_t script_obj::get_true_index() +u16 script_obj::get_false_index() const { - return next_index; + return params_.next_if_false; } -uint16_t script_obj::get_false_index() +u16 script_obj::get_cond_id() const { - return next_false_index; -} - -uint16_t script_obj::get_cond_id() -{ - return conditional_index; + return params_.conditional_index; } \ No newline at end of file diff --git a/source/script_var.cpp b/source/script_var.cpp index 7b9e6b0..16bbb43 100644 --- a/source/script_var.cpp +++ b/source/script_var.cpp @@ -153,28 +153,17 @@ void textbox_var::set_virtual_start() start_location_in_script = *curr_loc_ptr - 4; } -void textbox_var::insert_text(const u16 *charset, u8 mg_array[]) +void textbox_var::insert_text(const u16 *charset, u8 mg_array[], bool should_set_virtual_start) { - set_start(); - for (int parser = 0; parser < text_length; parser++) + if(!should_set_virtual_start) { - if (curr_rom.is_hoenn() && (text[parser] == 0xFC) && (get_char_from_charset(charset, (char16_t)(text[parser + 1])) == 0x01)) // Removes colored text - { - parser += 2; - } - else - { - mg_array[*curr_loc_ptr] = text[parser]; - (*curr_loc_ptr)++; - } + set_start(); + } + else + { + set_virtual_start(); } - mg_array[*curr_loc_ptr] = 0xFF; // End string - (*curr_loc_ptr)++; -} -void textbox_var::insert_virtual_text(const u16 *charset, u8 mg_array[]) -{ - set_virtual_start(); for (int parser = 0; parser < text_length; parser++) { if (curr_rom.is_hoenn() && (text[parser] == 0xFC) && (get_char_from_charset(charset, (char16_t)(text[parser + 1])) == 0x01)) // Removes colored text diff --git a/source/select_menu.cpp b/source/select_menu.cpp index c3b656f..8f6cc0d 100644 --- a/source/select_menu.cpp +++ b/source/select_menu.cpp @@ -1,5 +1,7 @@ #include "select_menu.h" #include "sprite_data.h" +#include "translated_text.h" +#include "text_data_table.h" #define TEXT_HEIGHT 10 #define TEXT_WIDTH 8 @@ -14,7 +16,7 @@ Select_Menu::Select_Menu(bool enable_cancel, u8 nMenu_type, int nStartTileX, int startTileY = nStartTileY; } -void Select_Menu::add_option(const byte *option, u8 return_value) +void Select_Menu::add_option(const u8 option, u8 return_value) { menu_options.push_back(option); return_values.push_back(return_value); @@ -101,11 +103,15 @@ int Select_Menu::select_menu_main() void Select_Menu::show_menu() { + u8 decompression_buffer[2048]; + text_data_table text_data(decompression_buffer); + 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++) { tte_set_pos((startTileX + 2) * TEXT_WIDTH, (startTileY + 1) * TILE_HEIGHT + (i * TEXT_HEIGHT)); - ptgb_write(menu_options[i], true); + ptgb_write(text_data.get_text_entry(menu_options[i]), true); } obj_unhide(point_arrow, 0); // obj_set_pos(point_arrow, startTileX + (2 * TEXT_WIDTH), (1 + i) * TEXT_HEIGHT); diff --git a/source/text_data_table.cpp b/source/text_data_table.cpp new file mode 100644 index 0000000..313313d --- /dev/null +++ b/source/text_data_table.cpp @@ -0,0 +1,154 @@ +#include "text_data_table.h" +#include "zx0_decompressor.h" +#include + +static uint16_t get_entry_offset_by_index(const uint8_t *text_table, uint8_t index) +{ + return *((uint16_t*)(text_table + 2 + index * 2)); +} + +static uint16_t get_entries_start_offset_of(uint8_t num_text_entries) +{ + // This returns the byte offset to skip the table index and reach the start of the actual entries. + return 2 + (num_text_entries * 2); +} + +text_data_table::text_data_table(uint8_t *decompression_buffer) + : decompression_buffer_(decompression_buffer) +{ +} + +void text_data_table::decompress(const uint8_t *compressed_table) +{ + zx0_decompressor_start(decompression_buffer_, compressed_table); + zx0_decompressor_read(zx0_decompressor_get_decompressed_size()); +} + +uint16_t text_data_table::get_number_of_text_entries() const +{ + return *((uint16_t*)decompression_buffer_); +} + +const uint8_t* text_data_table::get_text_entry(uint8_t index) const +{ + const uint16_t entry_offset = get_entry_offset_by_index(decompression_buffer_, index); + return decompression_buffer_ + get_entries_start_offset_of(get_number_of_text_entries()) + entry_offset; +} + +streamed_text_data_table::streamed_text_data_table(uint8_t *decompression_buffer, uint32_t decompression_buffer_size, uint8_t *index_buffer) + : compressed_table_(nullptr) + , decompression_buffer_(decompression_buffer) + , decompression_buffer_size_(decompression_buffer_size) + , index_buffer_(index_buffer) + , bytes_decompressed_(0) + , last_chunk_size_(0) +{ +} + +void streamed_text_data_table::decompress(const uint8_t *compressed_table) +{ + zx0_decompressor_start(index_buffer_, compressed_table); + zx0_decompressor_read(2); + zx0_decompressor_read(get_number_of_text_entries() * 2); + compressed_table_ = compressed_table; + bytes_decompressed_ = 2 + get_number_of_text_entries() * 2; + + // for further decompressing, we need this data to be available in the decompression buffer too. + // ZX0 looks back to already decompressed data after all. + memcpy(decompression_buffer_ + ZX0_DEFAULT_WINDOW_SIZE, index_buffer_, bytes_decompressed_); + last_chunk_size_ = bytes_decompressed_; +} + +uint16_t streamed_text_data_table::get_number_of_text_entries() const +{ + return *((uint16_t*)index_buffer_); +} + +const uint8_t* streamed_text_data_table::get_text_entry(uint8_t index) +{ + const uint8_t num_text_entries = get_number_of_text_entries(); + const uint16_t entries_start_offset = get_entries_start_offset_of(num_text_entries); + const uint16_t entry_offset = get_entry_offset_by_index(index_buffer_, index); + const uint16_t entry_byte_offset = entries_start_offset + entry_offset; + const uint16_t space_remaining_outside_lookback_window = decompression_buffer_size_ - ZX0_DEFAULT_WINDOW_SIZE; + const uint16_t current_window_size = get_current_zx0_window_size(); + const uint16_t window_start_offset = bytes_decompressed_ - current_window_size; + uint16_t bytes_to_decompress; + uint16_t chunk_size; + uint16_t entry_size_in_bytes; + uint16_t entry_end_byte_offset; + + // figure out how many bytes we need to read to have the entire text entry + // unfortunately ZX0 doesn't have random access, so we need to linearly decompress + // until we have reached the bytes we actually want. + if(index != num_text_entries - 1) + { + const uint16_t next_entry_offset = get_entry_offset_by_index(index_buffer_, index + 1); + entry_size_in_bytes = next_entry_offset - entry_offset; + } + else + { + // we don't have a next entry. So we need to consider the end of the file + const uint32_t decompressed_size = zx0_decompressor_get_decompressed_size(); + entry_size_in_bytes = decompressed_size - entry_byte_offset; + } + entry_end_byte_offset = entry_byte_offset + entry_size_in_bytes; + + if(entry_end_byte_offset < bytes_decompressed_) + { + // already decoded, let's check if we have it completely in our current decompressed window + if(entry_byte_offset >= window_start_offset) + { + // one thing to realize is that when we have less than our ZX0 window size, the decoded data doesn't start + // at the start of the buffer. But instead it ends at decompression_buffer + ZX0_DEFAULT_WINDOW_SIZE + return get_window_start() + (entry_byte_offset - window_start_offset); + } + else + { + // unfortunately it's in front of our current decompression window. + // Since ZX0 doesn't actually have random access, it means we have to start + // decompression from scratch + decompress(compressed_table_); + // now that we decompressed JUST the index table again, + // we should be able to reach desired_byte_offset. + } + } + + bytes_to_decompress = entry_end_byte_offset - bytes_decompressed_; + // keep decompressing until we have decompressed what we need. + while(bytes_to_decompress > 0) + { + // move the last decompressed chunk backwards + memmove(decompression_buffer_, decompression_buffer_ + last_chunk_size_, ZX0_DEFAULT_WINDOW_SIZE); + chunk_size = (bytes_to_decompress > space_remaining_outside_lookback_window) ? space_remaining_outside_lookback_window : bytes_to_decompress; + + zx0_decompressor_read_partial(decompression_buffer_ + ZX0_DEFAULT_WINDOW_SIZE, chunk_size); + last_chunk_size_ = chunk_size; + bytes_to_decompress -= chunk_size; + bytes_decompressed_ += chunk_size; + } + + // we know the last byte we decompressed should be the last byte of the entry + // so we need to count backwards to get to the beginning + return decompression_buffer_ + ZX0_DEFAULT_WINDOW_SIZE + last_chunk_size_ - entry_size_in_bytes; +} + +uint8_t* streamed_text_data_table::get_window_start() const +{ + uint16_t without_last_chunk_size = (bytes_decompressed_ - last_chunk_size_); + if(without_last_chunk_size > ZX0_DEFAULT_WINDOW_SIZE) + { + without_last_chunk_size = ZX0_DEFAULT_WINDOW_SIZE; + } + return decompression_buffer_ + ZX0_DEFAULT_WINDOW_SIZE - without_last_chunk_size; +} + +uint8_t* streamed_text_data_table::get_window_end() const +{ + return decompression_buffer_ + ZX0_DEFAULT_WINDOW_SIZE + last_chunk_size_; +} + +uint16_t streamed_text_data_table::get_current_zx0_window_size() const +{ + return static_cast(get_window_end() - get_window_start()); +} \ No newline at end of file diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 01ad03d..a01d129 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -11,6 +11,7 @@ #include "sprite_data.h" #include "latin_short.h" #include "japanese_small.h" +#include "text_data_table.h" #define TEXT_CBB 0 #define TEXT_SBB 10 @@ -55,17 +56,23 @@ void init_text_engine() int text_loop(int script) { + u8 text_decompression_buffer[3072]; + u8 index_buffer[100]; + streamed_text_data_table dialogue_table(text_decompression_buffer, sizeof(text_decompression_buffer), index_buffer); + + dialogue_table.decompress(get_compressed_PTGB_table()); switch (script) { case BTN_TRANSFER: - curr_line = transfer_script[T_SCRIPT_START]; + curr_line = transfer_script_params[T_SCRIPT_START]; break; case BTN_EVENTS: - curr_line = event_script[E_SCRIPT_START]; + curr_line = event_script_params[E_SCRIPT_START]; break; } - curr_text = curr_line.get_text(); + + curr_text = (curr_line.has_text()) ? dialogue_table.get_text_entry(curr_line.get_text_entry_index()) : NULL; REG_BG1CNT = (REG_BG1CNT && !BG_PRIO_MASK) | BG_PRIO(2); // Show Fennel show_text_box(); @@ -85,13 +92,14 @@ int text_loop(int script) switch (script) { case BTN_TRANSFER: - curr_line = transfer_script[text_next_obj_id(curr_line)]; + curr_line = transfer_script_params[text_next_obj_id(curr_line)]; break; case BTN_EVENTS: - curr_line = event_script[text_next_obj_id(curr_line)]; + curr_line = event_script_params[text_next_obj_id(curr_line)]; break; } - curr_text = curr_line.get_text(); + + curr_text = (curr_line.has_text()) ? dialogue_table.get_text_entry(curr_line.get_text_entry_index()) : NULL; char_index = 0; if (text_exit) @@ -154,14 +162,6 @@ 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]; - } - while ((ch = *str) != 0xFF && num < length) { if (get_frame_count() % 2 == 0 || key_held(KEY_B) || key_held(KEY_A) || instant) diff --git a/source/zx0_decompressor.cpp b/source/zx0_decompressor.cpp index 7ec717a..8aac8a3 100644 --- a/source/zx0_decompressor.cpp +++ b/source/zx0_decompressor.cpp @@ -64,6 +64,11 @@ public: * @brief This function reads of data into */ IWRAM_CODE void read(uint32_t num_bytes); + + /** + * @brief This function swaps out the current output buffer for the given one + */ + IWRAM_CODE void swap_output_buffer(uint8_t *new_output_buffer); protected: private: IWRAM_CODE void read_next_command(); @@ -73,7 +78,7 @@ private: ZX0Command cur_command_; const uint8_t *input_data_; uint8_t *back_pos_; - uint8_t *cur_out; + uint8_t *cur_out_; uint32_t last_offset_; }; @@ -145,7 +150,7 @@ ZX0Decompressor::ZX0Decompressor() , cur_command_({ZX0OperationType::NONE, 0, 0, 0}) , input_data_(nullptr) , back_pos_(nullptr) - , cur_out(nullptr) + , cur_out_(nullptr) , last_offset_(UINT32_MAX) { } @@ -156,7 +161,7 @@ void ZX0Decompressor::start(uint8_t *output_buffer, const uint8_t *input_data) cur_command_ = {ZX0OperationType::NONE, 0, 0, 0}; input_data_ = input_data; back_pos_ = nullptr; - cur_out = output_buffer; + cur_out_ = output_buffer; last_offset_ = UINT32_MAX; } @@ -185,6 +190,13 @@ IWRAM_CODE void ZX0Decompressor::read(uint32_t num_bytes) } } +IWRAM_CODE void ZX0Decompressor::swap_output_buffer(uint8_t *new_output_buffer) +{ + const uint32_t current_offset = cur_out_ - back_pos_; + cur_out_ = new_output_buffer; + back_pos_ = new_output_buffer - current_offset; +} + IWRAM_CODE inline void ZX0Decompressor::read_next_command() { const uint32_t cmd_bit = reader_.read(1); @@ -226,10 +238,10 @@ IWRAM_CODE uint32_t ZX0Decompressor::copy_block(uint32_t num_bytes) { // Literal copy - // Align cur_out first - while (bytes_remaining && ((uintptr_t)cur_out & 3)) + // Align cur_out_ first + while (bytes_remaining && ((uintptr_t)cur_out_ & 3)) { - (*cur_out++) = reader_.read(8); + (*cur_out_++) = reader_.read(8); bytes_remaining--; } @@ -239,27 +251,27 @@ IWRAM_CODE uint32_t ZX0Decompressor::copy_block(uint32_t num_bytes) // we need to swap again, because the data was originally stored in big endian format // BitReader converted it to little endian format to make reading easier. // and now we need to convert it back to big endian format. - *(uint32_t*)cur_out = __builtin_bswap32(reader_.read(32)); - cur_out += 4; + *(uint32_t*)cur_out_ = __builtin_bswap32(reader_.read(32)); + cur_out_ += 4; bytes_remaining -= 4; } // Handle remaining bytes while (bytes_remaining--) { - (*cur_out++) = reader_.read(8); + (*cur_out_++) = reader_.read(8); } } else { if(!cur_command_.byte_pos) { - back_pos_ = cur_out - cur_command_.offset; + back_pos_ = cur_out_ - cur_command_.offset; } - // try to get cur_out and back_pos aligned to 32 bit accesses first - while (bytes_remaining && (((uintptr_t)cur_out & 3) || ((uintptr_t)back_pos_ & 3))) + // try to get cur_out_ and back_pos aligned to 32 bit accesses first + while (bytes_remaining && (((uintptr_t)cur_out_ & 3) || ((uintptr_t)back_pos_ & 3))) { - (*cur_out++) = (*back_pos_++); + (*cur_out_++) = (*back_pos_++); bytes_remaining--; } @@ -268,15 +280,15 @@ IWRAM_CODE uint32_t ZX0Decompressor::copy_block(uint32_t num_bytes) { // these don't need to be byteswapped, because the data is being read with the same endianness as it is being written. // this is different when reading from BitReader. - *(uint32_t*)cur_out = *((uint32_t*)back_pos_); - cur_out += 4; + *(uint32_t*)cur_out_ = *((uint32_t*)back_pos_); + cur_out_ += 4; back_pos_ += 4; bytes_remaining -= 4; } while(bytes_remaining--) { - (*cur_out++) = (*back_pos_++); + (*cur_out_++) = (*back_pos_++); } } @@ -304,4 +316,11 @@ void zx0_decompressor_read(uint32_t num_bytes) { decompressor.read(num_bytes); } + +void zx0_decompressor_read_partial(uint8_t *output_buffer, uint16_t num_bytes) +{ + decompressor.swap_output_buffer(output_buffer); + decompressor.read(num_bytes); +} + } \ No newline at end of file diff --git a/text_helper/main.py b/text_helper/main.py old mode 100644 new mode 100755 index 5ffe141..f91826e --- a/text_helper/main.py +++ b/text_helper/main.py @@ -10,14 +10,14 @@ import math update = True -print ("\n\n\n\n\n---------------") +print ("\nRunning text_helper:\n\n\n\n---------------") if (update == True): url = 'https://docs.google.com/spreadsheets/d/14LLs5lLqWasFcssBmJdGXjjYxARAJBa_QUOUhXZt4v8/export?format=xlsx' response = requests.get(url) - file_Path = 'text_helper\\text.xlsx' + file_Path = 'text_helper/text.xlsx' if response.status_code == 200: with open(file_Path, 'wb') as file: @@ -272,7 +272,7 @@ class Languages(Enum): SpanishLA = 6 # read by default 1st sheet of an excel file -dir = os.curdir + "\\text_helper" +dir = os.curdir + "/text_helper" mainDict = {} @@ -357,18 +357,57 @@ def convert_item(ogDict): else: arr = engCharArray for char in outStr[:-1]: - byteStr += hex(convertByte(ord(char), arr)) + ", " + byteStr += f"{convertByte(ord(char), arr):02x} " if (len(outStr) > 0 and outStr[-1] != ' '): # Check if the last char is a space - byteStr += hex(convertByte(ord(outStr[-1]), arr)) + ", " + byteStr += f"{convertByte(ord(outStr[-1]), arr):02x} " - byteStr += "0xff" + byteStr += "ff" ogDict["bytes"] = byteStr return ogDict +def write_text_bin_file(filename, dictionary): + with open(filename, 'wb') as binFile: + # Let the first byte indicate the number of entries + dict_size = len(dictionary) + # We need to store 2 bytes instead of one, because not aligning the data to 16 bits will cause corruption on the gba. + binFile.write(bytes([dict_size & 0xFF, (dict_size >> 8) & 0xFF])) + # After this initial byte, we will read the offset (16 bit) of each line (relative to the last index byte) + index = bytearray(len(dictionary) * 2) + # bindata will contain the binary data of each entry + bindata = bytearray() + current_offset = 0 + + num = 0 + # 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) + # 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 + linedata = bytes.fromhex(dictionary[key]['bytes']) + bindata.extend(linedata) + current_offset += len(linedata) + num += 1 + + # Write the index and bindata to the file + binFile.write(index) + binFile.write(bindata) + binFile.close() + +def write_enum_to_header_file(hFile, prefix, dictionary): + num = 0 + for key, line in dictionary.items(): + hFile.write(f"#define {prefix}{key} {num}\n") + num += 1 + hFile.write("\n") + return num + + print("\n\nStarting parse: \n") -currSheet = pd.read_excel(dir + "\\text.xlsx", sheet_name="Translations") +currSheet = pd.read_excel(dir + "/text.xlsx", sheet_name="Translations") for row in currSheet.iterrows(): #print(row) for lang in Languages: @@ -383,99 +422,118 @@ for row in currSheet.iterrows(): "pixelsInLine" : currRow.iloc[4], "includeBoxBreaks": currRow.iloc[5], } - -with open(os.curdir + '\\source\\translated_text.cpp', 'w') as cppFile: - cppFile.write("#include \"translated_text.h\"\n#include \"debug_mode.h\"\n#include \"pokemon_data.h\"\n") - for lang in Languages: # putting this here is a really silly way to loop through all the CPP values but only write to H once - with open (os.curdir + '\\include\\translated_text.h', 'w') as hFile: - hFile.write("#ifndef DIALOGUE_H\n#define DIALOGUE_H\n\n#include \n#include \n\n") - cppFile.write(f"#if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") +# 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") - # PTGB - PTGB = mainDict[lang.name]["PTGB"] - - num = 0 - for key, line in PTGB.items(): - #print("--------") - PTGB[key] = convert_item(line) - cppFile.write("\nconst byte dialogueLine" + str(num) + "[] = {" + PTGB[key]["bytes"] + "};") - hFile.write(f"#define {key} {num}\n") - num += 1 - - cppFile.write("\n") - hFile.write(f"\n#define DIA_SIZE {num}\n#define DIA_END DIA_SIZE\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") - cppFile.write("\n\nconst byte *dialogue[DIA_SIZE] = {") - for i in range(num): - cppFile.write("\ndialogueLine" + str(i) + ", ") - cppFile.write("\n};\n") - hFile.write("extern const byte *dialogue[DIA_SIZE];\n") + # RSEFRLG + write_enum_to_header_file(hFile, "RSEFRLG_", mainDict[lang.name]["RSEFRLG"]) - # RSEFRLG - RSEFRLG = mainDict[lang.name]["RSEFRLG"] - for key, line in RSEFRLG.items(): - RSEFRLG[key] = convert_item(line) - cppFile.write(f"\nconst byte {key}[] = {{{RSEFRLG[key]["bytes"]}}};") - hFile.write(f"\nextern const byte {key}[];") + # GENERAL + write_enum_to_header_file(hFile, "GENERAL_", mainDict[lang.name]["GENERAL"]) - # General - GENERAL = mainDict[lang.name]["GENERAL"] - for key, line in GENERAL.items(): - GENERAL[key] = convert_item(line) - cppFile.write(f"const byte {key}[] = {{{GENERAL[key]["bytes"]}}};\n") - hFile.write(f"extern const byte {key}[];\n") - - # Credits - CREDITS = mainDict[lang.name]["CREDITS"] - for key, line in CREDITS.items(): - CREDITS[key] = convert_item(line) - cppFile.write(f"const byte {key}[] = {{{CREDITS[key]["bytes"]}}};\n") - hFile.write(f"extern const byte {key}[];\n") - cppFile.write("\n") - - # Pokemon Names - PKMN_NAMES = mainDict[lang.name]["PKMN_NAMES"] - - num = 0 - for key, line in PKMN_NAMES.items(): - #print("--------") - PKMN_NAMES[key] = convert_item(line) - cppFile.write("const byte PKMN_NAMES" + str(num) + "[] = {" + PKMN_NAMES[key]["bytes"] + "};\n") - num += 1 - - cppFile.write("\n") + # CREDITS + write_enum_to_header_file(hFile, "CREDITS_", mainDict[lang.name]["CREDITS"]) - cppFile.write(f"\n\nconst byte *PKMN_NAMES[{num}] = " + "{") - for i in range(num): - cppFile.write("\nPKMN_NAMES" + str(i) + ", ") - cppFile.write("\n};\n") - hFile.write(f"extern const byte *PKMN_NAMES[{num}];\n") + # PKMN_NAMES + write_enum_to_header_file(hFile, "PKMN_NAMES_", mainDict[lang.name]["PKMN_NAMES"]) + + hFile.write("/** Returns the ZX0 compressed PTGB text table.*/\n") + hFile.write("const u8* get_compressed_PTGB_table();\n\n") + hFile.write("/** Returns the ZX0 compressed RSEFRLG text table.*/\n") + hFile.write("const u8* get_compressed_rsefrlg_table();\n\n") + hFile.write("/** Returns the ZX0 compressed GENERAL text table.*/\n") + hFile.write("const u8* get_compressed_general_table();\n\n") + hFile.write("/** Returns the ZX0 compressed CREDITS text table.*/\n") + hFile.write("const u8* get_compressed_credits_table();\n\n") + hFile.write("/** Returns the ZX0 compressed PKMN_NAMES text table.*/\n") + hFile.write("const u8* get_compressed_pkmn_names_table();\n\n") + + hFile.write("\n#endif") + hFile.close() + +# 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"]) + +# 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#include \"pokemon_data.h\"\n#include \"zx0_decompressor.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() + "_zx0_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() + "_zx0_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() + "_zx0_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() + "_zx0_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() + "_zx0_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() + "_zx0_bin;\n") + cppFile.write("}\n\n") + + cppFile.write(f"#endif\n\n\n") - - - - - cppFile.write("\n") - cppFile.write(f"#endif\n\n\n") - - hFile.write("\n#endif") 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(", ") + 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, 0)] + byte = engCharArray[int(byte, 16)] outText += chr(byte) mainDict[lang.name][cat][item]["text"] = outText -with open(dir + '\\output.json', 'w') as jsonFile: +with open(dir + '/output.json', 'w') as jsonFile: jsonFile.write(json.dumps(mainDict)) diff --git a/tools/data-generator/include/common.h b/tools/data-generator/include/common.h index cee7281..5bbb51d 100644 --- a/tools/data-generator/include/common.h +++ b/tools/data-generator/include/common.h @@ -4,6 +4,6 @@ #include #include -void writeTable(const char* filename, const uint8_t *buffer, size_t buffer_size); +void writeTable(const char *output_path, const char *filename, const uint8_t *buffer, size_t buffer_size); #endif \ No newline at end of file diff --git a/tools/data-generator/include/pokemon_data.h b/tools/data-generator/include/pokemon_data.h index 51094d6..356d520 100644 --- a/tools/data-generator/include/pokemon_data.h +++ b/tools/data-generator/include/pokemon_data.h @@ -12,6 +12,6 @@ typedef uint8_t byte; typedef uint16_t u16; typedef uint32_t u32; -void generate_pokemon_data(); +void generate_pokemon_data(const char *output_path); #endif \ No newline at end of file diff --git a/tools/data-generator/src/common.cpp b/tools/data-generator/src/common.cpp index 5ef6aa2..90336eb 100644 --- a/tools/data-generator/src/common.cpp +++ b/tools/data-generator/src/common.cpp @@ -1,12 +1,23 @@ #include "common.h" #include +#include -void writeTable(const char* filename, const uint8_t *buffer, size_t buffer_size) +void writeTable(const char* output_path, const char *filename, const uint8_t *buffer, size_t buffer_size) { + char full_path[4096]; FILE* f; - f = fopen(filename, "wb+"); + if(output_path[0] != '\0') + { + snprintf(full_path, sizeof(full_path), "%s/%s", output_path, filename); + } + else + { + strncpy(full_path, filename, sizeof(full_path)); + } + + f = fopen(full_path, "wb+"); fwrite(buffer, 1, buffer_size, f); fclose(f); } \ No newline at end of file diff --git a/tools/data-generator/src/main.cpp b/tools/data-generator/src/main.cpp index 0e72ae2..42aaa58 100644 --- a/tools/data-generator/src/main.cpp +++ b/tools/data-generator/src/main.cpp @@ -1,11 +1,12 @@ #include "pokemon_data.h" - +#include // This application holds the various long static data arrays that Poke Transporter GB uses // and it writes them to .bin files that can be compressed with compressZX0 later. // it's useful to do it this way because it keeps this data easy to view, edit and document -int main(int /*argc*/, char **/*argv*/) +int main(int argc, char **argv) { - generate_pokemon_data(); + const char *output_path = (argc > 1) ? argv[1] : ""; + generate_pokemon_data(output_path); return 0; } \ No newline at end of file diff --git a/tools/data-generator/src/pokemon_data.cpp b/tools/data-generator/src/pokemon_data.cpp index 3e408a1..8db6d2a 100644 --- a/tools/data-generator/src/pokemon_data.cpp +++ b/tools/data-generator/src/pokemon_data.cpp @@ -4703,18 +4703,18 @@ const u8 TYPES[POKEMON_ARRAY_SIZE][2]{ {0xc, 0xc}, // Treecko is grass/grass }; -void generate_pokemon_data() +void generate_pokemon_data(const char *output_path) { - writeTable("gen_1_charsets.bin", (const uint8_t*)gen_1_charsets, sizeof(gen_1_charsets)); - writeTable("gen_2_charsets.bin", (const uint8_t*)gen_2_charsets, sizeof(gen_2_charsets)); - writeTable("gen_3_charsets.bin", (const uint8_t*)gen_3_charsets, sizeof(gen_3_charsets)); - writeTable("EXP_GROUPS.bin", EXP_GROUPS, sizeof(EXP_GROUPS)); - writeTable("GENDER_RATIO.bin", GENDER_RATIO, sizeof(GENDER_RATIO)); - writeTable("NUM_ABILITIES.bin", (const uint8_t*)(NUM_ABILITIES), sizeof(NUM_ABILITIES)); - writeTable("FIRST_MOVES.bin", FIRST_MOVES, sizeof(FIRST_MOVES)); - writeTable("JPN_NAMES.bin", (const uint8_t*)JPN_NAMES, sizeof(JPN_NAMES)); - writeTable("POWER_POINTS.bin", POWER_POINTS, sizeof(POWER_POINTS)); - writeTable("MENU_SPRITE_PALS.bin", (const uint8_t*)MENU_SPRITE_PALS, sizeof(MENU_SPRITE_PALS)); - writeTable("EVENT_PKMN.bin", (const uint8_t*)EVENT_PKMN, sizeof(EVENT_PKMN)); - writeTable("TYPES.bin", (const uint8_t*)TYPES, sizeof(TYPES)); + writeTable(output_path, "gen_1_charsets.bin", (const uint8_t*)gen_1_charsets, sizeof(gen_1_charsets)); + writeTable(output_path, "gen_2_charsets.bin", (const uint8_t*)gen_2_charsets, sizeof(gen_2_charsets)); + writeTable(output_path, "gen_3_charsets.bin", (const uint8_t*)gen_3_charsets, sizeof(gen_3_charsets)); + writeTable(output_path, "EXP_GROUPS.bin", EXP_GROUPS, sizeof(EXP_GROUPS)); + writeTable(output_path, "GENDER_RATIO.bin", GENDER_RATIO, sizeof(GENDER_RATIO)); + writeTable(output_path, "NUM_ABILITIES.bin", (const uint8_t*)(NUM_ABILITIES), sizeof(NUM_ABILITIES)); + writeTable(output_path, "FIRST_MOVES.bin", FIRST_MOVES, sizeof(FIRST_MOVES)); + writeTable(output_path, "JPN_NAMES.bin", (const uint8_t*)JPN_NAMES, sizeof(JPN_NAMES)); + writeTable(output_path, "POWER_POINTS.bin", POWER_POINTS, sizeof(POWER_POINTS)); + writeTable(output_path, "MENU_SPRITE_PALS.bin", (const uint8_t*)MENU_SPRITE_PALS, sizeof(MENU_SPRITE_PALS)); + writeTable(output_path, "EVENT_PKMN.bin", (const uint8_t*)EVENT_PKMN, sizeof(EVENT_PKMN)); + writeTable(output_path, "TYPES.bin", (const uint8_t*)TYPES, sizeof(TYPES)); } \ No newline at end of file