From c5c16c98caf25835daccce1f3fbe2c0cab667bb5 Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Mon, 16 Feb 2026 03:44:41 -0600 Subject: [PATCH 1/3] fix: move utility dialog update calls outside GU display list frame sceUtilityOskUpdate() and sceUtilityMsgDialogUpdate() were called inside an open GU display list (between sceGuStart/sceGuFinish). Per PSPSDK convention, dialog update functions manage their own GU rendering and must be called after the caller's display list is closed. The incorrect ordering corrupts GE state and crashes on real PSP hardware when opening the OSK. Also adds screen clear each frame (matching PSPSDK samples) and breaks on negative status values (error conditions). Co-Authored-By: Claude Opus 4.6 --- psp/src/dialog.rs | 23 ++++++++++++++--------- psp/src/osk.rs | 23 ++++++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/psp/src/dialog.rs b/psp/src/dialog.rs index eb05282..92fe257 100644 --- a/psp/src/dialog.rs +++ b/psp/src/dialog.rs @@ -102,11 +102,14 @@ fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result Result unsafe { crate::sys::sceUtilityMsgDialogUpdate(1); @@ -126,14 +137,8 @@ fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result {}, } - // SAFETY: GU frame lifecycle calls to finalize the dialog's - // display list and present it on screen. + // SAFETY: Present the frame. unsafe { - crate::sys::sceGuFinish(); - crate::sys::sceGuSync( - crate::sys::GuSyncMode::Finish, - crate::sys::GuSyncBehavior::Wait, - ); crate::sys::sceDisplayWaitVblankStart(); crate::sys::sceGuSwapBuffers(); } diff --git a/psp/src/osk.rs b/psp/src/osk.rs index 32c0ea8..a0348bb 100644 --- a/psp/src/osk.rs +++ b/psp/src/osk.rs @@ -169,11 +169,14 @@ impl OskBuilder { for _ in 0..MAX_OSK_ITERATIONS { let status = unsafe { crate::sys::sceUtilityOskGetStatus() }; - if status == 0 { + if status == 0 || status < 0 { break; } - // Provide a GU frame for the dialog to render into. + // Provide a GU frame with a cleared screen as the dialog + // background, then close the frame before updating the + // utility dialog. PSPSDK convention: the dialog update + // must be called **outside** any open GU display list. // SAFETY: DIALOG_LIST is only used by utility dialog loops // which run on the main thread and never overlap. unsafe { @@ -181,8 +184,16 @@ impl OskBuilder { crate::sys::GuContextType::Direct, &raw mut DIALOG_LIST as *mut core::ffi::c_void, ); + crate::sys::sceGuClearColor(0xff00_0000); // opaque black + crate::sys::sceGuClear(crate::sys::ClearBuffer::COLOR_BUFFER_BIT); + crate::sys::sceGuFinish(); + crate::sys::sceGuSync( + crate::sys::GuSyncMode::Finish, + crate::sys::GuSyncBehavior::Wait, + ); } + // Update the utility dialog outside the GU frame. match status { 2 => unsafe { crate::sys::sceUtilityOskUpdate(1); @@ -193,14 +204,8 @@ impl OskBuilder { _ => {}, } - // SAFETY: GU frame lifecycle calls to finalize the dialog's - // display list and present it on screen. + // SAFETY: Present the frame. unsafe { - crate::sys::sceGuFinish(); - crate::sys::sceGuSync( - crate::sys::GuSyncMode::Finish, - crate::sys::GuSyncBehavior::Wait, - ); crate::sys::sceDisplayWaitVblankStart(); crate::sys::sceGuSwapBuffers(); } From 31ed8d02aaf7343b5a0c70731c2b1633dff8ebbe Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Mon, 16 Feb 2026 03:47:35 -0600 Subject: [PATCH 2/3] fix: force-shutdown utility dialogs on polling loop timeout After the polling loop exits (timeout or error), check if the dialog is still active and force-shutdown to prevent subsequent utility calls from failing with UTILITY_INVALID_STATUS. This is particularly important in PPSSPP headless mode where no user input closes the dialog, but also protects against edge cases on real hardware. Co-Authored-By: Claude Opus 4.6 --- psp/src/dialog.rs | 19 +++++++++++++++++++ psp/src/osk.rs | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/psp/src/dialog.rs b/psp/src/dialog.rs index 92fe257..5569c17 100644 --- a/psp/src/dialog.rs +++ b/psp/src/dialog.rs @@ -144,6 +144,25 @@ fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result 0 { + unsafe { + crate::sys::sceUtilityMsgDialogShutdownStart(); + } + for _ in 0..120 { + let s = unsafe { crate::sys::sceUtilityMsgDialogGetStatus() }; + if s == 0 || s < 0 { + break; + } + unsafe { + crate::sys::sceDisplayWaitVblankStart(); + } + } + } + Ok(match params.button_pressed { UtilityMsgDialogPressed::Yes => DialogResult::Confirm, UtilityMsgDialogPressed::No => DialogResult::Cancel, diff --git a/psp/src/osk.rs b/psp/src/osk.rs index a0348bb..1c627e7 100644 --- a/psp/src/osk.rs +++ b/psp/src/osk.rs @@ -211,6 +211,26 @@ impl OskBuilder { } } + // If the dialog is still active after the polling loop (timeout or + // error), force-shutdown so subsequent utility calls don't fail + // with UTILITY_INVALID_STATUS. + let final_status = unsafe { crate::sys::sceUtilityOskGetStatus() }; + if final_status > 0 { + unsafe { + crate::sys::sceUtilityOskShutdownStart(); + } + // Drain the shutdown state machine. + for _ in 0..120 { + let s = unsafe { crate::sys::sceUtilityOskGetStatus() }; + if s == 0 || s < 0 { + break; + } + unsafe { + crate::sys::sceDisplayWaitVblankStart(); + } + } + } + match osk_data.result { SceUtilityOskResult::Changed => { let text = utf16_to_string(&output_buf); From 81782997be46884727899f4b22d0c49f625d8e81 Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Mon, 16 Feb 2026 03:47:35 -0600 Subject: [PATCH 3/3] fix: drain dialog shutdown state after polling loop timeout After the polling loop exits, if the dialog reached QUIT (3) or FINISHED (4) but didn't drain to NONE (0), complete the shutdown sequence. Only attempts shutdown from valid states -- ShutdownStart requires the dialog to be in QUIT state, not RUNNING. Co-Authored-By: Claude Opus 4.6 --- psp/src/dialog.rs | 27 ++++++++++++--------------- psp/src/osk.rs | 30 ++++++++++++++---------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/psp/src/dialog.rs b/psp/src/dialog.rs index 5569c17..d6ed0fa 100644 --- a/psp/src/dialog.rs +++ b/psp/src/dialog.rs @@ -144,22 +144,19 @@ fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result 0 { - unsafe { - crate::sys::sceUtilityMsgDialogShutdownStart(); - } - for _ in 0..120 { - let s = unsafe { crate::sys::sceUtilityMsgDialogGetStatus() }; - if s == 0 || s < 0 { - break; - } - unsafe { + // If the dialog reached QUIT (3) or FINISHED (4) but the polling + // loop exited before it drained to NONE (0), finish the shutdown. + for _ in 0..120 { + let s = unsafe { crate::sys::sceUtilityMsgDialogGetStatus() }; + match s { + 3 => unsafe { + crate::sys::sceUtilityMsgDialogShutdownStart(); crate::sys::sceDisplayWaitVblankStart(); - } + }, + 4 => unsafe { + crate::sys::sceDisplayWaitVblankStart(); + }, + _ => break, } } diff --git a/psp/src/osk.rs b/psp/src/osk.rs index 1c627e7..eb2f81e 100644 --- a/psp/src/osk.rs +++ b/psp/src/osk.rs @@ -211,23 +211,21 @@ impl OskBuilder { } } - // If the dialog is still active after the polling loop (timeout or - // error), force-shutdown so subsequent utility calls don't fail - // with UTILITY_INVALID_STATUS. - let final_status = unsafe { crate::sys::sceUtilityOskGetStatus() }; - if final_status > 0 { - unsafe { - crate::sys::sceUtilityOskShutdownStart(); - } - // Drain the shutdown state machine. - for _ in 0..120 { - let s = unsafe { crate::sys::sceUtilityOskGetStatus() }; - if s == 0 || s < 0 { - break; - } - unsafe { + // If the dialog reached QUIT (3) or FINISHED (4) but the polling + // loop exited before it drained to NONE (0), finish the shutdown. + // We cannot call ShutdownStart from RUNNING (2) -- that requires + // user interaction to transition to QUIT first. + for _ in 0..120 { + let s = unsafe { crate::sys::sceUtilityOskGetStatus() }; + match s { + 3 => unsafe { + crate::sys::sceUtilityOskShutdownStart(); crate::sys::sceDisplayWaitVblankStart(); - } + }, + 4 => unsafe { + crate::sys::sceDisplayWaitVblankStart(); + }, + _ => break, } }