From 564d75e518729721a9a0c2efb4e1a06f98b2c89a Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sun, 9 Jan 2022 17:31:11 -0800 Subject: [PATCH 1/7] Add file-explorer example to test file system via std Note: there's some issue in release mode causing a segfault. Currently investigating. --- ctru-rs/examples/file-explorer.rs | 156 ++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 ctru-rs/examples/file-explorer.rs diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs new file mode 100644 index 0000000..690c6a8 --- /dev/null +++ b/ctru-rs/examples/file-explorer.rs @@ -0,0 +1,156 @@ +//! A file explorer which shows off using standard library file system APIs to +//! read the SD card. + +use ctru::applets::swkbd::{Button, Swkbd}; +use ctru::console::Console; +use ctru::gfx::Screen; +use ctru::services::hid::KeyPad; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; +use std::fs::DirEntry; +use std::path::PathBuf; + +fn main() { + ctru::init(); + let apt = Apt::init().unwrap(); + let hid = Hid::init().unwrap(); + let gfx = Gfx::default(); + + FileExplorer::init(&apt, &hid, &gfx).run(); +} + +struct FileExplorer<'a> { + apt: &'a Apt, + hid: &'a Hid, + gfx: &'a Gfx, + console: Console<'a>, + path: PathBuf, + entries: Vec, + running: bool, +} + +impl<'a> FileExplorer<'a> { + fn init(apt: &'a Apt, hid: &'a Hid, gfx: &'a Gfx) -> Self { + gfx.set_wide_mode(true); + let console = Console::init(gfx, Screen::Top); + + FileExplorer { + apt, + hid, + gfx, + console, + path: PathBuf::from("/"), + entries: Vec::new(), + running: false, + } + } + + fn run(&mut self) { + self.running = true; + self.print_menu(); + + while self.running && self.apt.main_loop() { + self.hid.scan_input(); + let input = self.hid.keys_down(); + + if input.contains(KeyPad::KEY_START) { + break; + } else if input.contains(KeyPad::KEY_B) { + self.path.pop(); + self.console.clear(); + self.print_menu(); + } else if input.contains(KeyPad::KEY_A) { + self.get_next_path(); + } + + self.gfx.wait_for_vblank(); + } + } + + fn print_menu(&mut self) { + println!("Viewing {}", self.path.display()); + + let dir_listing = std::fs::read_dir(&self.path).expect("Failed to open path"); + self.entries = Vec::new(); + + for (i, entry) in dir_listing.enumerate() { + match entry { + Ok(entry) => { + println!("{:2} - {}", i, entry.file_name().to_string_lossy()); + self.entries.push(entry); + + // Paginate the output + if (i + 1) % 20 == 0 { + println!("Press A to go to next page, or Start to exit"); + + while self.apt.main_loop() { + self.hid.scan_input(); + let input = self.hid.keys_down(); + + if input.contains(KeyPad::KEY_A) { + break; + } + + if input.contains(KeyPad::KEY_START) { + self.running = false; + return; + } + + self.gfx.wait_for_vblank(); + } + } + } + Err(e) => { + println!("{} - Error: {}", i, e); + } + } + } + + println!("Start to exit, A to select an entry by number, B to go up a directory"); + } + + fn get_next_path(&mut self) { + let mut keyboard = Swkbd::default(); + let mut next_path_index = String::new(); + + match keyboard.get_utf8(&mut next_path_index) { + Ok(Button::Right) => { + // Clicked "OK" + let next_path_index: usize = match next_path_index.parse() { + Ok(index) => index, + Err(e) => { + println!("Number parsing error: {}", e); + return; + } + }; + + let next_entry = match self.entries.get(next_path_index) { + Some(entry) => entry, + None => { + println!("Input number of bounds"); + return; + } + }; + + if !next_entry.file_type().unwrap().is_dir() { + println!("Not a directory: {}", next_path_index); + return; + } + + self.console.clear(); + self.path = next_entry.path(); + self.print_menu(); + } + Ok(Button::Left) => { + // Clicked "Cancel" + } + Ok(Button::Middle) => { + // This button wasn't shown + unreachable!() + } + Err(e) => { + panic!("Error: {:?}", e) + } + } + } +} From ea4410a0c4c7f9dc43f23060b99443cc426eea50 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 22 Jan 2022 18:39:38 -0800 Subject: [PATCH 2/7] Make standard gfx calls on every frame in file-explorer --- ctru-rs/examples/file-explorer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 988283b..845e56d 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -62,6 +62,8 @@ impl<'a> FileExplorer<'a> { self.get_next_path(); } + self.gfx.flush_buffers(); + self.gfx.swap_buffers(); self.gfx.wait_for_vblank(); } } From 07ed1dc1962bfbb35171b6ac7cc9bc5b0354bdad Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 22 Jan 2022 18:40:19 -0800 Subject: [PATCH 3/7] Add basic RomFS support Seems to crash the 3DS if there is no actual RomFS data in the 3dsx file, so there's room for improvement. --- ctru-rs/src/lib.rs | 1 + ctru-rs/src/romfs.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 ctru-rs/src/romfs.rs diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index c2c8243..eef067d 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -34,6 +34,7 @@ pub mod console; pub mod error; pub mod gfx; pub mod sdmc; +pub mod romfs; pub mod services; pub mod srv; pub mod thread; diff --git a/ctru-rs/src/romfs.rs b/ctru-rs/src/romfs.rs new file mode 100644 index 0000000..09cfaa1 --- /dev/null +++ b/ctru-rs/src/romfs.rs @@ -0,0 +1,24 @@ +use std::ffi::CStr; + +#[non_exhaustive] +pub struct RomFS; + +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) + } + } +} + +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()) }; + } +} \ No newline at end of file From 1e7326b32ba6cb77f2a2288a19f3e520b83af9a9 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sat, 22 Jan 2022 18:40:53 -0800 Subject: [PATCH 4/7] Add RomFS support to the file-explorer example (by jumping to romfs:/) --- ctru-rs/examples/file-explorer.rs | 80 +++++++++++++++++++------------ romfs/test-file.txt | 1 + 2 files changed, 51 insertions(+), 30 deletions(-) create mode 100644 romfs/test-file.txt diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 845e56d..c67c1f4 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -3,17 +3,19 @@ use ctru::applets::swkbd::{Button, Swkbd}; use ctru::console::Console; +use ctru::romfs::RomFS; use ctru::services::hid::KeyPad; use ctru::services::{Apt, Hid}; use ctru::Gfx; use std::fs::DirEntry; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; fn main() { ctru::init(); let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::default(); + let _romfs = RomFS::new().unwrap(); FileExplorer::init(&apt, &hid, &gfx).run(); } @@ -59,7 +61,9 @@ impl<'a> FileExplorer<'a> { self.console.clear(); self.print_menu(); } else if input.contains(KeyPad::KEY_A) { - self.get_next_path(); + self.get_input_and_run(Self::set_next_path); + } else if input.contains(KeyPad::KEY_X) { + self.get_input_and_run(Self::set_exact_path); } self.gfx.flush_buffers(); @@ -107,40 +111,17 @@ impl<'a> FileExplorer<'a> { } } - println!("Start to exit, A to select an entry by number, B to go up a directory"); + println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path."); } - fn get_next_path(&mut self) { + fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { let mut keyboard = Swkbd::default(); - let mut next_path_index = String::new(); + let mut new_path_str = String::new(); - match keyboard.get_utf8(&mut next_path_index) { + match keyboard.get_utf8(&mut new_path_str) { Ok(Button::Right) => { // Clicked "OK" - let next_path_index: usize = match next_path_index.parse() { - Ok(index) => index, - Err(e) => { - println!("Number parsing error: {}", e); - return; - } - }; - - let next_entry = match self.entries.get(next_path_index) { - Some(entry) => entry, - None => { - println!("Input number of bounds"); - return; - } - }; - - if !next_entry.file_type().unwrap().is_dir() { - println!("Not a directory: {}", next_path_index); - return; - } - - self.console.clear(); - self.path = next_entry.path(); - self.print_menu(); + action(self, new_path_str); } Ok(Button::Left) => { // Clicked "Cancel" @@ -154,4 +135,43 @@ impl<'a> FileExplorer<'a> { } } } + + fn set_next_path(&mut self, next_path_index: String) { + let next_path_index: usize = match next_path_index.parse() { + Ok(index) => index, + Err(e) => { + println!("Number parsing error: {}", e); + return; + } + }; + + let next_entry = match self.entries.get(next_path_index) { + Some(entry) => entry, + None => { + println!("Input number of bounds"); + return; + } + }; + + if !next_entry.file_type().unwrap().is_dir() { + println!("Not a directory: {}", next_path_index); + return; + } + + self.console.clear(); + self.path = next_entry.path(); + self.print_menu(); + } + + fn set_exact_path(&mut self, new_path_str: String) { + let new_path = Path::new(&new_path_str); + if !new_path.is_dir() { + println!("Not a directory: {}", new_path_str); + return; + } + + self.console.clear(); + self.path = new_path.to_path_buf(); + self.print_menu(); + } } diff --git a/romfs/test-file.txt b/romfs/test-file.txt new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/romfs/test-file.txt @@ -0,0 +1 @@ +test \ No newline at end of file From 39de31a662fec6c1ea7fbf7912697cb502fb2c46 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Sun, 23 Jan 2022 19:07:51 -0800 Subject: [PATCH 5/7] Support conditional compilation of RomFS If the user tries to open the RomFS when one has not been added, it will result in a segfault (libctru doesn't verify the RomFS actually is configured). This avoids that scenario when used in conjunction with cargo-3ds. An alternative approach would be to do as libctru does and read the 3dsx file header, and use that to determine if RomFS is enabled at runtime. This would require more code, but it would be more accurate and compatible (wouldn't require a tool like cargo-3ds to ensure the RomFS is added). --- ctru-rs/Cargo.toml | 7 ++++++ ctru-rs/build.rs | 32 ++++++++++++++++++++++++++ ctru-rs/examples/file-explorer.rs | 5 ++-- {romfs => ctru-rs/romfs}/test-file.txt | 0 ctru-rs/src/lib.rs | 17 +++++++++++++- ctru-rs/src/romfs.rs | 14 ++++++++++- 6 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 ctru-rs/build.rs rename {romfs => ctru-rs/romfs}/test-file.txt (100%) diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 29323b4..5e29ef1 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -19,5 +19,12 @@ libc = { git = "https://github.com/Meziu/libc.git" } bitflags = "1.0.0" widestring = "0.2.2" +[build-dependencies] +toml = "0.5" + [dev-dependencies] ferris-says = "0.2.1" + +[features] +default = ["romfs"] +romfs = [] diff --git a/ctru-rs/build.rs b/ctru-rs/build.rs new file mode 100644 index 0000000..ecdef33 --- /dev/null +++ b/ctru-rs/build.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +fn main() { + // Open Cargo.toml + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest_path = format!("{manifest_dir}/Cargo.toml"); + let manifest_str = std::fs::read_to_string(&manifest_path) + .unwrap_or_else(|e| panic!("Could not open {manifest_path}: {e}")); + let manifest_data: toml::Value = + toml::de::from_str(&manifest_str).expect("Could not parse Cargo manifest as TOML"); + + // Find the romfs setting and compute the path + let romfs_dir_setting = manifest_data + .as_table() + .and_then(|table| table.get("package")) + .and_then(toml::Value::as_table) + .and_then(|table| table.get("metadata")) + .and_then(toml::Value::as_table) + .and_then(|table| table.get("cargo-3ds")) + .and_then(toml::Value::as_table) + .and_then(|table| table.get("romfs_dir")) + .and_then(toml::Value::as_str) + .unwrap_or("romfs"); + let romfs_path = PathBuf::from(format!("{manifest_dir}/{romfs_dir_setting}")); + + // Check if the romfs path exists so we can compile the module + if romfs_path.exists() { + println!("cargo:rustc-cfg=romfs_exists"); + } + + println!("cargo:rerun-if-changed={}", manifest_dir); +} diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index c67c1f4..8a249f9 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -3,7 +3,6 @@ use ctru::applets::swkbd::{Button, Swkbd}; use ctru::console::Console; -use ctru::romfs::RomFS; use ctru::services::hid::KeyPad; use ctru::services::{Apt, Hid}; use ctru::Gfx; @@ -15,7 +14,9 @@ fn main() { let apt = Apt::init().unwrap(); let hid = Hid::init().unwrap(); let gfx = Gfx::default(); - let _romfs = RomFS::new().unwrap(); + + #[cfg(all(feature = "romfs", romfs_exists))] + let _romfs = ctru::romfs::RomFS::new().unwrap(); FileExplorer::init(&apt, &hid, &gfx).run(); } diff --git a/romfs/test-file.txt b/ctru-rs/romfs/test-file.txt similarity index 100% rename from romfs/test-file.txt rename to ctru-rs/romfs/test-file.txt diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index eef067d..a323060 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -34,11 +34,26 @@ pub mod console; pub mod error; pub mod gfx; pub mod sdmc; -pub mod romfs; pub mod services; pub mod srv; pub mod thread; +#[cfg(all(feature = "romfs", romfs_exists))] +pub mod romfs; +#[cfg(not(all(feature = "romfs", romfs_exists)))] +pub mod romfs { + //! The RomFS folder has not been detected and/or the `romfs` feature has not been enabled. + //! + //! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the + //! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of + //! your package. + //! + //! ```toml + //! [package.metadata.cargo-3ds] + //! romfs_dir = "romfs" + //! ``` +} + pub use crate::error::{Error, Result}; pub use crate::gfx::Gfx; diff --git a/ctru-rs/src/romfs.rs b/ctru-rs/src/romfs.rs index 09cfaa1..12190b9 100644 --- a/ctru-rs/src/romfs.rs +++ b/ctru-rs/src/romfs.rs @@ -1,3 +1,15 @@ +//! This module only gets compiled if the configured RomFS directory is found and the `romfs` +//! feature is enabled. +//! +//! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the +//! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of +//! your package. +//! +//! ```toml +//! [package.metadata.cargo-3ds] +//! romfs_dir = "romfs" +//! ``` + use std::ffi::CStr; #[non_exhaustive] @@ -21,4 +33,4 @@ impl Drop for RomFS { let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; } -} \ No newline at end of file +} From 4b95068faccdab26a411bcee17c2a59aec9fe7bc Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 24 Jan 2022 16:45:07 -0800 Subject: [PATCH 6/7] Remove the sdmc module The SD card is automatically mounted by libctru on startup, so this isn't needed anymore. --- ctru-rs/src/lib.rs | 2 -- ctru-rs/src/sdmc.rs | 20 -------------------- 2 files changed, 22 deletions(-) delete mode 100644 ctru-rs/src/sdmc.rs diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index a323060..6669c60 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -33,7 +33,6 @@ pub mod applets; pub mod console; pub mod error; pub mod gfx; -pub mod sdmc; pub mod services; pub mod srv; pub mod thread; @@ -57,5 +56,4 @@ pub mod romfs { pub use crate::error::{Error, Result}; pub use crate::gfx::Gfx; -pub use crate::sdmc::Sdmc; pub use crate::srv::Srv; diff --git a/ctru-rs/src/sdmc.rs b/ctru-rs/src/sdmc.rs deleted file mode 100644 index eaef331..0000000 --- a/ctru-rs/src/sdmc.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub struct Sdmc(()); - -impl Sdmc { - pub fn init() -> crate::Result { - unsafe { - let r = ctru_sys::archiveMountSdmc(); - if r < 0 { - Err(r.into()) - } else { - Ok(Sdmc(())) - } - } - } -} - -impl Drop for Sdmc { - fn drop(&mut self) { - unsafe { ctru_sys::archiveUnmountAll() }; - } -} From 1c66a1c770f70cb15811adcb3de12ae67d87b6a1 Mon Sep 17 00:00:00 2001 From: AzureMarker Date: Mon, 24 Jan 2022 17:27:18 -0800 Subject: [PATCH 7/7] Use cfg-if --- ctru-rs/Cargo.toml | 1 + ctru-rs/src/lib.rs | 31 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 5e29ef1..24f1efa 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["rlib"] name = "ctru" [dependencies] +cfg-if = "1.0" ctru-sys = { path = "../ctru-sys", version = "0.4" } const-zero = "0.1.0" linker-fix-3ds = { git = "https://github.com/Meziu/rust-linker-fix-3ds.git" } diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 6669c60..23be6b8 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -37,20 +37,23 @@ pub mod services; pub mod srv; pub mod thread; -#[cfg(all(feature = "romfs", romfs_exists))] -pub mod romfs; -#[cfg(not(all(feature = "romfs", romfs_exists)))] -pub mod romfs { - //! The RomFS folder has not been detected and/or the `romfs` feature has not been enabled. - //! - //! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the - //! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of - //! your package. - //! - //! ```toml - //! [package.metadata.cargo-3ds] - //! romfs_dir = "romfs" - //! ``` +cfg_if::cfg_if! { + if #[cfg(all(feature = "romfs", romfs_exists))] { + pub mod romfs; + } else { + pub mod romfs { + //! The RomFS folder has not been detected and/or the `romfs` feature has not been enabled. + //! + //! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the + //! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of + //! your package. + //! + //! ```toml + //! [package.metadata.cargo-3ds] + //! romfs_dir = "romfs" + //! ``` + } + } } pub use crate::error::{Error, Result};