diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index abc6f79..baf2c79 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -19,6 +19,7 @@ pthread-3ds = { git = "https://github.com/Meziu/pthread-3ds.git" } libc = "0.2.116" bitflags = "1.0.0" widestring = "0.2.2" +once_cell = "1.10.0" [build-dependencies] toml = "0.5" diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 152ede6..e3250c4 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().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..181c846 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().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 46a4f64..c66c199 100644 --- a/ctru-rs/examples/futures-basic.rs +++ b/ctru-rs/examples/futures-basic.rs @@ -16,7 +16,7 @@ use std::os::horizon::thread::BuilderExt; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init().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/futures-tokio.rs b/ctru-rs/examples/futures-tokio.rs index d700fa3..c437ed8 100644 --- a/ctru-rs/examples/futures-tokio.rs +++ b/ctru-rs/examples/futures-tokio.rs @@ -9,7 +9,7 @@ use std::time::Duration; fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init().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/gfx-wide-mode.rs b/ctru-rs/examples/gfx-wide-mode.rs index fcf3fa9..f84f5c4 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().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..0d82ecf 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().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/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index f5377f1..0b07939 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().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..483e5c3 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().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..2fcd7fb 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().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..2172d81 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().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..dc1d133 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().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()); @@ -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/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 6be9e81..799d65d 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.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().unwrap(); let _console = Console::init(gfx.top_screen.borrow_mut()); println!("Press A to enter some text or press Start to quit"); diff --git a/ctru-rs/examples/thread-basic.rs b/ctru-rs/examples/thread-basic.rs index 690c236..0b818f5 100644 --- a/ctru-rs/examples/thread-basic.rs +++ b/ctru-rs/examples/thread-basic.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().unwrap(); let _console = Console::init(gfx.top_screen.borrow_mut()); let prio = std::os::horizon::thread::current_priority(); diff --git a/ctru-rs/examples/thread-locals.rs b/ctru-rs/examples/thread-locals.rs index 571eff7..dcc8b7a 100644 --- a/ctru-rs/examples/thread-locals.rs +++ b/ctru-rs/examples/thread-locals.rs @@ -13,7 +13,7 @@ std::thread_local! { fn main() { ctru::init(); - let gfx = Gfx::default(); + let gfx = Gfx::init().expect("Couldn't obtain 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..b77615a 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().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"); diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 53c3fbe..96ade26 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, } 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 => f.debug_tuple("ServiceAlreadyActive").finish(), } } } @@ -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 => write!(f, "Service already active"), } } } diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index c761549..6400d97 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -1,11 +1,13 @@ //! LCD screens manipulation helper +use once_cell::sync::Lazy; use std::cell::RefCell; -use std::default::Default; use std::marker::PhantomData; -use std::ops::Drop; +use std::sync::Mutex; +use crate::error::Result; use crate::services::gspgpu::{self, FramebufferFormat}; +use crate::services::ServiceReference; /// Trait implemented by TopScreen and BottomScreen for common methods pub trait Screen { @@ -71,25 +73,43 @@ pub enum Side { pub struct Gfx { pub top_screen: RefCell, pub bottom_screen: RefCell, + _service_handler: ServiceReference, } +static GFX_ACTIVE: Lazy> = Lazy::new(|| Mutex::new(0)); + 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()` instead of this function to initialize the module with default parameters + pub fn with_formats( 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 { + ) -> Result { + let _service_handler = ServiceReference::new( + &GFX_ACTIVE, + false, + || unsafe { + ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); + + Ok(()) + }, + || unsafe { ctru_sys::gfxExit() }, + )?; + + Ok(Self { top_screen: RefCell::new(TopScreen), bottom_screen: RefCell::new(BottomScreen), - } + _service_handler, + }) + } + + /// Creates a new Gfx instance with default init values + /// It's the same as calling: `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) + pub fn init() -> Result { + Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) } /// Flushes the current framebuffers @@ -197,18 +217,14 @@ 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), - } - } -} +#[cfg(test)] +mod tests { + use super::*; + use crate::Error; -impl Drop for Gfx { - fn drop(&mut self) { - unsafe { ctru_sys::gfxExit() }; + #[test] + fn gfx_duplicate() { + // We don't need to build a `Gfx` because the test runner has one already + assert!(matches!(Gfx::init(), Err(Error::ServiceAlreadyActive))) } } diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 55d285d..810c5df 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -26,6 +26,13 @@ pub fn init() { libc::atexit(services_deinit); } + #[cfg(not(test))] + panic_hook_setup(); +} + +#[cfg(not(test))] +fn panic_hook_setup() { + use crate::services::hid::{Hid, KeyPad}; use std::panic::PanicInfo; let main_thread = std::thread::current().id(); @@ -38,13 +45,16 @@ pub fn init() { // Only for panics in the main thread if main_thread == std::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; - } + match Hid::init() { + Ok(hid) => loop { + hid.scan_input(); + let keys = hid.keys_down(); + if keys.contains(KeyPad::KEY_SELECT) { + break; + } + }, + Err(e) => println!("Error while intializing Hid controller during panic: {e}"), } } }); diff --git a/ctru-rs/src/romfs.rs b/ctru-rs/src/romfs.rs index 12190b9..e054985 100644 --- a/ctru-rs/src/romfs.rs +++ b/ctru-rs/src/romfs.rs @@ -10,27 +10,58 @@ //! romfs_dir = "romfs" //! ``` +use once_cell::sync::Lazy; use std::ffi::CStr; +use std::sync::Mutex; + +use crate::services::ServiceReference; #[non_exhaustive] -pub struct RomFS; +pub struct RomFS { + _service_handler: ServiceReference, +} + +static ROMFS_ACTIVE: Lazy> = Lazy::new(|| Mutex::new(0)); 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()) }; - - if result < 0 { - Err(result.into()) - } else { - Ok(Self) - } + pub fn init() -> crate::Result { + let _service_handler = ServiceReference::new( + &ROMFS_ACTIVE, + true, + || { + let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); + let r = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; + if r < 0 { + return Err(r.into()); + } + + Ok(()) + }, + || { + let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); + unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; + }, + )?; + + Ok(Self { _service_handler }) } } -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()) }; +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn romfs_counter() { + let _romfs = RomFS::init().unwrap(); + let value = *ROMFS_ACTIVE.lock().unwrap(); + + assert_eq!(value, 1); + + drop(_romfs); + + let value = *ROMFS_ACTIVE.lock().unwrap(); + + assert_eq!(value, 0); } } diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 2159e9e..0dc750c 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -3,9 +3,12 @@ pub mod fs; pub mod gspgpu; pub mod hid; pub mod ps; +mod reference; pub mod soc; pub mod sslc; pub use self::apt::Apt; pub use self::hid::Hid; pub use self::sslc::SslC; + +pub(crate) use self::reference::ServiceReference; diff --git a/ctru-rs/src/services/reference.rs b/ctru-rs/src/services/reference.rs new file mode 100644 index 0000000..7fac227 --- /dev/null +++ b/ctru-rs/src/services/reference.rs @@ -0,0 +1,44 @@ +use crate::Error; +use std::sync::Mutex; +pub(crate) struct ServiceReference { + counter: &'static Mutex, + close: Box, +} + +impl ServiceReference { + pub fn new( + counter: &'static Mutex, + allow_multiple: bool, + start: S, + close: E, + ) -> crate::Result + where + S: FnOnce() -> crate::Result<()>, + E: Fn() + Send + Sync + 'static, + { + let mut value = counter.lock().expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning + + if *value == 0 { + start()?; + } else if !allow_multiple { + return Err(Error::ServiceAlreadyActive); + } + + *value += 1; + + Ok(Self { + counter, + close: Box::new(close), + }) + } +} + +impl Drop for ServiceReference { + fn drop(&mut self) { + let mut value = self.counter.lock().expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning + *value -= 1; + if *value == 0 { + (self.close)(); + } + } +} diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index 026bd2d..5705d06 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 libc::memalign; +use once_cell::sync::Lazy; use std::net::Ipv4Addr; +use std::sync::Mutex; + +use crate::services::ServiceReference; /// 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, + _service_handler: ServiceReference, } +static SOC_ACTIVE: Lazy> = Lazy::new(|| Mutex::new(0)); + 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,18 +30,29 @@ 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 { + let _service_handler = ServiceReference::new( + &SOC_ACTIVE, + false, + || { + let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; + let r = unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) }; + if r < 0 { + return Err(r.into()); + } + + Ok(()) + }, + // `socExit` returns an error code. There is no documentantion of when errors could happen, + // but we wouldn't be able to handle them in the `Drop` implementation anyways. + // Surely nothing bad will happens :D + || unsafe { + // The socket buffer is freed automatically by `socExit` + ctru_sys::socExit(); + }, + )?; + + Ok(Self { _service_handler }) } /// IP Address of the Nintendo 3DS system. @@ -45,11 +62,15 @@ impl Soc { } } -impl Drop for Soc { - fn drop(&mut self) { - unsafe { - socExit(); - free(self.soc_mem as *mut _); - } +#[cfg(test)] +mod tests { + use super::*; + use crate::Error; + + #[test] + fn soc_duplicate() { + let _soc = Soc::init().unwrap(); + + assert!(matches!(Soc::init(), Err(Error::ServiceAlreadyActive))) } } diff --git a/ctru-rs/src/test_runner.rs b/ctru-rs/src/test_runner.rs index 3c7aa9a..ab4c60f 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().unwrap(); let hid = Hid::init().unwrap(); let apt = Apt::init().unwrap();