From ae8ef99a7f78e885056a9ba522e2044579a86df2 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 7 Jul 2025 21:04:42 +0100 Subject: [PATCH 01/31] Explicitly build with C23 Among other things, this ensures that function declarations like `void foo();` are treated as having no parameters, regardless of GCC version. --- patches/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/Makefile b/patches/Makefile index 2baccfb..f8a7f12 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 := From fe7a7aae1d0b3a48645611ebf1120f1948410b81 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 17 May 2025 20:09:47 +0100 Subject: [PATCH 02/31] First pass at reading discs, load banner into menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This creates a thread that handles disc access, including adapting to the user opening the lid, and either changing disc or having no disc inserted. This state then gets reported to the GameCube menu as per the stock IPL. So far, it’ll just load enough of the disc to load the banner into the ‘game play’ menu. --- patches/linker/link_ntsc10.ld | 2 + patches/linker/link_ntsc11.ld | 2 + patches/linker/link_ntsc12_001.ld | 2 + patches/linker/link_ntsc12_101.ld | 2 + patches/linker/link_pal10.ld | 2 + patches/linker/link_pal11.ld | 2 + patches/linker/link_pal12.ld | 2 + patches/source/dolphin_dvd.c | 58 ++++++----- patches/source/dolphin_dvd.h | 1 + patches/source/dvd_threaded.c | 45 +++++++++ patches/source/dvd_threaded.h | 3 + patches/source/games.c | 154 +++++++++++++++++++++++++++++- patches/source/games.h | 9 ++ patches/source/gc_dvd.h | 1 + patches/source/main.c | 23 ++++- patches/source/menu.c | 6 +- 16 files changed, 273 insertions(+), 41 deletions(-) diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index d054458..8563015 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -104,3 +104,5 @@ ntsc10_EXIDeselect = 0x81335838; ntsc10_EXISync = 0x81335134; ntsc10_EXIImm = 0x81334d4c; ntsc10_EXIImmEx = 0x81334fa8; + +ntsc10_stock_banner_ptr = 0x80af8440; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index f4c171b..fec880d 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -104,3 +104,5 @@ ntsc11_EXIDeselect = 0x8135b298; ntsc11_EXISync = 0x8135ab5c; ntsc11_EXIImm = 0x8135a774; ntsc11_EXIImmEx = 0x8135a9d0; + +ntsc11_stock_banner_ptr = 0x809f8440; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index 0114570..e334c2f 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -99,3 +99,5 @@ ntsc12_001_EXIDeselect = 0x81364790; ntsc12_001_EXISync = 0x81363f4c; ntsc12_001_EXIImm = 0x81363b64; ntsc12_001_EXIImmEx = 0x81363dc0; + +ntsc12_001_stock_banner_ptr = 0x809f8440; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index 2161f9d..54c0885 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -99,3 +99,5 @@ ntsc12_101_EXIDeselect = 0x813648f4; ntsc12_101_EXISync = 0x813640b0; ntsc12_101_EXIImm = 0x81363cc8; ntsc12_101_EXIImmEx = 0x81363f24; + +ntsc12_101_stock_banner_ptr = 0x809f8440; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index 76ec3d7..c389319 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -101,3 +101,5 @@ pal10_EXIDeselect = 0x8135e878; pal10_EXISync = 0x8135e13c; pal10_EXIImm = 0x8135dd54; pal10_EXIImmEx = 0x8135dfb0; + +pal10_stock_banner_ptr = 0x80a7d4a0; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index 5a070fb..09b39fd 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -99,3 +99,5 @@ pal11_EXIDeselect = 0x8135b1b8; pal11_EXISync = 0x8135aa7c; pal11_EXIImm = 0x8135a694; pal11_EXIImmEx = 0x8135a8f0; + +pal11_stock_banner_ptr = 0x809efb80; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index 9e4985d..2b11add 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -100,3 +100,5 @@ pal12_EXIDeselect = 0x81367c6c; pal12_EXISync = 0x81367428; pal12_EXIImm = 0x81367040; pal12_EXIImmEx = 0x8136729c; + +pal12_stock_banner_ptr = 0x80a7d4a0; diff --git a/patches/source/dolphin_dvd.c b/patches/source/dolphin_dvd.c index ad72769..4a2c0a3 100644 --- a/patches/source/dolphin_dvd.c +++ b/patches/source/dolphin_dvd.c @@ -83,37 +83,18 @@ static bnr_info_t get_banner_offset_slow(DiskHeader *header, uint32_t fd) { } // 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) { __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 + dvd_threaded_read(&header, sizeof(DiskHeader), 0, fd); //Read in the disc header // 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 + dvd_threaded_read(small_buf, 32, fast_bnr_offset, fd); //Read in the banner data u32 magic = small_buf[0]; if (magic == BANNER_MAGIC_1 || magic == BANNER_MAGIC_2) { @@ -128,8 +109,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 +117,13 @@ 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); if (bnr_info.offset != 0) { - dvd_threaded_read(small_buf, 32, bnr_info.offset, status->fd); //Read in the banner data + dvd_threaded_read(small_buf, 32, bnr_info.offset, fd); //Read in the banner data u32 magic = small_buf[0]; if (magic == BANNER_MAGIC_1 || magic == BANNER_MAGIC_2) { @@ -160,8 +138,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; } } @@ -169,6 +145,28 @@ dolphin_game_into_t get_game_info(char *game_path) { // OSReport("DEBUG: FST was loaded\n"); // invalid file - dvd_custom_close(status->fd); return (dolphin_game_into_t) { .valid = false }; } + +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); + dvd_custom_close(status->fd); + return game_info; +} diff --git a/patches/source/dolphin_dvd.h b/patches/source/dolphin_dvd.h index 8ceb31d..d98b9f9 100644 --- a/patches/source/dolphin_dvd.h +++ b/patches/source/dolphin_dvd.h @@ -122,6 +122,7 @@ typedef struct { _Static_assert(sizeof(dolphin_game_into_t) == 32); +dolphin_game_into_t get_game_info_with_open_game(u8 fd); dolphin_game_into_t get_game_info(char *game_path); #endif diff --git a/patches/source/dvd_threaded.c b/patches/source/dvd_threaded.c index 122f852..fe42cb1 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,6 +28,7 @@ #define DI_CFG 9 // 0xCC006024 - DI Configuration Register #define DVD_OEM_READ 0xA8000000 +#define DVD_OEM_ERROR 0xE0000000 static vu32* const _di_regs = (vu32*)0xCC006000; @@ -57,3 +59,46 @@ int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int } return 0; } + +int dvd_threaded_read_id() { + _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) { + OSYieldThread(); + } + + // check if ERR was asserted + if (_di_regs[DI_SR] & DI_SR_DEINT) { + return 1; + } + return 0; +} + +unsigned int dvd_threaded_get_error(void) { + _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_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..bb441fd 100644 --- a/patches/source/dvd_threaded.h +++ b/patches/source/dvd_threaded.h @@ -2,3 +2,6 @@ #include int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd); +int dvd_threaded_read_id(); +unsigned int dvd_threaded_get_error(void); +void dvd_threaded_reset(); diff --git a/patches/source/games.c b/patches/source/games.c index 42e2b0f..18d46b5 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -23,8 +23,9 @@ #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 "gc_dvd.h" #include "metaphrasis.h" @@ -48,6 +49,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; // TODO: use a log2 malloc copy strategy for this __attribute_data_lowmem__ static gm_path_entry_t __gm_early_path_list[2000]; @@ -56,6 +58,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]; @@ -960,6 +964,127 @@ void *gm_thread_worker(void* param) { return NULL; } +// Stops the disc-reading loop, so we can switch to the FlippyDrive +static atomic_bool request_disc_stop = false; + +// Stops the disc-reading loop, so we can start the loaded disc +static atomic_bool request_disc_start = false; + +#define STATE_WAIT_LOAD 0x0f +#define STATE_START_GAME 0x10 +#define STATE_NO_DISC 0x12 +#define STATE_COVER_OPEN 0x13 +#define STATE_READ_ERROR 0x16 +#define STATE_FATAL_ERROR 0x17 + +#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 = '?'; + +void *gm_disc_thread_worker(void *param) { + disc_read_state = STATE_WAIT_LOAD; + + dvd_custom_bypass_enter(); + udelay_threaded(10 * 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 && !request_disc_start) { + 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(); + 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; + // TODO: Spin down the disc? + continue; + } + + if (request_disc_stop) { + break; + } + + // TODO: Run the apploader + + if (request_disc_stop) { + break; + } + + // TODO: Get the banner + dolphin_game_into_t game_info = get_game_info_with_open_game(fd); + if (!game_info.valid) { + disc_read_state = STATE_READ_ERROR; + finished_reading_disc = true; + // TODO: Spin down the disc? + continue; + } + + if (request_disc_stop) { + break; + } + + dvd_threaded_read(stock_banner_ptr, sizeof(BNR), game_info.bnr_offset, fd); + disc_read_region = (char)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) { + // TODO: Should we spin down the disc? + dvd_custom_bypass_exit(); + } else { + bool ready_to_start = request_disc_start && finished_reading_disc && disc_read_state == STATE_START_GAME; + if (!ready_to_start) { + while (true); + } + } + + return NULL; +} + void gm_init_thread() { OSInitMutex(game_enum_mutex); } @@ -968,8 +1093,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; } @@ -1037,6 +1162,28 @@ void gm_start_thread(const char *target) { dolphin_OSResumeThread(&thread_obj); } +void gm_start_disc_thread() { + 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)); + + // 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) { @@ -1047,5 +1194,6 @@ void gm_deinit_thread() { OSReport("File enum done\n"); OSUnlockMutex(game_enum_mutex); } + // TODO: Handle the disc thread! } diff --git a/patches/source/games.h b/patches/source/games.h index 99c7e81..8a9a9ce 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" @@ -107,10 +108,18 @@ 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_uint disc_read_state; +extern atomic_bool disc_read_banner_ready; +extern atomic_char disc_read_region; + +extern BNR* stock_banner_ptr; + 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 96bfe84..846ce35 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -353,17 +353,18 @@ __attribute_used__ void pre_thread_init() { gm_init_heap(); gm_init_thread(); - if (!start_passthrough_game) { - gm_start_thread("/"); - } + gm_start_disc_thread(); + // if (!start_passthrough_game) { + // gm_start_thread("/"); + // } } __attribute_used__ void pre_menu_init(int unk) { menu_init(unk); // change default menu - *prev_menu_id = MENU_GAMESELECT_TRANSITION_ID; - *cur_menu_id = MENU_GAMESELECT_ID; + // *prev_menu_id = MENU_GAMESELECT_TRANSITION_ID; + // *cur_menu_id = MENU_GAMESELECT_ID; custom_gameselect_init(); @@ -430,8 +431,20 @@ __attribute_used__ u32 get_tvmode() { return rmode->viTVMode; } +extern u32 *banner_ready; +extern const BNR **banner_pointer; + __attribute_data__ int frame_count = 0; __attribute_used__ u32 bs2tick() { + // If the disc thread is running, do things relating to it + // TODO: Make this conditional! + *banner_ready = disc_read_banner_ready; + *banner_pointer = stock_banner_ptr; + return disc_read_state; + + + + frame_count++; if (!completed_time && cube_state->cube_anim_done) { OSReport("FINISHED (%d frames)\n", frame_count); diff --git a/patches/source/menu.c b/patches/source/menu.c index 8c7679b..f3126f9 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -87,7 +87,7 @@ __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 { @@ -199,7 +199,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 @@ -770,7 +770,7 @@ __attribute_used__ s32 handle_gameselect_inputs() { gm_start_thread(".."); } else { anim_step = 0; // anim reset - *banner_pointer = (u32)&default_opening_bin[0]; // banner reset + *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset Jac_PlaySe(SOUND_MENU_EXIT); return MENU_GAMESELECT_ID; } From b3d1dbb726db8d72c5797cd0c0b58972d492fba9 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Thu, 22 May 2025 22:19:12 +0100 Subject: [PATCH 03/31] Surround my temporary changes with a `#if` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I’ve made some temporary changes that essentially replace the FlippyDrive with the disc. This `#define` and these `#if`s should make it a bit clearer what bits of the code need tidying up (once I get to that stage!). --- patches/source/main.c | 17 ++++++++++++----- patches/source/temp_test_disc.h | 3 +++ 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 patches/source/temp_test_disc.h diff --git a/patches/source/main.c b/patches/source/main.c index 846ce35..dc713fe 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -24,6 +24,8 @@ #include "boot.h" #include "gameid.h" +#include "temp_test_disc.h" + #define CUBE_TEX_WIDTH 84 #define CUBE_TEX_HEIGHT 84 @@ -353,18 +355,21 @@ __attribute_used__ void pre_thread_init() { gm_init_heap(); gm_init_thread(); +#if !TEMP_TEST_DISC + if (!start_passthrough_game) { + gm_start_thread("/"); + } +#else gm_start_disc_thread(); - // if (!start_passthrough_game) { - // gm_start_thread("/"); - // } +#endif } __attribute_used__ void pre_menu_init(int unk) { menu_init(unk); // change default menu - // *prev_menu_id = MENU_GAMESELECT_TRANSITION_ID; - // *cur_menu_id = MENU_GAMESELECT_ID; + *prev_menu_id = MENU_GAMESELECT_TRANSITION_ID; + *cur_menu_id = MENU_GAMESELECT_ID; custom_gameselect_init(); @@ -436,11 +441,13 @@ extern const BNR **banner_pointer; __attribute_data__ int frame_count = 0; __attribute_used__ u32 bs2tick() { +#if TEMP_TEST_DISC // If the disc thread is running, do things relating to it // TODO: Make this conditional! *banner_ready = disc_read_banner_ready; *banner_pointer = stock_banner_ptr; return disc_read_state; +#endif diff --git a/patches/source/temp_test_disc.h b/patches/source/temp_test_disc.h new file mode 100644 index 0000000..772fd2a --- /dev/null +++ b/patches/source/temp_test_disc.h @@ -0,0 +1,3 @@ +#pragma once + +#define TEMP_TEST_DISC 1 From d9dca51cb34e587f562edade167a236eb3ac81c9 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Thu, 22 May 2025 22:54:08 +0100 Subject: [PATCH 04/31] Show the disc within the gameplay menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This replaces the FlippyDrive ‘loader’ menu with the standard ‘press start’ screen again, and displays the disc’s state, including any ‘insert a disc’ or ‘loading disc’ messages, and the loaded disc’s banner. --- patches/linker/link_ntsc10.ld | 6 ++ patches/linker/link_ntsc11.ld | 6 ++ patches/linker/link_ntsc12_001.ld | 6 ++ patches/linker/link_ntsc12_101.ld | 6 ++ patches/linker/link_pal10.ld | 6 ++ patches/linker/link_pal11.ld | 6 ++ patches/linker/link_pal12.ld | 6 ++ patches/source/menu.c | 108 +++++++++++++++++++++++++++--- 8 files changed, 140 insertions(+), 10 deletions(-) diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index 8563015..4c20e57 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -64,6 +64,9 @@ 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_cur_menu_id = 0x8145d70c; @@ -106,3 +109,6 @@ ntsc10_EXIImm = 0x81334d4c; ntsc10_EXIImmEx = 0x81334fa8; ntsc10_stock_banner_ptr = 0x80af8440; + +ntsc10_get_element_alpha = 0x8130ab40; +ntsc10_banner_element_alpha = 0x8148dd88; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index fec880d..830a9f9 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -60,6 +60,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; @@ -106,3 +109,6 @@ ntsc11_EXIImm = 0x8135a774; ntsc11_EXIImmEx = 0x8135a9d0; ntsc11_stock_banner_ptr = 0x809f8440; + +ntsc11_get_element_alpha = 0x8130aa4c; +ntsc11_banner_element_alpha = 0x814b25b8; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index e334c2f..f05efad 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -58,6 +58,9 @@ 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; @@ -101,3 +104,6 @@ ntsc12_001_EXIImm = 0x81363b64; ntsc12_001_EXIImmEx = 0x81363dc0; ntsc12_001_stock_banner_ptr = 0x809f8440; + +ntsc12_001_get_element_alpha = 0x8130adc0; +ntsc12_001_banner_element_alpha = 0x814b3b98; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index 54c0885..8caa4bc 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -58,6 +58,9 @@ 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; @@ -101,3 +104,6 @@ ntsc12_101_EXIImm = 0x81363cc8; ntsc12_101_EXIImmEx = 0x81363f24; ntsc12_101_stock_banner_ptr = 0x809f8440; + +ntsc12_101_get_element_alpha = 0x8130add8; +ntsc12_101_banner_element_alpha = 0x814b4038; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index c389319..02e3cd7 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -66,6 +66,9 @@ pal10_ptr_menu_blob = 0x814ad484; pal10_ptr_game_blob_b = 0x814ad570; pal10_banner_pointer = 0x814ad2f0; pal10_game_blob_text = 0x814de4c8; +pal10_game_blob_insert_disc = 0x814de4d8; +pal10_game_blob_reading_disc = 0x814de4f8; +pal10_game_blob_could_not_read_disc = 0x814de508; pal10_prev_menu_id = 0x814ad3f8; pal10_save_empty = 0x814ca968; @@ -103,3 +106,6 @@ pal10_EXIImm = 0x8135dd54; pal10_EXIImmEx = 0x8135dfb0; pal10_stock_banner_ptr = 0x80a7d4a0; + +pal10_get_element_alpha = 0x8130a964; +pal10_banner_element_alpha = 0x814de4b8; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index 09b39fd..602b0f4 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -58,6 +58,9 @@ 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; @@ -101,3 +104,6 @@ pal11_EXIImm = 0x8135a694; pal11_EXIImmEx = 0x8135a8f0; pal11_stock_banner_ptr = 0x809efb80; + +pal11_get_element_alpha = 0x8130aa4c; +pal11_banner_element_alpha = 0x814ad278; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index 2b11add..563c7e4 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -59,6 +59,9 @@ 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; @@ -102,3 +105,6 @@ pal12_EXIImm = 0x81367040; pal12_EXIImmEx = 0x8136729c; pal12_stock_banner_ptr = 0x80a7d4a0; + +pal12_get_element_alpha = 0x8130aaa4; +pal12_banner_element_alpha = 0x814dfc78; diff --git a/patches/source/menu.c b/patches/source/menu.c index f3126f9..863a853 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -30,6 +30,8 @@ #include "gcm.h" #include "bnr.h" +#include "temp_test_disc.h" + // for setup __attribute_reloc__ void (*menu_alpha_setup)(); @@ -65,6 +67,8 @@ __attribute_reloc__ void (*draw_blob_border)(u32 type, void *blob, GXColor *colo __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 (*get_element_alpha)(void *element, u16 *alpha, u32 *unk); +__attribute_reloc__ void *banner_element_alpha; // unknown blob (from memcard menu) __attribute_reloc__ void **ptr_menu_blob; @@ -72,6 +76,9 @@ __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; @@ -517,7 +524,11 @@ void fix_gameselect_view() { GXSetCurrentMtx(0); } +#if !TEMP_TEST_DISC __attribute_data__ u32 current_gameselect_state = SUBMENU_GAMESELECT_LOADER; +#else +__attribute_data__ u32 current_gameselect_state = SUBMENU_GAMESELECT_START; +#endif __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { // color u8 ui_alpha = alpha_1; @@ -638,30 +649,83 @@ __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 } __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { +#if TEMP_TEST_DISC + // TODO: I don't think this is necessary - check! + static bool first_draw = true; + if (first_draw) { + first_draw = false; + setup_gameselect_anim(); + setup_cube_anim(); + } +#endif + // menu alpha u8 ui_alpha = alpha_1; GXColor white = {0xFF, 0xFF, 0xFF, ui_alpha}; + u16 banner_alpha = 0; + get_element_alpha(banner_element_alpha, &banner_alpha, NULL); + GXColor banner_white = {0xFF, 0xFF, 0xFF, (banner_alpha * ui_alpha) / 0xFF}; + +#if !TEMP_TEST_DISC 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(); + + char game_region = entry->extra.game_id[3]; +#else + char game_region = disc_read_region; +#endif + if (game_region == 'J') switch_lang_jpn(); else switch_lang_eng(); + u8* pixelData = NULL; +#if !TEMP_TEST_DISC if (entry->type == GM_FILE_TYPE_GAME && entry->asset.banner.state == GM_LOAD_STATE_LOADED) { + pixelData = entry->asset.banner.buf->data; + } +#else + 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; + } +#endif + + 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); } + BNRDesc *desc = NULL; +#if !TEMP_TEST_DISC + desc = &entry->desc; +#else + if (is_valid_disc_bnr) { + 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]; + } +#endif + // 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 !TEMP_TEST_DISC + if (!entry || entry->type == GM_FILE_TYPE_GAME) { +#else + if (true) { +#endif + 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 @@ -674,19 +738,35 @@ __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 TEMP_TEST_DISC + 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); +#endif return; } static bool first_transition = true; static bool in_submenu_transition = false; +#if !TEMP_TEST_DISC static u8 custom_menu_transition_alpha = 0xFF; static u8 original_menu_transition_alpha = 0; +#else +static u8 custom_menu_transition_alpha = 0; +static u8 original_menu_transition_alpha = 0xFF; +#endif __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) { +#if !TEMP_TEST_DISC OSReport("Resetting back to SUBMENU_GAMESELECT_LOADER\n"); current_gameselect_state = SUBMENU_GAMESELECT_LOADER; +#else + OSReport("Resetting back to SUBMENU_GAMESELECT_START\n"); + current_gameselect_state = SUBMENU_GAMESELECT_START; +#endif if (first_transition) { Jac_PlaySe(SOUND_MENU_ENTER); @@ -705,7 +785,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); @@ -759,8 +840,15 @@ __attribute_used__ s32 handle_gameselect_inputs() { if (pad_status->buttons_down & PAD_BUTTON_B) { if (current_gameselect_state == SUBMENU_GAMESELECT_START && !in_submenu_transition) { +#if !TEMP_TEST_DISC in_submenu_transition = true; current_gameselect_state = SUBMENU_GAMESELECT_LOADER; +#else + anim_step = 0; // anim reset + // *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset - not relevant for disc + Jac_PlaySe(SOUND_MENU_EXIT); + return MENU_GAMESELECT_ID; +#endif Jac_PlaySe(SOUND_SUBMENU_EXIT); } else if (!in_submenu_transition) { // TODO: check current path depth From 35fec7f7c7f4aa965e936c6ee4a193738060ca95 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Fri, 30 May 2025 19:06:30 +0100 Subject: [PATCH 05/31] Allow disc games to be started MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should allow games to be started both from the ‘Press Start’ menu and just from starting up the GameCube, just using the existing booting code (so reading the disc’s DOL while the screen is blank). --- patches/source/games.c | 23 +++++++++++++---------- patches/source/games.h | 2 ++ patches/source/main.c | 11 +++++++++-- patches/source/menu.c | 40 +++++++++++++++++++++++++++++++--------- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/patches/source/games.c b/patches/source/games.c index 18d46b5..bf9f330 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -965,11 +965,12 @@ void *gm_thread_worker(void* param) { } // Stops the disc-reading loop, so we can switch to the FlippyDrive -static atomic_bool request_disc_stop = false; +atomic_bool request_disc_stop_thread = false; // Stops the disc-reading loop, so we can start the loaded disc -static atomic_bool request_disc_start = false; +atomic_bool request_disc_start_game = false; +// TODO: Find a centralised spot for these! #define STATE_WAIT_LOAD 0x0f #define STATE_START_GAME 0x10 #define STATE_NO_DISC 0x12 @@ -998,7 +999,7 @@ void *gm_disc_thread_worker(void *param) { 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 && !request_disc_start) { + while (!request_disc_stop_thread && !request_disc_start_game) { OSYieldThread(); bool is_cover_open = dvd_cover_status(); @@ -1040,13 +1041,13 @@ void *gm_disc_thread_worker(void *param) { continue; } - if (request_disc_stop) { + if (request_disc_stop_thread) { break; } - // TODO: Run the apploader + // TODO: Run the apploader, if that's at all possible - if (request_disc_stop) { + if (request_disc_stop_thread) { break; } @@ -1059,7 +1060,7 @@ void *gm_disc_thread_worker(void *param) { continue; } - if (request_disc_stop) { + if (request_disc_stop_thread) { break; } @@ -1072,16 +1073,18 @@ void *gm_disc_thread_worker(void *param) { disc_read_state = STATE_START_GAME; } - if (request_disc_stop) { + if (request_disc_stop_thread) { // TODO: Should we spin down the disc? dvd_custom_bypass_exit(); } else { - bool ready_to_start = request_disc_start && finished_reading_disc && disc_read_state == STATE_START_GAME; + 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; } @@ -1186,7 +1189,7 @@ void gm_start_disc_thread() { 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 8a9a9ce..6d5b77c 100644 --- a/patches/source/games.h +++ b/patches/source/games.h @@ -109,6 +109,8 @@ 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; diff --git a/patches/source/main.c b/patches/source/main.c index dc713fe..9db9487 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -489,11 +489,18 @@ __attribute_used__ void bs2start() { // read boot info into lowmem struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; +#if TEMP_TEST_DISC + start_passthrough_game = true; +#endif + if (!start_passthrough_game) { gm_deinit_thread(); } else { - dvd_custom_bypass_enter(); - udelay(10 * 1000); + // dvd_custom_bypass_enter(); + // udelay(10 * 1000); + + request_disc_start_game = true; + gm_deinit_thread(); int ret = dvd_read_id(); int err = dvd_get_error(); diff --git a/patches/source/menu.c b/patches/source/menu.c index 863a853..33b1548 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -795,6 +795,14 @@ __attribute_used__ void mod_gameselect_draw(u8 alpha_0, u8 alpha_1, u8 alpha_2) return; } +// TODO: Find a centralised spot for these! +#define STATE_WAIT_LOAD 0x0f +#define STATE_START_GAME 0x10 +#define STATE_NO_DISC 0x12 +#define STATE_COVER_OPEN 0x13 +#define STATE_READ_ERROR 0x16 +#define STATE_FATAL_ERROR 0x17 + __attribute_used__ s32 handle_gameselect_inputs() { update_icon_positions(); grid_update_icon_positions(); @@ -898,16 +906,30 @@ __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) { +#if !TEMP_TEST_DISC + gm_file_entry_t *entry = gm_get_game_entry(selected_slot) + bool ready_to_start = entry != NULL; +#else + bool ready_to_start = disc_read_state == STATE_START_GAME; +#endif + + if (pad_status->buttons_down & PAD_BUTTON_START && ready_to_start) { + Jac_StopSoundAll(); + Jac_PlaySe(SOUND_MENU_FINAL); + +#if !TEMP_TEST_DISC + 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; + } +#else + // ??? +#endif + *bs2start_ready = 1; } - *bs2start_ready = 1; } if (current_gameselect_state == SUBMENU_GAMESELECT_LOADER) { From 455c0fafc15387a462c3ec5dfa188ec437a31589 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Fri, 30 May 2025 20:12:43 +0100 Subject: [PATCH 06/31] Restart the BGM if game startup is interrupted This can happen if the disc cover is opened after pressing Start. The fade-in and the cubes already adapt to this, we just have to restart the BGM that we stopped. --- patches/linker/link_ntsc10.ld | 1 + patches/linker/link_ntsc11.ld | 1 + patches/linker/link_ntsc12_001.ld | 1 + patches/linker/link_ntsc12_101.ld | 1 + patches/linker/link_pal10.ld | 1 + patches/linker/link_pal11.ld | 1 + patches/linker/link_pal12.ld | 1 + patches/source/menu.c | 33 +++++++++++++++++++++---------- 8 files changed, 30 insertions(+), 10 deletions(-) diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index 4c20e57..3538099 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; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index 830a9f9..dd3e504 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; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index f05efad..a63ad5d 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -40,6 +40,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; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index 8caa4bc..762b4a9 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -40,6 +40,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; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index 02e3cd7..18a166c 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -37,6 +37,7 @@ pal10_set_obj_pos = 0x8130537c; pal10_set_obj_cam = 0x813053c8; pal10_Jac_StopSoundAll = 0x8135abe0; +pal10_Jac_PlayBgm = 0x8135ac20; pal10_InitializeUART = 0x81363f00; pal10_WriteUARTN = 0x81363f50; pal10___pformatter = 0x8137c2d8; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index 602b0f4..9ee6846 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -40,6 +40,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; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index 563c7e4..86badf8 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -41,6 +41,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; diff --git a/patches/source/menu.c b/patches/source/menu.c index 33b1548..a433c69 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -48,6 +48,7 @@ __attribute_reloc__ model_data *save_empty; // for audio __attribute_reloc__ void (*Jac_PlaySe)(u32); __attribute_reloc__ void (*Jac_StopSoundAll)(); +__attribute_reloc__ void (*Jac_PlayBgm)(u32); // for model gx __attribute_reloc__ void (*model_init)(model* m, int process); @@ -803,6 +804,8 @@ __attribute_used__ void mod_gameselect_draw(u8 alpha_0, u8 alpha_1, u8 alpha_2) #define STATE_READ_ERROR 0x16 #define STATE_FATAL_ERROR 0x17 +static bool starting_game = false; + __attribute_used__ s32 handle_gameselect_inputs() { update_icon_positions(); grid_update_icon_positions(); @@ -915,20 +918,30 @@ __attribute_used__ s32 handle_gameselect_inputs() { bool ready_to_start = disc_read_state == STATE_START_GAME; #endif - if (pad_status->buttons_down & PAD_BUTTON_START && ready_to_start) { - Jac_StopSoundAll(); - Jac_PlaySe(SOUND_MENU_FINAL); + 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 !TEMP_TEST_DISC - 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; - } + 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; + } #else - // ??? + // ??? #endif - *bs2start_ready = 1; + *bs2start_ready = 1; + starting_game = true; + } } } From 91f36b12d8b313a075060a297f951c47e8b64aae Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sun, 1 Jun 2025 15:36:57 +0100 Subject: [PATCH 07/31] Allow switching between disc drive and FlippyDrive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using L and R on the ‘game play’ screen, it’s now possible to switch between the disc drive and the FlippyDrive. There’s no visual feedback yet – that’s still yet to come. To support this, all the `#if TEMP_TEST_DISC`s have been replaced with dynamic conditionals based on which device is active, with some simplifications where possible. --- patches/linker/link_ntsc10.ld | 4 +- patches/linker/link_ntsc11.ld | 4 +- patches/linker/link_ntsc12_001.ld | 4 +- patches/linker/link_ntsc12_101.ld | 4 +- patches/linker/link_pal10.ld | 4 +- patches/linker/link_pal11.ld | 4 +- patches/linker/link_pal12.ld | 4 +- patches/source/dvd_threaded.c | 13 ++- patches/source/dvd_threaded.h | 3 +- patches/source/games.c | 5 +- patches/source/games.h | 3 + patches/source/main.c | 143 ++++++++++++++++++----- patches/source/menu.c | 184 +++++++++++++++--------------- patches/source/menu.h | 3 +- patches/source/patch.s | 9 ++ patches/source/reloc.c | 2 +- patches/source/reloc.h | 2 +- patches/source/temp_test_disc.h | 3 - 18 files changed, 260 insertions(+), 138 deletions(-) delete mode 100644 patches/source/temp_test_disc.h diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index 3538099..29c5639 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -69,7 +69,7 @@ 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; @@ -113,3 +113,5 @@ ntsc10_stock_banner_ptr = 0x80af8440; ntsc10_get_element_alpha = 0x8130ab40; ntsc10_banner_element_alpha = 0x8148dd88; + +ntsc10_cube_menu_rotation_vertical = 0x81465882; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index dd3e504..548064b 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -29,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; @@ -113,3 +113,5 @@ ntsc11_stock_banner_ptr = 0x809f8440; ntsc11_get_element_alpha = 0x8130aa4c; ntsc11_banner_element_alpha = 0x814b25b8; + +ntsc11_cube_menu_rotation_vertical = 0x81489fb2; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index a63ad5d..1c73551 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -67,7 +67,7 @@ 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; @@ -108,3 +108,5 @@ ntsc12_001_stock_banner_ptr = 0x809f8440; ntsc12_001_get_element_alpha = 0x8130adc0; ntsc12_001_banner_element_alpha = 0x814b3b98; + +ntsc12_001_cube_menu_rotation_vertical = 0x8148b592; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index 762b4a9..b2293ff 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -67,7 +67,7 @@ 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; @@ -108,3 +108,5 @@ ntsc12_101_stock_banner_ptr = 0x809f8440; ntsc12_101_get_element_alpha = 0x8130add8; ntsc12_101_banner_element_alpha = 0x814b4038; + +ntsc12_101_cube_menu_rotation_vertical = 0x8148ba32; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index 18a166c..6b8a5a0 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -70,7 +70,7 @@ pal10_game_blob_text = 0x814de4c8; pal10_game_blob_insert_disc = 0x814de4d8; pal10_game_blob_reading_disc = 0x814de4f8; pal10_game_blob_could_not_read_disc = 0x814de508; -pal10_prev_menu_id = 0x814ad3f8; +pal10_next_menu_id = 0x814ad3f8; pal10_save_empty = 0x814ca968; pal10_orig_thread_init = 0x8131c5c4; @@ -110,3 +110,5 @@ pal10_stock_banner_ptr = 0x80a7d4a0; pal10_get_element_alpha = 0x8130a964; pal10_banner_element_alpha = 0x814de4b8; + +pal10_cube_menu_rotation_vertical = 0x814b5eb2; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index 9ee6846..d09aa9f 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -67,7 +67,7 @@ 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; @@ -108,3 +108,5 @@ pal11_stock_banner_ptr = 0x809efb80; pal11_get_element_alpha = 0x8130aa4c; pal11_banner_element_alpha = 0x814ad278; + +pal11_cube_menu_rotation_vertical = 0x81484c72; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index 86badf8..19030a5 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -68,7 +68,7 @@ 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; @@ -109,3 +109,5 @@ pal12_stock_banner_ptr = 0x80a7d4a0; pal12_get_element_alpha = 0x8130aaa4; pal12_banner_element_alpha = 0x814dfc78; + +pal12_cube_menu_rotation_vertical = 0x814b7672; diff --git a/patches/source/dvd_threaded.c b/patches/source/dvd_threaded.c index fe42cb1..21ec381 100644 --- a/patches/source/dvd_threaded.c +++ b/patches/source/dvd_threaded.c @@ -29,6 +29,7 @@ #define DVD_OEM_READ 0xA8000000 #define DVD_OEM_ERROR 0xE0000000 +#define DVD_OEM_STOP_MOTOR 0xE3000000 static vu32* const _di_regs = (vu32*)0xCC006000; @@ -83,7 +84,7 @@ int dvd_threaded_read_id() { return 0; } -unsigned int dvd_threaded_get_error(void) { +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 @@ -95,6 +96,16 @@ unsigned int dvd_threaded_get_error(void) { 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; diff --git a/patches/source/dvd_threaded.h b/patches/source/dvd_threaded.h index bb441fd..94164fb 100644 --- a/patches/source/dvd_threaded.h +++ b/patches/source/dvd_threaded.h @@ -3,5 +3,6 @@ int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd); int dvd_threaded_read_id(); -unsigned int dvd_threaded_get_error(void); +unsigned int dvd_threaded_get_error(); +void dvd_threaded_stop_motor(); void dvd_threaded_reset(); diff --git a/patches/source/games.c b/patches/source/games.c index bf9f330..1537968 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -1074,7 +1074,7 @@ void *gm_disc_thread_worker(void *param) { } if (request_disc_stop_thread) { - // TODO: Should we spin down the disc? + 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; @@ -1176,6 +1176,9 @@ void gm_start_disc_thread() { game_disc_running = true; DCBlockStore((void*)OSRoundDown32B((u32)&game_disc_running)); + request_disc_stop_thread = false; + request_disc_start_game = false; + // OSUnlockMutex(game_enum_mutex); // Start the thread diff --git a/patches/source/games.h b/patches/source/games.h index 6d5b77c..d777958 100644 --- a/patches/source/games.h +++ b/patches/source/games.h @@ -117,6 +117,9 @@ extern atomic_char disc_read_region; 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(); diff --git a/patches/source/main.c b/patches/source/main.c index 9db9487..fe7c4e7 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -24,7 +24,7 @@ #include "boot.h" #include "gameid.h" -#include "temp_test_disc.h" +#include "default_opening_bin.h" #define CUBE_TEX_WIDTH 84 #define CUBE_TEX_HEIGHT 84 @@ -74,6 +74,8 @@ __attribute_reloc__ model *gc_text_model; __attribute_reloc__ model *logo_model; __attribute_reloc__ model *cube_model; +__attribute_reloc__ s16 *cube_menu_rotation_vertical; + // locals __attribute_data__ static GXColorS10 color_cube; __attribute_data__ static GXColorS10 color_cube_low; @@ -85,6 +87,12 @@ __attribute_data__ static GXColorS10 color_bg_outer_1; __attribute_data__ gm_file_entry_t boot_entry; __attribute_data__ gm_file_entry_t second_boot_entry; +extern void (*Jac_PlaySe)(u32); + +bool is_disc_drive_selected = false; +static bool is_disc_drive_active = false; +static bool is_switching_device = false; + __attribute_used__ void mod_cube_colors() { if (cube_color == 0) { OSReport("Using default colors\n"); @@ -355,20 +363,23 @@ __attribute_used__ void pre_thread_init() { gm_init_heap(); gm_init_thread(); -#if !TEMP_TEST_DISC - if (!start_passthrough_game) { + + is_disc_drive_selected = start_passthrough_game; + is_disc_drive_active = is_disc_drive_selected; + is_switching_device = false; + + if (is_disc_drive_active) { + gm_start_disc_thread(); + } else { gm_start_thread("/"); } -#else - gm_start_disc_thread(); -#endif } __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(); @@ -436,22 +447,32 @@ __attribute_used__ u32 get_tvmode() { return rmode->viTVMode; } -extern u32 *banner_ready; -extern const BNR **banner_pointer; +__attribute_used__ void top_level_menu_extra_inputs() { + s16 gameselect_vertical_cube_rotation = 0x4000; -__attribute_data__ int frame_count = 0; -__attribute_used__ u32 bs2tick() { -#if TEMP_TEST_DISC - // If the disc thread is running, do things relating to it - // TODO: Make this conditional! - *banner_ready = disc_read_banner_ready; - *banner_pointer = stock_banner_ptr; - return disc_read_state; -#endif + if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation) { + if (!is_switching_device) { + // Handle L and R to select between disc drive and FlippyDrive + if ((pad_status->buttons_down & PAD_TRIGGER_L) && !is_disc_drive_selected) { + // Switch to the disc drive + Jac_PlaySe(SOUND_SUBMENU_ENTER); + is_disc_drive_selected = true; + } else if ((pad_status->buttons_down & PAD_TRIGGER_R) && is_disc_drive_selected) { + // Switch to the FlippyDrive + Jac_PlaySe(SOUND_SUBMENU_ENTER); + is_disc_drive_selected = false; + } + } + } +} +extern u32 *banner_ready; +extern const BNR **banner_pointer; +__attribute_data__ int frame_count = 0; +u32 bs2tick_flippydrive() { frame_count++; if (!completed_time && cube_state->cube_anim_done) { OSReport("FINISHED (%d frames)\n", frame_count); @@ -483,22 +504,90 @@ __attribute_used__ u32 bs2tick() { 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; + return disc_read_state; +} + +int switching_device_frame_count = 0; +void bs2tick_check_device_switch() { + if ((is_disc_drive_selected != is_disc_drive_active) && !is_switching_device) { + // Begin switching to the new device + is_switching_device = true; + switching_device_frame_count = 0; + + if (is_disc_drive_active) { + // 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 + // TODO: Can we do this better than just relying on timing? + bool is_banner_visible = switching_device_frame_count < 25; + switching_device_frame_count += 1; + + if (!is_banner_visible) { + // If the thread's stopped, restart it and stop switching + if (is_disc_drive_active) { + if (!game_disc_running) { + gm_deinit_thread(); + is_switching_device = false; + } + } else { + if (!game_enum_running) { + gm_deinit_thread(); + is_switching_device = false; + } + } + } + + if (!is_switching_device) { + // Start spinning up the new thread + is_disc_drive_active = is_disc_drive_selected; + if (is_disc_drive_active) { + gm_start_disc_thread(); + } else { + if (*cur_menu_id != MENU_GAMESELECT_TRANSITION_ID) { + *banner_pointer = (const BNR *)&default_opening_bin[0]; + *banner_ready = 1; + } + gm_start_thread("/"); + } + } + } +} + +__attribute_used__ u32 bs2tick() { + // TODO: On boot, try each device in a configurable order, and stick with the first successful one + bs2tick_check_device_switch(); + if (is_switching_device) { + // Just show as 'loading' while we wait + return STATE_WAIT_LOAD; + } + + if (is_disc_drive_active) { + return bs2tick_disc(); + } else { + return bs2tick_flippydrive(); + } +} + __attribute_used__ void bs2start() { OSReport("DONE\n"); // read boot info into lowmem struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; -#if TEMP_TEST_DISC - start_passthrough_game = true; -#endif - - if (!start_passthrough_game) { + if (!is_disc_drive_active) { gm_deinit_thread(); } else { - // dvd_custom_bypass_enter(); - // udelay(10 * 1000); - request_disc_start_game = true; gm_deinit_thread(); @@ -539,7 +628,7 @@ __attribute_used__ void bs2start() { ICInvalidateRange((void*)start_addr, len); // Passthrough mode - if (start_passthrough_game) { + if (is_disc_drive_active) { chainload_boot_game(NULL, true); } diff --git a/patches/source/menu.c b/patches/source/menu.c index a433c69..b7fd4ea 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -30,8 +30,6 @@ #include "gcm.h" #include "bnr.h" -#include "temp_test_disc.h" - // for setup __attribute_reloc__ void (*menu_alpha_setup)(); @@ -98,6 +96,9 @@ __attribute_reloc__ u32 *bs2start_ready; __attribute_reloc__ const BNR **banner_pointer; __attribute_reloc__ u32 *banner_ready; +// TODO: Find a better home for this +extern bool is_disc_drive_selected; + typedef struct { f32 scale; f32 opacity; @@ -525,11 +526,7 @@ void fix_gameselect_view() { GXSetCurrentMtx(0); } -#if !TEMP_TEST_DISC __attribute_data__ u32 current_gameselect_state = SUBMENU_GAMESELECT_LOADER; -#else -__attribute_data__ u32 current_gameselect_state = SUBMENU_GAMESELECT_START; -#endif __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { // color u8 ui_alpha = alpha_1; @@ -650,15 +647,15 @@ __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 } __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { -#if TEMP_TEST_DISC - // TODO: I don't think this is necessary - check! - static bool first_draw = true; - if (first_draw) { - first_draw = false; - setup_gameselect_anim(); - setup_cube_anim(); + if (is_disc_drive_selected) { + // TODO: I don't think this is necessary - check! + static bool first_draw = true; + if (first_draw) { + first_draw = false; + setup_gameselect_anim(); + setup_cube_anim(); + } } -#endif // menu alpha u8 ui_alpha = alpha_1; @@ -668,29 +665,43 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, get_element_alpha(banner_element_alpha, &banner_alpha, NULL); GXColor banner_white = {0xFF, 0xFF, 0xFF, (banner_alpha * ui_alpha) / 0xFF}; -#if !TEMP_TEST_DISC - gm_file_entry_t *entry = gm_get_game_entry(selected_slot); - if (entry == NULL) return; // protect against transition during enum + char game_region = '?'; + u8* pixelData = NULL; + BNRDesc *desc = NULL; + bool show_full_banner = true; - char game_region = entry->extra.game_id[3]; -#else - char game_region = disc_read_region; -#endif - if (game_region == 'J') switch_lang_jpn(); - else switch_lang_eng(); + if (is_disc_drive_selected) { + game_region = disc_read_region; - u8* pixelData = NULL; -#if !TEMP_TEST_DISC - if (entry->type == GM_FILE_TYPE_GAME && entry->asset.banner.state == GM_LOAD_STATE_LOADED) { - pixelData = entry->asset.banner.buf->data; - } -#else - bool is_valid_disc_bnr = strncmp(stock_banner_ptr->magic, "BNR1", 4) || strncmp(stock_banner_ptr->magic, "BNR2", 4); + 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; - 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]; + } + + } else { + 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; } -#endif + + if (game_region == 'J') switch_lang_jpn(); + else switch_lang_eng(); if (pixelData) { // game banner @@ -699,29 +710,11 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, draw_blob_tex(make_type('b','a','n','a'), game_blob_b, &banner_white, &banner_texture); } - BNRDesc *desc = NULL; -#if !TEMP_TEST_DISC - desc = &entry->desc; -#else - if (is_valid_disc_bnr) { - 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]; - } -#endif - // game info prep_text_mode(); if (desc) { draw_blob_text(make_type('t','i','t','l'), game_blob_b, &banner_white, desc->fullGameName, 0x40); -#if !TEMP_TEST_DISC - if (!entry || entry->type == GM_FILE_TYPE_GAME) { -#else - if (true) { -#endif + 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 { @@ -740,34 +733,32 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, draw_blob_fixed(game_blob_text, game_blob_a, game_blob_b, &white); // Messages relating to reading a disc -#if TEMP_TEST_DISC - 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); -#endif + if (is_disc_drive_selected) { + 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 bool first_transition = true; static bool in_submenu_transition = false; -#if !TEMP_TEST_DISC static u8 custom_menu_transition_alpha = 0xFF; static u8 original_menu_transition_alpha = 0; -#else -static u8 custom_menu_transition_alpha = 0; -static u8 original_menu_transition_alpha = 0xFF; -#endif __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) { -#if !TEMP_TEST_DISC - OSReport("Resetting back to SUBMENU_GAMESELECT_LOADER\n"); - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; -#else - OSReport("Resetting back to SUBMENU_GAMESELECT_START\n"); - current_gameselect_state = SUBMENU_GAMESELECT_START; -#endif + if (*cur_menu_id == MENU_GAMESELECT_ID && *next_menu_id == MENU_GAMESELECT_TRANSITION_ID) { + if (is_disc_drive_selected) { + OSReport("Resetting back to SUBMENU_GAMESELECT_START\n"); + current_gameselect_state = SUBMENU_GAMESELECT_START; + } else { + OSReport("Resetting back to SUBMENU_GAMESELECT_LOADER\n"); + current_gameselect_state = SUBMENU_GAMESELECT_LOADER; + } + + custom_menu_transition_alpha = current_gameselect_state == SUBMENU_GAMESELECT_LOADER ? 0xFF : 0; + original_menu_transition_alpha = current_gameselect_state == SUBMENU_GAMESELECT_START ? 0xFF : 0; if (first_transition) { Jac_PlaySe(SOUND_MENU_ENTER); @@ -851,15 +842,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) { -#if !TEMP_TEST_DISC - in_submenu_transition = true; - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; -#else - anim_step = 0; // anim reset - // *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset - not relevant for disc - Jac_PlaySe(SOUND_MENU_EXIT); - return MENU_GAMESELECT_ID; -#endif + // TODO: Implement a proper navigation stack, so we don't need so much special casing + if (is_disc_drive_selected) { + anim_step = 0; // anim reset + // *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset - not relevant for disc + Jac_PlaySe(SOUND_MENU_EXIT); + return MENU_GAMESELECT_ID; + } else { + 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 @@ -883,7 +876,7 @@ __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); @@ -892,7 +885,7 @@ __attribute_used__ s32 handle_gameselect_inputs() { in_submenu_transition = true; current_gameselect_state = SUBMENU_GAMESELECT_START; - Jac_PlaySe(SOUND_SUBMENU_ENTER); + Jac_PlaySe(SOUND_SUBMENU_CONFIRM); setup_gameselect_anim(); setup_cube_anim(); @@ -911,12 +904,14 @@ __attribute_used__ s32 handle_gameselect_inputs() { if (current_gameselect_state == SUBMENU_GAMESELECT_START) { -#if !TEMP_TEST_DISC - gm_file_entry_t *entry = gm_get_game_entry(selected_slot) - bool ready_to_start = entry != NULL; -#else - bool ready_to_start = disc_read_state == STATE_START_GAME; -#endif + bool ready_to_start = false; + gm_file_entry_t *entry = NULL; + if (is_disc_drive_selected) { + ready_to_start = disc_read_state == STATE_START_GAME; + } else { + entry = gm_get_game_entry(selected_slot); + ready_to_start = entry != NULL; + } if (!*bs2start_ready) { if (starting_game) { @@ -930,15 +925,14 @@ __attribute_used__ s32 handle_gameselect_inputs() { Jac_StopSoundAll(); Jac_PlaySe(SOUND_MENU_FINAL); -#if !TEMP_TEST_DISC - 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 (!is_disc_drive_selected) { + 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; + } } -#else - // ??? -#endif + *bs2start_ready = 1; starting_game = true; } 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..35f0ca6 100644 --- a/patches/source/patch.s +++ b/patches/source/patch.s @@ -115,3 +115,12 @@ 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 + +// 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; diff --git a/patches/source/temp_test_disc.h b/patches/source/temp_test_disc.h deleted file mode 100644 index 772fd2a..0000000 --- a/patches/source/temp_test_disc.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#define TEMP_TEST_DISC 1 From ca4c073389799ac718d1f4fcd531f6498b4741c8 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 2 Jun 2025 18:11:25 +0100 Subject: [PATCH 08/31] Allow disc access to be interrupted/cancelled This should hopefully make it a bit more responsive to switch from the disc to the FlippyDrive, if the switch happens while a disc is being loaded. --- patches/source/dolphin_dvd.c | 40 +++++++++++++++++++++++------------ patches/source/dolphin_dvd.h | 4 +++- patches/source/dvd_threaded.c | 22 +++++++++++++++++-- patches/source/dvd_threaded.h | 6 ++++-- patches/source/games.c | 16 ++++++++------ 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/patches/source/dolphin_dvd.c b/patches/source/dolphin_dvd.c index 4a2c0a3..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,25 +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_with_open_game(u8 fd) { +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 __attribute__((aligned(32))) static DiskHeader header; - dvd_threaded_read(&header, sizeof(DiskHeader), 0, 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, 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) { @@ -121,9 +131,12 @@ dolphin_game_into_t get_game_info_with_open_game(u8 fd) { } // If we didn't find the banner in the fast location, try the FST - bnr_info_t bnr_info = get_banner_offset_slow(&header, 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, 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) { @@ -144,8 +157,7 @@ dolphin_game_into_t get_game_info_with_open_game(u8 fd) { // OSReport("DEBUG: FST was loaded\n"); - // invalid file - return (dolphin_game_into_t) { .valid = false }; + return invalid_info; } dolphin_game_into_t get_game_info(char *game_path) { @@ -166,7 +178,7 @@ dolphin_game_into_t get_game_info(char *game_path) { return (dolphin_game_into_t) { .valid = false }; } - dolphin_game_into_t game_info = get_game_info_with_open_game(status->fd); + dolphin_game_into_t game_info = get_game_info_with_open_game(status->fd, NULL); dvd_custom_close(status->fd); return game_info; } diff --git a/patches/source/dolphin_dvd.h b/patches/source/dolphin_dvd.h index d98b9f9..f3ecfb9 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 @@ -122,7 +124,7 @@ typedef struct { _Static_assert(sizeof(dolphin_game_into_t) == 32); -dolphin_game_into_t get_game_info_with_open_game(u8 fd); +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/dvd_threaded.c b/patches/source/dvd_threaded.c index 21ec381..2733886 100644 --- a/patches/source/dvd_threaded.c +++ b/patches/source/dvd_threaded.c @@ -33,7 +33,15 @@ 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; @@ -49,6 +57,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(); } @@ -61,7 +74,7 @@ int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int return 0; } -int dvd_threaded_read_id() { +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 @@ -74,6 +87,11 @@ int dvd_threaded_read_id() { _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(); } diff --git a/patches/source/dvd_threaded.h b/patches/source/dvd_threaded.h index 94164fb..587cb88 100644 --- a/patches/source/dvd_threaded.h +++ b/patches/source/dvd_threaded.h @@ -1,8 +1,10 @@ #include #include -int dvd_threaded_read(void* dst, unsigned int len, uint64_t offset, unsigned int fd); -int dvd_threaded_read_id(); +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); unsigned int dvd_threaded_get_error(); void dvd_threaded_stop_motor(); void dvd_threaded_reset(); diff --git a/patches/source/games.c b/patches/source/games.c index 1537968..c29c1d8 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -594,7 +594,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; @@ -645,7 +645,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); @@ -989,6 +989,10 @@ atomic_uint disc_read_state = STATE_WAIT_LOAD; atomic_bool disc_read_banner_ready = false; atomic_char disc_read_region = '?'; +bool should_stop_disc_thread_worker() { + return request_disc_stop_thread; +} + void *gm_disc_thread_worker(void *param) { disc_read_state = STATE_WAIT_LOAD; @@ -1022,7 +1026,7 @@ void *gm_disc_thread_worker(void *param) { dvd_threaded_reset(); - int ret = dvd_threaded_read_id(); + 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; @@ -1051,8 +1055,8 @@ void *gm_disc_thread_worker(void *param) { break; } - // TODO: Get the banner - dolphin_game_into_t game_info = get_game_info_with_open_game(fd); + // Get the banner + dolphin_game_into_t game_info = get_game_info_with_open_game(fd, should_stop_disc_thread_worker); if (!game_info.valid) { disc_read_state = STATE_READ_ERROR; finished_reading_disc = true; @@ -1064,7 +1068,7 @@ void *gm_disc_thread_worker(void *param) { break; } - dvd_threaded_read(stock_banner_ptr, sizeof(BNR), game_info.bnr_offset, fd); + dvd_threaded_read(stock_banner_ptr, sizeof(BNR), game_info.bnr_offset, fd, should_stop_disc_thread_worker); disc_read_region = (char)game_info.game_id[3]; disc_read_banner_ready = true; From 686655be7a8aad8a34796620c23946ebb5dbc8c8 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 2 Jun 2025 20:15:22 +0100 Subject: [PATCH 09/31] Ensure banner is faded before switching device This replaces a hardcoded time to wait for, instead inspecting the actual alpha values. This delay is to allow the banner fade to complete before changing the banner pointer, as the banner would otherwise abruptly disappear when this switch happens. --- patches/linker/link_ntsc10.ld | 2 ++ patches/linker/link_ntsc11.ld | 2 ++ patches/linker/link_ntsc12_001.ld | 2 ++ patches/linker/link_ntsc12_101.ld | 2 ++ patches/linker/link_pal10.ld | 2 ++ patches/linker/link_pal11.ld | 2 ++ patches/linker/link_pal12.ld | 2 ++ patches/source/games.c | 1 + patches/source/main.c | 12 +++++++----- 9 files changed, 22 insertions(+), 5 deletions(-) diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index 29c5639..b1770ed 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -113,5 +113,7 @@ ntsc10_stock_banner_ptr = 0x80af8440; ntsc10_get_element_alpha = 0x8130ab40; ntsc10_banner_element_alpha = 0x8148dd88; +ntsc10_top_level_banner_element_alpha = 0x81465c3c; ntsc10_cube_menu_rotation_vertical = 0x81465882; +ntsc10_cube_menu_alpha = 0x81465888; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index 548064b..bbd988d 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -113,5 +113,7 @@ ntsc11_stock_banner_ptr = 0x809f8440; ntsc11_get_element_alpha = 0x8130aa4c; ntsc11_banner_element_alpha = 0x814b25b8; +ntsc11_top_level_banner_element_alpha = 0x8148a3bc; ntsc11_cube_menu_rotation_vertical = 0x81489fb2; +ntsc11_cube_menu_alpha = 0x81489fb8; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index 1c73551..9427278 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -108,5 +108,7 @@ ntsc12_001_stock_banner_ptr = 0x809f8440; ntsc12_001_get_element_alpha = 0x8130adc0; 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; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index b2293ff..b24bfe8 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -108,5 +108,7 @@ ntsc12_101_stock_banner_ptr = 0x809f8440; ntsc12_101_get_element_alpha = 0x8130add8; 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; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index 6b8a5a0..d34963f 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -110,5 +110,7 @@ pal10_stock_banner_ptr = 0x80a7d4a0; pal10_get_element_alpha = 0x8130a964; pal10_banner_element_alpha = 0x814de4b8; +pal10_top_level_banner_element_alpha = 0x814b62bc; pal10_cube_menu_rotation_vertical = 0x814b5eb2; +pal10_cube_menu_alpha = 0x814b5eb8; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index d09aa9f..e7ba675 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -108,5 +108,7 @@ pal11_stock_banner_ptr = 0x809efb80; pal11_get_element_alpha = 0x8130aa4c; pal11_banner_element_alpha = 0x814ad278; +pal11_top_level_banner_element_alpha = 0x8148507c; pal11_cube_menu_rotation_vertical = 0x81484c72; +pal11_cube_menu_alpha = 0x81484c78; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index 19030a5..b62722e 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -109,5 +109,7 @@ pal12_stock_banner_ptr = 0x80a7d4a0; pal12_get_element_alpha = 0x8130aaa4; pal12_banner_element_alpha = 0x814dfc78; +pal12_top_level_banner_element_alpha = 0x814b7a7c; pal12_cube_menu_rotation_vertical = 0x814b7672; +pal12_cube_menu_alpha = 0x814b7678; diff --git a/patches/source/games.c b/patches/source/games.c index c29c1d8..e638201 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -1182,6 +1182,7 @@ void gm_start_disc_thread() { request_disc_stop_thread = false; request_disc_start_game = false; + disc_read_state = STATE_WAIT_LOAD; // OSUnlockMutex(game_enum_mutex); diff --git a/patches/source/main.c b/patches/source/main.c index fe7c4e7..248f5f1 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -75,6 +75,7 @@ __attribute_reloc__ model *logo_model; __attribute_reloc__ model *cube_model; __attribute_reloc__ s16 *cube_menu_rotation_vertical; +__attribute_reloc__ u16 *cube_menu_alpha; // locals __attribute_data__ static GXColorS10 color_cube; @@ -87,6 +88,9 @@ __attribute_data__ static GXColorS10 color_bg_outer_1; __attribute_data__ gm_file_entry_t boot_entry; __attribute_data__ gm_file_entry_t second_boot_entry; +// Top-level menu +__attribute_reloc__ u16 *top_level_banner_element_alpha; // This could be a `element_alpha_state_t` (once that's merged), but we only need the first member + extern void (*Jac_PlaySe)(u32); bool is_disc_drive_selected = false; @@ -511,12 +515,10 @@ u32 bs2tick_disc() { return disc_read_state; } -int switching_device_frame_count = 0; void bs2tick_check_device_switch() { if ((is_disc_drive_selected != is_disc_drive_active) && !is_switching_device) { // Begin switching to the new device is_switching_device = true; - switching_device_frame_count = 0; if (is_disc_drive_active) { // Request the disc drive thread to stop @@ -529,9 +531,9 @@ void bs2tick_check_device_switch() { if (is_switching_device) { // Before completing the switch, make sure the banner on the menu's finished fading out - // TODO: Can we do this better than just relying on timing? - bool is_banner_visible = switching_device_frame_count < 25; - switching_device_frame_count += 1; + // 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 > 0 && *cube_menu_alpha < 0x7FFF; if (!is_banner_visible) { // If the thread's stopped, restart it and stop switching From 198afa6d0d889c129bf36aa30eeb0e221ace4418 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 14 Jun 2025 13:42:26 +0100 Subject: [PATCH 10/31] Move bs2tick and co. to a new file This non-functional change moves these bs2-prefixed functions to a new file (without changing their contents). As well as splitting this code out from `main.c`, it allows common constants and the device state to be made accessible from its header file. --- patches/source/bs2.c | 237 +++++++++++++++++++++++++++++++++++++++++ patches/source/bs2.h | 13 +++ patches/source/games.c | 9 +- patches/source/main.c | 229 +-------------------------------------- 4 files changed, 255 insertions(+), 233 deletions(-) create mode 100644 patches/source/bs2.c create mode 100644 patches/source/bs2.h diff --git a/patches/source/bs2.c b/patches/source/bs2.c new file mode 100644 index 0000000..d1cf686 --- /dev/null +++ b/patches/source/bs2.c @@ -0,0 +1,237 @@ +#include "bs2.h" + +#include "attr.h" +#include "boot.h" +#include "dol.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 + +bool is_disc_drive_selected = false; +static bool is_disc_drive_active = false; +static bool is_switching_device = false; + +__attribute_data__ u32 force_swiss_boot = 0; + +// used for optional delays +__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); + +// Top-level menu +__attribute_reloc__ u16 *top_level_banner_element_alpha; // This could be a `element_alpha_state_t` (once that's merged), but we only need the first member + +extern u16 *cube_menu_alpha; +extern u32 *banner_ready; +extern const BNR **banner_pointer; +extern u32 start_passthrough_game; + +void bs2init() { + is_disc_drive_selected = start_passthrough_game; + is_disc_drive_active = is_disc_drive_selected; + is_switching_device = false; + + if (is_disc_drive_active) { + gm_start_disc_thread(); + } else { + gm_start_thread("/"); + } +} + +bool bs2_is_switching_device() { + return is_switching_device; +} + +__attribute_data__ int frame_count = 0; +u32 bs2tick_flippydrive() { + 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; +} + +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; + return disc_read_state; +} + +void bs2tick_check_device_switch() { + if ((is_disc_drive_selected != is_disc_drive_active) && !is_switching_device) { + // Begin switching to the new device + is_switching_device = true; + + if (is_disc_drive_active) { + // 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 > 0 && *cube_menu_alpha < 0x7FFF; + + if (!is_banner_visible) { + // If the thread's stopped, restart it and stop switching + if (is_disc_drive_active) { + if (!game_disc_running) { + gm_deinit_thread(); + is_switching_device = false; + } + } else { + if (!game_enum_running) { + gm_deinit_thread(); + is_switching_device = false; + } + } + } + + if (!is_switching_device) { + // Start spinning up the new thread + is_disc_drive_active = is_disc_drive_selected; + if (is_disc_drive_active) { + gm_start_disc_thread(); + } else { + if (*cur_menu_id != MENU_GAMESELECT_TRANSITION_ID) { + *banner_pointer = (const BNR *)&default_opening_bin[0]; + *banner_ready = 1; + } + gm_start_thread("/"); + } + } + } +} + +__attribute_used__ u32 bs2tick() { + // TODO: On boot, try each device in a configurable order, and stick with the first successful one + bs2tick_check_device_switch(); + if (is_switching_device) { + // Just show as 'loading' while we wait + return STATE_WAIT_LOAD; + } + + if (is_disc_drive_active) { + return bs2tick_disc(); + } else { + return bs2tick_flippydrive(); + } +} + +__attribute_used__ void bs2start() { + OSReport("DONE\n"); + + // read boot info into lowmem + struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; + + if (!is_disc_drive_active) { + 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]); + 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 (is_disc_drive_active) { + 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..d405513 --- /dev/null +++ b/patches/source/bs2.h @@ -0,0 +1,13 @@ +#pragma once + +#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 bool is_disc_drive_selected; + +void bs2init(); +bool bs2_is_switching_device(); diff --git a/patches/source/games.c b/patches/source/games.c index e638201..eafddd3 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -29,6 +29,7 @@ #include "metaphrasis.h" +#include "bs2.h" #include "games.h" #include "grid.h" #include "menu.h" @@ -970,14 +971,6 @@ 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; -// TODO: Find a centralised spot for these! -#define STATE_WAIT_LOAD 0x0f -#define STATE_START_GAME 0x10 -#define STATE_NO_DISC 0x12 -#define STATE_COVER_OPEN 0x13 -#define STATE_READ_ERROR 0x16 -#define STATE_FATAL_ERROR 0x17 - #define ERROR_A_OK 0x00 #define ERROR_A_LID_OPEN 0x01 #define ERROR_A_NO_DISC_DISC_CHANGED 0x02 diff --git a/patches/source/main.c b/patches/source/main.c index 248f5f1..6405e04 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -6,37 +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" - -#include "default_opening_bin.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; @@ -48,20 +34,12 @@ __attribute_data__ u32 start_passthrough_game = 0; __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_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)(); @@ -88,15 +66,8 @@ __attribute_data__ static GXColorS10 color_bg_outer_1; __attribute_data__ gm_file_entry_t boot_entry; __attribute_data__ gm_file_entry_t second_boot_entry; -// Top-level menu -__attribute_reloc__ u16 *top_level_banner_element_alpha; // This could be a `element_alpha_state_t` (once that's merged), but we only need the first member - extern void (*Jac_PlaySe)(u32); -bool is_disc_drive_selected = false; -static bool is_disc_drive_active = false; -static bool is_switching_device = false; - __attribute_used__ void mod_cube_colors() { if (cube_color == 0) { OSReport("Using default colors\n"); @@ -368,15 +339,7 @@ __attribute_used__ void pre_thread_init() { gm_init_heap(); gm_init_thread(); - is_disc_drive_selected = start_passthrough_game; - is_disc_drive_active = is_disc_drive_selected; - is_switching_device = false; - - if (is_disc_drive_active) { - gm_start_disc_thread(); - } else { - gm_start_thread("/"); - } + bs2init(); } __attribute_used__ void pre_menu_init(int unk) { @@ -456,7 +419,7 @@ __attribute_used__ void top_level_menu_extra_inputs() { if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation) { - if (!is_switching_device) { + if (!bs2_is_switching_device()) { // Handle L and R to select between disc drive and FlippyDrive if ((pad_status->buttons_down & PAD_TRIGGER_L) && !is_disc_drive_selected) { // Switch to the disc drive @@ -472,190 +435,6 @@ __attribute_used__ void top_level_menu_extra_inputs() { } } -extern u32 *banner_ready; -extern const BNR **banner_pointer; - -__attribute_data__ int frame_count = 0; -u32 bs2tick_flippydrive() { - 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; -} - -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; - return disc_read_state; -} - -void bs2tick_check_device_switch() { - if ((is_disc_drive_selected != is_disc_drive_active) && !is_switching_device) { - // Begin switching to the new device - is_switching_device = true; - - if (is_disc_drive_active) { - // 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 > 0 && *cube_menu_alpha < 0x7FFF; - - if (!is_banner_visible) { - // If the thread's stopped, restart it and stop switching - if (is_disc_drive_active) { - if (!game_disc_running) { - gm_deinit_thread(); - is_switching_device = false; - } - } else { - if (!game_enum_running) { - gm_deinit_thread(); - is_switching_device = false; - } - } - } - - if (!is_switching_device) { - // Start spinning up the new thread - is_disc_drive_active = is_disc_drive_selected; - if (is_disc_drive_active) { - gm_start_disc_thread(); - } else { - if (*cur_menu_id != MENU_GAMESELECT_TRANSITION_ID) { - *banner_pointer = (const BNR *)&default_opening_bin[0]; - *banner_ready = 1; - } - gm_start_thread("/"); - } - } - } -} - -__attribute_used__ u32 bs2tick() { - // TODO: On boot, try each device in a configurable order, and stick with the first successful one - bs2tick_check_device_switch(); - if (is_switching_device) { - // Just show as 'loading' while we wait - return STATE_WAIT_LOAD; - } - - if (is_disc_drive_active) { - return bs2tick_disc(); - } else { - return bs2tick_flippydrive(); - } -} - -__attribute_used__ void bs2start() { - OSReport("DONE\n"); - - // read boot info into lowmem - struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; - - if (!is_disc_drive_active) { - 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]); - 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 (is_disc_drive_active) { - 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); From b071a58c5a3bfcb0846264b4408913f73290b65c Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 14 Jun 2025 17:33:00 +0100 Subject: [PATCH 11/31] Move audio and drawing relocs to their own files Another non-functional change, this time making it easier to access these IPL functions from outside `menu.c`. --- patches/source/audio.c | 7 +++++++ patches/source/audio.h | 7 +++++++ patches/source/draw.c | 31 +++++++++++++++++++++++++++++++ patches/source/draw.h | 34 ++++++++++++++++++++++++++++++++++ patches/source/menu.c | 31 ++----------------------------- 5 files changed, 81 insertions(+), 29 deletions(-) create mode 100644 patches/source/audio.c create mode 100644 patches/source/audio.h create mode 100644 patches/source/draw.c create mode 100644 patches/source/draw.h 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/draw.c b/patches/source/draw.c new file mode 100644 index 0000000..6783714 --- /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, box_draw_group* header, GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); +__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); + +// 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..03d9149 --- /dev/null +++ b/patches/source/draw.h @@ -0,0 +1,34 @@ +#pragma once + +#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, box_draw_group* header, GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); +extern void (*draw_blob_fixed)(void *blob_ptr, void *blob_a, void *blob_b, GXColor *color); +extern void (*draw_blob_text)(u32 type, void *blob, GXColor *color, char *str, s32 len); +extern void (*draw_blob_text_long)(u32 type, void *blob, GXColor *color, char *str, s32 len); +extern void (*draw_blob_border)(u32 type, void *blob, GXColor *color); +extern void (*draw_blob_tex)(u32 type, void *blob, GXColor *color, tex_data *dat); +extern void (*setup_tex_draw)(s32 unk0, s32 unk1, s32 unk2); +extern void (*draw_named_tex)(u32 type, void *blob, 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/menu.c b/patches/source/menu.c index b7fd4ea..da85f55 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -8,6 +8,8 @@ #include +#include "audio.h" +#include "draw.h" #include "usbgecko.h" #include "menu.h" #include "grid.h" @@ -34,8 +36,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)(); @@ -43,29 +43,9 @@ __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__ void (*Jac_PlayBgm)(u32); - -// 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 (*get_element_alpha)(void *element, u16 *alpha, u32 *unk); __attribute_reloc__ void *banner_element_alpha; @@ -83,14 +63,7 @@ __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__ const BNR **banner_pointer; From 433c91c6aa89edda61733a82ff0a791abcf6fe51 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 14 Jun 2025 19:53:54 +0100 Subject: [PATCH 12/31] Add element alpha and button description code This adds the structs and functions used for getting and updating the alpha used by various elements, as well as wrapping the button description updates and drawing. --- patches/linker/link_ntsc10.ld | 4 ++++ patches/linker/link_ntsc11.ld | 4 ++++ patches/linker/link_ntsc12_001.ld | 4 ++++ patches/linker/link_ntsc12_101.ld | 4 ++++ patches/linker/link_pal10.ld | 4 ++++ patches/linker/link_pal11.ld | 4 ++++ patches/linker/link_pal12.ld | 4 ++++ patches/source/button_descriptions.c | 15 +++++++++++++++ patches/source/element_alpha.c | 6 ++++++ patches/source/element_alpha.h | 23 +++++++++++++++++++++++ patches/source/menu.c | 3 ++- patches/source/patch.s | 6 ++++++ 12 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 patches/source/button_descriptions.c create mode 100644 patches/source/element_alpha.c create mode 100644 patches/source/element_alpha.h diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index b1770ed..4e1253d 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -112,8 +112,12 @@ ntsc10_EXIImmEx = 0x81334fa8; 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_update_button_alphas = 0x81311a98; diff --git a/patches/linker/link_ntsc11.ld b/patches/linker/link_ntsc11.ld index bbd988d..3cc7865 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -112,8 +112,12 @@ ntsc11_EXIImmEx = 0x8135a9d0; 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_update_button_alphas = 0x81311ce8; diff --git a/patches/linker/link_ntsc12_001.ld b/patches/linker/link_ntsc12_001.ld index 9427278..f24a1b3 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -107,8 +107,12 @@ ntsc12_001_EXIImmEx = 0x81363dc0; ntsc12_001_stock_banner_ptr = 0x809f8440; 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_update_button_alphas = 0x81312080; diff --git a/patches/linker/link_ntsc12_101.ld b/patches/linker/link_ntsc12_101.ld index b24bfe8..d0e485f 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -107,8 +107,12 @@ ntsc12_101_EXIImmEx = 0x81363f24; 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_update_button_alphas = 0x81312098; diff --git a/patches/linker/link_pal10.ld b/patches/linker/link_pal10.ld index d34963f..8b0f8f5 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -109,8 +109,12 @@ pal10_EXIImmEx = 0x8135dfb0; 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_update_button_alphas = 0x81312580; diff --git a/patches/linker/link_pal11.ld b/patches/linker/link_pal11.ld index e7ba675..4ff78f6 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -107,8 +107,12 @@ pal11_EXIImmEx = 0x8135a8f0; 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_update_button_alphas = 0x81311c14; diff --git a/patches/linker/link_pal12.ld b/patches/linker/link_pal12.ld index b62722e..d1f688c 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -108,8 +108,12 @@ pal12_EXIImmEx = 0x8136729c; 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_update_button_alphas = 0x813126c0; diff --git a/patches/source/button_descriptions.c b/patches/source/button_descriptions.c new file mode 100644 index 0000000..544301d --- /dev/null +++ b/patches/source/button_descriptions.c @@ -0,0 +1,15 @@ +#include + +#include "attr.h" +#include "element_alpha.h" + +__attribute_reloc__ void (*draw_buttons)(u8); +__attribute_reloc__ void (*update_button_alphas)(); + +__attribute_used__ void patch_draw_buttons(u8 buttons_alpha) { + draw_buttons(buttons_alpha); // Always called with `buttons_alpha` of 255 +} + +__attribute_used__ void patch_update_button_alphas() { + update_button_alphas(); +} 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..f6a3fd6 --- /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 unk1; +} 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/menu.c b/patches/source/menu.c index da85f55..8f36224 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -27,6 +27,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" @@ -46,7 +48,6 @@ __attribute_reloc__ model_data *save_empty; // for menu elements // __attribute_reloc__ void (*draw_start_info)(u8 alpha); __attribute_reloc__ void (*draw_start_anim)(u8 alpha); -__attribute_reloc__ void (*get_element_alpha)(void *element, u16 *alpha, u32 *unk); __attribute_reloc__ void *banner_element_alpha; // unknown blob (from memcard menu) diff --git a/patches/source/patch.s b/patches/source/patch.s index 35f0ca6..8b7958c 100644 --- a/patches/source/patch.s +++ b/patches/source/patch.s @@ -116,6 +116,12 @@ patch_inst_pal "_fix_video_mode_init" 0x81300520 0x81300520 0x81300610 bl get_tv 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 From c15cce368655fec61f99f0f53d360887097cd83d Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 14 Jun 2025 20:07:49 +0100 Subject: [PATCH 13/31] Add UI for switching between Flippy and disc drive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While on the cube’s ‘Game play’ menu (before pressing A to enter the menu), this displays a ‘L: Disc’ and ‘R: FlippyDrive’ UI, using icons to represent the two devices. The selected device is highlighted, and this will update when a new one is selected (using L or R). As not much gets drawn outside of the cube on this screen, the simplest way to do this was to draw it along with the button descriptions (making care to only show it while this screen is active). Additionally, this relies on the element alpha and blob systems, hence the added code for them. --- patches/Makefile | 8 +++ patches/data/disc.bti | Bin 0 -> 832 bytes patches/data/flippydrive.bti | Bin 0 -> 1056 bytes patches/data/l_button.bti | Bin 0 -> 320 bytes patches/data/r_button.bti | Bin 0 -> 320 bytes patches/linker/link_ntsc10.ld | 1 + patches/linker/link_ntsc11.ld | 1 + patches/linker/link_ntsc12_001.ld | 1 + patches/linker/link_ntsc12_101.ld | 1 + patches/linker/link_pal10.ld | 1 + patches/linker/link_pal11.ld | 1 + patches/linker/link_pal12.ld | 1 + patches/source/blob.h | 26 +++++++++ patches/source/button_descriptions.c | 8 +++ patches/source/button_descriptions.h | 3 + patches/source/custom_ui_blob.c | 79 +++++++++++++++++++++++++++ patches/source/custom_ui_blob.h | 11 ++++ patches/source/device_selector.c | 56 +++++++++++++++++++ patches/source/device_selector.h | 4 ++ patches/source/draw.c | 14 ++--- patches/source/draw.h | 15 ++--- patches/source/main.c | 1 + 22 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 patches/data/disc.bti create mode 100644 patches/data/flippydrive.bti create mode 100644 patches/data/l_button.bti create mode 100644 patches/data/r_button.bti create mode 100644 patches/source/blob.h create mode 100644 patches/source/button_descriptions.h create mode 100644 patches/source/custom_ui_blob.c create mode 100644 patches/source/custom_ui_blob.h create mode 100644 patches/source/device_selector.c create mode 100644 patches/source/device_selector.h diff --git a/patches/Makefile b/patches/Makefile index f8a7f12..e7ffcb7 100644 --- a/patches/Makefile +++ b/patches/Makefile @@ -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 0000000000000000000000000000000000000000..b0272c451f176af05616377d42c5229068ee0672 GIT binary patch literal 832 zcmZ`%Jx{_=6g@>vDEPtXW=97Ton2%kPRtJWcQ}|Z>Qv&yWHAm-ildW%K^7B@8YUMO zP&6r|ay_TmpaK`}dwu7A^!B_1V4c4|<~RWVGA~&tmn@+MkRAMlO2~xfGzLXKDj>c9 zF3m&7u9NVs4R0Qgj$4y$Mo5vHodCOb2CP~c*oX)PbiHS))rl$z@UP&+Ipp4f%?c0} z08Tak!Qw9a+9!Qj@Xe~3JL|! z0uNXtn(K}R;Dq@muuN#BGIpJXYd&DEKPpJGw5x$|gbS^E*MDf0U$E4SSfO2K;Ywc2M^hwdZX^7Kk2YI4x;|+dD`PQY~rSD literal 0 HcmV?d00001 diff --git a/patches/data/flippydrive.bti b/patches/data/flippydrive.bti new file mode 100644 index 0000000000000000000000000000000000000000..a69e7355e02a794a2f1d85970dd063bcb6ed0616 GIT binary patch literal 1056 zcmc&yF;2rk5F7`R*noXdP!n0A10_;A8dA_hfg76S2jK@X9Sy?p0Ypp11Ca0&@&qIr zM0IimmOyBkb7JElhnAJ+Zue%^+Bq9Q4=v1vWdVFlw6gWrqC_Nv{zc46(pkXPUX>!} zxyOI;Zd%0xc|XvPsuVvwmjB|VW6J_w^BsDEORaQ0P}&ovtqrAX-Dz%Wf}-GriD+-1 z=o2Ove!&xeUKb`$62CUHJL$~?SK4ZLtA^uqd)7lO5!spt=vCm1_Vjl_%WbiZdmT!Lf$ z@Q;!p4UWLe!|UBGUe3?v`inVBNx8ptT%_a|{KYeg3Y$<@yD;7>f?hAQGj0!@@oEK~ gdF;{Wz42QS!&lf_Z>X;=m?CD+`#}$n{C@ut-{Q_kqyPW_ literal 0 HcmV?d00001 diff --git a/patches/data/l_button.bti b/patches/data/l_button.bti new file mode 100644 index 0000000000000000000000000000000000000000..c015a17381dc6b5becff91f9c041aed57a4cd669 GIT binary patch literal 320 zcmZQzU=RU96u`*HzyL%bpuoVudzXQM|35D508|u!2Qx7I2kOO#ahnaY6Q9{2Is9gW e>_s*IKN?9~ch(|H5$K{SSr%`~Lu)NSgUT gccOp;_5TG9*#8eW!2f^30q*}7P$YpIbo0^W0ip+?9{>OV literal 0 HcmV?d00001 diff --git a/patches/linker/link_ntsc10.ld b/patches/linker/link_ntsc10.ld index 4e1253d..c822ce0 100644 --- a/patches/linker/link_ntsc10.ld +++ b/patches/linker/link_ntsc10.ld @@ -120,4 +120,5 @@ 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 3cc7865..1d173bf 100644 --- a/patches/linker/link_ntsc11.ld +++ b/patches/linker/link_ntsc11.ld @@ -120,4 +120,5 @@ 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 f24a1b3..6852b91 100644 --- a/patches/linker/link_ntsc12_001.ld +++ b/patches/linker/link_ntsc12_001.ld @@ -115,4 +115,5 @@ 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 d0e485f..e08331d 100644 --- a/patches/linker/link_ntsc12_101.ld +++ b/patches/linker/link_ntsc12_101.ld @@ -115,4 +115,5 @@ 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 8b0f8f5..217d0bc 100644 --- a/patches/linker/link_pal10.ld +++ b/patches/linker/link_pal10.ld @@ -117,4 +117,5 @@ 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 4ff78f6..618cc56 100644 --- a/patches/linker/link_pal11.ld +++ b/patches/linker/link_pal11.ld @@ -115,4 +115,5 @@ 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 d1f688c..3eddcd0 100644 --- a/patches/linker/link_pal12.ld +++ b/patches/linker/link_pal12.ld @@ -116,4 +116,5 @@ 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/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/button_descriptions.c b/patches/source/button_descriptions.c index 544301d..bcde74a 100644 --- a/patches/source/button_descriptions.c +++ b/patches/source/button_descriptions.c @@ -1,15 +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..e6fc0cb --- /dev/null +++ b/patches/source/device_selector.c @@ -0,0 +1,56 @@ +#include "device_selector.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 + +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 }; + +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}; + + // TODO: What parameters are appropriate?! + setup_tex_draw(1, 0, 1); // 101 / 110 / 100 + + 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; + update_element_alpha(&device_icons_alpha, should_show_device_icons ? element_alpha_visible : element_alpha_hidden); + + update_element_alpha(&disc_drive_icon_alpha, is_disc_drive_selected ? element_alpha_visible : element_alpha_dimmed); + update_element_alpha(&flippydrive_icon_alpha, !is_disc_drive_selected ? element_alpha_visible : element_alpha_dimmed); +} diff --git a/patches/source/device_selector.h b/patches/source/device_selector.h new file mode 100644 index 0000000..72008b6 --- /dev/null +++ b/patches/source/device_selector.h @@ -0,0 +1,4 @@ +#pragma once + +void draw_device_icons(); +void update_device_icon_alphas(); diff --git a/patches/source/draw.c b/patches/source/draw.c index 6783714..732f3db 100644 --- a/patches/source/draw.c +++ b/patches/source/draw.c @@ -6,14 +6,14 @@ __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, box_draw_group* header, GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); -__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 (*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 blob_header_t *blob_ptr, const void *blob_a, const void *blob_b, 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)(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 (*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); diff --git a/patches/source/draw.h b/patches/source/draw.h index 03d9149..000497f 100644 --- a/patches/source/draw.h +++ b/patches/source/draw.h @@ -1,5 +1,6 @@ #pragma once +#include "blob.h" #include "structs.h" #include @@ -9,14 +10,14 @@ 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, box_draw_group* header, GXColor* texa, int inside_x, int inside_y, int inside_width, int inside_height); -extern void (*draw_blob_fixed)(void *blob_ptr, void *blob_a, void *blob_b, GXColor *color); -extern void (*draw_blob_text)(u32 type, void *blob, GXColor *color, char *str, s32 len); -extern void (*draw_blob_text_long)(u32 type, void *blob, GXColor *color, char *str, s32 len); -extern void (*draw_blob_border)(u32 type, void *blob, GXColor *color); -extern void (*draw_blob_tex)(u32 type, void *blob, GXColor *color, tex_data *dat); +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 blob_header_t *blob_ptr, const void *blob_a, const void *blob_b, 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)(s32 unk0, s32 unk1, s32 unk2); -extern void (*draw_named_tex)(u32 type, void *blob, GXColor *color, s16 x, s16 y); +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); diff --git a/patches/source/main.c b/patches/source/main.c index 6405e04..ca08214 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -417,6 +417,7 @@ __attribute_used__ u32 get_tvmode() { __attribute_used__ void top_level_menu_extra_inputs() { s16 gameselect_vertical_cube_rotation = 0x4000; + // TODO: Make sure the menu's also not currently fading if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation) { if (!bs2_is_switching_device()) { From d1859f439c73d55516a52ec6ac83652d40146d43 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 14 Jun 2025 20:14:49 +0100 Subject: [PATCH 14/31] Move device selection code, minor tidy-ups These should all be non-functional changes: - Moves the device selection input handling to `device_selector.c` - Move unused variables out of `main.c` - Make `top_level_banner_element_alpha` use our struct type, for clarity --- patches/source/bs2.c | 7 ++++--- patches/source/device_selector.c | 25 +++++++++++++++++++++++++ patches/source/main.c | 27 --------------------------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/patches/source/bs2.c b/patches/source/bs2.c index d1cf686..d6f3df6 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -3,6 +3,7 @@ #include "attr.h" #include "boot.h" #include "dol.h" +#include "element_alpha.h" #include "flippy_sync.h" #include "gameid.h" #include "games.h" @@ -32,9 +33,9 @@ __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__ u16 *top_level_banner_element_alpha; // This could be a `element_alpha_state_t` (once that's merged), but we only need the first member +__attribute_reloc__ element_alpha_state_t *top_level_banner_element_alpha; +__attribute_reloc__ u16 *cube_menu_alpha; -extern u16 *cube_menu_alpha; extern u32 *banner_ready; extern const BNR **banner_pointer; extern u32 start_passthrough_game; @@ -113,7 +114,7 @@ void bs2tick_check_device_switch() { // 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 > 0 && *cube_menu_alpha < 0x7FFF; + 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 diff --git a/patches/source/device_selector.c b/patches/source/device_selector.c index e6fc0cb..36d2f9f 100644 --- a/patches/source/device_selector.c +++ b/patches/source/device_selector.c @@ -1,5 +1,6 @@ #include "device_selector.h" +#include "audio.h" #include "bs2.h" #include "button_descriptions.h" #include "custom_ui_blob.h" @@ -16,10 +17,34 @@ #include +__attribute_reloc__ s16 *cube_menu_rotation_vertical; + 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() { + s16 gameselect_vertical_cube_rotation = 0x4000; + + // TODO: Make sure the menu's also not currently fading + if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation) { + + if (!bs2_is_switching_device()) { + // Handle L and R to select between disc drive and FlippyDrive + if ((pad_status->buttons_down & PAD_TRIGGER_L) && !is_disc_drive_selected) { + // Switch to the disc drive + Jac_PlaySe(SOUND_SUBMENU_ENTER); + is_disc_drive_selected = true; + + } else if ((pad_status->buttons_down & PAD_TRIGGER_R) && is_disc_drive_selected) { + // Switch to the FlippyDrive + Jac_PlaySe(SOUND_SUBMENU_ENTER); + is_disc_drive_selected = false; + } + } + } +} + void draw_device_icons() { u16 device_icons_output_alpha; get_element_alpha(&device_icons_alpha, &device_icons_output_alpha, NULL); diff --git a/patches/source/main.c b/patches/source/main.c index ca08214..81a0633 100644 --- a/patches/source/main.c +++ b/patches/source/main.c @@ -52,9 +52,6 @@ __attribute_reloc__ model *gc_text_model; __attribute_reloc__ model *logo_model; __attribute_reloc__ model *cube_model; -__attribute_reloc__ s16 *cube_menu_rotation_vertical; -__attribute_reloc__ u16 *cube_menu_alpha; - // locals __attribute_data__ static GXColorS10 color_cube; __attribute_data__ static GXColorS10 color_cube_low; @@ -66,8 +63,6 @@ __attribute_data__ static GXColorS10 color_bg_outer_1; __attribute_data__ gm_file_entry_t boot_entry; __attribute_data__ gm_file_entry_t second_boot_entry; -extern void (*Jac_PlaySe)(u32); - __attribute_used__ void mod_cube_colors() { if (cube_color == 0) { OSReport("Using default colors\n"); @@ -414,28 +409,6 @@ __attribute_used__ u32 get_tvmode() { return rmode->viTVMode; } -__attribute_used__ void top_level_menu_extra_inputs() { - s16 gameselect_vertical_cube_rotation = 0x4000; - - // TODO: Make sure the menu's also not currently fading - if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation) { - - if (!bs2_is_switching_device()) { - // Handle L and R to select between disc drive and FlippyDrive - if ((pad_status->buttons_down & PAD_TRIGGER_L) && !is_disc_drive_selected) { - // Switch to the disc drive - Jac_PlaySe(SOUND_SUBMENU_ENTER); - is_disc_drive_selected = true; - - } else if ((pad_status->buttons_down & PAD_TRIGGER_R) && is_disc_drive_selected) { - // Switch to the FlippyDrive - Jac_PlaySe(SOUND_SUBMENU_ENTER); - is_disc_drive_selected = false; - } - } - } -} - 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); From af89f0b4877d6c49957e4ceef2fbd40992b65371 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 14 Jun 2025 20:22:17 +0100 Subject: [PATCH 15/31] Only allow device changes when fully faded in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This matches how other inputs behave while backing out from the ‘press start’ or FlippyDrive menus. --- patches/source/device_selector.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/patches/source/device_selector.c b/patches/source/device_selector.c index 36d2f9f..a87304e 100644 --- a/patches/source/device_selector.c +++ b/patches/source/device_selector.c @@ -19,6 +19,9 @@ __attribute_reloc__ s16 *cube_menu_rotation_vertical; +// TODO: Can we find a home for this? +extern u16 *cube_menu_alpha; + 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 }; @@ -26,8 +29,7 @@ static element_alpha_state_t flippydrive_icon_alpha = (element_alpha_state_t){ . __attribute_used__ void top_level_menu_extra_inputs() { s16 gameselect_vertical_cube_rotation = 0x4000; - // TODO: Make sure the menu's also not currently fading - if (*next_menu_id == MENU_GAMESELECT_ID && *cube_menu_rotation_vertical == gameselect_vertical_cube_rotation) { + 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 From 4318ed5e56d56560bbc49b54644c8df71cce37e5 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sun, 15 Jun 2025 19:47:06 +0100 Subject: [PATCH 16/31] Make an enum for the disc drive and Flippy devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should make things a bit clearer than a simple `bool`, especially once auto-device switching (on boot) gets implemented. As a nice bonus, it opens the door for this to be extended in future (e.g. for adding separate SD Card and network ‘devices’). --- patches/source/bs2.c | 89 ++++++++++++++++----------- patches/source/bs2.h | 7 ++- patches/source/device_selector.c | 12 ++-- patches/source/menu.c | 101 +++++++++++++++++-------------- 4 files changed, 123 insertions(+), 86 deletions(-) diff --git a/patches/source/bs2.c b/patches/source/bs2.c index d6f3df6..012371d 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -17,8 +17,8 @@ #include -bool is_disc_drive_selected = false; -static bool is_disc_drive_active = false; +device_t selected_device; +static device_t active_device; static bool is_switching_device = false; __attribute_data__ u32 force_swiss_boot = 0; @@ -41,14 +41,18 @@ extern const BNR **banner_pointer; extern u32 start_passthrough_game; void bs2init() { - is_disc_drive_selected = start_passthrough_game; - is_disc_drive_active = is_disc_drive_selected; + selected_device = start_passthrough_game ? device_disc_drive : device_flippydrive; + active_device = selected_device; is_switching_device = false; - if (is_disc_drive_active) { - gm_start_disc_thread(); - } else { - gm_start_thread("/"); + switch (active_device) { + case device_disc_drive: + gm_start_disc_thread(); + break; + + case device_flippydrive: + gm_start_thread("/"); + break; } } @@ -97,11 +101,11 @@ u32 bs2tick_disc() { } void bs2tick_check_device_switch() { - if ((is_disc_drive_selected != is_disc_drive_active) && !is_switching_device) { + if ((selected_device != active_device) && !is_switching_device) { // Begin switching to the new device is_switching_device = true; - if (is_disc_drive_active) { + if (active_device == device_disc_drive) { // Request the disc drive thread to stop request_disc_stop_thread = true; @@ -118,30 +122,38 @@ void bs2tick_check_device_switch() { if (!is_banner_visible) { // If the thread's stopped, restart it and stop switching - if (is_disc_drive_active) { - if (!game_disc_running) { - gm_deinit_thread(); - is_switching_device = false; - } - } else { - if (!game_enum_running) { - gm_deinit_thread(); - is_switching_device = false; - } + 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 - is_disc_drive_active = is_disc_drive_selected; - if (is_disc_drive_active) { - gm_start_disc_thread(); - } else { - if (*cur_menu_id != MENU_GAMESELECT_TRANSITION_ID) { - *banner_pointer = (const BNR *)&default_opening_bin[0]; - *banner_ready = 1; - } - gm_start_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; } } } @@ -155,11 +167,18 @@ __attribute_used__ u32 bs2tick() { return STATE_WAIT_LOAD; } - if (is_disc_drive_active) { - return bs2tick_disc(); - } else { - return bs2tick_flippydrive(); + 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; } + + return return_value; } __attribute_used__ void bs2start() { @@ -168,7 +187,7 @@ __attribute_used__ void bs2start() { // read boot info into lowmem struct dolphin_lowmem *lowmem = (struct dolphin_lowmem*)0x80000000; - if (!is_disc_drive_active) { + if (active_device != device_disc_drive) { gm_deinit_thread(); } else { request_disc_start_game = true; @@ -211,7 +230,7 @@ __attribute_used__ void bs2start() { ICInvalidateRange((void*)start_addr, len); // Passthrough mode - if (is_disc_drive_active) { + if (active_device == device_disc_drive) { chainload_boot_game(NULL, true); } diff --git a/patches/source/bs2.h b/patches/source/bs2.h index d405513..e20d674 100644 --- a/patches/source/bs2.h +++ b/patches/source/bs2.h @@ -7,7 +7,12 @@ #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 bool is_disc_drive_selected; +typedef enum { + device_disc_drive, + device_flippydrive +} device_t; + +extern device_t selected_device; void bs2init(); bool bs2_is_switching_device(); diff --git a/patches/source/device_selector.c b/patches/source/device_selector.c index a87304e..b73e955 100644 --- a/patches/source/device_selector.c +++ b/patches/source/device_selector.c @@ -33,15 +33,15 @@ __attribute_used__ void top_level_menu_extra_inputs() { if (!bs2_is_switching_device()) { // Handle L and R to select between disc drive and FlippyDrive - if ((pad_status->buttons_down & PAD_TRIGGER_L) && !is_disc_drive_selected) { + if ((pad_status->buttons_down & PAD_TRIGGER_L) && selected_device != device_disc_drive) { // Switch to the disc drive Jac_PlaySe(SOUND_SUBMENU_ENTER); - is_disc_drive_selected = true; + selected_device = device_disc_drive; - } else if ((pad_status->buttons_down & PAD_TRIGGER_R) && is_disc_drive_selected) { + } else if ((pad_status->buttons_down & PAD_TRIGGER_R) && selected_device != device_flippydrive) { // Switch to the FlippyDrive Jac_PlaySe(SOUND_SUBMENU_ENTER); - is_disc_drive_selected = false; + selected_device = device_flippydrive; } } } @@ -78,6 +78,6 @@ void update_device_icon_alphas() { bool should_show_device_icons = *next_menu_id == MENU_GAMESELECT_ID; update_element_alpha(&device_icons_alpha, should_show_device_icons ? element_alpha_visible : element_alpha_hidden); - update_element_alpha(&disc_drive_icon_alpha, is_disc_drive_selected ? element_alpha_visible : element_alpha_dimmed); - update_element_alpha(&flippydrive_icon_alpha, !is_disc_drive_selected ? element_alpha_visible : element_alpha_dimmed); + 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); } diff --git a/patches/source/menu.c b/patches/source/menu.c index 8f36224..692b0d7 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -9,6 +9,7 @@ #include #include "audio.h" +#include "bs2.h" #include "draw.h" #include "usbgecko.h" #include "menu.h" @@ -70,9 +71,6 @@ __attribute_reloc__ u32 *bs2start_ready; __attribute_reloc__ const BNR **banner_pointer; __attribute_reloc__ u32 *banner_ready; -// TODO: Find a better home for this -extern bool is_disc_drive_selected; - typedef struct { f32 scale; f32 opacity; @@ -621,7 +619,7 @@ __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 } __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { - if (is_disc_drive_selected) { + if (selected_device == device_disc_drive) { // TODO: I don't think this is necessary - check! static bool first_draw = true; if (first_draw) { @@ -644,34 +642,38 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, BNRDesc *desc = NULL; bool show_full_banner = true; - if (is_disc_drive_selected) { - game_region = disc_read_region; + 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); + 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; + 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? + 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]; } - 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 - } else { - 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]; - 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; + } - 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; } - - desc = &entry->desc; - show_full_banner = entry->type == GM_FILE_TYPE_GAME; } if (game_region == 'J') switch_lang_jpn(); @@ -707,7 +709,7 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, draw_blob_fixed(game_blob_text, game_blob_a, game_blob_b, &white); // Messages relating to reading a disc - if (is_disc_drive_selected) { + 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); @@ -723,12 +725,16 @@ __attribute_used__ void pre_menu_alpha_setup() { menu_alpha_setup(); // run original function if (*cur_menu_id == MENU_GAMESELECT_ID && *next_menu_id == MENU_GAMESELECT_TRANSITION_ID) { - if (is_disc_drive_selected) { - OSReport("Resetting back to SUBMENU_GAMESELECT_START\n"); - current_gameselect_state = SUBMENU_GAMESELECT_START; - } else { - OSReport("Resetting back to SUBMENU_GAMESELECT_LOADER\n"); - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; + 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; } custom_menu_transition_alpha = current_gameselect_state == SUBMENU_GAMESELECT_LOADER ? 0xFF : 0; @@ -817,14 +823,18 @@ __attribute_used__ s32 handle_gameselect_inputs() { if (pad_status->buttons_down & PAD_BUTTON_B) { if (current_gameselect_state == SUBMENU_GAMESELECT_START && !in_submenu_transition) { // TODO: Implement a proper navigation stack, so we don't need so much special casing - if (is_disc_drive_selected) { - anim_step = 0; // anim reset - // *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset - not relevant for disc - Jac_PlaySe(SOUND_MENU_EXIT); - return MENU_GAMESELECT_ID; - } else { - in_submenu_transition = true; - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; + switch (selected_device) { + case device_disc_drive: + anim_step = 0; // anim reset + // *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset - not relevant for disc + Jac_PlaySe(SOUND_MENU_EXIT); + return MENU_GAMESELECT_ID; + break; + + case device_flippydrive: + in_submenu_transition = true; + current_gameselect_state = SUBMENU_GAMESELECT_LOADER; + break; } Jac_PlaySe(SOUND_SUBMENU_EXIT); @@ -880,11 +890,14 @@ __attribute_used__ s32 handle_gameselect_inputs() { if (current_gameselect_state == SUBMENU_GAMESELECT_START) { bool ready_to_start = false; gm_file_entry_t *entry = NULL; - if (is_disc_drive_selected) { - ready_to_start = disc_read_state == STATE_START_GAME; - } else { - entry = gm_get_game_entry(selected_slot); - ready_to_start = entry != NULL; + switch (selected_device) { + case device_disc_drive: + ready_to_start = disc_read_state == STATE_START_GAME; + break; + case device_flippydrive: + entry = gm_get_game_entry(selected_slot); + ready_to_start = entry != NULL; + break; } if (!*bs2start_ready) { @@ -899,7 +912,7 @@ __attribute_used__ s32 handle_gameselect_inputs() { Jac_StopSoundAll(); Jac_PlaySe(SOUND_MENU_FINAL); - if (!is_disc_drive_selected) { + 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)); From 37f7def8ed5db4a7fa4b0e74b905e9b1066bf448 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 28 Jun 2025 13:56:19 +0100 Subject: [PATCH 17/31] Add support for an automatic boot order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the user to specify, for instance, that they’d like to check the disc drive first, and fall back to the FlippyDrive if no disc is inserted. This will try each device in order, until it reaches the first successful one (or the boot animation finishes). --- cubeboot/include/device.h | 6 ++ cubeboot/source/main.c | 3 + cubeboot/source/settings.c | 92 +++++++++++++++++++++++++ cubeboot/source/settings.h | 5 ++ patches/include/device.h | 1 + patches/source/bs2.c | 134 +++++++++++++++++++++++++++++-------- patches/source/bs2.h | 7 +- 7 files changed, 214 insertions(+), 34 deletions(-) create mode 100644 cubeboot/include/device.h create mode 120000 patches/include/device.h 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 8f4ab94..e682223 100644 --- a/cubeboot/source/main.c +++ b/cubeboot/source/main.c @@ -316,6 +316,9 @@ 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); + // 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 b315092..6404a7e 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"); @@ -151,6 +210,39 @@ 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; + } + } + } + // // must stay allocated!! // free(config_buf); } diff --git a/cubeboot/source/settings.h b/cubeboot/source/settings.h index 6f6bc6c..37cf5a7 100644 --- a/cubeboot/source/settings.h +++ b/cubeboot/source/settings.h @@ -1,6 +1,9 @@ #include #include "const.h" +#include "device.h" + +#define MAX_BOOT_DEVICES 8 typedef struct settings { u32 cube_color; @@ -13,6 +16,8 @@ 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; } settings_t; extern char *buttons_names[]; 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/source/bs2.c b/patches/source/bs2.c index 012371d..57f510a 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -16,11 +16,31 @@ #include "default_opening_bin.h" #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 @@ -41,10 +61,18 @@ extern const BNR **banner_pointer; extern u32 start_passthrough_game; void bs2init() { - selected_device = start_passthrough_game ? device_disc_drive : device_flippydrive; + if (boot_devices_count > 0) { + selected_device = boot_devices[0]; + } else { + selected_device = start_passthrough_game ? device_disc_drive : device_flippydrive; + } active_device = selected_device; is_switching_device = false; + current_boot_device_index = 0; + finished_automatic_switching = boot_devices_count <= 0; + current_device_state = device_waiting; + switch (active_device) { case device_disc_drive: gm_start_disc_thread(); @@ -68,20 +96,12 @@ u32 bs2tick_flippydrive() { 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; - } + // 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 >= 3) { + if (*main_menu_id >= MAIN_MENU_ID_NO_DISC_FADE_IN) { return STATE_START_GAME; } @@ -97,13 +117,26 @@ 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; - return disc_read_state; + 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; + } + + // TODO: Do we need to handle post-boot delay? + + 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 @@ -159,26 +192,69 @@ void bs2tick_check_device_switch() { } } -__attribute_used__ u32 bs2tick() { - // TODO: On boot, try each device in a configurable order, and stick with the first successful one - bs2tick_check_device_switch(); - if (is_switching_device) { - // Just show as 'loading' while we wait - return STATE_WAIT_LOAD; +void bs2tick_auto_device_switch() { + if (finished_automatic_switching) { + // Automatic switching isn't active + return; } - u32 return_value = STATE_FATAL_ERROR; - switch (active_device) { - case device_disc_drive: - return_value = bs2tick_disc(); - break; + if (*main_menu_id >= MAIN_MENU_ID_ANIMATING_TO_MENU) { + // The GameCube logo is finished and we're transitioning to the main menu; + // disable automtatic switching, handing control to the user + finished_automatic_switching = true; + return; + } - case device_flippydrive: - return_value = bs2tick_flippydrive(); - break; + 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 + if (current_boot_device_index < boot_devices_count - 1) { + current_boot_device_index += 1; + 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; } +} - return return_value; +__attribute_used__ u32 bs2tick() { + 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() { diff --git a/patches/source/bs2.h b/patches/source/bs2.h index e20d674..2433459 100644 --- a/patches/source/bs2.h +++ b/patches/source/bs2.h @@ -1,5 +1,7 @@ #pragma once +#include "device.h" + #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 @@ -7,11 +9,6 @@ #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 -typedef enum { - device_disc_drive, - device_flippydrive -} device_t; - extern device_t selected_device; void bs2init(); From e5ec143a2a3776f7b48dc11937b4dff73c7911bc Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 28 Jun 2025 14:05:04 +0100 Subject: [PATCH 18/31] Minor tidy-ups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove some unneeded code when initialising the disc ‘Press Start’ menu, and remove some constants that are now in `bs2.h`. --- patches/source/menu.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/patches/source/menu.c b/patches/source/menu.c index 692b0d7..2c3f871 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -619,16 +619,6 @@ __attribute_used__ void custom_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 } __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, u8 broken_alpha_2) { - if (selected_device == device_disc_drive) { - // TODO: I don't think this is necessary - check! - static bool first_draw = true; - if (first_draw) { - first_draw = false; - setup_gameselect_anim(); - setup_cube_anim(); - } - } - // menu alpha u8 ui_alpha = alpha_1; GXColor white = {0xFF, 0xFF, 0xFF, ui_alpha}; @@ -767,14 +757,6 @@ __attribute_used__ void mod_gameselect_draw(u8 alpha_0, u8 alpha_1, u8 alpha_2) return; } -// TODO: Find a centralised spot for these! -#define STATE_WAIT_LOAD 0x0f -#define STATE_START_GAME 0x10 -#define STATE_NO_DISC 0x12 -#define STATE_COVER_OPEN 0x13 -#define STATE_READ_ERROR 0x16 -#define STATE_FATAL_ERROR 0x17 - static bool starting_game = false; __attribute_used__ s32 handle_gameselect_inputs() { From d01dfaf45cadeed209269dd38dc7364c284fe6ee Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 28 Jun 2025 15:02:58 +0100 Subject: [PATCH 19/31] Maintain a navigation stack for the gameplay menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it a bit simpler to maintain the different menu flows for the disc drive and FlippyDrive modes. As before, pressing B while on the ‘Press Start’ screen will return to the top-level gameplay menu if the disc drive is active, and back to the ‘loader’ menu if the FlippyDrive is active – just now with less device-specific code. --- patches/source/menu.c | 89 ++++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/patches/source/menu.c b/patches/source/menu.c index 2c3f871..3070b33 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -499,6 +499,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; @@ -727,6 +730,8 @@ __attribute_used__ void pre_menu_alpha_setup() { 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; @@ -757,6 +762,52 @@ __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 + switch (current_gameselect_state) { + case SUBMENU_GAMESELECT_LOADER: + break; + + case SUBMENU_GAMESELECT_START: + setup_gameselect_anim(); + setup_cube_anim(); + break; + } +} + +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() { @@ -803,34 +854,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) { - // TODO: Implement a proper navigation stack, so we don't need so much special casing - switch (selected_device) { - case device_disc_drive: - anim_step = 0; // anim reset - // *banner_pointer = (const BNR *)&default_opening_bin[0]; // banner reset - not relevant for disc - Jac_PlaySe(SOUND_MENU_EXIT); - return MENU_GAMESELECT_ID; - break; - - case device_flippydrive: - in_submenu_transition = true; - current_gameselect_state = SUBMENU_GAMESELECT_LOADER; - break; - } - - 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 = (const BNR *)&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; + } } } } @@ -848,12 +882,7 @@ __attribute_used__ s32 handle_gameselect_inputs() { sprintf(path, "%s/", entry->path); gm_start_thread(path); } else { - in_submenu_transition = true; - current_gameselect_state = SUBMENU_GAMESELECT_START; - - Jac_PlaySe(SOUND_SUBMENU_CONFIRM); - setup_gameselect_anim(); - setup_cube_anim(); + push_menu_stack(SUBMENU_GAMESELECT_START); if (entry->type == GM_FILE_TYPE_GAME) { mcp_set_gameid(entry); From 17d6fd6e98a362e402863e1234ca8eec553629c6 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 7 Jul 2025 20:58:00 +0100 Subject: [PATCH 20/31] Correct icon texture colours & resolve most TODOs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The disc/FlippyDrive/L/R icons now look significantly better, as they’re displayed with the correct colours. I think my I4-encoded icons are in linear colour space, and I previously called `setup_tex_draw()` with a flag to treat them as sRGB. Other than that, this re-adds support for post-boot delays, moves `cube_menu_alpha` to a shared header, and removes some TODOs from disc-loading that aren’t going to be done, or have already been done. --- patches/source/bs2.c | 22 ++++++++++++++-------- patches/source/bs2.h | 3 +++ patches/source/device_selector.c | 7 ++----- patches/source/draw.c | 2 +- patches/source/draw.h | 2 +- patches/source/games.c | 3 --- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/patches/source/bs2.c b/patches/source/bs2.c index 57f510a..b7f4f40 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -88,14 +88,7 @@ bool bs2_is_switching_device() { return is_switching_device; } -__attribute_data__ int frame_count = 0; u32 bs2tick_flippydrive() { - frame_count++; - if (!completed_time && cube_state->cube_anim_done) { - OSReport("FINISHED (%d frames)\n", frame_count); - completed_time = gettime(); - } - // 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; @@ -127,7 +120,13 @@ u32 bs2tick_disc() { current_device_state = device_waiting; } - // TODO: Do we need to handle post-boot delay? + // 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; } @@ -228,7 +227,14 @@ void bs2tick_auto_device_switch() { } } +__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(); + } + while (true) { bs2tick_check_device_switch(); if (is_switching_device) { diff --git a/patches/source/bs2.h b/patches/source/bs2.h index 2433459..52cab77 100644 --- a/patches/source/bs2.h +++ b/patches/source/bs2.h @@ -2,6 +2,8 @@ #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 @@ -10,6 +12,7 @@ #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; void bs2init(); bool bs2_is_switching_device(); diff --git a/patches/source/device_selector.c b/patches/source/device_selector.c index b73e955..d4456e7 100644 --- a/patches/source/device_selector.c +++ b/patches/source/device_selector.c @@ -19,9 +19,6 @@ __attribute_reloc__ s16 *cube_menu_rotation_vertical; -// TODO: Can we find a home for this? -extern u16 *cube_menu_alpha; - 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 }; @@ -64,8 +61,8 @@ void draw_device_icons() { GXColor disc_color = {0xFF, 0xFF, 0xFF, disc_drive_icon_output_alpha}; GXColor flippydrive_color = {0xFF, 0xFF, 0xFF, flippydrive_icon_output_alpha}; - // TODO: What parameters are appropriate?! - setup_tex_draw(1, 0, 1); // 101 / 110 / 100 + // 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); diff --git a/patches/source/draw.c b/patches/source/draw.c index 732f3db..78b3c7b 100644 --- a/patches/source/draw.c +++ b/patches/source/draw.c @@ -12,7 +12,7 @@ __attribute_reloc__ void (*draw_blob_text)(u32 type, const blob_header_t *blob, __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)(s32 unk0, s32 unk1, s32 unk2); +__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 diff --git a/patches/source/draw.h b/patches/source/draw.h index 000497f..2d541ff 100644 --- a/patches/source/draw.h +++ b/patches/source/draw.h @@ -16,7 +16,7 @@ extern void (*draw_blob_text)(u32 type, const blob_header_t *blob, const GXColor 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)(s32 unk0, s32 unk1, s32 unk2); +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 diff --git a/patches/source/games.c b/patches/source/games.c index eafddd3..fccad32 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -1034,7 +1034,6 @@ void *gm_disc_thread_worker(void *param) { } finished_reading_disc = true; - // TODO: Spin down the disc? continue; } @@ -1053,7 +1052,6 @@ void *gm_disc_thread_worker(void *param) { if (!game_info.valid) { disc_read_state = STATE_READ_ERROR; finished_reading_disc = true; - // TODO: Spin down the disc? continue; } @@ -1198,6 +1196,5 @@ void gm_deinit_thread() { OSReport("File enum done\n"); OSUnlockMutex(game_enum_mutex); } - // TODO: Handle the disc thread! } From 651a365c15e445ea6eb78bb22b3bab0fec3fff30 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 21 Jul 2025 21:25:59 +0100 Subject: [PATCH 21/31] Add a setting to disallow disc drive access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Intended for those who don’t have an original disc drive installed, setting `allow_disc_drive` to `0` will disable the device selector (so discs can’t be selected), and will skip past discs in the automatic boot order. --- cubeboot/source/main.c | 1 + cubeboot/source/settings.c | 9 +++++++ cubeboot/source/settings.h | 1 + patches/source/bs2.c | 46 ++++++++++++++++++++++++++------ patches/source/bs2.h | 1 + patches/source/device_selector.c | 12 ++++++++- patches/source/device_selector.h | 1 + patches/source/games.c | 6 +++++ 8 files changed, 68 insertions(+), 9 deletions(-) diff --git a/cubeboot/source/main.c b/cubeboot/source/main.c index e682223..0ccd6e3 100644 --- a/cubeboot/source/main.c +++ b/cubeboot/source/main.c @@ -318,6 +318,7 @@ int main(int argc, char **argv) { 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 6404a7e..8f07fd0 100644 --- a/cubeboot/source/settings.c +++ b/cubeboot/source/settings.c @@ -241,6 +241,15 @@ void load_settings() { 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; + } } // // must stay allocated!! diff --git a/cubeboot/source/settings.h b/cubeboot/source/settings.h index 37cf5a7..4c5fa0e 100644 --- a/cubeboot/source/settings.h +++ b/cubeboot/source/settings.h @@ -18,6 +18,7 @@ typedef struct settings { char *boot_buttons[MAX_BUTTONS]; device_t boot_devices[MAX_BOOT_DEVICES]; u32 boot_devices_count; + u32 allow_disc_drive; } settings_t; extern char *buttons_names[]; diff --git a/patches/source/bs2.c b/patches/source/bs2.c index b7f4f40..f1f9e6d 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -1,6 +1,7 @@ #include "bs2.h" #include "attr.h" +#include "device_selector.h" #include "boot.h" #include "dol.h" #include "element_alpha.h" @@ -47,6 +48,8 @@ __attribute_data__ u32 force_swiss_boot = 0; __attribute_data__ u32 postboot_delay_ms = 0; __attribute_data__ u64 completed_time = 0; +__attribute_data__ u32 is_disc_drive_allowed = 1; + // used to start game __attribute_reloc__ u32 (*PADSync)(); __attribute_reloc__ void (*__OSStopAudioSystem)(); @@ -60,17 +63,42 @@ 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() { - if (boot_devices_count > 0) { - selected_device = boot_devices[0]; + 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 ? device_disc_drive : device_flippydrive; + 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_boot_device_index = 0; - finished_automatic_switching = boot_devices_count <= 0; current_device_state = device_waiting; switch (active_device) { @@ -216,8 +244,10 @@ void bs2tick_auto_device_switch() { case device_not_ready: // The current device isn't currently usable (e.g. no disc); switch to the next one - if (current_boot_device_index < boot_devices_count - 1) { - current_boot_device_index += 1; + 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 diff --git a/patches/source/bs2.h b/patches/source/bs2.h index 52cab77..e8a4752 100644 --- a/patches/source/bs2.h +++ b/patches/source/bs2.h @@ -13,6 +13,7 @@ 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/device_selector.c b/patches/source/device_selector.c index d4456e7..002daa2 100644 --- a/patches/source/device_selector.c +++ b/patches/source/device_selector.c @@ -19,11 +19,17 @@ __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) { @@ -72,9 +78,13 @@ void draw_device_icons() { } void update_device_icon_alphas() { - bool should_show_device_icons = *next_menu_id == MENU_GAMESELECT_ID; + 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 index 72008b6..ca42094 100644 --- a/patches/source/device_selector.h +++ b/patches/source/device_selector.h @@ -2,3 +2,4 @@ void draw_device_icons(); void update_device_icon_alphas(); +void set_device_selector_enabled(bool is_enabled); diff --git a/patches/source/games.c b/patches/source/games.c index fccad32..591f49f 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -1161,6 +1161,12 @@ void gm_start_thread(const char *target) { } 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; From b26799ca51ea638187b0415e6f6eb42a75a497f0 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sun, 7 Sep 2025 20:04:40 +0100 Subject: [PATCH 22/31] Always read the `allow_disc_drive` setting This fixes a typo, where this setting was previously only read when `boot_order` was present. --- cubeboot/source/settings.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cubeboot/source/settings.c b/cubeboot/source/settings.c index 8f07fd0..fe40164 100644 --- a/cubeboot/source/settings.c +++ b/cubeboot/source/settings.c @@ -241,15 +241,15 @@ void load_settings() { 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; - } + // 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; } // // must stay allocated!! From b4309b46d17cc0cecde54e079275396445fbe863 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 8 Sep 2025 22:45:17 +0100 Subject: [PATCH 23/31] Increase delay after entering bypass mode Previously, on NTSC 1.0, the disc worker thread might briefly detect that the disc cover is open during startup. This would cause the automatic device switching logic to move onto the next boot device, or the GameCube to go to the menu. We already have a delay after switching into bypass mode. Not my favourite kind of fix, but upping this delay from 10ms to 20ms seems to resolve the issue. --- patches/source/games.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/source/games.c b/patches/source/games.c index 591f49f..69dcf8e 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -990,7 +990,7 @@ void *gm_disc_thread_worker(void *param) { disc_read_state = STATE_WAIT_LOAD; dvd_custom_bypass_enter(); - udelay_threaded(10 * 1000); + udelay_threaded(20 * 1000); // TODO: How do we recover if no DVD drive is installed? From 044f3189e142ca91edeeb1d41cf8555947a9209b Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Mon, 8 Sep 2025 22:47:12 +0100 Subject: [PATCH 24/31] Ensure `bs2init()` runs before `bs2tick()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently `bs2tick` may run before `pre_thread_init` does – so let’s protect against that. --- patches/source/bs2.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/patches/source/bs2.c b/patches/source/bs2.c index f1f9e6d..b4e530d 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -50,6 +50,8 @@ __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)(); @@ -110,6 +112,8 @@ void bs2init() { gm_start_thread("/"); break; } + + has_bs2init_run = true; } bool bs2_is_switching_device() { @@ -227,7 +231,7 @@ void bs2tick_auto_device_switch() { if (*main_menu_id >= MAIN_MENU_ID_ANIMATING_TO_MENU) { // The GameCube logo is finished and we're transitioning to the main menu; - // disable automtatic switching, handing control to the user + // disable automatic switching, handing control to the user finished_automatic_switching = true; return; } @@ -259,6 +263,10 @@ void bs2tick_auto_device_switch() { __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); From 47a530afd13c195b9e2f616277ff8631c25632be Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 20 Sep 2025 15:52:45 +0100 Subject: [PATCH 25/31] Set up audio streaming for discs properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an undetected error when calling `dvd_audio_config()`, due to ‘configuration out of permitted period’ (`0x052402`). It seems like `dvd_audio_config()` needs to be run shortly after initialising the disc (i.e. reading the disc ID), before reading other data. So, this moves this call from `bs2start()` to the disc-reading thread, so it can be run before reading the disc’s banner. --- patches/source/bs2.c | 1 - patches/source/dvd_threaded.c | 25 +++++++++++++++++++++++++ patches/source/dvd_threaded.h | 1 + patches/source/games.c | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/patches/source/bs2.c b/patches/source/bs2.c index b4e530d..2bf1383 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -329,7 +329,6 @@ __attribute_used__ void bs2start() { } 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); diff --git a/patches/source/dvd_threaded.c b/patches/source/dvd_threaded.c index 2733886..534be98 100644 --- a/patches/source/dvd_threaded.c +++ b/patches/source/dvd_threaded.c @@ -30,6 +30,7 @@ #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; @@ -102,6 +103,30 @@ int dvd_threaded_read_id(dvd_should_cancel_callback should_cancel) { 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; diff --git a/patches/source/dvd_threaded.h b/patches/source/dvd_threaded.h index 587cb88..6375c09 100644 --- a/patches/source/dvd_threaded.h +++ b/patches/source/dvd_threaded.h @@ -5,6 +5,7 @@ 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/games.c b/patches/source/games.c index 69dcf8e..cff78d7 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -1041,6 +1041,20 @@ void *gm_disc_thread_worker(void *param) { 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) { From 3c0c6fa699412e791a793c59e063ff519212e37b Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sat, 20 Sep 2025 16:01:26 +0100 Subject: [PATCH 26/31] Check for errors when reading banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure how, but apparently this was missed before. Doesn’t hurt to check. --- patches/source/games.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/patches/source/games.c b/patches/source/games.c index cff78d7..d203e43 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -1073,7 +1073,14 @@ void *gm_disc_thread_worker(void *param) { break; } - dvd_threaded_read(stock_banner_ptr, sizeof(BNR), game_info.bnr_offset, fd, should_stop_disc_thread_worker); + ret = dvd_threaded_read(stock_banner_ptr, sizeof(BNR), 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)game_info.game_id[3]; disc_read_banner_ready = true; From 8f29453f1f42f540d6af1efb5bca2658016cb722 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Wed, 30 Jul 2025 21:16:55 +0100 Subject: [PATCH 27/31] Fix types in `draw_blob_fixed` declaration This updates the `draw_blob_fixed()` declaration to more accurately match the types that it actually uses. Cherry-picked and trimmed down from 32234b9 on `improve-pal-language-support`; that commit will also change the names and types of the variables we pass in to this function. --- patches/source/draw.c | 2 +- patches/source/draw.h | 3 ++- patches/source/element_alpha.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/patches/source/draw.c b/patches/source/draw.c index 78b3c7b..7bf2d22 100644 --- a/patches/source/draw.c +++ b/patches/source/draw.c @@ -7,7 +7,7 @@ __attribute_reloc__ void (*gx_draw_text)(u16 index, text_group* text, text_draw_ __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 blob_header_t *blob_ptr, const void *blob_a, const void *blob_b, const GXColor *color); +__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); diff --git a/patches/source/draw.h b/patches/source/draw.h index 2d541ff..f9eae16 100644 --- a/patches/source/draw.h +++ b/patches/source/draw.h @@ -1,6 +1,7 @@ #pragma once #include "blob.h" +#include "element_alpha.h" #include "structs.h" #include @@ -11,7 +12,7 @@ extern void (*gx_draw_text)(u16 index, text_group* text, text_draw_group* text_d 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 blob_header_t *blob_ptr, const void *blob_a, const void *blob_b, const GXColor *color); +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); diff --git a/patches/source/element_alpha.h b/patches/source/element_alpha.h index f6a3fd6..9696c0c 100644 --- a/patches/source/element_alpha.h +++ b/patches/source/element_alpha.h @@ -10,7 +10,7 @@ typedef struct { u8 unk0; u8 max_output; u16 unknown_output_multiplier; - u32 unk1; + u32 blob_offset; // Used by `draw_blob_fixed()` to find the relevant element } element_alpha_state_t; typedef enum { From 25a148d74e694fe17092961e9e1f7af051d8c78a Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Thu, 26 Mar 2026 21:43:12 +0000 Subject: [PATCH 28/31] Update the MemCard Pro game ID for discs in-menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the disc drive is selected, this updates the MemCard Pro GC when: - The gameplay menu is entered while a disc is ready to start, or - A disc becomes ready to start while the gameplay menu is open - As a disc is started (as before, though now passing through the game name from the loaded banner, rather than ‘DISC GAME’) I decided to only perform switches while in the gameplay menu, to avoid the risk of issues from the memory card being switched while in the Memory Card menu. I’ve also added some more stringent synchronisation to the MemCard Pro code, whlie I’m looking at it. --- patches/source/bs2.c | 5 ++- patches/source/dolphin_dvd.h | 2 +- patches/source/gameid.c | 66 +++++++++++++++++++++++++++--------- patches/source/gameid.h | 3 ++ patches/source/games.c | 10 +++--- patches/source/games.h | 5 +++ patches/source/menu.c | 61 ++++++++++++++++++++++++++------- 7 files changed, 117 insertions(+), 35 deletions(-) diff --git a/patches/source/bs2.c b/patches/source/bs2.c index 2bf1383..9b33090 100644 --- a/patches/source/bs2.c +++ b/patches/source/bs2.c @@ -18,6 +18,7 @@ #include #include +#include #define MAIN_MENU_ID_SETUP_ERROR 0 #define MAIN_MENU_ID_READING_DISC_ANIMATION 1 @@ -330,7 +331,9 @@ __attribute_used__ void bs2start() { 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]); - char diskName[64] = "DISC GAME\0"; + // 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); } diff --git a/patches/source/dolphin_dvd.h b/patches/source/dolphin_dvd.h index f3ecfb9..ecb9115 100644 --- a/patches/source/dolphin_dvd.h +++ b/patches/source/dolphin_dvd.h @@ -109,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; 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 8f9db9d..3f62656 100644 --- a/patches/source/games.c +++ b/patches/source/games.c @@ -25,6 +25,7 @@ #include "dolphin_dvd.h" #include "dvd_threaded.h" #include "flippy_sync.h" +#include "gameid.h" #include "gc_dvd.h" #include "metaphrasis.h" @@ -986,6 +987,7 @@ atomic_bool request_disc_start_game = false; 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; @@ -1067,8 +1069,8 @@ void *gm_disc_thread_worker(void *param) { } // Get the banner - dolphin_game_into_t game_info = get_game_info_with_open_game(fd, should_stop_disc_thread_worker); - if (!game_info.valid) { + 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; @@ -1078,7 +1080,7 @@ void *gm_disc_thread_worker(void *param) { break; } - ret = dvd_threaded_read(stock_banner_ptr, sizeof(BNR), game_info.bnr_offset, fd, should_stop_disc_thread_worker); + 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; @@ -1086,7 +1088,7 @@ void *gm_disc_thread_worker(void *param) { continue; } - disc_read_region = (char)game_info.game_id[3]; + disc_read_region = (char)disc_game_info.game_id[3]; disc_read_banner_ready = true; // The disc's loaded! diff --git a/patches/source/games.h b/patches/source/games.h index d777958..78233c0 100644 --- a/patches/source/games.h +++ b/patches/source/games.h @@ -9,6 +9,8 @@ #include "decomp_ar.h" +typedef struct dolphin_game_into_t dolphin_game_into_t; + // Backing typedef enum { GM_LOAD_STATE_NONE, @@ -115,6 +117,9 @@ 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; diff --git a/patches/source/menu.c b/patches/source/menu.c index 48e791a..e111f71 100644 --- a/patches/source/menu.c +++ b/patches/source/menu.c @@ -93,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(); @@ -766,6 +768,39 @@ __attribute_used__ void original_gameselect_menu(u8 broken_alpha_0, u8 alpha_1, 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; @@ -791,6 +826,11 @@ __attribute_used__ void pre_menu_alpha_setup() { 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); first_transition = false; @@ -833,15 +873,7 @@ static void push_menu_stack(u32 new_state) { in_submenu_transition = true; // Run code when transitioning to the new menu - switch (current_gameselect_state) { - case SUBMENU_GAMESELECT_LOADER: - break; - - case SUBMENU_GAMESELECT_START: - setup_gameselect_anim(); - setup_cube_anim(); - break; - } + on_submenu_shown(); } static bool pop_menu_stack() { @@ -940,10 +972,6 @@ __attribute_used__ s32 handle_gameselect_inputs() { } else { push_menu_stack(SUBMENU_GAMESELECT_START); - if (entry->type == GM_FILE_TYPE_GAME) { - mcp_set_gameid(entry); - } - // OSReport("Selected slot: %d (%p)\n", selected_slot, asset); } } @@ -960,6 +988,13 @@ __attribute_used__ s32 handle_gameselect_inputs() { 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); From 903640f0e98edd03b630e3c36d19d593bfdb83e8 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Fri, 29 May 2026 22:26:31 +0100 Subject: [PATCH 29/31] Add script to generate BTI textures from PNGs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a `convert.sh` that generates the BTI textures from PNGs in the expected format, making it easier to modify these. I’ve generated the PNGs from the original BTIs, ensuring that the pixel data is identical (modulo wrapping and alpha metadata that `wimgt` doesn’t let you change, and differences in padding data compared to GCFT). I’ve also included the SVGs (which were originally just in the pull request description), to make it easier to make modifications to these icons. --- patches/data/disc.bti | Bin 832 -> 832 bytes patches/data/flippydrive.bti | Bin 1056 -> 1056 bytes patches/data/l_button.bti | Bin 320 -> 320 bytes patches/data/original_png/disc.png | Bin 0 -> 428 bytes patches/data/original_png/flippydrive.png | Bin 0 -> 396 bytes patches/data/original_png/l_button.png | Bin 0 -> 122 bytes patches/data/original_png/r_button.png | Bin 0 -> 180 bytes patches/data/original_svg/disc.svg | 14 ++++++++++++++ patches/data/original_svg/flippydrive.svg | 6 ++++++ patches/data/original_svg/l_button.svg | 8 ++++++++ patches/data/original_svg/r_button.svg | 8 ++++++++ patches/data/r_button.bti | Bin 320 -> 320 bytes patches/scripts/convert.sh | 11 +++++++++++ 13 files changed, 47 insertions(+) create mode 100644 patches/data/original_png/disc.png create mode 100644 patches/data/original_png/flippydrive.png create mode 100644 patches/data/original_png/l_button.png create mode 100644 patches/data/original_png/r_button.png create mode 100644 patches/data/original_svg/disc.svg create mode 100644 patches/data/original_svg/flippydrive.svg create mode 100644 patches/data/original_svg/l_button.svg create mode 100644 patches/data/original_svg/r_button.svg create mode 100755 patches/scripts/convert.sh diff --git a/patches/data/disc.bti b/patches/data/disc.bti index b0272c451f176af05616377d42c5229068ee0672..ee2fc0cab5a3238e55329121f1f8326c1b961414 100644 GIT binary patch delta 17 YcmX@Wc7Tn8fr&wbL4%QTBZmbu03RI!-2eap delta 17 XcmX@Wc7Tn8fq_8-2pKkVSTF+s9{d8? diff --git a/patches/data/flippydrive.bti b/patches/data/flippydrive.bti index a69e7355e02a794a2f1d85970dd063bcb6ed0616..44d87193eef72a4401dd6263567f35e6bb091138 100644 GIT binary patch delta 17 YcmZ3$v4Devfr-I^L4lERBZmYF02{gjk^lez delta 17 YcmZ3$v4Devfq}t+L4ko`BZmYF02`SCjsO4v diff --git a/patches/data/l_button.bti b/patches/data/l_button.bti index c015a17381dc6b5becff91f9c041aed57a4cd669..20f850ae04b1e152ef1db1260362d1dbee67bad0 100644 GIT binary patch literal 320 zcmZQzVh~{vVPs@L0w5k#K!JgQ_bvki|9@QA0jMtk4+csR0JzNt*@@3=kQ`xvZa%6G UkQt{|?_&7>{~s8m$`a-Q0Osf-s{jB1 literal 320 zcmZQzU=RU96u`*HzyL%bpuoVudzXQM|35D508|u!2Qx7I2kOO#ahnaY6Q9{2Is9gW e>_s*IKNuk0004VNklNu zMgiOF^?beu>iG*;pBtbi4g7qsLvAIcOc%G1q6Roiy4tTCX*--_khyj0K-aj05=lLH zFYP&zTzW;W;qj2mqLP)lJQw^v>5}cZ6f%^E<*<$Q;B{$TIY>keIvVjn)ZK3OTF0yD_F7-vQ<)*jpiy*>?ndnpk%!~#$K2tasXRtvE@_Hk zM45*p7Bd4o{(K>sq#8{(L~1fgbJL!)%~6_70l1ushO*y1_qW-f`|HDPT40000@0003~NklA5xfE&fs8;$a7G9tzzBE*7$J;s`(dC;Leof5rF`W6@C3f+ z-Dewk>0!O0$NcY)XV4O%5}YJS>Y!ISe+Y_m=_Lo1<_DX+0swmjw+C7u8aR;~9CJ9h(cw4uEns&#Eo~34q~#z%$&$XOJYRgea*2P8FyEF9(ez z{jsNt)^>t;a$ZFS9D9f-C{%_JZkRU!->fiW!ySO+_V8f^Ny-Tmwyn9A5R0SK+B z0eb{K$2$Se*Y#Al!O#K>_a!g@!>(G!t)s00hW%~8JAg5^$ej+>&j3sfBv*Lve{)Zg qBoz>QXqtQq(B(Vc%>VyO|EF)7qR4mij6P%l0000hJ5gnVB{i zfPjXf0f!Q&kzoSk;-(`UMn)j9Kj{e;)%}hW9@a`sx>B!E(e7@|@aL+46kAHub&#Q+ Lu6{1-oD!M literal 0 HcmV?d00001 diff --git a/patches/data/original_png/r_button.png b/patches/data/original_png/r_button.png new file mode 100644 index 0000000000000000000000000000000000000000..59659c8900f3bd34cef66667f8e6a1f700a0e71c GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^A|T8GBp6maa=Hklsy$sCLnNl=o^upDY{0{MF;{Yl zfQzDtlcGq|(#8t`or*UEa=4ln$VioEMO*E=X}_4AVR=KA#gzT0zwN#1EX*+P>g8 + + + + + + + + + + + + + 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 index 9f2b3d9d0e67599f04a278cd858ee5e165fb0491..e2b13f27b9a5e7bc456a660d9c96dd35f9f7cf68 100644 GIT binary patch literal 320 zcmZQzVh~{vVPs@L0w5k#K!JgQ`z`|m?|)qQ&UOKY|Np-N@qdMb{r?vn;QfE$0N4Ky zK>Ps6K7eE_4km81L3SRR|3BgX|KC9KH!!>f8h7mf2Zlrczc3tl|AXPc{y#X(L>5Lj eA6brp;XwU=fdlsc0}k;2pKyTt{{>W8!aM+dm`Qs8 literal 320 zcmZQzU=RU96u`*HzyL%bpuoVueV2iO_dhOtXS)Ey|Nmcs_`kxz{{IUO@czGWfb0JU zAbtR3A7B8=;K#Vl2HAON{{Mvk|9=Bb*}(7?Xxy>?9~ch(|H5$K{SSr%`~Lu)NSgUT gccOp;_5TG9*#8eW!2f^30q*}7P$YpIbo0^W0ip+?9{>OV diff --git a/patches/scripts/convert.sh b/patches/scripts/convert.sh new file mode 100755 index 0000000..c0e3813 --- /dev/null +++ b/patches/scripts/convert.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +# Generates BTI textures from source PNGs +# (while SVGs are available too, they must be converted back to PNG first after any edits) +# Requires wimgt (within Wiimms SZS Tools, https://szs.wiimm.de) to be on the PATH +cd "$(dirname "$0")/../data" +wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/disc.png disc.bti +wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/flippydrive.png flippydrive.bti +wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/l_button.png l_button.bti +wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/r_button.png r_button.bti From e24a02d61b34000c516845084b8ed3b371bdb08a Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sun, 7 Jun 2026 14:47:46 +0100 Subject: [PATCH 30/31] Fix BTI textures, use Python gclib for conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I was slow to realise that the BTIs generated by wimgt don’t work within Cubeboot, likely due to the wrapping or alpha properties being hardcoded… As I previously generated working BTIs using GameCube File Tools, I’ve moved the conversion script to use the Python gclib that GCFT’s based on instead, and write it in Python. This now generates identical BTIs to the original GCFT-generated ones, and work in Cubeboot. --- patches/data/disc.bti | Bin 832 -> 832 bytes patches/data/flippydrive.bti | Bin 1056 -> 1056 bytes patches/data/l_button.bti | Bin 320 -> 320 bytes patches/data/r_button.bti | Bin 320 -> 320 bytes patches/scripts/convert.py | 54 +++++++++++++++++++++++++++++++ patches/scripts/convert.sh | 11 ------- patches/scripts/requirements.txt | 1 + 7 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 patches/scripts/convert.py delete mode 100755 patches/scripts/convert.sh diff --git a/patches/data/disc.bti b/patches/data/disc.bti index ee2fc0cab5a3238e55329121f1f8326c1b961414..b0272c451f176af05616377d42c5229068ee0672 100644 GIT binary patch delta 17 XcmX@Wc7Tn8fq_8-2pKkVSTF+s9{d8? delta 17 YcmX@Wc7Tn8fr&wbL4%QTBZmbu03RI!-2eap diff --git a/patches/data/flippydrive.bti b/patches/data/flippydrive.bti index 44d87193eef72a4401dd6263567f35e6bb091138..a69e7355e02a794a2f1d85970dd063bcb6ed0616 100644 GIT binary patch delta 17 YcmZ3$v4Devfq}t+L4ko`BZmYF02`SCjsO4v delta 17 YcmZ3$v4Devfr-I^L4lERBZmYF02{gjk^lez diff --git a/patches/data/l_button.bti b/patches/data/l_button.bti index 20f850ae04b1e152ef1db1260362d1dbee67bad0..c015a17381dc6b5becff91f9c041aed57a4cd669 100644 GIT binary patch literal 320 zcmZQzU=RU96u`*HzyL%bpuoVudzXQM|35D508|u!2Qx7I2kOO#ahnaY6Q9{2Is9gW e>_s*IKN{~s8m$`a-Q0Osf-s{jB1 diff --git a/patches/data/r_button.bti b/patches/data/r_button.bti index e2b13f27b9a5e7bc456a660d9c96dd35f9f7cf68..9f2b3d9d0e67599f04a278cd858ee5e165fb0491 100644 GIT binary patch literal 320 zcmZQzU=RU96u`*HzyL%bpuoVueV2iO_dhOtXS)Ey|Nmcs_`kxz{{IUO@czGWfb0JU zAbtR3A7B8=;K#Vl2HAON{{Mvk|9=Bb*}(7?Xxy>?9~ch(|H5$K{SSr%`~Lu)NSgUT gccOp;_5TG9*#8eW!2f^30q*}7P$YpIbo0^W0ip+?9{>OV literal 320 zcmZQzVh~{vVPs@L0w5k#K!JgQ`z`|m?|)qQ&UOKY|Np-N@qdMb{r?vn;QfE$0N4Ky zK>Ps6K7eE_4km81L3SRR|3BgX|KC9KH!!>f8h7mf2Zlrczc3tl|AXPc{y#X(L>5Lj eA6brp;XwU=fdlsc0}k;2pKyTt{{>W8!aM+dm`Qs8 diff --git a/patches/scripts/convert.py b/patches/scripts/convert.py new file mode 100644 index 0000000..0d274f4 --- /dev/null +++ b/patches/scripts/convert.py @@ -0,0 +1,54 @@ +import os +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()) + + +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) diff --git a/patches/scripts/convert.sh b/patches/scripts/convert.sh deleted file mode 100755 index c0e3813..0000000 --- a/patches/scripts/convert.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -set -e - -# Generates BTI textures from source PNGs -# (while SVGs are available too, they must be converted back to PNG first after any edits) -# Requires wimgt (within Wiimms SZS Tools, https://szs.wiimm.de) to be on the PATH -cd "$(dirname "$0")/../data" -wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/disc.png disc.bti -wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/flippydrive.png flippydrive.bti -wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/l_button.png l_button.bti -wimgt copy --transform BTI.I4 --n-mipmaps 0 original_png/r_button.png r_button.bti 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 From 870f22ccdaa4a527b61def77c122920a028dcc42 Mon Sep 17 00:00:00 2001 From: Ben Hetherington Date: Sun, 7 Jun 2026 15:59:18 +0100 Subject: [PATCH 31/31] Add remaining textures to `convert.py` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit May as well get `dir_tex`, `dol_tex`, and `default_opening` integrated into this script too, while I’m here. All of the generated data is identical to before. --- patches/data/original_png/default_opening.png | Bin 0 -> 3166 bytes patches/data/original_png/dir_tex.png | Bin 0 -> 211 bytes patches/data/original_png/dol_tex.png | Bin 0 -> 1138 bytes patches/data/original_svg/default_opening.svg | 15 +++++ patches/scripts/convert.py | 62 ++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 patches/data/original_png/default_opening.png create mode 100644 patches/data/original_png/dir_tex.png create mode 100644 patches/data/original_png/dol_tex.png create mode 100644 patches/data/original_svg/default_opening.svg diff --git a/patches/data/original_png/default_opening.png b/patches/data/original_png/default_opening.png new file mode 100644 index 0000000000000000000000000000000000000000..6872d00211bb1b72b291ba1634591a94eaa4dd8f GIT binary patch literal 3166 zcmZ`*3pkY78~+#-La2zvG+Ot$x(`M!iKN63A_jxWn9G!bf-5V-6Ivf8l^1EkqX^KRY`>1GF~+LVQvHga-jTz$XU?uGj$J z%qRcdKEkK=4Z{y&&;U3CTfyOXjsysN!=C_GUb6-VsIi~ATj+LNG zIM~{i4gjLkyuk;gq{#vRq>zMnVYt}s!}?Hz5CmVUHxa=M3g#&R#!M_|1`!zqC^Lvm zp<|gQuoVOrw0YY|7<2`~2sDAY*x5rZs5Bx}4}n6UU^o#d6lzTK^}{+?T79E~UnZ~s z1|t}YM23ZhA;Po~RGL2$ZD?qSL}8E^3>-wj>ERRxfeEM3l~+an*0ChgeQ2a$28l|6 z@^lH_)DVUV48{xey{+a+WRlh*QRv@}1s)K|^B~a(6!K>@@UY+W`v;3j2>wqJgXH%g zv3QfHg{M-#>juUf26K4 zohT#*4!rUe$`9K=vRv2vKMT0t{Uh}NjYI@%c_ml$w`lA4zTu6LymDSw-m7nCWfy!r zI1%t#{(gu!5zbga4FGI7ZewYN4-lMAChc?Dv!xX&!JJAJId!ilDz;f5hoqC0Rbz?P zIGDP(FHb4%Vy~6Om24HOeMfbz?x-B??zGocIcjHju%PIbT8K@S+SSM2O?}>)L}|rT z^7F6kg_(|s4+LFU#>~!nzjtYWrCndl&>m;M)G5EV>F0fu21N06T_1q>l!%FXCp4J2 zVfVI=avzd91h+!aX!H_%Te}UCUwzZlL)|v`F#T-#MSTM;X)<)#{HVf+fLl(cJbPHA zQph7)E#4z@yXJCaoAhY=gG14*b6>AU2E!sZQJg(lG#bq_q20j1z@?qde$CI%Uy+>+ zgKKKq22v=Y4<;a`qSD6g>P3o%(>Q5w3 zzAkbzGmFj&>xn&o-XTI#Vg6-6+o(|UL78%|dmVm(1hlA+lW#(n#ksSl;d8#`Wb)K3 zhr?-{8fq}ADl80KNK|*k9FK@V4%`cyy#zs2c|SNiGI)R^y4cliV`pa<@{E&3O)M!X z*)cRUggYm$(08^7qM)GgHZn4@G(A1Nip%AyKqe+KKl@6^8=IIQ6O)p*PI8w%&tAxB zwB5V6KqcFqe}emQvT|Y?4u=;W+ckRAHhHR=`i3H_PjP=6BdM%-YWCH%j-FoV)#T)U z4Nc9FV@-SZ5kt+KKKNnf8NDE}IjY~zVEPieaewv%_=YhTE`FiHsF0D4=+!|%tL z-pzs%idEmd_2ZTG_Zv(v)E z=HGRgiplF~{YKciv#qvGLeY4_G<2%>72AdKcU?Rx%_qqY=d&bw!I${j%7S#NOY;+y?{_vRTXhX3RpL*pL2eilzQ=O`Tg@H*R}nvZkV1N+TZPR$2o93R$BG`K)L_rK8b}RB?xhurQVrLE2-Ms z15LFt-L-Cq4<`qoE_#-*98ggf38^;YZ@U@n=p8(x!Jp;9&v!>)uS_piCetTABct)g z@>oeR_OkZbkki9z>6;S77F*!-mI>A)KJjSgUn1eN8x)$vHhNT%UB!fQlwmM;*n!qB zo|7XvUq*%Z7aZ)(Y-ng0lMFrVdG<^+RCgfg0Zwn>9L%enwqEA25vzl zr*q39p1seJBQZWqbY1SeEoRWZw6Dk7O!)ykbGh^`d$6XcCR*z$gDtE^CqnHeT z3tyJPu=t^q+z*_pr&m-DG)p23Un}&cYw7A%R_5k5XF%jU%N2}5nF#lqPIH9#nO^4? z`CO~`>dBsB5BXE+en*W$w#^sBYSaoYW1}EFITCqtzfs-z4Qq!N-j7_~Y!#INFlV+y zUtRkPra*2JJ0Y2WIL=^K#|?vH-m>A{2XL7u_6^a^569pa*<=&i=#y%7b@e$ynW8~( zlMxon^`UX3HaI(6%T>K~`#E_&WwVF0QbDU_4Ge2+d}2!(P@I1evT$XHj6vgYxT%S* z9Fr5dW88L4n-QyLM2c2~%Z`!AHX3a%RYD0D=hOJVJdb+jdlp+et@O8$Gio92#YY;B zpJzCBgi(dnH65m2b$8e3wzqSXFb}x{0|WRMWBU}EADPeIchD5CX%`B7nj~4j^UHQs z!^4(6c6dFDsSWi^3i>`@Y`K)QwBWYc{G%eWOJHG+{yO&Pqa$YVIyR(WGp70A?p%?b zA8^b+ccrO_+gGD?bh@!VEt?i2+|ojat19bB1OFd8Dtj?a?w`!=B?v*}9-lX1ss&fX(jxmc`~?r~VJ; CsrxYi literal 0 HcmV?d00001 diff --git a/patches/data/original_png/dir_tex.png b/patches/data/original_png/dir_tex.png new file mode 100644 index 0000000000000000000000000000000000000000..c702ec32e484116e75c737d11976aac2bdc326dc GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJS)MMAAr*7pPPxc=K!L|4_XJa< zT%s3vWR)ST8@39VSt~8^lqV~8` zJ)-6cH;(W@kdSa>Qcv}0ml6=2BGqgGOANgYwMQyqDJ*wVuEK=Ez)L;pC5|J;7~5J( zwpPg+3El-`U$dfID+Xs7SiyTfb(3aV*jz7-5nVvsj|dWMy@U}do#p_aQ}~JDad0?U zMS2Qg^I%)&l3U7{88=2tW9yg(uH+fw83Cr(K*J`Zs)*dnSE&VS^)?%Z6@0^5mpU(u z<-GGP#gut)ZEIE>ISIK31@<%t17k4UuhM*p4Yt{4j)W0|McKg#$cS05ML0~g00tgcupCS0Wi%pf~S5>h7`{>w*{>nsS72> zV5;wlM>W+^7(MM_Aw{1F)8e}eHTHotEIqxj-m05M1+#vsCZs7D&k)B3*eIcLPf=9U z$fBo$62f{Wc;gc+OY!qxh>n`_7-qK3d1tJxvx-R-zn*5RJ11Q!U5M0-R){z#nWlsg zMiVU4Yx}7&|N3z~WNlPfwH3J6-}Yq1)dyXz??D$85lV6QEAa(wBO;O_xF3n0%JYygqZb;II*`cI+-Wc#HAXI^90p+2=WmJNWX^xk}Pyhe`07*qoM6N<$ Ef-XEDv;Y7A literal 0 HcmV?d00001 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/scripts/convert.py b/patches/scripts/convert.py index 0d274f4..b853cf6 100644 --- a/patches/scripts/convert.py +++ b/patches/scripts/convert.py @@ -1,4 +1,5 @@ import os +from gclib import texture_utils from gclib.bti import BTI from gclib.gx_enums import * @@ -47,8 +48,69 @@ def convert_ui_texture_to_bti(image_name: str, image_format: ImageFormat): 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()