Browse Source

Merge branch 'master' into static-inline-fns

pull/133/head
Meziu 1 year ago committed by GitHub
parent
commit
ac505801fc
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

4
.github/workflows/ci.yml

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

1
.gitignore vendored

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

1
Cargo.toml

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

28
README.md

@ -1,33 +1,27 @@ @@ -1,33 +1,27 @@
# ctru-rs
A Rust wrapper library for smealum's [ctrulib](https://github.com/smealum/ctrulib).
This repository is home of the `ctru-rs` project, which aims to bring full control of the Nintendo 3DS console to homebrew developers using Rust.
## Structure
This repository is organized as follows:
* `ctru-rs`: Safe, idiomatic wrapper around `ctru-sys`
* [`ctru-rs`](./ctru-rs/) - Safe, idiomatic wrapper around [`ctru-sys`](./ctru-sys/).
* [`ctru-sys`](./ctru-sys/) - Low-level, unsafe bindings to [`libctru`](https://github.com/devkitPro/libctru).
* `ctru-sys`: Low-level, unsafe bindings to ctrulib.
## Getting Started
This crate's version changes according to the version of `libctru`
used to generate the bindings, with the following convention:
* `libctru` version `X.Y.Z-W`
* `ctru-sys` version `XY.Z.P+X.Y.Z-W`
where `P` is usually 0 but may be incremented for fixes in e.g.
binding generation, `libc` dependency bump, etc.
It may be possible to build this crate against a different version of `libctru`,
but you may encounter linker errors or ABI issues. A build-time Cargo warning
(displayed when built with `-vv`) will be issued if the build script detects
a mismatch or is unable to check the installed `libctru` version.
Specific information about how to use the crates is present in the individual README for each package.
Have a look at `ctru-rs`' [README.md](./ctru-rs/README.md) for a broad overview.
## Original version
This project is based on the efforts the original authors:
This project is based on the efforts of the original authors:
* [Eidolon](https://github.com/HybridEidolon)
* [FenrirWolf](https://github.com/FenrirWolf)
The old version is archived [here](https://github.com/rust3ds/ctru-rs-old).
## License
This project is distributed under the Zlib license.

19
ctru-rs/Cargo.toml

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

22
ctru-rs/README.md

@ -0,0 +1,22 @@ @@ -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 @@ @@ -1,3 +1,7 @@
//! Audio Filters example.
//!
//! This example showcases basic audio functionality using [`Ndsp`].
#![feature(allocator_api)]
use std::f32::consts::PI;
@ -5,21 +9,22 @@ use std::f32::consts::PI; @@ -5,21 +9,22 @@ use std::f32::consts::PI;
use ctru::linear::LinearAllocator;
use ctru::prelude::*;
use ctru::services::ndsp::{
wave::{Wave, WaveStatus},
wave::{Status, Wave},
AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode,
};
// Configuration for the NDSP process and channels.
const SAMPLE_RATE: usize = 22050;
const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205
const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size();
const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE;
// Note Frequencies
// Note frequencies.
const NOTEFREQ: [f32; 7] = [220., 440., 880., 1760., 3520., 7040., 14080.];
// The audio format is Stereo PCM16
// As such, a sample is made up of 2 "Mono" samples (2 * i16 = u32), one for each channel (left and right)
fn fill_buffer(audio_data: &mut [u8], frequency: f32) {
// The audio format is Stereo PCM16.
// As such, a sample is made up of 2 "Mono" samples (2 * i16), one for each channel (left and right).
let formatted_data = bytemuck::cast_slice_mut::<_, [i16; 2]>(audio_data);
for (i, chunk) in formatted_data.iter_mut().enumerate() {
@ -44,8 +49,7 @@ fn main() { @@ -44,8 +49,7 @@ fn main() {
let mut note: usize = 4;
// Filters
// Filter names to display.
let filter_names = [
"None",
"Low-Pass",
@ -60,19 +64,26 @@ fn main() { @@ -60,19 +64,26 @@ fn main() {
// We set up two wave buffers and alternate between the two,
// effectively streaming an infinitely long sine wave.
// We create a buffer on the LINEAR memory that will hold our audio data.
// It's necessary for the buffer to live on the LINEAR memory sector since it needs to be accessed by the DSP processor.
let mut audio_data1 = Box::new_in([0u8; AUDIO_WAVE_LENGTH], LinearAllocator);
// Fill the buffer with the first set of data. This simply writes a sine wave into the buffer.
fill_buffer(audio_data1.as_mut_slice(), NOTEFREQ[4]);
// Clone the original buffer to obtain an equal buffer on the LINEAR memory used for double buffering.
let audio_data2 = audio_data1.clone();
// Setup two wave info objects with the correct configuration and ownership of the audio data.
let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = Wave::new(audio_data2, AudioFormat::PCM16Stereo, false);
let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller");
// Setup the NDSP service and its configuration.
// This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo`
let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller");
ndsp.set_output_mode(OutputMode::Stereo);
// Channel configuration. We use channel zero but any channel would do just fine.
let mut channel_zero = ndsp.channel(0).unwrap();
channel_zero.set_interpolation(InterpolationType::Linear);
channel_zero.set_sample_rate(SAMPLE_RATE as f32);
@ -82,6 +93,7 @@ fn main() { @@ -82,6 +93,7 @@ fn main() {
let mix = AudioMix::default();
channel_zero.set_mix(&mix);
// First set of queueing for the two buffers. The second one will only play after the first one has ended.
channel_zero.queue_wave(&mut wave_info1).unwrap();
channel_zero.queue_wave(&mut wave_info2).unwrap();
@ -93,6 +105,8 @@ fn main() { @@ -93,6 +105,8 @@ fn main() {
filter_names[filter as usize]
);
println!("\x1b[29;16HPress Start to exit");
let mut altern = true; // true is wave_info1, false is wave_info2
while apt.main_loop() {
@ -101,14 +115,16 @@ fn main() { @@ -101,14 +115,16 @@ fn main() {
if keys_down.contains(KeyPad::START) {
break;
} // break in order to return to hbmenu
}
// Note frequency controller using the buttons.
if keys_down.intersects(KeyPad::DOWN) {
note = note.saturating_sub(1);
} else if keys_down.intersects(KeyPad::UP) {
note = std::cmp::min(note + 1, NOTEFREQ.len() - 1);
}
// Filter controller using the buttons.
let mut update_params = false;
if keys_down.intersects(KeyPad::LEFT) {
filter -= 1;
@ -139,14 +155,16 @@ fn main() { @@ -139,14 +155,16 @@ fn main() {
}
}
// Double buffer alternation depending on the one used.
let current: &mut Wave = if altern {
&mut wave_info1
} else {
&mut wave_info2
};
// If the current buffer has finished playing, we can refill it with new data and re-queue it.
let status = current.status();
if let WaveStatus::Done = status {
if let Status::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);
channel_zero.queue_wave(current).unwrap();
@ -154,7 +172,6 @@ fn main() { @@ -154,7 +172,6 @@ fn main() {
altern = !altern;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

24
ctru-rs/examples/buttons.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! Buttons example.
//!
//! This example showcases how to retrieve button inputs from the console's HID.
use ctru::prelude::*;
fn main() {
@ -18,28 +22,28 @@ fn main() { @@ -18,28 +22,28 @@ fn main() {
// Scan for user input on the current frame.
hid.scan_input();
// Get information about which keys were held down on this frame
// Get information about which keys were held down on this frame.
let keys = hid.keys_held();
// We only want to print when the keys we're holding now are different
// from what they were on the previous frame
// from what they were on the previous frame.
if keys != old_keys {
// Clear the screen
// Clear the screen.
console.clear();
// We print these again because we just cleared the screen above
// We print these again because we just cleared the screen above.
println!("Hi there! Try pressing a button");
println!("\x1b[29;16HPress Start to exit");
// Move the cursor back to the top of the screen
// Move the cursor back to the top of the screen.
println!("\x1b[3;0H");
// Print to the screen depending on which keys were held.
//
// The .contains() method checks for all of the provided keys,
// and the .intersects() method checks for any of the provided keys.
// The `.contains()` method checks for all of the provided keys,
// and the `.intersects()` method checks for any of the provided keys.
//
// You can also use the .bits() method to do direct comparisons on
// You can also use the `.bits()` method to do direct comparisons on
// the underlying bits
if keys.contains(KeyPad::A) {
@ -54,13 +58,13 @@ fn main() { @@ -54,13 +58,13 @@ fn main() {
if keys.intersects(KeyPad::L | KeyPad::R | KeyPad::ZL | KeyPad::ZR) {
println!("You held a shoulder button!");
}
if keys.intersects(KeyPad::START) {
if keys.contains(KeyPad::START) {
println!("See ya!");
break;
}
}
// Save our current key presses for the next frame
// Save our current key presses for the next frame.
old_keys = keys;
gfx.wait_for_vblank();

22
ctru-rs/examples/camera-image.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! Camera image example.
//!
//! This example demonstrates how to use the built-in cameras to take a picture and display it to the screen.
use ctru::prelude::*;
use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize};
use ctru::services::gfx::{Flush, Screen, Swap};
@ -8,7 +12,7 @@ use std::time::Duration; @@ -8,7 +12,7 @@ use std::time::Duration;
const WIDTH: usize = 400;
const HEIGHT: usize = 240;
// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes)
// The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes).
const BUF_SIZE: usize = WIDTH * HEIGHT * 2;
const WAIT_TIMEOUT: Duration = Duration::from_millis(300);
@ -26,12 +30,11 @@ fn main() { @@ -26,12 +30,11 @@ fn main() {
let _console = Console::new(gfx.bottom_screen.borrow_mut());
let mut keys_down;
println!("Initializing camera");
let mut cam = Cam::new().expect("Failed to initialize CAM service.");
// Camera setup.
{
let camera = &mut cam.outer_right_cam;
@ -58,21 +61,23 @@ fn main() { @@ -58,21 +61,23 @@ fn main() {
let mut buf = vec![0u8; BUF_SIZE];
println!("\nPress R to take a new picture");
println!("Press Start to exit to Homebrew Launcher");
println!("Press Start to exit");
while apt.main_loop() {
hid.scan_input();
keys_down = hid.keys_down();
let keys_down = hid.keys_down();
if keys_down.contains(KeyPad::START) {
break;
}
// If the user presses the R button.
if keys_down.contains(KeyPad::R) {
println!("Capturing new image");
let camera = &mut cam.outer_right_cam;
// Take a picture and write it to the buffer.
camera
.take_picture(
&mut buf,
@ -82,12 +87,14 @@ fn main() { @@ -82,12 +87,14 @@ fn main() {
)
.expect("Failed to take picture");
// Play the normal shutter sound.
cam.play_shutter_sound(ShutterSound::Normal)
.expect("Failed to play shutter sound");
// Rotate the image and correctly display it on the screen.
rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT);
// We will only flush the "camera" screen, since the other screen is handled by `Console`
// We will only flush and swap the "camera" screen, since the other screen is handled by the `Console`.
top_screen.flush_buffers();
top_screen.swap_buffers();
@ -99,6 +106,7 @@ fn main() { @@ -99,6 +106,7 @@ fn main() {
// The 3DS' screens are 2 vertical LCD panels rotated by 90 degrees.
// As such, we'll need to write a "vertical" image to the framebuffer to have it displayed properly.
// This functions rotates an horizontal image by 90 degrees to the right.
// This function is only supposed to be used in this example. In a real world application, the program should use the GPU to draw to the screen.
fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: usize) {
for j in 0..height {
for i in 0..width {
@ -115,7 +123,7 @@ fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: u @@ -115,7 +123,7 @@ fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: u
let draw_index = (draw_x * height + draw_y) * 2; // This 2 stands for the number of bytes per pixel (16 bits)
unsafe {
// We'll work with pointers since the frambuffer is a raw pointer regardless.
// We'll work with pointers since the framebuffer is a raw pointer regardless.
// The offsets are completely safe as long as the width and height are correct.
let pixel_pointer = framebuf.offset(draw_index as isize);
pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2);

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

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
//! A file explorer which shows off using standard library file system APIs to
//! read the SD card.
//! File Explorer example.
//!
//! This (rather complex) example creates a working text-based file explorer which shows off using standard library file system APIs to
//! read the SD card and RomFS (if properly read via the `romfs:/` prefix).
use ctru::applets::swkbd::{Button, Swkbd};
use ctru::applets::swkbd::{Button, SoftwareKeyboard};
use ctru::prelude::*;
use std::fs::DirEntry;
@ -15,6 +17,7 @@ fn main() { @@ -15,6 +17,7 @@ fn main() {
let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap();
// Mount the RomFS if available.
#[cfg(all(feature = "romfs", romfs_exists))]
let _romfs = ctru::services::romfs::RomFS::new().unwrap();
@ -50,6 +53,7 @@ impl<'a> FileExplorer<'a> { @@ -50,6 +53,7 @@ impl<'a> FileExplorer<'a> {
fn run(&mut self) {
self.running = true;
// Print the file explorer commands.
self.print_menu();
while self.running && self.apt.main_loop() {
@ -62,8 +66,10 @@ impl<'a> FileExplorer<'a> { @@ -62,8 +66,10 @@ impl<'a> FileExplorer<'a> {
self.path.pop();
self.console.clear();
self.print_menu();
// Open a directory/file to read.
} else if input.contains(KeyPad::A) {
self.get_input_and_run(Self::set_next_path);
// Open a specific path using the `SoftwareKeyboard` applet.
} else if input.contains(KeyPad::X) {
self.get_input_and_run(Self::set_exact_path);
}
@ -100,7 +106,7 @@ impl<'a> FileExplorer<'a> { @@ -100,7 +106,7 @@ impl<'a> FileExplorer<'a> {
}
};
println!("Start to exit, A to select an entry by number, B to go up a directory, X to set the path.");
println!("Press Start to exit, A to select an entry by number, B to go up a directory, X to set the path.");
}
fn print_dir_entries(&mut self) {
@ -137,7 +143,7 @@ impl<'a> FileExplorer<'a> { @@ -137,7 +143,7 @@ impl<'a> FileExplorer<'a> {
}
}
/// Paginate output
// Paginate output.
fn wait_for_page_down(&mut self) {
println!("Press A to go to next page, or Start to exit");
@ -159,18 +165,18 @@ impl<'a> FileExplorer<'a> { @@ -159,18 +165,18 @@ impl<'a> FileExplorer<'a> {
}
fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) {
let mut keyboard = Swkbd::default();
let mut keyboard = SoftwareKeyboard::default();
match keyboard.get_string(2048) {
Ok((path, Button::Right)) => {
// Clicked "OK"
// Clicked "OK".
action(self, path);
}
Ok((_, Button::Left)) => {
// Clicked "Cancel"
// Clicked "Cancel".
}
Ok((_, Button::Middle)) => {
// This button wasn't shown
// This button wasn't shown.
unreachable!()
}
Err(e) => {

31
ctru-rs/examples/gfx-3d-mode.rs

@ -1,11 +1,18 @@ @@ -1,11 +1,18 @@
//! 3D Graphics example.
//!
//! This example showcases 3D mode rendering (using the CPU).
//! In a normal application, all rendering should be handled via the GPU.
//!
//! See `gfx-bitmap.rs` for details on how the image is generated.
//!
//! # Warning
//!
//! This example uses 3D mode in a rather unnatural way, and should
//! probably not be viewed for too long or at all if you are photosensitive.
use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D};
/// See `graphics-bitmap.rs` for details on how the image is generated.
///
/// WARNING: this example uses 3D mode in a rather unnatural way, and should
/// probably not be viewed for too long or at all if you are photosensitive.
const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
static ZERO: &[u8] = &[0; IMAGE.len()];
@ -17,7 +24,9 @@ fn main() { @@ -17,7 +24,9 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.bottom_screen.borrow_mut());
println!("Press Start to exit.\nPress A to switch sides (be sure to have 3D mode enabled).");
println!("Press A to switch sides.");
println!("Make sure to have set the 3D slider correctly");
println!("\x1b[29;12HPress Start to exit");
gfx.top_screen.borrow_mut().set_double_buffering(true);
@ -25,39 +34,40 @@ fn main() { @@ -25,39 +34,40 @@ fn main() {
let mut current_side = Side::Left;
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Split the TopScreen3D to get references to the two render surfaces.
let (mut left, mut right) = top_screen.split_mut();
let left_buf = left.raw_framebuffer();
let right_buf = right.raw_framebuffer();
// Clear both buffers every time, in case the user switches sides this loop
// Clear both buffers every time, in case the user switches sides this loop.
unsafe {
left_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len());
right_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len());
}
if hid.keys_down().contains(KeyPad::A) {
// flip which buffer we're writing to
// Switch which buffer we're writing to.
current_side = match current_side {
Side::Left => Side::Right,
Side::Right => Side::Left,
};
}
// Obtain the framebuffer of the currently rendered side.
let buf = match current_side {
Side::Left => left_buf.ptr,
Side::Right => right_buf.ptr,
};
// Render the image to the surface's buffer.
unsafe {
buf.copy_from(IMAGE.as_ptr(), IMAGE.len());
}
@ -67,7 +77,6 @@ fn main() { @@ -67,7 +77,6 @@ fn main() {
top_screen.flush_buffers();
top_screen.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}
}

28
ctru-rs/examples/gfx-bitmap.rs

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
/// Bitmap Graphics example.
///
/// This example uses the CPU to render a simple bitmap image to the screen.
use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Swap};
@ -22,7 +25,8 @@ fn main() { @@ -22,7 +25,8 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
println!("\x1b[21;4HPress Start to exit, or A to flip the image.");
println!("\x1b[21;4HPress A to flip the image.");
println!("\x1b[29;16HPress Start to exit");
let mut bottom_screen = gfx.bottom_screen.borrow_mut();
@ -37,38 +41,44 @@ fn main() { @@ -37,38 +41,44 @@ fn main() {
let mut image_bytes = IMAGE;
// Main loop
// We assume the image is the correct size already, so we drop width + height.
let frame_buffer = bottom_screen.raw_framebuffer();
// We copy the image to the framebuffer.
unsafe {
frame_buffer
.ptr
.copy_from(image_bytes.as_ptr(), image_bytes.len());
}
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
// We assume the image is the correct size already, so we drop width + height.
let frame_buffer = bottom_screen.raw_framebuffer();
if hid.keys_down().contains(KeyPad::A) {
image_bytes = if std::ptr::eq(image_bytes, IMAGE) {
&flipped_image[..]
} else {
IMAGE
};
}
// this copies more than necessary (once per frame) but it's fine...
let frame_buffer = bottom_screen.raw_framebuffer();
// We render the newly switched image to the framebuffer.
unsafe {
frame_buffer
.ptr
.copy_from(image_bytes.as_ptr(), image_bytes.len());
}
}
// Flush framebuffers. Since we're not using double buffering,
// this will render the pixels immediately
bottom_screen.flush_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}
}

12
ctru-rs/examples/gfx-wide-mode.rs

@ -1,3 +1,10 @@ @@ -1,3 +1,10 @@
//! Wide-Mode Graphics example.
//!
//! This example demonstrates the wide-mode capability of the top screen
//! which doubles the horizontal resolution of the screen by merging the 2 stereoscopic 3D sides.
//!
//! Beware, wide-mode doesn't work on Old 2DS consoles.
use ctru::prelude::*;
fn main() {
@ -9,6 +16,7 @@ fn main() { @@ -9,6 +16,7 @@ fn main() {
let mut console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enable/disable wide screen mode.");
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
hid.scan_input();
@ -17,14 +25,18 @@ fn main() { @@ -17,14 +25,18 @@ fn main() {
break;
}
// Since we can't set wide-mode while running the console (since that would break the currently displayed text),
// we need to rebuild the console each time we want to make the switch.
if hid.keys_down().contains(KeyPad::A) {
drop(console);
// Switch the state of the wide-mode.
let wide_mode = gfx.top_screen.borrow().is_wide();
gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode);
console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enable/disable wide screen mode.");
println!("\x1b[29;16HPress Start to exit");
}
gfx.wait_for_vblank();

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

@ -1,57 +0,0 @@ @@ -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 @@ @@ -1,10 +1,16 @@
//! Hashmap example.
//!
//! This example showcases using Hashmaps on the 3DS console using the functionality implemented by the standard library.
//! While it may seem inappropriate for such a simple (and somewhat out-of-scope) example to be included here, it's important to note how
//! normally Hashmaps wouldn't work on the console, and are only capable to because of the internal implementations made by `ctru-rs`.
//!
//! As such, this example functions more closely to a test than a demonstration.
use ctru::prelude::*;
fn main() {
ctru::use_panic_handler();
// Initialize services
//
// HashMaps generate hashes thanks to the 3DS' cryptografically secure generator.
// This generator is only active when activating the `PS` service.
// This service is automatically initialized.
@ -19,6 +25,7 @@ fn main() { @@ -19,6 +25,7 @@ fn main() {
map.remove("A Key!");
println!("{map:#?}");
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
gfx.wait_for_vblank();

11
ctru-rs/examples/hello-both-screens.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! Hello World example using both screens.
//!
//! This is similar to the `hello-world` example, with the main difference of using 2 virtual `Console`s that can be alternated to print on both screens.
use ctru::prelude::*;
fn main() {
@ -11,14 +15,15 @@ fn main() { @@ -11,14 +15,15 @@ fn main() {
let top_screen = Console::new(gfx.top_screen.borrow_mut());
// Start a console on the bottom screen.
// The most recently initialized console will be active by default
// The most recently initialized console will be active by default.
let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut());
// Let's print on the top screen first
// Let's print on the top screen first.
// Since the bottom screen is currently selected (being created afterwards), it is required to select the top screen console first.
top_screen.select();
println!("This is the top screen! We have a lot of space up here!");
// Now let's print something on the bottom screen
// Now let's print something on the bottom screen.
bottom_screen.select();
println!("\x1b[14;00HThis is the bottom screen.");
println!("There's not as much space down here, but that's okay.");

20
ctru-rs/examples/hello-world.rs

@ -1,15 +1,25 @@ @@ -1,15 +1,25 @@
use ctru::prelude::*;
//! Hello World example.
//!
//! Simple "Hello World" application to showcase the basic setup needed for any user-oriented app to work.
use ctru::prelude::*;
use std::io::BufWriter;
fn main() {
// Setup the custom panic handler in case any errors arise.
// Thanks to it the user will get promptly notified of any panics.
ctru::use_panic_handler();
// Setup Graphics, Controller Inputs, Application runtime.
// These is standard setup any app would need.
let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller");
// Create a Console to print our "Hello, World!" to.
let _console = Console::new(gfx.top_screen.borrow_mut());
// Snazzy message created via `ferris_says`.
let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!";
let width = 24;
@ -21,16 +31,18 @@ fn main() { @@ -21,16 +31,18 @@ fn main() {
String::from_utf8_lossy(&writer.into_inner().unwrap())
);
// Main loop
println!("\x1b[29;16HPress Start to exit");
// Main application loop. This checks whether the app is normally running in the foreground.
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
// Scan all the controller inputs.
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
//Wait for VBlank
// Use VSync to cap the framerate at the same speed as the LCD screen's refresh rate (60 fps).
gfx.wait_for_vblank();
}
}

15
ctru-rs/examples/linear-memory.rs

@ -1,3 +1,9 @@ @@ -1,3 +1,9 @@
//! LINEAR memory example.
//!
//! This example showcases simple allocation on the LINEAR memory sector.
//! Using LINEAR memory is required when sending data to the GPU or DSP processor.
// You will need to activate this unstable feature to use custom allocators.
#![feature(allocator_api)]
use ctru::linear::LinearAllocator;
@ -11,11 +17,13 @@ fn main() { @@ -11,11 +17,13 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
// The `LinearAllocator` is always available for use.
// Luckily, we can always read how much memory is available to be allocated on it.
let linear_space_before = LinearAllocator::free_space();
// Normal `Box` on the heap
// Normal `Box` on the heap.
let heap_box = Box::new(1492);
// `Box` living on the linear memory sector
// `Box` living on the LINEAR memory.
let linear_box: Box<i32, LinearAllocator> = Box::new_in(2022, LinearAllocator);
println!("This value is from the heap: {heap_box}");
@ -29,16 +37,13 @@ fn main() { @@ -29,16 +37,13 @@ fn main() {
println!("\x1b[29;16HPress Start to exit");
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

21
ctru-rs/examples/mii-selector.rs

@ -1,4 +1,8 @@ @@ -1,4 +1,8 @@
use ctru::applets::mii_selector::{LaunchError, MiiSelector, Options};
//! Mii Selector example.
//!
//! This example showcases the use of the MiiSelector applet to obtain Mii data from the user's input.
use ctru::applets::mii_selector::{Error, MiiSelector, Options};
use ctru::prelude::*;
fn main() {
@ -9,12 +13,16 @@ fn main() { @@ -9,12 +13,16 @@ fn main() {
let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
// Setup the Mii Selector configuration.
let mut mii_selector = MiiSelector::new();
mii_selector.set_options(Options::MII_SELECTOR_CANCEL);
// The Mii Selector window can be closed without selecting a Mii.
mii_selector.set_options(Options::ENABLE_CANCEL);
mii_selector.set_initial_index(3);
// The first user-made Mii cannot be used.
mii_selector.blacklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!");
// Launch the Mii Selector and use its result to print the selected Mii's information.
match mii_selector.launch() {
Ok(result) => {
println!("Mii type: {:?}", result.mii_type);
@ -25,20 +33,19 @@ fn main() { @@ -25,20 +33,19 @@ fn main() {
result.mii_data.mole_details.is_enabled
);
}
Err(LaunchError::InvalidChecksum) => println!("Corrupt Mii selected"),
Err(LaunchError::NoMiiSelected) => println!("No Mii selected"),
Err(Error::InvalidChecksum) => println!("Corrupt Mii selected"),
Err(Error::NoMiiSelected) => println!("No Mii selected"),
}
// Main loop
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

31
ctru-rs/examples/network-sockets.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! Network Sockets example.
//!
//! This example showcases the use of network sockets via the `Soc` service and the standard library's implementations.
use ctru::prelude::*;
use std::io::{self, Read, Write};
@ -8,26 +12,35 @@ fn main() { @@ -8,26 +12,35 @@ fn main() {
ctru::use_panic_handler();
let gfx = Gfx::new().unwrap();
let _console = Console::new(gfx.top_screen.borrow_mut());
let mut hid = Hid::new().unwrap();
let apt = Apt::new().unwrap();
println!("\nlibctru sockets demo\n");
// Owning a living handle to the `Soc` service is required to use network functionalities.
let soc = Soc::new().unwrap();
// Listen on the standard HTTP port (80).
let server = TcpListener::bind("0.0.0.0:80").unwrap();
server.set_nonblocking(true).unwrap();
println!("Point your browser to http://{}/\n", soc.host_address());
let _bottom_console = Console::new(gfx.bottom_screen.borrow_mut());
println!("Point your browser at:\nhttp://{}/\n", soc.host_address());
println!("\x1b[29;12HPress Start to exit");
let _top_console = Console::new(gfx.top_screen.borrow_mut());
while apt.main_loop() {
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
};
// Receive any incoming connections.
match server.accept() {
Ok((mut stream, socket_addr)) => {
println!("Got connection from {socket_addr}");
// Print the HTTP request sent by the client (most likely, a web browser).
let mut buf = [0u8; 4096];
match stream.read(&mut buf) {
Ok(_) => {
@ -43,15 +56,18 @@ fn main() { @@ -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";
if let Err(e) = stream.write(response) {
println!("Error writing http response: {e}");
}
// Shutdown the stream (depending on the web browser used to view the page, this might cause some issues).
stream.shutdown(Shutdown::Both).unwrap();
}
Err(e) => match e.kind() {
// If the TCP socket would block execution, just try again.
std::io::ErrorKind::WouldBlock => {}
_ => {
println!("Error accepting connection: {e}");
@ -60,9 +76,6 @@ fn main() { @@ -60,9 +76,6 @@ fn main() {
},
}
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
};
gfx.wait_for_vblank();
}
}

9
ctru-rs/examples/output-3dslink.rs

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
//! Use the `3dslink --server` option for redirecting output from the 3DS back
//! Output redirection example.
//!
//! This example uses the `3dslink --server` option for redirecting output from the 3DS back
//! to the device that sent the executable.
//!
//! For now, `cargo 3ds run` does not support this flag, so to run this example
@ -17,24 +19,23 @@ fn main() { @@ -17,24 +19,23 @@ fn main() {
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller");
// We need to use network sockets to send the data stream back.
let mut soc = Soc::new().expect("Couldn't obtain SOC controller");
// Set the output to be redirected to the `3dslink` server.
soc.redirect_to_3dslink(true, true)
.expect("unable to redirect stdout/err to 3dslink server");
println!("Hello 3dslink!");
eprintln!("Press Start on the device to disconnect and exit.");
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

20
ctru-rs/examples/romfs.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! RomFS example.
//!
//! This example showcases the RomFS service and how to mount it to include a read-only filesystem within the application bundle.
use ctru::prelude::*;
fn main() {
@ -9,35 +13,35 @@ fn main() { @@ -9,35 +13,35 @@ fn main() {
let _console = Console::new(gfx.top_screen.borrow_mut());
cfg_if::cfg_if! {
// Run this code if RomFS are wanted and available
// This never fails as `ctru-rs` examples inherit all of the `ctru` features,
// but it might if a normal user application wasn't setup correctly
// Run this code if RomFS are wanted and available.
// This never fails as `ctru-rs` examples inherit all of the `ctru-rs` features,
// but it might if a normal user application wasn't setup correctly.
if #[cfg(all(feature = "romfs", romfs_exists))] {
// Mount the romfs volume.
let _romfs = ctru::services::romfs::RomFS::new().unwrap();
// Open a simple text file present in the RomFS volume.
// Remember to use the `romfs:/` prefix when working with `RomFS`.
let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap();
println!("Contents of test-file.txt: \n{f}\n");
let f = std::fs::read_to_string("romfs:/ファイル.txt").unwrap();
// While RomFS supports UTF-16 file paths, `Console` doesn't...
// While `RomFS` supports UTF-16 file paths, `Console` does not, so we'll use a placeholder for the text.
println!("Contents of [UTF-16 File]: \n{f}\n");
} else {
println!("No RomFS was found, are you sure you included it?")
}
}
println!("\nPress START to exit");
println!("\x1b[29;16HPress Start to exit");
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

25
ctru-rs/examples/software-keyboard.rs

@ -1,4 +1,8 @@ @@ -1,4 +1,8 @@
use ctru::applets::swkbd::{Button, Swkbd};
//! Software Keyboard example.
//!
//! This example showcases the use of the Software Keyboard applet to receive text input from the user.
use ctru::applets::swkbd::{Button, SoftwareKeyboard};
use ctru::prelude::*;
fn main() {
@ -9,21 +13,24 @@ fn main() { @@ -9,21 +13,24 @@ fn main() {
let gfx = Gfx::new().unwrap();
let _console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enter some text or press Start to quit");
println!("Press A to enter some text or press Start to exit.");
while apt.main_loop() {
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Check if the user request to write some input.
if hid.keys_down().contains(KeyPad::A) {
// Prepares a software keyboard with two buttons: One to cancel input and one
// to accept it. You can also use `Swkbd::new()` to launch the keyboard in different
// to accept it. You can also use `SoftwareKeyboard::new()` to launch the keyboard in different
// configurations.
let mut keyboard = Swkbd::default();
let mut keyboard = SoftwareKeyboard::default();
// Raise the software keyboard. You can perform different actions depending on which
// software button the user pressed
// software button the user pressed.
match keyboard.get_string(2048) {
Ok((text, Button::Right)) => println!("You entered: {text}"),
Ok((_, Button::Left)) => println!("Cancelled"),
@ -32,8 +39,6 @@ fn main() { @@ -32,8 +39,6 @@ fn main() {
}
}
if hid.keys_down().contains(KeyPad::START) {
break;
}
gfx.wait_for_vblank();
}
}

14
ctru-rs/examples/system-configuration.rs

@ -1,3 +1,8 @@ @@ -1,3 +1,8 @@
//! System Configuration example.
//!
//! This example showcases the CFGU service to retrieve information about the console that the application is running on,
//! such as the model, region and used language.
use ctru::prelude::*;
use ctru::services::cfgu::Cfgu;
@ -7,23 +12,24 @@ fn main() { @@ -7,23 +12,24 @@ fn main() {
let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller");
let cfgu = Cfgu::new().expect("Couldn't obtain CFGU controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
// Initialize the CFGU service to retrieve all wanted information.
let cfgu = Cfgu::new().expect("Couldn't obtain CFGU controller");
println!("\x1b[0;0HRegion: {:?}", cfgu.region().unwrap());
println!("\x1b[10;0HLanguage: {:?}", cfgu.language().unwrap());
println!("\x1b[20;0HModel: {:?}", cfgu.model().unwrap());
// Main loop
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

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

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

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

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

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

@ -52,7 +52,7 @@ fn main() { @@ -52,7 +52,7 @@ fn main() {
);
});
println!("Press Start to exit");
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
hid.scan_input();

12
ctru-rs/examples/time-rtc.rs

@ -1,3 +1,8 @@ @@ -1,3 +1,8 @@
//! Time Clock example.
//!
//! This example showcases how to retrieve the local time set in the console's configuration
//! using the implementations of the standard library.
use ctru::prelude::*;
fn main() {
@ -9,11 +14,9 @@ fn main() { @@ -9,11 +14,9 @@ fn main() {
let _console = Console::new(gfx.top_screen.borrow_mut());
print!("\x1b[30;16HPress Start to exit.");
println!("\x1b[29;16HPress Start to exit");
// Main loop
while apt.main_loop() {
// Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
@ -21,7 +24,7 @@ fn main() { @@ -21,7 +24,7 @@ fn main() {
}
// Technically, this actually just gets local time and assumes it's UTC,
// since the 3DS doesn't seem to support timezones...
// since the 3DS doesn't seem to support timezones.
let cur_time = time::OffsetDateTime::now_utc();
let hours = cur_time.hour();
@ -36,7 +39,6 @@ fn main() { @@ -36,7 +39,6 @@ fn main() {
println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}");
println!("{weekday} {month} {day} {year}");
//Wait for VBlank
gfx.wait_for_vblank();
}
}

46
ctru-rs/examples/title-info.rs

@ -1,3 +1,8 @@ @@ -1,3 +1,8 @@
//! Title Info example.
//!
//! This example showcases how to retrieve information about the titles installed on the console running the application
//! via the Application Manager (Am) service.
use ctru::prelude::*;
use ctru::services::am::Am;
use ctru::services::fs::FsMediaType;
@ -8,20 +13,27 @@ fn main() { @@ -8,20 +13,27 @@ fn main() {
let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::new().expect("Couldn't obtain APT controller");
let am = Am::new().expect("Couldn't obtain AM controller");
let top_screen = Console::new(gfx.top_screen.borrow_mut());
let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut());
// Setup the AM service to retrieve the wanted information.
let am = Am::new().expect("Couldn't obtain AM controller");
// Amount of titles installed on the SD card.
let sd_count = am
.title_count(FsMediaType::Sd)
.expect("Failed to get sd title count");
// List of titles installed on the SD card.
let sd_list = am
.title_list(FsMediaType::Sd)
.expect("Failed to get sd title list");
// Amount of titles installed on the NAND storage.
let nand_count = am
.title_count(FsMediaType::Nand)
.expect("Failed to get nand title count");
// List of titles installed on the NAND storage.
let nand_list = am
.title_list(FsMediaType::Nand)
.expect("Failed to get nand title list");
@ -30,9 +42,7 @@ fn main() { @@ -30,9 +42,7 @@ fn main() {
let mut refresh = true;
let mut use_nand = false;
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
@ -58,13 +68,15 @@ fn main() { @@ -58,13 +68,15 @@ fn main() {
}
}
// Render the title list via a scrollable text UI.
if refresh {
let mut selected_title = cur_list.iter().skip(offset).next().unwrap();
// Clear top screen and write title ids to it
// Clear the top screen and write title IDs to it.
top_screen.select();
print!("\x1b[2J");
// Top screen seems to have only 30 rows
// Top screen has 30 rows.
for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() {
if i == 0 {
selected_title = title;
@ -74,25 +86,18 @@ fn main() { @@ -74,25 +86,18 @@ fn main() {
}
}
// Clear bottom screen and write properties of selected title to it
// Clear the bottom screen and write the properties of selected title to it.
bottom_screen.select();
println!("\x1b[2J");
// Move cursor to top left
bottom_screen.clear();
println!("Press Start to exit");
// Move cursor to top left.
println!("\x1b[1;1");
match selected_title.title_info() {
Ok(info) => {
println!("Size: {} KB", info.size_bytes() / 1024);
println!("Version: 0x{:x}", info.version());
}
Err(e) => println!("Failed to get title info: {}", e),
}
match selected_title.product_code() {
Ok(code) => println!("Product code: \"{code}\""),
Err(e) => println!("Failed to get product code: {}", e),
}
println!("Size: {} kB", selected_title.size() / 1024);
println!("Version: 0x{:x}", selected_title.version());
println!("Product code: \"{}\"", selected_title.product_code());
println!("\x1b[26;0HPress START to exit");
if use_nand {
println!("Press SELECT to choose SD Card");
println!("Current medium: NAND");
@ -106,7 +111,6 @@ fn main() { @@ -106,7 +111,6 @@ fn main() {
refresh = false;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

18
ctru-rs/examples/touch-screen.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! Touch Screen example.
//!
//! This example showcases how to retrieve the touch screen's touch information via the HID service.
use ctru::prelude::*;
fn main() {
@ -9,7 +13,7 @@ fn main() { @@ -9,7 +13,7 @@ fn main() {
let console = Console::new(gfx.top_screen.borrow_mut());
// We'll hold the previous touch position for comparison.
// We'll save the previous touch position for comparison.
let mut old_touch: (u16, u16) = (0, 0);
println!("\x1b[29;16HPress Start to exit");
@ -26,22 +30,22 @@ fn main() { @@ -26,22 +30,22 @@ fn main() {
let touch: (u16, u16) = hid.touch_position();
// We only want to print the position when it's different
// from what it was on the previous frame
// from what it was on the previous frame.
if touch != old_touch {
// Move the cursor back to the top of the screen and print the coordinates.
print!("\x1b[1;1HTouch Screen position: {:#?}", touch);
// Special case for when the user lifts the stylus/finger from the screen.
// This is done to avoid some screen tearing.
if touch == (0, 0) {
console.clear();
// Print again because we just cleared the screen
// Print again because we just cleared the screen.
println!("\x1b[29;16HPress Start to exit");
}
// Move the cursor back to the top of the screen and print the coordinates
print!("\x1b[1;1HTouch Screen position: {:#?}", touch);
}
// Save our current touch position for the next frame
// Save our current touch position for the next frame.
old_touch = touch;
gfx.wait_for_vblank();

248
ctru-rs/src/applets/mii_selector.rs

@ -1,75 +1,87 @@ @@ -1,75 +1,87 @@
//! Mii Selector applet
//! Mii Selector applet.
//!
//! This module contains the methods to launch the Mii Selector.
//! This applet opens a window which lets the player/user choose a Mii from the ones present on their console.
//! The selected Mii is readable as a [`Mii`](crate::mii::Mii).
use crate::mii::MiiData;
use crate::mii::Mii;
use bitflags::bitflags;
use std::ffi::CString;
use std::{ffi::CString, fmt};
/// Index of a Mii used to configure some parameters of the Mii Selector
/// Can be either a single index, or _all_ Miis
/// Index of a Mii on the [`MiiSelector`] interface.
///
/// See [`MiiSelector::whitelist_user_mii()`] and related functions for more information.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Index {
/// Specific Mii index.
///
/// # Notes
///
/// Indexes start at 0.
Index(u32),
/// All Miis.
All,
}
/// The type of a Mii with their respective data
/// The type of a Mii.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MiiType {
Guest { index: u32, name: String },
/// Guest Mii.
Guest {
/// Guest Mii index.
index: u32,
/// Guest Mii name.
name: String,
},
/// User-made Mii.
User,
}
bitflags! {
/// Options for the Mii Selector
/// Options to configure the [`MiiSelector`].
///
/// See [`MiiSelector::set_options()`] to learn how to use them.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Options: u32 {
/// Show the cancel button
const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL;
/// Make guest Miis selectable
const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS;
/// Show on the top screen
const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP;
/// Start on the guest's page
const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART;
/// Show the cancel button.
const ENABLE_CANCEL = ctru_sys::MIISELECTOR_CANCEL;
/// Make guest Miis available to select.
const ENABLE_GUESTS = ctru_sys::MIISELECTOR_GUESTS;
/// Show the [`MiiSelector`] window on the top screen.
const USE_TOP_SCREEN = ctru_sys::MIISELECTOR_TOP;
/// Start the [`MiiSelector`] on the guests' page. Requires [`Options::ENABLE_GUESTS`].
const START_WITH_GUESTS = ctru_sys::MIISELECTOR_GUESTSTART;
}
}
/// An instance of the Mii Selector
///
/// # Example
/// ```
/// use ctru::applets::mii_selector::MiiSelector;
///
/// let mut mii_selector = MiiSelector::new();
/// mii_selector.set_title("Example Mii selector");
///
/// let result = mii_selector.launch().unwrap();
/// ```
/// Configuration structure to setup the Mii Selector applet.
#[doc(alias = "MiiSelectorConf")]
#[derive(Clone, Debug)]
pub struct MiiSelector {
config: Box<ctru_sys::MiiSelectorConf>,
}
/// Return value from a MiiSelector's launch
/// Return value of a successful [`MiiSelector::launch()`].
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct SelectionResult {
pub mii_data: MiiData,
pub struct Selection {
/// Data of the selected Mii.
pub mii_data: Mii,
/// Type of the selected Mii.
pub mii_type: MiiType,
}
/// Error type for the Mii selector
/// Error returned by an unsuccessful [`MiiSelector::launch()`].
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum LaunchError {
/// The selected Mii's data is corrupt in some way
pub enum Error {
/// The selected Mii's data is corrupt.
InvalidChecksum,
/// Either the user cancelled the selection (see [Options::MII_SELECTOR_CANCEL]), or no valid Miis were available to select
/// Either the user cancelled the selection (see [`Options::ENABLE_CANCEL`]) or no valid Miis were available to select.
NoMiiSelected,
}
impl MiiSelector {
/// Initializes a Mii Selector
/// Initialize a new configuration for the Mii Selector applet.
#[doc(alias = "miiSelectorInit")]
pub fn new() -> Self {
let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
unsafe {
@ -78,9 +90,22 @@ impl MiiSelector { @@ -78,9 +90,22 @@ impl MiiSelector {
Self { config }
}
/// Set the title of the Mii Selector.
/// Set the title of the Mii Selector window.
///
/// # Panics
/// This function will panic if the given `&str` contains NUL bytes.
///
/// # Example
///
/// ```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) {
// This can only fail if the text contains NUL bytes in the string... which seems
// unlikely and is documented
@ -90,12 +115,47 @@ impl MiiSelector { @@ -90,12 +115,47 @@ impl MiiSelector {
}
}
/// Set the options of the Mii Selector
/// Set the options of the Mii Selector.
///
/// This will overwrite any previously saved options. Use bitwise operations to set all your wanted options at once.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// use ctru::applets::mii_selector::{MiiSelector, Options};
/// let mut mii_selector = MiiSelector::new();
///
/// // Setup a `MiiSelector` that can be cancelled and that makes Guest Miis available to select.
/// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS;
/// mii_selector.set_options(opts);
/// # }
/// ```
#[doc(alias = "miiSelectorSetOptions")]
pub fn set_options(&mut self, options: Options) {
unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits) }
unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits()) }
}
/// Whitelist a guest Mii
/// Whitelist a guest Mii based on its index.
///
/// # Notes
///
/// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
/// Look into [`MiiSelector::set_options()`] to see how to work with options.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new();
///
/// // Whitelist the guest Mii at index 2.
/// mii_selector.whitelist_guest_mii(Index::Index(2));
/// # }
/// ```
#[doc(alias = "miiSelectorWhitelistGuestMii")]
pub fn whitelist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index {
Index::Index(i) => i,
@ -105,7 +165,26 @@ impl MiiSelector { @@ -105,7 +165,26 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) }
}
/// Blacklist a guest Mii
/// Blacklist a guest Mii based on its index.
///
/// # Notes
///
/// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
/// Look into [`MiiSelector::set_options()`] to see how to work with options.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new();
///
/// // Blacklist the guest Mii at index 1 so that it cannot be selected.
/// mii_selector.blacklist_guest_mii(Index::Index(1));
/// # }
/// ```
#[doc(alias = "miiSelectorBlacklistGuestMii")]
pub fn blacklist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index {
Index::Index(i) => i,
@ -115,7 +194,21 @@ impl MiiSelector { @@ -115,7 +194,21 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) }
}
/// Whitelist a user Mii
/// Whitelist a user-created Mii based on its index.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new();
///
/// // Whitelist the user-created Mii at index 0.
/// mii_selector.whitelist_user_mii(Index::Index(0));
/// # }
/// ```
#[doc(alias = "miiSelectorWhitelistUserMii")]
pub fn whitelist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index {
Index::Index(i) => i,
@ -125,7 +218,21 @@ impl MiiSelector { @@ -125,7 +218,21 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) }
}
/// Blacklist a user Mii
/// Blacklist a user-created Mii based on its index.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new();
///
/// // Blacklist all user-created Miis so that they cannot be selected.
/// mii_selector.blacklist_user_mii(Index::All);
/// # }
/// ```
#[doc(alias = "miiSelectorBlacklistUserMii")]
pub fn blacklist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index {
Index::Index(i) => i,
@ -135,8 +242,10 @@ impl MiiSelector { @@ -135,8 +242,10 @@ impl MiiSelector {
unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
}
/// Set where the cursor will be.
/// If there's no Mii at that index, the cursor will start at the Mii with the index 0
/// Set where the GUI cursor will start at.
///
/// If there's no Mii at that index, the cursor will start at the Mii with the index 0.
#[doc(alias = "miiSelectorSetInitialIndex")]
pub fn set_initial_index(&mut self, index: usize) {
// This function is static inline in libctru
// https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155
@ -144,19 +253,43 @@ impl MiiSelector { @@ -144,19 +253,43 @@ impl MiiSelector {
}
/// Launch the Mii Selector.
/// Returns an error when the checksum of the Mii is invalid.
pub fn launch(&mut self) -> Result<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();
unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) }
if return_val.no_mii_selected != 0 {
return Err(LaunchError::NoMiiSelected);
return Err(Error::NoMiiSelected);
}
if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } {
Ok((*return_val).into())
} else {
Err(LaunchError::InvalidChecksum)
Err(Error::InvalidChecksum)
}
}
}
@ -167,12 +300,23 @@ impl Default for MiiSelector { @@ -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 {
let raw_mii_data = ret.mii;
let mut guest_mii_name = ret.guest_mii_name;
SelectionResult {
Selection {
mii_data: raw_mii_data.into(),
mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
MiiType::Guest {

10
ctru-rs/src/applets/mod.rs

@ -1,2 +1,12 @@ @@ -1,2 +1,12 @@
//! System Applets.
//!
//! Applets are small integrated programs that the OS makes available to the developer to streamline commonly needed functionality.
//! Thanks to these integrations the developer can avoid wasting time re-implementing common features and instead use a more reliable base for their application.
//!
//! Unlike [services](crate::services), applets aren't accessed via a system subprocess (which would require obtaining a special handle at runtime).
//! Instead, the application builds a configuration storing the various parameters which is then used to "launch" the applet.
//!
//! Applets block execution of the thread that launches them as long as the user doesn't close the applet.
pub mod mii_selector;
pub mod swkbd;

339
ctru-rs/src/applets/swkbd.rs

@ -1,106 +1,209 @@ @@ -1,106 +1,209 @@
//! Software Keyboard applet.
//!
//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text.
// TODO: Implement remaining functionality (password mode, filter callbacks, etc.). Also improve "max text length" API. Improve `number of buttons` API when creating a new SoftwareKeyboard.
// TODO: Split the Parental PIN lock operations into a different type.
#![doc(alias = "keyboard")]
use bitflags::bitflags;
use ctru_sys::{
self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState,
};
use libc;
use std::fmt::Display;
use std::iter::once;
use std::str;
/// An instance of the software keyboard.
/// Configuration structure to setup the Software Keyboard applet.
#[doc(alias = "SwkbdState")]
#[derive(Clone)]
pub struct Swkbd {
pub struct SoftwareKeyboard {
state: Box<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)
/// Qwerty is a QWERTY-only keyboard.
/// Numpad is a number pad.
/// Western is a text keyboard without japanese symbols (only applies to JPN systems). For other
/// systems it's the same as a Normal keyboard.
/// Can be set with [`SoftwareKeyboard::new()`]
#[doc(alias = "SwkbdType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Kind {
/// Normal keyboard composed of several pages (QWERTY, accents, symbols, mobile).
Normal = ctru_sys::SWKBD_TYPE_NORMAL,
/// Only QWERTY keyboard.
Qwerty = ctru_sys::SWKBD_TYPE_QWERTY,
/// Only number pad.
Numpad = ctru_sys::SWKBD_TYPE_NUMPAD,
/// On JPN systems: a keyboard without japanese input capabilities.
///
/// On any other region: same as [`Normal`](Kind::Normal).
Western = ctru_sys::SWKBD_TYPE_WESTERN,
}
/// Represents which button the user pressed to close the software keyboard.
/// Represents which button the user pressed to close the [`SoftwareKeyboard`].
///
/// Button text and behaviour can be customized with [`SoftwareKeyboard::configure_button()`].
#[doc(alias = "SwkbdButton")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Button {
/// Left button. Usually corresponds to "Cancel".
Left = ctru_sys::SWKBD_BUTTON_LEFT,
/// Middle button. Usually corresponds to "I Forgot".
Middle = ctru_sys::SWKBD_BUTTON_MIDDLE,
/// Right button. Usually corresponds to "OK".
Right = ctru_sys::SWKBD_BUTTON_RIGHT,
}
/// Error type for the software keyboard.
/// Error returned by an unsuccessful [`SoftwareKeyboard::get_string()`].
#[doc(alias = "SwkbdResult")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum Error {
InvalidInput = ctru_sys::SWKBD_INVALID_INPUT,
/// Invalid parameters given to the [`SoftwareKeyboard`] configuration.
InvalidParameters = ctru_sys::SWKBD_INVALID_INPUT,
/// [`SoftwareKeyboard`] ran out of memory.
OutOfMem = ctru_sys::SWKBD_OUTOFMEM,
/// Home button was pressed while [`SoftwareKeyboard`] was running.
HomePressed = ctru_sys::SWKBD_HOMEPRESSED,
/// Reset button was pressed while [`SoftwareKeyboard`] was running.
ResetPressed = ctru_sys::SWKBD_RESETPRESSED,
/// Power button was pressed while [`SoftwareKeyboard`] was running.
PowerPressed = ctru_sys::SWKBD_POWERPRESSED,
/// The parental lock PIN was correct.
///
/// While this variant isn't *technically* considerable an error
/// the result of a Parental PIN operation won't return a string to the program, thus it's still exceptional behaviour.
ParentalOk = ctru_sys::SWKBD_PARENTAL_OK,
/// The parental lock PIN was incorrect.
ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL,
/// Input triggered the filter.
///
/// You can have a look at [`Filters`] to activate custom filters.
BannedInput = ctru_sys::SWKBD_BANNED_INPUT,
}
/// Restrictions on keyboard input
/// Restrictions to enforce rules on the keyboard input.
///
/// See [`SoftwareKeyboard::set_validation()`]
#[doc(alias = "SwkbdValidInput")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ValidInput {
/// All inputs are accepted.
Anything = ctru_sys::SWKBD_ANYTHING,
/// Empty inputs are not accepted.
NotEmpty = ctru_sys::SWKBD_NOTEMPTY,
NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK,
/// Blank inputs (consisting only of whitespaces) are not accepted.
NotBlank = ctru_sys::SWKBD_NOTBLANK,
/// Neither empty inputs nor blank inputs are accepted.
NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK,
/// Input must have a fixed length. Maximum length can be specified with [`SoftwareKeyboard::set_max_text_len()`];
FixedLen = ctru_sys::SWKBD_FIXEDLEN,
}
bitflags! {
/// Keyboard feature flags
/// Special features that can be activated via [`SoftwareKeyboard::set_features()`].
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Features: u32 {
/// Parental PIN mode.
///
/// # Notes
///
/// Refer to [`Error::ParentalOk`] and [`Error::ParentalFail`] to check whether the Parental PIN lock was successfully opened.
const PARENTAL_PIN = ctru_sys::SWKBD_PARENTAL;
/// Darken top screen while the [`SoftwareKeyboard`] is active.
const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN;
/// Enable predictive input (necessary for Kanji on JPN consoles).
const PREDICTIVE_INPUT = ctru_sys::SWKBD_PREDICTIVE_INPUT;
/// Enable multiline input.
const MULTILINE = ctru_sys::SWKBD_MULTILINE;
/// Enable fixed-width mode.
const FIXED_WIDTH = ctru_sys::SWKBD_FIXED_WIDTH;
/// Allow the usage of the Home Button while the [`SoftwareKeyboard`] is running.
const ALLOW_HOME = ctru_sys::SWKBD_ALLOW_HOME;
/// Allow the usage of the Reset Button while the [`SoftwareKeyboard`] is running.
const ALLOW_RESET = ctru_sys::SWKBD_ALLOW_RESET;
/// Allow the usage of the Power Button while the [`SoftwareKeyboard`] is running.
const ALLOW_POWER = ctru_sys::SWKBD_ALLOW_POWER;
/// Default to the QWERTY page when the [`SoftwareKeyboard`] is shown.
const DEFAULT_QWERTY = ctru_sys::SWKBD_DEFAULT_QWERTY;
}
/// Keyboard input filtering flags
/// Availble filters to disallow some types of input for the [`SoftwareKeyboard`].
///
/// See [`SoftwareKeyboard::set_validation()`]
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Filters: u32 {
/// Disallow the usage of numerical digits.
///
/// The maximum number of digits that are allowed to be used while this filter is active
/// can be configured with [`SoftwareKeyboard::set_max_digits()`] (default is 0).
const DIGITS = ctru_sys::SWKBD_FILTER_DIGITS;
/// Disallow the usage of the "at" (@) sign.
const AT = ctru_sys::SWKBD_FILTER_AT;
/// Disallow the usage of the "percent" (%) sign.
const PERCENT = ctru_sys::SWKBD_FILTER_PERCENT;
/// Disallow the usage of the "backslash" (\) sign.
const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH;
/// Disallow the use of profanity via Nintendo's profanity filter.
const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY;
/// Use a custom callback in order to filter the input.
///
/// TODO: It's currently impossible to setup a custom filter callback.
const CALLBACK = ctru_sys::SWKBD_FILTER_CALLBACK;
}
}
impl Swkbd {
/// Initializes a software keyboard of the specified type and the chosen number of buttons
/// (from 1-3).
impl SoftwareKeyboard {
/// Initialize a new configuration for the Software Keyboard applet depending on how many "exit" buttons are available to the user (1, 2 or 3).
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Kind};
///
/// // Standard keyboard.
/// let keyboard = SoftwareKeyboard::new(Kind::Normal, 2);
///
/// // Numpad (with only the "confirm" button).
/// let keyboard = SoftwareKeyboard::new(Kind::Numpad, 1);
/// #
/// # }
#[doc(alias = "swkbdInit")]
pub fn new(keyboard_type: Kind, num_buttons: i32) -> Self {
unsafe {
let mut state = Box::<SwkbdState>::default();
swkbdInit(state.as_mut(), keyboard_type.into(), num_buttons, -1);
Swkbd { state }
SoftwareKeyboard { state }
}
}
/// Gets input from this keyboard and appends it to the provided string.
/// Launches the applet based on the given configuration and returns a string containing the text input.
///
/// # Notes
///
/// The text received from the keyboard will be truncated if it is longer than `max_bytes`.
///
/// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
// Unfortunately the libctru API doesn't really provide a way to get the exact length
// of the string that it receieves from the software keyboard. Instead it expects you
@ -123,8 +226,30 @@ impl Swkbd { @@ -123,8 +226,30 @@ impl Swkbd {
/// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from
/// this software keyboard.
///
/// # Notes
///
/// If the buffer is too small to contain the entire sequence received from the keyboard,
/// the output will be truncated but should still be well-formed UTF-8.
/// the output will be truncated.
///
/// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
unsafe {
match swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) {
@ -137,25 +262,90 @@ impl Swkbd { @@ -137,25 +262,90 @@ impl Swkbd {
}
}
/// Sets special features for this keyboard
/// Set special features for this keyboard.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Features};
///
/// let mut keyboard = SoftwareKeyboard::default();
///
/// let features = Features::DARKEN_TOP_SCREEN & Features::MULTILINE;
/// keyboard.set_features(features);
/// #
/// # }
#[doc(alias = "swkbdSetFeatures")]
pub fn set_features(&mut self, features: Features) {
unsafe { swkbdSetFeatures(self.state.as_mut(), features.bits) }
unsafe { swkbdSetFeatures(self.state.as_mut(), features.bits()) }
}
/// Configures input validation for this keyboard
/// Configure input validation for this keyboard.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters};
/// let mut keyboard = SoftwareKeyboard::default();
///
/// // Disallow empty or blank input.
/// let validation = ValidInput::NotEmptyNotBlank;
///
/// // Disallow the use of numerical digits and profanity.
/// let filters = Filters::DIGITS & Filters::PROFANITY;
/// keyboard.set_validation(validation, filters);
/// #
/// # }
pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) {
self.state.valid_input = validation.into();
self.state.filter_flags = filters.bits;
self.state.filter_flags = filters.bits();
}
/// Configures the maximum number of digits that can be entered in the keyboard when the
/// `Filters::DIGITS` flag is enabled
/// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, ValidInput, Filters};
/// let mut keyboard = SoftwareKeyboard::default();
///
/// // Disallow empty or blank input.
/// let validation = ValidInput::NotEmptyNotBlank;
///
/// // Disallow the use of numerical digits. This filter is customizable thanks to `set_max_digits`.
/// let filters = Filters::DIGITS;
/// keyboard.set_validation(validation, filters);
///
/// // No more than 3 numbers are accepted.
/// keyboard.set_max_digits(3);
/// #
/// # }
pub fn set_max_digits(&mut self, digits: u16) {
self.state.max_digits = digits;
}
/// Sets the hint text for this software keyboard (that is, the help text that is displayed
/// when the textbox is empty)
/// Set the hint text for this software keyboard.
///
/// The hint text is the text shown in gray before any text gets written in the input box.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::SoftwareKeyboard;
/// let mut keyboard = SoftwareKeyboard::default();
///
/// keyboard.set_hint_text("Write here what you like!");
/// #
/// # }
#[doc(alias = "swkbdSetHintText")]
pub fn set_hint_text(&mut self, text: &str) {
unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect();
@ -163,12 +353,32 @@ impl Swkbd { @@ -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
/// `text` configures the display text for the button
/// `submit` configures whether pressing the button will accept the keyboard's input or
/// discard it.
/// - `button` - the [`Button`] to be configured based on the position.
/// - `text` - the text displayed in the button.
/// - `submit` - whether pressing the button will accept the keyboard's input or discard it.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind};
///
/// // We create a `SoftwareKeyboard` with left and right buttons.
/// let mut keyboard = SoftwareKeyboard::new(Kind::Normal, 2);
///
/// // Set the left button text to "Cancel" and pressing it will NOT return the user's input.
/// keyboard.configure_button(Button::Left, "Cancel", false);
///
/// // Set the right button text to "Ok" and pressing it will return the user's input.
/// keyboard.configure_button(Button::Right, "Ok", true);
/// #
/// # }
#[doc(alias = "swkbdSetButton")]
pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) {
unsafe {
let nul_terminated: String = text.chars().chain(once('\0')).collect();
@ -181,19 +391,34 @@ impl Swkbd { @@ -181,19 +391,34 @@ impl Swkbd {
}
}
/// Configures the maximum number of UTF-16 code units that can be entered into the software
/// keyboard. By default the limit is 0xFDE8 code units.
/// Configure the maximum number of UTF-16 code units that can be entered into the software
/// keyboard. By default the limit is `65000` code units.
///
/// # 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
/// receivable by the `get_utf8` and `get_bytes` functions.
/// receivable by [`SoftwareKeyboard::get_string()`] and [`SoftwareKeyboard::write_exact()`].
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::swkbd::{SoftwareKeyboard, Button, Kind};
/// let mut keyboard = SoftwareKeyboard::default();
///
/// // Set the maximum text length to 18 UTF-16 code units.
/// keyboard.set_max_text_len(18);
/// #
/// # }
pub fn set_max_text_len(&mut self, len: u16) {
self.state.max_text_len = len;
}
fn parse_swkbd_error(&self) -> Error {
match self.state.result {
ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidInput,
ctru_sys::SWKBD_INVALID_INPUT => Error::InvalidParameters,
ctru_sys::SWKBD_OUTOFMEM => Error::OutOfMem,
ctru_sys::SWKBD_HOMEPRESSED => Error::HomePressed,
ctru_sys::SWKBD_RESETPRESSED => Error::ResetPressed,
@ -206,11 +431,43 @@ impl Swkbd { @@ -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 {
Swkbd::new(Kind::Normal, 2)
SoftwareKeyboard::new(Kind::Normal, 2)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidParameters => write!(
f,
"software keyboard was configured with invalid parameters"
),
Self::OutOfMem => write!(f, "software keyboard ran out of memory"),
Self::HomePressed => {
write!(f, "home button pressed while software keyboard was running")
}
Self::ResetPressed => write!(
f,
"reset button pressed while software keyboard was running"
),
Self::PowerPressed => write!(
f,
"power button pressed while software keyboard was running"
),
Self::ParentalOk => write!(f, "parental lock pin was correct"),
Self::ParentalFail => write!(f, "parental lock pin was incorrect"),
Self::BannedInput => write!(
f,
"input given to the software keyboard triggered the active filters"
),
}
}
}
impl std::error::Error for Error {}
from_impl!(Kind, ctru_sys::SwkbdType);
from_impl!(Button, ctru_sys::SwkbdButton);

140
ctru-rs/src/console.rs

@ -1,3 +1,10 @@ @@ -1,3 +1,10 @@
//! Virtual text console.
//!
//! The [`Console`] works as a virtual shell that renders on screen all output of `stdout`. As such, it is useful as a basic interface to show info to the user,
//! such as in simple "Hello World" applications or more complex software that does not need much user interaction.
//!
//! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications.
use std::cell::RefMut;
use std::default::Default;
@ -7,19 +14,63 @@ use crate::services::gfx::Screen; @@ -7,19 +14,63 @@ use crate::services::gfx::Screen;
static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) };
/// Virtual text console.
///
/// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen.
/// This means that any text written to `stdout` and `stderr` (e.g. using `println!`, `eprintln!` or `dbg!`) will become visible in the area taken by the console.
///
/// # Notes
///
/// The [`Console`] will take full possession of the screen handed to it as long as it stays alive. It also supports some ANSI codes, such as text color and cursor positioning.
/// The [`Console`]'s window size will be:
/// - 40x30 on the [`BottomScreen`](crate::services::gfx::BottomScreen).
/// - 50x30 on the normal [`TopScreen`](crate::services::gfx::TopScreen).
/// - 100x30 on the [`TopScreen`](crate::services::gfx::TopScreen) when wide mode is enabled.
///
/// # Alternatives
///
/// If you'd like to see live standard output while running the application but cannot or do not want to show the text on the 3DS itself,
/// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`).
/// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables).
#[doc(alias = "PrintConsole")]
pub struct Console<'screen> {
context: Box<PrintConsole>,
_screen: RefMut<'screen, dyn Screen>,
}
impl<'screen> Console<'screen> {
/// Initialize a console on the chosen screen, overwriting whatever was on the screen
/// previously (including other consoles). The new console is automatically selected for
/// printing.
/// Initialize a console on the chosen screen.
///
/// # Notes
///
/// [Console] automatically takes care of flushing and swapping buffers for its screen when printing.
/// This operation overwrites whatever was on the screen before the initialization (including other [`Console`]s)
/// and changes the [`FramebufferFormat`](crate::services::gspgpu::FramebufferFormat) of the selected screen to better suit the [`Console`].
///
/// The new console is automatically selected for printing.
///
/// [`Console`] automatically takes care of flushing and swapping buffers for its screen when printing.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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 {
let mut context = Box::<PrintConsole>::default();
@ -31,7 +82,35 @@ impl<'screen> Console<'screen> { @@ -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 {
unsafe {
let current_console = ctru_sys::consoleSelect(&mut EMPTY_CONSOLE);
@ -44,26 +123,65 @@ impl<'screen> Console<'screen> { @@ -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) {
unsafe {
consoleSelect(self.context.as_ref() as *const _ as *mut _);
}
}
/// Clears all text from the console
/// Clear all text from the console.
#[doc(alias = "consoleClear")]
pub fn clear(&self) {
unsafe { consoleClear() }
}
/// Resizes the active console to fit in a smaller portion of the screen.
/// Resize the console to fit in a smaller portion of the screen.
///
/// # Notes
///
/// The first two arguments are the desired coordinates of the top-left corner
/// of the console, and the second pair is the new width and height
/// of the console, and the second pair is the new width and height.
///
/// # Safety
/// This function is unsafe because it does not validate that the input will produce
/// a console that actually fits on the screen
///
/// This function is unsafe because it does not validate whether the input will produce
/// a console that actually fits on the screen.
// TODO: Wrap this safely.
#[doc(alias = "consoleSetWindow")]
pub unsafe fn set_window(&mut self, x: i32, y: i32, width: i32, height: i32) {
consoleSetWindow(self.context.as_mut(), x, y, width, height);
}

39
ctru-rs/src/error.rs

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
//! Error handling interface.
//!
//! This module holds the generic error and result types to interface with `ctru_sys` and the [`ctru-rs`](crate) safe wrapper.
use std::borrow::Cow;
use std::error;
use std::ffi::CStr;
@ -6,8 +10,29 @@ use std::ops::{ControlFlow, FromResidual, Try}; @@ -6,8 +10,29 @@ use std::ops::{ControlFlow, FromResidual, Try};
use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY};
/// Custom type alias for generic [`ctru-rs`](crate) operations.
///
/// This type is compatible with [`ctru_sys::Result`] codes.
pub type Result<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)]
#[repr(transparent)]
pub struct ResultCode(pub ctru_sys::Result);
@ -48,13 +73,20 @@ impl<T> FromResidual<Error> for Result<T> { @@ -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]
pub enum Error {
/// Raw [`ctru_sys::Result`] codes.
Os(ctru_sys::Result),
/// Generic [`libc`] errors.
Libc(String),
/// Requested service is already active and cannot be activated again.
ServiceAlreadyActive,
/// `stdout` is already being redirected.
OutputAlreadyRedirected,
/// The buffer provided by the user to store some data is shorter than required.
BufferTooShort {
/// Length of the buffer provided by the user.
provided: usize,
@ -64,8 +96,9 @@ pub enum Error { @@ -64,8 +96,9 @@ pub enum Error {
}
impl Error {
/// Create an [`Error`] out of the last set value in `errno`. This can be used
/// to get a human-readable error string from calls to `libc` functions.
/// Create an [`Error`] out of the last set value in `errno`.
///
/// This can be used to get a human-readable error string from calls to `libc` functions.
pub(crate) fn from_errno() -> Self {
let error_str = unsafe {
let errno = ctru_sys::errno();

43
ctru-rs/src/lib.rs

@ -1,16 +1,44 @@ @@ -1,16 +1,44 @@
//! Safe and idiomatic Rust wrapper around [`libctru`](https://github.com/devkitPro/libctru).
//!
//! # About
//!
//! This crate behaves as the main tool to access system-specific functionality on the Nintendo 3DS when developing homebrew software in Rust.
//! Thanks to it, developers can develop userland applications by accessing access the underlying system services and the console's hardware
//! (such as [HID devices](crate::services::hid), [network capabilities](crate::services::soc), [graphics](crate::services::gfx), [built-in cameras](crate::services::cam), etc.).
//!
//! Among these features, [`ctru-rs`](crate) also automatically includes functionality to properly integrate the Rust `std` with the console's operating system,
//! which the developer would otherwise need to implement manually.
//!
//! # Usage
//!
//! Thoroughly read the official [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki) which guides you through the setup needed to install the required toolchain and helpful tools.
//! After following the guide and understanding the many quirks of the Nintendo 3DS homebrew development environment, you can create a new project by including this crate as a dependency
//! in your `Cargo.toml` manifest and build your binaries either manually (for the `armv6k-nintendo-3ds` target) or via [`cargo-3ds`](https://github.com/rust3ds/cargo-3ds).
#![crate_type = "rlib"]
#![crate_name = "ctru"]
#![warn(missing_docs)]
#![feature(test)]
#![feature(custom_test_frameworks)]
#![feature(try_trait_v2)]
#![feature(allocator_api)]
#![feature(nonnull_slice_from_raw_parts)]
#![test_runner(test_runner::run)]
#![doc(
html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"
)]
#![doc(
html_logo_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"
)]
// Nothing is imported from these crates but their inclusion here assures correct linking of the missing implementations.
extern crate pthread_3ds;
extern crate shim_3ds;
/// Expanded stack size used to spawn the main thread by `libctru`.
///
/// It takes effect only if the `big-stack` feature is active. Otherwise, the default stack size should be ~32kB.
///
/// This value was chosen to support crate dependencies which expected more stack than provided. It's suggested to use less stack if possible.
#[no_mangle]
#[cfg(feature = "big-stack")]
static __stacksize__: usize = 2 * 1024 * 1024; // 2MB
@ -25,19 +53,24 @@ macro_rules! from_impl { @@ -25,19 +53,24 @@ macro_rules! from_impl {
};
}
/// Activate the default panic handler.
/// Activate the custom [`ctru-rs`](crate) panic handler.
///
/// With this implementation, the main thread will stop and try to print debug info to an available [console::Console].
/// In case it fails to find an active [console::Console] the program will just exit.
/// With this implementation, the main thread will stop and try to print debug info to an available [`Console`](console::Console).
/// In case it fails to find an active [`Console`](console::Console) the program will just exit.
///
/// # Notes
///
/// When ´test´ is enabled, this function won't do anything, as it should be overridden by the ´test´ environment.
/// When `test` is enabled, this function will not do anything, as its behaviour should be overridden by the `test` environment.
pub fn use_panic_handler() {
#[cfg(not(test))]
panic_hook_setup();
}
/// Internal protocol to activate the custom panic handler hook.
///
/// # Notes
///
/// When `test` is enabled, this function will be ignored.
#[cfg(not(test))]
fn panic_hook_setup() {
use crate::services::hid::{Hid, KeyPad};

21
ctru-rs/src/linear.rs

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
//! Linear memory allocator
//! LINEAR memory allocator.
//!
//! Linear memory is a sector of the 3DS' RAM that binds virtual addresses exactly to the physical address.
//! As such, it is used for fast and safe memory sharing between services (and is especially needed for GPU and DSP).
//! LINEAR memory is a sector of the 3DS' RAM that binds virtual addresses exactly to the physical address.
//! As such, it is used for fast and safe memory sharing between different hardware components (such as the GPU and the DSP processor).
//!
//! Resources:<br>
//! <https://github.com/devkitPro/libctru/blob/master/libctru/source/allocator/linear.cpp><br>
//! <https://www.3dbrew.org/wiki/Memory_layout>
//! # Additional Resources
//!
//! - <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::ptr::NonNull;
@ -15,19 +16,22 @@ use std::ptr::NonNull; @@ -15,19 +16,22 @@ use std::ptr::NonNull;
// Sadly the linear memory allocator included in `libctru` doesn't implement `linearRealloc` at the time of these additions,
// but the default fallback of the `std` will take care of that for us.
/// [`std::alloc::Allocator`] struct for LINEAR memory
/// [`Allocator`](std::alloc::Allocator) struct for LINEAR memory.
///
/// To use this struct the main crate must activate the `allocator_api` unstable feature.
#[derive(Copy, Clone, Default, Debug)]
pub struct LinearAllocator;
impl LinearAllocator {
/// Returns the amount of free space left in the LINEAR sector
/// Returns the amount of free space left in the LINEAR memory sector.
#[doc(alias = "linearSpaceFree")]
pub fn free_space() -> u32 {
unsafe { ctru_sys::linearSpaceFree() }
}
}
unsafe impl Allocator for LinearAllocator {
#[doc(alias = "linearAlloc", alias = "linearMemAlign")]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let pointer = unsafe { ctru_sys::linearMemAlign(layout.size(), layout.align()) };
@ -36,6 +40,7 @@ unsafe impl Allocator for LinearAllocator { @@ -36,6 +40,7 @@ unsafe impl Allocator for LinearAllocator {
.ok_or(AllocError)
}
#[doc(alias = "linearFree")]
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
ctru_sys::linearFree(ptr.as_ptr().cast());
}

176
ctru-rs/src/mii.rs

@ -1,213 +1,301 @@ @@ -1,213 +1,301 @@
//! Mii Data
//! Mii data.
//!
//! This module contains the structs that represent all the data of a Mii.
//! This data is given by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector)
//!
//! Have a look at the [`MiiSelector`](crate::applets::mii_selector::MiiSelector) applet to learn how to ask the user for a specific Mii.
/// Represents the region lock of the console
/// Region lock of the Mii.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RegionLock {
/// No region-lock.
None,
/// Japan region-lock.
Japan,
/// USA region-lock.
USA,
/// Europe region-lock.
Europe,
}
/// Represent the charset of the console
/// Charset of the Mii.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Charset {
/// Japan-USA-Europe unified charset.
JapanUSAEurope,
/// China charset.
China,
/// Korea charset.
Korea,
/// Taiwan charset.
Taiwan,
}
/// Represents the options of the Mii
/// Generic options of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct MiiDataOptions {
pub struct Options {
/// Whether it is allowed to copy the Mii.
pub is_copying_allowed: bool,
/// Whether the profanity flag is active.
pub is_profanity_flag_enabled: bool,
/// The Mii's active region-lock.
pub region_lock: RegionLock,
/// The Mii's used charset.
pub charset: Charset,
}
/// Represents the position that the Mii has on the selector
/// Positional Index that the Mii has on the [`MiiSelector`](crate::applets::mii_selector::MiiSelector) window.
#[derive(Copy, Clone, Debug)]
pub struct SelectorPosition {
/// Index of the page where the Mii is found.
pub page_index: u8,
/// Index of the slot (relative to the page) where the Mii is found.
pub slot_index: u8,
}
/// Represents the kind of origin console
/// Console model from which the Mii originated.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OriginConsole {
/// Nintendo Wii.
Wii,
/// Nintendo DSi.
DSi,
/// Both New 3DS and Old 3DS
/// Nintendo 3DS.
///
/// This includes all consoles of the 3DS family (3DS, 2DS, and their respective "New" or "XL" variants).
N3DS,
/// Nintendo Wii U/Switch.
WiiUSwitch,
}
/// Represents the identity of the origin console
/// Identity of the origin console.
#[derive(Copy, Clone, Debug)]
pub struct ConsoleIdentity {
/// From which console the Mii originated from.
pub origin_console: OriginConsole,
}
/// Represents the sex of the Mii
/// Sex of the Mii.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiSex {
pub enum Sex {
/// Male sex.
Male,
/// Female sex.
Female,
}
/// Represents the details of the Mii
/// Generic details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct Details {
pub sex: MiiSex,
/// Sex of the Mii.
pub sex: Sex,
/// Birthday month.
pub birthday_month: u8,
/// Birthday day.
pub birthday_day: u8,
/// Color of the Mii's shirt.
pub shirt_color: u8,
/// Whether the Mii is a favorite.
pub is_favorite: bool,
/// Whether the Mii can be shared.
pub is_sharing_enabled: bool,
}
/// Represents the face style of the Mii
/// Face style of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct FaceStyle {
pub is_sharing_enabled: bool,
/// Face shape.
pub shape: u8,
/// Skin color.
pub skin_color: u8,
}
/// Represents the face details of the Mii
/// Face details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct FaceDetails {
/// Face style.
pub style: FaceStyle,
/// Wrinkles.
pub wrinkles: u8,
/// Makeup.
pub makeup: u8,
}
/// Represents the hair details of the Mii
/// Hair details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct HairDetails {
/// Hair style.
pub style: u8,
/// Hair color.
pub color: u8,
/// Whether the Mii's hair is flipped.
pub is_flipped: bool,
}
/// Represents the eye details of the Mii
/// Eye details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct EyeDetails {
/// Eye style.
pub style: u8,
/// Eye color.
pub color: u8,
/// Eye scale.
pub scale: u8,
/// Eye scale (y-axis).
pub y_scale: u8,
/// Eye rotation.
pub rotation: u8,
/// Spacing between the eyes
/// Spacing between the eyes.
pub x_spacing: u8,
/// Eye height.
pub y_position: u8,
}
/// Represents the eyebrow details of the Mii
/// Eyebrow details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct EyebrowDetails {
/// Eyebrow style.
pub style: u8,
/// Eyebrow color.
pub color: u8,
/// Eyebrow scale.
pub scale: u8,
/// Eyebrow scale (y-axis).
pub y_scale: u8,
/// Eyebrow rotation.
pub rotation: u8,
/// Spacing between the eyebrows
pub x_spacing: u8,
/// Eyebrow height.
pub y_position: u8,
}
/// Represents the details of the nose
/// Nose details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct NoseDetails {
/// Nose style.
pub style: u8,
/// Nose scale.
pub scale: u8,
/// Nose height.
pub y_position: u8,
}
/// Represents the details of the mouth
/// Mouth details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct MouthDetails {
/// Mouth style.
pub style: u8,
/// Mouth color.
pub color: u8,
/// Mouth scale.
pub scale: u8,
/// Mouth scale (y-axis).
pub y_scale: u8,
/// Mouth height.
pub y_position: u8,
}
/// Represents the details of the mustache
/// Mustache details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct MustacheDetails {
pub mouth_y_position: u8,
/// Mustache style.
pub mustache_style: u8,
}
/// Represents the details of the beard
/// Beard details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct BeardDetails {
/// Beard style
pub style: u8,
/// Beard color.
pub color: u8,
/// Beard scale.
pub scale: u8,
/// Beard height.
pub y_position: u8,
}
/// Represents the details of the glass
/// Glasses details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct GlassDetails {
pub struct GlassesDetails {
/// Glasses style.
pub style: u8,
/// Glasses color.
pub color: u8,
/// Glasses scale.
pub scale: u8,
/// Glasses height.
pub y_position: u8,
}
/// Represents the details of the mole
/// Mole details of the Mii.
#[derive(Copy, Clone, Debug)]
pub struct MoleDetails {
/// Whether the Mii has a mole.
pub is_enabled: bool,
/// Mole scale.
pub scale: u8,
/// Mole position (x-axis).
pub x_position: u8,
/// Mole position (y-axis).
pub y_position: u8,
}
/// Represents all the data of a Mii
/// Full Mii data representation.
///
/// Some values are not ordered _like_ the Mii Editor UI. The mapped values can be seen here:
/// <https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values>
/// Some values are not ordered *like* the Mii Editor UI. The mapped values can be seen [here](https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values).
///
/// This struct is returned by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector)
/// This struct can be retrieved by [`MiiSelector::launch()`](crate::applets::mii_selector::MiiSelector::launch).
#[derive(Clone, Debug)]
pub struct MiiData {
pub options: MiiDataOptions,
pub struct Mii {
/// Mii options.
pub options: Options,
/// Position taken by the Mii on the Mii Selector screen.
pub selector_position: SelectorPosition,
/// Console the Mii was created on.
pub console_identity: ConsoleIdentity,
/// Unique system ID, not dependant on the MAC address
pub system_id: [u8; 8],
/// Console's MAC address.
pub mac_address: [u8; 6],
/// General information about the Mii.
pub details: Details,
/// Mii name.
pub name: String,
/// Mii height.
pub height: u8,
/// Mii width.
pub width: u8,
/// Face details.
pub face_details: FaceDetails,
/// Hair details.
pub hair_details: HairDetails,
/// Eyes details.
pub eye_details: EyeDetails,
/// Eyebrow details.
pub eyebrow_details: EyebrowDetails,
/// Nose details.
pub nose_details: NoseDetails,
/// Mouth details.
pub mouth_details: MouthDetails,
/// Mustache details.
pub mustache_details: MustacheDetails,
/// Beard details.
pub beard_details: BeardDetails,
pub glass_details: GlassDetails,
/// Glasses details.
pub glass_details: GlassesDetails,
/// Mole details.
pub mole_details: MoleDetails,
/// Name of the Mii's original author.
pub author_name: String,
}
impl From<ctru_sys::MiiData> for MiiData {
impl From<ctru_sys::MiiData> for Mii {
fn from(mii_data: ctru_sys::MiiData) -> Self {
let raw_mii_data = mii_data._bindgen_opaque_blob;
// Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii
@ -272,7 +360,7 @@ impl From<ctru_sys::MiiData> for MiiData { @@ -272,7 +360,7 @@ impl From<ctru_sys::MiiData> for MiiData {
let name = utf16_byte_pairs_to_string(raw_utf16_name);
let author_name = utf16_byte_pairs_to_string(raw_utf16_author);
let options = MiiDataOptions {
let options = Options {
is_copying_allowed: raw_options[0],
is_profanity_flag_enabled: raw_options[1],
region_lock: {
@ -312,19 +400,19 @@ impl From<ctru_sys::MiiData> for MiiData { @@ -312,19 +400,19 @@ impl From<ctru_sys::MiiData> for MiiData {
let details = Details {
sex: {
match raw_details[0] {
true => MiiSex::Female,
false => MiiSex::Male,
true => Sex::Female,
false => Sex::Male,
}
},
birthday_month: partial_u8_bits_to_u8(&raw_details[1..=4]),
birthday_day: partial_u8_bits_to_u8(&raw_details[5..=9]),
shirt_color: partial_u8_bits_to_u8(&raw_details[10..=13]),
is_favorite: raw_details[14],
is_sharing_enabled: !raw_face_style[0],
};
let face_details = FaceDetails {
style: FaceStyle {
is_sharing_enabled: !raw_face_style[0],
shape: partial_u8_bits_to_u8(&raw_face_style[1..=4]),
skin_color: partial_u8_bits_to_u8(&raw_face_style[5..=7]),
},
@ -371,10 +459,10 @@ impl From<ctru_sys::MiiData> for MiiData { @@ -371,10 +459,10 @@ impl From<ctru_sys::MiiData> for MiiData {
color: partial_u8_bits_to_u8(&raw_mouth_details[6..=8]),
scale: partial_u8_bits_to_u8(&raw_mouth_details[9..=12]),
y_scale: partial_u8_bits_to_u8(&raw_mouth_details[13..=15]),
y_position: partial_u8_bits_to_u8(&raw_mustache_details[0..=4]),
};
let mustache_details = MustacheDetails {
mouth_y_position: partial_u8_bits_to_u8(&raw_mustache_details[0..=4]),
mustache_style: partial_u8_bits_to_u8(&raw_mustache_details[5..=7]),
};
@ -385,7 +473,7 @@ impl From<ctru_sys::MiiData> for MiiData { @@ -385,7 +473,7 @@ impl From<ctru_sys::MiiData> for MiiData {
y_position: partial_u8_bits_to_u8(&raw_beard_details[10..=14]),
};
let glass_details = GlassDetails {
let glass_details = GlassesDetails {
style: partial_u8_bits_to_u8(&raw_glass_details[0..=3]),
color: partial_u8_bits_to_u8(&raw_glass_details[4..=6]),
scale: partial_u8_bits_to_u8(&raw_glass_details[7..=10]),
@ -399,7 +487,7 @@ impl From<ctru_sys::MiiData> for MiiData { @@ -399,7 +487,7 @@ impl From<ctru_sys::MiiData> for MiiData {
y_position: partial_u8_bits_to_u8(&raw_mole_details[10..=14]),
};
MiiData {
Mii {
options,
selector_position,
console_identity,

11
ctru-rs/src/prelude.rs

@ -1,2 +1,11 @@ @@ -1,2 +1,11 @@
//! `use ctru::prelude::*;` to import common services, members and functions.
//!
//! Particularly useful when writing very small applications.
pub use crate::console::Console;
pub use crate::services::{gfx::Gfx, hid::KeyPad, soc::Soc, Apt, Hid};
pub use crate::services::{
apt::Apt,
gfx::Gfx,
hid::{Hid, KeyPad},
soc::Soc,
};

149
ctru-rs/src/services/am.rs

@ -1,67 +1,78 @@ @@ -1,67 +1,78 @@
//! Application Manager service.
//!
//! As the name implies, the AM service manages installed applications. It can:
//! - Read the installed applications on the console and their information (depending on the install location).
//! - Install compatible applications to the console.
//!
//! TODO: [`ctru-rs`](crate) doesn't support installing or uninstalling titles yet.
#![doc(alias = "app")]
#![doc(alias = "manager")]
use crate::error::ResultCode;
use crate::services::fs::FsMediaType;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct TitleInfo(ctru_sys::AM_TitleEntry);
impl TitleInfo {
pub fn id(&self) -> u64 {
self.0.titleID
}
pub fn size_bytes(&self) -> u64 {
self.0.size
}
pub fn version(&self) -> u16 {
self.0.version
}
}
/// General information about a specific title entry.
#[doc(alias = "AM_TitleEntry")]
pub struct Title<'a> {
id: u64,
mediatype: FsMediaType,
size: u64,
version: u16,
_am: PhantomData<&'a Am>,
}
impl<'a> Title<'a> {
/// Returns this title's ID.
pub fn id(&self) -> u64 {
self.id
}
pub fn product_code(&self) -> crate::Result<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];
// This operation is safe as long as the title was correctly obtained via [`Am::title_list()`].
unsafe {
ResultCode(ctru_sys::AM_GetTitleProductCode(
self.mediatype.into(),
self.id,
buf.as_mut_ptr(),
))?;
}
Ok(String::from_utf8_lossy(&buf).to_string())
let _ =
ctru_sys::AM_GetTitleProductCode(self.mediatype.into(), self.id, buf.as_mut_ptr());
}
pub fn title_info(&self) -> crate::Result<TitleInfo> {
let mut info = MaybeUninit::zeroed();
unsafe {
ResultCode(ctru_sys::AM_GetTitleInfo(
self.mediatype.into(),
1,
&mut self.id.clone(),
info.as_mut_ptr() as _,
))?;
String::from_utf8_lossy(&buf).to_string()
}
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(());
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> {
unsafe {
ResultCode(ctru_sys::amInit())?;
@ -69,6 +80,27 @@ impl Am { @@ -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> {
unsafe {
let mut count = 0;
@ -77,10 +109,32 @@ impl Am { @@ -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>> {
let count = self.title_count(mediatype)?;
let mut buf = vec![0; count as usize];
let mut read_amount = 0;
unsafe {
ResultCode(ctru_sys::AM_GetTitleList(
&mut read_amount,
@ -89,11 +143,27 @@ impl Am { @@ -89,11 +143,27 @@ impl Am {
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()
.map(|id| Title {
id,
.map(|title| Title {
id: title.titleID,
mediatype,
size: title.size,
version: title.version,
_am: PhantomData,
})
.collect())
@ -101,6 +171,7 @@ impl Am { @@ -101,6 +171,7 @@ impl Am {
}
impl Drop for Am {
#[doc(alias = "amExit")]
fn drop(&mut self) {
unsafe { ctru_sys::amExit() };
}

61
ctru-rs/src/services/apt.rs

@ -1,8 +1,33 @@ @@ -1,8 +1,33 @@
//! Applet service.
//!
//! The APT service handles integration with other applications,
//! including high-level OS features such as Sleep mode, the Home Menu and application switching.
//!
//! It also handles running applets, small programs made available by the OS to streamline specific functionality.
//! Those are implemented in the [`applets`](crate::applets) module.
use crate::error::ResultCode;
/// Handle to the Applet service.
pub struct Apt(());
impl Apt {
/// Initialize a new service handle.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::apt::Apt;
///
/// let apt = Apt::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "aptInit")]
pub fn new() -> crate::Result<Apt> {
unsafe {
ResultCode(ctru_sys::aptInit())?;
@ -10,10 +35,45 @@ impl Apt { @@ -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 {
unsafe { ctru_sys::aptMainLoop() }
}
/// Set (in percentage) the amount of time to lend to the application thread spawned on the syscore (core #1).
///
/// # Notes
///
/// It is necessary to set a time limit before spawning threads on the syscore.
/// The percentage value must be withing 5% and 89%, though it is suggested to use lower values (around 30-45%) to avoid slowing down the OS processes.
#[doc(alias = "APT_SetAppCpuTimeLimit")]
pub fn set_app_cpu_time_limit(&mut self, percent: u32) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?;
@ -23,6 +83,7 @@ impl Apt { @@ -23,6 +83,7 @@ impl Apt {
}
impl Drop for Apt {
#[doc(alias = "aptExit")]
fn drop(&mut self) {
unsafe { ctru_sys::aptExit() };
}

452
ctru-rs/src/services/cam.rs

@ -1,174 +1,232 @@ @@ -1,174 +1,232 @@
//! Camera service
//! Camera service.
//!
//! The CAM service provides access to the cameras. Cameras can return images
//! in the form of byte vectors which can be displayed or used in other ways.
//! The CAM service provides access to the built-in cameras. [`Camera`]s can return images
//! in the form of byte vectors which can be displayed to the screen or used in other ways.
#![doc(alias = "camera")]
use crate::error::{Error, ResultCode};
use crate::services::gspgpu::FramebufferFormat;
use ctru_sys::Handle;
use std::time::Duration;
/// A reference-counted handle to the CAM service and the usable cameras.
/// The service is closed when all instances of this struct fall out of scope.
///
/// This service requires no special permissions to use.
/// Handle to the Camera service.
#[non_exhaustive]
pub struct Cam {
/// Inside-facing camera.
pub inner_cam: InwardCam,
/// Outside-facing right camera.
pub outer_right_cam: OutwardRightCam,
/// Outside-facing left camera.
pub outer_left_cam: OutwardLeftCam,
/// Both outside-facing cameras (mainly used for 3D photos).
pub both_outer_cams: BothOutwardCam,
}
/// Flag to pass to [Camera::flip_image]
/// Different kinds of flip modes.
///
/// See [`Camera::flip_image()`] to learn how to use this.
#[doc(alias = "CAMU_Flip")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FlipMode {
/// No flip.
None = ctru_sys::FLIP_NONE,
/// Horizontal flip.
Horizontal = ctru_sys::FLIP_HORIZONTAL,
/// Vertical flip.
Vertical = ctru_sys::FLIP_VERTICAL,
/// Both vertical and horizontal flip.
Reverse = ctru_sys::FLIP_REVERSE,
}
/// Flag to pass to [Camera::set_view_size]
/// Size of the camera view.
///
/// See [`Camera::set_view_size()`] to learn how to use this.
#[doc(alias = "CAMU_Size")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ViewSize {
/// Size of the 3DS' top screen. (400 × 240)
///
/// Useful if the image is meant to be displayed immediately.
TopLCD = ctru_sys::SIZE_CTR_TOP_LCD,
/// Equivalent to QVga
/// Size of the 3DS' bottom screen. (320×240)
///
/// Equivalent to QVga.
BottomLCD = ctru_sys::SIZE_CTR_BOTTOM_LCD,
/// VGA display size. (640×480)
Vga = ctru_sys::SIZE_VGA,
/// QQVGA display size. (160×120)
QQVga = ctru_sys::SIZE_QQVGA,
/// CIF display size. (352 × 288)
Cif = ctru_sys::SIZE_CIF,
/// QCIF display size. (176 × 144)
QCif = ctru_sys::SIZE_QCIF,
/// Nintendo DS Screen
/// Nintendo DS Screen size. (256 × 192)
DS = ctru_sys::SIZE_DS_LCD,
/// Nintendo DS Screen x4
/// Nintendo DS Screen size x4. (512 × 384)
DSX4 = ctru_sys::SIZE_DS_LCDx4,
}
/// Flag to pass to [Camera::set_frame_rate]
/// Framerate settings.
///
/// See [`Camera::set_frame_rate()`] to learn how to use this.
#[doc(alias = "CAMU_FramRate")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FrameRate {
/// 15 FPS.
Fps15 = ctru_sys::FRAME_RATE_15,
/// 15 to 5 FPS.
Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5,
/// 15 to 2 FPS.
Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2,
/// 10 FPS.
Fps10 = ctru_sys::FRAME_RATE_10,
/// 8.5 FPS.
Fps8_5 = ctru_sys::FRAME_RATE_8_5,
/// 5 FPS.
Fps5 = ctru_sys::FRAME_RATE_5,
/// 20 FPS.
Fps20 = ctru_sys::FRAME_RATE_20,
/// 20 to 5 FPS.
Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5,
/// 30 FPS.
Fps30 = ctru_sys::FRAME_RATE_30,
/// 30 to 5 FPS.
Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5,
/// 15 to 10 FPS.
Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10,
/// 20 to 10 FPS.
Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10,
/// 30 to 10 FPS.
Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10,
}
/// Flag to pass to [Camera::set_white_balance] or
/// [Camera::set_white_balance_without_base_up]
/// White balance settings.
///
/// See [`Camera::set_white_balance()`] and [`Camera::set_white_balance_without_base_up()`] to learn how to use this.
#[doc(alias = "CAMU_WhiteBalance")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum WhiteBalance {
/// Normal
/// Automatic white balance.
Auto = ctru_sys::WHITE_BALANCE_AUTO,
/// Tungsten
/// Tungsten.
Temp3200K = ctru_sys::WHITE_BALANCE_3200K,
/// Fluorescent Light
/// Fluorescent Light.
Temp4150K = ctru_sys::WHITE_BALANCE_4150K,
/// Daylight
/// Daylight.
Temp5200K = ctru_sys::WHITE_BALANCE_5200K,
/// Cloudy/Horizon
/// Cloudy/Horizon.
Temp6000K = ctru_sys::WHITE_BALANCE_6000K,
///Shade
/// Shade.
Temp7000K = ctru_sys::WHITE_BALANCE_7000K,
}
/// Flag to pass to [Camera::set_photo_mode]
/// Photo mode settings.
///
/// See [`Camera::set_photo_mode()`] to learn how to use this.
#[doc(alias = "CAMU_PhotoMode")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum PhotoMode {
/// Normal mode.
Normal = ctru_sys::PHOTO_MODE_NORMAL,
/// Portrait mode.
Portrait = ctru_sys::PHOTO_MODE_PORTRAIT,
/// Landscape mode.
Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE,
/// NightView mode.
NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW,
/// Letter mode.
Letter = ctru_sys::PHOTO_MODE_LETTER,
}
/// Flag to pass to [Camera::set_effect]
/// Special camera effects.
///
/// See [`Camera::set_effect()`] to learn how to use this.
#[doc(alias = "CAMU_Effect")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Effect {
/// No effects.
None = ctru_sys::EFFECT_NONE,
/// Mono effect.
Mono = ctru_sys::EFFECT_MONO,
/// Sepia effect.
Sepia = ctru_sys::EFFECT_SEPIA,
/// Negative effect.
Negative = ctru_sys::EFFECT_NEGATIVE,
/// Negative film effect.
Negafilm = ctru_sys::EFFECT_NEGAFILM,
/// Sepia effect.
///
/// The difference between this and [`Sepia`](Effect::Sepia) is unknown.
Sepia01 = ctru_sys::EFFECT_SEPIA01,
}
/// Flag to pass to [Camera::set_contrast]
/// Contrast settings.
///
/// See [`Camera::set_contrast()`] to learn how to use this.
#[doc(alias = "CAMU_Contrast")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Contrast {
/// OFF
/// Low contrast.
Low = ctru_sys::CONTRAST_LOW,
/// Brightness ratio: 70
/// Brightness ratio: 70.
Normal = ctru_sys::CONTRAST_NORMAL,
/// Brightness ratio: 90
/// Brightness ratio: 90.
High = ctru_sys::CONTRAST_HIGH,
}
/// Flag to pass to [Camera::set_lens_correction]
/// Lens correction settings.
///
/// See [`Camera::set_lens_correction()`] to learn how to use this.
#[doc(alias = "CAMU_LensCorrection")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum LensCorrection {
/// No lens correction.
Off = ctru_sys::LENS_CORRECTION_DARK,
/// Normal lens correction.
Normal = ctru_sys::LENS_CORRECTION_NORMAL,
/// Bright lens correction.
Bright = ctru_sys::LENS_CORRECTION_BRIGHT,
}
/// Flag to pass to [Camera::set_output_format]
/// Image output format.
///
/// See [`Camera::set_output_format()`] to learn how to use this.
#[doc(alias = "CAMU_OutputFormat")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum OutputFormat {
/// YUV422 output format. 16 bits per pixel.
Yuv422 = ctru_sys::OUTPUT_YUV_422,
/// RGB565 output format. 16 bits per pixel.
Rgb565 = ctru_sys::OUTPUT_RGB_565,
}
/// Flag to pass to [Cam::play_shutter_sound]
/// Playable shutter sounds.
///
/// See [`Cam::play_shutter_sound()`] to learn how to use this.
#[doc(alias = "CAMU_ShutterSoundType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ShutterSound {
/// Photo shutter sound.
Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL,
/// Shutter sound to begin a movie recording.
Movie = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE,
/// Shutter sound to finish a movie recording.
MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END,
}
impl TryFrom<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(()),
}
}
}
/// Struct containing coordinates passed to [Camera::set_trimming_params].
/// Parameters to handle image trimming.
///
/// See [`Camera::set_trimming_params()`] to learn how to use this.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrimmingParams {
x_start: i16,
@ -178,10 +236,12 @@ pub struct TrimmingParams { @@ -178,10 +236,12 @@ pub struct TrimmingParams {
}
impl TrimmingParams {
/// Creates a new [CamTrimmingParams] and guarantees the start coordinates are less than or
/// Creates a new [`TrimmingParams`] and guarantees the start coordinates are less than or
/// equal to the end coordinates.
///
/// `x_start <= x_end && y_start <= y_end`
/// # Panics
///
/// This function panics if the start coordinates are larger than the end coordinates (for each axis).
pub fn new(x_start: i16, y_start: i16, x_end: i16, y_end: i16) -> TrimmingParams {
assert!(x_start <= x_end && y_start <= y_end);
Self {
@ -193,15 +253,20 @@ impl TrimmingParams { @@ -193,15 +253,20 @@ impl TrimmingParams {
}
}
/// Represents data used by the camera to calibrate image quality
/// Data used by the camera to calibrate image quality for a single camera.
#[doc(alias = "CAMU_ImageQualityCalibrationData")]
#[derive(Default, Clone, Copy, Debug)]
pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibrationData);
/// Represents data used by the camera to calibrate image quality when using both outward cameras
/// Data used by the camera to calibrate image quality when using both outward cameras.
// TODO: Implement Stereo camera calibration.
#[doc(alias = "CAMU_StereoCameraCalibrationData")]
#[derive(Default, Clone, Copy, Debug)]
pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData);
/// Represents the camera on the inside of the 3DS
/// Inward camera representation (facing the user of the 3DS).
///
/// Usually used for selfies.
#[non_exhaustive]
pub struct InwardCam;
@ -211,8 +276,7 @@ impl Camera for InwardCam { @@ -211,8 +276,7 @@ impl Camera for InwardCam {
}
}
/// Represents the the outer right camera when the 3DS is open and the dual cameras are pointed
/// away from the user
/// Right-side outward camera representation.
#[non_exhaustive]
pub struct OutwardRightCam;
@ -222,8 +286,7 @@ impl Camera for OutwardRightCam { @@ -222,8 +286,7 @@ impl Camera for OutwardRightCam {
}
}
/// Represents the the outer left camera when the 3DS is open and the dual cameras are pointed
/// away from the user
/// Left-side outward camera representation.
#[non_exhaustive]
pub struct OutwardLeftCam;
@ -233,13 +296,15 @@ impl Camera for OutwardLeftCam { @@ -233,13 +296,15 @@ impl Camera for OutwardLeftCam {
}
}
/// Represents the both outer cameras combined
/// Both outer cameras combined.
///
/// Usually used for 3D photos.
#[non_exhaustive]
pub struct BothOutwardCam;
impl BothOutwardCam {
/// Sets whether to enable or disable synchronization
/// of brightness for both left and right cameras
/// Set whether to enable or disable brightness synchronization between the two cameras.
#[doc(alias = "CAMU_SetBrightnessSynchronization")]
pub fn set_brightness_synchronization(
&mut self,
brightness_synchronization: bool,
@ -263,17 +328,37 @@ impl Camera for BothOutwardCam { @@ -263,17 +328,37 @@ impl Camera for BothOutwardCam {
}
}
/// Represents a camera and its functionality
/// Generic functionality common to all cameras.
// TODO: Change "set true/set parameters" scheme (classic of C code) into a single "set parameter" scheme using enums. This is valid for stuff such as [`TrimmingParams`]
pub trait Camera {
/// Returns the raw value of the selected camera
/// Returns the raw value of the selected camera.
fn camera_as_raw(&self) -> ctru_sys::u32_;
/// Returns the raw port of the selected camera
/// Returns the raw port of the selected camera.
fn port_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::PORT_CAM1
}
/// Returns true if the camera is busy (receiving data)
/// Returns `true` if the camera is busy (receiving data).
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
unsafe {
let mut is_busy = false;
@ -283,7 +368,26 @@ pub trait Camera { @@ -283,7 +368,26 @@ pub trait Camera {
}
/// Returns the maximum amount of transfer bytes based on the view size, trimming, and other
/// modifications set to the camera
/// modifications set to the camera.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
unsafe {
let mut transfer_bytes = 0;
@ -295,8 +399,10 @@ pub trait Camera { @@ -295,8 +399,10 @@ pub trait Camera {
}
}
/// Sets whether or not the camera should trim the image based on parameters set by
/// [Camera::set_trimming_params]
/// Set whether or not the camera should trim the image.
///
/// [`TrimmingParams`] can be set via [`Camera::set_trimming_params`].
#[doc(alias = "CAMU_SetTrimming")]
fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), enabled))?;
@ -304,7 +410,8 @@ pub trait Camera { @@ -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> {
unsafe {
let mut trimming = false;
@ -313,7 +420,10 @@ pub trait Camera { @@ -313,7 +420,10 @@ pub trait Camera {
}
}
/// Sets trimming parameters based on coordinates specified inside a [TrimmingParams]
/// Set trimming bounds based on image coordinates.
///
/// For trimming to take effect it is required to pass `true` into [`Camera::set_trimming()`].
#[doc(alias = "CAMU_SetTrimmingParams")]
fn set_trimming_params(&mut self, params: TrimmingParams) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetTrimmingParams(
@ -327,7 +437,8 @@ pub trait Camera { @@ -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> {
unsafe {
let mut x_start = 0;
@ -351,9 +462,14 @@ pub trait Camera { @@ -351,9 +462,14 @@ pub trait Camera {
}
}
/// Sets the trimming parameters revolving around the center of the image.
/// Set the trimming bounds relatively to the center of the image.
///
/// # Notes
///
/// The new width will be `trim_width / 2` to the left and right of the center.
/// The new height will be `trim_height / 2` above and below the center.
// TODO: This function doesn't use `TrimmingParams`. It'd be better to merge it with `set_trimming_params()` and change the `TrimmingParams` representation.
#[doc(alias = "CAMU_SetTrimmingParamsCenter")]
fn set_trimming_params_center(
&mut self,
trim_width: i16,
@ -373,7 +489,8 @@ pub trait Camera { @@ -373,7 +489,8 @@ pub trait Camera {
}
}
/// Sets the exposure level of the camera
/// Set the exposure level of the camera.å
#[doc(alias = "CAMU_SetExposure")]
fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetExposure(self.camera_as_raw(), exposure))?;
@ -381,7 +498,8 @@ pub trait Camera { @@ -381,7 +498,8 @@ pub trait Camera {
}
}
/// Sets the white balance mod of the camera based on the passed [WhiteBalance] argument
/// Set the white balance of the camera.
#[doc(alias = "CAMU_SetWhiteBalance")]
fn set_white_balance(&mut self, white_balance: WhiteBalance) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetWhiteBalance(
@ -392,8 +510,9 @@ pub trait Camera { @@ -392,8 +510,9 @@ pub trait Camera {
}
}
/// Sets the white balance mode of the camera based on the passed [WhiteBalance] argument
// TODO: Explain base up
/// Set the white balance of the camera.
// TODO: Explain what "without base up" means.
#[doc(alias = "CAMU_SetWhiteBalanceWithoutBaseUp")]
fn set_white_balance_without_base_up(
&mut self,
white_balance: WhiteBalance,
@ -407,7 +526,8 @@ pub trait Camera { @@ -407,7 +526,8 @@ pub trait Camera {
}
}
/// Sets the sharpness of the camera
/// Set the sharpness of the camera.
#[doc(alias = "CAMU_SetSharpness")]
fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSharpness(self.camera_as_raw(), sharpness))?;
@ -415,7 +535,8 @@ pub trait Camera { @@ -415,7 +535,8 @@ pub trait Camera {
}
}
/// Sets whether auto exposure is enabled or disabled for the camera
/// Set whether auto exposure is enabled or disabled for the camera.
#[doc(alias = "CAMU_SetAutoExposure")]
fn set_auto_exposure(&mut self, enabled: bool) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetAutoExposure(
@ -426,7 +547,8 @@ pub trait Camera { @@ -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> {
unsafe {
let mut enabled = false;
@ -438,7 +560,8 @@ pub trait Camera { @@ -438,7 +560,8 @@ pub trait Camera {
}
}
/// Sets whether auto white balance is enabled or disabled for the camera
/// Set whether auto white balance is enabled or disabled for the camera.
#[doc(alias = "CAMU_SetAutoWhiteBalance")]
fn set_auto_white_balance(&mut self, enabled: bool) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetAutoWhiteBalance(
@ -449,7 +572,8 @@ pub trait Camera { @@ -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> {
unsafe {
let mut enabled = false;
@ -461,7 +585,8 @@ pub trait Camera { @@ -461,7 +585,8 @@ pub trait Camera {
}
}
/// Sets the flip direction of the camera's image based on the passed [FlipMode] argument
/// Set the flip mode of the camera's image.
#[doc(alias = "CAMU_FlipImage")]
fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_FlipImage(
@ -473,7 +598,7 @@ pub trait Camera { @@ -473,7 +598,7 @@ pub trait Camera {
}
}
/// Sets the image resolution of the camera in detail
/// Set the image resolution of the camera in detail.
///
/// # Errors
///
@ -481,10 +606,12 @@ pub trait Camera { @@ -481,10 +606,12 @@ pub trait Camera {
/// coordinates of the second crop point.
///
/// # Arguments
///
/// * `width` - Width of the image
/// * `height` - height of the image
/// * `crop_0` - The first crop point in which the image will be trimmed
/// * `crop_0` - The second crop point in which the image will be trimmed
/// * `crop_1` - The second crop point in which the image will be trimmed
#[doc(alias = "CAMU_SetDetailSize")]
fn set_detail_size(
&mut self,
width: i16,
@ -507,7 +634,8 @@ pub trait Camera { @@ -507,7 +634,8 @@ pub trait Camera {
}
}
/// Sets the view size of the camera based on the passed [ViewSize] argument.
/// Set the view size of the camera.
#[doc(alias = "CAMU_SetSize")]
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSize(
@ -519,7 +647,8 @@ pub trait Camera { @@ -519,7 +647,8 @@ pub trait Camera {
}
}
/// Sets the frame rate of the camera based on the passed [FrameRate] argument.
/// Set the frame rate of the camera.
#[doc(alias = "CAMU_SetFrameRate")]
fn set_frame_rate(&mut self, frame_rate: FrameRate) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetFrameRate(
@ -530,7 +659,8 @@ pub trait Camera { @@ -530,7 +659,8 @@ pub trait Camera {
}
}
/// Sets the photo mode of the camera based on the passed [PhotoMode] argument.
/// Set the photo mode of the camera.
#[doc(alias = "CAMU_SetPhotoMode")]
fn set_photo_mode(&mut self, photo_mode: PhotoMode) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetPhotoMode(
@ -541,9 +671,13 @@ pub trait Camera { @@ -541,9 +671,13 @@ pub trait Camera {
}
}
/// Sets the effect of the camera based on the passed [Effect] argument.
/// Set the effect of the camera.
///
/// # Notes
///
/// Multiple effects can be set at once by combining the bitflags of [CamEffect]
/// This operation will override any previously set [`Effect`]s.
/// Multiple effects can be set at once by combining the bitflags of [`Effect`].
#[doc(alias = "CAMU_SetEffect")]
fn set_effect(&mut self, effect: Effect) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetEffect(
@ -555,7 +689,8 @@ pub trait Camera { @@ -555,7 +689,8 @@ pub trait Camera {
}
}
/// Sets the contrast of the camera based on the passed [Contrast] argument.
/// Set the contrast of the camera.
#[doc(alias = "CAMU_SetContrast")]
fn set_contrast(&mut self, contrast: Contrast) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetContrast(
@ -566,7 +701,8 @@ pub trait Camera { @@ -566,7 +701,8 @@ pub trait Camera {
}
}
/// Sets the lens correction of the camera based on the passed [LensCorrection] argument.
/// Set the lens correction of the camera.
#[doc(alias = "CAMU_SetLensCorrection")]
fn set_lens_correction(&mut self, lens_correction: LensCorrection) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetLensCorrection(
@ -577,7 +713,8 @@ pub trait Camera { @@ -577,7 +713,8 @@ pub trait Camera {
}
}
/// Sets the output format of the camera based on the passed [OutputFormat] argument.
/// Set the output format of the camera.
#[doc(alias = "CAMU_SetOutputFormat")]
fn set_output_format(&mut self, format: OutputFormat) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetOutputFormat(
@ -589,7 +726,7 @@ pub trait Camera { @@ -589,7 +726,7 @@ pub trait Camera {
}
}
/// Sets the region in which auto exposure should be based on.
/// Set the region in which auto exposure should be based on.
///
/// # Arguments
///
@ -597,6 +734,7 @@ pub trait Camera { @@ -597,6 +734,7 @@ pub trait Camera {
/// * `y` - Starting y coordinate of the window
/// * `width` - Width of the window
/// * `height` - Height of the window
#[doc(alias = "CAMU_SetAutoExposureWindow")]
fn set_auto_exposure_window(
&mut self,
x: i16,
@ -616,7 +754,7 @@ pub trait Camera { @@ -616,7 +754,7 @@ pub trait Camera {
}
}
/// Sets the region in which auto white balance should be based on.
/// Set the region in which auto white balance should be based on.
///
/// # Arguments
///
@ -624,6 +762,11 @@ pub trait Camera { @@ -624,6 +762,11 @@ pub trait Camera {
/// * `y` - Starting y coordinate of the window
/// * `width` - Width of the window
/// * `height` - Height of the window
///
/// # Notes
///
/// To activate automatic white balance, you must pass [`WhiteBalance::Auto`] into [`Camera::set_white_balance()`].
#[doc(alias = "CAMU_SetAutoWhiteBalanceWindow")]
fn set_auto_white_balance_window(
&mut self,
x: i16,
@ -643,7 +786,8 @@ pub trait Camera { @@ -643,7 +786,8 @@ pub trait Camera {
}
}
/// Sets whether the noise filter should be enabled or disabled for the camera
/// Set whether the noise filter should be enabled or disabled for the camera.
#[doc(alias = "CAMU_SetNoiseFilter")]
fn set_noise_filter(&mut self, enabled: bool) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetNoiseFilter(self.camera_as_raw(), enabled))?;
@ -651,8 +795,8 @@ pub trait Camera { @@ -651,8 +795,8 @@ pub trait Camera {
}
}
/// Sets the image quality calibration data for the camera based on the passed in
/// [ImageQualityCalibrationData] argument
/// Set the [`ImageQualityCalibrationData`] for the camera.
#[doc(alias = "CAMU_SetImageQualityCalibrationData")]
fn set_image_quality_calibration_data(
&mut self,
data: ImageQualityCalibrationData,
@ -663,7 +807,8 @@ pub trait Camera { @@ -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> {
unsafe {
let mut data = ImageQualityCalibrationData::default();
@ -672,8 +817,9 @@ pub trait Camera { @@ -672,8 +817,9 @@ pub trait Camera {
}
}
/// Sets the camera as the current sleep camera
/// Set the camera as the current sleep camera.
// TODO: Explain sleep camera
#[doc(alias = "CAMU_SetSleepCamera")]
fn set_sleep_camera(&mut self) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSleepCamera(self.camera_as_raw()))?;
@ -681,17 +827,48 @@ pub trait Camera { @@ -681,17 +827,48 @@ pub trait Camera {
}
}
/// Requests the camera to take a picture and write it in a buffer.
/// Request the camera to take a picture and write it in a buffer.
///
/// # Errors
///
/// This will error if the camera is busy or if the timeout duration is reached.
/// This function will return an error if the camera is busy or if the timeout duration gets reached.
///
/// # Arguments
///
/// * `width` - Width of the desired image
/// * `height` - Height of the desired image
/// * `timeout` - Duration to wait for the image
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # use std::time::Duration;
/// # fn main() -> Result<(), Box<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(
&mut self,
buffer: &mut [u8],
@ -764,13 +941,28 @@ pub trait Camera { @@ -764,13 +941,28 @@ pub trait Camera {
}
impl Cam {
/// Initializes the CAM service.
/// Initialize a new service handle.
///
/// # Errors
///
/// This function will return an error if the service was unable to be initialized.
/// Since this service requires no special or elevated permissions, errors are
/// rare in practice.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::cam::Cam;
///
/// let cam = Cam::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "camInit")]
pub fn new() -> crate::Result<Cam> {
unsafe {
ResultCode(ctru_sys::camInit())?;
@ -783,7 +975,30 @@ impl Cam { @@ -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<()> {
unsafe {
ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?;
@ -793,11 +1008,34 @@ impl Cam { @@ -793,11 +1008,34 @@ impl Cam {
}
impl Drop for Cam {
#[doc(alias = "camExit")]
fn drop(&mut self) {
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!(ViewSize, ctru_sys::CAMU_Size);
from_impl!(FrameRate, ctru_sys::CAMU_FrameRate);

160
ctru-rs/src/services/cfgu.rs

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

149
ctru-rs/src/services/fs.rs

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

217
ctru-rs/src/services/gfx.rs

@ -1,4 +1,8 @@ @@ -1,4 +1,8 @@
//! LCD screens manipulation helper
//! Graphics service.
//!
//! The GFX service controls (in a somewhat high-level way) the console's LCD screens.
//! The screens are subordinate to the GFX service handle and can be used by only one borrower at a time.
#![doc(alias = "graphics")]
use std::cell::{Ref, RefCell, RefMut};
use std::marker::PhantomData;
@ -20,9 +24,12 @@ mod private { @@ -20,9 +24,12 @@ mod private {
impl Sealed for BottomScreen {}
}
/// Trait to handle common functionality for all screens.
///
/// This trait is implemented by the screen structs for working with frame buffers and
/// drawing to the screens. Graphics-related code can be made generic over this
/// trait to work with any of the given screens.
#[doc(alias = "gfxScreen_t")]
pub trait Screen: private::Sealed {
/// Returns the `libctru` value for the Screen kind.
fn as_raw(&self) -> ctru_sys::gfxScreen_t;
@ -34,6 +41,7 @@ pub trait Screen: private::Sealed { @@ -34,6 +41,7 @@ pub trait Screen: private::Sealed {
///
/// Note that the pointer of the framebuffer returned by this function can
/// change after each call to this function if double buffering is enabled.
#[doc(alias = "gfxGetFramebuffer")]
fn raw_framebuffer(&mut self) -> RawFrameBuffer {
let mut width: u16 = 0;
let mut height: u16 = 0;
@ -48,15 +56,8 @@ pub trait Screen: private::Sealed { @@ -48,15 +56,8 @@ pub trait Screen: private::Sealed {
}
}
/// Sets whether to use double buffering. Enabled by default.
///
/// [`Swap::swap_buffers`] must be called after this function for the configuration
/// change to take effect.
fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) }
}
/// Gets the framebuffer format.
#[doc(alias = "gfxGetScreenFormat")]
fn framebuffer_format(&self) -> FramebufferFormat {
unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into()
}
@ -65,40 +66,53 @@ pub trait Screen: private::Sealed { @@ -65,40 +66,53 @@ pub trait Screen: private::Sealed {
///
/// [`Swap::swap_buffers`] must be called after this method for the configuration
/// change to take effect.
#[doc(alias = "gfxSetScreenFormat")]
fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) {
unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) }
}
}
/// The top screen. Mutable access to this struct is required to write to the top
/// screen's frame buffer. To enable 3D mode, it can be converted into a [`TopScreen3D`].
/// The top LCD screen.
///
/// Mutable access to this struct is required to write to the top screen's frame buffer.
///
/// To enable 3D mode, it can be converted into a [`TopScreen3D`].
pub struct TopScreen {
left: TopScreenLeft,
right: TopScreenRight,
}
/// The top LCD screen set in stereoscopic 3D mode.
///
/// A helper container for both sides of the top screen. Once the [`TopScreen`] is
/// converted into this, 3D mode will be enabled until this struct is dropped.
pub struct TopScreen3D<'screen> {
screen: &'screen RefCell<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.
pub trait Swap: private::Sealed {
/// Swaps the video buffers.
///
/// If double buffering is disabled, "swapping" the buffers has the side effect
/// of committing any configuration changes to the buffers (e.g. [`set_wide_mode`],
/// [`set_framebuffer_format`], [`set_double_buffering`]).
/// Even if double buffering is disabled, "swapping" the buffers has the side effect
/// of committing any configuration changes to the buffers (e.g. [`TopScreen::set_wide_mode()`],
/// [`Screen::set_framebuffer_format()`], [`Swap::set_double_buffering()`]), so it should still be used.
///
/// This should be called once per frame at most.
///
/// [`set_wide_mode`]: TopScreen::set_wide_mode
/// [`set_framebuffer_format`]: Screen::set_framebuffer_format
/// [`set_double_buffering`]: Screen::set_double_buffering
#[doc(alias = "gfxScreenSwapBuffers")]
fn swap_buffers(&mut self);
/// Set whether to use double buffering.
///
/// # Notes
///
/// Double buffering is enabled by default.
/// [`Swap::swap_buffers`] must be called after this function for the configuration
/// change to take effect.
#[doc(alias = "gfxSetDoubleBuffering")]
fn set_double_buffering(&mut self, enabled: bool);
}
impl Swap for TopScreen3D<'_> {
@ -107,6 +121,10 @@ impl Swap for TopScreen3D<'_> { @@ -107,6 +121,10 @@ impl Swap for TopScreen3D<'_> {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, true);
}
}
fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_TOP, enabled) }
}
}
impl Swap for TopScreen {
@ -115,6 +133,10 @@ impl Swap for TopScreen { @@ -115,6 +133,10 @@ impl Swap for TopScreen {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, false);
}
}
fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_TOP, enabled) }
}
}
impl Swap for BottomScreen {
@ -123,13 +145,20 @@ impl Swap for BottomScreen { @@ -123,13 +145,20 @@ impl Swap for BottomScreen {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_BOTTOM, false);
}
}
fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(ctru_sys::GFX_BOTTOM, enabled) }
}
}
/// A screen with buffers that can be flushed. This trait applies to any [`Screen`]
/// that has data written to its frame buffer.
/// A screen with buffers that can be flushed.
///
/// This trait applies to any [`Screen`] that has data written to its frame buffer.
pub trait Flush: private::Sealed {
/// Flushes the video buffer(s) for this screen. Note that you must still call
/// [`Swap::swap_buffers`] after this method for the buffer contents to be displayed.
/// Flushes the video buffer(s) for this screen.
///
/// Note that you must still call [`Swap::swap_buffers`] after this method for the buffer contents to be displayed.
#[doc(alias = "gfxFlushBuffers")]
fn flush_buffers(&mut self);
}
@ -138,7 +167,7 @@ impl<S: Screen> Flush for S { @@ -138,7 +167,7 @@ impl<S: Screen> Flush for S {
let framebuffer = self.raw_framebuffer();
// Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens
unsafe {
let _ = unsafe {
ctru_sys::GSPGPU_FlushDataCache(
framebuffer.ptr.cast(),
(framebuffer.height * framebuffer.width) as u32,
@ -167,14 +196,16 @@ pub struct TopScreenLeft; @@ -167,14 +196,16 @@ pub struct TopScreenLeft;
#[non_exhaustive]
pub struct TopScreenRight;
/// The bottom screen. Mutable access to this struct is required to write to the
/// bottom screen's frame buffer.
/// The bottom LCD screen.
///
/// Mutable access to this struct is required to write to the bottom screen's frame buffer.
#[derive(Debug)]
#[non_exhaustive]
pub struct BottomScreen;
/// Representation of a framebuffer for one [`Side`] of the top screen, or the
/// entire bottom screen. The inner pointer is only valid for one frame if double
/// Representation of a framebuffer for one [`Side`] of the top screen, or the entire bottom screen.
///
/// The inner pointer is only valid for one frame if double
/// buffering is enabled. Data written to `ptr` will be rendered to the screen.
#[derive(Debug)]
pub struct RawFrameBuffer<'screen> {
@ -188,11 +219,12 @@ pub struct RawFrameBuffer<'screen> { @@ -188,11 +219,12 @@ pub struct RawFrameBuffer<'screen> {
screen: PhantomData<&'screen mut dyn Screen>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
/// Side of top screen framebuffer
/// Side of the [`TopScreen`]'s framebuffer.
///
/// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality
#[doc(alias = "gfx3dSide_t")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Side {
/// The left framebuffer. This framebuffer is also the one used when 3D is disabled
Left = ctru_sys::GFX_LEFT,
@ -200,12 +232,14 @@ pub enum Side { @@ -200,12 +232,14 @@ pub enum Side {
Right = ctru_sys::GFX_RIGHT,
}
/// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that
/// provides helper functions and utilities for software rendering.
/// Handle to the GFX service.
///
/// The service exits when this struct is dropped.
/// This service is a wrapper around the lower-level [GSPGPU](crate::services::gspgpu) service that
/// provides helper functions and utilities for software rendering.
pub struct Gfx {
/// Top screen representation.
pub top_screen: RefCell<TopScreen>,
/// Bottom screen representation.
pub bottom_screen: RefCell<BottomScreen>,
_service_handler: ServiceReference,
}
@ -213,17 +247,63 @@ pub struct Gfx { @@ -213,17 +247,63 @@ pub struct Gfx {
static GFX_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Gfx {
/// Creates a new [Gfx] instance with default init values
/// Initialize a new default service handle.
///
/// # Notes
///
/// It's the same as calling:
/// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)`
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)
}
/// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom
/// screens
/// Initialize a new service handle with the chosen framebuffer formats for the top and bottom screens.
///
/// Use `Gfx::new()` instead of this function to initialize the module with default parameters
/// Use [`Gfx::new()`] instead of this function to initialize the module with default parameters
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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(
top_fb_fmt: FramebufferFormat,
bottom_fb_fmt: FramebufferFormat,
@ -247,9 +327,32 @@ impl Gfx { @@ -247,9 +327,32 @@ impl Gfx {
})
}
/// Waits for the vertical blank interrupt
/// Waits for the vertical blank event.
///
/// Use this to synchronize your application with the refresh rate of the LCD screens
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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) {
gspgpu::wait_for_event(gspgpu::Event::VBlank0, true);
}
@ -269,7 +372,35 @@ impl TopScreen3D<'_> { @@ -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> {
#[doc(alias = "gfxSet3D")]
fn from(top_screen: &'screen RefCell<TopScreen>) -> Self {
unsafe {
ctru_sys::gfxSet3D(true);
@ -297,8 +428,13 @@ impl TopScreen { @@ -297,8 +428,13 @@ impl TopScreen {
/// Enable or disable wide mode on the top screen.
///
/// # Notes
///
/// [`Swap::swap_buffers`] must be called after this method for the configuration
/// to take effect.
///
/// Wide mode does NOT work on Old 2DS models (but still does on New 2DS XL models).
#[doc(alias = "gfxSetWide")]
pub fn set_wide_mode(&mut self, enable: bool) {
unsafe {
ctru_sys::gfxSetWide(enable);
@ -306,6 +442,7 @@ impl TopScreen { @@ -306,6 +442,7 @@ impl TopScreen {
}
/// Returns whether or not wide mode is enabled on the top screen.
#[doc(alias = "gfxIsWide")]
pub fn is_wide(&self) -> bool {
unsafe { ctru_sys::gfxIsWide() }
}

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

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

201
ctru-rs/src/services/hid.rs

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

15
ctru-rs/src/services/mod.rs

@ -1,9 +1,15 @@ @@ -1,9 +1,15 @@
//! System services used to handle system-specific functionalities.
//! OS services used to handle system-specific functionality.
//!
//! Most of the 3DS console's functionalities (when writing homebrew) are locked behind services,
//! Most of the 3DS console's functionalities (when writing user-land homebrew) are accessible via services,
//! which need to be initialized before accessing any particular feature.
//!
//! Some include: button input, audio playback, graphics rendering, built-in cameras, etc.
//! To ensure safety while using the underlying services, [`ctru-rs`](crate) leverages Rust's lifetime model.
//! After initializing the handle for a specific service (e.g. [`Apt`](apt::Apt)) the service will be accessible as long as there is at least one handle "alive".
//! As such, handles should be dropped *after* the use of a specific service. This is particularly important for services which are necessary for functionality
//! "outside" their associated methods, such as [`RomFS`](romfs::RomFS), which creates an accessible virtual filesystem, or [`Soc`](soc::Soc),
//! which enables all network communications via sockets.
//!
//! In [`ctru-rs`](crate) some services only allow a single handle to be created at a time, to ensure a safe and controlled environment.
pub mod am;
pub mod apt;
@ -42,7 +48,4 @@ cfg_if::cfg_if! { @@ -42,7 +48,4 @@ cfg_if::cfg_if! {
}
}
pub use self::apt::Apt;
pub use self::hid::Hid;
pub(crate) use self::reference::ServiceReference;

420
ctru-rs/src/services/ndsp/mod.rs

@ -1,7 +1,12 @@ @@ -1,7 +1,12 @@
//! NDSP (Audio) service
//! NDSP (Audio) service.
//!
//! The NDSP service is used to handle communications to the DSP processor present on the console's motherboard.
//! Thanks to the DSP processor the program can play sound effects and music on the console's built-in speakers or to any audio device
//! connected via the audio jack.
#![doc(alias = "audio")]
pub mod wave;
use wave::{Wave, WaveStatus};
use wave::{Status, Wave};
use crate::error::ResultCode;
use crate::services::ServiceReference;
@ -14,49 +19,80 @@ use std::sync::Mutex; @@ -14,49 +19,80 @@ use std::sync::Mutex;
const NUMBER_OF_CHANNELS: u8 = 24;
/// Audio output mode.
#[doc(alias = "ndspOutputMode")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum OutputMode {
/// Single-Channel.
Mono = ctru_sys::NDSP_OUTPUT_MONO,
/// Dual-Channel.
Stereo = ctru_sys::NDSP_OUTPUT_STEREO,
/// Surround.
Surround = ctru_sys::NDSP_OUTPUT_SURROUND,
}
/// PCM formats supported by the audio engine.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum AudioFormat {
/// PCM 8bit single-channel.
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
/// PCM 16bit single-channel.
PCM16Mono = ctru_sys::NDSP_FORMAT_MONO_PCM16,
/// PCM 8bit interleaved dual-channel.
PCM8Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM8,
/// PCM 16bit interleaved dual-channel.
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
}
/// Representation of volume mix for a channel.
/// Representation of the volume mix for a channel.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct AudioMix {
raw: [f32; 12],
}
/// Interpolation used between audio frames.
#[doc(alias = "ndspInterpType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum InterpolationType {
/// Polyphase interpolation.
Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
/// Linear interpolation.
Linear = ctru_sys::NDSP_INTERP_LINEAR,
/// No interpolation.
None = ctru_sys::NDSP_INTERP_NONE,
}
/// Errors returned by [`ndsp`](self) functions.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NdspError {
/// Channel ID
/// Channel with the specified ID does not exist.
InvalidChannel(u8),
/// Channel ID
/// Channel with the specified ID is already being used.
ChannelAlreadyInUse(u8),
/// Channel ID
/// The wave is already busy playing in the channel with the specified ID.
WaveBusy(u8),
/// Sample amount requested, Max sample amount
/// The sample amount requested was larger than the maximum.
SampleCountOutOfBounds(usize, usize),
}
/// NDSP Channel representation.
///
/// There are 24 individual channels in total and each can play a different audio [`Wave`] simultaneuosly.
///
/// # Default
///
/// NDSP initialises all channels with default values on initialization, but the developer is supposed to change these values to correctly work with the service.
///
/// In particular:
/// - Default audio format is set to [`AudioFormat::PCM16Mono`].
/// - Default sample rate is set to 1 Hz.
/// - Default interpolation type is set to [`InterpolationType::Polyphase`].
/// - Default mix is set to [`AudioMix::default()`]
///
/// The handle to a channel can be retrieved with [`Ndsp::channel()`]
pub struct Channel<'ndsp> {
id: u8,
_rf: RefMut<'ndsp, ()>, // we don't need to hold any data
@ -64,10 +100,9 @@ pub struct Channel<'ndsp> { @@ -64,10 +100,9 @@ pub struct Channel<'ndsp> {
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 "instance" of this struct can exist at a time.
/// Only one handle for this service can exist at a time.
pub struct Ndsp {
_service_handler: ServiceReference,
channel_flags: [RefCell<()>; NUMBER_OF_CHANNELS as usize],
@ -78,8 +113,23 @@ impl Ndsp { @@ -78,8 +113,23 @@ impl Ndsp {
///
/// # Errors
///
/// This function will return an error if an instance of the `Ndsp` struct already exists
/// This function will return an error if an instance of the [`Ndsp`] struct already exists
/// or if there are any issues during initialization.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::ndsp::Ndsp;
///
/// let ndsp = Ndsp::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "ndspInit")]
pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new(
&NDSP_ACTIVE,
@ -105,6 +155,21 @@ impl Ndsp { @@ -105,6 +155,21 @@ impl Ndsp {
/// # Errors
///
/// An error will be returned if the channel ID is not between 0 and 23 or if the specified channel is already being used.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
let in_bounds = self.channel_flags.get(id as usize);
@ -120,34 +185,138 @@ impl Ndsp { @@ -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) {
unsafe { ctru_sys::ndspSetOutputMode(mode.into()) };
}
}
impl Channel<'_> {
/// Reset the channel
/// Reset the channel (clear the queue and reset parameters).
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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) {
unsafe { ctru_sys::ndspChnReset(self.id.into()) };
}
/// Initialize the channel's parameters
pub fn init_parameters(&self) {
/// Initialize the channel's parameters with default values.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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()) };
}
/// 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 {
unsafe { ctru_sys::ndspChnIsPlaying(self.id.into()) }
}
/// Returns whether the channel's playback is currently paused.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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 {
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 {
self.id
}
@ -155,46 +324,161 @@ impl Channel<'_> { @@ -155,46 +324,161 @@ impl Channel<'_> {
/// Returns the index of the currently played sample.
///
/// Because of how fast this value changes, it should only be used as a rough estimate of the current progress.
#[doc(alias = "ndspChnGetSamplePos")]
pub fn sample_position(&self) -> usize {
(unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize
}
/// Returns the channel's current wave sequence's id.
#[doc(alias = "ndspChnGetWaveBufSeq")]
pub fn wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
}
/// Pause or un-pause the channel's playback.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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) {
unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) };
}
/// Set the channel's output format.
/// Change this setting based on the used sample's format.
///
/// Change this setting based on the used wave's format.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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) {
unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) };
}
/// Set the channel's interpolation mode.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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) {
unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) };
}
/// Set the channel's volume mix.
///
/// Look at [`AudioMix`] for more information on the volume mix.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # use std::default::Default;
/// # fn main() -> Result<(), Box<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) {
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) {
unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) };
}
// `ndspChnSetAdpcmCoefs` isn't wrapped on purpose.
// DSPADPCM is a proprietary format used by Nintendo, unavailable by "normal" means.
// We suggest using other wave formats when developing homebrew applications.
// TODO: wrap ADPCM format helpers.
/// Clear the wave buffer queue and stop playback.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) };
}
@ -204,11 +488,38 @@ impl Channel<'_> { @@ -204,11 +488,38 @@ impl Channel<'_> {
///
/// # Warning
///
/// `libctru` expects the user to manually keep the info data (in this case [Wave]) alive during playback.
/// To ensure safety, checks within [Wave] will clear the whole channel queue if any queued [Wave] is dropped prematurely.
/// `libctru` expects the user to manually keep the info data (in this case [`Wave`]) alive during playback.
/// To ensure safety, checks within [`Wave`] will clear the whole channel queue if any queued [`Wave`] is dropped prematurely.
///
/// # Example
///
/// ```no_run
/// # #![feature(allocator_api)]
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
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<'_> { @@ -222,65 +533,74 @@ impl Channel<'_> {
/// Functions to handle audio filtering.
///
/// Refer to [libctru](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info.
/// Refer to [`libctru`](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info.
impl Channel<'_> {
/// Enables/disables monopole filters.
#[doc(alias = "ndspChnIirMonoSetEnable")]
pub fn iir_mono_set_enabled(&mut self, enable: bool) {
unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) };
}
/// Sets the monopole to be a high pass filter.
/// Set the monopole to be a high pass filter.
///
/// # Notes
///
/// This is a lower quality filter than the Biquad alternative.
#[doc(alias = "ndspChnIirMonoSetParamsHighPassFilter")]
pub fn iir_mono_set_params_high_pass_filter(&mut self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) };
}
/// Sets the monopole to be a low pass filter.
/// Set the monopole to be a low pass filter.
///
/// # Notes
///
/// This is a lower quality filter than the Biquad alternative.
#[doc(alias = "ndspChnIirMonoSetParamsLowPassFilter")]
pub fn iir_mono_set_params_low_pass_filter(&mut self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) };
}
/// Enables/disables biquad filters.
#[doc(alias = "ndspChnIirBiquadSetEnable")]
pub fn iir_biquad_set_enabled(&mut self, enable: bool) {
unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) };
}
/// Sets the biquad to be a high pass filter.
/// Set the biquad to be a high pass filter.
#[doc(alias = "ndspChnIirBiquadSetParamsHighPassFilter")]
pub fn iir_biquad_set_params_high_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality)
};
}
/// Sets the biquad to be a low pass filter.
/// Set the biquad to be a low pass filter.
#[doc(alias = "ndspChnIirBiquadSetParamsLowPassFilter")]
pub fn iir_biquad_set_params_low_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality)
};
}
/// Sets the biquad to be a notch filter.
/// Set the biquad to be a notch filter.
#[doc(alias = "ndspChnIirBiquadSetParamsNotchFilter")]
pub fn iir_biquad_set_params_notch_filter(&mut self, notch_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality)
};
}
/// Sets the biquad to be a band pass filter.
/// Set the biquad to be a band pass filter.
#[doc(alias = "ndspChnIirBiquadSetParamsBandPassFilter")]
pub fn iir_biquad_set_params_band_pass_filter(&mut self, mid_freq: f32, quality: f32) {
unsafe {
ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality)
};
}
/// Sets the biquad to be a peaking equalizer.
/// Set the biquad to be a peaking equalizer.
#[doc(alias = "ndspChnIirBiquadSetParamsPeakingEqualizer")]
pub fn iir_biquad_set_params_peaking_equalizer(
&mut self,
central_freq: f32,
@ -301,9 +621,10 @@ impl Channel<'_> { @@ -301,9 +621,10 @@ impl Channel<'_> {
impl AudioFormat {
/// Returns the amount of bytes needed to store one sample
///
/// Eg.
/// 8 bit mono formats return 1 (byte)
/// 16 bit stereo (dual-channel) formats return 4 (bytes)
/// # Example
///
/// - 8 bit mono formats return 1 (byte)
/// - 16 bit stereo (dual-channel) formats return 4 (bytes)
pub const fn size(self) -> usize {
match self {
Self::PCM8Mono => 1,
@ -314,7 +635,7 @@ impl AudioFormat { @@ -314,7 +635,7 @@ impl AudioFormat {
}
impl AudioMix {
/// Creates a new [AudioMix] with all volumes set to 0.
/// Creates a new [`AudioMix`] with all volumes set to 0.
pub fn zeroed() -> Self {
Self { raw: [0.; 12] }
}
@ -361,34 +682,34 @@ impl AudioMix { @@ -361,34 +682,34 @@ impl AudioMix {
(self.raw[index], self.raw[index + 1])
}
/// Sets the values for the "front" volume mix (left and right channel).
/// Set the values for the "front" volume mix (left and right channel).
///
/// # Notes
///
/// [Channel] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid.
/// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_front(&mut self, left: f32, right: f32) {
self.raw[0] = left;
self.raw[1] = right;
}
/// Sets the values for the "back" volume mix (left and right channel).
/// Set the values for the "back" volume mix (left and right channel).
///
/// # Notes
///
/// [Channel] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid.
/// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_back(&mut self, left: f32, right: f32) {
self.raw[2] = left;
self.raw[3] = right;
}
/// Sets the values for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
/// Set the values for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
///
/// # Notes
///
/// [Channel] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid.
/// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) {
if id > 1 {
panic!("invalid auxiliary output device index")
@ -400,12 +721,12 @@ impl AudioMix { @@ -400,12 +721,12 @@ impl AudioMix {
self.raw[index + 1] = right;
}
/// Sets the values for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
/// Set the values for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
///
/// # Notes
///
/// [Channel] will normalize the mix values to be within 0 and 1.
/// However, an [AudioMix] instance with larger/smaller values is valid.
/// [`Channel`] will normalize the mix values to be within 0 and 1.
/// However, an [`AudioMix`] instance with larger/smaller values is valid.
pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) {
if id > 1 {
panic!("invalid auxiliary output device index")
@ -418,8 +739,8 @@ impl AudioMix { @@ -418,8 +739,8 @@ impl AudioMix {
}
}
/// Returns an [AudioMix] object with "front left" and "front right" volumes set to 100%, and all other volumes set to 0%.
impl Default for AudioMix {
/// Returns an [`AudioMix`] object with "front left" and "front right" volumes set to 100%, and all other volumes set to 0%.
fn default() -> Self {
let mut mix = AudioMix::zeroed();
mix.set_front(1.0, 1.0);
@ -448,6 +769,7 @@ impl fmt::Display for NdspError { @@ -448,6 +769,7 @@ impl fmt::Display for NdspError {
impl error::Error for NdspError {}
impl Drop for Ndsp {
#[doc(alias = "ndspExit")]
fn drop(&mut self) {
for i in 0..NUMBER_OF_CHANNELS {
self.channel(i).unwrap().reset();

85
ctru-rs/src/services/ndsp/wave.rs

@ -1,7 +1,13 @@ @@ -1,7 +1,13 @@
//! Audio wave.
//!
//! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service.
use super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf].
/// Informational struct holding the raw audio data and playback info.
///
/// You can play audio [`Wave`]s by using [`Channel::queue_wave()`](super::Channel::queue_wave).
pub struct Wave {
/// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>,
@ -13,16 +19,36 @@ pub struct Wave { @@ -13,16 +19,36 @@ pub struct Wave {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
/// Enum representing the playback status of a [Wave].
pub enum WaveStatus {
/// Playback status of a [`Wave`].
pub enum Status {
/// Wave has never been used.
Free = ctru_sys::NDSP_WBUF_FREE as u8,
/// Wave is currently queued for usage.
Queued = ctru_sys::NDSP_WBUF_QUEUED as u8,
/// Wave is currently playing.
Playing = ctru_sys::NDSP_WBUF_PLAYING as u8,
/// Wave has finished playing.
Done = ctru_sys::NDSP_WBUF_DONE as u8,
}
impl Wave {
/// Build a new playable wave object from a raw buffer on LINEAR memory and a some info.
/// Build a new playable wave object from a raw buffer on [LINEAR memory](`crate::linear`) and some info.
///
/// # Example
///
/// ```no_run
/// # #![feature(allocator_api)]
/// # fn main() {
/// #
/// use ctru::linear::LinearAllocator;
/// use ctru::services::ndsp::{AudioFormat, wave::Wave};
///
/// // Zeroed box allocated in the LINEAR memory.
/// let audio_data = Box::new_in([0u8; 96], LinearAllocator);
///
/// let wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false);
/// # }
/// ```
pub fn new(
buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
@ -60,41 +86,60 @@ impl Wave { @@ -60,41 +86,60 @@ impl Wave {
}
}
/// Return a slice to the audio data (on the LINEAR memory).
/// Returns a slice to the audio data (on the LINEAR memory).
pub fn get_buffer(&self) -> &[u8] {
&self.buffer
}
/// Return a mutable slice to the audio data (on the LINEAR memory).
/// Returns a mutable slice to the audio data (on the LINEAR memory).
///
/// # Errors
///
/// This function will return an error if the [Wave] is currently busy,
/// This function will return an error if the [`Wave`] is currently busy,
/// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> {
match self.status() {
WaveStatus::Playing | WaveStatus::Queued => {
Status::Playing | Status::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
}
_ => Ok(&mut self.buffer),
}
}
/// Return this wave's playback status.
pub fn status(&self) -> WaveStatus {
/// Returns this wave's playback status.
///
/// # Example
///
/// ```no_run
/// # #![feature(allocator_api)]
/// # fn main() {
/// #
/// # use ctru::linear::LinearAllocator;
/// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator);
/// #
/// use ctru::services::ndsp::{AudioFormat, wave::{Wave, Status}};
///
/// // Provide your own audio data.
/// let wave = Wave::new(_audio_data, AudioFormat::PCM16Stereo, false);
///
/// // The `Wave` is free if never played before.
/// assert!(matches!(wave.status(), Status::Free));
/// # }
/// ```
pub fn status(&self) -> Status {
self.raw_data.status.try_into().unwrap()
}
/// Get the amounts of samples *read* by the NDSP process.
/// Returns the amount of samples *read* by the NDSP process.
///
/// # Notes
///
/// This value varies depending on [Self::set_sample_count].
/// This value varies depending on [`Wave::set_sample_count`].
pub fn sample_count(&self) -> usize {
self.raw_data.nsamples as usize
}
/// Get the format of the audio data.
/// Returns the format of the audio data.
pub fn format(&self) -> AudioFormat {
self.audio_format
}
@ -107,20 +152,20 @@ impl Wave { @@ -107,20 +152,20 @@ impl Wave {
}
/// Set the amount of samples to be read.
/// This function doesn't resize the internal buffer.
///
/// # Note
///
/// Operations of this kind are particularly useful to allocate memory pools
/// for VBR (Variable BitRate) Formats, like OGG Vorbis.
///
/// This function doesn't resize the internal buffer. Operations of this kind are particularly useful to allocate memory pools
/// for VBR (Variable BitRate) formats, like OGG Vorbis.
///
/// # Errors
///
/// This function will return an error if the sample size exceeds the buffer's capacity
/// or if the [Wave] is currently queued.
/// or if the [`Wave`] is currently queued.
pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> {
match self.status() {
WaveStatus::Playing | WaveStatus::Queued => {
Status::Playing | Status::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap()));
}
_ => (),
@ -138,7 +183,7 @@ impl Wave { @@ -138,7 +183,7 @@ impl Wave {
}
}
impl TryFrom<u8> for WaveStatus {
impl TryFrom<u8> for Status {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
@ -157,7 +202,7 @@ impl Drop for Wave { @@ -157,7 +202,7 @@ impl Drop for Wave {
// This was the only way I found I could check for improper drops of `Wave`.
// A panic was considered, but it would cause issues with drop order against `Ndsp`.
match self.status() {
WaveStatus::Free | WaveStatus::Done => (),
Status::Free | Status::Done => (),
// If the status flag is "unfinished"
_ => {
// The unwrap is safe, since it must have a value in the case the status is "unfinished".

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

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

41
ctru-rs/src/services/romfs.rs

@ -1,9 +1,14 @@ @@ -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`
//! feature is enabled.
//!
//! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the
//! Configure the path in your project's `Cargo.toml` manifest (the default path is "romfs"). Paths are relative to the
//! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of
//! your package.
//!
@ -11,6 +16,16 @@ @@ -11,6 +16,16 @@
//! [package.metadata.cargo-3ds]
//! romfs_dir = "romfs"
//! ```
//!
//! Alternatively, you can include the RomFS archive manually when building with `3dsxtool`.
//!
//! # Notes
//!
//! `std::path` has problems when parsing file paths that include the `romfs:` prefix.
//! As such, it's suggested to use the paths directly or to do simple append operations to avoid unexpected behaviour.
//! Related [issue](https://github.com/rust-lang/rust/issues/52331).
#![doc(alias = "embed")]
#![doc(alias = "filesystem")]
use crate::error::ResultCode;
use std::ffi::CStr;
@ -18,6 +33,7 @@ use std::sync::Mutex; @@ -18,6 +33,7 @@ use std::sync::Mutex;
use crate::services::ServiceReference;
/// Handle to the RomFS service.
pub struct RomFS {
_service_handler: ServiceReference,
}
@ -25,6 +41,25 @@ pub struct RomFS { @@ -25,6 +41,25 @@ pub struct RomFS {
static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0);
impl RomFS {
/// Mount the bundled RomFS archive as a virtual drive.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
let _service_handler = ServiceReference::new(
&ROMFS_ACTIVE,
@ -36,7 +71,7 @@ impl RomFS { @@ -36,7 +71,7 @@ impl RomFS {
},
|| {
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap();
unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) };
let _ = unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) };
},
)?;

104
ctru-rs/src/services/soc.rs

@ -1,4 +1,9 @@ @@ -1,4 +1,9 @@
//! Network Socket
//! Network Socket service.
//!
//! By using this service the program enables the use of network sockets and utilities such as those found in `std::net`, which are completely inaccessible by default.
//! As such, remember to hold a handle to this service handle while using any network functionality, or else the `std::net` methods will return generic OS errors.
#![doc(alias = "socket")]
#![doc(alias = "network")]
use libc::memalign;
use std::net::Ipv4Addr;
@ -8,10 +13,7 @@ use crate::error::ResultCode; @@ -8,10 +13,7 @@ use crate::error::ResultCode;
use crate::services::ServiceReference;
use crate::Error;
/// Network socket service
///
/// Initializing this service will enable the use of network sockets and utilities
/// such as those found in `std::net`. The service will close once this struct gets dropped.
/// Handle to the Network Socket service.
pub struct Soc {
_service_handler: ServiceReference,
sock_3dslink: libc::c_int,
@ -20,21 +22,52 @@ pub struct Soc { @@ -20,21 +22,52 @@ pub struct Soc {
static SOC_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Soc {
/// Initialize the Soc service with a default buffer size of 0x100000 bytes
/// Initialize a new service handle using a socket buffer size of `0x100000` bytes.
///
/// # Errors
///
/// This function will return an error if the `Soc` service is already initialized
/// This function will return an error if the [`Soc`] service is already being used.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::soc::Soc;
///
/// let soc = Soc::new()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "socInit")]
pub fn new() -> crate::Result<Self> {
Self::init_with_buffer_size(0x100000)
}
/// Initialize the Soc service with a custom buffer size in bytes. The size should be
/// 0x100000 bytes or greater.
/// Initialize a new service handle using a custom socket buffer size.
///
/// The size should be `0x100000` bytes or greater.
///
/// # Errors
///
/// This function will return an error if the `Soc` service is already initialized
/// This function will return an error if the [`Soc`] service is already being used.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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> {
let _service_handler = ServiceReference::new(
&SOC_ACTIVE,
@ -60,19 +93,59 @@ impl Soc { @@ -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 {
let raw_id = unsafe { libc::gethostid() };
Ipv4Addr::from(raw_id.to_ne_bytes())
}
/// Redirect output streams (i.e. [`println`] and [`eprintln`]) to the `3dslink` server.
/// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0.
/// Redirect output streams (i.e. `stdout` and `stderr`) to the `3dslink` server.
///
/// With this redirection it is possible to send (and view in real time) the output of `stdout` operations,
/// such as `println!` or `dbg!`.
///
/// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0 and the use of the `--server` flag.
/// The `--server` flag is also availble to use via `cargo-3ds` if the requirements are met.
///
/// # Errors
///
/// Returns an error if a connection cannot be established to the server, or
/// output was already previously redirected.
/// Returns an error if a connection cannot be established to the server,
/// or if the output was already being redirected.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<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<()> {
if self.sock_3dslink >= 0 {
return Err(Error::OutputAlreadyRedirected);
@ -92,6 +165,7 @@ impl Soc { @@ -92,6 +165,7 @@ impl Soc {
}
impl Drop for Soc {
#[doc(alias = "socExit")]
fn drop(&mut self) {
if self.sock_3dslink >= 0 {
unsafe {

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

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

10
ctru-sys/Cargo.toml

@ -2,6 +2,11 @@ @@ -2,6 +2,11 @@
name = "ctru-sys"
version = "22.2.0+2.2.2-1"
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 = ["src/.gitattributes"]
license = "Zlib"
links = "ctru"
edition = "2021"
@ -14,3 +19,8 @@ bindgen = { version = "0.65.1", features = ["experimental"] } @@ -14,3 +19,8 @@ bindgen = { version = "0.65.1", features = ["experimental"] }
cc = "1.0"
doxygen-rs = "0.4.2"
which = "4.4.0"
[package.metadata.docs.rs]
default-target = "armv6k-nintendo-3ds"
targets = []
cargo-args = ["-Z", "build-std"]

33
ctru-sys/README.md

@ -0,0 +1,33 @@ @@ -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.
Loading…
Cancel
Save