diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de60e6b..ee97ccc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: matrix: toolchain: # Run against a "known good" nightly - - nightly-2023-01-13 + - nightly-2023-05-31 # Check for breakage on latest nightly - nightly @@ -57,7 +57,7 @@ jobs: strategy: matrix: toolchain: - - nightly-2023-01-13 + - nightly-2023-05-31 - nightly continue-on-error: ${{ matrix.toolchain == 'nightly' }} runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 54f1838..f4d733b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ Cargo.lock # IDE files .idea +.vscode diff --git a/Cargo.toml b/Cargo.toml index 2200c78..889c913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = ["ctru-rs", "ctru-sys"] default-members = ["ctru-rs", "ctru-sys"] +resolver = "2" [patch.'https://github.com/rust3ds/ctru-rs'] # Make sure all dependencies use the local ctru-sys package diff --git a/README.md b/README.md index abd23dd..8a061b4 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,27 @@ # ctru-rs -A Rust wrapper library for smealum's [ctrulib](https://github.com/smealum/ctrulib). +This repository is home of the `ctru-rs` project, which aims to bring full control of the Nintendo 3DS console to homebrew developers using Rust. ## Structure This repository is organized as follows: -* `ctru-rs`: Safe, idiomatic wrapper around `ctru-sys` + * [`ctru-rs`](./ctru-rs/) - Safe, idiomatic wrapper around [`ctru-sys`](./ctru-sys/). + * [`ctru-sys`](./ctru-sys/) - Low-level, unsafe bindings to [`libctru`](https://github.com/devkitPro/libctru). -* `ctru-sys`: Low-level, unsafe bindings to ctrulib. +## Getting Started - This crate's version changes according to the version of `libctru` - used to generate the bindings, with the following convention: - - * `libctru` version `X.Y.Z-W` - * `ctru-sys` version `XY.Z.P+X.Y.Z-W` - - where `P` is usually 0 but may be incremented for fixes in e.g. - binding generation, `libc` dependency bump, etc. - - It may be possible to build this crate against a different version of `libctru`, - but you may encounter linker errors or ABI issues. A build-time Cargo warning - (displayed when built with `-vv`) will be issued if the build script detects - a mismatch or is unable to check the installed `libctru` version. +Specific information about how to use the crates is present in the individual README for each package. +Have a look at `ctru-rs`' [README.md](./ctru-rs/README.md) for a broad overview. ## Original version -This project is based on the efforts the original authors: - * [Eidolon](https://github.com/HybridEidolon) - * [FenrirWolf](https://github.com/FenrirWolf) +This project is based on the efforts of the original authors: + * [Eidolon](https://github.com/HybridEidolon) + * [FenrirWolf](https://github.com/FenrirWolf) The old version is archived [here](https://github.com/rust3ds/ctru-rs-old). + +## License + +This project is distributed under the Zlib license. diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 2811900..0523b27 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -1,11 +1,15 @@ [package] -authors = ["Rust3DS Org", "Ronald Kinard "] -description = "A safe wrapper around smealum's ctrulib." -license = "Zlib" name = "ctru-rs" version = "0.7.1" +authors = ["Rust3DS Org", "Ronald Kinard "] +description = "A safe wrapper around libctru" +repository = "https://github.com/rust3ds/ctru-rs" +keywords = ["3ds", "libctru"] +categories = ["os", "api-bindings", "hardware-support"] +exclude = ["examples"] +license = "Zlib" edition = "2021" -rust-version = "1.64" +rust-version = "1.70" [lib] crate-type = ["rlib"] @@ -18,7 +22,7 @@ const-zero = "0.1.0" shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } libc = "0.2.121" -bitflags = "1.0.0" +bitflags = "2.3.3" widestring = "0.2.2" [build-dependencies] @@ -45,6 +49,11 @@ std-threads = [] [package.metadata.cargo-3ds] romfs_dir = "examples/romfs" +[package.metadata.docs.rs] +default-target = "armv6k-nintendo-3ds" +targets = [] +cargo-args = ["-Z", "build-std"] + [[example]] name = "thread-basic" required-features = ["std-threads"] diff --git a/ctru-rs/README.md b/ctru-rs/README.md new file mode 100644 index 0000000..0cc8a79 --- /dev/null +++ b/ctru-rs/README.md @@ -0,0 +1,22 @@ +# ctru-rs + +Safe and idiomatic Rust wrapper around [`libctru`](https://github.com/devkitPro/libctru). + +## Getting Started + +Thoroughly read the [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki/Getting-Started) to meet the requirements +and to understand what it takes to develop homebrew software on the Nintendo 3DS family of consoles. +After that, you can simply add the crate as a dependency to your project and build your final binary by using [`cargo-3ds`](https://github.com/rust3ds/cargo-3ds) +or by manually compiling for the `armv6k-nintendo-3ds` target. + +## Examples + +Many examples to demonstrate the `ctru-rs` functionality are available in the [`examples`](./examples/) folder. Simply run them via + +```bash +cargo 3ds run --example +``` + +## License + +This project is distributed under the Zlib license. diff --git a/ctru-rs/examples/audio-filters.rs b/ctru-rs/examples/audio-filters.rs index 3ef76a6..75941b7 100644 --- a/ctru-rs/examples/audio-filters.rs +++ b/ctru-rs/examples/audio-filters.rs @@ -1,3 +1,7 @@ +//! Audio Filters example. +//! +//! This example showcases basic audio functionality using [`Ndsp`]. + #![feature(allocator_api)] use std::f32::consts::PI; @@ -5,21 +9,22 @@ use std::f32::consts::PI; use ctru::linear::LinearAllocator; use ctru::prelude::*; use ctru::services::ndsp::{ - wave::{Wave, WaveStatus}, + wave::{Status, Wave}, AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode, }; +// Configuration for the NDSP process and channels. const SAMPLE_RATE: usize = 22050; const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205 const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size(); const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE; -// Note Frequencies +// Note frequencies. const NOTEFREQ: [f32; 7] = [220., 440., 880., 1760., 3520., 7040., 14080.]; -// The audio format is Stereo PCM16 -// As such, a sample is made up of 2 "Mono" samples (2 * i16 = u32), one for each channel (left and right) fn fill_buffer(audio_data: &mut [u8], frequency: f32) { + // The audio format is Stereo PCM16. + // As such, a sample is made up of 2 "Mono" samples (2 * i16), one for each channel (left and right). let formatted_data = bytemuck::cast_slice_mut::<_, [i16; 2]>(audio_data); for (i, chunk) in formatted_data.iter_mut().enumerate() { @@ -44,8 +49,7 @@ fn main() { let mut note: usize = 4; - // Filters - + // Filter names to display. let filter_names = [ "None", "Low-Pass", @@ -60,19 +64,26 @@ fn main() { // We set up two wave buffers and alternate between the two, // effectively streaming an infinitely long sine wave. + // We create a buffer on the LINEAR memory that will hold our audio data. + // It's necessary for the buffer to live on the LINEAR memory sector since it needs to be accessed by the DSP processor. let mut audio_data1 = Box::new_in([0u8; AUDIO_WAVE_LENGTH], LinearAllocator); + + // Fill the buffer with the first set of data. This simply writes a sine wave into the buffer. fill_buffer(audio_data1.as_mut_slice(), NOTEFREQ[4]); + // Clone the original buffer to obtain an equal buffer on the LINEAR memory used for double buffering. let audio_data2 = audio_data1.clone(); + // Setup two wave info objects with the correct configuration and ownership of the audio data. let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false); let mut wave_info2 = Wave::new(audio_data2, AudioFormat::PCM16Stereo, false); - let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller"); + // Setup the NDSP service and its configuration. - // This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo` + let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller"); ndsp.set_output_mode(OutputMode::Stereo); + // Channel configuration. We use channel zero but any channel would do just fine. let mut channel_zero = ndsp.channel(0).unwrap(); channel_zero.set_interpolation(InterpolationType::Linear); channel_zero.set_sample_rate(SAMPLE_RATE as f32); @@ -82,6 +93,7 @@ fn main() { let mix = AudioMix::default(); channel_zero.set_mix(&mix); + // First set of queueing for the two buffers. The second one will only play after the first one has ended. channel_zero.queue_wave(&mut wave_info1).unwrap(); channel_zero.queue_wave(&mut wave_info2).unwrap(); @@ -93,6 +105,8 @@ fn main() { filter_names[filter as usize] ); + println!("\x1b[29;16HPress Start to exit"); + let mut altern = true; // true is wave_info1, false is wave_info2 while apt.main_loop() { @@ -101,14 +115,16 @@ fn main() { if keys_down.contains(KeyPad::START) { break; - } // break in order to return to hbmenu + } + // Note frequency controller using the buttons. if keys_down.intersects(KeyPad::DOWN) { note = note.saturating_sub(1); } else if keys_down.intersects(KeyPad::UP) { note = std::cmp::min(note + 1, NOTEFREQ.len() - 1); } + // Filter controller using the buttons. let mut update_params = false; if keys_down.intersects(KeyPad::LEFT) { filter -= 1; @@ -139,14 +155,16 @@ fn main() { } } + // Double buffer alternation depending on the one used. let current: &mut Wave = if altern { &mut wave_info1 } else { &mut wave_info2 }; + // If the current buffer has finished playing, we can refill it with new data and re-queue it. let status = current.status(); - if let WaveStatus::Done = status { + if let Status::Done = status { fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); channel_zero.queue_wave(current).unwrap(); @@ -154,7 +172,6 @@ fn main() { altern = !altern; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 8bba6c7..0de9e7c 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -1,3 +1,7 @@ +//! Buttons example. +//! +//! This example showcases how to retrieve button inputs from the console's HID. + use ctru::prelude::*; fn main() { @@ -18,28 +22,28 @@ fn main() { // Scan for user input on the current frame. hid.scan_input(); - // Get information about which keys were held down on this frame + // Get information about which keys were held down on this frame. let keys = hid.keys_held(); // We only want to print when the keys we're holding now are different - // from what they were on the previous frame + // from what they were on the previous frame. if keys != old_keys { - // Clear the screen + // Clear the screen. console.clear(); - // We print these again because we just cleared the screen above + // We print these again because we just cleared the screen above. println!("Hi there! Try pressing a button"); println!("\x1b[29;16HPress Start to exit"); - // Move the cursor back to the top of the screen + // Move the cursor back to the top of the screen. println!("\x1b[3;0H"); // Print to the screen depending on which keys were held. // - // The .contains() method checks for all of the provided keys, - // and the .intersects() method checks for any of the provided keys. + // The `.contains()` method checks for all of the provided keys, + // and the `.intersects()` method checks for any of the provided keys. // - // You can also use the .bits() method to do direct comparisons on + // You can also use the `.bits()` method to do direct comparisons on // the underlying bits if keys.contains(KeyPad::A) { @@ -54,13 +58,13 @@ fn main() { if keys.intersects(KeyPad::L | KeyPad::R | KeyPad::ZL | KeyPad::ZR) { println!("You held a shoulder button!"); } - if keys.intersects(KeyPad::START) { + if keys.contains(KeyPad::START) { println!("See ya!"); break; } } - // Save our current key presses for the next frame + // Save our current key presses for the next frame. old_keys = keys; gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs index 0042599..851aa2a 100644 --- a/ctru-rs/examples/camera-image.rs +++ b/ctru-rs/examples/camera-image.rs @@ -1,3 +1,7 @@ +//! Camera image example. +//! +//! This example demonstrates how to use the built-in cameras to take a picture and display it to the screen. + use ctru::prelude::*; use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; use ctru::services::gfx::{Flush, Screen, Swap}; @@ -8,7 +12,7 @@ use std::time::Duration; const WIDTH: usize = 400; const HEIGHT: usize = 240; -// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes) +// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes). const BUF_SIZE: usize = WIDTH * HEIGHT * 2; const WAIT_TIMEOUT: Duration = Duration::from_millis(300); @@ -26,12 +30,11 @@ fn main() { let _console = Console::new(gfx.bottom_screen.borrow_mut()); - let mut keys_down; - println!("Initializing camera"); let mut cam = Cam::new().expect("Failed to initialize CAM service."); + // Camera setup. { let camera = &mut cam.outer_right_cam; @@ -58,21 +61,23 @@ fn main() { let mut buf = vec![0u8; BUF_SIZE]; println!("\nPress R to take a new picture"); - println!("Press Start to exit to Homebrew Launcher"); + println!("Press Start to exit"); while apt.main_loop() { hid.scan_input(); - keys_down = hid.keys_down(); + let keys_down = hid.keys_down(); if keys_down.contains(KeyPad::START) { break; } + // If the user presses the R button. if keys_down.contains(KeyPad::R) { println!("Capturing new image"); let camera = &mut cam.outer_right_cam; + // Take a picture and write it to the buffer. camera .take_picture( &mut buf, @@ -82,12 +87,14 @@ fn main() { ) .expect("Failed to take picture"); + // Play the normal shutter sound. cam.play_shutter_sound(ShutterSound::Normal) .expect("Failed to play shutter sound"); + // Rotate the image and correctly display it on the screen. rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT); - // We will only flush the "camera" screen, since the other screen is handled by `Console` + // We will only flush and swap the "camera" screen, since the other screen is handled by the `Console`. top_screen.flush_buffers(); top_screen.swap_buffers(); @@ -99,6 +106,7 @@ fn main() { // The 3DS' screens are 2 vertical LCD panels rotated by 90 degrees. // As such, we'll need to write a "vertical" image to the framebuffer to have it displayed properly. // This functions rotates an horizontal image by 90 degrees to the right. +// This function is only supposed to be used in this example. In a real world application, the program should use the GPU to draw to the screen. fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: usize) { for j in 0..height { for i in 0..width { @@ -115,7 +123,7 @@ fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: u let draw_index = (draw_x * height + draw_y) * 2; // This 2 stands for the number of bytes per pixel (16 bits) unsafe { - // We'll work with pointers since the frambuffer is a raw pointer regardless. + // We'll work with pointers since the framebuffer is a raw pointer regardless. // The offsets are completely safe as long as the width and height are correct. let pixel_pointer = framebuf.offset(draw_index as isize); pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2); diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index 6d30590..6fa1183 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -1,7 +1,9 @@ -//! A file explorer which shows off using standard library file system APIs to -//! read the SD card. +//! File Explorer example. +//! +//! This (rather complex) example creates a working text-based file explorer which shows off using standard library file system APIs to +//! read the SD card and RomFS (if properly read via the `romfs:/` prefix). -use ctru::applets::swkbd::{Button, Swkbd}; +use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; use std::fs::DirEntry; @@ -15,6 +17,7 @@ fn main() { let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); + // Mount the RomFS if available. #[cfg(all(feature = "romfs", romfs_exists))] let _romfs = ctru::services::romfs::RomFS::new().unwrap(); @@ -50,6 +53,7 @@ impl<'a> FileExplorer<'a> { fn run(&mut self) { self.running = true; + // Print the file explorer commands. self.print_menu(); while self.running && self.apt.main_loop() { @@ -62,8 +66,10 @@ impl<'a> FileExplorer<'a> { self.path.pop(); self.console.clear(); self.print_menu(); + // Open a directory/file to read. } else if input.contains(KeyPad::A) { self.get_input_and_run(Self::set_next_path); + // Open a specific path using the `SoftwareKeyboard` applet. } else if input.contains(KeyPad::X) { self.get_input_and_run(Self::set_exact_path); } @@ -100,7 +106,7 @@ impl<'a> FileExplorer<'a> { } }; - println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path."); + println!("Press Start to exit, A to select an entry by number, B to go up a directory, X to set the path."); } fn print_dir_entries(&mut self) { @@ -137,7 +143,7 @@ impl<'a> FileExplorer<'a> { } } - /// Paginate output + // Paginate output. fn wait_for_page_down(&mut self) { println!("Press A to go to next page, or Start to exit"); @@ -159,18 +165,18 @@ impl<'a> FileExplorer<'a> { } fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { - let mut keyboard = Swkbd::default(); + let mut keyboard = SoftwareKeyboard::default(); match keyboard.get_string(2048) { Ok((path, Button::Right)) => { - // Clicked "OK" + // Clicked "OK". action(self, path); } Ok((_, Button::Left)) => { - // Clicked "Cancel" + // Clicked "Cancel". } Ok((_, Button::Middle)) => { - // This button wasn't shown + // This button wasn't shown. unreachable!() } Err(e) => { diff --git a/ctru-rs/examples/gfx-3d-mode.rs b/ctru-rs/examples/gfx-3d-mode.rs index 582994e..7176085 100644 --- a/ctru-rs/examples/gfx-3d-mode.rs +++ b/ctru-rs/examples/gfx-3d-mode.rs @@ -1,11 +1,18 @@ +//! 3D Graphics example. +//! +//! This example showcases 3D mode rendering (using the CPU). +//! In a normal application, all rendering should be handled via the GPU. +//! +//! See `gfx-bitmap.rs` for details on how the image is generated. +//! +//! # Warning +//! +//! This example uses 3D mode in a rather unnatural way, and should +//! probably not be viewed for too long or at all if you are photosensitive. + use ctru::prelude::*; use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D}; -/// See `graphics-bitmap.rs` for details on how the image is generated. -/// -/// WARNING: this example uses 3D mode in a rather unnatural way, and should -/// probably not be viewed for too long or at all if you are photosensitive. - const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); static ZERO: &[u8] = &[0; IMAGE.len()]; @@ -17,7 +24,9 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.bottom_screen.borrow_mut()); - println!("Press Start to exit.\nPress A to switch sides (be sure to have 3D mode enabled)."); + println!("Press A to switch sides."); + println!("Make sure to have set the 3D slider correctly"); + println!("\x1b[29;12HPress Start to exit"); gfx.top_screen.borrow_mut().set_double_buffering(true); @@ -25,39 +34,40 @@ fn main() { let mut current_side = Side::Left; - // 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::START) { break; } + // Split the TopScreen3D to get references to the two render surfaces. let (mut left, mut right) = top_screen.split_mut(); let left_buf = left.raw_framebuffer(); let right_buf = right.raw_framebuffer(); - // Clear both buffers every time, in case the user switches sides this loop + // Clear both buffers every time, in case the user switches sides this loop. unsafe { left_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); right_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); } if hid.keys_down().contains(KeyPad::A) { - // flip which buffer we're writing to + // Switch which buffer we're writing to. current_side = match current_side { Side::Left => Side::Right, Side::Right => Side::Left, }; } + // Obtain the framebuffer of the currently rendered side. let buf = match current_side { Side::Left => left_buf.ptr, Side::Right => right_buf.ptr, }; + // Render the image to the surface's buffer. unsafe { buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); } @@ -67,7 +77,6 @@ fn main() { top_screen.flush_buffers(); top_screen.swap_buffers(); - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/gfx-bitmap.rs b/ctru-rs/examples/gfx-bitmap.rs index f680ff2..8efb2be 100644 --- a/ctru-rs/examples/gfx-bitmap.rs +++ b/ctru-rs/examples/gfx-bitmap.rs @@ -1,3 +1,6 @@ +/// Bitmap Graphics example. +/// +/// This example uses the CPU to render a simple bitmap image to the screen. use ctru::prelude::*; use ctru::services::gfx::{Flush, Screen, Swap}; @@ -22,7 +25,8 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); - println!("\x1b[21;4HPress Start to exit, or A to flip the image."); + println!("\x1b[21;4HPress A to flip the image."); + println!("\x1b[29;16HPress Start to exit"); let mut bottom_screen = gfx.bottom_screen.borrow_mut(); @@ -37,38 +41,44 @@ fn main() { let mut image_bytes = IMAGE; - // Main loop + // We assume the image is the correct size already, so we drop width + height. + let frame_buffer = bottom_screen.raw_framebuffer(); + + // We copy the image to the framebuffer. + unsafe { + frame_buffer + .ptr + .copy_from(image_bytes.as_ptr(), image_bytes.len()); + } + 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::START) { break; } - // We assume the image is the correct size already, so we drop width + height. - let frame_buffer = bottom_screen.raw_framebuffer(); - if hid.keys_down().contains(KeyPad::A) { image_bytes = if std::ptr::eq(image_bytes, IMAGE) { &flipped_image[..] } else { IMAGE }; - } - // this copies more than necessary (once per frame) but it's fine... - unsafe { - frame_buffer - .ptr - .copy_from(image_bytes.as_ptr(), image_bytes.len()); + let frame_buffer = bottom_screen.raw_framebuffer(); + + // We render the newly switched image to the framebuffer. + unsafe { + frame_buffer + .ptr + .copy_from(image_bytes.as_ptr(), image_bytes.len()); + } } // Flush framebuffers. Since we're not using double buffering, // this will render the pixels immediately bottom_screen.flush_buffers(); - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/gfx-wide-mode.rs b/ctru-rs/examples/gfx-wide-mode.rs index 8036538..109169d 100644 --- a/ctru-rs/examples/gfx-wide-mode.rs +++ b/ctru-rs/examples/gfx-wide-mode.rs @@ -1,3 +1,10 @@ +//! Wide-Mode Graphics example. +//! +//! This example demonstrates the wide-mode capability of the top screen +//! which doubles the horizontal resolution of the screen by merging the 2 stereoscopic 3D sides. +//! +//! Beware, wide-mode doesn't work on Old 2DS consoles. + use ctru::prelude::*; fn main() { @@ -9,6 +16,7 @@ fn main() { let mut console = Console::new(gfx.top_screen.borrow_mut()); println!("Press A to enable/disable wide screen mode."); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); @@ -17,14 +25,18 @@ fn main() { break; } + // Since we can't set wide-mode while running the console (since that would break the currently displayed text), + // we need to rebuild the console each time we want to make the switch. if hid.keys_down().contains(KeyPad::A) { drop(console); + // Switch the state of the wide-mode. let wide_mode = gfx.top_screen.borrow().is_wide(); gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode); console = Console::new(gfx.top_screen.borrow_mut()); println!("Press A to enable/disable wide screen mode."); + println!("\x1b[29;16HPress Start to exit"); } gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/graphics-bitmap.rs b/ctru-rs/examples/graphics-bitmap.rs deleted file mode 100644 index df9ce11..0000000 --- a/ctru-rs/examples/graphics-bitmap.rs +++ /dev/null @@ -1,57 +0,0 @@ -use ctru::prelude::*; -use ctru::services::gfx::{Flush, Screen, Swap}; - -/// Ferris image taken from and scaled down to 320x240px. -/// To regenerate the data, you will need to install `imagemagick` and run this -/// command from the `examples` directory: -/// -/// ```sh -/// magick assets/ferris.png -channel-fx "red<=>blue" -rotate 90 assets/ferris.rgb -/// ``` -/// -/// This creates an image appropriate for the default frame buffer format of -/// [`Bgr8`](ctru::services::gspgpu::FramebufferFormat::Bgr8) -/// and rotates the image 90° to account for the portrait mode screen. -static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); - -fn main() { - ctru::use_panic_handler(); - - let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); - let mut hid = Hid::new().expect("Couldn't obtain HID controller"); - let apt = Apt::new().expect("Couldn't obtain APT controller"); - let _console = Console::new(gfx.top_screen.borrow_mut()); - - println!("\x1b[21;16HPress Start to exit."); - - let mut bottom_screen = gfx.bottom_screen.borrow_mut(); - - // We don't need double buffering in this example. - // In this way we can draw our image only once on screen. - bottom_screen.set_double_buffering(false); - - // We assume the image is the correct size already, so we drop width + height. - let frame_buffer = bottom_screen.raw_framebuffer(); - - // Copy the image into the frame buffer - unsafe { - frame_buffer.ptr.copy_from(IMAGE.as_ptr(), IMAGE.len()); - } - - // 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::START) { - break; - } - - // Flush and swap framebuffers - bottom_screen.flush_buffers(); - bottom_screen.swap_buffers(); - - //Wait for VBlank - gfx.wait_for_vblank(); - } -} diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index 7d4d8c4..e563068 100644 --- a/ctru-rs/examples/hashmaps.rs +++ b/ctru-rs/examples/hashmaps.rs @@ -1,10 +1,16 @@ +//! Hashmap example. +//! +//! This example showcases using Hashmaps on the 3DS console using the functionality implemented by the standard library. +//! While it may seem inappropriate for such a simple (and somewhat out-of-scope) example to be included here, it's important to note how +//! normally Hashmaps wouldn't work on the console, and are only capable to because of the internal implementations made by `ctru-rs`. +//! +//! As such, this example functions more closely to a test than a demonstration. + use ctru::prelude::*; fn main() { ctru::use_panic_handler(); - // Initialize services - // // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. // This generator is only active when activating the `PS` service. // This service is automatically initialized. @@ -19,6 +25,7 @@ fn main() { map.remove("A Key!"); println!("{map:#?}"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/hello-both-screens.rs b/ctru-rs/examples/hello-both-screens.rs index f2007f1..1f2b383 100644 --- a/ctru-rs/examples/hello-both-screens.rs +++ b/ctru-rs/examples/hello-both-screens.rs @@ -1,3 +1,7 @@ +//! Hello World example using both screens. +//! +//! This is similar to the `hello-world` example, with the main difference of using 2 virtual `Console`s that can be alternated to print on both screens. + use ctru::prelude::*; fn main() { @@ -11,14 +15,15 @@ fn main() { let top_screen = Console::new(gfx.top_screen.borrow_mut()); // Start a console on the bottom screen. - // The most recently initialized console will be active by default + // The most recently initialized console will be active by default. let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut()); - // Let's print on the top screen first + // Let's print on the top screen first. + // Since the bottom screen is currently selected (being created afterwards), it is required to select the top screen console first. top_screen.select(); println!("This is the top screen! We have a lot of space up here!"); - // Now let's print something on the bottom screen + // Now let's print something on the bottom screen. bottom_screen.select(); println!("\x1b[14;00HThis is the bottom screen."); println!("There's not as much space down here, but that's okay."); diff --git a/ctru-rs/examples/hello-world.rs b/ctru-rs/examples/hello-world.rs index a92cb90..9210484 100644 --- a/ctru-rs/examples/hello-world.rs +++ b/ctru-rs/examples/hello-world.rs @@ -1,15 +1,25 @@ -use ctru::prelude::*; +//! Hello World example. +//! +//! Simple "Hello World" application to showcase the basic setup needed for any user-oriented app to work. +use ctru::prelude::*; use std::io::BufWriter; fn main() { + // Setup the custom panic handler in case any errors arise. + // Thanks to it the user will get promptly notified of any panics. ctru::use_panic_handler(); + // Setup Graphics, Controller Inputs, Application runtime. + // These is standard setup any app would need. let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); + + // Create a Console to print our "Hello, World!" to. let _console = Console::new(gfx.top_screen.borrow_mut()); + // Snazzy message created via `ferris_says`. let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; let width = 24; @@ -21,16 +31,18 @@ fn main() { String::from_utf8_lossy(&writer.into_inner().unwrap()) ); - // Main loop + println!("\x1b[29;16HPress Start to exit"); + + // Main application loop. This checks whether the app is normally running in the foreground. while apt.main_loop() { - //Scan all the inputs. This should be done once for each frame + // Scan all the controller inputs. hid.scan_input(); if hid.keys_down().contains(KeyPad::START) { break; } - //Wait for VBlank + // Use VSync to cap the framerate at the same speed as the LCD screen's refresh rate (60 fps). gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/linear-memory.rs b/ctru-rs/examples/linear-memory.rs index e6c18dc..de653e6 100644 --- a/ctru-rs/examples/linear-memory.rs +++ b/ctru-rs/examples/linear-memory.rs @@ -1,3 +1,9 @@ +//! LINEAR memory example. +//! +//! This example showcases simple allocation on the LINEAR memory sector. +//! Using LINEAR memory is required when sending data to the GPU or DSP processor. + +// You will need to activate this unstable feature to use custom allocators. #![feature(allocator_api)] use ctru::linear::LinearAllocator; @@ -11,11 +17,13 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); + // The `LinearAllocator` is always available for use. + // Luckily, we can always read how much memory is available to be allocated on it. let linear_space_before = LinearAllocator::free_space(); - // Normal `Box` on the heap + // Normal `Box` on the heap. let heap_box = Box::new(1492); - // `Box` living on the linear memory sector + // `Box` living on the LINEAR memory. let linear_box: Box = Box::new_in(2022, LinearAllocator); println!("This value is from the heap: {heap_box}"); @@ -29,16 +37,13 @@ fn main() { println!("\x1b[29;16HPress Start to exit"); - // 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs index 4602e9b..db3a9ea 100644 --- a/ctru-rs/examples/mii-selector.rs +++ b/ctru-rs/examples/mii-selector.rs @@ -1,4 +1,8 @@ -use ctru::applets::mii_selector::{LaunchError, MiiSelector, Options}; +//! Mii Selector example. +//! +//! This example showcases the use of the MiiSelector applet to obtain Mii data from the user's input. + +use ctru::applets::mii_selector::{Error, MiiSelector, Options}; use ctru::prelude::*; fn main() { @@ -9,12 +13,16 @@ fn main() { let apt = Apt::new().expect("Couldn't obtain APT controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); + // Setup the Mii Selector configuration. let mut mii_selector = MiiSelector::new(); - mii_selector.set_options(Options::MII_SELECTOR_CANCEL); + // The Mii Selector window can be closed without selecting a Mii. + mii_selector.set_options(Options::ENABLE_CANCEL); mii_selector.set_initial_index(3); + // The first user-made Mii cannot be used. mii_selector.blacklist_user_mii(0.into()); mii_selector.set_title("Great Mii Selector!"); + // Launch the Mii Selector and use its result to print the selected Mii's information. match mii_selector.launch() { Ok(result) => { println!("Mii type: {:?}", result.mii_type); @@ -25,20 +33,19 @@ fn main() { result.mii_data.mole_details.is_enabled ); } - Err(LaunchError::InvalidChecksum) => println!("Corrupt Mii selected"), - Err(LaunchError::NoMiiSelected) => println!("No Mii selected"), + Err(Error::InvalidChecksum) => println!("Corrupt Mii selected"), + Err(Error::NoMiiSelected) => println!("No Mii selected"), } - // Main loop + println!("\x1b[29;16HPress Start to exit"); + 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/network-sockets.rs b/ctru-rs/examples/network-sockets.rs index bbea510..d55e29c 100644 --- a/ctru-rs/examples/network-sockets.rs +++ b/ctru-rs/examples/network-sockets.rs @@ -1,3 +1,7 @@ +//! Network Sockets example. +//! +//! This example showcases the use of network sockets via the `Soc` service and the standard library's implementations. + use ctru::prelude::*; use std::io::{self, Read, Write}; @@ -8,26 +12,35 @@ fn main() { ctru::use_panic_handler(); let gfx = Gfx::new().unwrap(); - let _console = Console::new(gfx.top_screen.borrow_mut()); let mut hid = Hid::new().unwrap(); let apt = Apt::new().unwrap(); - println!("\nlibctru sockets demo\n"); - + // Owning a living handle to the `Soc` service is required to use network functionalities. let soc = Soc::new().unwrap(); + // Listen on the standard HTTP port (80). let server = TcpListener::bind("0.0.0.0:80").unwrap(); server.set_nonblocking(true).unwrap(); - println!("Point your browser to http://{}/\n", soc.host_address()); + let _bottom_console = Console::new(gfx.bottom_screen.borrow_mut()); + + println!("Point your browser at:\nhttp://{}/\n", soc.host_address()); + println!("\x1b[29;12HPress Start to exit"); + + let _top_console = Console::new(gfx.top_screen.borrow_mut()); while apt.main_loop() { - gfx.wait_for_vblank(); + hid.scan_input(); + if hid.keys_down().contains(KeyPad::START) { + break; + }; + // Receive any incoming connections. match server.accept() { Ok((mut stream, socket_addr)) => { println!("Got connection from {socket_addr}"); + // Print the HTTP request sent by the client (most likely, a web browser). let mut buf = [0u8; 4096]; match stream.read(&mut buf) { Ok(_) => { @@ -43,15 +56,18 @@ fn main() { } } + // Return a HTML page with a simple "Hello World!". let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHello world\r\n"; if let Err(e) = stream.write(response) { println!("Error writing http response: {e}"); } + // Shutdown the stream (depending on the web browser used to view the page, this might cause some issues). stream.shutdown(Shutdown::Both).unwrap(); } Err(e) => match e.kind() { + // If the TCP socket would block execution, just try again. std::io::ErrorKind::WouldBlock => {} _ => { println!("Error accepting connection: {e}"); @@ -60,9 +76,6 @@ fn main() { }, } - hid.scan_input(); - if hid.keys_down().contains(KeyPad::START) { - break; - }; + gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs index 8314efa..9df3f3d 100644 --- a/ctru-rs/examples/output-3dslink.rs +++ b/ctru-rs/examples/output-3dslink.rs @@ -1,4 +1,6 @@ -//! Use the `3dslink --server` option for redirecting output from the 3DS back +//! Output redirection example. +//! +//! This example uses the `3dslink --server` option for redirecting output from the 3DS back //! to the device that sent the executable. //! //! For now, `cargo 3ds run` does not support this flag, so to run this example @@ -17,24 +19,23 @@ fn main() { let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); + // We need to use network sockets to send the data stream back. let mut soc = Soc::new().expect("Couldn't obtain SOC controller"); + // Set the output to be redirected to the `3dslink` server. soc.redirect_to_3dslink(true, true) .expect("unable to redirect stdout/err to 3dslink server"); println!("Hello 3dslink!"); eprintln!("Press Start on the device to disconnect and exit."); - // 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/romfs.rs b/ctru-rs/examples/romfs.rs index 1138203..490a785 100644 --- a/ctru-rs/examples/romfs.rs +++ b/ctru-rs/examples/romfs.rs @@ -1,3 +1,7 @@ +//! RomFS example. +//! +//! This example showcases the RomFS service and how to mount it to include a read-only filesystem within the application bundle. + use ctru::prelude::*; fn main() { @@ -9,35 +13,35 @@ fn main() { let _console = Console::new(gfx.top_screen.borrow_mut()); cfg_if::cfg_if! { - // Run this code if RomFS are wanted and available - // This never fails as `ctru-rs` examples inherit all of the `ctru` features, - // but it might if a normal user application wasn't setup correctly + // Run this code if RomFS are wanted and available. + // This never fails as `ctru-rs` examples inherit all of the `ctru-rs` features, + // but it might if a normal user application wasn't setup correctly. if #[cfg(all(feature = "romfs", romfs_exists))] { + // Mount the romfs volume. let _romfs = ctru::services::romfs::RomFS::new().unwrap(); + // Open a simple text file present in the RomFS volume. + // Remember to use the `romfs:/` prefix when working with `RomFS`. let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap(); println!("Contents of test-file.txt: \n{f}\n"); let f = std::fs::read_to_string("romfs:/ファイル.txt").unwrap(); - // While RomFS supports UTF-16 file paths, `Console` doesn't... + // While `RomFS` supports UTF-16 file paths, `Console` does not, so we'll use a placeholder for the text. println!("Contents of [UTF-16 File]: \n{f}\n"); } else { println!("No RomFS was found, are you sure you included it?") } } - println!("\nPress START to exit"); + println!("\x1b[29;16HPress Start to exit"); - // 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index 39b5549..e754781 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -1,4 +1,8 @@ -use ctru::applets::swkbd::{Button, Swkbd}; +//! Software Keyboard example. +//! +//! This example showcases the use of the Software Keyboard applet to receive text input from the user. + +use ctru::applets::swkbd::{Button, SoftwareKeyboard}; use ctru::prelude::*; fn main() { @@ -9,21 +13,24 @@ fn main() { let gfx = Gfx::new().unwrap(); let _console = Console::new(gfx.top_screen.borrow_mut()); - println!("Press A to enter some text or press Start to quit"); + println!("Press A to enter some text or press Start to exit."); while apt.main_loop() { - gfx.wait_for_vblank(); - hid.scan_input(); + if hid.keys_down().contains(KeyPad::START) { + break; + } + + // Check if the user request to write some input. if hid.keys_down().contains(KeyPad::A) { // Prepares a software keyboard with two buttons: One to cancel input and one - // to accept it. You can also use `Swkbd::new()` to launch the keyboard in different + // to accept it. You can also use `SoftwareKeyboard::new()` to launch the keyboard in different // configurations. - let mut keyboard = Swkbd::default(); + let mut keyboard = SoftwareKeyboard::default(); // Raise the software keyboard. You can perform different actions depending on which - // software button the user pressed + // software button the user pressed. match keyboard.get_string(2048) { Ok((text, Button::Right)) => println!("You entered: {text}"), Ok((_, Button::Left)) => println!("Cancelled"), @@ -32,8 +39,6 @@ fn main() { } } - if hid.keys_down().contains(KeyPad::START) { - break; - } + gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/system-configuration.rs b/ctru-rs/examples/system-configuration.rs index 001233d..f1748f7 100644 --- a/ctru-rs/examples/system-configuration.rs +++ b/ctru-rs/examples/system-configuration.rs @@ -1,3 +1,8 @@ +//! System Configuration example. +//! +//! This example showcases the CFGU service to retrieve information about the console that the application is running on, +//! such as the model, region and used language. + use ctru::prelude::*; use ctru::services::cfgu::Cfgu; @@ -7,23 +12,24 @@ fn main() { let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); - let cfgu = Cfgu::new().expect("Couldn't obtain CFGU controller"); let _console = Console::new(gfx.top_screen.borrow_mut()); + // Initialize the CFGU service to retrieve all wanted information. + let cfgu = Cfgu::new().expect("Couldn't obtain CFGU controller"); + println!("\x1b[0;0HRegion: {:?}", cfgu.region().unwrap()); println!("\x1b[10;0HLanguage: {:?}", cfgu.language().unwrap()); println!("\x1b[20;0HModel: {:?}", cfgu.model().unwrap()); - // Main loop + println!("\x1b[29;16HPress Start to exit"); + 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::START) { break; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/thread-basic.rs b/ctru-rs/examples/thread-basic.rs index 33a8ea9..3e4604b 100644 --- a/ctru-rs/examples/thread-basic.rs +++ b/ctru-rs/examples/thread-basic.rs @@ -33,6 +33,8 @@ fn main() { println!("Created thread {ix}"); } + println!("\x1b[29;16HPress Start to exit"); + while apt.main_loop() { gfx.wait_for_vblank(); diff --git a/ctru-rs/examples/thread-info.rs b/ctru-rs/examples/thread-info.rs index 28297ac..46d34d3 100644 --- a/ctru-rs/examples/thread-info.rs +++ b/ctru-rs/examples/thread-info.rs @@ -36,7 +36,7 @@ fn main() { .unwrap(); println!("sys thread exited"); - println!("\nPress Start to exit"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); diff --git a/ctru-rs/examples/thread-locals.rs b/ctru-rs/examples/thread-locals.rs index 80d8508..72458c9 100644 --- a/ctru-rs/examples/thread-locals.rs +++ b/ctru-rs/examples/thread-locals.rs @@ -52,7 +52,7 @@ fn main() { ); }); - println!("Press Start to exit"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { hid.scan_input(); diff --git a/ctru-rs/examples/time-rtc.rs b/ctru-rs/examples/time-rtc.rs index 377fd5c..ca4709a 100644 --- a/ctru-rs/examples/time-rtc.rs +++ b/ctru-rs/examples/time-rtc.rs @@ -1,3 +1,8 @@ +//! Time Clock example. +//! +//! This example showcases how to retrieve the local time set in the console's configuration +//! using the implementations of the standard library. + use ctru::prelude::*; fn main() { @@ -9,11 +14,9 @@ fn main() { let _console = Console::new(gfx.top_screen.borrow_mut()); - print!("\x1b[30;16HPress Start to exit."); + println!("\x1b[29;16HPress Start to exit"); - // 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::START) { @@ -21,7 +24,7 @@ fn main() { } // Technically, this actually just gets local time and assumes it's UTC, - // since the 3DS doesn't seem to support timezones... + // since the 3DS doesn't seem to support timezones. let cur_time = time::OffsetDateTime::now_utc(); let hours = cur_time.hour(); @@ -36,7 +39,6 @@ fn main() { println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}"); println!("{weekday} {month} {day} {year}"); - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/title-info.rs b/ctru-rs/examples/title-info.rs index fee5cb6..d2898ac 100644 --- a/ctru-rs/examples/title-info.rs +++ b/ctru-rs/examples/title-info.rs @@ -1,3 +1,8 @@ +//! Title Info example. +//! +//! This example showcases how to retrieve information about the titles installed on the console running the application +//! via the Application Manager (Am) service. + use ctru::prelude::*; use ctru::services::am::Am; use ctru::services::fs::FsMediaType; @@ -8,20 +13,27 @@ fn main() { let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let apt = Apt::new().expect("Couldn't obtain APT controller"); - let am = Am::new().expect("Couldn't obtain AM controller"); + let top_screen = Console::new(gfx.top_screen.borrow_mut()); let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut()); + // Setup the AM service to retrieve the wanted information. + let am = Am::new().expect("Couldn't obtain AM controller"); + + // Amount of titles installed on the SD card. let sd_count = am .title_count(FsMediaType::Sd) .expect("Failed to get sd title count"); + // List of titles installed on the SD card. let sd_list = am .title_list(FsMediaType::Sd) .expect("Failed to get sd title list"); + // Amount of titles installed on the NAND storage. let nand_count = am .title_count(FsMediaType::Nand) .expect("Failed to get nand title count"); + // List of titles installed on the NAND storage. let nand_list = am .title_list(FsMediaType::Nand) .expect("Failed to get nand title list"); @@ -30,9 +42,7 @@ fn main() { 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::START) { @@ -58,13 +68,15 @@ fn main() { } } + // Render the title list via a scrollable text UI. if refresh { let mut selected_title = cur_list.iter().skip(offset).next().unwrap(); - // Clear top screen and write title ids to it + + // Clear the top screen and write title IDs to it. top_screen.select(); print!("\x1b[2J"); - // Top screen seems to have only 30 rows + // Top screen has 30 rows. for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() { if i == 0 { selected_title = title; @@ -74,25 +86,18 @@ fn main() { } } - // Clear bottom screen and write properties of selected title to it + // Clear the bottom screen and write the properties of selected title to it. bottom_screen.select(); - println!("\x1b[2J"); - // Move cursor to top left + bottom_screen.clear(); + println!("Press Start to exit"); + + // 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.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 { println!("Press SELECT to choose SD Card"); println!("Current medium: NAND"); @@ -106,7 +111,6 @@ fn main() { refresh = false; } - //Wait for VBlank gfx.wait_for_vblank(); } } diff --git a/ctru-rs/examples/touch-screen.rs b/ctru-rs/examples/touch-screen.rs index 204f324..fe2409d 100644 --- a/ctru-rs/examples/touch-screen.rs +++ b/ctru-rs/examples/touch-screen.rs @@ -1,3 +1,7 @@ +//! Touch Screen example. +//! +//! This example showcases how to retrieve the touch screen's touch information via the HID service. + use ctru::prelude::*; fn main() { @@ -9,7 +13,7 @@ fn main() { let console = Console::new(gfx.top_screen.borrow_mut()); - // We'll hold the previous touch position for comparison. + // We'll save the previous touch position for comparison. let mut old_touch: (u16, u16) = (0, 0); println!("\x1b[29;16HPress Start to exit"); @@ -26,22 +30,22 @@ fn main() { let touch: (u16, u16) = hid.touch_position(); // We only want to print the position when it's different - // from what it was on the previous frame + // from what it was on the previous frame. if touch != old_touch { + // Move the cursor back to the top of the screen and print the coordinates. + print!("\x1b[1;1HTouch Screen position: {:#?}", touch); + // Special case for when the user lifts the stylus/finger from the screen. // This is done to avoid some screen tearing. if touch == (0, 0) { console.clear(); - // Print again because we just cleared the screen + // Print again because we just cleared the screen. println!("\x1b[29;16HPress Start to exit"); } - - // Move the cursor back to the top of the screen and print the coordinates - print!("\x1b[1;1HTouch Screen position: {:#?}", touch); } - // Save our current touch position for the next frame + // Save our current touch position for the next frame. old_touch = touch; gfx.wait_for_vblank(); diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index bd0eac2..bd0e4b4 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -1,75 +1,87 @@ -//! Mii Selector applet +//! Mii Selector applet. //! -//! This module contains the methods to launch the Mii Selector. +//! This applet opens a window which lets the player/user choose a Mii from the ones present on their console. +//! The selected Mii is readable as a [`Mii`](crate::mii::Mii). -use crate::mii::MiiData; +use crate::mii::Mii; use bitflags::bitflags; -use std::ffi::CString; +use std::{ffi::CString, fmt}; -/// Index of a Mii used to configure some parameters of the Mii Selector -/// Can be either a single index, or _all_ Miis +/// Index of a Mii on the [`MiiSelector`] interface. +/// +/// See [`MiiSelector::whitelist_user_mii()`] and related functions for more information. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Index { + /// Specific Mii index. + /// + /// # Notes + /// + /// Indexes start at 0. Index(u32), + /// All Miis. All, } -/// The type of a Mii with their respective data +/// The type of a Mii. #[derive(Debug, Clone, Eq, PartialEq)] pub enum MiiType { - Guest { index: u32, name: String }, + /// Guest Mii. + Guest { + /// Guest Mii index. + index: u32, + /// Guest Mii name. + name: String, + }, + /// User-made Mii. User, } bitflags! { - /// Options for the Mii Selector + /// Options to configure the [`MiiSelector`]. + /// + /// See [`MiiSelector::set_options()`] to learn how to use them. + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Options: u32 { - /// Show the cancel button - const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL; - /// Make guest Miis selectable - const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS; - /// Show on the top screen - const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP; - /// Start on the guest's page - const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART; + /// Show the cancel button. + const ENABLE_CANCEL = ctru_sys::MIISELECTOR_CANCEL; + /// Make guest Miis available to select. + const ENABLE_GUESTS = ctru_sys::MIISELECTOR_GUESTS; + /// Show the [`MiiSelector`] window on the top screen. + const USE_TOP_SCREEN = ctru_sys::MIISELECTOR_TOP; + /// Start the [`MiiSelector`] on the guests' page. Requires [`Options::ENABLE_GUESTS`]. + const START_WITH_GUESTS = ctru_sys::MIISELECTOR_GUESTSTART; } } -/// An instance of the Mii Selector -/// -/// # Example -/// ``` -/// use ctru::applets::mii_selector::MiiSelector; -/// -/// let mut mii_selector = MiiSelector::new(); -/// mii_selector.set_title("Example Mii selector"); -/// -/// let result = mii_selector.launch().unwrap(); -/// ``` +/// Configuration structure to setup the Mii Selector applet. +#[doc(alias = "MiiSelectorConf")] #[derive(Clone, Debug)] pub struct MiiSelector { config: Box, } -/// Return value from a MiiSelector's launch +/// Return value of a successful [`MiiSelector::launch()`]. #[non_exhaustive] #[derive(Clone, Debug)] -pub struct SelectionResult { - pub mii_data: MiiData, +pub struct Selection { + /// Data of the selected Mii. + pub mii_data: Mii, + /// Type of the selected Mii. pub mii_type: MiiType, } -/// Error type for the Mii selector +/// Error returned by an unsuccessful [`MiiSelector::launch()`]. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum LaunchError { - /// The selected Mii's data is corrupt in some way +pub enum Error { + /// The selected Mii's data is corrupt. InvalidChecksum, - /// Either the user cancelled the selection (see [Options::MII_SELECTOR_CANCEL]), or no valid Miis were available to select + /// Either the user cancelled the selection (see [`Options::ENABLE_CANCEL`]) or no valid Miis were available to select. NoMiiSelected, } impl MiiSelector { - /// Initializes a Mii Selector + /// Initialize a new configuration for the Mii Selector applet. + #[doc(alias = "miiSelectorInit")] pub fn new() -> Self { let mut config = Box::::default(); unsafe { @@ -78,9 +90,22 @@ impl MiiSelector { Self { config } } - /// Set the title of the Mii Selector. + /// Set the title of the Mii Selector window. + /// + /// # Panics + /// This function will panic if the given `&str` contains NUL bytes. + /// + /// # Example /// - /// This function would panic if the given ``&str`` contains NUL bytes. + /// ```no_run + /// # fn main() { + /// use ctru::applets::mii_selector::MiiSelector; + /// + /// let mut mii_selector = MiiSelector::new(); + /// mii_selector.set_title("Select a Mii!"); + /// # } + /// ``` + #[doc(alias = "miiSelectorSetTitle")] pub fn set_title(&mut self, text: &str) { // This can only fail if the text contains NUL bytes in the string... which seems // unlikely and is documented @@ -90,12 +115,47 @@ impl MiiSelector { } } - /// Set the options of the Mii Selector + /// Set the options of the Mii Selector. + /// + /// This will overwrite any previously saved options. Use bitwise operations to set all your wanted options at once. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// use ctru::applets::mii_selector::{MiiSelector, Options}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Setup a `MiiSelector` that can be cancelled and that makes Guest Miis available to select. + /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS; + /// mii_selector.set_options(opts); + /// # } + /// ``` + #[doc(alias = "miiSelectorSetOptions")] pub fn set_options(&mut self, options: Options) { - unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits) } + unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits()) } } - /// Whitelist a guest Mii + /// Whitelist a guest Mii based on its index. + /// + /// # Notes + /// + /// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`]. + /// Look into [`MiiSelector::set_options()`] to see how to work with options. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Whitelist the guest Mii at index 2. + /// mii_selector.whitelist_guest_mii(Index::Index(2)); + /// # } + /// ``` + #[doc(alias = "miiSelectorWhitelistGuestMii")] pub fn whitelist_guest_mii(&mut self, mii_index: Index) { let index = match mii_index { Index::Index(i) => i, @@ -105,7 +165,26 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) } } - /// Blacklist a guest Mii + /// Blacklist a guest Mii based on its index. + /// + /// # Notes + /// + /// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`]. + /// Look into [`MiiSelector::set_options()`] to see how to work with options. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Blacklist the guest Mii at index 1 so that it cannot be selected. + /// mii_selector.blacklist_guest_mii(Index::Index(1)); + /// # } + /// ``` + #[doc(alias = "miiSelectorBlacklistGuestMii")] pub fn blacklist_guest_mii(&mut self, mii_index: Index) { let index = match mii_index { Index::Index(i) => i, @@ -115,7 +194,21 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) } } - /// Whitelist a user Mii + /// Whitelist a user-created Mii based on its index. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Whitelist the user-created Mii at index 0. + /// mii_selector.whitelist_user_mii(Index::Index(0)); + /// # } + /// ``` + #[doc(alias = "miiSelectorWhitelistUserMii")] pub fn whitelist_user_mii(&mut self, mii_index: Index) { let index = match mii_index { Index::Index(i) => i, @@ -125,7 +218,21 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) } } - /// Blacklist a user Mii + /// Blacklist a user-created Mii based on its index. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::mii_selector::{Index, MiiSelector}; + /// let mut mii_selector = MiiSelector::new(); + /// + /// // Blacklist all user-created Miis so that they cannot be selected. + /// mii_selector.blacklist_user_mii(Index::All); + /// # } + /// ``` + #[doc(alias = "miiSelectorBlacklistUserMii")] pub fn blacklist_user_mii(&mut self, mii_index: Index) { let index = match mii_index { Index::Index(i) => i, @@ -135,8 +242,10 @@ impl MiiSelector { unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) } } - /// Set where the cursor will be. - /// If there's no Mii at that index, the cursor will start at the Mii with the index 0 + /// Set where the GUI cursor will start at. + /// + /// If there's no Mii at that index, the cursor will start at the Mii with the index 0. + #[doc(alias = "miiSelectorSetInitialIndex")] pub fn set_initial_index(&mut self, index: usize) { // This function is static inline in libctru // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 @@ -144,19 +253,43 @@ impl MiiSelector { } /// Launch the Mii Selector. - /// Returns an error when the checksum of the Mii is invalid. - pub fn launch(&mut self) -> Result { + /// + /// Depending on the configuration, the Mii Selector window will appear either on the bottom screen (default behaviour) or the top screen (see [`Options::USE_TOP_SCREEN`]). + /// + /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::applets::mii_selector::{MiiSelector, Options}; + /// + /// let mut mii_selector = MiiSelector::new(); + /// mii_selector.set_title("Select a Mii!"); + /// + /// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS; + /// mii_selector.set_options(opts); + /// + /// let result = mii_selector.launch()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "miiSelectorLaunch")] + pub fn launch(&mut self) -> Result { let mut return_val = Box::::default(); unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) } if return_val.no_mii_selected != 0 { - return Err(LaunchError::NoMiiSelected); + return Err(Error::NoMiiSelected); } if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } { Ok((*return_val).into()) } else { - Err(LaunchError::InvalidChecksum) + Err(Error::InvalidChecksum) } } } @@ -167,12 +300,23 @@ impl Default for MiiSelector { } } -impl From for SelectionResult { +impl fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidChecksum => write!(f, "selected mii has invalid checksum"), + Self::NoMiiSelected => write!(f, "no mii was selected"), + } + } +} + +impl std::error::Error for Error {} + +impl From for Selection { fn from(ret: ctru_sys::MiiSelectorReturn) -> Self { let raw_mii_data = ret.mii; let mut guest_mii_name = ret.guest_mii_name; - SelectionResult { + Selection { mii_data: raw_mii_data.into(), mii_type: if ret.guest_mii_index != 0xFFFFFFFF { MiiType::Guest { diff --git a/ctru-rs/src/applets/mod.rs b/ctru-rs/src/applets/mod.rs index f5813ad..244fb74 100644 --- a/ctru-rs/src/applets/mod.rs +++ b/ctru-rs/src/applets/mod.rs @@ -1,2 +1,12 @@ +//! System Applets. +//! +//! Applets are small integrated programs that the OS makes available to the developer to streamline commonly needed functionality. +//! Thanks to these integrations the developer can avoid wasting time re-implementing common features and instead use a more reliable base for their application. +//! +//! Unlike [services](crate::services), applets aren't accessed via a system subprocess (which would require obtaining a special handle at runtime). +//! Instead, the application builds a configuration storing the various parameters which is then used to "launch" the applet. +//! +//! Applets block execution of the thread that launches them as long as the user doesn't close the applet. + pub mod mii_selector; pub mod swkbd; diff --git a/ctru-rs/src/applets/swkbd.rs b/ctru-rs/src/applets/swkbd.rs index 8f4aab6..8861ace 100644 --- a/ctru-rs/src/applets/swkbd.rs +++ b/ctru-rs/src/applets/swkbd.rs @@ -1,106 +1,209 @@ +//! Software Keyboard applet. +//! +//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text. +// TODO: Implement remaining functionality (password mode, filter callbacks, etc.). Also improve "max text length" API. Improve `number of buttons` API when creating a new SoftwareKeyboard. +// TODO: Split the Parental PIN lock operations into a different type. +#![doc(alias = "keyboard")] + use bitflags::bitflags; use ctru_sys::{ self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState, }; use libc; +use std::fmt::Display; use std::iter::once; use std::str; -/// An instance of the software keyboard. +/// Configuration structure to setup the Software Keyboard applet. +#[doc(alias = "SwkbdState")] #[derive(Clone)] -pub struct Swkbd { +pub struct SoftwareKeyboard { state: Box, } -/// The kind of keyboard to be initialized. +/// The type of keyboard used by the [`SoftwareKeyboard`]. /// -/// Normal is the full keyboard with several pages (QWERTY/accents/symbol/mobile) -/// Qwerty is a QWERTY-only keyboard. -/// Numpad is a number pad. -/// Western is a text keyboard without japanese symbols (only applies to JPN systems). For other -/// systems it's the same as a Normal keyboard. +/// Can be set with [`SoftwareKeyboard::new()`] +#[doc(alias = "SwkbdType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Kind { + /// Normal keyboard composed of several pages (QWERTY, accents, symbols, mobile). Normal = ctru_sys::SWKBD_TYPE_NORMAL, + /// Only QWERTY keyboard. Qwerty = ctru_sys::SWKBD_TYPE_QWERTY, + /// Only number pad. Numpad = ctru_sys::SWKBD_TYPE_NUMPAD, + /// On JPN systems: a keyboard without japanese input capabilities. + /// + /// On any other region: same as [`Normal`](Kind::Normal). Western = ctru_sys::SWKBD_TYPE_WESTERN, } -/// Represents which button the user pressed to close the software keyboard. +/// Represents which button the user pressed to close the [`SoftwareKeyboard`]. +/// +/// Button text and behaviour can be customized with [`SoftwareKeyboard::configure_button()`]. +#[doc(alias = "SwkbdButton")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Button { + /// Left button. Usually corresponds to "Cancel". Left = ctru_sys::SWKBD_BUTTON_LEFT, + /// Middle button. Usually corresponds to "I Forgot". Middle = ctru_sys::SWKBD_BUTTON_MIDDLE, + /// Right button. Usually corresponds to "OK". Right = ctru_sys::SWKBD_BUTTON_RIGHT, } -/// Error type for the software keyboard. +/// Error returned by an unsuccessful [`SoftwareKeyboard::get_string()`]. +#[doc(alias = "SwkbdResult")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(i32)] pub enum Error { - InvalidInput = ctru_sys::SWKBD_INVALID_INPUT, + /// Invalid parameters given to the [`SoftwareKeyboard`] configuration. + InvalidParameters = ctru_sys::SWKBD_INVALID_INPUT, + /// [`SoftwareKeyboard`] ran out of memory. OutOfMem = ctru_sys::SWKBD_OUTOFMEM, + /// Home button was pressed while [`SoftwareKeyboard`] was running. HomePressed = ctru_sys::SWKBD_HOMEPRESSED, + /// Reset button was pressed while [`SoftwareKeyboard`] was running. ResetPressed = ctru_sys::SWKBD_RESETPRESSED, + /// Power button was pressed while [`SoftwareKeyboard`] was running. PowerPressed = ctru_sys::SWKBD_POWERPRESSED, + /// The parental lock PIN was correct. + /// + /// While this variant isn't *technically* considerable an error + /// the result of a Parental PIN operation won't return a string to the program, thus it's still exceptional behaviour. ParentalOk = ctru_sys::SWKBD_PARENTAL_OK, + /// The parental lock PIN was incorrect. ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL, + /// Input triggered the filter. + /// + /// You can have a look at [`Filters`] to activate custom filters. BannedInput = ctru_sys::SWKBD_BANNED_INPUT, } -/// Restrictions on keyboard input +/// Restrictions to enforce rules on the keyboard input. +/// +/// See [`SoftwareKeyboard::set_validation()`] +#[doc(alias = "SwkbdValidInput")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum ValidInput { + /// All inputs are accepted. Anything = ctru_sys::SWKBD_ANYTHING, + /// Empty inputs are not accepted. NotEmpty = ctru_sys::SWKBD_NOTEMPTY, - NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK, + /// Blank inputs (consisting only of whitespaces) are not accepted. NotBlank = ctru_sys::SWKBD_NOTBLANK, + /// Neither empty inputs nor blank inputs are accepted. + NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK, + /// Input must have a fixed length. Maximum length can be specified with [`SoftwareKeyboard::set_max_text_len()`]; FixedLen = ctru_sys::SWKBD_FIXEDLEN, } bitflags! { - /// Keyboard feature flags + /// Special features that can be activated via [`SoftwareKeyboard::set_features()`]. + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Features: u32 { + /// Parental PIN mode. + /// + /// # Notes + /// + /// Refer to [`Error::ParentalOk`] and [`Error::ParentalFail`] to check whether the Parental PIN lock was successfully opened. const PARENTAL_PIN = ctru_sys::SWKBD_PARENTAL; - const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN; + /// Darken top screen while the [`SoftwareKeyboard`] is active. + const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN; + /// Enable predictive input (necessary for Kanji on JPN consoles). const PREDICTIVE_INPUT = ctru_sys::SWKBD_PREDICTIVE_INPUT; - const MULTILINE = ctru_sys::SWKBD_MULTILINE; + /// Enable multiline input. + const MULTILINE = ctru_sys::SWKBD_MULTILINE; + /// Enable fixed-width mode. const FIXED_WIDTH = ctru_sys::SWKBD_FIXED_WIDTH; + /// Allow the usage of the Home Button while the [`SoftwareKeyboard`] is running. const ALLOW_HOME = ctru_sys::SWKBD_ALLOW_HOME; + /// Allow the usage of the Reset Button while the [`SoftwareKeyboard`] is running. const ALLOW_RESET = ctru_sys::SWKBD_ALLOW_RESET; + /// Allow the usage of the Power Button while the [`SoftwareKeyboard`] is running. const ALLOW_POWER = ctru_sys::SWKBD_ALLOW_POWER; + /// Default to the QWERTY page when the [`SoftwareKeyboard`] is shown. const DEFAULT_QWERTY = ctru_sys::SWKBD_DEFAULT_QWERTY; } - /// Keyboard input filtering flags + /// Availble filters to disallow some types of input for the [`SoftwareKeyboard`]. + /// + /// See [`SoftwareKeyboard::set_validation()`] + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct Filters: u32 { + /// Disallow the usage of numerical digits. + /// + /// The maximum number of digits that are allowed to be used while this filter is active + /// can be configured with [`SoftwareKeyboard::set_max_digits()`] (default is 0). const DIGITS = ctru_sys::SWKBD_FILTER_DIGITS; + /// Disallow the usage of the "at" (@) sign. const AT = ctru_sys::SWKBD_FILTER_AT; + /// Disallow the usage of the "percent" (%) sign. const PERCENT = ctru_sys::SWKBD_FILTER_PERCENT; + /// Disallow the usage of the "backslash" (\) sign. const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH; + /// Disallow the use of profanity via Nintendo's profanity filter. const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY; + /// Use a custom callback in order to filter the input. + /// + /// TODO: It's currently impossible to setup a custom filter callback. const CALLBACK = ctru_sys::SWKBD_FILTER_CALLBACK; } } -impl Swkbd { - /// Initializes a software keyboard of the specified type and the chosen number of buttons - /// (from 1-3). +impl SoftwareKeyboard { + /// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3). + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Kind}; + /// + /// // Standard keyboard. + /// let keyboard = SoftwareKeyboard::new(Kind::Normal, 2); + /// + /// // Numpad (with only the "confirm" button). + /// let keyboard = SoftwareKeyboard::new(Kind::Numpad, 1); + /// # + /// # } + #[doc(alias = "swkbdInit")] pub fn new(keyboard_type: Kind, num_buttons: i32) -> Self { unsafe { let mut state = Box::::default(); swkbdInit(state.as_mut(), keyboard_type.into(), num_buttons, -1); - Swkbd { state } + SoftwareKeyboard { state } } } - /// Gets input from this keyboard and appends it to the provided string. + /// Launches the applet based on the given configuration and returns a string containing the text input. + /// + /// # Notes /// /// The text received from the keyboard will be truncated if it is longer than `max_bytes`. + /// + /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::applets::swkbd::SoftwareKeyboard; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// let (text, button) = keyboard.get_string(2048)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "swkbdInputText")] pub fn get_string(&mut self, max_bytes: usize) -> Result<(String, Button), Error> { // Unfortunately the libctru API doesn't really provide a way to get the exact length // of the string that it receieves from the software keyboard. Instead it expects you @@ -123,8 +226,30 @@ impl Swkbd { /// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from /// this software keyboard. /// + /// # Notes + /// /// If the buffer is too small to contain the entire sequence received from the keyboard, - /// the output will be truncated but should still be well-formed UTF-8. + /// the output will be truncated. + /// + /// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::applets::swkbd::SoftwareKeyboard; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// let mut buffer = vec![0; 100]; + /// + /// let button = keyboard.write_exact(&mut buffer)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "swkbdInputText")] pub fn write_exact(&mut self, buf: &mut [u8]) -> Result { unsafe { match swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { @@ -137,25 +262,90 @@ impl Swkbd { } } - /// Sets special features for this keyboard + /// Set special features for this keyboard. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Features}; + /// + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// let features = Features::DARKEN_TOP_SCREEN & Features::MULTILINE; + /// keyboard.set_features(features); + /// # + /// # } + #[doc(alias = "swkbdSetFeatures")] pub fn set_features(&mut self, features: Features) { - unsafe { swkbdSetFeatures(self.state.as_mut(), features.bits) } + unsafe { swkbdSetFeatures(self.state.as_mut(), features.bits()) } } - /// Configures input validation for this keyboard + /// Configure input validation for this keyboard. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// // Disallow empty or blank input. + /// let validation = ValidInput::NotEmptyNotBlank; + /// + /// // Disallow the use of numerical digits and profanity. + /// let filters = Filters::DIGITS & Filters::PROFANITY; + /// keyboard.set_validation(validation, filters); + /// # + /// # } pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) { self.state.valid_input = validation.into(); - self.state.filter_flags = filters.bits; + self.state.filter_flags = filters.bits(); } - /// Configures the maximum number of digits that can be entered in the keyboard when the - /// `Filters::DIGITS` flag is enabled + /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// // Disallow empty or blank input. + /// let validation = ValidInput::NotEmptyNotBlank; + /// + /// // Disallow the use of numerical digits. This filter is customizable thanks to `set_max_digits`. + /// let filters = Filters::DIGITS; + /// keyboard.set_validation(validation, filters); + /// + /// // No more than 3 numbers are accepted. + /// keyboard.set_max_digits(3); + /// # + /// # } pub fn set_max_digits(&mut self, digits: u16) { self.state.max_digits = digits; } - /// Sets the hint text for this software keyboard (that is, the help text that is displayed - /// when the textbox is empty) + /// Set the hint text for this software keyboard. + /// + /// The hint text is the text shown in gray before any text gets written in the input box. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::SoftwareKeyboard; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// keyboard.set_hint_text("Write here what you like!"); + /// # + /// # } + #[doc(alias = "swkbdSetHintText")] pub fn set_hint_text(&mut self, text: &str) { unsafe { let nul_terminated: String = text.chars().chain(once('\0')).collect(); @@ -163,12 +353,32 @@ impl Swkbd { } } - /// Configures the look and behavior of a button for this keyboard. + /// Configure the look and behavior of a button for this keyboard. + /// + /// # Arguments + /// + /// - `button` - the [`Button`] to be configured based on the position. + /// - `text` - the text displayed in the button. + /// - `submit` - whether pressing the button will accept the keyboard's input or discard it. /// - /// `button` is the `Button` to be configured - /// `text` configures the display text for the button - /// `submit` configures whether pressing the button will accept the keyboard's input or - /// discard it. + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; + /// + /// // We create a `SoftwareKeyboard` with left and right buttons. + /// let mut keyboard = SoftwareKeyboard::new(Kind::Normal, 2); + /// + /// // Set the left button text to "Cancel" and pressing it will NOT return the user's input. + /// keyboard.configure_button(Button::Left, "Cancel", false); + /// + /// // Set the right button text to "Ok" and pressing it will return the user's input. + /// keyboard.configure_button(Button::Right, "Ok", true); + /// # + /// # } + #[doc(alias = "swkbdSetButton")] pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) { unsafe { let nul_terminated: String = text.chars().chain(once('\0')).collect(); @@ -181,19 +391,34 @@ impl Swkbd { } } - /// Configures the maximum number of UTF-16 code units that can be entered into the software - /// keyboard. By default the limit is 0xFDE8 code units. + /// Configure the maximum number of UTF-16 code units that can be entered into the software + /// keyboard. By default the limit is `65000` code units. /// - /// Note that keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, + /// # Notes + /// + /// Keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, /// so this code point limit does not necessarily equal the max number of UTF-8 code points - /// receivable by the `get_utf8` and `get_bytes` functions. + /// receivable by [`SoftwareKeyboard::get_string()`] and [`SoftwareKeyboard::write_exact()`]. + /// + /// # Example + /// + /// ```no_run + /// # fn main() { + /// # + /// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind}; + /// let mut keyboard = SoftwareKeyboard::default(); + /// + /// // Set the maximum text length to 18 UTF-16 code units. + /// keyboard.set_max_text_len(18); + /// # + /// # } pub fn set_max_text_len(&mut self, len: u16) { self.state.max_text_len = len; } fn parse_swkbd_error(&self) -> Error { match self.state.result { - ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidInput, + ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidParameters, ctru_sys::SWKBD_OUTOFMEM => Error::OutOfMem, ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed, ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed, @@ -206,12 +431,44 @@ impl Swkbd { } } -impl Default for Swkbd { +/// Creates a new [`SoftwareKeyboard`] configuration set to using a [`Kind::Normal`] keyboard and 2 [`Button`]s. +impl Default for SoftwareKeyboard { fn default() -> Self { - Swkbd::new(Kind::Normal, 2) + SoftwareKeyboard::new(Kind::Normal, 2) } } +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidParameters => write!( + f, + "software keyboard was configured with invalid parameters" + ), + Self::OutOfMem => write!(f, "software keyboard ran out of memory"), + Self::HomePressed => { + write!(f, "home button pressed while software keyboard was running") + } + Self::ResetPressed => write!( + f, + "reset button pressed while software keyboard was running" + ), + Self::PowerPressed => write!( + f, + "power button pressed while software keyboard was running" + ), + Self::ParentalOk => write!(f, "parental lock pin was correct"), + Self::ParentalFail => write!(f, "parental lock pin was incorrect"), + Self::BannedInput => write!( + f, + "input given to the software keyboard triggered the active filters" + ), + } + } +} + +impl std::error::Error for Error {} + from_impl!(Kind, ctru_sys::SwkbdType); from_impl!(Button, ctru_sys::SwkbdButton); from_impl!(Error, ctru_sys::SwkbdResult); diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index e3ba62e..6768d47 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -1,3 +1,10 @@ +//! Virtual text console. +//! +//! The [`Console`] works as a virtual shell that renders on screen all output of `stdout`. As such, it is useful as a basic interface to show info to the user, +//! such as in simple "Hello World" applications or more complex software that does not need much user interaction. +//! +//! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications. + use std::cell::RefMut; use std::default::Default; @@ -7,19 +14,63 @@ use crate::services::gfx::Screen; static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) }; +/// Virtual text console. +/// +/// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen. +/// This means that any text written to `stdout` and `stderr` (e.g. using `println!`, `eprintln!` or `dbg!`) will become visible in the area taken by the console. +/// +/// # Notes +/// +/// The [`Console`] will take full possession of the screen handed to it as long as it stays alive. It also supports some ANSI codes, such as text color and cursor positioning. +/// The [`Console`]'s window size will be: +/// - 40x30 on the [`BottomScreen`](crate::services::gfx::BottomScreen). +/// - 50x30 on the normal [`TopScreen`](crate::services::gfx::TopScreen). +/// - 100x30 on the [`TopScreen`](crate::services::gfx::TopScreen) when wide mode is enabled. +/// +/// # Alternatives +/// +/// If you'd like to see live standard output while running the application but cannot or do not want to show the text on the 3DS itself, +/// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`). +/// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables). +#[doc(alias = "PrintConsole")] pub struct Console<'screen> { context: Box, _screen: RefMut<'screen, dyn Screen>, } impl<'screen> Console<'screen> { - /// Initialize a console on the chosen screen, overwriting whatever was on the screen - /// previously (including other consoles). The new console is automatically selected for - /// printing. + /// Initialize a console on the chosen screen. /// /// # Notes /// - /// [Console] automatically takes care of flushing and swapping buffers for its screen when printing. + /// This operation overwrites whatever was on the screen before the initialization (including other [`Console`]s) + /// and changes the [`FramebufferFormat`](crate::services::gspgpu::FramebufferFormat) of the selected screen to better suit the [`Console`]. + /// + /// The new console is automatically selected for printing. + /// + /// [`Console`] automatically takes care of flushing and swapping buffers for its screen when printing. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::gfx::Gfx; + /// use ctru::console::Console; + /// + /// // Initialize graphics. + /// let gfx = Gfx::new()?; + /// + /// // Create a `Console` that takes control of the upper LCD screen. + /// let top_console = Console::new(gfx.top_screen.borrow_mut()); + /// + /// println!("I'm on the top screen!"); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "consoleInit")] pub fn new(screen: RefMut<'screen, dyn Screen>) -> Self { let mut context = Box::::default(); @@ -31,7 +82,35 @@ impl<'screen> Console<'screen> { } } - /// Returns true if a valid Console to print on is selected + /// Returns `true` if a valid [`Console`] to print on is currently selected. + /// + /// # Notes + /// + /// This function is used to check whether one of the two screens has an existing (and selected) [`Console`], + /// so that the program can be sure its output will be shown *somewhere*. + /// + /// The main use of this is within the [`ctru::use_panic_handler()`](crate::use_panic_handler()) hook, + /// since it will only stop the program's execution if the user is able to see the panic information output on screen. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// # use ctru::services::gfx::Gfx; + /// # // Initialize graphics. + /// # let gfx = Gfx::new()?; + /// # + /// use ctru::console::Console; + /// let top_console = Console::new(gfx.top_screen.borrow_mut()); + /// + /// // There is at least one selected `Console`. + /// assert!(Console::exists()); + /// # + /// # Ok(()) + /// # } + /// ``` pub fn exists() -> bool { unsafe { let current_console = ctru_sys::consoleSelect(&mut EMPTY_CONSOLE); @@ -44,26 +123,65 @@ impl<'screen> Console<'screen> { } } - /// Select this console as the current target for stdout + /// Select this console as the current target for standard output. + /// + /// # Notes + /// + /// Any previously selected console will be unhooked and will not show the `stdout` and `stderr` output. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// # use ctru::services::gfx::Gfx; + /// # let gfx = Gfx::new()?; + /// # + /// use ctru::console::Console; + /// + /// // Create a `Console` that takes control of the upper LCD screen. + /// let top_console = Console::new(gfx.top_screen.borrow_mut()); + /// + /// // Create a `Console` that takes control of the lower LCD screen. + /// let bottom_console = Console::new(gfx.bottom_screen.borrow_mut()); + /// + /// // Remember that `Console::new` automatically selects the new `Console` for output. + /// println!("I'm on the bottom screen!"); + /// + /// top_console.select(); + /// + /// println!("Being on the upper screen is much better!"); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "consoleSelect")] pub fn select(&self) { unsafe { consoleSelect(self.context.as_ref() as *const _ as *mut _); } } - /// Clears all text from the console + /// Clear all text from the console. + #[doc(alias = "consoleClear")] pub fn clear(&self) { unsafe { consoleClear() } } - /// Resizes the active console to fit in a smaller portion of the screen. + /// Resize the console to fit in a smaller portion of the screen. + /// + /// # Notes /// /// The first two arguments are the desired coordinates of the top-left corner - /// of the console, and the second pair is the new width and height + /// of the console, and the second pair is the new width and height. /// /// # Safety - /// This function is unsafe because it does not validate that the input will produce - /// a console that actually fits on the screen + /// + /// This function is unsafe because it does not validate whether the input will produce + /// a console that actually fits on the screen. + // TODO: Wrap this safely. + #[doc(alias = "consoleSetWindow")] pub unsafe fn set_window(&mut self, x: i32, y: i32, width: i32, height: i32) { consoleSetWindow(self.context.as_mut(), x, y, width, height); } diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 004b461..41b09f8 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -1,3 +1,7 @@ +//! Error handling interface. +//! +//! This module holds the generic error and result types to interface with `ctru_sys` and the [`ctru-rs`](crate) safe wrapper. + use std::borrow::Cow; use std::error; use std::ffi::CStr; @@ -6,8 +10,29 @@ use std::ops::{ControlFlow, FromResidual, Try}; use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY}; +/// Custom type alias for generic [`ctru-rs`](crate) operations. +/// +/// This type is compatible with [`ctru_sys::Result`] codes. pub type Result = ::std::result::Result; +/// Validity checker of raw [`ctru_sys::Result`] codes. +/// +/// This struct supports the "try" syntax (`?`) to convert to an [`Error::Os`]. +/// +/// # Example +/// +/// ```no_run +/// use ctru::error::{Result, ResultCode}; +/// +/// pub fn hid_init() -> Result<()> { +/// // We run an unsafe function which returns a `ctru_sys::Result`. +/// let result: ctru_sys::Result = unsafe { ctru_sys::hidInit() }; +/// +/// // The result code is parsed and any possible error gets returned by the function. +/// ResultCode(result)?; +/// Ok(()) +/// } +/// ``` #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)] #[repr(transparent)] pub struct ResultCode(pub ctru_sys::Result); @@ -48,13 +73,20 @@ impl FromResidual for Result { } } -/// The error type returned by all libctru functions. +/// The generic error enum returned by [`ctru-rs`](crate) functions. +/// +/// This error enum supports parsing and displaying [`ctru_sys::Result`] codes. #[non_exhaustive] pub enum Error { + /// Raw [`ctru_sys::Result`] codes. Os(ctru_sys::Result), + /// Generic [`libc`] errors. Libc(String), + /// Requested service is already active and cannot be activated again. ServiceAlreadyActive, + /// `stdout` is already being redirected. OutputAlreadyRedirected, + /// The buffer provided by the user to store some data is shorter than required. BufferTooShort { /// Length of the buffer provided by the user. provided: usize, @@ -64,8 +96,9 @@ pub enum Error { } impl Error { - /// Create an [`Error`] out of the last set value in `errno`. This can be used - /// to get a human-readable error string from calls to `libc` functions. + /// Create an [`Error`] out of the last set value in `errno`. + /// + /// This can be used to get a human-readable error string from calls to `libc` functions. pub(crate) fn from_errno() -> Self { let error_str = unsafe { let errno = ctru_sys::errno(); diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 210d37f..ecc7dd9 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -1,16 +1,44 @@ +//! Safe and idiomatic Rust wrapper around [`libctru`](https://github.com/devkitPro/libctru). +//! +//! # About +//! +//! 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 develop userland applications by accessing access the underlying system services and the console's hardware +//! (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`](crate) 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 +//! +//! 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 +//! 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). + #![crate_type = "rlib"] #![crate_name = "ctru"] +#![warn(missing_docs)] #![feature(test)] #![feature(custom_test_frameworks)] #![feature(try_trait_v2)] #![feature(allocator_api)] -#![feature(nonnull_slice_from_raw_parts)] #![test_runner(test_runner::run)] +#![doc( + html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" +)] +#![doc( + html_logo_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" +)] // Nothing is imported from these crates but their inclusion here assures correct linking of the missing implementations. extern crate pthread_3ds; extern crate shim_3ds; +/// Expanded stack size used to spawn the main thread by `libctru`. +/// +/// 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 @@ -25,19 +53,24 @@ macro_rules! from_impl { }; } -/// Activate the default panic handler. +/// Activate the custom [`ctru-rs`](crate) panic handler. /// -/// With this implementation, the main thread will stop and try to print debug info to an available [console::Console]. -/// In case it fails to find an active [console::Console] the program will just exit. +/// With this implementation, the main thread will stop and try to print debug info to an available [`Console`](console::Console). +/// In case it fails to find an active [`Console`](console::Console) the program will just exit. /// /// # Notes /// -/// When ´test´ is enabled, this function won't do anything, as it should be overridden by the ´test´ environment. +/// When `test` is enabled, this function will not do anything, as its behaviour should be overridden by the `test` environment. pub fn use_panic_handler() { #[cfg(not(test))] panic_hook_setup(); } +/// Internal protocol to activate the custom panic handler hook. +/// +/// # Notes +/// +/// When `test` is enabled, this function will be ignored. #[cfg(not(test))] fn panic_hook_setup() { use crate::services::hid::{Hid, KeyPad}; diff --git a/ctru-rs/src/linear.rs b/ctru-rs/src/linear.rs index c07ce49..269e5dd 100644 --- a/ctru-rs/src/linear.rs +++ b/ctru-rs/src/linear.rs @@ -1,11 +1,12 @@ -//! Linear memory allocator +//! LINEAR memory allocator. //! -//! Linear memory is a sector of the 3DS' RAM that binds virtual addresses exactly to the physical address. -//! As such, it is used for fast and safe memory sharing between services (and is especially needed for GPU and DSP). +//! LINEAR memory is a sector of the 3DS' RAM that binds virtual addresses exactly to the physical address. +//! As such, it is used for fast and safe memory sharing between different hardware components (such as the GPU and the DSP processor). //! -//! Resources:
-//!
-//! +//! # Additional Resources +//! +//! - +//! - use std::alloc::{AllocError, Allocator, Layout}; use std::ptr::NonNull; @@ -15,19 +16,22 @@ use std::ptr::NonNull; // Sadly the linear memory allocator included in `libctru` doesn't implement `linearRealloc` at the time of these additions, // but the default fallback of the `std` will take care of that for us. -/// [`std::alloc::Allocator`] struct for LINEAR memory +/// [`Allocator`](std::alloc::Allocator) struct for LINEAR memory. +/// /// To use this struct the main crate must activate the `allocator_api` unstable feature. #[derive(Copy, Clone, Default, Debug)] pub struct LinearAllocator; impl LinearAllocator { - /// Returns the amount of free space left in the LINEAR sector + /// Returns the amount of free space left in the LINEAR memory sector. + #[doc(alias = "linearSpaceFree")] pub fn free_space() -> u32 { unsafe { ctru_sys::linearSpaceFree() } } } unsafe impl Allocator for LinearAllocator { + #[doc(alias = "linearAlloc", alias = "linearMemAlign")] fn allocate(&self, layout: Layout) -> Result, AllocError> { let pointer = unsafe { ctru_sys::linearMemAlign(layout.size(), layout.align()) }; @@ -36,6 +40,7 @@ unsafe impl Allocator for LinearAllocator { .ok_or(AllocError) } + #[doc(alias = "linearFree")] unsafe fn deallocate(&self, ptr: NonNull, _layout: Layout) { ctru_sys::linearFree(ptr.as_ptr().cast()); } diff --git a/ctru-rs/src/mii.rs b/ctru-rs/src/mii.rs index 17cb9bf..78fef80 100644 --- a/ctru-rs/src/mii.rs +++ b/ctru-rs/src/mii.rs @@ -1,213 +1,301 @@ -//! Mii Data +//! Mii data. //! //! This module contains the structs that represent all the data of a Mii. -//! This data is given by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector) +//! +//! Have a look at the [`MiiSelector`](crate::applets::mii_selector::MiiSelector) applet to learn how to ask the user for a specific Mii. -/// Represents the region lock of the console +/// Region lock of the Mii. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum RegionLock { + /// No region-lock. None, + /// Japan region-lock. Japan, + /// USA region-lock. USA, + /// Europe region-lock. Europe, } -/// Represent the charset of the console +/// Charset of the Mii. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Charset { + /// Japan-USA-Europe unified charset. JapanUSAEurope, + /// China charset. China, + /// Korea charset. Korea, + /// Taiwan charset. Taiwan, } -/// Represents the options of the Mii +/// Generic options of the Mii. #[derive(Copy, Clone, Debug)] -pub struct MiiDataOptions { +pub struct Options { + /// Whether it is allowed to copy the Mii. pub is_copying_allowed: bool, + /// Whether the profanity flag is active. pub is_profanity_flag_enabled: bool, + /// The Mii's active region-lock. pub region_lock: RegionLock, + /// The Mii's used charset. pub charset: Charset, } -/// Represents the position that the Mii has on the selector +/// Positional Index that the Mii has on the [`MiiSelector`](crate::applets::mii_selector::MiiSelector) window. #[derive(Copy, Clone, Debug)] pub struct SelectorPosition { + /// Index of the page where the Mii is found. pub page_index: u8, + /// Index of the slot (relative to the page) where the Mii is found. pub slot_index: u8, } -/// Represents the kind of origin console +/// Console model from which the Mii originated. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum OriginConsole { + /// Nintendo Wii. Wii, + /// Nintendo DSi. DSi, - /// Both New 3DS and Old 3DS + /// Nintendo 3DS. + /// + /// This includes all consoles of the 3DS family (3DS, 2DS, and their respective "New" or "XL" variants). N3DS, + /// Nintendo Wii U/Switch. WiiUSwitch, } -/// Represents the identity of the origin console +/// Identity of the origin console. #[derive(Copy, Clone, Debug)] pub struct ConsoleIdentity { + /// From which console the Mii originated from. pub origin_console: OriginConsole, } -/// Represents the sex of the Mii +/// Sex of the Mii. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum MiiSex { +pub enum Sex { + /// Male sex. Male, + /// Female sex. Female, } -/// Represents the details of the Mii +/// Generic details of the Mii. #[derive(Copy, Clone, Debug)] pub struct Details { - pub sex: MiiSex, + /// Sex of the Mii. + pub sex: Sex, + /// Birthday month. pub birthday_month: u8, + /// Birthday day. pub birthday_day: u8, + /// Color of the Mii's shirt. pub shirt_color: u8, + /// Whether the Mii is a favorite. pub is_favorite: bool, + /// Whether the Mii can be shared. + pub is_sharing_enabled: bool, } -/// Represents the face style of the Mii +/// Face style of the Mii. #[derive(Copy, Clone, Debug)] pub struct FaceStyle { - pub is_sharing_enabled: bool, + /// Face shape. pub shape: u8, + /// Skin color. pub skin_color: u8, } -/// Represents the face details of the Mii +/// Face details of the Mii. #[derive(Copy, Clone, Debug)] pub struct FaceDetails { + /// Face style. pub style: FaceStyle, + /// Wrinkles. pub wrinkles: u8, + /// Makeup. pub makeup: u8, } -/// Represents the hair details of the Mii +/// Hair details of the Mii. #[derive(Copy, Clone, Debug)] pub struct HairDetails { + /// Hair style. pub style: u8, + /// Hair color. pub color: u8, + /// Whether the Mii's hair is flipped. pub is_flipped: bool, } -/// Represents the eye details of the Mii +/// Eye details of the Mii. #[derive(Copy, Clone, Debug)] pub struct EyeDetails { + /// Eye style. pub style: u8, + /// Eye color. pub color: u8, + /// Eye scale. pub scale: u8, + /// Eye scale (y-axis). pub y_scale: u8, + /// Eye rotation. pub rotation: u8, - /// Spacing between the eyes + /// Spacing between the eyes. pub x_spacing: u8, + /// Eye height. pub y_position: u8, } -/// Represents the eyebrow details of the Mii +/// Eyebrow details of the Mii. #[derive(Copy, Clone, Debug)] pub struct EyebrowDetails { + /// Eyebrow style. pub style: u8, + /// Eyebrow color. pub color: u8, + /// Eyebrow scale. pub scale: u8, + /// Eyebrow scale (y-axis). pub y_scale: u8, + /// Eyebrow rotation. pub rotation: u8, /// Spacing between the eyebrows pub x_spacing: u8, + /// Eyebrow height. pub y_position: u8, } -/// Represents the details of the nose +/// Nose details of the Mii. #[derive(Copy, Clone, Debug)] pub struct NoseDetails { + /// Nose style. pub style: u8, + /// Nose scale. pub scale: u8, + /// Nose height. pub y_position: u8, } -/// Represents the details of the mouth +/// Mouth details of the Mii. #[derive(Copy, Clone, Debug)] pub struct MouthDetails { + /// Mouth style. pub style: u8, + /// Mouth color. pub color: u8, + /// Mouth scale. pub scale: u8, + /// Mouth scale (y-axis). pub y_scale: u8, + /// Mouth height. + pub y_position: u8, } -/// Represents the details of the mustache +/// Mustache details of the Mii. #[derive(Copy, Clone, Debug)] pub struct MustacheDetails { - pub mouth_y_position: u8, + /// Mustache style. pub mustache_style: u8, } -/// Represents the details of the beard +/// Beard details of the Mii. #[derive(Copy, Clone, Debug)] pub struct BeardDetails { + /// Beard style pub style: u8, + /// Beard color. pub color: u8, + /// Beard scale. pub scale: u8, + /// Beard height. pub y_position: u8, } -/// Represents the details of the glass +/// Glasses details of the Mii. #[derive(Copy, Clone, Debug)] -pub struct GlassDetails { +pub struct GlassesDetails { + /// Glasses style. pub style: u8, + /// Glasses color. pub color: u8, + /// Glasses scale. pub scale: u8, + /// Glasses height. pub y_position: u8, } -/// Represents the details of the mole +/// Mole details of the Mii. #[derive(Copy, Clone, Debug)] pub struct MoleDetails { + /// Whether the Mii has a mole. pub is_enabled: bool, + /// Mole scale. pub scale: u8, + /// Mole position (x-axis). pub x_position: u8, + /// Mole position (y-axis). pub y_position: u8, } -/// Represents all the data of a Mii +/// Full Mii data representation. /// -/// Some values are not ordered _like_ the Mii Editor UI. The mapped values can be seen here: -/// +/// Some values are not ordered *like* the Mii Editor UI. The mapped values can be seen [here](https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values). /// -/// This struct is returned by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector) +/// This struct can be retrieved by [`MiiSelector::launch()`](crate::applets::mii_selector::MiiSelector::launch). #[derive(Clone, Debug)] -pub struct MiiData { - pub options: MiiDataOptions, +pub struct Mii { + /// Mii options. + pub options: Options, + /// Position taken by the Mii on the Mii Selector screen. pub selector_position: SelectorPosition, + /// Console the Mii was created on. pub console_identity: ConsoleIdentity, /// Unique system ID, not dependant on the MAC address pub system_id: [u8; 8], + /// Console's MAC address. pub mac_address: [u8; 6], + /// General information about the Mii. pub details: Details, + /// Mii name. pub name: String, + /// Mii height. pub height: u8, + /// Mii width. pub width: u8, + /// Face details. pub face_details: FaceDetails, + /// Hair details. pub hair_details: HairDetails, + /// Eyes details. pub eye_details: EyeDetails, + /// Eyebrow details. pub eyebrow_details: EyebrowDetails, + /// Nose details. pub nose_details: NoseDetails, + /// Mouth details. pub mouth_details: MouthDetails, + /// Mustache details. pub mustache_details: MustacheDetails, + /// Beard details. pub beard_details: BeardDetails, - pub glass_details: GlassDetails, + /// Glasses details. + pub glass_details: GlassesDetails, + /// Mole details. pub mole_details: MoleDetails, + /// Name of the Mii's original author. pub author_name: String, } -impl From for MiiData { +impl From for Mii { fn from(mii_data: ctru_sys::MiiData) -> Self { let raw_mii_data = mii_data._bindgen_opaque_blob; // Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii @@ -272,7 +360,7 @@ impl From for MiiData { let name = utf16_byte_pairs_to_string(raw_utf16_name); let author_name = utf16_byte_pairs_to_string(raw_utf16_author); - let options = MiiDataOptions { + let options = Options { is_copying_allowed: raw_options[0], is_profanity_flag_enabled: raw_options[1], region_lock: { @@ -312,19 +400,19 @@ impl From for MiiData { let details = Details { sex: { match raw_details[0] { - true => MiiSex::Female, - false => MiiSex::Male, + true => Sex::Female, + false => Sex::Male, } }, birthday_month: partial_u8_bits_to_u8(&raw_details[1..=4]), birthday_day: partial_u8_bits_to_u8(&raw_details[5..=9]), shirt_color: partial_u8_bits_to_u8(&raw_details[10..=13]), is_favorite: raw_details[14], + is_sharing_enabled: !raw_face_style[0], }; let face_details = FaceDetails { style: FaceStyle { - is_sharing_enabled: !raw_face_style[0], shape: partial_u8_bits_to_u8(&raw_face_style[1..=4]), skin_color: partial_u8_bits_to_u8(&raw_face_style[5..=7]), }, @@ -371,10 +459,10 @@ impl From for MiiData { color: partial_u8_bits_to_u8(&raw_mouth_details[6..=8]), scale: partial_u8_bits_to_u8(&raw_mouth_details[9..=12]), y_scale: partial_u8_bits_to_u8(&raw_mouth_details[13..=15]), + y_position: partial_u8_bits_to_u8(&raw_mustache_details[0..=4]), }; let mustache_details = MustacheDetails { - mouth_y_position: partial_u8_bits_to_u8(&raw_mustache_details[0..=4]), mustache_style: partial_u8_bits_to_u8(&raw_mustache_details[5..=7]), }; @@ -385,7 +473,7 @@ impl From for MiiData { y_position: partial_u8_bits_to_u8(&raw_beard_details[10..=14]), }; - let glass_details = GlassDetails { + let glass_details = GlassesDetails { style: partial_u8_bits_to_u8(&raw_glass_details[0..=3]), color: partial_u8_bits_to_u8(&raw_glass_details[4..=6]), scale: partial_u8_bits_to_u8(&raw_glass_details[7..=10]), @@ -399,7 +487,7 @@ impl From for MiiData { y_position: partial_u8_bits_to_u8(&raw_mole_details[10..=14]), }; - MiiData { + Mii { options, selector_position, console_identity, diff --git a/ctru-rs/src/prelude.rs b/ctru-rs/src/prelude.rs index 74faa41..ee18eab 100644 --- a/ctru-rs/src/prelude.rs +++ b/ctru-rs/src/prelude.rs @@ -1,2 +1,11 @@ +//! `use ctru::prelude::*;` to import common services, members and functions. +//! +//! Particularly useful when writing very small applications. + pub use crate::console::Console; -pub use crate::services::{gfx::Gfx, hid::KeyPad, soc::Soc, Apt, Hid}; +pub use crate::services::{ + apt::Apt, + gfx::Gfx, + hid::{Hid, KeyPad}, + soc::Soc, +}; diff --git a/ctru-rs/src/services/am.rs b/ctru-rs/src/services/am.rs index 70bd151..68b3af3 100644 --- a/ctru-rs/src/services/am.rs +++ b/ctru-rs/src/services/am.rs @@ -1,67 +1,78 @@ +//! 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. +//! +//! TODO: [`ctru-rs`](crate) doesn't support installing or uninstalling titles yet. +#![doc(alias = "app")] +#![doc(alias = "manager")] + 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 - } -} +/// General information about a specific title entry. +#[doc(alias = "AM_TitleEntry")] pub struct Title<'a> { id: u64, mediatype: FsMediaType, + size: u64, + version: u16, _am: PhantomData<&'a Am>, } impl<'a> Title<'a> { + /// Returns this title's ID. pub fn id(&self) -> u64 { self.id } - pub fn product_code(&self) -> crate::Result { + /// Returns this title's unique product code. + #[doc(alias = "AM_GetTitleProductCode")] + 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()) - } - pub fn title_info(&self) -> crate::Result { - let mut info = MaybeUninit::zeroed(); + String::from_utf8_lossy(&buf).to_string() + } - unsafe { - ResultCode(ctru_sys::AM_GetTitleInfo( - self.mediatype.into(), - 1, - &mut self.id.clone(), - info.as_mut_ptr() as _, - ))?; + /// Returns the size of this title in bytes. + pub fn size(&self) -> u64 { + self.size + } - Ok(info.assume_init()) - } + /// Returns the installed version of this title. + pub fn version(&self) -> u16 { + self.version } } +/// Handle to the Application Manager service. pub struct Am(()); impl Am { + /// 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 { ResultCode(ctru_sys::amInit())?; @@ -69,6 +80,27 @@ 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 { let mut count = 0; @@ -77,10 +109,32 @@ 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, @@ -89,11 +143,27 @@ 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, + .map(|title| Title { + id: title.titleID, mediatype, + size: title.size, + version: title.version, _am: PhantomData, }) .collect()) @@ -101,6 +171,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..b30be85 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -1,8 +1,33 @@ +//! Applet service. +//! +//! The APT service handles integration with other applications, +//! including high-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 service handle. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::apt::Apt; + /// + /// let apt = Apt::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "aptInit")] pub fn new() -> crate::Result { unsafe { ResultCode(ctru_sys::aptInit())?; @@ -10,10 +35,45 @@ 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. + /// + /// # Example + /// + /// ```no_run + /// use std::error::Error; + /// use ctru::services::apt::Apt; + /// + /// // In a simple `main` function, the structure should be the following. + /// fn main() -> Result<(), Box> { + /// + /// let apt = Apt::new()?; + /// + /// while apt.main_loop() { + /// // Main program logic should be written here. + /// } + /// + /// // Optional clean-ups after running the application should be written after the main loop. + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "aptMainLoop")] pub fn main_loop(&self) -> bool { unsafe { ctru_sys::aptMainLoop() } } + /// Set (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 +83,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/cam.rs b/ctru-rs/src/services/cam.rs index 4a06421..8d4af7b 100644 --- a/ctru-rs/src/services/cam.rs +++ b/ctru-rs/src/services/cam.rs @@ -1,174 +1,232 @@ -//! Camera service +//! Camera service. //! -//! The CAM service provides access to the cameras. Cameras can return images -//! in the form of byte vectors which can be displayed or used in other ways. +//! The CAM service provides access to the built-in cameras. [`Camera`]s can return images +//! in the form of byte vectors which can be displayed to the screen or used in other ways. +#![doc(alias = "camera")] use crate::error::{Error, ResultCode}; use crate::services::gspgpu::FramebufferFormat; use ctru_sys::Handle; use std::time::Duration; -/// A reference-counted handle to the CAM service and the usable cameras. -/// The service is closed when all instances of this struct fall out of scope. -/// -/// This service requires no special permissions to use. +/// Handle to the Camera service. #[non_exhaustive] pub struct Cam { + /// Inside-facing camera. pub inner_cam: InwardCam, + /// Outside-facing right camera. pub outer_right_cam: OutwardRightCam, + /// Outside-facing left camera. pub outer_left_cam: OutwardLeftCam, + /// Both outside-facing cameras (mainly used for 3D photos). pub both_outer_cams: BothOutwardCam, } -/// Flag to pass to [Camera::flip_image] +/// Different kinds of flip modes. +/// +/// See [`Camera::flip_image()`] to learn how to use this. +#[doc(alias = "CAMU_Flip")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum FlipMode { + /// No flip. None = ctru_sys::FLIP_NONE, + /// Horizontal flip. Horizontal = ctru_sys::FLIP_HORIZONTAL, + /// Vertical flip. Vertical = ctru_sys::FLIP_VERTICAL, + /// Both vertical and horizontal flip. Reverse = ctru_sys::FLIP_REVERSE, } -/// Flag to pass to [Camera::set_view_size] +/// Size of the camera view. +/// +/// See [`Camera::set_view_size()`] to learn how to use this. +#[doc(alias = "CAMU_Size")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum ViewSize { + /// Size of the 3DS' top screen. (400 × 240) + /// + /// Useful if the image is meant to be displayed immediately. TopLCD = ctru_sys::SIZE_CTR_TOP_LCD, - /// Equivalent to QVga + /// Size of the 3DS' bottom screen. (320 × 240) + /// + /// Equivalent to QVga. BottomLCD = ctru_sys::SIZE_CTR_BOTTOM_LCD, + /// VGA display size. (640 × 480) Vga = ctru_sys::SIZE_VGA, + /// QQVGA display size. (160 × 120) QQVga = ctru_sys::SIZE_QQVGA, + /// CIF display size. (352 × 288) Cif = ctru_sys::SIZE_CIF, + /// QCIF display size. (176 × 144) QCif = ctru_sys::SIZE_QCIF, - /// Nintendo DS Screen + /// Nintendo DS Screen size. (256 × 192) DS = ctru_sys::SIZE_DS_LCD, - /// Nintendo DS Screen x4 + /// Nintendo DS Screen size x4. (512 × 384) DSX4 = ctru_sys::SIZE_DS_LCDx4, } -/// Flag to pass to [Camera::set_frame_rate] +/// Framerate settings. +/// +/// See [`Camera::set_frame_rate()`] to learn how to use this. +#[doc(alias = "CAMU_FramRate")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum FrameRate { + /// 15 FPS. Fps15 = ctru_sys::FRAME_RATE_15, + /// 15 to 5 FPS. Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5, + /// 15 to 2 FPS. Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2, + /// 10 FPS. Fps10 = ctru_sys::FRAME_RATE_10, + /// 8.5 FPS. Fps8_5 = ctru_sys::FRAME_RATE_8_5, + /// 5 FPS. Fps5 = ctru_sys::FRAME_RATE_5, + /// 20 FPS. Fps20 = ctru_sys::FRAME_RATE_20, + /// 20 to 5 FPS. Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5, + /// 30 FPS. Fps30 = ctru_sys::FRAME_RATE_30, + /// 30 to 5 FPS. Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5, + /// 15 to 10 FPS. Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10, + /// 20 to 10 FPS. Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10, + /// 30 to 10 FPS. Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10, } -/// Flag to pass to [Camera::set_white_balance] or -/// [Camera::set_white_balance_without_base_up] +/// White balance settings. +/// +/// See [`Camera::set_white_balance()`] and [`Camera::set_white_balance_without_base_up()`] to learn how to use this. +#[doc(alias = "CAMU_WhiteBalance")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum WhiteBalance { - /// Normal + /// Automatic white balance. Auto = ctru_sys::WHITE_BALANCE_AUTO, - /// Tungsten + /// Tungsten. Temp3200K = ctru_sys::WHITE_BALANCE_3200K, - /// Fluorescent Light + /// Fluorescent Light. Temp4150K = ctru_sys::WHITE_BALANCE_4150K, - /// Daylight + /// Daylight. Temp5200K = ctru_sys::WHITE_BALANCE_5200K, - /// Cloudy/Horizon + /// Cloudy/Horizon. Temp6000K = ctru_sys::WHITE_BALANCE_6000K, - ///Shade + /// Shade. Temp7000K = ctru_sys::WHITE_BALANCE_7000K, } -/// Flag to pass to [Camera::set_photo_mode] +/// Photo mode settings. +/// +/// See [`Camera::set_photo_mode()`] to learn how to use this. +#[doc(alias = "CAMU_PhotoMode")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum PhotoMode { + /// Normal mode. Normal = ctru_sys::PHOTO_MODE_NORMAL, + /// Portrait mode. Portrait = ctru_sys::PHOTO_MODE_PORTRAIT, + /// Landscape mode. Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE, + /// NightView mode. NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW, + /// Letter mode. Letter = ctru_sys::PHOTO_MODE_LETTER, } -/// Flag to pass to [Camera::set_effect] +/// Special camera effects. +/// +/// See [`Camera::set_effect()`] to learn how to use this. +#[doc(alias = "CAMU_Effect")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Effect { + /// No effects. None = ctru_sys::EFFECT_NONE, + /// Mono effect. Mono = ctru_sys::EFFECT_MONO, + /// Sepia effect. Sepia = ctru_sys::EFFECT_SEPIA, + /// Negative effect. Negative = ctru_sys::EFFECT_NEGATIVE, + /// Negative film effect. Negafilm = ctru_sys::EFFECT_NEGAFILM, + /// Sepia effect. + /// + /// The difference between this and [`Sepia`](Effect::Sepia) is unknown. Sepia01 = ctru_sys::EFFECT_SEPIA01, } -/// Flag to pass to [Camera::set_contrast] +/// Contrast settings. +/// +/// See [`Camera::set_contrast()`] to learn how to use this. +#[doc(alias = "CAMU_Contrast")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Contrast { - /// OFF + /// Low contrast. Low = ctru_sys::CONTRAST_LOW, - /// Brightness ratio: 70 + /// Brightness ratio: 70. Normal = ctru_sys::CONTRAST_NORMAL, - /// Brightness ratio: 90 + /// Brightness ratio: 90. High = ctru_sys::CONTRAST_HIGH, } -/// Flag to pass to [Camera::set_lens_correction] +/// Lens correction settings. +/// +/// See [`Camera::set_lens_correction()`] to learn how to use this. +#[doc(alias = "CAMU_LensCorrection")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum LensCorrection { + /// No lens correction. Off = ctru_sys::LENS_CORRECTION_DARK, + /// Normal lens correction. Normal = ctru_sys::LENS_CORRECTION_NORMAL, + /// Bright lens correction. Bright = ctru_sys::LENS_CORRECTION_BRIGHT, } -/// Flag to pass to [Camera::set_output_format] +/// Image output format. +/// +/// See [`Camera::set_output_format()`] to learn how to use this. +#[doc(alias = "CAMU_OutputFormat")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum OutputFormat { + /// YUV422 output format. 16 bits per pixel. Yuv422 = ctru_sys::OUTPUT_YUV_422, + /// RGB565 output format. 16 bits per pixel. Rgb565 = ctru_sys::OUTPUT_RGB_565, } -/// Flag to pass to [Cam::play_shutter_sound] +/// Playable shutter sounds. +/// +/// See [`Cam::play_shutter_sound()`] to learn how to use this. +#[doc(alias = "CAMU_ShutterSoundType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum ShutterSound { + /// Photo shutter sound. Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL, + /// Shutter sound to begin a movie recording. Movie = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE, + /// Shutter sound to finish a movie recording. MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END, } -impl TryFrom for OutputFormat { - type Error = (); - - fn try_from(value: FramebufferFormat) -> Result { - match value { - FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565), - _ => Err(()), - } - } -} - -impl TryFrom for FramebufferFormat { - type Error = (); - - fn try_from(value: OutputFormat) -> Result { - match value { - OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565), - _ => Err(()), - } - } -} - -/// Struct containing coordinates passed to [Camera::set_trimming_params]. +/// Parameters to handle image trimming. +/// +/// See [`Camera::set_trimming_params()`] to learn how to use this. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TrimmingParams { x_start: i16, @@ -178,10 +236,12 @@ pub struct TrimmingParams { } impl TrimmingParams { - /// Creates a new [CamTrimmingParams] and guarantees the start coordinates are less than or + /// Creates a new [`TrimmingParams`] and guarantees the start coordinates are less than or /// equal to the end coordinates. /// - /// `x_start <= x_end && y_start <= y_end` + /// # Panics + /// + /// This function panics if the start coordinates are larger than the end coordinates (for each axis). pub fn new(x_start: i16, y_start: i16, x_end: i16, y_end: i16) -> TrimmingParams { assert!(x_start <= x_end && y_start <= y_end); Self { @@ -193,15 +253,20 @@ impl TrimmingParams { } } -/// Represents data used by the camera to calibrate image quality +/// Data used by the camera to calibrate image quality for a single camera. +#[doc(alias = "CAMU_ImageQualityCalibrationData")] #[derive(Default, Clone, Copy, Debug)] pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibrationData); -/// Represents data used by the camera to calibrate image quality when using both outward cameras +/// Data used by the camera to calibrate image quality when using both outward cameras. +// TODO: Implement Stereo camera calibration. +#[doc(alias = "CAMU_StereoCameraCalibrationData")] #[derive(Default, Clone, Copy, Debug)] pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); -/// Represents the camera on the inside of the 3DS +/// Inward camera representation (facing the user of the 3DS). +/// +/// Usually used for selfies. #[non_exhaustive] pub struct InwardCam; @@ -211,8 +276,7 @@ impl Camera for InwardCam { } } -/// Represents the the outer right camera when the 3DS is open and the dual cameras are pointed -/// away from the user +/// Right-side outward camera representation. #[non_exhaustive] pub struct OutwardRightCam; @@ -222,8 +286,7 @@ impl Camera for OutwardRightCam { } } -/// Represents the the outer left camera when the 3DS is open and the dual cameras are pointed -/// away from the user +/// Left-side outward camera representation. #[non_exhaustive] pub struct OutwardLeftCam; @@ -233,13 +296,15 @@ impl Camera for OutwardLeftCam { } } -/// Represents the both outer cameras combined +/// Both outer cameras combined. +/// +/// Usually used for 3D photos. #[non_exhaustive] pub struct BothOutwardCam; impl BothOutwardCam { - /// Sets whether to enable or disable synchronization - /// of brightness for both left and right cameras + /// Set whether to enable or disable brightness synchronization between the two cameras. + #[doc(alias = "CAMU_SetBrightnessSynchronization")] pub fn set_brightness_synchronization( &mut self, brightness_synchronization: bool, @@ -263,17 +328,37 @@ impl Camera for BothOutwardCam { } } -/// Represents a camera and its functionality +/// Generic functionality common to all cameras. +// TODO: Change "set true/set parameters" scheme (classic of C code) into a single "set parameter" scheme using enums. This is valid for stuff such as [`TrimmingParams`] pub trait Camera { - /// Returns the raw value of the selected camera + /// Returns the raw value of the selected camera. fn camera_as_raw(&self) -> ctru_sys::u32_; - /// Returns the raw port of the selected camera + /// Returns the raw port of the selected camera. fn port_as_raw(&self) -> ctru_sys::u32_ { ctru_sys::PORT_CAM1 } - /// Returns true if the camera is busy (receiving data) + /// Returns `true` if the camera is busy (receiving data). + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::{Cam, Camera}; + /// let cam = Cam::new()?; + /// + /// let inward = &cam.inner_cam; + /// + /// // Inward cam is not busy since it is not being used. + /// assert!(!inward.is_busy()?); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CAMU_IsBusy")] fn is_busy(&self) -> crate::Result { unsafe { let mut is_busy = false; @@ -283,7 +368,26 @@ pub trait Camera { } /// Returns the maximum amount of transfer bytes based on the view size, trimming, and other - /// modifications set to the camera + /// modifications set to the camera. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::{Cam, Camera}; + /// let cam = Cam::new()?; + /// + /// let inward = &cam.inner_cam; + /// + /// // Inward cam is not busy since it is not being used. + /// let transfer_count = inward.transfer_byte_count(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CAMU_GetTransferBytes")] fn transfer_byte_count(&self) -> crate::Result { unsafe { let mut transfer_bytes = 0; @@ -295,8 +399,10 @@ pub trait Camera { } } - /// Sets whether or not the camera should trim the image based on parameters set by - /// [Camera::set_trimming_params] + /// Set whether or not the camera should trim the image. + /// + /// [`TrimmingParams`] can be set via [`Camera::set_trimming_params`]. + #[doc(alias = "CAMU_SetTrimming")] fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), enabled))?; @@ -304,7 +410,8 @@ pub trait Camera { } } - /// Returns whether or not trimming is currently enabled for the camera + /// Returns whether or not trimming is currently enabled for the camera. + #[doc(alias = "CAMU_IsTrimming")] fn is_trimming_enabled(&self) -> crate::Result { unsafe { let mut trimming = false; @@ -313,7 +420,10 @@ pub trait Camera { } } - /// Sets trimming parameters based on coordinates specified inside a [TrimmingParams] + /// Set trimming bounds based on image coordinates. + /// + /// For trimming to take effect it is required to pass `true` into [`Camera::set_trimming()`]. + #[doc(alias = "CAMU_SetTrimmingParams")] fn set_trimming_params(&mut self, params: TrimmingParams) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetTrimmingParams( @@ -327,7 +437,8 @@ pub trait Camera { } } - /// Returns the [TrimmingParams] set + /// Returns the [`TrimmingParams`] currently set. + #[doc(alias = "CAMU_GetTrimmingParams")] fn trimming_params(&self) -> crate::Result { unsafe { let mut x_start = 0; @@ -351,9 +462,14 @@ pub trait Camera { } } - /// Sets the trimming parameters revolving around the center of the image. + /// Set the trimming bounds relatively to the center of the image. + /// + /// # Notes + /// /// The new width will be `trim_width / 2` to the left and right of the center. /// The new height will be `trim_height / 2` above and below the center. + // TODO: This function doesn't use `TrimmingParams`. It'd be better to merge it with `set_trimming_params()` and change the `TrimmingParams` representation. + #[doc(alias = "CAMU_SetTrimmingParamsCenter")] fn set_trimming_params_center( &mut self, trim_width: i16, @@ -373,7 +489,8 @@ pub trait Camera { } } - /// Sets the exposure level of the camera + /// Set the exposure level of the camera.å + #[doc(alias = "CAMU_SetExposure")] fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetExposure(self.camera_as_raw(), exposure))?; @@ -381,7 +498,8 @@ pub trait Camera { } } - /// Sets the white balance mod of the camera based on the passed [WhiteBalance] argument + /// Set the white balance of the camera. + #[doc(alias = "CAMU_SetWhiteBalance")] fn set_white_balance(&mut self, white_balance: WhiteBalance) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetWhiteBalance( @@ -392,8 +510,9 @@ pub trait Camera { } } - /// Sets the white balance mode of the camera based on the passed [WhiteBalance] argument - // TODO: Explain base up + /// Set the white balance of the camera. + // TODO: Explain what "without base up" means. + #[doc(alias = "CAMU_SetWhiteBalanceWithoutBaseUp")] fn set_white_balance_without_base_up( &mut self, white_balance: WhiteBalance, @@ -407,7 +526,8 @@ pub trait Camera { } } - /// Sets the sharpness of the camera + /// Set the sharpness of the camera. + #[doc(alias = "CAMU_SetSharpness")] fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetSharpness(self.camera_as_raw(), sharpness))?; @@ -415,7 +535,8 @@ pub trait Camera { } } - /// Sets whether auto exposure is enabled or disabled for the camera + /// Set whether auto exposure is enabled or disabled for the camera. + #[doc(alias = "CAMU_SetAutoExposure")] fn set_auto_exposure(&mut self, enabled: bool) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetAutoExposure( @@ -426,7 +547,8 @@ pub trait Camera { } } - /// Returns true if auto exposure is enabled for the camera + /// Returns `true` if auto exposure is enabled for the camera. + #[doc(alias = "CAMU_IsAutoExposure")] fn is_auto_exposure_enabled(&self) -> crate::Result { unsafe { let mut enabled = false; @@ -438,7 +560,8 @@ pub trait Camera { } } - /// Sets whether auto white balance is enabled or disabled for the camera + /// Set whether auto white balance is enabled or disabled for the camera. + #[doc(alias = "CAMU_SetAutoWhiteBalance")] fn set_auto_white_balance(&mut self, enabled: bool) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetAutoWhiteBalance( @@ -449,7 +572,8 @@ pub trait Camera { } } - /// Returns true if auto white balance is enabled for the camera + /// Returns `true` if auto white balance is enabled for the camera. + #[doc(alias = "CAMU_IsAutoWhiteBalance")] fn is_auto_white_balance_enabled(&self) -> crate::Result { unsafe { let mut enabled = false; @@ -461,7 +585,8 @@ pub trait Camera { } } - /// Sets the flip direction of the camera's image based on the passed [FlipMode] argument + /// Set the flip mode of the camera's image. + #[doc(alias = "CAMU_FlipImage")] fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_FlipImage( @@ -473,7 +598,7 @@ pub trait Camera { } } - /// Sets the image resolution of the camera in detail + /// Set the image resolution of the camera in detail. /// /// # Errors /// @@ -481,10 +606,12 @@ pub trait Camera { /// coordinates of the second crop point. /// /// # Arguments + /// /// * `width` - Width of the image /// * `height` - height of the image /// * `crop_0` - The first crop point in which the image will be trimmed - /// * `crop_0` - The second crop point in which the image will be trimmed + /// * `crop_1` - The second crop point in which the image will be trimmed + #[doc(alias = "CAMU_SetDetailSize")] fn set_detail_size( &mut self, width: i16, @@ -507,7 +634,8 @@ pub trait Camera { } } - /// Sets the view size of the camera based on the passed [ViewSize] argument. + /// Set the view size of the camera. + #[doc(alias = "CAMU_SetSize")] fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetSize( @@ -519,7 +647,8 @@ pub trait Camera { } } - /// Sets the frame rate of the camera based on the passed [FrameRate] argument. + /// Set the frame rate of the camera. + #[doc(alias = "CAMU_SetFrameRate")] fn set_frame_rate(&mut self, frame_rate: FrameRate) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetFrameRate( @@ -530,7 +659,8 @@ pub trait Camera { } } - /// Sets the photo mode of the camera based on the passed [PhotoMode] argument. + /// Set the photo mode of the camera. + #[doc(alias = "CAMU_SetPhotoMode")] fn set_photo_mode(&mut self, photo_mode: PhotoMode) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetPhotoMode( @@ -541,9 +671,13 @@ pub trait Camera { } } - /// Sets the effect of the camera based on the passed [Effect] argument. + /// Set the effect of the camera. + /// + /// # Notes /// - /// Multiple effects can be set at once by combining the bitflags of [CamEffect] + /// This operation will override any previously set [`Effect`]s. + /// Multiple effects can be set at once by combining the bitflags of [`Effect`]. + #[doc(alias = "CAMU_SetEffect")] fn set_effect(&mut self, effect: Effect) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetEffect( @@ -555,7 +689,8 @@ pub trait Camera { } } - /// Sets the contrast of the camera based on the passed [Contrast] argument. + /// Set the contrast of the camera. + #[doc(alias = "CAMU_SetContrast")] fn set_contrast(&mut self, contrast: Contrast) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetContrast( @@ -566,7 +701,8 @@ pub trait Camera { } } - /// Sets the lens correction of the camera based on the passed [LensCorrection] argument. + /// Set the lens correction of the camera. + #[doc(alias = "CAMU_SetLensCorrection")] fn set_lens_correction(&mut self, lens_correction: LensCorrection) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetLensCorrection( @@ -577,7 +713,8 @@ pub trait Camera { } } - /// Sets the output format of the camera based on the passed [OutputFormat] argument. + /// Set the output format of the camera. + #[doc(alias = "CAMU_SetOutputFormat")] fn set_output_format(&mut self, format: OutputFormat) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetOutputFormat( @@ -589,7 +726,7 @@ pub trait Camera { } } - /// Sets the region in which auto exposure should be based on. + /// Set the region in which auto exposure should be based on. /// /// # Arguments /// @@ -597,6 +734,7 @@ pub trait Camera { /// * `y` - Starting y coordinate of the window /// * `width` - Width of the window /// * `height` - Height of the window + #[doc(alias = "CAMU_SetAutoExposureWindow")] fn set_auto_exposure_window( &mut self, x: i16, @@ -616,7 +754,7 @@ pub trait Camera { } } - /// Sets the region in which auto white balance should be based on. + /// Set the region in which auto white balance should be based on. /// /// # Arguments /// @@ -624,6 +762,11 @@ pub trait Camera { /// * `y` - Starting y coordinate of the window /// * `width` - Width of the window /// * `height` - Height of the window + /// + /// # Notes + /// + /// To activate automatic white balance, you must pass [`WhiteBalance::Auto`] into [`Camera::set_white_balance()`]. + #[doc(alias = "CAMU_SetAutoWhiteBalanceWindow")] fn set_auto_white_balance_window( &mut self, x: i16, @@ -643,7 +786,8 @@ pub trait Camera { } } - /// Sets whether the noise filter should be enabled or disabled for the camera + /// Set whether the noise filter should be enabled or disabled for the camera. + #[doc(alias = "CAMU_SetNoiseFilter")] fn set_noise_filter(&mut self, enabled: bool) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetNoiseFilter(self.camera_as_raw(), enabled))?; @@ -651,8 +795,8 @@ pub trait Camera { } } - /// Sets the image quality calibration data for the camera based on the passed in - /// [ImageQualityCalibrationData] argument + /// Set the [`ImageQualityCalibrationData`] for the camera. + #[doc(alias = "CAMU_SetImageQualityCalibrationData")] fn set_image_quality_calibration_data( &mut self, data: ImageQualityCalibrationData, @@ -663,7 +807,8 @@ pub trait Camera { } } - /// Returns the current [ImageQualityCalibrationData] for the camera + /// Returns the current [`ImageQualityCalibrationData`] for the camera. + #[doc(alias = "CAMU_GetImageQualityCalibrationData")] fn image_quality_calibration_data(&self) -> crate::Result { unsafe { let mut data = ImageQualityCalibrationData::default(); @@ -672,8 +817,9 @@ pub trait Camera { } } - /// Sets the camera as the current sleep camera + /// Set the camera as the current sleep camera. // TODO: Explain sleep camera + #[doc(alias = "CAMU_SetSleepCamera")] fn set_sleep_camera(&mut self) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_SetSleepCamera(self.camera_as_raw()))?; @@ -681,17 +827,48 @@ pub trait Camera { } } - /// Requests the camera to take a picture and write it in a buffer. + /// Request the camera to take a picture and write it in a buffer. /// /// # Errors /// - /// This will error if the camera is busy or if the timeout duration is reached. + /// This function will return an error if the camera is busy or if the timeout duration gets reached. /// /// # Arguments /// /// * `width` - Width of the desired image /// * `height` - Height of the desired image /// * `timeout` - Duration to wait for the image + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # use std::time::Duration; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::{Cam, Camera, ViewSize, OutputFormat}; + /// let mut cam = Cam::new()?; + /// + /// // We borrow the inward facing `Camera`. + /// let inward = &mut cam.inner_cam; + /// + /// inward.set_view_size(ViewSize::TopLCD)?; + /// inward.set_output_format(OutputFormat::Rgb565)?; + /// inward.set_noise_filter(true)?; + /// inward.set_auto_exposure(true)?; + /// inward.set_auto_white_balance(true)?; + /// + /// // Size of the top screen buffer at 2 bytes per pixel (RGB565). + /// let mut buffer = vec![0; 400*240*2]; + /// + /// // Take picture with 3 seconds of timeout. + /// inward.take_picture(&mut buffer, 400, 240, Duration::from_secs(3)); + /// # + /// # Ok(()) + /// # } + /// ``` + // TODO: This should use the value passed within `set_view_size` rather than arbitrary `width` and `height` values. + // Furthermore, it's pretty unclear what the "default" view size is. What happens if the user doesn't set it before taking the picture? fn take_picture( &mut self, buffer: &mut [u8], @@ -764,13 +941,28 @@ pub trait Camera { } impl Cam { - /// Initializes the CAM service. + /// Initialize a new service handle. /// /// # Errors /// /// This function will return an error if the service was unable to be initialized. /// Since this service requires no special or elevated permissions, errors are /// rare in practice. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::Cam; + /// + /// let cam = Cam::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "camInit")] pub fn new() -> crate::Result { unsafe { ResultCode(ctru_sys::camInit())?; @@ -783,7 +975,30 @@ impl Cam { } } - /// Plays the specified sound based on the [ShutterSound] argument + /// Play the specified sound based on the [`ShutterSound`] argument + /// + /// # Notes + /// + /// Playing the shutter sound does not require a living handle to the [`Ndsp`](crate::services::ndsp::Ndsp) service. + /// Volume will always be maxed out to ensure everyone within photo range can hear the picture being taken (as by Japanese law). + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cam::{Cam, ShutterSound}; + /// let cam = Cam::new()?; + /// + /// // We play the shutter sound on the console's speakers! + /// // (even though we aren't taking a photo :P) + /// cam.play_shutter_sound(ShutterSound::Normal); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CAMU_PlayShutterSound")] pub fn play_shutter_sound(&self, sound: ShutterSound) -> crate::Result<()> { unsafe { ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?; @@ -793,11 +1008,34 @@ impl Cam { } impl Drop for Cam { + #[doc(alias = "camExit")] fn drop(&mut self) { unsafe { ctru_sys::camExit() }; } } +impl TryFrom for OutputFormat { + type Error = (); + + fn try_from(value: FramebufferFormat) -> Result { + match value { + FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565), + _ => Err(()), + } + } +} + +impl TryFrom for FramebufferFormat { + type Error = (); + + fn try_from(value: OutputFormat) -> Result { + match value { + OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565), + _ => Err(()), + } + } +} + from_impl!(FlipMode, ctru_sys::CAMU_Flip); from_impl!(ViewSize, ctru_sys::CAMU_Size); from_impl!(FrameRate, ctru_sys::CAMU_FrameRate); diff --git a/ctru-rs/src/services/cfgu.rs b/ctru-rs/src/services/cfgu.rs index a9cc411..b1cbe76 100644 --- a/ctru-rs/src/services/cfgu.rs +++ b/ctru-rs/src/services/cfgu.rs @@ -1,72 +1,123 @@ -//! Configuration service +//! System Configuration service. //! -//! This module contains basic methods to retrieve and change configuration from the console. +//! This module contains basic methods to retrieve the console's system configuration. +#![doc(alias = "configuration")] use crate::error::ResultCode; +/// Console region. +#[doc(alias = "CFG_Region")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Region { + /// Japan. Japan = ctru_sys::CFG_REGION_JPN, + /// USA. USA = ctru_sys::CFG_REGION_USA, + /// Europe. Europe = ctru_sys::CFG_REGION_EUR, + /// Australia. Australia = ctru_sys::CFG_REGION_AUS, + /// China. China = ctru_sys::CFG_REGION_CHN, + /// Korea. Korea = ctru_sys::CFG_REGION_KOR, + /// Taiwan. Taiwan = ctru_sys::CFG_REGION_TWN, } +/// Language set for the console's OS. +#[doc(alias = "CFG_Language")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Language { + /// Japanese. Japanese = ctru_sys::CFG_LANGUAGE_JP, + /// English. English = ctru_sys::CFG_LANGUAGE_EN, + /// French. French = ctru_sys::CFG_LANGUAGE_FR, + /// German. German = ctru_sys::CFG_LANGUAGE_DE, + /// Italian. Italian = ctru_sys::CFG_LANGUAGE_IT, + /// Spanish. Spanish = ctru_sys::CFG_LANGUAGE_ES, - SimplifiedChinese = ctru_sys::CFG_LANGUAGE_ZH, + /// Korean. Korean = ctru_sys::CFG_LANGUAGE_KO, + /// Dutch. Dutch = ctru_sys::CFG_LANGUAGE_NL, + /// Portuguese. Portuguese = ctru_sys::CFG_LANGUAGE_PT, + /// Russian. Russian = ctru_sys::CFG_LANGUAGE_RU, + /// Simplified Chinese. + SimplifiedChinese = ctru_sys::CFG_LANGUAGE_ZH, + /// Traditional Chinese. TraditionalChinese = ctru_sys::CFG_LANGUAGE_TW, } +/// Specific model of the console. +#[doc(alias = "CFG_SystemModel")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum SystemModel { + /// Old Nintendo 3DS. Old3DS = ctru_sys::CFG_MODEL_3DS, + /// Old Nintendo 3DS XL. Old3DSXL = ctru_sys::CFG_MODEL_3DSXL, + /// New Nintendo 3DS. New3DS = ctru_sys::CFG_MODEL_N3DS, + /// Old Nintendo 2DS. Old2DS = ctru_sys::CFG_MODEL_2DS, + /// New Nintendo 3DS XL. New3DSXL = ctru_sys::CFG_MODEL_N3DSXL, + /// New Nintendo 2DS XL. New2DSXL = ctru_sys::CFG_MODEL_N2DSXL, } -/// Represents the configuration service. No actions can be performed -/// until an instance of this struct is created. -/// -/// The service exits when all instances of this struct go out of scope. +/// Handle to the System Configuration service. pub struct Cfgu(()); impl Cfgu { - /// Initializes the CFGU service. + /// Initialize a new service handle. /// - /// # Errors + /// # Example /// - /// This function will return Err if there was an error initializing the - /// CFGU service. + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cfgu::Cfgu; /// - /// ctrulib services are reference counted, so this function may be called - /// as many times as desired and the service will not exit until all - /// instances of Cfgu drop out of scope. + /// let cfgu = Cfgu::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "cfguInit")] pub fn new() -> crate::Result { ResultCode(unsafe { ctru_sys::cfguInit() })?; Ok(Cfgu(())) } - /// Gets system region from secure info + /// Returns the console's region from the system's secure info. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cfgu::Cfgu; + /// let cfgu = Cfgu::new()?; + /// + /// let region = cfgu.region()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CFGU_SecureInfoGetRegion")] pub fn region(&self) -> crate::Result { let mut region: u8 = 0; @@ -74,7 +125,23 @@ impl Cfgu { Ok(Region::try_from(region).unwrap()) } - /// Gets system's model + /// Returns the console's model. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cfgu::Cfgu; + /// let cfgu = Cfgu::new()?; + /// + /// let model = cfgu.model()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CFGU_GetSystemModel")] pub fn model(&self) -> crate::Result { let mut model: u8 = 0; @@ -82,7 +149,23 @@ impl Cfgu { Ok(SystemModel::try_from(model).unwrap()) } - /// Gets system's language + /// Returns the system language set for the console. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cfgu::Cfgu; + /// let cfgu = Cfgu::new()?; + /// + /// let language = cfgu.language()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CFGU_GetSystemLanguage")] pub fn language(&self) -> crate::Result { let mut language: u8 = 0; @@ -90,7 +173,25 @@ impl Cfgu { Ok(Language::try_from(language).unwrap()) } - /// Checks if NFC is supported by the console + /// Check if NFC is supported by the console. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cfgu::Cfgu; + /// let cfgu = Cfgu::new()?; + /// + /// if cfgu.is_nfc_supported()? { + /// println!("NFC is available!"); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CFGU_IsNFCSupported")] pub fn is_nfc_supported(&self) -> crate::Result { let mut supported: bool = false; @@ -98,7 +199,27 @@ impl Cfgu { Ok(supported) } - /// Check if the console is from the 2DS family (2DS, New2DS, New2DSXL) + /// Check if the console is from the 2DS family ([`Old2DS`](SystemModel::Old2DS), [`New2DSXL`](SystemModel::New2DSXL)). + /// + /// Useful to avoid stereoscopic 3D rendering when working with 2DS consoles. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::cfgu::Cfgu; + /// let cfgu = Cfgu::new()?; + /// + /// if cfgu.is_2ds_family()? { + /// println!("Stereoscopic 3D is not supported."); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "CFGU_GetModelNintendo2DS")] pub fn is_2ds_family(&self) -> crate::Result { let mut is_2ds_family: u8 = 0; @@ -108,6 +229,7 @@ impl Cfgu { } impl Drop for Cfgu { + #[doc(alias = "cfguExit")] fn drop(&mut self) { unsafe { ctru_sys::cfguExit(); diff --git a/ctru-rs/src/services/fs.rs b/ctru-rs/src/services/fs.rs index 04e0c1e..821b8b9 100644 --- a/ctru-rs/src/services/fs.rs +++ b/ctru-rs/src/services/fs.rs @@ -1,7 +1,9 @@ -//! Filesystem service +//! FileSystem service. //! //! This module contains basic methods to manipulate the contents of the 3DS's filesystem. //! Only the SD card is currently supported. You should prefer using `std::fs`. +// TODO: Refactor service to accomodate for various changes (such as SMDH support). Properly document the public API. +#![doc(alias = "filesystem")] use bitflags::bitflags; use std::ffi::OsString; @@ -17,70 +19,108 @@ use std::sync::Arc; use widestring::{WideCStr, WideCString}; bitflags! { - #[derive(Default)] + #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] struct FsOpen: u32 { - const FS_OPEN_READ = 1; - const FS_OPEN_WRITE = 2; - const FS_OPEN_CREATE = 4; + const FS_OPEN_READ = ctru_sys::FS_OPEN_READ; + const FS_OPEN_WRITE = ctru_sys::FS_OPEN_WRITE; + const FS_OPEN_CREATE = ctru_sys::FS_OPEN_CREATE; } - #[derive(Default)] + #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] struct FsWrite: u32 { - const FS_WRITE_FLUSH = 1; - const FS_WRITE_UPDATE_TIME = 256; + const FS_WRITE_FLUSH = ctru_sys::FS_WRITE_FLUSH; + const FS_WRITE_UPDATE_TIME = ctru_sys::FS_WRITE_UPDATE_TIME; } - #[derive(Default)] + #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] struct FsAttribute: u32 { - const FS_ATTRIBUTE_DIRECTORY = 1; - const FS_ATTRIBUTE_HIDDEN = 256; - const FS_ATTRIBUTE_ARCHIVE = 65536; - const FS_ATTRIBUTE_READ_ONLY = 16777216; + const FS_ATTRIBUTE_DIRECTORY = ctru_sys::FS_ATTRIBUTE_DIRECTORY; + const FS_ATTRIBUTE_HIDDEN = ctru_sys::FS_ATTRIBUTE_HIDDEN; + const FS_ATTRIBUTE_ARCHIVE = ctru_sys::FS_ATTRIBUTE_ARCHIVE; + const FS_ATTRIBUTE_READ_ONLY = ctru_sys::FS_ATTRIBUTE_READ_ONLY; } } +/// Media type used for storage. +#[doc(alias = "FS_MediaType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum FsMediaType { + /// Internal NAND memory. Nand = ctru_sys::MEDIATYPE_NAND, + /// External SD card. Sd = ctru_sys::MEDIATYPE_SD, + /// Game Cartridge. GameCard = ctru_sys::MEDIATYPE_GAME_CARD, } +/// Kind of file path. +#[doc(alias = "FS_PathType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum PathType { + /// Invalid path. Invalid = ctru_sys::PATH_INVALID, + /// Empty path. Empty = ctru_sys::PATH_EMPTY, + /// Binary path. + /// + /// Its meaning differs depending on the Archive it is used on. Binary = ctru_sys::PATH_BINARY, + /// ASCII path. ASCII = ctru_sys::PATH_ASCII, + /// UTF-16 path. UTF16 = ctru_sys::PATH_UTF16, } +/// Index of the various usable data archives. +#[doc(alias = "FS_ArchiveID")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum ArchiveID { + /// Read-Only Memory File System. RomFS = ctru_sys::ARCHIVE_ROMFS, + /// Game save data. Savedata = ctru_sys::ARCHIVE_SAVEDATA, + /// Game ext data. Extdata = ctru_sys::ARCHIVE_EXTDATA, + /// Shared ext data. SharedExtdata = ctru_sys::ARCHIVE_SHARED_EXTDATA, + /// System save data. SystemSavedata = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA, + /// SD card. Sdmc = ctru_sys::ARCHIVE_SDMC, + /// SD card (write-only). SdmcWriteOnly = ctru_sys::ARCHIVE_SDMC_WRITE_ONLY, + /// BOSS ext data. BossExtdata = ctru_sys::ARCHIVE_BOSS_EXTDATA, + /// Card SPI File System. CardSpiFS = ctru_sys::ARCHIVE_CARD_SPIFS, + /// Game ext data and BOSS data. ExtDataAndBossExtdata = ctru_sys::ARCHIVE_EXTDATA_AND_BOSS_EXTDATA, + /// System save data. SystemSaveData2 = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA2, + /// Internal NAND (read-write). NandRW = ctru_sys::ARCHIVE_NAND_RW, + /// Internal NAND (read-only). NandRO = ctru_sys::ARCHIVE_NAND_RO, + /// Internal NAND (read-only write access). NandROWriteAccess = ctru_sys::ARCHIVE_NAND_RO_WRITE_ACCESS, + /// User save data and ExeFS/RomFS. SaveDataAndContent = ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT, + /// User save data and ExeFS/RomFS (only ExeFS for fs:LDR). SaveDataAndContent2 = ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT2, + /// NAND CTR File System. NandCtrFS = ctru_sys::ARCHIVE_NAND_CTR_FS, + /// TWL photo. TwlPhoto = ctru_sys::ARCHIVE_TWL_PHOTO, + /// NAND TWL File System. NandTwlFS = ctru_sys::ARCHIVE_NAND_TWL_FS, + /// Game card save data. GameCardSavedata = ctru_sys::ARCHIVE_GAMECARD_SAVEDATA, + /// User save data. UserSavedata = ctru_sys::ARCHIVE_USER_SAVEDATA, + /// Demo save data. DemoSavedata = ctru_sys::ARCHIVE_DEMO_SAVEDATA, } @@ -183,36 +223,26 @@ pub struct File { /// Metadata information about a file. /// -/// This structure is returned from the [`metadata`] function and +/// This structure is returned from the [`File::metadata`] function and /// represents known metadata about a file. -/// -/// [`metadata`]: fn.metadata.html pub struct Metadata { attributes: u32, size: u64, } /// Options and flags which can be used to configure how a [`File`] is opened. -/// This builder exposes the ability to configure how a `File` is opened +/// This builder exposes the ability to configure how a [`File`] is opened /// and what operations are permitted on the open file. The [`File::open`] /// and [`File::create`] methods are aliases for commonly used options /// using this builder. /// -/// [`File`]: struct.File.html -/// [`File::open`]: struct.File.html#method.open -/// [`File::create`]: struct.File.html#method.create -/// -/// Generally speaking, when using `OpenOptions`, you'll first call [`new()`], -/// then chain calls to methods to set each option, then call [`open()`], +/// Generally speaking, when using [`OpenOptions`], you'll first call [`OpenOptions::new`], +/// then chain calls to methods to set each option, then call [`OpenOptions::open`], /// passing the path of the file you're trying to open. /// /// It is required to also pass a reference to the [`Archive`] that the /// file lives in. /// -/// [`new()`]: struct.OpenOptions.html#method.new -/// [`open()`]: struct.OpenOptions.html#method.open -/// [`Archive`]: struct.Archive.html -/// /// # Examples /// /// Opening a file to read: @@ -257,14 +287,11 @@ pub struct OpenOptions { /// Iterator over the entries in a directory. /// -/// This iterator is returned from the [`read_dir`] function of this module and -/// will yield instances of `Result`. Through a [`DirEntry`] +/// This iterator is returned from the [`read_dir`] function and +/// will yield instances of [`Result`]. Through a [`DirEntry`] /// information like the entry's path and possibly other metadata can be /// learned. /// -/// [`read_dir`]: fn.read_dir.html -/// [`DirEntry`]: struct.DirEntry.html -/// /// # Errors /// /// This Result will return Err if there's some sort of intermittent IO error @@ -277,8 +304,6 @@ pub struct ReadDir<'a> { /// Entries returned by the [`ReadDir`] iterator. /// -/// [`ReadDir`]: struct.ReadDir.html -/// /// An instance of `DirEntry` represents an entry inside of a directory on the /// filesystem. Each entry can be inspected via methods to learn about the full /// path or possibly other metadata. @@ -297,7 +322,7 @@ unsafe impl Send for Dir {} unsafe impl Sync for Dir {} impl Fs { - /// Initializes the FS service. + /// Initialize a new service handle. /// /// # Errors /// @@ -338,8 +363,6 @@ impl Fs { impl Archive { /// Retrieves an Archive's [`ArchiveID`] - /// - /// [`ArchiveID`]: enum.ArchiveID.html pub fn id(&self) -> ArchiveID { self.id } @@ -355,8 +378,6 @@ impl File { /// This function will return an error if `path` does not already exit. /// Other errors may also be returned accoridng to [`OpenOptions::open`] /// - /// [`OpenOptions::open`]: struct.OpenOptions.html#method.open - /// /// # Examples /// /// ```no_run @@ -382,9 +403,7 @@ impl File { /// # Errors /// /// This function will return an error if `path` does not already exit. - /// Other errors may also be returned accoridng to [`OpenOptions::create`] - /// - /// [`OpenOptions::create`]: struct.OpenOptions.html#method.create + /// Other errors may also be returned accoridng to [`OpenOptions::create`]. /// /// # Examples /// @@ -395,7 +414,7 @@ impl File { /// let mut sdmc_archive = fs.sdmc().unwrap(); /// let mut f = File::create(&mut sdmc_archive, "/foo.txt").unwrap(); /// ``` - pub fn create>(arch: &mut Archive, path: P) -> IoResult { + pub fn create>(arch: &Archive, path: P) -> IoResult { OpenOptions::new() .write(true) .create(true) @@ -412,6 +431,7 @@ impl File { /// # Errors /// /// This function will return an error if the file is not opened for writing. + #[doc(alias = "FSFILE_SetSize")] pub fn set_len(&mut self, size: u64) -> IoResult<()> { unsafe { let r = ctru_sys::FSFILE_SetSize(self.handle, size); @@ -447,6 +467,7 @@ impl File { } } + #[doc(alias = "FSFILE_Read")] fn read(&mut self, buf: &mut [u8]) -> IoResult { unsafe { let mut n_read = 0; @@ -470,6 +491,7 @@ impl File { unsafe { read_to_end_uninitialized(self, buf) } } + #[doc(alias = "FSFILE_Write")] fn write(&mut self, buf: &[u8]) -> IoResult { unsafe { let mut n_written = 0; @@ -521,7 +543,7 @@ impl OpenOptions { Self::default() } - /// Sets the option for read access. + /// Set the option for read access. /// /// This option, when true, will indicate that the file should be /// `read`-able if opened. @@ -530,7 +552,7 @@ impl OpenOptions { self } - /// Sets the option for write access. + /// Set the option for write access. /// /// This option, when true, will indicate that the file should be /// `write`-able if opened. @@ -542,7 +564,7 @@ impl OpenOptions { self } - /// Sets the option for the append mode. + /// Set the option for the append mode. /// /// This option, when true, means that writes will append to a file instead /// of overwriting previous contents. Note that setting .write(true).append(true) @@ -554,7 +576,7 @@ impl OpenOptions { self } - /// Sets the option for truncating a previous file. + /// Set the option for truncating a previous file. /// /// If a file is successfully opened with this option set it will truncate /// the file to 0 length if it already exists. @@ -565,7 +587,7 @@ impl OpenOptions { self } - /// Sets the option for creating a new file. + /// Set the option for creating a new file. /// /// This option indicates whether a new file will be created /// if the file does not yet already @@ -577,7 +599,7 @@ impl OpenOptions { self } - /// Sets which archive the file is to be opened in. + /// Set which archive the file is to be opened in. /// /// Failing to pass in an archive will result in the file failing to open. pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions { @@ -597,8 +619,7 @@ impl OpenOptions { /// to the `archive` method. /// * Filesystem-level errors (full disk, etc). /// * Invalid combinations of open options. - /// - /// [`Archive`]: struct.Archive.html + #[doc(alias = "FSUSER_OpenFile")] pub fn open>(&mut self, path: P) -> IoResult { self._open(path.as_ref(), self.open_flags()) } @@ -717,7 +738,8 @@ impl<'a> DirEntry<'a> { /// but is not limited to just these cases: /// /// * User lacks permissions to create directory at `path` -pub fn create_dir>(arch: &mut Archive, path: P) -> IoResult<()> { +#[doc(alias = "FSUSER_CreateDirectory")] +pub fn create_dir>(arch: &Archive, path: P) -> IoResult<()> { unsafe { let path = to_utf16(path.as_ref()); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); @@ -743,7 +765,8 @@ pub fn create_dir>(arch: &mut Archive, path: P) -> IoResult<()> { /// /// * If any directory in the path specified by `path` does not already exist /// and it could not be created otherwise. -pub fn create_dir_all>(arch: &mut Archive, path: P) -> IoResult<()> { +#[doc(alias = "FSUSER_CreateDirectory")] +pub fn create_dir_all>(arch: &Archive, path: P) -> IoResult<()> { let path = path.as_ref(); let mut dir = PathBuf::new(); let mut result = Ok(()); @@ -779,7 +802,8 @@ pub fn metadata>(arch: &Archive, path: P) -> IoResult { /// /// * The user lacks permissions to remove the directory at the provided path. /// * The directory isn't empty. -pub fn remove_dir>(arch: &mut Archive, path: P) -> IoResult<()> { +#[doc(alias = "FSUSER_DeleteDirectory")] +pub fn remove_dir>(arch: &Archive, path: P) -> IoResult<()> { unsafe { let path = to_utf16(path.as_ref()); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); @@ -797,7 +821,8 @@ pub fn remove_dir>(arch: &mut Archive, path: P) -> IoResult<()> { /// # Errors /// /// see `file::remove_file` and `fs::remove_dir` -pub fn remove_dir_all>(arch: &mut Archive, path: P) -> IoResult<()> { +#[doc(alias = "FSUSER_DeleteDirectoryRecursively")] +pub fn remove_dir_all>(arch: &Archive, path: P) -> IoResult<()> { unsafe { let path = to_utf16(path.as_ref()); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); @@ -821,6 +846,7 @@ pub fn remove_dir_all>(arch: &mut Archive, path: P) -> IoResult<( /// * The provided path doesn't exist. /// * The process lacks permissions to view the contents. /// * The path points at a non-directory file. +#[doc(alias = "FSUSER_OpenDirectory")] pub fn read_dir>(arch: &Archive, path: P) -> IoResult { unsafe { let mut handle = 0; @@ -849,7 +875,8 @@ pub fn read_dir>(arch: &Archive, path: P) -> IoResult { /// /// * path points to a directory. /// * The user lacks permissions to remove the file. -pub fn remove_file>(arch: &mut Archive, path: P) -> IoResult<()> { +#[doc(alias = "FSUSER_DeleteFile")] +pub fn remove_file>(arch: &Archive, path: P) -> IoResult<()> { unsafe { let path = to_utf16(path.as_ref()); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); @@ -872,7 +899,8 @@ pub fn remove_file>(arch: &mut Archive, path: P) -> IoResult<()> /// /// * from does not exist. /// * The user lacks permissions to view contents. -pub fn rename(arch: &mut Archive, from: P, to: Q) -> IoResult<()> +#[doc(alias = "FSUSER_RenameFile", alias = "FSUSER_RenameDirectory")] +pub fn rename(arch: &Archive, from: P, to: Q) -> IoResult<()> where P: AsRef, Q: AsRef, @@ -901,7 +929,6 @@ fn to_utf16(path: &Path) -> WideCString { WideCString::from_str(path).unwrap() } -// Adapted from sys/windows/fs.rs in libstd fn truncate_utf16_at_nul(v: &[u16]) -> &[u16] { match v.iter().position(|c| *c == 0) { // don't include the 0 @@ -910,8 +937,6 @@ fn truncate_utf16_at_nul(v: &[u16]) -> &[u16] { } } -// Copied from sys/common/io.rs in libstd - // Provides read_to_end functionality over an uninitialized buffer. // This function is unsafe because it calls the underlying // read function with a slice into uninitialized memory. The default @@ -998,6 +1023,7 @@ impl Seek for File { } impl Drop for Fs { + #[doc(alias = "fsExit")] fn drop(&mut self) { unsafe { ctru_sys::fsExit(); @@ -1006,6 +1032,7 @@ impl Drop for Fs { } impl Drop for Archive { + #[doc(alias = "FSUSER_CloseArchive")] fn drop(&mut self) { unsafe { let _ = ctru_sys::FSUSER_CloseArchive(self.handle); @@ -1014,6 +1041,7 @@ impl Drop for Archive { } impl Drop for File { + #[doc(alias = "FSFILE_Close")] fn drop(&mut self) { unsafe { let _ = ctru_sys::FSFILE_Close(self.handle); @@ -1022,6 +1050,7 @@ impl Drop for File { } impl Drop for Dir { + #[doc(alias = "FSDIR_Close")] fn drop(&mut self) { unsafe { let _ = ctru_sys::FSDIR_Close(self.0); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 3a5ba37..68ac957 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -1,4 +1,8 @@ -//! LCD screens manipulation helper +//! Graphics service. +//! +//! The GFX service controls (in a somewhat high-level way) the console's LCD screens. +//! The screens are subordinate to the GFX service handle and can be used by only one borrower at a time. +#![doc(alias = "graphics")] use std::cell::{Ref, RefCell, RefMut}; use std::marker::PhantomData; @@ -20,9 +24,12 @@ mod private { impl Sealed for BottomScreen {} } +/// Trait to handle common functionality for all screens. +/// /// This trait is implemented by the screen structs for working with frame buffers and /// drawing to the screens. Graphics-related code can be made generic over this /// trait to work with any of the given screens. +#[doc(alias = "gfxScreen_t")] pub trait Screen: private::Sealed { /// Returns the `libctru` value for the Screen kind. fn as_raw(&self) -> ctru_sys::gfxScreen_t; @@ -34,6 +41,7 @@ pub trait Screen: private::Sealed { /// /// Note that the pointer of the framebuffer returned by this function can /// change after each call to this function if double buffering is enabled. + #[doc(alias = "gfxGetFramebuffer")] fn raw_framebuffer(&mut self) -> RawFrameBuffer { let mut width: u16 = 0; let mut height: u16 = 0; @@ -48,15 +56,8 @@ pub trait Screen: private::Sealed { } } - /// Sets whether to use double buffering. Enabled by default. - /// - /// [`Swap::swap_buffers`] must be called after this function for the configuration - /// change to take effect. - fn set_double_buffering(&mut self, enabled: bool) { - unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) } - } - /// Gets the framebuffer format. + #[doc(alias = "gfxGetScreenFormat")] fn framebuffer_format(&self) -> FramebufferFormat { unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into() } @@ -65,40 +66,53 @@ pub trait Screen: private::Sealed { /// /// [`Swap::swap_buffers`] must be called after this method for the configuration /// change to take effect. + #[doc(alias = "gfxSetScreenFormat")] fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) { unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } } } -/// The top screen. Mutable access to this struct is required to write to the top -/// screen's frame buffer. To enable 3D mode, it can be converted into a [`TopScreen3D`]. +/// The top LCD screen. +/// +/// Mutable access to this struct is required to write to the top screen's frame buffer. +/// +/// To enable 3D mode, it can be converted into a [`TopScreen3D`]. pub struct TopScreen { left: TopScreenLeft, right: TopScreenRight, } +/// The top LCD screen set in stereoscopic 3D mode. +/// /// A helper container for both sides of the top screen. Once the [`TopScreen`] is /// converted into this, 3D mode will be enabled until this struct is dropped. pub struct TopScreen3D<'screen> { screen: &'screen RefCell, } -/// A screen that can have its frame buffers swapped, if double buffering is enabled. +/// Trait for screens that can have its frame buffers swapped, when double buffering is enabled. /// /// This trait applies to all [`Screen`]s that have swappable frame buffers. pub trait Swap: private::Sealed { /// Swaps the video buffers. /// - /// If double buffering is disabled, "swapping" the buffers has the side effect - /// of committing any configuration changes to the buffers (e.g. [`set_wide_mode`], - /// [`set_framebuffer_format`], [`set_double_buffering`]). + /// Even if double buffering is disabled, "swapping" the buffers has the side effect + /// of committing any configuration changes to the buffers (e.g. [`TopScreen::set_wide_mode()`], + /// [`Screen::set_framebuffer_format()`], [`Swap::set_double_buffering()`]), so it should still be used. /// /// This should be called once per frame at most. - /// - /// [`set_wide_mode`]: TopScreen::set_wide_mode - /// [`set_framebuffer_format`]: Screen::set_framebuffer_format - /// [`set_double_buffering`]: Screen::set_double_buffering + #[doc(alias = "gfxScreenSwapBuffers")] fn swap_buffers(&mut self); + + /// Set whether to use double buffering. + /// + /// # Notes + /// + /// Double buffering is enabled by default. + /// [`Swap::swap_buffers`] must be called after this function for the configuration + /// change to take effect. + #[doc(alias = "gfxSetDoubleBuffering")] + fn set_double_buffering(&mut self, enabled: bool); } impl Swap for TopScreen3D<'_> { @@ -107,6 +121,10 @@ impl Swap for TopScreen3D<'_> { ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, true); } } + + fn set_double_buffering(&mut self, enabled: bool) { + unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_TOP, enabled) } + } } impl Swap for TopScreen { @@ -115,6 +133,10 @@ impl Swap for TopScreen { ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, false); } } + + fn set_double_buffering(&mut self, enabled: bool) { + unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_TOP, enabled) } + } } impl Swap for BottomScreen { @@ -123,13 +145,20 @@ impl Swap for BottomScreen { ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_BOTTOM, false); } } + + fn set_double_buffering(&mut self, enabled: bool) { + unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_BOTTOM, enabled) } + } } -/// A screen with buffers that can be flushed. This trait applies to any [`Screen`] -/// that has data written to its frame buffer. +/// A screen with buffers that can be flushed. +/// +/// This trait applies to any [`Screen`] that has data written to its frame buffer. pub trait Flush: private::Sealed { - /// Flushes the video buffer(s) for this screen. Note that you must still call - /// [`Swap::swap_buffers`] after this method for the buffer contents to be displayed. + /// Flushes the video buffer(s) for this screen. + /// + /// Note that you must still call [`Swap::swap_buffers`] after this method for the buffer contents to be displayed. + #[doc(alias = "gfxFlushBuffers")] fn flush_buffers(&mut self); } @@ -138,7 +167,7 @@ impl Flush for S { let framebuffer = self.raw_framebuffer(); // Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens - unsafe { + let _ = unsafe { ctru_sys::GSPGPU_FlushDataCache( framebuffer.ptr.cast(), (framebuffer.height * framebuffer.width) as u32, @@ -167,14 +196,16 @@ pub struct TopScreenLeft; #[non_exhaustive] pub struct TopScreenRight; -/// The bottom screen. Mutable access to this struct is required to write to the -/// bottom screen's frame buffer. +/// The bottom LCD screen. +/// +/// Mutable access to this struct is required to write to the bottom screen's frame buffer. #[derive(Debug)] #[non_exhaustive] pub struct BottomScreen; -/// Representation of a framebuffer for one [`Side`] of the top screen, or the -/// entire bottom screen. The inner pointer is only valid for one frame if double +/// Representation of a framebuffer for one [`Side`] of the top screen, or the entire bottom screen. +/// +/// The inner pointer is only valid for one frame if double /// buffering is enabled. Data written to `ptr` will be rendered to the screen. #[derive(Debug)] pub struct RawFrameBuffer<'screen> { @@ -188,11 +219,12 @@ pub struct RawFrameBuffer<'screen> { screen: PhantomData<&'screen mut dyn Screen>, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[repr(u32)] -/// Side of top screen framebuffer +/// Side of the [`TopScreen`]'s framebuffer. /// /// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality +#[doc(alias = "gfx3dSide_t")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] pub enum Side { /// The left framebuffer. This framebuffer is also the one used when 3D is disabled Left = ctru_sys::GFX_LEFT, @@ -200,12 +232,14 @@ pub enum Side { Right = ctru_sys::GFX_RIGHT, } -/// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that -/// provides helper functions and utilities for software rendering. +/// Handle to the GFX service. /// -/// The service exits when this struct is dropped. +/// This service is a wrapper around the lower-level [GSPGPU](crate::services::gspgpu) service that +/// provides helper functions and utilities for software rendering. pub struct Gfx { + /// Top screen representation. pub top_screen: RefCell, + /// Bottom screen representation. pub bottom_screen: RefCell, _service_handler: ServiceReference, } @@ -213,17 +247,63 @@ pub struct Gfx { static GFX_ACTIVE: Mutex = Mutex::new(0); impl Gfx { - /// Creates a new [Gfx] instance with default init values + /// Initialize a new default service handle. + /// + /// # Notes + /// /// It's the same as calling: - /// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)` + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// # use ctru::services::gfx::Gfx; + /// # use ctru::services::gspgpu::FramebufferFormat; + /// # + /// Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)?; + /// # + /// # Ok(()) + /// # } + /// ``` + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::gfx::Gfx; + /// + /// let gfx = Gfx::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "gfxInit")] pub fn new() -> Result { Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) } - /// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom - /// screens + /// Initialize a new service handle with the chosen framebuffer formats for the top and bottom screens. /// - /// Use `Gfx::new()` instead of this function to initialize the module with default parameters + /// Use [`Gfx::new()`] instead of this function to initialize the module with default parameters + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::{gfx::Gfx, gspgpu::FramebufferFormat}; + /// + /// // Top screen uses RGBA8, bottom screen uses RGB565. + /// // The screen buffers are allocated in the standard HEAP memory, and not in VRAM. + /// let gfx = Gfx::with_formats(FramebufferFormat::Rgba8, FramebufferFormat::Rgb565, false)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "gfxInit")] pub fn with_formats( top_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat, @@ -247,9 +327,32 @@ impl Gfx { }) } - /// Waits for the vertical blank interrupt + /// Waits for the vertical blank event. /// /// Use this to synchronize your application with the refresh rate of the LCD screens + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::{apt::Apt, gfx::Gfx}; + /// let apt = Apt::new()?; + /// let gfx = Gfx::new()?; + /// + /// // Simple main loop. + /// while apt.main_loop() { + /// // Main program logic + /// + /// // Wait for the screens to refresh. + /// // This blocks the current thread to make it run at 60Hz. + /// gfx.wait_for_vblank(); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` pub fn wait_for_vblank(&self) { gspgpu::wait_for_event(gspgpu::Event::VBlank0, true); } @@ -269,7 +372,35 @@ impl TopScreen3D<'_> { } } +/// Convert the [`TopScreen`] into a [`TopScreen3D`] and activate stereoscopic 3D. +/// +/// # Example +/// +/// ```no_run +/// # use std::error::Error; +/// # fn main() -> Result<(), Box> { +/// # +/// use ctru::services::gfx::{Gfx, TopScreen, TopScreen3D}; +/// let gfx = Gfx::new()?; +/// +/// let mut top_screen = TopScreen3D::from(&gfx.top_screen); +/// +/// let (left, right) = top_screen.split_mut(); +/// +/// // Rendering must be done twice for each side +/// // (with a slight variation in perspective to simulate the eye-to-eye distance). +/// render(left); +/// render(right); +/// # +/// # Ok(()) +/// # } +/// # +/// # use ctru::services::gfx::Screen; +/// # use std::cell::RefMut; +/// # fn render(screen: RefMut<'_, dyn Screen>) {} +/// ``` impl<'screen> From<&'screen RefCell> for TopScreen3D<'screen> { + #[doc(alias = "gfxSet3D")] fn from(top_screen: &'screen RefCell) -> Self { unsafe { ctru_sys::gfxSet3D(true); @@ -297,8 +428,13 @@ impl TopScreen { /// Enable or disable wide mode on the top screen. /// + /// # Notes + /// /// [`Swap::swap_buffers`] must be called after this method for the configuration /// to take effect. + /// + /// Wide mode does NOT work on Old 2DS models (but still does on New 2DS XL models). + #[doc(alias = "gfxSetWide")] pub fn set_wide_mode(&mut self, enable: bool) { unsafe { ctru_sys::gfxSetWide(enable); @@ -306,6 +442,7 @@ impl TopScreen { } /// Returns whether or not wide mode is enabled on the top screen. + #[doc(alias = "gfxIsWide")] pub fn is_wide(&self) -> bool { unsafe { ctru_sys::gfxIsWide() } } diff --git a/ctru-rs/src/services/gspgpu.rs b/ctru-rs/src/services/gspgpu.rs index 9f9219c..81872d0 100644 --- a/ctru-rs/src/services/gspgpu.rs +++ b/ctru-rs/src/services/gspgpu.rs @@ -1,18 +1,28 @@ //! GSPGPU service +/// GSPGPU events that can be awaited. +#[doc(alias = "GSPGPU_Event")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum Event { + /// Memory fill completed. Psc0 = ctru_sys::GSPGPU_EVENT_PSC0, + /// TODO: Unknown. Psc1 = ctru_sys::GSPGPU_EVENT_PSC1, + /// TODO: Unknown. VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0, + /// TODO: Unknown. VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1, + /// Display transfer finished. PPF = ctru_sys::GSPGPU_EVENT_PPF, + /// Command list processing finished. P3D = ctru_sys::GSPGPU_EVENT_P3D, + /// TODO: Unknown. DMA = ctru_sys::GSPGPU_EVENT_DMA, } -/// Framebuffer formats supported by the 3DS +#[doc(alias = "GSPGPU_FramebufferFormat")] +/// Framebuffer formats supported by the 3DS' screens. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum FramebufferFormat { @@ -45,6 +55,7 @@ impl FramebufferFormat { /// Waits for a GSPGPU event to occur. /// /// `discard_current` determines whether to discard the current event and wait for the next event +#[doc(alias = "gspWaitForEvent")] pub fn wait_for_event(ev: Event, discard_current: bool) { unsafe { ctru_sys::gspWaitForEvent(ev.into(), discard_current); diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index fa91657..56a6035 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -1,59 +1,104 @@ -//! HID service +//! Human Interface Device service. //! -//! The HID service provides access to user input such as button presses, touch screen presses, -//! and circle pad information. It also provides information from the sound volume slider, -//! the accelerometer, and the gyroscope. +//! The HID service provides read access to user input such as [button presses](Hid::keys_down), [touch screen presses](Hid::touch_position), +//! and [circle pad information](Hid::circlepad_position). It also provides information from the sound volume slider, the accelerometer, and the gyroscope. +// TODO: Implement volume slider, accelerometer and gyroscope + any other missing functionality. +#![doc(alias = "input")] +#![doc(alias = "controller")] +#![doc(alias = "gamepad")] use crate::error::ResultCode; -bitflags::bitflags! { - /// A set of flags corresponding to the button and directional pad - /// inputs on the 3DS +use bitflags::bitflags; + +bitflags! { + /// A set of flags corresponding to the button and directional pad inputs present on the 3DS. + #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] pub struct KeyPad: u32 { + /// A button. const A = ctru_sys::KEY_A; + /// B button. const B = ctru_sys::KEY_B; + /// Select button. const SELECT = ctru_sys::KEY_SELECT; + /// Start button. const START = ctru_sys::KEY_START; + /// D-Pad Right. const DPAD_RIGHT = ctru_sys::KEY_DRIGHT; + /// D-Pad Left. const DPAD_LEFT = ctru_sys::KEY_DLEFT; + /// D-Pad Up. const DPAD_UP = ctru_sys::KEY_DUP; + /// D-Pad Down. const DPAD_DOWN = ctru_sys::KEY_DDOWN; + /// R button. const R = ctru_sys::KEY_R; + /// L button. const L = ctru_sys::KEY_L; + /// X button. const X = ctru_sys::KEY_X; + /// Y button. const Y = ctru_sys::KEY_Y; + /// ZL button. const ZL = ctru_sys::KEY_ZL; + /// ZR button. const ZR = ctru_sys::KEY_ZR; + /// Touchscreen. const TOUCH = ctru_sys::KEY_TOUCH; + /// C-Stick Right. const CSTICK_RIGHT = ctru_sys::KEY_CSTICK_RIGHT; + /// C-Stick Left. const CSTICK_LEFT = ctru_sys::KEY_CSTICK_LEFT; + /// C-Stick Up. const CSTICK_UP = ctru_sys::KEY_CSTICK_UP; + /// C-Stick Down. const CSTICK_DOWN = ctru_sys::KEY_CSTICK_DOWN; + /// CirclePad Right. const CPAD_RIGHT = ctru_sys::KEY_CPAD_RIGHT; + /// CirclePad Left. const CPAD_LEFT = ctru_sys::KEY_CPAD_LEFT; + /// CirclePad Up. const CPAD_UP = ctru_sys::KEY_CPAD_UP; + /// CirclePad Down. const CPAD_DOWN = ctru_sys::KEY_CPAD_DOWN; - // Convenience catch-all for the dpad and cpad + + // Convenience catch-all for the D-Pad and the CirclePad + + /// Direction Up (either D-Pad or CirclePad). const UP = KeyPad::DPAD_UP.bits() | KeyPad::CPAD_UP.bits(); + /// Direction Down (either D-Pad or CirclePad). const DOWN = KeyPad::DPAD_DOWN.bits() | KeyPad::CPAD_DOWN.bits(); + /// Direction Left (either D-Pad or CirclePad). const LEFT = KeyPad::DPAD_LEFT.bits() | KeyPad::CPAD_LEFT.bits(); + /// Direction Right (either D-Pad or CirclePad). const RIGHT = KeyPad::DPAD_RIGHT.bits() | KeyPad::CPAD_RIGHT.bits(); } } -/// A reference-counted handle to the HID service. The service is closed -/// when all instances of this struct fall out of scope. -/// -/// This service requires no special permissions to use. +/// Handle to the HID service. pub struct Hid(()); -/// Initializes the HID service. -/// -/// # Errors -/// -/// This function will return an error if the service was unable to be initialized. -/// Since this service requires no special or elevated permissions, errors are -/// rare in practice. impl Hid { + /// Initialize a new service handle. + /// + /// # Errors + /// + /// This function will return an error if the service was unable to be initialized. + /// Since this service requires no special or elevated permissions, errors are rare in practice. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// + /// let hid = Hid::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidInit")] pub fn new() -> crate::Result { unsafe { ResultCode(ctru_sys::hidInit())?; @@ -61,15 +106,52 @@ impl Hid { } } - /// Scans the HID service for all user input occurring on the current - /// frame. This function should be called on every frame when polling + /// Scan the HID service for all user input occurring on the current frame. + /// + /// This function should be called on every frame when polling /// for user input. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidScanInput")] pub fn scan_input(&mut self) { unsafe { ctru_sys::hidScanInput() }; } /// Returns a bitflag struct representing which buttons have just been pressed /// on the current frame (and were not pressed on the previous frame). + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::{Hid, KeyPad}; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// if hid.keys_down().contains(KeyPad::A) { + /// println!("You have pressed the A button!") + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidKeysDown")] pub fn keys_down(&self) -> KeyPad { unsafe { let keys = ctru_sys::hidKeysDown(); @@ -79,6 +161,26 @@ impl Hid { /// Returns a bitflag struct representing which buttons have been held down /// during the current frame. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::{Hid, KeyPad}; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// if hid.keys_held().contains(KeyPad::START) { + /// println!("You are holding the START button!") + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidKeysHeld")] pub fn keys_held(&self) -> KeyPad { unsafe { let keys = ctru_sys::hidKeysHeld(); @@ -88,6 +190,26 @@ impl Hid { /// Returns a bitflag struct representing which buttons have just been released on /// the current frame. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::{Hid, KeyPad}; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// if hid.keys_held().contains(KeyPad::B) { + /// println!("You have released the B button!") + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidKeysUp")] pub fn keys_up(&self) -> KeyPad { unsafe { let keys = ctru_sys::hidKeysUp(); @@ -100,12 +222,31 @@ impl Hid { /// # Notes /// /// (0, 0) represents the top left corner of the screen. - pub fn touch_position(&mut self) -> (u16, u16) { + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// let (touch_x, touch_y) = hid.touch_position(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidTouchRead")] + pub fn touch_position(&self) -> (u16, u16) { let mut res = ctru_sys::touchPosition { px: 0, py: 0 }; unsafe { ctru_sys::hidTouchRead(&mut res); } + (res.px, res.py) } @@ -114,17 +255,37 @@ impl Hid { /// # Notes /// /// (0, 0) represents the center of the circle pad. - pub fn circlepad_position(&mut self) -> (i16, i16) { + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// let (pad_x, pad_y) = hid.circlepad_position(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "hidCircleRead")] + pub fn circlepad_position(&self) -> (i16, i16) { let mut res = ctru_sys::circlePosition { dx: 0, dy: 0 }; unsafe { ctru_sys::hidCircleRead(&mut res); } + (res.dx, res.dy) } } impl Drop for Hid { + #[doc(alias = "hidExit")] fn drop(&mut self) { unsafe { ctru_sys::hidExit() }; } diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 1d31dea..8aadf87 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 while using the underlying services, [`ctru-rs`](crate) 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-rs`](crate) 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; @@ -42,7 +48,4 @@ cfg_if::cfg_if! { } } -pub use self::apt::Apt; -pub use self::hid::Hid; - pub(crate) use self::reference::ServiceReference; diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 8f67b1b..8cda802 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -1,7 +1,12 @@ -//! NDSP (Audio) service +//! NDSP (Audio) service. +//! +//! The NDSP service is used to handle communications to the DSP processor present on the console's motherboard. +//! Thanks to the DSP processor the program can play sound effects and music on the console's built-in speakers or to any audio device +//! connected via the audio jack. +#![doc(alias = "audio")] pub mod wave; -use wave::{Wave, WaveStatus}; +use wave::{Status, Wave}; use crate::error::ResultCode; use crate::services::ServiceReference; @@ -14,49 +19,80 @@ use std::sync::Mutex; const NUMBER_OF_CHANNELS: u8 = 24; +/// Audio output mode. +#[doc(alias = "ndspOutputMode")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum OutputMode { + /// Single-Channel. Mono = ctru_sys::NDSP_OUTPUT_MONO, + /// Dual-Channel. Stereo = ctru_sys::NDSP_OUTPUT_STEREO, + /// Surround. Surround = ctru_sys::NDSP_OUTPUT_SURROUND, } +/// PCM formats supported by the audio engine. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum AudioFormat { + /// PCM 8bit single-channel. PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8, + /// PCM 16bit single-channel. PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16, + /// PCM 8bit interleaved dual-channel. PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8, + /// PCM 16bit interleaved dual-channel. PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16, } -/// Representation of volume mix for a channel. +/// Representation of the volume mix for a channel. #[derive(Copy, Clone, Debug, PartialEq)] pub struct AudioMix { raw: [f32; 12], } +/// Interpolation used between audio frames. +#[doc(alias = "ndspInterpType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u32)] pub enum InterpolationType { + /// Polyphase interpolation. Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE, + /// Linear interpolation. Linear = ctru_sys::NDSP_INTERP_LINEAR, + /// No interpolation. None = ctru_sys::NDSP_INTERP_NONE, } +/// Errors returned by [`ndsp`](self) functions. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum NdspError { - /// Channel ID + /// Channel with the specified ID does not exist. InvalidChannel(u8), - /// Channel ID + /// Channel with the specified ID is already being used. ChannelAlreadyInUse(u8), - /// Channel ID + /// The wave is already busy playing in the channel with the specified ID. WaveBusy(u8), - /// Sample amount requested, Max sample amount + /// The sample amount requested was larger than the maximum. SampleCountOutOfBounds(usize, usize), } +/// NDSP Channel representation. +/// +/// There are 24 individual channels in total and each can play a different audio [`Wave`] simultaneuosly. +/// +/// # Default +/// +/// NDSP initialises all channels with default values on initialization, but the developer is supposed to change these values to correctly work with the service. +/// +/// In particular: +/// - Default audio format is set to [`AudioFormat::PCM16Mono`]. +/// - Default sample rate is set to 1 Hz. +/// - Default interpolation type is set to [`InterpolationType::Polyphase`]. +/// - Default mix is set to [`AudioMix::default()`] +/// +/// The handle to a channel can be retrieved with [`Ndsp::channel()`] pub struct Channel<'ndsp> { id: u8, _rf: RefMut<'ndsp, ()>, // we don't need to hold any data @@ -64,10 +100,9 @@ pub struct Channel<'ndsp> { static NDSP_ACTIVE: Mutex = Mutex::new(0); -/// Handler of the DSP service and DSP processor. +/// Handle to the DSP service. /// -/// This is the main struct to handle audio playback using the 3DS' speakers and headphone jack. -/// Only one "instance" of this struct can exist at a time. +/// Only one handle for this service can exist at a time. pub struct Ndsp { _service_handler: ServiceReference, channel_flags: [RefCell<()>; NUMBER_OF_CHANNELS as usize], @@ -78,8 +113,23 @@ impl Ndsp { /// /// # Errors /// - /// This function will return an error if an instance of the `Ndsp` struct already exists + /// This function will return an error if an instance of the [`Ndsp`] struct already exists /// or if there are any issues during initialization. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// + /// let ndsp = Ndsp::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspInit")] pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &NDSP_ACTIVE, @@ -105,6 +155,21 @@ impl Ndsp { /// # Errors /// /// An error will be returned if the channel ID is not between 0 and 23 or if the specified channel is already being used. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// + /// let channel_0 = ndsp.channel(0)?; + /// # + /// # Ok(()) + /// # } + /// ``` pub fn channel(&self, id: u8) -> std::result::Result { let in_bounds = self.channel_flags.get(id as usize); @@ -120,34 +185,138 @@ impl Ndsp { } } - /// Set the audio output mode. Defaults to `OutputMode::Stereo`. + /// Set the audio output mode. Defaults to [`OutputMode::Stereo`]. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::{Ndsp, OutputMode}; + /// let mut ndsp = Ndsp::new()?; + /// + /// // Use dual-channel output. + /// ndsp.set_output_mode(OutputMode::Stereo); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspSetOutputMode")] pub fn set_output_mode(&mut self, mode: OutputMode) { unsafe { ctru_sys::ndspSetOutputMode(mode.into()) }; } } impl Channel<'_> { - /// Reset the channel + /// Reset the channel (clear the queue and reset parameters). + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// channel_0.reset(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnReset")] pub fn reset(&mut self) { unsafe { ctru_sys::ndspChnReset(self.id.into()) }; } - /// Initialize the channel's parameters - pub fn init_parameters(&self) { + /// Initialize the channel's parameters with default values. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// channel_0.init_parameters(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnInitParams")] + pub fn init_parameters(&mut self) { unsafe { ctru_sys::ndspChnInitParams(self.id.into()) }; } /// Returns whether the channel is playing any audio. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // The channel is not playing any audio. + /// assert!(!channel_0.is_playing()); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnIsPlaying")] pub fn is_playing(&self) -> bool { unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) } } /// Returns whether the channel's playback is currently paused. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // The channel is not paused. + /// assert!(!channel_0.is_paused()); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnIsPaused")] pub fn is_paused(&self) -> bool { unsafe { ctru_sys::ndspChnIsPaused(self.id.into()) } } - // Returns the channel's id + /// Returns the channel's index. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // The channel's index is 0. + /// assert_eq!(channel_0.id(), 0); + /// # + /// # Ok(()) + /// # } + /// ``` pub fn id(&self) -> u8 { self.id } @@ -155,46 +324,161 @@ impl Channel<'_> { /// Returns the index of the currently played sample. /// /// Because of how fast this value changes, it should only be used as a rough estimate of the current progress. + #[doc(alias = "ndspChnGetSamplePos")] pub fn sample_position(&self) -> usize { (unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize } /// Returns the channel's current wave sequence's id. + #[doc(alias = "ndspChnGetWaveBufSeq")] pub fn wave_sequence_id(&self) -> u16 { unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) } } /// Pause or un-pause the channel's playback. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// channel_0.set_paused(true); + /// + /// // The channel is paused. + /// assert!(channel_0.is_paused()); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnSetPaused")] pub fn set_paused(&mut self, state: bool) { unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) }; } /// Set the channel's output format. - /// Change this setting based on the used sample's format. + /// + /// Change this setting based on the used wave's format. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::{AudioFormat, Ndsp}; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // Use the PCM16 interleaved dual-channel audio format. + /// channel_0.set_format(AudioFormat::PCM16Stereo); + /// # + /// # Ok(()) + /// # } + /// ``` + // TODO: Channels treat all waves as equal and do not read their format when playing them. Another good reason to re-write the service. + #[doc(alias = "ndspChnSetFormat")] pub fn set_format(&mut self, format: AudioFormat) { unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) }; } /// Set the channel's interpolation mode. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::{InterpolationType, Ndsp}; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // Use linear interpolation within frames. + /// channel_0.set_interpolation(InterpolationType::Linear); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnSetInterp")] pub fn set_interpolation(&mut self, interp_type: InterpolationType) { unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) }; } /// Set the channel's volume mix. + /// + /// Look at [`AudioMix`] for more information on the volume mix. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # use std::default::Default; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::{AudioMix, Ndsp}; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // Front-left and front-right channel maxed. + /// channel_0.set_mix(&AudioMix::default()); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnSetMix")] pub fn set_mix(&mut self, mix: &AudioMix) { unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) } } - /// Set the channel's rate of sampling. + /// Set the channel's rate of sampling in hertz. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // Standard CD sample rate. (44100 Hz) + /// channel_0.set_sample_rate(44100.); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnSetRate")] pub fn set_sample_rate(&mut self, rate: f32) { unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) }; } - // `ndspChnSetAdpcmCoefs` isn't wrapped on purpose. - // DSPADPCM is a proprietary format used by Nintendo, unavailable by "normal" means. - // We suggest using other wave formats when developing homebrew applications. + // TODO: wrap ADPCM format helpers. /// Clear the wave buffer queue and stop playback. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ndsp::Ndsp; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// // Clear the audio queue and stop playback. + /// channel_0.clear_queue(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "ndspChnWaveBufClear")] pub fn clear_queue(&mut self) { unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) }; } @@ -204,11 +488,38 @@ impl Channel<'_> { /// /// # Warning /// - /// `libctru` expects the user to manually keep the info data (in this case [Wave]) alive during playback. - /// To ensure safety, checks within [Wave] will clear the whole channel queue if any queued [Wave] is dropped prematurely. + /// `libctru` expects the user to manually keep the info data (in this case [`Wave`]) alive during playback. + /// To ensure safety, checks within [`Wave`] will clear the whole channel queue if any queued [`Wave`] is dropped prematurely. + /// + /// # Example + /// + /// ```no_run + /// # #![feature(allocator_api)] + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// # use ctru::linear::LinearAllocator; + /// use ctru::services::ndsp::{AudioFormat, Ndsp, wave::Wave}; + /// let ndsp = Ndsp::new()?; + /// let mut channel_0 = ndsp.channel(0)?; + /// + /// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator); + /// + /// // Provide your own audio data. + /// let mut wave = Wave::new(_audio_data, AudioFormat::PCM16Stereo, false); + /// + /// // Clear the audio queue and stop playback. + /// channel_0.queue_wave(&mut wave); + /// # + /// # Ok(()) + /// # } + /// ``` + // TODO: Find a better way to handle the wave lifetime problem. + // These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust. + #[doc(alias = "ndspChnWaveBufAdd")] pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), NdspError> { match wave.status() { - WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)), + Status::Playing | Status::Queued => return Err(NdspError::WaveBusy(self.id)), _ => (), } @@ -222,65 +533,74 @@ impl Channel<'_> { /// Functions to handle audio filtering. /// -/// Refer to [libctru](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info. +/// Refer to [`libctru`](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info. impl Channel<'_> { /// Enables/disables monopole filters. + #[doc(alias = "ndspChnIirMonoSetEnable")] pub fn iir_mono_set_enabled(&mut self, enable: bool) { unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) }; } - /// Sets the monopole to be a high pass filter. + /// Set the monopole to be a high pass filter. /// /// # Notes /// /// This is a lower quality filter than the Biquad alternative. + #[doc(alias = "ndspChnIirMonoSetParamsHighPassFilter")] pub fn iir_mono_set_params_high_pass_filter(&mut self, cut_off_freq: f32) { unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) }; } - /// Sets the monopole to be a low pass filter. + /// Set the monopole to be a low pass filter. /// /// # Notes /// /// This is a lower quality filter than the Biquad alternative. + #[doc(alias = "ndspChnIirMonoSetParamsLowPassFilter")] pub fn iir_mono_set_params_low_pass_filter(&mut self, cut_off_freq: f32) { unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) }; } /// Enables/disables biquad filters. + #[doc(alias = "ndspChnIirBiquadSetEnable")] pub fn iir_biquad_set_enabled(&mut self, enable: bool) { unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) }; } - /// Sets the biquad to be a high pass filter. + /// Set the biquad to be a high pass filter. + #[doc(alias = "ndspChnIirBiquadSetParamsHighPassFilter")] pub fn iir_biquad_set_params_high_pass_filter(&mut self, cut_off_freq: f32, quality: f32) { unsafe { ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality) }; } - /// Sets the biquad to be a low pass filter. + /// Set the biquad to be a low pass filter. + #[doc(alias = "ndspChnIirBiquadSetParamsLowPassFilter")] pub fn iir_biquad_set_params_low_pass_filter(&mut self, cut_off_freq: f32, quality: f32) { unsafe { ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality) }; } - /// Sets the biquad to be a notch filter. + /// Set the biquad to be a notch filter. + #[doc(alias = "ndspChnIirBiquadSetParamsNotchFilter")] pub fn iir_biquad_set_params_notch_filter(&mut self, notch_freq: f32, quality: f32) { unsafe { ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality) }; } - /// Sets the biquad to be a band pass filter. + /// Set the biquad to be a band pass filter. + #[doc(alias = "ndspChnIirBiquadSetParamsBandPassFilter")] pub fn iir_biquad_set_params_band_pass_filter(&mut self, mid_freq: f32, quality: f32) { unsafe { ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality) }; } - /// Sets the biquad to be a peaking equalizer. + /// Set the biquad to be a peaking equalizer. + #[doc(alias = "ndspChnIirBiquadSetParamsPeakingEqualizer")] pub fn iir_biquad_set_params_peaking_equalizer( &mut self, central_freq: f32, @@ -301,9 +621,10 @@ impl Channel<'_> { impl AudioFormat { /// Returns the amount of bytes needed to store one sample /// - /// Eg. - /// 8 bit mono formats return 1 (byte) - /// 16 bit stereo (dual-channel) formats return 4 (bytes) + /// # Example + /// + /// - 8 bit mono formats return 1 (byte) + /// - 16 bit stereo (dual-channel) formats return 4 (bytes) pub const fn size(self) -> usize { match self { Self::PCM8Mono => 1, @@ -314,7 +635,7 @@ impl AudioFormat { } impl AudioMix { - /// Creates a new [AudioMix] with all volumes set to 0. + /// Creates a new [`AudioMix`] with all volumes set to 0. pub fn zeroed() -> Self { Self { raw: [0.; 12] } } @@ -361,34 +682,34 @@ impl AudioMix { (self.raw[index], self.raw[index + 1]) } - /// Sets the values for the "front" volume mix (left and right channel). + /// Set the values for the "front" volume mix (left and right channel). /// /// # Notes /// - /// [Channel] will normalize the mix values to be within 0 and 1. - /// However, an [AudioMix] instance with larger/smaller values is valid. + /// [`Channel`] will normalize the mix values to be within 0 and 1. + /// However, an [`AudioMix`] instance with larger/smaller values is valid. pub fn set_front(&mut self, left: f32, right: f32) { self.raw[0] = left; self.raw[1] = right; } - /// Sets the values for the "back" volume mix (left and right channel). + /// Set the values for the "back" volume mix (left and right channel). /// /// # Notes /// - /// [Channel] will normalize the mix values to be within 0 and 1. - /// However, an [AudioMix] instance with larger/smaller values is valid. + /// [`Channel`] will normalize the mix values to be within 0 and 1. + /// However, an [`AudioMix`] instance with larger/smaller values is valid. pub fn set_back(&mut self, left: f32, right: f32) { self.raw[2] = left; self.raw[3] = right; } - /// Sets the values for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). + /// Set the values for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). /// /// # Notes /// - /// [Channel] will normalize the mix values to be within 0 and 1. - /// However, an [AudioMix] instance with larger/smaller values is valid. + /// [`Channel`] will normalize the mix values to be within 0 and 1. + /// However, an [`AudioMix`] instance with larger/smaller values is valid. pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) { if id > 1 { panic!("invalid auxiliary output device index") @@ -400,12 +721,12 @@ impl AudioMix { self.raw[index + 1] = right; } - /// Sets the values for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). + /// Set the values for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). /// /// # Notes /// - /// [Channel] will normalize the mix values to be within 0 and 1. - /// However, an [AudioMix] instance with larger/smaller values is valid. + /// [`Channel`] will normalize the mix values to be within 0 and 1. + /// However, an [`AudioMix`] instance with larger/smaller values is valid. pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) { if id > 1 { panic!("invalid auxiliary output device index") @@ -418,8 +739,8 @@ impl AudioMix { } } -/// Returns an [AudioMix] object with "front left" and "front right" volumes set to 100%, and all other volumes set to 0%. impl Default for AudioMix { + /// Returns an [`AudioMix`] object with "front left" and "front right" volumes set to 100%, and all other volumes set to 0%. fn default() -> Self { let mut mix = AudioMix::zeroed(); mix.set_front(1.0, 1.0); @@ -448,6 +769,7 @@ impl fmt::Display for NdspError { impl error::Error for NdspError {} impl Drop for Ndsp { + #[doc(alias = "ndspExit")] fn drop(&mut self) { for i in 0..NUMBER_OF_CHANNELS { self.channel(i).unwrap().reset(); diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs index feff4cd..1a383ca 100644 --- a/ctru-rs/src/services/ndsp/wave.rs +++ b/ctru-rs/src/services/ndsp/wave.rs @@ -1,7 +1,13 @@ +//! Audio wave. +//! +//! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service. + use super::{AudioFormat, NdspError}; use crate::linear::LinearAllocator; -/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf]. +/// Informational struct holding the raw audio data and playback info. +/// +/// You can play audio [`Wave`]s by using [`Channel::queue_wave()`](super::Channel::queue_wave). pub struct Wave { /// Data block of the audio wave (and its format information). buffer: Box<[u8], LinearAllocator>, @@ -13,16 +19,36 @@ pub struct Wave { #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u8)] -/// Enum representing the playback status of a [Wave]. -pub enum WaveStatus { +/// Playback status of a [`Wave`]. +pub enum Status { + /// Wave has never been used. Free = ctru_sys::NDSP_WBUF_FREE as u8, + /// Wave is currently queued for usage. Queued = ctru_sys::NDSP_WBUF_QUEUED as u8, + /// Wave is currently playing. Playing = ctru_sys::NDSP_WBUF_PLAYING as u8, + /// Wave has finished playing. Done = ctru_sys::NDSP_WBUF_DONE as u8, } impl Wave { - /// Build a new playable wave object from a raw buffer on LINEAR memory and a some info. + /// Build a new playable wave object from a raw buffer on [LINEAR memory](`crate::linear`) and some info. + /// + /// # Example + /// + /// ```no_run + /// # #![feature(allocator_api)] + /// # fn main() { + /// # + /// use ctru::linear::LinearAllocator; + /// use ctru::services::ndsp::{AudioFormat, wave::Wave}; + /// + /// // Zeroed box allocated in the LINEAR memory. + /// let audio_data = Box::new_in([0u8; 96], LinearAllocator); + /// + /// let wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false); + /// # } + /// ``` pub fn new( buffer: Box<[u8], LinearAllocator>, audio_format: AudioFormat, @@ -60,41 +86,60 @@ impl Wave { } } - /// Return a slice to the audio data (on the LINEAR memory). + /// Returns a slice to the audio data (on the LINEAR memory). pub fn get_buffer(&self) -> &[u8] { &self.buffer } - /// Return a mutable slice to the audio data (on the LINEAR memory). + /// Returns a mutable slice to the audio data (on the LINEAR memory). /// /// # Errors /// - /// This function will return an error if the [Wave] is currently busy, + /// This function will return an error if the [`Wave`] is currently busy, /// with the id to the channel in which it's queued. pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { match self.status() { - WaveStatus::Playing | WaveStatus::Queued => { + Status::Playing | Status::Queued => { Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) } _ => Ok(&mut self.buffer), } } - /// Return this wave's playback status. - pub fn status(&self) -> WaveStatus { + /// Returns this wave's playback status. + /// + /// # Example + /// + /// ```no_run + /// # #![feature(allocator_api)] + /// # fn main() { + /// # + /// # use ctru::linear::LinearAllocator; + /// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator); + /// # + /// use ctru::services::ndsp::{AudioFormat, wave::{Wave, Status}}; + /// + /// // Provide your own audio data. + /// let wave = Wave::new(_audio_data, AudioFormat::PCM16Stereo, false); + /// + /// // The `Wave` is free if never played before. + /// assert!(matches!(wave.status(), Status::Free)); + /// # } + /// ``` + pub fn status(&self) -> Status { self.raw_data.status.try_into().unwrap() } - /// Get the amounts of samples *read* by the NDSP process. + /// Returns the amount of samples *read* by the NDSP process. /// /// # Notes /// - /// This value varies depending on [Self::set_sample_count]. + /// This value varies depending on [`Wave::set_sample_count`]. pub fn sample_count(&self) -> usize { self.raw_data.nsamples as usize } - /// Get the format of the audio data. + /// Returns the format of the audio data. pub fn format(&self) -> AudioFormat { self.audio_format } @@ -107,20 +152,20 @@ impl Wave { } /// Set the amount of samples to be read. - /// This function doesn't resize the internal buffer. /// /// # Note /// - /// Operations of this kind are particularly useful to allocate memory pools - /// for VBR (Variable BitRate) Formats, like OGG Vorbis. + /// + /// This function doesn't resize the internal buffer. Operations of this kind are particularly useful to allocate memory pools + /// for VBR (Variable BitRate) formats, like OGG Vorbis. /// /// # Errors /// /// This function will return an error if the sample size exceeds the buffer's capacity - /// or if the [Wave] is currently queued. + /// or if the [`Wave`] is currently queued. pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> { match self.status() { - WaveStatus::Playing | WaveStatus::Queued => { + Status::Playing | Status::Queued => { return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); } _ => (), @@ -138,7 +183,7 @@ impl Wave { } } -impl TryFrom for WaveStatus { +impl TryFrom for Status { type Error = &'static str; fn try_from(value: u8) -> Result { @@ -157,7 +202,7 @@ impl Drop for Wave { // This was the only way I found I could check for improper drops of `Wave`. // A panic was considered, but it would cause issues with drop order against `Ndsp`. match self.status() { - WaveStatus::Free | WaveStatus::Done => (), + Status::Free | Status::Done => (), // If the status flag is "unfinished" _ => { // The unwrap is safe, since it must have a value in the case the status is "unfinished". diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs index 1d5bd69..fc20a78 100644 --- a/ctru-rs/src/services/ps.rs +++ b/ctru-rs/src/services/ps.rs @@ -1,40 +1,80 @@ -//! Process Services (PS) module. This is used for miscellaneous utility tasks, but -//! is particularly important because it is used to generate random data, which -//! is required for common things like [`HashMap`](std::collections::HashMap). +//! Process Services. +//! +//! This service handles miscellaneous utility tasks used by the various processes. +//! However, it is particularly important because it is used to generate cryptographically secure random data, which +//! is required for commonly used functionality such as hashing (e.g. [`HashMap`](std::collections::HashMap) will not work without it). +//! //! See also use crate::error::ResultCode; use crate::Result; +/// Type of AES algorithm to use. +#[doc(alias = "PS_AESAlgorithm")] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum AESAlgorithm { + /// CBC encryption. CbcEnc = ctru_sys::PS_ALGORITHM_CBC_ENC, + /// CBC decryption. CbcDec = ctru_sys::PS_ALGORITHM_CBC_DEC, + /// CTR encryption. CtrEnc = ctru_sys::PS_ALGORITHM_CTR_ENC, + /// CTR decryption. CtrDec = ctru_sys::PS_ALGORITHM_CTR_DEC, + /// CCM encryption. CcmEnc = ctru_sys::PS_ALGORITHM_CCM_ENC, + /// CCM decryption. CcmDec = ctru_sys::PS_ALGORITHM_CCM_DEC, } +/// PS Key slot to use. +#[doc(alias = "PS_AESKeyType")] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum AESKeyType { + /// Keyslot 0x0D. Keyslot0D = ctru_sys::PS_KEYSLOT_0D, + /// Keyslot 0x2D. Keyslot2D = ctru_sys::PS_KEYSLOT_2D, + /// Keyslot 0x2E. Keyslot2E = ctru_sys::PS_KEYSLOT_2E, + /// Keyslot 0x31. Keyslot31 = ctru_sys::PS_KEYSLOT_31, + /// Keyslot 0x32. Keyslot32 = ctru_sys::PS_KEYSLOT_32, + /// Keyslot 0x36. Keyslot36 = ctru_sys::PS_KEYSLOT_36, + /// Keyslot 0x38. Keyslot38 = ctru_sys::PS_KEYSLOT_38, + /// Keyslot 0x39 (DLP). Keyslot39Dlp = ctru_sys::PS_KEYSLOT_39_DLP, + /// Keyslot 0x39 (NFC). Keyslot39Nfc = ctru_sys::PS_KEYSLOT_39_NFC, + /// Invalid keyslot. KeyslotInvalid = ctru_sys::PS_KEYSLOT_INVALID, } +/// Handle to the PS service. pub struct Ps(()); impl Ps { + /// Initialize a new service handle. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ps::Ps; + /// + /// let ps = Ps::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "psInit")] pub fn new() -> Result { unsafe { ResultCode(ctru_sys::psInit())?; @@ -42,6 +82,23 @@ impl Ps { } } + /// Returns the console's local friend code seed. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ps::Ps; + /// let ps = Ps::new()?; + /// + /// let friend_code_seed = ps.local_friend_code_seed()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PS_GetLocalFriendCodeSeed")] pub fn local_friend_code_seed(&self) -> crate::Result { let mut seed: u64 = 0; @@ -49,6 +106,23 @@ impl Ps { Ok(seed) } + /// Returns the console's devide ID. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ps::Ps; + /// let ps = Ps::new()?; + /// + /// let device_id = ps.device_id()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PS_GetDeviceId")] pub fn device_id(&self) -> crate::Result { let mut id: u32 = 0; @@ -56,6 +130,26 @@ impl Ps { Ok(id) } + /// Generates cryptografically secure random bytes and writes them into the `out` buffer. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::ps::Ps; + /// let ps = Ps::new()?; + /// + /// let mut buffer = vec![0; 128]; + /// + /// // The buffer is now randomized! + /// ps.generate_random_bytes(&mut buffer)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "PS_GenerateRandomBytes")] pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> { ResultCode(unsafe { ctru_sys::PS_GenerateRandomBytes(out.as_mut_ptr().cast(), out.len()) @@ -65,6 +159,7 @@ impl Ps { } impl Drop for Ps { + #[doc(alias = "psExit")] fn drop(&mut self) { unsafe { ctru_sys::psExit(); diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index 49369a6..e6f1c3b 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -1,9 +1,14 @@ -//! Read-Only Memory FileSystem +//! Read-Only Memory FileSystem service. +//! +//! This service lets the application access a virtual mounted device created using a folder included within the application bundle. +//! After mounting the RomFS file system, the included files and folders will be accessible exactly like any other file, just by using the drive prefix `romfs:/`. +//! +//! # Usage //! //! 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 +//! Configure the path in your project's `Cargo.toml` manifest (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. //! @@ -11,6 +16,16 @@ //! [package.metadata.cargo-3ds] //! romfs_dir = "romfs" //! ``` +//! +//! Alternatively, you can include the RomFS archive manually when building with `3dsxtool`. +//! +//! # Notes +//! +//! `std::path` has problems when parsing file paths that include the `romfs:` prefix. +//! As such, it's suggested to use the paths directly or to do simple append operations to avoid unexpected behaviour. +//! Related [issue](https://github.com/rust-lang/rust/issues/52331). +#![doc(alias = "embed")] +#![doc(alias = "filesystem")] use crate::error::ResultCode; use std::ffi::CStr; @@ -18,6 +33,7 @@ use std::sync::Mutex; use crate::services::ServiceReference; +/// Handle to the RomFS service. pub struct RomFS { _service_handler: ServiceReference, } @@ -25,6 +41,25 @@ pub struct RomFS { static ROMFS_ACTIVE: Mutex = Mutex::new(0); impl RomFS { + /// Mount the bundled RomFS archive as a virtual drive. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::romfs::RomFS; + /// + /// let romfs = RomFS::new()?; + /// + /// // Remember to include the RomFS archive and to use your actual files! + /// let contents = std::fs::read_to_string("romfs:/test-file.txt"); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "romfsMountSelf")] pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &ROMFS_ACTIVE, @@ -36,7 +71,7 @@ impl RomFS { }, || { let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); - unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; + let _ = unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; }, )?; diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index 5b9c696..d418919 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -1,4 +1,9 @@ -//! Network Socket +//! Network Socket service. +//! +//! By using this service the program enables the use of network sockets and utilities such as those found in `std::net`, which are completely inaccessible by default. +//! As such, remember to hold a handle to this service handle while using any network functionality, or else the `std::net` methods will return generic OS errors. +#![doc(alias = "socket")] +#![doc(alias = "network")] use libc::memalign; use std::net::Ipv4Addr; @@ -8,10 +13,7 @@ use crate::error::ResultCode; use crate::services::ServiceReference; use crate::Error; -/// Network socket service -/// -/// Initializing this service will enable the use of network sockets and utilities -/// such as those found in `std::net`. The service will close once this struct gets dropped. +/// Handle to the Network Socket service. pub struct Soc { _service_handler: ServiceReference, sock_3dslink: libc::c_int, @@ -20,21 +22,52 @@ pub struct Soc { static SOC_ACTIVE: Mutex = Mutex::new(0); impl Soc { - /// Initialize the Soc service with a default buffer size of 0x100000 bytes + /// Initialize a new service handle using a socket buffer size of `0x100000` bytes. /// /// # Errors /// - /// This function will return an error if the `Soc` service is already initialized + /// This function will return an error if the [`Soc`] service is already being used. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::soc::Soc; + /// + /// let soc = Soc::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "socInit")] pub fn new() -> crate::Result { Self::init_with_buffer_size(0x100000) } - /// Initialize the Soc service with a custom buffer size in bytes. The size should be - /// 0x100000 bytes or greater. + /// Initialize a new service handle using a custom socket buffer size. + /// + /// The size should be `0x100000` bytes or greater. /// /// # Errors /// - /// This function will return an error if the `Soc` service is already initialized + /// This function will return an error if the [`Soc`] service is already being used. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::soc::Soc; + /// + /// let soc = Soc::init_with_buffer_size(0x100000)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "socInit")] pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result { let _service_handler = ServiceReference::new( &SOC_ACTIVE, @@ -60,19 +93,59 @@ impl Soc { }) } - /// IP Address of the Nintendo 3DS system. + /// Returns the local IP Address of the Nintendo 3DS system. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::soc::Soc; + /// let soc = Soc::new()?; + /// + /// let address = soc.host_address(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "gethostid")] pub fn host_address(&self) -> Ipv4Addr { let raw_id = unsafe { libc::gethostid() }; Ipv4Addr::from(raw_id.to_ne_bytes()) } - /// Redirect output streams (i.e. [`println`] and [`eprintln`]) to the `3dslink` server. - /// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0. + /// Redirect output streams (i.e. `stdout` and `stderr`) to the `3dslink` server. + /// + /// With this redirection it is possible to send (and view in real time) the output of `stdout` operations, + /// such as `println!` or `dbg!`. + /// + /// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0 and the use of the `--server` flag. + /// The `--server` flag is also availble to use via `cargo-3ds` if the requirements are met. /// /// # Errors /// - /// Returns an error if a connection cannot be established to the server, or - /// output was already previously redirected. + /// Returns an error if a connection cannot be established to the server, + /// or if the output was already being redirected. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::soc::Soc; + /// let mut soc = Soc::new()?; + /// + /// // Redirect to the `3dslink` server that sent this program. + /// let address = soc.redirect_to_3dslink(true, true)?; + /// + /// println!("I'm visible from a PC!"); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "link3dsConnectToHost")] pub fn redirect_to_3dslink(&mut self, stdout: bool, stderr: bool) -> crate::Result<()> { if self.sock_3dslink >= 0 { return Err(Error::OutputAlreadyRedirected); @@ -92,6 +165,7 @@ impl Soc { } impl Drop for Soc { + #[doc(alias = "socExit")] fn drop(&mut self) { if self.sock_3dslink >= 0 { unsafe { diff --git a/ctru-rs/src/services/sslc.rs b/ctru-rs/src/services/sslc.rs index 341155f..af8b65d 100644 --- a/ctru-rs/src/services/sslc.rs +++ b/ctru-rs/src/services/sslc.rs @@ -1,13 +1,29 @@ -//! SSLC (TLS) service +//! SSLC (TLS) service. // TODO: Implement remaining functions use crate::error::ResultCode; +/// Handle to the SSLC service. pub struct SslC(()); impl SslC { - /// Initialize the service + /// Initialize a new service handle. + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::sslc::SslC; + /// + /// let sslc = SslC::new()?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "sslcInit")] pub fn new() -> crate::Result { unsafe { ResultCode(ctru_sys::sslcInit(0))?; @@ -17,6 +33,7 @@ impl SslC { } impl Drop for SslC { + #[doc(alias = "sslcExit")] fn drop(&mut self) { unsafe { ctru_sys::sslcExit() }; } diff --git a/ctru-sys/Cargo.toml b/ctru-sys/Cargo.toml index e83bb03..eba9db0 100644 --- a/ctru-sys/Cargo.toml +++ b/ctru-sys/Cargo.toml @@ -2,6 +2,11 @@ name = "ctru-sys" version = "22.2.0+2.2.2-1" authors = ["Rust3DS Org", "Ronald Kinard "] +description = "Raw bindings to libctru" +repository = "https://github.com/rust3ds/ctru-rs" +keywords = ["3ds", "libctru"] +categories = ["os", "external-ffi-bindings", "no-std", "hardware-support"] +exclude = ["src/.gitattributes"] license = "Zlib" links = "ctru" edition = "2021" @@ -14,3 +19,8 @@ bindgen = { version = "0.65.1", features = ["experimental"] } cc = "1.0" doxygen-rs = "0.4.2" which = "4.4.0" + +[package.metadata.docs.rs] +default-target = "armv6k-nintendo-3ds" +targets = [] +cargo-args = ["-Z", "build-std"] diff --git a/ctru-sys/README.md b/ctru-sys/README.md new file mode 100644 index 0000000..280fd83 --- /dev/null +++ b/ctru-sys/README.md @@ -0,0 +1,33 @@ +# ctru-sys + +Raw Rust bindings over the [`libctru`](https://github.com/devkitPro/libctru) C library. + +## Requirements + +To use the bindings provided by this crate you will need to link against the [`libctru`](https://github.com/devkitPro/libctru) library. +Consult the [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki/Getting-Started) to learn how to obtain the required packages +to use this library. + +## Version + +This crate's version changes according to the version of `libctru` +used to generate the bindings, with the following convention: + + * [`libctru`](https://github.com/devkitPro/libctru) version `X.Y.Z-W` + * `ctru-sys` version `XY.Z.P+X.Y.Z-W` + + where `P` is usually 0 but may be incremented for fixes in e.g. + binding generation, `libc` dependency bump, etc. + +It may be possible to build this crate against a different version of [`libctru`](https://github.com/devkitPro/libctru), +but you may encounter linker errors or ABI issues. A build-time Cargo warning +(displayed when built with `-vv`) will be issued if the build script detects +a mismatch or is unable to check the installed [`libctru`](https://github.com/devkitPro/libctru) version. + +## Generating bindings + +Bindings of new versions of [`libctru`](https://github.com/devkitPro/libctru) can be built using the integrated [`bindgen.sh`](./bindgen.sh) script. + +## License + +This project is distributed under the Zlib license.