Browse Source

Merge pull request #134 from rust3ds/improve/docs

Improve documentation and examples
pull/136/head
Meziu 1 year ago committed by GitHub
parent
commit
3e89922f9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/workflows/ci.yml
  2. 1
      .gitignore
  3. 1
      Cargo.toml
  4. 28
      README.md
  5. 19
      ctru-rs/Cargo.toml
  6. 22
      ctru-rs/README.md
  7. 39
      ctru-rs/examples/audio-filters.rs
  8. 24
      ctru-rs/examples/buttons.rs
  9. 22
      ctru-rs/examples/camera-image.rs
  10. 24
      ctru-rs/examples/file-explorer.rs
  11. 31
      ctru-rs/examples/gfx-3d-mode.rs
  12. 28
      ctru-rs/examples/gfx-bitmap.rs
  13. 12
      ctru-rs/examples/gfx-wide-mode.rs
  14. 57
      ctru-rs/examples/graphics-bitmap.rs
  15. 11
      ctru-rs/examples/hashmaps.rs
  16. 11
      ctru-rs/examples/hello-both-screens.rs
  17. 20
      ctru-rs/examples/hello-world.rs
  18. 15
      ctru-rs/examples/linear-memory.rs
  19. 21
      ctru-rs/examples/mii-selector.rs
  20. 31
      ctru-rs/examples/network-sockets.rs
  21. 9
      ctru-rs/examples/output-3dslink.rs
  22. 20
      ctru-rs/examples/romfs.rs
  23. 25
      ctru-rs/examples/software-keyboard.rs
  24. 14
      ctru-rs/examples/system-configuration.rs
  25. 2
      ctru-rs/examples/thread-basic.rs
  26. 2
      ctru-rs/examples/thread-info.rs
  27. 2
      ctru-rs/examples/thread-locals.rs
  28. 12
      ctru-rs/examples/time-rtc.rs
  29. 46
      ctru-rs/examples/title-info.rs
  30. 18
      ctru-rs/examples/touch-screen.rs
  31. 248
      ctru-rs/src/applets/mii_selector.rs
  32. 10
      ctru-rs/src/applets/mod.rs
  33. 339
      ctru-rs/src/applets/swkbd.rs
  34. 140
      ctru-rs/src/console.rs
  35. 39
      ctru-rs/src/error.rs
  36. 43
      ctru-rs/src/lib.rs
  37. 21
      ctru-rs/src/linear.rs
  38. 176
      ctru-rs/src/mii.rs
  39. 11
      ctru-rs/src/prelude.rs
  40. 149
      ctru-rs/src/services/am.rs
  41. 61
      ctru-rs/src/services/apt.rs
  42. 452
      ctru-rs/src/services/cam.rs
  43. 160
      ctru-rs/src/services/cfgu.rs
  44. 149
      ctru-rs/src/services/fs.rs
  45. 217
      ctru-rs/src/services/gfx.rs
  46. 13
      ctru-rs/src/services/gspgpu.rs
  47. 201
      ctru-rs/src/services/hid.rs
  48. 15
      ctru-rs/src/services/mod.rs
  49. 420
      ctru-rs/src/services/ndsp/mod.rs
  50. 85
      ctru-rs/src/services/ndsp/wave.rs
  51. 101
      ctru-rs/src/services/ps.rs
  52. 41
      ctru-rs/src/services/romfs.rs
  53. 104
      ctru-rs/src/services/soc.rs
  54. 21
      ctru-rs/src/services/sslc.rs
  55. 10
      ctru-sys/Cargo.toml
  56. 33
      ctru-sys/README.md
  57. 5
      ctru-sys/build.rs

4
.github/workflows/ci.yml

