Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions psp/src/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(T);
static mut DIALOG_LIST: Align16<[u8; 0x4000]> = Align16([0u8; 0x4000]);
pub(crate) struct Align16<T>(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<DialogResult, DialogError> {
let ret =
Expand Down Expand Up @@ -146,17 +153,23 @@ fn run_dialog(params: &mut UtilityMsgDialogParams) -> Result<DialogResult, Dialo

// 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 {
// 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,
}
}
}

Expand Down
116 changes: 116 additions & 0 deletions psp/src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<sys::UtilityNetconfData>() 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() };
Expand Down
2 changes: 1 addition & 1 deletion psp/src/sys/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@ psp_extern! {
}

#[repr(u32)]
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApctlState {
Disconnected,
Scanning,
Expand Down