From 87bbc04dfd59e81814758f0112829507ad8d2417 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Wed, 28 Dec 2022 17:07:41 -0800 Subject: [PATCH 01/42] Add a rough ir:USER service wrapper and example Works in the Citra emulator, but not on device. --- ctru-rs/examples/ir-user.rs | 107 ++++++++++ ctru-rs/src/services/ir_user.rs | 318 ++++++++++++++++++++++++++++++ ctru-rs/src/services/mod.rs | 1 + ctru-rs/src/services/reference.rs | 1 + ctru-sys/src/lib.rs | 12 ++ 5 files changed, 439 insertions(+) create mode 100644 ctru-rs/examples/ir-user.rs create mode 100644 ctru-rs/src/services/ir_user.rs diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs new file mode 100644 index 0000000..eda0eb9 --- /dev/null +++ b/ctru-rs/examples/ir-user.rs @@ -0,0 +1,107 @@ +use ctru::prelude::*; +use ctru::services::ir_user::{IrDeviceId, IrUser}; +use time::Duration; +use ctru::error::ResultCode; + +const PACKET_INFO_SIZE: usize = 8; +const MAX_PACKET_SIZE: usize = 32; +const PACKET_COUNT: usize = 1; +const PACKET_BUFFER_SIZE: usize = PACKET_COUNT * (PACKET_INFO_SIZE + MAX_PACKET_SIZE); + +fn main() { + ctru::init(); + let apt = Apt::init().unwrap(); + let hid = Hid::init().unwrap(); + let gfx = Gfx::init().unwrap(); + let console = Console::init(gfx.top_screen.borrow_mut()); + let ir_user = IrUser::init( + PACKET_BUFFER_SIZE, + PACKET_COUNT, + PACKET_BUFFER_SIZE, + PACKET_COUNT, + ) + .expect("Couldn't initialize ir:USER service"); + let ir_user_connection_status_event = ir_user + .get_connection_status_event() + .expect("Couldn't get ir:USER connection status event"); + ir_user + .require_connection(IrDeviceId::CirclePadPro) + .expect("Couldn't initialize circle pad pro connection"); + let ir_user_recv_event = ir_user + .get_recv_event() + .expect("Couldn't get ir:USER recv event"); + println!("StatusInfo:\n{:#?}", ir_user.get_status_info()); + + // Wait for the connection to establish + (|| unsafe { + ResultCode(ctru_sys::svcWaitSynchronization( + ir_user_connection_status_event, + Duration::seconds(10).whole_nanoseconds() as i64, + ))?; + + println!("Finished waiting on connection status event"); + println!("StatusInfo:\n{:#?}", ir_user.get_status_info()); + + Ok(()) + })().expect("Failed to connect to circle pad pro"); + + ir_user + .start_polling_input(20) + .expect("Couldn't configure circle pad pro polling interval"); + + while apt.main_loop() { + hid.scan_input(); + + // Check if we've received a packet from the circle pad pro + let check_ir_packet = + unsafe { ctru_sys::svcWaitSynchronization(ir_user_recv_event, 0) == 0 }; + + if check_ir_packet { + console.clear(); + + // Move the cursor back to the top of the screen + print!("\x1b[0;0H"); + + println!("StatusInfo:\n{:?}", ir_user.get_status_info()); + + ir_user.process_shared_memory(|ir_mem| { + println!("\nReceiveBufferInfo:"); + for byte in &ir_mem[0x10..0x20] { + print!("{byte:02x} "); + } + + println!("\nReceiveBuffer:"); + let mut counter = 0; + for byte in &ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE] { + print!("{byte:02x} "); + counter += 1; + if counter % 16 == 0 { + println!() + } + } + + println!("\nSendBufferInfo:"); + for byte in &ir_mem[0x20 + PACKET_BUFFER_SIZE..0x30 + PACKET_BUFFER_SIZE] { + print!("{byte:02x} "); + } + + println!("\n(skipping send packet buffer)"); + }); + + // Done handling the packet, release it + ir_user + .release_received_data(1) + .expect("Failed to release ir:USER packet"); + + println!("\x1b[29;16HPress Start to exit"); + } + + if hid.keys_held().intersects(KeyPad::KEY_START) { + break; + } + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs new file mode 100644 index 0000000..212588e --- /dev/null +++ b/ctru-rs/src/services/ir_user.rs @@ -0,0 +1,318 @@ +use crate::error::ResultCode; +use crate::services::ServiceReference; +use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; +use std::alloc::Layout; +use std::cmp::max; +use std::ffi::CString; +use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; +use std::sync::Mutex; + +static IR_USER_ACTIVE: Mutex = Mutex::new(0); +static IR_USER_STATE: Mutex> = Mutex::new(None); + +#[non_exhaustive] +pub struct IrUser { + _service_reference: ServiceReference, +} + +struct IrUserState { + service_handle: Handle, + shared_memory_handle: Handle, + shared_memory: &'static [u8], + // shared_memory: Box<[u8]>, +} + +const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; +const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040; +const GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER: u32 = 0x000C0000; +const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = 0x000A0000; +const SEND_IR_NOP_COMMAND_HEADER: u32 = 0x000D0042; +const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040; + +impl IrUser { + pub fn init(recv_buffer_size: usize, recv_packet_count: usize, send_buffer_size: usize, send_packet_count: usize) -> crate::Result { + let service_reference = ServiceReference::new( + &IR_USER_ACTIVE, + true, + || unsafe { + println!("Starting IrUser"); + println!("Getting ir:USER service handle"); + let mut service_handle = Handle::default(); + let service_name = CString::new("ir:USER").unwrap(); + ResultCode(ctru_sys::srvGetServiceHandle( + &mut service_handle, + service_name.as_ptr(), + ))?; + + println!("Getting shared memory pointer"); + let info_sections_size = 0x30; + // let packet_count = 3; + // let max_packet_size = 32; + // let packet_info_size = 8; + // let recv_buffer_len = recv_packet_count * (packet_info_size + max_packet_size); + // let send_buffer_len = send_packet_count * (packet_info_size + max_packet_size); + + let minimum_shared_memory_len = info_sections_size + recv_buffer_size + send_buffer_size; + let shared_memory_len = if minimum_shared_memory_len % 0x1000 != 0 { + (minimum_shared_memory_len / 0x1000) * 0x1000 + 0x1000 + } else { + (minimum_shared_memory_len / 0x1000) * 0x1000 + }; + assert_eq!(shared_memory_len % 0x1000, 0); + // let shared_memory_len = info_sections_size + recv_buffer_size + send_buffer_size; + println!("Shared memory size: {shared_memory_len:#x}"); + + // let shared_memory_len = info_sections_size + packet_count * (packet_info_size + max_packet_size); + // let shared_memory = Box::new([0; 0x1000]); + // let shared_memory_ptr = ctru_sys::mappableAlloc(shared_memory_len) as *const u8; + let shared_memory_layout = + Layout::from_size_align(shared_memory_len, 0x1000).unwrap(); + let shared_memory_ptr = std::alloc::alloc_zeroed(shared_memory_layout); + println!( + "Using shared memory address: {:#08x}", + shared_memory_ptr as usize + ); + + println!("Marking memory block as shared memory"); + let mut shared_memory_handle = Handle::default(); + ResultCode(ctru_sys::svcCreateMemoryBlock( + &mut shared_memory_handle, + shared_memory_ptr as u32, + shared_memory_len as u32, + MEMPERM_READ, + MEMPERM_READWRITE, + ))?; + let shared_memory = &*slice_from_raw_parts(shared_memory_ptr, shared_memory_len); + + println!("Initializing ir:USER service"); + initialize_irnop_shared(InitializeIrnopSharedParams { + ir_user_handle: service_handle, + shared_memory_len: shared_memory_len as u32, + recv_packet_buffer_len: recv_buffer_size as u32, + recv_packet_count: recv_packet_count as u32, + send_packet_buffer_len: send_buffer_size as u32, + send_packet_count: send_packet_count as u32, + bit_rate: 4, + shared_memory_handle, + })?; + + println!("Setting IrUserState in static"); + let user_state = IrUserState { + service_handle, + shared_memory_handle, + shared_memory, + }; + *IR_USER_STATE.lock().unwrap() = Some(user_state); + + println!("Done starting IrUser"); + Ok(()) + }, + || { + println!("Close called for IrUser"); + let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); + let shared_mem = shared_mem_guard + .take() + .expect("ir:USER shared memory mutex shouldn't be empty"); + // (|| unsafe { + // // println!("Unmapping the ir:USER shared memory"); + // // ResultCode(ctru_sys::svcUnmapMemoryBlock( + // // shared_mem.shared_memory_handle, + // // shared_mem.shared_memory.as_ptr() as u32, + // // ))?; + // + // println!("Closing memory and service handles"); + // // ResultCode(ctru_sys::svcCloseHandle(shared_mem.shared_memory_handle))?; + // ResultCode(ctru_sys::svcCloseHandle(shared_mem.service_handle))?; + // + // // println!("Freeing shared memory"); + // // ctru_sys::mappableFree(shared_mem.shared_memory.as_ptr() as *mut libc::c_void); + // + // Ok(()) + // })() + // .unwrap(); + println!("Done closing IrUser"); + }, + )?; + + Ok(IrUser { + _service_reference: service_reference, + }) + } + + pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> { + println!("RequireConnection called"); + self.send_service_request( + vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], + 2, + )?; + + println!("RequireConnection succeeded"); + Ok(()) + } + + pub fn get_connection_status_event(&self) -> crate::Result { + println!("GetConnectionStatusEvent called"); + let response = self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; + let status_event = response[3] as Handle; + + println!("GetConnectionStatusEvent succeeded"); + Ok(status_event) + } + + pub fn get_recv_event(&self) -> crate::Result { + println!("GetReceiveEvent called"); + let response = self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4)?; + let recv_event = response[3] as Handle; + + println!("GetReceiveEvent succeeded"); + Ok(recv_event) + } + + pub fn start_polling_input(&self, period_ms: u8) -> crate::Result<()> { + println!("SendIrnop (start_polling_input) called"); + let ir_request: [u8; 3] = [1, period_ms, 0]; + self.send_service_request( + vec![ + SEND_IR_NOP_COMMAND_HEADER, + ir_request.len() as u32, + 2 + (ir_request.len() << 14) as u32, + ir_request.as_ptr() as u32, + ], + 2, + )?; + + println!("SendIrnop (start_polling_input) succeeded"); + Ok(()) + } + + pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> { + println!("ReleaseReceivedData called"); + self.send_service_request( + vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], + 2 + )?; + + println!("ReleaseReceivedData succeeded"); + Ok(()) + } + + pub fn process_shared_memory(&self, process_fn: impl FnOnce(&[u8])) { + println!("Process shared memory started"); + let shared_mem_guard = IR_USER_STATE.lock().unwrap(); + let shared_mem = shared_mem_guard.as_ref().unwrap(); + + process_fn(shared_mem.shared_memory); + + println!("Process shared memory succeeded"); + } + + pub fn get_status_info(&self) -> IrUserStatusInfo { + let shared_mem_guard = IR_USER_STATE.lock().unwrap(); + let shared_mem = shared_mem_guard.as_ref().unwrap().shared_memory; + + IrUserStatusInfo { + recv_err_result: i32::from_ne_bytes([shared_mem[0], shared_mem[1], shared_mem[2], shared_mem[3]]), + send_err_result: i32::from_ne_bytes([shared_mem[4], shared_mem[5], shared_mem[6], shared_mem[7]]), + connection_status: shared_mem[8], + trying_to_connect_status: shared_mem[9], + connection_role: shared_mem[10], + machine_id: shared_mem[11], + unknown_field_1: shared_mem[12], + network_id: shared_mem[13], + unknown_field_2: shared_mem[14], + unknown_field_3: shared_mem[15], + } + } + + fn send_service_request( + &self, + mut request: Vec, + expected_response_len: usize, + ) -> crate::Result> { + let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); + let shared_mem = shared_mem_guard.as_mut().unwrap(); + + let cmd_buffer = unsafe { + &mut *(slice_from_raw_parts_mut( + ctru_sys::getThreadCommandBuffer(), + max(request.len(), expected_response_len), + )) + }; + cmd_buffer[0..request.len()].copy_from_slice(&request); + + // Send the request + unsafe { + ResultCode(ctru_sys::svcSendSyncRequest(shared_mem.service_handle))?; + } + + // Handle the result returned by the service + ResultCode(cmd_buffer[1] as ctru_sys::Result)?; + + // Copy back the response + request.clear(); + request.extend_from_slice(&cmd_buffer[0..expected_response_len]); + + Ok(request) + } +} + +struct InitializeIrnopSharedParams { + ir_user_handle: Handle, + shared_memory_len: u32, + recv_packet_buffer_len: u32, + recv_packet_count: u32, + send_packet_buffer_len: u32, + send_packet_count: u32, + bit_rate: u32, + shared_memory_handle: Handle, +} + +unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate::Result<()> { + let cmd_buffer = &mut *(slice_from_raw_parts_mut(ctru_sys::getThreadCommandBuffer(), 9)); + cmd_buffer[0] = INITIALIZE_IRNOP_SHARED_COMMAND_HEADER; + cmd_buffer[1] = params.shared_memory_len; + cmd_buffer[2] = params.recv_packet_buffer_len; + cmd_buffer[3] = params.recv_packet_count; + cmd_buffer[4] = params.send_packet_buffer_len; + cmd_buffer[5] = params.send_packet_count; + cmd_buffer[6] = params.bit_rate; + cmd_buffer[7] = 0; + cmd_buffer[8] = params.shared_memory_handle; + + // Send the request + ResultCode(ctru_sys::svcSendSyncRequest(params.ir_user_handle))?; + + // Handle the result returned by the service + ResultCode(cmd_buffer[1] as ctru_sys::Result)?; + + Ok(()) +} + +pub enum IrDeviceId { + CirclePadPro, + // Pretty sure no other IDs are recognized, but just in case + Custom(u32), +} + +impl IrDeviceId { + pub fn get_id(&self) -> u32 { + match *self { + IrDeviceId::CirclePadPro => 1, + IrDeviceId::Custom(id) => id, + } + } +} + +#[derive(Debug)] +pub struct IrUserStatusInfo { + recv_err_result: ctru_sys::Result, + send_err_result: ctru_sys::Result, + connection_status: u8, + trying_to_connect_status: u8, + connection_role: u8, + machine_id: u8, + unknown_field_1: u8, + network_id: u8, + unknown_field_2: u8, + unknown_field_3: u8, +} diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 5a52608..97e1a0f 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -4,6 +4,7 @@ pub mod cfgu; pub mod fs; pub mod gspgpu; pub mod hid; +pub mod ir_user; pub mod ps; mod reference; pub mod soc; diff --git a/ctru-rs/src/services/reference.rs b/ctru-rs/src/services/reference.rs index 41319a7..65c683e 100644 --- a/ctru-rs/src/services/reference.rs +++ b/ctru-rs/src/services/reference.rs @@ -1,5 +1,6 @@ use crate::Error; use std::sync::Mutex; + pub(crate) struct ServiceReference { counter: &'static Mutex, close: Box, diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 595cbd3..4c4dd01 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -4,6 +4,8 @@ #![allow(non_snake_case)] #![allow(clippy::all)] +use core::arch::asm; + pub mod result; mod bindings; @@ -17,3 +19,13 @@ pub use result::*; pub unsafe fn errno() -> s32 { (*__getreent())._errno } + +pub unsafe fn getThreadLocalStorage() -> *mut libc::c_void { + let return_value: *mut libc::c_void; + asm!("mrc p15, 0, {}, c13, c0, 3", out(reg) return_value); + return_value +} + +pub unsafe fn getThreadCommandBuffer() -> *mut u32 { + (getThreadLocalStorage() as *mut u8).add(0x80) as *mut u32 +} From 6c2b184e00d238ecd4f9950f91fe23afb6f4852b Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 2 Jan 2023 09:11:49 -0800 Subject: [PATCH 02/42] Got connection working and packets flowing Need to parse packets now to make sure input signal is getting transferred correctly. --- ctru-rs/examples/ir-user.rs | 226 +++++++++++++++++++++++--------- ctru-rs/src/services/ir_user.rs | 44 ++++--- 2 files changed, 187 insertions(+), 83 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index eda0eb9..2c5b31e 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,19 +1,23 @@ +use ctru::error::ResultCode; use ctru::prelude::*; -use ctru::services::ir_user::{IrDeviceId, IrUser}; +use ctru::services::ir_user::{IrDeviceId, IrUser, IrUserStatusInfo}; use time::Duration; -use ctru::error::ResultCode; const PACKET_INFO_SIZE: usize = 8; const MAX_PACKET_SIZE: usize = 32; const PACKET_COUNT: usize = 1; const PACKET_BUFFER_SIZE: usize = PACKET_COUNT * (PACKET_INFO_SIZE + MAX_PACKET_SIZE); +const CPP_CONNECTION_POLLING_PERIOD_MS: u8 = 0x08; +const CPP_POLLING_PERIOD_MS: u8 = 0x32; fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::init().unwrap(); - let console = Console::init(gfx.top_screen.borrow_mut()); + let bottom_console = Console::init(gfx.bottom_screen.borrow_mut()); + let top_console = Console::init(gfx.top_screen.borrow_mut()); + let ir_user = IrUser::init( PACKET_BUFFER_SIZE, PACKET_COUNT, @@ -21,87 +25,181 @@ fn main() { PACKET_COUNT, ) .expect("Couldn't initialize ir:USER service"); - let ir_user_connection_status_event = ir_user + + let print_status_info = || { + bottom_console.select(); + bottom_console.clear(); + println!("{:#x?}", ir_user.get_status_info()); + top_console.select(); + }; + + let conn_status_event = ir_user .get_connection_status_event() .expect("Couldn't get ir:USER connection status event"); - ir_user - .require_connection(IrDeviceId::CirclePadPro) - .expect("Couldn't initialize circle pad pro connection"); - let ir_user_recv_event = ir_user + let recv_event = ir_user .get_recv_event() .expect("Couldn't get ir:USER recv event"); - println!("StatusInfo:\n{:#?}", ir_user.get_status_info()); - - // Wait for the connection to establish - (|| unsafe { - ResultCode(ctru_sys::svcWaitSynchronization( - ir_user_connection_status_event, - Duration::seconds(10).whole_nanoseconds() as i64, - ))?; - - println!("Finished waiting on connection status event"); - println!("StatusInfo:\n{:#?}", ir_user.get_status_info()); - - Ok(()) - })().expect("Failed to connect to circle pad pro"); - - ir_user - .start_polling_input(20) - .expect("Couldn't configure circle pad pro polling interval"); - - while apt.main_loop() { + print_status_info(); + + // // Wait for the connection to establish + // (|| unsafe { + // ResultCode(ctru_sys::svcWaitSynchronization( + // ir_user_connection_status_event, + // Duration::seconds(10).whole_nanoseconds() as i64, + // ))?; + // + // println!("Finished waiting on connection status event"); + // println!("StatusInfo:\n{:#?}", ir_user.get_status_info()); + // + // Ok(()) + // })().expect("Failed to connect to circle pad pro"); + + let mut step = 0; + + 'main_loop: while apt.main_loop() { hid.scan_input(); // Check if we've received a packet from the circle pad pro - let check_ir_packet = - unsafe { ctru_sys::svcWaitSynchronization(ir_user_recv_event, 0) == 0 }; + let check_ir_packet = unsafe { ctru_sys::svcWaitSynchronization(recv_event, 0) == 0 }; if check_ir_packet { - console.clear(); - - // Move the cursor back to the top of the screen - print!("\x1b[0;0H"); - - println!("StatusInfo:\n{:?}", ir_user.get_status_info()); + print_status_info(); + handle_packet(&ir_user, &top_console, &bottom_console); + } - ir_user.process_shared_memory(|ir_mem| { - println!("\nReceiveBufferInfo:"); - for byte in &ir_mem[0x10..0x20] { - print!("{byte:02x} "); - } + if hid.keys_held().contains(KeyPad::KEY_START) { + break; + } - println!("\nReceiveBuffer:"); - let mut counter = 0; - for byte in &ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE] { - print!("{byte:02x} "); - counter += 1; - if counter % 16 == 0 { - println!() + if hid.keys_down().contains(KeyPad::KEY_A) { + match step { + 0 => { + loop { + hid.scan_input(); + if hid.keys_held().contains(KeyPad::KEY_START) { + break 'main_loop; + } + + ir_user + .require_connection(IrDeviceId::CirclePadPro) + .expect("Couldn't initialize circle pad pro connection"); + + // Wait for the connection to establish + (|| unsafe { + ResultCode(ctru_sys::svcWaitSynchronization( + conn_status_event, + Duration::milliseconds(10).whole_nanoseconds() as i64, + ))?; + + Ok(()) + })() + .expect("Failed to synchronize on connection status event"); + + print_status_info(); + let status_info = ir_user.get_status_info(); + if status_info.connection_status == 2 { + println!("Connected!"); + break; + } + + ir_user + .disconnect() + .expect("Failed to disconnect circle pad pro connection"); + + // Wait for the disconnect to go through + (|| unsafe { + ResultCode(ctru_sys::svcWaitSynchronization( + conn_status_event, + Duration::milliseconds(10).whole_nanoseconds() as i64, + ))?; + + Ok(()) + })() + .expect("Failed to synchronize on connection status event"); + } + // } + // _ => { + loop { + hid.scan_input(); + if hid.keys_held().contains(KeyPad::KEY_START) { + break 'main_loop; + } + + if let Err(e) = ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) { + println!("Error: {e:?}"); + } + print_status_info(); + + let check_ir_packet = unsafe { + ctru_sys::svcWaitSynchronization( + recv_event, + Duration::milliseconds(10).whole_nanoseconds() as i64, + ) == 0 + }; + print_status_info(); + + if check_ir_packet { + println!("Got packet from CPP"); + handle_packet(&ir_user, &top_console, &bottom_console); + break; + } } } + _ => {} + } - println!("\nSendBufferInfo:"); - for byte in &ir_mem[0x20 + PACKET_BUFFER_SIZE..0x30 + PACKET_BUFFER_SIZE] { - print!("{byte:02x} "); - } + step += 1; + } - println!("\n(skipping send packet buffer)"); - }); + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} - // Done handling the packet, release it - ir_user - .release_received_data(1) - .expect("Failed to release ir:USER packet"); +fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Console) { + ir_user.process_shared_memory(|ir_mem| { + bottom_console.select(); + println!("\nReceiveBufferInfo:"); + let mut counter = 0; + for byte in &ir_mem[0x10..0x20] { + print!("{byte:02x} "); + counter += 1; + if counter % 12 == 0 { + println!() + } + } - println!("\x1b[29;16HPress Start to exit"); + println!("\nReceiveBuffer:"); + counter = 0; + for byte in &ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE] { + print!("{byte:02x} "); + counter += 1; + if counter % 12 == 0 { + println!() + } } - if hid.keys_held().intersects(KeyPad::KEY_START) { - break; + println!("\nSendBufferInfo:"); + counter = 0; + for byte in &ir_mem[0x20 + PACKET_BUFFER_SIZE..0x30 + PACKET_BUFFER_SIZE] { + print!("{byte:02x} "); + counter += 1; + if counter % 12 == 0 { + println!() + } } - gfx.flush_buffers(); - gfx.swap_buffers(); - gfx.wait_for_vblank(); + println!("\n(skipping send packet buffer)"); + top_console.select(); + }); + + // Done handling the packet, release it + ir_user + .release_received_data(1) + .expect("Failed to release ir:USER packet"); + + if let Err(e) = ir_user.start_polling_input(CPP_POLLING_PERIOD_MS) { + println!("Error: {e:?}"); } } diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 212588e..10bbf4c 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -22,11 +22,12 @@ struct IrUserState { // shared_memory: Box<[u8]>, } -const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040; -const GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER: u32 = 0x000C0000; +const DISCONNECT_COMMAND_HEADER: u32 = 0x00090000; const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = 0x000A0000; +const GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER: u32 = 0x000C0000; const SEND_IR_NOP_COMMAND_HEADER: u32 = 0x000D0042; +const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040; impl IrUser { @@ -46,12 +47,6 @@ impl IrUser { println!("Getting shared memory pointer"); let info_sections_size = 0x30; - // let packet_count = 3; - // let max_packet_size = 32; - // let packet_info_size = 8; - // let recv_buffer_len = recv_packet_count * (packet_info_size + max_packet_size); - // let send_buffer_len = send_packet_count * (packet_info_size + max_packet_size); - let minimum_shared_memory_len = info_sections_size + recv_buffer_size + send_buffer_size; let shared_memory_len = if minimum_shared_memory_len % 0x1000 != 0 { (minimum_shared_memory_len / 0x1000) * 0x1000 + 0x1000 @@ -150,6 +145,17 @@ impl IrUser { Ok(()) } + pub fn disconnect(&self) -> crate::Result<()> { + println!("Disconnect called"); + self.send_service_request( + vec![DISCONNECT_COMMAND_HEADER], + 2, + )?; + + println!("Disconnect succeeded"); + Ok(()) + } + pub fn get_connection_status_event(&self) -> crate::Result { println!("GetConnectionStatusEvent called"); let response = self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; @@ -170,7 +176,7 @@ impl IrUser { pub fn start_polling_input(&self, period_ms: u8) -> crate::Result<()> { println!("SendIrnop (start_polling_input) called"); - let ir_request: [u8; 3] = [1, period_ms, 0]; + let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; self.send_service_request( vec![ SEND_IR_NOP_COMMAND_HEADER, @@ -305,14 +311,14 @@ impl IrDeviceId { #[derive(Debug)] pub struct IrUserStatusInfo { - recv_err_result: ctru_sys::Result, - send_err_result: ctru_sys::Result, - connection_status: u8, - trying_to_connect_status: u8, - connection_role: u8, - machine_id: u8, - unknown_field_1: u8, - network_id: u8, - unknown_field_2: u8, - unknown_field_3: u8, + pub recv_err_result: ctru_sys::Result, + pub send_err_result: ctru_sys::Result, + pub connection_status: u8, + pub trying_to_connect_status: u8, + pub connection_role: u8, + pub machine_id: u8, + pub unknown_field_1: u8, + pub network_id: u8, + pub unknown_field_2: u8, + pub unknown_field_3: u8, } From ebcb2608771383c3ec214565f1bdc31fd37e7480 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 2 Jan 2023 11:26:52 -0800 Subject: [PATCH 03/42] Improve packet handling and parse packets --- ctru-rs/examples/ir-user.rs | 60 ++++++----- ctru-rs/src/services/ir_user.rs | 183 ++++++++++++++++++++++++++++---- 2 files changed, 197 insertions(+), 46 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 2c5b31e..76f7c1e 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,6 +1,7 @@ use ctru::error::ResultCode; use ctru::prelude::*; -use ctru::services::ir_user::{IrDeviceId, IrUser, IrUserStatusInfo}; +use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser, IrUserStatusInfo}; +use std::io::Write; use time::Duration; const PACKET_INFO_SIZE: usize = 8; @@ -63,7 +64,6 @@ fn main() { let check_ir_packet = unsafe { ctru_sys::svcWaitSynchronization(recv_event, 0) == 0 }; if check_ir_packet { - print_status_info(); handle_packet(&ir_user, &top_console, &bottom_console); } @@ -117,15 +117,17 @@ fn main() { })() .expect("Failed to synchronize on connection status event"); } - // } - // _ => { + // } + // _ => { loop { hid.scan_input(); if hid.keys_held().contains(KeyPad::KEY_START) { break 'main_loop; } - if let Err(e) = ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) { + if let Err(e) = + ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) + { println!("Error: {e:?}"); } print_status_info(); @@ -158,45 +160,53 @@ fn main() { } fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Console) { + // Use a buffer to avoid flickering the screen (write all output at once) + let mut output_buffer = Vec::with_capacity(0x1000); + ir_user.process_shared_memory(|ir_mem| { - bottom_console.select(); - println!("\nReceiveBufferInfo:"); + writeln!(&mut output_buffer, "ReceiveBufferInfo:").unwrap(); let mut counter = 0; for byte in &ir_mem[0x10..0x20] { - print!("{byte:02x} "); + write!(&mut output_buffer, "{byte:02x} ").unwrap(); counter += 1; if counter % 12 == 0 { - println!() + writeln!(&mut output_buffer, "").unwrap(); } } - println!("\nReceiveBuffer:"); + writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap(); counter = 0; for byte in &ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE] { - print!("{byte:02x} "); + write!(&mut output_buffer, "{byte:02x} ").unwrap(); counter += 1; if counter % 12 == 0 { - println!() + writeln!(&mut output_buffer, "").unwrap(); } } - println!("\nSendBufferInfo:"); - counter = 0; - for byte in &ir_mem[0x20 + PACKET_BUFFER_SIZE..0x30 + PACKET_BUFFER_SIZE] { - print!("{byte:02x} "); - counter += 1; - if counter % 12 == 0 { - println!() - } - } - - println!("\n(skipping send packet buffer)"); - top_console.select(); + writeln!(&mut output_buffer, "").unwrap(); }); + let mut packets = ir_user.get_packets(); + let packet_count = packets.len(); + writeln!(&mut output_buffer, "Packet count: {packet_count}").unwrap(); + let last_packet = packets.pop().unwrap(); + writeln!(&mut output_buffer, "Last packet:\n{last_packet:02x?}").unwrap(); + + bottom_console.select(); + bottom_console.clear(); + std::io::stdout().write_all(&output_buffer).unwrap(); + + // Use println in case this fails + let cpp_response = CirclePadProInputResponse::try_from(last_packet) + .expect("Failed to parse CPP response from IR packet"); + println!("CPP Response:\n{cpp_response:#02x?}"); + + top_console.select(); + // Done handling the packet, release it ir_user - .release_received_data(1) + .release_received_data(packet_count as u32) .expect("Failed to release ir:USER packet"); if let Err(e) = ir_user.start_polling_input(CPP_POLLING_PERIOD_MS) { diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 10bbf4c..410d433 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -19,6 +19,8 @@ struct IrUserState { service_handle: Handle, shared_memory_handle: Handle, shared_memory: &'static [u8], + recv_buffer_size: usize, + recv_packet_count: usize, // shared_memory: Box<[u8]>, } @@ -31,7 +33,12 @@ const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040; impl IrUser { - pub fn init(recv_buffer_size: usize, recv_packet_count: usize, send_buffer_size: usize, send_packet_count: usize) -> crate::Result { + pub fn init( + recv_buffer_size: usize, + recv_packet_count: usize, + send_buffer_size: usize, + send_packet_count: usize, + ) -> crate::Result { let service_reference = ServiceReference::new( &IR_USER_ACTIVE, true, @@ -47,7 +54,8 @@ impl IrUser { println!("Getting shared memory pointer"); let info_sections_size = 0x30; - let minimum_shared_memory_len = info_sections_size + recv_buffer_size + send_buffer_size; + let minimum_shared_memory_len = + info_sections_size + recv_buffer_size + send_buffer_size; let shared_memory_len = if minimum_shared_memory_len % 0x1000 != 0 { (minimum_shared_memory_len / 0x1000) * 0x1000 + 0x1000 } else { @@ -96,6 +104,8 @@ impl IrUser { service_handle, shared_memory_handle, shared_memory, + recv_buffer_size, + recv_packet_count, }; *IR_USER_STATE.lock().unwrap() = Some(user_state); @@ -147,10 +157,7 @@ impl IrUser { pub fn disconnect(&self) -> crate::Result<()> { println!("Disconnect called"); - self.send_service_request( - vec![DISCONNECT_COMMAND_HEADER], - 2, - )?; + self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; println!("Disconnect succeeded"); Ok(()) @@ -158,7 +165,8 @@ impl IrUser { pub fn get_connection_status_event(&self) -> crate::Result { println!("GetConnectionStatusEvent called"); - let response = self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; + let response = + self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; let status_event = response[3] as Handle; println!("GetConnectionStatusEvent succeeded"); @@ -175,7 +183,6 @@ impl IrUser { } pub fn start_polling_input(&self, period_ms: u8) -> crate::Result<()> { - println!("SendIrnop (start_polling_input) called"); let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; self.send_service_request( vec![ @@ -187,29 +194,19 @@ impl IrUser { 2, )?; - println!("SendIrnop (start_polling_input) succeeded"); Ok(()) } pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> { - println!("ReleaseReceivedData called"); - self.send_service_request( - vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], - 2 - )?; - - println!("ReleaseReceivedData succeeded"); + self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?; Ok(()) } pub fn process_shared_memory(&self, process_fn: impl FnOnce(&[u8])) { - println!("Process shared memory started"); let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_ref().unwrap(); process_fn(shared_mem.shared_memory); - - println!("Process shared memory succeeded"); } pub fn get_status_info(&self) -> IrUserStatusInfo { @@ -217,8 +214,18 @@ impl IrUser { let shared_mem = shared_mem_guard.as_ref().unwrap().shared_memory; IrUserStatusInfo { - recv_err_result: i32::from_ne_bytes([shared_mem[0], shared_mem[1], shared_mem[2], shared_mem[3]]), - send_err_result: i32::from_ne_bytes([shared_mem[4], shared_mem[5], shared_mem[6], shared_mem[7]]), + recv_err_result: i32::from_ne_bytes([ + shared_mem[0], + shared_mem[1], + shared_mem[2], + shared_mem[3], + ]), + send_err_result: i32::from_ne_bytes([ + shared_mem[4], + shared_mem[5], + shared_mem[6], + shared_mem[7], + ]), connection_status: shared_mem[8], trying_to_connect_status: shared_mem[9], connection_role: shared_mem[10], @@ -230,6 +237,86 @@ impl IrUser { } } + pub fn get_packets(&self) -> Vec { + let shared_mem_guard = IR_USER_STATE.lock().unwrap(); + let user_state = shared_mem_guard.as_ref().unwrap(); + let shared_mem = user_state.shared_memory; + + let start_index = u32::from_ne_bytes([ + shared_mem[0x10], + shared_mem[0x11], + shared_mem[0x12], + shared_mem[0x13], + ]); + // let end_index = u32::from_ne_bytes([ + // shared_mem[0x14], + // shared_mem[0x15], + // shared_mem[0x16], + // shared_mem[0x17], + // ]); + let valid_packet_count = u32::from_ne_bytes([ + shared_mem[0x18], + shared_mem[0x19], + shared_mem[0x1a], + shared_mem[0x1b], + ]); + + (0..valid_packet_count) + .map(|i| { + let packet_index = (i + start_index) % user_state.recv_packet_count as u32; + let packet_info_offset = 0x20 + (packet_index * 8) as usize; + let packet_info = &shared_mem[packet_info_offset..packet_info_offset + 8]; + + let offset_to_data_buffer = u32::from_ne_bytes([ + packet_info[0], + packet_info[1], + packet_info[2], + packet_info[3], + ]) as usize; + let data_length = u32::from_ne_bytes([ + packet_info[4], + packet_info[5], + packet_info[6], + packet_info[7], + ]) as usize; + + let packet_info_section_size = (user_state.recv_packet_count * 8); + // let data_start_offset = 0x20 + packet_info_section_size + offset_to_data_buffer; + // let packet_data_offset = &shared_mem + // [data_start_offset..data_start_offset + data_length]; + let packet_data = |idx| -> u8 { + shared_mem[0x20 + + packet_info_section_size + + (offset_to_data_buffer + idx) + % (user_state.recv_buffer_size - packet_info_section_size)] + }; + + let (payload_length, payload_offset) = if packet_data(2) & 0x40 != 0 { + // Big payload + ( + ((packet_data(2) as usize & 0x3F) << 8) + packet_data(3) as usize, + 4, + ) + } else { + // Small payload + ((packet_data(2) & 0x3F) as usize, 3) + }; + + assert_eq!(data_length, payload_offset + payload_length + 1); + + IrUserPacket { + magic_number: packet_data(0), + destination_network_id: packet_data(1), + payload_length, + payload: (payload_offset..payload_offset + payload_length) + .map(packet_data) + .collect(), + checksum: packet_data(payload_offset + payload_length), + } + }) + .collect() + } + fn send_service_request( &self, mut request: Vec, @@ -322,3 +409,57 @@ pub struct IrUserStatusInfo { pub unknown_field_2: u8, pub unknown_field_3: u8, } + +#[derive(Debug)] +pub struct IrUserPacket { + pub magic_number: u8, + pub destination_network_id: u8, + pub payload_length: usize, + pub payload: Vec, + pub checksum: u8, +} + +#[derive(Debug)] +pub struct CirclePadProInputResponse { + pub response_id: u8, + pub c_stick_x: u16, + pub c_stick_y: u16, + pub battery_level: u8, + pub zl_pressed: bool, + pub zr_pressed: bool, + pub r_pressed: bool, + pub unknown_field: u8, +} + +impl TryFrom for CirclePadProInputResponse { + type Error = String; + + fn try_from(packet: IrUserPacket) -> Result { + if packet.payload.len() != 6 { + return Err(format!( + "Invalid payload length (expected 6 bytes, got {})", + packet.payload.len() + )); + } + + let response_id = packet.payload[0]; + let c_stick_x = packet.payload[1] as u16 + (((packet.payload[2] & 0x0F) as u16) << 8); + let c_stick_y = (((packet.payload[2] & 0xF0) as u16) >> 4) + ((packet.payload[3] as u16) << 4); + let battery_level = packet.payload[4] & 0x1F; + let zl_pressed = packet.payload[4] & 0x20 == 0; + let zr_pressed = packet.payload[4] & 0x40 == 0; + let r_pressed = packet.payload[4] & 0x80 == 0; + let unknown_field = packet.payload[5]; + + Ok(CirclePadProInputResponse { + response_id, + c_stick_x, + c_stick_y, + battery_level, + zl_pressed, + zr_pressed, + r_pressed, + unknown_field, + }) + } +} From d1deb60f3696738556d004bb58e32b1bb10ff206 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 2 Jan 2023 19:33:41 -0800 Subject: [PATCH 04/42] Fix some result codes not getting recognized as errors --- ctru-rs/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 18fd175..44c9d41 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -21,7 +21,7 @@ impl Try for ResultCode { } fn branch(self) -> ControlFlow { - if self.0 < 0 { + if self.0 != 0 { ControlFlow::Break(self.into()) } else { ControlFlow::Continue(()) From d798f56ad91083fe3df72fa5a7086fe50c3b2eac Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 2 Jan 2023 19:34:32 -0800 Subject: [PATCH 05/42] Clean up some of the code and warnings --- ctru-rs/examples/ir-user.rs | 188 ++++++++++++++------------------ ctru-rs/src/error.rs | 7 ++ ctru-rs/src/services/ir_user.rs | 16 ++- 3 files changed, 100 insertions(+), 111 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 76f7c1e..0cbaf7d 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,8 +1,7 @@ -use ctru::error::ResultCode; use ctru::prelude::*; -use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser, IrUserStatusInfo}; +use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; use std::io::Write; -use time::Duration; +use std::time::Duration; const PACKET_INFO_SIZE: usize = 8; const MAX_PACKET_SIZE: usize = 32; @@ -42,115 +41,84 @@ fn main() { .expect("Couldn't get ir:USER recv event"); print_status_info(); - // // Wait for the connection to establish - // (|| unsafe { - // ResultCode(ctru_sys::svcWaitSynchronization( - // ir_user_connection_status_event, - // Duration::seconds(10).whole_nanoseconds() as i64, - // ))?; - // - // println!("Finished waiting on connection status event"); - // println!("StatusInfo:\n{:#?}", ir_user.get_status_info()); - // - // Ok(()) - // })().expect("Failed to connect to circle pad pro"); - - let mut step = 0; - + let mut is_connected = false; 'main_loop: while apt.main_loop() { hid.scan_input(); - // Check if we've received a packet from the circle pad pro - let check_ir_packet = unsafe { ctru_sys::svcWaitSynchronization(recv_event, 0) == 0 }; + if hid.keys_held().contains(KeyPad::KEY_START) { + break; + } - if check_ir_packet { + // Check if we've received a packet from the circle pad pro + let packet_received = IrUser::wait_for_event(recv_event, Duration::ZERO).is_ok(); + if packet_received { handle_packet(&ir_user, &top_console, &bottom_console); } - if hid.keys_held().contains(KeyPad::KEY_START) { - break; - } + if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected { + // Connection loop + loop { + hid.scan_input(); + if hid.keys_held().contains(KeyPad::KEY_START) { + break 'main_loop; + } - if hid.keys_down().contains(KeyPad::KEY_A) { - match step { - 0 => { - loop { - hid.scan_input(); - if hid.keys_held().contains(KeyPad::KEY_START) { - break 'main_loop; - } - - ir_user - .require_connection(IrDeviceId::CirclePadPro) - .expect("Couldn't initialize circle pad pro connection"); - - // Wait for the connection to establish - (|| unsafe { - ResultCode(ctru_sys::svcWaitSynchronization( - conn_status_event, - Duration::milliseconds(10).whole_nanoseconds() as i64, - ))?; - - Ok(()) - })() - .expect("Failed to synchronize on connection status event"); - - print_status_info(); - let status_info = ir_user.get_status_info(); - if status_info.connection_status == 2 { - println!("Connected!"); - break; - } - - ir_user - .disconnect() - .expect("Failed to disconnect circle pad pro connection"); - - // Wait for the disconnect to go through - (|| unsafe { - ResultCode(ctru_sys::svcWaitSynchronization( - conn_status_event, - Duration::milliseconds(10).whole_nanoseconds() as i64, - ))?; - - Ok(()) - })() - .expect("Failed to synchronize on connection status event"); + ir_user + .require_connection(IrDeviceId::CirclePadPro) + .expect("Couldn't initialize circle pad pro connection"); + + // Wait for the connection to establish + if let Err(e) = IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) + { + if !e.is_timeout() { + panic!("Couldn't initialize circle pad pro connection: {e}"); } - // } - // _ => { - loop { - hid.scan_input(); - if hid.keys_held().contains(KeyPad::KEY_START) { - break 'main_loop; - } - - if let Err(e) = - ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) - { - println!("Error: {e:?}"); - } - print_status_info(); - - let check_ir_packet = unsafe { - ctru_sys::svcWaitSynchronization( - recv_event, - Duration::milliseconds(10).whole_nanoseconds() as i64, - ) == 0 - }; - print_status_info(); - - if check_ir_packet { - println!("Got packet from CPP"); - handle_packet(&ir_user, &top_console, &bottom_console); - break; - } + } + + print_status_info(); + let status_info = ir_user.get_status_info(); + if status_info.connection_status == 2 { + println!("Connected!"); + break; + } + + ir_user + .disconnect() + .expect("Failed to disconnect circle pad pro connection"); + + // Wait for the disconnect to go through + if let Err(e) = IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) + { + if !e.is_timeout() { + panic!("Couldn't initialize circle pad pro connection: {e}"); } } - _ => {} } - step += 1; + // Sending first packet retry loop + loop { + hid.scan_input(); + if hid.keys_held().contains(KeyPad::KEY_START) { + break 'main_loop; + } + + if let Err(e) = ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) { + println!("Error: {e:?}"); + } + print_status_info(); + + let recv_event_result = + IrUser::wait_for_event(recv_event, Duration::from_millis(100)); + print_status_info(); + + if recv_event_result.is_ok() { + println!("Got packet from CPP"); + handle_packet(&ir_user, &top_console, &bottom_console); + break; + } + } + + is_connected = true; } gfx.flush_buffers(); @@ -163,6 +131,8 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso // Use a buffer to avoid flickering the screen (write all output at once) let mut output_buffer = Vec::with_capacity(0x1000); + writeln!(&mut output_buffer, "{:x?}", ir_user.get_status_info()).unwrap(); + ir_user.process_shared_memory(|ir_mem| { writeln!(&mut output_buffer, "ReceiveBufferInfo:").unwrap(); let mut counter = 0; @@ -170,7 +140,7 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso write!(&mut output_buffer, "{byte:02x} ").unwrap(); counter += 1; if counter % 12 == 0 { - writeln!(&mut output_buffer, "").unwrap(); + writeln!(&mut output_buffer).unwrap(); } } @@ -180,28 +150,27 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso write!(&mut output_buffer, "{byte:02x} ").unwrap(); counter += 1; if counter % 12 == 0 { - writeln!(&mut output_buffer, "").unwrap(); + writeln!(&mut output_buffer).unwrap(); } } - writeln!(&mut output_buffer, "").unwrap(); + writeln!(&mut output_buffer).unwrap(); }); let mut packets = ir_user.get_packets(); let packet_count = packets.len(); writeln!(&mut output_buffer, "Packet count: {packet_count}").unwrap(); let last_packet = packets.pop().unwrap(); - writeln!(&mut output_buffer, "Last packet:\n{last_packet:02x?}").unwrap(); - - bottom_console.select(); - bottom_console.clear(); - std::io::stdout().write_all(&output_buffer).unwrap(); + writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap(); - // Use println in case this fails let cpp_response = CirclePadProInputResponse::try_from(last_packet) .expect("Failed to parse CPP response from IR packet"); - println!("CPP Response:\n{cpp_response:#02x?}"); + writeln!(&mut output_buffer, "{cpp_response:#02x?}").unwrap(); + // Write output to bottom screen + bottom_console.select(); + bottom_console.clear(); + std::io::stdout().write_all(&output_buffer).unwrap(); top_console.select(); // Done handling the packet, release it @@ -209,6 +178,7 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso .release_received_data(packet_count as u32) .expect("Failed to release ir:USER packet"); + // Remind the CPP that we're still listening if let Err(e) = ir_user.start_polling_input(CPP_POLLING_PERIOD_MS) { println!("Error: {e:?}"); } diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 44c9d41..af377c9 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -69,6 +69,13 @@ impl Error { // Copy out of the error string, since it may be changed by other libc calls later Self::Libc(error_str.to_string_lossy().into()) } + + pub fn is_timeout(&self) -> bool { + match *self { + Error::Os(code) => R_DESCRIPTION(code) == ctru_sys::RD_TIMEOUT as ctru_sys::Result, + _ => false, + } + } } impl From for Error { diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 410d433..949fbbf 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -6,6 +6,7 @@ use std::cmp::max; use std::ffi::CString; use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use std::sync::Mutex; +use std::time::Duration; static IR_USER_ACTIVE: Mutex = Mutex::new(0); static IR_USER_STATE: Mutex> = Mutex::new(None); @@ -182,6 +183,16 @@ impl IrUser { Ok(recv_event) } + pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::svcWaitSynchronization( + event, + timeout.as_nanos() as i64, + ))?; + } + Ok(()) + } + pub fn start_polling_input(&self, period_ms: u8) -> crate::Result<()> { let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; self.send_service_request( @@ -280,7 +291,7 @@ impl IrUser { packet_info[7], ]) as usize; - let packet_info_section_size = (user_state.recv_packet_count * 8); + let packet_info_section_size = user_state.recv_packet_count * 8; // let data_start_offset = 0x20 + packet_info_section_size + offset_to_data_buffer; // let packet_data_offset = &shared_mem // [data_start_offset..data_start_offset + data_length]; @@ -444,7 +455,8 @@ impl TryFrom for CirclePadProInputResponse { let response_id = packet.payload[0]; let c_stick_x = packet.payload[1] as u16 + (((packet.payload[2] & 0x0F) as u16) << 8); - let c_stick_y = (((packet.payload[2] & 0xF0) as u16) >> 4) + ((packet.payload[3] as u16) << 4); + let c_stick_y = + (((packet.payload[2] & 0xF0) as u16) >> 4) + ((packet.payload[3] as u16) << 4); let battery_level = packet.payload[4] & 0x1F; let zl_pressed = packet.payload[4] & 0x20 == 0; let zr_pressed = packet.payload[4] & 0x40 == 0; From 5a357dc84486c55fe204c17e73d6996f085d8189 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 2 Jan 2023 20:06:12 -0800 Subject: [PATCH 06/42] Implement ir:USER service shutdown, and do more code cleanup --- ctru-rs/examples/ir-user.rs | 6 ++- ctru-rs/src/services/ir_user.rs | 96 +++++++++++---------------------- 2 files changed, 36 insertions(+), 66 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 0cbaf7d..c467e5e 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -68,7 +68,8 @@ fn main() { .expect("Couldn't initialize circle pad pro connection"); // Wait for the connection to establish - if let Err(e) = IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) + if let Err(e) = + IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) { if !e.is_timeout() { panic!("Couldn't initialize circle pad pro connection: {e}"); @@ -87,7 +88,8 @@ fn main() { .expect("Failed to disconnect circle pad pro connection"); // Wait for the disconnect to go through - if let Err(e) = IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) + if let Err(e) = + IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) { if !e.is_timeout() { panic!("Couldn't initialize circle pad pro connection: {e}"); diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 949fbbf..eacd8a7 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -20,9 +20,9 @@ struct IrUserState { service_handle: Handle, shared_memory_handle: Handle, shared_memory: &'static [u8], + shared_memory_layout: Layout, recv_buffer_size: usize, recv_packet_count: usize, - // shared_memory: Box<[u8]>, } const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040; @@ -42,10 +42,9 @@ impl IrUser { ) -> crate::Result { let service_reference = ServiceReference::new( &IR_USER_ACTIVE, - true, + false, || unsafe { - println!("Starting IrUser"); - println!("Getting ir:USER service handle"); + // Get the ir:USER service handle let mut service_handle = Handle::default(); let service_name = CString::new("ir:USER").unwrap(); ResultCode(ctru_sys::srvGetServiceHandle( @@ -53,7 +52,8 @@ impl IrUser { service_name.as_ptr(), ))?; - println!("Getting shared memory pointer"); + // Calculate the shared memory size. + // Shared memory length must be page aligned. let info_sections_size = 0x30; let minimum_shared_memory_len = info_sections_size + recv_buffer_size + send_buffer_size; @@ -63,21 +63,14 @@ impl IrUser { (minimum_shared_memory_len / 0x1000) * 0x1000 }; assert_eq!(shared_memory_len % 0x1000, 0); - // let shared_memory_len = info_sections_size + recv_buffer_size + send_buffer_size; - println!("Shared memory size: {shared_memory_len:#x}"); - // let shared_memory_len = info_sections_size + packet_count * (packet_info_size + max_packet_size); - // let shared_memory = Box::new([0; 0x1000]); - // let shared_memory_ptr = ctru_sys::mappableAlloc(shared_memory_len) as *const u8; + // Allocate the shared memory let shared_memory_layout = Layout::from_size_align(shared_memory_len, 0x1000).unwrap(); let shared_memory_ptr = std::alloc::alloc_zeroed(shared_memory_layout); - println!( - "Using shared memory address: {:#08x}", - shared_memory_ptr as usize - ); + let shared_memory = &*slice_from_raw_parts(shared_memory_ptr, shared_memory_len); - println!("Marking memory block as shared memory"); + // Mark the memory as shared let mut shared_memory_handle = Handle::default(); ResultCode(ctru_sys::svcCreateMemoryBlock( &mut shared_memory_handle, @@ -86,9 +79,8 @@ impl IrUser { MEMPERM_READ, MEMPERM_READWRITE, ))?; - let shared_memory = &*slice_from_raw_parts(shared_memory_ptr, shared_memory_len); - println!("Initializing ir:USER service"); + // Initialize the ir:USER service with the shared memory initialize_irnop_shared(InitializeIrnopSharedParams { ir_user_handle: service_handle, shared_memory_len: shared_memory_len as u32, @@ -100,43 +92,38 @@ impl IrUser { shared_memory_handle, })?; - println!("Setting IrUserState in static"); + // Set up our service state let user_state = IrUserState { service_handle, shared_memory_handle, shared_memory, + shared_memory_layout, recv_buffer_size, recv_packet_count, }; *IR_USER_STATE.lock().unwrap() = Some(user_state); - println!("Done starting IrUser"); Ok(()) }, || { - println!("Close called for IrUser"); + // Remove our service state from the global location let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); - let shared_mem = shared_mem_guard - .take() - .expect("ir:USER shared memory mutex shouldn't be empty"); - // (|| unsafe { - // // println!("Unmapping the ir:USER shared memory"); - // // ResultCode(ctru_sys::svcUnmapMemoryBlock( - // // shared_mem.shared_memory_handle, - // // shared_mem.shared_memory.as_ptr() as u32, - // // ))?; - // - // println!("Closing memory and service handles"); - // // ResultCode(ctru_sys::svcCloseHandle(shared_mem.shared_memory_handle))?; - // ResultCode(ctru_sys::svcCloseHandle(shared_mem.service_handle))?; - // - // // println!("Freeing shared memory"); - // // ctru_sys::mappableFree(shared_mem.shared_memory.as_ptr() as *mut libc::c_void); - // - // Ok(()) - // })() - // .unwrap(); - println!("Done closing IrUser"); + let shared_mem = shared_mem_guard.take().unwrap(); + + (move || unsafe { + // Close service and memory handles + ResultCode(ctru_sys::svcCloseHandle(shared_mem.service_handle))?; + ResultCode(ctru_sys::svcCloseHandle(shared_mem.shared_memory_handle))?; + + // Free shared memory + std::alloc::dealloc( + shared_mem.shared_memory.as_ptr() as *mut u8, + shared_mem.shared_memory_layout, + ); + + Ok(()) + })() + .unwrap(); }, )?; @@ -146,40 +133,30 @@ impl IrUser { } pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> { - println!("RequireConnection called"); self.send_service_request( vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], 2, )?; - - println!("RequireConnection succeeded"); Ok(()) } pub fn disconnect(&self) -> crate::Result<()> { - println!("Disconnect called"); self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; - - println!("Disconnect succeeded"); Ok(()) } pub fn get_connection_status_event(&self) -> crate::Result { - println!("GetConnectionStatusEvent called"); let response = self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; let status_event = response[3] as Handle; - println!("GetConnectionStatusEvent succeeded"); Ok(status_event) } pub fn get_recv_event(&self) -> crate::Result { - println!("GetReceiveEvent called"); let response = self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4)?; let recv_event = response[3] as Handle; - println!("GetReceiveEvent succeeded"); Ok(recv_event) } @@ -259,12 +236,6 @@ impl IrUser { shared_mem[0x12], shared_mem[0x13], ]); - // let end_index = u32::from_ne_bytes([ - // shared_mem[0x14], - // shared_mem[0x15], - // shared_mem[0x16], - // shared_mem[0x17], - // ]); let valid_packet_count = u32::from_ne_bytes([ shared_mem[0x18], shared_mem[0x19], @@ -292,14 +263,10 @@ impl IrUser { ]) as usize; let packet_info_section_size = user_state.recv_packet_count * 8; - // let data_start_offset = 0x20 + packet_info_section_size + offset_to_data_buffer; - // let packet_data_offset = &shared_mem - // [data_start_offset..data_start_offset + data_length]; let packet_data = |idx| -> u8 { - shared_mem[0x20 - + packet_info_section_size - + (offset_to_data_buffer + idx) - % (user_state.recv_buffer_size - packet_info_section_size)] + let offset = 0x20 + packet_info_section_size + (offset_to_data_buffer + idx); + let data_buffer_size = (user_state.recv_buffer_size - packet_info_section_size); + shared_mem[offset % data_buffer_size] }; let (payload_length, payload_offset) = if packet_data(2) & 0x40 != 0 { @@ -336,6 +303,7 @@ impl IrUser { let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_mut().unwrap(); + // Set up the request let cmd_buffer = unsafe { &mut *(slice_from_raw_parts_mut( ctru_sys::getThreadCommandBuffer(), From 39b6bc92b5b18278ce000b5e14a0005735068227 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 2 Jan 2023 20:37:38 -0800 Subject: [PATCH 07/42] Tidy up the ir:USER code/example and fix a bug in the service code --- ctru-rs/examples/ir-user.rs | 64 +++++++++++++++++---------------- ctru-rs/src/services/ir_user.rs | 24 ++++++++----- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index c467e5e..d0c1abd 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -15,9 +15,12 @@ fn main() { let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::init().unwrap(); - let bottom_console = Console::init(gfx.bottom_screen.borrow_mut()); let top_console = Console::init(gfx.top_screen.borrow_mut()); + let bottom_console = Console::init(gfx.bottom_screen.borrow_mut()); + + println!("Welcome to the ir:USER / Circle Pad Pro Demo"); + println!("Starting up ir:USER service"); let ir_user = IrUser::init( PACKET_BUFFER_SIZE, PACKET_COUNT, @@ -25,12 +28,13 @@ fn main() { PACKET_COUNT, ) .expect("Couldn't initialize ir:USER service"); + println!("ir:USER service initialized\nPress A to connect to the CPP"); let print_status_info = || { - bottom_console.select(); - bottom_console.clear(); - println!("{:#x?}", ir_user.get_status_info()); top_console.select(); + top_console.clear(); + println!("{:#x?}", ir_user.get_status_info()); + bottom_console.select(); }; let conn_status_event = ir_user @@ -56,6 +60,8 @@ fn main() { } if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected { + println!("Attempting to connect to the CPP"); + // Connection loop loop { hid.scan_input(); @@ -114,7 +120,7 @@ fn main() { print_status_info(); if recv_event_result.is_ok() { - println!("Got packet from CPP"); + println!("Got first packet from CPP"); handle_packet(&ir_user, &top_console, &bottom_console); break; } @@ -136,44 +142,29 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso writeln!(&mut output_buffer, "{:x?}", ir_user.get_status_info()).unwrap(); ir_user.process_shared_memory(|ir_mem| { - writeln!(&mut output_buffer, "ReceiveBufferInfo:").unwrap(); - let mut counter = 0; - for byte in &ir_mem[0x10..0x20] { - write!(&mut output_buffer, "{byte:02x} ").unwrap(); - counter += 1; - if counter % 12 == 0 { - writeln!(&mut output_buffer).unwrap(); - } - } + writeln!(&mut output_buffer, "\nReceiveBufferInfo:").unwrap(); + write_buffer_as_hex(&ir_mem[0x10..0x20], &mut output_buffer); writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap(); - counter = 0; - for byte in &ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE] { - write!(&mut output_buffer, "{byte:02x} ").unwrap(); - counter += 1; - if counter % 12 == 0 { - writeln!(&mut output_buffer).unwrap(); - } - } - + write_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE], &mut output_buffer); writeln!(&mut output_buffer).unwrap(); }); - let mut packets = ir_user.get_packets(); + let packets = ir_user.get_packets(); let packet_count = packets.len(); - writeln!(&mut output_buffer, "Packet count: {packet_count}").unwrap(); - let last_packet = packets.pop().unwrap(); + writeln!(&mut output_buffer, "\nPacket count: {packet_count}").unwrap(); + let last_packet = packets.last().unwrap(); writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap(); let cpp_response = CirclePadProInputResponse::try_from(last_packet) .expect("Failed to parse CPP response from IR packet"); - writeln!(&mut output_buffer, "{cpp_response:#02x?}").unwrap(); + writeln!(&mut output_buffer, "\n{cpp_response:#02x?}").unwrap(); - // Write output to bottom screen - bottom_console.select(); - bottom_console.clear(); - std::io::stdout().write_all(&output_buffer).unwrap(); + // Write output to top screen top_console.select(); + top_console.clear(); + std::io::stdout().write_all(&output_buffer).unwrap(); + bottom_console.select(); // Done handling the packet, release it ir_user @@ -185,3 +176,14 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso println!("Error: {e:?}"); } } + +fn write_buffer_as_hex(buffer: &[u8], output: &mut Vec) { + let mut counter = 0; + for byte in buffer { + write!(output, "{byte:02x} ").unwrap(); + counter += 1; + if counter % 16 == 0 { + writeln!(output).unwrap(); + } + } +} diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index eacd8a7..a171812 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -11,7 +11,6 @@ use std::time::Duration; static IR_USER_ACTIVE: Mutex = Mutex::new(0); static IR_USER_STATE: Mutex> = Mutex::new(None); -#[non_exhaustive] pub struct IrUser { _service_reference: ServiceReference, } @@ -264,9 +263,10 @@ impl IrUser { let packet_info_section_size = user_state.recv_packet_count * 8; let packet_data = |idx| -> u8 { - let offset = 0x20 + packet_info_section_size + (offset_to_data_buffer + idx); - let data_buffer_size = (user_state.recv_buffer_size - packet_info_section_size); - shared_mem[offset % data_buffer_size] + let header_size = 0x20 + packet_info_section_size; + let data_buffer_offset = offset_to_data_buffer + idx; + let data_buffer_size = user_state.recv_buffer_size - packet_info_section_size; + shared_mem[header_size + data_buffer_offset % data_buffer_size] }; let (payload_length, payload_offset) = if packet_data(2) & 0x40 != 0 { @@ -282,6 +282,9 @@ impl IrUser { assert_eq!(data_length, payload_offset + payload_length + 1); + let magic_number = packet_data(0); + assert_eq!(magic_number, 0xA5); + IrUserPacket { magic_number: packet_data(0), destination_network_id: packet_data(1), @@ -400,7 +403,6 @@ pub struct IrUserPacket { #[derive(Debug)] pub struct CirclePadProInputResponse { - pub response_id: u8, pub c_stick_x: u16, pub c_stick_y: u16, pub battery_level: u8, @@ -410,10 +412,10 @@ pub struct CirclePadProInputResponse { pub unknown_field: u8, } -impl TryFrom for CirclePadProInputResponse { +impl TryFrom<&IrUserPacket> for CirclePadProInputResponse { type Error = String; - fn try_from(packet: IrUserPacket) -> Result { + fn try_from(packet: &IrUserPacket) -> Result { if packet.payload.len() != 6 { return Err(format!( "Invalid payload length (expected 6 bytes, got {})", @@ -422,6 +424,13 @@ impl TryFrom for CirclePadProInputResponse { } let response_id = packet.payload[0]; + if response_id != 0x10 { + return Err(format!( + "Invalid response ID (expected 0x10, got {:#x}", + packet.payload[0] + )); + } + let c_stick_x = packet.payload[1] as u16 + (((packet.payload[2] & 0x0F) as u16) << 8); let c_stick_y = (((packet.payload[2] & 0xF0) as u16) >> 4) + ((packet.payload[3] as u16) << 4); @@ -432,7 +441,6 @@ impl TryFrom for CirclePadProInputResponse { let unknown_field = packet.payload[5]; Ok(CirclePadProInputResponse { - response_id, c_stick_x, c_stick_y, battery_level, From 8af2c96360431d95c29f352ab5dfb6a457fa5f35 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Wed, 4 Jan 2023 16:37:02 -0800 Subject: [PATCH 08/42] More ir:USER service/example tidying and documentation --- ctru-rs/examples/ir-user.rs | 18 +++++-- ctru-rs/src/services/ir_user.rs | 86 ++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index d0c1abd..2b04260 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,3 +1,5 @@ +//! A demo of using the ir:USER service to connect to the Circle Pad Pro. + use ctru::prelude::*; use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; use std::io::Write; @@ -37,6 +39,7 @@ fn main() { bottom_console.select(); }; + // Get event handles let conn_status_event = ir_user .get_connection_status_event() .expect("Couldn't get ir:USER connection status event"); @@ -49,6 +52,7 @@ fn main() { 'main_loop: while apt.main_loop() { hid.scan_input(); + // Check if we need to exit if hid.keys_held().contains(KeyPad::KEY_START) { break; } @@ -59,6 +63,7 @@ fn main() { handle_packet(&ir_user, &top_console, &bottom_console); } + // Check if we should start the connection if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected { println!("Attempting to connect to the CPP"); @@ -69,6 +74,7 @@ fn main() { break 'main_loop; } + // Start the connection process ir_user .require_connection(IrDeviceId::CirclePadPro) .expect("Couldn't initialize circle pad pro connection"); @@ -83,12 +89,12 @@ fn main() { } print_status_info(); - let status_info = ir_user.get_status_info(); - if status_info.connection_status == 2 { + if ir_user.get_status_info().connection_status == 2 { println!("Connected!"); break; } + // If not connected (ex. timeout), disconnect so we can retry ir_user .disconnect() .expect("Failed to disconnect circle pad pro connection"); @@ -110,11 +116,13 @@ fn main() { break 'main_loop; } - if let Err(e) = ir_user.start_polling_input(CPP_CONNECTION_POLLING_PERIOD_MS) { + // Send a request for input to the CPP + if let Err(e) = ir_user.request_input_polling(CPP_CONNECTION_POLLING_PERIOD_MS) { println!("Error: {e:?}"); } print_status_info(); + // Wait for the response let recv_event_result = IrUser::wait_for_event(recv_event, Duration::from_millis(100)); print_status_info(); @@ -124,6 +132,8 @@ fn main() { handle_packet(&ir_user, &top_console, &bottom_console); break; } + + // We didn't get a response in time, so loop and retry } is_connected = true; @@ -172,7 +182,7 @@ fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Conso .expect("Failed to release ir:USER packet"); // Remind the CPP that we're still listening - if let Err(e) = ir_user.start_polling_input(CPP_POLLING_PERIOD_MS) { + if let Err(e) = ir_user.request_input_polling(CPP_POLLING_PERIOD_MS) { println!("Error: {e:?}"); } } diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index a171812..303490a 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -1,5 +1,6 @@ use crate::error::ResultCode; use crate::services::ServiceReference; +use crate::Error; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use std::alloc::Layout; use std::cmp::max; @@ -11,10 +12,13 @@ use std::time::Duration; static IR_USER_ACTIVE: Mutex = Mutex::new(0); static IR_USER_STATE: Mutex> = Mutex::new(None); +/// The "ir:USER" service. This service is used to talk to IR devices such as +/// the Circle Pad Pro. pub struct IrUser { _service_reference: ServiceReference, } +// We need to hold on to some extra service state, hence this struct. struct IrUserState { service_handle: Handle, shared_memory_handle: Handle, @@ -24,6 +28,7 @@ struct IrUserState { recv_packet_count: usize, } +// ir:USER syscall command headers const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040; const DISCONNECT_COMMAND_HEADER: u32 = 0x00090000; const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = 0x000A0000; @@ -32,7 +37,18 @@ const SEND_IR_NOP_COMMAND_HEADER: u32 = 0x000D0042; const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040; +// Misc constants +const SHARED_MEM_INFO_SECTIONS_SIZE: usize = 0x30; +const SHARED_MEM_RECV_BUFFER_OFFSET: usize = 0x20; +const PAGE_SIZE: usize = 0x1000; +const IR_BITRATE: u32 = 4; +const PACKET_INFO_SIZE: u32 = 8; +const CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID: u8 = 0x10; + impl IrUser { + /// Initialize the ir:USER service. The provided buffer sizes and packet + /// counts are used to calculate the size of shared memory used by the + /// service. pub fn init( recv_buffer_size: usize, recv_packet_count: usize, @@ -53,19 +69,13 @@ impl IrUser { // Calculate the shared memory size. // Shared memory length must be page aligned. - let info_sections_size = 0x30; let minimum_shared_memory_len = - info_sections_size + recv_buffer_size + send_buffer_size; - let shared_memory_len = if minimum_shared_memory_len % 0x1000 != 0 { - (minimum_shared_memory_len / 0x1000) * 0x1000 + 0x1000 - } else { - (minimum_shared_memory_len / 0x1000) * 0x1000 - }; - assert_eq!(shared_memory_len % 0x1000, 0); + SHARED_MEM_INFO_SECTIONS_SIZE + recv_buffer_size + send_buffer_size; + let shared_memory_len = round_up(minimum_shared_memory_len, PAGE_SIZE); // Allocate the shared memory let shared_memory_layout = - Layout::from_size_align(shared_memory_len, 0x1000).unwrap(); + Layout::from_size_align(shared_memory_len, PAGE_SIZE).unwrap(); let shared_memory_ptr = std::alloc::alloc_zeroed(shared_memory_layout); let shared_memory = &*slice_from_raw_parts(shared_memory_ptr, shared_memory_len); @@ -87,7 +97,7 @@ impl IrUser { recv_packet_count: recv_packet_count as u32, send_packet_buffer_len: send_buffer_size as u32, send_packet_count: send_packet_count as u32, - bit_rate: 4, + bit_rate: IR_BITRATE, shared_memory_handle, })?; @@ -131,6 +141,7 @@ impl IrUser { }) } + /// Try to connect to the device with the provided ID. pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> { self.send_service_request( vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], @@ -139,11 +150,13 @@ impl IrUser { Ok(()) } + /// Close the current IR connection. pub fn disconnect(&self) -> crate::Result<()> { self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; Ok(()) } + /// Get an event handle that activates on connection status changes. pub fn get_connection_status_event(&self) -> crate::Result { let response = self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; @@ -152,6 +165,7 @@ impl IrUser { Ok(status_event) } + /// Get an event handle that activates when a packet is received. pub fn get_recv_event(&self) -> crate::Result { let response = self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4)?; let recv_event = response[3] as Handle; @@ -159,6 +173,8 @@ impl IrUser { Ok(recv_event) } + /// Wait for an event to fire. If the timeout is reached, an error is returned. You can use + /// [`Error::is_timeout`] to check if the error is due to a timeout. pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::svcWaitSynchronization( @@ -169,7 +185,11 @@ impl IrUser { Ok(()) } - pub fn start_polling_input(&self, period_ms: u8) -> crate::Result<()> { + /// Circle Pad Pro specific request. + /// + /// This will send a packet to the CPP requesting it to send back packets + /// with the current device input values. + pub fn request_input_polling(&self, period_ms: u8) -> crate::Result<()> { let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; self.send_service_request( vec![ @@ -184,11 +204,14 @@ impl IrUser { Ok(()) } + /// Mark the last `packet_count` packets as processed, so their memory in + /// the receive buffer can be reused. pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> { self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?; Ok(()) } + /// This will let you directly read the ir:USER shared memory via a callback. pub fn process_shared_memory(&self, process_fn: impl FnOnce(&[u8])) { let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_ref().unwrap(); @@ -196,6 +219,7 @@ impl IrUser { process_fn(shared_mem.shared_memory); } + /// Read and parse the ir:USER service status data from shared memory. pub fn get_status_info(&self) -> IrUserStatusInfo { let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_ref().unwrap().shared_memory; @@ -224,11 +248,13 @@ impl IrUser { } } + /// Read and parse the current packets received from the IR device. pub fn get_packets(&self) -> Vec { let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let user_state = shared_mem_guard.as_ref().unwrap(); let shared_mem = user_state.shared_memory; + // Find where the packets are, and how many let start_index = u32::from_ne_bytes([ shared_mem[0x10], shared_mem[0x11], @@ -242,11 +268,15 @@ impl IrUser { shared_mem[0x1b], ]); + // Parse the packets (0..valid_packet_count) .map(|i| { + // Get the packet info let packet_index = (i + start_index) % user_state.recv_packet_count as u32; - let packet_info_offset = 0x20 + (packet_index * 8) as usize; - let packet_info = &shared_mem[packet_info_offset..packet_info_offset + 8]; + let packet_info_offset = + SHARED_MEM_RECV_BUFFER_OFFSET + (packet_index * PACKET_INFO_SIZE) as usize; + let packet_info = + &shared_mem[packet_info_offset..packet_info_offset + PACKET_INFO_SIZE]; let offset_to_data_buffer = u32::from_ne_bytes([ packet_info[0], @@ -261,14 +291,17 @@ impl IrUser { packet_info[7], ]) as usize; - let packet_info_section_size = user_state.recv_packet_count * 8; + // Find the packet data. The packet data may wrap around the buffer end, so + // `packet_data` is a function from packet byte offset to value. + let packet_info_section_size = user_state.recv_packet_count * PACKET_INFO_SIZE; + let header_size = SHARED_MEM_RECV_BUFFER_OFFSET + packet_info_section_size; + let data_buffer_size = user_state.recv_buffer_size - packet_info_section_size; let packet_data = |idx| -> u8 { - let header_size = 0x20 + packet_info_section_size; let data_buffer_offset = offset_to_data_buffer + idx; - let data_buffer_size = user_state.recv_buffer_size - packet_info_section_size; shared_mem[header_size + data_buffer_offset % data_buffer_size] }; + // Find out how long the payload is (payload length is variable-length encoded) let (payload_length, payload_offset) = if packet_data(2) & 0x40 != 0 { // Big payload ( @@ -280,8 +313,10 @@ impl IrUser { ((packet_data(2) & 0x3F) as usize, 3) }; + // Check our payload length math against what the packet info contains assert_eq!(data_length, payload_offset + payload_length + 1); + // IR packets start with a magic number, so double check it let magic_number = packet_data(0); assert_eq!(magic_number, 0xA5); @@ -298,6 +333,7 @@ impl IrUser { .collect() } + /// Internal helper for calling ir:USER service methods. fn send_service_request( &self, mut request: Vec, @@ -331,6 +367,15 @@ impl IrUser { } } +// Internal helper for rounding up a value to a multiple of another value. +fn round_up(value: usize, multiple: usize) -> usize { + if value % multiple != 0 { + (value / multiple) * multiple + multiple + } else { + (value / multiple) * multiple + } +} + struct InitializeIrnopSharedParams { ir_user_handle: Handle, shared_memory_len: u32, @@ -342,6 +387,7 @@ struct InitializeIrnopSharedParams { shared_memory_handle: Handle, } +/// Internal helper for initializing the ir:USER service unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate::Result<()> { let cmd_buffer = &mut *(slice_from_raw_parts_mut(ctru_sys::getThreadCommandBuffer(), 9)); cmd_buffer[0] = INITIALIZE_IRNOP_SHARED_COMMAND_HEADER; @@ -363,6 +409,8 @@ unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate: Ok(()) } +/// An enum which represents the different IR devices the 3DS can connect to via +/// the ir:USER service. pub enum IrDeviceId { CirclePadPro, // Pretty sure no other IDs are recognized, but just in case @@ -370,6 +418,7 @@ pub enum IrDeviceId { } impl IrDeviceId { + /// Get the ID of the device. pub fn get_id(&self) -> u32 { match *self { IrDeviceId::CirclePadPro => 1, @@ -378,6 +427,7 @@ impl IrDeviceId { } } +/// This struct holds a parsed copy of the ir:USER service status (from shared memory). #[derive(Debug)] pub struct IrUserStatusInfo { pub recv_err_result: ctru_sys::Result, @@ -392,6 +442,7 @@ pub struct IrUserStatusInfo { pub unknown_field_3: u8, } +/// A packet of data sent/received to/from the IR device. #[derive(Debug)] pub struct IrUserPacket { pub magic_number: u8, @@ -401,6 +452,7 @@ pub struct IrUserPacket { pub checksum: u8, } +/// Circle Pad Pro response packet holding the current device input signals and status. #[derive(Debug)] pub struct CirclePadProInputResponse { pub c_stick_x: u16, @@ -424,7 +476,7 @@ impl TryFrom<&IrUserPacket> for CirclePadProInputResponse { } let response_id = packet.payload[0]; - if response_id != 0x10 { + if response_id != CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID { return Err(format!( "Invalid response ID (expected 0x10, got {:#x}", packet.payload[0] From 9aa866d35f7f0ed9f36630dd73a792cc7f590f45 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Fri, 13 Jan 2023 21:09:10 -0800 Subject: [PATCH 09/42] Fix some type issues in ir_user service --- ctru-rs/src/services/ir_user.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 303490a..adbc690 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -1,6 +1,5 @@ use crate::error::ResultCode; use crate::services::ServiceReference; -use crate::Error; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use std::alloc::Layout; use std::cmp::max; @@ -42,7 +41,7 @@ const SHARED_MEM_INFO_SECTIONS_SIZE: usize = 0x30; const SHARED_MEM_RECV_BUFFER_OFFSET: usize = 0x20; const PAGE_SIZE: usize = 0x1000; const IR_BITRATE: u32 = 4; -const PACKET_INFO_SIZE: u32 = 8; +const PACKET_INFO_SIZE: usize = 8; const CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID: u8 = 0x10; impl IrUser { @@ -269,12 +268,12 @@ impl IrUser { ]); // Parse the packets - (0..valid_packet_count) + (0..valid_packet_count as usize) .map(|i| { // Get the packet info - let packet_index = (i + start_index) % user_state.recv_packet_count as u32; + let packet_index = (i + start_index as usize) % user_state.recv_packet_count; let packet_info_offset = - SHARED_MEM_RECV_BUFFER_OFFSET + (packet_index * PACKET_INFO_SIZE) as usize; + SHARED_MEM_RECV_BUFFER_OFFSET + (packet_index * PACKET_INFO_SIZE); let packet_info = &shared_mem[packet_info_offset..packet_info_offset + PACKET_INFO_SIZE]; From d8593088c2f12922eab9d0bfa986f5e8832c5636 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Fri, 13 Jan 2023 21:09:23 -0800 Subject: [PATCH 10/42] Refactor the ir-user example --- ctru-rs/examples/ir-user.rs | 301 ++++++++++++++++++++---------------- 1 file changed, 172 insertions(+), 129 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 2b04260..252dbf7 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -2,6 +2,7 @@ use ctru::prelude::*; use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; +use ctru_sys::Handle; use std::io::Write; use std::time::Duration; @@ -20,36 +21,13 @@ fn main() { let top_console = Console::init(gfx.top_screen.borrow_mut()); let bottom_console = Console::init(gfx.bottom_screen.borrow_mut()); - println!("Welcome to the ir:USER / Circle Pad Pro Demo"); - - println!("Starting up ir:USER service"); - let ir_user = IrUser::init( - PACKET_BUFFER_SIZE, - PACKET_COUNT, - PACKET_BUFFER_SIZE, - PACKET_COUNT, - ) - .expect("Couldn't initialize ir:USER service"); - println!("ir:USER service initialized\nPress A to connect to the CPP"); - - let print_status_info = || { - top_console.select(); - top_console.clear(); - println!("{:#x?}", ir_user.get_status_info()); - bottom_console.select(); - }; + let demo = CirclePadProDemo::new(top_console, bottom_console); + demo.print_status_info(); - // Get event handles - let conn_status_event = ir_user - .get_connection_status_event() - .expect("Couldn't get ir:USER connection status event"); - let recv_event = ir_user - .get_recv_event() - .expect("Couldn't get ir:USER recv event"); - print_status_info(); + println!("Press A to connect to the CPP"); let mut is_connected = false; - 'main_loop: while apt.main_loop() { + while apt.main_loop() { hid.scan_input(); // Check if we need to exit @@ -58,132 +36,197 @@ fn main() { } // Check if we've received a packet from the circle pad pro - let packet_received = IrUser::wait_for_event(recv_event, Duration::ZERO).is_ok(); + let packet_received = + IrUser::wait_for_event(demo.receive_packet_event, Duration::ZERO).is_ok(); if packet_received { - handle_packet(&ir_user, &top_console, &bottom_console); + demo.handle_packets(); } // Check if we should start the connection if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected { println!("Attempting to connect to the CPP"); - // Connection loop - loop { - hid.scan_input(); - if hid.keys_held().contains(KeyPad::KEY_START) { - break 'main_loop; - } + match demo.connect_to_cpp(&hid) { + ConnectionResult::Connected => is_connected = true, + ConnectionResult::Canceled => break, + } + } - // Start the connection process - ir_user - .require_connection(IrDeviceId::CirclePadPro) - .expect("Couldn't initialize circle pad pro connection"); - - // Wait for the connection to establish - if let Err(e) = - IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) - { - if !e.is_timeout() { - panic!("Couldn't initialize circle pad pro connection: {e}"); - } - } + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} - print_status_info(); - if ir_user.get_status_info().connection_status == 2 { - println!("Connected!"); - break; - } +struct CirclePadProDemo<'screen> { + top_console: Console<'screen>, + bottom_console: Console<'screen>, + ir_user: IrUser, + connection_status_event: Handle, + receive_packet_event: Handle, +} - // If not connected (ex. timeout), disconnect so we can retry - ir_user - .disconnect() - .expect("Failed to disconnect circle pad pro connection"); - - // Wait for the disconnect to go through - if let Err(e) = - IrUser::wait_for_event(conn_status_event, Duration::from_millis(100)) - { - if !e.is_timeout() { - panic!("Couldn't initialize circle pad pro connection: {e}"); - } - } +enum ConnectionResult { + Connected, + Canceled, +} + +impl<'screen> CirclePadProDemo<'screen> { + fn new(top_console: Console<'screen>, bottom_console: Console<'screen>) -> Self { + bottom_console.select(); + println!("Welcome to the ir:USER / Circle Pad Pro Demo"); + + println!("Starting up ir:USER service"); + let ir_user = IrUser::init( + PACKET_BUFFER_SIZE, + PACKET_COUNT, + PACKET_BUFFER_SIZE, + PACKET_COUNT, + ) + .expect("Couldn't initialize ir:USER service"); + println!("ir:USER service initialized"); + + // Get event handles + let connection_status_event = ir_user + .get_connection_status_event() + .expect("Couldn't get ir:USER connection status event"); + let receive_packet_event = ir_user + .get_recv_event() + .expect("Couldn't get ir:USER recv event"); + + Self { + top_console, + bottom_console, + ir_user, + connection_status_event, + receive_packet_event, + } + } + + fn print_status_info(&self) { + self.top_console.select(); + self.top_console.clear(); + println!("{:#x?}", self.ir_user.get_status_info()); + self.bottom_console.select(); + } + + fn connect_to_cpp(&self, hid: &Hid) -> ConnectionResult { + // Connection loop + loop { + hid.scan_input(); + if hid.keys_held().contains(KeyPad::KEY_START) { + return ConnectionResult::Canceled; } - // Sending first packet retry loop - loop { - hid.scan_input(); - if hid.keys_held().contains(KeyPad::KEY_START) { - break 'main_loop; + // Start the connection process + self.ir_user + .require_connection(IrDeviceId::CirclePadPro) + .expect("Couldn't initialize circle pad pro connection"); + + // Wait for the connection to establish + if let Err(e) = + IrUser::wait_for_event(self.connection_status_event, Duration::from_millis(100)) + { + if !e.is_timeout() { + panic!("Couldn't initialize circle pad pro connection: {e}"); } + } - // Send a request for input to the CPP - if let Err(e) = ir_user.request_input_polling(CPP_CONNECTION_POLLING_PERIOD_MS) { - println!("Error: {e:?}"); + self.print_status_info(); + if self.ir_user.get_status_info().connection_status == 2 { + println!("Connected!"); + break; + } + + // If not connected (ex. timeout), disconnect so we can retry + self.ir_user + .disconnect() + .expect("Failed to disconnect circle pad pro connection"); + + // Wait for the disconnect to go through + if let Err(e) = + IrUser::wait_for_event(self.connection_status_event, Duration::from_millis(100)) + { + if !e.is_timeout() { + panic!("Couldn't initialize circle pad pro connection: {e}"); } - print_status_info(); + } + } - // Wait for the response - let recv_event_result = - IrUser::wait_for_event(recv_event, Duration::from_millis(100)); - print_status_info(); + // Sending first packet retry loop + loop { + hid.scan_input(); + if hid.keys_held().contains(KeyPad::KEY_START) { + return ConnectionResult::Canceled; + } - if recv_event_result.is_ok() { - println!("Got first packet from CPP"); - handle_packet(&ir_user, &top_console, &bottom_console); - break; - } + // Send a request for input to the CPP + if let Err(e) = self + .ir_user + .request_input_polling(CPP_CONNECTION_POLLING_PERIOD_MS) + { + println!("Error: {e:?}"); + } + self.print_status_info(); + + // Wait for the response + let recv_event_result = + IrUser::wait_for_event(self.receive_packet_event, Duration::from_millis(100)); + self.print_status_info(); - // We didn't get a response in time, so loop and retry + if recv_event_result.is_ok() { + println!("Got first packet from CPP"); + self.handle_packets(); + break; } - is_connected = true; + // We didn't get a response in time, so loop and retry } - gfx.flush_buffers(); - gfx.swap_buffers(); - gfx.wait_for_vblank(); + ConnectionResult::Connected } -} -fn handle_packet(ir_user: &IrUser, top_console: &Console, bottom_console: &Console) { - // Use a buffer to avoid flickering the screen (write all output at once) - let mut output_buffer = Vec::with_capacity(0x1000); - - writeln!(&mut output_buffer, "{:x?}", ir_user.get_status_info()).unwrap(); - - ir_user.process_shared_memory(|ir_mem| { - writeln!(&mut output_buffer, "\nReceiveBufferInfo:").unwrap(); - write_buffer_as_hex(&ir_mem[0x10..0x20], &mut output_buffer); - - writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap(); - write_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE], &mut output_buffer); - writeln!(&mut output_buffer).unwrap(); - }); - - let packets = ir_user.get_packets(); - let packet_count = packets.len(); - writeln!(&mut output_buffer, "\nPacket count: {packet_count}").unwrap(); - let last_packet = packets.last().unwrap(); - writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap(); - - let cpp_response = CirclePadProInputResponse::try_from(last_packet) - .expect("Failed to parse CPP response from IR packet"); - writeln!(&mut output_buffer, "\n{cpp_response:#02x?}").unwrap(); - - // Write output to top screen - top_console.select(); - top_console.clear(); - std::io::stdout().write_all(&output_buffer).unwrap(); - bottom_console.select(); - - // Done handling the packet, release it - ir_user - .release_received_data(packet_count as u32) - .expect("Failed to release ir:USER packet"); - - // Remind the CPP that we're still listening - if let Err(e) = ir_user.request_input_polling(CPP_POLLING_PERIOD_MS) { - println!("Error: {e:?}"); + fn handle_packets(&self) { + let packets = self.ir_user.get_packets(); + let packet_count = packets.len(); + let Some(last_packet) = packets.last() else { return }; + + // Use a buffer to avoid flickering the screen (write all output at once) + let mut output_buffer = Vec::with_capacity(0x1000); + + writeln!(&mut output_buffer, "{:x?}", self.ir_user.get_status_info()).unwrap(); + + self.ir_user.process_shared_memory(|ir_mem| { + writeln!(&mut output_buffer, "\nReceiveBufferInfo:").unwrap(); + write_buffer_as_hex(&ir_mem[0x10..0x20], &mut output_buffer); + + writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap(); + write_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE], &mut output_buffer); + writeln!(&mut output_buffer).unwrap(); + }); + + writeln!(&mut output_buffer, "\nPacket count: {packet_count}").unwrap(); + writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap(); + + let cpp_response = CirclePadProInputResponse::try_from(last_packet) + .expect("Failed to parse CPP response from IR packet"); + writeln!(&mut output_buffer, "\n{cpp_response:#02x?}").unwrap(); + + // Write output to top screen + self.top_console.select(); + self.top_console.clear(); + std::io::stdout().write_all(&output_buffer).unwrap(); + self.bottom_console.select(); + + // Done handling the packets, release them + self.ir_user + .release_received_data(packet_count as u32) + .expect("Failed to release ir:USER packet"); + + // Remind the CPP that we're still listening + if let Err(e) = self.ir_user.request_input_polling(CPP_POLLING_PERIOD_MS) { + println!("Error: {e:?}"); + } } } From da348b2dcc9dd325f7ee3a5c243c3dbc7814e2a2 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Fri, 13 Jan 2023 21:40:20 -0800 Subject: [PATCH 11/42] Update "known good" nightly version in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ccd74a..102c704 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: matrix: toolchain: # Run against a "known good" nightly - - nightly-2022-07-18 + - nightly-2023-01-13 # Check for breakage on latest nightly - nightly # But if latest nightly fails, allow the workflow to continue From 6229905df2d61b0088e25f947b4176aa1ab2814e Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Sat, 28 Jan 2023 14:05:01 -0800 Subject: [PATCH 12/42] Add a srv module and move wait_for_event to it --- ctru-rs/examples/ir-user.rs | 9 +++++---- ctru-rs/src/services/ir_user.rs | 13 ------------- ctru-rs/src/services/mod.rs | 1 + ctru-rs/src/services/srv.rs | 20 ++++++++++++++++++++ 4 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 ctru-rs/src/services/srv.rs diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 252dbf7..24a9013 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -2,6 +2,7 @@ use ctru::prelude::*; use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; +use ctru::services::srv; use ctru_sys::Handle; use std::io::Write; use std::time::Duration; @@ -37,7 +38,7 @@ fn main() { // Check if we've received a packet from the circle pad pro let packet_received = - IrUser::wait_for_event(demo.receive_packet_event, Duration::ZERO).is_ok(); + srv::wait_for_event(demo.receive_packet_event, Duration::ZERO).is_ok(); if packet_received { demo.handle_packets(); } @@ -125,7 +126,7 @@ impl<'screen> CirclePadProDemo<'screen> { // Wait for the connection to establish if let Err(e) = - IrUser::wait_for_event(self.connection_status_event, Duration::from_millis(100)) + srv::wait_for_event(self.connection_status_event, Duration::from_millis(100)) { if !e.is_timeout() { panic!("Couldn't initialize circle pad pro connection: {e}"); @@ -145,7 +146,7 @@ impl<'screen> CirclePadProDemo<'screen> { // Wait for the disconnect to go through if let Err(e) = - IrUser::wait_for_event(self.connection_status_event, Duration::from_millis(100)) + srv::wait_for_event(self.connection_status_event, Duration::from_millis(100)) { if !e.is_timeout() { panic!("Couldn't initialize circle pad pro connection: {e}"); @@ -171,7 +172,7 @@ impl<'screen> CirclePadProDemo<'screen> { // Wait for the response let recv_event_result = - IrUser::wait_for_event(self.receive_packet_event, Duration::from_millis(100)); + srv::wait_for_event(self.receive_packet_event, Duration::from_millis(100)); self.print_status_info(); if recv_event_result.is_ok() { diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index adbc690..df35e99 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -6,7 +6,6 @@ use std::cmp::max; use std::ffi::CString; use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use std::sync::Mutex; -use std::time::Duration; static IR_USER_ACTIVE: Mutex = Mutex::new(0); static IR_USER_STATE: Mutex> = Mutex::new(None); @@ -172,18 +171,6 @@ impl IrUser { Ok(recv_event) } - /// Wait for an event to fire. If the timeout is reached, an error is returned. You can use - /// [`Error::is_timeout`] to check if the error is due to a timeout. - pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::svcWaitSynchronization( - event, - timeout.as_nanos() as i64, - ))?; - } - Ok(()) - } - /// Circle Pad Pro specific request. /// /// This will send a packet to the CPP requesting it to send back packets diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 97e1a0f..62bf822 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -8,6 +8,7 @@ pub mod ir_user; pub mod ps; mod reference; pub mod soc; +pub mod srv; pub mod sslc; pub use self::apt::Apt; diff --git a/ctru-rs/src/services/srv.rs b/ctru-rs/src/services/srv.rs new file mode 100644 index 0000000..00ca358 --- /dev/null +++ b/ctru-rs/src/services/srv.rs @@ -0,0 +1,20 @@ +//! Service API +//! +//! Not all APIs are wrapped in this module, since a lot are fundamentally unsafe. +//! Most APIs should be used directly from `ctru-sys`. + +use crate::error::ResultCode; +use ctru_sys::Handle; +use std::time::Duration; + +/// Wait for an event to fire. If the timeout is reached, an error is returned. You can use +/// [`Error::is_timeout`] to check if the error is due to a timeout. +pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::svcWaitSynchronization( + event, + timeout.as_nanos() as i64, + ))?; + } + Ok(()) +} From 7f63384aa6eb4a9c6f325b3b756022227d156be0 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Sun, 29 Jan 2023 13:46:06 -0800 Subject: [PATCH 13/42] Initialize HID after ir:USER to try and fix New 3DS case --- ctru-rs/examples/ir-user.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 24a9013..934d801 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -17,15 +17,18 @@ const CPP_POLLING_PERIOD_MS: u8 = 0x32; fn main() { ctru::init(); let apt = Apt::init().unwrap(); - let hid = Hid::init().unwrap(); let gfx = Gfx::init().unwrap(); let top_console = Console::init(gfx.top_screen.borrow_mut()); let bottom_console = Console::init(gfx.bottom_screen.borrow_mut()); - let demo = CirclePadProDemo::new(top_console, bottom_console); demo.print_status_info(); - println!("Press A to connect to the CPP"); + // Initialize HID after ir:USER because libctru also initializes ir:rst, + // which is mutually exclusive with ir:USER. Initializing HID before ir:USER + // on New 3DS causes ir:USER to not work. + let hid = Hid::init().unwrap(); + + println!("Press A to connect to the CPP, or Start to exit"); let mut is_connected = false; while apt.main_loop() { From eb8628d9bccfea168df9f1d8636c272a0265fbab Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Sun, 29 Jan 2023 13:54:31 -0800 Subject: [PATCH 14/42] Make wait_for_event a method on Handle --- ctru-rs/examples/ir-user.rs | 23 ++++++++++++++--------- ctru-rs/src/services/srv.rs | 25 ++++++++++++++++--------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 934d801..9a834a0 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -2,7 +2,7 @@ use ctru::prelude::*; use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; -use ctru::services::srv; +use ctru::services::srv::HandleExt; use ctru_sys::Handle; use std::io::Write; use std::time::Duration; @@ -40,8 +40,10 @@ fn main() { } // Check if we've received a packet from the circle pad pro - let packet_received = - srv::wait_for_event(demo.receive_packet_event, Duration::ZERO).is_ok(); + let packet_received = demo + .receive_packet_event + .wait_for_event(Duration::ZERO) + .is_ok(); if packet_received { demo.handle_packets(); } @@ -128,8 +130,9 @@ impl<'screen> CirclePadProDemo<'screen> { .expect("Couldn't initialize circle pad pro connection"); // Wait for the connection to establish - if let Err(e) = - srv::wait_for_event(self.connection_status_event, Duration::from_millis(100)) + if let Err(e) = self + .connection_status_event + .wait_for_event(Duration::from_millis(100)) { if !e.is_timeout() { panic!("Couldn't initialize circle pad pro connection: {e}"); @@ -148,8 +151,9 @@ impl<'screen> CirclePadProDemo<'screen> { .expect("Failed to disconnect circle pad pro connection"); // Wait for the disconnect to go through - if let Err(e) = - srv::wait_for_event(self.connection_status_event, Duration::from_millis(100)) + if let Err(e) = self + .connection_status_event + .wait_for_event(Duration::from_millis(100)) { if !e.is_timeout() { panic!("Couldn't initialize circle pad pro connection: {e}"); @@ -174,8 +178,9 @@ impl<'screen> CirclePadProDemo<'screen> { self.print_status_info(); // Wait for the response - let recv_event_result = - srv::wait_for_event(self.receive_packet_event, Duration::from_millis(100)); + let recv_event_result = self + .receive_packet_event + .wait_for_event(Duration::from_millis(100)); self.print_status_info(); if recv_event_result.is_ok() { diff --git a/ctru-rs/src/services/srv.rs b/ctru-rs/src/services/srv.rs index 00ca358..eeaa723 100644 --- a/ctru-rs/src/services/srv.rs +++ b/ctru-rs/src/services/srv.rs @@ -7,14 +7,21 @@ use crate::error::ResultCode; use ctru_sys::Handle; use std::time::Duration; -/// Wait for an event to fire. If the timeout is reached, an error is returned. You can use -/// [`Error::is_timeout`] to check if the error is due to a timeout. -pub fn wait_for_event(event: Handle, timeout: Duration) -> crate::Result<()> { - unsafe { - ResultCode(ctru_sys::svcWaitSynchronization( - event, - timeout.as_nanos() as i64, - ))?; +/// Extension trait for [Handle] +pub trait HandleExt { + /// Wait for an event to fire. If the timeout is reached, an error is returned. You can use + /// [`Error::is_timeout`] to check if the error is due to a timeout. + fn wait_for_event(self, timeout: Duration) -> crate::Result<()>; +} + +impl HandleExt for Handle { + fn wait_for_event(self, timeout: Duration) -> crate::Result<()> { + unsafe { + ResultCode(ctru_sys::svcWaitSynchronization( + self, + timeout.as_nanos() as i64, + ))?; + } + Ok(()) } - Ok(()) } From 86b1820dbb2891e9f4608b4575c5e0e4704d46a9 Mon Sep 17 00:00:00 2001 From: Mark Drobnak Date: Sun, 29 Jan 2023 15:03:09 -0800 Subject: [PATCH 15/42] Derive Default for CirclePadProInputResponse --- ctru-rs/src/services/ir_user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index df35e99..788c02e 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -439,7 +439,7 @@ pub struct IrUserPacket { } /// Circle Pad Pro response packet holding the current device input signals and status. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct CirclePadProInputResponse { pub c_stick_x: u16, pub c_stick_y: u16, From 0659df38434ae4f3b801e2403b3b8edf40ff1b14 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Fri, 5 May 2023 05:43:47 -0700 Subject: [PATCH 16/42] Update ir-user example after merge --- ctru-rs/examples/ir-user.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 9a834a0..bbe2c08 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -15,18 +15,18 @@ const CPP_CONNECTION_POLLING_PERIOD_MS: u8 = 0x08; const CPP_POLLING_PERIOD_MS: u8 = 0x32; fn main() { - ctru::init(); - let apt = Apt::init().unwrap(); - let gfx = Gfx::init().unwrap(); - let top_console = Console::init(gfx.top_screen.borrow_mut()); - let bottom_console = Console::init(gfx.bottom_screen.borrow_mut()); + ctru::use_panic_handler(); + let apt = Apt::new().unwrap(); + let gfx = Gfx::new().unwrap(); + let top_console = Console::new(gfx.top_screen.borrow_mut()); + let bottom_console = Console::new(gfx.bottom_screen.borrow_mut()); let demo = CirclePadProDemo::new(top_console, bottom_console); demo.print_status_info(); // Initialize HID after ir:USER because libctru also initializes ir:rst, // which is mutually exclusive with ir:USER. Initializing HID before ir:USER // on New 3DS causes ir:USER to not work. - let hid = Hid::init().unwrap(); + let mut hid = Hid::new().unwrap(); println!("Press A to connect to the CPP, or Start to exit"); @@ -35,7 +35,7 @@ fn main() { hid.scan_input(); // Check if we need to exit - if hid.keys_held().contains(KeyPad::KEY_START) { + if hid.keys_held().contains(KeyPad::START) { break; } @@ -49,17 +49,15 @@ fn main() { } // Check if we should start the connection - if hid.keys_down().contains(KeyPad::KEY_A) && !is_connected { + if hid.keys_down().contains(KeyPad::A) && !is_connected { println!("Attempting to connect to the CPP"); - match demo.connect_to_cpp(&hid) { + match demo.connect_to_cpp(&mut hid) { ConnectionResult::Connected => is_connected = true, ConnectionResult::Canceled => break, } } - gfx.flush_buffers(); - gfx.swap_buffers(); gfx.wait_for_vblank(); } } @@ -116,11 +114,11 @@ impl<'screen> CirclePadProDemo<'screen> { self.bottom_console.select(); } - fn connect_to_cpp(&self, hid: &Hid) -> ConnectionResult { + fn connect_to_cpp(&self, hid: &mut Hid) -> ConnectionResult { // Connection loop loop { hid.scan_input(); - if hid.keys_held().contains(KeyPad::KEY_START) { + if hid.keys_held().contains(KeyPad::START) { return ConnectionResult::Canceled; } @@ -164,7 +162,7 @@ impl<'screen> CirclePadProDemo<'screen> { // Sending first packet retry loop loop { hid.scan_input(); - if hid.keys_held().contains(KeyPad::KEY_START) { + if hid.keys_held().contains(KeyPad::START) { return ConnectionResult::Canceled; } From 7e99463048c8ffc94928eb82dd77460b77c45211 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 22 Nov 2023 21:00:34 +0100 Subject: [PATCH 17/42] Remove panic handler --- ctru-rs/examples/ir-user.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index bbe2c08..075569b 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -15,7 +15,6 @@ const CPP_CONNECTION_POLLING_PERIOD_MS: u8 = 0x08; const CPP_POLLING_PERIOD_MS: u8 = 0x32; fn main() { - ctru::use_panic_handler(); let apt = Apt::new().unwrap(); let gfx = Gfx::new().unwrap(); let top_console = Console::new(gfx.top_screen.borrow_mut()); From d5ae6419aa04a8609ca0095322e9767fda0743ee Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 22 Nov 2023 21:02:35 +0100 Subject: [PATCH 18/42] Fmt --- ctru-rs/examples/ir-user.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 075569b..a8141d0 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -195,7 +195,9 @@ impl<'screen> CirclePadProDemo<'screen> { fn handle_packets(&self) { let packets = self.ir_user.get_packets(); let packet_count = packets.len(); - let Some(last_packet) = packets.last() else { return }; + let Some(last_packet) = packets.last() else { + return; + }; // Use a buffer to avoid flickering the screen (write all output at once) let mut output_buffer = Vec::with_capacity(0x1000); From 47f1f78f509b6aa87741dd06e961642a12f0f55c Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Wed, 22 Nov 2023 21:09:12 +0100 Subject: [PATCH 19/42] Update PR to build with the latest versions --- ctru-rs/src/services/ir_user.rs | 3 +-- ctru-sys/src/lib.rs | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 788c02e..7992de9 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -7,7 +7,7 @@ use std::ffi::CString; use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use std::sync::Mutex; -static IR_USER_ACTIVE: Mutex = Mutex::new(0); +static IR_USER_ACTIVE: Mutex<()> = Mutex::new(()); static IR_USER_STATE: Mutex> = Mutex::new(None); /// The "ir:USER" service. This service is used to talk to IR devices such as @@ -55,7 +55,6 @@ impl IrUser { ) -> crate::Result { let service_reference = ServiceReference::new( &IR_USER_ACTIVE, - false, || unsafe { // Get the ir:USER service handle let mut service_handle = Handle::default(); diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index f091001..32252e6 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -4,8 +4,6 @@ #![allow(non_snake_case)] #![allow(clippy::all)] -use core::arch::asm; - pub mod result; pub use result::*; @@ -17,16 +15,6 @@ pub unsafe fn errno() -> s32 { *__errno() } -pub unsafe fn getThreadLocalStorage() -> *mut libc::c_void { - let return_value: *mut libc::c_void; - asm!("mrc p15, 0, {}, c13, c0, 3", out(reg) return_value); - return_value -} - -pub unsafe fn getThreadCommandBuffer() -> *mut u32 { - (getThreadLocalStorage() as *mut u8).add(0x80) as *mut u32 -} - // TODO: not sure if there's a better way to do this, but I have gotten myself // with this a couple times so having the hint seems nice to have. #[cfg(test)] From e09ad16003345dc1cb07756942a8651a147606de Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 14:24:47 -0500 Subject: [PATCH 20/42] Clarify a comment --- ctru-rs/src/services/ir_user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 7992de9..bb451d0 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -65,7 +65,7 @@ impl IrUser { ))?; // Calculate the shared memory size. - // Shared memory length must be page aligned. + // Shared memory length must be a multiple of the page size. let minimum_shared_memory_len = SHARED_MEM_INFO_SECTIONS_SIZE + recv_buffer_size + send_buffer_size; let shared_memory_len = round_up(minimum_shared_memory_len, PAGE_SIZE); From 87f33f144ea4a26f517c1fbb9c65de7257949fcc Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 14:44:49 -0500 Subject: [PATCH 21/42] Improve some service start/stop error handling --- ctru-rs/src/error.rs | 6 +++++- ctru-rs/src/services/ir_user.rs | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 87170ad..186de3d 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -94,6 +94,8 @@ pub enum Error { /// Size of the requested data (in bytes). wanted: usize, }, + /// An error that doesn't fit into the other categories. + Other(String), } impl Error { @@ -153,6 +155,7 @@ impl fmt::Debug for Error { .field("provided", provided) .field("wanted", wanted) .finish(), + Self::Other(err) => f.debug_tuple("Other").field(err).finish(), } } } @@ -175,7 +178,8 @@ impl fmt::Display for Error { Self::OutputAlreadyRedirected => { write!(f, "output streams are already redirected to 3dslink") } - Self::BufferTooShort{provided, wanted} => write!(f, "the provided buffer's length is too short (length = {provided}) to hold the wanted data (size = {wanted})") + Self::BufferTooShort{provided, wanted} => write!(f, "the provided buffer's length is too short (length = {provided}) to hold the wanted data (size = {wanted})"), + Self::Other(err) => write!(f, "{err}"), } } } diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index bb451d0..0860046 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -1,5 +1,6 @@ use crate::error::ResultCode; use crate::services::ServiceReference; +use crate::Error; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use std::alloc::Layout; use std::cmp::max; @@ -107,14 +108,22 @@ impl IrUser { recv_buffer_size, recv_packet_count, }; - *IR_USER_STATE.lock().unwrap() = Some(user_state); + let mut ir_user_state = IR_USER_STATE + .lock() + .map_err(|e| Error::Other(format!("Failed to write to IR_USER_STATE: {e}")))?; + *ir_user_state = Some(user_state); Ok(()) }, || { // Remove our service state from the global location - let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); - let shared_mem = shared_mem_guard.take().unwrap(); + let mut shared_mem_guard = IR_USER_STATE + .lock() + .expect("Failed to write to IR_USER_STATE"); + let Some(shared_mem) = shared_mem_guard.take() else { + // If we don't have any state, then we don't need to clean up. + return; + }; (move || unsafe { // Close service and memory handles From b4fa7bd0cb83cf48945beca4b27ba312d12c50d6 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 14:51:40 -0500 Subject: [PATCH 22/42] Improve packet parser error handling --- ctru-rs/examples/ir-user.rs | 5 ++++- ctru-rs/src/services/ir_user.rs | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index a8141d0..37af6ee 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -193,7 +193,10 @@ impl<'screen> CirclePadProDemo<'screen> { } fn handle_packets(&self) { - let packets = self.ir_user.get_packets(); + let packets = self + .ir_user + .get_packets() + .expect("Packets should be well formed"); let packet_count = packets.len(); let Some(last_packet) = packets.last() else { return; diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 0860046..2709c13 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -5,6 +5,7 @@ use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use std::alloc::Layout; use std::cmp::max; use std::ffi::CString; +use std::fmt::format; use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use std::sync::Mutex; @@ -243,7 +244,7 @@ impl IrUser { } /// Read and parse the current packets received from the IR device. - pub fn get_packets(&self) -> Vec { + pub fn get_packets(&self) -> Result, String> { let shared_mem_guard = IR_USER_STATE.lock().unwrap(); let user_state = shared_mem_guard.as_ref().unwrap(); let shared_mem = user_state.shared_memory; @@ -312,9 +313,13 @@ impl IrUser { // IR packets start with a magic number, so double check it let magic_number = packet_data(0); - assert_eq!(magic_number, 0xA5); + if magic_number != 0xA5 { + return Err(format!( + "Invalid magic number in packet: {magic_number:#x}, expected 0xA5" + )); + } - IrUserPacket { + Ok(IrUserPacket { magic_number: packet_data(0), destination_network_id: packet_data(1), payload_length, @@ -322,7 +327,7 @@ impl IrUser { .map(packet_data) .collect(), checksum: packet_data(payload_offset + payload_length), - } + }) }) .collect() } From dd9f84f47d8f6a6e91b860f678ea4e339e487efd Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 15:05:16 -0500 Subject: [PATCH 23/42] Improve safety of send_service_request --- ctru-rs/src/services/ir_user.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 2709c13..e1a9731 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -342,13 +342,10 @@ impl IrUser { let shared_mem = shared_mem_guard.as_mut().unwrap(); // Set up the request - let cmd_buffer = unsafe { - &mut *(slice_from_raw_parts_mut( - ctru_sys::getThreadCommandBuffer(), - max(request.len(), expected_response_len), - )) - }; - cmd_buffer[0..request.len()].copy_from_slice(&request); + let thread_command_buffer = unsafe { ctru_sys::getThreadCommandBuffer() }; + unsafe { + std::ptr::copy(request.as_ptr(), thread_command_buffer, request.len()); + } // Send the request unsafe { @@ -356,11 +353,19 @@ impl IrUser { } // Handle the result returned by the service - ResultCode(cmd_buffer[1] as ctru_sys::Result)?; + let result = unsafe { std::ptr::read(thread_command_buffer.add(1)) }; + ResultCode(result as ctru_sys::Result)?; // Copy back the response request.clear(); - request.extend_from_slice(&cmd_buffer[0..expected_response_len]); + request.resize(expected_response_len, 0); + unsafe { + std::ptr::copy( + thread_command_buffer, + request.as_mut_ptr(), + expected_response_len, + ); + } Ok(request) } From 051ab2614d82c4f83a929605c2aeede0726fe801 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 15:15:04 -0500 Subject: [PATCH 24/42] Add enum for connection status --- ctru-rs/examples/ir-user.rs | 4 ++-- ctru-rs/src/services/ir_user.rs | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index 37af6ee..ee1d426 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,7 +1,7 @@ //! A demo of using the ir:USER service to connect to the Circle Pad Pro. use ctru::prelude::*; -use ctru::services::ir_user::{CirclePadProInputResponse, IrDeviceId, IrUser}; +use ctru::services::ir_user::{CirclePadProInputResponse, ConnectionStatus, IrDeviceId, IrUser}; use ctru::services::srv::HandleExt; use ctru_sys::Handle; use std::io::Write; @@ -137,7 +137,7 @@ impl<'screen> CirclePadProDemo<'screen> { } self.print_status_info(); - if self.ir_user.get_status_info().connection_status == 2 { + if self.ir_user.get_status_info().connection_status == ConnectionStatus::Connected { println!("Connected!"); break; } diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index e1a9731..3d58f8c 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -232,7 +232,12 @@ impl IrUser { shared_mem[6], shared_mem[7], ]), - connection_status: shared_mem[8], + connection_status: match shared_mem[8] { + 0 => ConnectionStatus::Disconnected, + 1 => ConnectionStatus::Connecting, + 2 => ConnectionStatus::Connected, + n => ConnectionStatus::Unknown(n), + }, trying_to_connect_status: shared_mem[9], connection_role: shared_mem[10], machine_id: shared_mem[11], @@ -436,7 +441,7 @@ impl IrDeviceId { pub struct IrUserStatusInfo { pub recv_err_result: ctru_sys::Result, pub send_err_result: ctru_sys::Result, - pub connection_status: u8, + pub connection_status: ConnectionStatus, pub trying_to_connect_status: u8, pub connection_role: u8, pub machine_id: u8, @@ -446,6 +451,20 @@ pub struct IrUserStatusInfo { pub unknown_field_3: u8, } +/// Connection status values for [`IrUserStatusInfo`]. +#[repr(u8)] +#[derive(Debug, PartialEq, Eq)] +pub enum ConnectionStatus { + /// Device is not connected + Disconnected = 0, + /// Waiting for device to connect + Connecting = 1, + /// Device is connected + Connected = 2, + /// Unknown connection status + Unknown(u8), +} + /// A packet of data sent/received to/from the IR device. #[derive(Debug)] pub struct IrUserPacket { From 09361e50da2e42732a7963f4a6a786c7c4dae0ad Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 23 Dec 2023 22:24:04 -0500 Subject: [PATCH 25/42] Try using double buffering to reduce flickering (actually increased :/) Required refactoring Console to take screen type as a generic. Not all Screen types support swapping or flushing. --- ctru-rs/examples/ir-user.rs | 75 ++++++++++++++++++++++--------------- ctru-rs/src/console.rs | 15 +++++--- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user.rs index ee1d426..acd4b1d 100644 --- a/ctru-rs/examples/ir-user.rs +++ b/ctru-rs/examples/ir-user.rs @@ -1,10 +1,10 @@ //! A demo of using the ir:USER service to connect to the Circle Pad Pro. use ctru::prelude::*; +use ctru::services::gfx::{BottomScreen, Flush, Swap, TopScreen}; use ctru::services::ir_user::{CirclePadProInputResponse, ConnectionStatus, IrDeviceId, IrUser}; use ctru::services::srv::HandleExt; use ctru_sys::Handle; -use std::io::Write; use std::time::Duration; const PACKET_INFO_SIZE: usize = 8; @@ -19,7 +19,7 @@ fn main() { let gfx = Gfx::new().unwrap(); let top_console = Console::new(gfx.top_screen.borrow_mut()); let bottom_console = Console::new(gfx.bottom_screen.borrow_mut()); - let demo = CirclePadProDemo::new(top_console, bottom_console); + let mut demo = CirclePadProDemo::new(top_console, bottom_console); demo.print_status_info(); // Initialize HID after ir:USER because libctru also initializes ir:rst, @@ -62,8 +62,8 @@ fn main() { } struct CirclePadProDemo<'screen> { - top_console: Console<'screen>, - bottom_console: Console<'screen>, + top_console: Console<'screen, TopScreen>, + bottom_console: Console<'screen, BottomScreen>, ir_user: IrUser, connection_status_event: Handle, receive_packet_event: Handle, @@ -75,7 +75,17 @@ enum ConnectionResult { } impl<'screen> CirclePadProDemo<'screen> { - fn new(top_console: Console<'screen>, bottom_console: Console<'screen>) -> Self { + fn new( + mut top_console: Console<'screen, TopScreen>, + bottom_console: Console<'screen, BottomScreen>, + ) -> Self { + // Set up double buffering on top screen + top_console.with_screen(|screen| { + screen.set_double_buffering(true); + screen.swap_buffers(); + }); + + // Write messages to bottom screen (not double buffered) bottom_console.select(); println!("Welcome to the ir:USER / Circle Pad Pro Demo"); @@ -106,14 +116,18 @@ impl<'screen> CirclePadProDemo<'screen> { } } - fn print_status_info(&self) { + fn print_status_info(&mut self) { self.top_console.select(); self.top_console.clear(); println!("{:#x?}", self.ir_user.get_status_info()); + self.top_console.with_screen(|screen| { + screen.flush_buffers(); + screen.swap_buffers(); + }); self.bottom_console.select(); } - fn connect_to_cpp(&self, hid: &mut Hid) -> ConnectionResult { + fn connect_to_cpp(&mut self, hid: &mut Hid) -> ConnectionResult { // Connection loop loop { hid.scan_input(); @@ -192,7 +206,7 @@ impl<'screen> CirclePadProDemo<'screen> { ConnectionResult::Connected } - fn handle_packets(&self) { + fn handle_packets(&mut self) { let packets = self .ir_user .get_packets() @@ -201,32 +215,33 @@ impl<'screen> CirclePadProDemo<'screen> { let Some(last_packet) = packets.last() else { return; }; + let status_info = self.ir_user.get_status_info(); + let cpp_response = CirclePadProInputResponse::try_from(last_packet) + .expect("Failed to parse CPP response from IR packet"); - // Use a buffer to avoid flickering the screen (write all output at once) - let mut output_buffer = Vec::with_capacity(0x1000); - - writeln!(&mut output_buffer, "{:x?}", self.ir_user.get_status_info()).unwrap(); + // Write data to top screen + self.top_console.select(); + self.top_console.clear(); + println!("{:x?}", status_info); self.ir_user.process_shared_memory(|ir_mem| { - writeln!(&mut output_buffer, "\nReceiveBufferInfo:").unwrap(); - write_buffer_as_hex(&ir_mem[0x10..0x20], &mut output_buffer); + println!("\nReceiveBufferInfo:"); + print_buffer_as_hex(&ir_mem[0x10..0x20]); - writeln!(&mut output_buffer, "\nReceiveBuffer:").unwrap(); - write_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE], &mut output_buffer); - writeln!(&mut output_buffer).unwrap(); + println!("\nReceiveBuffer:"); + print_buffer_as_hex(&ir_mem[0x20..0x20 + PACKET_BUFFER_SIZE]); + println!(); }); - writeln!(&mut output_buffer, "\nPacket count: {packet_count}").unwrap(); - writeln!(&mut output_buffer, "{last_packet:02x?}").unwrap(); - - let cpp_response = CirclePadProInputResponse::try_from(last_packet) - .expect("Failed to parse CPP response from IR packet"); - writeln!(&mut output_buffer, "\n{cpp_response:#02x?}").unwrap(); + println!("\nPacket count: {packet_count}"); + println!("{last_packet:02x?}"); + println!("\n{cpp_response:#02x?}"); - // Write output to top screen - self.top_console.select(); - self.top_console.clear(); - std::io::stdout().write_all(&output_buffer).unwrap(); + // Flush output and switch back to bottom screen + self.top_console.with_screen(|screen| { + screen.flush_buffers(); + screen.swap_buffers(); + }); self.bottom_console.select(); // Done handling the packets, release them @@ -241,13 +256,13 @@ impl<'screen> CirclePadProDemo<'screen> { } } -fn write_buffer_as_hex(buffer: &[u8], output: &mut Vec) { +fn print_buffer_as_hex(buffer: &[u8]) { let mut counter = 0; for byte in buffer { - write!(output, "{byte:02x} ").unwrap(); + print!("{byte:02x} "); counter += 1; if counter % 16 == 0 { - writeln!(output).unwrap(); + println!(); } } } diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 5a8c772..452c785 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -58,12 +58,12 @@ pub enum Dimension { /// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`). /// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables). #[doc(alias = "PrintConsole")] -pub struct Console<'screen> { +pub struct Console<'screen, S: Screen> { context: Box, - screen: RefMut<'screen, dyn Screen>, + screen: RefMut<'screen, S>, } -impl<'screen> Console<'screen> { +impl<'screen, S: Screen> Console<'screen, S> { /// Initialize a console on the chosen screen. /// /// # Notes @@ -102,7 +102,7 @@ impl<'screen> Console<'screen> { /// # } /// ``` #[doc(alias = "consoleInit")] - pub fn new(screen: RefMut<'screen, dyn Screen>) -> Self { + pub fn new(screen: RefMut<'screen, S>) -> Self { let mut context = Box::::default(); unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; @@ -322,9 +322,14 @@ impl<'screen> Console<'screen> { _ => unreachable!(), } } + + /// Run a function with access to the underlying screen. + pub fn with_screen(&mut self, action: impl FnOnce(&mut RefMut<'_, S>)) { + action(&mut self.screen); + } } -impl Drop for Console<'_> { +impl Drop for Console<'_, S> { fn drop(&mut self) { unsafe { // Safety: We are about to deallocate the PrintConsole data pointed From 08b4c37d0d2a74d0d2f81d8cecc689ceca89b5b2 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sun, 24 Dec 2023 13:12:07 -0500 Subject: [PATCH 26/42] Fixed file-explorer example (Console usage) --- ctru-rs/examples/file-explorer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 3ad37c9..0c2b595 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -6,6 +6,7 @@ use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; +use ctru::services::gfx::TopScreen; use std::fs::DirEntry; use std::os::horizon::fs::MetadataExt; use std::path::{Path, PathBuf}; @@ -26,7 +27,7 @@ struct FileExplorer<'a> { apt: &'a Apt, hid: &'a mut Hid, gfx: &'a Gfx, - console: Console<'a>, + console: Console<'a, TopScreen>, path: PathBuf, entries: Vec, running: bool, From 02126e9e7ff96fe2d5f5fb4a6d3d446af6b4bfce Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sun, 24 Dec 2023 13:13:49 -0500 Subject: [PATCH 27/42] Add missing documentation to ir_user.rs --- ctru-rs/src/services/ir_user.rs | 38 +++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 3d58f8c..67fa17d 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -1,11 +1,21 @@ +//! IR (Infrared) User Service. +//! +//! The ir:USER service allows you to communicate with IR devices such as the Circle Pad Pro. +//! +//! The Circle Pad Pro (CPP) is an accessory for the 3DS which adds a second Circle Pad and extra shoulder buttons. +//! On New 3DS systems, the ir:USER service uses the built-in C-stick and new shoulder buttons to emulate the Circle Pad +//! Pro. Many released games which support the second stick and extra shoulder buttons use this service to communicate +//! so they can support both Old 3DS + CPP and New 3DS. +#![doc(alias = "input")] +#![doc(alias = "controller")] +#![doc(alias = "gamepad")] + use crate::error::ResultCode; use crate::services::ServiceReference; use crate::Error; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use std::alloc::Layout; -use std::cmp::max; use std::ffi::CString; -use std::fmt::format; use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use std::sync::Mutex; @@ -421,7 +431,9 @@ unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate: /// An enum which represents the different IR devices the 3DS can connect to via /// the ir:USER service. pub enum IrDeviceId { + /// Circle Pad Pro CirclePadPro, + /// Other devices // Pretty sure no other IDs are recognized, but just in case Custom(u32), } @@ -439,15 +451,25 @@ impl IrDeviceId { /// This struct holds a parsed copy of the ir:USER service status (from shared memory). #[derive(Debug)] pub struct IrUserStatusInfo { + /// The result of the last receive operation. pub recv_err_result: ctru_sys::Result, + /// The result of the last send operation. pub send_err_result: ctru_sys::Result, + /// The current connection status. pub connection_status: ConnectionStatus, + /// The status of the connection attempt. pub trying_to_connect_status: u8, + /// The role of the device in the connection (value meaning is unknown). pub connection_role: u8, + /// The machine ID of the device. pub machine_id: u8, + /// Unknown field. pub unknown_field_1: u8, + /// The network ID of the connection. pub network_id: u8, + /// Unknown field. pub unknown_field_2: u8, + /// Unknown field. pub unknown_field_3: u8, } @@ -468,22 +490,34 @@ pub enum ConnectionStatus { /// A packet of data sent/received to/from the IR device. #[derive(Debug)] pub struct IrUserPacket { + /// The magic number of the packet. Should always be 0xA5. pub magic_number: u8, + /// The destination network ID. pub destination_network_id: u8, + /// The length of the payload. pub payload_length: usize, + /// The payload data. pub payload: Vec, + /// The checksum of the packet. pub checksum: u8, } /// Circle Pad Pro response packet holding the current device input signals and status. #[derive(Debug, Default)] pub struct CirclePadProInputResponse { + /// The X value of the C-stick. pub c_stick_x: u16, + /// The Y value of the C-stick. pub c_stick_y: u16, + /// The battery level of the Circle Pad Pro. pub battery_level: u8, + /// Whether the ZL button is pressed. pub zl_pressed: bool, + /// Whether the ZR button is pressed. pub zr_pressed: bool, + /// Whether the R button is pressed. pub r_pressed: bool, + /// Unknown field. pub unknown_field: u8, } From ef1c91365546641cb7b03043652f19b475457537 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sun, 24 Dec 2023 13:20:50 -0500 Subject: [PATCH 28/42] Rename ir-user example to ir-user-circle-pad-pro --- ctru-rs/examples/{ir-user.rs => ir-user-circle-pad-pro.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ctru-rs/examples/{ir-user.rs => ir-user-circle-pad-pro.rs} (100%) diff --git a/ctru-rs/examples/ir-user.rs b/ctru-rs/examples/ir-user-circle-pad-pro.rs similarity index 100% rename from ctru-rs/examples/ir-user.rs rename to ctru-rs/examples/ir-user-circle-pad-pro.rs From 1f45da1ebc5f9f3fea994046e97fd284621de6a6 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sun, 24 Dec 2023 13:42:29 -0500 Subject: [PATCH 29/42] Fix double buffering in example via impl Swap and Flush for Console --- ctru-rs/examples/ir-user-circle-pad-pro.rs | 18 +++++---------- ctru-rs/src/console.rs | 26 +++++++++++++++++++++- ctru-rs/src/services/gfx.rs | 4 +++- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/ctru-rs/examples/ir-user-circle-pad-pro.rs b/ctru-rs/examples/ir-user-circle-pad-pro.rs index acd4b1d..10fa58d 100644 --- a/ctru-rs/examples/ir-user-circle-pad-pro.rs +++ b/ctru-rs/examples/ir-user-circle-pad-pro.rs @@ -80,10 +80,8 @@ impl<'screen> CirclePadProDemo<'screen> { bottom_console: Console<'screen, BottomScreen>, ) -> Self { // Set up double buffering on top screen - top_console.with_screen(|screen| { - screen.set_double_buffering(true); - screen.swap_buffers(); - }); + top_console.set_double_buffering(true); + top_console.swap_buffers(); // Write messages to bottom screen (not double buffered) bottom_console.select(); @@ -120,10 +118,8 @@ impl<'screen> CirclePadProDemo<'screen> { self.top_console.select(); self.top_console.clear(); println!("{:#x?}", self.ir_user.get_status_info()); - self.top_console.with_screen(|screen| { - screen.flush_buffers(); - screen.swap_buffers(); - }); + self.top_console.flush_buffers(); + self.top_console.swap_buffers(); self.bottom_console.select(); } @@ -238,10 +234,8 @@ impl<'screen> CirclePadProDemo<'screen> { println!("\n{cpp_response:#02x?}"); // Flush output and switch back to bottom screen - self.top_console.with_screen(|screen| { - screen.flush_buffers(); - screen.swap_buffers(); - }); + self.top_console.flush_buffers(); + self.top_console.swap_buffers(); self.bottom_console.select(); // Done handling the packets, release them diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 452c785..322072e 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -10,7 +10,7 @@ use std::default::Default; use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole}; -use crate::services::gfx::Screen; +use crate::services::gfx::{Flush, Screen, Swap}; static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) }; @@ -329,6 +329,30 @@ impl<'screen, S: Screen> Console<'screen, S> { } } +impl Swap for Console<'_, S> { + /// Swaps the video buffers. Note: The console's cursor position is not reset, only the framebuffer is changed. + /// + /// Even if double buffering is disabled, "swapping" the buffers has the side effect + /// of committing any configuration changes to the buffers (e.g. [`TopScreen::set_wide_mode()`], + /// [`Screen::set_framebuffer_format()`], [`Swap::set_double_buffering()`]), so it should still be used. + /// + /// This should be called once per frame at most. + fn swap_buffers(&mut self) { + self.screen.swap_buffers(); + self.context.frameBuffer = self.screen.raw_framebuffer().ptr as *mut u16; + } + + fn set_double_buffering(&mut self, enabled: bool) { + self.screen.set_double_buffering(enabled); + } +} + +impl Flush for Console<'_, S> { + fn flush_buffers(&mut self) { + self.screen.flush_buffers(); + } +} + impl Drop for Console<'_, S> { fn drop(&mut self) { unsafe { diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 6eb04f6..2420842 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -13,7 +13,8 @@ use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; mod private { - use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; + use super::{BottomScreen, Screen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; + use crate::console::Console; pub trait Sealed {} @@ -22,6 +23,7 @@ mod private { impl Sealed for TopScreenLeft {} impl Sealed for TopScreenRight {} impl Sealed for BottomScreen {} + impl Sealed for Console<'_, S> {} } /// Trait to handle common functionality for all screens. From 964a3d9e004e81001c16c29d4d99e44083cb0663 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 12:18:43 -0500 Subject: [PATCH 30/42] Remove Console::with_screen --- ctru-rs/src/console.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 322072e..78e8840 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -322,11 +322,6 @@ impl<'screen, S: Screen> Console<'screen, S> { _ => unreachable!(), } } - - /// Run a function with access to the underlying screen. - pub fn with_screen(&mut self, action: impl FnOnce(&mut RefMut<'_, S>)) { - action(&mut self.screen); - } } impl Swap for Console<'_, S> { From 323538bcff6caa3c6d7b87a2df40620dfcc5476d Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 12:23:28 -0500 Subject: [PATCH 31/42] Handle timeout int conversion better --- ctru-rs/src/services/srv.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ctru-rs/src/services/srv.rs b/ctru-rs/src/services/srv.rs index eeaa723..98b7282 100644 --- a/ctru-rs/src/services/srv.rs +++ b/ctru-rs/src/services/srv.rs @@ -16,11 +16,14 @@ pub trait HandleExt { impl HandleExt for Handle { fn wait_for_event(self, timeout: Duration) -> crate::Result<()> { + let timeout = i64::try_from(timeout.as_nanos()).map_err(|e| { + crate::Error::Other(format!( + "Failed to convert timeout to 64-bit nanoseconds: {}", + e + )) + })?; unsafe { - ResultCode(ctru_sys::svcWaitSynchronization( - self, - timeout.as_nanos() as i64, - ))?; + ResultCode(ctru_sys::svcWaitSynchronization(self, timeout))?; } Ok(()) } From daf613482b1636d01d793958cd0a34072000d2fa Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 12:34:00 -0500 Subject: [PATCH 32/42] Add missing docs to Error::is_timeout --- ctru-rs/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 186de3d..0be67b7 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -116,6 +116,7 @@ impl Error { Self::Libc(error_str.to_string_lossy().into()) } + /// Check if the error is a timeout. pub fn is_timeout(&self) -> bool { match *self { Error::Os(code) => R_DESCRIPTION(code) == ctru_sys::RD_TIMEOUT as ctru_sys::Result, From c6d8d8823f63b82784f6db49e8108582c465091d Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 12:35:40 -0500 Subject: [PATCH 33/42] Remove Console S generic by making ConsoleScreen trait alias --- ctru-rs/examples/file-explorer.rs | 3 +-- ctru-rs/examples/ir-user-circle-pad-pro.rs | 11 ++++------- ctru-rs/src/console.rs | 18 +++++++++++------- ctru-rs/src/services/gfx.rs | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 0c2b595..3ad37c9 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -6,7 +6,6 @@ use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; -use ctru::services::gfx::TopScreen; use std::fs::DirEntry; use std::os::horizon::fs::MetadataExt; use std::path::{Path, PathBuf}; @@ -27,7 +26,7 @@ struct FileExplorer<'a> { apt: &'a Apt, hid: &'a mut Hid, gfx: &'a Gfx, - console: Console<'a, TopScreen>, + console: Console<'a>, path: PathBuf, entries: Vec, running: bool, diff --git a/ctru-rs/examples/ir-user-circle-pad-pro.rs b/ctru-rs/examples/ir-user-circle-pad-pro.rs index 10fa58d..bdb88cb 100644 --- a/ctru-rs/examples/ir-user-circle-pad-pro.rs +++ b/ctru-rs/examples/ir-user-circle-pad-pro.rs @@ -1,7 +1,7 @@ //! A demo of using the ir:USER service to connect to the Circle Pad Pro. use ctru::prelude::*; -use ctru::services::gfx::{BottomScreen, Flush, Swap, TopScreen}; +use ctru::services::gfx::{Flush, Swap}; use ctru::services::ir_user::{CirclePadProInputResponse, ConnectionStatus, IrDeviceId, IrUser}; use ctru::services::srv::HandleExt; use ctru_sys::Handle; @@ -62,8 +62,8 @@ fn main() { } struct CirclePadProDemo<'screen> { - top_console: Console<'screen, TopScreen>, - bottom_console: Console<'screen, BottomScreen>, + top_console: Console<'screen>, + bottom_console: Console<'screen>, ir_user: IrUser, connection_status_event: Handle, receive_packet_event: Handle, @@ -75,10 +75,7 @@ enum ConnectionResult { } impl<'screen> CirclePadProDemo<'screen> { - fn new( - mut top_console: Console<'screen, TopScreen>, - bottom_console: Console<'screen, BottomScreen>, - ) -> Self { + fn new(mut top_console: Console<'screen>, bottom_console: Console<'screen>) -> Self { // Set up double buffering on top screen top_console.set_double_buffering(true); top_console.swap_buffers(); diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index 78e8840..cb98840 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -39,6 +39,10 @@ pub enum Dimension { Height, } +/// A [`Screen`] that can be used as a target for [`Console`]. +pub trait ConsoleScreen: Screen + Swap + Flush {} +impl ConsoleScreen for S {} + /// Virtual text console. /// /// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen. @@ -58,12 +62,12 @@ pub enum Dimension { /// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`). /// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables). #[doc(alias = "PrintConsole")] -pub struct Console<'screen, S: Screen> { +pub struct Console<'screen> { context: Box, - screen: RefMut<'screen, S>, + screen: RefMut<'screen, dyn ConsoleScreen>, } -impl<'screen, S: Screen> Console<'screen, S> { +impl<'screen> Console<'screen> { /// Initialize a console on the chosen screen. /// /// # Notes @@ -102,7 +106,7 @@ impl<'screen, S: Screen> Console<'screen, S> { /// # } /// ``` #[doc(alias = "consoleInit")] - pub fn new(screen: RefMut<'screen, S>) -> Self { + pub fn new(screen: RefMut<'screen, S>) -> Self { let mut context = Box::::default(); unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; @@ -324,7 +328,7 @@ impl<'screen, S: Screen> Console<'screen, S> { } } -impl Swap for Console<'_, S> { +impl Swap for Console<'_> { /// Swaps the video buffers. Note: The console's cursor position is not reset, only the framebuffer is changed. /// /// Even if double buffering is disabled, "swapping" the buffers has the side effect @@ -342,13 +346,13 @@ impl Swap for Console<'_, S> { } } -impl Flush for Console<'_, S> { +impl Flush for Console<'_> { fn flush_buffers(&mut self) { self.screen.flush_buffers(); } } -impl Drop for Console<'_, S> { +impl Drop for Console<'_> { fn drop(&mut self) { unsafe { // Safety: We are about to deallocate the PrintConsole data pointed diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 2420842..59540cb 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -13,7 +13,7 @@ use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; mod private { - use super::{BottomScreen, Screen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; + use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; use crate::console::Console; pub trait Sealed {} @@ -23,7 +23,7 @@ mod private { impl Sealed for TopScreenLeft {} impl Sealed for TopScreenRight {} impl Sealed for BottomScreen {} - impl Sealed for Console<'_, S> {} + impl Sealed for Console<'_> {} } /// Trait to handle common functionality for all screens. From 208a9171e43d9dc770eb222dc935defca0021f6f Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 12:53:44 -0500 Subject: [PATCH 34/42] Add srv::make_ipc_header and use in ir_user --- ctru-rs/src/services/ir_user.rs | 15 ++++++++------- ctru-rs/src/services/srv.rs | 7 +++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 67fa17d..051baec 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -11,6 +11,7 @@ #![doc(alias = "gamepad")] use crate::error::ResultCode; +use crate::services::srv::make_ipc_header; use crate::services::ServiceReference; use crate::Error; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; @@ -39,13 +40,13 @@ struct IrUserState { } // ir:USER syscall command headers -const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = 0x00060040; -const DISCONNECT_COMMAND_HEADER: u32 = 0x00090000; -const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = 0x000A0000; -const GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER: u32 = 0x000C0000; -const SEND_IR_NOP_COMMAND_HEADER: u32 = 0x000D0042; -const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = 0x00180182; -const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = 0x00190040; +const REQUIRE_CONNECTION_COMMAND_HEADER: u32 = make_ipc_header(6, 1, 0); +const DISCONNECT_COMMAND_HEADER: u32 = make_ipc_header(9, 0, 0); +const GET_RECEIVE_EVENT_COMMAND_HEADER: u32 = make_ipc_header(10, 0, 0); +const GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER: u32 = make_ipc_header(12, 0, 0); +const SEND_IR_NOP_COMMAND_HEADER: u32 = make_ipc_header(13, 1, 2); +const INITIALIZE_IRNOP_SHARED_COMMAND_HEADER: u32 = make_ipc_header(24, 6, 2); +const RELEASE_RECEIVED_DATA_COMMAND_HEADER: u32 = make_ipc_header(25, 1, 0); // Misc constants const SHARED_MEM_INFO_SECTIONS_SIZE: usize = 0x30; diff --git a/ctru-rs/src/services/srv.rs b/ctru-rs/src/services/srv.rs index 98b7282..c74eff2 100644 --- a/ctru-rs/src/services/srv.rs +++ b/ctru-rs/src/services/srv.rs @@ -28,3 +28,10 @@ impl HandleExt for Handle { Ok(()) } } + +/// Creates a command header to be used for IPC. This is a const fn version of [`ctru_sys::IPC_MakeHeader`]. +pub const fn make_ipc_header(command_id: u16, normal_params: u8, translate_params: u8) -> u32 { + ((command_id as u32) << 16) + | (((normal_params as u32) & 0x3F) << 6) + | ((translate_params as u32) & 0x3F) +} From 86c59eec5bc1c50bd4148190dfb109a3165fd511 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 13:02:48 -0500 Subject: [PATCH 35/42] Simplify some of the syscall code and fix &mut unsafety --- ctru-rs/src/services/ir_user.rs | 49 +++++++++++++++++---------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 051baec..c3b205c 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -357,25 +357,21 @@ impl IrUser { let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_mut().unwrap(); - // Set up the request - let thread_command_buffer = unsafe { ctru_sys::getThreadCommandBuffer() }; unsafe { + // Set up the request + let thread_command_buffer = unsafe { ctru_sys::getThreadCommandBuffer() }; std::ptr::copy(request.as_ptr(), thread_command_buffer, request.len()); - } - // Send the request - unsafe { + // Send the request ResultCode(ctru_sys::svcSendSyncRequest(shared_mem.service_handle))?; - } - // Handle the result returned by the service - let result = unsafe { std::ptr::read(thread_command_buffer.add(1)) }; - ResultCode(result as ctru_sys::Result)?; + // Handle the result returned by the service + let result = unsafe { std::ptr::read(thread_command_buffer.add(1)) }; + ResultCode(result as ctru_sys::Result)?; - // Copy back the response - request.clear(); - request.resize(expected_response_len, 0); - unsafe { + // Copy back the response + request.clear(); + request.resize(expected_response_len, 0); std::ptr::copy( thread_command_buffer, request.as_mut_ptr(), @@ -409,22 +405,27 @@ struct InitializeIrnopSharedParams { /// Internal helper for initializing the ir:USER service unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate::Result<()> { - let cmd_buffer = &mut *(slice_from_raw_parts_mut(ctru_sys::getThreadCommandBuffer(), 9)); - cmd_buffer[0] = INITIALIZE_IRNOP_SHARED_COMMAND_HEADER; - cmd_buffer[1] = params.shared_memory_len; - cmd_buffer[2] = params.recv_packet_buffer_len; - cmd_buffer[3] = params.recv_packet_count; - cmd_buffer[4] = params.send_packet_buffer_len; - cmd_buffer[5] = params.send_packet_count; - cmd_buffer[6] = params.bit_rate; - cmd_buffer[7] = 0; - cmd_buffer[8] = params.shared_memory_handle; + // Set up the request + let request = [ + INITIALIZE_IRNOP_SHARED_COMMAND_HEADER, + params.shared_memory_len, + params.recv_packet_buffer_len, + params.recv_packet_count, + params.send_packet_buffer_len, + params.send_packet_count, + params.bit_rate, + 0, + params.shared_memory_handle, + ]; + let cmd_buffer_ptr = ctru_sys::getThreadCommandBuffer(); + std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len()); // Send the request ResultCode(ctru_sys::svcSendSyncRequest(params.ir_user_handle))?; // Handle the result returned by the service - ResultCode(cmd_buffer[1] as ctru_sys::Result)?; + let result = std::ptr::read(cmd_buffer_ptr.add(1)); + ResultCode(result as ctru_sys::Result)?; Ok(()) } From df50702c13e88847e35fd0260148db25d6236772 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 13:55:06 -0500 Subject: [PATCH 36/42] Rename srv module to svc and add Handle::send_service_request --- ctru-rs/examples/ir-user-circle-pad-pro.rs | 2 +- ctru-rs/src/services/ir_user.rs | 138 +++++++-------------- ctru-rs/src/services/mod.rs | 2 +- ctru-rs/src/services/srv.rs | 37 ------ ctru-rs/src/services/svc.rs | 76 ++++++++++++ 5 files changed, 124 insertions(+), 131 deletions(-) delete mode 100644 ctru-rs/src/services/srv.rs create mode 100644 ctru-rs/src/services/svc.rs diff --git a/ctru-rs/examples/ir-user-circle-pad-pro.rs b/ctru-rs/examples/ir-user-circle-pad-pro.rs index bdb88cb..3f50972 100644 --- a/ctru-rs/examples/ir-user-circle-pad-pro.rs +++ b/ctru-rs/examples/ir-user-circle-pad-pro.rs @@ -3,7 +3,7 @@ use ctru::prelude::*; use ctru::services::gfx::{Flush, Swap}; use ctru::services::ir_user::{CirclePadProInputResponse, ConnectionStatus, IrDeviceId, IrUser}; -use ctru::services::srv::HandleExt; +use ctru::services::svc::HandleExt; use ctru_sys::Handle; use std::time::Duration; diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index c3b205c..77fd952 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -11,13 +11,13 @@ #![doc(alias = "gamepad")] use crate::error::ResultCode; -use crate::services::srv::make_ipc_header; +use crate::services::svc::{make_ipc_header, HandleExt}; use crate::services::ServiceReference; use crate::Error; use ctru_sys::{Handle, MEMPERM_READ, MEMPERM_READWRITE}; use std::alloc::Layout; use std::ffi::CString; -use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; +use std::ptr::slice_from_raw_parts; use std::sync::Mutex; static IR_USER_ACTIVE: Mutex<()> = Mutex::new(()); @@ -100,16 +100,18 @@ impl IrUser { ))?; // Initialize the ir:USER service with the shared memory - initialize_irnop_shared(InitializeIrnopSharedParams { - ir_user_handle: service_handle, - shared_memory_len: shared_memory_len as u32, - recv_packet_buffer_len: recv_buffer_size as u32, - recv_packet_count: recv_packet_count as u32, - send_packet_buffer_len: send_buffer_size as u32, - send_packet_count: send_packet_count as u32, - bit_rate: IR_BITRATE, + let request = vec![ + INITIALIZE_IRNOP_SHARED_COMMAND_HEADER, + shared_memory_len as u32, + recv_buffer_size as u32, + recv_packet_count as u32, + send_buffer_size as u32, + send_packet_count as u32, + IR_BITRATE, + 0, shared_memory_handle, - })?; + ]; + service_handle.send_service_request(request, 2)?; // Set up our service state let user_state = IrUserState { @@ -161,23 +163,28 @@ impl IrUser { /// Try to connect to the device with the provided ID. pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> { - self.send_service_request( - vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], - 2, - )?; + unsafe { + self.send_service_request( + vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], + 2, + )?; + } Ok(()) } /// Close the current IR connection. pub fn disconnect(&self) -> crate::Result<()> { - self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; + unsafe { + self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; + } Ok(()) } /// Get an event handle that activates on connection status changes. pub fn get_connection_status_event(&self) -> crate::Result { - let response = - self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4)?; + let response = unsafe { + self.send_service_request(vec![GET_CONNECTION_STATUS_EVENT_COMMAND_HEADER], 4) + }?; let status_event = response[3] as Handle; Ok(status_event) @@ -185,7 +192,8 @@ impl IrUser { /// Get an event handle that activates when a packet is received. pub fn get_recv_event(&self) -> crate::Result { - let response = self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4)?; + let response = + unsafe { self.send_service_request(vec![GET_RECEIVE_EVENT_COMMAND_HEADER], 4) }?; let recv_event = response[3] as Handle; Ok(recv_event) @@ -197,15 +205,17 @@ impl IrUser { /// with the current device input values. pub fn request_input_polling(&self, period_ms: u8) -> crate::Result<()> { let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; - self.send_service_request( - vec![ - SEND_IR_NOP_COMMAND_HEADER, - ir_request.len() as u32, - 2 + (ir_request.len() << 14) as u32, - ir_request.as_ptr() as u32, - ], - 2, - )?; + unsafe { + self.send_service_request( + vec![ + SEND_IR_NOP_COMMAND_HEADER, + ir_request.len() as u32, + 2 + (ir_request.len() << 14) as u32, + ir_request.as_ptr() as u32, + ], + 2, + )?; + } Ok(()) } @@ -213,7 +223,9 @@ impl IrUser { /// Mark the last `packet_count` packets as processed, so their memory in /// the receive buffer can be reused. pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> { - self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?; + unsafe { + self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?; + } Ok(()) } @@ -349,37 +361,17 @@ impl IrUser { } /// Internal helper for calling ir:USER service methods. - fn send_service_request( + unsafe fn send_service_request( &self, - mut request: Vec, + request: Vec, expected_response_len: usize, ) -> crate::Result> { let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); let shared_mem = shared_mem_guard.as_mut().unwrap(); - unsafe { - // Set up the request - let thread_command_buffer = unsafe { ctru_sys::getThreadCommandBuffer() }; - std::ptr::copy(request.as_ptr(), thread_command_buffer, request.len()); - - // Send the request - ResultCode(ctru_sys::svcSendSyncRequest(shared_mem.service_handle))?; - - // Handle the result returned by the service - let result = unsafe { std::ptr::read(thread_command_buffer.add(1)) }; - ResultCode(result as ctru_sys::Result)?; - - // Copy back the response - request.clear(); - request.resize(expected_response_len, 0); - std::ptr::copy( - thread_command_buffer, - request.as_mut_ptr(), - expected_response_len, - ); - } - - Ok(request) + shared_mem + .service_handle + .send_service_request(request, expected_response_len) } } @@ -392,44 +384,6 @@ fn round_up(value: usize, multiple: usize) -> usize { } } -struct InitializeIrnopSharedParams { - ir_user_handle: Handle, - shared_memory_len: u32, - recv_packet_buffer_len: u32, - recv_packet_count: u32, - send_packet_buffer_len: u32, - send_packet_count: u32, - bit_rate: u32, - shared_memory_handle: Handle, -} - -/// Internal helper for initializing the ir:USER service -unsafe fn initialize_irnop_shared(params: InitializeIrnopSharedParams) -> crate::Result<()> { - // Set up the request - let request = [ - INITIALIZE_IRNOP_SHARED_COMMAND_HEADER, - params.shared_memory_len, - params.recv_packet_buffer_len, - params.recv_packet_count, - params.send_packet_buffer_len, - params.send_packet_count, - params.bit_rate, - 0, - params.shared_memory_handle, - ]; - let cmd_buffer_ptr = ctru_sys::getThreadCommandBuffer(); - std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len()); - - // Send the request - ResultCode(ctru_sys::svcSendSyncRequest(params.ir_user_handle))?; - - // Handle the result returned by the service - let result = std::ptr::read(cmd_buffer_ptr.add(1)); - ResultCode(result as ctru_sys::Result)?; - - Ok(()) -} - /// An enum which represents the different IR devices the 3DS can connect to via /// the ir:USER service. pub enum IrDeviceId { diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index f280823..4fadeb3 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -24,8 +24,8 @@ pub mod ndsp; pub mod ps; mod reference; pub mod soc; -pub mod srv; pub mod sslc; +pub mod svc; cfg_if::cfg_if! { if #[cfg(all(feature = "romfs", romfs_exists))] { diff --git a/ctru-rs/src/services/srv.rs b/ctru-rs/src/services/srv.rs deleted file mode 100644 index c74eff2..0000000 --- a/ctru-rs/src/services/srv.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Service API -//! -//! Not all APIs are wrapped in this module, since a lot are fundamentally unsafe. -//! Most APIs should be used directly from `ctru-sys`. - -use crate::error::ResultCode; -use ctru_sys::Handle; -use std::time::Duration; - -/// Extension trait for [Handle] -pub trait HandleExt { - /// Wait for an event to fire. If the timeout is reached, an error is returned. You can use - /// [`Error::is_timeout`] to check if the error is due to a timeout. - fn wait_for_event(self, timeout: Duration) -> crate::Result<()>; -} - -impl HandleExt for Handle { - fn wait_for_event(self, timeout: Duration) -> crate::Result<()> { - let timeout = i64::try_from(timeout.as_nanos()).map_err(|e| { - crate::Error::Other(format!( - "Failed to convert timeout to 64-bit nanoseconds: {}", - e - )) - })?; - unsafe { - ResultCode(ctru_sys::svcWaitSynchronization(self, timeout))?; - } - Ok(()) - } -} - -/// Creates a command header to be used for IPC. This is a const fn version of [`ctru_sys::IPC_MakeHeader`]. -pub const fn make_ipc_header(command_id: u16, normal_params: u8, translate_params: u8) -> u32 { - ((command_id as u32) << 16) - | (((normal_params as u32) & 0x3F) << 6) - | ((translate_params as u32) & 0x3F) -} diff --git a/ctru-rs/src/services/svc.rs b/ctru-rs/src/services/svc.rs new file mode 100644 index 0000000..11530f0 --- /dev/null +++ b/ctru-rs/src/services/svc.rs @@ -0,0 +1,76 @@ +//! Syscall APIs +//! +//! Not all APIs are wrapped in this module, since a lot are fundamentally unsafe. +//! Most APIs should be used directly from `ctru-sys`. + +use crate::error::ResultCode; +use ctru_sys::Handle; +use std::time::Duration; + +/// Extension trait for [Handle] +pub trait HandleExt { + /// Wait for an event to fire. If the timeout is reached, an error is returned. You can use + /// [`Error::is_timeout`] to check if the error is due to a timeout. + fn wait_for_event(self, timeout: Duration) -> crate::Result<()>; + + /// Send a service request to the handle. + /// The request vector must contain the command header and any parameters. + /// The request vector is overwritten with the response and returned. + /// The error in the response is checked and returned as a `Result::Err` if the operation failed. + /// + /// # Safety + /// This function is unsafe because it directly accesses the thread command buffer. + /// If the request vector or the expected response length is incorrect, or the handle is not a service that accepts + /// requests, the function may cause undefined behavior. + unsafe fn send_service_request( + self, + request: Vec, + expected_response_len: usize, + ) -> crate::Result>; +} + +impl HandleExt for Handle { + fn wait_for_event(self, timeout: Duration) -> crate::Result<()> { + let timeout = i64::try_from(timeout.as_nanos()).map_err(|e| { + crate::Error::Other(format!( + "Failed to convert timeout to 64-bit nanoseconds: {}", + e + )) + })?; + unsafe { + ResultCode(ctru_sys::svcWaitSynchronization(self, timeout))?; + } + Ok(()) + } + + unsafe fn send_service_request( + self, + mut request: Vec, + expected_response_len: usize, + ) -> crate::Result> { + // Copy over the request + let cmd_buffer_ptr = unsafe { ctru_sys::getThreadCommandBuffer() }; + std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len()); + + // Send the request + ResultCode(ctru_sys::svcSendSyncRequest(self))?; + + // Handle the result returned by the service + let result = unsafe { std::ptr::read(cmd_buffer_ptr.add(1)) }; + ResultCode(result as ctru_sys::Result)?; + + // Copy back the response + request.clear(); + request.resize(expected_response_len, 0); + std::ptr::copy_nonoverlapping(cmd_buffer_ptr, request.as_mut_ptr(), expected_response_len); + + Ok(request) + } +} + +/// Creates a command header to be used for IPC. This is a const fn version of [`ctru_sys::IPC_MakeHeader`]. +pub const fn make_ipc_header(command_id: u16, normal_params: u8, translate_params: u8) -> u32 { + ((command_id as u32) << 16) + | (((normal_params as u32) & 0x3F) << 6) + | ((translate_params as u32) & 0x3F) +} From 979fd50fe2cfcc37a0038ee0c788e35447d5deee Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 26 Dec 2023 14:09:32 -0500 Subject: [PATCH 37/42] Simplify some of the packet parsing --- ctru-rs/src/services/ir_user.rs | 54 ++++++++++----------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 77fd952..2ed4b2e 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -243,18 +243,8 @@ impl IrUser { let shared_mem = shared_mem_guard.as_ref().unwrap().shared_memory; IrUserStatusInfo { - recv_err_result: i32::from_ne_bytes([ - shared_mem[0], - shared_mem[1], - shared_mem[2], - shared_mem[3], - ]), - send_err_result: i32::from_ne_bytes([ - shared_mem[4], - shared_mem[5], - shared_mem[6], - shared_mem[7], - ]), + recv_err_result: i32::from_ne_bytes(shared_mem[0..4].try_into().unwrap()), + send_err_result: i32::from_ne_bytes(shared_mem[4..8].try_into().unwrap()), connection_status: match shared_mem[8] { 0 => ConnectionStatus::Disconnected, 1 => ConnectionStatus::Connecting, @@ -278,18 +268,8 @@ impl IrUser { let shared_mem = user_state.shared_memory; // Find where the packets are, and how many - let start_index = u32::from_ne_bytes([ - shared_mem[0x10], - shared_mem[0x11], - shared_mem[0x12], - shared_mem[0x13], - ]); - let valid_packet_count = u32::from_ne_bytes([ - shared_mem[0x18], - shared_mem[0x19], - shared_mem[0x1a], - shared_mem[0x1b], - ]); + let start_index = u32::from_ne_bytes(shared_mem[0x10..0x14].try_into().unwrap()); + let valid_packet_count = u32::from_ne_bytes(shared_mem[0x18..0x1c].try_into().unwrap()); // Parse the packets (0..valid_packet_count as usize) @@ -301,18 +281,10 @@ impl IrUser { let packet_info = &shared_mem[packet_info_offset..packet_info_offset + PACKET_INFO_SIZE]; - let offset_to_data_buffer = u32::from_ne_bytes([ - packet_info[0], - packet_info[1], - packet_info[2], - packet_info[3], - ]) as usize; - let data_length = u32::from_ne_bytes([ - packet_info[4], - packet_info[5], - packet_info[6], - packet_info[7], - ]) as usize; + let offset_to_data_buffer = + u32::from_ne_bytes(packet_info[0..4].try_into().unwrap()) as usize; + let data_length = + u32::from_ne_bytes(packet_info[4..8].try_into().unwrap()) as usize; // Find the packet data. The packet data may wrap around the buffer end, so // `packet_data` is a function from packet byte offset to value. @@ -337,7 +309,13 @@ impl IrUser { }; // Check our payload length math against what the packet info contains - assert_eq!(data_length, payload_offset + payload_length + 1); + if data_length != payload_offset + payload_length + 1 { + return Err(format!( + "Invalid payload length (expected {}, got {})", + data_length, + payload_offset + payload_length + 1 + )); + } // IR packets start with a magic number, so double check it let magic_number = packet_data(0); @@ -491,7 +469,7 @@ impl TryFrom<&IrUserPacket> for CirclePadProInputResponse { let response_id = packet.payload[0]; if response_id != CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID { return Err(format!( - "Invalid response ID (expected 0x10, got {:#x}", + "Invalid response ID (expected {CIRCLE_PAD_PRO_INPUT_RESPONSE_PACKET_ID}, got {:#x}", packet.payload[0] )); } From fe0bb5154e014678adf5603e44fd6a86f050b60b Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 2 Jan 2024 17:24:57 -0500 Subject: [PATCH 38/42] Update example file documentation --- ctru-rs/examples/ir-user-circle-pad-pro.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ctru-rs/examples/ir-user-circle-pad-pro.rs b/ctru-rs/examples/ir-user-circle-pad-pro.rs index 3f50972..c3de057 100644 --- a/ctru-rs/examples/ir-user-circle-pad-pro.rs +++ b/ctru-rs/examples/ir-user-circle-pad-pro.rs @@ -1,3 +1,5 @@ +//! ir:USER Circle Pad Pro example. +//! //! A demo of using the ir:USER service to connect to the Circle Pad Pro. use ctru::prelude::*; From de1604b36468f35abfee10b7a894f5ebd32c51d9 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 2 Jan 2024 17:29:26 -0500 Subject: [PATCH 39/42] Add ir:rst disable in CPP demo to improve New 3DS compatibility --- ctru-rs/examples/ir-user-circle-pad-pro.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ctru-rs/examples/ir-user-circle-pad-pro.rs b/ctru-rs/examples/ir-user-circle-pad-pro.rs index c3de057..43cf0e2 100644 --- a/ctru-rs/examples/ir-user-circle-pad-pro.rs +++ b/ctru-rs/examples/ir-user-circle-pad-pro.rs @@ -9,6 +9,7 @@ use ctru::services::svc::HandleExt; use ctru_sys::Handle; use std::time::Duration; +// Configuration for this demo of the Circle Pad Pro (not general purpose ir:USER values). const PACKET_INFO_SIZE: usize = 8; const MAX_PACKET_SIZE: usize = 32; const PACKET_COUNT: usize = 1; @@ -16,6 +17,13 @@ const PACKET_BUFFER_SIZE: usize = PACKET_COUNT * (PACKET_INFO_SIZE + MAX_PACKET_ const CPP_CONNECTION_POLLING_PERIOD_MS: u8 = 0x08; const CPP_POLLING_PERIOD_MS: u8 = 0x32; +// This export tells libctru to not initialize ir:rst when initializing HID. +// This is necessary on the New 3DS because ir:rst is mutually exclusive with ir:USER. +#[no_mangle] +unsafe extern "C" fn hidShouldUseIrrst() -> bool { + false +} + fn main() { let apt = Apt::new().unwrap(); let gfx = Gfx::new().unwrap(); From 1496d47ef2192ed255c68d2f8e072f63c1847e1c Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 2 Jan 2024 17:32:26 -0500 Subject: [PATCH 40/42] Move Sealed to separate file --- ctru-rs/src/lib.rs | 1 + ctru-rs/src/sealed.rs | 15 +++++++++++++++ ctru-rs/src/services/gfx.rs | 21 ++++----------------- 3 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 ctru-rs/src/sealed.rs diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index e8c6483..2874839 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -64,6 +64,7 @@ pub mod linear; pub mod mii; pub mod os; pub mod prelude; +mod sealed; pub mod services; pub use crate::error::{Error, Result}; diff --git a/ctru-rs/src/sealed.rs b/ctru-rs/src/sealed.rs new file mode 100644 index 0000000..34d5fee --- /dev/null +++ b/ctru-rs/src/sealed.rs @@ -0,0 +1,15 @@ +//! This is a private module to prevent users from implementing certain traits. +//! This is done by requiring a `Sealed` trait implementation, which can only be +//! done in this crate. + +use crate::console::Console; +use crate::services::gfx::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; + +pub trait Sealed {} + +impl Sealed for TopScreen {} +impl Sealed for TopScreen3D<'_> {} +impl Sealed for TopScreenLeft {} +impl Sealed for TopScreenRight {} +impl Sealed for BottomScreen {} +impl Sealed for Console<'_> {} diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 59540cb..7a6ec6e 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -9,30 +9,17 @@ use std::marker::PhantomData; use std::sync::Mutex; use crate::error::Result; +use crate::sealed::Sealed; use crate::services::gspgpu::{self, FramebufferFormat}; use crate::services::ServiceReference; -mod private { - use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight}; - use crate::console::Console; - - pub trait Sealed {} - - impl Sealed for TopScreen {} - impl Sealed for TopScreen3D<'_> {} - impl Sealed for TopScreenLeft {} - impl Sealed for TopScreenRight {} - impl Sealed for BottomScreen {} - impl Sealed for Console<'_> {} -} - /// Trait to handle common functionality for all screens. /// /// This trait is implemented by the screen structs for working with frame buffers and /// drawing to the screens. Graphics-related code can be made generic over this /// trait to work with any of the given screens. #[doc(alias = "gfxScreen_t")] -pub trait Screen: private::Sealed { +pub trait Screen: Sealed { /// Returns the `libctru` value for the Screen kind. fn as_raw(&self) -> ctru_sys::gfxScreen_t; @@ -101,7 +88,7 @@ pub struct TopScreen3D<'screen> { /// Trait for screens that can have its frame buffers swapped, when double buffering is enabled. /// /// This trait applies to all [`Screen`]s that have swappable frame buffers. -pub trait Swap: private::Sealed { +pub trait Swap: Sealed { /// Swaps the video buffers. /// /// Even if double buffering is disabled, "swapping" the buffers has the side effect @@ -162,7 +149,7 @@ impl Swap for BottomScreen { /// A screen with buffers that can be flushed. /// /// This trait applies to any [`Screen`] that has data written to its frame buffer. -pub trait Flush: private::Sealed { +pub trait Flush: Sealed { /// Flushes the video buffer(s) for this screen. /// /// Note that you must still call [`Swap::swap_buffers`] after this method for the buffer contents to be displayed. From 5233237e9eb159de2488e99c06694e53cd410375 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 2 Jan 2024 17:36:12 -0500 Subject: [PATCH 41/42] Make more IrUser functions take &mut self --- ctru-rs/src/services/ir_user.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 2ed4b2e..4386bd0 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -162,7 +162,7 @@ impl IrUser { } /// Try to connect to the device with the provided ID. - pub fn require_connection(&self, device_id: IrDeviceId) -> crate::Result<()> { + pub fn require_connection(&mut self, device_id: IrDeviceId) -> crate::Result<()> { unsafe { self.send_service_request( vec![REQUIRE_CONNECTION_COMMAND_HEADER, device_id.get_id()], @@ -173,7 +173,7 @@ impl IrUser { } /// Close the current IR connection. - pub fn disconnect(&self) -> crate::Result<()> { + pub fn disconnect(&mut self) -> crate::Result<()> { unsafe { self.send_service_request(vec![DISCONNECT_COMMAND_HEADER], 2)?; } @@ -203,7 +203,7 @@ impl IrUser { /// /// This will send a packet to the CPP requesting it to send back packets /// with the current device input values. - pub fn request_input_polling(&self, period_ms: u8) -> crate::Result<()> { + pub fn request_input_polling(&mut self, period_ms: u8) -> crate::Result<()> { let ir_request: [u8; 3] = [1, period_ms, (period_ms + 2) << 2]; unsafe { self.send_service_request( @@ -222,7 +222,7 @@ impl IrUser { /// Mark the last `packet_count` packets as processed, so their memory in /// the receive buffer can be reused. - pub fn release_received_data(&self, packet_count: u32) -> crate::Result<()> { + pub fn release_received_data(&mut self, packet_count: u32) -> crate::Result<()> { unsafe { self.send_service_request(vec![RELEASE_RECEIVED_DATA_COMMAND_HEADER, packet_count], 2)?; } @@ -340,7 +340,7 @@ impl IrUser { /// Internal helper for calling ir:USER service methods. unsafe fn send_service_request( - &self, + &mut self, request: Vec, expected_response_len: usize, ) -> crate::Result> { From 06d26fc96db2da5b4e00eb8ae1dcb92abd716b3d Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Tue, 2 Jan 2024 17:39:49 -0500 Subject: [PATCH 42/42] Revert one &mut self --- ctru-rs/src/services/ir_user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/ir_user.rs b/ctru-rs/src/services/ir_user.rs index 4386bd0..672fe20 100644 --- a/ctru-rs/src/services/ir_user.rs +++ b/ctru-rs/src/services/ir_user.rs @@ -340,7 +340,7 @@ impl IrUser { /// Internal helper for calling ir:USER service methods. unsafe fn send_service_request( - &mut self, + &self, request: Vec, expected_response_len: usize, ) -> crate::Result> {