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.
345 lines
8.8 KiB
C++
345 lines
8.8 KiB
C++
#include <tonc.h>
|
|
#include <cmath>
|
|
#include "libstd_replacements.h"
|
|
#include "global_frame_controller.h"
|
|
#include "random.h"
|
|
#include "background_engine.h"
|
|
#include "text_engine.h"
|
|
#include "sprite_data.h"
|
|
#include "string.h"
|
|
#include "text_data_table.h"
|
|
#include "translated_text.h"
|
|
|
|
int global_frame_count = 0;
|
|
bool rand_enabled = true;
|
|
int cable_frame = 0;
|
|
int curr_link_animation_state = 0;
|
|
int fennel_blink_timer = 0;
|
|
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();
|
|
rand_next_frame();
|
|
// tte_set_pos(0, 0);
|
|
// tte_write(ptgb::to_string(get_rand_u32()));
|
|
background_frame(global_frame_count);
|
|
determine_fennel_blink();
|
|
if (missingno_enabled)
|
|
{
|
|
set_background_pal(0xFF, false, false);
|
|
}
|
|
oam_copy(oam_mem, obj_buffer, num_sprites);
|
|
// mmFrame(); //Music
|
|
if (global_frame_count % 60 == 0)
|
|
{
|
|
set_menu_sprite_pal(0);
|
|
if (!curr_rom.verify_rom())
|
|
{
|
|
REG_BG0CNT = (REG_BG0CNT & ~BG_PRIO_MASK) | BG_PRIO(2);
|
|
REG_BG2CNT = (REG_BG2CNT & ~BG_PRIO_MASK) | BG_PRIO(1);
|
|
tte_set_pos(40, 24);
|
|
create_textbox(4, 1, 160, 80, true);
|
|
obj_hide_multi(ptgb_logo_l, num_sprites);
|
|
|
|
show_pulled_cart_error();
|
|
|
|
oam_copy(oam_mem, obj_buffer, num_sprites);
|
|
while (true)
|
|
{
|
|
};
|
|
}
|
|
}
|
|
else if (global_frame_count % 60 == 30)
|
|
{
|
|
set_menu_sprite_pal(1);
|
|
}
|
|
|
|
if (global_frame_count % (40 / curr_link_animation_state) == 0)
|
|
{
|
|
cable_frame = (cable_frame + 1) % 12;
|
|
if (curr_link_animation_state > 0)
|
|
{
|
|
run_link_cable_animation(cable_frame);
|
|
}
|
|
}
|
|
global_frame_count++;
|
|
VBlankIntrWait();
|
|
};
|
|
|
|
int get_frame_count()
|
|
{
|
|
return global_frame_count;
|
|
}
|
|
|
|
void enable_auto_random()
|
|
{
|
|
rand_enabled = true;
|
|
}
|
|
|
|
void disable_auto_random()
|
|
{
|
|
rand_enabled = false;
|
|
}
|
|
|
|
const unsigned short MENU_PALS[5][4] = {
|
|
{RGB15(31, 31, 31), RGB15(31, 19, 10), RGB15(31, 7, 01), RGB15(00, 00, 00)}, // Red
|
|
{RGB15(31, 31, 31), RGB15(31, 19, 10), RGB15(10, 9, 31), RGB15(00, 00, 00)}, // Blue
|
|
{RGB15(31, 31, 31), RGB15(31, 19, 10), RGB15(07, 23, 03), RGB15(00, 00, 00)}, // Green
|
|
{RGB15(31, 31, 31), RGB15(31, 19, 10), RGB15(15, 10, 03), RGB15(00, 00, 00)}, // Brown
|
|
{RGB15(31, 31, 31), RGB15(31, 19, 10), RGB15(29, 5, 13), RGB15(00, 00, 00)}, // Pink
|
|
};
|
|
|
|
void set_menu_sprite_pal(int frame)
|
|
{
|
|
return;
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
unsigned short curr_pal[16] = {
|
|
// frame: 1 | 2
|
|
MENU_PALS[i][(!frame ? 0 : 0)],
|
|
MENU_PALS[i][(!frame ? 0 : 1)],
|
|
MENU_PALS[i][(!frame ? 0 : 2)],
|
|
MENU_PALS[i][(!frame ? 0 : 3)],
|
|
MENU_PALS[i][(!frame ? 1 : 0)],
|
|
MENU_PALS[i][(!frame ? 1 : 1)],
|
|
MENU_PALS[i][(!frame ? 1 : 2)],
|
|
MENU_PALS[i][(!frame ? 1 : 3)],
|
|
MENU_PALS[i][(!frame ? 2 : 0)],
|
|
MENU_PALS[i][(!frame ? 2 : 1)],
|
|
MENU_PALS[i][(!frame ? 2 : 2)],
|
|
MENU_PALS[i][(!frame ? 2 : 3)],
|
|
MENU_PALS[i][(!frame ? 3 : 0)],
|
|
MENU_PALS[i][(!frame ? 3 : 1)],
|
|
MENU_PALS[i][(!frame ? 3 : 2)],
|
|
MENU_PALS[i][(!frame ? 3 : 3)],
|
|
};
|
|
memcpy(pal_obj_mem + ((MENU_PAL_START + i) * 16), curr_pal, 32);
|
|
}
|
|
}
|
|
|
|
static const int path[12][2] = {{19, 18}, {19, 19}, {18, 19}, {17, 19}, {16, 19}, {15, 19}, {14, 19}, {13, 19}, {12, 19}, {11, 19}, {10, 19}, {24, 24}};
|
|
|
|
void run_link_cable_animation(int frame)
|
|
{
|
|
switch (curr_link_animation_state)
|
|
{
|
|
case STATE_CONNECTION:
|
|
frame %= 4;
|
|
obj_hide_multi(link_frame1, 4);
|
|
obj_unhide_multi(link_frame1, 0, frame);
|
|
break;
|
|
case STATE_TRANSFER:
|
|
obj_set_pos(link_blob1, path[frame][0] * 8, path[frame][1] * 8);
|
|
obj_set_pos(link_blob2, path[frame][0] * 8, path[frame][1] * 8);
|
|
obj_set_pos(link_blob3, path[frame][0] * 8, path[frame][1] * 8);
|
|
|
|
obj_hide_multi(link_blob1, 3);
|
|
switch (frame % 3)
|
|
{
|
|
case 0:
|
|
obj_unhide(link_blob1, 0);
|
|
break;
|
|
case 1:
|
|
obj_unhide(link_blob2, 0);
|
|
break;
|
|
case 2:
|
|
obj_unhide(link_blob3, 0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void link_animation_state(int state)
|
|
{
|
|
cable_frame = 0;
|
|
switch (state)
|
|
{
|
|
case STATE_CONNECTION:
|
|
obj_unhide(gba_cart, 0);
|
|
obj_set_pos(gba_cart, 17 * 8, 14 * 8);
|
|
|
|
obj_unhide(cart_shell, 0);
|
|
obj_set_pos(cart_shell, (8 * 8), (11 * 8) + 11);
|
|
obj_unhide(cart_label, 0);
|
|
obj_set_pos(cart_label, (8 * 8) + 8, (11 * 8) + 11 + 13);
|
|
|
|
obj_set_pos(link_frame1, 17 * 8, 17 * 8);
|
|
obj_set_pos(link_frame2, 13 * 8, 19 * 8);
|
|
obj_set_pos(link_frame3, 9 * 8, 18 * 8);
|
|
break;
|
|
case STATE_TRANSFER:
|
|
obj_unhide_multi(link_blob1, 0, 3);
|
|
case STATE_NO_ANIM:
|
|
obj_unhide(gba_cart, 0);
|
|
obj_set_pos(gba_cart, 17 * 8, 14 * 8);
|
|
|
|
obj_unhide(cart_shell, 0);
|
|
obj_set_pos(cart_shell, (8 * 8), (11 * 8) + 11);
|
|
obj_unhide(cart_label, 0);
|
|
obj_set_pos(cart_label, (8 * 8) + 8, (11 * 8) + 11 + 13);
|
|
|
|
obj_unhide(link_frame1, 0);
|
|
obj_set_pos(link_frame1, 17 * 8, 17 * 8);
|
|
obj_unhide(link_frame2, 0);
|
|
obj_set_pos(link_frame2, 13 * 8, 19 * 8);
|
|
obj_unhide(link_frame3, 0);
|
|
obj_set_pos(link_frame3, 9 * 8, 18 * 8);
|
|
break;
|
|
|
|
default:
|
|
obj_hide_multi(link_frame1, 3);
|
|
obj_hide_multi(link_blob1, 3);
|
|
obj_hide(gba_cart);
|
|
obj_hide(cart_shell);
|
|
obj_hide(cart_label);
|
|
break;
|
|
}
|
|
curr_link_animation_state = state;
|
|
}
|
|
|
|
void determine_fennel_blink()
|
|
{
|
|
if (get_curr_flex_background() == BG_FENNEL)
|
|
{
|
|
if (fennel_blink_timer == 0)
|
|
{
|
|
fennel_blink(fennel_blink_state);
|
|
fennel_blink_state = (fennel_blink_state + 1) % 4;
|
|
if (fennel_blink_state == 3) // Wait a random amount of time
|
|
{
|
|
fennel_blink_timer = get_rand_range(4 * 60, 8 * 60);
|
|
}
|
|
else // Continue with the animation
|
|
{
|
|
|
|
fennel_blink_timer = 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fennel_blink_timer--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fennel_blink_timer = get_rand_range(4 * 60, 8 * 60);
|
|
}
|
|
}
|
|
|
|
void set_missingno(bool val)
|
|
{
|
|
missingno_enabled = val;
|
|
if (val == false)
|
|
{
|
|
set_background_pal(curr_rom.gamecode, false, false);
|
|
fennel_blink_timer = 0;
|
|
}
|
|
}
|
|
|
|
void set_treecko(bool val)
|
|
{
|
|
treecko_enabled = val;
|
|
}
|
|
|
|
bool get_missingno_enabled()
|
|
{
|
|
return missingno_enabled;
|
|
}
|
|
|
|
bool get_treecko_enabled()
|
|
{
|
|
return treecko_enabled;
|
|
}
|
|
|
|
// FNV-1a 32-bit hash function for byte arrays
|
|
u32 fnv1a_hash(unsigned char *data, size_t length)
|
|
{
|
|
const uint32_t fnv_prime = 0x01000193;
|
|
const uint32_t fnv_offset_basis = 0x811C9DC5;
|
|
uint32_t hash = fnv_offset_basis;
|
|
|
|
for (size_t i = 0; i < length; ++i)
|
|
{
|
|
hash ^= data[i];
|
|
hash *= fnv_prime;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
int get_string_length(const byte *str)
|
|
{
|
|
int size = 0;
|
|
while (str[size] != 0xFF)
|
|
{
|
|
size++;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void convert_int_to_ptgb_str(int val, byte str[], int min_length)
|
|
{
|
|
int div = 1;
|
|
int count = 0;
|
|
int num;
|
|
bool non_zero = false;
|
|
bool first = true;
|
|
|
|
// Set it up so the number has all the zeros it needs
|
|
for (int i = 0; i < min_length; i++)
|
|
{
|
|
div *= 10;
|
|
}
|
|
|
|
// Increase it if the number is still larger
|
|
while (div <= val)
|
|
{
|
|
div *= 10;
|
|
}
|
|
|
|
while (div != 0)
|
|
{
|
|
num = val / div;
|
|
if (num != 0 || non_zero)
|
|
{
|
|
non_zero = true;
|
|
str[count] = num + 0xA1; // 0xA1 is 0 in the chart
|
|
count++;
|
|
}
|
|
else
|
|
{
|
|
if (!first)
|
|
{
|
|
str[count] = 0xA1; // 0xA1 is 0 in the chart
|
|
count++;
|
|
} else {
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
val %= div;
|
|
div /= 10;
|
|
}
|
|
str[count] = 0xFF;
|
|
}
|
|
|
|
void convert_int_to_ptgb_str(int val, byte str[])
|
|
{
|
|
convert_int_to_ptgb_str(val, str, 0);
|
|
}
|