mirror of
https://github.com/GearsProgress/Poke_Transporter_GB.git
synced 2026-03-21 17:34:42 -05:00
There was a crash happening with ptgb::vector when you'd press A on the CONFIRM button of the box screen. It only occurred on actual gba hardware and was a real heisenbug: as soon as you'd add code to display logs on screen, the problem would disappear. So it was very difficult to figure this one out. We're not even entirely sure why, but it looks like the malloc/realloc/free use in ptgb::vector would cause issues. Maybe it was alignment, but after messing with the code we also saw a warning appear in the terminal telling us that realloc wouldn't properly deal with non-POD types. It complained about this very thing while referring to the add_track() function, which stores ptgb::vectors inside another ptgb::vector. We also didn't have a custom copy constructor yet to actually copy the buffer instead of its pointer. All of these could potentially have led to the crash. But debugging during the link cable flow was difficult, so we were never able to confirm it in a debugger, log or dump. Because I suspected the high IWRAM consumption (especially now with ZX0 decompression) for a while, I also did an optimization in mystery_gift_builder to pass global_memory_buffer as its section_30_data buffer instead. This reduces IWRAM consumption by 4 KB. There was another problem I discovered during my crash hunt: the out_array (now payload_buffer) was allocated as a 672 byte array, but the payloads were actually 707 bytes. Therefore writing this to the buffer caused a buffer overflow, thereby corrupting the global variables appearing after it in IWRAM. It turned out eventually that none of these variables were really critical, but it could explain some minor bugs GearsProgress has seen. I also did a few performance optimizations: - At various stages in the code, for loops were used to copy data from one buffer into another byte-by-byte. This was far from optimal because the gba cpu can load/copy 4 bytes at a time if you ask it to. So I replaced those with memcpy(), which is a hand-optimized assembly function to copy data using this principle. - generate_payload was being called twice: once at start_link and once at continue_link, giving the exact same result, even though it was already being stored in a global buffer allocated in IWRAM. This was also a fairly heavy function. So I optimized the code to only initialize it once in the script chain and then just retrieve the buffer. - generate_payload was constructing the eventual payload twice even within the same call. That's because it first merged z80_rng_seed, z80_payload and z80_patchlist into a full_data ptgb::vector, after which it then copied the data again to out_array (now called payload_buffer). I eliminated the full_data vector now.
308 lines
9.1 KiB
C++
308 lines
9.1 KiB
C++
#include <tonc.h>
|
|
#include <cstring>
|
|
|
|
#include "text_engine.h"
|
|
#include "global_frame_controller.h"
|
|
#include "pkmn_font.h"
|
|
#include "script_array.h"
|
|
#include "debug_mode.h"
|
|
#include "button_menu.h"
|
|
#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
|
|
|
|
script_obj curr_line;
|
|
uint char_index;
|
|
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
|
|
// tte_init_se(3, BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB) | BG_PRIO(0), 0, CLR_WHITE, 14, &japanese_smallFont, NULL);
|
|
|
|
tte_init_chr4c(3, // BG 0
|
|
BG_CBB(TEXT_CBB) | BG_SBB(TEXT_SBB), // Charblock 0; screenblock 31
|
|
0xF000, // Screen-entry offset
|
|
bytes2word( // Color attributes:
|
|
15, // Text color
|
|
0, // Shadow color
|
|
0, // Paper
|
|
0), // Special
|
|
CLR_WHITE, // White text
|
|
BUILD_FONT, // Custom font
|
|
NULL // Use default chr4 renderer
|
|
);
|
|
tte_init_con();
|
|
|
|
// tte_set_margins(LEFT, TOP, RIGHT, BOTTOM);
|
|
// tte_set_pos(LEFT, TOP);
|
|
|
|
pal_bg_bank[15][INK_WHITE] = CLR_WHITE; // White
|
|
pal_bg_bank[15][INK_DARK_GREY] = 0b0000110001100010; // Dark Grey
|
|
// 14 will be changed to game color
|
|
|
|
// Set default variables
|
|
char_index = 0;
|
|
line_char_index = 0;
|
|
text_exit = false;
|
|
}
|
|
|
|
int text_loop(int script)
|
|
{
|
|
// 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:
|
|
curr_line = transfer_script_params[T_SCRIPT_START];
|
|
break;
|
|
|
|
case BTN_EVENTS:
|
|
curr_line = event_script_params[E_SCRIPT_START];
|
|
break;
|
|
}
|
|
|
|
curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL;
|
|
|
|
REG_BG1CNT = (REG_BG1CNT && !BG_PRIO_MASK) | BG_PRIO(2); // Show Fennel
|
|
show_text_box();
|
|
// tte_set_margins(LEFT, TOP, RIGHT, BOTTOM);
|
|
while (true) // This loops through all the connected script objects
|
|
{
|
|
if (curr_text != NULL && curr_text[char_index] != 0xFF && curr_text[char_index] != 0xFB)
|
|
{
|
|
tte_set_pos(LEFT, TOP);
|
|
tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM);
|
|
ptgb_write(curr_text, char_index);
|
|
}
|
|
|
|
wait_for_user_to_continue(false);
|
|
|
|
line_char_index = 0;
|
|
switch (script)
|
|
{
|
|
case BTN_TRANSFER:
|
|
curr_line = transfer_script_params[text_next_obj_id(curr_line)];
|
|
break;
|
|
case BTN_EVENTS:
|
|
curr_line = event_script_params[text_next_obj_id(curr_line)];
|
|
break;
|
|
}
|
|
|
|
curr_text = (curr_line.has_text()) ? read_dialogue_text_entry(curr_line.get_text_entry_index(), diag_entry_text_buffer) : NULL;
|
|
char_index = 0;
|
|
|
|
if (text_exit)
|
|
{
|
|
hide_text_box();
|
|
tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM);
|
|
text_exit = false;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int text_next_obj_id(script_obj current_line)
|
|
{
|
|
if (current_line.get_cond_id() == 0)
|
|
{
|
|
return current_line.get_true_index();
|
|
}
|
|
else
|
|
{
|
|
const bool ret = run_conditional(current_line.get_cond_id());
|
|
VBlankIntrWait(); // this is needed to handle interrupts
|
|
if (ret)
|
|
{
|
|
return current_line.get_true_index();
|
|
}
|
|
return current_line.get_false_index();
|
|
}
|
|
}
|
|
|
|
void show_text_box()
|
|
{
|
|
REG_BG2CNT = (REG_BG2CNT & ~BG_PRIO_MASK) | BG_PRIO(1);
|
|
}
|
|
|
|
void hide_text_box()
|
|
{
|
|
REG_BG2CNT = (REG_BG2CNT & ~BG_PRIO_MASK) | BG_PRIO(3);
|
|
}
|
|
|
|
void set_text_exit()
|
|
{
|
|
text_exit = true;
|
|
key_poll(); // This removes the "A Hit" when exiting the text
|
|
}
|
|
|
|
// Implement a version that just writes the whole string
|
|
int ptgb_write(const byte *text, bool instant)
|
|
{
|
|
return ptgb_write(text, instant, 9999); // This is kinda silly but it'll work.
|
|
}
|
|
|
|
// Re-implementing TTE's "tte_write" to use the gen 3 character encoding chart
|
|
int ptgb_write(const byte *text, bool instant, int length)
|
|
{
|
|
if (text == NULL)
|
|
return 0;
|
|
|
|
uint ch, gid;
|
|
char *str = (char *)text;
|
|
TTC *tc = tte_get_context();
|
|
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)
|
|
{
|
|
str++;
|
|
switch (ch)
|
|
{
|
|
case 0xFB:
|
|
if (DISPLAY_CONTROL_CHAR)
|
|
{
|
|
tc->drawgProc(0xB9);
|
|
}
|
|
wait_for_user_to_continue(true);
|
|
break;
|
|
case 0xFE:
|
|
if (DISPLAY_CONTROL_CHAR)
|
|
{
|
|
tc->drawgProc(0xEF);
|
|
}
|
|
tc->cursorY += tc->font->charH;
|
|
tc->cursorX = tc->marginLeft;
|
|
break;
|
|
default:
|
|
|
|
// Get glyph index and call renderer
|
|
font = tc->font;
|
|
gid = ch - font->charOffset;
|
|
if (tc->charLut)
|
|
gid = tc->charLut[gid];
|
|
|
|
// Character wrap
|
|
int charW = font->widths ? font->widths[gid] : font->charW;
|
|
|
|
// We don't want this tbh- all of the newlines should deal with moving to the next line
|
|
/* if (tc->cursorX + charW > tc->marginRight)
|
|
{
|
|
tc->cursorY += 10; // font->charH;
|
|
tc->cursorX = tc->marginLeft;
|
|
} */
|
|
|
|
// Draw and update position
|
|
tc->drawgProc(gid);
|
|
tc->cursorX += charW;
|
|
}
|
|
num += 1;
|
|
}
|
|
if (get_curr_flex_background() == BG_FENNEL && !instant)
|
|
{
|
|
fennel_speak(((num / 4) % 4) + 1);
|
|
}
|
|
if (!instant)
|
|
{
|
|
global_next_frame();
|
|
}
|
|
}
|
|
|
|
// Return characters used (PONDER: is this really the right thing?)
|
|
return 0; // str - text;
|
|
}
|
|
// This is mostly used for debug stuff, I shouldn't rely it on it much.
|
|
int ptgb_write_debug(const u16* charset, const char *text, bool instant)
|
|
{
|
|
byte temp_holding[256];
|
|
int i;
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
if (text[i] == '\0')
|
|
{
|
|
temp_holding[i] = 0xFF;
|
|
i = 256;
|
|
}
|
|
else if (text[i] == '\n')
|
|
{
|
|
temp_holding[i] = 0xFE;
|
|
}
|
|
else
|
|
{
|
|
temp_holding[i] = get_char_from_charset(charset, text[i]);
|
|
}
|
|
}
|
|
return ptgb_write(temp_holding, instant);
|
|
}
|
|
|
|
// Adding this to avoid compiler issues temporarilly
|
|
int ptgb_write(const char *text)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void wait_for_user_to_continue(bool clear_text)
|
|
{
|
|
if (get_curr_flex_background() == BG_FENNEL)
|
|
{
|
|
if (get_missingno_enabled())
|
|
{
|
|
fennel_speak(0);
|
|
fennel_blink(4);
|
|
}
|
|
else
|
|
{
|
|
fennel_speak(0);
|
|
}
|
|
}
|
|
while (!(key_hit(KEY_A) || key_hit(KEY_B) || curr_text == NULL))
|
|
{
|
|
global_next_frame();
|
|
}
|
|
if (clear_text)
|
|
{
|
|
tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM);
|
|
tte_set_pos(LEFT, TOP);
|
|
}
|
|
} |