diff --git a/cubeboot/include/device.h b/cubeboot/include/device.h new file mode 100644 index 0000000..dbe5b27 --- /dev/null +++ b/cubeboot/include/device.h @@ -0,0 +1,6 @@ +#pragma once + +typedef enum { + device_disc_drive, + device_flippydrive +} device_t; diff --git a/cubeboot/source/main.c b/cubeboot/source/main.c index f8d5e9d..413fa2d 100644 --- a/cubeboot/source/main.c +++ b/cubeboot/source/main.c @@ -319,6 +319,10 @@ int main(int argc, char **argv) { // strcpy(cube_logo_ptr, settings.cube_logo); // } + set_patch_value(symshdr, syment, symstringdata, "boot_devices", (uintptr_t)&settings.boot_devices); + set_patch_value(symshdr, syment, symstringdata, "boot_devices_count", settings.boot_devices_count); + set_patch_value(symshdr, syment, symstringdata, "is_disc_drive_allowed", settings.allow_disc_drive); + // Copy other variables set_patch_value(symshdr, syment, symstringdata, "is_running_dolphin", is_running_dolphin); diff --git a/cubeboot/source/settings.c b/cubeboot/source/settings.c index f3e976d..079af01 100644 --- a/cubeboot/source/settings.c +++ b/cubeboot/source/settings.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "sd.h" #include "halt.h" @@ -28,6 +29,64 @@ char *buttons_names[] = { "start", // START 0x1000 }; +static void trim_string(const char **inout_string, u32 *inout_length) { + while (*inout_length > 0 && isspace((unsigned char)(*inout_string)[0])) { + // Trim leading whitespace + *inout_string += 1; + *inout_length -= 1; + } + while (*inout_length > 0 && isspace((unsigned char)(*inout_string)[*inout_length - 1])) { + // Trim trailing whitespace + *inout_length -= 1; + } +} + +static bool get_device_from_string(const char *string, u32 length, device_t *out_device) { + trim_string(&string, &length); + + if (strncmp("disc_drive", string, length) == 0) { + *out_device = device_disc_drive; + return true; + } + if (strncmp("flippydrive", string, length) == 0) { + *out_device = device_flippydrive; + return true; + } + return false; +} + +static bool tokenise_string(const char **inout_string, u32 *inout_substring_length, const char *delimeters) { + bool has_found_any_delimeters = *inout_substring_length > 0; + if (has_found_any_delimeters) { + // Advance `string` to the next delimiter + *inout_string += *inout_substring_length; + *inout_substring_length = 0; + } + + while (*inout_substring_length == 0) { + if ((*inout_string)[0] == '\0') { + // Reached the end of the input string + return false; + } + + if (has_found_any_delimeters) { + // Advance beyond that delimiter + *inout_string += 1; + } + has_found_any_delimeters = true; + + // Find the next delimiter, and therefore the length of the substring up to that delimiter + const char *delimeter_ptr = strpbrk(*inout_string, delimeters); + if (delimeter_ptr) { + *inout_substring_length = delimeter_ptr - *inout_string; + } else { + *inout_substring_length = strlen(*inout_string); + } + } + + return true; +} + void load_settings() { memset(&settings, 0, sizeof(settings)); int config_size = get_file_size("/config.ini"); @@ -160,6 +219,48 @@ void load_settings() { } } + // Boot order + settings.boot_devices_count = 0; + const char *remaining_boot_order = ini_get(conf, "cubeboot", "boot_order"); + if (remaining_boot_order != NULL) { + + u32 device_string_length = 0; + while (tokenise_string(&remaining_boot_order, &device_string_length, ",")) { + device_t found_device; + if (get_device_from_string(remaining_boot_order, device_string_length, &found_device)) { + bool already_in_boot_devices = false; + for (u32 i = 0; i < settings.boot_devices_count; i++) { + already_in_boot_devices |= settings.boot_devices[i] == found_device; + } + + if (!already_in_boot_devices) { + settings.boot_devices[settings.boot_devices_count] = found_device; + settings.boot_devices_count += 1; + } else { + iprintf("Found duplicate boot device: %.*s\n", device_string_length, remaining_boot_order); + } + } else { + iprintf("Found unknown boot device: %.*s\n", device_string_length, remaining_boot_order); + } + + if (settings.boot_devices_count >= MAX_BOOT_DEVICES) { + if (strlen(remaining_boot_order + device_string_length) > 0) { + iprintf("Reached max boot devices (%d), skipping any remaining ones in config\n", MAX_BOOT_DEVICES); + } + break; + } + } + } + + // allow_disc_drive + int allow_disc_drive = 0; + if (!ini_sget(conf, "cubeboot", "allow_disc_drive", "%d", &allow_disc_drive)) { + settings.allow_disc_drive = 1; + } else { + iprintf("Found allow_disc_drive = %d\n", allow_disc_drive); + settings.allow_disc_drive = allow_disc_drive; + } + // menu grid type settings.menu_grid_type = MENU_GRID_SQUARE_ICONS; const char *menu_grid_type = ini_get(conf, "cubeboot", "menu_grid_type"); diff --git a/cubeboot/source/settings.h b/cubeboot/source/settings.h index d07ceef..44f1323 100644 --- a/cubeboot/source/settings.h +++ b/cubeboot/source/settings.h @@ -1,8 +1,11 @@ #include #include "const.h" +#include "device.h" #include "settings_types.h" +#define MAX_BOOT_DEVICES 8 + typedef struct settings { u32 cube_color; char *cube_logo; @@ -15,6 +18,9 @@ typedef struct settings { u32 postboot_delay_ms; char *default_program; char *boot_buttons[MAX_BUTTONS]; + device_t boot_devices[MAX_BOOT_DEVICES]; + u32 boot_devices_count; + u32 allow_disc_drive; menu_grid_type_t menu_grid_type; } settings_t; diff --git a/patches/Makefile b/patches/Makefile index 2baccfb..e7ffcb7 100644 --- a/patches/Makefile +++ b/patches/Makefile @@ -35,7 +35,7 @@ endif # options for code generation #--------------------------------------------------------------------------------- -CFLAGS = -O2 -Wall -Wno-main -ffixed-r12 -ffixed-r13 -ffunction-sections -fdata-sections -Wno-unused-function -Werror-implicit-function-declaration $(MACHDEP) $(INCLUDE) +CFLAGS = -O2 -std=gnu23 -Wall -Wno-main -ffixed-r12 -ffixed-r13 -ffunction-sections -fdata-sections -Wno-unused-function -Werror-implicit-function-declaration $(MACHDEP) $(INCLUDE) CXXFLAGS = $(CFLAGS) LINKER_SCRIPTS := @@ -151,6 +151,14 @@ fake.o: patch.o @echo $(notdir $<) $(bin2o) +#--------------------------------------------------------------------------------- +# This rule links in binary texture data with the .bti extension +#--------------------------------------------------------------------------------- +%.bti.o %_bti.h: %.bti +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + $(bin2o) + -include $(DEPENDS) #--------------------------------------------------------------------------------- diff --git a/patches/data/disc.bti b/patches/data/disc.bti new file mode 100644 index 0000000..b0272c4 Binary files /dev/null and b/patches/data/disc.bti differ diff --git a/patches/data/flippydrive.bti b/patches/data/flippydrive.bti new file mode 100644 index 0000000..a69e735 Binary files /dev/null and b/patches/data/flippydrive.bti differ diff --git a/patches/data/l_button.bti b/patches/data/l_button.bti new file mode 100644 index 0000000..c015a17 Binary files /dev/null and b/patches/data/l_button.bti differ diff --git a/patches/data/original_png/default_opening.png b/patches/data/original_png/default_opening.png new file mode 100644 index 0000000..6872d00 Binary files /dev/null and b/patches/data/original_png/default_opening.png differ diff --git a/patches/data/original_png/dir_tex.png b/patches/data/original_png/dir_tex.png new file mode 100644 index 0000000..c702ec3 Binary files /dev/null and b/patches/data/original_png/dir_tex.png differ diff --git a/patches/data/original_png/disc.png b/patches/data/original_png/disc.png new file mode 100644 index 0000000..ddfbc9f Binary files /dev/null and b/patches/data/original_png/disc.png differ diff --git a/patches/data/original_png/dol_tex.png b/patches/data/original_png/dol_tex.png new file mode 100644 index 0000000..1d12986 Binary files /dev/null and b/patches/data/original_png/dol_tex.png differ diff --git a/patches/data/original_png/flippydrive.png b/patches/data/original_png/flippydrive.png new file mode 100644 index 0000000..bb9420f Binary files /dev/null and b/patches/data/original_png/flippydrive.png differ diff --git a/patches/data/original_png/l_button.png b/patches/data/original_png/l_button.png new file mode 100644 index 0000000..acc1f75 Binary files /dev/null and b/patches/data/original_png/l_button.png differ diff --git a/patches/data/original_png/r_button.png b/patches/data/original_png/r_button.png new file mode 100644 index 0000000..59659c8 Binary files /dev/null and b/patches/data/original_png/r_button.png differ diff --git a/patches/data/original_svg/default_opening.svg b/patches/data/original_svg/default_opening.svg new file mode 100644 index 0000000..2118fcd --- /dev/null +++ b/patches/data/original_svg/default_opening.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/patches/data/original_svg/disc.svg b/patches/data/original_svg/disc.svg new file mode 100644 index 0000000..485135e --- /dev/null +++ b/patches/data/original_svg/disc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/patches/data/original_svg/flippydrive.svg b/patches/data/original_svg/flippydrive.svg new file mode 100644 index 0000000..0b1d1f5 --- /dev/null +++ b/patches/data/original_svg/flippydrive.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/patches/data/original_svg/l_button.svg b/patches/data/original_svg/l_button.svg new file mode 100644 index 0000000..2b3dbf1 --- /dev/null +++ b/patches/data/original_svg/l_button.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/patches/data/original_svg/r_button.svg b/patches/data/original_svg/r_button.svg new file mode 100644 index 0000000..90ba995 --- /dev/null +++ b/patches/data/original_svg/r_button.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/patches/data/r_button.bti b/patches/data/r_button.bti new file mode 100644 index 0000000..9f2b3d9 Binary files /dev/null and b/patches/data/r_button.bti differ diff --git a/patches/include/device.h b/patches/include/device.h new file mode 120000 index 0000000..94cc779 --- /dev/null +++ b/patches/include/device.h @@ -0,0 +1 @@ +../../cubeboot/include/device.h \ No newline at end of file diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index a2970d6..ca07837 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -1,5 +1,6 @@ ntsc10_Jac_PlaySe = 0x813594e0; ntsc10_Jac_StopSoundAll = 0x81359640; +ntsc10_Jac_PlayBgm = 0x81359680; ntsc10_PADSync = 0x81341074; ntsc10_OSDisableInterrupts = 0x813361b0; ntsc10___OSStopAudioSystem = 0x81333b88; @@ -65,8 +66,11 @@ ntsc10_ptr_menu_blob = 0x8145d79c; ntsc10_ptr_game_blob_a = 0x8145d88c; ntsc10_ptr_game_blob_b = 0x8145d888; ntsc10_game_blob_text = 0x8148dd98; +ntsc10_game_blob_insert_disc = 0x8148dda8; +ntsc10_game_blob_reading_disc = 0x8148ddc8; +ntsc10_game_blob_could_not_read_disc = 0x8148ddd8; -ntsc10_prev_menu_id = 0x8145d710; +ntsc10_next_menu_id = 0x8145d710; ntsc10_cur_menu_id = 0x8145d70c; ntsc10_uart_init = 0x8145dca8; @@ -107,3 +111,17 @@ ntsc10_EXIImm = 0x81334d4c; ntsc10_EXIImmEx = 0x81334fa8; ntsc10_is_key_repeat = 0x81302ef8; + +ntsc10_stock_banner_ptr = 0x80af8440; + +ntsc10_get_element_alpha = 0x8130ab40; +ntsc10_update_element_alpha = 0x8130aa58; +ntsc10_banner_element_alpha = 0x8148dd88; +ntsc10_top_level_banner_element_alpha = 0x81465c3c; + +ntsc10_cube_menu_rotation_vertical = 0x81465882; +ntsc10_cube_menu_alpha = 0x81465888; + +ntsc10_draw_buttons = 0x813142ec; +ntsc10_setup_buttons_matrix = 0x813031c8; +ntsc10_update_button_alphas = 0x81311a98; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index 59f44ae..296300c 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -1,5 +1,6 @@ ntsc11_Jac_PlaySe = 0x813574a0; ntsc11_Jac_StopSoundAll = 0x81357600; +ntsc11_Jac_PlayBgm = 0x81357640; ntsc11_PADSync = 0x81368c34; ntsc11_OSDisableInterrupts = 0x8135bc70; @@ -28,7 +29,7 @@ ntsc11_bg_inner_model = 0x81483ab8; ntsc11_gc_text_model = 0x81483d40; ntsc11_menu_alpha_setup = 0x813121b0; -ntsc11_prev_menu_id = 0x81481558; +ntsc11_next_menu_id = 0x81481558; ntsc11_cur_menu_id = 0x81481554; ntsc11_get_save_color = 0x81320424; @@ -60,6 +61,9 @@ ntsc11_ptr_menu_blob = 0x814815e4; ntsc11_ptr_game_blob_a = 0x814816d4; ntsc11_ptr_game_blob_b = 0x814816d0; ntsc11_game_blob_text = 0x814b25c8; +ntsc11_game_blob_insert_disc = 0x814b25d8; +ntsc11_game_blob_reading_disc = 0x814b25f8; +ntsc11_game_blob_could_not_read_disc = 0x814b2608; ntsc11_get_camera_mtx = 0x81302f94; ntsc11_set_obj_pos = 0x8130537c; @@ -107,3 +111,17 @@ ntsc11_EXIImm = 0x8135a774; ntsc11_EXIImmEx = 0x8135a9d0; ntsc11_is_key_repeat = 0x81302de0; + +ntsc11_stock_banner_ptr = 0x809f8440; + +ntsc11_get_element_alpha = 0x8130aa4c; +ntsc11_update_element_alpha = 0x8130a964; +ntsc11_banner_element_alpha = 0x814b25b8; +ntsc11_top_level_banner_element_alpha = 0x8148a3bc; + +ntsc11_cube_menu_rotation_vertical = 0x81489fb2; +ntsc11_cube_menu_alpha = 0x81489fb8; + +ntsc11_draw_buttons = 0x81314604; +ntsc11_setup_buttons_matrix = 0x813030b0; +ntsc11_update_button_alphas = 0x81311ce8; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index 1df5c56..076e725 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -41,6 +41,7 @@ ntsc12_001___pformatter = 0x8137a1d0; ntsc12_001___StringWrite = 0x8137a164; ntsc12_001_Jac_StopSoundAll = 0x813579a0; +ntsc12_001_Jac_PlayBgm = 0x813579e0; ntsc12_001_menu_alpha_setup = 0x81312548; ntsc12_001_get_save_color = 0x813207bc; ntsc12_001_setup_gameselect_anim = 0x813273d4; @@ -59,12 +60,15 @@ ntsc12_001_apply_save_rot = 0x813078ac; ntsc12_001_banner_ready = 0x814834c8; ntsc12_001_banner_pointer = 0x81483528; ntsc12_001_game_blob_text = 0x814b3ba8; +ntsc12_001_game_blob_insert_disc = 0x814b3bb8; +ntsc12_001_game_blob_reading_disc = 0x814b3bd8; +ntsc12_001_game_blob_could_not_read_disc = 0x814b3be8; ntsc12_001_ptr_menu_blob = 0x814836bc; ntsc12_001_ptr_game_blob_a = 0x814837ac; ntsc12_001_ptr_game_blob_b = 0x814837a8; ntsc12_001_bs2start_ready = 0x814837b0; ntsc12_001_cur_menu_id = 0x8148362c; -ntsc12_001_prev_menu_id = 0x81483630; +ntsc12_001_next_menu_id = 0x81483630; ntsc12_001_save_empty = 0x814a0048; ntsc12_001_save_icon = 0x814a0080; @@ -101,4 +105,18 @@ ntsc12_001_EXISync = 0x81363f4c; ntsc12_001_EXIImm = 0x81363b64; ntsc12_001_EXIImmEx = 0x81363dc0; +ntsc12_001_stock_banner_ptr = 0x809f8440; + ntsc12_001_is_key_repeat = 0x8130317c; + +ntsc12_001_get_element_alpha = 0x8130adc0; +ntsc12_001_update_element_alpha = 0x8130acd8; +ntsc12_001_banner_element_alpha = 0x814b3b98; +ntsc12_001_top_level_banner_element_alpha = 0x8148b99c; + +ntsc12_001_cube_menu_rotation_vertical = 0x8148b592; +ntsc12_001_cube_menu_alpha = 0x8148b598; + +ntsc12_001_draw_buttons = 0x8131499c; +ntsc12_001_setup_buttons_matrix = 0x8130344c; +ntsc12_001_update_button_alphas = 0x81312080; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index c6941af..7bd7661 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -41,6 +41,7 @@ ntsc12_101___pformatter = 0x8137a654; ntsc12_101___StringWrite = 0x8137a5e8; ntsc12_101_Jac_StopSoundAll = 0x813579a0; +ntsc12_101_Jac_PlayBgm = 0x813579e0; ntsc12_101_menu_alpha_setup = 0x81312560; ntsc12_101_get_save_color = 0x813207d4; ntsc12_101_setup_gameselect_anim = 0x813273ec; @@ -59,12 +60,15 @@ ntsc12_101_fast_cos = 0x813080a0; ntsc12_101_banner_ready = 0x81483948; ntsc12_101_banner_pointer = 0x814839a8; ntsc12_101_game_blob_text = 0x814b4048; +ntsc12_101_game_blob_insert_disc = 0x814b4058; +ntsc12_101_game_blob_reading_disc = 0x814b4078; +ntsc12_101_game_blob_could_not_read_disc = 0x814b4088; ntsc12_101_ptr_menu_blob = 0x81483b3c; ntsc12_101_ptr_game_blob_a = 0x81483c2c; ntsc12_101_ptr_game_blob_b = 0x81483c28; ntsc12_101_bs2start_ready = 0x81483c30; ntsc12_101_cur_menu_id = 0x81483aac; -ntsc12_101_prev_menu_id = 0x81483ab0; +ntsc12_101_next_menu_id = 0x81483ab0; ntsc12_101_save_empty = 0x814a04e8; ntsc12_101_save_icon = 0x814a0520; @@ -102,3 +106,17 @@ ntsc12_101_EXIImm = 0x81363cc8; ntsc12_101_EXIImmEx = 0x81363f24; ntsc12_101_is_key_repeat = 0x81303194; + +ntsc12_101_stock_banner_ptr = 0x809f8440; + +ntsc12_101_get_element_alpha = 0x8130add8; +ntsc12_101_update_element_alpha = 0x8130acf0; +ntsc12_101_banner_element_alpha = 0x814b4038; +ntsc12_101_top_level_banner_element_alpha = 0x8148be3c; + +ntsc12_101_cube_menu_rotation_vertical = 0x8148ba32; +ntsc12_101_cube_menu_alpha = 0x8148ba38; + +ntsc12_101_draw_buttons = 0x813149b4; +ntsc12_101_setup_buttons_matrix = 0x81303464; +ntsc12_101_update_button_alphas = 0x81312098; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index 66490a0..1d0e0af 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -38,6 +38,7 @@ pal10_set_obj_cam = 0x813053c8; pal10_projection_parameters = 0x814adb40; pal10_Jac_StopSoundAll = 0x8135abe0; +pal10_Jac_PlayBgm = 0x8135ac20; pal10_InitializeUART = 0x81363f00; pal10_WriteUARTN = 0x81363f50; pal10___pformatter = 0x8137c2d8; @@ -67,7 +68,10 @@ pal10_ptr_menu_blob = 0x814ad484; pal10_ptr_game_blob_b = 0x814ad570; pal10_banner_pointer = 0x814ad2f0; pal10_game_blob_text = 0x814de4c8; -pal10_prev_menu_id = 0x814ad3f8; +pal10_game_blob_insert_disc = 0x814de4d8; +pal10_game_blob_reading_disc = 0x814de4f8; +pal10_game_blob_could_not_read_disc = 0x814de508; +pal10_next_menu_id = 0x814ad3f8; pal10_save_empty = 0x814ca968; pal10_orig_thread_init = 0x8131c5c4; @@ -104,3 +108,17 @@ pal10_EXIImm = 0x8135dd54; pal10_EXIImmEx = 0x8135dfb0; pal10_is_key_repeat = 0x81302de0; + +pal10_stock_banner_ptr = 0x80a7d4a0; + +pal10_get_element_alpha = 0x8130a964; +pal10_update_element_alpha = 0x8130a87c; +pal10_banner_element_alpha = 0x814de4b8; +pal10_top_level_banner_element_alpha = 0x814b62bc; + +pal10_cube_menu_rotation_vertical = 0x814b5eb2; +pal10_cube_menu_alpha = 0x814b5eb8; + +pal10_draw_buttons = 0x8131508c; +pal10_setup_buttons_matrix = 0x813030b0; +pal10_update_button_alphas = 0x81312580; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index 7dff048..4bc131d 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -41,6 +41,7 @@ pal11___pformatter = 0x81378c18; pal11___StringWrite = 0x81378b54; pal11_Jac_StopSoundAll = 0x81357520; +pal11_Jac_PlayBgm = 0x81357560; pal11_menu_alpha_setup = 0x813120dc; pal11_get_save_color = 0x81320350; pal11_setup_gameselect_anim = 0x81326f68; @@ -59,12 +60,15 @@ pal11_fast_cos = 0x81307cec; pal11_banner_ready = 0x8147c0b0; pal11_banner_pointer = 0x8147c110; pal11_game_blob_text = 0x814ad288; +pal11_game_blob_insert_disc = 0x814ad298; +pal11_game_blob_reading_disc = 0x814ad2b8; +pal11_game_blob_could_not_read_disc = 0x814ad2c8; pal11_ptr_menu_blob = 0x8147c2a4; pal11_ptr_game_blob_a = 0x8147c394; pal11_ptr_game_blob_b = 0x8147c390; pal11_bs2start_ready = 0x8147c398; pal11_cur_menu_id = 0x8147c214; -pal11_prev_menu_id = 0x8147c218; +pal11_next_menu_id = 0x8147c218; pal11_save_empty = 0x81499728; pal11_save_icon = 0x81499760; @@ -102,3 +106,17 @@ pal11_EXIImm = 0x8135a694; pal11_EXIImmEx = 0x8135a8f0; pal11_is_key_repeat = 0x81302de0; + +pal11_stock_banner_ptr = 0x809efb80; + +pal11_get_element_alpha = 0x8130aa4c; +pal11_update_element_alpha = 0x8130a964; +pal11_banner_element_alpha = 0x814ad278; +pal11_top_level_banner_element_alpha = 0x8148507c; + +pal11_cube_menu_rotation_vertical = 0x81484c72; +pal11_cube_menu_alpha = 0x81484c78; + +pal11_draw_buttons = 0x81314530; +pal11_setup_buttons_matrix = 0x813030b0; +pal11_update_button_alphas = 0x81311c14; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index a8a297b..da8937d 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -42,6 +42,7 @@ pal12___pformatter = 0x8137d9cc; pal12___StringWrite = 0x8137d960; pal12_Jac_StopSoundAll = 0x8135ad20; +pal12_Jac_PlayBgm = 0x8135ad60; pal12_menu_alpha_setup = 0x81312bd4; pal12_get_save_color = 0x81320f10; pal12_setup_gameselect_anim = 0x81327b50; @@ -60,12 +61,15 @@ pal12_fast_cos = 0x81307e54; pal12_banner_ready = 0x814af588; pal12_banner_pointer = 0x814af5e8; pal12_game_blob_text = 0x814dfc88; +pal12_game_blob_insert_disc = 0x814dfc98; +pal12_game_blob_reading_disc = 0x814dfcb8; +pal12_game_blob_could_not_read_disc = 0x814dfcc8; pal12_ptr_menu_blob = 0x814af77c; pal12_ptr_game_blob_a = 0x814af86c; pal12_ptr_game_blob_b = 0x814af868; pal12_bs2start_ready = 0x814af870; pal12_cur_menu_id = 0x814af6ec; -pal12_prev_menu_id = 0x814af6f0; +pal12_next_menu_id = 0x814af6f0; pal12_save_empty = 0x814cc128; pal12_save_icon = 0x814cc160; @@ -103,3 +107,17 @@ pal12_EXIImm = 0x81367040; pal12_EXIImmEx = 0x8136729c; pal12_is_key_repeat = 0x81302f48; + +pal12_stock_banner_ptr = 0x80a7d4a0; + +pal12_get_element_alpha = 0x8130aaa4; +pal12_update_element_alpha = 0x8130a9bc; +pal12_banner_element_alpha = 0x814dfc78; +pal12_top_level_banner_element_alpha = 0x814b7a7c; + +pal12_cube_menu_rotation_vertical = 0x814b7672; +pal12_cube_menu_alpha = 0x814b7678; + +pal12_draw_buttons = 0x813151cc; +pal12_setup_buttons_matrix = 0x81303218; +pal12_update_button_alphas = 0x813126c0; diff --git a/patches/scripts/convert.py b/patches/scripts/convert.py new file mode 100644 index 0000000..b853cf6 --- /dev/null +++ b/patches/scripts/convert.py @@ -0,0 +1,116 @@ +import os +from gclib import texture_utils +from gclib.bti import BTI +from gclib.gx_enums import * + +# This script generates BTI textures from source PNGs +# (while SVGs are available too, they must be converted back to PNG first after any edits) + +data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/") +png_dir = os.path.join(data_dir, "original_png/") + + +def create_ui_bti(image_path: str, image_format: ImageFormat) -> BTI: + bti = BTI() + + bti.image_format = image_format + bti.alpha_setting = u8(0) + + bti.wrap_s = WrapMode.ClampToEdge + bti.wrap_t = WrapMode.ClampToEdge + + bti.palettes_enabled = False + bti.palette_format = PaletteFormat.IA8 + bti.num_colors = 0 + bti.palette_data_offset = u32(0) + + bti.min_filter = FilterMode.Linear + bti.mag_filter = FilterMode.Linear + + bti.min_lod = u8(0) + bti.max_lod = u8(0) + bti.mipmap_count = u8(1) + bti.unknown_3 = u8(0) + bti.lod_bias = u8(0) + + bti.replace_image_from_path(image_path) + bti.save_changes() + return bti + + +def convert_ui_texture_to_bti(image_name: str, image_format: ImageFormat): + png_path = os.path.join(png_dir, image_name + ".png") + bti_path = os.path.join(data_dir, image_name + ".bti") + + bti = create_ui_bti(png_path, image_format) + with open(bti_path, "wb") as f: + bti.data.seek(0) + f.write(bti.data.read()) + + +def convert_icon_texture_to_bin(image_name: str, image_format: ImageFormat): + png_path = os.path.join(png_dir, image_name + ".png") + bin_path = os.path.join(data_dir, image_name + ".bin") + + image_data, palette_data, encoded_colors, width, height = texture_utils.encode_image_from_path( + png_path, image_format, PaletteFormat.IA8, + mipmap_count=1 + ) + + assert(width == 32 and height == 32) + assert(len(image_data.getbuffer()) == 2048) + + with open(bin_path, "wb") as f: + image_data.seek(0) + f.write(image_data.read()) + + +def write_bnr1(bnr_path: str, image_data: bytes, short_title: bytes, short_company: bytes, long_title: bytes, long_company: bytes, description: bytes): + assert(len(image_data.getbuffer()) == 0x1800) + assert(len(short_title) <= 0x20 and len(short_company) <= 0x20) + assert(len(long_title) <= 0x40 and len(long_company) <= 0x40) + assert(len(description) <= 0x80) + + image_data.seek(0) + + with open(bnr_path, "wb") as f: + f.write(b"BNR1".ljust(0x20, b'\00')) + f.write(image_data.read()) + f.write(short_title.ljust(0x20, b'\00')) + f.write(short_company.ljust(0x20, b'\00')) + f.write(long_title.ljust(0x40, b'\00')) + f.write(long_company.ljust(0x40, b'\00')) + f.write(description.ljust(0x80, b'\00')) + + +def generate_banner(): + png_path = os.path.join(png_dir, "default_opening.png") + bnr_path = os.path.join(data_dir, "default_opening.bin") + + image_data, palette_data, encoded_colors, width, height = texture_utils.encode_image_from_path( + png_path, ImageFormat.RGB5A3, PaletteFormat.IA8, + mipmap_count=1 + ) + + assert(width == 96 and height == 32) + + write_bnr1(bnr_path, + image_data=image_data, + short_title=b"Cubeboot Loader", + short_company=b"Team OffBroadway", + long_title=b"Cubeboot Loader", + long_company=b"Team OffBroadway", + description=b"Fluffy Systems LLC" + ) + + +if __name__ == "__main__": + convert_ui_texture_to_bti("disc", ImageFormat.I4) + convert_ui_texture_to_bti("flippydrive", ImageFormat.I4) + convert_ui_texture_to_bti("l_button", ImageFormat.I4) + convert_ui_texture_to_bti("r_button", ImageFormat.I4) + + convert_icon_texture_to_bin("dir_tex", ImageFormat.RGB5A3) + convert_icon_texture_to_bin("dol_tex", ImageFormat.RGB5A3) + + generate_banner() diff --git a/patches/scripts/requirements.txt b/patches/scripts/requirements.txt index 5664e53..3a5bce9 100644 --- a/patches/scripts/requirements.txt +++ b/patches/scripts/requirements.txt @@ -1 +1,2 @@ pyelftools==0.28 +gclib @ git+https://github.com/LagoLunatic/gclib@64127742467acb633d51685b9b1798ab45bb4034 diff --git a/patches/source/audio.c b/patches/source/audio.c new file mode 100644 index 0000000..db0adf3 --- /dev/null +++ b/patches/source/audio.c @@ -0,0 +1,7 @@ +#include "audio.h" + +#include "attr.h" + +__attribute_reloc__ void (*Jac_PlaySe)(u32); +__attribute_reloc__ void (*Jac_StopSoundAll)(); +__attribute_reloc__ void (*Jac_PlayBgm)(u32); diff --git a/patches/source/audio.h b/patches/source/audio.h new file mode 100644 index 0000000..f61eb48 --- /dev/null +++ b/patches/source/audio.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +extern void (*Jac_PlaySe)(u32); +extern void (*Jac_StopSoundAll)(); +extern void (*Jac_PlayBgm)(u32); diff --git a/patches/source/blob.h b/patches/source/blob.h new file mode 100644 index 0000000..bfa23a8 --- /dev/null +++ b/patches/source/blob.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +typedef struct { + u32 magic; + u32 textures_offset; + u32 text_offset; + u32 borders_offset; + u16 texture_count; + u16 text_count; + u16 border_count; + u16 unk0; +} blob_header_t; + +typedef struct { + u32 magic; + u16 x_position; + u16 y_position; + u16 width; + u16 height; + u8 unk0; + u8 texture_index; + u8 unk1; + u8 unk2; +} blob_texture_element_t; diff --git a/patches/source/bs2.c b/patches/source/bs2.c new file mode 100644 index 0000000..9b33090 --- /dev/null +++ b/patches/source/bs2.c @@ -0,0 +1,379 @@ +#include "bs2.h" + +#include "attr.h" +#include "device_selector.h" +#include "boot.h" +#include "dol.h" +#include "element_alpha.h" +#include "flippy_sync.h" +#include "gameid.h" +#include "games.h" +#include "gc_dvd.h" +#include "menu.h" +#include "os.h" +#include "reloc.h" +#include "time.h" + +#include "default_opening_bin.h" + +#include +#include +#include + +#define MAIN_MENU_ID_SETUP_ERROR 0 +#define MAIN_MENU_ID_READING_DISC_ANIMATION 1 +#define MAIN_MENU_ID_RTC_ERROR 2 +#define MAIN_MENU_ID_NO_DISC_FADE_IN 3 +#define MAIN_MENU_ID_ANIMATING_TO_MENU 4 +#define MAIN_MENU_ID_MENU_ACTIVE 5 + +typedef enum { + device_waiting, + device_ready, + device_not_ready +} device_state_t; + +device_t selected_device; +static device_t active_device; +static bool is_switching_device = false; + +bool finished_automatic_switching = false; +__attribute_data__ device_t *boot_devices; +__attribute_data__ u32 boot_devices_count; +static u32 current_boot_device_index = 0; +static device_state_t current_device_state = device_waiting; + +__attribute_data__ u32 force_swiss_boot = 0; + +// used for optional delays +__attribute_data__ u32 postboot_delay_ms = 0; +__attribute_data__ u64 completed_time = 0; + +__attribute_data__ u32 is_disc_drive_allowed = 1; + +static bool has_bs2init_run = false; + +// used to start game +__attribute_reloc__ u32 (*PADSync)(); +__attribute_reloc__ void (*__OSStopAudioSystem)(); +// __attribute_reloc__ void (*run)(register void* entry_point, register u32 clear_start, register u32 clear_size); + +// Top-level menu +__attribute_reloc__ element_alpha_state_t *top_level_banner_element_alpha; +__attribute_reloc__ u16 *cube_menu_alpha; + +extern u32 *banner_ready; +extern const BNR **banner_pointer; +extern u32 start_passthrough_game; + +bool bs2_is_device_allowed(device_t device) { + switch (device) { + case device_disc_drive: + return is_disc_drive_allowed; + + default: + return true; + } +} + +u32 bs2_get_next_allowed_device_index(u32 start_index) { + for (u32 i = start_index; i < boot_devices_count; i++) { + if (bs2_is_device_allowed(boot_devices[i])) { + return i; + } + } + + // No more compatible devices + return boot_devices_count; +} + +void bs2init() { + set_device_selector_enabled(is_disc_drive_allowed); + + current_boot_device_index = bs2_get_next_allowed_device_index(0); + + if (current_boot_device_index < boot_devices_count) { + selected_device = boot_devices[current_boot_device_index]; + finished_automatic_switching = false; + } else { + selected_device = start_passthrough_game && is_disc_drive_allowed ? device_disc_drive : device_flippydrive; + finished_automatic_switching = true; + } + + active_device = selected_device; + is_switching_device = false; + current_device_state = device_waiting; + + switch (active_device) { + case device_disc_drive: + gm_start_disc_thread(); + break; + + case device_flippydrive: + gm_start_thread("/"); + break; + } + + has_bs2init_run = true; +} + +bool bs2_is_switching_device() { + return is_switching_device; +} + +u32 bs2tick_flippydrive() { + // For now, assume that the FlippyDrive is ready to go + // Ideally, this should check if the network or SD card is accessible + current_device_state = device_ready; + + // this helps the start menu show correctly + if (*main_menu_id >= MAIN_MENU_ID_NO_DISC_FADE_IN) { + return STATE_START_GAME; + } + +#ifdef TEST_SKIP_ANIMATION + return STATE_COVER_OPEN; +#endif + + // TODO: allow the user to decide if they want to logo to play + return STATE_NO_DISC; +} + +u32 bs2tick_disc() { + // If the disc thread is running, do things relating to it + *banner_ready = disc_read_banner_ready; + *banner_pointer = stock_banner_ptr; + u32 found_disc_read_state = disc_read_state; + + if (found_disc_read_state == STATE_START_GAME) { + current_device_state = device_ready; + } else if (found_disc_read_state == STATE_NO_DISC || found_disc_read_state == STATE_COVER_OPEN || found_disc_read_state == STATE_READ_ERROR || found_disc_read_state == STATE_FATAL_ERROR) { + current_device_state = device_not_ready; + } else { + current_device_state = device_waiting; + } + + // Handle the post-boot delay + if (found_disc_read_state == STATE_START_GAME && postboot_delay_ms) { + u64 elapsed = diff_msec(completed_time, gettime()); + if (completed_time == 0 || elapsed < postboot_delay_ms) { + return STATE_WAIT_LOAD; + } + } + + return found_disc_read_state; +} + +void bs2tick_check_device_switch() { + if ((selected_device != active_device) && !is_switching_device) { + // Begin switching to the new device + is_switching_device = true; + current_device_state = device_waiting; + + if (active_device == device_disc_drive) { + // Request the disc drive thread to stop + request_disc_stop_thread = true; + + } else { + // TODO: Can we do the same for the FlippyDrive thread? + } + } + + if (is_switching_device) { + // Before completing the switch, make sure the banner on the menu's finished fading out + // Note that the banner alpha is only updated while on the top-level menu, so check if the top-level menu's visible too + // (The banner alpha also doesn't change during the startup animation, and is instead always set to 0) + bool is_banner_visible = top_level_banner_element_alpha->current_alpha > 0 && *cube_menu_alpha < 0x7FFF; + + if (!is_banner_visible) { + // If the thread's stopped, restart it and stop switching + switch (active_device) { + case device_disc_drive: + if (!game_disc_running) { + gm_deinit_thread(); + is_switching_device = false; + } + break; + + case device_flippydrive: + if (!game_enum_running) { + gm_deinit_thread(); + is_switching_device = false; + } + break; + } + } + + if (!is_switching_device) { + // Start spinning up the new thread + active_device = selected_device; + switch (active_device) { + case device_disc_drive: + gm_start_disc_thread(); + break; + + case device_flippydrive: + if (*cur_menu_id != MENU_GAMESELECT_TRANSITION_ID) { + *banner_pointer = (const BNR *)&default_opening_bin[0]; + *banner_ready = 1; + } + gm_start_thread("/"); + break; + } + } + } +} + +void bs2tick_auto_device_switch() { + if (finished_automatic_switching) { + // Automatic switching isn't active + return; + } + + if (*main_menu_id >= MAIN_MENU_ID_ANIMATING_TO_MENU) { + // The GameCube logo is finished and we're transitioning to the main menu; + // disable automatic switching, handing control to the user + finished_automatic_switching = true; + return; + } + + switch (current_device_state) { + case device_waiting: + // Keep waiting for the device to finish loading + break; + + case device_ready: + // The current device is ready to go - stop auto-switching + finished_automatic_switching = true; + break; + + case device_not_ready: + // The current device isn't currently usable (e.g. no disc); switch to the next one + u32 new_boot_device_index = bs2_get_next_allowed_device_index(current_boot_device_index + 1); + + if (new_boot_device_index < boot_devices_count) { + current_boot_device_index = new_boot_device_index; + selected_device = boot_devices[current_boot_device_index]; + } else { + // We've tried every device, and none are ready; just stick with the last one + finished_automatic_switching = true; + } + break; + } +} + +__attribute_data__ int frame_count = 0; +__attribute_used__ u32 bs2tick() { + if (!has_bs2init_run) { + return STATE_WAIT_LOAD; + } + + frame_count++; + if (!completed_time && cube_state->cube_anim_done) { + OSReport("FINISHED (%d frames)\n", frame_count); + completed_time = gettime(); + } + + while (true) { + bs2tick_check_device_switch(); + if (is_switching_device) { + // Just show as 'loading' while we wait + return STATE_WAIT_LOAD; + } + + u32 return_value = STATE_FATAL_ERROR; + switch (active_device) { + case device_disc_drive: + return_value = bs2tick_disc(); + break; + + case device_flippydrive: + return_value = bs2tick_flippydrive(); + break; + } + + // Given the return value, check if we need to automatically switch to another device + bs2tick_auto_device_switch(); + if (selected_device != active_device) { + continue; + } + + return return_value; + } +} + +__attribute_used__ void bs2start() { + OSReport("DONE\n"); + + // read boot info into lowmem + struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; + + if (active_device != device_disc_drive) { + gm_deinit_thread(); + } else { + request_disc_start_game = true; + gm_deinit_thread(); + + int ret = dvd_read_id(); + int err = dvd_get_error(); + if (ret != 0 || err != 0) { + custom_OSReport("Failed to read disc ID\n"); + dvd_custom_bypass_exit(); + udelay(10 * 1000); + + load_stub(); // exit to loader again + u32 *sig = (u32*)0x80001804; + if ((*sig++ == 0x53545542 || *sig++ == 0x53545542) && *sig == 0x48415858) { + static void (*reload)(void) = (void(*)(void))0x80001800; + run(reload); + } + } + + custom_OSReport("Game ID: %c%c%c%c\n", lowmem->b_disk_info.game_code[0], lowmem->b_disk_info.game_code[1], lowmem->b_disk_info.game_code[2], lowmem->b_disk_info.game_code[3]); + + // TODO: Get the correct BNRDesc for the current language + char diskName[64]; + strcpy(diskName, stock_banner_ptr->desc[0].gameName); + setup_gameid_commands(&lowmem->b_disk_info, diskName); + } + + // no IPL code should be running after this point + + while (!PADSync()); + OSDisableInterrupts(); + __OSStopAudioSystem(); + + u32 start_addr = 0x80100000; + u32 end_addr = 0x81600000; + u32 len = end_addr - start_addr; + + memset((void*)start_addr, 0, len); // cleanup + DCFlushRange((void*)start_addr, len); + ICInvalidateRange((void*)start_addr, len); + + // Passthrough mode + if (active_device == device_disc_drive) { + chainload_boot_game(NULL, true); + } + + char *boot_path = boot_entry.path; + if (boot_entry.type == GM_FILE_TYPE_PROGRAM) { + custom_OSReport("Booting DOL\n"); + load_stub(); + + dol_info_t info = load_dol_file(boot_path, false); + run(info.entrypoint); + } else { + custom_OSReport("Booting ISO\n"); + + if (!force_swiss_boot) { + custom_OSReport("Booting ISO (custom apploader)\n"); + chainload_boot_game(&boot_entry, false); + } else { + custom_OSReport("Booting ISO (swiss chainload)\n"); + chainload_swiss_game(boot_path, false); + } + } + + __builtin_unreachable(); +} diff --git a/patches/source/bs2.h b/patches/source/bs2.h new file mode 100644 index 0000000..e8a4752 --- /dev/null +++ b/patches/source/bs2.h @@ -0,0 +1,19 @@ +#pragma once + +#include "device.h" + +#include + +#define STATE_WAIT_LOAD 0x0f // delay after animation +#define STATE_START_GAME 0x10 // play full animation and start game +#define STATE_NO_DISC 0x12 // play full animation before menu +#define STATE_COVER_OPEN 0x13 // force direct to menu +#define STATE_READ_ERROR 0x16 // 'The disc could not be read' error message +#define STATE_FATAL_ERROR 0x17 // 'An error has occurred' message, UI stops responding to inputs + +extern device_t selected_device; +extern u16 *cube_menu_alpha; +extern u32 is_disc_drive_allowed; + +void bs2init(); +bool bs2_is_switching_device(); diff --git a/patches/source/button_descriptions.c b/patches/source/button_descriptions.c new file mode 100644 index 0000000..bcde74a --- /dev/null +++ b/patches/source/button_descriptions.c @@ -0,0 +1,23 @@ +#include "button_descriptions.h" + +#include + +#include "attr.h" +#include "device_selector.h" +#include "element_alpha.h" + +__attribute_reloc__ void (*draw_buttons)(u8); +__attribute_reloc__ void (*setup_buttons_matrix)(); +__attribute_reloc__ void (*update_button_alphas)(); + +__attribute_used__ void patch_draw_buttons(u8 buttons_alpha) { + draw_device_icons(); + + draw_buttons(buttons_alpha); // Always called with `buttons_alpha` of 255 +} + +__attribute_used__ void patch_update_button_alphas() { + update_device_icon_alphas(); + + update_button_alphas(); +} diff --git a/patches/source/button_descriptions.h b/patches/source/button_descriptions.h new file mode 100644 index 0000000..137c565 --- /dev/null +++ b/patches/source/button_descriptions.h @@ -0,0 +1,3 @@ +#pragma once + +extern void (*setup_buttons_matrix)(); diff --git a/patches/source/custom_ui_blob.c b/patches/source/custom_ui_blob.c new file mode 100644 index 0000000..e2630dd --- /dev/null +++ b/patches/source/custom_ui_blob.c @@ -0,0 +1,79 @@ +#include "custom_ui_blob.h" + +#include + +#define NUM_TEXTURE_ELEMENTS 4 + +typedef struct { + blob_header_t header; + blob_texture_element_t textures[NUM_TEXTURE_ELEMENTS]; +} custom_ui_blob_t; + +static const custom_ui_blob_t custom_ui_blob_impl = (custom_ui_blob_t){ + .header = (blob_header_t){ + .magic = make_type('C','B','U','I'), // Cubeboot UI + .textures_offset = offsetof(custom_ui_blob_t, textures), + .text_offset = 0, + .borders_offset = 0, + .texture_count = NUM_TEXTURE_ELEMENTS, + .text_count = 0, + .border_count = 0, + .unk0 = 0 + + }, + .textures = { + // L button + (blob_texture_element_t){ + .magic = L_BUTTON_BLOB_TYPE, + .x_position = 0x240, // Total width seems to be about 0x2500. + .y_position = 0x240, // Buttons icons use 0x19C0. Total height seems to be about 0x1C00 - so this should be approximately the same distance from the top of the screen + .width = 0x140, + .height = 0x130, + .unk0 = 0, + .texture_index = 0, // Should be unused when using `draw_blob_tex()` + .unk1 = 0xF, + .unk2 = 0 + }, + + // R button + (blob_texture_element_t){ + .magic = R_BUTTON_BLOB_TYPE, + .x_position = 0x2500 - 0x240, // Total width seems to be about 0x2500, so this should be approximately the same distance from the right of the screen + .y_position = 0x240, + .width = 0x140, + .height = 0x130, + .unk0 = 0, + .texture_index = 0, // Should be unused when using `draw_blob_tex()` + .unk1 = 0xF, + .unk2 = 0 + }, + + // Disc icon + (blob_texture_element_t){ + .magic = DISC_BLOB_TYPE, + .x_position = 0x240 + (0x140 / 2) + (0x280 / 2) + 0x30, // ~3 pixels to the right of the L button + .y_position = 0x240, + .width = 0x280, + .height = 0x280, + .unk0 = 0, + .texture_index = 0, // Should be unused when using `draw_blob_tex()` + .unk1 = 0xF, + .unk2 = 0 + }, + + // FlippyDrive icon + (blob_texture_element_t){ + .magic = FLIPPYDRIVE_BLOB_TYPE, + .x_position = 0x2500 - 0x240 - (0x140 / 2) - (0x400 / 2) - 0x30, // ~3 pixels to the left of the R button + .y_position = 0x240, + .width = 0x400, + .height = 0x200, + .unk0 = 0, + .texture_index = 0, // Should be unused when using `draw_blob_tex()` + .unk1 = 0xF, + .unk2 = 0 + } + } +}; + +const blob_header_t *const custom_ui_blob = &custom_ui_blob_impl.header; diff --git a/patches/source/custom_ui_blob.h b/patches/source/custom_ui_blob.h new file mode 100644 index 0000000..05cff34 --- /dev/null +++ b/patches/source/custom_ui_blob.h @@ -0,0 +1,11 @@ +#pragma once + +#include "attr.h" +#include "blob.h" + +#define L_BUTTON_BLOB_TYPE make_type('L','b','t','n') +#define R_BUTTON_BLOB_TYPE make_type('R','b','t','n') +#define DISC_BLOB_TYPE make_type('D','I','d','v') +#define FLIPPYDRIVE_BLOB_TYPE make_type('F','D','d','v') + +extern const blob_header_t *const custom_ui_blob; diff --git a/patches/source/device_selector.c b/patches/source/device_selector.c new file mode 100644 index 0000000..002daa2 --- /dev/null +++ b/patches/source/device_selector.c @@ -0,0 +1,90 @@ +#include "device_selector.h" + +#include "audio.h" +#include "bs2.h" +#include "button_descriptions.h" +#include "custom_ui_blob.h" +#include "draw.h" +#include "element_alpha.h" +#include "menu.h" +#include "reloc.h" +#include "structs.h" + +#include "l_button_bti.h" +#include "r_button_bti.h" +#include "disc_bti.h" +#include "flippydrive_bti.h" + +#include + +__attribute_reloc__ s16 *cube_menu_rotation_vertical; + +bool is_device_selector_enabled = true; + +static element_alpha_state_t device_icons_alpha = (element_alpha_state_t){ .current_alpha = 0, .fade_duration = 20, .start_delay = 0, .max_output = 0xFF }; +static element_alpha_state_t disc_drive_icon_alpha = (element_alpha_state_t){ .current_alpha = 0, .fade_duration = 20, .start_delay = 0, .max_output = 0xFF }; +static element_alpha_state_t flippydrive_icon_alpha = (element_alpha_state_t){ .current_alpha = 0, .fade_duration = 20, .start_delay = 0, .max_output = 0xFF }; + +__attribute_used__ void top_level_menu_extra_inputs() { + if (!is_device_selector_enabled) { + return; + } + + s16 gameselect_vertical_cube_rotation = 0x4000; + + if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation && *cube_menu_alpha == 0) { + + if (!bs2_is_switching_device()) { + // Handle L and R to select between disc drive and FlippyDrive + if ((pad_status->buttons_down & PAD_TRIGGER_L) && selected_device != device_disc_drive) { + // Switch to the disc drive + Jac_PlaySe(SOUND_SUBMENU_ENTER); + selected_device = device_disc_drive; + + } else if ((pad_status->buttons_down & PAD_TRIGGER_R) && selected_device != device_flippydrive) { + // Switch to the FlippyDrive + Jac_PlaySe(SOUND_SUBMENU_ENTER); + selected_device = device_flippydrive; + } + } + } +} + +void draw_device_icons() { + u16 device_icons_output_alpha; + get_element_alpha(&device_icons_alpha, &device_icons_output_alpha, NULL); + + if (device_icons_output_alpha > 0) { + u16 disc_drive_icon_output_alpha; + u16 flippydrive_icon_output_alpha; + get_element_alpha(&disc_drive_icon_alpha, &disc_drive_icon_output_alpha, NULL); + get_element_alpha(&flippydrive_icon_alpha, &flippydrive_icon_output_alpha, NULL); + + disc_drive_icon_output_alpha = (disc_drive_icon_output_alpha * device_icons_output_alpha) / disc_drive_icon_alpha.max_output; + flippydrive_icon_output_alpha = (flippydrive_icon_output_alpha * device_icons_output_alpha) / disc_drive_icon_alpha.max_output; + + setup_buttons_matrix(); + GXColor disc_color = {0xFF, 0xFF, 0xFF, disc_drive_icon_output_alpha}; + GXColor flippydrive_color = {0xFF, 0xFF, 0xFF, flippydrive_icon_output_alpha}; + + // Not sure what the first two parameters do, but the third ensures it draws using linear colors + setup_tex_draw(true, false, false); + + draw_blob_tex(L_BUTTON_BLOB_TYPE, custom_ui_blob, &disc_color, (const tex_data *)l_button_bti); + draw_blob_tex(DISC_BLOB_TYPE, custom_ui_blob, &disc_color, (const tex_data *)disc_bti); + draw_blob_tex(R_BUTTON_BLOB_TYPE, custom_ui_blob, &flippydrive_color, (const tex_data *)r_button_bti); + draw_blob_tex(FLIPPYDRIVE_BLOB_TYPE, custom_ui_blob, &flippydrive_color, (const tex_data *)flippydrive_bti); + } +} + +void update_device_icon_alphas() { + bool should_show_device_icons = *next_menu_id == MENU_GAMESELECT_ID && is_device_selector_enabled; + update_element_alpha(&device_icons_alpha, should_show_device_icons ? element_alpha_visible : element_alpha_hidden); + + update_element_alpha(&disc_drive_icon_alpha, (selected_device == device_disc_drive) ? element_alpha_visible : element_alpha_dimmed); + update_element_alpha(&flippydrive_icon_alpha, (selected_device == device_flippydrive) ? element_alpha_visible : element_alpha_dimmed); +} + +void set_device_selector_enabled(bool is_enabled) { + is_device_selector_enabled = is_enabled; +} diff --git a/patches/source/device_selector.h b/patches/source/device_selector.h new file mode 100644 index 0000000..ca42094 --- /dev/null +++ b/patches/source/device_selector.h @@ -0,0 +1,5 @@ +#pragma once + +void draw_device_icons(); +void update_device_icon_alphas(); +void set_device_selector_enabled(bool is_enabled); diff --git a/patches/source/dolphin_dvd.c b/patches/source/dolphin_dvd.c index ad72769..b1edac9 100644 --- a/patches/source/dolphin_dvd.c +++ b/patches/source/dolphin_dvd.c @@ -32,13 +32,19 @@ typedef struct { u32 length; } bnr_info_t; -static bnr_info_t get_banner_offset_slow(DiskHeader *header, uint32_t fd) { +static bnr_info_t get_banner_offset_slow(DiskHeader *header, uint32_t fd, dvd_should_cancel_callback should_cancel) { + bnr_info_t invalid_info = (bnr_info_t) { + .offset = 0, + .length = 0, + }; u32 size = OSRoundUp32B(header->FSTSize); u32 offset = header->FSTOffset; u8 *fst = (void*)0x81700000; // read FST - dvd_threaded_read(fst, size, offset, fd); + if (dvd_threaded_read(fst, size, offset, fd, should_cancel) != 0) { + return invalid_info; + } FSTEntry *entry_table = (FSTEntry*)fst; u32 total_entries = entry_table[0].len; @@ -76,44 +82,29 @@ static bnr_info_t get_banner_offset_slow(DiskHeader *header, uint32_t fd) { } OSYieldThread(); // allow rescheduling - return (bnr_info_t) { - .offset = 0, - .length = 0, - }; + return invalid_info; } // Get the BNR offset on the disc -dolphin_game_into_t get_game_info(char *game_path) { +dolphin_game_into_t get_game_info_with_open_game(u8 fd, dvd_should_cancel_callback should_cancel) { + dolphin_game_into_t invalid_info = (dolphin_game_into_t) { .valid = false }; __attribute__((aligned(32))) static u32 small_buf[8]; // for BNR reads - const uint8_t flags = IPC_FILE_FLAG_DISABLECACHE | IPC_FILE_FLAG_DISABLESPEEDEMU; - int ret = dvd_custom_open(game_path, FILE_ENTRY_TYPE_FILE, flags); - if (ret != 0) { - OSReport("ERROR: Failed to open %s\n", game_path); - return (dolphin_game_into_t) { .valid = false }; - } - - // OSReport("DEBUG: file opened %s\n", game_path); - - file_status_t *status = dvd_custom_status(); - if (status->result != 0) { - OSReport("ERROR: Failed to get status for %s\n", game_path); - - dvd_custom_close(status->fd); - return (dolphin_game_into_t) { .valid = false }; - } - - // OSReport("DEBUG: status loaded %d\n", status->fd); - __attribute__((aligned(32))) static DiskHeader header; - dvd_threaded_read(&header, sizeof(DiskHeader), 0, status->fd); //Read in the disc header + // Read in the disc header + if (dvd_threaded_read(&header, sizeof(DiskHeader), 0, fd, should_cancel) != 0) { + return invalid_info; + } // OSReport("DEBUG: disk header loaded\n"); u32 fast_bnr_offset = get_banner_offset_fast(&header); // OSReport("DEBUG: Fast BNR offset: %08x\n", fast_bnr_offset); if (fast_bnr_offset != 0) { - dvd_threaded_read(small_buf, 32, fast_bnr_offset, status->fd); //Read in the banner data + // Read in the banner data + if (dvd_threaded_read(small_buf, 32, fast_bnr_offset, fd, should_cancel) != 0) { + return invalid_info; + } u32 magic = small_buf[0]; if (magic == BANNER_MAGIC_1 || magic == BANNER_MAGIC_2) { @@ -128,8 +119,6 @@ dolphin_game_into_t get_game_info(char *game_path) { info.fst_offset = header.FSTOffset; info.fst_size = header.FSTSize; info.max_fst_size = header.MaxFSTSize; - - dvd_custom_close(status->fd); return info; } } @@ -138,14 +127,16 @@ dolphin_game_into_t get_game_info(char *game_path) { if (header.FSTSize > 0x100000) { OSReport("ERROR: FST size is too large: %08x\n", header.FSTSize); - dvd_custom_close(status->fd); return (dolphin_game_into_t) { .valid = false }; } // If we didn't find the banner in the fast location, try the FST - bnr_info_t bnr_info = get_banner_offset_slow(&header, status->fd); + bnr_info_t bnr_info = get_banner_offset_slow(&header, fd, should_cancel); if (bnr_info.offset != 0) { - dvd_threaded_read(small_buf, 32, bnr_info.offset, status->fd); //Read in the banner data + // Read in the banner data + if (dvd_threaded_read(small_buf, 32, bnr_info.offset, fd, should_cancel) != 0) { + return invalid_info; + } u32 magic = small_buf[0]; if (magic == BANNER_MAGIC_1 || magic == BANNER_MAGIC_2) { @@ -160,15 +151,34 @@ dolphin_game_into_t get_game_info(char *game_path) { info.fst_offset = header.FSTOffset; info.fst_size = header.FSTSize; info.max_fst_size = header.MaxFSTSize; - - dvd_custom_close(status->fd); return info; } } // OSReport("DEBUG: FST was loaded\n"); - // invalid file + return invalid_info; +} + +dolphin_game_into_t get_game_info(char *game_path) { + const uint8_t flags = IPC_FILE_FLAG_DISABLECACHE | IPC_FILE_FLAG_DISABLESPEEDEMU; + int ret = dvd_custom_open(game_path, FILE_ENTRY_TYPE_FILE, flags); + if (ret != 0) { + OSReport("ERROR: Failed to open %s\n", game_path); + return (dolphin_game_into_t) { .valid = false }; + } + + // OSReport("DEBUG: file opened\n"); + + file_status_t *status = dvd_custom_status(); + if (status->result != 0) { + OSReport("ERROR: Failed to get status for %s\n", game_path); + + dvd_custom_close(status->fd); + return (dolphin_game_into_t) { .valid = false }; + } + + dolphin_game_into_t game_info = get_game_info_with_open_game(status->fd, NULL); dvd_custom_close(status->fd); - return (dolphin_game_into_t) { .valid = false }; + return game_info; } diff --git a/patches/source/dolphin_dvd.h b/patches/source/dolphin_dvd.h index 8ceb31d..ecb9115 100644 --- a/patches/source/dolphin_dvd.h +++ b/patches/source/dolphin_dvd.h @@ -4,6 +4,8 @@ #include #include +#include "dvd_threaded.h" + #define T_FILE 0 #define T_DIR 1 @@ -107,7 +109,7 @@ _Static_assert(sizeof(bool) == 1); // just make sure this is a single byte #define BANNER_SINGLE_LANG 0 #define BANNER_MULTI_LANG 1 -typedef struct { +typedef struct dolphin_game_into_t { bool valid; u8 game_id[6]; u8 disc_num; @@ -122,6 +124,7 @@ typedef struct { _Static_assert(sizeof(dolphin_game_into_t) == 32); +dolphin_game_into_t get_game_info_with_open_game(u8 fd, dvd_should_cancel_callback should_cancel); dolphin_game_into_t get_game_info(char *game_path); #endif diff --git a/patches/source/draw.c b/patches/source/draw.c new file mode 100644 index 0000000..7bf2d22 --- /dev/null +++ b/patches/source/draw.c @@ -0,0 +1,31 @@ +#include "draw.h" + +#include "attr.h" + +__attribute_reloc__ void (*prep_text_mode)(); +__attribute_reloc__ void (*gx_draw_text)(u16 index, text_group* text, text_draw_group* text_draw, GXColor* color); + +__attribute_reloc__ void (*draw_grid)(Mtx position, u8 alpha); +__attribute_reloc__ void (*draw_box)(u32 index, const box_draw_group* header, const GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); +__attribute_reloc__ void (*draw_blob_fixed)(const element_alpha_state_t *element_alpha, const blob_header_t *sth0_blob, const blob_header_t *glh0_blob, const GXColor *color); +__attribute_reloc__ void (*draw_blob_text)(u32 type, const blob_header_t *blob, const GXColor *color, const char *str, s32 len); +__attribute_reloc__ void (*draw_blob_text_long)(u32 type, const blob_header_t *blob, const GXColor *color, const char *str, s32 len); +__attribute_reloc__ void (*draw_blob_border)(u32 type, const blob_header_t *blob, const GXColor *color); +__attribute_reloc__ void (*draw_blob_tex)(u32 type, const blob_header_t *blob, const GXColor *color, const tex_data *dat); +__attribute_reloc__ void (*setup_tex_draw)(bool unk0, bool unk1, bool is_srgb); +__attribute_reloc__ void (*draw_named_tex)(u32 type, const blob_header_t *blob, const GXColor *color, s16 x, s16 y); + +// for model gx +__attribute_reloc__ void (*model_init)(model* m, int process); +__attribute_reloc__ void (*draw_model)(model* m); +__attribute_reloc__ void (*draw_partial)(model* m, model_part* part); +__attribute_reloc__ void (*change_model)(model* m); + +// for camera gx +__attribute_reloc__ void (*set_obj_pos)(model* m, MtxP matrix, guVector vector); +__attribute_reloc__ void (*set_obj_cam)(model* m, MtxP matrix); +__attribute_reloc__ MtxP (*get_camera_mtx)(); + +// helpers +__attribute_reloc__ f32 (*fast_sin)(s16 deg); +__attribute_reloc__ f32 (*fast_cos)(s16 deg); diff --git a/patches/source/draw.h b/patches/source/draw.h new file mode 100644 index 0000000..f9eae16 --- /dev/null +++ b/patches/source/draw.h @@ -0,0 +1,36 @@ +#pragma once + +#include "blob.h" +#include "element_alpha.h" +#include "structs.h" + +#include +#include + +extern void (*prep_text_mode)(); +extern void (*gx_draw_text)(u16 index, text_group* text, text_draw_group* text_draw, GXColor* color); + +extern void (*draw_grid)(Mtx position, u8 alpha); +extern void (*draw_box)(u32 index, const box_draw_group* header, const GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); +extern void (*draw_blob_fixed)(const element_alpha_state_t *element_alpha, const blob_header_t *sth0_blob, const blob_header_t *glh0_blob, const GXColor *color); +extern void (*draw_blob_text)(u32 type, const blob_header_t *blob, const GXColor *color, const char *str, s32 len); +extern void (*draw_blob_text_long)(u32 type, const blob_header_t *blob, const GXColor *color, const char *str, s32 len); +extern void (*draw_blob_border)(u32 type, const blob_header_t *blob, const GXColor *color); +extern void (*draw_blob_tex)(u32 type, const blob_header_t *blob, const GXColor *color, const tex_data *dat); +extern void (*setup_tex_draw)(bool unk0, bool unk1, bool is_srgb); +extern void (*draw_named_tex)(u32 type, const blob_header_t *blob, const GXColor *color, s16 x, s16 y); + +// for model gx +extern void (*model_init)(model* m, int process); +extern void (*draw_model)(model* m); +extern void (*draw_partial)(model* m, model_part* part); +extern void (*change_model)(model* m); + +// for camera gx +extern void (*set_obj_pos)(model* m, MtxP matrix, guVector vector); +extern void (*set_obj_cam)(model* m, MtxP matrix); +extern MtxP (*get_camera_mtx)(); + +// helpers +extern f32 (*fast_sin)(s16 deg); +extern f32 (*fast_cos)(s16 deg); diff --git a/patches/source/dvd_threaded.c b/patches/source/dvd_threaded.c index 122f852..534be98 100644 --- a/patches/source/dvd_threaded.c +++ b/patches/source/dvd_threaded.c @@ -1,6 +1,7 @@ #include "dvd_threaded.h" #include "dolphin_os.h" #include "os.h" +#include "time.h" // DI regs from YAGCD #define DI_SR 0 // 0xCC006000 - DI Status Register @@ -27,10 +28,21 @@ #define DI_CFG 9 // 0xCC006024 - DI Configuration Register #define DVD_OEM_READ 0xA8000000 +#define DVD_OEM_ERROR 0xE0000000 +#define DVD_OEM_STOP_MOTOR 0xE3000000 +#define DVD_OEM_AUDIO 0xE4000000 static vu32* const _di_regs = (vu32*)0xCC006000; -int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd) { +void dvd_break() { + _di_regs[DI_SR] = DI_SR_BRK; + + while (_di_regs[DI_SR] & DI_SR_BRK) { + OSYieldThread(); + } +} + +int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd, dvd_should_cancel_callback should_cancel) { if (offset >> 2 > 0xFFFFFFFF) return -1; @@ -46,6 +58,11 @@ int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int _di_regs[DI_CR] = (DI_CR_DMA | DI_CR_TSTART); // start transfer while (_di_regs[DI_CR] & DI_CR_TSTART) { + if (should_cancel && should_cancel()) { + dvd_break(); + return 1; + } + OSYieldThread(); } @@ -57,3 +74,85 @@ int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int } return 0; } + +int dvd_threaded_read_id(dvd_should_cancel_callback should_cancel) { + _di_regs[DI_SR] = (DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINT | DI_SR_DEINTMASK); + _di_regs[DI_CVR] = 0; // clear cover int + + _di_regs[DI_CMDBUF0] = DVD_OEM_READ | 0x40; + _di_regs[DI_CMDBUF1] = 0; + _di_regs[DI_CMDBUF2] = 0x20; + + _di_regs[DI_MAR] = 0; + _di_regs[DI_LENGTH] = 0x20; + _di_regs[DI_CR] = (DI_CR_DMA | DI_CR_TSTART); // start transfer + + while (_di_regs[DI_CR] & DI_CR_TSTART) { + if (should_cancel && should_cancel()) { + dvd_break(); + return 1; + } + + OSYieldThread(); + } + + // check if ERR was asserted + if (_di_regs[DI_SR] & DI_SR_DEINT) { + return 1; + } + return 0; +} + +void dvd_threaded_audio_config(char use_streaming, char size) { + _di_regs[DI_SR] = (DI_SR_BRKINTMASK | DI_SR_TCINTMASK | DI_SR_DEINT | DI_SR_DEINTMASK); + _di_regs[DI_CVR] = 0; // clear cover int + + if(use_streaming) { + if (!size) size = 10; + _di_regs[DI_CMDBUF0] = DVD_OEM_AUDIO | 0x10000 | size; + _di_regs[DI_CMDBUF1] = 0; + _di_regs[DI_CMDBUF2] = 0; + } else { + _di_regs[DI_CMDBUF0] = DVD_OEM_AUDIO; + _di_regs[DI_CMDBUF1] = 0; + _di_regs[DI_CMDBUF2] = 0; + } + + _di_regs[DI_MAR] = 0; + _di_regs[DI_LENGTH] = 0; + _di_regs[DI_CR] = DI_CR_TSTART; // start transfer + + while (_di_regs[DI_CR] & DI_CR_TSTART) { + OSYieldThread(); + } +} + +unsigned int dvd_threaded_get_error() { + _di_regs[DI_CMDBUF0] = DVD_OEM_ERROR; + _di_regs[DI_IMMBUF] = 0; + _di_regs[DI_CR] = DI_CR_TSTART; // IMM + + while (_di_regs[DI_CR] & DI_CR_TSTART) { + OSYieldThread(); + } + + return _di_regs[DI_IMMBUF]; +} + +void dvd_threaded_stop_motor() { + _di_regs[DI_CMDBUF0] = DVD_OEM_STOP_MOTOR; + _di_regs[DI_IMMBUF] = 0; + _di_regs[DI_CR] = DI_CR_TSTART; // IMM + + while (_di_regs[DI_CR] & DI_CR_TSTART) { + OSYieldThread(); + } +} + +void dvd_threaded_reset() { + _di_regs[DI_CVR] = 2; + volatile unsigned long v = *(volatile unsigned long*)0xcc003024; + *(volatile unsigned long*)0xcc003024 = (v & ~4) | 1; + udelay_threaded(12); + *(volatile unsigned long*)0xcc003024 = v | 5; +} diff --git a/patches/source/dvd_threaded.h b/patches/source/dvd_threaded.h index 2055c74..6375c09 100644 --- a/patches/source/dvd_threaded.h +++ b/patches/source/dvd_threaded.h @@ -1,4 +1,11 @@ #include #include -int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd); +typedef bool (*dvd_should_cancel_callback)(); + +int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd, dvd_should_cancel_callback should_cancel); +int dvd_threaded_read_id(dvd_should_cancel_callback should_cancel); +void dvd_threaded_audio_config(char use_streaming, char size); +unsigned int dvd_threaded_get_error(); +void dvd_threaded_stop_motor(); +void dvd_threaded_reset(); diff --git a/patches/source/element_alpha.c b/patches/source/element_alpha.c new file mode 100644 index 0000000..2687619 --- /dev/null +++ b/patches/source/element_alpha.c @@ -0,0 +1,6 @@ +#include "element_alpha.h" + +#include "attr.h" + +__attribute_reloc__ void (*update_element_alpha)(element_alpha_state_t *element, element_alpha_update_state_t new_state); +__attribute_reloc__ void (*get_element_alpha)(element_alpha_state_t *element, u16 *output_alpha, u32 *output_unknown); diff --git a/patches/source/element_alpha.h b/patches/source/element_alpha.h new file mode 100644 index 0000000..9696c0c --- /dev/null +++ b/patches/source/element_alpha.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef struct { + u16 current_alpha; // Ranges from 0 to (fade_duration + start_delay) + u16 fade_duration; // Frames; typically 0x14 + u16 start_delay; // Frames; typically 0 + u16 frame_counter; // Incremented with every update + u8 unk0; + u8 max_output; + u16 unknown_output_multiplier; + u32 blob_offset; // Used by `draw_blob_fixed()` to find the relevant element +} element_alpha_state_t; + +typedef enum { + element_alpha_visible, + element_alpha_hidden, + element_alpha_dimmed +} element_alpha_update_state_t; + +extern void (*update_element_alpha)(element_alpha_state_t *element, element_alpha_update_state_t new_state); +extern void (*get_element_alpha)(element_alpha_state_t *element, u16 *output_alpha, u32 *output_unknown); diff --git a/patches/source/gameid.c b/patches/source/gameid.c index 84bb876..eec8c6b 100644 --- a/patches/source/gameid.c +++ b/patches/source/gameid.c @@ -1,3 +1,6 @@ +#include "gameid.h" + +#include "dolphin_dvd.h" #include "dolphin_os.h" #include "games.h" #include "mcp.h" @@ -11,11 +14,51 @@ __attribute_reloc__ OSMessageQueue *card_thread_mq; __attribute_data__ u32 disable_mcp_select = 0; -__attribute_data__ static gm_file_entry_t *mcp_selected_entry = NULL; + +static OSMutex disk_data_mutex; +__attribute_data__ static struct gcm_disk_info disk_id; +__attribute_data__ static char disk_name[64]; void mcp_set_gameid(gm_file_entry_t *entry) { if (disable_mcp_select) return; - mcp_selected_entry = entry; + + OSLockMutex(&disk_data_mutex); + { + gm_extra_t *extra = &entry->extra; + disk_id = (struct gcm_disk_info){ + .game_code = { extra->game_id[0], extra->game_id[1], extra->game_id[2], extra->game_id[3] }, + .maker_code = { extra->game_id[4], extra->game_id[5] }, + .disk_id = extra->disc_num, + .version = extra->disc_ver, + }; + DCFlushRange(&disk_id, sizeof(disk_id)); + + strcpy(disk_name, entry->desc.gameName); + DCFlushRange(disk_name, sizeof(disk_name)); + } + OSUnlockMutex(&disk_data_mutex); + + OSSendMessage(card_thread_mq, (OSMessage)0xc, 0); +} + +void mcp_set_gameid_for_disc(dolphin_game_into_t* game_info, const BNRDesc *desc) { + if (disable_mcp_select) return; + + OSLockMutex(&disk_data_mutex); + { + disk_id = (struct gcm_disk_info){ + .game_code = { game_info->game_id[0], game_info->game_id[1], game_info->game_id[2], game_info->game_id[3] }, + .maker_code = { game_info->game_id[4], game_info->game_id[5] }, + .disk_id = game_info->disc_num, + .version = game_info->disc_ver, + }; + DCFlushRange(&disk_id, sizeof(disk_id)); + + strcpy(disk_name, desc->gameName); + DCFlushRange(disk_name, sizeof(disk_name)); + } + OSUnlockMutex(&disk_data_mutex); + OSSendMessage(card_thread_mq, (OSMessage)0xc, 0); } @@ -38,21 +81,12 @@ void setup_gameid_commands(struct gcm_disk_info *di, char diskName[64]) { BOOL pre_custom_card_OSSendMessage(OSMessageQueue* mq, OSMessage msg, s32 flags) { OSReport("Sending message %d to %08x\n", msg, mq); - if (mcp_selected_entry != NULL) { - gm_extra_t *extra = &mcp_selected_entry->extra; - struct gcm_disk_info diskID = { - .game_code = { extra->game_id[0], extra->game_id[1], extra->game_id[2], extra->game_id[3] }, - .maker_code = { extra->game_id[4], extra->game_id[5] }, - .disk_id = extra->disc_num, - .version = extra->disc_ver, - }; - DCFlushRange(&diskID, sizeof(diskID)); - - char diskInfo[64]; - strcpy(&diskInfo[0], mcp_selected_entry->desc.gameName); - DCFlushRange(diskInfo, 64); - setup_gameid_commands(&diskID, diskInfo); + OSLockMutex(&disk_data_mutex); + { + setup_gameid_commands(&disk_id, disk_name); } + OSUnlockMutex(&disk_data_mutex); + return OSSendMessage(mq, msg, flags); } diff --git a/patches/source/gameid.h b/patches/source/gameid.h index dc1ab15..ff54472 100644 --- a/patches/source/gameid.h +++ b/patches/source/gameid.h @@ -1,4 +1,7 @@ #include "games.h" +typedef struct dolphin_game_into_t dolphin_game_into_t; + void setup_gameid_commands(struct gcm_disk_info *di, char diskName[64]); // direct void mcp_set_gameid(gm_file_entry_t *entry); +void mcp_set_gameid_for_disc(dolphin_game_into_t* game_info, const BNRDesc *desc); diff --git a/patches/source/games.c b/patches/source/games.c index 3904310..3f62656 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -23,11 +23,14 @@ #include "dolphin_os.h" #include "dolphin_arq.h" #include "dolphin_dvd.h" -#include "flippy_sync.h" #include "dvd_threaded.h" +#include "flippy_sync.h" +#include "gameid.h" +#include "gc_dvd.h" #include "metaphrasis.h" +#include "bs2.h" #include "games.h" #include "grid.h" #include "menu.h" @@ -45,6 +48,7 @@ OSMutex *game_enum_mutex = &game_enum_mutex_obj; char game_enum_path[128] = {0}; bool game_enum_running = false; +bool game_disc_running = false; int assets_per_page; int assets_initial_count; @@ -56,6 +60,8 @@ __attribute_data_lowmem__ static gm_file_entry_t *gm_entry_backing[2000]; static u32 gm_entry_count = 0; +__attribute_reloc__ BNR* stock_banner_ptr; + gm_file_entry_t *gm_get_game_entry(int index) { if (index >= gm_entry_count) return NULL; return gm_entry_backing[index]; @@ -590,7 +596,7 @@ static int gm_load_banner(gm_file_entry_t *entry, u32 aram_offset, bool force_un } __attribute_aligned_data_lowmem__ static BNR banner_buffer; - dvd_threaded_read(&banner_buffer, sizeof(BNR), entry->extra.dvd_bnr_offset, status->fd); + dvd_threaded_read(&banner_buffer, sizeof(BNR), entry->extra.dvd_bnr_offset, status->fd, NULL); dvd_custom_close(status->fd); entry->asset.banner.state = GM_LOAD_STATE_LOADING; @@ -641,7 +647,7 @@ static bool gm_load_icon(gm_file_entry_t *entry, u32 aram_offset, bool force_unl void *file_buf = gm_malloc(file_size); // read - dvd_threaded_read(file_buf, file_size, 0, status->fd); + dvd_threaded_read(file_buf, file_size, 0, status->fd, NULL); dvd_custom_close(status->fd); ok_png png = gm_png_decode(file_buf, file_size); @@ -965,6 +971,146 @@ void *gm_thread_worker(void* param) { return NULL; } +// Stops the disc-reading loop, so we can switch to the FlippyDrive +atomic_bool request_disc_stop_thread = false; + +// Stops the disc-reading loop, so we can start the loaded disc +atomic_bool request_disc_start_game = false; + +#define ERROR_A_OK 0x00 +#define ERROR_A_LID_OPEN 0x01 +#define ERROR_A_NO_DISC_DISC_CHANGED 0x02 +#define ERROR_A_NO_DISC 0x03 +#define ERROR_A_MOTOR_OFF 0x04 +#define ERROR_A_DISC_NOT_INITIALIZED 0x05 + +atomic_uint disc_read_state = STATE_WAIT_LOAD; +atomic_bool disc_read_banner_ready = false; +atomic_char disc_read_region = '?'; +dolphin_game_into_t disc_game_info; + +bool should_stop_disc_thread_worker() { + return request_disc_stop_thread; +} + +void *gm_disc_thread_worker(void *param) { + disc_read_state = STATE_WAIT_LOAD; + + dvd_custom_bypass_enter(); + udelay_threaded(20 * 1000); + + // TODO: How do we recover if no DVD drive is installed? + + bool finished_reading_disc = false; + const u8 fd = 0; // The DVD drive doesn't use file descriptors; leave its bits set to 0 + while (!request_disc_stop_thread && !request_disc_start_game) { + OSYieldThread(); + + bool is_cover_open = dvd_cover_status(); + if (finished_reading_disc && !is_cover_open) { + // If we've successfully read the disc, or encountered a disc read error (e.g. no disc), + // wait until the cover's opened before doing anything else + continue; + } + + finished_reading_disc = false; + + if (is_cover_open) { + // Cover is open - wait until the cover's been closed + disc_read_state = STATE_COVER_OPEN; + disc_read_banner_ready = false; + continue; + } + + disc_read_state = STATE_WAIT_LOAD; + + dvd_threaded_reset(); + + int ret = dvd_threaded_read_id(should_stop_disc_thread_worker); + u32 error = dvd_threaded_get_error(); + if (ret != 0 || error != 0) { + u32 error_a = error >> 24; + // u32 error_r = error & 0x00FFFFFF; + + if (error_a == ERROR_A_LID_OPEN) { + disc_read_state = STATE_COVER_OPEN; + } else if (error_a == ERROR_A_NO_DISC || error_a == ERROR_A_NO_DISC_DISC_CHANGED) { + disc_read_state = STATE_NO_DISC; + } else { + disc_read_state = STATE_READ_ERROR; + } + + finished_reading_disc = true; + continue; + } + + if (request_disc_stop_thread) { + break; + } + + // Set up audio streaming + struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; + dvd_threaded_audio_config(lowmem->b_disk_info.audio_streaming, lowmem->b_disk_info.stream_buffer_size); + error = dvd_threaded_get_error(); + if (error != 0) { + disc_read_state = STATE_READ_ERROR; + finished_reading_disc = true; + continue; + } + + if (request_disc_stop_thread) { + break; + } + + // TODO: Run the apploader, if that's at all possible + + if (request_disc_stop_thread) { + break; + } + + // Get the banner + disc_game_info = get_game_info_with_open_game(fd, should_stop_disc_thread_worker); + if (!disc_game_info.valid) { + disc_read_state = STATE_READ_ERROR; + finished_reading_disc = true; + continue; + } + + if (request_disc_stop_thread) { + break; + } + + ret = dvd_threaded_read(stock_banner_ptr, sizeof(BNR), disc_game_info.bnr_offset, fd, should_stop_disc_thread_worker); + error = dvd_threaded_get_error(); + if (ret != 0 || error != 0) { + disc_read_state = STATE_READ_ERROR; + finished_reading_disc = true; + continue; + } + + disc_read_region = (char)disc_game_info.game_id[3]; + disc_read_banner_ready = true; + + // The disc's loaded! + finished_reading_disc = true; + disc_read_state = STATE_START_GAME; + } + + if (request_disc_stop_thread) { + dvd_threaded_stop_motor(); + dvd_custom_bypass_exit(); + } else { + bool ready_to_start = request_disc_start_game && finished_reading_disc && disc_read_state == STATE_START_GAME; + if (!ready_to_start) { + while (true); + } + } + + game_disc_running = false; + + return NULL; +} + void gm_init_thread() { OSInitMutex(game_enum_mutex); } @@ -973,8 +1119,8 @@ void gm_init_thread() { static OSThread thread_obj; static u8 thread_stack[32 * 1024]; // TODO: move to lowmem slab? void gm_start_thread(const char *target) { - if (game_enum_running) { - OSReport("ERROR: game enum thread is already running\n"); + if (game_enum_running || game_disc_running) { + OSReport("ERROR: game enum or disc thread is already running\n"); return; } @@ -1042,9 +1188,41 @@ void gm_start_thread(const char *target) { dolphin_OSResumeThread(&thread_obj); } +void gm_start_disc_thread() { + if (!is_disc_drive_allowed) { + OSReport("ERROR: attempted to start the disc thread, but disc drive access is disabled in config\n"); + disc_read_state = STATE_FATAL_ERROR; + return; + } + + if (game_enum_running || game_disc_running) { + OSReport("ERROR: game enum or disc thread is already running\n"); + return; + } + + OSReport("Starting game disc thread %s\n", path); + + game_disc_running = true; + DCBlockStore((void*)OSRoundDown32B((u32)&game_disc_running)); + + request_disc_stop_thread = false; + request_disc_start_game = false; + disc_read_state = STATE_WAIT_LOAD; + + // OSUnlockMutex(game_enum_mutex); + + // Start the thread + u32 thread_stack_size = sizeof(thread_stack); + void *thread_stack_top = thread_stack + thread_stack_size; + s32 thread_priority = DEFAULT_THREAD_PRIO + 3; + + dolphin_OSCreateThread(&thread_obj, gm_disc_thread_worker, NULL, thread_stack_top, thread_stack_size, thread_priority, 0); + dolphin_OSResumeThread(&thread_obj); +} + void gm_deinit_thread() { - if (game_enum_running) { + if (game_enum_running || game_disc_running) { OSReport("Stopping file enum\n"); OSLockMutex(game_enum_mutex); OSReport("Waiting for thread to exit, %d\n", game_enum_running); diff --git a/patches/source/games.h b/patches/source/games.h index 99c7e81..78233c0 100644 --- a/patches/source/games.h +++ b/patches/source/games.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "dolphin_os.h" #include "icon.h" @@ -8,6 +9,8 @@ #include "decomp_ar.h" +typedef struct dolphin_game_into_t dolphin_game_into_t; + // Backing typedef enum { GM_LOAD_STATE_NONE, @@ -107,10 +110,26 @@ extern char game_enum_path[]; extern gm_file_entry_t boot_entry; extern gm_file_entry_t second_boot_entry; +// For DVD-reading thread +extern atomic_bool request_disc_stop_thread; +extern atomic_bool request_disc_start_game; +extern atomic_uint disc_read_state; +extern atomic_bool disc_read_banner_ready; +extern atomic_char disc_read_region; + +// Only usable when `disc_read_banner_ready` is true and/or `disc_read_state` is `STATE_START_GAME` +extern dolphin_game_into_t disc_game_info; + +extern BNR* stock_banner_ptr; + +extern bool game_enum_running; +extern bool game_disc_running; + void gm_init_heap(); void gm_init_thread(); void gm_deinit_thread(); void gm_start_thread(const char *target); +void gm_start_disc_thread(); void gm_line_changed(int delta); bool gm_can_move(); gm_file_entry_t *gm_get_game_entry(int index); diff --git a/patches/source/gc_dvd.h b/patches/source/gc_dvd.h index 2f3ce8f..9b2f8b1 100644 --- a/patches/source/gc_dvd.h +++ b/patches/source/gc_dvd.h @@ -1,3 +1,4 @@ +int dvd_cover_status(); void dvd_reset(); unsigned int dvd_get_error(void); int dvd_read_id(); diff --git a/patches/source/main.c b/patches/source/main.c index f2d68fb..761607c 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -6,35 +6,23 @@ #include "attr.h" #include "util.h" #include "os.h" +#include "ipc.h" #include "usbgecko.h" #include "state.h" -#include "time.h" +#include "bs2.h" #include "reloc.h" #include "menu.h" #include "dolphin_arq.h" -#include "flippy_sync.h" -#include "gc_dvd.h" #include "games.h" #include "video.h" -#include "dol.h" -#include "boot.h" -#include "gameid.h" #define CUBE_TEX_WIDTH 84 #define CUBE_TEX_HEIGHT 84 -#define GAMECUBE_LOGO_WIDTH 352 -#define GAMECUBE_LOGO_HEIGHT 40 - -#define STATE_WAIT_LOAD 0x0f // delay after animation -#define STATE_START_GAME 0x10 // play full animation and start game -#define STATE_NO_DISC 0x12 // play full animation before menu -#define STATE_COVER_OPEN 0x13 // force direct to menu - // __attribute_data__ u32 prog_entrypoint; // __attribute_data__ u32 prog_dst; // __attribute_data__ u32 prog_src; @@ -47,20 +35,12 @@ __attribute_data__ static u8 *cube_text_tex = NULL; __attribute_data__ char cube_logo_path[MAX_FILE_NAME] = {0}; __attribute_data__ u32 force_progressive = 0; __attribute_data__ u32 force_widescreen = 0; -__attribute_data__ u32 force_swiss_boot = 0; // used if we are switching to 60Hz on a PAL IPL __attribute_data__ static int fix_pal_ntsc = 0; // used for optional delays __attribute_data__ u32 preboot_delay_ms = 0; -__attribute_data__ u32 postboot_delay_ms = 0; -__attribute_data__ u64 completed_time = 0; - -// used to start game -__attribute_reloc__ u32 (*PADSync)(); -__attribute_reloc__ void (*__OSStopAudioSystem)(); -// __attribute_reloc__ void (*run)(register void* entry_point, register u32 clear_start, register u32 clear_size); // for setup __attribute_reloc__ void (*orig_thread_init)(); @@ -371,16 +351,15 @@ __attribute_used__ void pre_thread_init() { gm_init_heap(); gm_init_thread(); - if (!start_passthrough_game) { - gm_start_thread("/"); - } + + bs2init(); } __attribute_used__ void pre_menu_init(int unk) { menu_init(unk); // change default menu - *prev_menu_id = MENU_GAMESELECT_TRANSITION_ID; + *next_menu_id = MENU_GAMESELECT_TRANSITION_ID; *cur_menu_id = MENU_GAMESELECT_ID; custom_gameselect_init(); @@ -460,114 +439,6 @@ __attribute_used__ u32 get_tvmode() { return rmode->viTVMode; } -__attribute_data__ int frame_count = 0; -__attribute_used__ u32 bs2tick() { - frame_count++; - if (!completed_time && cube_state->cube_anim_done) { - OSReport("FINISHED (%d frames)\n", frame_count); - completed_time = gettime(); - } - - if (start_passthrough_game) { - if (postboot_delay_ms) { - u64 elapsed = diff_msec(completed_time, gettime()); - if (completed_time > 0 && elapsed > postboot_delay_ms) { - return STATE_START_GAME; - } else { - return STATE_WAIT_LOAD; - } - } - return STATE_START_GAME; - } - - // this helps the start menu show correctly - if (*main_menu_id >= 3) { - return STATE_START_GAME; - } - -#ifdef TEST_SKIP_ANIMATION - return STATE_COVER_OPEN; -#endif - - // TODO: allow the user to decide if they want to logo to play - return STATE_NO_DISC; -} - -__attribute_used__ void bs2start() { - OSReport("DONE\n"); - - // read boot info into lowmem - struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; - - if (!start_passthrough_game) { - gm_deinit_thread(); - } else { - dvd_custom_bypass_enter(); - udelay(10 * 1000); - - int ret = dvd_read_id(); - int err = dvd_get_error(); - if (ret != 0 || err != 0) { - custom_OSReport("Failed to read disc ID\n"); - dvd_custom_bypass_exit(); - udelay(10 * 1000); - - load_stub(); // exit to loader again - u32 *sig = (u32*)0x80001804; - if ((*sig++ == 0x53545542 || *sig++ == 0x53545542) && *sig == 0x48415858) { - static void (*reload)(void) = (void(*)(void))0x80001800; - run(reload); - } - } - - custom_OSReport("Game ID: %c%c%c%c\n", lowmem->b_disk_info.game_code[0], lowmem->b_disk_info.game_code[1], lowmem->b_disk_info.game_code[2], lowmem->b_disk_info.game_code[3]); - dvd_audio_config(lowmem->b_disk_info.audio_streaming, lowmem->b_disk_info.stream_buffer_size); - - char diskName[64] = "DISC GAME\0"; - setup_gameid_commands(&lowmem->b_disk_info, diskName); - } - - // no IPL code should be running after this point - - while (!PADSync()); - OSDisableInterrupts(); - __OSStopAudioSystem(); - - u32 start_addr = 0x80100000; - u32 end_addr = 0x81600000; - u32 len = end_addr - start_addr; - - memset((void*)start_addr, 0, len); // cleanup - DCFlushRange((void*)start_addr, len); - ICInvalidateRange((void*)start_addr, len); - - // Passthrough mode - if (start_passthrough_game) { - chainload_boot_game(NULL, true); - } - - char *boot_path = boot_entry.path; - if (boot_entry.type == GM_FILE_TYPE_PROGRAM) { - custom_OSReport("Booting DOL\n"); - load_stub(); - - dol_info_t info = load_dol_file(boot_path, false); - run(info.entrypoint); - } else { - custom_OSReport("Booting ISO\n"); - - if (!force_swiss_boot) { - custom_OSReport("Booting ISO (custom apploader)\n"); - chainload_boot_game(&boot_entry, false); - } else { - custom_OSReport("Booting ISO (swiss chainload)\n"); - chainload_swiss_game(boot_path, false); - } - } - - __builtin_unreachable(); -} - void mega_trap(u32 r3, u32 r4, u32 r5, u32 r6) { u32 caller = (u32)__builtin_return_address(0); OSReport("[%08x] (r3=%08x r4=%08x r5=%08x, r6=%08x) You hit the mega trap dog\n", caller, r3, r4, r5, r6); diff --git a/patches/source/menu.c b/patches/source/menu.c index c21570f..e111f71 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -8,6 +8,9 @@ #include +#include "audio.h" +#include "bs2.h" +#include "draw.h" #include "usbgecko.h" #include "menu.h" #include "grid.h" @@ -26,6 +29,8 @@ #include "time.h" +#include "element_alpha.h" + // TODO: this is all zeros except for one BNRDesc, so replace it with a sparse version #include "default_opening_bin.h" #include "gcm.h" @@ -35,8 +40,6 @@ __attribute_reloc__ void (*menu_alpha_setup)(); // for custom menus -__attribute_reloc__ void (*prep_text_mode)(); -__attribute_reloc__ void (*gx_draw_text)(u16 index, text_group* text, text_draw_group* text_draw, GXColor* color); __attribute_reloc__ void (*setup_gameselect_menu)(u8 alpha_0, u8 alpha_1, u8 alpha_2); __attribute_reloc__ GXColorS10 *(*get_save_color)(u32 color_index, s32 save_type); __attribute_reloc__ void (*setup_gameselect_anim)(); @@ -44,29 +47,12 @@ __attribute_reloc__ void (*setup_cube_anim)(); __attribute_reloc__ model_data *save_icon; __attribute_reloc__ model_data *save_empty; -// for audio -__attribute_reloc__ void (*Jac_PlaySe)(u32); -__attribute_reloc__ void (*Jac_StopSoundAll)(); __attribute_reloc__ bool (*is_key_repeat)(); -// for model gx -__attribute_reloc__ void (*model_init)(model* m, int process); -__attribute_reloc__ void (*draw_model)(model* m); -__attribute_reloc__ void (*draw_partial)(model* m, model_part* part); -__attribute_reloc__ void (*change_model)(model* m); - // for menu elements -__attribute_reloc__ void (*draw_grid)(Mtx position, u8 alpha); -__attribute_reloc__ void (*draw_box)(u32 index, box_draw_group* header, GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); // __attribute_reloc__ void (*draw_start_info)(u8 alpha); __attribute_reloc__ void (*draw_start_anim)(u8 alpha); -__attribute_reloc__ void (*draw_blob_fixed)(void *blob_ptr, void *blob_a, void *blob_b, GXColor *color); -__attribute_reloc__ void (*draw_blob_text)(u32 type, void *blob, GXColor *color, char *str, s32 len); -__attribute_reloc__ void (*draw_blob_text_long)(u32 type, void *blob, GXColor *color, char *str, s32 len); -__attribute_reloc__ void (*draw_blob_border)(u32 type, void *blob, GXColor *color); -__attribute_reloc__ void (*draw_blob_tex)(u32 type, void *blob, GXColor *color, tex_data *dat); -__attribute_reloc__ void (*setup_tex_draw)(s32 unk0, s32 unk1, s32 unk2); -__attribute_reloc__ void (*draw_named_tex)(u32 type, void *blob, GXColor *color, s16 x, s16 y); +__attribute_reloc__ void *banner_element_alpha; // unknown blob (from memcard menu) __attribute_reloc__ void **ptr_menu_blob; @@ -74,22 +60,18 @@ __attribute_data__ void *menu_blob = NULL; // unknown blobs (from gameselect menu) __attribute_reloc__ void *game_blob_text; +__attribute_reloc__ void *game_blob_insert_disc; +__attribute_reloc__ void *game_blob_reading_disc; +__attribute_reloc__ void *game_blob_could_not_read_disc; __attribute_reloc__ void **ptr_game_blob_a; __attribute_data__ void *game_blob_a = NULL; __attribute_reloc__ void **ptr_game_blob_b; __attribute_data__ void *game_blob_b = NULL; -// for camera gx -__attribute_reloc__ void (*set_obj_pos)(model* m, MtxP matrix, guVector vector); -__attribute_reloc__ void (*set_obj_cam)(model* m, MtxP matrix); -__attribute_reloc__ MtxP (*get_camera_mtx)(); - // helpers -__attribute_reloc__ f32 (*fast_sin)(s16 deg); -__attribute_reloc__ f32 (*fast_cos)(s16 deg); __attribute_reloc__ void (*apply_save_rot)(s32 x, s32 y, s32 z, Mtx matrix); __attribute_reloc__ u32 *bs2start_ready; -__attribute_reloc__ u32 *banner_pointer; +__attribute_reloc__ const BNR **banner_pointer; __attribute_reloc__ u32 *banner_ready; typedef struct { @@ -111,6 +93,8 @@ typedef struct { static selected_mod_t selected_icon_mod; +static bool last_disc_ready_to_start = false; + // Define constants for max dimensions void setup_icon_positions(); @@ -201,7 +185,7 @@ void set_textured_icon_unselected() { __attribute_used__ void custom_gameselect_init() { // default banner - *banner_pointer = (u32)&default_opening_bin[0]; + *banner_pointer = (const BNR *)&default_opening_bin[0]; *banner_ready = 1; // menu setup @@ -573,6 +557,9 @@ void fix_gameselect_view() { } __attribute_data__ u32 current_gameselect_state = SUBMENU_GAMESELECT_LOADER; +u32 gameselect_menu_stack[] = { -1 }; +u32 gameselect_menu_stack_count = 0; + __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { // color u8 ui_alpha = alpha_1; @@ -697,26 +684,69 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 ui_alpha = alpha_1; GXColor white = {0xFF, 0xFF, 0xFF, ui_alpha}; - gm_file_entry_t *entry = gm_get_game_entry(selected_slot); - if (entry == NULL) return; // protect against transition during enum - if (entry->extra.game_id[3] == 'J') switch_lang_jpn(); + u16 banner_alpha = 0; + get_element_alpha(banner_element_alpha, &banner_alpha, NULL); + GXColor banner_white = {0xFF, 0xFF, 0xFF, (banner_alpha * ui_alpha) / 0xFF}; + + char game_region = '?'; + u8* pixelData = NULL; + BNRDesc *desc = NULL; + bool show_full_banner = true; + + switch (selected_device) { + case device_disc_drive: { + game_region = disc_read_region; + + bool is_valid_disc_bnr = strncmp(stock_banner_ptr->magic, "BNR1", 4) || strncmp(stock_banner_ptr->magic, "BNR2", 4); + + if (is_valid_disc_bnr) { + pixelData = stock_banner_ptr->pixelData; + + int language = 0; + if (stock_banner_ptr->magic[3] == '2' /*&& is_pal_console */) { + // BNR2 banners support multiple PAL languages, so use the appropriate one for this PAL console + // TODO: How do we get the current PAL language? + } + desc = &stock_banner_ptr->desc[language]; + } + break; + } + case device_flippydrive: { + gm_file_entry_t *entry = gm_get_game_entry(selected_slot); + if (entry == NULL) return; // protect against transition during enum + + game_region = entry->extra.game_id[3]; + + if (entry->type == GM_FILE_TYPE_GAME && entry->asset.banner.state == GM_LOAD_STATE_LOADED) { + pixelData = entry->asset.banner.buf->data; + } + + desc = &entry->desc; + show_full_banner = entry->type == GM_FILE_TYPE_GAME; + break; + } + } + + if (game_region == 'J') switch_lang_jpn(); else switch_lang_eng(); - if (entry->type == GM_FILE_TYPE_GAME && entry->asset.banner.state == GM_LOAD_STATE_LOADED) { + if (pixelData) { // game banner setup_tex_draw(1, 0, 1); - banner_texture.offset = (s32)((u32)(entry->asset.banner.buf->data) - (u32)&banner_texture); - draw_blob_tex(make_type('b','a','n','a'), game_blob_b, &white, &banner_texture); + banner_texture.offset = (s32)((u32)(pixelData) - (u32)&banner_texture); + draw_blob_tex(make_type('b','a','n','a'), game_blob_b, &banner_white, &banner_texture); } // game info prep_text_mode(); - draw_blob_text(make_type('t','i','t','l'), game_blob_b, &white, entry->desc.fullGameName, 0x40); - if (entry->type == GM_FILE_TYPE_GAME) { - draw_blob_text(make_type('m','a','k','r'), game_blob_b, &white, entry->desc.fullCompany, 0x40); - draw_blob_text_long(make_type('i','n','f','o'), game_blob_b, &white, entry->desc.description, 0x80); - } else { - draw_blob_text(make_type('m','a','k','r'), game_blob_b, &white, entry->desc.description, 0x40); + if (desc) { + draw_blob_text(make_type('t','i','t','l'), game_blob_b, &banner_white, desc->fullGameName, 0x40); + if (show_full_banner) { + draw_blob_text(make_type('m','a','k','r'), game_blob_b, &banner_white, desc->fullCompany, 0x40); + draw_blob_text_long(make_type('i','n','f','o'), game_blob_b, &banner_white, desc->description, 0x80); + } else { + draw_blob_text(make_type('m','a','k','r'), game_blob_b, &banner_white, desc->description, 0x40); + } } // press start anim @@ -729,9 +759,48 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, switch_lang_orig(); draw_blob_fixed(game_blob_text, game_blob_a, game_blob_b, &white); + // Messages relating to reading a disc + if (selected_device == device_disc_drive) { + draw_blob_fixed(game_blob_insert_disc, game_blob_a, game_blob_b, &white); + draw_blob_fixed(game_blob_reading_disc, game_blob_a, game_blob_b, &white); + draw_blob_fixed(game_blob_could_not_read_disc, game_blob_a, game_blob_b, &white); + } return; } +static void on_submenu_shown() { + switch (current_gameselect_state) { + case SUBMENU_GAMESELECT_LOADER: + break; + + case SUBMENU_GAMESELECT_START: + setup_gameselect_anim(); + setup_cube_anim(); + + switch (selected_device) { + case device_disc_drive: + { + bool ready_to_start = disc_read_state == STATE_START_GAME; + if (ready_to_start) { + // TODO: Get the correct BNRDesc for the current language + mcp_set_gameid_for_disc(&disc_game_info, &stock_banner_ptr->desc[0]); + } + last_disc_ready_to_start = ready_to_start; + break; + } + case device_flippydrive: + { + gm_file_entry_t *entry = gm_get_game_entry(selected_slot); + if (entry && entry->type == GM_FILE_TYPE_GAME) { + mcp_set_gameid(entry); + } + break; + } + } + break; + } +} + static bool first_transition = true; static bool in_submenu_transition = false; static u8 custom_menu_transition_alpha = 0xFF; @@ -739,9 +808,28 @@ static u8 original_menu_transition_alpha = 0; __attribute_used__ void pre_menu_alpha_setup() { menu_alpha_setup(); // run original function - if (*cur_menu_id == MENU_GAMESELECT_ID && *prev_menu_id == MENU_GAMESELECT_TRANSITION_ID) { - OSReport("Resetting back to SUBMENU_GAMESELECT_LOADER\n"); - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; + if (*cur_menu_id == MENU_GAMESELECT_ID && *next_menu_id == MENU_GAMESELECT_TRANSITION_ID) { + switch (selected_device) { + case device_disc_drive: + OSReport("Resetting back to SUBMENU_GAMESELECT_START\n"); + current_gameselect_state = SUBMENU_GAMESELECT_START; + break; + + case device_flippydrive: + OSReport("Resetting back to SUBMENU_GAMESELECT_LOADER\n"); + current_gameselect_state = SUBMENU_GAMESELECT_LOADER; + break; + } + + gameselect_menu_stack_count = 0; + + custom_menu_transition_alpha = current_gameselect_state == SUBMENU_GAMESELECT_LOADER ? 0xFF : 0; + original_menu_transition_alpha = current_gameselect_state == SUBMENU_GAMESELECT_START ? 0xFF : 0; + + // Force the game ID to be re-sent, in case the disc has changed since the menu was last opened + last_disc_ready_to_start = false; + + on_submenu_shown(); if (first_transition) { Jac_PlaySe(SOUND_MENU_ENTER); @@ -760,7 +848,8 @@ __attribute_used__ void mod_gameselect_draw(u8 alpha_0, u8 alpha_1, u8 alpha_2) u8 original_alpha_1 = original_menu_transition_alpha; if (alpha_1 != 0xFF) { - custom_alpha_1 = alpha_1; + custom_alpha_1 = ((int)custom_alpha_1 * alpha_1) / 0xFF; + original_alpha_1 = ((int)original_alpha_1 * alpha_1) / 0xFF; } if (custom_alpha_1 != 0) custom_gameselect_menu(alpha_0, custom_alpha_1, alpha_2); @@ -769,6 +858,46 @@ __attribute_used__ void mod_gameselect_draw(u8 alpha_0, u8 alpha_1, u8 alpha_2) return; } +static void push_menu_stack(u32 new_state) { + if (gameselect_menu_stack_count >= countof(gameselect_menu_stack)) { + // Out of menu stack space + return; + } + + Jac_PlaySe(SOUND_SUBMENU_CONFIRM); + + gameselect_menu_stack[gameselect_menu_stack_count] = current_gameselect_state; + gameselect_menu_stack_count += 1; + current_gameselect_state = new_state; + + in_submenu_transition = true; + + // Run code when transitioning to the new menu + on_submenu_shown(); +} + +static bool pop_menu_stack() { + Jac_PlaySe(SOUND_MENU_EXIT); + + if (gameselect_menu_stack_count > 0) { + gameselect_menu_stack_count -= 1; + current_gameselect_state = gameselect_menu_stack[gameselect_menu_stack_count]; + + in_submenu_transition = true; + return false; + + } else { + anim_step = 0; // anim reset + if (selected_device == device_flippydrive) { + // banner reset - only relevant for the FlippyDrive (the disc drive always uses a single banner) + *banner_pointer = (const BNR *)&default_opening_bin[0]; + } + return true; + } +} + +static bool starting_game = false; + __attribute_used__ s32 handle_gameselect_inputs() { update_icon_positions(); grid_update_icon_positions(); @@ -813,21 +942,17 @@ __attribute_used__ s32 handle_gameselect_inputs() { } if (pad_status->buttons_down & PAD_BUTTON_B) { - if (current_gameselect_state == SUBMENU_GAMESELECT_START && !in_submenu_transition) { - in_submenu_transition = true; - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; - Jac_PlaySe(SOUND_SUBMENU_EXIT); - } else if (!in_submenu_transition) { - // TODO: check current path depth - if (strcmp(game_enum_path, "/") != 0) { + if (!in_submenu_transition) { + if (current_gameselect_state == SUBMENU_GAMESELECT_LOADER && strcmp(game_enum_path, "/") != 0) { gm_deinit_thread(); Jac_PlaySe(SOUND_MENU_EXIT); gm_start_thread(".."); + } else { - anim_step = 0; // anim reset - *banner_pointer = (u32)&default_opening_bin[0]; // banner reset - Jac_PlaySe(SOUND_MENU_EXIT); - return MENU_GAMESELECT_ID; + if (pop_menu_stack()) { + // If we're reached the end of the stack, then need to return to the menu + return MENU_GAMESELECT_ID; + } } } } @@ -839,22 +964,13 @@ __attribute_used__ s32 handle_gameselect_inputs() { OSReport("Selected DIR slot: %d (%p)\n", selected_slot, entry); gm_deinit_thread(); - Jac_PlaySe(SOUND_SUBMENU_ENTER); + Jac_PlaySe(SOUND_SUBMENU_CONFIRM); char path[128]; sprintf(path, "%s/", entry->path); gm_start_thread(path); } else { - in_submenu_transition = true; - current_gameselect_state = SUBMENU_GAMESELECT_START; - - Jac_PlaySe(SOUND_SUBMENU_ENTER); - setup_gameselect_anim(); - setup_cube_anim(); - - if (entry->type == GM_FILE_TYPE_GAME) { - mcp_set_gameid(entry); - } + push_menu_stack(SUBMENU_GAMESELECT_START); // OSReport("Selected slot: %d (%p)\n", selected_slot, asset); } @@ -865,16 +981,51 @@ __attribute_used__ s32 handle_gameselect_inputs() { // ... // } - if (pad_status->buttons_down & PAD_BUTTON_START && current_gameselect_state == SUBMENU_GAMESELECT_START) { - Jac_StopSoundAll(); - Jac_PlaySe(SOUND_MENU_FINAL); - gm_file_entry_t *entry = gm_get_game_entry(selected_slot); - memcpy(&boot_entry, entry, sizeof(gm_file_entry_t)); - if (boot_entry.second != NULL) { - memcpy(&second_boot_entry, boot_entry.second, sizeof(gm_file_entry_t)); - boot_entry.second = &second_boot_entry; + + if (current_gameselect_state == SUBMENU_GAMESELECT_START) { + bool ready_to_start = false; + gm_file_entry_t *entry = NULL; + switch (selected_device) { + case device_disc_drive: + ready_to_start = disc_read_state == STATE_START_GAME; + + if (ready_to_start && !last_disc_ready_to_start) { + // TODO: Get the correct BNRDesc for the current language + mcp_set_gameid_for_disc(&disc_game_info, &stock_banner_ptr->desc[0]); + } + + last_disc_ready_to_start = ready_to_start; + break; + case device_flippydrive: + entry = gm_get_game_entry(selected_slot); + ready_to_start = entry != NULL; + break; + } + + if (!*bs2start_ready) { + if (starting_game) { + // Disc startup has been interrupted (e.g. disc cover opened during animation) + // We have to restart the background music that we previously stopped + Jac_PlayBgm(0); + starting_game = false; + } + + if (pad_status->buttons_down & PAD_BUTTON_START && ready_to_start) { + Jac_StopSoundAll(); + Jac_PlaySe(SOUND_MENU_FINAL); + + if (selected_device == device_flippydrive) { + memcpy(&boot_entry, entry, sizeof(gm_file_entry_t)); + if (boot_entry.second != NULL) { + memcpy(&second_boot_entry, boot_entry.second, sizeof(gm_file_entry_t)); + boot_entry.second = &second_boot_entry; + } + } + + *bs2start_ready = 1; + starting_game = true; + } } - *bs2start_ready = 1; } if (current_gameselect_state == SUBMENU_GAMESELECT_LOADER) { diff --git a/patches/source/menu.h b/patches/source/menu.h index ffd7a9f..9805270 100644 --- a/patches/source/menu.h +++ b/patches/source/menu.h @@ -17,7 +17,8 @@ #define SAVE_EMPTY_SEL 2 #define SAVE_EMPTY 3 -#define SOUND_SUBMENU_ENTER 0x0c +#define SOUND_SUBMENU_ENTER 0x08 +#define SOUND_SUBMENU_CONFIRM 0x0c #define SOUND_SUBMENU_EXIT 0x07 #define SOUND_MENU_ENTER 0x05 #define SOUND_MENU_EXIT 0x06 diff --git a/patches/source/patch.s b/patches/source/patch.s index 22d128f..8b7958c 100644 --- a/patches/source/patch.s +++ b/patches/source/patch.s @@ -115,3 +115,18 @@ patch_inst_pal "_patch_menu_alpha_setup" 0x81312c3c 0x81312284 0x81312d7c bl pre patch_inst_pal "_fix_video_mode_init" 0x81300520 0x81300520 0x81300610 bl get_tvmode patch_inst_global "_patch_pre_main" 0x81300090 bl pre_main + +patch_inst_ntsc "_patch_draw_buttons" 0x813149b8 0x81314d30 0x813150c8 0x813150e0 bl patch_draw_buttons +patch_inst_pal "_patch_draw_buttons" 0x813156b4 0x81314c5c 0x813157f4 bl patch_draw_buttons + +patch_inst_ntsc "_patch_update_button_alphas" 0x81312104 0x81312354 0x813126ec 0x81312704 bl patch_update_button_alphas +patch_inst_pal "_patch_update_button_alphas" 0x81312c38 0x81312280 0x81312d78 bl patch_update_button_alphas + +// Add a hook for handling additional inputs on the top-level menu +.macro insert_top_level_menu_extra_inputs + bl top_level_menu_extra_inputs // Where the next instruction (loading return address) previously was + lwz r0, 0x0014 (sp) // Was `li r3, 0`, which is never read +.endm + +patch_inst_ntsc "_patch_top_level_extra_inputs" 0x81311250 0x813114a0 0x81311838 0x81311850 insert_top_level_menu_extra_inputs +patch_inst_pal "_patch_top_level_extra_inputs" 0x81311d3c 0x813113cc 0x81311e78 insert_top_level_menu_extra_inputs diff --git a/patches/source/reloc.c b/patches/source/reloc.c index 00dd801..f387f27 100644 --- a/patches/source/reloc.c +++ b/patches/source/reloc.c @@ -10,7 +10,7 @@ __attribute_reloc__ void (*OSReport)(const char* text, ...); #endif __attribute_reloc__ bios_pad *pad_status; -__attribute_reloc__ u32 *prev_menu_id; +__attribute_reloc__ u32 *next_menu_id; __attribute_reloc__ u32 *cur_menu_id; __attribute_reloc__ u32 *main_menu_id; __attribute_reloc__ GXRModeObj *rmode; diff --git a/patches/source/reloc.h b/patches/source/reloc.h index e74a5a8..25ddd0c 100644 --- a/patches/source/reloc.h +++ b/patches/source/reloc.h @@ -12,7 +12,7 @@ extern void (*OSReport)(const char* text, ...); extern void custom_OSReport(const char *fmt, ...); extern bios_pad *pad_status; -extern u32 *prev_menu_id; +extern u32 *next_menu_id; extern u32 *cur_menu_id; extern u32 *main_menu_id; extern GXRModeObj *rmode;