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 +}