diff --git a/psp/src/dialog.rs b/psp/src/dialog.rs index d6ed0fa..c5b8fbc 100644 --- a/psp/src/dialog.rs +++ b/psp/src/dialog.rs @@ -78,8 +78,15 @@ const MAX_DIALOG_ITERATIONS: u32 = 600; /// Small display list for utility dialog GU frames (16KB, 16-byte aligned). #[repr(C, align(16))] -struct Align16(T); -static mut DIALOG_LIST: Align16<[u8; 0x4000]> = Align16([0u8; 0x4000]); +pub(crate) struct Align16(pub T); +/// Shared display list for all utility dialog loops. Only accessed from +/// the main thread, never concurrently. +pub(crate) static mut DIALOG_LIST: Align16<[u8; 0x4000]> = Align16([0u8; 0x4000]); + +/// Create a `UtilityDialogCommon` for the netconf dialog. +pub(crate) fn make_netconf_common(size: u32) -> UtilityDialogCommon { + make_common(size) +} fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result { let ret = @@ -146,17 +153,23 @@ fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result unsafe { - crate::sys::sceUtilityMsgDialogShutdownStart(); - crate::sys::sceDisplayWaitVblankStart(); - }, - 4 => unsafe { + // Call ShutdownStart once if still in QUIT state, then wait for + // the status to drain to NONE (0). + let s = unsafe { crate::sys::sceUtilityMsgDialogGetStatus() }; + if s == 3 { + unsafe { + crate::sys::sceUtilityMsgDialogShutdownStart(); + } + } + if s == 3 || s == 4 { + for _ in 0..120 { + let s = unsafe { crate::sys::sceUtilityMsgDialogGetStatus() }; + if s != 3 && s != 4 { + break; + } + unsafe { crate::sys::sceDisplayWaitVblankStart(); - }, - _ => break, + } } } diff --git a/psp/src/net.rs b/psp/src/net.rs index 065673b..4674a6f 100644 --- a/psp/src/net.rs +++ b/psp/src/net.rs @@ -149,6 +149,122 @@ pub fn connect_ap_timeout(config_index: i32, timeout_ms: u32) -> Result<(), NetE Err(NetError(-1)) } +/// Connect to WiFi using the PSP's built-in network configuration dialog. +/// +/// Shows a system dialog that lets the user select a stored WiFi profile +/// and manages the entire connection flow (scan, auth, DHCP). This is the +/// most compatible way to establish internet connectivity — it works on +/// both real PSP hardware and PPSSPP. +/// +/// The dialog renders into the framebuffer, so the caller's GU display +/// list is finished before the dialog runs. The caller must re-open a +/// new display list with `sceGuStart` after this function returns. +/// +/// Returns `Ok(())` when connected, or `Err` if the user cancelled or +/// the dialog failed. +pub fn connect_dialog() -> Result<(), NetError> { + // Check if we're already connected. + let mut state = sys::ApctlState::Disconnected; + let ret = unsafe { sys::sceNetApctlGetState(&mut state) }; + if ret >= 0 && state == sys::ApctlState::GotIp { + return Ok(()); + } + + let mut data = sys::UtilityNetconfData { + base: crate::dialog::make_netconf_common( + core::mem::size_of::() as u32 + ), + action: sys::UtilityNetconfAction::ConnectAP, + adhocparam: core::ptr::null_mut(), + hotspot: 0, + hotspot_connected: 0, + wifisp: 0, + }; + + let ret = unsafe { sys::sceUtilityNetconfInitStart(&mut data) }; + if ret < 0 { + return Err(NetError(ret)); + } + + // Close the caller's open GU display list. + unsafe { + sys::sceGuFinish(); + sys::sceGuSync(sys::GuSyncMode::Finish, sys::GuSyncBehavior::Wait); + } + + // Dialog rendering loop (up to ~30 seconds at 60 fps). + for _ in 0..1800 { + let status = unsafe { sys::sceUtilityNetconfGetStatus() }; + if status < 0 { + return Err(NetError(status)); + } + if status == 0 { + break; + } + + // Provide a GU frame for the dialog background. + // SAFETY: DIALOG_LIST is shared with dialog.rs but both run on + // the main thread and never overlap. + unsafe { + sys::sceGuStart( + sys::GuContextType::Direct, + &raw mut crate::dialog::DIALOG_LIST as *mut core::ffi::c_void, + ); + sys::sceGuClearColor(0xff00_0000); + sys::sceGuClear(sys::ClearBuffer::COLOR_BUFFER_BIT); + sys::sceGuFinish(); + sys::sceGuSync(sys::GuSyncMode::Finish, sys::GuSyncBehavior::Wait); + } + + match status { + 2 => unsafe { + sys::sceUtilityNetconfUpdate(1); + }, + 3 => unsafe { + sys::sceUtilityNetconfShutdownStart(); + }, + _ => {}, + } + + unsafe { + sys::sceDisplayWaitVblankStart(); + sys::sceGuSwapBuffers(); + } + } + + // Drain shutdown if needed. Call ShutdownStart once if still in + // QUIT state (3), then wait for FINISHED (4) to drain to NONE (0). + let s = unsafe { sys::sceUtilityNetconfGetStatus() }; + if s == 3 { + unsafe { + sys::sceUtilityNetconfShutdownStart(); + } + } + if s == 3 || s == 4 { + for _ in 0..120 { + let s = unsafe { sys::sceUtilityNetconfGetStatus() }; + if s != 3 && s != 4 { + break; + } + unsafe { + sys::sceDisplayWaitVblankStart(); + } + } + } + + // Verify we actually got connected. + let mut state = sys::ApctlState::Disconnected; + let ret = unsafe { sys::sceNetApctlGetState(&mut state) }; + if ret < 0 { + return Err(NetError(ret)); + } + if state != sys::ApctlState::GotIp { + return Err(NetError(-1)); + } + + Ok(()) +} + /// Disconnect from the current access point. pub fn disconnect_ap() -> Result<(), NetError> { let ret = unsafe { sys::sceNetApctlDisconnect() }; diff --git a/psp/src/sys/net.rs b/psp/src/sys/net.rs index fe356ae..02b816c 100644 --- a/psp/src/sys/net.rs +++ b/psp/src/sys/net.rs @@ -1225,7 +1225,7 @@ psp_extern! { } #[repr(u32)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ApctlState { Disconnected, Scanning,