From 127eafdc4b067cbac47d71c73620f83cc30f17a8 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Sat, 22 Jul 2023 13:31:14 +0200 Subject: [PATCH] Small changes and finalized AM service --- ctru-rs/examples/title-info.rs | 15 +--- ctru-rs/src/lib.rs | 5 +- ctru-rs/src/services/am.rs | 136 +++++++++++++++++++++------------ ctru-rs/src/services/mod.rs | 2 +- 4 files changed, 93 insertions(+), 65 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index a03c002..25b5c75 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -80,18 +80,9 @@ fn main() { // Move cursor to top left println!("\x1b[1;1"); - 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}\""), - Err(e) => println!("Failed to get product code: {}", e), - } + println!("Size: {} kB", selected_title.size() / 1024); + println!("Version: 0x{:x}", selected_title.version()); + println!("Product code: \"{}\"", selected_title.product_code()); println!("\x1b[26;0HPress START to exit"); if use_nand { diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 64ef0fa..b2267f0 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -4,14 +4,14 @@ //! //! This crate behaves as the main tool to access system-specific functionality on the Nintendo 3DS when developing homebrew software in Rust. //! Thanks to it, developers can access the underlying system services and the console's hardware to develop userland applications -//! (such as HID devices, network capabilities, graphics, built-in cameras, etc.). +//! (such as [HID devices](crate::services::hid), [network capabilities](crate::services::soc), [graphics](crate::services::gfx), [built-in cameras](crate::services::cam), etc.). //! //! Among these features, `ctru-rs` also automatically includes functionality to properly integrate the Rust `std` with the console's operating system, //! which the developer would otherwise need to implement manually. //! //! # Usage //! -//! Read thoroughly the official [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki) which guides you through the setup needed to install the required toolchain and helpful tools. +//! Thoroughly read the official [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki) which guides you through the setup needed to install the required toolchain and helpful tools. //! After following the guide and understanding the many quirks of the Nintendo 3DS homebrew development environment, you can create a new project by including this crate as a dependency //! of your project in your `Cargo.toml` manifest and build your binaries either manually (for the `armv6k-nintendo-3ds` target) or via [`cargo-3ds`](https://github.com/rust3ds/cargo-3ds). @@ -21,7 +21,6 @@ #![feature(test)] #![feature(custom_test_frameworks)] #![feature(try_trait_v2)] -#![feature(once_cell_try)] #![feature(allocator_api)] #![test_runner(test_runner::run)] #![doc( diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index 31d46b5..d570529 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -4,20 +4,19 @@ //! - Read the installed applications on the console and their information (depending on the install location). //! - Install compatible applications to the console. //! -//! `ctru-rs` doesn't support installing titles (yet). +//! TODO: `ctru-rs` doesn't support installing or uninstalling titles yet. use crate::error::ResultCode; use crate::services::fs::FsMediaType; -use std::cell::OnceCell; use std::marker::PhantomData; -use std::mem::MaybeUninit; -/// Struct holding general information about a specific title. +/// General information about a specific title entry. #[doc(alias = "AM_TitleEntry")] pub struct Title<'a> { id: u64, mediatype: FsMediaType, - entry: OnceCell, + size: u64, + version: u16, _am: PhantomData<&'a Am>, } @@ -29,54 +28,26 @@ impl<'a> Title<'a> { /// Returns this title's unique product code. #[doc(alias = "AM_GetTitleProductCode")] - pub fn product_code(&self) -> crate::Result { + pub fn product_code(&self) -> String { let mut buf: [u8; 16] = [0; 16]; + // This operation is safe as long as the title was correctly obtained via [`Am::title_list()`]. unsafe { - ResultCode(ctru_sys::AM_GetTitleProductCode( - self.mediatype.into(), - self.id, - buf.as_mut_ptr(), - ))?; + let _ = + ctru_sys::AM_GetTitleProductCode(self.mediatype.into(), self.id, buf.as_mut_ptr()); } - Ok(String::from_utf8_lossy(&buf).to_string()) - } - - /// Retrieves additional information on the title. - #[doc(alias = "AM_GetTitleInfo")] - fn title_info(&self) -> crate::Result { - let mut info = MaybeUninit::zeroed(); - - unsafe { - ResultCode(ctru_sys::AM_GetTitleInfo( - self.mediatype.into(), - 1, - &mut self.id.clone(), - info.as_mut_ptr() as _, - ))?; - Ok(info.assume_init()) - } + String::from_utf8_lossy(&buf).to_string() } /// 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) + pub fn size(&self) -> u64 { + self.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) + pub fn version(&self) -> u16 { + self.version } } @@ -84,7 +55,20 @@ impl<'a> Title<'a> { pub struct Am(()); impl Am { - /// Initialize a new handle. + /// Initialize a new service handle. + /// + /// # Example + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::am::Am; + /// + /// let app_manager = Am::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "amInit")] pub fn new() -> crate::Result { unsafe { @@ -94,6 +78,24 @@ impl Am { } /// Returns the amount of titles currently installed in a specific install location. + /// + /// # Example + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::{fs::FsMediaType, am::Am}; + /// let app_manager = Am::new()?; + /// + /// // Number of titles installed on the Nand storage. + /// let nand_count = app_manager.title_count(FsMediaType::Nand); + /// + /// // Number of apps installed on the SD card storage + /// let sd_count = app_manager.title_count(FsMediaType::Sd); + /// # + /// # Ok(()) + /// # } + /// ``` #[doc(alias = "AM_GetTitleCount")] pub fn title_count(&self, mediatype: FsMediaType) -> crate::Result { unsafe { @@ -104,11 +106,30 @@ impl Am { } /// Returns the list of titles installed in a specific install location. + /// + /// # Example + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::{fs::FsMediaType, am::Am}; + /// let app_manager = Am::new()?; + /// + /// // Number of apps installed on the SD card storage + /// let sd_titles = app_manager.title_list(FsMediaType::Sd)?; + /// + /// // Unique product code identifier of the 5th installed title. + /// let product_code = sd_titles[4].product_code(); + /// # + /// # Ok(()) + /// # } + /// ``` #[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]; let mut read_amount = 0; + unsafe { ResultCode(ctru_sys::AM_GetTitleList( &mut read_amount, @@ -117,13 +138,30 @@ impl Am { buf.as_mut_ptr(), ))?; } - Ok(buf + + let mut info: Vec = Vec::with_capacity(count as _); + + unsafe { + ResultCode(ctru_sys::AM_GetTitleInfo( + mediatype.into(), + count, + buf.as_mut_ptr(), + info.as_mut_ptr() as _, + ))?; + + info.set_len(count as _); + }; + + Ok(info .into_iter() - .map(|id| Title { - id, - mediatype, - entry: OnceCell::new(), - _am: PhantomData, + .map(|title| { + Title { + id: title.titleID, + mediatype, + size: title.size, + version: title.version, + _am: PhantomData, + } }) .collect()) } diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index ab948b3..9ff975e 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -3,7 +3,7 @@ //! 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. //! -//! To ensure safety measures when using the underlying services, `ctru-rs` leverages Rust's lifetime model. +//! To ensure safety while using the underlying services, `ctru-rs` 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),