diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 152ede6..ca4ba3a 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -8,7 +8,7 @@ fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); let console = Console::init(gfx.top_screen.borrow_mut()); println!("Hi there! Try pressing a button"); diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 5a638ee..0451989 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -14,10 +14,10 @@ fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); #[cfg(all(feature = "romfs", romfs_exists))] - let _romfs = ctru::romfs::RomFS::new().unwrap(); + let _romfs = ctru::romfs::RomFS::init().unwrap(); FileExplorer::init(&apt, &hid, &gfx).run(); } diff --git a/ctru-rs/examples/futures-basic.rs b/ctru-rs/examples/futures-basic.rs index c48964b..dd78795 100644 --- a/ctru-rs/examples/futures-basic.rs +++ b/ctru-rs/examples/futures-basic.rs @@ -13,7 +13,7 @@ use futures::StreamExt; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtaint GFX controller"); 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()); diff --git a/ctru-rs/examples/futures-tokio.rs b/ctru-rs/examples/futures-tokio.rs index 7eb3d03..a478603 100644 --- a/ctru-rs/examples/futures-tokio.rs +++ b/ctru-rs/examples/futures-tokio.rs @@ -6,7 +6,7 @@ use std::time::Duration; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtaint GFX controller"); 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()); diff --git a/ctru-rs/examples/gfx-wide-mode.rs b/ctru-rs/examples/gfx-wide-mode.rs index fcf3fa9..1d39a2f 100644 --- a/ctru-rs/examples/gfx-wide-mode.rs +++ b/ctru-rs/examples/gfx-wide-mode.rs @@ -7,7 +7,7 @@ fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); let mut console = Console::init(gfx.top_screen.borrow_mut()); println!("Press A to enable/disable wide screen mode."); diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs index fc65c9d..822d03d 100644 --- a/ctru-rs/examples/graphics-bitmap.rs +++ b/ctru-rs/examples/graphics-bitmap.rs @@ -19,7 +19,7 @@ static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtaint GFX controller"); 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()); diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index f5377f1..8dc26e2 100644 --- a/ctru-rs/examples/hashmaps.rs +++ b/ctru-rs/examples/hashmaps.rs @@ -12,7 +12,7 @@ fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); let _console = Console::init(gfx.top_screen.borrow_mut()); let mut map = std::collections::HashMap::new(); diff --git a/ctru-rs/examples/hello-both-screens.rs b/ctru-rs/examples/hello-both-screens.rs index 67113a3..5a96187 100644 --- a/ctru-rs/examples/hello-both-screens.rs +++ b/ctru-rs/examples/hello-both-screens.rs @@ -8,7 +8,7 @@ fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); // Start a console on the top screen let top_screen = Console::init(gfx.top_screen.borrow_mut()); diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index 930549f..b620025 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -7,7 +7,7 @@ use std::io::BufWriter; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtain GFX controller"); 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()); diff --git a/ctru-rs/examples/network-sockets.rs b/ctru-rs/examples/network-sockets.rs index 84978e9..c459619 100644 --- a/ctru-rs/examples/network-sockets.rs +++ b/ctru-rs/examples/network-sockets.rs @@ -10,7 +10,7 @@ use std::time::Duration; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); let _console = Console::init(gfx.top_screen.borrow_mut()); let hid = Hid::init().unwrap(); let apt = Apt::init().unwrap(); diff --git a/ctru-rs/examples/romfs.rs b/ctru-rs/examples/romfs.rs index ac8b443..6e7ece9 100644 --- a/ctru-rs/examples/romfs.rs +++ b/ctru-rs/examples/romfs.rs @@ -5,7 +5,7 @@ use ctru::services::hid::{Hid, KeyPad}; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtaint GFX controller"); 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()); @@ -15,7 +15,7 @@ fn main() { // 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 _romfs = ctru::romfs::RomFS::init().unwrap(); let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap(); println!("Contents of test-file.txt: \n{f}\n"); diff --git a/ctru-rs/examples/thread-basic.rs b/ctru-rs/examples/thread-basic.rs index 7657718..5da5560 100644 --- a/ctru-rs/examples/thread-basic.rs +++ b/ctru-rs/examples/thread-basic.rs @@ -11,7 +11,7 @@ fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); let _console = Console::init(gfx.top_screen.borrow_mut()); let prio = thread::current().priority(); diff --git a/ctru-rs/examples/thread-locals.rs b/ctru-rs/examples/thread-locals.rs index 937855a..a6618a1 100644 --- a/ctru-rs/examples/thread-locals.rs +++ b/ctru-rs/examples/thread-locals.rs @@ -10,7 +10,7 @@ std::thread_local! { fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtaint GFX controller"); gfx.top_screen.borrow_mut().set_wide_mode(true); let hid = Hid::init().expect("Couldn't obtain HID controller"); let apt = Apt::init().expect("Couldn't obtain APT controller"); diff --git a/ctru-rs/examples/time-rtc.rs b/ctru-rs/examples/time-rtc.rs index 23096ed..696a7f0 100644 --- a/ctru-rs/examples/time-rtc.rs +++ b/ctru-rs/examples/time-rtc.rs @@ -6,7 +6,7 @@ use ctru::services::hid::{Hid, KeyPad}; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().expect("Couldn't obtaint GFX controller"); let hid = Hid::init().expect("Couldn't obtain HID controller"); let apt = Apt::init().expect("Couldn't obtain APT controller"); diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 53c3fbe..790866c 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -9,6 +9,7 @@ pub type Result = ::std::result::Result; #[non_exhaustive] pub enum Error { Os(ctru_sys::Result), + ServiceAlreadyActive(&'static str), } impl From for Error { @@ -28,6 +29,7 @@ impl fmt::Debug for Error { .field("summary", &R_SUMMARY(err)) .field("level", &R_LEVEL(err)) .finish(), + Error::ServiceAlreadyActive(service) => write!(f, "Service {service} already active"), } } } @@ -39,6 +41,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), + Error::ServiceAlreadyActive(service) => write!(f, "Service {service} already active"), } } } diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index c761549..b38419f 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -1,10 +1,11 @@ //! LCD screens manipulation helper use std::cell::RefCell; -use std::default::Default; use std::marker::PhantomData; use std::ops::Drop; +use std::sync::atomic::{AtomicBool, Ordering}; +use crate::error::{Error, Result}; use crate::services::gspgpu::{self, FramebufferFormat}; /// Trait implemented by TopScreen and BottomScreen for common methods @@ -73,25 +74,39 @@ pub struct Gfx { pub bottom_screen: RefCell, } +static GFX_ACTIVE: AtomicBool = AtomicBool::new(false); + impl Gfx { /// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom /// screens /// - /// Use `Gfx::default()` instead of this function to initialize the module with default parameters - pub fn new( + /// Use `Gfx::init_default()` instead of this function to initialize the module with default parameters + pub fn init( top_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat, use_vram_buffers: bool, - ) -> Self { - unsafe { - ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); - } - Gfx { - top_screen: RefCell::new(TopScreen), - bottom_screen: RefCell::new(BottomScreen), + ) -> Result { + match GFX_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + unsafe { + ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); + } + + Ok(Gfx { + top_screen: RefCell::new(TopScreen), + bottom_screen: RefCell::new(BottomScreen), + }) + } + Err(_) => Err(Error::ServiceAlreadyActive("Gfx")), } } + /// Creates a new Gfx instance with default init values + /// It's the same as calling: `Gfx::init(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) + pub fn init_default() -> Result { + Gfx::init(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) + } + /// Flushes the current framebuffers pub fn flush_buffers(&self) { unsafe { ctru_sys::gfxFlushBuffers() }; @@ -197,18 +212,21 @@ impl From for ctru_sys::gfx3dSide_t { } } -impl Default for Gfx { - fn default() -> Self { - unsafe { ctru_sys::gfxInitDefault() }; - Gfx { - top_screen: RefCell::new(TopScreen), - bottom_screen: RefCell::new(BottomScreen), - } - } -} - impl Drop for Gfx { fn drop(&mut self) { unsafe { ctru_sys::gfxExit() }; + + GFX_ACTIVE.store(false, Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gfx_duplicate() { + // We don't need to build a `Gfx` because the test runner has one already + assert!(Gfx::init_default().is_err()); } } diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 89756d4..85b9c63 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -38,12 +38,31 @@ pub fn init() { // Only for panics in the main thread if main_thread == thread::current().id() && console::Console::exists() { println!("\nPress SELECT to exit the software"); - let hid = services::hid::Hid::init().unwrap(); - loop { - hid.scan_input(); - if hid.keys_down().contains(services::hid::KeyPad::KEY_SELECT) { - break; + // The use of unsafe functions here is basically obligatory. + // To have memory safety when using the `Hid` struct, we must not make more + // than one available at the same time, so no drop/service ownership issues arise. + // The problem here is that the `panic_hook` is run _before_ the app cleanup, + // so an `Hid` stuct may still be alive and thus make the `panic_hook` panic. + // If that were to happen, the system would have to reboot to properly close the app. + // + // Using `hidInit` is safe when another instance is open, and we can do safe operations afterwards. + // The only (probably) unsafe part of this system is the `hidExit`, since in a multithreaded + // environment some other threads may still be doing operations on the service + // before the cleanup, though the time window would be almost nonexistent, and it would only + // really be a problem in preemptive threads. + // + // TL;DR : This code is bad. + unsafe { + ctru_sys::hidInit(); + + loop { + ctru_sys::hidScanInput(); + let keys = services::hid::KeyPad::from_bits_truncate(ctru_sys::hidKeysDown()); + if keys.contains(services::hid::KeyPad::KEY_SELECT) { + ctru_sys::hidExit(); + break; + } } } } diff --git a/ctru-rs/src/romfs.rs b/ctru-rs/src/romfs.rs index 12190b9..19b0990 100644 --- a/ctru-rs/src/romfs.rs +++ b/ctru-rs/src/romfs.rs @@ -11,19 +11,29 @@ //! ``` use std::ffi::CStr; +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::error::Error; #[non_exhaustive] pub struct RomFS; +static ROMFS_ACTIVE: AtomicBool = AtomicBool::new(false); + impl RomFS { - pub fn new() -> crate::Result { - let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); - let result = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; + pub fn init() -> crate::Result { + match ROMFS_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); + let result = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; - if result < 0 { - Err(result.into()) - } else { - Ok(Self) + if result < 0 { + Err(result.into()) + } else { + Ok(Self) + } + } + Err(_) => Err(Error::ServiceAlreadyActive("RomFS")), } } } @@ -32,5 +42,19 @@ impl Drop for RomFS { fn drop(&mut self) { let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; + + ROMFS_ACTIVE.store(false, Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn romfs_duplicate() { + let _romfs = RomFS::init().unwrap(); + + assert!(RomFS::init().is_err()); } } diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 4662a4e..2e425ff 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -1,14 +1,24 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::error::Error; + +#[non_exhaustive] pub struct Apt(()); +static APT_ACTIVE: AtomicBool = AtomicBool::new(false); + impl Apt { - pub fn init() -> crate::Result { - unsafe { - let r = ctru_sys::aptInit(); - if r < 0 { - Err(r.into()) - } else { - Ok(Apt(())) + pub fn init() -> crate::Result { + match APT_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + let r = unsafe { ctru_sys::aptInit() }; + if r < 0 { + Err(r.into()) + } else { + Ok(Self(())) + } } + Err(_) => Err(Error::ServiceAlreadyActive("Apt")), } } @@ -17,13 +27,11 @@ impl Apt { } pub fn set_app_cpu_time_limit(&self, percent: u32) -> crate::Result<()> { - unsafe { - let r = ctru_sys::APT_SetAppCpuTimeLimit(percent); - if r < 0 { - Err(r.into()) - } else { - Ok(()) - } + let r = unsafe { ctru_sys::APT_SetAppCpuTimeLimit(percent) }; + if r < 0 { + Err(r.into()) + } else { + Ok(()) } } } @@ -31,5 +39,19 @@ impl Apt { impl Drop for Apt { fn drop(&mut self) { unsafe { ctru_sys::aptExit() }; + + APT_ACTIVE.store(false, Ordering::Release); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gfx_duplicate() { + // We don't need to build a `Apt` because the test runner has one already + assert!(Apt::init().is_err()); + } +} + diff --git a/ctru-rs/src/services/fs.rs b/ctru-rs/src/services/fs.rs index 9f3334a..cc9bbe7 100644 --- a/ctru-rs/src/services/fs.rs +++ b/ctru-rs/src/services/fs.rs @@ -13,9 +13,12 @@ use std::mem; use std::path::{Path, PathBuf}; use std::ptr; use std::slice; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use widestring::{WideCStr, WideCString}; +use crate::error::Error; + bitflags! { #[derive(Default)] struct FsOpen: u32 { @@ -82,8 +85,11 @@ pub enum ArchiveID { /// until an instance of this struct is created. /// /// The service exits when all instances of this struct go out of scope. +#[non_exhaustive] pub struct Fs(()); +static FS_ACTIVE: AtomicBool = AtomicBool::new(false); + /// Handle to an open filesystem archive. /// /// Archives are automatically closed when they go out of scope. @@ -96,6 +102,7 @@ pub struct Fs(()); /// let fs = Fs::init().unwrap(); /// let sdmc_archive = fs.sdmc().unwrap(); /// ``` +#[non_exhaustive] pub struct Archive { id: ArchiveID, handle: u64, @@ -164,6 +171,7 @@ pub struct Archive { /// # Ok(()) /// # } /// ``` +#[non_exhaustive] pub struct File { handle: u32, offset: u64, @@ -175,6 +183,7 @@ pub struct File { /// represents known metadata about a file. /// /// [`metadata`]: fn.metadata.html +#[non_exhaustive] pub struct Metadata { attributes: u32, size: u64, @@ -257,6 +266,7 @@ pub struct OpenOptions { /// /// This Result will return Err if there's some sort of intermittent IO error /// during iteration. +#[non_exhaustive] pub struct ReadDir<'a> { handle: Dir, root: Arc, @@ -297,14 +307,17 @@ impl Fs { /// ctrulib services are reference counted, so this function may be called /// as many times as desired and the service will not exit until all /// instances of Fs drop out of scope. - pub fn init() -> crate::Result { - unsafe { - let r = ctru_sys::fsInit(); - if r < 0 { - Err(r.into()) - } else { - Ok(Fs(())) + pub fn init() -> crate::Result { + match FS_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + let r = unsafe { ctru_sys::fsInit() }; + if r < 0 { + Err(r.into()) + } else { + Ok(Self(())) + } } + Err(_) => Err(Error::ServiceAlreadyActive("Fs")), } } @@ -990,6 +1003,8 @@ impl Drop for Fs { unsafe { ctru_sys::fsExit(); } + + FS_ACTIVE.store(false, Ordering::Release); } } @@ -1059,3 +1074,16 @@ impl From for ctru_sys::FS_ArchiveID { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fs_duplicate() { + let _fs = Fs::init().unwrap(); + + assert!(Fs::init().is_err()); + } +} + diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 18790f1..6ad67fb 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -4,6 +4,10 @@ //! and circle pad information. It also provides information from the sound volume slider, //! the accelerometer, and the gyroscope. +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::error::Error; + bitflags::bitflags! { /// A set of flags corresponding to the button and directional pad /// inputs on the 3DS @@ -44,12 +48,17 @@ bitflags::bitflags! { /// when all instances of this struct fall out of scope. /// /// This service requires no special permissions to use. +#[non_exhaustive] pub struct Hid(()); +static HID_ACTIVE: AtomicBool = AtomicBool::new(false); + /// Represents user input to the touchscreen. +#[non_exhaustive] pub struct TouchPosition(ctru_sys::touchPosition); /// Represents the current position of the 3DS circle pad. +#[non_exhaustive] pub struct CirclePosition(ctru_sys::circlePosition); /// Initializes the HID service. @@ -60,14 +69,17 @@ pub struct CirclePosition(ctru_sys::circlePosition); /// Since this service requires no special or elevated permissions, errors are /// rare in practice. impl Hid { - pub fn init() -> crate::Result { - unsafe { - let r = ctru_sys::hidInit(); - if r < 0 { - Err(r.into()) - } else { - Ok(Hid(())) + pub fn init() -> crate::Result { + match HID_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + let r = unsafe { ctru_sys::hidInit() }; + if r < 0 { + Err(r.into()) + } else { + Ok(Self(())) + } } + Err(_) => Err(Error::ServiceAlreadyActive("Hid")), } } @@ -151,5 +163,18 @@ impl CirclePosition { impl Drop for Hid { fn drop(&mut self) { unsafe { ctru_sys::hidExit() }; + + HID_ACTIVE.store(false, Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hid_duplicate() { + // We don't need to build a `Hid` because the test runner has one already + assert!(Hid::init().is_err()); } } diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs index 8a419f4..3b4ef1a 100644 --- a/ctru-rs/src/services/ps.rs +++ b/ctru-rs/src/services/ps.rs @@ -63,8 +63,6 @@ pub fn generate_random_bytes(out: &mut [u8]) -> crate::Result<()> { mod tests { use std::collections::HashMap; - use super::*; - #[test] fn construct_hash_map() { let mut input = vec![ diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index 026bd2d..a15541a 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -1,21 +1,27 @@ -use ctru_sys::{socExit, socInit}; use libc::{free, memalign}; use std::net::Ipv4Addr; +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::error::Error; +use ctru_sys::{socExit, socInit}; /// Soc service. Initializing this service will enable the use of network sockets and utilities /// such as those found in `std::net`. The service will be closed when this struct is is dropped. +#[non_exhaustive] pub struct Soc { soc_mem: *mut u32, } +static SOC_ACTIVE: AtomicBool = AtomicBool::new(false); + impl Soc { /// Initialize the Soc service with a default buffer size of 0x100000 bytes /// /// # Errors /// /// This function will return an error if the `Soc` service is already initialized - pub fn init() -> crate::Result { - Soc::init_with_buffer_size(0x100000) + pub fn init() -> crate::Result { + Self::init_with_buffer_size(0x100000) } /// Initialize the Soc service with a custom buffer size in bytes. The size should be @@ -24,17 +30,20 @@ impl Soc { /// # Errors /// /// This function will return an error if the `Soc` service is already initialized - pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result { - unsafe { - let soc_mem = memalign(0x1000, num_bytes) as *mut u32; - - let r = socInit(soc_mem, num_bytes as u32); - if r < 0 { - free(soc_mem as *mut _); - Err(r.into()) - } else { - Ok(Soc { soc_mem }) - } + pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result { + match SOC_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => unsafe { + let soc_mem = memalign(0x1000, num_bytes) as *mut u32; + + let r = socInit(soc_mem, num_bytes as u32); + if r < 0 { + free(soc_mem as *mut _); + Err(r.into()) + } else { + Ok(Self { soc_mem }) + } + }, + Err(_) => Err(Error::ServiceAlreadyActive("Soc")), } } @@ -51,5 +60,19 @@ impl Drop for Soc { socExit(); free(self.soc_mem as *mut _); } + + SOC_ACTIVE.store(false, Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn soc_duplicate() { + let _soc = Soc::init().unwrap(); + + assert!(Soc::init().is_err()); } } diff --git a/ctru-rs/src/services/sslc.rs b/ctru-rs/src/services/sslc.rs index bdd5407..0b2192e 100644 --- a/ctru-rs/src/services/sslc.rs +++ b/ctru-rs/src/services/sslc.rs @@ -1,29 +1,37 @@ // TODO: Implement remaining functions +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::error::Error; + +#[non_exhaustive] pub struct SslC(()); +static SSLC_ACTIVE: AtomicBool = AtomicBool::new(false); + impl SslC { /// Initialize sslc pub fn init() -> crate::Result { - unsafe { - let r = ctru_sys::sslcInit(0); - if r < 0 { - Err(r.into()) - } else { - Ok(SslC(())) + match SSLC_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + let r = unsafe { ctru_sys::sslcInit(0) }; + if r < 0 { + Err(r.into()) + } else { + Ok(Self(())) + } } + Err(_) => Err(Error::ServiceAlreadyActive("SslC")), } } /// Fill `buf` with `buf.len()` random bytes pub fn generate_random_data(&self, buf: &mut [u8]) -> crate::Result<()> { - unsafe { - let r = ctru_sys::sslcGenerateRandomData(buf.as_ptr() as _, buf.len() as u32); - if r < 0 { - Err(r.into()) - } else { - Ok(()) - } + let r = unsafe { ctru_sys::sslcGenerateRandomData(buf.as_ptr() as _, buf.len() as u32) }; + if r < 0 { + Err(r.into()) + } else { + Ok(()) } } } @@ -31,5 +39,20 @@ impl SslC { impl Drop for SslC { fn drop(&mut self) { unsafe { ctru_sys::sslcExit() }; + + SSLC_ACTIVE.store(false, Ordering::Release); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sslc_duplicate() { + let _sslc = SslC::init().unwrap(); + + assert!(SslC::init().is_err()); + } +} + diff --git a/ctru-rs/src/srv.rs b/ctru-rs/src/srv.rs index 7d2ad21..8cca5c3 100644 --- a/ctru-rs/src/srv.rs +++ b/ctru-rs/src/srv.rs @@ -1,14 +1,24 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::error::Error; + +#[non_exhaustive] pub struct Srv(()); +static SRV_ACTIVE: AtomicBool = AtomicBool::new(false); + impl Srv { - pub fn init() -> crate::Result { - unsafe { - let r = ctru_sys::srvInit(); - if r < 0 { - Err(r.into()) - } else { - Ok(Srv(())) + pub fn init() -> crate::Result { + match SRV_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { + Ok(_) => { + let r = unsafe { ctru_sys::srvInit() }; + if r < 0 { + Err(r.into()) + } else { + Ok(Self(())) + } } + Err(_) => Err(Error::ServiceAlreadyActive("Srv")), } } } @@ -16,5 +26,19 @@ impl Srv { impl Drop for Srv { fn drop(&mut self) { unsafe { ctru_sys::srvExit() }; + + SRV_ACTIVE.store(false, Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn srv_duplicate() { + let _srv = Srv::init().unwrap(); + + assert!(Srv::init().is_err()); } } diff --git a/ctru-rs/src/test_runner.rs b/ctru-rs/src/test_runner.rs index ff3a459..0e9ab06 100644 --- a/ctru-rs/src/test_runner.rs +++ b/ctru-rs/src/test_runner.rs @@ -17,7 +17,7 @@ use crate::services::Apt; pub(crate) fn run(tests: &[&TestDescAndFn]) { crate::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init_default().unwrap(); let hid = Hid::init().unwrap(); let apt = Apt::init().unwrap(); @@ -115,9 +115,4 @@ mod link_fix { extern "C" fn sigemptyset(_arg1: *mut libc::sigset_t) -> ::libc::c_int { -1 } - - #[no_mangle] - extern "C" fn sysconf(_name: libc::c_int) -> libc::c_long { - -1 - } }