diff --git a/include/text_data_table.h b/include/text_data_table.h index f93fe1f..92fd6ee 100644 --- a/include/text_data_table.h +++ b/include/text_data_table.h @@ -32,7 +32,12 @@ public: /** * This function returns a pointer to a text entry in the decompression_buffer */ - const uint8_t* get_text_entry(uint8_t index) const; + 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_; }; @@ -66,7 +71,12 @@ public: /** * This function returns a pointer to a text entry in the decompression_buffer */ - const uint8_t* get_text_entry(uint8_t index); + const uint8_t* get_text_entry(uint16_t index); + + /** + * 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* get_window_start() const; uint8_t* get_window_end() const; diff --git a/source/text_data_table.cpp b/source/text_data_table.cpp index 4c13576..01e3559 100644 --- a/source/text_data_table.cpp +++ b/source/text_data_table.cpp @@ -2,7 +2,7 @@ #include "zx0_decompressor.h" #include -static uint16_t get_entry_offset_by_index(const uint8_t *text_table, uint8_t index) +static uint16_t get_entry_offset_by_index(const uint8_t *text_table, uint16_t index) { return *((uint16_t*)(text_table + 2 + index * 2)); } @@ -13,6 +13,32 @@ static uint16_t get_entries_start_offset_of(uint8_t num_text_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, 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 decompressed_size = static_cast(zx0_decompressor_get_decompressed_size()); + entry_size_in_bytes = decompressed_size - entry_byte_offset; + } + return entry_size_in_bytes; +} + text_data_table::text_data_table(uint8_t *decompression_buffer) : decompression_buffer_(decompression_buffer) { @@ -26,15 +52,20 @@ void text_data_table::decompress(const uint8_t *compressed_table) uint16_t text_data_table::get_number_of_text_entries() const { - return *((uint16_t*)decompression_buffer_); + return get_num_text_entries(decompression_buffer_); } -const uint8_t* text_data_table::get_text_entry(uint8_t index) const +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_, index); +} + 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) @@ -64,34 +95,20 @@ 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* streamed_text_data_table::get_text_entry(uint16_t index) { const uint16_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 entry_byte_offset = get_entries_start_offset_of(num_text_entries) + get_entry_offset_by_index(index_buffer_, index); + const uint16_t entry_size_in_bytes = get_text_entry_size(index); 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; + const uint16_t window_start_offset = bytes_decompressed_ - get_current_zx0_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 uint16_t decompressed_size = static_cast(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_) @@ -133,6 +150,11 @@ const uint8_t* streamed_text_data_table::get_text_entry(uint8_t index) return decompression_buffer_ + ZX0_DEFAULT_WINDOW_SIZE + last_chunk_size_ - entry_size_in_bytes; } +uint16_t streamed_text_data_table::get_text_entry_size(uint16_t index) const +{ + return get_entry_size_in_bytes(index_buffer_, index); +} + uint8_t* streamed_text_data_table::get_window_start() const { uint16_t without_last_chunk_size = (bytes_decompressed_ - last_chunk_size_); diff --git a/source/text_engine.cpp b/source/text_engine.cpp index a01d129..5cd3076 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -22,6 +22,29 @@ uint line_char_index; const byte *curr_text; bool text_exit; +// This function was separated from text_loop to reduce the scope of the text_decompression_buffer. +// if we didn't do this, the decompression_buffer would be kept on the stack (=IWRAM) for the entire duration of the +// text_loop() call. This is particularly bad because the whole mystery_gift_builder sequence is being triggered from within +// text_loop(). And there we need all the IWRAM we can muster. +// 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) +{ + u8 text_decompression_buffer[3072]; + u8 index_buffer[100]; + const u8 *text_entry; + + streamed_text_data_table dialogue_table(text_decompression_buffer, sizeof(text_decompression_buffer), index_buffer); + + dialogue_table.decompress(get_compressed_PTGB_table()); + + text_entry = dialogue_table.get_text_entry(index); + memcpy(output_buffer, text_entry, dialogue_table.get_text_entry_size(index)); + + return output_buffer; +} + void init_text_engine() { // Load the TTE @@ -56,11 +79,9 @@ 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()); + // we have restricted the dialog entries to 1024 bytes in the text_helper main.py + // so we shouldn't run into problems when we only use 1 KB to contain a text entry. + u8 diag_entry_text_buffer[1024]; switch (script) { case BTN_TRANSFER: @@ -72,7 +93,7 @@ int text_loop(int script) break; } - curr_text = (curr_line.has_text()) ? dialogue_table.get_text_entry(curr_line.get_text_entry_index()) : NULL; + curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; REG_BG1CNT = (REG_BG1CNT && !BG_PRIO_MASK) | BG_PRIO(2); // Show Fennel show_text_box(); @@ -99,7 +120,7 @@ int text_loop(int script) break; } - curr_text = (curr_line.has_text()) ? dialogue_table.get_text_entry(curr_line.get_text_entry_index()) : NULL; + curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL; char_index = 0; if (text_exit) diff --git a/text_helper/main.py b/text_helper/main.py index f91826e..723d0ed 100755 --- a/text_helper/main.py +++ b/text_helper/main.py @@ -7,6 +7,7 @@ import requests from collections import defaultdict import copy import math +import sys update = True @@ -389,6 +390,11 @@ def write_text_bin_file(filename, dictionary): linedata = bytes.fromhex(dictionary[key]['bytes']) bindata.extend(linedata) current_offset += len(linedata) + + if len(linedata) > 1024: + print(f"Error: entry '{key}' numBytes exceeds 1024 (got {len(linedata)})", file=sys.stderr) + sys.exit(1) + num += 1 # Write the index and bindata to the file