@ -21,7 +21,7 @@ jobs:
matrix: matrix:
toolchain: toolchain:
# Run against a "known good" nightly # Run against a "known good" nightly
- nightly-2023-01-13 - nightly-2023-05-31
# Check for breakage on latest nightly # Check for breakage on latest nightly
- nightly - nightly
@ -57,7 +57,7 @@ jobs:
strategy: strategy:
matrix: matrix:
toolchain: toolchain:
- nightly-2023-01-13 - nightly-2023-05-31
- nightly - nightly
continue-on-error: ${{ matrix.toolchain == 'nightly' }} continue-on-error: ${{ matrix.toolchain == 'nightly' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest

1
.gitignore vendored

@ -4,3 +4,4 @@ Cargo.lock
# IDE files # IDE files
.idea .idea
.vscode

1
Cargo.toml

@ -1,6 +1,7 @@
[workspace] [workspace]
members = ["ctru-rs", "ctru-sys", "ctru-sys/bindgen-ctru-sys"] members = ["ctru-rs", "ctru-sys", "ctru-sys/bindgen-ctru-sys"]
default-members = ["ctru-rs", "ctru-sys"] default-members = ["ctru-rs", "ctru-sys"]
resolver = "2"
[patch.'https://github.com/rust3ds/ctru-rs'] [patch.'https://github.com/rust3ds/ctru-rs']
# Make sure all dependencies use the local ctru-sys package # Make sure all dependencies use the local ctru-sys package

28
README.md

@ -1,33 +1,27 @@
# ctru-rs # 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 ## Structure
This repository is organized as follows: 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` Specific information about how to use the crates is present in the individual README for each package.
used to generate the bindings, with the following convention: Have a look at `ctru-rs`' [README.md](./ctru-rs/README.md) for a broad overview.
* `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.
## Original version ## Original version
This project is based on the efforts the original authors: This project is based on the efforts of the original authors:
* [Eidolon](https://github.com/HybridEidolon) * [Eidolon](https://github.com/HybridEidolon)
* [FenrirWolf](https://github.com/FenrirWolf) * [FenrirWolf](https://github.com/FenrirWolf)
The old version is archived [here](https://github.com/rust3ds/ctru-rs-old). The old version is archived [here](https://github.com/rust3ds/ctru-rs-old).
## License
This project is distributed under the Zlib license.

19
ctru-rs/Cargo.toml

@ -1,11 +1,15 @@
[package] [package]
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "A safe wrapper around smealum's ctrulib."
license = "Zlib"
name = "ctru-rs" name = "ctru-rs"
version = "0.7.1" version = "0.7.1"
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
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" edition = "2021"
rust-version = "1.64" rust-version = "1.70"
[lib] [lib]
crate-type = ["rlib"] crate-type = ["rlib"]
@ -18,7 +22,7 @@ const-zero = "0.1.0"
shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" }
pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" }
libc = "0.2.121" libc = "0.2.121"
bitflags = "1.0.0" bitflags = "2.3.3"
widestring = "0.2.2" widestring = "0.2.2"
[build-dependencies] [build-dependencies]
@ -45,6 +49,11 @@ std-threads = []
[package.metadata.cargo-3ds] [package.metadata.cargo-3ds]
romfs_dir = "examples/romfs" romfs_dir = "examples/romfs"
[package.metadata.docs.rs]
default-target = "armv6k-nintendo-3ds"
targets = []
cargo-args = ["-Z", "build-std"]
[[example]] [[example]]
name = "thread-basic" name = "thread-basic"
required-features = ["std-threads"] required-features = ["std-threads"]

22
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 <example-name>
```
## License
This project is distributed under the Zlib license.

39
ctru-rs/examples/audio-filters.rs

@ -1,3 +1,7 @@
//! Audio Filters example.
//!
//! This example showcases basic audio functionality using [`Ndsp`].
#![feature(allocator_api)] #![feature(allocator_api)]
use std::f32::consts::PI; use std::f32::consts::PI;
@ -5,21 +9,22 @@ use std::f32::consts::PI;
use ctru::linear::LinearAllocator; use ctru::linear::LinearAllocator;
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::ndsp::{ use ctru::services::ndsp::{
wave::{Wave, WaveStatus}, wave::{Status, Wave},
AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode, AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode,
}; };
// Configuration for the NDSP process and channels.
const SAMPLE_RATE: usize = 22050; const SAMPLE_RATE: usize = 22050;
const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205 const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205
const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size(); const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size();
const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE; 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.]; 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) { 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); let formatted_data = bytemuck::cast_slice_mut::<_, [i16; 2]>(audio_data);
for (i, chunk) in formatted_data.iter_mut().enumerate() { for (i, chunk) in formatted_data.iter_mut().enumerate() {
@ -44,8 +49,7 @@ fn main() {
let mut note: usize = 4; let mut note: usize = 4;
// Filters // Filter names to display.
let filter_names = [ let filter_names = [
"None", "None",
"Low-Pass", "Low-Pass",
@ -60,19 +64,26 @@ fn main() {
// We set up two wave buffers and alternate between the two, // We set up two wave buffers and alternate between the two,
// effectively streaming an infinitely long sine wave. // 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); 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]); 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(); 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_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = Wave::new(audio_data2, 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); 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(); let mut channel_zero = ndsp.channel(0).unwrap();
channel_zero.set_interpolation(InterpolationType::Linear); channel_zero.set_interpolation(InterpolationType::Linear);
channel_zero.set_sample_rate(SAMPLE_RATE as f32); channel_zero.set_sample_rate(SAMPLE_RATE as f32);
@ -82,6 +93,7 @@ fn main() {
let mix = AudioMix::default(); let mix = AudioMix::default();
channel_zero.set_mix(&mix); 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_info1).unwrap();
channel_zero.queue_wave(&mut wave_info2).unwrap(); channel_zero.queue_wave(&mut wave_info2).unwrap();
@ -93,6 +105,8 @@ fn main() {
filter_names[filter as usize] filter_names[filter as usize]
); );
println!("\x1b[29;16HPress Start to exit");
let mut altern = true; // true is wave_info1, false is wave_info2 let mut altern = true; // true is wave_info1, false is wave_info2
while apt.main_loop() { while apt.main_loop() {
@ -101,14 +115,16 @@ fn main() {
if keys_down.contains(KeyPad::START) { if keys_down.contains(KeyPad::START) {
break; break;
} // break in order to return to hbmenu }
// Note frequency controller using the buttons.
if keys_down.intersects(KeyPad::DOWN) { if keys_down.intersects(KeyPad::DOWN) {
note = note.saturating_sub(1); note = note.saturating_sub(1);
} else if keys_down.intersects(KeyPad::UP) { } else if keys_down.intersects(KeyPad::UP) {
note = std::cmp::min(note + 1, NOTEFREQ.len() - 1); note = std::cmp::min(note + 1, NOTEFREQ.len() - 1);
} }
// Filter controller using the buttons.
let mut update_params = false; let mut update_params = false;
if keys_down.intersects(KeyPad::LEFT) { if keys_down.intersects(KeyPad::LEFT) {
filter -= 1; filter -= 1;
@ -139,14 +155,16 @@ fn main() {
} }
} }
// Double buffer alternation depending on the one used.
let current: &mut Wave = if altern { let current: &mut Wave = if altern {
&mut wave_info1 &mut wave_info1
} else { } else {
&mut wave_info2 &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(); let status = current.status();
if let WaveStatus::Done = status { if let Status::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);
channel_zero.queue_wave(current).unwrap(); channel_zero.queue_wave(current).unwrap();
@ -154,7 +172,6 @@ fn main() {
altern = !altern; altern = !altern;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

24
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -18,28 +22,28 @@ fn main() {
// Scan for user input on the current frame. // Scan for user input on the current frame.
hid.scan_input(); 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(); let keys = hid.keys_held();
// We only want to print when the keys we're holding now are different // 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 { if keys != old_keys {
// Clear the screen // Clear the screen.
console.clear(); 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!("Hi there! Try pressing a button");
println!("\x1b[29;16HPress Start to exit"); 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"); println!("\x1b[3;0H");
// Print to the screen depending on which keys were held. // Print to the screen depending on which keys were held.
// //
// The .contains() method checks for all 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. // 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 // the underlying bits
if keys.contains(KeyPad::A) { if keys.contains(KeyPad::A) {
@ -54,13 +58,13 @@ fn main() {
if keys.intersects(KeyPad::L | KeyPad::R | KeyPad::ZL | KeyPad::ZR) { if keys.intersects(KeyPad::L | KeyPad::R | KeyPad::ZL | KeyPad::ZR) {
println!("You held a shoulder button!"); println!("You held a shoulder button!");
} }
if keys.intersects(KeyPad::START) { if keys.contains(KeyPad::START) {
println!("See ya!"); println!("See ya!");
break; break;
} }
} }
// Save our current key presses for the next frame // Save our current key presses for the next frame.
old_keys = keys; old_keys = keys;
gfx.wait_for_vblank(); gfx.wait_for_vblank();

22
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::prelude::*;
use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize}; use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize};
use ctru::services::gfx::{Flush, Screen, Swap}; use ctru::services::gfx::{Flush, Screen, Swap};
@ -8,7 +12,7 @@ use std::time::Duration;
const WIDTH: usize = 400; const WIDTH: usize = 400;
const HEIGHT: usize = 240; 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 BUF_SIZE: usize = WIDTH * HEIGHT * 2;
const WAIT_TIMEOUT: Duration = Duration::from_millis(300); const WAIT_TIMEOUT: Duration = Duration::from_millis(300);
@ -26,12 +30,11 @@ fn main() {
let _console = Console::new(gfx.bottom_screen.borrow_mut()); let _console = Console::new(gfx.bottom_screen.borrow_mut());
let mut keys_down;
println!("Initializing camera"); println!("Initializing camera");
let mut cam = Cam::new().expect("Failed to initialize CAM service."); let mut cam = Cam::new().expect("Failed to initialize CAM service.");
// Camera setup.
{ {
let camera = &mut cam.outer_right_cam; let camera = &mut cam.outer_right_cam;
@ -58,21 +61,23 @@ fn main() {
let mut buf = vec![0u8; BUF_SIZE]; let mut buf = vec![0u8; BUF_SIZE];
println!("\nPress R to take a new picture"); 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() { while apt.main_loop() {
hid.scan_input(); hid.scan_input();
keys_down = hid.keys_down(); let keys_down = hid.keys_down();
if keys_down.contains(KeyPad::START) { if keys_down.contains(KeyPad::START) {
break; break;
} }
// If the user presses the R button.
if keys_down.contains(KeyPad::R) { if keys_down.contains(KeyPad::R) {
println!("Capturing new image"); println!("Capturing new image");
let camera = &mut cam.outer_right_cam; let camera = &mut cam.outer_right_cam;
// Take a picture and write it to the buffer.
camera camera
.take_picture( .take_picture(
&mut buf, &mut buf,
@ -82,12 +87,14 @@ fn main() {
) )
.expect("Failed to take picture"); .expect("Failed to take picture");
// Play the normal shutter sound.
cam.play_shutter_sound(ShutterSound::Normal) cam.play_shutter_sound(ShutterSound::Normal)
.expect("Failed to play shutter sound"); .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); 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.flush_buffers();
top_screen.swap_buffers(); top_screen.swap_buffers();
@ -99,6 +106,7 @@ fn main() {
// The 3DS' screens are 2 vertical LCD panels rotated by 90 degrees. // 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. // 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 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) { fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: usize) {
for j in 0..height { for j in 0..height {
for i in 0..width { 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) let draw_index = (draw_x * height + draw_y) * 2; // This 2 stands for the number of bytes per pixel (16 bits)
unsafe { 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. // The offsets are completely safe as long as the width and height are correct.
let pixel_pointer = framebuf.offset(draw_index as isize); let pixel_pointer = framebuf.offset(draw_index as isize);
pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2); pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2);

24
ctru-rs/examples/file-explorer.rs

@ -1,7 +1,9 @@
//! A file explorer which shows off using standard library file system APIs to //! File Explorer example.
//! read the SD card. //!
//! 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 ctru::prelude::*;
use std::fs::DirEntry; use std::fs::DirEntry;
@ -15,6 +17,7 @@ fn main() {
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();
// Mount the RomFS if available.
#[cfg(all(feature = "romfs", romfs_exists))] #[cfg(all(feature = "romfs", romfs_exists))]
let _romfs = ctru::services::romfs::RomFS::new().unwrap(); let _romfs = ctru::services::romfs::RomFS::new().unwrap();
@ -50,6 +53,7 @@ impl<'a> FileExplorer<'a> {
fn run(&mut self) { fn run(&mut self) {
self.running = true; self.running = true;
// Print the file explorer commands.
self.print_menu(); self.print_menu();
while self.running && self.apt.main_loop() { while self.running && self.apt.main_loop() {
@ -62,8 +66,10 @@ impl<'a> FileExplorer<'a> {
self.path.pop(); self.path.pop();
self.console.clear(); self.console.clear();
self.print_menu(); self.print_menu();
// Open a directory/file to read.
} else if input.contains(KeyPad::A) { } else if input.contains(KeyPad::A) {
self.get_input_and_run(Self::set_next_path); self.get_input_and_run(Self::set_next_path);
// Open a specific path using the `SoftwareKeyboard` applet.
} else if input.contains(KeyPad::X) { } else if input.contains(KeyPad::X) {
self.get_input_and_run(Self::set_exact_path); 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) { 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) { fn wait_for_page_down(&mut self) {
println!("Press A to go to next page, or Start to exit"); 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)) { 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) { match keyboard.get_string(2048) {
Ok((path, Button::Right)) => { Ok((path, Button::Right)) => {
// Clicked "OK" // Clicked "OK".
action(self, path); action(self, path);
} }
Ok((_, Button::Left)) => { Ok((_, Button::Left)) => {
// Clicked "Cancel" // Clicked "Cancel".
} }
Ok((_, Button::Middle)) => { Ok((_, Button::Middle)) => {
// This button wasn't shown // This button wasn't shown.
unreachable!() unreachable!()
} }
Err(e) => { Err(e) => {

31
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::prelude::*;
use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D}; 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"); const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
static ZERO: &[u8] = &[0; IMAGE.len()]; static ZERO: &[u8] = &[0; IMAGE.len()];
@ -17,7 +24,9 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.bottom_screen.borrow_mut()); 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); gfx.top_screen.borrow_mut().set_double_buffering(true);
@ -25,39 +34,40 @@ fn main() {
let mut current_side = Side::Left; let mut current_side = Side::Left;
// Main loop
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Split the TopScreen3D to get references to the two render surfaces.
let (mut left, mut right) = top_screen.split_mut(); let (mut left, mut right) = top_screen.split_mut();
let left_buf = left.raw_framebuffer(); let left_buf = left.raw_framebuffer();
let right_buf = right.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 { unsafe {
left_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); left_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len());
right_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) { 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 { current_side = match current_side {
Side::Left => Side::Right, Side::Left => Side::Right,
Side::Right => Side::Left, Side::Right => Side::Left,
}; };
} }
// Obtain the framebuffer of the currently rendered side.
let buf = match current_side { let buf = match current_side {
Side::Left => left_buf.ptr, Side::Left => left_buf.ptr,
Side::Right => right_buf.ptr, Side::Right => right_buf.ptr,
}; };
// Render the image to the surface's buffer.
unsafe { unsafe {
buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); buf.copy_from(IMAGE.as_ptr(), IMAGE.len());
} }
@ -67,7 +77,6 @@ fn main() {
top_screen.flush_buffers(); top_screen.flush_buffers();
top_screen.swap_buffers(); top_screen.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

28
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::prelude::*;
use ctru::services::gfx::{Flush, Screen, Swap}; use ctru::services::gfx::{Flush, Screen, Swap};
@ -22,7 +25,8 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut()); 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(); let mut bottom_screen = gfx.bottom_screen.borrow_mut();
@ -37,38 +41,44 @@ fn main() {
let mut image_bytes = IMAGE; 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() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; 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) { if hid.keys_down().contains(KeyPad::A) {
image_bytes = if std::ptr::eq(image_bytes, IMAGE) { image_bytes = if std::ptr::eq(image_bytes, IMAGE) {
&flipped_image[..] &flipped_image[..]
} else { } else {
IMAGE IMAGE
}; };
}
// this copies more than necessary (once per frame) but it's fine... let frame_buffer = bottom_screen.raw_framebuffer();
// We render the newly switched image to the framebuffer.
unsafe { unsafe {
frame_buffer frame_buffer
.ptr .ptr
.copy_from(image_bytes.as_ptr(), image_bytes.len()); .copy_from(image_bytes.as_ptr(), image_bytes.len());
} }
}
// Flush framebuffers. Since we're not using double buffering, // Flush framebuffers. Since we're not using double buffering,
// this will render the pixels immediately // this will render the pixels immediately
bottom_screen.flush_buffers(); bottom_screen.flush_buffers();
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

12
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -9,6 +16,7 @@ fn main() {
let mut console = Console::new(gfx.top_screen.borrow_mut()); let mut console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enable/disable wide screen mode."); println!("Press A to enable/disable wide screen mode.");
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
hid.scan_input(); hid.scan_input();
@ -17,14 +25,18 @@ fn main() {
break; 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) { if hid.keys_down().contains(KeyPad::A) {
drop(console); drop(console);
// Switch the state of the wide-mode.
let wide_mode = gfx.top_screen.borrow().is_wide(); let wide_mode = gfx.top_screen.borrow().is_wide();
gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode); gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode);
console = Console::new(gfx.top_screen.borrow_mut()); console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enable/disable wide screen mode."); println!("Press A to enable/disable wide screen mode.");
println!("\x1b[29;16HPress Start to exit");
} }
gfx.wait_for_vblank(); gfx.wait_for_vblank();

57
ctru-rs/examples/graphics-bitmap.rs

@ -1,57 +0,0 @@
use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Swap};
/// Ferris image taken from <https://rustacean.net> 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();
}
}

11
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::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler(); ctru::use_panic_handler();
// Initialize services
//
// HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator.
// This generator is only active when activating the `PS` service. // This generator is only active when activating the `PS` service.
// This service is automatically initialized. // This service is automatically initialized.
@ -19,6 +25,7 @@ fn main() {
map.remove("A Key!"); map.remove("A Key!");
println!("{map:#?}"); println!("{map:#?}");
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
gfx.wait_for_vblank(); gfx.wait_for_vblank();

11
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -11,14 +15,15 @@ fn main() {
let top_screen = Console::new(gfx.top_screen.borrow_mut()); let top_screen = Console::new(gfx.top_screen.borrow_mut());
// Start a console on the bottom screen. // 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 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(); top_screen.select();
println!("This is the top screen! We have a lot of space up here!"); 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(); bottom_screen.select();
println!("\x1b[14;00HThis is the bottom screen."); println!("\x1b[14;00HThis is the bottom screen.");
println!("There's not as much space down here, but that's okay."); println!("There's not as much space down here, but that's okay.");

20
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; use std::io::BufWriter;
fn main() { 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(); 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 gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT 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()); 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 out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!";
let width = 24; let width = 24;
@ -21,16 +31,18 @@ fn main() {
String::from_utf8_lossy(&writer.into_inner().unwrap()) 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() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame // Scan all the controller inputs.
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; 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(); gfx.wait_for_vblank();
} }
} }

15
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)] #![feature(allocator_api)]
use ctru::linear::LinearAllocator; use ctru::linear::LinearAllocator;
@ -11,11 +17,13 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut()); 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(); let linear_space_before = LinearAllocator::free_space();
// Normal `Box` on the heap // Normal `Box` on the heap.
let heap_box = Box::new(1492); let heap_box = Box::new(1492);
// `Box` living on the linear memory sector // `Box` living on the LINEAR memory.
let linear_box: Box<i32, LinearAllocator> = Box::new_in(2022, LinearAllocator); let linear_box: Box<i32, LinearAllocator> = Box::new_in(2022, LinearAllocator);
println!("This value is from the heap: {heap_box}"); println!("This value is from the heap: {heap_box}");
@ -29,16 +37,13 @@ fn main() {
println!("\x1b[29;16HPress Start to exit"); println!("\x1b[29;16HPress Start to exit");
// Main loop
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

21
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -9,12 +13,16 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
// Setup the Mii Selector configuration.
let mut mii_selector = MiiSelector::new(); 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); mii_selector.set_initial_index(3);
// The first user-made Mii cannot be used.
mii_selector.blacklist_user_mii(0.into()); mii_selector.blacklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!"); 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() { match mii_selector.launch() {
Ok(result) => { Ok(result) => {
println!("Mii type: {:?}", result.mii_type); println!("Mii type: {:?}", result.mii_type);
@ -25,20 +33,19 @@ fn main() {
result.mii_data.mole_details.is_enabled result.mii_data.mole_details.is_enabled
); );
} }
Err(LaunchError::InvalidChecksum) => println!("Corrupt Mii selected"), Err(Error::InvalidChecksum) => println!("Corrupt Mii selected"),
Err(LaunchError::NoMiiSelected) => println!("No Mii selected"), Err(Error::NoMiiSelected) => println!("No Mii selected"),
} }
// Main loop println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

31
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 ctru::prelude::*;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
@ -8,26 +12,35 @@ fn main() {
ctru::use_panic_handler(); ctru::use_panic_handler();
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();
let _console = Console::new(gfx.top_screen.borrow_mut());
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
let apt = Apt::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(); let soc = Soc::new().unwrap();
// Listen on the standard HTTP port (80).
let server = TcpListener::bind("0.0.0.0:80").unwrap(); let server = TcpListener::bind("0.0.0.0:80").unwrap();
server.set_nonblocking(true).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() { 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() { match server.accept() {
Ok((mut stream, socket_addr)) => { Ok((mut stream, socket_addr)) => {
println!("Got connection from {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]; let mut buf = [0u8; 4096];
match stream.read(&mut buf) { match stream.read(&mut buf) {
Ok(_) => { 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\n<html><body>Hello world</body></html>\r\n"; let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";
if let Err(e) = stream.write(response) { if let Err(e) = stream.write(response) {
println!("Error writing http response: {e}"); 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(); stream.shutdown(Shutdown::Both).unwrap();
} }
Err(e) => match e.kind() { Err(e) => match e.kind() {
// If the TCP socket would block execution, just try again.
std::io::ErrorKind::WouldBlock => {} std::io::ErrorKind::WouldBlock => {}
_ => { _ => {
println!("Error accepting connection: {e}"); println!("Error accepting connection: {e}");
@ -60,9 +76,6 @@ fn main() {
}, },
} }
hid.scan_input(); gfx.wait_for_vblank();
if hid.keys_down().contains(KeyPad::START) {
break;
};
} }
} }

9
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. //! to the device that sent the executable.
//! //!
//! For now, `cargo 3ds run` does not support this flag, so to run this example //! 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 mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT 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"); 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) soc.redirect_to_3dslink(true, true)
.expect("unable to redirect stdout/err to 3dslink server"); .expect("unable to redirect stdout/err to 3dslink server");
println!("Hello 3dslink!"); println!("Hello 3dslink!");
eprintln!("Press Start on the device to disconnect and exit."); eprintln!("Press Start on the device to disconnect and exit.");
// Main loop
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

20
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -9,35 +13,35 @@ fn main() {
let _console = Console::new(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
cfg_if::cfg_if! { cfg_if::cfg_if! {
// Run this code if RomFS are wanted and available // Run this code if RomFS are wanted and available.
// This never fails as `ctru-rs` examples inherit all of the `ctru` features, // 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 // but it might if a normal user application wasn't setup correctly.
if #[cfg(all(feature = "romfs", romfs_exists))] { if #[cfg(all(feature = "romfs", romfs_exists))] {
// Mount the romfs volume.
let _romfs = ctru::services::romfs::RomFS::new().unwrap(); 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(); let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap();
println!("Contents of test-file.txt: \n{f}\n"); println!("Contents of test-file.txt: \n{f}\n");
let f = std::fs::read_to_string("romfs:/ファイル.txt").unwrap(); 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"); println!("Contents of [UTF-16 File]: \n{f}\n");
} else { } else {
println!("No RomFS was found, are you sure you included it?") 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() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

25
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -9,21 +13,24 @@ fn main() {
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();
let _console = Console::new(gfx.top_screen.borrow_mut()); 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() { while apt.main_loop() {
gfx.wait_for_vblank();
hid.scan_input(); 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) { if hid.keys_down().contains(KeyPad::A) {
// Prepares a software keyboard with two buttons: One to cancel input and one // 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. // configurations.
let mut keyboard = Swkbd::default(); let mut keyboard = SoftwareKeyboard::default();
// Raise the software keyboard. You can perform different actions depending on which // 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) { match keyboard.get_string(2048) {
Ok((text, Button::Right)) => println!("You entered: {text}"), Ok((text, Button::Right)) => println!("You entered: {text}"),
Ok((_, Button::Left)) => println!("Cancelled"), Ok((_, Button::Left)) => println!("Cancelled"),
@ -32,8 +39,6 @@ fn main() {
} }
} }
if hid.keys_down().contains(KeyPad::START) { gfx.wait_for_vblank();
break;
}
} }
} }

14
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::prelude::*;
use ctru::services::cfgu::Cfgu; use ctru::services::cfgu::Cfgu;
@ -7,23 +12,24 @@ fn main() {
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT 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()); 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[0;0HRegion: {:?}", cfgu.region().unwrap());
println!("\x1b[10;0HLanguage: {:?}", cfgu.language().unwrap()); println!("\x1b[10;0HLanguage: {:?}", cfgu.language().unwrap());
println!("\x1b[20;0HModel: {:?}", cfgu.model().unwrap()); println!("\x1b[20;0HModel: {:?}", cfgu.model().unwrap());
// Main loop println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

2
ctru-rs/examples/thread-basic.rs

@ -33,6 +33,8 @@ fn main() {
println!("Created thread {ix}"); println!("Created thread {ix}");
} }
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
gfx.wait_for_vblank(); gfx.wait_for_vblank();

2
ctru-rs/examples/thread-info.rs

@ -36,7 +36,7 @@ fn main() {
.unwrap(); .unwrap();
println!("sys thread exited"); println!("sys thread exited");
println!("\nPress Start to exit"); println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
hid.scan_input(); hid.scan_input();

2
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() { while apt.main_loop() {
hid.scan_input(); hid.scan_input();

12
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -9,11 +14,9 @@ fn main() {
let _console = Console::new(gfx.top_screen.borrow_mut()); 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() { while apt.main_loop() {
// Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { 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, // 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 cur_time = time::OffsetDateTime::now_utc();
let hours = cur_time.hour(); 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!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}");
println!("{weekday} {month} {day} {year}"); println!("{weekday} {month} {day} {year}");
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

46
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::prelude::*;
use ctru::services::am::Am; use ctru::services::am::Am;
use ctru::services::fs::FsMediaType; use ctru::services::fs::FsMediaType;
@ -8,20 +13,27 @@ fn main() {
let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT 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 top_screen = Console::new(gfx.top_screen.borrow_mut());
let bottom_screen = Console::new(gfx.bottom_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 let sd_count = am
.title_count(FsMediaType::Sd) .title_count(FsMediaType::Sd)
.expect("Failed to get sd title count"); .expect("Failed to get sd title count");
// List of titles installed on the SD card.
let sd_list = am let sd_list = am
.title_list(FsMediaType::Sd) .title_list(FsMediaType::Sd)
.expect("Failed to get sd title list"); .expect("Failed to get sd title list");
// Amount of titles installed on the NAND storage.
let nand_count = am let nand_count = am
.title_count(FsMediaType::Nand) .title_count(FsMediaType::Nand)
.expect("Failed to get nand title count"); .expect("Failed to get nand title count");
// List of titles installed on the NAND storage.
let nand_list = am let nand_list = am
.title_list(FsMediaType::Nand) .title_list(FsMediaType::Nand)
.expect("Failed to get nand title list"); .expect("Failed to get nand title list");
@ -30,9 +42,7 @@ fn main() {
let mut refresh = true; let mut refresh = true;
let mut use_nand = false; let mut use_nand = false;
// Main loop
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::START) { if hid.keys_down().contains(KeyPad::START) {
@ -58,13 +68,15 @@ fn main() {
} }
} }
// Render the title list via a scrollable text UI.
if refresh { if refresh {
let mut selected_title = cur_list.iter().skip(offset).next().unwrap(); 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(); top_screen.select();
print!("\x1b[2J"); 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() { for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() {
if i == 0 { if i == 0 {
selected_title = title; 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(); bottom_screen.select();
println!("\x1b[2J"); bottom_screen.clear();
// Move cursor to top left println!("Press Start to exit");
// Move cursor to top left.
println!("\x1b[1;1"); println!("\x1b[1;1");
match selected_title.title_info() { println!("Size: {} kB", selected_title.size() / 1024);
Ok(info) => { println!("Version: 0x{:x}", selected_title.version());
println!("Size: {} KB", info.size_bytes() / 1024); println!("Product code: \"{}\"", selected_title.product_code());
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!("\x1b[26;0HPress START to exit");
if use_nand { if use_nand {
println!("Press SELECT to choose SD Card"); println!("Press SELECT to choose SD Card");
println!("Current medium: NAND"); println!("Current medium: NAND");
@ -106,7 +111,6 @@ fn main() {
refresh = false; refresh = false;
} }
//Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

18
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::*; use ctru::prelude::*;
fn main() { fn main() {
@ -9,7 +13,7 @@ fn main() {
let console = Console::new(gfx.top_screen.borrow_mut()); 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); let mut old_touch: (u16, u16) = (0, 0);
println!("\x1b[29;16HPress Start to exit"); println!("\x1b[29;16HPress Start to exit");
@ -26,22 +30,22 @@ fn main() {
let touch: (u16, u16) = hid.touch_position(); let touch: (u16, u16) = hid.touch_position();
// We only want to print the position when it's different // 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 { 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. // Special case for when the user lifts the stylus/finger from the screen.
// This is done to avoid some screen tearing. // This is done to avoid some screen tearing.
if touch == (0, 0) { if touch == (0, 0) {
console.clear(); 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"); 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; old_touch = touch;
gfx.wait_for_vblank(); gfx.wait_for_vblank();

248
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 bitflags::bitflags;
use std::ffi::CString; use std::{ffi::CString, fmt};
/// Index of a Mii used to configure some parameters of the Mii Selector /// Index of a Mii on the [`MiiSelector`] interface.
/// Can be either a single index, or _all_ Miis ///
/// See [`MiiSelector::whitelist_user_mii()`] and related functions for more information.
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Index { pub enum Index {
/// Specific Mii index.
///
/// # Notes
///
/// Indexes start at 0.
Index(u32), Index(u32),
/// All Miis.
All, All,
} }
/// The type of a Mii with their respective data /// The type of a Mii.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum MiiType { 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, User,
} }
bitflags! { 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 { pub struct Options: u32 {
/// Show the cancel button /// Show the cancel button.
const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL; const ENABLE_CANCEL = ctru_sys::MIISELECTOR_CANCEL;
/// Make guest Miis selectable /// Make guest Miis available to select.
const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS; const ENABLE_GUESTS = ctru_sys::MIISELECTOR_GUESTS;
/// Show on the top screen /// Show the [`MiiSelector`] window on the top screen.
const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP; const USE_TOP_SCREEN = ctru_sys::MIISELECTOR_TOP;
/// Start on the guest's page /// Start the [`MiiSelector`] on the guests' page. Requires [`Options::ENABLE_GUESTS`].
const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART; const START_WITH_GUESTS = ctru_sys::MIISELECTOR_GUESTSTART;
} }
} }
/// An instance of the Mii Selector /// Configuration structure to setup the Mii Selector applet.
/// #[doc(alias = "MiiSelectorConf")]
/// # 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();
/// ```
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MiiSelector { pub struct MiiSelector {
config: Box<ctru_sys::MiiSelectorConf>, config: Box<ctru_sys::MiiSelectorConf>,
} }
/// Return value from a MiiSelector's launch /// Return value of a successful [`MiiSelector::launch()`].
#[non_exhaustive] #[non_exhaustive]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SelectionResult { pub struct Selection {
pub mii_data: MiiData, /// Data of the selected Mii.
pub mii_data: Mii,
/// Type of the selected Mii.
pub mii_type: MiiType, pub mii_type: MiiType,
} }
/// Error type for the Mii selector /// Error returned by an unsuccessful [`MiiSelector::launch()`].
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum LaunchError { pub enum Error {
/// The selected Mii's data is corrupt in some way /// The selected Mii's data is corrupt.
InvalidChecksum, 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, NoMiiSelected,
} }
impl MiiSelector { impl MiiSelector {
/// Initializes a Mii Selector /// Initialize a new configuration for the Mii Selector applet.
#[doc(alias = "miiSelectorInit")]
pub fn new() -> Self { pub fn new() -> Self {
let mut config = Box::<ctru_sys::MiiSelectorConf>::default(); let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
unsafe { unsafe {
@ -78,9 +90,22 @@ impl MiiSelector {
Self { config } 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
///
/// ```no_run
/// # fn main() {
/// use ctru::applets::mii_selector::MiiSelector;
/// ///
/// This function would panic if the given ``&str`` contains NUL bytes. /// let mut mii_selector = MiiSelector::new();
/// mii_selector.set_title("Select a Mii!");
/// # }
/// ```
#[doc(alias = "miiSelectorSetTitle")]
pub fn set_title(&mut self, text: &str) { pub fn set_title(&mut self, text: &str) {
// This can only fail if the text contains NUL bytes in the string... which seems // This can only fail if the text contains NUL bytes in the string... which seems
// unlikely and is documented // 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) { 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) { pub fn whitelist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
@ -105,7 +165,26 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) } 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) { pub fn blacklist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
@ -115,7 +194,21 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) } 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) { pub fn whitelist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
@ -125,7 +218,21 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) } 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) { pub fn blacklist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
Index::Index(i) => i, Index::Index(i) => i,
@ -135,8 +242,10 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
} }
/// Set where the cursor will be. /// 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 ///
/// 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) { pub fn set_initial_index(&mut self, index: usize) {
// This function is static inline in libctru // This function is static inline in libctru
// https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155
@ -144,19 +253,43 @@ impl MiiSelector {
} }
/// Launch the Mii Selector. /// Launch the Mii Selector.
/// Returns an error when the checksum of the Mii is invalid. ///
pub fn launch(&mut self) -> Result<SelectionResult, LaunchError> { /// 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<dyn Error>> {
/// #
/// 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<Selection, Error> {
let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::default(); let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::default();
unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) } unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) }
if return_val.no_mii_selected != 0 { if return_val.no_mii_selected != 0 {
return Err(LaunchError::NoMiiSelected); return Err(Error::NoMiiSelected);
} }
if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } { if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } {
Ok((*return_val).into()) Ok((*return_val).into())
} else { } else {
Err(LaunchError::InvalidChecksum) Err(Error::InvalidChecksum)
} }
} }
} }
@ -167,12 +300,23 @@ impl Default for MiiSelector {
} }
} }
impl From<ctru_sys::MiiSelectorReturn> 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<ctru_sys::MiiSelectorReturn> for Selection {
fn from(ret: ctru_sys::MiiSelectorReturn) -> Self { fn from(ret: ctru_sys::MiiSelectorReturn) -> Self {
let raw_mii_data = ret.mii; let raw_mii_data = ret.mii;
let mut guest_mii_name = ret.guest_mii_name; let mut guest_mii_name = ret.guest_mii_name;
SelectionResult { Selection {
mii_data: raw_mii_data.into(), mii_data: raw_mii_data.into(),
mii_type: if ret.guest_mii_index != 0xFFFFFFFF { mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
MiiType::Guest { MiiType::Guest {

10
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 mii_selector;
pub mod swkbd; pub mod swkbd;

339
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 bitflags::bitflags;
use ctru_sys::{ use ctru_sys::{
self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState, self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState,
}; };
use libc; use libc;
use std::fmt::Display;
use std::iter::once; use std::iter::once;
use std::str; use std::str;
/// An instance of the software keyboard. /// Configuration structure to setup the Software Keyboard applet.
#[doc(alias = "SwkbdState")]
#[derive(Clone)] #[derive(Clone)]
pub struct Swkbd { pub struct SoftwareKeyboard {
state: Box<SwkbdState>, state: Box<SwkbdState>,
} }
/// 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) /// Can be set with [`SoftwareKeyboard::new()`]
/// Qwerty is a QWERTY-only keyboard. #[doc(alias = "SwkbdType")]
/// 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.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Kind { pub enum Kind {
/// Normal keyboard composed of several pages (QWERTY, accents, symbols, mobile).
Normal = ctru_sys::SWKBD_TYPE_NORMAL, Normal = ctru_sys::SWKBD_TYPE_NORMAL,
/// Only QWERTY keyboard.
Qwerty = ctru_sys::SWKBD_TYPE_QWERTY, Qwerty = ctru_sys::SWKBD_TYPE_QWERTY,
/// Only number pad.
Numpad = ctru_sys::SWKBD_TYPE_NUMPAD, 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, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Button { pub enum Button {
/// Left button. Usually corresponds to "Cancel".
Left = ctru_sys::SWKBD_BUTTON_LEFT, Left = ctru_sys::SWKBD_BUTTON_LEFT,
/// Middle button. Usually corresponds to "I Forgot".
Middle = ctru_sys::SWKBD_BUTTON_MIDDLE, Middle = ctru_sys::SWKBD_BUTTON_MIDDLE,
/// Right button. Usually corresponds to "OK".
Right = ctru_sys::SWKBD_BUTTON_RIGHT, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)] #[repr(i32)]
pub enum Error { 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, OutOfMem = ctru_sys::SWKBD_OUTOFMEM,
/// Home button was pressed while [`SoftwareKeyboard`] was running.
HomePressed = ctru_sys::SWKBD_HOMEPRESSED, HomePressed = ctru_sys::SWKBD_HOMEPRESSED,
/// Reset button was pressed while [`SoftwareKeyboard`] was running.
ResetPressed = ctru_sys::SWKBD_RESETPRESSED, ResetPressed = ctru_sys::SWKBD_RESETPRESSED,
/// Power button was pressed while [`SoftwareKeyboard`] was running.
PowerPressed = ctru_sys::SWKBD_POWERPRESSED, 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, ParentalOk = ctru_sys::SWKBD_PARENTAL_OK,
/// The parental lock PIN was incorrect.
ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL, 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, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum ValidInput { pub enum ValidInput {
/// All inputs are accepted.
Anything = ctru_sys::SWKBD_ANYTHING, Anything = ctru_sys::SWKBD_ANYTHING,
/// Empty inputs are not accepted.
NotEmpty = ctru_sys::SWKBD_NOTEMPTY, 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, 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, FixedLen = ctru_sys::SWKBD_FIXEDLEN,
} }
bitflags! { 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 { 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 PARENTAL_PIN = ctru_sys::SWKBD_PARENTAL;
/// Darken top screen while the [`SoftwareKeyboard`] is active.
const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN; 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 PREDICTIVE_INPUT = ctru_sys::SWKBD_PREDICTIVE_INPUT;
/// Enable multiline input.
const MULTILINE = ctru_sys::SWKBD_MULTILINE; const MULTILINE = ctru_sys::SWKBD_MULTILINE;
/// Enable fixed-width mode.
const FIXED_WIDTH = ctru_sys::SWKBD_FIXED_WIDTH; 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; 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; 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; 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; 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 { 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; const DIGITS = ctru_sys::SWKBD_FILTER_DIGITS;
/// Disallow the usage of the "at" (@) sign.
const AT = ctru_sys::SWKBD_FILTER_AT; const AT = ctru_sys::SWKBD_FILTER_AT;
/// Disallow the usage of the "percent" (%) sign.
const PERCENT = ctru_sys::SWKBD_FILTER_PERCENT; const PERCENT = ctru_sys::SWKBD_FILTER_PERCENT;
/// Disallow the usage of the "backslash" (\) sign.
const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH; const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH;
/// Disallow the use of profanity via Nintendo's profanity filter.
const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY; 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; const CALLBACK = ctru_sys::SWKBD_FILTER_CALLBACK;
} }
} }
impl Swkbd { impl SoftwareKeyboard {
/// Initializes a software keyboard of the specified type and the chosen number of buttons /// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3).
/// (from 1-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 { pub fn new(keyboard_type: Kind, num_buttons: i32) -> Self {
unsafe { unsafe {
let mut state = Box::<SwkbdState>::default(); let mut state = Box::<SwkbdState>::default();
swkbdInit(state.as_mut(), keyboard_type.into(), num_buttons, -1); 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`. /// 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<dyn Error>> {
/// #
/// 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> { 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 // 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 // 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 /// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from
/// this software keyboard. /// this software keyboard.
/// ///
/// # Notes
///
/// If the buffer is too small to contain the entire sequence received from the keyboard, /// 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<dyn Error>> {
/// #
/// 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<Button, Error> { pub fn write_exact(&mut self, buf: &mut [u8]) -> Result<Button, Error> {
unsafe { unsafe {
match swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) { 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) { 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) { pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) {
self.state.valid_input = validation.into(); 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 /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled.
/// `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) { pub fn set_max_digits(&mut self, digits: u16) {
self.state.max_digits = digits; self.state.max_digits = digits;
} }
/// Sets the hint text for this software keyboard (that is, the help text that is displayed /// Set the hint text for this software keyboard.
/// when the textbox is empty) ///
/// 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) { pub fn set_hint_text(&mut self, text: &str) {
unsafe { unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect(); 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` is the `Button` to be configured /// - `button` - the [`Button`] to be configured based on the position.
/// `text` configures the display text for the button /// - `text` - the text displayed in the button.
/// `submit` configures whether pressing the button will accept the keyboard's input or /// - `submit` - whether pressing the button will accept the keyboard's input or discard it.
/// 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) { pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) {
unsafe { unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect(); 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 /// Configure the maximum number of UTF-16 code units that can be entered into the software
/// keyboard. By default the limit is 0xFDE8 code units. /// keyboard. By default the limit is `65000` code units.
///
/// # Notes
/// ///
/// Note that keyboard input is converted from UTF-16 to UTF-8 before being handed to Rust, /// 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 /// 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) { pub fn set_max_text_len(&mut self, len: u16) {
self.state.max_text_len = len; self.state.max_text_len = len;
} }
fn parse_swkbd_error(&self) -> Error { fn parse_swkbd_error(&self) -> Error {
match self.state.result { 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_OUTOFMEM => Error::OutOfMem,
ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed, ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed,
ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed, ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed,
@ -206,11 +431,43 @@ 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 { 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!(Kind, ctru_sys::SwkbdType);
from_impl!(Button, ctru_sys::SwkbdButton); from_impl!(Button, ctru_sys::SwkbdButton);

140
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::cell::RefMut;
use std::default::Default; use std::default::Default;
@ -7,19 +14,63 @@ use crate::services::gfx::Screen;
static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) }; 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> { pub struct Console<'screen> {
context: Box<PrintConsole>, context: Box<PrintConsole>,
_screen: RefMut<'screen, dyn Screen>, _screen: RefMut<'screen, dyn Screen>,
} }
impl<'screen> Console<'screen> { impl<'screen> Console<'screen> {
/// Initialize a console on the chosen screen, overwriting whatever was on the screen /// Initialize a console on the chosen screen.
/// previously (including other consoles). The new console is automatically selected for
/// printing.
/// ///
/// # Notes /// # 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<dyn Error>> {
/// #
/// 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 { pub fn new(screen: RefMut<'screen, dyn Screen>) -> Self {
let mut context = Box::<PrintConsole>::default(); let mut context = Box::<PrintConsole>::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<dyn Error>> {
/// #
/// # 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 { pub fn exists() -> bool {
unsafe { unsafe {
let current_console = ctru_sys::consoleSelect(&mut EMPTY_CONSOLE); 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<dyn Error>> {
/// #
/// # 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) { pub fn select(&self) {
unsafe { unsafe {
consoleSelect(self.context.as_ref() as *const _ as *mut _); 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) { pub fn clear(&self) {
unsafe { consoleClear() } 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 /// 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 /// # 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) { pub unsafe fn set_window(&mut self, x: i32, y: i32, width: i32, height: i32) {
consoleSetWindow(self.context.as_mut(), x, y, width, height); consoleSetWindow(self.context.as_mut(), x, y, width, height);
} }

39
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::borrow::Cow;
use std::error; use std::error;
use std::ffi::CStr; 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}; 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<T> = ::std::result::Result<T, Error>; pub type Result<T> = ::std::result::Result<T, Error>;
/// 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)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
#[repr(transparent)] #[repr(transparent)]
pub struct ResultCode(pub ctru_sys::Result); pub struct ResultCode(pub ctru_sys::Result);
@ -48,13 +73,20 @@ impl<T> FromResidual<Error> for Result<T> {
} }
} }
/// 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] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Raw [`ctru_sys::Result`] codes.
Os(ctru_sys::Result), Os(ctru_sys::Result),
/// Generic [`libc`] errors.
Libc(String), Libc(String),
/// Requested service is already active and cannot be activated again.
ServiceAlreadyActive, ServiceAlreadyActive,
/// `stdout` is already being redirected.
OutputAlreadyRedirected, OutputAlreadyRedirected,
/// The buffer provided by the user to store some data is shorter than required.
BufferTooShort { BufferTooShort {
/// Length of the buffer provided by the user. /// Length of the buffer provided by the user.
provided: usize, provided: usize,
@ -64,8 +96,9 @@ pub enum Error {
} }
impl Error { impl Error {
/// Create an [`Error`] out of the last set value in `errno`. This can be used /// Create an [`Error`] out of the last set value in `errno`.
/// to get a human-readable error string from calls to `libc` functions. ///
/// This can be used to get a human-readable error string from calls to `libc` functions.
pub(crate) fn from_errno() -> Self { pub(crate) fn from_errno() -> Self {
let error_str = unsafe { let error_str = unsafe {
let errno = ctru_sys::errno(); let errno = ctru_sys::errno();

43
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_type = "rlib"]
#![crate_name = "ctru"] #![crate_name = "ctru"]
#![warn(missing_docs)]
#![feature(test)] #![feature(test)]
#![feature(custom_test_frameworks)] #![feature(custom_test_frameworks)]
#![feature(try_trait_v2)] #![feature(try_trait_v2)]
#![feature(allocator_api)] #![feature(allocator_api)]
#![feature(nonnull_slice_from_raw_parts)]
#![test_runner(test_runner::run)] #![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. // Nothing is imported from these crates but their inclusion here assures correct linking of the missing implementations.
extern crate pthread_3ds; extern crate pthread_3ds;
extern crate shim_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] #[no_mangle]
#[cfg(feature = "big-stack")] #[cfg(feature = "big-stack")]
static __stacksize__: usize = 2 * 1024 * 1024; // 2MB 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]. /// 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] the program will just exit. /// In case it fails to find an active [`Console`](console::Console) the program will just exit.
/// ///
/// # Notes /// # 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() { pub fn use_panic_handler() {
#[cfg(not(test))] #[cfg(not(test))]
panic_hook_setup(); 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))] #[cfg(not(test))]
fn panic_hook_setup() { fn panic_hook_setup() {
use crate::services::hid::{Hid, KeyPad}; use crate::services::hid::{Hid, KeyPad};

21
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. //! 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). //! As such, it is used for fast and safe memory sharing between different hardware components (such as the GPU and the DSP processor).
//! //!
//! Resources:<br> //! # Additional Resources
//! <https://github.com/devkitPro/libctru/blob/master/libctru/source/allocator/linear.cpp><br> //!
//! <https://www.3dbrew.org/wiki/Memory_layout> //! - <https://github.com/devkitPro/libctru/blob/master/libctru/source/allocator/linear.cpp>
//! - <https://www.3dbrew.org/wiki/Memory_layout>
use std::alloc::{AllocError, Allocator, Layout}; use std::alloc::{AllocError, Allocator, Layout};
use std::ptr::NonNull; 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, // 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. // 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. /// To use this struct the main crate must activate the `allocator_api` unstable feature.
#[derive(Copy, Clone, Default, Debug)] #[derive(Copy, Clone, Default, Debug)]
pub struct LinearAllocator; pub struct LinearAllocator;
impl 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 { pub fn free_space() -> u32 {
unsafe { ctru_sys::linearSpaceFree() } unsafe { ctru_sys::linearSpaceFree() }
} }
} }
unsafe impl Allocator for LinearAllocator { unsafe impl Allocator for LinearAllocator {
#[doc(alias = "linearAlloc", alias = "linearMemAlign")]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let pointer = unsafe { ctru_sys::linearMemAlign(layout.size(), layout.align()) }; let pointer = unsafe { ctru_sys::linearMemAlign(layout.size(), layout.align()) };
@ -36,6 +40,7 @@ unsafe impl Allocator for LinearAllocator {
.ok_or(AllocError) .ok_or(AllocError)
} }
#[doc(alias = "linearFree")]
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) { unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
ctru_sys::linearFree(ptr.as_ptr().cast()); ctru_sys::linearFree(ptr.as_ptr().cast());
} }

176
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 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)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RegionLock { pub enum RegionLock {
/// No region-lock.
None, None,
/// Japan region-lock.
Japan, Japan,
/// USA region-lock.
USA, USA,
/// Europe region-lock.
Europe, Europe,
} }
/// Represent the charset of the console /// Charset of the Mii.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Charset { pub enum Charset {
/// Japan-USA-Europe unified charset.
JapanUSAEurope, JapanUSAEurope,
/// China charset.
China, China,
/// Korea charset.
Korea, Korea,
/// Taiwan charset.
Taiwan, Taiwan,
} }
/// Represents the options of the Mii /// Generic options of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct MiiDataOptions { pub struct Options {
/// Whether it is allowed to copy the Mii.
pub is_copying_allowed: bool, pub is_copying_allowed: bool,
/// Whether the profanity flag is active.
pub is_profanity_flag_enabled: bool, pub is_profanity_flag_enabled: bool,
/// The Mii's active region-lock.
pub region_lock: RegionLock, pub region_lock: RegionLock,
/// The Mii's used charset.
pub charset: 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)] #[derive(Copy, Clone, Debug)]
pub struct SelectorPosition { pub struct SelectorPosition {
/// Index of the page where the Mii is found.
pub page_index: u8, pub page_index: u8,
/// Index of the slot (relative to the page) where the Mii is found.
pub slot_index: u8, pub slot_index: u8,
} }
/// Represents the kind of origin console /// Console model from which the Mii originated.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OriginConsole { pub enum OriginConsole {
/// Nintendo Wii.
Wii, Wii,
/// Nintendo DSi.
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, N3DS,
/// Nintendo Wii U/Switch.
WiiUSwitch, WiiUSwitch,
} }
/// Represents the identity of the origin console /// Identity of the origin console.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct ConsoleIdentity { pub struct ConsoleIdentity {
/// From which console the Mii originated from.
pub origin_console: OriginConsole, pub origin_console: OriginConsole,
} }
/// Represents the sex of the Mii /// Sex of the Mii.
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiSex { pub enum Sex {
/// Male sex.
Male, Male,
/// Female sex.
Female, Female,
} }
/// Represents the details of the Mii /// Generic details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Details { pub struct Details {
pub sex: MiiSex, /// Sex of the Mii.
pub sex: Sex,
/// Birthday month.
pub birthday_month: u8, pub birthday_month: u8,
/// Birthday day.
pub birthday_day: u8, pub birthday_day: u8,
/// Color of the Mii's shirt.
pub shirt_color: u8, pub shirt_color: u8,
/// Whether the Mii is a favorite.
pub is_favorite: bool, 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)] #[derive(Copy, Clone, Debug)]
pub struct FaceStyle { pub struct FaceStyle {
pub is_sharing_enabled: bool, /// Face shape.
pub shape: u8, pub shape: u8,
/// Skin color.
pub skin_color: u8, pub skin_color: u8,
} }
/// Represents the face details of the Mii /// Face details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct FaceDetails { pub struct FaceDetails {
/// Face style.
pub style: FaceStyle, pub style: FaceStyle,
/// Wrinkles.
pub wrinkles: u8, pub wrinkles: u8,
/// Makeup.
pub makeup: u8, pub makeup: u8,
} }
/// Represents the hair details of the Mii /// Hair details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct HairDetails { pub struct HairDetails {
/// Hair style.
pub style: u8, pub style: u8,
/// Hair color.
pub color: u8, pub color: u8,
/// Whether the Mii's hair is flipped.
pub is_flipped: bool, pub is_flipped: bool,
} }
/// Represents the eye details of the Mii /// Eye details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct EyeDetails { pub struct EyeDetails {
/// Eye style.
pub style: u8, pub style: u8,
/// Eye color.
pub color: u8, pub color: u8,
/// Eye scale.
pub scale: u8, pub scale: u8,
/// Eye scale (y-axis).
pub y_scale: u8, pub y_scale: u8,
/// Eye rotation.
pub rotation: u8, pub rotation: u8,
/// Spacing between the eyes /// Spacing between the eyes.
pub x_spacing: u8, pub x_spacing: u8,
/// Eye height.
pub y_position: u8, pub y_position: u8,
} }
/// Represents the eyebrow details of the Mii /// Eyebrow details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct EyebrowDetails { pub struct EyebrowDetails {
/// Eyebrow style.
pub style: u8, pub style: u8,
/// Eyebrow color.
pub color: u8, pub color: u8,
/// Eyebrow scale.
pub scale: u8, pub scale: u8,
/// Eyebrow scale (y-axis).
pub y_scale: u8, pub y_scale: u8,
/// Eyebrow rotation.
pub rotation: u8, pub rotation: u8,
/// Spacing between the eyebrows /// Spacing between the eyebrows
pub x_spacing: u8, pub x_spacing: u8,
/// Eyebrow height.
pub y_position: u8, pub y_position: u8,
} }
/// Represents the details of the nose /// Nose details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct NoseDetails { pub struct NoseDetails {
/// Nose style.
pub style: u8, pub style: u8,
/// Nose scale.
pub scale: u8, pub scale: u8,
/// Nose height.
pub y_position: u8, pub y_position: u8,
} }
/// Represents the details of the mouth /// Mouth details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct MouthDetails { pub struct MouthDetails {
/// Mouth style.
pub style: u8, pub style: u8,
/// Mouth color.
pub color: u8, pub color: u8,
/// Mouth scale.
pub scale: u8, pub scale: u8,
/// Mouth scale (y-axis).
pub y_scale: u8, 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)] #[derive(Copy, Clone, Debug)]
pub struct MustacheDetails { pub struct MustacheDetails {
pub mouth_y_position: u8, /// Mustache style.
pub mustache_style: u8, pub mustache_style: u8,
} }
/// Represents the details of the beard /// Beard details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct BeardDetails { pub struct BeardDetails {
/// Beard style
pub style: u8, pub style: u8,
/// Beard color.
pub color: u8, pub color: u8,
/// Beard scale.
pub scale: u8, pub scale: u8,
/// Beard height.
pub y_position: u8, pub y_position: u8,
} }
/// Represents the details of the glass /// Glasses details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct GlassDetails { pub struct GlassesDetails {
/// Glasses style.
pub style: u8, pub style: u8,
/// Glasses color.
pub color: u8, pub color: u8,
/// Glasses scale.
pub scale: u8, pub scale: u8,
/// Glasses height.
pub y_position: u8, pub y_position: u8,
} }
/// Represents the details of the mole /// Mole details of the Mii.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct MoleDetails { pub struct MoleDetails {
/// Whether the Mii has a mole.
pub is_enabled: bool, pub is_enabled: bool,
/// Mole scale.
pub scale: u8, pub scale: u8,
/// Mole position (x-axis).
pub x_position: u8, pub x_position: u8,
/// Mole position (y-axis).
pub y_position: u8, 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).
/// <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)] #[derive(Clone, Debug)]
pub struct MiiData { pub struct Mii {
pub options: MiiDataOptions, /// Mii options.
pub options: Options,
/// Position taken by the Mii on the Mii Selector screen.
pub selector_position: SelectorPosition, pub selector_position: SelectorPosition,
/// Console the Mii was created on.
pub console_identity: ConsoleIdentity, pub console_identity: ConsoleIdentity,
/// Unique system ID, not dependant on the MAC address /// Unique system ID, not dependant on the MAC address
pub system_id: [u8; 8], pub system_id: [u8; 8],
/// Console's MAC address.
pub mac_address: [u8; 6], pub mac_address: [u8; 6],
/// General information about the Mii.
pub details: Details, pub details: Details,
/// Mii name.
pub name: String, pub name: String,
/// Mii height.
pub height: u8, pub height: u8,
/// Mii width.
pub width: u8, pub width: u8,
/// Face details.
pub face_details: FaceDetails, pub face_details: FaceDetails,
/// Hair details.
pub hair_details: HairDetails, pub hair_details: HairDetails,
/// Eyes details.
pub eye_details: EyeDetails, pub eye_details: EyeDetails,
/// Eyebrow details.
pub eyebrow_details: EyebrowDetails, pub eyebrow_details: EyebrowDetails,
/// Nose details.
pub nose_details: NoseDetails, pub nose_details: NoseDetails,
/// Mouth details.
pub mouth_details: MouthDetails, pub mouth_details: MouthDetails,
/// Mustache details.
pub mustache_details: MustacheDetails, pub mustache_details: MustacheDetails,
/// Beard details.
pub beard_details: BeardDetails, pub beard_details: BeardDetails,
pub glass_details: GlassDetails, /// Glasses details.
pub glass_details: GlassesDetails,
/// Mole details.
pub mole_details: MoleDetails, pub mole_details: MoleDetails,
/// Name of the Mii's original author.
pub author_name: String, pub author_name: String,
} }
impl From<ctru_sys::MiiData> for MiiData { impl From<ctru_sys::MiiData> for Mii {
fn from(mii_data: ctru_sys::MiiData) -> Self { fn from(mii_data: ctru_sys::MiiData) -> Self {
let raw_mii_data = mii_data._bindgen_opaque_blob; let raw_mii_data = mii_data._bindgen_opaque_blob;
// Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii // Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii
@ -272,7 +360,7 @@ impl From<ctru_sys::MiiData> for MiiData {
let name = utf16_byte_pairs_to_string(raw_utf16_name); let name = utf16_byte_pairs_to_string(raw_utf16_name);
let author_name = utf16_byte_pairs_to_string(raw_utf16_author); let author_name = utf16_byte_pairs_to_string(raw_utf16_author);
let options = MiiDataOptions { let options = Options {
is_copying_allowed: raw_options[0], is_copying_allowed: raw_options[0],
is_profanity_flag_enabled: raw_options[1], is_profanity_flag_enabled: raw_options[1],
region_lock: { region_lock: {
@ -312,19 +400,19 @@ impl From<ctru_sys::MiiData> for MiiData {
let details = Details { let details = Details {
sex: { sex: {
match raw_details[0] { match raw_details[0] {
true => MiiSex::Female, true => Sex::Female,
false => MiiSex::Male, false => Sex::Male,
} }
}, },
birthday_month: partial_u8_bits_to_u8(&raw_details[1..=4]), birthday_month: partial_u8_bits_to_u8(&raw_details[1..=4]),
birthday_day: partial_u8_bits_to_u8(&raw_details[5..=9]), birthday_day: partial_u8_bits_to_u8(&raw_details[5..=9]),
shirt_color: partial_u8_bits_to_u8(&raw_details[10..=13]), shirt_color: partial_u8_bits_to_u8(&raw_details[10..=13]),
is_favorite: raw_details[14], is_favorite: raw_details[14],
is_sharing_enabled: !raw_face_style[0],
}; };
let face_details = FaceDetails { let face_details = FaceDetails {
style: FaceStyle { style: FaceStyle {
is_sharing_enabled: !raw_face_style[0],
shape: partial_u8_bits_to_u8(&raw_face_style[1..=4]), shape: partial_u8_bits_to_u8(&raw_face_style[1..=4]),
skin_color: partial_u8_bits_to_u8(&raw_face_style[5..=7]), skin_color: partial_u8_bits_to_u8(&raw_face_style[5..=7]),
}, },
@ -371,10 +459,10 @@ impl From<ctru_sys::MiiData> for MiiData {
color: partial_u8_bits_to_u8(&raw_mouth_details[6..=8]), color: partial_u8_bits_to_u8(&raw_mouth_details[6..=8]),
scale: partial_u8_bits_to_u8(&raw_mouth_details[9..=12]), scale: partial_u8_bits_to_u8(&raw_mouth_details[9..=12]),
y_scale: partial_u8_bits_to_u8(&raw_mouth_details[13..=15]), 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 { 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]), mustache_style: partial_u8_bits_to_u8(&raw_mustache_details[5..=7]),
}; };
@ -385,7 +473,7 @@ impl From<ctru_sys::MiiData> for MiiData {
y_position: partial_u8_bits_to_u8(&raw_beard_details[10..=14]), 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]), style: partial_u8_bits_to_u8(&raw_glass_details[0..=3]),
color: partial_u8_bits_to_u8(&raw_glass_details[4..=6]), color: partial_u8_bits_to_u8(&raw_glass_details[4..=6]),
scale: partial_u8_bits_to_u8(&raw_glass_details[7..=10]), scale: partial_u8_bits_to_u8(&raw_glass_details[7..=10]),
@ -399,7 +487,7 @@ impl From<ctru_sys::MiiData> for MiiData {
y_position: partial_u8_bits_to_u8(&raw_mole_details[10..=14]), y_position: partial_u8_bits_to_u8(&raw_mole_details[10..=14]),
}; };
MiiData { Mii {
options, options,
selector_position, selector_position,
console_identity, console_identity,

11
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::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,
};

149
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::error::ResultCode;
use crate::services::fs::FsMediaType; use crate::services::fs::FsMediaType;
use std::marker::PhantomData; 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> { pub struct Title<'a> {
id: u64, id: u64,
mediatype: FsMediaType, mediatype: FsMediaType,
size: u64,
version: u16,
_am: PhantomData<&'a Am>, _am: PhantomData<&'a Am>,
} }
impl<'a> Title<'a> { impl<'a> Title<'a> {
/// Returns this title's ID.
pub fn id(&self) -> u64 { pub fn id(&self) -> u64 {
self.id self.id
} }
pub fn product_code(&self) -> crate::Result<String> { /// Returns this title's unique product code.
#[doc(alias = "AM_GetTitleProductCode")]
pub fn product_code(&self) -> String {
let mut buf: [u8; 16] = [0; 16]; let mut buf: [u8; 16] = [0; 16];
// This operation is safe as long as the title was correctly obtained via [`Am::title_list()`].
unsafe { unsafe {
ResultCode(ctru_sys::AM_GetTitleProductCode( let _ =
self.mediatype.into(), ctru_sys::AM_GetTitleProductCode(self.mediatype.into(), self.id, buf.as_mut_ptr());
self.id,
buf.as_mut_ptr(),
))?;
}
Ok(String::from_utf8_lossy(&buf).to_string())
} }
pub fn title_info(&self) -> crate::Result<TitleInfo> { String::from_utf8_lossy(&buf).to_string()
let mut info = MaybeUninit::zeroed(); }
unsafe {
ResultCode(ctru_sys::AM_GetTitleInfo(
self.mediatype.into(),
1,
&mut self.id.clone(),
info.as_mut_ptr() as _,
))?;
Ok(info.assume_init()) /// Returns the size of this title in bytes.
pub fn size(&self) -> u64 {
self.size
} }
/// Returns the installed version of this title.
pub fn version(&self) -> u16 {
self.version
} }
} }
/// Handle to the Application Manager service.
pub struct Am(()); pub struct Am(());
impl Am { impl Am {
/// Initialize a new service handle.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::am::Am;
///
/// let app_manager = Am::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "amInit")]
pub fn new() -> crate::Result<Am> { pub fn new() -> crate::Result<Am> {
unsafe { unsafe {
ResultCode(ctru_sys::amInit())?; 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<dyn Error>> {
/// #
/// 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<u32> { pub fn title_count(&self, mediatype: FsMediaType) -> crate::Result<u32> {
unsafe { unsafe {
let mut count = 0; 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<dyn Error>> {
/// #
/// 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<Vec<Title>> { pub fn title_list(&self, mediatype: FsMediaType) -> crate::Result<Vec<Title>> {
let count = self.title_count(mediatype)?; let count = self.title_count(mediatype)?;
let mut buf = vec![0; count as usize]; let mut buf = vec![0; count as usize];
let mut read_amount = 0; let mut read_amount = 0;
unsafe { unsafe {
ResultCode(ctru_sys::AM_GetTitleList( ResultCode(ctru_sys::AM_GetTitleList(
&mut read_amount, &mut read_amount,
@ -89,11 +143,27 @@ impl Am {
buf.as_mut_ptr(), buf.as_mut_ptr(),
))?; ))?;
} }
Ok(buf
let mut info: Vec<ctru_sys::AM_TitleEntry> = 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() .into_iter()
.map(|id| Title { .map(|title| Title {
id, id: title.titleID,
mediatype, mediatype,
size: title.size,
version: title.version,
_am: PhantomData, _am: PhantomData,
}) })
.collect()) .collect())
@ -101,6 +171,7 @@ impl Am {
} }
impl Drop for Am { impl Drop for Am {
#[doc(alias = "amExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ctru_sys::amExit() }; unsafe { ctru_sys::amExit() };
} }

61
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; use crate::error::ResultCode;
/// Handle to the Applet service.
pub struct Apt(()); pub struct Apt(());
impl Apt { impl Apt {
/// Initialize a new service handle.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::apt::Apt;
///
/// let apt = Apt::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "aptInit")]
pub fn new() -> crate::Result<Apt> { pub fn new() -> crate::Result<Apt> {
unsafe { unsafe {
ResultCode(ctru_sys::aptInit())?; 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<dyn Error>> {
///
/// 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 { pub fn main_loop(&self) -> bool {
unsafe { ctru_sys::aptMainLoop() } 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<()> { pub fn set_app_cpu_time_limit(&mut self, percent: u32) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?; ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?;
@ -23,6 +83,7 @@ impl Apt {
} }
impl Drop for Apt { impl Drop for Apt {
#[doc(alias = "aptExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ctru_sys::aptExit() }; unsafe { ctru_sys::aptExit() };
} }

452
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 //! 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 or used in other ways. //! 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::error::{Error, ResultCode};
use crate::services::gspgpu::FramebufferFormat; use crate::services::gspgpu::FramebufferFormat;
use ctru_sys::Handle; use ctru_sys::Handle;
use std::time::Duration; use std::time::Duration;
/// A reference-counted handle to the CAM service and the usable cameras. /// Handle to the Camera service.
/// The service is closed when all instances of this struct fall out of scope.
///
/// This service requires no special permissions to use.
#[non_exhaustive] #[non_exhaustive]
pub struct Cam { pub struct Cam {
/// Inside-facing camera.
pub inner_cam: InwardCam, pub inner_cam: InwardCam,
/// Outside-facing right camera.
pub outer_right_cam: OutwardRightCam, pub outer_right_cam: OutwardRightCam,
/// Outside-facing left camera.
pub outer_left_cam: OutwardLeftCam, pub outer_left_cam: OutwardLeftCam,
/// Both outside-facing cameras (mainly used for 3D photos).
pub both_outer_cams: BothOutwardCam, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum FlipMode { pub enum FlipMode {
/// No flip.
None = ctru_sys::FLIP_NONE, None = ctru_sys::FLIP_NONE,
/// Horizontal flip.
Horizontal = ctru_sys::FLIP_HORIZONTAL, Horizontal = ctru_sys::FLIP_HORIZONTAL,
/// Vertical flip.
Vertical = ctru_sys::FLIP_VERTICAL, Vertical = ctru_sys::FLIP_VERTICAL,
/// Both vertical and horizontal flip.
Reverse = ctru_sys::FLIP_REVERSE, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum ViewSize { 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, 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, BottomLCD = ctru_sys::SIZE_CTR_BOTTOM_LCD,
/// VGA display size. (640×480)
Vga = ctru_sys::SIZE_VGA, Vga = ctru_sys::SIZE_VGA,
/// QQVGA display size. (160×120)
QQVga = ctru_sys::SIZE_QQVGA, QQVga = ctru_sys::SIZE_QQVGA,
/// CIF display size. (352 × 288)
Cif = ctru_sys::SIZE_CIF, Cif = ctru_sys::SIZE_CIF,
/// QCIF display size. (176 × 144)
QCif = ctru_sys::SIZE_QCIF, QCif = ctru_sys::SIZE_QCIF,
/// Nintendo DS Screen /// Nintendo DS Screen size. (256 × 192)
DS = ctru_sys::SIZE_DS_LCD, DS = ctru_sys::SIZE_DS_LCD,
/// Nintendo DS Screen x4 /// Nintendo DS Screen size x4. (512 × 384)
DSX4 = ctru_sys::SIZE_DS_LCDx4, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum FrameRate { pub enum FrameRate {
/// 15 FPS.
Fps15 = ctru_sys::FRAME_RATE_15, Fps15 = ctru_sys::FRAME_RATE_15,
/// 15 to 5 FPS.
Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5, Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5,
/// 15 to 2 FPS.
Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2, Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2,
/// 10 FPS.
Fps10 = ctru_sys::FRAME_RATE_10, Fps10 = ctru_sys::FRAME_RATE_10,
/// 8.5 FPS.
Fps8_5 = ctru_sys::FRAME_RATE_8_5, Fps8_5 = ctru_sys::FRAME_RATE_8_5,
/// 5 FPS.
Fps5 = ctru_sys::FRAME_RATE_5, Fps5 = ctru_sys::FRAME_RATE_5,
/// 20 FPS.
Fps20 = ctru_sys::FRAME_RATE_20, Fps20 = ctru_sys::FRAME_RATE_20,
/// 20 to 5 FPS.
Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5, Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5,
/// 30 FPS.
Fps30 = ctru_sys::FRAME_RATE_30, Fps30 = ctru_sys::FRAME_RATE_30,
/// 30 to 5 FPS.
Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5, Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5,
/// 15 to 10 FPS.
Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10, Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10,
/// 20 to 10 FPS.
Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10, Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10,
/// 30 to 10 FPS.
Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10, Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10,
} }
/// Flag to pass to [Camera::set_white_balance] or /// White balance settings.
/// [Camera::set_white_balance_without_base_up] ///
/// 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum WhiteBalance { pub enum WhiteBalance {
/// Normal /// Automatic white balance.
Auto = ctru_sys::WHITE_BALANCE_AUTO, Auto = ctru_sys::WHITE_BALANCE_AUTO,
/// Tungsten /// Tungsten.
Temp3200K = ctru_sys::WHITE_BALANCE_3200K, Temp3200K = ctru_sys::WHITE_BALANCE_3200K,
/// Fluorescent Light /// Fluorescent Light.
Temp4150K = ctru_sys::WHITE_BALANCE_4150K, Temp4150K = ctru_sys::WHITE_BALANCE_4150K,
/// Daylight /// Daylight.
Temp5200K = ctru_sys::WHITE_BALANCE_5200K, Temp5200K = ctru_sys::WHITE_BALANCE_5200K,
/// Cloudy/Horizon /// Cloudy/Horizon.
Temp6000K = ctru_sys::WHITE_BALANCE_6000K, Temp6000K = ctru_sys::WHITE_BALANCE_6000K,
///Shade /// Shade.
Temp7000K = ctru_sys::WHITE_BALANCE_7000K, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum PhotoMode { pub enum PhotoMode {
/// Normal mode.
Normal = ctru_sys::PHOTO_MODE_NORMAL, Normal = ctru_sys::PHOTO_MODE_NORMAL,
/// Portrait mode.
Portrait = ctru_sys::PHOTO_MODE_PORTRAIT, Portrait = ctru_sys::PHOTO_MODE_PORTRAIT,
/// Landscape mode.
Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE, Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE,
/// NightView mode.
NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW, NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW,
/// Letter mode.
Letter = ctru_sys::PHOTO_MODE_LETTER, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Effect { pub enum Effect {
/// No effects.
None = ctru_sys::EFFECT_NONE, None = ctru_sys::EFFECT_NONE,
/// Mono effect.
Mono = ctru_sys::EFFECT_MONO, Mono = ctru_sys::EFFECT_MONO,
/// Sepia effect.
Sepia = ctru_sys::EFFECT_SEPIA, Sepia = ctru_sys::EFFECT_SEPIA,
/// Negative effect.
Negative = ctru_sys::EFFECT_NEGATIVE, Negative = ctru_sys::EFFECT_NEGATIVE,
/// Negative film effect.
Negafilm = ctru_sys::EFFECT_NEGAFILM, Negafilm = ctru_sys::EFFECT_NEGAFILM,
/// Sepia effect.
///
/// The difference between this and [`Sepia`](Effect::Sepia) is unknown.
Sepia01 = ctru_sys::EFFECT_SEPIA01, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Contrast { pub enum Contrast {
/// OFF /// Low contrast.
Low = ctru_sys::CONTRAST_LOW, Low = ctru_sys::CONTRAST_LOW,
/// Brightness ratio: 70 /// Brightness ratio: 70.
Normal = ctru_sys::CONTRAST_NORMAL, Normal = ctru_sys::CONTRAST_NORMAL,
/// Brightness ratio: 90 /// Brightness ratio: 90.
High = ctru_sys::CONTRAST_HIGH, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum LensCorrection { pub enum LensCorrection {
/// No lens correction.
Off = ctru_sys::LENS_CORRECTION_DARK, Off = ctru_sys::LENS_CORRECTION_DARK,
/// Normal lens correction.
Normal = ctru_sys::LENS_CORRECTION_NORMAL, Normal = ctru_sys::LENS_CORRECTION_NORMAL,
/// Bright lens correction.
Bright = ctru_sys::LENS_CORRECTION_BRIGHT, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum OutputFormat { pub enum OutputFormat {
/// YUV422 output format. 16 bits per pixel.
Yuv422 = ctru_sys::OUTPUT_YUV_422, Yuv422 = ctru_sys::OUTPUT_YUV_422,
/// RGB565 output format. 16 bits per pixel.
Rgb565 = ctru_sys::OUTPUT_RGB_565, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum ShutterSound { pub enum ShutterSound {
/// Photo shutter sound.
Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL, Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL,
/// Shutter sound to begin a movie recording.
Movie = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE, Movie = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE,
/// Shutter sound to finish a movie recording.
MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END, MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END,
} }
impl TryFrom<FramebufferFormat> for OutputFormat { /// Parameters to handle image trimming.
type Error = (); ///
/// See [`Camera::set_trimming_params()`] to learn how to use this.
fn try_from(value: FramebufferFormat) -> Result<Self, Self::Error> {
match value {
FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565),
_ => Err(()),
}
}
}
impl TryFrom<OutputFormat> for FramebufferFormat {
type Error = ();
fn try_from(value: OutputFormat) -> Result<Self, Self::Error> {
match value {
OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565),
_ => Err(()),
}
}
}
/// Struct containing coordinates passed to [Camera::set_trimming_params].
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrimmingParams { pub struct TrimmingParams {
x_start: i16, x_start: i16,
@ -178,10 +236,12 @@ pub struct TrimmingParams {
} }
impl 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. /// 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 { 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); assert!(x_start <= x_end && y_start <= y_end);
Self { 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)] #[derive(Default, Clone, Copy, Debug)]
pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibrationData); 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)] #[derive(Default, Clone, Copy, Debug)]
pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); 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] #[non_exhaustive]
pub struct InwardCam; 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 /// Right-side outward camera representation.
/// away from the user
#[non_exhaustive] #[non_exhaustive]
pub struct OutwardRightCam; 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 /// Left-side outward camera representation.
/// away from the user
#[non_exhaustive] #[non_exhaustive]
pub struct OutwardLeftCam; 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] #[non_exhaustive]
pub struct BothOutwardCam; pub struct BothOutwardCam;
impl BothOutwardCam { impl BothOutwardCam {
/// Sets whether to enable or disable synchronization /// Set whether to enable or disable brightness synchronization between the two cameras.
/// of brightness for both left and right cameras #[doc(alias = "CAMU_SetBrightnessSynchronization")]
pub fn set_brightness_synchronization( pub fn set_brightness_synchronization(
&mut self, &mut self,
brightness_synchronization: bool, 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 { 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_; 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_ { fn port_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::PORT_CAM1 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<dyn Error>> {
/// #
/// 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<bool> { fn is_busy(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut is_busy = false; 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 /// 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<dyn Error>> {
/// #
/// 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<u32> { fn transfer_byte_count(&self) -> crate::Result<u32> {
unsafe { unsafe {
let mut transfer_bytes = 0; 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 /// Set whether or not the camera should trim the image.
/// [Camera::set_trimming_params] ///
/// [`TrimmingParams`] can be set via [`Camera::set_trimming_params`].
#[doc(alias = "CAMU_SetTrimming")]
fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> { fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), enabled))?; 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<bool> { fn is_trimming_enabled(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut trimming = false; 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<()> { fn set_trimming_params(&mut self, params: TrimmingParams) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetTrimmingParams( 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<TrimmingParams> { fn trimming_params(&self) -> crate::Result<TrimmingParams> {
unsafe { unsafe {
let mut x_start = 0; 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 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. /// 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( fn set_trimming_params_center(
&mut self, &mut self,
trim_width: i16, 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<()> { fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetExposure(self.camera_as_raw(), exposure))?; 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<()> { fn set_white_balance(&mut self, white_balance: WhiteBalance) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetWhiteBalance( 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 /// Set the white balance of the camera.
// TODO: Explain base up // TODO: Explain what "without base up" means.
#[doc(alias = "CAMU_SetWhiteBalanceWithoutBaseUp")]
fn set_white_balance_without_base_up( fn set_white_balance_without_base_up(
&mut self, &mut self,
white_balance: WhiteBalance, 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<()> { fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetSharpness(self.camera_as_raw(), sharpness))?; 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<()> { fn set_auto_exposure(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetAutoExposure( 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<bool> { fn is_auto_exposure_enabled(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut enabled = false; 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<()> { fn set_auto_white_balance(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetAutoWhiteBalance( 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<bool> { fn is_auto_white_balance_enabled(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut enabled = false; 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<()> { fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_FlipImage( 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 /// # Errors
/// ///
@ -481,10 +606,12 @@ pub trait Camera {
/// coordinates of the second crop point. /// coordinates of the second crop point.
/// ///
/// # Arguments /// # Arguments
///
/// * `width` - Width of the image /// * `width` - Width of the image
/// * `height` - height of the image /// * `height` - height of the image
/// * `crop_0` - The first crop point in which the image will be trimmed /// * `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( fn set_detail_size(
&mut self, &mut self,
width: i16, 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<()> { fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetSize( 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<()> { fn set_frame_rate(&mut self, frame_rate: FrameRate) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetFrameRate( 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<()> { fn set_photo_mode(&mut self, photo_mode: PhotoMode) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetPhotoMode( 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<()> { fn set_effect(&mut self, effect: Effect) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetEffect( 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<()> { fn set_contrast(&mut self, contrast: Contrast) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetContrast( 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<()> { fn set_lens_correction(&mut self, lens_correction: LensCorrection) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetLensCorrection( 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<()> { fn set_output_format(&mut self, format: OutputFormat) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetOutputFormat( 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 /// # Arguments
/// ///
@ -597,6 +734,7 @@ pub trait Camera {
/// * `y` - Starting y coordinate of the window /// * `y` - Starting y coordinate of the window
/// * `width` - Width of the window /// * `width` - Width of the window
/// * `height` - Height of the window /// * `height` - Height of the window
#[doc(alias = "CAMU_SetAutoExposureWindow")]
fn set_auto_exposure_window( fn set_auto_exposure_window(
&mut self, &mut self,
x: i16, 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 /// # Arguments
/// ///
@ -624,6 +762,11 @@ pub trait Camera {
/// * `y` - Starting y coordinate of the window /// * `y` - Starting y coordinate of the window
/// * `width` - Width of the window /// * `width` - Width of the window
/// * `height` - Height 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( fn set_auto_white_balance_window(
&mut self, &mut self,
x: i16, 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<()> { fn set_noise_filter(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetNoiseFilter(self.camera_as_raw(), enabled))?; 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 /// Set the [`ImageQualityCalibrationData`] for the camera.
/// [ImageQualityCalibrationData] argument #[doc(alias = "CAMU_SetImageQualityCalibrationData")]
fn set_image_quality_calibration_data( fn set_image_quality_calibration_data(
&mut self, &mut self,
data: ImageQualityCalibrationData, 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<ImageQualityCalibrationData> { fn image_quality_calibration_data(&self) -> crate::Result<ImageQualityCalibrationData> {
unsafe { unsafe {
let mut data = ImageQualityCalibrationData::default(); 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 // TODO: Explain sleep camera
#[doc(alias = "CAMU_SetSleepCamera")]
fn set_sleep_camera(&mut self) -> crate::Result<()> { fn set_sleep_camera(&mut self) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetSleepCamera(self.camera_as_raw()))?; 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 /// # 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 /// # Arguments
/// ///
/// * `width` - Width of the desired image /// * `width` - Width of the desired image
/// * `height` - Height of the desired image /// * `height` - Height of the desired image
/// * `timeout` - Duration to wait for the image /// * `timeout` - Duration to wait for the image
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # use std::time::Duration;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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( fn take_picture(
&mut self, &mut self,
buffer: &mut [u8], buffer: &mut [u8],
@ -764,13 +941,28 @@ pub trait Camera {
} }
impl Cam { impl Cam {
/// Initializes the CAM service. /// Initialize a new service handle.
/// ///
/// # Errors /// # Errors
/// ///
/// This function will return an error if the service was unable to be initialized. /// 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 /// Since this service requires no special or elevated permissions, errors are
/// rare in practice. /// rare in practice.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::cam::Cam;
///
/// let cam = Cam::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "camInit")]
pub fn new() -> crate::Result<Cam> { pub fn new() -> crate::Result<Cam> {
unsafe { unsafe {
ResultCode(ctru_sys::camInit())?; 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<dyn Error>> {
/// #
/// 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<()> { pub fn play_shutter_sound(&self, sound: ShutterSound) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?; ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?;
@ -793,11 +1008,34 @@ impl Cam {
} }
impl Drop for Cam { impl Drop for Cam {
#[doc(alias = "camExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ctru_sys::camExit() }; unsafe { ctru_sys::camExit() };
} }
} }
impl TryFrom<FramebufferFormat> for OutputFormat {
type Error = ();
fn try_from(value: FramebufferFormat) -> Result<Self, Self::Error> {
match value {
FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565),
_ => Err(()),
}
}
}
impl TryFrom<OutputFormat> for FramebufferFormat {
type Error = ();
fn try_from(value: OutputFormat) -> Result<Self, Self::Error> {
match value {
OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565),
_ => Err(()),
}
}
}
from_impl!(FlipMode, ctru_sys::CAMU_Flip); from_impl!(FlipMode, ctru_sys::CAMU_Flip);
from_impl!(ViewSize, ctru_sys::CAMU_Size); from_impl!(ViewSize, ctru_sys::CAMU_Size);
from_impl!(FrameRate, ctru_sys::CAMU_FrameRate); from_impl!(FrameRate, ctru_sys::CAMU_FrameRate);

160
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; use crate::error::ResultCode;
/// Console region.
#[doc(alias = "CFG_Region")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Region { pub enum Region {
/// Japan.
Japan = ctru_sys::CFG_REGION_JPN, Japan = ctru_sys::CFG_REGION_JPN,
/// USA.
USA = ctru_sys::CFG_REGION_USA, USA = ctru_sys::CFG_REGION_USA,
/// Europe.
Europe = ctru_sys::CFG_REGION_EUR, Europe = ctru_sys::CFG_REGION_EUR,
/// Australia.
Australia = ctru_sys::CFG_REGION_AUS, Australia = ctru_sys::CFG_REGION_AUS,
/// China.
China = ctru_sys::CFG_REGION_CHN, China = ctru_sys::CFG_REGION_CHN,
/// Korea.
Korea = ctru_sys::CFG_REGION_KOR, Korea = ctru_sys::CFG_REGION_KOR,
/// Taiwan.
Taiwan = ctru_sys::CFG_REGION_TWN, Taiwan = ctru_sys::CFG_REGION_TWN,
} }
/// Language set for the console's OS.
#[doc(alias = "CFG_Language")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Language { pub enum Language {
/// Japanese.
Japanese = ctru_sys::CFG_LANGUAGE_JP, Japanese = ctru_sys::CFG_LANGUAGE_JP,
/// English.
English = ctru_sys::CFG_LANGUAGE_EN, English = ctru_sys::CFG_LANGUAGE_EN,
/// French.
French = ctru_sys::CFG_LANGUAGE_FR, French = ctru_sys::CFG_LANGUAGE_FR,
/// German.
German = ctru_sys::CFG_LANGUAGE_DE, German = ctru_sys::CFG_LANGUAGE_DE,
/// Italian.
Italian = ctru_sys::CFG_LANGUAGE_IT, Italian = ctru_sys::CFG_LANGUAGE_IT,
/// Spanish.
Spanish = ctru_sys::CFG_LANGUAGE_ES, Spanish = ctru_sys::CFG_LANGUAGE_ES,
SimplifiedChinese = ctru_sys::CFG_LANGUAGE_ZH, /// Korean.
Korean = ctru_sys::CFG_LANGUAGE_KO, Korean = ctru_sys::CFG_LANGUAGE_KO,
/// Dutch.
Dutch = ctru_sys::CFG_LANGUAGE_NL, Dutch = ctru_sys::CFG_LANGUAGE_NL,
/// Portuguese.
Portuguese = ctru_sys::CFG_LANGUAGE_PT, Portuguese = ctru_sys::CFG_LANGUAGE_PT,
/// Russian.
Russian = ctru_sys::CFG_LANGUAGE_RU, Russian = ctru_sys::CFG_LANGUAGE_RU,
/// Simplified Chinese.
SimplifiedChinese = ctru_sys::CFG_LANGUAGE_ZH,
/// Traditional Chinese.
TraditionalChinese = ctru_sys::CFG_LANGUAGE_TW, TraditionalChinese = ctru_sys::CFG_LANGUAGE_TW,
} }
/// Specific model of the console.
#[doc(alias = "CFG_SystemModel")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum SystemModel { pub enum SystemModel {
/// Old Nintendo 3DS.
Old3DS = ctru_sys::CFG_MODEL_3DS, Old3DS = ctru_sys::CFG_MODEL_3DS,
/// Old Nintendo 3DS XL.
Old3DSXL = ctru_sys::CFG_MODEL_3DSXL, Old3DSXL = ctru_sys::CFG_MODEL_3DSXL,
/// New Nintendo 3DS.
New3DS = ctru_sys::CFG_MODEL_N3DS, New3DS = ctru_sys::CFG_MODEL_N3DS,
/// Old Nintendo 2DS.
Old2DS = ctru_sys::CFG_MODEL_2DS, Old2DS = ctru_sys::CFG_MODEL_2DS,
/// New Nintendo 3DS XL.
New3DSXL = ctru_sys::CFG_MODEL_N3DSXL, New3DSXL = ctru_sys::CFG_MODEL_N3DSXL,
/// New Nintendo 2DS XL.
New2DSXL = ctru_sys::CFG_MODEL_N2DSXL, New2DSXL = ctru_sys::CFG_MODEL_N2DSXL,
} }
/// Represents the configuration service. No actions can be performed /// Handle to the System Configuration service.
/// until an instance of this struct is created.
///
/// The service exits when all instances of this struct go out of scope.
pub struct Cfgu(()); pub struct Cfgu(());
impl 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 /// ```no_run
/// CFGU service. /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::cfgu::Cfgu;
/// ///
/// ctrulib services are reference counted, so this function may be called /// let cfgu = Cfgu::new()?;
/// as many times as desired and the service will not exit until all /// #
/// instances of Cfgu drop out of scope. /// # Ok(())
/// # }
/// ```
#[doc(alias = "cfguInit")]
pub fn new() -> crate::Result<Cfgu> { pub fn new() -> crate::Result<Cfgu> {
ResultCode(unsafe { ctru_sys::cfguInit() })?; ResultCode(unsafe { ctru_sys::cfguInit() })?;
Ok(Cfgu(())) 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<dyn Error>> {
/// #
/// use ctru::services::cfgu::Cfgu;
/// let cfgu = Cfgu::new()?;
///
/// let region = cfgu.region()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "CFGU_SecureInfoGetRegion")]
pub fn region(&self) -> crate::Result<Region> { pub fn region(&self) -> crate::Result<Region> {
let mut region: u8 = 0; let mut region: u8 = 0;
@ -74,7 +125,23 @@ impl Cfgu {
Ok(Region::try_from(region).unwrap()) 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<dyn Error>> {
/// #
/// use ctru::services::cfgu::Cfgu;
/// let cfgu = Cfgu::new()?;
///
/// let model = cfgu.model()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "CFGU_GetSystemModel")]
pub fn model(&self) -> crate::Result<SystemModel> { pub fn model(&self) -> crate::Result<SystemModel> {
let mut model: u8 = 0; let mut model: u8 = 0;
@ -82,7 +149,23 @@ impl Cfgu {
Ok(SystemModel::try_from(model).unwrap()) 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<dyn Error>> {
/// #
/// use ctru::services::cfgu::Cfgu;
/// let cfgu = Cfgu::new()?;
///
/// let language = cfgu.language()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "CFGU_GetSystemLanguage")]
pub fn language(&self) -> crate::Result<Language> { pub fn language(&self) -> crate::Result<Language> {
let mut language: u8 = 0; let mut language: u8 = 0;
@ -90,7 +173,25 @@ impl Cfgu {
Ok(Language::try_from(language).unwrap()) 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<dyn Error>> {
/// #
/// 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<bool> { pub fn is_nfc_supported(&self) -> crate::Result<bool> {
let mut supported: bool = false; let mut supported: bool = false;
@ -98,7 +199,27 @@ impl Cfgu {
Ok(supported) 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<dyn Error>> {
/// #
/// 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<bool> { pub fn is_2ds_family(&self) -> crate::Result<bool> {
let mut is_2ds_family: u8 = 0; let mut is_2ds_family: u8 = 0;
@ -108,6 +229,7 @@ impl Cfgu {
} }
impl Drop for Cfgu { impl Drop for Cfgu {
#[doc(alias = "cfguExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
ctru_sys::cfguExit(); ctru_sys::cfguExit();

149
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. //! 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`. //! 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 bitflags::bitflags;
use std::ffi::OsString; use std::ffi::OsString;
@ -17,70 +19,108 @@ use std::sync::Arc;
use widestring::{WideCStr, WideCString}; use widestring::{WideCStr, WideCString};
bitflags! { bitflags! {
#[derive(Default)] #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
struct FsOpen: u32 { struct FsOpen: u32 {
const FS_OPEN_READ = 1; const FS_OPEN_READ = ctru_sys::FS_OPEN_READ;
const FS_OPEN_WRITE = 2; const FS_OPEN_WRITE = ctru_sys::FS_OPEN_WRITE;
const FS_OPEN_CREATE = 4; const FS_OPEN_CREATE = ctru_sys::FS_OPEN_CREATE;
} }
#[derive(Default)] #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
struct FsWrite: u32 { struct FsWrite: u32 {
const FS_WRITE_FLUSH = 1; const FS_WRITE_FLUSH = ctru_sys::FS_WRITE_FLUSH;
const FS_WRITE_UPDATE_TIME = 256; 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 { struct FsAttribute: u32 {
const FS_ATTRIBUTE_DIRECTORY = 1; const FS_ATTRIBUTE_DIRECTORY = ctru_sys::FS_ATTRIBUTE_DIRECTORY;
const FS_ATTRIBUTE_HIDDEN = 256; const FS_ATTRIBUTE_HIDDEN = ctru_sys::FS_ATTRIBUTE_HIDDEN;
const FS_ATTRIBUTE_ARCHIVE = 65536; const FS_ATTRIBUTE_ARCHIVE = ctru_sys::FS_ATTRIBUTE_ARCHIVE;
const FS_ATTRIBUTE_READ_ONLY = 16777216; 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum FsMediaType { pub enum FsMediaType {
/// Internal NAND memory.
Nand = ctru_sys::MEDIATYPE_NAND, Nand = ctru_sys::MEDIATYPE_NAND,
/// External SD card.
Sd = ctru_sys::MEDIATYPE_SD, Sd = ctru_sys::MEDIATYPE_SD,
/// Game Cartridge.
GameCard = ctru_sys::MEDIATYPE_GAME_CARD, GameCard = ctru_sys::MEDIATYPE_GAME_CARD,
} }
/// Kind of file path.
#[doc(alias = "FS_PathType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum PathType { pub enum PathType {
/// Invalid path.
Invalid = ctru_sys::PATH_INVALID, Invalid = ctru_sys::PATH_INVALID,
/// Empty path.
Empty = ctru_sys::PATH_EMPTY, Empty = ctru_sys::PATH_EMPTY,
/// Binary path.
///
/// Its meaning differs depending on the Archive it is used on.
Binary = ctru_sys::PATH_BINARY, Binary = ctru_sys::PATH_BINARY,
/// ASCII path.
ASCII = ctru_sys::PATH_ASCII, ASCII = ctru_sys::PATH_ASCII,
/// UTF-16 path.
UTF16 = ctru_sys::PATH_UTF16, UTF16 = ctru_sys::PATH_UTF16,
} }
/// Index of the various usable data archives.
#[doc(alias = "FS_ArchiveID")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum ArchiveID { pub enum ArchiveID {
/// Read-Only Memory File System.
RomFS = ctru_sys::ARCHIVE_ROMFS, RomFS = ctru_sys::ARCHIVE_ROMFS,
/// Game save data.
Savedata = ctru_sys::ARCHIVE_SAVEDATA, Savedata = ctru_sys::ARCHIVE_SAVEDATA,
/// Game ext data.
Extdata = ctru_sys::ARCHIVE_EXTDATA, Extdata = ctru_sys::ARCHIVE_EXTDATA,
/// Shared ext data.
SharedExtdata = ctru_sys::ARCHIVE_SHARED_EXTDATA, SharedExtdata = ctru_sys::ARCHIVE_SHARED_EXTDATA,
/// System save data.
SystemSavedata = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA, SystemSavedata = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA,
/// SD card.
Sdmc = ctru_sys::ARCHIVE_SDMC, Sdmc = ctru_sys::ARCHIVE_SDMC,
/// SD card (write-only).
SdmcWriteOnly = ctru_sys::ARCHIVE_SDMC_WRITE_ONLY, SdmcWriteOnly = ctru_sys::ARCHIVE_SDMC_WRITE_ONLY,
/// BOSS ext data.
BossExtdata = ctru_sys::ARCHIVE_BOSS_EXTDATA, BossExtdata = ctru_sys::ARCHIVE_BOSS_EXTDATA,
/// Card SPI File System.
CardSpiFS = ctru_sys::ARCHIVE_CARD_SPIFS, CardSpiFS = ctru_sys::ARCHIVE_CARD_SPIFS,
/// Game ext data and BOSS data.
ExtDataAndBossExtdata = ctru_sys::ARCHIVE_EXTDATA_AND_BOSS_EXTDATA, ExtDataAndBossExtdata = ctru_sys::ARCHIVE_EXTDATA_AND_BOSS_EXTDATA,
/// System save data.
SystemSaveData2 = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA2, SystemSaveData2 = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA2,
/// Internal NAND (read-write).
NandRW = ctru_sys::ARCHIVE_NAND_RW, NandRW = ctru_sys::ARCHIVE_NAND_RW,
/// Internal NAND (read-only).
NandRO = ctru_sys::ARCHIVE_NAND_RO, NandRO = ctru_sys::ARCHIVE_NAND_RO,
/// Internal NAND (read-only write access).
NandROWriteAccess = ctru_sys::ARCHIVE_NAND_RO_WRITE_ACCESS, NandROWriteAccess = ctru_sys::ARCHIVE_NAND_RO_WRITE_ACCESS,
/// User save data and ExeFS/RomFS.
SaveDataAndContent = ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT, 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, SaveDataAndContent2 = ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT2,
/// NAND CTR File System.
NandCtrFS = ctru_sys::ARCHIVE_NAND_CTR_FS, NandCtrFS = ctru_sys::ARCHIVE_NAND_CTR_FS,
/// TWL photo.
TwlPhoto = ctru_sys::ARCHIVE_TWL_PHOTO, TwlPhoto = ctru_sys::ARCHIVE_TWL_PHOTO,
/// NAND TWL File System.
NandTwlFS = ctru_sys::ARCHIVE_NAND_TWL_FS, NandTwlFS = ctru_sys::ARCHIVE_NAND_TWL_FS,
/// Game card save data.
GameCardSavedata = ctru_sys::ARCHIVE_GAMECARD_SAVEDATA, GameCardSavedata = ctru_sys::ARCHIVE_GAMECARD_SAVEDATA,
/// User save data.
UserSavedata = ctru_sys::ARCHIVE_USER_SAVEDATA, UserSavedata = ctru_sys::ARCHIVE_USER_SAVEDATA,
/// Demo save data.
DemoSavedata = ctru_sys::ARCHIVE_DEMO_SAVEDATA, DemoSavedata = ctru_sys::ARCHIVE_DEMO_SAVEDATA,
} }
@ -183,36 +223,26 @@ pub struct File {
/// Metadata information about a 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. /// represents known metadata about a file.
///
/// [`metadata`]: fn.metadata.html
pub struct Metadata { pub struct Metadata {
attributes: u32, attributes: u32,
size: u64, size: u64,
} }
/// Options and flags which can be used to configure how a [`File`] is opened. /// 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 what operations are permitted on the open file. The [`File::open`]
/// and [`File::create`] methods are aliases for commonly used options /// and [`File::create`] methods are aliases for commonly used options
/// using this builder. /// using this builder.
/// ///
/// [`File`]: struct.File.html /// Generally speaking, when using [`OpenOptions`], you'll first call [`OpenOptions::new`],
/// [`File::open`]: struct.File.html#method.open /// then chain calls to methods to set each option, then call [`OpenOptions::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()`],
/// passing the path of the file you're trying to 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 /// It is required to also pass a reference to the [`Archive`] that the
/// file lives in. /// file lives in.
/// ///
/// [`new()`]: struct.OpenOptions.html#method.new
/// [`open()`]: struct.OpenOptions.html#method.open
/// [`Archive`]: struct.Archive.html
///
/// # Examples /// # Examples
/// ///
/// Opening a file to read: /// Opening a file to read:
@ -257,14 +287,11 @@ pub struct OpenOptions {
/// Iterator over the entries in a directory. /// Iterator over the entries in a directory.
/// ///
/// This iterator is returned from the [`read_dir`] function of this module and /// This iterator is returned from the [`read_dir`] function and
/// will yield instances of `Result<DirEntry, i32>`. Through a [`DirEntry`] /// will yield instances of [`Result<DirEntry, i32>`]. Through a [`DirEntry`]
/// information like the entry's path and possibly other metadata can be /// information like the entry's path and possibly other metadata can be
/// learned. /// learned.
/// ///
/// [`read_dir`]: fn.read_dir.html
/// [`DirEntry`]: struct.DirEntry.html
///
/// # Errors /// # Errors
/// ///
/// This Result will return Err if there's some sort of intermittent IO error /// 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. /// Entries returned by the [`ReadDir`] iterator.
/// ///
/// [`ReadDir`]: struct.ReadDir.html
///
/// An instance of `DirEntry` represents an entry inside of a directory on the /// 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 /// filesystem. Each entry can be inspected via methods to learn about the full
/// path or possibly other metadata. /// path or possibly other metadata.
@ -297,7 +322,7 @@ unsafe impl Send for Dir {}
unsafe impl Sync for Dir {} unsafe impl Sync for Dir {}
impl Fs { impl Fs {
/// Initializes the FS service. /// Initialize a new service handle.
/// ///
/// # Errors /// # Errors
/// ///
@ -338,8 +363,6 @@ impl Fs {
impl Archive { impl Archive {
/// Retrieves an Archive's [`ArchiveID`] /// Retrieves an Archive's [`ArchiveID`]
///
/// [`ArchiveID`]: enum.ArchiveID.html
pub fn id(&self) -> ArchiveID { pub fn id(&self) -> ArchiveID {
self.id self.id
} }
@ -355,8 +378,6 @@ impl File {
/// This function will return an error if `path` does not already exit. /// This function will return an error if `path` does not already exit.
/// Other errors may also be returned accoridng to [`OpenOptions::open`] /// Other errors may also be returned accoridng to [`OpenOptions::open`]
/// ///
/// [`OpenOptions::open`]: struct.OpenOptions.html#method.open
///
/// # Examples /// # Examples
/// ///
/// ```no_run /// ```no_run
@ -382,9 +403,7 @@ impl File {
/// # Errors /// # Errors
/// ///
/// This function will return an error if `path` does not already exit. /// This function will return an error if `path` does not already exit.
/// Other errors may also be returned accoridng to [`OpenOptions::create`] /// Other errors may also be returned accoridng to [`OpenOptions::create`].
///
/// [`OpenOptions::create`]: struct.OpenOptions.html#method.create
/// ///
/// # Examples /// # Examples
/// ///
@ -395,7 +414,7 @@ impl File {
/// let mut sdmc_archive = fs.sdmc().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::create(&mut sdmc_archive, "/foo.txt").unwrap(); /// let mut f = File::create(&mut sdmc_archive, "/foo.txt").unwrap();
/// ``` /// ```
pub fn create<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<File> { pub fn create<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
OpenOptions::new() OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
@ -412,6 +431,7 @@ impl File {
/// # Errors /// # Errors
/// ///
/// This function will return an error if the file is not opened for writing. /// 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<()> { pub fn set_len(&mut self, size: u64) -> IoResult<()> {
unsafe { unsafe {
let r = ctru_sys::FSFILE_SetSize(self.handle, size); 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<usize> { fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
unsafe { unsafe {
let mut n_read = 0; let mut n_read = 0;
@ -470,6 +491,7 @@ impl File {
unsafe { read_to_end_uninitialized(self, buf) } unsafe { read_to_end_uninitialized(self, buf) }
} }
#[doc(alias = "FSFILE_Write")]
fn write(&mut self, buf: &[u8]) -> IoResult<usize> { fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
unsafe { unsafe {
let mut n_written = 0; let mut n_written = 0;
@ -521,7 +543,7 @@ impl OpenOptions {
Self::default() 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 /// This option, when true, will indicate that the file should be
/// `read`-able if opened. /// `read`-able if opened.
@ -530,7 +552,7 @@ impl OpenOptions {
self self
} }
/// Sets the option for write access. /// Set the option for write access.
/// ///
/// This option, when true, will indicate that the file should be /// This option, when true, will indicate that the file should be
/// `write`-able if opened. /// `write`-able if opened.
@ -542,7 +564,7 @@ impl OpenOptions {
self 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 /// This option, when true, means that writes will append to a file instead
/// of overwriting previous contents. Note that setting .write(true).append(true) /// of overwriting previous contents. Note that setting .write(true).append(true)
@ -554,7 +576,7 @@ impl OpenOptions {
self 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 /// If a file is successfully opened with this option set it will truncate
/// the file to 0 length if it already exists. /// the file to 0 length if it already exists.
@ -565,7 +587,7 @@ impl OpenOptions {
self 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 /// This option indicates whether a new file will be created
/// if the file does not yet already /// if the file does not yet already
@ -577,7 +599,7 @@ impl OpenOptions {
self 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. /// Failing to pass in an archive will result in the file failing to open.
pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions { pub fn archive(&mut self, archive: &Archive) -> &mut OpenOptions {
@ -597,8 +619,7 @@ impl OpenOptions {
/// to the `archive` method. /// to the `archive` method.
/// * Filesystem-level errors (full disk, etc). /// * Filesystem-level errors (full disk, etc).
/// * Invalid combinations of open options. /// * Invalid combinations of open options.
/// #[doc(alias = "FSUSER_OpenFile")]
/// [`Archive`]: struct.Archive.html
pub fn open<P: AsRef<Path>>(&mut self, path: P) -> IoResult<File> { pub fn open<P: AsRef<Path>>(&mut self, path: P) -> IoResult<File> {
self._open(path.as_ref(), self.open_flags()) self._open(path.as_ref(), self.open_flags())
} }
@ -717,7 +738,8 @@ impl<'a> DirEntry<'a> {
/// but is not limited to just these cases: /// but is not limited to just these cases:
/// ///
/// * User lacks permissions to create directory at `path` /// * User lacks permissions to create directory at `path`
pub fn create_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> { #[doc(alias = "FSUSER_CreateDirectory")]
pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -743,7 +765,8 @@ pub fn create_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
/// ///
/// * If any directory in the path specified by `path` does not already exist /// * If any directory in the path specified by `path` does not already exist
/// and it could not be created otherwise. /// and it could not be created otherwise.
pub fn create_dir_all<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> { #[doc(alias = "FSUSER_CreateDirectory")]
pub fn create_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
let path = path.as_ref(); let path = path.as_ref();
let mut dir = PathBuf::new(); let mut dir = PathBuf::new();
let mut result = Ok(()); let mut result = Ok(());
@ -779,7 +802,8 @@ pub fn metadata<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<Metadata> {
/// ///
/// * The user lacks permissions to remove the directory at the provided path. /// * The user lacks permissions to remove the directory at the provided path.
/// * The directory isn't empty. /// * The directory isn't empty.
pub fn remove_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> { #[doc(alias = "FSUSER_DeleteDirectory")]
pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -797,7 +821,8 @@ pub fn remove_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
/// # Errors /// # Errors
/// ///
/// see `file::remove_file` and `fs::remove_dir` /// see `file::remove_file` and `fs::remove_dir`
pub fn remove_dir_all<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> { #[doc(alias = "FSUSER_DeleteDirectoryRecursively")]
pub fn remove_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -821,6 +846,7 @@ pub fn remove_dir_all<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<(
/// * The provided path doesn't exist. /// * The provided path doesn't exist.
/// * The process lacks permissions to view the contents. /// * The process lacks permissions to view the contents.
/// * The path points at a non-directory file. /// * The path points at a non-directory file.
#[doc(alias = "FSUSER_OpenDirectory")]
pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<ReadDir> { pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<ReadDir> {
unsafe { unsafe {
let mut handle = 0; let mut handle = 0;
@ -849,7 +875,8 @@ pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<ReadDir> {
/// ///
/// * path points to a directory. /// * path points to a directory.
/// * The user lacks permissions to remove the file. /// * The user lacks permissions to remove the file.
pub fn remove_file<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> { #[doc(alias = "FSUSER_DeleteFile")]
pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -872,7 +899,8 @@ pub fn remove_file<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()>
/// ///
/// * from does not exist. /// * from does not exist.
/// * The user lacks permissions to view contents. /// * The user lacks permissions to view contents.
pub fn rename<P, Q>(arch: &mut Archive, from: P, to: Q) -> IoResult<()> #[doc(alias = "FSUSER_RenameFile", alias = "FSUSER_RenameDirectory")]
pub fn rename<P, Q>(arch: &Archive, from: P, to: Q) -> IoResult<()>
where where
P: AsRef<Path>, P: AsRef<Path>,
Q: AsRef<Path>, Q: AsRef<Path>,
@ -901,7 +929,6 @@ fn to_utf16(path: &Path) -> WideCString {
WideCString::from_str(path).unwrap() WideCString::from_str(path).unwrap()
} }
// Adapted from sys/windows/fs.rs in libstd
fn truncate_utf16_at_nul(v: &[u16]) -> &[u16] { fn truncate_utf16_at_nul(v: &[u16]) -> &[u16] {
match v.iter().position(|c| *c == 0) { match v.iter().position(|c| *c == 0) {
// don't include the 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. // Provides read_to_end functionality over an uninitialized buffer.
// This function is unsafe because it calls the underlying // This function is unsafe because it calls the underlying
// read function with a slice into uninitialized memory. The default // read function with a slice into uninitialized memory. The default
@ -998,6 +1023,7 @@ impl Seek for File {
} }
impl Drop for Fs { impl Drop for Fs {
#[doc(alias = "fsExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
ctru_sys::fsExit(); ctru_sys::fsExit();
@ -1006,6 +1032,7 @@ impl Drop for Fs {
} }
impl Drop for Archive { impl Drop for Archive {
#[doc(alias = "FSUSER_CloseArchive")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let _ = ctru_sys::FSUSER_CloseArchive(self.handle); let _ = ctru_sys::FSUSER_CloseArchive(self.handle);
@ -1014,6 +1041,7 @@ impl Drop for Archive {
} }
impl Drop for File { impl Drop for File {
#[doc(alias = "FSFILE_Close")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let _ = ctru_sys::FSFILE_Close(self.handle); let _ = ctru_sys::FSFILE_Close(self.handle);
@ -1022,6 +1050,7 @@ impl Drop for File {
} }
impl Drop for Dir { impl Drop for Dir {
#[doc(alias = "FSDIR_Close")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let _ = ctru_sys::FSDIR_Close(self.0); let _ = ctru_sys::FSDIR_Close(self.0);

217
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::cell::{Ref, RefCell, RefMut};
use std::marker::PhantomData; use std::marker::PhantomData;
@ -20,9 +24,12 @@ mod private {
impl Sealed for BottomScreen {} 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 /// 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 /// drawing to the screens. Graphics-related code can be made generic over this
/// trait to work with any of the given screens. /// trait to work with any of the given screens.
#[doc(alias = "gfxScreen_t")]
pub trait Screen: private::Sealed { pub trait Screen: private::Sealed {
/// Returns the `libctru` value for the Screen kind. /// Returns the `libctru` value for the Screen kind.
fn as_raw(&self) -> ctru_sys::gfxScreen_t; 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 /// Note that the pointer of the framebuffer returned by this function can
/// change after each call to this function if double buffering is enabled. /// change after each call to this function if double buffering is enabled.
#[doc(alias = "gfxGetFramebuffer")]
fn raw_framebuffer(&mut self) -> RawFrameBuffer { fn raw_framebuffer(&mut self) -> RawFrameBuffer {
let mut width: u16 = 0; let mut width: u16 = 0;
let mut height: 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. /// Gets the framebuffer format.
#[doc(alias = "gfxGetScreenFormat")]
fn framebuffer_format(&self) -> FramebufferFormat { fn framebuffer_format(&self) -> FramebufferFormat {
unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into() 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 /// [`Swap::swap_buffers`] must be called after this method for the configuration
/// change to take effect. /// change to take effect.
#[doc(alias = "gfxSetScreenFormat")]
fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) { fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) {
unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) }
} }
} }
/// The top screen. Mutable access to this struct is required to write to the top /// The top LCD screen.
/// screen's frame buffer. To enable 3D mode, it can be converted into a [`TopScreen3D`]. ///
/// 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 { pub struct TopScreen {
left: TopScreenLeft, left: TopScreenLeft,
right: TopScreenRight, 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 /// 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. /// converted into this, 3D mode will be enabled until this struct is dropped.
pub struct TopScreen3D<'screen> { pub struct TopScreen3D<'screen> {
screen: &'screen RefCell<TopScreen>, screen: &'screen RefCell<TopScreen>,
} }
/// 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. /// This trait applies to all [`Screen`]s that have swappable frame buffers.
pub trait Swap: private::Sealed { pub trait Swap: private::Sealed {
/// Swaps the video buffers. /// Swaps the video buffers.
/// ///
/// If double buffering is disabled, "swapping" the buffers has the side effect /// Even 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`], /// of committing any configuration changes to the buffers (e.g. [`TopScreen::set_wide_mode()`],
/// [`set_framebuffer_format`], [`set_double_buffering`]). /// [`Screen::set_framebuffer_format()`], [`Swap::set_double_buffering()`]), so it should still be used.
/// ///
/// This should be called once per frame at most. /// This should be called once per frame at most.
/// #[doc(alias = "gfxScreenSwapBuffers")]
/// [`set_wide_mode`]: TopScreen::set_wide_mode
/// [`set_framebuffer_format`]: Screen::set_framebuffer_format
/// [`set_double_buffering`]: Screen::set_double_buffering
fn swap_buffers(&mut self); 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<'_> { impl Swap for TopScreen3D<'_> {
@ -107,6 +121,10 @@ impl Swap for TopScreen3D<'_> {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, true); 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 { impl Swap for TopScreen {
@ -115,6 +133,10 @@ impl Swap for TopScreen {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, false); 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 { impl Swap for BottomScreen {
@ -123,13 +145,20 @@ impl Swap for BottomScreen {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_BOTTOM, false); 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`] /// A screen with buffers that can be flushed.
/// that has data written to its frame buffer. ///
/// This trait applies to any [`Screen`] that has data written to its frame buffer.
pub trait Flush: private::Sealed { pub trait Flush: private::Sealed {
/// Flushes the video buffer(s) for this screen. Note that you must still call /// Flushes the video buffer(s) for this screen.
/// [`Swap::swap_buffers`] after this method for the buffer contents to be displayed. ///
/// 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); fn flush_buffers(&mut self);
} }
@ -138,7 +167,7 @@ impl<S: Screen> Flush for S {
let framebuffer = self.raw_framebuffer(); let framebuffer = self.raw_framebuffer();
// Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens // Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens
unsafe { let _ = unsafe {
ctru_sys::GSPGPU_FlushDataCache( ctru_sys::GSPGPU_FlushDataCache(
framebuffer.ptr.cast(), framebuffer.ptr.cast(),
(framebuffer.height * framebuffer.width) as u32, (framebuffer.height * framebuffer.width) as u32,
@ -167,14 +196,16 @@ pub struct TopScreenLeft;
#[non_exhaustive] #[non_exhaustive]
pub struct TopScreenRight; pub struct TopScreenRight;
/// The bottom screen. Mutable access to this struct is required to write to the /// The bottom LCD screen.
/// bottom screen's frame buffer. ///
/// Mutable access to this struct is required to write to the bottom screen's frame buffer.
#[derive(Debug)] #[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
pub struct BottomScreen; pub struct BottomScreen;
/// Representation of a framebuffer for one [`Side`] of the top screen, or the /// Representation of a framebuffer for one [`Side`] of the top screen, or the entire bottom screen.
/// entire bottom screen. The inner pointer is only valid for one frame if double ///
/// The inner pointer is only valid for one frame if double
/// buffering is enabled. Data written to `ptr` will be rendered to the screen. /// buffering is enabled. Data written to `ptr` will be rendered to the screen.
#[derive(Debug)] #[derive(Debug)]
pub struct RawFrameBuffer<'screen> { pub struct RawFrameBuffer<'screen> {
@ -188,11 +219,12 @@ pub struct RawFrameBuffer<'screen> {
screen: PhantomData<&'screen mut dyn Screen>, screen: PhantomData<&'screen mut dyn Screen>,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Side of the [`TopScreen`]'s framebuffer.
#[repr(u32)]
/// Side of top screen framebuffer
/// ///
/// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality /// 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 { pub enum Side {
/// The left framebuffer. This framebuffer is also the one used when 3D is disabled /// The left framebuffer. This framebuffer is also the one used when 3D is disabled
Left = ctru_sys::GFX_LEFT, Left = ctru_sys::GFX_LEFT,
@ -200,12 +232,14 @@ pub enum Side {
Right = ctru_sys::GFX_RIGHT, Right = ctru_sys::GFX_RIGHT,
} }
/// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that /// Handle to the GFX service.
/// provides helper functions and utilities for software rendering.
/// ///
/// 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 { pub struct Gfx {
/// Top screen representation.
pub top_screen: RefCell<TopScreen>, pub top_screen: RefCell<TopScreen>,
/// Bottom screen representation.
pub bottom_screen: RefCell<BottomScreen>, pub bottom_screen: RefCell<BottomScreen>,
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
@ -213,17 +247,63 @@ pub struct Gfx {
static GFX_ACTIVE: Mutex<usize> = Mutex::new(0); static GFX_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Gfx { impl Gfx {
/// Creates a new [Gfx] instance with default init values /// Initialize a new default service handle.
///
/// # Notes
///
/// It's the same as calling: /// It's the same as calling:
/// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)` ///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// # 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<dyn Error>> {
/// #
/// use ctru::services::gfx::Gfx;
///
/// let gfx = Gfx::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "gfxInit")]
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false) Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)
} }
/// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom /// Initialize a new service handle with the chosen framebuffer formats for the top and bottom screens.
/// 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<dyn Error>> {
/// #
/// 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( pub fn with_formats(
top_fb_fmt: FramebufferFormat, top_fb_fmt: FramebufferFormat,
bottom_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 /// 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<dyn Error>> {
/// #
/// 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) { pub fn wait_for_vblank(&self) {
gspgpu::wait_for_event(gspgpu::Event::VBlank0, true); 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<dyn Error>> {
/// #
/// 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<TopScreen>> for TopScreen3D<'screen> { impl<'screen> From<&'screen RefCell<TopScreen>> for TopScreen3D<'screen> {
#[doc(alias = "gfxSet3D")]
fn from(top_screen: &'screen RefCell<TopScreen>) -> Self { fn from(top_screen: &'screen RefCell<TopScreen>) -> Self {
unsafe { unsafe {
ctru_sys::gfxSet3D(true); ctru_sys::gfxSet3D(true);
@ -297,8 +428,13 @@ impl TopScreen {
/// Enable or disable wide mode on the top screen. /// Enable or disable wide mode on the top screen.
/// ///
/// # Notes
///
/// [`Swap::swap_buffers`] must be called after this method for the configuration /// [`Swap::swap_buffers`] must be called after this method for the configuration
/// to take effect. /// 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) { pub fn set_wide_mode(&mut self, enable: bool) {
unsafe { unsafe {
ctru_sys::gfxSetWide(enable); ctru_sys::gfxSetWide(enable);
@ -306,6 +442,7 @@ impl TopScreen {
} }
/// Returns whether or not wide mode is enabled on the top screen. /// Returns whether or not wide mode is enabled on the top screen.
#[doc(alias = "gfxIsWide")]
pub fn is_wide(&self) -> bool { pub fn is_wide(&self) -> bool {
unsafe { ctru_sys::gfxIsWide() } unsafe { ctru_sys::gfxIsWide() }
} }

13
ctru-rs/src/services/gspgpu.rs

@ -1,18 +1,28 @@
//! GSPGPU service //! GSPGPU service
/// GSPGPU events that can be awaited.
#[doc(alias = "GSPGPU_Event")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Event { pub enum Event {
/// Memory fill completed.
Psc0 = ctru_sys::GSPGPU_EVENT_PSC0, Psc0 = ctru_sys::GSPGPU_EVENT_PSC0,
/// TODO: Unknown.
Psc1 = ctru_sys::GSPGPU_EVENT_PSC1, Psc1 = ctru_sys::GSPGPU_EVENT_PSC1,
/// TODO: Unknown.
VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0, VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0,
/// TODO: Unknown.
VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1, VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1,
/// Display transfer finished.
PPF = ctru_sys::GSPGPU_EVENT_PPF, PPF = ctru_sys::GSPGPU_EVENT_PPF,
/// Command list processing finished.
P3D = ctru_sys::GSPGPU_EVENT_P3D, P3D = ctru_sys::GSPGPU_EVENT_P3D,
/// TODO: Unknown.
DMA = ctru_sys::GSPGPU_EVENT_DMA, 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum FramebufferFormat { pub enum FramebufferFormat {
@ -45,6 +55,7 @@ impl FramebufferFormat {
/// Waits for a GSPGPU event to occur. /// Waits for a GSPGPU event to occur.
/// ///
/// `discard_current` determines whether to discard the current event and wait for the next event /// `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) { pub fn wait_for_event(ev: Event, discard_current: bool) {
unsafe { unsafe {
ctru_sys::gspWaitForEvent(ev.into(), discard_current); ctru_sys::gspWaitForEvent(ev.into(), discard_current);

201
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, //! 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. It also provides information from the sound volume slider, //! and [circle pad information](Hid::circlepad_position). It also provides information from the sound volume slider, the accelerometer, and the gyroscope.
//! 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; use crate::error::ResultCode;
bitflags::bitflags! { use bitflags::bitflags;
/// A set of flags corresponding to the button and directional pad
/// inputs on the 3DS 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 { pub struct KeyPad: u32 {
/// A button.
const A = ctru_sys::KEY_A; const A = ctru_sys::KEY_A;
/// B button.
const B = ctru_sys::KEY_B; const B = ctru_sys::KEY_B;
/// Select button.
const SELECT = ctru_sys::KEY_SELECT; const SELECT = ctru_sys::KEY_SELECT;
/// Start button.
const START = ctru_sys::KEY_START; const START = ctru_sys::KEY_START;
/// D-Pad Right.
const DPAD_RIGHT = ctru_sys::KEY_DRIGHT; const DPAD_RIGHT = ctru_sys::KEY_DRIGHT;
/// D-Pad Left.
const DPAD_LEFT = ctru_sys::KEY_DLEFT; const DPAD_LEFT = ctru_sys::KEY_DLEFT;
/// D-Pad Up.
const DPAD_UP = ctru_sys::KEY_DUP; const DPAD_UP = ctru_sys::KEY_DUP;
/// D-Pad Down.
const DPAD_DOWN = ctru_sys::KEY_DDOWN; const DPAD_DOWN = ctru_sys::KEY_DDOWN;
/// R button.
const R = ctru_sys::KEY_R; const R = ctru_sys::KEY_R;
/// L button.
const L = ctru_sys::KEY_L; const L = ctru_sys::KEY_L;
/// X button.
const X = ctru_sys::KEY_X; const X = ctru_sys::KEY_X;
/// Y button.
const Y = ctru_sys::KEY_Y; const Y = ctru_sys::KEY_Y;
/// ZL button.
const ZL = ctru_sys::KEY_ZL; const ZL = ctru_sys::KEY_ZL;
/// ZR button.
const ZR = ctru_sys::KEY_ZR; const ZR = ctru_sys::KEY_ZR;
/// Touchscreen.
const TOUCH = ctru_sys::KEY_TOUCH; const TOUCH = ctru_sys::KEY_TOUCH;
/// C-Stick Right.
const CSTICK_RIGHT = ctru_sys::KEY_CSTICK_RIGHT; const CSTICK_RIGHT = ctru_sys::KEY_CSTICK_RIGHT;
/// C-Stick Left.
const CSTICK_LEFT = ctru_sys::KEY_CSTICK_LEFT; const CSTICK_LEFT = ctru_sys::KEY_CSTICK_LEFT;
/// C-Stick Up.
const CSTICK_UP = ctru_sys::KEY_CSTICK_UP; const CSTICK_UP = ctru_sys::KEY_CSTICK_UP;
/// C-Stick Down.
const CSTICK_DOWN = ctru_sys::KEY_CSTICK_DOWN; const CSTICK_DOWN = ctru_sys::KEY_CSTICK_DOWN;
/// CirclePad Right.
const CPAD_RIGHT = ctru_sys::KEY_CPAD_RIGHT; const CPAD_RIGHT = ctru_sys::KEY_CPAD_RIGHT;
/// CirclePad Left.
const CPAD_LEFT = ctru_sys::KEY_CPAD_LEFT; const CPAD_LEFT = ctru_sys::KEY_CPAD_LEFT;
/// CirclePad Up.
const CPAD_UP = ctru_sys::KEY_CPAD_UP; const CPAD_UP = ctru_sys::KEY_CPAD_UP;
/// CirclePad Down.
const CPAD_DOWN = ctru_sys::KEY_CPAD_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(); 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(); 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(); 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(); const RIGHT = KeyPad::DPAD_RIGHT.bits() | KeyPad::CPAD_RIGHT.bits();
} }
} }
/// A reference-counted handle to the HID service. The service is closed /// Handle to the HID service.
/// when all instances of this struct fall out of scope.
///
/// This service requires no special permissions to use.
pub struct Hid(()); pub struct Hid(());
/// Initializes the HID service. impl Hid {
/// Initialize a new service handle.
/// ///
/// # Errors /// # Errors
/// ///
/// This function will return an error if the service was unable to be initialized. /// 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 /// Since this service requires no special or elevated permissions, errors are rare in practice.
/// rare in practice. ///
impl Hid { /// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
///
/// let hid = Hid::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "hidInit")]
pub fn new() -> crate::Result<Hid> { pub fn new() -> crate::Result<Hid> {
unsafe { unsafe {
ResultCode(ctru_sys::hidInit())?; ResultCode(ctru_sys::hidInit())?;
@ -61,15 +106,52 @@ impl Hid {
} }
} }
/// Scans the HID service for all user input occurring on the current /// Scan the HID service for all user input occurring on the current frame.
/// frame. This function should be called on every frame when polling ///
/// This function should be called on every frame when polling
/// for user input. /// for user input.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::hid::Hid;
/// let mut hid = Hid::new()?;
///
/// hid.scan_input();
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "hidScanInput")]
pub fn scan_input(&mut self) { pub fn scan_input(&mut self) {
unsafe { ctru_sys::hidScanInput() }; unsafe { ctru_sys::hidScanInput() };
} }
/// Returns a bitflag struct representing which buttons have just been pressed /// Returns a bitflag struct representing which buttons have just been pressed
/// on the current frame (and were not pressed on the previous frame). /// on the current frame (and were not pressed on the previous frame).
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { pub fn keys_down(&self) -> KeyPad {
unsafe { unsafe {
let keys = ctru_sys::hidKeysDown(); let keys = ctru_sys::hidKeysDown();
@ -79,6 +161,26 @@ impl Hid {
/// Returns a bitflag struct representing which buttons have been held down /// Returns a bitflag struct representing which buttons have been held down
/// during the current frame. /// during the current frame.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { pub fn keys_held(&self) -> KeyPad {
unsafe { unsafe {
let keys = ctru_sys::hidKeysHeld(); let keys = ctru_sys::hidKeysHeld();
@ -88,6 +190,26 @@ impl Hid {
/// Returns a bitflag struct representing which buttons have just been released on /// Returns a bitflag struct representing which buttons have just been released on
/// the current frame. /// the current frame.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { pub fn keys_up(&self) -> KeyPad {
unsafe { unsafe {
let keys = ctru_sys::hidKeysUp(); let keys = ctru_sys::hidKeysUp();
@ -100,12 +222,31 @@ impl Hid {
/// # Notes /// # Notes
/// ///
/// (0, 0) represents the top left corner of the screen. /// (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<dyn Error>> {
/// #
/// 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 }; let mut res = ctru_sys::touchPosition { px: 0, py: 0 };
unsafe { unsafe {
ctru_sys::hidTouchRead(&mut res); ctru_sys::hidTouchRead(&mut res);
} }
(res.px, res.py) (res.px, res.py)
} }
@ -114,17 +255,37 @@ impl Hid {
/// # Notes /// # Notes
/// ///
/// (0, 0) represents the center of the circle pad. /// (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<dyn Error>> {
/// #
/// 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 }; let mut res = ctru_sys::circlePosition { dx: 0, dy: 0 };
unsafe { unsafe {
ctru_sys::hidCircleRead(&mut res); ctru_sys::hidCircleRead(&mut res);
} }
(res.dx, res.dy) (res.dx, res.dy)
} }
} }
impl Drop for Hid { impl Drop for Hid {
#[doc(alias = "hidExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ctru_sys::hidExit() }; unsafe { ctru_sys::hidExit() };
} }

15
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. //! 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 am;
pub mod apt; 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; pub(crate) use self::reference::ServiceReference;

420
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; pub mod wave;
use wave::{Wave, WaveStatus}; use wave::{Status, Wave};
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
@ -14,49 +19,80 @@ use std::sync::Mutex;
const NUMBER_OF_CHANNELS: u8 = 24; const NUMBER_OF_CHANNELS: u8 = 24;
/// Audio output mode.
#[doc(alias = "ndspOutputMode")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum OutputMode { pub enum OutputMode {
/// Single-Channel.
Mono = ctru_sys::NDSP_OUTPUT_MONO, Mono = ctru_sys::NDSP_OUTPUT_MONO,
/// Dual-Channel.
Stereo = ctru_sys::NDSP_OUTPUT_STEREO, Stereo = ctru_sys::NDSP_OUTPUT_STEREO,
/// Surround.
Surround = ctru_sys::NDSP_OUTPUT_SURROUND, Surround = ctru_sys::NDSP_OUTPUT_SURROUND,
} }
/// PCM formats supported by the audio engine.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum AudioFormat { pub enum AudioFormat {
/// PCM 8bit single-channel.
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8, PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
/// PCM 16bit single-channel.
PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16, PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16,
/// PCM 8bit interleaved dual-channel.
PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8, PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8,
/// PCM 16bit interleaved dual-channel.
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16, 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)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct AudioMix { pub struct AudioMix {
raw: [f32; 12], raw: [f32; 12],
} }
/// Interpolation used between audio frames.
#[doc(alias = "ndspInterpType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum InterpolationType { pub enum InterpolationType {
/// Polyphase interpolation.
Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE, Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
/// Linear interpolation.
Linear = ctru_sys::NDSP_INTERP_LINEAR, Linear = ctru_sys::NDSP_INTERP_LINEAR,
/// No interpolation.
None = ctru_sys::NDSP_INTERP_NONE, None = ctru_sys::NDSP_INTERP_NONE,
} }
/// Errors returned by [`ndsp`](self) functions.
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NdspError { pub enum NdspError {
/// Channel ID /// Channel with the specified ID does not exist.
InvalidChannel(u8), InvalidChannel(u8),
/// Channel ID /// Channel with the specified ID is already being used.
ChannelAlreadyInUse(u8), ChannelAlreadyInUse(u8),
/// Channel ID /// The wave is already busy playing in the channel with the specified ID.
WaveBusy(u8), WaveBusy(u8),
/// Sample amount requested, Max sample amount /// The sample amount requested was larger than the maximum.
SampleCountOutOfBounds(usize, usize), 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> { pub struct Channel<'ndsp> {
id: u8, id: u8,
_rf: RefMut<'ndsp, ()>, // we don't need to hold any data _rf: RefMut<'ndsp, ()>, // we don't need to hold any data
@ -64,10 +100,9 @@ pub struct Channel<'ndsp> {
static NDSP_ACTIVE: Mutex<usize> = Mutex::new(0); static NDSP_ACTIVE: Mutex<usize> = 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 handle for this service can exist at a time.
/// Only one "instance" of this struct can exist at a time.
pub struct Ndsp { pub struct Ndsp {
_service_handler: ServiceReference, _service_handler: ServiceReference,
channel_flags: [RefCell<()>; NUMBER_OF_CHANNELS as usize], channel_flags: [RefCell<()>; NUMBER_OF_CHANNELS as usize],
@ -78,8 +113,23 @@ impl Ndsp {
/// ///
/// # Errors /// # 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. /// or if there are any issues during initialization.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
///
/// let ndsp = Ndsp::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "ndspInit")]
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&NDSP_ACTIVE, &NDSP_ACTIVE,
@ -105,6 +155,21 @@ impl Ndsp {
/// # Errors /// # 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. /// 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<dyn Error>> {
/// #
/// 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<Channel, NdspError> { pub fn channel(&self, id: u8) -> std::result::Result<Channel, NdspError> {
let in_bounds = self.channel_flags.get(id as usize); 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<dyn Error>> {
/// #
/// 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) { pub fn set_output_mode(&mut self, mode: OutputMode) {
unsafe { ctru_sys::ndspSetOutputMode(mode.into()) }; unsafe { ctru_sys::ndspSetOutputMode(mode.into()) };
} }
} }
impl Channel<'_> { 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<dyn Error>> {
/// #
/// 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) { pub fn reset(&mut self) {
unsafe { ctru_sys::ndspChnReset(self.id.into()) }; unsafe { ctru_sys::ndspChnReset(self.id.into()) };
} }
/// Initialize the channel's parameters /// Initialize the channel's parameters with default values.
pub fn init_parameters(&self) { ///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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()) }; unsafe { ctru_sys::ndspChnInitParams(self.id.into()) };
} }
/// Returns whether the channel is playing any audio. /// Returns whether the channel is playing any audio.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { pub fn is_playing(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) } unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) }
} }
/// Returns whether the channel's playback is currently paused. /// Returns whether the channel's playback is currently paused.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { pub fn is_paused(&self) -> bool {
unsafe { ctru_sys::ndspChnIsPaused(self.id.into()) } 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<dyn Error>> {
/// #
/// 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 { pub fn id(&self) -> u8 {
self.id self.id
} }
@ -155,46 +324,161 @@ impl Channel<'_> {
/// Returns the index of the currently played sample. /// 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. /// 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 { pub fn sample_position(&self) -> usize {
(unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize (unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize
} }
/// Returns the channel's current wave sequence's id. /// Returns the channel's current wave sequence's id.
#[doc(alias = "ndspChnGetWaveBufSeq")]
pub fn wave_sequence_id(&self) -> u16 { pub fn wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) } unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
} }
/// Pause or un-pause the channel's playback. /// Pause or un-pause the channel's playback.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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) { pub fn set_paused(&mut self, state: bool) {
unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) }; unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) };
} }
/// Set the channel's output format. /// 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<dyn Error>> {
/// #
/// 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) { pub fn set_format(&mut self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) }; unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) };
} }
/// Set the channel's interpolation mode. /// Set the channel's interpolation mode.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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) { pub fn set_interpolation(&mut self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) }; unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) };
} }
/// Set the channel's volume mix. /// 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<dyn Error>> {
/// #
/// 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) { pub fn set_mix(&mut self, mix: &AudioMix) {
unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) } 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<dyn Error>> {
/// #
/// 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) { pub fn set_sample_rate(&mut self, rate: f32) {
unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) }; unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) };
} }
// `ndspChnSetAdpcmCoefs` isn't wrapped on purpose. // TODO: wrap ADPCM format helpers.
// DSPADPCM is a proprietary format used by Nintendo, unavailable by "normal" means.
// We suggest using other wave formats when developing homebrew applications.
/// Clear the wave buffer queue and stop playback. /// Clear the wave buffer queue and stop playback.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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) { pub fn clear_queue(&mut self) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) }; unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) };
} }
@ -204,11 +488,38 @@ impl Channel<'_> {
/// ///
/// # Warning /// # Warning
/// ///
/// `libctru` expects the user to manually keep the info data (in this case [Wave]) alive during playback. /// `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. /// 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<dyn Error>> {
/// #
/// # 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> { pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), NdspError> {
match wave.status() { 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. /// 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<'_> { impl Channel<'_> {
/// Enables/disables monopole filters. /// Enables/disables monopole filters.
#[doc(alias = "ndspChnIirMonoSetEnable")]
pub fn iir_mono_set_enabled(&mut self, enable: bool) { pub fn iir_mono_set_enabled(&mut self, enable: bool) {
unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) }; 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 /// # Notes
/// ///
/// This is a lower quality filter than the Biquad alternative. /// 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) { 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) }; 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 /// # Notes
/// ///
/// This is a lower quality filter than the Biquad alternative. /// 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) { 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) }; unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) };
} }
/// Enables/disables biquad filters. /// Enables/disables biquad filters.
#[doc(alias = "ndspChnIirBiquadSetEnable")]
pub fn iir_biquad_set_enabled(&mut self, enable: bool) { pub fn iir_biquad_set_enabled(&mut self, enable: bool) {
unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) }; 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) { pub fn iir_biquad_set_params_high_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality) 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) { pub fn iir_biquad_set_params_low_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality) 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) { pub fn iir_biquad_set_params_notch_filter(&mut self, notch_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality) 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) { pub fn iir_biquad_set_params_band_pass_filter(&mut self, mid_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality) 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( pub fn iir_biquad_set_params_peaking_equalizer(
&mut self, &mut self,
central_freq: f32, central_freq: f32,
@ -301,9 +621,10 @@ impl Channel<'_> {
impl AudioFormat { impl AudioFormat {
/// Returns the amount of bytes needed to store one sample /// Returns the amount of bytes needed to store one sample
/// ///
/// Eg. /// # Example
/// 8 bit mono formats return 1 (byte) ///
/// 16 bit stereo (dual-channel) formats return 4 (bytes) /// - 8 bit mono formats return 1 (byte)
/// - 16 bit stereo (dual-channel) formats return 4 (bytes)
pub const fn size(self) -> usize { pub const fn size(self) -> usize {
match self { match self {
Self::PCM8Mono => 1, Self::PCM8Mono => 1,
@ -314,7 +635,7 @@ impl AudioFormat {
} }
impl AudioMix { 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 { pub fn zeroed() -> Self {
Self { raw: [0.; 12] } Self { raw: [0.; 12] }
} }
@ -361,34 +682,34 @@ impl AudioMix {
(self.raw[index], self.raw[index + 1]) (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 /// # Notes
/// ///
/// [Channel] will normalize the mix values to be within 0 and 1. /// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid. /// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_front(&mut self, left: f32, right: f32) { pub fn set_front(&mut self, left: f32, right: f32) {
self.raw[0] = left; self.raw[0] = left;
self.raw[1] = right; 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 /// # Notes
/// ///
/// [Channel] will normalize the mix values to be within 0 and 1. /// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid. /// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_back(&mut self, left: f32, right: f32) { pub fn set_back(&mut self, left: f32, right: f32) {
self.raw[2] = left; self.raw[2] = left;
self.raw[3] = right; 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 /// # Notes
/// ///
/// [Channel] will normalize the mix values to be within 0 and 1. /// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid. /// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) { pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) {
if id > 1 { if id > 1 {
panic!("invalid auxiliary output device index") panic!("invalid auxiliary output device index")
@ -400,12 +721,12 @@ impl AudioMix {
self.raw[index + 1] = right; 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 /// # Notes
/// ///
/// [Channel] will normalize the mix values to be within 0 and 1. /// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid. /// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) { pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) {
if id > 1 { if id > 1 {
panic!("invalid auxiliary output device index") 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 { 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 { fn default() -> Self {
let mut mix = AudioMix::zeroed(); let mut mix = AudioMix::zeroed();
mix.set_front(1.0, 1.0); mix.set_front(1.0, 1.0);
@ -448,6 +769,7 @@ impl fmt::Display for NdspError {
impl error::Error for NdspError {} impl error::Error for NdspError {}
impl Drop for Ndsp { impl Drop for Ndsp {
#[doc(alias = "ndspExit")]
fn drop(&mut self) { fn drop(&mut self) {
for i in 0..NUMBER_OF_CHANNELS { for i in 0..NUMBER_OF_CHANNELS {
self.channel(i).unwrap().reset(); self.channel(i).unwrap().reset();

85
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 super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator; 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 { pub struct Wave {
/// Data block of the audio wave (and its format information). /// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
@ -13,16 +19,36 @@ pub struct Wave {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
/// Enum representing the playback status of a [Wave]. /// Playback status of a [`Wave`].
pub enum WaveStatus { pub enum Status {
/// Wave has never been used.
Free = ctru_sys::NDSP_WBUF_FREE as u8, Free = ctru_sys::NDSP_WBUF_FREE as u8,
/// Wave is currently queued for usage.
Queued = ctru_sys::NDSP_WBUF_QUEUED as u8, Queued = ctru_sys::NDSP_WBUF_QUEUED as u8,
/// Wave is currently playing.
Playing = ctru_sys::NDSP_WBUF_PLAYING as u8, Playing = ctru_sys::NDSP_WBUF_PLAYING as u8,
/// Wave has finished playing.
Done = ctru_sys::NDSP_WBUF_DONE as u8, Done = ctru_sys::NDSP_WBUF_DONE as u8,
} }
impl Wave { 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( pub fn new(
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat, 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] { pub fn get_buffer(&self) -> &[u8] {
&self.buffer &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 /// # 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. /// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> {
match self.status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { Status::Playing | Status::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
} }
_ => Ok(&mut self.buffer), _ => Ok(&mut self.buffer),
} }
} }
/// Return this wave's playback status. /// Returns this wave's playback status.
pub fn status(&self) -> WaveStatus { ///
/// # 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() 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 /// # 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 { pub fn sample_count(&self) -> usize {
self.raw_data.nsamples as 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 { pub fn format(&self) -> AudioFormat {
self.audio_format self.audio_format
} }
@ -107,20 +152,20 @@ impl Wave {
} }
/// Set the amount of samples to be read. /// Set the amount of samples to be read.
/// This function doesn't resize the internal buffer.
/// ///
/// # Note /// # 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 /// # Errors
/// ///
/// This function will return an error if the sample size exceeds the buffer's capacity /// 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> { pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> {
match self.status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { Status::Playing | Status::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); return Err(NdspError::WaveBusy(self.played_on_channel.unwrap()));
} }
_ => (), _ => (),
@ -138,7 +183,7 @@ impl Wave {
} }
} }
impl TryFrom<u8> for WaveStatus { impl TryFrom<u8> for Status {
type Error = &'static str; type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
@ -157,7 +202,7 @@ impl Drop for Wave {
// This was the only way I found I could check for improper drops of `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`. // A panic was considered, but it would cause issues with drop order against `Ndsp`.
match self.status() { match self.status() {
WaveStatus::Free | WaveStatus::Done => (), Status::Free | Status::Done => (),
// If the status flag is "unfinished" // If the status flag is "unfinished"
_ => { _ => {
// The unwrap is safe, since it must have a value in the case the status is "unfinished". // The unwrap is safe, since it must have a value in the case the status is "unfinished".

101
ctru-rs/src/services/ps.rs

@ -1,40 +1,80 @@
//! Process Services (PS) module. This is used for miscellaneous utility tasks, but //! Process Services.
//! is particularly important because it is used to generate random data, which //!
//! is required for common things like [`HashMap`](std::collections::HashMap). //! 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 <https://www.3dbrew.org/wiki/Process_Services> //! See also <https://www.3dbrew.org/wiki/Process_Services>
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::Result; use crate::Result;
/// Type of AES algorithm to use.
#[doc(alias = "PS_AESAlgorithm")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum AESAlgorithm { pub enum AESAlgorithm {
/// CBC encryption.
CbcEnc = ctru_sys::PS_ALGORITHM_CBC_ENC, CbcEnc = ctru_sys::PS_ALGORITHM_CBC_ENC,
/// CBC decryption.
CbcDec = ctru_sys::PS_ALGORITHM_CBC_DEC, CbcDec = ctru_sys::PS_ALGORITHM_CBC_DEC,
/// CTR encryption.
CtrEnc = ctru_sys::PS_ALGORITHM_CTR_ENC, CtrEnc = ctru_sys::PS_ALGORITHM_CTR_ENC,
/// CTR decryption.
CtrDec = ctru_sys::PS_ALGORITHM_CTR_DEC, CtrDec = ctru_sys::PS_ALGORITHM_CTR_DEC,
/// CCM encryption.
CcmEnc = ctru_sys::PS_ALGORITHM_CCM_ENC, CcmEnc = ctru_sys::PS_ALGORITHM_CCM_ENC,
/// CCM decryption.
CcmDec = ctru_sys::PS_ALGORITHM_CCM_DEC, CcmDec = ctru_sys::PS_ALGORITHM_CCM_DEC,
} }
/// PS Key slot to use.
#[doc(alias = "PS_AESKeyType")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum AESKeyType { pub enum AESKeyType {
/// Keyslot 0x0D.
Keyslot0D = ctru_sys::PS_KEYSLOT_0D, Keyslot0D = ctru_sys::PS_KEYSLOT_0D,
/// Keyslot 0x2D.
Keyslot2D = ctru_sys::PS_KEYSLOT_2D, Keyslot2D = ctru_sys::PS_KEYSLOT_2D,
/// Keyslot 0x2E.
Keyslot2E = ctru_sys::PS_KEYSLOT_2E, Keyslot2E = ctru_sys::PS_KEYSLOT_2E,
/// Keyslot 0x31.
Keyslot31 = ctru_sys::PS_KEYSLOT_31, Keyslot31 = ctru_sys::PS_KEYSLOT_31,
/// Keyslot 0x32.
Keyslot32 = ctru_sys::PS_KEYSLOT_32, Keyslot32 = ctru_sys::PS_KEYSLOT_32,
/// Keyslot 0x36.
Keyslot36 = ctru_sys::PS_KEYSLOT_36, Keyslot36 = ctru_sys::PS_KEYSLOT_36,
/// Keyslot 0x38.
Keyslot38 = ctru_sys::PS_KEYSLOT_38, Keyslot38 = ctru_sys::PS_KEYSLOT_38,
/// Keyslot 0x39 (DLP).
Keyslot39Dlp = ctru_sys::PS_KEYSLOT_39_DLP, Keyslot39Dlp = ctru_sys::PS_KEYSLOT_39_DLP,
/// Keyslot 0x39 (NFC).
Keyslot39Nfc = ctru_sys::PS_KEYSLOT_39_NFC, Keyslot39Nfc = ctru_sys::PS_KEYSLOT_39_NFC,
/// Invalid keyslot.
KeyslotInvalid = ctru_sys::PS_KEYSLOT_INVALID, KeyslotInvalid = ctru_sys::PS_KEYSLOT_INVALID,
} }
/// Handle to the PS service.
pub struct Ps(()); pub struct Ps(());
impl Ps { impl Ps {
/// Initialize a new service handle.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ps::Ps;
///
/// let ps = Ps::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "psInit")]
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
unsafe { unsafe {
ResultCode(ctru_sys::psInit())?; 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<dyn Error>> {
/// #
/// 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<u64> { pub fn local_friend_code_seed(&self) -> crate::Result<u64> {
let mut seed: u64 = 0; let mut seed: u64 = 0;
@ -49,6 +106,23 @@ impl Ps {
Ok(seed) Ok(seed)
} }
/// Returns the console's devide ID.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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<u32> { pub fn device_id(&self) -> crate::Result<u32> {
let mut id: u32 = 0; let mut id: u32 = 0;
@ -56,6 +130,26 @@ impl Ps {
Ok(id) 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<dyn Error>> {
/// #
/// 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<()> { pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> {
ResultCode(unsafe { ResultCode(unsafe {
ctru_sys::PS_GenerateRandomBytes(out.as_mut_ptr().cast(), out.len()) ctru_sys::PS_GenerateRandomBytes(out.as_mut_ptr().cast(), out.len())
@ -65,6 +159,7 @@ impl Ps {
} }
impl Drop for Ps { impl Drop for Ps {
#[doc(alias = "psExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
ctru_sys::psExit(); ctru_sys::psExit();

41
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:/<file-path>`.
//!
//! # Usage
//! //!
//! This module only gets compiled if the configured RomFS directory is found and the `romfs` //! This module only gets compiled if the configured RomFS directory is found and the `romfs`
//! feature is enabled. //! 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 //! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of
//! your package. //! your package.
//! //!
@ -11,6 +16,16 @@
//! [package.metadata.cargo-3ds] //! [package.metadata.cargo-3ds]
//! romfs_dir = "romfs" //! 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 crate::error::ResultCode;
use std::ffi::CStr; use std::ffi::CStr;
@ -18,6 +33,7 @@ use std::sync::Mutex;
use crate::services::ServiceReference; use crate::services::ServiceReference;
/// Handle to the RomFS service.
pub struct RomFS { pub struct RomFS {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
@ -25,6 +41,25 @@ pub struct RomFS {
static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0); static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0);
impl RomFS { impl RomFS {
/// Mount the bundled RomFS archive as a virtual drive.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&ROMFS_ACTIVE, &ROMFS_ACTIVE,
@ -36,7 +71,7 @@ impl RomFS {
}, },
|| { || {
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); 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()) };
}, },
)?; )?;

104
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 libc::memalign;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
@ -8,10 +13,7 @@ use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use crate::Error; use crate::Error;
/// Network socket service /// Handle to the 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.
pub struct Soc { pub struct Soc {
_service_handler: ServiceReference, _service_handler: ServiceReference,
sock_3dslink: libc::c_int, sock_3dslink: libc::c_int,
@ -20,21 +22,52 @@ pub struct Soc {
static SOC_ACTIVE: Mutex<usize> = Mutex::new(0); static SOC_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Soc { 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 /// # 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<dyn Error>> {
/// #
/// use ctru::services::soc::Soc;
///
/// let soc = Soc::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "socInit")]
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
Self::init_with_buffer_size(0x100000) Self::init_with_buffer_size(0x100000)
} }
/// Initialize the Soc service with a custom buffer size in bytes. The size should be /// Initialize a new service handle using a custom socket buffer size.
/// 0x100000 bytes or greater. ///
/// The size should be `0x100000` bytes or greater.
/// ///
/// # Errors /// # 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<dyn Error>> {
/// #
/// 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<Self> { pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&SOC_ACTIVE, &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<dyn Error>> {
/// #
/// use ctru::services::soc::Soc;
/// let soc = Soc::new()?;
///
/// let address = soc.host_address();
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "gethostid")]
pub fn host_address(&self) -> Ipv4Addr { pub fn host_address(&self) -> Ipv4Addr {
let raw_id = unsafe { libc::gethostid() }; let raw_id = unsafe { libc::gethostid() };
Ipv4Addr::from(raw_id.to_ne_bytes()) Ipv4Addr::from(raw_id.to_ne_bytes())
} }
/// Redirect output streams (i.e. [`println`] and [`eprintln`]) to the `3dslink` server. /// Redirect output streams (i.e. `stdout` and `stderr`) to the `3dslink` server.
/// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0. ///
/// 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 /// # Errors
/// ///
/// Returns an error if a connection cannot be established to the server, or /// Returns an error if a connection cannot be established to the server,
/// output was already previously redirected. /// or if the output was already being redirected.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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<()> { pub fn redirect_to_3dslink(&mut self, stdout: bool, stderr: bool) -> crate::Result<()> {
if self.sock_3dslink >= 0 { if self.sock_3dslink >= 0 {
return Err(Error::OutputAlreadyRedirected); return Err(Error::OutputAlreadyRedirected);
@ -92,6 +165,7 @@ impl Soc {
} }
impl Drop for Soc { impl Drop for Soc {
#[doc(alias = "socExit")]
fn drop(&mut self) { fn drop(&mut self) {
if self.sock_3dslink >= 0 { if self.sock_3dslink >= 0 {
unsafe { unsafe {

21
ctru-rs/src/services/sslc.rs

@ -1,13 +1,29 @@
//! SSLC (TLS) service //! SSLC (TLS) service.
// TODO: Implement remaining functions // TODO: Implement remaining functions
use crate::error::ResultCode; use crate::error::ResultCode;
/// Handle to the SSLC service.
pub struct SslC(()); pub struct SslC(());
impl SslC { impl SslC {
/// Initialize the service /// Initialize a new service handle.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::sslc::SslC;
///
/// let sslc = SslC::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "sslcInit")]
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
unsafe { unsafe {
ResultCode(ctru_sys::sslcInit(0))?; ResultCode(ctru_sys::sslcInit(0))?;
@ -17,6 +33,7 @@ impl SslC {
} }
impl Drop for SslC { impl Drop for SslC {
#[doc(alias = "sslcExit")]
fn drop(&mut self) { fn drop(&mut self) {
unsafe { ctru_sys::sslcExit() }; unsafe { ctru_sys::sslcExit() };
} }

10
ctru-sys/Cargo.toml

@ -2,6 +2,11 @@
name = "ctru-sys" name = "ctru-sys"
version = "22.2.0+2.2.2-1" version = "22.2.0+2.2.2-1"
authors = [ "Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>" ] authors = [ "Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>" ]
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 = ["bindgen.sh", "src/.gitattributes"]
license = "Zlib" license = "Zlib"
links = "ctru" links = "ctru"
edition = "2021" edition = "2021"
@ -11,3 +16,8 @@ libc = { version = "0.2.121", default-features = false }
[build-dependencies] [build-dependencies]
which = "4.4.0" which = "4.4.0"
[package.metadata.docs.rs]
default-target = "armv6k-nintendo-3ds"
targets = []
cargo-args = ["-Z", "build-std"]

33
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.

5
ctru-sys/build.rs

@ -77,8 +77,9 @@ fn check_libctru_version() -> Result<(String, String, String), Box<dyn Error>> {
.output()?; .output()?;
for line in String::from_utf8_lossy(&stdout).split('\n') { for line in String::from_utf8_lossy(&stdout).split('\n') {
let Some((_pkg, file)) = line.split_once(char::is_whitespace) let Some((_pkg, file)) = line.split_once(char::is_whitespace) else {
else { continue }; continue;
};
println!("cargo:rerun-if-changed={file}"); println!("cargo:rerun-if-changed={file}");
} }

Loading…
Cancel
Save