From ab72fcba5c418b7bef33b10cee1db91cd19833e9 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 11:46:13 +0000 Subject: [PATCH 01/15] fix booting from OPL, add make iso --- Makefile | 10 +++++++++- ps2/ntsc/SYSTEM.CNF | 3 +++ src/pc/pc_main.c | 8 ++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 ps2/ntsc/SYSTEM.CNF diff --git a/Makefile b/Makefile index 4600aca898..8683ee97ff 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,8 @@ TARGET_PS2 ?= 1 # Compiler to use (ido or gcc) COMPILER ?= ido +GAME_CODE ?= SLUS_064.64 + # Automatic settings only for ports ifeq ($(TARGET_N64),0) @@ -862,7 +864,7 @@ endif -.PHONY: all clean distclean default diff test load libultra +.PHONY: all clean distclean default diff test load libultra iso # with no prerequisites, .SECONDARY causes no intermediate target to be removed .SECONDARY: @@ -872,3 +874,9 @@ MAKEFLAGS += --no-builtin-rules -include $(DEP_FILES) print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true + +iso: $(ELF) + @echo Creating iso from $(ELF) + @cp -r ps2/ntsc $(BUILD_DIR)/iso + @cp $< $(BUILD_DIR)/iso/$(GAME_CODE) + @mkisofs -o $(BUILD_DIR)/sm64.iso $(BUILD_DIR)/iso/ diff --git a/ps2/ntsc/SYSTEM.CNF b/ps2/ntsc/SYSTEM.CNF new file mode 100644 index 0000000000..e34b1ef3ed --- /dev/null +++ b/ps2/ntsc/SYSTEM.CNF @@ -0,0 +1,3 @@ +BOOT2 = cdrom0:\SLUS_064.64;1 +VER = 1.00 +VMODE = NTSC \ No newline at end of file diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index b6aade8ab8..d2867b4a43 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -38,6 +38,7 @@ #ifdef TARGET_PS2 # include # include +# include # include # include # include @@ -150,17 +151,20 @@ void reset_IOP() { static void prepare_IOP() { reset_IOP(); SifInitRpc(0); + sbv_patch_enable_lmb(); sbv_patch_disable_prefix_check(); } static void init_drivers() { - init_ps2_filesystem_driver(); + init_only_boot_ps2_filesystem_driver(); + init_memcard_driver(TRUE); ps2_memcard_init(); } static void deinit_drivers() { - deinit_ps2_filesystem_driver(); + deinit_memcard_driver(TRUE); + deinit_only_boot_ps2_filesystem_driver(); } #endif From decdfef4986ba0cda0efe12c282944aafb0bf500 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 13:37:02 +0000 Subject: [PATCH 02/15] finished --- .gitignore | 4 +++ src/pc/gfx/gfx_ps2_rapi.c | 10 +++++- src/pc/gfx/gfx_ps2_wapi.c | 67 +++++++++++++++++++++++++++++---------- 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 4e5c5f3b8e..d6b366d659 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,7 @@ sm64config.txt !/sound/**/*custom*/**/*.aiff !/assets/**/*custom*.bin !/assets/**/*custom*/**/*.bin + +# PS2dev files +ps2dev*.tar.gz +ps2dev diff --git a/src/pc/gfx/gfx_ps2_rapi.c b/src/pc/gfx/gfx_ps2_rapi.c index f938555716..7d4b9a4e83 100644 --- a/src/pc/gfx/gfx_ps2_rapi.c +++ b/src/pc/gfx/gfx_ps2_rapi.c @@ -316,6 +316,10 @@ static void gfx_ps2_set_viewport(int x, int y, int width, int height) { r_view.x = x; r_view.y = y; r_view.w = width; + // 1080i requires the view point is half height + if (gs_global->Mode == GS_MODE_DTV_1080I) { + height /= 2; + } r_view.h = height; r_view.hw = r_view.w * 0.5f; r_view.hh = r_view.h * 0.5f; @@ -324,6 +328,11 @@ static void gfx_ps2_set_viewport(int x, int y, int width, int height) { } static inline void draw_set_scissor(const int x0, const int y0, const int x1, const int y1) { + // scissor doesn't work with gskit hires + if (gs_global->Mode == GS_MODE_DTV_720P || gs_global->Mode == GS_MODE_DTV_1080I) { + return; + } + u64 *p_data = gsKit_heap_alloc(gs_global, 1, 16, GIF_AD); *p_data++ = GIF_TAG_AD(1); @@ -645,7 +654,6 @@ static void draw_clear(const u64 color) { u32 pos = 0; - strips++; while (strips--) { gsKit_prim_sprite(gs_global, pos, 0, pos + 64, gs_global->Height, 0, color); pos += 64; diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index 2b9441aac3..fdcd1b91f9 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -8,12 +8,16 @@ #include #include +#include #include #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" #include "gfx_ps2.h" +#define FRAMERATE_SHIFT 1 +#define FRAMESKIP 10 + struct VidMode { const char *name; s16 mode; @@ -24,20 +28,21 @@ struct VidMode { int width; int height; int vck; + int iPassCount; int x_off; int y_off; }; static const struct VidMode vid_modes[] = { // NTSC - { "480i", GS_MODE_NTSC, GS_INTERLACED, GS_FIELD, 704, 480, 704, 452, 4, 0, 0 }, - { "480p", GS_MODE_DTV_480P, GS_NONINTERLACED, GS_FRAME, 704, 480, 704, 452, 2, 0, 0 }, + { "480i", GS_MODE_NTSC, GS_INTERLACED, GS_FIELD, 704, 480, 704, 452, 4, 1, 0, 0 }, + { "480p", GS_MODE_DTV_480P, GS_NONINTERLACED, GS_FRAME, 704, 480, 704, 452, 2, 1, 0, 0 }, // PAL - { "576i", GS_MODE_PAL, GS_INTERLACED, GS_FIELD, 704, 576, 704, 536, 4, 0, 0 }, - { "576p", GS_MODE_DTV_576P, GS_NONINTERLACED, GS_FRAME, 704, 576, 704, 536, 2, 0, 0 }, + { "576i", GS_MODE_PAL, GS_INTERLACED, GS_FIELD, 704, 576, 704, 536, 4, 1, 0, 0 }, + { "576p", GS_MODE_DTV_576P, GS_NONINTERLACED, GS_FRAME, 704, 576, 704, 536, 2, 1, 0, 0 }, // HDTV - { "720p", GS_MODE_DTV_720P, GS_NONINTERLACED, GS_FRAME, 1280, 720, 1280, 698, 1, 0, 0 }, - {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 0, 0 }, + { "720p", GS_MODE_DTV_720P, GS_NONINTERLACED, GS_FRAME, 1280, 720, 1280, 720, 1, 2, 0, 0 }, + {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 2, 0, 0 }, }; GSGLOBAL *gs_global; @@ -47,6 +52,7 @@ static int vsync_sema_2nd_id; static int vsync_sema_id = -1; static const struct VidMode *vid_mode; +static bool use_hires = false; /* Copy of gsKit_sync_flip, but without the 'flip' */ static void gsKit_sync(GSGLOBAL *gsGlobal) @@ -99,31 +105,51 @@ static void prepare_sema() { } static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { - gs_global = gsKit_init_global(); - - dmaKit_init(D_CTRL_RELE_OFF, D_CTRL_MFD_OFF, D_CTRL_STS_UNSPEC, - D_CTRL_STD_OFF, D_CTRL_RCYC_8, 1 << DMA_CHANNEL_GIF); - - dmaKit_chan_init(DMA_CHANNEL_GIF); - #if defined(VERSION_EU) vid_mode = &vid_modes[2]; // PAL #else vid_mode = &vid_modes[0]; // NTCS + // change to 5 for 1080i + // vid_mode = &vid_modes[5]; #endif + use_hires = (vid_mode->mode == GS_MODE_DTV_720P || vid_mode->mode == GS_MODE_DTV_1080I); + + if (use_hires) { + gs_global = gsKit_hires_init_global(); + } else { + gs_global = gsKit_init_global(); + } + + dmaKit_init(D_CTRL_RELE_OFF, D_CTRL_MFD_OFF, D_CTRL_STS_UNSPEC, + D_CTRL_STD_OFF, D_CTRL_RCYC_8, 1 << DMA_CHANNEL_GIF); + + dmaKit_chan_init(DMA_CHANNEL_GIF); gs_global->Mode = vid_mode->mode; gs_global->Width = vid_mode->width; gs_global->Height = vid_mode->height; + if (gs_global->Mode == GS_MODE_DTV_1080I) { + gs_global->Height /= 2; + } + gs_global->Interlace = vid_mode->interlace; gs_global->Field = vid_mode->field; gs_global->ZBuffering = GS_SETTING_ON; gs_global->DoubleBuffering = GS_SETTING_ON; gs_global->PrimAAEnable = GS_SETTING_OFF; - gs_global->PSM = GS_PSM_CT24; + // this could be enabled for hires, but I don't like it + gs_global->Dithering = GS_SETTING_OFF; + // hires runs out of VRAM if using more than 16bit color + gs_global->PSM = use_hires ? GS_PSM_CT16 : GS_PSM_CT24; gs_global->PSMZ = GS_PSMZ_16; // 16-bit unsigned zbuffer - gsKit_init_screen(gs_global); + if (use_hires) { + gsKit_hires_init_screen(gs_global, vid_mode->iPassCount); + } else { + gsKit_init_screen(gs_global); + } + // hires sets the texture pointer to the wrong location. Ensure it's correct. + gs_global->TexturePointer = gs_global->CurrentPointer; gsKit_TexManager_init(gs_global); } @@ -146,6 +172,11 @@ static void gfx_ps2_main_loop(void (*run_one_game_iter)(void)) { static void gfx_ps2_get_dimensions(uint32_t *width, uint32_t *height) { *width = gs_global->Width; *height = gs_global->Height; + // the game doesn't need to know that we are + // rendering at half height for 1080i + if (gs_global->Mode == GS_MODE_DTV_1080I) { + *height *= 2; + } } static void gfx_ps2_handle_events(void) { @@ -167,8 +198,12 @@ static void gfx_ps2_swap_buffers_begin(void) { static void gfx_ps2_swap_buffers_end(void) { /* How SM64 expect to run at 30 PFS we need to wait for 2 vsync */ gsKit_sync(gs_global); + if (use_hires) { + gsKit_hires_flip(gs_global); + } else { + gsKit_flip(gs_global); + } - gsKit_flip(gs_global); gsKit_queue_exec(gs_global); gsKit_TexManager_nextFrame(gs_global); } From d9c1ff3ca455c937dd3d473f15fc61ddc1807b04 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 13:40:51 +0000 Subject: [PATCH 03/15] remove --- src/pc/gfx/gfx_ps2_wapi.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index fdcd1b91f9..f1b481e112 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -15,9 +15,6 @@ #include "gfx_screen_config.h" #include "gfx_ps2.h" -#define FRAMERATE_SHIFT 1 -#define FRAMESKIP 10 - struct VidMode { const char *name; s16 mode; From e62a2829a05490be220a576aa7c81402debb30fc Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 13:42:19 +0000 Subject: [PATCH 04/15] fixes --- src/pc/pc_main.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index d2867b4a43..343997865d 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -38,7 +38,6 @@ #ifdef TARGET_PS2 # include # include -# include # include # include # include @@ -157,14 +156,14 @@ static void prepare_IOP() { } static void init_drivers() { - init_only_boot_ps2_filesystem_driver(); + init_only_boot_ps2_filesystem_driver(); init_memcard_driver(TRUE); ps2_memcard_init(); } static void deinit_drivers() { deinit_memcard_driver(TRUE); - deinit_only_boot_ps2_filesystem_driver(); + deinit_only_boot_ps2_filesystem_driver(); } #endif From 7600db42654bd3e4087db882d7f8599ed07f4920 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 14:08:39 +0000 Subject: [PATCH 05/15] fix tearing --- src/pc/gfx/gfx_ps2_wapi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index f1b481e112..cf6933daa3 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -105,7 +105,7 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { #if defined(VERSION_EU) vid_mode = &vid_modes[2]; // PAL #else - vid_mode = &vid_modes[0]; // NTCS + vid_mode = &vid_modes[5]; // NTCS // change to 5 for 1080i // vid_mode = &vid_modes[5]; #endif @@ -196,6 +196,7 @@ static void gfx_ps2_swap_buffers_end(void) { /* How SM64 expect to run at 30 PFS we need to wait for 2 vsync */ gsKit_sync(gs_global); if (use_hires) { + gsKit_hires_sync(gs_global); gsKit_hires_flip(gs_global); } else { gsKit_flip(gs_global); From d6ec5f4c591025a26e655a402bc28d9e6ab4ff96 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 14:14:56 +0000 Subject: [PATCH 06/15] downgrade default res --- src/pc/gfx/gfx_ps2_wapi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index cf6933daa3..2db8edbed2 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -39,7 +39,7 @@ static const struct VidMode vid_modes[] = { { "576p", GS_MODE_DTV_576P, GS_NONINTERLACED, GS_FRAME, 704, 576, 704, 536, 2, 1, 0, 0 }, // HDTV { "720p", GS_MODE_DTV_720P, GS_NONINTERLACED, GS_FRAME, 1280, 720, 1280, 720, 1, 2, 0, 0 }, - {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 2, 0, 0 }, + {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 3, 0, 0 }, }; GSGLOBAL *gs_global; @@ -105,7 +105,7 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { #if defined(VERSION_EU) vid_mode = &vid_modes[2]; // PAL #else - vid_mode = &vid_modes[5]; // NTCS + vid_mode = &vid_modes[0]; // NTCS // change to 5 for 1080i // vid_mode = &vid_modes[5]; #endif From f088d3f30596cbd06e50f11c7fa3cf2c80ced9a9 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 16:31:20 +0000 Subject: [PATCH 07/15] undo 3 pass --- src/pc/gfx/gfx_ps2_wapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index 2db8edbed2..5f26ab55b5 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -39,7 +39,7 @@ static const struct VidMode vid_modes[] = { { "576p", GS_MODE_DTV_576P, GS_NONINTERLACED, GS_FRAME, 704, 576, 704, 536, 2, 1, 0, 0 }, // HDTV { "720p", GS_MODE_DTV_720P, GS_NONINTERLACED, GS_FRAME, 1280, 720, 1280, 720, 1, 2, 0, 0 }, - {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 3, 0, 0 }, + {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 2, 0, 0 }, }; GSGLOBAL *gs_global; From 35dd6d41424d51fb1eb76869196aad15e93fedb1 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 22 Jan 2026 17:39:41 +0000 Subject: [PATCH 08/15] undo change --- src/pc/pc_main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index 9726ca2436..a83a4e8b43 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -150,7 +150,6 @@ void reset_IOP() { static void prepare_IOP() { reset_IOP(); SifInitRpc(0); - sbv_patch_enable_lmb(); sbv_patch_disable_prefix_check(); } From 9abab4ff00388823dce404f36365eb8a8c2133ed Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Sat, 24 Jan 2026 16:34:17 +0000 Subject: [PATCH 09/15] fix 1080i issues, at dithering --- src/pc/gfx/gfx_ps2_wapi.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index 5f26ab55b5..7d12e6c6ea 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -39,7 +39,7 @@ static const struct VidMode vid_modes[] = { { "576p", GS_MODE_DTV_576P, GS_NONINTERLACED, GS_FRAME, 704, 576, 704, 536, 2, 1, 0, 0 }, // HDTV { "720p", GS_MODE_DTV_720P, GS_NONINTERLACED, GS_FRAME, 1280, 720, 1280, 720, 1, 2, 0, 0 }, - {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FIELD, 1920, 1080, 1920, 1080, 1, 2, 0, 0 }, + {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FRAME, 1920, 1080, 1920, 1080, 1, 2, 0, 0 }, }; GSGLOBAL *gs_global; @@ -105,7 +105,7 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { #if defined(VERSION_EU) vid_mode = &vid_modes[2]; // PAL #else - vid_mode = &vid_modes[0]; // NTCS + vid_mode = &vid_modes[5]; // NTCS // change to 5 for 1080i // vid_mode = &vid_modes[5]; #endif @@ -135,7 +135,7 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { gs_global->DoubleBuffering = GS_SETTING_ON; gs_global->PrimAAEnable = GS_SETTING_OFF; // this could be enabled for hires, but I don't like it - gs_global->Dithering = GS_SETTING_OFF; + gs_global->Dithering = GS_SETTING_ON; // hires runs out of VRAM if using more than 16bit color gs_global->PSM = use_hires ? GS_PSM_CT16 : GS_PSM_CT24; gs_global->PSMZ = GS_PSMZ_16; // 16-bit unsigned zbuffer @@ -185,6 +185,9 @@ static bool gfx_ps2_start_frame(void) { } static void gfx_ps2_swap_buffers_begin(void) { + if (use_hires) { + return; + } if (vsync_sema_id != -1) return; prepare_sema(); @@ -194,11 +197,10 @@ static void gfx_ps2_swap_buffers_begin(void) { static void gfx_ps2_swap_buffers_end(void) { /* How SM64 expect to run at 30 PFS we need to wait for 2 vsync */ - gsKit_sync(gs_global); if (use_hires) { - gsKit_hires_sync(gs_global); - gsKit_hires_flip(gs_global); + gsKit_hires_flip_ext(gs_global, GSFLIP_RATE_LIMIT_2); } else { + gsKit_sync(gs_global); gsKit_flip(gs_global); } From 4ee642a8008d8b306452a5bfd60aba704fb39248 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Sat, 24 Jan 2026 16:35:28 +0000 Subject: [PATCH 10/15] correct queue exec --- src/pc/gfx/gfx_ps2_wapi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index 7d12e6c6ea..de943d2494 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -202,9 +202,8 @@ static void gfx_ps2_swap_buffers_end(void) { } else { gsKit_sync(gs_global); gsKit_flip(gs_global); + gsKit_queue_exec(gs_global); } - - gsKit_queue_exec(gs_global); gsKit_TexManager_nextFrame(gs_global); } From e22594d552dbcc595bdabebca06c380c0fd52df9 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Sun, 25 Jan 2026 03:28:44 +0000 Subject: [PATCH 11/15] initial implementation of video mode selection --- src/game/area.c | 8 ++ src/game/game_init.c | 14 ++- src/game/ingame_menu.h | 1 + src/pc/controller/controller_api.h | 2 + src/pc/controller/controller_ps2.c | 15 ++- src/pc/controller/controller_ps2.h | 1 + src/pc/gfx/gfx_ps2_wapi.c | 45 ++++++-- src/pc/gfx/gfx_window_manager_api.h | 1 + src/pc/pc_main.h | 4 + src/pc/ps2_vid_mode_select.c | 156 ++++++++++++++++++++++++++++ src/pc/ps2_vid_mode_select.h | 10 ++ 11 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 src/pc/pc_main.h create mode 100644 src/pc/ps2_vid_mode_select.c create mode 100644 src/pc/ps2_vid_mode_select.h diff --git a/src/game/area.c b/src/game/area.c index d458bf7129..8ee299824f 100644 --- a/src/game/area.c +++ b/src/game/area.c @@ -22,6 +22,10 @@ #include "save_file.h" #include "level_table.h" +#ifdef TARGET_PS2 +#include "pc/ps2_vid_mode_select.h" +#endif + struct SpawnInfo gPlayerSpawnInfos[1]; struct GraphNode *D_8033A160[0x100]; struct Area gAreaData[8]; @@ -383,6 +387,10 @@ void render_game(void) { gSaveOptSelectIndex = gPauseScreenMode; } + #ifdef TARGET_PS2 + handle_ps2_vid_mode_select(); + #endif + if (D_8032CE78 != NULL) { make_viewport_clip_rect(D_8032CE78); } else diff --git a/src/game/game_init.c b/src/game/game_init.c index 3ce5f2d620..e6f8bd71d5 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -21,6 +21,11 @@ #include "thread6.h" #include +#ifdef TARGET_PS2 +#include "pc/ps2_vid_mode_select.h" +#endif + + // FIXME: I'm not sure all of these variables belong in this file, but I don't // know of a good way to split them struct Controller gControllers[3]; @@ -480,7 +485,14 @@ void read_controller_inputs(void) { // if any controllers are plugged in, update the // controller information. - if (gControllerBits) { + + if (gControllerBits + #ifdef TARGET_PS2 + // HACK: prevent the rest of the game from getting input + // when we are changing resolution + && !gShowVidModeSelect + #endif + ) { osRecvMesg(&gSIEventMesgQueue, &D_80339BEC, OS_MESG_BLOCK); osContGetReadData(&gControllerPads[0]); #ifdef VERSION_SH diff --git a/src/game/ingame_menu.h b/src/game/ingame_menu.h index 1435e1648a..796d72b860 100644 --- a/src/game/ingame_menu.h +++ b/src/game/ingame_menu.h @@ -116,6 +116,7 @@ extern s8 gRedCoinsCollected; void create_dl_identity_matrix(void); void create_dl_translation_matrix(s8 pushOp, f32 x, f32 y, f32 z); +void create_dl_scale_matrix(s8 pushOp, f32 x, f32 y, f32 z); void create_dl_ortho_matrix(void); void print_generic_string(s16 x, s16 y, const u8 *str); void print_hud_lut_string(s8 hudLUT, s16 x, s16 y, const u8 *str); diff --git a/src/pc/controller/controller_api.h b/src/pc/controller/controller_api.h index dd318a8ac9..994ed63440 100644 --- a/src/pc/controller/controller_api.h +++ b/src/pc/controller/controller_api.h @@ -6,6 +6,8 @@ struct ControllerAPI { void (*init)(void); void (*read)(OSContPad *pad); + // returns the raw button values for this controller + u32 (*read_btns)(void); }; #endif diff --git a/src/pc/controller/controller_ps2.c b/src/pc/controller/controller_ps2.c index 41bc0f14de..080591607d 100644 --- a/src/pc/controller/controller_ps2.c +++ b/src/pc/controller/controller_ps2.c @@ -7,6 +7,7 @@ #include #include +#include "controller_ps2.h" #include "controller_api.h" #define DEADZONE 24 @@ -38,6 +39,9 @@ static struct { }; static int num_joy_binds = sizeof(joy_binds) / sizeof(joy_binds[0]); +// Used for signalling a special n64 controller combination when +// some buttons are held down for a number of frames +static int special_input_hold_timer = 0; static inline int wait_pad(int tries) { int state = padGetState(joy_port, joy_slot); @@ -142,7 +146,16 @@ static void controller_ps2_read(OSContPad *pad) { } } +u32 controller_ps2_read_btns(void) { + if (joy_id > -1 && padRead(joy_port, joy_slot, &joy_buttons)) { + const u32 btns = 0xffff ^ joy_buttons.btns; + return btns; + } + return 0; +} + struct ControllerAPI controller_ps2 = { controller_ps2_init, - controller_ps2_read + controller_ps2_read, + controller_ps2_read_btns, }; diff --git a/src/pc/controller/controller_ps2.h b/src/pc/controller/controller_ps2.h index 0420a52f20..c38b763ad2 100644 --- a/src/pc/controller/controller_ps2.h +++ b/src/pc/controller/controller_ps2.h @@ -1,6 +1,7 @@ #ifndef CONTROLLER_PS2_H #define CONTROLLER_PS2_H +#include #include "controller_api.h" extern struct ControllerAPI controller_ps2; diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index de943d2494..9b75cadfa5 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -11,6 +11,8 @@ #include #include +#include "macros.h" + #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" #include "gfx_ps2.h" @@ -31,12 +33,16 @@ struct VidMode { }; static const struct VidMode vid_modes[] = { + { "240p", GS_MODE_NTSC, GS_NONINTERLACED, GS_FRAME, 652, 224, 320, 224, 2, 1, 0, 0 }, +#if !defined(VERSION_EU) // NTSC { "480i", GS_MODE_NTSC, GS_INTERLACED, GS_FIELD, 704, 480, 704, 452, 4, 1, 0, 0 }, { "480p", GS_MODE_DTV_480P, GS_NONINTERLACED, GS_FRAME, 704, 480, 704, 452, 2, 1, 0, 0 }, +#else // PAL { "576i", GS_MODE_PAL, GS_INTERLACED, GS_FIELD, 704, 576, 704, 536, 4, 1, 0, 0 }, { "576p", GS_MODE_DTV_576P, GS_NONINTERLACED, GS_FRAME, 704, 576, 704, 536, 2, 1, 0, 0 }, +#endif // HDTV { "720p", GS_MODE_DTV_720P, GS_NONINTERLACED, GS_FRAME, 1280, 720, 1280, 720, 1, 2, 0, 0 }, {"1080i", GS_MODE_DTV_1080I, GS_INTERLACED, GS_FRAME, 1920, 1080, 1920, 1080, 1, 2, 0, 0 }, @@ -47,6 +53,7 @@ GSGLOBAL *gs_global; static int vsync_sema_1st_id; static int vsync_sema_2nd_id; static int vsync_sema_id = -1; +static int vsync_id = -1; static const struct VidMode *vid_mode; static bool use_hires = false; @@ -102,13 +109,19 @@ static void prepare_sema() { } static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { -#if defined(VERSION_EU) - vid_mode = &vid_modes[2]; // PAL -#else - vid_mode = &vid_modes[5]; // NTCS - // change to 5 for 1080i - // vid_mode = &vid_modes[5]; -#endif + if (vid_mode == NULL) { + vid_mode = &vid_modes[1]; // Standard Def + } else { + if (use_hires) { + gsKit_hires_deinit_global(gs_global); + } else { + gsKit_deinit_global(gs_global); + if (vsync_id != -1) { + gsKit_remove_vsync_handler(vsync_id); + } + vsync_sema_id = -1; + } + } use_hires = (vid_mode->mode == GS_MODE_DTV_720P || vid_mode->mode == GS_MODE_DTV_1080I); if (use_hires) { @@ -135,7 +148,7 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { gs_global->DoubleBuffering = GS_SETTING_ON; gs_global->PrimAAEnable = GS_SETTING_OFF; // this could be enabled for hires, but I don't like it - gs_global->Dithering = GS_SETTING_ON; + gs_global->Dithering = use_hires ? GS_SETTING_ON : GS_SETTING_OFF; // hires runs out of VRAM if using more than 16bit color gs_global->PSM = use_hires ? GS_PSM_CT16 : GS_PSM_CT24; gs_global->PSMZ = GS_PSMZ_16; // 16-bit unsigned zbuffer @@ -150,6 +163,17 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { gsKit_TexManager_init(gs_global); } +static void gfx_ps2_set_vid_mode(uint8_t vid_mode_idx) { + if (vid_mode_idx >= ARRAY_COUNT(vid_modes)) { + return; + } + + if (vid_mode != &vid_modes[vid_mode_idx]) { + vid_mode = &vid_modes[vid_mode_idx]; + gfx_ps2_init(NULL, false); + } +} + static void gfx_ps2_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) { } @@ -192,7 +216,7 @@ static void gfx_ps2_swap_buffers_begin(void) { prepare_sema(); vsync_sema_id = 0; - gsKit_add_vsync_handler(vsync_handler); + vsync_id = gsKit_add_vsync_handler(vsync_handler); } static void gfx_ps2_swap_buffers_end(void) { @@ -222,7 +246,8 @@ struct GfxWindowManagerAPI gfx_ps2_wapi = { gfx_ps2_start_frame, gfx_ps2_swap_buffers_begin, gfx_ps2_swap_buffers_end, - gfx_ps2_get_time + gfx_ps2_get_time, + gfx_ps2_set_vid_mode, }; #endif // TARGET_PS2 diff --git a/src/pc/gfx/gfx_window_manager_api.h b/src/pc/gfx/gfx_window_manager_api.h index 6ba4a2d820..a799c44990 100644 --- a/src/pc/gfx/gfx_window_manager_api.h +++ b/src/pc/gfx/gfx_window_manager_api.h @@ -16,6 +16,7 @@ struct GfxWindowManagerAPI { void (*swap_buffers_begin)(void); void (*swap_buffers_end)(void); double (*get_time)(void); // For debug + void (*set_vid_mode)(uint8_t vid_mode_idx); // only used for window managers which require it }; #endif diff --git a/src/pc/pc_main.h b/src/pc/pc_main.h new file mode 100644 index 0000000000..e1b1a7627f --- /dev/null +++ b/src/pc/pc_main.h @@ -0,0 +1,4 @@ +#ifndef PC_MAIN_H +#define PC_MAIN_H + +#endif \ No newline at end of file diff --git a/src/pc/ps2_vid_mode_select.c b/src/pc/ps2_vid_mode_select.c new file mode 100644 index 0000000000..e375a815fe --- /dev/null +++ b/src/pc/ps2_vid_mode_select.c @@ -0,0 +1,156 @@ +#include + +#include "sm64.h" + +#include "gfx_dimensions.h" + +#include "game/game_init.h" +#include "game/ingame_menu.h" +#include "game/segment2.h" + +#include "audio/external.h" + +#include "pc/ps2_vid_mode_select.h" +#include "pc/gfx/gfx_ps2.h" +#include "pc/controller/controller_ps2.h" + +#include "stdio.h" + +static u8 gMenuHoldKeyIndex = 0; +static u8 gMenuHoldKeyTimer = 0; +static s8 gDialogLineNum = 0; + +// Init in SD interlaced +u8 gShowVidModeSelect = FALSE; + +extern void adjust_analog_stick(struct Controller *controller); + +static void shade_screen(void) { + create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), SCREEN_HEIGHT, 0); + + create_dl_scale_matrix(MENU_MTX_NOPUSH, + GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT / 130.0f, 3.0f, 1.0f); + + gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 110); + gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); +} + +static void read_stick_input(struct Controller* controller) { + OSContPad pads[4]; + osContGetReadData(&pads[0]); + controller->rawStickX = pads[0].stick_x; + controller->rawStickY = pads[0].stick_y; + adjust_analog_stick(controller); +} + +static void handle_vertical_menu_scrolling(s8 scrollDirection, s8 *currentIndex, s8 minIndex, s8 maxIndex) { + u8 index = 0; + struct Controller controller; + read_stick_input(&controller); + + if (controller.rawStickY > 60) { + index++; + } + + if (controller.rawStickY < -60) { + index += 2; + } + + if (((index ^ gMenuHoldKeyIndex) & index) == 2) { + if (currentIndex[0] == maxIndex) { + //! Probably originally a >=, but later replaced with an == and an else statement. + currentIndex[0] = maxIndex; + } else { + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + currentIndex[0]++; + } + } + + if (((index ^ gMenuHoldKeyIndex) & index) == 1) { + if (currentIndex[0] == minIndex) { + // Same applies to here as above + } else { + play_sound(SOUND_MENU_CHANGE_SELECT, gDefaultSoundArgs); + currentIndex[0]--; + } + } + + if (gMenuHoldKeyTimer == 10) { + gMenuHoldKeyTimer = 8; + gMenuHoldKeyIndex = 0; + } else { + gMenuHoldKeyTimer++; + gMenuHoldKeyIndex = index; + } + + if ((index & 3) == 0) { + gMenuHoldKeyTimer = 0; + } +} + + +void render_ps2_vid_mode_options(s16 x, s16 y, s8 *index, s16 yIndex) { + u8 text240p[] = { 0x02,0x04,0x00,0x33,0xFF }; + u8 text480i[] = { 0x04,0x08,0x00,0x2C,0xFF }; + u8 text480p[] = { 0x04,0x08,0x00,0x33,0xFF }; + u8 text720p[] = { 0x07,0x02,0x00,0x33,0xFF }; + u8 text1080i[] = { 0x01,0x00,0x08,0x00,0x2C,0xFF }; + u8* options[] = { text240p, text480i, text480p, text720p, text1080i }; + + handle_vertical_menu_scrolling(MENU_SCROLL_VERTICAL, index, 1, 5); + + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); + + u8 i; + for (i = 0; i < ARRAY_COUNT(options); i++) { + print_generic_string(x + 10, y - 2 - (i * 15), options[i]); + } + + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); + + create_dl_translation_matrix(MENU_MTX_PUSH, x - 4, (y - ((index[0] - 1) * yIndex)) - 2, 0); + + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); + gSPDisplayList(gDisplayListHead++, dl_draw_triangle); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); +} + +void handle_ps2_vid_mode_select(void) { + // Special (impossible) button combo + u32 btns = controller_ps2.read_btns(); + if (btns & (PAD_CROSS | PAD_TRIANGLE) && !gShowVidModeSelect) { + special_input_hold_timer++; + } else { + special_input_hold_timer = 0; + } + + if (special_input_hold_timer > 15) { + gShowVidModeSelect = TRUE; + gDialogTextAlpha = 0; + gDialogLineNum = 3; + special_input_hold_timer = 0; + } + + if (gShowVidModeSelect) { + + shade_screen(); + render_ps2_vid_mode_options(99, 93, &gDialogLineNum, 15); + // This will do nothing unless the vid mode has changed + // so it's safe to call every frame + gfx_ps2_wapi.set_vid_mode(gDialogLineNum - 1); + + if ((btns & PAD_CROSS) && !(btns & PAD_TRIANGLE)) + { + play_sound(SOUND_MENU_PAUSE_2, gDefaultSoundArgs); + // TODO: save vid mode + gShowVidModeSelect = FALSE; + } + + if (gDialogTextAlpha < 250) { + gDialogTextAlpha += 25; + } + gDialogColorFadeTimer = (s16) gDialogColorFadeTimer + 0x1000; + } +} diff --git a/src/pc/ps2_vid_mode_select.h b/src/pc/ps2_vid_mode_select.h new file mode 100644 index 0000000000..7824c07b30 --- /dev/null +++ b/src/pc/ps2_vid_mode_select.h @@ -0,0 +1,10 @@ +#ifndef PS2_VID_MODE_SELECT_H +#define PS2_VID_MODE_SELECT_H + +#include "sm64.h" + +void handle_ps2_vid_mode_select(void); + +extern u8 gShowVidModeSelect; + +#endif From ec2267d8a15961e5ec28d18e30280ecf7532a214 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Sun, 25 Jan 2026 03:30:07 +0000 Subject: [PATCH 12/15] fix broken commit --- src/pc/controller/controller_ps2.c | 3 --- src/pc/ps2_vid_mode_select.c | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pc/controller/controller_ps2.c b/src/pc/controller/controller_ps2.c index 080591607d..c616b3764f 100644 --- a/src/pc/controller/controller_ps2.c +++ b/src/pc/controller/controller_ps2.c @@ -39,9 +39,6 @@ static struct { }; static int num_joy_binds = sizeof(joy_binds) / sizeof(joy_binds[0]); -// Used for signalling a special n64 controller combination when -// some buttons are held down for a number of frames -static int special_input_hold_timer = 0; static inline int wait_pad(int tries) { int state = padGetState(joy_port, joy_slot); diff --git a/src/pc/ps2_vid_mode_select.c b/src/pc/ps2_vid_mode_select.c index e375a815fe..c36dcd9e13 100644 --- a/src/pc/ps2_vid_mode_select.c +++ b/src/pc/ps2_vid_mode_select.c @@ -23,6 +23,11 @@ static s8 gDialogLineNum = 0; // Init in SD interlaced u8 gShowVidModeSelect = FALSE; +// Used for signalling a special n64 controller combination when +// some buttons are held down for a number of frames +static int special_input_hold_timer = 0; + + extern void adjust_analog_stick(struct Controller *controller); static void shade_screen(void) { From 8d46c4a846df22d27526b1e09eb63eec34f69f50 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Mon, 26 Jan 2026 01:24:46 +0000 Subject: [PATCH 13/15] save resolution preference --- src/game/game_init.c | 10 ++- src/game/save_file.c | 17 +++++ src/game/save_file.h | 18 +++++ src/pc/gfx/gfx_pc.c | 9 ++- src/pc/gfx/gfx_pc.h | 1 + src/pc/gfx/gfx_ps2_wapi.c | 8 ++- src/pc/gfx/gfx_window_manager_api.h | 2 +- src/pc/ps2_vid_mode_select.c | 106 ++++++++++++++++++++++------ src/pc/ps2_vid_mode_select.h | 1 + 9 files changed, 144 insertions(+), 28 deletions(-) diff --git a/src/game/game_init.c b/src/game/game_init.c index e6f8bd71d5..73f2c3f7b7 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -487,11 +487,11 @@ void read_controller_inputs(void) { // controller information. if (gControllerBits - #ifdef TARGET_PS2 +#ifdef TARGET_PS2 // HACK: prevent the rest of the game from getting input // when we are changing resolution && !gShowVidModeSelect - #endif +#endif ) { osRecvMesg(&gSIEventMesgQueue, &D_80339BEC, OS_MESG_BLOCK); osContGetReadData(&gControllerPads[0]); @@ -614,6 +614,12 @@ void thread5_game_loop(UNUSED void *arg) { #endif save_file_load_all(); +#ifdef TARGET_PS2 + // Must be called after save file loaded to + // configure preferred video mode + ps2_vid_mode_select_init(); +#endif + set_vblank_handler(2, &gGameVblankHandler, &gGameVblankQueue, (OSMesg) 1); // point levelCommandAddr to the entry point into the level script data. diff --git a/src/game/save_file.c b/src/game/save_file.c index 8c15558620..2d603b8f15 100644 --- a/src/game/save_file.c +++ b/src/game/save_file.c @@ -571,6 +571,23 @@ u16 save_file_get_sound_mode(void) { return gSaveBuffer.menuData[0].soundMode; } +#ifdef TARGET_PS2 +void save_file_set_ps2_vid_mode(u16 mode) { + gSaveBuffer.menuData[0].ps2VidMode = mode; + gSaveBuffer.menuData[0].ps2VidModeSet = 1; + + gMainMenuDataModified = TRUE; + save_main_menu_data(); +} + +u16 save_file_get_ps2_vid_mode(void) { + if (gSaveBuffer.menuData[0].ps2VidModeSet == 1) { + return gSaveBuffer.menuData[0].ps2VidMode; + } + return (u16)-1; +} +#endif + void save_file_move_cap_to_default_location(void) { if (save_file_get_flags() & SAVE_FLAG_CAP_ON_GROUND) { switch (gSaveBuffer.files[gCurrSaveFileNum - 1][0].capLevel) { diff --git a/src/game/save_file.h b/src/game/save_file.h index 3ee5a19ac6..d626dede70 100644 --- a/src/game/save_file.h +++ b/src/game/save_file.h @@ -55,9 +55,23 @@ struct MainMenuSaveData #ifdef VERSION_EU u16 language; +#endif + +#ifdef TARGET_PS2 + // u16 for consistency, like why is language u16? + u16 ps2VidMode; + u16 ps2VidModeSet; +#ifdef VERSION_EU +#define SUBTRAHEND 12 +#else +#define SUBTRAHEND 10 +#endif +#else +#ifdef VERSION_EU #define SUBTRAHEND 8 #else #define SUBTRAHEND 6 +#endif #endif // Pad to match the EEPROM size of 0x200 (10 bytes on JP/US, 8 bytes on EU) @@ -143,6 +157,10 @@ void save_file_set_cap_pos(s16 x, s16 y, s16 z); s32 save_file_get_cap_pos(Vec3s capPos); void save_file_set_sound_mode(u16 mode); u16 save_file_get_sound_mode(void); +#ifdef TARGET_PS2 +void save_file_set_ps2_vid_mode(u16 mode); +u16 save_file_get_ps2_vid_mode(void); +#endif void save_file_move_cap_to_default_location(void); void disable_warp_checkpoint(void); diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 54e85d0129..b83ccb77c9 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -322,6 +322,11 @@ static struct ColorCombiner *gfx_lookup_or_create_color_combiner(uint32_t cc_id) return prev_combiner = comb; } +void gfx_clear_texture_cache(void) { + if (gfx_rapi->flush_textures) gfx_rapi->flush_textures(); + gfx_texture_cache.pool_pos = 0; +} + static bool gfx_texture_cache_lookup(int tile, struct TextureHashmapNode **n, const uint8_t *orig_addr, uint32_t fmt, uint32_t siz) { size_t hash = (uintptr_t)orig_addr; hash = (hash >> 5) & 0x3ff; @@ -336,8 +341,7 @@ static bool gfx_texture_cache_lookup(int tile, struct TextureHashmapNode **n, co } if (gfx_texture_cache.pool_pos == sizeof(gfx_texture_cache.pool) / sizeof(struct TextureHashmapNode)) { // Pool is full. We just invalidate everything and start over. - if (gfx_rapi->flush_textures) gfx_rapi->flush_textures(); - gfx_texture_cache.pool_pos = 0; + gfx_clear_texture_cache(); node = &gfx_texture_cache.hashmap[hash]; //puts("Clearing texture cache"); } @@ -2025,3 +2029,4 @@ void gfx_end_frame(void) { gfx_wapi->swap_buffers_end(); } } + diff --git a/src/pc/gfx/gfx_pc.h b/src/pc/gfx/gfx_pc.h index 0ad4a302b4..564fac1e23 100644 --- a/src/pc/gfx/gfx_pc.h +++ b/src/pc/gfx/gfx_pc.h @@ -22,6 +22,7 @@ struct GfxRenderingAPI *gfx_get_current_rendering_api(void); void gfx_start_frame(void); void gfx_run(Gfx *commands); void gfx_end_frame(void); +void gfx_clear_texture_cache(void); #ifdef __cplusplus } diff --git a/src/pc/gfx/gfx_ps2_wapi.c b/src/pc/gfx/gfx_ps2_wapi.c index 9b75cadfa5..bd62a463b3 100644 --- a/src/pc/gfx/gfx_ps2_wapi.c +++ b/src/pc/gfx/gfx_ps2_wapi.c @@ -110,7 +110,7 @@ static void prepare_sema() { static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { if (vid_mode == NULL) { - vid_mode = &vid_modes[1]; // Standard Def + vid_mode = &vid_modes[1]; // Standard def 480i } else { if (use_hires) { gsKit_hires_deinit_global(gs_global); @@ -163,15 +163,17 @@ static void gfx_ps2_init(const char *game_name, bool start_in_fullscreen) { gsKit_TexManager_init(gs_global); } -static void gfx_ps2_set_vid_mode(uint8_t vid_mode_idx) { +static bool gfx_ps2_set_vid_mode(uint8_t vid_mode_idx) { if (vid_mode_idx >= ARRAY_COUNT(vid_modes)) { - return; + return false; } if (vid_mode != &vid_modes[vid_mode_idx]) { vid_mode = &vid_modes[vid_mode_idx]; gfx_ps2_init(NULL, false); + return true; } + return false; } static void gfx_ps2_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) { diff --git a/src/pc/gfx/gfx_window_manager_api.h b/src/pc/gfx/gfx_window_manager_api.h index a799c44990..df5d7b4e7f 100644 --- a/src/pc/gfx/gfx_window_manager_api.h +++ b/src/pc/gfx/gfx_window_manager_api.h @@ -16,7 +16,7 @@ struct GfxWindowManagerAPI { void (*swap_buffers_begin)(void); void (*swap_buffers_end)(void); double (*get_time)(void); // For debug - void (*set_vid_mode)(uint8_t vid_mode_idx); // only used for window managers which require it + bool (*set_vid_mode)(uint8_t vid_mode_idx); // only used for window managers which require it }; #endif diff --git a/src/pc/ps2_vid_mode_select.c b/src/pc/ps2_vid_mode_select.c index c36dcd9e13..98ff54da11 100644 --- a/src/pc/ps2_vid_mode_select.c +++ b/src/pc/ps2_vid_mode_select.c @@ -7,26 +7,42 @@ #include "game/game_init.h" #include "game/ingame_menu.h" #include "game/segment2.h" +#include "game/save_file.h" #include "audio/external.h" -#include "pc/ps2_vid_mode_select.h" -#include "pc/gfx/gfx_ps2.h" #include "pc/controller/controller_ps2.h" +#include "pc/gfx/gfx_ps2.h" +#include "pc/gfx/gfx_pc.h" +#include "pc/ps2_vid_mode_select.h" -#include "stdio.h" +// stolen from `ingame_menu.c` static u8 gMenuHoldKeyIndex = 0; static u8 gMenuHoldKeyTimer = 0; static s8 gDialogLineNum = 0; -// Init in SD interlaced u8 gShowVidModeSelect = FALSE; // Used for signalling a special n64 controller combination when // some buttons are held down for a number of frames -static int special_input_hold_timer = 0; +static int specialInputHoldTimer = 0; +static u32 coolOffTimer = FALSE; +// NTSC (480i) or PAL (576i) +#define DEFAULT_VID_MODE 1 + +#define TEXT_SELECT_VIDEO_MODE \ + 0x1C,0x28,0x2F,0x28,0x26,0x37, DIALOG_CHAR_SPACE, \ + 0x39,0x2C,0x27,0x28,0x32, DIALOG_CHAR_SPACE, \ + 0x30,0x32,0x27,0x28, DIALOG_CHAR_SPACE, \ + 0x37,0x32, DIALOG_CHAR_TERMINATOR + +#define TEXT_TO_SAVE_YOUR_PREFERENCE \ + 0x36,0x24,0x39,0x28, DIALOG_CHAR_SPACE, \ + 0x3C,0x32,0x38,0x35, DIALOG_CHAR_SPACE, \ + 0x33,0x35,0x28,0x29,0x28,0x35,0x28,0x31,0x26,0x28, DIALOG_CHAR_SPACE, \ + DIALOG_CHAR_TERMINATOR extern void adjust_analog_stick(struct Controller *controller); @@ -102,12 +118,17 @@ void render_ps2_vid_mode_options(s16 x, s16 y, s8 *index, s16 yIndex) { u8 text720p[] = { 0x07,0x02,0x00,0x33,0xFF }; u8 text1080i[] = { 0x01,0x00,0x08,0x00,0x2C,0xFF }; u8* options[] = { text240p, text480i, text480p, text720p, text1080i }; + u8 infoText1[] = { TEXT_SELECT_VIDEO_MODE }; + u8 infoText2[] = { TEXT_TO_SAVE_YOUR_PREFERENCE }; handle_vertical_menu_scrolling(MENU_SCROLL_VERTICAL, index, 1, 5); gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, gDialogTextAlpha); + print_generic_string(x, y + 30, infoText1); + print_generic_string(x, y + 15, infoText2); + u8 i; for (i = 0; i < ARRAY_COUNT(options); i++) { print_generic_string(x + 10, y - 2 - (i * 15), options[i]); @@ -122,36 +143,80 @@ void render_ps2_vid_mode_options(s16 x, s16 y, s8 *index, s16 yIndex) { gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); } -void handle_ps2_vid_mode_select(void) { - // Special (impossible) button combo - u32 btns = controller_ps2.read_btns(); - if (btns & (PAD_CROSS | PAD_TRIANGLE) && !gShowVidModeSelect) { - special_input_hold_timer++; +static void ps2_vid_mode_select_open(void) { + gShowVidModeSelect = TRUE; + + // reset vid mode + u8 vidMode = DEFAULT_VID_MODE; + if (gfx_ps2_wapi.set_vid_mode(vidMode)) { + save_file_set_ps2_vid_mode(vidMode); + } + + gDialogTextAlpha = 0; + gDialogLineNum = vidMode + 1; + + // prevent a mode being selected by the X + // button after the menu opens + coolOffTimer = 60; +} + +static void ps2_vid_mode_select_detect_open(u32 btns) { + if ((btns & (PAD_CROSS | PAD_TRIANGLE)) == (PAD_CROSS | PAD_TRIANGLE) && !gShowVidModeSelect) { + specialInputHoldTimer++; } else { - special_input_hold_timer = 0; + specialInputHoldTimer = 0; } - if (special_input_hold_timer > 15) { - gShowVidModeSelect = TRUE; - gDialogTextAlpha = 0; - gDialogLineNum = 3; - special_input_hold_timer = 0; + if (specialInputHoldTimer > 15) { + ps2_vid_mode_select_open(); + specialInputHoldTimer = 0; } + if (coolOffTimer > 0) { + coolOffTimer--; + } +} + +void ps2_vid_mode_select_init(void) { + u8 vidMode = DEFAULT_VID_MODE; // 480i; + u16 savedVidMode = save_file_get_ps2_vid_mode(); + + // Vid mode has never been saved + if (savedVidMode != (u16)-1) { + vidMode = savedVidMode; + } + gfx_ps2_wapi.set_vid_mode(vidMode); +} + + +void handle_ps2_vid_mode_select(void) { + // Read raw PS2 controls so that we can read triangle + u32 btns = controller_ps2.read_btns(); + // Look forthe special button combo + ps2_vid_mode_select_detect_open(btns); + if (gShowVidModeSelect) { - shade_screen(); render_ps2_vid_mode_options(99, 93, &gDialogLineNum, 15); + // Preview the vid mode + u8 vidMode = gDialogLineNum - 1; // This will do nothing unless the vid mode has changed // so it's safe to call every frame - gfx_ps2_wapi.set_vid_mode(gDialogLineNum - 1); + if (gfx_ps2_wapi.set_vid_mode(vidMode)) { + // Reset the texture cache since we've changed + // video mode so VRAM might get a little weird + // This atm does not work (causes more VRAM issues) + // gfx_clear_texture_cache(); + }; - if ((btns & PAD_CROSS) && !(btns & PAD_TRIANGLE)) + + if ((btns & PAD_CROSS) && coolOffTimer == 0) { + save_file_set_ps2_vid_mode(vidMode); play_sound(SOUND_MENU_PAUSE_2, gDefaultSoundArgs); - // TODO: save vid mode gShowVidModeSelect = FALSE; } + if (gDialogTextAlpha < 250) { gDialogTextAlpha += 25; @@ -159,3 +224,4 @@ void handle_ps2_vid_mode_select(void) { gDialogColorFadeTimer = (s16) gDialogColorFadeTimer + 0x1000; } } + diff --git a/src/pc/ps2_vid_mode_select.h b/src/pc/ps2_vid_mode_select.h index 7824c07b30..81831b0eb8 100644 --- a/src/pc/ps2_vid_mode_select.h +++ b/src/pc/ps2_vid_mode_select.h @@ -4,6 +4,7 @@ #include "sm64.h" void handle_ps2_vid_mode_select(void); +void ps2_vid_mode_select_init(void); extern u8 gShowVidModeSelect; From 2da822153df17e81925e19e598484c97c72178f0 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Mon, 26 Jan 2026 22:31:42 +0000 Subject: [PATCH 14/15] source improvements --- src/game/area.c | 4 ++-- src/game/game_init.c | 7 ++----- src/game/main.c | 4 ++++ src/game/main.h | 3 +++ src/pc/pc_main.h | 4 ---- src/pc/ps2_vid_mode_select.c | 11 +++++++++-- 6 files changed, 20 insertions(+), 13 deletions(-) delete mode 100644 src/pc/pc_main.h diff --git a/src/game/area.c b/src/game/area.c index 8ee299824f..0b15598858 100644 --- a/src/game/area.c +++ b/src/game/area.c @@ -387,9 +387,9 @@ void render_game(void) { gSaveOptSelectIndex = gPauseScreenMode; } - #ifdef TARGET_PS2 +#ifdef TARGET_PS2 handle_ps2_vid_mode_select(); - #endif +#endif if (D_8032CE78 != NULL) { make_viewport_clip_rect(D_8032CE78); diff --git a/src/game/game_init.c b/src/game/game_init.c index 73f2c3f7b7..c9c5f6f01d 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -485,12 +485,9 @@ void read_controller_inputs(void) { // if any controllers are plugged in, update the // controller information. - if (gControllerBits -#ifdef TARGET_PS2 - // HACK: prevent the rest of the game from getting input - // when we are changing resolution - && !gShowVidModeSelect +#ifndef TARGET_N64 + && !gDisableInput #endif ) { osRecvMesg(&gSIEventMesgQueue, &D_80339BEC, OS_MESG_BLOCK); diff --git a/src/game/main.c b/src/game/main.c index 9e53e50b2a..70a1fd29ce 100644 --- a/src/game/main.c +++ b/src/game/main.c @@ -71,6 +71,10 @@ s8 D_8032C650 = 0; s8 gShowProfiler = FALSE; s8 gShowDebugText = FALSE; +#ifndef TARGET_N64 +s8 gDisableInput = FALSE; +#endif + // unused void handle_debug_key_sequences(void) { static u16 sProfilerKeySequence[] = { diff --git a/src/game/main.h b/src/game/main.h index d7efe89454..be5166f657 100644 --- a/src/game/main.h +++ b/src/game/main.h @@ -64,6 +64,9 @@ extern s8 gDebugLevelSelect; extern s8 D_8032C650; extern s8 gShowProfiler; extern s8 gShowDebugText; +#ifndef TARGET_N64 +extern s8 gDisableInput; +#endif void set_vblank_handler(s32 index, struct VblankHandler *handler, OSMesgQueue *queue, OSMesg *msg); void dispatch_audio_sptask(struct SPTask *spTask); diff --git a/src/pc/pc_main.h b/src/pc/pc_main.h deleted file mode 100644 index e1b1a7627f..0000000000 --- a/src/pc/pc_main.h +++ /dev/null @@ -1,4 +0,0 @@ -#ifndef PC_MAIN_H -#define PC_MAIN_H - -#endif \ No newline at end of file diff --git a/src/pc/ps2_vid_mode_select.c b/src/pc/ps2_vid_mode_select.c index 98ff54da11..755e1e5640 100644 --- a/src/pc/ps2_vid_mode_select.c +++ b/src/pc/ps2_vid_mode_select.c @@ -1,6 +1,7 @@ #include #include "sm64.h" +#include "main.h" #include "gfx_dimensions.h" @@ -144,7 +145,8 @@ void render_ps2_vid_mode_options(s16 x, s16 y, s8 *index, s16 yIndex) { } static void ps2_vid_mode_select_open(void) { - gShowVidModeSelect = TRUE; + gShowVidModeSelect = TRUE; + gDisableInput = TRUE; // reset vid mode u8 vidMode = DEFAULT_VID_MODE; @@ -160,6 +162,11 @@ static void ps2_vid_mode_select_open(void) { coolOffTimer = 60; } +static ps2_vid_mode_select_close(void) { + gShowVidModeSelect = FALSE; + gDisableInput = FALSE; +} + static void ps2_vid_mode_select_detect_open(u32 btns) { if ((btns & (PAD_CROSS | PAD_TRIANGLE)) == (PAD_CROSS | PAD_TRIANGLE) && !gShowVidModeSelect) { specialInputHoldTimer++; @@ -214,7 +221,7 @@ void handle_ps2_vid_mode_select(void) { { save_file_set_ps2_vid_mode(vidMode); play_sound(SOUND_MENU_PAUSE_2, gDefaultSoundArgs); - gShowVidModeSelect = FALSE; + ps2_vid_mode_select_close(); } From 14051a459b9adb50fc422b2277a550324eebe258 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Mon, 26 Jan 2026 22:41:03 +0000 Subject: [PATCH 15/15] fixes --- src/pc/pc_main.c | 1 + src/pc/ps2_vid_mode_select.c | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index a83a4e8b43..8bf0b735db 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -54,6 +54,7 @@ s8 D_8032C648; s8 gDebugLevelSelect; s8 gShowProfiler; s8 gShowDebugText; +s8 gDisableInput = FALSE; static struct AudioAPI *audio_api; static struct GfxWindowManagerAPI *wm_api; diff --git a/src/pc/ps2_vid_mode_select.c b/src/pc/ps2_vid_mode_select.c index 755e1e5640..10e4ec6bc1 100644 --- a/src/pc/ps2_vid_mode_select.c +++ b/src/pc/ps2_vid_mode_select.c @@ -1,10 +1,10 @@ #include #include "sm64.h" -#include "main.h" #include "gfx_dimensions.h" +#include "game/main.h" #include "game/game_init.h" #include "game/ingame_menu.h" #include "game/segment2.h" @@ -162,7 +162,7 @@ static void ps2_vid_mode_select_open(void) { coolOffTimer = 60; } -static ps2_vid_mode_select_close(void) { +static void ps2_vid_mode_select_close(void) { gShowVidModeSelect = FALSE; gDisableInput = FALSE; } @@ -216,7 +216,6 @@ void handle_ps2_vid_mode_select(void) { // gfx_clear_texture_cache(); }; - if ((btns & PAD_CROSS) && coolOffTimer == 0) { save_file_set_ps2_vid_mode(vidMode);