From 39829e18206a76782b68fb778f17cd35a90665cd Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 8 Jul 2023 12:52:44 +0200 Subject: [PATCH] Am, Apt and refactoring to streamline the API and docs --- ctru-rs/examples/title-info.rs | 13 +++---- ctru-rs/src/lib.rs | 5 ++- ctru-rs/src/services/am.rs | 64 +++++++++++++++++++++++++--------- ctru-rs/src/services/apt.rs | 24 +++++++++++++ ctru-rs/src/services/mod.rs | 12 +++++-- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index fee5cb6..a03c002 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -80,12 +80,13 @@ fn main() { // Move cursor to top left println!("\x1b[1;1"); - match selected_title.title_info() { - Ok(info) => { - println!("Size: {} KB", info.size_bytes() / 1024); - println!("Version: 0x{:x}", info.version()); - } - Err(e) => println!("Failed to get title info: {}", e), + match selected_title.size() { + Ok(size) => println!("Size: {} kB", size / 1024), + Err(e) => println!("Failed to get title size: {}", e), + } + match selected_title.version() { + Ok(version) => println!("Version: 0x{:x}", version), + Err(e) => println!("Failed to get title version: {}", e), } match selected_title.product_code() { Ok(code) => println!("Product code: \"{code}\""), diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index f2f3e7c..37428f8 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -24,6 +24,7 @@ #![feature(test)] #![feature(custom_test_frameworks)] #![feature(try_trait_v2)] +#![feature(once_cell_try)] #![feature(allocator_api)] #![test_runner(test_runner::run)] #![doc( @@ -39,7 +40,9 @@ extern crate shim_3ds; /// Expanded stack size used to spawn the main thread by `libctru`. /// -/// This value was chosen to support crate dependencies which expected more stack than provided, without compromising performance. +/// It takes effect only if the `big-stack` feature is active. Otherwise, the default stack size should be ~32kB. +/// +/// This value was chosen to support crate dependencies which expected more stack than provided. It's suggested to use less stack if possible. #[no_mangle] #[cfg(feature = "big-stack")] static __stacksize__: usize = 2 * 1024 * 1024; // 2MB diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index 70bd151..2af1152 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -1,35 +1,34 @@ +//! Application Manager service. +//! +//! As the name implies, the AM service manages installed applications. It can: +//! - Read the installed applications on the console and their information (depending on the install location). +//! - Install compatible applications to the console. +//! +//! `ctru` doesn't support installing titles (yet). + use crate::error::ResultCode; use crate::services::fs::FsMediaType; +use std::cell::OnceCell; use std::marker::PhantomData; use std::mem::MaybeUninit; -#[derive(Copy, Clone, Debug)] -#[repr(transparent)] -pub struct TitleInfo(ctru_sys::AM_TitleEntry); - -impl TitleInfo { - pub fn id(&self) -> u64 { - self.0.titleID - } - pub fn size_bytes(&self) -> u64 { - self.0.size - } - pub fn version(&self) -> u16 { - self.0.version - } -} - +/// Struct holding general information about a specific title. +#[doc(alias = "AM_TitleEntry")] pub struct Title<'a> { id: u64, mediatype: FsMediaType, + entry: OnceCell, _am: PhantomData<&'a Am>, } impl<'a> Title<'a> { + /// Returns this title's ID. pub fn id(&self) -> u64 { self.id } + /// Returns this title's unique product code. + #[doc(alias = "AM_GetTitleProductCode")] pub fn product_code(&self) -> crate::Result { let mut buf: [u8; 16] = [0; 16]; @@ -43,7 +42,9 @@ impl<'a> Title<'a> { Ok(String::from_utf8_lossy(&buf).to_string()) } - pub fn title_info(&self) -> crate::Result { + /// Retrieves additional information on the title. + #[doc(alias = "AM_GetTitleInfo")] + fn title_info(&self) -> crate::Result { let mut info = MaybeUninit::zeroed(); unsafe { @@ -57,11 +58,34 @@ impl<'a> Title<'a> { Ok(info.assume_init()) } } + + /// Returns the size of this title in bytes. + pub fn size(&self) -> crate::Result { + // Get the internal entry, or fill it if empty. + let entry = self.entry.get_or_try_init(|| -> crate::Result { + self.title_info() + })?; + + Ok(entry.size) + } + + /// Returns the installed version of this title. + pub fn version(&self) -> crate::Result { + // Get the internal entry, or fill it if empty. + let entry = self.entry.get_or_try_init(|| -> crate::Result { + self.title_info() + })?; + + Ok(entry.version) + } } +/// Handle to the Application Manager service. pub struct Am(()); impl Am { + /// Initialize a new handle. + #[doc(alias = "amInit")] pub fn new() -> crate::Result { unsafe { ResultCode(ctru_sys::amInit())?; @@ -69,6 +93,8 @@ impl Am { } } + /// Returns the amount of titles currently installed in a specific install location. + #[doc(alias = "AM_GetTitleCount")] pub fn title_count(&self, mediatype: FsMediaType) -> crate::Result { unsafe { let mut count = 0; @@ -77,6 +103,8 @@ impl Am { } } + /// Returns the list of titles installed in a specific install location. + #[doc(alias = "AM_GetTitleList")] pub fn title_list(&self, mediatype: FsMediaType) -> crate::Result> { let count = self.title_count(mediatype)?; let mut buf = vec![0; count as usize]; @@ -94,6 +122,7 @@ impl Am { .map(|id| Title { id, mediatype, + entry: OnceCell::new(), _am: PhantomData, }) .collect()) @@ -101,6 +130,7 @@ impl Am { } impl Drop for Am { + #[doc(alias = "amExit")] fn drop(&mut self) { unsafe { ctru_sys::amExit() }; } diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index f7731be..0f5eb25 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -1,8 +1,17 @@ +//! Applet service. +//! +//! The APT service handles integration with some higher level OS features such as Sleep mode, the Home Menu and application switching. +//! +//! It also handles running applets, small programs made available by the OS to streamline specific functionality. Those are implemented in the [`applets`](crate::applets) module. + use crate::error::ResultCode; +/// Handle to the Applet service. pub struct Apt(()); impl Apt { + /// Initialize a new handle. + #[doc(alias = "aptInit")] pub fn new() -> crate::Result { unsafe { ResultCode(ctru_sys::aptInit())?; @@ -10,10 +19,24 @@ impl Apt { } } + /// Returns `true` if the application is running in the foreground as normal. + /// + /// # Notes + /// + /// This function is called as such since it automatically handles all checks for Home Menu switching, Sleep mode and other events that could take away control from the application. + /// For this reason, its main use is as the condition of a while loop that controls the main logic for your program. + #[doc(alias = "aptMainLoop")] pub fn main_loop(&self) -> bool { unsafe { ctru_sys::aptMainLoop() } } + /// Sets (in percentage) the amount of time to lend to the application thread spawned on the syscore (core #1). + /// + /// # Notes + /// + /// It is necessary to set a time limit before spawning threads on the syscore. + /// The percentage value must be withing 5% and 89%, though it is suggested to use lower values (around 30-45%) to avoid slowing down the OS processes. + #[doc(alias = "APT_SetAppCpuTimeLimit")] pub fn set_app_cpu_time_limit(&mut self, percent: u32) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?; @@ -23,6 +46,7 @@ impl Apt { } impl Drop for Apt { + #[doc(alias = "aptExit")] fn drop(&mut self) { unsafe { ctru_sys::aptExit() }; } diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index d825c46..a387933 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -1,9 +1,15 @@ -//! System services used to handle system-specific functionalities. +//! OS services used to handle system-specific functionality. //! -//! Most of the 3DS console's functionalities (when writing homebrew) are locked behind services, +//! Most of the 3DS console's functionalities (when writing user-land homebrew) are accessible via services, //! which need to be initialized before accessing any particular feature. //! -//! Some include: button input, audio playback, graphics rendering, built-in cameras, etc. +//! To ensure safety measures when using the underlying services, `ctru` leverages Rust's lifetime model. +//! After initializing the handle for a specific service (e.g. [`Apt`](apt::Apt)) the service will be accessible as long as there is at least one handle "alive". +//! As such, handles should be dropped *after* the use of a specific service. This is particularly important for services which are necessary for functionality +//! "outside" their associated methods, such as [`RomFS`](romfs::RomFS), which creates an accessible virtual filesystem, or [`Soc`](soc::Soc), +//! which enables all network communications via sockets. +//! +//! In `ctru` some services only allow a single handle to be created at a time, to ensure a safe and controlled environment. pub mod am; pub mod apt;