diff --git a/include/dbg/debug_menu.h b/include/dbg/debug_menu.h index d84855c..48755be 100644 --- a/include/dbg/debug_menu.h +++ b/include/dbg/debug_menu.h @@ -3,10 +3,15 @@ #include "vertical_menu.h" +// This file contains the implementation of the debug menu. +// But it doesn't actually define the entries or callback functions. +// For the menu entries, check debug_menu_entries.cpp +// For the callback functions, check debug_menu_functions.cpp + typedef void (*on_execute_callback)(void *context, unsigned user_param); /** - * @brief This struct represents the metadata associated to a single option of a single row in the debug menu. + * @brief This struct represents the metadata associated to a single toggle option of a single row in the debug menu. */ typedef struct option_data { @@ -20,20 +25,61 @@ typedef struct option_data */ typedef struct debug_menu_row_data { + /** + * @brief a charset with which to render the text in this row. + */ const u16 *charset; + /** + * @brief The text to be shown. This is just a plain old C string, + * because we don't need translations in the debug menu. + */ const char *labelText; + /** + * @brief Toggle options below (optional): + */ struct { + /** + * @brief Number of options. If this is 0, then this row will just be a label with no options to select. + */ u8 num_options; + + /** + * @brief The actual array of options alongside their values. + * Note: if you want the widget to free() the array when the widget is getting destroyed, + * set the should_free_on_destruct flag. + */ const option_data *options; + + /** + * @brief This callback will be fired when the user selects an option. (just with LEFT/RIGHT) + */ on_execute_callback on_option_activate; + + /** + * @brief The initial selected option index. + * But this field will get maintained by the widget to track the current selected index. + */ u8 selected_option_index; + + /** + * @brief This flag indicates whether the widget should call free() on the options pointer when the widget is getting destroyed. + */ bool should_free_on_destruct; } option_section; + /** - * @brief A callback that you can set to execute when the user selects this row and presses the A button. + * @brief A callback that you can set to execute when the user selects this row and presses the A button. (optional) */ on_execute_callback on_execute; + + /** + * @brief Context for the on_option-activate and on_execute callbacks. (optional) + */ void *context; + + /** + * @brief This user_param will be passed as an argument to the on_execute callback. (optional) + */ unsigned user_param; } debug_menu_row_data; diff --git a/include/dbg/debug_menu_functions.h b/include/dbg/debug_menu_functions.h index f62840c..fbcad70 100644 --- a/include/dbg/debug_menu_functions.h +++ b/include/dbg/debug_menu_functions.h @@ -1,9 +1,30 @@ #ifndef _DEBUG_MENU_FUNCTIONS_H #define _DEBUG_MENU_FUNCTIONS_H +// Here we define debug menu callback functions, to be used as on_execute_callback or on_option_activate in the debug menu entries. + +/** + * @brief This function will open the text debug screen. + */ void show_text_debug_screen(void *context, unsigned user_param); + +/** + * @brief This function will show the debug info screen, which contains various flags and info about the current game state. + */ void show_debug_info_screen(void *context, unsigned user_param); + +/** + * @brief This generic boolean callback function can be used to set/unset any boolean flag. + * + * @param context a (bool *) pointer to the flag you want to set/unset. + * @param user_param the value to set the flag to. 0 -> false, anything else -> true. + */ void dbg_set_boolean_flag(void *context, unsigned user_param); + +/** + * @brief This function will take a song index and play the corresponding song. + * If the index is UINT32_MAX, it will stop the music instead. + */ void dbg_play_song(void *context, unsigned user_param); #endif \ No newline at end of file diff --git a/include/dbg/debug_mode.h b/include/dbg/debug_mode.h index 9e7c163..c812882 100644 --- a/include/dbg/debug_mode.h +++ b/include/dbg/debug_mode.h @@ -19,18 +19,78 @@ */ typedef struct debug_options { + /** This option will print the link cable data. Pause the transfer with L, resume with R. Skip printing with DOWN. */ bool print_link_data; + + /** + * @brief If this option has been set, we won't animate the text, but show the full text immediately. + */ bool instant_text_speed; + + /** + * @brief If this option is set, we won't try to access a game cartridge. Instead, we'll pretend we're using + * a game cartridge with predefined data. (DEBUG_GAME, DEBUG_VERS, DEBUG_LANG) + * We won't show game sprites either. + * + * This is useful for testing our code in an emulator or without a game cartridge. + */ bool ignore_game_pak; + + /** + * @brief If this option is set, we won't try to show sprites that are stored on the game cartridge. + * This is useful for testing our code in an emulator or without a game cartridge. + */ bool ignore_game_pak_sprites; + + /** + * @brief If this flag is set, we won't actually do a link cable connection. + * Instead, we pretend the connection was successful and use gen1_rb_debug_box_data or gen2_debug_box_data. + */ bool ignore_link_cable; + + /** + * @brief If this option is set, we won't block the user from proceeding if he/she hasn't beat the E4 or + * hasn't activated Mystery Gift yet. This is mostly useful for debug purposes. + */ bool ignore_mg_e4_flags; + + /** + * @brief If this option is set, we won't block the transfer from finishing if the player hasn't collected + * the pokémon from previous transfers. Instead those will get overwritten. This does make you lose those pokémon. + * But it can be useful for debugging. (because you wouldn't need to boot into the game everytime between transfer attempts) + */ bool ignore_unreceived_pkmn; + + /** + * @brief This option will make PTGB show the tutorial even if the tutorial completion flag was already set before. + * Useful for debugging purposes. + */ bool force_tutorial; + + /** + * @brief This option will show the pokémon even if not a single one is valid. + * Once again, this can be useful for debugging purposes. + */ bool dont_hide_invalid_pkmn; + + /** + * @brief This option will avoid blocking the transfer if the player hasn't caught enough pokémon yet. + */ bool ignore_dex_completion; + + /** + * @brief This option will pretend as if the player caught all pokémon. This is useful to get past the dex completion check + */ bool force_all_caught; + + /** + * @brief If this option is set, we will write the data received over the link cable to the save data at offset 0x0000. + */ bool write_cable_data_to_save; + + /** + * @brief If this option is enabled, we will show control characters in the text. + */ bool display_control_char; } debug_options; diff --git a/include/vertical_menu.h b/include/vertical_menu.h index 4b4ca28..bb1841d 100644 --- a/include/vertical_menu.h +++ b/include/vertical_menu.h @@ -80,14 +80,35 @@ private: typedef struct vertical_menu_settings { + // bounds properties unsigned x; unsigned y; unsigned width; unsigned height; + + /** + * @brief The top margin (in pixels) after which we start rendering the first menu item. + */ unsigned margin_top; + + /** + * @brief The bottom margin (in pixels) after which we stop rendering the last menu item. + */ unsigned margin_bottom; + + /** + * @brief The index of the first item to be focused when the menu is shown. + */ unsigned initial_focus_index; + + /** + * @brief The height of each menu item in pixels. Will be used to position the items. + */ unsigned item_height; + + /** + * @brief This index defines which text table to use for the menu entries. + */ int text_table_index; /** @@ -220,18 +241,49 @@ private: bool is_focused_; }; +/** + * @brief Metadata for the simple_item_renderer below. + */ typedef struct simple_item_widget_data { + /** + * @brief label text related properties. + */ struct { + /** + * @brief The index of the text table (see vertical_menu_settings) entry to use as the label of this item. + */ unsigned text_table_index; + + /** + * @brief The left pixel offset after which we render the label text. + */ unsigned margin_left; + /** + * @brief The top pixel offset after which we render the label text. + */ unsigned margin_top; } text; + + /** + * @brief The value this widget represents. This is useful for choice/input handling. + * But it won't be used if an on_execute_callback is set. + */ unsigned value; + + /** + * @brief Optional execute callback. This will be executed when the user presses A while this item is focused. + * If this is nullptr, then MenuInputHandleState::CHOICE_MADE will be returned when the user presses A, + * and the caller can check the value field to know what choice was made. + */ void (*on_execute_callback)(void *context); } simple_item_widget_data; +/** + * @brief This is the simplest possible item widget, which will just render a single line of text for a single item + * and allow you to press A to execute a callback. It's useful for simple menus that don't require toggles or multiple options. + */ class simple_item_renderer : public i_item_widget { public: diff --git a/source/dbg/debug_menu.cpp b/source/dbg/debug_menu.cpp index e7b7f2b..12d6f8d 100644 --- a/source/dbg/debug_menu.cpp +++ b/source/dbg/debug_menu.cpp @@ -38,7 +38,7 @@ void show_debug_menu() .item_height = 10, .text_table_index = INT32_MAX, .allow_cancel = true, - .should_delete_item_widgets_on_destruct = false, + .should_delete_item_widgets_on_destruct = true, .should_hide_state_changed_handler_on_not_focused = true }; diff --git a/source/dbg/debug_menu_entries.cpp b/source/dbg/debug_menu_entries.cpp index 1a750d0..1059ee3 100644 --- a/source/dbg/debug_menu_entries.cpp +++ b/source/dbg/debug_menu_entries.cpp @@ -21,7 +21,7 @@ static const option_data toggle_options[2] = { /** * @brief This helper function makes it easy to define a multiple choice debug row. */ -static debug_menu_row_widget __attribute__((noinline)) define_debug_choice_row(const u16 *charset, const char *labelText, on_execute_callback option_activate_callback, const option_data *options_metadata, u8 num_options, bool should_delete_options, u8 default_option_index, void *context) +static debug_menu_row_widget* __attribute__((noinline)) define_debug_choice_row(const u16 *charset, const char *labelText, on_execute_callback option_activate_callback, const option_data *options_metadata, u8 num_options, bool should_delete_options, u8 default_option_index, void *context) { const debug_menu_row_data row_data = { .charset = charset, @@ -37,18 +37,21 @@ static debug_menu_row_widget __attribute__((noinline)) define_debug_choice_row(c .context = context, .user_param = 0 }; - return debug_menu_row_widget(row_data); + return new debug_menu_row_widget(row_data); } /** * @brief This helper function makes it easy to define a toggle row. */ -static debug_menu_row_widget define_toggle_row(const u16 *charset, const char* labelText, on_execute_callback on_toggle_callback, bool default_value, void *context) +static debug_menu_row_widget* define_toggle_row(const u16 *charset, const char* labelText, on_execute_callback on_toggle_callback, bool default_value, void *context) { return define_debug_choice_row(charset, labelText, on_toggle_callback, toggle_options, 2, false, default_value ? 1 : 0, context); } -static debug_menu_row_widget __attribute__((noinline)) define_song_row(const u16 *charset, const char* labelText) +/** + * @brief This helper function will define the song row, which will let you toggle between any available maxmod song or None at all. + */ +static debug_menu_row_widget* __attribute__((noinline)) define_song_row(const u16 *charset, const char* labelText) { option_data *song_options = (option_data*)malloc(sizeof(option_data) * (MSL_NSONGS + 1)); song_options[0] = { @@ -70,7 +73,7 @@ static debug_menu_row_widget __attribute__((noinline)) define_song_row(const u16 /** * @brief This helper function makes it easy to define a row that executes a function when you select it and press A. */ -static debug_menu_row_widget __attribute__((noinline)) define_executable_row(const u16 *charset, const char* labelText, on_execute_callback on_execute, unsigned user_param, void *context) +static debug_menu_row_widget* __attribute__((noinline)) define_executable_row(const u16 *charset, const char* labelText, on_execute_callback on_execute, unsigned user_param, void *context) { const debug_menu_row_data row_data = { .charset = charset, @@ -86,49 +89,31 @@ static debug_menu_row_widget __attribute__((noinline)) define_executable_row(con .context = context, .user_param = user_param }; - return debug_menu_row_widget(row_data); + return new debug_menu_row_widget(row_data); } +// Here we define the actual entries of the debug menu, using the helper functions defined above. void fill_debug_menu_with_entries(vertical_menu &menu, u16 *charset) { -#if ENABLE_TEXT_DEBUG_SCREEN - debug_menu_row_widget txt_dbg_screen = define_executable_row(charset, "Text Debug", show_text_debug_screen, 0, nullptr); -#endif - debug_menu_row_widget dbg_info_screen = define_executable_row(charset, "Info", show_debug_info_screen, 0, nullptr); - debug_menu_row_widget row_play_song = define_song_row(charset, "Song"); - debug_menu_row_widget row_print_link_data = define_toggle_row(charset, "Print Link", dbg_set_boolean_flag, g_debug_options.print_link_data, &g_debug_options.print_link_data); - debug_menu_row_widget row_instant_text_speed = define_toggle_row(charset, "Instant Text", dbg_set_boolean_flag, g_debug_options.instant_text_speed, &g_debug_options.instant_text_speed); - debug_menu_row_widget row_ignore_game_pak = define_toggle_row(charset, "Ign Cart", dbg_set_boolean_flag, g_debug_options.ignore_game_pak, &g_debug_options.ignore_game_pak); - debug_menu_row_widget row_ignore_game_pak_sprites = define_toggle_row(charset, "Ign Sprites", dbg_set_boolean_flag, g_debug_options.ignore_game_pak_sprites, &g_debug_options.ignore_game_pak_sprites); - debug_menu_row_widget row_ignore_link_cable = define_toggle_row(charset, "Ign Link", dbg_set_boolean_flag, g_debug_options.ignore_link_cable, &g_debug_options.ignore_link_cable); - debug_menu_row_widget row_ignore_mg_e4_flags = define_toggle_row(charset, "Ign MG/E4", dbg_set_boolean_flag, g_debug_options.ignore_mg_e4_flags, &g_debug_options.ignore_mg_e4_flags); - debug_menu_row_widget row_ignore_unreceived_pkmn = define_toggle_row(charset, "Ign Unrec PKMN", dbg_set_boolean_flag, g_debug_options.ignore_unreceived_pkmn, &g_debug_options.ignore_unreceived_pkmn); - debug_menu_row_widget row_force_tutorial = define_toggle_row(charset, "Force Tut", dbg_set_boolean_flag, g_debug_options.force_tutorial, &g_debug_options.force_tutorial); - debug_menu_row_widget row_dont_hide_invalid_pkmn = define_toggle_row(charset, "Show Invalid", dbg_set_boolean_flag, g_debug_options.dont_hide_invalid_pkmn, &g_debug_options.dont_hide_invalid_pkmn); - debug_menu_row_widget row_ignore_dex_completion = define_toggle_row(charset, "Ign Dex Compl", dbg_set_boolean_flag, g_debug_options.ignore_dex_completion, &g_debug_options.ignore_dex_completion); - debug_menu_row_widget row_force_all_caught = define_toggle_row(charset, "Force Caught", dbg_set_boolean_flag, g_debug_options.force_all_caught, &g_debug_options.force_all_caught); - debug_menu_row_widget row_write_cable_data_to_save = define_toggle_row(charset, "Write Cbl Data", dbg_set_boolean_flag, g_debug_options.write_cable_data_to_save, &g_debug_options.write_cable_data_to_save); - debug_menu_row_widget row_display_control_char = define_toggle_row(charset, "Disp CtrlChr", dbg_set_boolean_flag, g_debug_options.display_control_char, &g_debug_options.display_control_char); - i_item_widget* item_widgets[] = { #if ENABLE_TEXT_DEBUG_SCREEN - &txt_dbg_screen, + define_executable_row(charset, "Text Debug", show_text_debug_screen, 0, nullptr), #endif - &dbg_info_screen, - &row_play_song, - &row_print_link_data, - &row_instant_text_speed, - &row_ignore_game_pak, - &row_ignore_game_pak_sprites, - &row_ignore_link_cable, - &row_ignore_mg_e4_flags, - &row_ignore_unreceived_pkmn, - &row_force_tutorial, - &row_dont_hide_invalid_pkmn, - &row_ignore_dex_completion, - &row_force_all_caught, - &row_write_cable_data_to_save, - &row_display_control_char + define_executable_row(charset, "Info", show_debug_info_screen, 0, nullptr), + define_song_row(charset, "Song"), + define_toggle_row(charset, "Print Link", dbg_set_boolean_flag, g_debug_options.print_link_data, &g_debug_options.print_link_data), + define_toggle_row(charset, "Instant Text", dbg_set_boolean_flag, g_debug_options.instant_text_speed, &g_debug_options.instant_text_speed), + define_toggle_row(charset, "Ign Cart", dbg_set_boolean_flag, g_debug_options.ignore_game_pak, &g_debug_options.ignore_game_pak), + define_toggle_row(charset, "Ign Sprites", dbg_set_boolean_flag, g_debug_options.ignore_game_pak_sprites, &g_debug_options.ignore_game_pak_sprites), + define_toggle_row(charset, "Ign Link", dbg_set_boolean_flag, g_debug_options.ignore_link_cable, &g_debug_options.ignore_link_cable), + define_toggle_row(charset, "Ign MG/E4", dbg_set_boolean_flag, g_debug_options.ignore_mg_e4_flags, &g_debug_options.ignore_mg_e4_flags), + define_toggle_row(charset, "Ign Unrec PKMN", dbg_set_boolean_flag, g_debug_options.ignore_unreceived_pkmn, &g_debug_options.ignore_unreceived_pkmn), + define_toggle_row(charset, "Force Tut", dbg_set_boolean_flag, g_debug_options.force_tutorial, &g_debug_options.force_tutorial), + define_toggle_row(charset, "Show Invalid", dbg_set_boolean_flag, g_debug_options.dont_hide_invalid_pkmn, &g_debug_options.dont_hide_invalid_pkmn), + define_toggle_row(charset, "Ign Dex Compl", dbg_set_boolean_flag, g_debug_options.ignore_dex_completion, &g_debug_options.ignore_dex_completion), + define_toggle_row(charset, "Force Caught", dbg_set_boolean_flag, g_debug_options.force_all_caught, &g_debug_options.force_all_caught), + define_toggle_row(charset, "Write Cbl Data", dbg_set_boolean_flag, g_debug_options.write_cable_data_to_save, &g_debug_options.write_cable_data_to_save), + define_toggle_row(charset, "Disp CtrlChr", dbg_set_boolean_flag, g_debug_options.display_control_char, &g_debug_options.display_control_char) }; menu.add_item_widgets(item_widgets, sizeof(item_widgets) / sizeof(item_widgets[0])); diff --git a/source/dbg/debug_menu_functions.cpp b/source/dbg/debug_menu_functions.cpp index 621d6ab..9616d6e 100644 --- a/source/dbg/debug_menu_functions.cpp +++ b/source/dbg/debug_menu_functions.cpp @@ -25,14 +25,19 @@ void show_debug_info_screen(void *context, unsigned user_param) (void)context; (void)user_param; - char hexBuffer[16]; + char flags_hex_str[16]; + char def_lang_hex_str[16]; + char text_buffer[256]; uint16_t charset[256]; + const char *game_code; + u32 pkmn_flags = 0; + load_localized_charset(charset, 3, ENGLISH); if (key_held(KEY_UP) && key_held(KEY_L) && key_held(KEY_R)) { set_treecko(true); } - u32 pkmn_flags = 0; + bool e4_flag = read_flag(curr_GBA_rom.e4_flag); bool mg_flag = read_flag(curr_GBA_rom.mg_flag); bool all_collected_flag = read_flag(curr_GBA_rom.all_collected_flag); @@ -44,51 +49,48 @@ void show_debug_info_screen(void *context, unsigned user_param) bool tutorial = get_tutorial_flag(); int def_lang = get_def_lang_num(); - create_textbox(4, 1, 160, 80, true); - show_text_box(); - ptgb_write_debug(charset, "Debug info:\n\nG: ", true); - ptgb_write_debug(charset, ptgb::to_string(curr_GBA_rom.language), true); switch (curr_GBA_rom.gamecode) { case RUBY_ID: - ptgb_write_debug(charset, "-R-", true); + game_code = "-R-"; break; case SAPPHIRE_ID: - ptgb_write_debug(charset, "-S-", true); + game_code = "-S-"; break; case FIRERED_ID: - ptgb_write_debug(charset, "-F-", true); + game_code = "-F-"; break; case LEAFGREEN_ID: - ptgb_write_debug(charset, "-L-", true); + game_code = "-L-"; break; case EMERALD_ID: - ptgb_write_debug(charset, "-E-", true); + game_code = "-E-"; break; } - ptgb_write_debug(charset, ptgb::to_string(curr_GBA_rom.version), true); + n2hexstr(flags_hex_str, pkmn_flags); + n2hexstr(def_lang_hex_str, def_lang); - ptgb_write_debug(charset, "\nF: ", true); - ptgb_write_debug(charset, ptgb::to_string(e4_flag), true); - ptgb_write_debug(charset, ptgb::to_string(mg_flag), true); - ptgb_write_debug(charset, ptgb::to_string(all_collected_flag), true); - ptgb_write_debug(charset, "-", true); + create_textbox(4, 1, 160, 80, true); + show_text_box(); - n2hexstr(hexBuffer, pkmn_flags); - ptgb_write_debug(charset, hexBuffer, true); - ptgb_write_debug(charset, "\nS: ", true); - ptgb_write_debug(charset, ptgb::to_string(tutorial), true); - ptgb_write_debug(charset, "-", true); - n2hexstr(hexBuffer, def_lang); - ptgb_write_debug(charset, hexBuffer, true); + npf_snprintf(text_buffer, sizeof(text_buffer), + "Debug info:\n\nG: %d%s%d\nF: %d%d%d-%s\nS: %d-%s\n%s%s", + curr_GBA_rom.language, + game_code, + curr_GBA_rom.version, + e4_flag, + mg_flag, + all_collected_flag, + flags_hex_str, + tutorial, + def_lang_hex_str, + BUILD_INFO, + get_treecko_enabled() ? ".T" : "" + ); + + ptgb_write_debug(charset, text_buffer, true); - ptgb_write_debug(charset, "\n", true); - ptgb_write_debug(charset, BUILD_INFO, true); - if (get_treecko_enabled()) - { - ptgb_write_debug(charset, ".T", true); - } while (true) { if (key_hit(KEY_B)) diff --git a/source/sprite_data.cpp b/source/sprite_data.cpp index d6b7d03..76c2163 100644 --- a/source/sprite_data.cpp +++ b/source/sprite_data.cpp @@ -448,6 +448,15 @@ void load_eternal_sprites() load_sprite_compressed(button_yes, button_yesTiles, curr_tile_id, BTN_PAL, ATTR0_WIDE, ATTR1_SIZE_64x32, 1); load_sprite_compressed(button_no, button_noTiles, curr_tile_id, BTN_PAL, ATTR0_WIDE, ATTR1_SIZE_64x32, 1); load_sprite_compressed(cart_label, Label_GreenTiles, curr_tile_id, GB_CART_PAL, ATTR0_SQUARE, ATTR1_SIZE_32x32, 1); + + /** + * @brief Okay, the next lines to load the arrows are a bit confusing. + * Be aware that tempTileBuf is NOT a u8 array, but a u32 array! + * The incoming arrows image is 1bpp, but we convert it into 4bpp in tempTileBuf. So every pixel is a nibble. + * + * Still, every index of tempTileBuf is a full 32 bit integer, yet the third argument of load_sprite is a size_in_bytes. + * This is why those indexes look off. But they are correct. + */ unsigned int tempTileBuf[48]; BitUnPack(arrowsTiles, tempTileBuf, arrowsTilesLen, 1, 4); load_sprite(point_arrow, &tempTileBuf[32], 32, curr_tile_id, BTN_PAL, ATTR0_SQUARE, ATTR1_SIZE_8x8, 1); @@ -455,6 +464,7 @@ void load_eternal_sprites() load_sprite(toggle_arrow_left, &tempTileBuf[40], 32, curr_tile_id, BTN_PAL, ATTR0_SQUARE, ATTR1_SIZE_8x8 | ATTR1_HFLIP, 1); load_sprite(down_arrow, &tempTileBuf[0], 64, curr_tile_id, BTN_PAL, ATTR0_WIDE, ATTR1_SIZE_16x8, 1); load_sprite(up_arrow, &tempTileBuf[16], 64, curr_tile_id, BTN_PAL, ATTR0_WIDE, ATTR1_SIZE_16x8, 1); + load_sprite_compressed(link_frame1, link_frame1Tiles, curr_tile_id, LINK_CABLE_PAL, ATTR0_SQUARE, ATTR1_SIZE_32x32, 1); load_sprite_compressed(link_frame2, link_frame2Tiles, curr_tile_id, LINK_CABLE_PAL, ATTR0_WIDE, ATTR1_SIZE_8x32, 1); load_sprite_compressed(link_frame3, link_frame3Tiles, curr_tile_id, LINK_CABLE_PAL, ATTR0_WIDE, ATTR1_SIZE_16x32, 1);