diff --git a/Makefile b/Makefile index 7a993a7..34de716 100644 --- a/Makefile +++ b/Makefile @@ -215,7 +215,6 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) GENERATE_STAMP := $(BUILD)/.generate_data.$(BUILD_LANG).$(BUILD_TYPE).stamp BUILD_STAMP := $(BUILD)/.build.$(BUILD_LANG).$(BUILD_TYPE).stamp -TEXT_HELPER_INPUTS := tools/text_helper/main.py $(wildcard tools/text_helper/fonts/*.png) $(wildcard tools/text_helper/text.xlsx) TEXT_GENERATED_OUTPUTS := \ $(GENERATED_DIR)/translated_text.h \ $(GENERATED_DIR)/translated_text.cpp \ @@ -247,7 +246,7 @@ generated_dir: generate_data: $(GENERATE_STAMP) -$(GENERATE_STAMP): $(TEXT_HELPER_INPUTS) compress_lz10.sh | data to_compress generated_dir +$(GENERATE_STAMP): compress_lz10.sh | data to_compress generated_dir @if [ "$(BUILD_XLSX)" != "remote" ]; then \ $(MAKE) --no-print-directory text_generated BUILD_LANG=$(BUILD_LANG) BUILD_TYPE=$(BUILD_TYPE) BUILD_XLSX=$(BUILD_XLSX); \ fi @@ -259,7 +258,8 @@ $(GENERATE_STAMP): $(TEXT_HELPER_INPUTS) compress_lz10.sh | data to_compress gen @echo @echo "----------------------------------------------------------------" @echo - @find $(FILE_CONTAINERS) -name "*.containerdef" -print0 | xargs -0 -n1 tools/make-file-container/make-file-container -H $(BUILD) to_compress + @find $(FILE_CONTAINERS) -name "*.containerdef" -print0 | xargs -0 -n1 tools/make-file-container/make-file-container -H $(BUILD) to_compress + @find tools/text_helper/build -name "*.containerdef" -print0 | xargs -0 -n1 tools/make-file-container/make-file-container to_compress @echo "Compressing bin files!" @echo -n "[" @find to_compress -name "*.bin" -print0 | xargs -0 -n1 ./compress_lz10.sh diff --git a/include/FileContainerReader.h b/include/FileContainerReader.h index 910dcac..f439dbd 100644 --- a/include/FileContainerReader.h +++ b/include/FileContainerReader.h @@ -4,6 +4,7 @@ #include "typeDefs.h" #define FILE_NAME_LENGTH 16 +#define DEFAULT_CHUNK_SIZE 2048 /** * @brief This class provides functionality to read files from the file container format @@ -19,7 +20,7 @@ class FileContainerReader { public: - FileContainerReader(const u8 **chunkList, u32 chunkCount, u32 chunkSize = 4096); + FileContainerReader(const u8 **chunkList, u32 chunkCount, u32 chunkSize = DEFAULT_CHUNK_SIZE); ~FileContainerReader(); /** @@ -36,6 +37,11 @@ public: */ bool init(u8 *decompressionBuffer, u32 decompressionBufferSize); + /** + * @brief Returns the number of files stored in the file container. + */ + u32 getNumberOfFiles() const; + /** * @brief IF the file container stores the file names, * this function returns a pointer to the file name of the file at the given index. @@ -57,6 +63,34 @@ public: * @brief Read data from the current position in the file container into the provided buffer. */ void read(u8 *buffer, u32 size); + + /** + * @brief Gives you a direct pointer to the specified file in the decompression buffer. (unsafe!) + * + * This is useful to use data directly from the decompression buffer without having to allocate and + * copy another buffer. This is essential in high memory pressure scenarios, such as mystery_gift_builder. + * We can't afford to keep multiple linebuffers in memory in addition to the decompression buffer and all the other variables there. + * + * WARNING: this is only safe if you know the file is stored fully in the current chunk, because files may span multiple chunks. + * Or if the container only consists of a single chunk. + * Pointers acquired this way will become stale whenever a seek is done to a position in a different chunk, + * as that will cause the decompression buffer to be overwritten with the new chunk's data. + */ + u8 *getPointerToFileInDecompressionBuffer(u32 fileIndex); + + /** + * @brief Combines seekToFile and read(). + * Just a convenience function to make our code shorter :-) + */ + void seekAndRead(u32 fileIndex, u8 *buffer, u32 size); + + /** + * @brief Even shorter variant of seekAndRead that reads the entire file at once. + * But it assumes you have provided a large enough buffer to hold the entire file. + * + * (useful for text table reading) + */ + void readFile(u32 fileIndex, u8 *buffer); protected: private: /** diff --git a/include/dbg/debug_menu.h b/include/dbg/debug_menu.h index 48755be..3eaac91 100644 --- a/include/dbg/debug_menu.h +++ b/include/dbg/debug_menu.h @@ -92,7 +92,7 @@ class debug_menu_row_widget : public i_item_widget public: debug_menu_row_widget(const debug_menu_row_data &data); virtual ~debug_menu_row_widget(); - void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) override; + void render_item(FileContainerReader &text_table, unsigned x, unsigned y, bool is_focused) override; MenuInputHandleState handle_input() override; protected: private: diff --git a/include/text_data_table.h b/include/text_data_table.h deleted file mode 100644 index 3b942e3..0000000 --- a/include/text_data_table.h +++ /dev/null @@ -1,42 +0,0 @@ -#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. - */ -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(uint16_t index) const; - - /** - * This function returns the text entry size in bytes at the given index - */ - uint16_t get_text_entry_size(uint16_t index) const; -private: - uint8_t *decompression_buffer_; - uint32_t decompressed_size_; -}; - -#endif \ No newline at end of file diff --git a/include/text_tables.h b/include/text_tables.h new file mode 100644 index 0000000..cea6531 --- /dev/null +++ b/include/text_tables.h @@ -0,0 +1,16 @@ +#ifndef _TEXT_TABLES_H +#define _TEXT_TABLES_H + +#include "typeDefs.h" + +extern "C" +{ + +/** + * @brief This function returns a list of pointers to the text table chunks and the number of chunks. + */ +void get_text_table_chunks(u32 tableIndex, const u8 ***chunkList, u32 *numChunks, u32 *chunkSize); + +} + +#endif \ No newline at end of file diff --git a/include/vertical_menu.h b/include/vertical_menu.h index bb1841d..782b32b 100644 --- a/include/vertical_menu.h +++ b/include/vertical_menu.h @@ -3,7 +3,7 @@ #include "typeDefs.h" #include "libstd_replacements.h" -#include "text_data_table.h" +#include "FileContainerReader.h" enum class MenuInputHandleState { @@ -41,7 +41,7 @@ class i_item_widget { public: virtual ~i_item_widget(); - virtual void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) = 0; + virtual void render_item(FileContainerReader &text_table, unsigned x, unsigned y, bool is_focused) = 0; virtual MenuInputHandleState handle_input() = 0; protected: private: @@ -292,7 +292,7 @@ public: const simple_item_widget_data& get_data() const; - void render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) override; + void render_item(FileContainerReader &text_table, unsigned x, unsigned y, bool is_focused) override; MenuInputHandleState handle_input() override; protected: private: diff --git a/source/FileContainerReader.cpp b/source/FileContainerReader.cpp index e43b816..c028a3a 100644 --- a/source/FileContainerReader.cpp +++ b/source/FileContainerReader.cpp @@ -56,6 +56,11 @@ bool FileContainerReader::init(u8 *decompressionBuffer, u32 decompressionBufferS return true; } +u32 FileContainerReader::getNumberOfFiles() const +{ + return fileCount_; +} + const char* FileContainerReader::getFileName(u32 fileIndex) { if(!hasNames_) @@ -111,6 +116,24 @@ void FileContainerReader::read(u8 *buffer, u32 size) } } +u8 *FileContainerReader::getPointerToFileInDecompressionBuffer(u32 fileIndex) +{ + seekToFile(fileIndex); + return decompressionBuffer_ + curPos_; +} + +void FileContainerReader::seekAndRead(u32 fileIndex, u8 *buffer, u32 size) +{ + seekToFile(fileIndex); + read(buffer, size); +} + +void FileContainerReader::readFile(u32 fileIndex, u8 *buffer) +{ + const u32 fileSize = getFileSize(fileIndex); + seekAndRead(fileIndex, buffer, fileSize); +} + u32 FileContainerReader::getFileOffset(u32 entryIndex) const { // files data start after: diff --git a/source/box_menu.cpp b/source/box_menu.cpp index 0b7192e..d9cdac2 100644 --- a/source/box_menu.cpp +++ b/source/box_menu.cpp @@ -9,14 +9,21 @@ #include "box_menu.h" #include "text_engine.h" #include "translated_text.h" -#include "text_data_table.h" +#include "FileContainerReader.h" +#include "text_tables.h" Box_Menu::Box_Menu() {}; int Box_Menu::box_main(PokeBox* box) { - u8 names_decompression_buffer[3072]; - text_data_table PKMN_NAMES(names_decompression_buffer); + u8 names_decompression_buffer[2048]; + u8 single_name_buffer[16]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; + + get_text_table_chunks(PKMN_NAMES_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader namesReader(chunkList, numChunks, chunkSize); tte_erase_screen(); load_flex_background(FLEXBG_BOX, 2); @@ -36,7 +43,7 @@ int Box_Menu::box_main(PokeBox* box) obj_unhide(box_select, 0); int index = 0; - PKMN_NAMES.decompress(get_compressed_text_table(PKMN_NAMES_INDEX)); + namesReader.init(names_decompression_buffer, sizeof(names_decompression_buffer)); while (true) { @@ -133,6 +140,8 @@ int Box_Menu::box_main(PokeBox* box) if (index < box->getNumInBox() && curr_pkmn->isValid) { byte val[11]; + u32 nameEntryIndex = curr_pkmn->getSpeciesIndexNumber(); + tte_set_pos(6, 88); curr_pkmn->externalConvertNickname(val); ptgb_write_simple(val, true); @@ -144,15 +153,14 @@ int Box_Menu::box_main(PokeBox* box) ptgb_write_simple(val, true); } tte_set_pos(14, 98); - if (curr_pkmn->getSpeciesIndexNumber() == MISSINGNO) - { - ptgb_write_simple(PKMN_NAMES.get_text_entry(0), true); - } - else + if(nameEntryIndex == MISSINGNO) { - ptgb_write_simple(PKMN_NAMES.get_text_entry(curr_pkmn->getSpeciesIndexNumber()), true); + nameEntryIndex = 0; } + namesReader.readFile(nameEntryIndex, single_name_buffer); + ptgb_write_simple(single_name_buffer, true); + tte_set_pos(6, 108); val[0] = 0xC6; // L val[1] = 0xEA; // v diff --git a/source/dbg/debug_menu.cpp b/source/dbg/debug_menu.cpp index 3cfb535..b50b6f2 100644 --- a/source/dbg/debug_menu.cpp +++ b/source/dbg/debug_menu.cpp @@ -83,7 +83,7 @@ debug_menu_row_widget::~debug_menu_row_widget() } } -void debug_menu_row_widget::render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) +void debug_menu_row_widget::render_item(FileContainerReader &text_table, unsigned x, unsigned y, bool is_focused) { const int label_margin_left = 12; const int margin_top = 2; diff --git a/source/global_frame_controller.cpp b/source/global_frame_controller.cpp index cd60ab3..c916e3f 100644 --- a/source/global_frame_controller.cpp +++ b/source/global_frame_controller.cpp @@ -7,9 +7,10 @@ #include "text_engine.h" #include "sprite_data.h" #include "string.h" -#include "text_data_table.h" #include "translated_text.h" #include "dbg/debug_mode.h" +#include "FileContainerReader.h" +#include "text_tables.h" int global_frame_count = 0; bool rand_enabled = true; @@ -25,11 +26,17 @@ bool treecko_enabled = false; // 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); + u8 decompression_buffer[2048]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); - ptgb_write_textbox(general_text.get_text_entry(GENERAL_pulled_cart_error), true, true, + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + + FileContainerReader general_text_reader(chunkList, numChunks, chunkSize); + general_text_reader.init(decompression_buffer, sizeof(decompression_buffer)); + + ptgb_write_textbox(general_text_reader.getPointerToFileInDecompressionBuffer(GENERAL_pulled_cart_error), true, true, GENERAL_INDEX, GENERAL_pulled_cart_error, true); } diff --git a/source/link_handler.cpp b/source/link_handler.cpp index 77f5065..6afbd1a 100644 --- a/source/link_handler.cpp +++ b/source/link_handler.cpp @@ -14,10 +14,11 @@ #include "global_frame_controller.h" #include "background_engine.h" #include "sprite_data.h" -#include "text_data_table.h" #include "libraries/Pokemon-Gen3-to-Gen-X/include/save.h" #include "flash_mem.h" #include "FileContainerReader.h" +#include "text_tables.h" +#include "translated_text.h" #include "GB_Payloads_chunk0_lz10_bin.h" #include "GB_Payloads.h" @@ -64,11 +65,17 @@ void LinkConnection::setup(const u16 *debug_charset) { u8 general_text_table_buffer[2048]; - text_data_table general_text(general_text_table_buffer); + u8 lineBuffer[1024]; // text_helper/main.py restricts the lines to 1024 bytes. + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); - ptgb_write_textbox(general_text.get_text_entry(GENERAL_connecting), true, false, - GENERAL_INDEX, GENERAL_connecting, false); + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + + FileContainerReader general_text_reader(chunkList, numChunks, chunkSize); + general_text_reader.init(general_text_table_buffer, sizeof(general_text_table_buffer)); + general_text_reader.readFile(GENERAL_connecting, lineBuffer); + ptgb_write_textbox(lineBuffer, true, false, GENERAL_INDEX, GENERAL_connecting, false); } create_textbox(0, 0, 138, 128, false); diff --git a/source/main.cpp b/source/main.cpp index 1ebda4f..aec008a 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -27,7 +27,9 @@ #include "multiboot_upload.h" #include "rom_data.h" #include "libraries/Pokemon-Gen3-to-Gen-X/include/save.h" -#include "text_data_table.h" +#include "FileContainerReader.h" +#include "translated_text.h" +#include "text_tables.h" #include "custom_malloc.h" #include "sound.h" @@ -111,18 +113,23 @@ void initialization_script(void) PTGB_MGBA_INFO("Hello from PTGB!"); }; -void game_load_error(void) +// attribute noinline is used to make sure it doesn't get inlined and permanently use IWRAM for the decompression_buffer +void __attribute__((noinline)) game_load_error(void) { + u8 general_text_buffer[2048]; + u8 lineBuffer[1024]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; + + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader text_reader(chunkList, numChunks, chunkSize); + + text_reader.init(general_text_buffer, sizeof(general_text_buffer)); + BG_TEXTBOX = (BG_TEXTBOX & ~BG_PRIO_MASK) | BG_PRIO(1); - - { - u8 general_text_table_buffer[2048]; - text_data_table general_text(general_text_table_buffer); - - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); - ptgb_write_textbox(general_text.get_text_entry(GENERAL_cart_load_error), true, false, - GENERAL_INDEX, GENERAL_cart_load_error, false); - } + text_reader.readFile(GENERAL_cart_load_error, lineBuffer); + ptgb_write_textbox(lineBuffer, true, false, GENERAL_INDEX, GENERAL_cart_load_error, false); // key_poll(); do @@ -153,39 +160,28 @@ void game_load_error(void) } } -void first_load_message(void) +// avoid inlining to avoid permanently storing the credits_decompression_buffer in IWRAM +int __attribute__((noinline)) credits() { - tte_set_ink(INK_ROM_COLOR); + u8 credits_decompression_buffer[2048]; + u8 lineBuffer[1024]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; - { - u8 general_text_table_buffer[2048]; - text_data_table general_text(general_text_table_buffer); + get_text_table_chunks(CREDITS_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader creditsReader(chunkList, numChunks, chunkSize); + u32 curr_credits_num = 0; - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); - ptgb_write_simple(general_text.get_text_entry(GENERAL_intro_first), true); - } - - while (!key_hit(KEY_A)) - { - VBlankIntrWait(); - } -} - -int credits() -{ - u8 text_decompression_buffer[2048]; - text_data_table credits_text_table(text_decompression_buffer); - int curr_credits_num = 0; - - credits_text_table.decompress(get_compressed_text_table(CREDITS_INDEX)); + creditsReader.init(credits_decompression_buffer, sizeof(credits_decompression_buffer)); bool update = true; while (true) { if (update) { - ptgb_write_textbox(credits_text_table.get_text_entry(curr_credits_num), true, false, - CREDITS_INDEX, curr_credits_num, false); + creditsReader.readFile(curr_credits_num, lineBuffer); + ptgb_write_textbox(lineBuffer, true, false, CREDITS_INDEX, curr_credits_num, false); update = false; } @@ -201,7 +197,7 @@ int credits() curr_credits_num--; update = true; } - if (key_hit(KEY_RIGHT) && curr_credits_num < (credits_text_table.get_number_of_text_entries() - 1)) + if (key_hit(KEY_RIGHT) && curr_credits_num < (creditsReader.getNumberOfFiles() - 1)) { curr_credits_num++; update = true; @@ -211,19 +207,24 @@ int credits() } }; -int main_menu_loop() +// attribute noinline is used to avoid permanently storing the general_text_table_buffer in IWRAM +int __attribute__((noinline)) main_menu_loop() { + uint8_t general_text_table_buffer[2048]; + u8 lineBuffer[1024]; #define NUM_MENU_OPTIONS 3 const uint8_t menu_options[NUM_MENU_OPTIONS] = {GENERAL_option_transfer, GENERAL_option_dreamdex, GENERAL_option_credits}; int return_values[NUM_MENU_OPTIONS] = {BTN_TRANSFER, BTN_POKEDEX, BTN_CREDITS}; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; - uint8_t general_text_table_buffer[2048]; - text_data_table general_text(general_text_table_buffer); + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader text_reader(chunkList, numChunks, chunkSize); bool update = true; - const uint8_t *text_entry; u16 test = 0; - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); + text_reader.init(general_text_table_buffer, sizeof(general_text_table_buffer)); while (true) { @@ -231,8 +232,8 @@ int main_menu_loop() { for (int i = 0; i < NUM_MENU_OPTIONS; i++) { - text_entry = general_text.get_text_entry(menu_options[i]); - int string_length = get_string_length(text_entry); + text_reader.readFile(menu_options[i], lineBuffer); + int string_length = get_string_length(lineBuffer); int x = ((240 - string_length) / 2); tte_set_pos(x, ((i * (16 + 10)) + 70)); if (i == curr_selection) @@ -243,7 +244,7 @@ int main_menu_loop() { tte_set_ink(INK_ROM_COLOR); } - ptgb_write_simple(text_entry, true); + ptgb_write_simple(lineBuffer, true); test++; } } @@ -296,18 +297,21 @@ static void show_gears_of_progress() // 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 lineBuffer[1024]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; - text_data_table general_text(general_text_table_buffer); - const u8 *text_entry; + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader general_text_reader(chunkList, numChunks, chunkSize); + bool start_pressed = false; - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); - text_entry = general_text.get_text_entry(GENERAL_intro_legal); + general_text_reader.init(general_text_table_buffer, sizeof(general_text_table_buffer)); tte_set_ink(INK_ROM_COLOR); - ptgb_write_textbox(text_entry, true, true, - GENERAL_INDEX, GENERAL_intro_legal, true); + general_text_reader.readFile(GENERAL_intro_legal, lineBuffer); + ptgb_write_textbox(lineBuffer, true, true, GENERAL_INDEX, GENERAL_intro_legal, true); show_gears_of_progress(); @@ -322,12 +326,11 @@ static void __attribute__((noinline)) show_intro() REG_BLDCNT = BLD_BUILD(BLD_BG3, BLD_BG0, 1); - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); - text_entry = general_text.get_text_entry(GENERAL_press_start); + general_text_reader.readFile(GENERAL_press_start, lineBuffer); tte_set_pos(0, 12 * 8); tte_set_ink(INK_DARK_GREY); - ptgb_write_simple(text_entry, true); + ptgb_write_simple(lineBuffer, true); int fade = 0; while (!start_pressed) @@ -347,11 +350,6 @@ int main(void) // Set colors based on current ROM set_background_pal(0, false, false); - /* First load message doesn't really make sense anymore, since you have to load the ROM first. - if (!get_tutorial_flag()) - { - first_load_message(); - }*/ show_intro(); // key_poll(); diff --git a/source/multiboot_upload.cpp b/source/multiboot_upload.cpp index 360c0cc..2d60fd2 100644 --- a/source/multiboot_upload.cpp +++ b/source/multiboot_upload.cpp @@ -5,18 +5,26 @@ #include "libraries/gba-link-connection/LinkCableMultiboot.hpp" #include "text_engine.h" #include "translated_text.h" -#include "text_data_table.h" +#include "FileContainerReader.h" +#include "text_tables.h" void multiboot_upload_screen() { - u8 general_text_table_buffer[2048]; - text_data_table general_text(general_text_table_buffer); + u8 decompression_buffer[2048]; + u8 lineBuffer[1024]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; + + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader general_text_reader(chunkList, numChunks, chunkSize); LinkCableMultiboot linkCableMultiboot; - general_text.decompress(get_compressed_text_table(GENERAL_INDEX)); + general_text_reader.init(decompression_buffer, sizeof(decompression_buffer)); // multiboot_show_textbox(); - ptgb_write_textbox(general_text.get_text_entry(GENERAL_send_multiboot_instructions), true, + general_text_reader.readFile(GENERAL_send_multiboot_instructions, lineBuffer); + ptgb_write_textbox(lineBuffer, true, false, GENERAL_INDEX, GENERAL_send_multiboot_instructions, false); // wait for key press @@ -33,7 +41,8 @@ void multiboot_upload_screen() // start upload // multiboot_show_textbox(); - ptgb_write_textbox(general_text.get_text_entry(GENERAL_send_multiboot_wait), true, + general_text_reader.readFile(GENERAL_send_multiboot_wait, lineBuffer); + ptgb_write_textbox(lineBuffer, true, false, GENERAL_INDEX, GENERAL_send_multiboot_wait, false); VBlankIntrWait(); @@ -49,16 +58,10 @@ void multiboot_upload_screen() }); // show result // multiboot_show_textbox(); - if (multibootResult == LinkCableMultiboot::Result::SUCCESS) - { - ptgb_write_textbox(general_text.get_text_entry(GENERAL_send_multiboot_success), true, - false, GENERAL_INDEX, GENERAL_send_multiboot_success, false); - } - else - { - ptgb_write_textbox(general_text.get_text_entry(GENERAL_send_multiboot_failure), true, - false, GENERAL_INDEX, GENERAL_send_multiboot_failure, false); - } + u32 fileIndex = (multibootResult == LinkCableMultiboot::Result::SUCCESS) ? GENERAL_send_multiboot_success : GENERAL_send_multiboot_failure; + general_text_reader.readFile(fileIndex, lineBuffer); + + ptgb_write_textbox(lineBuffer, true, false, GENERAL_INDEX, fileIndex, false); // wait for keypress again. do diff --git a/source/mystery_gift_builder.cpp b/source/mystery_gift_builder.cpp index 978500c..5da8726 100644 --- a/source/mystery_gift_builder.cpp +++ b/source/mystery_gift_builder.cpp @@ -5,7 +5,8 @@ #include "pokemon_data.h" #include "rom_data.h" #include "translated_text.h" -#include "text_data_table.h" +#include "FileContainerReader.h" +#include "text_tables.h" #define MG_SCRIPT false #define S30_SCRIPT true @@ -101,7 +102,15 @@ mystery_gift_script::mystery_gift_script(u8 *save_section_30_buffer) void mystery_gift_script::build_script(PokeBox *box) { decompressed_data_storage_union decompressed_store; - text_data_table decompressed_text_table(decompressed_store.text.buffer); + const u8 **rsefrlgTableChunkList; + u32 rsefrlgNumChunks; + u32 rsefrlgChunkSize; + + // in order to safely use getPointerToFileInDecompressionBuffer() in FileContainerReader + // we must ensure that the table fits inside a single chunk + get_text_table_chunks(RSEFRLG_INDEX, &rsefrlgTableChunkList, &rsefrlgNumChunks, &rsefrlgChunkSize); + FileContainerReader rsefrlgTableReader(rsefrlgTableChunkList, rsefrlgNumChunks, rsefrlgChunkSize); + ptgb::vector mg_variable_list; ptgb::vector sec30_variable_list; @@ -379,39 +388,35 @@ void mystery_gift_script::build_script(PokeBox *box) // this decompresses the ZX0 compressed text table into the buffer inside of the decompressed_store union // thereby reusing the stack (=IWRAM) memory used earlier for the PokemonTables instance we used above - decompressed_text_table.decompress(get_compressed_text_table(RSEFRLG_INDEX)); + rsefrlgTableReader.init(decompressed_store.text.buffer, sizeof(decompressed_store.text.buffer)); switch (curr_GBA_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)); + textGreet.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textGreet_rse)); + textMoveBox.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textMoveBox_rs)); + textWeHere.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textWeHere_r)); 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)); + textGreet.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textGreet_frlg)); + textMoveBox.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textMoveBox_frlg)); + textWeHere.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(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)); + textGreet.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textGreet_rse)); + textMoveBox.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textMoveBox_e)); + textWeHere.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(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)); + textReceived.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textRecieved)); + textYouMustBe.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(first_time ? RSEFRLG_dia_textYouMustBe_first : RSEFRLG_dia_textYouMustBe_second)); + textIAm.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(first_time ? RSEFRLG_dia_textIAm_first : RSEFRLG_dia_textIAm_second)); + textPCConvo.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textPCConvo)); // ȼDon’t worry ƲÀ,Ňyou won’t have to do a thing!"); + textPCThanks.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textPCThanks)); + textThank.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textThank)); + textPCFull.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(RSEFRLG_dia_textPCFull)); + textLookerFull.set_text(rsefrlgTableReader.getPointerToFileInDecompressionBuffer(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); diff --git a/source/pokedex.cpp b/source/pokedex.cpp index b047a90..deae721 100644 --- a/source/pokedex.cpp +++ b/source/pokedex.cpp @@ -10,7 +10,8 @@ #include "button_handler.h" #include "translated_text.h" #include "text_engine.h" -#include "text_data_table.h" +#include "FileContainerReader.h" +#include "text_tables.h" #include "TYPES_lz10_bin.h" Dex dex_array[DEX_MAX]; @@ -29,22 +30,17 @@ 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) +static void load_general_table_text_entries(u8 *decompression_buffer, u32 decompression_buffer_size, u8 *kanto_buffer, u8 *johto_buffer) { - const u8 *entry = data_table.get_text_entry(entry_index); - const u8 *entry_end = (const u8*)strchr((const char*)entry, 0xFF); + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; + get_text_table_chunks(GENERAL_INDEX, &chunkList, &numChunks, &chunkSize); + FileContainerReader text_reader(chunkList, numChunks, chunkSize); + text_reader.init(decompression_buffer, decompression_buffer_size); - // 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_text_table(GENERAL_INDEX)); - - load_text_entry_into_buffer(data_table, kanto_buffer, GENERAL_kanto_name); - load_text_entry_into_buffer(data_table, johto_buffer, GENERAL_johto_name); + text_reader.readFile(GENERAL_kanto_name, kanto_buffer); + text_reader.readFile(GENERAL_johto_name, johto_buffer); } void pokedex_init() @@ -91,21 +87,27 @@ void pokedex_init() #include "gen_3_charsets_lz10_bin.h" #include "libstd_replacements.h" -int pokedex_loop() +// attribute noinline is used to make sure it doesn't get inlined and permanently use IWRAM for the decompression_buffer +int __attribute__((noinline)) pokedex_loop() { u8 TYPES[POKEMON_ARRAY_SIZE][2]; u8 kanto_name[12]; u8 johto_name[12]; - u8 decompression_buffer[3072]; + u8 decompression_buffer[2048]; u16 charset[256]; + u8 name_buffer[16]; + const u8 **namesChunkList; + u32 namesNumChunks; + u32 namesChunkSize; LZ77UnCompWram(TYPES_lz10_bin, (u8*)TYPES); LZ77UnCompWram(gen_3_charsets_lz10_bin, (u8*)charset); - load_general_table_text_entries(decompression_buffer, kanto_name, johto_name); + load_general_table_text_entries(decompression_buffer, sizeof(decompression_buffer), kanto_name, johto_name); - text_data_table PKMN_NAMES(decompression_buffer); - PKMN_NAMES.decompress(get_compressed_text_table(PKMN_NAMES_INDEX)); + get_text_table_chunks(PKMN_NAMES_INDEX, &namesChunkList, &namesNumChunks, &namesChunkSize); + FileContainerReader PKMN_NAMES(namesChunkList, namesNumChunks, namesChunkSize); + PKMN_NAMES.init(decompression_buffer, sizeof(decompression_buffer)); pokedex_init(); pokedex_show(); @@ -229,7 +231,8 @@ int pokedex_loop() ptgb_write_simple(temp_string, true); tte_set_pos(dex_x_cord + (7 * 8), (i * 8 * 2) + 28); - ptgb_write_simple(is_caught(dex_shift + i + 1 + mythic_skip) ? PKMN_NAMES.get_text_entry(dex_shift + i + 1 + mythic_skip) : undiscovered_text, true); + PKMN_NAMES.readFile(dex_shift + i + 1 + mythic_skip, name_buffer); + ptgb_write_simple(is_caught(dex_shift + i + 1 + mythic_skip) ? name_buffer : 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' diff --git a/source/select_menu.cpp b/source/select_menu.cpp index 5e9202c..d4cad16 100644 --- a/source/select_menu.cpp +++ b/source/select_menu.cpp @@ -1,7 +1,6 @@ #include "select_menu.h" #include "sprite_data.h" #include "translated_text.h" -#include "text_data_table.h" #include "global_frame_controller.h" #define TEXT_HEIGHT 10 diff --git a/source/text_data_table.cpp b/source/text_data_table.cpp deleted file mode 100644 index cee4f6b..0000000 --- a/source/text_data_table.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "text_data_table.h" -#include -#include - -static uint16_t get_entry_offset_by_index(const uint8_t *text_table, uint16_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); -} - -static uint16_t get_num_text_entries(const uint8_t *index_buffer) -{ - return *((uint16_t*)index_buffer); -} - -static uint16_t get_entry_size_in_bytes(const uint8_t *index_buffer, uint32_t decompressed_size, uint16_t index) -{ - const uint16_t entry_offset = get_entry_offset_by_index(index_buffer, index); - const uint16_t num_text_entries = get_num_text_entries(index_buffer); - uint16_t entry_size_in_bytes; - - 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 - { - const uint16_t entry_byte_offset = get_entries_start_offset_of(num_text_entries) + entry_offset; - // we don't have a next entry. So we need to consider the end of the file - const uint16_t nDecompressed_size = static_cast(decompressed_size); - entry_size_in_bytes = nDecompressed_size - entry_byte_offset; - } - return entry_size_in_bytes; -} - -text_data_table::text_data_table(uint8_t *decompression_buffer) - : decompression_buffer_(decompression_buffer) - , decompressed_size_(0) -{ -} - -void text_data_table::decompress(const uint8_t *compressed_table) -{ - // byte 2-4 of the compressed data store the decompressed size - decompressed_size_ = compressed_table[1] | (compressed_table[2] << 8) | (compressed_table[3] << 16); - LZ77UnCompWram(compressed_table, decompression_buffer_); -} - -uint16_t text_data_table::get_number_of_text_entries() const -{ - return get_num_text_entries(decompression_buffer_); -} - -const uint8_t* text_data_table::get_text_entry(uint16_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; -} - -uint16_t text_data_table::get_text_entry_size(uint16_t index) const -{ - return get_entry_size_in_bytes(decompression_buffer_, decompressed_size_, index); -} diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 55ef2f0..ed90cda 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -9,9 +9,10 @@ #include "button_menu.h" #include "sprite_data.h" #include "fonts.h" -#include "text_data_table.h" #include "background_engine.h" #include "pokemon_data.h" +#include "FileContainerReader.h" +#include "text_tables.h" #define TEXT_CBB 0 #define TEXT_SBB 10 @@ -29,32 +30,18 @@ bool text_exit; // Doing it this way does mean that we need to completely restart decompression whenever we switch from dialog entry. // but given that it requires user input to do so, I believe it's worth it and not time-critical. // attribute noinline was used to make sure the compiler doesn't inline this code back into text_loop() -static __attribute__((noinline)) const u8 *read_dialogue_text_entry(uint8_t index, u8 *output_buffer) +static __attribute__((noinline)) const u8 *read_dialogue_text_entry(u32 index, u32 text_section, u8 *output_buffer) { - u8 text_decompression_buffer[6144]; - const u8 *text_entry; + u8 text_decompression_buffer[4096]; + const u8 **chunkList; + u32 numChunks; + u32 chunkSize; - text_data_table dialogue_table(text_decompression_buffer); + get_text_table_chunks(text_section, &chunkList, &numChunks, &chunkSize); + FileContainerReader dialogueTable(chunkList, numChunks, chunkSize); - dialogue_table.decompress(get_compressed_text_table(PTGB_INDEX)); - - text_entry = dialogue_table.get_text_entry(index); - memcpy(output_buffer, text_entry, dialogue_table.get_text_entry_size(index)); - - return output_buffer; -} - -static __attribute__((noinline)) const u8 *read_dialogue_text_entry(uint8_t index, uint8_t text_section, u8 *output_buffer) -{ - u8 text_decompression_buffer[6144]; - const u8 *text_entry; - - text_data_table dialogue_table(text_decompression_buffer); - - dialogue_table.decompress(get_compressed_text_table(text_section)); - - text_entry = dialogue_table.get_text_entry(index); - memcpy(output_buffer, text_entry, dialogue_table.get_text_entry_size(index)); + dialogueTable.init(text_decompression_buffer, sizeof(text_decompression_buffer)); + dialogueTable.readFile(index, output_buffer); return output_buffer; } @@ -117,7 +104,7 @@ int text_loop(int script) break; } - curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; + curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(PTGB_INDEX, curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; // tte_set_margins(LEFT, TOP, RIGHT, BOTTOM); if (script != SCRIPT_DEBUG) @@ -143,7 +130,7 @@ int text_loop(int script) break; } - curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; + curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(PTGB_INDEX, curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; char_index = 0; if (text_exit) diff --git a/source/text_tables.cpp b/source/text_tables.cpp new file mode 100644 index 0000000..072a586 --- /dev/null +++ b/source/text_tables.cpp @@ -0,0 +1,56 @@ +#include "text_tables.h" +#include "translated_text.h" +#include "CREDITS_chunk0_lz10_bin.h" +#include "GB_chunk0_lz10_bin.h" +#include "GENERAL_chunk0_lz10_bin.h" +#include "PKMN_NAMES_chunk0_lz10_bin.h" +#include "PKMN_NAMES_chunk1_lz10_bin.h" +#include "PTGB_chunk0_lz10_bin.h" +#include "PTGB_chunk1_lz10_bin.h" +#include "PTGB_chunk2_lz10_bin.h" +#include "RSEFRLG_chunk0_lz10_bin.h" + +static const u8 *CREDITS_chunk_list[] = {CREDITS_chunk0_lz10_bin}; +static const u8 *GB_chunk_list[] = {GB_chunk0_lz10_bin}; +static const u8 *GENERAL_chunk_list[] = {GENERAL_chunk0_lz10_bin}; +static const u8 *PKMN_NAMES_chunk_list[] = {PKMN_NAMES_chunk0_lz10_bin, PKMN_NAMES_chunk1_lz10_bin}; +static const u8 *PTGB_chunk_list[] = {PTGB_chunk0_lz10_bin, PTGB_chunk1_lz10_bin, PTGB_chunk2_lz10_bin}; +static const u8 *RSEFRLG_chunk_list[] = {RSEFRLG_chunk0_lz10_bin}; + +void get_text_table_chunks(u32 tableIndex, const u8 ***chunkList, u32 *numChunks, u32 *chunkSize) +{ + // every table except for RSEFRLG is 2048 bytes per chunk, so we can set that by default and only change it for RSEFRLG + *chunkSize = 2048; + switch(tableIndex) + { + case PTGB_INDEX: + *chunkList = PTGB_chunk_list; + *numChunks = sizeof(PTGB_chunk_list) / sizeof(PTGB_chunk_list[0]); + break; + case RSEFRLG_INDEX: + *chunkList = RSEFRLG_chunk_list; + *numChunks = sizeof(RSEFRLG_chunk_list) / sizeof(RSEFRLG_chunk_list[0]); + *chunkSize = 4096; + break; + case GB_INDEX: + *chunkList = GB_chunk_list; + *numChunks = sizeof(GB_chunk_list) / sizeof(GB_chunk_list[0]); + break; + case GENERAL_INDEX: + *chunkList = GENERAL_chunk_list; + *numChunks = sizeof(GENERAL_chunk_list) / sizeof(GENERAL_chunk_list[0]); + break; + case CREDITS_INDEX: + *chunkList = CREDITS_chunk_list; + *numChunks = sizeof(CREDITS_chunk_list) / sizeof(CREDITS_chunk_list[0]); + break; + case PKMN_NAMES_INDEX: + *chunkList = PKMN_NAMES_chunk_list; + *numChunks = sizeof(PKMN_NAMES_chunk_list) / sizeof(PKMN_NAMES_chunk_list[0]); + break; + default: + *chunkList = NULL; + *numChunks = 0; + break; + } +} \ No newline at end of file diff --git a/source/vertical_menu.cpp b/source/vertical_menu.cpp index eb1e7b2..82b645f 100644 --- a/source/vertical_menu.cpp +++ b/source/vertical_menu.cpp @@ -3,6 +3,7 @@ #include "global_frame_controller.h" #include "text_engine.h" #include "translated_text.h" +#include "text_tables.h" #define TILE_HEIGHT 8 #define TILE_WIDTH 8 @@ -209,13 +210,18 @@ MenuInputHandleState vertical_menu::handle_input() void vertical_menu::update_viewport() { uint8_t decompression_buffer[2048]; - text_data_table text_table(decompression_buffer); + const u8 **textTableChunkList; + u32 textTableNumChunks; + u32 textTableChunkSize; + + get_text_table_chunks(settings_.text_table_index, &textTableChunkList, &textTableNumChunks, &textTableChunkSize); + FileContainerReader text_table(textTableChunkList, textTableNumChunks, textTableChunkSize); clear_viewport(); if (settings_.text_table_index != INT32_MAX) { - text_table.decompress(get_compressed_text_table(settings_.text_table_index)); + text_table.init(decompression_buffer, sizeof(decompression_buffer)); } const unsigned num_visible_items = get_num_visible_items(settings_.height, settings_.margin_top, settings_.margin_bottom, settings_.item_height); @@ -301,10 +307,14 @@ const simple_item_widget_data &simple_item_renderer::get_data() const return data_; } -void simple_item_renderer::render_item(text_data_table &text_table, unsigned x, unsigned y, bool is_focused) +void simple_item_renderer::render_item(FileContainerReader &text_table, unsigned x, unsigned y, bool is_focused) { + // text_helper/main.py restricts each line to 1024 bytes. + u8 lineBuffer[1024]; + + text_table.readFile(data_.text.text_table_index, lineBuffer); tte_set_pos(x + data_.text.margin_left, y + data_.text.margin_top); - ptgb_write_simple(text_table.get_text_entry(data_.text.text_table_index), true); + ptgb_write_simple(lineBuffer, true); } MenuInputHandleState simple_item_renderer::handle_input() diff --git a/tools/make-file-container/src/main.cpp b/tools/make-file-container/src/main.cpp index cc3bf7a..29e1582 100644 --- a/tools/make-file-container/src/main.cpp +++ b/tools/make-file-container/src/main.cpp @@ -13,7 +13,7 @@ #define FILE_RECORD_NAME_LENGTH 16 #define LINE_BUFFER_SIZE 2048 #define PATH_BUFFER_SIZE 4096 -#define DEFAULT_CHUNK_SIZE 4096 +#define DEFAULT_CHUNK_SIZE 2048 typedef struct @@ -38,6 +38,37 @@ static void printUsage() printf(" -H, --header-out Write generated header to this folder\n"); } +/** + * @brief This function parses a directive. + * A directive starts with '@' and is used to set certain parameters for the container generation. + * Right now, we only support @chunkSize + */ +static void parseDirective(ContainerMetadata &meta, char *line) +{ + char * equals = strchr(line, '='); + if(equals == NULL) + { + fprintf(stderr, "Invalid directive (missing '='): %s\n", line); + return; + } + if(strncmp(line + 1, "chunkSize", 9) == 0) + { + char *endptr; + const long value = strtol(equals + 1, &endptr, 10); + if(*endptr != '\0' || value <= 0 || value > 0xFFFF) + { + fprintf(stderr, "Invalid chunk size value: %s\n", equals + 1); + + return; + } + meta.chunkSize = static_cast(value); + } + else + { + fprintf(stderr, "Unknown directive: %s\n", line); + } +} + /** * @brief This function parses a single line from the definition file, extracting the file path and optional alternative name. */ @@ -120,6 +151,12 @@ static bool parseDefinition(const char *defPath, ContainerMetadata &meta) continue; } + if(line[0] == '@') + { + parseDirective(meta, line); + continue; + } + if(!parseDefinitionLine(line, &filePath, &altName)) { fprintf(stderr, "Invalid entry at line %zu\n", line_no); diff --git a/tools/text_helper/main.py b/tools/text_helper/main.py index e6a56e6..7c3f405 100644 --- a/tools/text_helper/main.py +++ b/tools/text_helper/main.py @@ -24,6 +24,20 @@ class Languages(Enum): ChineseTR = 9 PortugueseBR = 10 +LanguageStringMap = { + "japanese": Languages.Japanese, + "english": Languages.English, + "french": Languages.French, + "german": Languages.German, + "italian": Languages.Italian, + "spanishEU": Languages.SpanishEU, + "spanishLA": Languages.SpanishLA, + "korean": Languages.Korean, + "chineseSI": Languages.ChineseSI, + "chineseTR": Languages.ChineseTR, + "portugueseBR": Languages.PortugueseBR, +} + class Font: def __init__(self, fileName, numColors, numChars, numCharsX, numCharsY, cellWidth, cellHeight, charWidth, charHeight): self.fileName = fileName @@ -840,50 +854,35 @@ def convert_item(ogDict, lang, context=None): ogDict["bytes"] = encode_formatted_text(normalized_text, arr, lang, entry_id, context) return ogDict -def write_text_bin_file(filename, dictionary, lang, section, context=None): - MAX_BIN_SIZES = { - "PTGB": 6144, - "RSEFRLG": 3444, - "GB": 9999, - "GENERAL": 2048, - "CREDITS": 2048, - "PKMN_NAMES": 3072, - } +# in order to safely use getPointerToFileInDecompressionBuffer() in FileContainerReader +# we must ensure that a table that is used this way fits inside a single chunk +def get_file_container_chunk_size(section): + if section in ("RSEFRLG"): + return 4096 + else: + return 2048 - Path(filename).parent.mkdir(parents=True, exist_ok=True) - 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 +def write_text_file_container(filename, dictionary, lang, section, context=None): + buildpath = str(BUILD_DIR) + "/" + Path(buildpath).mkdir(parents=True, exist_ok=True) + defPath = buildpath + filename + ".containerdef" + with open(defPath, 'wb') as defFile: + defLine = "@chunkSize=" + str(get_file_container_chunk_size(section)) + "\n" + defFile.write(defLine.encode("utf-8")) + for key, line in dictionary.items(): - dictionary[key] = convert_item(line, lang, context) - # 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.seek(0, os.SEEK_END) - if binFile.tell() > MAX_BIN_SIZES[section]: - log_warning_error(lang, "Error", f'Section {section} exceeds the max binary file size by {binFile.tell() - MAX_BIN_SIZES[section]} bytes!', context=context) - binFile.close() + print(f"Processing {section} entry '{key}' for language {lang.name}...") + with open(buildpath + str(key), 'wb') as lineFile: + dictionary[key] = convert_item(line, lang, context) + linedata = bytes.fromhex(dictionary[key]['bytes']) + lineFile.write(linedata) + lineFile.close() + + defLine = buildpath + str(key) + "\n" + defFile.write(defLine.encode("utf-8")) + defFile.close() + def write_enum_to_header_file(hFile, prefix, dictionary): num = 0 @@ -1098,7 +1097,8 @@ def generate_header_file(): sectionEnds = [] index = 0 - lang = Languages.English # This doesn't matter, it just needs to be there. They're all the same + # The lang doesn't matter. They're all the same in this context. + lang = Languages.English for section in textSections: num = write_enum_to_header_file(hFile, section + "_", mainDict[lang.name][section]) hFile.write("#define " + section + "_INDEX " + str(index)) @@ -1136,7 +1136,6 @@ def generate_header_file(): hFile.write(f"\t{{{', '.join(values)}}},\n") hFile.write("};\n\n") - hFile.write("const u8* get_compressed_text_table(int table_index);\n") hFile.write("u8 get_text_box_type(int table_index, int text_index);\n") hFile.write("extern const u8* const text_box_type_tables[NUM_TEXT_SECTIONS];\n") @@ -1144,21 +1143,16 @@ def generate_header_file(): hFile.write("\n#endif") hFile.close() -def generate_text_tables(): +def generate_text_tables(lang): print("\tGenerating text tables") - for lang in Languages: - for section in textSections: - table_file = os.curdir + '/to_compress/' + section + '_' + lang.name.lower() + '.bin' - write_text_bin_file(table_file, mainDict[lang.name][section], lang, section, build_context) + for section in textSections: + table_name = section + write_text_file_container(table_name, mainDict[lang.name][section], lang, section, build_context) def generate_cpp_file(): print("\tGenerating cpp file") with open(TRANSLATED_CPP_PATH, 'w') as cppFile: cppFile.write("// THIS FILE HAS BEEN GENERATED BY text_helper/main.py !\n#include \"translated_text.h\"\n#include \"dbg/debug_mode.h\"\n") - # generate includes for each language - for lang in Languages: - for section in textSections: - cppFile.write("#include \"" + section.upper() + "_" + lang.name.lower() + "_lz10_bin.h\"\n") cppFile.write("\n") for section in textSections: @@ -1177,22 +1171,6 @@ def generate_cpp_file(): cppFile.write(f"\n\t{section_var}_box_types,") cppFile.write("\n};\n") - cppFile.write("\nconst u8* get_compressed_text_table(int table_index)\n") - - for i, lang in enumerate(Languages): - cppFile.write(f"\n#{'el' if i > 0 else ''}if PTGB_BUILD_LANGUAGE == {lang.value + 1}\n") - cppFile.write("{\n") - cppFile.write("\tswitch (table_index)\n\t{\n") - for section in textSections: - cppFile.write("\tcase (" + section + "_INDEX):\n") - if(section == "PTGB"): - cppFile.write("\tdefault:\n") - cppFile.write("\t\treturn " + section + "_" + lang.name.lower() + "_lz10_bin;\n") - cppFile.write("\t\tbreak;\n") - cppFile.write("\t}\n") - cppFile.write("}\n") - cppFile.write(f"#else\n#error \"Unsupported PTGB_BUILD_LANGUAGE\"\n#endif") - cppFile.write("\n\nu8 get_text_box_type(int table_index, int text_index)\n") cppFile.write("{\n") cppFile.write("\tif (text_index < 0)\n") @@ -1210,18 +1188,17 @@ def generate_cpp_file(): cppFile.write("\t}\n") cppFile.write("}\n") -def output_json_file(): +def output_json_file(lang): print("\tOutputting json file") - for lang in Languages: - for section in textSections: - for item in mainDict[lang.name][section]: - arr = get_language_config(lang).char_array["array"] - mainDict[lang.name][section][item]["text"] = render_debug_text( - mainDict[lang.name][section][item]["bytes"], arr - ) + for section in textSections: + for item in mainDict[lang.name][section]: + arr = get_language_config(lang).char_array["array"] + mainDict[lang.name][section][item]["text"] = render_debug_text( + mainDict[lang.name][section][item]["bytes"], arr + ) with open(OUTPUT_JSON_PATH, 'w', encoding='utf-8') as jsonFile: - jsonFile.write(json.dumps(mainDict, ensure_ascii=False, indent=2)) + jsonFile.write(json.dumps(mainDict[lang.name], ensure_ascii=False, indent=2)) def are_generated_files_stale(source_files, generated_files): source_paths = [Path(path) for path in source_files] @@ -1238,15 +1215,15 @@ def are_generated_files_stale(source_files, generated_files): oldest_generated_mtime = min(path.stat().st_mtime for path in generated_paths) return newest_source_mtime > oldest_generated_mtime -def update_generated_files(target_name, source_files, generated_files, generate_function): +def update_generated_files(target_name, source_files, generated_files, generate_function, lang, force_generate): if debugpy.is_client_connected(): print(f"\tDebugger connected, rebuilding {target_name}!") - generate_function() + generate_function(lang) return - if are_generated_files_stale(source_files, generated_files): + if force_generate or are_generated_files_stale(source_files, generated_files): print(f"\t{target_name} outputs are outdated or missing. Rebuilding...") - generate_function() + generate_function(lang) return print(f"\t{target_name} outputs are up to date. Skipping rebuild.") @@ -1373,7 +1350,7 @@ def get_font_source_files(): def get_font_generated_files(): return [FONTS_H_PATH] -def generate_font_files(): +def generate_font_files(lang): print("\tGenerating font tables:") generate_tables() build_h() @@ -1384,6 +1361,8 @@ def update_font_files(): source_files=get_font_source_files(), generated_files=get_font_generated_files(), generate_function=generate_font_files, + lang = Languages.English, + force_generate=False ) def get_text_source_files(): @@ -1396,22 +1375,27 @@ def get_text_generated_files(): generated_files.append(Path(os.curdir) / "to_compress" / f"{section}_{lang.name.lower()}.bin") return generated_files -def generate_text_files(): +def generate_text_files(lang): generate_header_file() - generate_text_tables() + generate_text_tables(lang) generate_cpp_file() - output_json_file() + output_json_file(lang) -def update_text_files(): +def update_text_files(lang): + # force_generate is set to true because we're only generating the BUILD_LANG. + # therefore if we generate another lang, we need to update it again too. + # So we might as well always update it. update_generated_files( target_name="Text", source_files=get_text_source_files(), generated_files=get_text_generated_files(), generate_function=generate_text_files, + lang=lang, + force_generate=True ) def main(): - _, _, build_xlsx_mode = parse_build_args(sys.argv) + lang_name, _, build_xlsx_mode = parse_build_args(sys.argv) print("Running text_helper:") update_font_files() update_xlsx_file(build_xlsx_mode) @@ -1419,7 +1403,7 @@ def main(): print("text_helper finished!\n") return transfer_xlsx_to_dict() - update_text_files() + update_text_files(LanguageStringMap[lang_name]) print("text_helper finished!\n") if __name__ == "__main__":