From 044cbeecc982dee6a300e02b73eaa9652530248d Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Tue, 28 Feb 2023 14:42:36 +0100 Subject: [PATCH 1/7] Add a basic AM service wrapper and an example that uses it --- ctru-rs/examples/title-info.rs | 37 +++++++++++++++++++++++++++++ ctru-rs/src/services/am.rs | 43 ++++++++++++++++++++++++++++++++++ ctru-rs/src/services/fs.rs | 8 +++++++ ctru-rs/src/services/mod.rs | 1 + 4 files changed, 89 insertions(+) create mode 100644 ctru-rs/examples/title-info.rs create mode 100644 ctru-rs/src/services/am.rs diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs new file mode 100644 index 0000000..2abb085 --- /dev/null +++ b/ctru-rs/examples/title-info.rs @@ -0,0 +1,37 @@ +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 _console = Console::init(gfx.top_screen.borrow_mut()); + + let title_count = am.get_title_count(FsMediaType::Sd).expect("Failed to get title count"); + println!("This 3DS has {title_count} titles on its SD Card:"); + + let title_list = am.get_title_list(FsMediaType::Sd).expect("Failed to get title list"); + for id in title_list { + println!("{id:x}"); + } + + // 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; + } + // 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..aea3927 --- /dev/null +++ b/ctru-rs/src/services/am.rs @@ -0,0 +1,43 @@ +use crate::error::ResultCode; +use crate::services::fs::FsMediaType; + +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> { + unsafe { + let count = self.get_title_count(mediatype)?; + let mut buf = Vec::with_capacity(count as usize); + let mut read_amount = 0; + ResultCode(ctru_sys::AM_GetTitleList( + &mut read_amount, + mediatype as u32, + count, + buf.as_mut_ptr(), + ))?; + buf.set_len(read_amount as usize); + Ok(buf) + } + } +} + +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..8346ea7 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -6,6 +6,7 @@ //! Some include: button input, audio playback, graphics rendering, built-in cameras, etc. pub mod apt; +pub mod am; pub mod cam; pub mod cfgu; pub mod fs; From 83b963d28bfbd5c80a009f71ac7e5d5d6ca42586 Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Tue, 28 Feb 2023 14:52:39 +0100 Subject: [PATCH 2/7] Make lint CI happy --- ctru-rs/examples/title-info.rs | 8 ++++++-- ctru-rs/src/services/mod.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index 2abb085..0faaadd 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -11,10 +11,14 @@ fn main() { let am = Am::init().expect("Couldn't obtain AM controller"); let _console = Console::init(gfx.top_screen.borrow_mut()); - let title_count = am.get_title_count(FsMediaType::Sd).expect("Failed to get title count"); + let title_count = am + .get_title_count(FsMediaType::Sd) + .expect("Failed to get title count"); println!("This 3DS has {title_count} titles on its SD Card:"); - let title_list = am.get_title_list(FsMediaType::Sd).expect("Failed to get title list"); + let title_list = am + .get_title_list(FsMediaType::Sd) + .expect("Failed to get title list"); for id in title_list { println!("{id:x}"); } diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 8346ea7..5abf93a 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -5,8 +5,8 @@ //! //! Some include: button input, audio playback, graphics rendering, built-in cameras, etc. -pub mod apt; pub mod am; +pub mod apt; pub mod cam; pub mod cfgu; pub mod fs; From 9eee38bdddd32771a9f6694c022f14e9da664e07 Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Wed, 1 Mar 2023 14:20:26 +0100 Subject: [PATCH 3/7] Make get_title_list function more readable --- ctru-rs/src/services/am.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index aea3927..cde4724 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -24,12 +24,14 @@ impl Am { let count = self.get_title_count(mediatype)?; let mut buf = Vec::with_capacity(count as usize); let mut read_amount = 0; + ResultCode(ctru_sys::AM_GetTitleList( &mut read_amount, mediatype as u32, count, buf.as_mut_ptr(), ))?; + buf.set_len(read_amount as usize); Ok(buf) } From 3ad9780d4cc2aea01c833ff228b7506a68bf36d7 Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Wed, 1 Mar 2023 21:32:22 +0100 Subject: [PATCH 4/7] Add more functionality to `Am` service wrapper and improve title-info.rs --- ctru-rs/examples/title-info.rs | 98 ++++++++++++++++++++++++++++++---- ctru-rs/src/services/am.rs | 56 +++++++++++++++++-- 2 files changed, 139 insertions(+), 15 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index 0faaadd..5bb6e76 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -9,19 +9,26 @@ fn main() { 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 _console = Console::init(gfx.top_screen.borrow_mut()); + let top_screen = Console::init(gfx.top_screen.borrow_mut()); + let bottom_screen = Console::init(gfx.bottom_screen.borrow_mut()); - let title_count = am + let sd_count = am .get_title_count(FsMediaType::Sd) - .expect("Failed to get title count"); - println!("This 3DS has {title_count} titles on its SD Card:"); - - let title_list = am + .expect("Failed to get sd title count"); + let sd_list = am .get_title_list(FsMediaType::Sd) - .expect("Failed to get title list"); - for id in title_list { - println!("{id:x}"); - } + .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() { @@ -31,6 +38,77 @@ fn main() { 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 = 0; + // 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, id) in cur_list.iter().skip(offset).take(29).enumerate() { + if i == 0 { + selected_title = *id; + println!("=> {id:x}"); + } else { + println!(" {id:x}"); + } + } + + // 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"); + let media = if use_nand { + FsMediaType::Nand + } else { + FsMediaType::Sd + }; + match am.get_title_info(media, &mut [selected_title]) { + Ok(info) => { + // Vec returned by Am::get_title_info always has same length as inputed slice + let info = info[0]; + println!("Size: {} KB", info.size_bytes() / 1024); + println!("Version: 0x{:x}", info.version()); + println!("Type: 0x{:x}", info.type_()); + } + Err(e) => println!("Failed to get title info: {}", 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(); diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index cde4724..89cc19a 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -1,6 +1,34 @@ use crate::error::ResultCode; use crate::services::fs::FsMediaType; +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct TitleInfo { + id: u64, + size: u64, + version: u16, + pad: u16, + type_: u32, +} + +// Make sure TitleInfo is correct size +const _TITLEINFO_SIZE_CHECK: [u8; 0x18] = [0; std::mem::size_of::()]; + +impl TitleInfo { + pub fn id(&self) -> u64 { + self.id + } + pub fn size_bytes(&self) -> u64 { + self.size + } + pub fn version(&self) -> u16 { + self.version + } + pub fn type_(&self) -> u32 { + self.type_ + } +} + pub struct Am(()); impl Am { @@ -20,11 +48,10 @@ impl Am { } pub fn get_title_list(&self, mediatype: FsMediaType) -> crate::Result> { + let count = self.get_title_count(mediatype)?; + let mut buf = Vec::with_capacity(count as usize); + let mut read_amount = 0; unsafe { - let count = self.get_title_count(mediatype)?; - let mut buf = Vec::with_capacity(count as usize); - let mut read_amount = 0; - ResultCode(ctru_sys::AM_GetTitleList( &mut read_amount, mediatype as u32, @@ -33,8 +60,27 @@ impl Am { ))?; buf.set_len(read_amount as usize); - Ok(buf) } + Ok(buf) + } + + pub fn get_title_info( + &self, + mediatype: FsMediaType, + id_list: &mut [u64], + ) -> crate::Result> { + let mut info = Vec::with_capacity(id_list.len()); + unsafe { + ResultCode(ctru_sys::AM_GetTitleInfo( + mediatype as u32, + id_list.len() as u32, + id_list.as_mut_ptr(), + info.as_mut_ptr() as _, + ))?; + + info.set_len(id_list.len()); + } + Ok(info) } } From 0fd7a340c8a53e4bbb4b0d305b104913822829c1 Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Wed, 1 Mar 2023 22:14:34 +0100 Subject: [PATCH 5/7] Add `Title` struct --- ctru-rs/examples/title-info.rs | 24 ++++++------ ctru-rs/src/services/am.rs | 72 ++++++++++++++++++++++++---------- 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index 5bb6e76..f4231a0 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -59,18 +59,18 @@ fn main() { } if refresh { - let mut selected_title = 0; + 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, id) in cur_list.iter().skip(offset).take(29).enumerate() { + for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() { if i == 0 { - selected_title = *id; - println!("=> {id:x}"); + selected_title = title; + println!("=> {:x}", title.id()); } else { - println!(" {id:x}"); + println!(" {:x}", title.id()); } } @@ -79,21 +79,19 @@ fn main() { println!("\x1b[2J"); // Move cursor to top left println!("\x1b[1;1"); - let media = if use_nand { - FsMediaType::Nand - } else { - FsMediaType::Sd - }; - match am.get_title_info(media, &mut [selected_title]) { + + match selected_title.get_title_info() { Ok(info) => { - // Vec returned by Am::get_title_info always has same length as inputed slice - let info = info[0]; println!("Size: {} KB", info.size_bytes() / 1024); println!("Version: 0x{:x}", info.version()); println!("Type: 0x{:x}", info.type_()); } 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 { diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index 89cc19a..c540a05 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -1,5 +1,7 @@ use crate::error::ResultCode; use crate::services::fs::FsMediaType; +use std::marker::PhantomData; +use std::mem::MaybeUninit; #[derive(Copy, Clone, Debug)] #[repr(C)] @@ -29,6 +31,46 @@ impl TitleInfo { } } +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 { @@ -47,7 +89,7 @@ impl Am { } } - pub fn get_title_list(&self, mediatype: FsMediaType) -> crate::Result> { + pub fn get_title_list(&self, mediatype: FsMediaType) -> crate::Result> { let count = self.get_title_count(mediatype)?; let mut buf = Vec::with_capacity(count as usize); let mut read_amount = 0; @@ -61,26 +103,14 @@ impl Am { buf.set_len(read_amount as usize); } - Ok(buf) - } - - pub fn get_title_info( - &self, - mediatype: FsMediaType, - id_list: &mut [u64], - ) -> crate::Result> { - let mut info = Vec::with_capacity(id_list.len()); - unsafe { - ResultCode(ctru_sys::AM_GetTitleInfo( - mediatype as u32, - id_list.len() as u32, - id_list.as_mut_ptr(), - info.as_mut_ptr() as _, - ))?; - - info.set_len(id_list.len()); - } - Ok(info) + Ok(buf + .into_iter() + .map(|id| Title { + id, + mediatype, + _am: PhantomData, + }) + .collect()) } } From 9b8f4239a7640bd9aa719ddcbe8bbcbea2e0a135 Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Fri, 3 Mar 2023 17:18:31 +0100 Subject: [PATCH 6/7] Make TitleInfo into a AM_TitleEntry wrapper --- ctru-rs/examples/title-info.rs | 1 - ctru-rs/src/services/am.rs | 23 +++++------------------ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index f4231a0..1f5654f 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -84,7 +84,6 @@ fn main() { Ok(info) => { println!("Size: {} KB", info.size_bytes() / 1024); println!("Version: 0x{:x}", info.version()); - println!("Type: 0x{:x}", info.type_()); } Err(e) => println!("Failed to get title info: {}", e), } diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index c540a05..5a52b99 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -4,30 +4,17 @@ use std::marker::PhantomData; use std::mem::MaybeUninit; #[derive(Copy, Clone, Debug)] -#[repr(C)] -pub struct TitleInfo { - id: u64, - size: u64, - version: u16, - pad: u16, - type_: u32, -} - -// Make sure TitleInfo is correct size -const _TITLEINFO_SIZE_CHECK: [u8; 0x18] = [0; std::mem::size_of::()]; - +#[repr(transparent)] +pub struct TitleInfo(ctru_sys::AM_TitleEntry); impl TitleInfo { pub fn id(&self) -> u64 { - self.id + self.0.titleID } pub fn size_bytes(&self) -> u64 { - self.size + self.0.size } pub fn version(&self) -> u16 { - self.version - } - pub fn type_(&self) -> u32 { - self.type_ + self.0.version } } From b6fec34b817777a4d6a0fd80a2fc09f0036b4c46 Mon Sep 17 00:00:00 2001 From: Maccraft123 Date: Fri, 3 Mar 2023 17:18:42 +0100 Subject: [PATCH 7/7] Use vec! macro to avoid setting vec length manually --- ctru-rs/src/services/am.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index 5a52b99..100b5fa 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -78,7 +78,7 @@ impl Am { pub fn get_title_list(&self, mediatype: FsMediaType) -> crate::Result> { let count = self.get_title_count(mediatype)?; - let mut buf = Vec::with_capacity(count as usize); + let mut buf = vec![0; count as usize]; let mut read_amount = 0; unsafe { ResultCode(ctru_sys::AM_GetTitleList( @@ -87,8 +87,6 @@ impl Am { count, buf.as_mut_ptr(), ))?; - - buf.set_len(read_amount as usize); } Ok(buf .into_iter()