diff --git a/.gitignore b/.gitignore index c34b8d3..54f1838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ target Cargo.lock .cargo + +# IDE files +.idea diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index ff327f5..4807f32 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -25,6 +25,7 @@ toml = "0.5" [dev-dependencies] ferris-says = "0.2.1" +futures = "0.3" time = "0.3.7" tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] } diff --git a/ctru-rs/examples/futures-basic.rs b/ctru-rs/examples/futures-basic.rs new file mode 100644 index 0000000..c48964b --- /dev/null +++ b/ctru-rs/examples/futures-basic.rs @@ -0,0 +1,73 @@ +//! This example runs a basic future executor from the `futures` library. +//! Every 60 frames (about 1 second) it prints "Tick" to the console. +//! The executor runs on a separate thread. Internally it yields when it has no more work to do, +//! allowing other threads to run. +//! The example also implements clean shutdown by using a oneshot channel to end the future, thus +//! ending the executor and the thread it runs on. + +use ctru::console::Console; +use ctru::services::hid::KeyPad; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; +use futures::StreamExt; + +fn main() { + ctru::init(); + let gfx = Gfx::default(); + let hid = Hid::init().expect("Couldn't obtain HID controller"); + let apt = Apt::init().expect("Couldn't obtain APT controller"); + let _console = Console::init(gfx.top_screen.borrow_mut()); + + // Give ourselves up to 30% of the system core's time + apt.set_app_cpu_time_limit(30) + .expect("Failed to enable system core"); + + println!("Starting executor..."); + + let (exit_sender, mut exit_receiver) = futures::channel::oneshot::channel(); + let (mut timer_sender, mut timer_receiver) = futures::channel::mpsc::channel(0); + let executor_thread = ctru::thread::Builder::new() + .affinity(1) + .spawn(move || { + let mut executor = futures::executor::LocalPool::new(); + + executor.run_until(async move { + loop { + futures::select! { + _ = exit_receiver => break, + _ = timer_receiver.next() => { + println!("Tick"); + } + } + } + }); + }) + .expect("Failed to create executor thread"); + + println!("Executor started!"); + + let mut frame_count = 0; + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + println!("Shutting down..."); + let _ = exit_sender.send(()); + let _ = executor_thread.join(); + break; + } + + frame_count += 1; + + if frame_count == 60 { + if let Err(e) = timer_sender.try_send(()) { + println!("Error sending timer message: {e}"); + } + frame_count = 0; + } + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs new file mode 100644 index 0000000..d0aab20 --- /dev/null +++ b/ctru-rs/examples/hashmaps.rs @@ -0,0 +1,39 @@ +use ctru::console::Console; +use ctru::gfx::Gfx; +use ctru::services::apt::Apt; +use ctru::services::hid::{Hid, KeyPad}; +use ctru::services::ps::Ps; + +fn main() { + // Initialize services + ctru::init(); + let apt = Apt::init().unwrap(); + let hid = Hid::init().unwrap(); + let gfx = Gfx::default(); + let _console = Console::init(gfx.top_screen.borrow_mut()); + + // HashMaps generate hashes thanks to the 3DS' criptografically secure generator. + // Sadly, this generator is only active when activating the `Ps` service. + // To do this, we have to make sure the `Ps` service handle is alive for the whole + // run time (or at least, when `HashMaps` are used). + // Not having a living `Ps` instance when using `HashMap`s results in a panic + let _ps = Ps::init().unwrap(); + + let mut map = std::collections::HashMap::new(); + map.insert("A Key!", 102); + map.insert("Another key?", 543); + map.remove("A Key!"); + + println!("{:#?}", map); + + while apt.main_loop() { + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + + hid.scan_input(); + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + } +} diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 7a0e194..2159e9e 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -2,6 +2,7 @@ pub mod apt; pub mod fs; pub mod gspgpu; pub mod hid; +pub mod ps; pub mod soc; pub mod sslc; diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs new file mode 100644 index 0000000..5b1ca27 --- /dev/null +++ b/ctru-rs/src/services/ps.rs @@ -0,0 +1,85 @@ +//! Process Services (PS) module. This is used for miscellaneous utility tasks, but +//! is particularly important because it is used to generate random data, which +//! is required for common things like [`HashMap`](std::collections::HashMap). +//! See also + +/// PS handle. This must not be dropped in order for random generation +/// to work (in most cases, the lifetime of an application). +#[non_exhaustive] +pub struct Ps; + +#[repr(u32)] +pub enum AESAlgorithm { + CbcEnc, + CbcDec, + CtrEnc, + CtrDec, + CcmEnc, + CcmDec, +} + +#[repr(u32)] +pub enum AESKeyType { + Keyslot0D, + Keyslot2D, + Keyslot31, + Keyslot38, + Keyslot32, + Keyslot39Dlp, + Keyslot2E, + KeyslotInvalid, + Keyslot36, + Keyslot39Nfc, +} + +impl Ps { + /// Initialize the PS module. + pub fn init() -> crate::Result { + let r = unsafe { ctru_sys::psInit() }; + if r < 0 { + Err(r.into()) + } else { + Ok(Self) + } + } + + pub fn local_friend_code_seed(&self) -> crate::Result { + let mut seed: u64 = 0; + + let r = unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) }; + if r < 0 { + Err(r.into()) + } else { + Ok(seed) + } + } + + pub fn device_id(&self) -> crate::Result { + let mut id: u32 = 0; + + let r = unsafe { ctru_sys::PS_GetDeviceId(&mut id) }; + if r < 0 { + Err(r.into()) + } else { + Ok(id) + } + } + + pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> { + let r = + unsafe { ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32) }; + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } +} + +impl Drop for Ps { + fn drop(&mut self) { + unsafe { + ctru_sys::psExit(); + } + } +}