diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 4807f32..abc6f79 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -28,6 +28,7 @@ ferris-says = "0.2.1" futures = "0.3" time = "0.3.7" tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] } +cfg-if = "1.0.0" [features] default = ["romfs"] diff --git a/ctru-rs/examples/futures-tokio.rs b/ctru-rs/examples/futures-tokio.rs index 9fc97c9..d700fa3 100644 --- a/ctru-rs/examples/futures-tokio.rs +++ b/ctru-rs/examples/futures-tokio.rs @@ -2,7 +2,6 @@ use ctru::console::Console; use ctru::services::hid::KeyPad; -use ctru::services::ps::Ps; use ctru::services::{Apt, Hid}; use ctru::Gfx; use std::os::horizon::thread::BuilderExt; @@ -13,7 +12,6 @@ fn main() { 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 _ps = Ps::init().expect("Couldn't initialize PS service"); let _console = Console::init(gfx.top_screen.borrow_mut()); // Give ourselves up to 30% of the system core's time diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index d0aab20..f5377f1 100644 --- a/ctru-rs/examples/hashmaps.rs +++ b/ctru-rs/examples/hashmaps.rs @@ -2,23 +2,19 @@ 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 + // + // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. + // This generator is only active when activating the `PS` service. + // This service is automatically initialized in `ctru::init` 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); diff --git a/ctru-rs/examples/romfs.rs b/ctru-rs/examples/romfs.rs new file mode 100644 index 0000000..ac8b443 --- /dev/null +++ b/ctru-rs/examples/romfs.rs @@ -0,0 +1,48 @@ +use ctru::console::Console; +use ctru::gfx::Gfx; +use ctru::services::apt::Apt; +use ctru::services::hid::{Hid, KeyPad}; + +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()); + + cfg_if::cfg_if! { + // Run this code if RomFS are wanted and available + // This never fails as `ctru-rs` examples inherit all of the `ctru` features, + // but it might if a normal user application wasn't setup correctly + if #[cfg(all(feature = "romfs", romfs_exists))] { + let _romfs = ctru::romfs::RomFS::new().unwrap(); + + let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap(); + println!("Contents of test-file.txt: \n{f}\n"); + + let f = std::fs::read_to_string("romfs:/ファイル.txt").unwrap(); + // While RomFS supports UTF-16 file paths, `Console` doesn't... + println!("Contents of [UTF-16 File]: \n{f}\n"); + } else { + println!("No RomFS was found, are you sure you included it?") + } + } + + println!("\nPress START to exit"); + + // Main loop + while apt.main_loop() { + //Scan all the inputs. This should be done once for each frame + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/romfs/ファイル.txt b/ctru-rs/examples/romfs/ファイル.txt new file mode 100644 index 0000000..d60c842 --- /dev/null +++ b/ctru-rs/examples/romfs/ファイル.txt @@ -0,0 +1 @@ +This filename has UTF-16 exclusive characters! \ No newline at end of file diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index ed77651..55d285d 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -4,6 +4,12 @@ #![feature(custom_test_frameworks)] #![test_runner(test_runner::run)] +extern "C" fn services_deinit() { + unsafe { + ctru_sys::psExit(); + } +} + /// Call this somewhere to force Rust to link some required crates /// This is also a setup for some crate integration only available at runtime /// @@ -12,6 +18,14 @@ pub fn init() { linker_fix_3ds::init(); pthread_3ds::init(); + // Initialize the PS service for random data generation + unsafe { + ctru_sys::psInit(); + + // Setup the deconstruction at the program's end + libc::atexit(services_deinit); + } + use std::panic::PanicInfo; let main_thread = std::thread::current().id(); diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs index e4c2da6..8a419f4 100644 --- a/ctru-rs/src/services/ps.rs +++ b/ctru-rs/src/services/ps.rs @@ -1,13 +1,9 @@ //! 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). +//! As such, it is initialized by default in `ctru::init` instead of having a safety handler //! 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, @@ -32,55 +28,34 @@ pub enum AESKeyType { 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; +pub fn local_friend_code_seed() -> 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) - } + 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 device_id() -> crate::Result { + let mut id: u32 = 0; - 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(()) - } + let r = unsafe { ctru_sys::PS_GetDeviceId(&mut id) }; + if r < 0 { + Err(r.into()) + } else { + Ok(id) } } -impl Drop for Ps { - fn drop(&mut self) { - unsafe { - ctru_sys::psExit(); - } +pub fn generate_random_bytes(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(()) } } @@ -92,8 +67,6 @@ mod tests { #[test] fn construct_hash_map() { - let _ps = Ps::init().unwrap(); - let mut input = vec![ (1_i32, String::from("123")), (2, String::from("2")), @@ -108,33 +81,4 @@ mod tests { assert_eq!(input, actual); } - - #[test] - fn construct_hash_map_no_rand() { - // Without initializing PS, we can't use `libc::getrandom` and constructing - // a HashMap panics at runtime. - // - // If any test case successfully creates a HashMap before this test, - // the thread-local RandomState in std will be initialized. We spawn - // a new thread to actually create the hash map, since even in multi-threaded - // test environment there's a chance this test wouldn't panic because - // some other test case ran before it. - // - // One downside of this approach is that the panic handler for the panicking - // thread prints to the console, which is not captured by the default test - // harness and prints even when the test passes. - std::thread::Builder::new() - .spawn(|| { - let map: HashMap = HashMap::from_iter([ - (1_i32, String::from("123")), - (2, String::from("2")), - (6, String::from("six")), - ]); - - dbg!(map); - }) - .unwrap() - .join() - .expect_err("should have panicked"); - } }