diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs new file mode 100644 index 0000000..1f5654f --- /dev/null +++ b/ctru-rs/examples/title-info.rs @@ -0,0 +1,116 @@ +use ctru::prelude::*; +use ctru::services::am::Am; +use ctru::services::fs::FsMediaType; + +fn main() { + ctru::use_panic_handler(); + + 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 am = Am::init().expect("Couldn't obtain AM controller"); + let top_screen = Console::init(gfx.top_screen.borrow_mut()); + let bottom_screen = Console::init(gfx.bottom_screen.borrow_mut()); + + let sd_count = am + .get_title_count(FsMediaType::Sd) + .expect("Failed to get sd title count"); + let sd_list = am + .get_title_list(FsMediaType::Sd) + .expect("Failed to get sd title list"); + + let nand_count = am + .get_title_count(FsMediaType::Nand) + .expect("Failed to get nand title count"); + let nand_list = am + .get_title_list(FsMediaType::Nand) + .expect("Failed to get nand title list"); + + let mut offset = 0; + let mut refresh = true; + let mut use_nand = false; + + // 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; + } + if hid.keys_down().contains(KeyPad::KEY_SELECT) { + refresh = true; + offset = 0; + use_nand = !use_nand; + } + + let cur_list = if use_nand { &nand_list } else { &sd_list }; + + if hid.keys_down().intersects(KeyPad::KEY_DOWN) { + if offset + 1 < cur_list.len() { + offset = offset + 1; + refresh = true; + } + } else if hid.keys_down().intersects(KeyPad::KEY_UP) { + if offset > 0 { + offset = offset - 1; + refresh = true; + } + } + + if refresh { + let mut selected_title = cur_list.iter().skip(offset).next().unwrap(); + // Clear top screen and write title ids to it + top_screen.select(); + print!("\x1b[2J"); + + // Top screen seems to have only 30 rows + for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() { + if i == 0 { + selected_title = title; + println!("=> {:x}", title.id()); + } else { + println!(" {:x}", title.id()); + } + } + + // Clear bottom screen and write properties of selected title to it + bottom_screen.select(); + println!("\x1b[2J"); + // Move cursor to top left + println!("\x1b[1;1"); + + match selected_title.get_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.get_product_code() { + Ok(code) => println!("Product code: \"{code}\""), + Err(e) => println!("Failed to get product code: {}", e), + } + + println!("\x1b[26;0HPress START to exit"); + if use_nand { + println!("Press SELECT to choose SD Card"); + println!("Current medium: NAND"); + println!("Title count: {}", nand_count); + } else { + println!("Press SELECT to choose NAND"); + println!("Current medium: SD Card"); + println!("Title count: {}", sd_count); + } + + refresh = false; + } + + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs new file mode 100644 index 0000000..100b5fa --- /dev/null +++ b/ctru-rs/src/services/am.rs @@ -0,0 +1,106 @@ +use crate::error::ResultCode; +use crate::services::fs::FsMediaType; +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 + } +} + +pub struct Title<'a> { + id: u64, + mediatype: FsMediaType, + _am: PhantomData<&'a Am>, +} + +impl<'a> Title<'a> { + pub fn id(&self) -> u64 { + self.id + } + + pub fn get_product_code(&self) -> crate::Result { + let mut buf: [u8; 16] = [0; 16]; + + unsafe { + ResultCode(ctru_sys::AM_GetTitleProductCode( + self.mediatype as u32, + self.id, + buf.as_mut_ptr(), + ))?; + } + Ok(String::from_utf8_lossy(&buf).to_string()) + } + + pub fn get_title_info(&self) -> crate::Result { + let mut info = MaybeUninit::zeroed(); + + unsafe { + ResultCode(ctru_sys::AM_GetTitleInfo( + self.mediatype as u32, + 1, + &mut self.id.clone(), + info.as_mut_ptr() as _, + ))?; + + Ok(info.assume_init()) + } + } +} + +pub struct Am(()); + +impl Am { + pub fn init() -> crate::Result { + unsafe { + ResultCode(ctru_sys::amInit())?; + Ok(Am(())) + } + } + + pub fn get_title_count(&self, mediatype: FsMediaType) -> crate::Result { + unsafe { + let mut count = 0; + ResultCode(ctru_sys::AM_GetTitleCount(mediatype as u32, &mut count))?; + Ok(count) + } + } + + pub fn get_title_list(&self, mediatype: FsMediaType) -> crate::Result> { + let count = self.get_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, + mediatype as u32, + count, + buf.as_mut_ptr(), + ))?; + } + Ok(buf + .into_iter() + .map(|id| Title { + id, + mediatype, + _am: PhantomData, + }) + .collect()) + } +} + +impl Drop for Am { + fn drop(&mut self) { + unsafe { ctru_sys::amExit() }; + } +} diff --git a/ctru-rs/src/services/fs.rs b/ctru-rs/src/services/fs.rs index 64da9c3..6cd3728 100644 --- a/ctru-rs/src/services/fs.rs +++ b/ctru-rs/src/services/fs.rs @@ -43,6 +43,14 @@ bitflags! { } } +#[derive(Copy, Clone, Debug)] +#[repr(u32)] +pub enum FsMediaType { + Nand = ctru_sys::MEDIATYPE_NAND, + Sd = ctru_sys::MEDIATYPE_SD, + GameCard = ctru_sys::MEDIATYPE_GAME_CARD, +} + #[derive(Copy, Clone, Debug)] pub enum PathType { Invalid, diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index b0cc8ef..5abf93a 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -5,6 +5,7 @@ //! //! Some include: button input, audio playback, graphics rendering, built-in cameras, etc. +pub mod am; pub mod apt; pub mod cam; pub mod cfgu;