Browse Source

Merge branch 'master' into feature/ir-user

# Conflicts:
#	ctru-rs/src/error.rs
pull/129/head
AzureMarker 2 years ago
parent
commit
c55244d9da
No known key found for this signature in database
GPG Key ID: 47A133F3BF9D03D3
  1. 1
      .github/CODEOWNERS
  2. 46
      .github/actions/setup/action.yml
  3. 56
      .github/workflows/ci.yml
  4. 4
      AUTHORS.md
  5. 21
      LICENSE
  6. 40
      README.md
  7. 8
      ctru-rs/Cargo.toml
  8. 48
      ctru-rs/examples/audio-filters.rs
  9. 25
      ctru-rs/examples/buttons.rs
  10. 112
      ctru-rs/examples/camera-image.rs
  11. 44
      ctru-rs/examples/file-explorer.rs
  12. 13
      ctru-rs/examples/futures-basic.rs
  13. 13
      ctru-rs/examples/futures-tokio.rs
  14. 33
      ctru-rs/examples/gfx-3d-mode.rs
  15. 74
      ctru-rs/examples/gfx-bitmap.rs
  16. 21
      ctru-rs/examples/gfx-wide-mode.rs
  17. 21
      ctru-rs/examples/graphics-bitmap.rs
  18. 17
      ctru-rs/examples/hashmaps.rs
  19. 18
      ctru-rs/examples/hello-both-screens.rs
  20. 16
      ctru-rs/examples/hello-world.rs
  21. 16
      ctru-rs/examples/linear-memory.rs
  22. 17
      ctru-rs/examples/mii-selector.rs
  23. 15
      ctru-rs/examples/network-sockets.rs
  24. 16
      ctru-rs/examples/output-3dslink.rs
  25. 18
      ctru-rs/examples/romfs.rs
  26. 30
      ctru-rs/examples/software-keyboard.rs
  27. 24
      ctru-rs/examples/system-configuration.rs
  28. 14
      ctru-rs/examples/thread-basic.rs
  29. 13
      ctru-rs/examples/thread-info.rs
  30. 13
      ctru-rs/examples/thread-locals.rs
  31. 16
      ctru-rs/examples/time-rtc.rs
  32. 112
      ctru-rs/examples/title-info.rs
  33. 49
      ctru-rs/examples/touch-screen.rs
  34. 58
      ctru-rs/src/applets/mii_selector.rs
  35. 129
      ctru-rs/src/applets/swkbd.rs
  36. 8
      ctru-rs/src/console.rs
  37. 40
      ctru-rs/src/error.rs
  38. 73
      ctru-rs/src/lib.rs
  39. 3
      ctru-rs/src/linear.rs
  40. 3
      ctru-rs/src/prelude.rs
  41. 107
      ctru-rs/src/services/am.rs
  42. 4
      ctru-rs/src/services/apt.rs
  43. 396
      ctru-rs/src/services/cam.rs
  44. 54
      ctru-rs/src/services/cfgu.rs
  45. 202
      ctru-rs/src/services/fs.rs
  46. 195
      ctru-rs/src/services/gfx.rs
  47. 63
      ctru-rs/src/services/gspgpu.rs
  48. 117
      ctru-rs/src/services/hid.rs
  49. 25
      ctru-rs/src/services/mod.rs
  50. 246
      ctru-rs/src/services/ndsp/mod.rs
  51. 49
      ctru-rs/src/services/ndsp/wave.rs
  52. 70
      ctru-rs/src/services/ps.rs
  53. 6
      ctru-rs/src/services/romfs.rs
  54. 14
      ctru-rs/src/services/soc.rs
  55. 17
      ctru-rs/src/services/sslc.rs
  56. 41
      ctru-rs/src/test_runner.rs
  57. 9
      ctru-sys/Cargo.toml
  58. 39
      ctru-sys/bindgen.sh
  59. 71
      ctru-sys/build.rs
  60. 2
      ctru-sys/docstring-to-rustdoc/Cargo.toml
  61. 1
      ctru-sys/src/.gitattributes
  62. 9755
      ctru-sys/src/bindings.rs

1
.github/CODEOWNERS

@ -0,0 +1 @@
* @rust3ds/active

46
.github/actions/setup/action.yml

@ -0,0 +1,46 @@
name: Setup
description: Set up CI environment for Rust + 3DS development
inputs:
toolchain:
description: The Rust toolchain to use for the steps
required: true
default: nightly
runs:
using: composite
steps:
# https://github.com/nektos/act/issues/917#issuecomment-1074421318
- if: ${{ env.ACT }}
shell: bash
name: Hack container for local development
run: |
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
- name: Setup default Rust toolchain
# Use this helper action so we get matcher support
# https://github.com/actions-rust-lang/setup-rust-toolchain/pull/15
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy, rustfmt, rust-src
toolchain: ${{ inputs.toolchain }}
- name: Install build tools for host
shell: bash
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Install cargo-3ds
uses: actions-rs/cargo@v1
with:
command: install
# TODO: this should probably just be a released version from crates.io
# once cargo-3ds gets published somewhere...
args: >-
--git https://github.com/rust3ds/cargo-3ds
--rev 78a652fdfb01e2614a792d1a56b10c980ee1dae9
- name: Set PATH to include devkitARM
shell: bash
# For some reason devkitARM/bin is not part of the default PATH in the container
run: echo "${DEVKITARM}/bin" >> $GITHUB_PATH

56
.github/workflows/ci.yml

@ -12,6 +12,8 @@ on:
env: env:
# https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html # https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html
CARGO_UNSTABLE_SPARSE_REGISTRY: "true" CARGO_UNSTABLE_SPARSE_REGISTRY: "true"
# actions-rust-lang/setup-rust-toolchain sets some default RUSTFLAGS
RUSTFLAGS: ""
jobs: jobs:
lint: lint:
@ -22,41 +24,24 @@ jobs:
- nightly-2023-01-13 - nightly-2023-01-13
# Check for breakage on latest nightly # Check for breakage on latest nightly
- nightly - nightly
# But if latest nightly fails, allow the workflow to continue # But if latest nightly fails, allow the workflow to continue
continue-on-error: ${{ matrix.toolchain == 'nightly' }} continue-on-error: ${{ matrix.toolchain == 'nightly' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: devkitpro/devkitarm container: devkitpro/devkitarm
steps: steps:
# https://github.com/nektos/act/issues/917#issuecomment-1074421318
- if: ${{ env.ACT }}
name: Hack container for local development
run: |
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs
- name: Checkout branch - name: Checkout branch
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup default Rust toolchain - uses: ./.github/actions/setup
uses: actions-rs/toolchain@v1
with: with:
components: clippy, rustfmt, rust-src
profile: minimal
toolchain: ${{ matrix.toolchain }} toolchain: ${{ matrix.toolchain }}
default: true
- name: Install build tools for host - name: Hide duplicate warnings from lint job
run: sudo apt-get update && sudo apt-get install -y build-essential if: ${{ matrix.toolchain == 'nightly' }}
run: |
- name: Install cargo-3ds echo "::remove-matcher owner=clippy::"
uses: actions-rs/cargo@v1 echo "::remove-matcher owner=rustfmt::"
with:
command: install
# TODO: this should probably just be a released version from crates.io
# once cargo-3ds gets published somewhere...
args: >-
--git https://github.com/rust3ds/cargo-3ds
--rev 7b70b6b26c4740b9a10ab85b832ee73c41142bbb
- name: Check formatting - name: Check formatting
run: cargo fmt --all --verbose -- --check run: cargo fmt --all --verbose -- --check
@ -68,5 +53,28 @@ jobs:
# feature, but https://github.com/actions/runner/issues/2341 means we # feature, but https://github.com/actions/runner/issues/2341 means we
# can't have both that *and* colored output. # can't have both that *and* colored output.
doctests:
strategy:
matrix:
toolchain:
- nightly-2023-01-13
- nightly
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
runs-on: ubuntu-latest
container: devkitpro/devkitarm
steps:
- name: Checkout branch
uses: actions/checkout@v2
- uses: ./.github/actions/setup
with:
toolchain: ${{ matrix.toolchain }}
- name: Hide duplicated warnings from lint job
run: echo "::remove-matcher owner=clippy::"
- name: Build doc tests
run: cargo 3ds test --doc --verbose
# TODO: it would be nice to actually build 3dsx for examples/tests, etc. # TODO: it would be nice to actually build 3dsx for examples/tests, etc.
# and run it somehow, but exactly how remains to be seen. # and run it somehow, but exactly how remains to be seen.

4
AUTHORS.md

@ -1,4 +0,0 @@
# Authors
* [Eidolon](https://github.com/HybridEidolon)
* [FenrirWolf](https://github.com/FenrirWolf)

21
LICENSE

@ -0,0 +1,21 @@
Copyright (C) Rust 3DS Project authors, 2015-2016
As with the original ctrulib, this library is licensed under zlib. This
applies to every file in the tree, unless otherwise noted.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

40
README.md

@ -5,35 +5,29 @@ A Rust wrapper library for smealum's [ctrulib](https://github.com/smealum/ctruli
## Structure ## Structure
This repository is organized as follows: This repository is organized as follows:
* `ctru-rs`: Safe, idiomatic wrapper around `ctru-sys`.
* `ctru-sys`: Low-level, unsafe bindings to ctrulib
## License * `ctru-rs`: Safe, idiomatic wrapper around `ctru-sys`
Copyright (C) Rust 3DS Project authors, 2015-2016 * `ctru-sys`: Low-level, unsafe bindings to ctrulib.
See AUTHORS.md. This crate's version changes according to the version of `libctru`
used to generate the bindings, with the following convention:
As with the original ctrulib, this library is licensed under zlib. This * `libctru` version `X.Y.Z-W`
applies to every file in the tree, unless otherwise noted. * `ctru-sys` version `XY.Z.P+X.Y.Z-W`
This software is provided 'as-is', without any express or implied where `P` is usually 0 but may be incremented for fixes in e.g.
warranty. In no event will the authors be held liable for any binding generation, `libc` dependency bump, etc.
damages arising from the use of this software.
Permission is granted to anyone to use this software for any It may be possible to build this crate against a different version of `libctru`,
purpose, including commercial applications, and to alter it and but you may encounter linker errors or ABI issues. A build-time Cargo warning
redistribute it freely, subject to the following restrictions: (displayed when built with `-vv`) will be issued if the build script detects
a mismatch or is unable to check the installed `libctru` version.
1. The origin of this software must not be misrepresented; you ## Original version
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
Rust is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0), with portions covered by various BSD-like licenses. This project is based on the efforts the original authors:
* [Eidolon](https://github.com/HybridEidolon)
* [FenrirWolf](https://github.com/FenrirWolf)
See [LICENSE-APACHE](https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE), [LICENSE-MIT](https://github.com/rust-lang/rust/blob/master/LICENSE-MIT), and [COPYRIGHT](https://github.com/rust-lang/rust/blob/master/COPYRIGHT) for details. The old version is archived [here](https://github.com/rust3ds/ctru-rs-old).

8
ctru-rs/Cargo.toml

@ -1,7 +1,7 @@
[package] [package]
authors = ["Ronald Kinard <furyhunter600@gmail.com>"] authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "A safe wrapper around smealum's ctrulib." description = "A safe wrapper around smealum's ctrulib."
license = "https://en.wikipedia.org/wiki/Zlib_License" license = "Zlib"
name = "ctru-rs" name = "ctru-rs"
version = "0.7.1" version = "0.7.1"
edition = "2021" edition = "2021"
@ -13,9 +13,9 @@ name = "ctru"
[dependencies] [dependencies]
cfg-if = "1.0" cfg-if = "1.0"
ctru-sys = { path = "../ctru-sys", version = "0.4" } ctru-sys = { path = "../ctru-sys", version = "21.2" }
const-zero = "0.1.0" const-zero = "0.1.0"
linker-fix-3ds = { git = "https://github.com/rust3ds/rust-linker-fix-3ds.git" } shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" }
pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" }
libc = "0.2.121" libc = "0.2.121"
bitflags = "1.0.0" bitflags = "1.0.0"

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

@ -5,13 +5,13 @@ use std::f32::consts::PI;
use ctru::linear::LinearAllocator; use ctru::linear::LinearAllocator;
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::ndsp::{ use ctru::services::ndsp::{
wave::{WaveInfo, WaveStatus}, wave::{Wave, WaveStatus},
AudioFormat, InterpolationType, Ndsp, OutputMode, AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode,
}; };
const SAMPLE_RATE: usize = 22050; const SAMPLE_RATE: usize = 22050;
const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205 const SAMPLES_PER_BUF: usize = SAMPLE_RATE / 10; // 2205
const BYTES_PER_SAMPLE: usize = 4; const BYTES_PER_SAMPLE: usize = AudioFormat::PCM16Stereo.size();
const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE; const AUDIO_WAVE_LENGTH: usize = SAMPLES_PER_BUF * BYTES_PER_SAMPLE;
// Note Frequencies // Note Frequencies
@ -35,11 +35,12 @@ fn fill_buffer(audio_data: &mut [u8], frequency: f32) {
} }
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
let mut note: usize = 4; let mut note: usize = 4;
@ -64,24 +65,21 @@ fn main() {
let audio_data2 = audio_data1.clone(); let audio_data2 = audio_data1.clone();
let mut wave_info1 = WaveInfo::new(audio_data1, AudioFormat::PCM16Stereo, false); let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = WaveInfo::new(audio_data2, AudioFormat::PCM16Stereo, false); let mut wave_info2 = Wave::new(audio_data2, AudioFormat::PCM16Stereo, false);
let mut ndsp = Ndsp::init().expect("Couldn't obtain NDSP controller"); let mut ndsp = Ndsp::new().expect("Couldn't obtain NDSP controller");
// This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo` // This line isn't needed since the default NDSP configuration already sets the output mode to `Stereo`
ndsp.set_output_mode(OutputMode::Stereo); ndsp.set_output_mode(OutputMode::Stereo);
let channel_zero = ndsp.channel(0).unwrap(); let mut channel_zero = ndsp.channel(0).unwrap();
channel_zero.set_interpolation(InterpolationType::Linear); channel_zero.set_interpolation(InterpolationType::Linear);
channel_zero.set_sample_rate(SAMPLE_RATE as f32); channel_zero.set_sample_rate(SAMPLE_RATE as f32);
channel_zero.set_format(AudioFormat::PCM16Stereo); channel_zero.set_format(AudioFormat::PCM16Stereo);
// Output at 100% on the first pair of left and right channels. // Output at 100% on the first pair of left and right channels.
let mix = AudioMix::default();
let mut mix: [f32; 12] = [0f32; 12];
mix[0] = 1.0;
mix[1] = 1.0;
channel_zero.set_mix(&mix); channel_zero.set_mix(&mix);
channel_zero.queue_wave(&mut wave_info1).unwrap(); channel_zero.queue_wave(&mut wave_info1).unwrap();
@ -101,23 +99,23 @@ fn main() {
hid.scan_input(); hid.scan_input();
let keys_down = hid.keys_down(); let keys_down = hid.keys_down();
if keys_down.contains(KeyPad::KEY_START) { if keys_down.contains(KeyPad::START) {
break; break;
} // break in order to return to hbmenu } // break in order to return to hbmenu
if keys_down.intersects(KeyPad::KEY_DOWN) { if keys_down.intersects(KeyPad::DOWN) {
note = note.saturating_sub(1); note = note.saturating_sub(1);
} else if keys_down.intersects(KeyPad::KEY_UP) { } else if keys_down.intersects(KeyPad::UP) {
note = std::cmp::min(note + 1, NOTEFREQ.len() - 1); note = std::cmp::min(note + 1, NOTEFREQ.len() - 1);
} }
let mut update_params = false; let mut update_params = false;
if keys_down.intersects(KeyPad::KEY_LEFT) { if keys_down.intersects(KeyPad::LEFT) {
filter -= 1; filter -= 1;
filter = filter.rem_euclid(filter_names.len() as _); filter = filter.rem_euclid(filter_names.len() as _);
update_params = true; update_params = true;
} else if keys_down.intersects(KeyPad::KEY_RIGHT) { } else if keys_down.intersects(KeyPad::RIGHT) {
filter += 1; filter += 1;
filter = filter.rem_euclid(filter_names.len() as _); filter = filter.rem_euclid(filter_names.len() as _);
@ -141,13 +139,13 @@ fn main() {
} }
} }
let current: &mut WaveInfo = if altern { let current: &mut Wave = if altern {
&mut wave_info1 &mut wave_info1
} else { } else {
&mut wave_info2 &mut wave_info2
}; };
let status = current.get_status(); let status = current.status();
if let WaveStatus::Done = status { if let WaveStatus::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]); fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);
@ -156,10 +154,6 @@ fn main() {
altern = !altern; altern = !altern;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }

25
ctru-rs/examples/buttons.rs

@ -1,12 +1,12 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
// Setup services ctru::use_panic_handler();
ctru::init();
let apt = Apt::init().unwrap(); let apt = Apt::new().unwrap();
let hid = Hid::init().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::init().unwrap(); let gfx = Gfx::new().unwrap();
let console = Console::init(gfx.top_screen.borrow_mut()); let console = Console::new(gfx.top_screen.borrow_mut());
println!("Hi there! Try pressing a button"); println!("Hi there! Try pressing a button");
println!("\x1b[29;16HPress Start to exit"); println!("\x1b[29;16HPress Start to exit");
@ -42,19 +42,19 @@ fn main() {
// You can also use the .bits() method to do direct comparisons on // You can also use the .bits() method to do direct comparisons on
// the underlying bits // the underlying bits
if keys.contains(KeyPad::KEY_A) { if keys.contains(KeyPad::A) {
println!("You held A!"); println!("You held A!");
} }
if keys.bits() & KeyPad::KEY_B.bits() != 0 { if keys.bits() & KeyPad::B.bits() != 0 {
println!("You held B!"); println!("You held B!");
} }
if keys.contains(KeyPad::KEY_X | KeyPad::KEY_Y) { if keys.contains(KeyPad::X | KeyPad::Y) {
println!("You held X and Y!"); println!("You held X and Y!");
} }
if keys.intersects(KeyPad::KEY_L | KeyPad::KEY_R | KeyPad::KEY_ZL | KeyPad::KEY_ZR) { if keys.intersects(KeyPad::L | KeyPad::R | KeyPad::ZL | KeyPad::ZR) {
println!("You held a shoulder button!"); println!("You held a shoulder button!");
} }
if keys.intersects(KeyPad::KEY_START) { if keys.intersects(KeyPad::START) {
println!("See ya!"); println!("See ya!");
break; break;
} }
@ -63,9 +63,6 @@ fn main() {
// Save our current key presses for the next frame // Save our current key presses for the next frame
old_keys = keys; old_keys = keys;
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

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

@ -1,45 +1,45 @@
use ctru::console::Console; use ctru::prelude::*;
use ctru::gfx::{Gfx, Screen}; use ctru::services::cam::{Cam, Camera, OutputFormat, ShutterSound, ViewSize};
use ctru::services::cam::{Cam, CamOutputFormat, CamShutterSoundType, CamSize, Camera}; use ctru::services::gfx::{Flush, Screen, Swap};
use ctru::services::hid::KeyPad; use ctru::services::gspgpu::FramebufferFormat;
use ctru::services::{Apt, Hid};
use std::time::Duration; use std::time::Duration;
const WIDTH: usize = 400; const WIDTH: usize = 400;
const HEIGHT: usize = 240; const HEIGHT: usize = 240;
// The screen size is the width and height multiplied by 2 and // The screen size is the width and height multiplied by 2 (RGB565 store pixels in 2 bytes)
// then multiplied by 2 again for 3D images const BUF_SIZE: usize = WIDTH * HEIGHT * 2;
const BUF_SIZE: usize = WIDTH * HEIGHT * 2 * 2;
const WAIT_TIMEOUT: Duration = Duration::from_micros(300); const WAIT_TIMEOUT: Duration = Duration::from_millis(300);
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let apt = Apt::init().expect("Failed to initialize Apt service."); let apt = Apt::new().expect("Failed to initialize Apt service.");
let hid = Hid::init().expect("Failed to initialize Hid service."); let mut hid = Hid::new().expect("Failed to initialize Hid service.");
let gfx = Gfx::init().expect("Failed to initialize GFX service."); let gfx = Gfx::new().expect("Failed to initialize GFX service.");
gfx.top_screen.borrow_mut().set_double_buffering(true); let mut top_screen = gfx.top_screen.borrow_mut();
gfx.bottom_screen.borrow_mut().set_double_buffering(false); top_screen.set_double_buffering(true);
top_screen.set_framebuffer_format(FramebufferFormat::Rgb565);
let _console = Console::init(gfx.bottom_screen.borrow_mut()); let _console = Console::new(gfx.bottom_screen.borrow_mut());
let mut keys_down; let mut keys_down;
println!("Initializing camera"); println!("Initializing camera");
let mut cam = Cam::init().expect("Failed to initialize CAM service."); let mut cam = Cam::new().expect("Failed to initialize CAM service.");
{ {
let camera = &mut cam.outer_right_cam; let camera = &mut cam.outer_right_cam;
camera camera
.set_view_size(CamSize::CTR_TOP_LCD) .set_view_size(ViewSize::TopLCD)
.expect("Failed to set camera size"); .expect("Failed to set camera size");
camera camera
.set_output_format(CamOutputFormat::RGB_565) .set_output_format(OutputFormat::Rgb565)
.expect("Failed to set camera output format"); .expect("Failed to set camera output format");
camera camera
.set_noise_filter(true) .set_noise_filter(true)
@ -54,6 +54,7 @@ fn main() {
.set_trimming(false) .set_trimming(false)
.expect("Failed to disable trimming"); .expect("Failed to disable trimming");
} }
let mut buf = vec![0u8; BUF_SIZE]; let mut buf = vec![0u8; BUF_SIZE];
println!("\nPress R to take a new picture"); println!("\nPress R to take a new picture");
@ -63,75 +64,62 @@ fn main() {
hid.scan_input(); hid.scan_input();
keys_down = hid.keys_down(); keys_down = hid.keys_down();
if keys_down.contains(KeyPad::KEY_START) { if keys_down.contains(KeyPad::START) {
break; break;
} }
if keys_down.contains(KeyPad::KEY_R) { if keys_down.contains(KeyPad::R) {
println!("Capturing new image"); println!("Capturing new image");
cam.play_shutter_sound(CamShutterSoundType::NORMAL)
.expect("Failed to play shutter sound");
let camera = &mut cam.outer_right_cam; let camera = &mut cam.outer_right_cam;
buf = camera camera
.take_picture( .take_picture(
&mut buf,
WIDTH.try_into().unwrap(), WIDTH.try_into().unwrap(),
HEIGHT.try_into().unwrap(), HEIGHT.try_into().unwrap(),
WAIT_TIMEOUT, WAIT_TIMEOUT,
) )
.expect("Failed to take picture"); .expect("Failed to take picture");
}
let img = convert_image_to_rgb8(&buf, 0, 0, WIDTH, HEIGHT); cam.play_shutter_sound(ShutterSound::Normal)
.expect("Failed to play shutter sound");
unsafe { rotate_image_to_screen(&buf, top_screen.raw_framebuffer().ptr, WIDTH, HEIGHT);
gfx.top_screen
.borrow_mut() // We will only flush the "camera" screen, since the other screen is handled by `Console`
.get_raw_framebuffer() top_screen.flush_buffers();
.ptr top_screen.swap_buffers();
.copy_from(img.as_ptr(), img.len());
}
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
}
} }
// The available camera output formats are both using u16 values. // The 3DS' screens are 2 vertical LCD panels rotated by 90 degrees.
// To write to the frame buffer with the default RGB8 format, // As such, we'll need to write a "vertical" image to the framebuffer to have it displayed properly.
// the values must be converted. // This functions rotates an horizontal image by 90 degrees to the right.
// fn rotate_image_to_screen(src: &[u8], framebuf: *mut u8, width: usize, height: usize) {
// Alternatively, the frame buffer format could be set to RGB565 as well
// but the image would need to be rotated 90 degrees.
fn convert_image_to_rgb8(img: &[u8], x: usize, y: usize, width: usize, height: usize) -> Vec<u8> {
let mut rgb8 = vec![0u8; img.len()];
for j in 0..height { for j in 0..height {
for i in 0..width { for i in 0..width {
// Y-coordinate of where to draw in the frame buffer // Y-coordinate of where to draw in the frame buffer
let draw_y = y + height - j; // Height must be esclusive of the upper end (otherwise, we'd be writing to the pixel one column to the right when having j=0)
let draw_y = (height - 1) - j;
// X-coordinate of where to draw in the frame buffer // X-coordinate of where to draw in the frame buffer
let draw_x = x + i; let draw_x = i;
// Initial index of where to draw in the frame buffer based on y and x coordinates
let draw_index = (draw_y + draw_x * height) * 3;
// Index of the pixel to draw within the image buffer // Index of the pixel to draw within the image buffer
let index = (j * width + i) * 2; let read_index = (j * width + i) * 2;
// Pixels in the image are 2 bytes because of the RGB565 format.
let pixel = u16::from_ne_bytes(img[index..index + 2].try_into().unwrap()); // Initial index of where to draw in the frame buffer based on y and x coordinates
// b value from the pixel let draw_index = (draw_x * height + draw_y) * 2; // This 2 stands for the number of bytes per pixel (16 bits)
let b = (((pixel >> 11) & 0x1F) << 3) as u8;
// g value from the pixel unsafe {
let g = (((pixel >> 5) & 0x3F) << 2) as u8; // We'll work with pointers since the frambuffer is a raw pointer regardless.
// r value from the pixel // The offsets are completely safe as long as the width and height are correct.
let r = ((pixel & 0x1F) << 3) as u8; let pixel_pointer = framebuf.offset(draw_index as isize);
pixel_pointer.copy_from(src.as_ptr().offset(read_index as isize), 2);
// set the r, g, and b values to the calculated index within the frame buffer }
rgb8[draw_index] = r;
rgb8[draw_index + 1] = g;
rgb8[draw_index + 2] = b;
} }
} }
rgb8
} }

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

@ -9,20 +9,21 @@ use std::os::horizon::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap(); let apt = Apt::new().unwrap();
let gfx = Gfx::init().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap();
#[cfg(all(feature = "romfs", romfs_exists))] #[cfg(all(feature = "romfs", romfs_exists))]
let _romfs = ctru::romfs::RomFS::init().unwrap(); let _romfs = ctru::services::romfs::RomFS::new().unwrap();
FileExplorer::init(&apt, &hid, &gfx).run(); FileExplorer::new(&apt, &mut hid, &gfx).run();
} }
struct FileExplorer<'a> { struct FileExplorer<'a> {
apt: &'a Apt, apt: &'a Apt,
hid: &'a Hid, hid: &'a mut Hid,
gfx: &'a Gfx, gfx: &'a Gfx,
console: Console<'a>, console: Console<'a>,
path: PathBuf, path: PathBuf,
@ -31,10 +32,10 @@ struct FileExplorer<'a> {
} }
impl<'a> FileExplorer<'a> { impl<'a> FileExplorer<'a> {
fn init(apt: &'a Apt, hid: &'a Hid, gfx: &'a Gfx) -> Self { fn new(apt: &'a Apt, hid: &'a mut Hid, gfx: &'a Gfx) -> Self {
let mut top_screen = gfx.top_screen.borrow_mut(); let mut top_screen = gfx.top_screen.borrow_mut();
top_screen.set_wide_mode(true); top_screen.set_wide_mode(true);
let console = Console::init(top_screen); let console = Console::new(top_screen);
FileExplorer { FileExplorer {
apt, apt,
@ -55,20 +56,18 @@ impl<'a> FileExplorer<'a> {
self.hid.scan_input(); self.hid.scan_input();
let input = self.hid.keys_down(); let input = self.hid.keys_down();
if input.contains(KeyPad::KEY_START) { if input.contains(KeyPad::START) {
break; break;
} else if input.contains(KeyPad::KEY_B) && self.path.components().count() > 1 { } else if input.contains(KeyPad::B) && self.path.components().count() > 1 {
self.path.pop(); self.path.pop();
self.console.clear(); self.console.clear();
self.print_menu(); self.print_menu();
} else if input.contains(KeyPad::KEY_A) { } else if input.contains(KeyPad::A) {
self.get_input_and_run(Self::set_next_path); self.get_input_and_run(Self::set_next_path);
} else if input.contains(KeyPad::KEY_X) { } else if input.contains(KeyPad::X) {
self.get_input_and_run(Self::set_exact_path); self.get_input_and_run(Self::set_exact_path);
} }
self.gfx.flush_buffers();
self.gfx.swap_buffers();
self.gfx.wait_for_vblank(); self.gfx.wait_for_vblank();
} }
} }
@ -146,11 +145,11 @@ impl<'a> FileExplorer<'a> {
self.hid.scan_input(); self.hid.scan_input();
let input = self.hid.keys_down(); let input = self.hid.keys_down();
if input.contains(KeyPad::KEY_A) { if input.contains(KeyPad::A) {
break; break;
} }
if input.contains(KeyPad::KEY_START) { if input.contains(KeyPad::START) {
self.running = false; self.running = false;
return; return;
} }
@ -161,17 +160,16 @@ impl<'a> FileExplorer<'a> {
fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) { fn get_input_and_run(&mut self, action: impl FnOnce(&mut Self, String)) {
let mut keyboard = Swkbd::default(); let mut keyboard = Swkbd::default();
let mut new_path_str = String::new();
match keyboard.get_utf8(&mut new_path_str) { match keyboard.get_string(2048) {
Ok(Button::Right) => { Ok((path, Button::Right)) => {
// Clicked "OK" // Clicked "OK"
action(self, new_path_str); action(self, path);
} }
Ok(Button::Left) => { Ok((_, Button::Left)) => {
// Clicked "Cancel" // Clicked "Cancel"
} }
Ok(Button::Middle) => { Ok((_, Button::Middle)) => {
// This button wasn't shown // This button wasn't shown
unreachable!() unreachable!()
} }

13
ctru-rs/examples/futures-basic.rs

@ -13,11 +13,12 @@ use futures::StreamExt;
use std::os::horizon::thread::BuilderExt; use std::os::horizon::thread::BuilderExt;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
// Give ourselves up to 30% of the system core's time // Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30) apt.set_app_cpu_time_limit(30)
@ -67,8 +68,6 @@ fn main() {
frame_count = 0; frame_count = 0;
} }
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

13
ctru-rs/examples/futures-tokio.rs

@ -6,11 +6,12 @@ use std::os::horizon::thread::BuilderExt;
use std::time::Duration; use std::time::Duration;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
// Give ourselves up to 30% of the system core's time // Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30) apt.set_app_cpu_time_limit(30)
@ -62,8 +63,6 @@ fn main() {
break; break;
} }
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

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

@ -1,5 +1,5 @@
use ctru::gfx::{Screen, Side, TopScreen3D};
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D};
/// See `graphics-bitmap.rs` for details on how the image is generated. /// See `graphics-bitmap.rs` for details on how the image is generated.
/// ///
@ -10,18 +10,18 @@ const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
static ZERO: &[u8] = &[0; IMAGE.len()]; static ZERO: &[u8] = &[0; IMAGE.len()];
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.bottom_screen.borrow_mut()); 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 Start to exit.\nPress A to switch sides (be sure to have 3D mode enabled).");
gfx.top_screen.borrow_mut().set_double_buffering(true); gfx.top_screen.borrow_mut().set_double_buffering(true);
let top_screen = TopScreen3D::from(&gfx.top_screen); let mut top_screen = TopScreen3D::from(&gfx.top_screen);
let (mut left, mut right) = top_screen.split_mut();
let mut current_side = Side::Left; let mut current_side = Side::Left;
@ -30,12 +30,14 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
let left_buf = left.get_raw_framebuffer(); let (mut left, mut right) = top_screen.split_mut();
let right_buf = right.get_raw_framebuffer();
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 { unsafe {
@ -43,7 +45,7 @@ fn main() {
right_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len()); right_buf.ptr.copy_from(ZERO.as_ptr(), ZERO.len());
} }
if hid.keys_down().contains(KeyPad::KEY_A) { if hid.keys_down().contains(KeyPad::A) {
// flip which buffer we're writing to // flip which buffer we're writing to
current_side = match current_side { current_side = match current_side {
Side::Left => Side::Right, Side::Left => Side::Right,
@ -60,9 +62,10 @@ fn main() {
buf.copy_from(IMAGE.as_ptr(), IMAGE.len()); buf.copy_from(IMAGE.as_ptr(), IMAGE.len());
} }
// Flush and swap framebuffers drop((left, right));
gfx.flush_buffers();
gfx.swap_buffers(); top_screen.flush_buffers();
top_screen.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

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

@ -0,0 +1,74 @@
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;4HPress Start to exit, or A to flip the image.");
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);
// Swapping buffers commits the change from the line above.
bottom_screen.swap_buffers();
// 3 bytes per pixel, we just want to reverse the pixels but not individual bytes
let flipped_image: Vec<_> = IMAGE.chunks(3).rev().flatten().copied().collect();
let mut image_bytes = IMAGE;
// 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;
}
// We assume the image is the correct size already, so we drop width + height.
let frame_buffer = bottom_screen.raw_framebuffer();
if hid.keys_down().contains(KeyPad::A) {
image_bytes = if std::ptr::eq(image_bytes, IMAGE) {
&flipped_image[..]
} else {
IMAGE
};
}
// this copies more than necessary (once per frame) but it's fine...
unsafe {
frame_buffer
.ptr
.copy_from(image_bytes.as_ptr(), image_bytes.len());
}
// 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();
}
}

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

@ -1,33 +1,32 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap(); let apt = Apt::new().unwrap();
let gfx = Gfx::init().unwrap(); let mut hid = Hid::new().unwrap();
let mut console = Console::init(gfx.top_screen.borrow_mut()); let gfx = Gfx::new().unwrap();
let mut console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enable/disable wide screen mode."); println!("Press A to enable/disable wide screen mode.");
while apt.main_loop() { while apt.main_loop() {
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
if hid.keys_down().contains(KeyPad::KEY_A) { if hid.keys_down().contains(KeyPad::A) {
drop(console); drop(console);
let wide_mode = gfx.top_screen.borrow().get_wide_mode(); let wide_mode = gfx.top_screen.borrow().is_wide();
gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode); gfx.top_screen.borrow_mut().set_wide_mode(!wide_mode);
console = Console::init(gfx.top_screen.borrow_mut()); console = Console::new(gfx.top_screen.borrow_mut());
println!("Press A to enable/disable wide screen mode."); println!("Press A to enable/disable wide screen mode.");
} }
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

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

@ -1,5 +1,5 @@
use ctru::gfx::Screen as _;
use ctru::prelude::*; use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Swap};
/// Ferris image taken from <https://rustacean.net> and scaled down to 320x240px. /// 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 /// To regenerate the data, you will need to install `imagemagick` and run this
@ -15,11 +15,12 @@ use ctru::prelude::*;
static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); static IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); 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."); println!("\x1b[21;16HPress Start to exit.");
@ -30,7 +31,7 @@ fn main() {
bottom_screen.set_double_buffering(false); bottom_screen.set_double_buffering(false);
// We assume the image is the correct size already, so we drop width + height. // We assume the image is the correct size already, so we drop width + height.
let frame_buffer = bottom_screen.get_raw_framebuffer(); let frame_buffer = bottom_screen.raw_framebuffer();
// Copy the image into the frame buffer // Copy the image into the frame buffer
unsafe { unsafe {
@ -42,13 +43,13 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers // Flush and swap framebuffers
gfx.flush_buffers(); bottom_screen.flush_buffers();
gfx.swap_buffers(); bottom_screen.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

17
ctru-rs/examples/hashmaps.rs

@ -1,16 +1,17 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::use_panic_handler();
// Initialize services // Initialize services
// //
// HashMaps generate hashes thanks to the 3DS' cryptografically secure generator. // HashMaps generate hashes thanks to the 3DS' cryptografically secure generator.
// This generator is only active when activating the `PS` service. // This generator is only active when activating the `PS` service.
// This service is automatically initialized in `ctru::init` // This service is automatically initialized.
ctru::init(); let apt = Apt::new().unwrap();
let apt = Apt::init().unwrap(); let mut hid = Hid::new().unwrap();
let hid = Hid::init().unwrap(); let gfx = Gfx::new().unwrap();
let gfx = Gfx::init().unwrap(); let _console = Console::new(gfx.top_screen.borrow_mut());
let _console = Console::init(gfx.top_screen.borrow_mut());
let mut map = std::collections::HashMap::new(); let mut map = std::collections::HashMap::new();
map.insert("A Key!", 102); map.insert("A Key!", 102);
@ -20,12 +21,10 @@ fn main() {
println!("{map:#?}"); println!("{map:#?}");
while apt.main_loop() { while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
} }

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

@ -1,18 +1,18 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
// Initialize services ctru::use_panic_handler();
ctru::init();
let apt = Apt::init().unwrap(); let apt = Apt::new().unwrap();
let hid = Hid::init().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::init().unwrap(); let gfx = Gfx::new().unwrap();
// Start a console on the top screen // Start a console on the top screen
let top_screen = Console::init(gfx.top_screen.borrow_mut()); let top_screen = Console::new(gfx.top_screen.borrow_mut());
// Start a console on the bottom screen. // Start a console on the bottom screen.
// The most recently initialized console will be active by default // The most recently initialized console will be active by default
let bottom_screen = Console::init(gfx.bottom_screen.borrow_mut()); let bottom_screen = Console::new(gfx.bottom_screen.borrow_mut());
// Let's print on the top screen first // Let's print on the top screen first
top_screen.select(); top_screen.select();
@ -27,12 +27,10 @@ fn main() {
println!("\x1b[29;16HPress Start to exit"); println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
} }

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

@ -3,11 +3,12 @@ use ctru::prelude::*;
use std::io::BufWriter; use std::io::BufWriter;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!"; let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!";
let width = 24; let width = 24;
@ -25,12 +26,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

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

@ -4,11 +4,12 @@ use ctru::linear::LinearAllocator;
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
let linear_space_before = LinearAllocator::free_space(); let linear_space_before = LinearAllocator::free_space();
@ -33,12 +34,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

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

@ -2,14 +2,14 @@ use ctru::applets::mii_selector::MiiSelector;
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
let mut mii_selector = MiiSelector::init(); let mut mii_selector = MiiSelector::new();
mii_selector.set_initial_index(3); mii_selector.set_initial_index(3);
mii_selector.blacklist_user_mii(0.into()); mii_selector.blacklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!"); mii_selector.set_title("Great Mii Selector!");
@ -30,12 +30,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

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

@ -5,15 +5,16 @@ use std::net::{Shutdown, TcpListener};
use std::time::Duration; use std::time::Duration;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().unwrap();
let _console = Console::init(gfx.top_screen.borrow_mut()); let gfx = Gfx::new().unwrap();
let hid = Hid::init().unwrap(); let _console = Console::new(gfx.top_screen.borrow_mut());
let apt = Apt::init().unwrap(); let mut hid = Hid::new().unwrap();
let apt = Apt::new().unwrap();
println!("\nlibctru sockets demo\n"); println!("\nlibctru sockets demo\n");
let soc = Soc::init().unwrap(); let soc = Soc::new().unwrap();
let server = TcpListener::bind("0.0.0.0:80").unwrap(); let server = TcpListener::bind("0.0.0.0:80").unwrap();
server.set_nonblocking(true).unwrap(); server.set_nonblocking(true).unwrap();
@ -60,7 +61,7 @@ fn main() {
} }
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
}; };
} }

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

@ -11,12 +11,13 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller");
let mut soc = Soc::init().expect("Couldn't obtain SOC controller"); 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 mut soc = Soc::new().expect("Couldn't obtain SOC controller");
soc.redirect_to_3dslink(true, true) soc.redirect_to_3dslink(true, true)
.expect("unable to redirect stdout/err to 3dslink server"); .expect("unable to redirect stdout/err to 3dslink server");
@ -29,12 +30,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

18
ctru-rs/examples/romfs.rs

@ -1,18 +1,19 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
cfg_if::cfg_if! { cfg_if::cfg_if! {
// Run this code if RomFS are wanted and available // Run this code if RomFS are wanted and available
// This never fails as `ctru-rs` examples inherit all of the `ctru` features, // This never fails as `ctru-rs` examples inherit all of the `ctru` features,
// but it might if a normal user application wasn't setup correctly // but it might if a normal user application wasn't setup correctly
if #[cfg(all(feature = "romfs", romfs_exists))] { if #[cfg(all(feature = "romfs", romfs_exists))] {
let _romfs = ctru::romfs::RomFS::init().unwrap(); let _romfs = ctru::services::romfs::RomFS::new().unwrap();
let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap(); let f = std::fs::read_to_string("romfs:/test-file.txt").unwrap();
println!("Contents of test-file.txt: \n{f}\n"); println!("Contents of test-file.txt: \n{f}\n");
@ -32,12 +33,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

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

@ -2,41 +2,37 @@ use ctru::applets::swkbd::{Button, Swkbd};
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap(); let apt = Apt::new().unwrap();
let gfx = Gfx::init().unwrap(); let mut hid = Hid::new().unwrap();
let _console = Console::init(gfx.top_screen.borrow_mut()); 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 quit");
while apt.main_loop() { while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_A) { if hid.keys_down().contains(KeyPad::A) {
// Prepares a software keyboard with two buttons: One to cancel input and one // Prepares a software keyboard with two buttons: One to cancel input and one
// to accept it. You can also use `Swkbd::init()` to launch the keyboard in different // to accept it. You can also use `Swkbd::new()` to launch the keyboard in different
// configurations. // configurations.
let mut keyboard = Swkbd::default(); let mut keyboard = Swkbd::default();
// String used to store text received from the keyboard
let mut text = String::new();
// Raise the software keyboard. You can perform different actions depending on which // Raise the software keyboard. You can perform different actions depending on which
// software button the user pressed // software button the user pressed
match keyboard.get_utf8(&mut text) { match keyboard.get_string(2048) {
Ok(Button::Right) => println!("You entered: {text}"), Ok((text, Button::Right)) => println!("You entered: {text}"),
Ok(Button::Left) => println!("Cancelled"), Ok((_, Button::Left)) => println!("Cancelled"),
Ok(Button::Middle) => println!("How did you even press this?"), Ok((_, Button::Middle)) => println!("How did you even press this?"),
Err(_) => println!("Oh noes, an error happened!"), Err(_) => println!("Oh noes, an error happened!"),
} }
} }
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
} }

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

@ -2,28 +2,26 @@ use ctru::prelude::*;
use ctru::services::cfgu::Cfgu; use ctru::services::cfgu::Cfgu;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller");
let cfgu = Cfgu::init().expect("Couldn't obtain CFGU controller");
let _console = Console::init(gfx.top_screen.borrow_mut());
println!("\x1b[0;0HRegion: {:?}", cfgu.get_region().unwrap()); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
println!("\x1b[10;0HLanguage: {:?}", cfgu.get_language().unwrap()); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
println!("\x1b[20;0HModel: {:?}", cfgu.get_model().unwrap()); 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());
println!("\x1b[0;0HRegion: {:?}", cfgu.region().unwrap());
println!("\x1b[10;0HLanguage: {:?}", cfgu.language().unwrap());
println!("\x1b[20;0HModel: {:?}", cfgu.model().unwrap());
// Main loop // Main loop
while apt.main_loop() { while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame //Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();

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

@ -6,12 +6,12 @@ use std::os::horizon::thread::BuilderExt;
use std::time::Duration; use std::time::Duration;
fn main() { fn main() {
// Initialize services ctru::use_panic_handler();
ctru::init();
let apt = Apt::init().unwrap(); let apt = Apt::new().unwrap();
let hid = Hid::init().unwrap(); let mut hid = Hid::new().unwrap();
let gfx = Gfx::init().unwrap(); let gfx = Gfx::new().unwrap();
let _console = Console::init(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
let prio = std::os::horizon::thread::current_priority(); let prio = std::os::horizon::thread::current_priority();
println!("Main thread prio: {}\n", prio); println!("Main thread prio: {}\n", prio);
@ -34,8 +34,6 @@ fn main() {
} }
while apt.main_loop() { while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
hid.scan_input(); hid.scan_input();

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

@ -7,11 +7,12 @@ use ctru::prelude::*;
use std::os::horizon::thread::BuilderExt; use std::os::horizon::thread::BuilderExt;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::new(gfx.top_screen.borrow_mut());
// Give ourselves up to 30% of the system core's time // Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30) apt.set_app_cpu_time_limit(30)
@ -44,8 +45,6 @@ fn main() {
break; break;
} }
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

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

@ -10,12 +10,13 @@ std::thread_local! {
} }
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller");
let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
gfx.top_screen.borrow_mut().set_wide_mode(true); gfx.top_screen.borrow_mut().set_wide_mode(true);
let hid = Hid::init().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
// Give ourselves up to 30% of the system core's time // Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30) apt.set_app_cpu_time_limit(30)
@ -60,8 +61,6 @@ fn main() {
break; break;
} }
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }
} }

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

@ -1,13 +1,13 @@
use ctru::prelude::*; use ctru::prelude::*;
fn main() { fn main() {
ctru::init(); ctru::use_panic_handler();
let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); let gfx = Gfx::new().expect("Couldn't obtain GFX controller");
let hid = Hid::init().expect("Couldn't obtain HID controller"); let mut hid = Hid::new().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let apt = Apt::new().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
print!("\x1b[30;16HPress Start to exit."); print!("\x1b[30;16HPress Start to exit.");
@ -16,7 +16,7 @@ fn main() {
// Scan all the inputs. This should be done once for each frame // Scan all the inputs. This should be done once for each frame
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
@ -36,10 +36,6 @@ fn main() {
println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}"); println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}");
println!("{weekday} {month} {day} {year}"); println!("{weekday} {month} {day} {year}");
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank //Wait for VBlank
gfx.wait_for_vblank(); gfx.wait_for_vblank();
} }

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

@ -0,0 +1,112 @@
use ctru::prelude::*;
use ctru::services::am::Am;
use ctru::services::fs::FsMediaType;
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 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());
let sd_count = am
.title_count(FsMediaType::Sd)
.expect("Failed to get sd title count");
let sd_list = am
.title_list(FsMediaType::Sd)
.expect("Failed to get sd title list");
let nand_count = am
.title_count(FsMediaType::Nand)
.expect("Failed to get nand title count");
let nand_list = am
.title_list(FsMediaType::Nand)
.expect("Failed to get nand title list");
let mut offset = 0;
let mut refresh = true;
let mut use_nand = false;
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
if hid.keys_down().contains(KeyPad::SELECT) {
refresh = true;
offset = 0;
use_nand = !use_nand;
}
let cur_list = if use_nand { &nand_list } else { &sd_list };
if hid.keys_down().intersects(KeyPad::DOWN) {
if offset + 1 < cur_list.len() {
offset = offset + 1;
refresh = true;
}
} else if hid.keys_down().intersects(KeyPad::UP) {
if offset > 0 {
offset = offset - 1;
refresh = true;
}
}
if refresh {
let mut selected_title = cur_list.iter().skip(offset).next().unwrap();
// Clear top screen and write title ids to it
top_screen.select();
print!("\x1b[2J");
// Top screen seems to have only 30 rows
for (i, title) in cur_list.iter().skip(offset).take(29).enumerate() {
if i == 0 {
selected_title = title;
println!("=> {:x}", title.id());
} else {
println!(" {:x}", title.id());
}
}
// Clear bottom screen and write properties of selected title to it
bottom_screen.select();
println!("\x1b[2J");
// Move cursor to top left
println!("\x1b[1;1");
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!("\x1b[26;0HPress START to exit");
if use_nand {
println!("Press SELECT to choose SD Card");
println!("Current medium: NAND");
println!("Title count: {}", nand_count);
} else {
println!("Press SELECT to choose NAND");
println!("Current medium: SD Card");
println!("Title count: {}", sd_count);
}
refresh = false;
}
//Wait for VBlank
gfx.wait_for_vblank();
}
}

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

@ -0,0 +1,49 @@
use ctru::prelude::*;
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());
// We'll hold the previous touch position for comparison.
let mut old_touch: (u16, u16) = (0, 0);
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Get X and Y coordinates of the touch point.
// The touch screen is 320x240.
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
if touch != old_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
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
old_touch = touch;
gfx.wait_for_vblank();
}
}

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

@ -8,14 +8,14 @@ use std::ffi::CString;
/// Index of a Mii used to configure some parameters of the Mii Selector /// Index of a Mii used to configure some parameters of the Mii Selector
/// Can be either a single index, or _all_ Miis /// Can be either a single index, or _all_ Miis
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum MiiConfigIndex { pub enum Index {
Index(u32), Index(u32),
All, All,
} }
/// The type of a Mii with their respective data /// The type of a Mii with their respective data
#[derive(Debug, Clone)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum MiiType { pub enum MiiType {
Guest { index: u32, name: String }, Guest { index: u32, name: String },
User, User,
@ -41,7 +41,7 @@ bitflags! {
/// ``` /// ```
/// use ctru::applets::mii_selector::MiiSelector; /// use ctru::applets::mii_selector::MiiSelector;
/// ///
/// let mut mii_selector = MiiSelector::init(); /// let mut mii_selector = MiiSelector::new();
/// mii_selector.set_title("Example Mii selector"); /// mii_selector.set_title("Example Mii selector");
/// ///
/// let result = mii_selector.launch().unwrap(); /// let result = mii_selector.launch().unwrap();
@ -54,7 +54,7 @@ pub struct MiiSelector {
/// Return value from a MiiSelector's launch /// Return value from a MiiSelector's launch
#[non_exhaustive] #[non_exhaustive]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MiiSelectorReturn { pub struct SelectionResult {
pub mii_data: MiiData, pub mii_data: MiiData,
pub is_mii_selected: bool, pub is_mii_selected: bool,
pub mii_type: MiiType, pub mii_type: MiiType,
@ -62,13 +62,13 @@ pub struct MiiSelectorReturn {
/// Error type for the Mii selector /// Error type for the Mii selector
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiLaunchError { pub enum LaunchError {
InvalidChecksum, InvalidChecksum,
} }
impl MiiSelector { impl MiiSelector {
/// Initializes a Mii Selector /// Initializes a Mii Selector
pub fn init() -> Self { pub fn new() -> Self {
let mut config = Box::<ctru_sys::MiiSelectorConf>::default(); let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
unsafe { unsafe {
ctru_sys::miiSelectorInit(config.as_mut()); ctru_sys::miiSelectorInit(config.as_mut());
@ -94,40 +94,40 @@ impl MiiSelector {
} }
/// Whitelist a guest Mii /// Whitelist a guest Mii
pub fn whitelist_guest_mii(&mut self, mii_index: MiiConfigIndex) { pub fn whitelist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
MiiConfigIndex::Index(i) => i, Index::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
}; };
unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) }
} }
/// Blacklist a guest Mii /// Blacklist a guest Mii
pub fn blacklist_guest_mii(&mut self, mii_index: MiiConfigIndex) { pub fn blacklist_guest_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
MiiConfigIndex::Index(i) => i, Index::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
}; };
unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) }
} }
/// Whitelist a user Mii /// Whitelist a user Mii
pub fn whitelist_user_mii(&mut self, mii_index: MiiConfigIndex) { pub fn whitelist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
MiiConfigIndex::Index(i) => i, Index::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
}; };
unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) }
} }
/// Blacklist a user Mii /// Blacklist a user Mii
pub fn blacklist_user_mii(&mut self, mii_index: MiiConfigIndex) { pub fn blacklist_user_mii(&mut self, mii_index: Index) {
let index = match mii_index { let index = match mii_index {
MiiConfigIndex::Index(i) => i, Index::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
}; };
unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) } unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
@ -135,32 +135,38 @@ impl MiiSelector {
/// Set where the cursor will be. /// 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 /// If there's no Mii at that index, the cursor will start at the Mii with the index 0
pub fn set_initial_index(&mut self, index: u32) { pub fn set_initial_index(&mut self, index: usize) {
// This function is static inline in libctru // This function is static inline in libctru
// https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155
self.config.initial_index = index self.config.initial_index = index as u32;
} }
/// Launch the Mii Selector. /// Launch the Mii Selector.
/// Returns an error when the checksum of the Mii is invalid. /// Returns an error when the checksum of the Mii is invalid.
pub fn launch(&mut self) -> Result<MiiSelectorReturn, MiiLaunchError> { pub fn launch(&mut self) -> Result<SelectionResult, LaunchError> {
let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::default(); let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::default();
unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) } unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) }
if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } { if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } {
Ok((*return_val).into()) Ok((*return_val).into())
} else { } else {
Err(MiiLaunchError::InvalidChecksum) Err(LaunchError::InvalidChecksum)
} }
} }
} }
impl From<ctru_sys::MiiSelectorReturn> for MiiSelectorReturn { impl Default for MiiSelector {
fn default() -> Self {
Self::new()
}
}
impl From<ctru_sys::MiiSelectorReturn> for SelectionResult {
fn from(ret: ctru_sys::MiiSelectorReturn) -> Self { fn from(ret: ctru_sys::MiiSelectorReturn) -> Self {
let raw_mii_data = ret.mii; let raw_mii_data = ret.mii;
let mut guest_mii_name = ret.guest_mii_name; let mut guest_mii_name = ret.guest_mii_name;
MiiSelectorReturn { SelectionResult {
mii_data: raw_mii_data.into(), mii_data: raw_mii_data.into(),
is_mii_selected: ret.no_mii_selected == 0, is_mii_selected: ret.no_mii_selected == 0,
mii_type: if ret.guest_mii_index != 0xFFFFFFFF { mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
@ -179,7 +185,7 @@ impl From<ctru_sys::MiiSelectorReturn> for MiiSelectorReturn {
} }
} }
impl From<u32> for MiiConfigIndex { impl From<u32> for Index {
fn from(v: u32) -> Self { fn from(v: u32) -> Self {
Self::Index(v) Self::Index(v)
} }

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

@ -3,11 +3,11 @@ use ctru_sys::{
self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState, self, swkbdInit, swkbdInputText, swkbdSetButton, swkbdSetFeatures, swkbdSetHintText, SwkbdState,
}; };
use libc; use libc;
use std::convert::TryInto;
use std::iter::once; use std::iter::once;
use std::str; use std::str;
/// An instance of the software keyboard. /// An instance of the software keyboard.
#[derive(Clone)]
pub struct Swkbd { pub struct Swkbd {
state: Box<SwkbdState>, state: Box<SwkbdState>,
} }
@ -19,119 +19,115 @@ pub struct Swkbd {
/// Numpad is a number pad. /// Numpad is a number pad.
/// Western is a text keyboard without japanese symbols (only applies to JPN systems). For other /// Western is a text keyboard without japanese symbols (only applies to JPN systems). For other
/// systems it's the same as a Normal keyboard. /// systems it's the same as a Normal keyboard.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Kind { pub enum Kind {
Normal, Normal = ctru_sys::SWKBD_TYPE_NORMAL,
Qwerty, Qwerty = ctru_sys::SWKBD_TYPE_QWERTY,
Numpad, Numpad = ctru_sys::SWKBD_TYPE_NUMPAD,
Western, Western = ctru_sys::SWKBD_TYPE_WESTERN,
} }
/// Represents which button the user pressed to close the software keyboard. /// Represents which button the user pressed to close the software keyboard.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Button { pub enum Button {
Left, Left = ctru_sys::SWKBD_BUTTON_LEFT,
Middle, Middle = ctru_sys::SWKBD_BUTTON_MIDDLE,
Right, Right = ctru_sys::SWKBD_BUTTON_RIGHT,
} }
/// Error type for the software keyboard. /// Error type for the software keyboard.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum Error { pub enum Error {
InvalidInput, InvalidInput = ctru_sys::SWKBD_INVALID_INPUT,
OutOfMem, OutOfMem = ctru_sys::SWKBD_OUTOFMEM,
HomePressed, HomePressed = ctru_sys::SWKBD_HOMEPRESSED,
ResetPressed, ResetPressed = ctru_sys::SWKBD_RESETPRESSED,
PowerPressed, PowerPressed = ctru_sys::SWKBD_POWERPRESSED,
ParentalOk, ParentalOk = ctru_sys::SWKBD_PARENTAL_OK,
ParentalFail, ParentalFail = ctru_sys::SWKBD_PARENTAL_FAIL,
BannedInput, BannedInput = ctru_sys::SWKBD_BANNED_INPUT,
} }
/// Restrictions on keyboard input /// Restrictions on keyboard input
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ValidInput { pub enum ValidInput {
Anything, Anything = ctru_sys::SWKBD_ANYTHING,
NotEmpty, NotEmpty = ctru_sys::SWKBD_NOTEMPTY,
NotEmptyNotBlank, NotEmptyNotBlank = ctru_sys::SWKBD_NOTEMPTY_NOTBLANK,
NotBlank, NotBlank = ctru_sys::SWKBD_NOTBLANK,
FixedLen, FixedLen = ctru_sys::SWKBD_FIXEDLEN,
} }
bitflags! { bitflags! {
/// Keyboard feature flags /// Keyboard feature flags
pub struct Features: u32 { pub struct Features: u32 {
const PARENTAL_PIN = 1 << 0; const PARENTAL_PIN = ctru_sys::SWKBD_PARENTAL;
const DARKEN_TOP_SCREEN = 1 << 1; const DARKEN_TOP_SCREEN = ctru_sys::SWKBD_DARKEN_TOP_SCREEN;
const PREDICTIVE_INPUT = 1 << 2; const PREDICTIVE_INPUT = ctru_sys::SWKBD_PREDICTIVE_INPUT;
const MULTILINE = 1 << 3; const MULTILINE = ctru_sys::SWKBD_MULTILINE;
const FIXED_WIDTH = 1 << 4; const FIXED_WIDTH = ctru_sys::SWKBD_FIXED_WIDTH;
const ALLOW_HOME = 1 << 5; const ALLOW_HOME = ctru_sys::SWKBD_ALLOW_HOME;
const ALLOW_RESET = 1 << 6; const ALLOW_RESET = ctru_sys::SWKBD_ALLOW_RESET;
const ALLOW_POWER = 1 << 7; const ALLOW_POWER = ctru_sys::SWKBD_ALLOW_POWER;
const DEFAULT_QWERTY = 1 << 8; const DEFAULT_QWERTY = ctru_sys::SWKBD_DEFAULT_QWERTY;
} }
}
bitflags! {
/// Keyboard input filtering flags /// Keyboard input filtering flags
pub struct Filters: u32 { pub struct Filters: u32 {
const DIGITS = 1 << 0; const DIGITS = ctru_sys::SWKBD_FILTER_DIGITS;
const AT = 1 << 1; const AT = ctru_sys::SWKBD_FILTER_AT;
const PERCENT = 1 << 2; const PERCENT = ctru_sys::SWKBD_FILTER_PERCENT;
const BACKSLASH = 1 << 3; const BACKSLASH = ctru_sys::SWKBD_FILTER_BACKSLASH;
const PROFANITY = 1 << 4; const PROFANITY = ctru_sys::SWKBD_FILTER_PROFANITY;
const CALLBACK = 1 << 5; const CALLBACK = ctru_sys::SWKBD_FILTER_CALLBACK;
} }
} }
impl Swkbd { impl Swkbd {
/// Initializes a software keyboard of the specified type and the chosen number of buttons /// Initializes a software keyboard of the specified type and the chosen number of buttons
/// (from 1-3). /// (from 1-3).
pub fn init(keyboard_type: Kind, num_buttons: i32) -> Self { pub fn new(keyboard_type: Kind, num_buttons: i32) -> Self {
unsafe { unsafe {
let mut state = Box::<SwkbdState>::default(); let mut state = Box::<SwkbdState>::default();
swkbdInit(state.as_mut(), keyboard_type as u32, num_buttons, -1); swkbdInit(state.as_mut(), keyboard_type.into(), num_buttons, -1);
Swkbd { state } Swkbd { state }
} }
} }
/// Gets input from this keyboard and appends it to the provided string. /// Gets input from this keyboard and appends it to the provided string.
/// ///
/// The text received from the keyboard will be truncated if it is greater than 2048 bytes /// The text received from the keyboard will be truncated if it is longer than `max_bytes`.
/// in length. pub fn get_string(&mut self, max_bytes: usize) -> Result<(String, Button), Error> {
pub fn get_utf8(&mut self, buf: &mut String) -> Result<Button, Error> {
// Unfortunately the libctru API doesn't really provide a way to get the exact length // Unfortunately the libctru API doesn't really provide a way to get the exact length
// of the string that it receieves from the software keyboard. Instead it expects you // of the string that it receieves from the software keyboard. Instead it expects you
// to pass in a buffer and hope that it's big enough to fit the entire string, so // to pass in a buffer and hope that it's big enough to fit the entire string, so
// you have to set some upper limit on the potential size of the user's input. // you have to set some upper limit on the potential size of the user's input.
const MAX_BYTES: usize = 2048; let mut tmp = vec![0u8; max_bytes];
let mut tmp = [0u8; MAX_BYTES]; let button = self.write_exact(&mut tmp)?;
let button = self.get_bytes(&mut tmp)?;
// libctru does, however, seem to ensure that the buffer will always contain a properly // libctru does, however, seem to ensure that the buffer will always contain a properly
// terminated UTF-8 sequence even if the input has to be truncated, so these operations // terminated UTF-8 sequence even if the input has to be truncated, so these operations
// should be safe. // should be safe.
let len = unsafe { libc::strlen(tmp.as_ptr()) }; let len = unsafe { libc::strlen(tmp.as_ptr()) };
let utf8 = unsafe { str::from_utf8_unchecked(&tmp[..len]) }; tmp.truncate(len);
let res = unsafe { String::from_utf8_unchecked(tmp) };
// Copy the input into the user's `String` Ok((res, button))
*buf += utf8;
Ok(button)
} }
/// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from /// Fills the provided buffer with a UTF-8 encoded, NUL-terminated sequence of bytes from
/// this software keyboard. /// this software keyboard.
/// ///
/// If the buffer is too small to contain the entire sequence received from the keyboard, /// If the buffer is too small to contain the entire sequence received from the keyboard,
/// the output will be truncated but should still be well-formed UTF-8 /// the output will be truncated but should still be well-formed UTF-8.
pub fn get_bytes(&mut self, buf: &mut [u8]) -> Result<Button, Error> { pub fn write_exact(&mut self, buf: &mut [u8]) -> Result<Button, Error> {
unsafe { unsafe {
match swkbdInputText( match swkbdInputText(self.state.as_mut(), buf.as_mut_ptr(), buf.len()) {
self.state.as_mut(),
buf.as_mut_ptr(),
buf.len().try_into().unwrap(),
) {
ctru_sys::SWKBD_BUTTON_NONE => Err(self.parse_swkbd_error()), ctru_sys::SWKBD_BUTTON_NONE => Err(self.parse_swkbd_error()),
ctru_sys::SWKBD_BUTTON_LEFT => Ok(Button::Left), ctru_sys::SWKBD_BUTTON_LEFT => Ok(Button::Left),
ctru_sys::SWKBD_BUTTON_MIDDLE => Ok(Button::Middle), ctru_sys::SWKBD_BUTTON_MIDDLE => Ok(Button::Middle),
@ -148,7 +144,7 @@ impl Swkbd {
/// Configures input validation for this keyboard /// Configures input validation for this keyboard
pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) { pub fn set_validation(&mut self, validation: ValidInput, filters: Filters) {
self.state.valid_input = validation as i32; self.state.valid_input = validation.into();
self.state.filter_flags = filters.bits; self.state.filter_flags = filters.bits;
} }
@ -178,7 +174,7 @@ impl Swkbd {
let nul_terminated: String = text.chars().chain(once('\0')).collect(); let nul_terminated: String = text.chars().chain(once('\0')).collect();
swkbdSetButton( swkbdSetButton(
self.state.as_mut(), self.state.as_mut(),
button as u32, button.into(),
nul_terminated.as_ptr(), nul_terminated.as_ptr(),
submit, submit,
); );
@ -212,6 +208,11 @@ impl Swkbd {
impl Default for Swkbd { impl Default for Swkbd {
fn default() -> Self { fn default() -> Self {
Swkbd::init(Kind::Normal, 2) Swkbd::new(Kind::Normal, 2)
} }
} }
from_impl!(Kind, ctru_sys::SwkbdType);
from_impl!(Button, ctru_sys::SwkbdButton);
from_impl!(Error, ctru_sys::SwkbdResult);
from_impl!(ValidInput, i32);

8
ctru-rs/src/console.rs

@ -3,7 +3,7 @@ use std::default::Default;
use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole}; use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole};
use crate::gfx::Screen; use crate::services::gfx::Screen;
static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) }; static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) };
@ -16,7 +16,11 @@ impl<'screen> Console<'screen> {
/// Initialize a console on the chosen screen, overwriting whatever was on the 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 /// previously (including other consoles). The new console is automatically selected for
/// printing. /// printing.
pub fn init(screen: RefMut<'screen, dyn Screen>) -> Self { ///
/// # Notes
///
/// [Console] automatically takes care of flushing and swapping buffers for its screen when printing.
pub fn new(screen: RefMut<'screen, dyn Screen>) -> Self {
let mut context = Box::<PrintConsole>::default(); let mut context = Box::<PrintConsole>::default();
unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; unsafe { consoleInit(screen.as_raw(), context.as_mut()) };

40
ctru-rs/src/error.rs

@ -21,7 +21,11 @@ impl Try for ResultCode {
} }
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> { fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
if self.0 != 0 { // Wait timeouts aren't counted as "failures" in libctru, but an unfinished task means unsafety for us.
// Luckily all summary cases are for system failures (except RS_SUCCESS).
// I don't know if there are any cases in libctru where a Result holds a "failing" summary but a "success" code, so we'll just check for both.
if ctru_sys::R_FAILED(self.0) || ctru_sys::R_SUMMARY(self.0) != ctru_sys::RS_SUCCESS as i32
{
ControlFlow::Break(self.into()) ControlFlow::Break(self.into())
} else { } else {
ControlFlow::Continue(()) ControlFlow::Continue(())
@ -51,6 +55,12 @@ pub enum Error {
Libc(String), Libc(String),
ServiceAlreadyActive, ServiceAlreadyActive,
OutputAlreadyRedirected, OutputAlreadyRedirected,
BufferTooShort {
/// Length of the buffer provided by the user.
provided: usize,
/// Size of the requested data (in bytes).
wanted: usize,
},
} }
impl Error { impl Error {
@ -104,22 +114,32 @@ impl fmt::Debug for Error {
Self::Libc(err) => f.debug_tuple("Libc").field(err).finish(), Self::Libc(err) => f.debug_tuple("Libc").field(err).finish(),
Self::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").finish(), Self::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").finish(),
Self::OutputAlreadyRedirected => f.debug_tuple("OutputAlreadyRedirected").finish(), Self::OutputAlreadyRedirected => f.debug_tuple("OutputAlreadyRedirected").finish(),
Self::BufferTooShort { provided, wanted } => f
.debug_struct("BufferTooShort")
.field("provided", provided)
.field("wanted", wanted)
.finish(),
} }
} }
} }
// TODO: Expand libctru result code into human-readable error message. These should be useful:
// https://www.3dbrew.org/wiki/Error_codes
// https://github.com/devkitPro/libctru/blob/master/libctru/include/3ds/result.h
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
&Self::Os(err) => write!(f, "libctru result code: 0x{err:08X}"), &Self::Os(err) => write!(
f,
"libctru result code 0x{err:08X}: [{} {}] {}: {}",
result_code_level_str(err),
result_code_module_str(err),
result_code_summary_str(err),
result_code_description_str(err)
),
Self::Libc(err) => write!(f, "{err}"), Self::Libc(err) => write!(f, "{err}"),
Self::ServiceAlreadyActive => write!(f, "Service already active"), Self::ServiceAlreadyActive => write!(f, "service already active"),
Self::OutputAlreadyRedirected => { Self::OutputAlreadyRedirected => {
write!(f, "output streams are already redirected to 3dslink") write!(f, "output streams are already redirected to 3dslink")
} }
Self::BufferTooShort{provided, wanted} => write!(f, "the provided buffer's length is too short (length = {provided}) to hold the wanted data (size = {wanted})")
} }
} }
} }
@ -142,7 +162,7 @@ fn result_code_level_str(result: ctru_sys::Result) -> Cow<'static, str> {
RL_PERMANENT => "permanent", RL_PERMANENT => "permanent",
RL_TEMPORARY => "temporary", RL_TEMPORARY => "temporary",
RL_STATUS => "status", RL_STATUS => "status",
code => return Cow::Owned(format!("(unknown: {code:#x})")), code => return Cow::Owned(format!("(unknown level: {code:#x})")),
}) })
} }
@ -167,7 +187,7 @@ fn result_code_summary_str(result: ctru_sys::Result) -> Cow<'static, str> {
RS_STATUSCHANGED => "status_changed", RS_STATUSCHANGED => "status_changed",
RS_INTERNAL => "internal", RS_INTERNAL => "internal",
RS_INVALIDRESVAL => "invalid_res_val", RS_INVALIDRESVAL => "invalid_res_val",
code => return Cow::Owned(format!("(unknown: {code:#x})")), code => return Cow::Owned(format!("(unknown summary: {code:#x})")),
}) })
} }
@ -207,7 +227,7 @@ fn result_code_description_str(result: ctru_sys::Result) -> Cow<'static, str> {
RD_NOT_AUTHORIZED => "not_authorized", RD_NOT_AUTHORIZED => "not_authorized",
RD_TOO_LARGE => "too_large", RD_TOO_LARGE => "too_large",
RD_INVALID_SELECTION => "invalid_selection", RD_INVALID_SELECTION => "invalid_selection",
code => return Cow::Owned(format!("(unknown: {code:#x})")), code => return Cow::Owned(format!("(unknown description: {code:#x})")),
}) })
} }
@ -324,6 +344,6 @@ fn result_code_module_str(result: ctru_sys::Result) -> Cow<'static, str> {
RM_NFP => "nfp", RM_NFP => "nfp",
RM_APPLICATION => "application", RM_APPLICATION => "application",
RM_INVALIDRESVAL => "invalid_res_val", RM_INVALIDRESVAL => "invalid_res_val",
code => return Cow::Owned(format!("(unknown: {code:#x})")), code => return Cow::Owned(format!("(unknown module: {code:#x})")),
}) })
} }

73
ctru-rs/src/lib.rs

@ -7,40 +7,35 @@
#![feature(nonnull_slice_from_raw_parts)] #![feature(nonnull_slice_from_raw_parts)]
#![test_runner(test_runner::run)] #![test_runner(test_runner::run)]
extern "C" fn services_deinit() { // Nothing is imported from these crates but their inclusion here assures correct linking of the missing implementations.
unsafe { extern crate pthread_3ds;
ctru_sys::psExit(); extern crate shim_3ds;
}
}
#[no_mangle] #[no_mangle]
#[cfg(feature = "big-stack")] #[cfg(feature = "big-stack")]
static __stacksize__: usize = 2 * 1024 * 1024; // 2MB static __stacksize__: usize = 2 * 1024 * 1024; // 2MB
/// Call this somewhere to force Rust to link some required crates macro_rules! from_impl {
/// This is also a setup for some crate integration only available at runtime ($from_type:ty, $into_type:ty) => {
/// impl From<$from_type> for $into_type {
/// See <https://github.com/rust-lang/rust/issues/47384> fn from(v: $from_type) -> Self {
pub fn init() { v as $into_type
linker_fix_3ds::init(); }
pthread_3ds::init(); }
};
}
/// Activate the default 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.
///
/// # Notes
///
/// When ´test´ is enabled, this function won't do anything, as it should be overridden by the ´test´ environment.
pub fn use_panic_handler() {
#[cfg(not(test))] #[cfg(not(test))]
panic_hook_setup(); panic_hook_setup();
// Initialize the PS service for random data generation
unsafe {
let ps_ret = ctru_sys::psInit();
if ctru_sys::R_FAILED(ps_ret) {
panic!(
"Failed to initialize random data generation: {:?}",
Error::from(ps_ret)
)
}
// Setup the deconstruction at the program's end
libc::atexit(services_deinit);
}
} }
#[cfg(not(test))] #[cfg(not(test))]
@ -59,11 +54,11 @@ fn panic_hook_setup() {
if main_thread == std::thread::current().id() && console::Console::exists() { if main_thread == std::thread::current().id() && console::Console::exists() {
println!("\nPress SELECT to exit the software"); println!("\nPress SELECT to exit the software");
match Hid::init() { match Hid::new() {
Ok(hid) => loop { Ok(mut hid) => loop {
hid.scan_input(); hid.scan_input();
let keys = hid.keys_down(); let keys = hid.keys_down();
if keys.contains(KeyPad::KEY_SELECT) { if keys.contains(KeyPad::SELECT) {
break; break;
} }
}, },
@ -77,31 +72,11 @@ fn panic_hook_setup() {
pub mod applets; pub mod applets;
pub mod console; pub mod console;
pub mod error; pub mod error;
pub mod gfx;
pub mod linear; pub mod linear;
pub mod mii; pub mod mii;
pub mod prelude; pub mod prelude;
pub mod services; pub mod services;
cfg_if::cfg_if! {
if #[cfg(all(feature = "romfs", romfs_exists))] {
pub mod romfs;
} else {
pub mod romfs {
//! The RomFS folder has not been detected and/or the `romfs` feature has not been enabled.
//!
//! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the
//! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of
//! your package.
//!
//! ```toml
//! [package.metadata.cargo-3ds]
//! romfs_dir = "romfs"
//! ```
}
}
}
#[cfg(test)] #[cfg(test)]
mod test_runner; mod test_runner;

3
ctru-rs/src/linear.rs

@ -29,8 +29,7 @@ impl LinearAllocator {
unsafe impl Allocator for LinearAllocator { unsafe impl Allocator for LinearAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let pointer = let pointer = unsafe { ctru_sys::linearMemAlign(layout.size(), layout.align()) };
unsafe { ctru_sys::linearMemAlign(layout.size() as u32, layout.align() as u32) };
NonNull::new(pointer.cast()) NonNull::new(pointer.cast())
.map(|ptr| NonNull::slice_from_raw_parts(ptr, layout.size())) .map(|ptr| NonNull::slice_from_raw_parts(ptr, layout.size()))

3
ctru-rs/src/prelude.rs

@ -1,3 +1,2 @@
pub use crate::console::Console; pub use crate::console::Console;
pub use crate::gfx::Gfx; pub use crate::services::{gfx::Gfx, hid::KeyPad, soc::Soc, Apt, Hid};
pub use crate::services::{hid::KeyPad, soc::Soc, Apt, Hid};

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

@ -0,0 +1,107 @@
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
}
}
pub struct Title<'a> {
id: u64,
mediatype: FsMediaType,
_am: PhantomData<&'a Am>,
}
impl<'a> Title<'a> {
pub fn id(&self) -> u64 {
self.id
}
pub fn product_code(&self) -> crate::Result<String> {
let mut buf: [u8; 16] = [0; 16];
unsafe {
ResultCode(ctru_sys::AM_GetTitleProductCode(
self.mediatype.into(),
self.id,
buf.as_mut_ptr(),
))?;
}
Ok(String::from_utf8_lossy(&buf).to_string())
}
pub fn title_info(&self) -> crate::Result<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 _,
))?;
Ok(info.assume_init())
}
}
}
pub struct Am(());
impl Am {
pub fn new() -> crate::Result<Am> {
unsafe {
ResultCode(ctru_sys::amInit())?;
Ok(Am(()))
}
}
pub fn title_count(&self, mediatype: FsMediaType) -> crate::Result<u32> {
unsafe {
let mut count = 0;
ResultCode(ctru_sys::AM_GetTitleCount(mediatype.into(), &mut count))?;
Ok(count)
}
}
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,
mediatype.into(),
count,
buf.as_mut_ptr(),
))?;
}
Ok(buf
.into_iter()
.map(|id| Title {
id,
mediatype,
_am: PhantomData,
})
.collect())
}
}
impl Drop for Am {
fn drop(&mut self) {
unsafe { ctru_sys::amExit() };
}
}

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

@ -3,7 +3,7 @@ use crate::error::ResultCode;
pub struct Apt(()); pub struct Apt(());
impl Apt { impl Apt {
pub fn init() -> crate::Result<Apt> { pub fn new() -> crate::Result<Apt> {
unsafe { unsafe {
ResultCode(ctru_sys::aptInit())?; ResultCode(ctru_sys::aptInit())?;
Ok(Apt(())) Ok(Apt(()))
@ -14,7 +14,7 @@ impl Apt {
unsafe { ctru_sys::aptMainLoop() } unsafe { ctru_sys::aptMainLoop() }
} }
pub fn set_app_cpu_time_limit(&self, percent: u32) -> crate::Result<()> { pub fn set_app_cpu_time_limit(&mut self, percent: u32) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?; ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?;
Ok(()) Ok(())

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

@ -1,11 +1,10 @@
//! CAM service //! Camera service
//! //!
//! The CAM service provides access to the cameras. Cameras can return 2D images //! The CAM service provides access to the cameras. Cameras can return images
//! in the form of byte vectors which can be used for display or other usages. //! in the form of byte vectors which can be displayed or used in other ways.
use crate::error::ResultCode; use crate::error::{Error, ResultCode};
use crate::services::gspgpu::FramebufferFormat; use crate::services::gspgpu::FramebufferFormat;
use bitflags::bitflags;
use ctru_sys::Handle; use ctru_sys::Handle;
use std::time::Duration; use std::time::Duration;
@ -21,191 +20,169 @@ pub struct Cam {
pub both_outer_cams: BothOutwardCam, pub both_outer_cams: BothOutwardCam,
} }
bitflags! { /// Flag to pass to [Camera::flip_image]
/// A set of flags to be passed to [Camera::flip_image] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamFlip: u32 { pub enum FlipMode {
const NONE = ctru_sys::FLIP_NONE; None = ctru_sys::FLIP_NONE,
const HORIZONTAL = ctru_sys::FLIP_HORIZONTAL; Horizontal = ctru_sys::FLIP_HORIZONTAL,
const VERTICAL = ctru_sys::FLIP_VERTICAL; Vertical = ctru_sys::FLIP_VERTICAL,
const REVERSE = ctru_sys::FLIP_REVERSE; Reverse = ctru_sys::FLIP_REVERSE,
}
} }
bitflags! { /// Flag to pass to [Camera::set_view_size]
/// A set of flags to be passed to [Camera::set_view_size] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamSize: u32 { pub enum ViewSize {
const VGA = ctru_sys::SIZE_VGA; TopLCD = ctru_sys::SIZE_CTR_TOP_LCD,
const QVGA = ctru_sys::SIZE_QVGA; /// Equivalent to QVga
const QQVGA = ctru_sys::SIZE_QQVGA; BottomLCD = ctru_sys::SIZE_CTR_BOTTOM_LCD,
const CIF = ctru_sys::SIZE_CIF; Vga = ctru_sys::SIZE_VGA,
const QCIF = ctru_sys::SIZE_QCIF; QQVga = ctru_sys::SIZE_QQVGA,
const DS_LCD = ctru_sys::SIZE_DS_LCD; Cif = ctru_sys::SIZE_CIF,
const DS_LCD_X4 = ctru_sys::SIZE_DS_LCDx4; QCif = ctru_sys::SIZE_QCIF,
const CTR_TOP_LCD = ctru_sys::SIZE_CTR_TOP_LCD; /// Nintendo DS Screen
const CTR_BOTTOM_LCD = ctru_sys::SIZE_CTR_BOTTOM_LCD; DS = ctru_sys::SIZE_DS_LCD,
} /// Nintendo DS Screen x4
DSX4 = ctru_sys::SIZE_DS_LCDx4,
} }
bitflags! { /// Flag to pass to [Camera::set_frame_rate]
/// A set of flags to be passed to [Camera::set_frame_rate] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamFrameRate: u32 { pub enum FrameRate {
const RATE_15 = ctru_sys::FRAME_RATE_15; Fps15 = ctru_sys::FRAME_RATE_15,
const RATE_15_TO_5 = ctru_sys::FRAME_RATE_15_TO_5; Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5,
const RATE_15_TO_2 = ctru_sys::FRAME_RATE_15_TO_2; Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2,
const RATE_10 = ctru_sys::FRAME_RATE_10; Fps10 = ctru_sys::FRAME_RATE_10,
const RATE_8_5 = ctru_sys::FRAME_RATE_8_5; Fps8_5 = ctru_sys::FRAME_RATE_8_5,
const RATE_5 = ctru_sys::FRAME_RATE_5; Fps5 = ctru_sys::FRAME_RATE_5,
const RATE_20 = ctru_sys::FRAME_RATE_20; Fps20 = ctru_sys::FRAME_RATE_20,
const RATE_20_TO_5 = ctru_sys::FRAME_RATE_20_TO_5; Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5,
const RATE_30 = ctru_sys::FRAME_RATE_30; Fps30 = ctru_sys::FRAME_RATE_30,
const RATE_30_TO_5 = ctru_sys::FRAME_RATE_30_TO_5; Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5,
const RATE_15_TO_10 = ctru_sys::FRAME_RATE_15_TO_10; Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10,
const RATE_20_TO_10 = ctru_sys::FRAME_RATE_20_TO_10; Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10,
const RATE_30_TO_10 = ctru_sys::FRAME_RATE_30_TO_10; Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10,
}
} }
bitflags! { /// Flag to pass to [Camera::set_white_balance] or
/// A set of flags to be passed to [Camera::set_white_balance] or /// [Camera::set_white_balance_without_base_up]
/// [Camera::set_white_balance_without_base_up] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamWhiteBalance: u32 { pub enum WhiteBalance {
const AUTO = ctru_sys::WHITE_BALANCE_AUTO; /// Normal
const BALANCE_3200K = ctru_sys::WHITE_BALANCE_3200K; Auto = ctru_sys::WHITE_BALANCE_AUTO,
const BALANCE_4150K = ctru_sys::WHITE_BALANCE_4150K; /// Tungsten
const BALANCE_5200K = ctru_sys::WHITE_BALANCE_5200K; Temp3200K = ctru_sys::WHITE_BALANCE_3200K,
const BALANCE_6000K = ctru_sys::WHITE_BALANCE_6000K; /// Fluorescent Light
const BALANCE_7000K = ctru_sys::WHITE_BALANCE_7000K; Temp4150K = ctru_sys::WHITE_BALANCE_4150K,
/// Daylight
const NORMAL = ctru_sys::WHITE_BALANCE_NORMAL; Temp5200K = ctru_sys::WHITE_BALANCE_5200K,
const TUNGSTEN = ctru_sys::WHITE_BALANCE_TUNGSTEN; /// Cloudy/Horizon
const WHITE_FLUORESCENT_LIGHT = ctru_sys::WHITE_BALANCE_WHITE_FLUORESCENT_LIGHT; Temp6000K = ctru_sys::WHITE_BALANCE_6000K,
const DAYLIGHT = ctru_sys::WHITE_BALANCE_DAYLIGHT; ///Shade
const CLOUDY = ctru_sys::WHITE_BALANCE_CLOUDY; Temp7000K = ctru_sys::WHITE_BALANCE_7000K,
const HORIZON = ctru_sys::WHITE_BALANCE_HORIZON;
const SHADE = ctru_sys::WHITE_BALANCE_SHADE;
}
} }
bitflags! { /// Flag to pass to [Camera::set_photo_mode]
/// A set of flags to be passed to [Camera::set_photo_mode] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamPhotoMode: u32 { pub enum PhotoMode {
const NORMAL = ctru_sys::PHOTO_MODE_NORMAL; Normal = ctru_sys::PHOTO_MODE_NORMAL,
const PORTRAIT = ctru_sys::PHOTO_MODE_PORTRAIT; Portrait = ctru_sys::PHOTO_MODE_PORTRAIT,
const LANDSCAPE = ctru_sys::PHOTO_MODE_LANDSCAPE; Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE,
const NIGHTVIEW = ctru_sys::PHOTO_MODE_NIGHTVIEW; NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW,
const LETTER = ctru_sys::PHOTO_MODE_LETTER; Letter = ctru_sys::PHOTO_MODE_LETTER,
}
} }
bitflags! { /// Flag to pass to [Camera::set_effect]
/// A set of flags to be passed to [Camera::set_effect] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamEffect: u32 { pub enum Effect {
const NONE = ctru_sys::EFFECT_NONE; None = ctru_sys::EFFECT_NONE,
const MONO = ctru_sys::EFFECT_MONO; Mono = ctru_sys::EFFECT_MONO,
const SEPIA = ctru_sys::EFFECT_SEPIA; Sepia = ctru_sys::EFFECT_SEPIA,
const NEGATIVE = ctru_sys::EFFECT_NEGATIVE; Negative = ctru_sys::EFFECT_NEGATIVE,
const NEGAFILM = ctru_sys::EFFECT_NEGAFILM; Negafilm = ctru_sys::EFFECT_NEGAFILM,
const SEPIA01 = ctru_sys::EFFECT_SEPIA01; Sepia01 = ctru_sys::EFFECT_SEPIA01,
}
} }
bitflags! { /// Flag to pass to [Camera::set_contrast]
/// A set of flags to be passed to [Camera::set_contrast] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamContrast: u32 { pub enum Contrast {
const PATTERN_01 = ctru_sys::CONTRAST_PATTERN_01; /// OFF
const PATTERN_02 = ctru_sys::CONTRAST_PATTERN_02; Low = ctru_sys::CONTRAST_LOW,
const PATTERN_03 = ctru_sys::CONTRAST_PATTERN_03; /// Brightness ratio: 70
const PATTERN_04 = ctru_sys::CONTRAST_PATTERN_04; Normal = ctru_sys::CONTRAST_NORMAL,
const PATTERN_05 = ctru_sys::CONTRAST_PATTERN_05; /// Brightness ratio: 90
const PATTERN_06 = ctru_sys::CONTRAST_PATTERN_06; High = ctru_sys::CONTRAST_HIGH,
const PATTERN_07 = ctru_sys::CONTRAST_PATTERN_07;
const PATTERN_08 = ctru_sys::CONTRAST_PATTERN_08;
const PATTERN_09 = ctru_sys::CONTRAST_PATTERN_09;
const PATTERN_10 = ctru_sys::CONTRAST_PATTERN_10;
const PATTERN_11 = ctru_sys::CONTRAST_PATTERN_11;
const LOW = ctru_sys::CONTRAST_LOW;
const NORMAL = ctru_sys::CONTRAST_NORMAL;
const HIGH = ctru_sys::CONTRAST_HIGH;
}
} }
bitflags! { /// Flag to pass to [Camera::set_lens_correction]
/// A set of flags to be passed to [Camera::set_lens_correction] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamLensCorrection: u32 { pub enum LensCorrection {
const OFF = ctru_sys::LENS_CORRECTION_OFF; Off = ctru_sys::LENS_CORRECTION_DARK,
const ON_70 = ctru_sys::LENS_CORRECTION_ON_70; Normal = ctru_sys::LENS_CORRECTION_NORMAL,
const ON_90 = ctru_sys::LENS_CORRECTION_ON_90; Bright = ctru_sys::LENS_CORRECTION_BRIGHT,
}
const DARK = ctru_sys::LENS_CORRECTION_DARK; /// Flag to pass to [Camera::set_output_format]
const NORMAL = ctru_sys::LENS_CORRECTION_NORMAL; #[derive(Copy, Clone, Debug, PartialEq, Eq)]
const BRIGHT = ctru_sys::LENS_CORRECTION_BRIGHT; #[repr(u32)]
} pub enum OutputFormat {
Yuv422 = ctru_sys::OUTPUT_YUV_422,
Rgb565 = ctru_sys::OUTPUT_RGB_565,
} }
bitflags! { /// Flag to pass to [Cam::play_shutter_sound]
/// A set of flags to be passed to [Camera::set_output_format] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Default)] #[repr(u32)]
pub struct CamOutputFormat: u32 { pub enum ShutterSound {
const YUV_422 = ctru_sys::OUTPUT_YUV_422; Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL,
const RGB_565 = ctru_sys::OUTPUT_RGB_565; Movie = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE,
} MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END,
} }
impl TryFrom<FramebufferFormat> for CamOutputFormat { impl TryFrom<FramebufferFormat> for OutputFormat {
type Error = (); type Error = ();
fn try_from(value: FramebufferFormat) -> Result<Self, Self::Error> { fn try_from(value: FramebufferFormat) -> Result<Self, Self::Error> {
match value { match value {
FramebufferFormat::Rgb565 => Ok(CamOutputFormat::RGB_565), FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565),
_ => Err(()), _ => Err(()),
} }
} }
} }
impl TryFrom<CamOutputFormat> for FramebufferFormat { impl TryFrom<OutputFormat> for FramebufferFormat {
type Error = (); type Error = ();
fn try_from(value: CamOutputFormat) -> Result<Self, Self::Error> { fn try_from(value: OutputFormat) -> Result<Self, Self::Error> {
match value { match value {
CamOutputFormat::RGB_565 => Ok(FramebufferFormat::Rgb565), OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565),
_ => Err(()), _ => Err(()),
} }
} }
} }
bitflags! {
/// A set of flags to be passed to [Cam::play_shutter_sound]
#[derive(Default)]
pub struct CamShutterSoundType: u32 {
const NORMAL = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL;
const MOVIE = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE;
const MOVIE_END = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END;
}
}
/// Struct containing coordinates passed to [Camera::set_trimming_params]. /// Struct containing coordinates passed to [Camera::set_trimming_params].
pub struct CamTrimmingParams { #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrimmingParams {
x_start: i16, x_start: i16,
y_start: i16, y_start: i16,
x_end: i16, x_end: i16,
y_end: i16, y_end: i16,
} }
impl CamTrimmingParams { impl TrimmingParams {
/// Creates a new [CamTrimmingParams] and guarantees the start coordinates are less than or /// Creates a new [CamTrimmingParams] and guarantees the start coordinates are less than or
/// equal to the end coordinates. /// equal to the end coordinates.
/// ///
/// `x_start <= x_end && y_start <= y_end` /// `x_start <= x_end && y_start <= y_end`
pub fn new(x_start: i16, y_start: i16, x_end: i16, y_end: i16) -> CamTrimmingParams { pub fn new(x_start: i16, y_start: i16, x_end: i16, y_end: i16) -> TrimmingParams {
assert!(x_start <= x_end && y_start <= y_end); assert!(x_start <= x_end && y_start <= y_end);
Self { Self {
x_start, x_start,
@ -217,11 +194,11 @@ impl CamTrimmingParams {
} }
/// Represents data used by the camera to calibrate image quality /// Represents data used by the camera to calibrate image quality
#[derive(Default)] #[derive(Default, Clone, Copy, Debug)]
pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibrationData); pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibrationData);
/// Represents data used by the camera to calibrate image quality when using both outward cameras /// Represents data used by the camera to calibrate image quality when using both outward cameras
#[derive(Default)] #[derive(Default, Clone, Copy, Debug)]
pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData);
/// Represents the camera on the inside of the 3DS /// Represents the camera on the inside of the 3DS
@ -307,7 +284,7 @@ pub trait Camera {
/// Returns the maximum amount of transfer bytes based on the view size, trimming, and other /// Returns the maximum amount of transfer bytes based on the view size, trimming, and other
/// modifications set to the camera /// modifications set to the camera
fn get_transfer_bytes(&self) -> crate::Result<u32> { fn transfer_byte_count(&self) -> crate::Result<u32> {
unsafe { unsafe {
let mut transfer_bytes = 0; let mut transfer_bytes = 0;
ResultCode(ctru_sys::CAMU_GetTransferBytes( ResultCode(ctru_sys::CAMU_GetTransferBytes(
@ -336,8 +313,8 @@ pub trait Camera {
} }
} }
/// Sets trimming parameters based on coordinates specified inside a [CamTrimmingParams] /// Sets trimming parameters based on coordinates specified inside a [TrimmingParams]
fn set_trimming_params(&mut self, params: CamTrimmingParams) -> crate::Result<()> { fn set_trimming_params(&mut self, params: TrimmingParams) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetTrimmingParams( ResultCode(ctru_sys::CAMU_SetTrimmingParams(
self.port_as_raw(), self.port_as_raw(),
@ -350,8 +327,8 @@ pub trait Camera {
} }
} }
/// Returns the set [CamTrimmingParams] from the camera /// Returns the [TrimmingParams] set
fn get_trimming_params(&self) -> crate::Result<CamTrimmingParams> { fn trimming_params(&self) -> crate::Result<TrimmingParams> {
unsafe { unsafe {
let mut x_start = 0; let mut x_start = 0;
let mut y_start = 0; let mut y_start = 0;
@ -365,7 +342,7 @@ pub trait Camera {
self.port_as_raw(), self.port_as_raw(),
))?; ))?;
Ok(CamTrimmingParams { Ok(TrimmingParams {
x_start, x_start,
y_start, y_start,
x_end, x_end,
@ -378,7 +355,7 @@ pub trait Camera {
/// The new width will be `trim_width / 2` to the left and right of the center. /// The new width will be `trim_width / 2` to the left and right of the center.
/// The new height will be `trim_height / 2` above and below the center. /// The new height will be `trim_height / 2` above and below the center.
fn set_trimming_params_center( fn set_trimming_params_center(
&self, &mut self,
trim_width: i16, trim_width: i16,
trim_height: i16, trim_height: i16,
cam_width: i16, cam_width: i16,
@ -404,27 +381,27 @@ pub trait Camera {
} }
} }
/// Sets the white balance mod of the camera based on the passed [CamWhiteBalance] argument /// Sets the white balance mod of the camera based on the passed [WhiteBalance] argument
fn set_white_balance(&mut self, white_balance: CamWhiteBalance) -> crate::Result<()> { fn set_white_balance(&mut self, white_balance: WhiteBalance) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetWhiteBalance( ResultCode(ctru_sys::CAMU_SetWhiteBalance(
self.camera_as_raw(), self.camera_as_raw(),
white_balance.bits(), white_balance.into(),
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the white balance mode of the camera based on the passed [CamWhiteBalance] argument /// Sets the white balance mode of the camera based on the passed [WhiteBalance] argument
// TODO: Explain base up // TODO: Explain base up
fn set_white_balance_without_base_up( fn set_white_balance_without_base_up(
&mut self, &mut self,
white_balance: CamWhiteBalance, white_balance: WhiteBalance,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetWhiteBalanceWithoutBaseUp( ResultCode(ctru_sys::CAMU_SetWhiteBalanceWithoutBaseUp(
self.camera_as_raw(), self.camera_as_raw(),
white_balance.bits(), white_balance.into(),
))?; ))?;
Ok(()) Ok(())
} }
@ -484,12 +461,12 @@ pub trait Camera {
} }
} }
/// Sets the flip direction of the camera's image based on the passed [CamFlip] argument /// Sets the flip direction of the camera's image based on the passed [FlipMode] argument
fn flip_image(&mut self, flip: CamFlip) -> crate::Result<()> { fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_FlipImage( ResultCode(ctru_sys::CAMU_FlipImage(
self.camera_as_raw(), self.camera_as_raw(),
flip.bits(), flip.into(),
ctru_sys::CONTEXT_A, ctru_sys::CONTEXT_A,
))?; ))?;
Ok(()) Ok(())
@ -530,82 +507,82 @@ pub trait Camera {
} }
} }
/// Sets the view size of the camera based on the passed [CamSize] argument. /// Sets the view size of the camera based on the passed [ViewSize] argument.
fn set_view_size(&mut self, size: CamSize) -> crate::Result<()> { fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetSize( ResultCode(ctru_sys::CAMU_SetSize(
self.camera_as_raw(), self.camera_as_raw(),
size.bits(), size.into(),
ctru_sys::CONTEXT_A, ctru_sys::CONTEXT_A,
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the frame rate of the camera based on the passed [CamFrameRate] argument. /// Sets the frame rate of the camera based on the passed [FrameRate] argument.
fn set_frame_rate(&mut self, frame_rate: CamFrameRate) -> crate::Result<()> { fn set_frame_rate(&mut self, frame_rate: FrameRate) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetFrameRate( ResultCode(ctru_sys::CAMU_SetFrameRate(
self.camera_as_raw(), self.camera_as_raw(),
frame_rate.bits(), frame_rate.into(),
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the photo mode of the camera based on the passed [CamPhotoMode] argument. /// Sets the photo mode of the camera based on the passed [PhotoMode] argument.
fn set_photo_mode(&mut self, photo_mode: CamPhotoMode) -> crate::Result<()> { fn set_photo_mode(&mut self, photo_mode: PhotoMode) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetPhotoMode( ResultCode(ctru_sys::CAMU_SetPhotoMode(
self.camera_as_raw(), self.camera_as_raw(),
photo_mode.bits(), photo_mode.into(),
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the effect of the camera based on the passed [CamEffect] argument. /// Sets the effect of the camera based on the passed [Effect] argument.
/// ///
/// Multiple effects can be set at once by combining the bitflags of [CamEffect] /// Multiple effects can be set at once by combining the bitflags of [CamEffect]
fn set_effect(&mut self, effect: CamEffect) -> crate::Result<()> { fn set_effect(&mut self, effect: Effect) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetEffect( ResultCode(ctru_sys::CAMU_SetEffect(
self.camera_as_raw(), self.camera_as_raw(),
effect.bits(), effect.into(),
ctru_sys::CONTEXT_A, ctru_sys::CONTEXT_A,
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the contrast of the camera based on the passed [CamContrast] argument. /// Sets the contrast of the camera based on the passed [Contrast] argument.
fn set_contrast(&mut self, contrast: CamContrast) -> crate::Result<()> { fn set_contrast(&mut self, contrast: Contrast) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetContrast( ResultCode(ctru_sys::CAMU_SetContrast(
self.camera_as_raw(), self.camera_as_raw(),
contrast.bits(), contrast.into(),
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the lens correction of the camera based on the passed [CamLensCorrection] argument. /// Sets the lens correction of the camera based on the passed [LensCorrection] argument.
fn set_lens_correction(&mut self, lens_correction: CamLensCorrection) -> crate::Result<()> { fn set_lens_correction(&mut self, lens_correction: LensCorrection) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetLensCorrection( ResultCode(ctru_sys::CAMU_SetLensCorrection(
self.camera_as_raw(), self.camera_as_raw(),
lens_correction.bits(), lens_correction.into(),
))?; ))?;
Ok(()) Ok(())
} }
} }
/// Sets the output format of the camera based on the passed [CamOutputFormat] argument. /// Sets the output format of the camera based on the passed [OutputFormat] argument.
fn set_output_format(&mut self, format: CamOutputFormat) -> crate::Result<()> { fn set_output_format(&mut self, format: OutputFormat) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetOutputFormat( ResultCode(ctru_sys::CAMU_SetOutputFormat(
self.camera_as_raw(), self.camera_as_raw(),
format.bits(), format.into(),
ctru_sys::CONTEXT_A, ctru_sys::CONTEXT_A,
))?; ))?;
Ok(()) Ok(())
@ -687,7 +664,7 @@ pub trait Camera {
} }
/// Returns the current [ImageQualityCalibrationData] for the camera /// Returns the current [ImageQualityCalibrationData] for the camera
fn get_image_quality_calibration_data(&self) -> crate::Result<ImageQualityCalibrationData> { fn image_quality_calibration_data(&self) -> crate::Result<ImageQualityCalibrationData> {
unsafe { unsafe {
let mut data = ImageQualityCalibrationData::default(); let mut data = ImageQualityCalibrationData::default();
ResultCode(ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0))?; ResultCode(ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0))?;
@ -704,7 +681,7 @@ pub trait Camera {
} }
} }
/// Requests the camera to take a picture and returns a vector containing the image bytes. /// Requests the camera to take a picture and write it in a buffer.
/// ///
/// # Errors /// # Errors
/// ///
@ -717,10 +694,11 @@ pub trait Camera {
/// * `timeout` - Duration to wait for the image /// * `timeout` - Duration to wait for the image
fn take_picture( fn take_picture(
&mut self, &mut self,
buffer: &mut [u8],
width: u16, width: u16,
height: u16, height: u16,
timeout: Duration, timeout: Duration,
) -> crate::Result<Vec<u8>> { ) -> crate::Result<()> {
let transfer_unit = unsafe { let transfer_unit = unsafe {
let mut buf_size = 0; let mut buf_size = 0;
ResultCode(ctru_sys::CAMU_GetMaxBytes( ResultCode(ctru_sys::CAMU_GetMaxBytes(
@ -731,10 +709,6 @@ pub trait Camera {
Ok::<u32, i32>(buf_size) Ok::<u32, i32>(buf_size)
}?; }?;
let screen_size = u32::from(width) * u32::from(width) * 2;
let mut buf = vec![0u8; usize::try_from(screen_size).unwrap()];
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetTransferBytes( ResultCode(ctru_sys::CAMU_SetTransferBytes(
self.port_as_raw(), self.port_as_raw(),
@ -744,6 +718,14 @@ pub trait Camera {
))?; ))?;
}; };
let screen_size: usize = usize::from(width) * usize::from(height) * 2;
if buffer.len() < screen_size {
return Err(Error::BufferTooShort {
provided: buffer.len(),
wanted: screen_size,
});
}
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?;
ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
@ -754,25 +736,30 @@ pub trait Camera {
let mut completion_handle: Handle = 0; let mut completion_handle: Handle = 0;
ResultCode(ctru_sys::CAMU_SetReceiving( ResultCode(ctru_sys::CAMU_SetReceiving(
&mut completion_handle, &mut completion_handle,
buf.as_mut_ptr() as *mut ::libc::c_void, buffer.as_mut_ptr().cast(),
self.port_as_raw(), self.port_as_raw(),
screen_size, screen_size as u32,
transfer_unit.try_into().unwrap(), transfer_unit.try_into().unwrap(),
))?; ))?;
Ok::<Handle, i32>(completion_handle) Ok::<Handle, i32>(completion_handle)
}?; }?;
unsafe { unsafe {
ResultCode(ctru_sys::svcWaitSynchronization( // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully (TODO: SVC module)
let wait_result = ResultCode(ctru_sys::svcWaitSynchronization(
receive_event, receive_event,
timeout.as_nanos().try_into().unwrap(), timeout.as_nanos().try_into().unwrap(),
))?; ));
// We close everything first, then we check for possible errors
let _ = ctru_sys::svcCloseHandle(receive_event); // We wouldn't return the error even if there was one, so no use of ResultCode is needed
ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?; ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?;
ResultCode(ctru_sys::svcCloseHandle(receive_event))?;
ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))?; ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))?;
wait_result?;
}; };
Ok(buf) Ok(())
} }
} }
@ -784,7 +771,7 @@ impl Cam {
/// This function will return an error if the service was unable to be initialized. /// This function will return an error if the service was unable to be initialized.
/// Since this service requires no special or elevated permissions, errors are /// Since this service requires no special or elevated permissions, errors are
/// rare in practice. /// rare in practice.
pub fn init() -> crate::Result<Cam> { pub fn new() -> crate::Result<Cam> {
unsafe { unsafe {
ResultCode(ctru_sys::camInit())?; ResultCode(ctru_sys::camInit())?;
Ok(Cam { Ok(Cam {
@ -796,10 +783,10 @@ impl Cam {
} }
} }
/// Plays the specified sound based on the [CamShutterSoundType] argument /// Plays the specified sound based on the [ShutterSound] argument
pub fn play_shutter_sound(&self, sound: CamShutterSoundType) -> crate::Result<()> { pub fn play_shutter_sound(&self, sound: ShutterSound) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.bits()))?; ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?;
Ok(()) Ok(())
} }
} }
@ -810,3 +797,14 @@ impl Drop for Cam {
unsafe { ctru_sys::camExit() }; unsafe { ctru_sys::camExit() };
} }
} }
from_impl!(FlipMode, ctru_sys::CAMU_Flip);
from_impl!(ViewSize, ctru_sys::CAMU_Size);
from_impl!(FrameRate, ctru_sys::CAMU_FrameRate);
from_impl!(WhiteBalance, ctru_sys::CAMU_WhiteBalance);
from_impl!(PhotoMode, ctru_sys::CAMU_PhotoMode);
from_impl!(Effect, ctru_sys::CAMU_Effect);
from_impl!(Contrast, ctru_sys::CAMU_Contrast);
from_impl!(LensCorrection, ctru_sys::CAMU_LensCorrection);
from_impl!(OutputFormat, ctru_sys::CAMU_OutputFormat);
from_impl!(ShutterSound, ctru_sys::CAMU_ShutterSoundType);

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

@ -4,7 +4,7 @@
use crate::error::ResultCode; use crate::error::ResultCode;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Region { pub enum Region {
Japan = ctru_sys::CFG_REGION_JPN, Japan = ctru_sys::CFG_REGION_JPN,
@ -16,7 +16,7 @@ pub enum Region {
Taiwan = ctru_sys::CFG_REGION_TWN, Taiwan = ctru_sys::CFG_REGION_TWN,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum Language { pub enum Language {
Japanese = ctru_sys::CFG_LANGUAGE_JP, Japanese = ctru_sys::CFG_LANGUAGE_JP,
@ -33,15 +33,15 @@ pub enum Language {
TraditionalChinese = ctru_sys::CFG_LANGUAGE_TW, TraditionalChinese = ctru_sys::CFG_LANGUAGE_TW,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum SystemModel { pub enum SystemModel {
Model3DS = ctru_sys::CFG_MODEL_3DS, Old3DS = ctru_sys::CFG_MODEL_3DS,
Model3DSXL = ctru_sys::CFG_MODEL_3DSXL, Old3DSXL = ctru_sys::CFG_MODEL_3DSXL,
ModelNew3DS = ctru_sys::CFG_MODEL_N3DS, New3DS = ctru_sys::CFG_MODEL_N3DS,
Model2DS = ctru_sys::CFG_MODEL_2DS, Old2DS = ctru_sys::CFG_MODEL_2DS,
ModelNew3DSXL = ctru_sys::CFG_MODEL_N3DSXL, New3DSXL = ctru_sys::CFG_MODEL_N3DSXL,
ModelNew2DSXL = ctru_sys::CFG_MODEL_N2DSXL, New2DSXL = ctru_sys::CFG_MODEL_N2DSXL,
} }
/// Represents the configuration service. No actions can be performed /// Represents the configuration service. No actions can be performed
@ -61,13 +61,13 @@ impl Cfgu {
/// ctrulib services are reference counted, so this function may be called /// ctrulib services are reference counted, so this function may be called
/// as many times as desired and the service will not exit until all /// as many times as desired and the service will not exit until all
/// instances of Cfgu drop out of scope. /// instances of Cfgu drop out of scope.
pub fn init() -> crate::Result<Cfgu> { pub fn new() -> crate::Result<Cfgu> {
ResultCode(unsafe { ctru_sys::cfguInit() })?; ResultCode(unsafe { ctru_sys::cfguInit() })?;
Ok(Cfgu(())) Ok(Cfgu(()))
} }
/// Gets system region from secure info /// Gets system region from secure info
pub fn get_region(&self) -> crate::Result<Region> { pub fn region(&self) -> crate::Result<Region> {
let mut region: u8 = 0; let mut region: u8 = 0;
ResultCode(unsafe { ctru_sys::CFGU_SecureInfoGetRegion(&mut region) })?; ResultCode(unsafe { ctru_sys::CFGU_SecureInfoGetRegion(&mut region) })?;
@ -75,7 +75,7 @@ impl Cfgu {
} }
/// Gets system's model /// Gets system's model
pub fn get_model(&self) -> crate::Result<SystemModel> { pub fn model(&self) -> crate::Result<SystemModel> {
let mut model: u8 = 0; let mut model: u8 = 0;
ResultCode(unsafe { ctru_sys::CFGU_GetSystemModel(&mut model) })?; ResultCode(unsafe { ctru_sys::CFGU_GetSystemModel(&mut model) })?;
@ -83,7 +83,7 @@ impl Cfgu {
} }
/// Gets system's language /// Gets system's language
pub fn get_language(&self) -> crate::Result<Language> { pub fn language(&self) -> crate::Result<Language> {
let mut language: u8 = 0; let mut language: u8 = 0;
ResultCode(unsafe { ctru_sys::CFGU_GetSystemLanguage(&mut language) })?; ResultCode(unsafe { ctru_sys::CFGU_GetSystemLanguage(&mut language) })?;
@ -115,19 +115,9 @@ impl Drop for Cfgu {
} }
} }
macro_rules! from_type_to_u8 { from_impl!(Region, u8);
($from_type:ty) => { from_impl!(Language, u8);
impl From<$from_type> for u8 { from_impl!(SystemModel, u8);
fn from(v: $from_type) -> Self {
v as u8
}
}
};
}
from_type_to_u8!(Region);
from_type_to_u8!(Language);
from_type_to_u8!(SystemModel);
impl TryFrom<u8> for Region { impl TryFrom<u8> for Region {
type Error = (); type Error = ();
@ -173,12 +163,12 @@ impl TryFrom<u8> for SystemModel {
fn try_from(value: u8) -> Result<Self, Self::Error> { fn try_from(value: u8) -> Result<Self, Self::Error> {
match value as u32 { match value as u32 {
ctru_sys::CFG_MODEL_3DS => Ok(SystemModel::Model3DS), ctru_sys::CFG_MODEL_3DS => Ok(SystemModel::Old3DS),
ctru_sys::CFG_MODEL_3DSXL => Ok(SystemModel::Model3DSXL), ctru_sys::CFG_MODEL_3DSXL => Ok(SystemModel::Old3DSXL),
ctru_sys::CFG_MODEL_N3DS => Ok(SystemModel::ModelNew3DS), ctru_sys::CFG_MODEL_N3DS => Ok(SystemModel::New3DS),
ctru_sys::CFG_MODEL_2DS => Ok(SystemModel::Model2DS), ctru_sys::CFG_MODEL_2DS => Ok(SystemModel::Old2DS),
ctru_sys::CFG_MODEL_N3DSXL => Ok(SystemModel::ModelNew3DSXL), ctru_sys::CFG_MODEL_N3DSXL => Ok(SystemModel::New3DSXL),
ctru_sys::CFG_MODEL_N2DSXL => Ok(SystemModel::ModelNew2DSXL), ctru_sys::CFG_MODEL_N2DSXL => Ok(SystemModel::New2DSXL),
_ => Err(()), _ => Err(()),
} }
} }

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

@ -1,7 +1,7 @@
//! Filesystem service //! Filesystem service
//! //!
//! This module contains basic methods to manipulate the contents of the 3DS's filesystem. //! This module contains basic methods to manipulate the contents of the 3DS's filesystem.
//! Only the SD card is currently supported. //! Only the SD card is currently supported. You should prefer using `std::fs`.
use bitflags::bitflags; use bitflags::bitflags;
use std::ffi::OsString; use std::ffi::OsString;
@ -23,17 +23,13 @@ bitflags! {
const FS_OPEN_WRITE = 2; const FS_OPEN_WRITE = 2;
const FS_OPEN_CREATE = 4; const FS_OPEN_CREATE = 4;
} }
}
bitflags! {
#[derive(Default)] #[derive(Default)]
struct FsWrite: u32 { struct FsWrite: u32 {
const FS_WRITE_FLUSH = 1; const FS_WRITE_FLUSH = 1;
const FS_WRITE_UPDATE_TIME = 256; const FS_WRITE_UPDATE_TIME = 256;
} }
}
bitflags! {
#[derive(Default)] #[derive(Default)]
struct FsAttribute: u32 { struct FsAttribute: u32 {
const FS_ATTRIBUTE_DIRECTORY = 1; const FS_ATTRIBUTE_DIRECTORY = 1;
@ -43,39 +39,49 @@ bitflags! {
} }
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FsMediaType {
Nand = ctru_sys::MEDIATYPE_NAND,
Sd = ctru_sys::MEDIATYPE_SD,
GameCard = ctru_sys::MEDIATYPE_GAME_CARD,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum PathType { pub enum PathType {
Invalid, Invalid = ctru_sys::PATH_INVALID,
Empty, Empty = ctru_sys::PATH_EMPTY,
Binary, Binary = ctru_sys::PATH_BINARY,
ASCII, ASCII = ctru_sys::PATH_ASCII,
UTF16, UTF16 = ctru_sys::PATH_UTF16,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ArchiveID { pub enum ArchiveID {
RomFS, RomFS = ctru_sys::ARCHIVE_ROMFS,
Savedata, Savedata = ctru_sys::ARCHIVE_SAVEDATA,
Extdata, Extdata = ctru_sys::ARCHIVE_EXTDATA,
SharedExtdata, SharedExtdata = ctru_sys::ARCHIVE_SHARED_EXTDATA,
SystemSavedata, SystemSavedata = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA,
Sdmc, Sdmc = ctru_sys::ARCHIVE_SDMC,
SdmcWriteOnly, SdmcWriteOnly = ctru_sys::ARCHIVE_SDMC_WRITE_ONLY,
BossExtdata, BossExtdata = ctru_sys::ARCHIVE_BOSS_EXTDATA,
CardSpiFS, CardSpiFS = ctru_sys::ARCHIVE_CARD_SPIFS,
ExtDataAndBossExtdata, ExtDataAndBossExtdata = ctru_sys::ARCHIVE_EXTDATA_AND_BOSS_EXTDATA,
SystemSaveData2, SystemSaveData2 = ctru_sys::ARCHIVE_SYSTEM_SAVEDATA2,
NandRW, NandRW = ctru_sys::ARCHIVE_NAND_RW,
NandRO, NandRO = ctru_sys::ARCHIVE_NAND_RO,
NandROWriteAccess, NandROWriteAccess = ctru_sys::ARCHIVE_NAND_RO_WRITE_ACCESS,
SaveDataAndContent, SaveDataAndContent = ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT,
SaveDataAndContent2, SaveDataAndContent2 = ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT2,
NandCtrFS, NandCtrFS = ctru_sys::ARCHIVE_NAND_CTR_FS,
TwlPhoto, TwlPhoto = ctru_sys::ARCHIVE_TWL_PHOTO,
NandTwlFS, NandTwlFS = ctru_sys::ARCHIVE_NAND_TWL_FS,
GameCardSavedata, GameCardSavedata = ctru_sys::ARCHIVE_GAMECARD_SAVEDATA,
UserSavedata, UserSavedata = ctru_sys::ARCHIVE_USER_SAVEDATA,
DemoSavedata, DemoSavedata = ctru_sys::ARCHIVE_DEMO_SAVEDATA,
} }
/// Represents the filesystem service. No file IO can be performed /// Represents the filesystem service. No file IO can be performed
@ -93,7 +99,7 @@ pub struct Fs(());
/// ```no_run /// ```no_run
/// use ctru::services::fs::Fs; /// use ctru::services::fs::Fs;
/// ///
/// let fs = Fs::init().unwrap(); /// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap(); /// let sdmc_archive = fs.sdmc().unwrap();
/// ``` /// ```
pub struct Archive { pub struct Archive {
@ -113,47 +119,62 @@ pub struct Archive {
/// Create a new file and write bytes to it: /// Create a new file and write bytes to it:
/// ///
/// ```no_run /// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::prelude::*; /// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File}; /// use ctru::services::fs::{Fs, File};
/// ///
/// let fs = Fs::init()?; /// let mut fs = Fs::new()?;
/// let sdmc = fs.sdmc()?; /// let mut sdmc = fs.sdmc()?;
/// /// #
/// let mut file = File::create(&sdmc, "/foo.txt")?; /// # Ok(())
/// file.write_all(b"Hello, world!")?; /// # }
/// ``` /// ```
/// ///
/// Read the contents of a file into a `String`:: /// Read the contents of a file into a `String`::
/// ///
/// ```no_run /// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::prelude::*; /// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File}; /// use ctru::services::fs::{Fs, File};
/// ///
/// let fs = Fs::init()?; /// let mut fs = Fs::new()?;
/// let sdmc = fs.sdmc()?; /// let mut sdmc = fs.sdmc()?;
/// ///
/// let mut file = File::open(&sdmc, "/foo.txt")?; /// let mut file = File::open(&sdmc, "/foo.txt")?;
/// let mut contents = String::new(); /// let mut contents = String::new();
/// file.read_to_string(&mut contents)?; /// file.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!"); /// assert_eq!(contents, "Hello, world!");
/// #
/// # Ok(())
/// # }
/// ``` /// ```
/// ///
/// It can be more efficient to read the contents of a file with a buffered /// It can be more efficient to read the contents of a file with a buffered
/// `Read`er. This can be accomplished with `BufReader<R>`: /// `Read`er. This can be accomplished with `BufReader<R>`:
/// ///
/// ```no_run /// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::BufReader; /// use std::io::BufReader;
/// use std::io::prelude::*; /// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File}; /// use ctru::services::fs::{Fs, File};
/// ///
/// let fs = Fs::init()?; /// let mut fs = Fs::new()?;
/// let sdmc = fs.sdmc()?; /// let mut sdmc = fs.sdmc()?;
/// ///
/// let file = File::open(&sdmc, "/foo.txt")?; /// let file = File::open(&sdmc, "/foo.txt")?;
/// let mut buf_reader = BufReader::new(file); /// let mut buf_reader = BufReader::new(file);
/// let mut contents = String::new(); /// let mut contents = String::new();
/// buf_reader.read_to_string(&mut contents)?; /// buf_reader.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!"); /// assert_eq!(contents, "Hello, world!");
/// #
/// # Ok(())
/// # }
/// ``` /// ```
pub struct File { pub struct File {
handle: u32, handle: u32,
@ -199,8 +220,8 @@ pub struct Metadata {
/// ```no_run /// ```no_run
/// use ctru::services::fs::{Fs, OpenOptions}; /// use ctru::services::fs::{Fs, OpenOptions};
/// ///
/// let fs = Fs::init().unwrap(); /// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap();
/// let file = OpenOptions::new() /// let file = OpenOptions::new()
/// .read(true) /// .read(true)
/// .archive(&sdmc_archive) /// .archive(&sdmc_archive)
@ -214,8 +235,8 @@ pub struct Metadata {
/// ```no_run /// ```no_run
/// use ctru::services::fs::{Fs, OpenOptions}; /// use ctru::services::fs::{Fs, OpenOptions};
/// ///
/// let fs = Fs::init().unwrap(); /// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap();
/// let file = OpenOptions::new() /// let file = OpenOptions::new()
/// .read(true) /// .read(true)
/// .write(true) /// .write(true)
@ -224,7 +245,7 @@ pub struct Metadata {
/// .open("foo.txt") /// .open("foo.txt")
/// .unwrap(); /// .unwrap();
/// ``` /// ```
#[derive(Clone, Default)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OpenOptions { pub struct OpenOptions {
read: bool, read: bool,
write: bool, write: bool,
@ -288,7 +309,7 @@ impl Fs {
/// ctrulib services are reference counted, so this function may be called /// ctrulib services are reference counted, so this function may be called
/// as many times as desired and the service will not exit until all /// as many times as desired and the service will not exit until all
/// instances of Fs drop out of scope. /// instances of Fs drop out of scope.
pub fn init() -> crate::Result<Fs> { pub fn new() -> crate::Result<Fs> {
unsafe { unsafe {
let r = ctru_sys::fsInit(); let r = ctru_sys::fsInit();
if r < 0 { if r < 0 {
@ -300,7 +321,7 @@ impl Fs {
} }
/// Returns a handle to the SDMC (memory card) Archive. /// Returns a handle to the SDMC (memory card) Archive.
pub fn sdmc(&self) -> crate::Result<Archive> { pub fn sdmc(&mut self) -> crate::Result<Archive> {
unsafe { unsafe {
let mut handle = 0; let mut handle = 0;
let id = ArchiveID::Sdmc; let id = ArchiveID::Sdmc;
@ -319,7 +340,7 @@ impl Archive {
/// Retrieves an Archive's [`ArchiveID`] /// Retrieves an Archive's [`ArchiveID`]
/// ///
/// [`ArchiveID`]: enum.ArchiveID.html /// [`ArchiveID`]: enum.ArchiveID.html
pub fn get_id(&self) -> ArchiveID { pub fn id(&self) -> ArchiveID {
self.id self.id
} }
} }
@ -341,8 +362,8 @@ impl File {
/// ```no_run /// ```no_run
/// use ctru::services::fs::{Fs, File}; /// use ctru::services::fs::{Fs, File};
/// ///
/// let fs = Fs::init().unwrap(); /// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap(); /// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap();
/// ``` /// ```
pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> { pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
@ -370,11 +391,11 @@ impl File {
/// ```no_run /// ```no_run
/// use ctru::services::fs::{Fs, File}; /// use ctru::services::fs::{Fs, File};
/// ///
/// let fs = Fs::init().unwrap(); /// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap(); /// let mut sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap(); /// let mut f = File::create(&mut sdmc_archive, "/foo.txt").unwrap();
/// ``` /// ```
pub fn create<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> { pub fn create<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<File> {
OpenOptions::new() OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
@ -578,11 +599,11 @@ impl OpenOptions {
/// * Invalid combinations of open options. /// * Invalid combinations of open options.
/// ///
/// [`Archive`]: struct.Archive.html /// [`Archive`]: struct.Archive.html
pub fn open<P: AsRef<Path>>(&self, path: P) -> IoResult<File> { pub fn open<P: AsRef<Path>>(&mut self, path: P) -> IoResult<File> {
self._open(path.as_ref(), self.get_open_flags()) self._open(path.as_ref(), self.open_flags())
} }
fn _open(&self, path: &Path, flags: FsOpen) -> IoResult<File> { fn _open(&mut self, path: &Path, flags: FsOpen) -> IoResult<File> {
unsafe { unsafe {
let mut file_handle = 0; let mut file_handle = 0;
let path = to_utf16(path); let path = to_utf16(path);
@ -618,7 +639,7 @@ impl OpenOptions {
} }
} }
fn get_open_flags(&self) -> FsOpen { fn open_flags(&self) -> FsOpen {
match (self.read, self.write || self.append, self.create) { match (self.read, self.write || self.append, self.create) {
(true, false, false) => FsOpen::FS_OPEN_READ, (true, false, false) => FsOpen::FS_OPEN_READ,
(false, true, false) => FsOpen::FS_OPEN_WRITE, (false, true, false) => FsOpen::FS_OPEN_WRITE,
@ -696,7 +717,7 @@ impl<'a> DirEntry<'a> {
/// but is not limited to just these cases: /// but is not limited to just these cases:
/// ///
/// * User lacks permissions to create directory at `path` /// * User lacks permissions to create directory at `path`
pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { pub fn create_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -722,7 +743,7 @@ pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
/// ///
/// * If any directory in the path specified by `path` does not already exist /// * If any directory in the path specified by `path` does not already exist
/// and it could not be created otherwise. /// and it could not be created otherwise.
pub fn create_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { pub fn create_dir_all<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
let path = path.as_ref(); let path = path.as_ref();
let mut dir = PathBuf::new(); let mut dir = PathBuf::new();
let mut result = Ok(()); let mut result = Ok(());
@ -758,7 +779,7 @@ pub fn metadata<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<Metadata> {
/// ///
/// * The user lacks permissions to remove the directory at the provided path. /// * The user lacks permissions to remove the directory at the provided path.
/// * The directory isn't empty. /// * The directory isn't empty.
pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { pub fn remove_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -776,7 +797,7 @@ pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
/// # Errors /// # Errors
/// ///
/// see `file::remove_file` and `fs::remove_dir` /// see `file::remove_file` and `fs::remove_dir`
pub fn remove_dir_all<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { pub fn remove_dir_all<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -828,7 +849,7 @@ pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<ReadDir> {
/// ///
/// * path points to a directory. /// * path points to a directory.
/// * The user lacks permissions to remove the file. /// * The user lacks permissions to remove the file.
pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { pub fn remove_file<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
unsafe { unsafe {
let path = to_utf16(path.as_ref()); let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _); let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -851,7 +872,7 @@ pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
/// ///
/// * from does not exist. /// * from does not exist.
/// * The user lacks permissions to view contents. /// * The user lacks permissions to view contents.
pub fn rename<P, Q>(arch: &Archive, from: P, to: Q) -> IoResult<()> pub fn rename<P, Q>(arch: &mut Archive, from: P, to: Q) -> IoResult<()>
where where
P: AsRef<Path>, P: AsRef<Path>,
Q: AsRef<Path>, Q: AsRef<Path>,
@ -1008,45 +1029,6 @@ impl Drop for Dir {
} }
} }
impl From<PathType> for ctru_sys::FS_PathType { from_impl!(FsMediaType, ctru_sys::FS_MediaType);
fn from(p: PathType) -> Self { from_impl!(PathType, ctru_sys::FS_PathType);
use self::PathType::*; from_impl!(ArchiveID, ctru_sys::FS_ArchiveID);
match p {
Invalid => ctru_sys::PATH_INVALID,
Empty => ctru_sys::PATH_EMPTY,
Binary => ctru_sys::PATH_BINARY,
ASCII => ctru_sys::PATH_ASCII,
UTF16 => ctru_sys::PATH_UTF16,
}
}
}
impl From<ArchiveID> for ctru_sys::FS_ArchiveID {
fn from(a: ArchiveID) -> Self {
use self::ArchiveID::*;
match a {
RomFS => ctru_sys::ARCHIVE_ROMFS,
Savedata => ctru_sys::ARCHIVE_SAVEDATA,
Extdata => ctru_sys::ARCHIVE_EXTDATA,
SharedExtdata => ctru_sys::ARCHIVE_SHARED_EXTDATA,
SystemSavedata => ctru_sys::ARCHIVE_SYSTEM_SAVEDATA,
Sdmc => ctru_sys::ARCHIVE_SDMC,
SdmcWriteOnly => ctru_sys::ARCHIVE_SDMC_WRITE_ONLY,
BossExtdata => ctru_sys::ARCHIVE_BOSS_EXTDATA,
CardSpiFS => ctru_sys::ARCHIVE_CARD_SPIFS,
ExtDataAndBossExtdata => ctru_sys::ARCHIVE_EXTDATA_AND_BOSS_EXTDATA,
SystemSaveData2 => ctru_sys::ARCHIVE_SYSTEM_SAVEDATA2,
NandRW => ctru_sys::ARCHIVE_NAND_RW,
NandRO => ctru_sys::ARCHIVE_NAND_RO,
NandROWriteAccess => ctru_sys::ARCHIVE_NAND_RO_WRITE_ACCESS,
SaveDataAndContent => ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT,
SaveDataAndContent2 => ctru_sys::ARCHIVE_SAVEDATA_AND_CONTENT2,
NandCtrFS => ctru_sys::ARCHIVE_NAND_CTR_FS,
TwlPhoto => ctru_sys::ARCHIVE_TWL_PHOTO,
NandTwlFS => ctru_sys::ARCHIVE_NAND_TWL_FS,
GameCardSavedata => ctru_sys::ARCHIVE_GAMECARD_SAVEDATA,
UserSavedata => ctru_sys::ARCHIVE_USER_SAVEDATA,
DemoSavedata => ctru_sys::ARCHIVE_DEMO_SAVEDATA,
}
}
}

195
ctru-rs/src/gfx.rs → ctru-rs/src/services/gfx.rs

@ -9,11 +9,12 @@ use crate::services::gspgpu::{self, FramebufferFormat};
use crate::services::ServiceReference; use crate::services::ServiceReference;
mod private { mod private {
use super::{BottomScreen, TopScreen, TopScreenLeft, TopScreenRight}; use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight};
pub trait Sealed {} pub trait Sealed {}
impl Sealed for TopScreen {} impl Sealed for TopScreen {}
impl Sealed for TopScreen3D<'_> {}
impl Sealed for TopScreenLeft {} impl Sealed for TopScreenLeft {}
impl Sealed for TopScreenRight {} impl Sealed for TopScreenRight {}
impl Sealed for BottomScreen {} impl Sealed for BottomScreen {}
@ -33,34 +34,37 @@ pub trait Screen: private::Sealed {
/// ///
/// Note that the pointer of the framebuffer returned by this function can /// Note that the pointer of the framebuffer returned by this function can
/// change after each call to this function if double buffering is enabled. /// change after each call to this function if double buffering is enabled.
fn get_raw_framebuffer(&mut self) -> RawFrameBuffer { fn raw_framebuffer(&mut self) -> RawFrameBuffer {
let mut width = 0; let mut width: u16 = 0;
let mut height = 0; let mut height: u16 = 0;
let ptr = unsafe { let ptr = unsafe {
ctru_sys::gfxGetFramebuffer(self.as_raw(), self.side().into(), &mut width, &mut height) ctru_sys::gfxGetFramebuffer(self.as_raw(), self.side().into(), &mut width, &mut height)
}; };
RawFrameBuffer { RawFrameBuffer {
ptr, ptr,
width, width: width.into(),
height, height: height.into(),
screen: PhantomData, screen: PhantomData,
} }
} }
/// Sets whether to use double buffering. Enabled by default. /// Sets whether to use double buffering. Enabled by default.
/// ///
/// Note that even when double buffering is disabled, one should still use the `swap_buffers` /// [`Swap::swap_buffers`] must be called after this function for the configuration
/// method on each frame to keep the gsp configuration up to date /// change to take effect.
fn set_double_buffering(&mut self, enabled: bool) { fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) } unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) }
} }
/// Gets the framebuffer format /// Gets the framebuffer format.
fn get_framebuffer_format(&self) -> FramebufferFormat { fn framebuffer_format(&self) -> FramebufferFormat {
unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()).into() } unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()) }.into()
} }
/// Change the framebuffer format /// Change the framebuffer format.
///
/// [`Swap::swap_buffers`] must be called after this method for the configuration
/// change to take effect.
fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) { fn set_framebuffer_format(&mut self, fmt: FramebufferFormat) {
unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) } unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) }
} }
@ -75,17 +79,98 @@ pub struct TopScreen {
/// A helper container for both sides of the top screen. Once the [`TopScreen`] is /// A helper container for both sides of the top screen. Once the [`TopScreen`] is
/// converted into this, 3D mode will be enabled until this struct is dropped. /// converted into this, 3D mode will be enabled until this struct is dropped.
pub struct TopScreen3D<'top_screen> { pub struct TopScreen3D<'screen> {
screen: &'top_screen RefCell<TopScreen>, screen: &'screen RefCell<TopScreen>,
}
/// A screen that can have its frame buffers swapped, if 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`]).
///
/// 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
fn swap_buffers(&mut self);
}
impl Swap for TopScreen3D<'_> {
fn swap_buffers(&mut self) {
unsafe {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, true);
}
}
}
impl Swap for TopScreen {
fn swap_buffers(&mut self) {
unsafe {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_TOP, false);
}
}
}
impl Swap for BottomScreen {
fn swap_buffers(&mut self) {
unsafe {
ctru_sys::gfxScreenSwapBuffers(ctru_sys::GFX_BOTTOM, false);
}
}
}
/// 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.
fn flush_buffers(&mut self);
} }
struct TopScreenLeft; impl<S: Screen> Flush for S {
fn flush_buffers(&mut self) {
let framebuffer = self.raw_framebuffer();
struct TopScreenRight; // Flush the data array. `self.raw_framebuffer` should get the correct parameters for all kinds of screens
unsafe {
ctru_sys::GSPGPU_FlushDataCache(
framebuffer.ptr.cast(),
(framebuffer.height * framebuffer.width) as u32,
)
};
}
}
impl Flush for TopScreen3D<'_> {
/// Unlike most other implementations of [`Flush`], this flushes the buffers for both
/// the left and right sides of the top screen.
fn flush_buffers(&mut self) {
let (mut left, mut right) = self.split_mut();
left.flush_buffers();
right.flush_buffers();
}
}
/// The left side of the top screen, when using 3D mode.
#[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
pub struct TopScreenLeft;
/// The right side of the top screen, when using 3D mode.
#[derive(Debug)]
#[non_exhaustive]
pub struct TopScreenRight;
/// The bottom screen. Mutable access to this struct is required to write to the /// The bottom screen. Mutable access to this struct is required to write to the
/// bottom screen's frame buffer. /// bottom screen's frame buffer.
#[derive(Debug)]
#[non_exhaustive]
pub struct BottomScreen; pub struct BottomScreen;
/// Representation of a framebuffer for one [`Side`] of the top screen, or the /// Representation of a framebuffer for one [`Side`] of the top screen, or the
@ -96,22 +181,23 @@ pub struct RawFrameBuffer<'screen> {
/// Pointer to graphics data to be rendered. /// Pointer to graphics data to be rendered.
pub ptr: *mut u8, pub ptr: *mut u8,
/// The width of the framebuffer in pixels. /// The width of the framebuffer in pixels.
pub width: u16, pub width: usize,
/// The height of the framebuffer in pixels. /// The height of the framebuffer in pixels.
pub height: u16, pub height: usize,
/// Keep a mutable reference to the Screen for which this framebuffer is tied. /// Keep a mutable reference to the Screen for which this framebuffer is tied.
screen: PhantomData<&'screen mut dyn Screen>, screen: PhantomData<&'screen mut dyn Screen>,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
/// Side of top screen framebuffer /// Side of top screen framebuffer
/// ///
/// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality /// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality
pub enum Side { pub enum Side {
/// The left framebuffer. This framebuffer is also the one used when 3D is disabled /// The left framebuffer. This framebuffer is also the one used when 3D is disabled
Left, Left = ctru_sys::GFX_LEFT,
/// The right framebuffer /// The right framebuffer
Right, Right = ctru_sys::GFX_RIGHT,
} }
/// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that /// A handle to libctru's gfx module. This module is a wrapper around the GSPGPU service that
@ -127,10 +213,17 @@ pub struct Gfx {
static GFX_ACTIVE: Mutex<usize> = Mutex::new(0); static GFX_ACTIVE: Mutex<usize> = Mutex::new(0);
impl Gfx { impl Gfx {
/// Creates a new [Gfx] instance with default init values
/// It's the same as calling:
/// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)`
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 /// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom
/// screens /// screens
/// ///
/// Use `Gfx::init()` 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
pub fn with_formats( pub fn with_formats(
top_fb_fmt: FramebufferFormat, top_fb_fmt: FramebufferFormat,
bottom_fb_fmt: FramebufferFormat, bottom_fb_fmt: FramebufferFormat,
@ -154,32 +247,6 @@ impl Gfx {
}) })
} }
/// Creates a new [Gfx] instance with default init values
/// It's the same as calling:
/// `Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)`
pub fn init() -> Result<Self> {
Gfx::with_formats(FramebufferFormat::Bgr8, FramebufferFormat::Bgr8, false)
}
/// Flushes the current framebuffers
pub fn flush_buffers(&self) {
unsafe { ctru_sys::gfxFlushBuffers() };
}
/// Swaps the framebuffers and sets the gsp state
///
/// Use this function when working with software rendering
pub fn swap_buffers(&self) {
unsafe { ctru_sys::gfxSwapBuffers() };
}
/// Swaps the framebuffers without manipulating the gsp state
///
/// Use this function when working with GPU rendering
pub fn swap_buffers_gpu(&self) {
unsafe { ctru_sys::gfxSwapBuffersGpu() };
}
/// Waits for the vertical blank interrupt /// Waits for the vertical blank interrupt
/// ///
/// Use this to synchronize your application with the refresh rate of the LCD screens /// Use this to synchronize your application with the refresh rate of the LCD screens
@ -190,22 +257,20 @@ impl Gfx {
impl TopScreen3D<'_> { impl TopScreen3D<'_> {
/// Immutably borrow the two sides of the screen as `(left, right)`. /// Immutably borrow the two sides of the screen as `(left, right)`.
pub fn split(&self) -> (Ref<dyn Screen>, Ref<dyn Screen>) { pub fn split(&self) -> (Ref<TopScreenLeft>, Ref<TopScreenRight>) {
Ref::map_split(self.screen.borrow(), |screen| { Ref::map_split(self.screen.borrow(), |screen| (&screen.left, &screen.right))
(&screen.left as _, &screen.right as _)
})
} }
/// Mutably borrow the two sides of the screen as `(left, right)`. /// Mutably borrow the two sides of the screen as `(left, right)`.
pub fn split_mut(&self) -> (RefMut<dyn Screen>, RefMut<dyn Screen>) { pub fn split_mut(&self) -> (RefMut<TopScreenLeft>, RefMut<TopScreenRight>) {
RefMut::map_split(self.screen.borrow_mut(), |screen| { RefMut::map_split(self.screen.borrow_mut(), |screen| {
(&mut screen.left as _, &mut screen.right as _) (&mut screen.left, &mut screen.right)
}) })
} }
} }
impl<'top_screen> From<&'top_screen RefCell<TopScreen>> for TopScreen3D<'top_screen> { impl<'screen> From<&'screen RefCell<TopScreen>> for TopScreen3D<'screen> {
fn from(top_screen: &'top_screen RefCell<TopScreen>) -> Self { fn from(top_screen: &'screen RefCell<TopScreen>) -> Self {
unsafe { unsafe {
ctru_sys::gfxSet3D(true); ctru_sys::gfxSet3D(true);
} }
@ -231,6 +296,9 @@ impl TopScreen {
} }
/// Enable or disable wide mode on the top screen. /// Enable or disable wide mode on the top screen.
///
/// [`Swap::swap_buffers`] must be called after this method for the configuration
/// to take effect.
pub fn set_wide_mode(&mut self, enable: bool) { pub fn set_wide_mode(&mut self, enable: bool) {
unsafe { unsafe {
ctru_sys::gfxSetWide(enable); ctru_sys::gfxSetWide(enable);
@ -238,11 +306,13 @@ impl TopScreen {
} }
/// Returns whether or not wide mode is enabled on the top screen. /// Returns whether or not wide mode is enabled on the top screen.
pub fn get_wide_mode(&self) -> bool { pub fn is_wide(&self) -> bool {
unsafe { ctru_sys::gfxIsWide() } unsafe { ctru_sys::gfxIsWide() }
} }
} }
// When 3D mode is disabled, only the left side is used, so this Screen impl
// just forwards everything to the TopScreenLeft.
impl Screen for TopScreen { impl Screen for TopScreen {
fn as_raw(&self) -> ctru_sys::gfxScreen_t { fn as_raw(&self) -> ctru_sys::gfxScreen_t {
self.left.as_raw() self.left.as_raw()
@ -283,14 +353,7 @@ impl Screen for BottomScreen {
} }
} }
impl From<Side> for ctru_sys::gfx3dSide_t { from_impl!(Side, ctru_sys::gfx3dSide_t);
fn from(s: Side) -> ctru_sys::gfx3dSide_t {
match s {
Side::Left => ctru_sys::GFX_LEFT,
Side::Right => ctru_sys::GFX_RIGHT,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -300,6 +363,6 @@ mod tests {
#[test] #[test]
fn gfx_duplicate() { fn gfx_duplicate() {
// We don't need to build a `Gfx` because the test runner has one already // We don't need to build a `Gfx` because the test runner has one already
assert!(matches!(Gfx::init(), Err(Error::ServiceAlreadyActive))); assert!(matches!(Gfx::new(), Err(Error::ServiceAlreadyActive)));
} }
} }

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

@ -1,31 +1,31 @@
//! GSPGPU service //! GSPGPU service
use std::convert::From; #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
#[derive(Copy, Clone, Debug)]
pub enum Event { pub enum Event {
Psc0, Psc0 = ctru_sys::GSPGPU_EVENT_PSC0,
Psc1, Psc1 = ctru_sys::GSPGPU_EVENT_PSC1,
VBlank0, VBlank0 = ctru_sys::GSPGPU_EVENT_VBlank0,
VBlank1, VBlank1 = ctru_sys::GSPGPU_EVENT_VBlank1,
PPF, PPF = ctru_sys::GSPGPU_EVENT_PPF,
P3D, P3D = ctru_sys::GSPGPU_EVENT_P3D,
DMA, DMA = ctru_sys::GSPGPU_EVENT_DMA,
} }
/// The different framebuffer formats supported by the 3DS /// Framebuffer formats supported by the 3DS
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FramebufferFormat { pub enum FramebufferFormat {
/// RGBA8. 4 bytes per pixel /// RGBA8. 4 bytes per pixel
Rgba8, Rgba8 = ctru_sys::GSP_RGBA8_OES,
/// BGR8. 3 bytes per pixel /// BGR8. 3 bytes per pixel
Bgr8, Bgr8 = ctru_sys::GSP_BGR8_OES,
/// RGB565. 2 bytes per pixel /// RGB565. 2 bytes per pixel
Rgb565, Rgb565 = ctru_sys::GSP_RGB565_OES,
/// RGB5A1. 2 bytes per pixel /// RGB5A1. 2 bytes per pixel
Rgb5A1, Rgb5A1 = ctru_sys::GSP_RGB5_A1_OES,
/// RGBA4. 2 bytes per pixel /// RGBA4. 2 bytes per pixel
Rgba4, Rgba4 = ctru_sys::GSP_RGBA4_OES,
} }
impl FramebufferFormat { impl FramebufferFormat {
@ -65,30 +65,5 @@ impl From<ctru_sys::GSPGPU_FramebufferFormat> for FramebufferFormat {
} }
} }
impl From<FramebufferFormat> for ctru_sys::GSPGPU_FramebufferFormat { from_impl!(FramebufferFormat, ctru_sys::GSPGPU_FramebufferFormat);
fn from(g: FramebufferFormat) -> Self { from_impl!(Event, ctru_sys::GSPGPU_Event);
use self::FramebufferFormat::*;
match g {
Rgba8 => ctru_sys::GSP_RGBA8_OES,
Bgr8 => ctru_sys::GSP_BGR8_OES,
Rgb565 => ctru_sys::GSP_RGB565_OES,
Rgb5A1 => ctru_sys::GSP_RGB5_A1_OES,
Rgba4 => ctru_sys::GSP_RGBA4_OES,
}
}
}
impl From<Event> for ctru_sys::GSPGPU_Event {
fn from(ev: Event) -> Self {
use self::Event::*;
match ev {
Psc0 => ctru_sys::GSPGPU_EVENT_PSC0,
Psc1 => ctru_sys::GSPGPU_EVENT_PSC1,
VBlank0 => ctru_sys::GSPGPU_EVENT_VBlank0,
VBlank1 => ctru_sys::GSPGPU_EVENT_VBlank1,
PPF => ctru_sys::GSPGPU_EVENT_PPF,
P3D => ctru_sys::GSPGPU_EVENT_P3D,
DMA => ctru_sys::GSPGPU_EVENT_DMA,
}
}
}

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

@ -8,36 +8,35 @@ use crate::error::ResultCode;
bitflags::bitflags! { bitflags::bitflags! {
/// A set of flags corresponding to the button and directional pad /// A set of flags corresponding to the button and directional pad
/// inputs on the 3DS /// inputs on the 3DS
#[derive(Default)]
pub struct KeyPad: u32 { pub struct KeyPad: u32 {
const KEY_A = 1u32 << 0; const A = ctru_sys::KEY_A;
const KEY_B = 1u32 << 1; const B = ctru_sys::KEY_B;
const KEY_SELECT = 1u32 << 2; const SELECT = ctru_sys::KEY_SELECT;
const KEY_START = 1u32 << 3; const START = ctru_sys::KEY_START;
const KEY_DRIGHT = 1u32 << 4; const DPAD_RIGHT = ctru_sys::KEY_DRIGHT;
const KEY_DLEFT = 1u32 << 5; const DPAD_LEFT = ctru_sys::KEY_DLEFT;
const KEY_DUP = 1u32 << 6; const DPAD_UP = ctru_sys::KEY_DUP;
const KEY_DDOWN = 1u32 << 7; const DPAD_DOWN = ctru_sys::KEY_DDOWN;
const KEY_R = 1u32 << 8; const R = ctru_sys::KEY_R;
const KEY_L = 1u32 << 9; const L = ctru_sys::KEY_L;
const KEY_X = 1u32 << 10; const X = ctru_sys::KEY_X;
const KEY_Y = 1u32 << 11; const Y = ctru_sys::KEY_Y;
const KEY_ZL = 1u32 << 14; const ZL = ctru_sys::KEY_ZL;
const KEY_ZR = 1u32 << 15; const ZR = ctru_sys::KEY_ZR;
const KEY_TOUCH = 1u32 << 20; const TOUCH = ctru_sys::KEY_TOUCH;
const KEY_CSTICK_RIGHT = 1u32 << 24; const CSTICK_RIGHT = ctru_sys::KEY_CSTICK_RIGHT;
const KEY_CSTICK_LEFT = 1u32 << 25; const CSTICK_LEFT = ctru_sys::KEY_CSTICK_LEFT;
const KEY_CSTICK_UP = 1u32 << 26; const CSTICK_UP = ctru_sys::KEY_CSTICK_UP;
const KEY_CSTICK_DOWN = 1u32 << 27; const CSTICK_DOWN = ctru_sys::KEY_CSTICK_DOWN;
const KEY_CPAD_RIGHT = 1u32 << 28; const CPAD_RIGHT = ctru_sys::KEY_CPAD_RIGHT;
const KEY_CPAD_LEFT = 1u32 << 29; const CPAD_LEFT = ctru_sys::KEY_CPAD_LEFT;
const KEY_CPAD_UP = 1u32 << 30; const CPAD_UP = ctru_sys::KEY_CPAD_UP;
const KEY_CPAD_DOWN = 1u32 << 31; const CPAD_DOWN = ctru_sys::KEY_CPAD_DOWN;
// convenience catch-all for the dpad and cpad // Convenience catch-all for the dpad and cpad
const KEY_UP = KeyPad::KEY_DUP.bits | KeyPad::KEY_CPAD_UP.bits; const UP = KeyPad::DPAD_UP.bits() | KeyPad::CPAD_UP.bits();
const KEY_DOWN = KeyPad::KEY_DDOWN.bits | KeyPad::KEY_CPAD_DOWN.bits; const DOWN = KeyPad::DPAD_DOWN.bits() | KeyPad::CPAD_DOWN.bits();
const KEY_LEFT = KeyPad::KEY_DLEFT.bits | KeyPad::KEY_CPAD_LEFT.bits; const LEFT = KeyPad::DPAD_LEFT.bits() | KeyPad::CPAD_LEFT.bits();
const KEY_RIGHT = KeyPad::KEY_DRIGHT.bits | KeyPad::KEY_CPAD_RIGHT.bits; const RIGHT = KeyPad::DPAD_RIGHT.bits() | KeyPad::CPAD_RIGHT.bits();
} }
} }
@ -47,12 +46,6 @@ bitflags::bitflags! {
/// This service requires no special permissions to use. /// This service requires no special permissions to use.
pub struct Hid(()); pub struct Hid(());
/// Represents user input to the touchscreen.
pub struct TouchPosition(ctru_sys::touchPosition);
/// Represents the current position of the 3DS circle pad.
pub struct CirclePosition(ctru_sys::circlePosition);
/// Initializes the HID service. /// Initializes the HID service.
/// ///
/// # Errors /// # Errors
@ -61,7 +54,7 @@ pub struct CirclePosition(ctru_sys::circlePosition);
/// Since this service requires no special or elevated permissions, errors are /// Since this service requires no special or elevated permissions, errors are
/// rare in practice. /// rare in practice.
impl Hid { impl Hid {
pub fn init() -> crate::Result<Hid> { pub fn new() -> crate::Result<Hid> {
unsafe { unsafe {
ResultCode(ctru_sys::hidInit())?; ResultCode(ctru_sys::hidInit())?;
Ok(Hid(())) Ok(Hid(()))
@ -71,7 +64,7 @@ impl Hid {
/// Scans the HID service for all user input occurring on the current /// Scans the HID service for all user input occurring on the current
/// frame. This function should be called on every frame when polling /// frame. This function should be called on every frame when polling
/// for user input. /// for user input.
pub fn scan_input(&self) { pub fn scan_input(&mut self) {
unsafe { ctru_sys::hidScanInput() }; unsafe { ctru_sys::hidScanInput() };
} }
@ -101,47 +94,33 @@ impl Hid {
KeyPad::from_bits_truncate(keys) KeyPad::from_bits_truncate(keys)
} }
} }
}
impl Default for TouchPosition {
fn default() -> Self {
TouchPosition(ctru_sys::touchPosition { px: 0, py: 0 })
}
}
impl TouchPosition { /// Returns the current touch position in pixels (x, y).
/// Create a new TouchPosition instance. ///
pub fn new() -> Self { /// # Notes
Self::default() ///
} /// (0, 0) represents the top left corner of the screen.
pub fn touch_position(&mut self) -> (u16, u16) {
let mut res = ctru_sys::touchPosition { px: 0, py: 0 };
/// Returns the current touch position in pixels.
pub fn get(&mut self) -> (u16, u16) {
unsafe { unsafe {
ctru_sys::hidTouchRead(&mut self.0); ctru_sys::hidTouchRead(&mut res);
}
(self.0.px, self.0.py)
} }
} (res.px, res.py)
impl Default for CirclePosition {
fn default() -> Self {
CirclePosition(ctru_sys::circlePosition { dx: 0, dy: 0 })
} }
}
impl CirclePosition { /// Returns the current circle pad position in relative (x, y).
/// Create a new CirclePosition instance. ///
pub fn new() -> Self { /// # Notes
Self::default() ///
} /// (0, 0) represents the center of the circle pad.
pub fn circlepad_position(&mut self) -> (i16, i16) {
let mut res = ctru_sys::circlePosition { dx: 0, dy: 0 };
/// Returns the current circle pad position in (x, y) form.
pub fn get(&mut self) -> (i16, i16) {
unsafe { unsafe {
ctru_sys::hidCircleRead(&mut self.0); ctru_sys::hidCircleRead(&mut res);
} }
(self.0.dx, self.0.dy) (res.dx, res.dy)
} }
} }

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

@ -5,10 +5,12 @@
//! //!
//! Some include: button input, audio playback, graphics rendering, built-in cameras, etc. //! Some include: button input, audio playback, graphics rendering, built-in cameras, etc.
pub mod am;
pub mod apt; pub mod apt;
pub mod cam; pub mod cam;
pub mod cfgu; pub mod cfgu;
pub mod fs; pub mod fs;
pub mod gfx;
pub mod gspgpu; pub mod gspgpu;
pub mod hid; pub mod hid;
pub mod ir_user; pub mod ir_user;
@ -19,6 +21,29 @@ pub mod soc;
pub mod srv; pub mod srv;
pub mod sslc; pub mod sslc;
cfg_if::cfg_if! {
if #[cfg(all(feature = "romfs", romfs_exists))] {
pub mod romfs;
} else {
pub mod romfs {
//! The RomFS folder has not been detected and/or the `romfs` feature has not been enabled.
//!
//! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the
//! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of
//! your package.
//!
//! ```toml
//! [package.metadata.cargo-3ds]
//! romfs_dir = "romfs"
//! ```
// If the feature is set, but no "romfs" directory was found: send an error during compilation.
#[cfg(feature = "romfs")]
compile_error!("romfs feature is enabled but no romfs found!");
}
}
}
pub use self::apt::Apt; pub use self::apt::Apt;
pub use self::hid::Hid; pub use self::hid::Hid;

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

@ -1,19 +1,20 @@
//! NDSP (Audio) service //! NDSP (Audio) service
pub mod wave; pub mod wave;
use wave::{WaveInfo, WaveStatus}; use wave::{Wave, WaveStatus};
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::default::Default;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::sync::Mutex; use std::sync::Mutex;
const NUMBER_OF_CHANNELS: u8 = 24; const NUMBER_OF_CHANNELS: u8 = 24;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum OutputMode { pub enum OutputMode {
Mono = ctru_sys::NDSP_OUTPUT_MONO, Mono = ctru_sys::NDSP_OUTPUT_MONO,
@ -21,7 +22,7 @@ pub enum OutputMode {
Surround = ctru_sys::NDSP_OUTPUT_SURROUND, Surround = ctru_sys::NDSP_OUTPUT_SURROUND,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum AudioFormat { pub enum AudioFormat {
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8, PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
@ -30,7 +31,13 @@ pub enum AudioFormat {
PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16, PCM16Stereo = ctru_sys::NDSP_FORMAT_STEREO_PCM16,
} }
#[derive(Copy, Clone, Debug)] /// Representation of volume mix for a channel.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct AudioMix {
raw: [f32; 12],
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum InterpolationType { pub enum InterpolationType {
Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE, Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
@ -38,7 +45,7 @@ pub enum InterpolationType {
None = ctru_sys::NDSP_INTERP_NONE, None = ctru_sys::NDSP_INTERP_NONE,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NdspError { pub enum NdspError {
/// Channel ID /// Channel ID
InvalidChannel(u8), InvalidChannel(u8),
@ -47,7 +54,7 @@ pub enum NdspError {
/// Channel ID /// Channel ID
WaveBusy(u8), WaveBusy(u8),
/// Sample amount requested, Max sample amount /// Sample amount requested, Max sample amount
SampleCountOutOfBounds(u32, u32), SampleCountOutOfBounds(usize, usize),
} }
pub struct Channel<'ndsp> { pub struct Channel<'ndsp> {
@ -73,7 +80,7 @@ impl Ndsp {
/// ///
/// This function will return an error if an instance of the `Ndsp` struct already exists /// This function will return an error if an instance of the `Ndsp` struct already exists
/// or if there are any issues during initialization. /// or if there are any issues during initialization.
pub fn init() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&NDSP_ACTIVE, &NDSP_ACTIVE,
false, false,
@ -115,13 +122,13 @@ impl Ndsp {
/// Set the audio output mode. Defaults to `OutputMode::Stereo`. /// Set the audio output mode. Defaults to `OutputMode::Stereo`.
pub fn set_output_mode(&mut self, mode: OutputMode) { pub fn set_output_mode(&mut self, mode: OutputMode) {
unsafe { ctru_sys::ndspSetOutputMode(mode as u32) }; unsafe { ctru_sys::ndspSetOutputMode(mode.into()) };
} }
} }
impl Channel<'_> { impl Channel<'_> {
/// Reset the channel /// Reset the channel
pub fn reset(&self) { pub fn reset(&mut self) {
unsafe { ctru_sys::ndspChnReset(self.id.into()) }; unsafe { ctru_sys::ndspChnReset(self.id.into()) };
} }
@ -141,54 +148,45 @@ impl Channel<'_> {
} }
// Returns the channel's id // Returns the channel's id
pub fn get_id(&self) -> u8 { pub fn id(&self) -> u8 {
self.id self.id
} }
/// Returns the channel's current sample's position. /// Returns the index of the currently played sample.
pub fn get_sample_position(&self) -> u32 { ///
unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) } /// Because of how fast this value changes, it should only be used as a rough estimate of the current progress.
pub fn sample_position(&self) -> usize {
(unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize
} }
/// Returns the channel's current wave sequence's id. /// Returns the channel's current wave sequence's id.
pub fn get_wave_sequence_id(&self) -> u16 { pub fn wave_sequence_id(&self) -> u16 {
unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) } unsafe { ctru_sys::ndspChnGetWaveBufSeq(self.id.into()) }
} }
/// Pause or un-pause the channel's playback. /// Pause or un-pause the channel's playback.
pub fn set_paused(&self, state: bool) { pub fn set_paused(&mut self, state: bool) {
unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) }; unsafe { ctru_sys::ndspChnSetPaused(self.id.into(), state) };
} }
/// Set the channel's output format. /// Set the channel's output format.
/// Change this setting based on the used sample's format. /// Change this setting based on the used sample's format.
pub fn set_format(&self, format: AudioFormat) { pub fn set_format(&mut self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format as u16) }; unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) };
} }
/// Set the channel's interpolation mode. /// Set the channel's interpolation mode.
pub fn set_interpolation(&self, interp_type: InterpolationType) { pub fn set_interpolation(&mut self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type as u32) }; unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) };
} }
/// Set the channel's volume mix. /// Set the channel's volume mix.
/// pub fn set_mix(&mut self, mix: &AudioMix) {
/// # Notes unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) }
///
/// The buffer's format is read as:
///
/// Index 0: Front left volume <br>
/// Index 1: Front right volume <br>
/// Index 2: Back left volume <br>
/// Index 3: Back right volume <br>
/// Index 4..7: Same as 0..3 but for auxiliary output 0 <br>
/// Index 8..11: Same as 0..3 but for auxiliary output 1 <br>
pub fn set_mix(&self, mix: &[f32; 12]) {
unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_ptr().cast_mut()) }
} }
/// Set the channel's rate of sampling. /// Set the channel's rate of sampling.
pub fn set_sample_rate(&self, rate: f32) { pub fn set_sample_rate(&mut self, rate: f32) {
unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) }; unsafe { ctru_sys::ndspChnSetRate(self.id.into(), rate) };
} }
@ -197,7 +195,7 @@ impl Channel<'_> {
// We suggest using other wave formats when developing homebrew applications. // We suggest using other wave formats when developing homebrew applications.
/// Clear the wave buffer queue and stop playback. /// Clear the wave buffer queue and stop playback.
pub fn clear_queue(&self) { pub fn clear_queue(&mut self) {
unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) }; unsafe { ctru_sys::ndspChnWaveBufClear(self.id.into()) };
} }
@ -206,10 +204,10 @@ impl Channel<'_> {
/// ///
/// # Warning /// # Warning
/// ///
/// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) alive during playback. /// `libctru` expects the user to manually keep the info data (in this case [Wave]) alive during playback.
/// To ensure safety, checks within [WaveInfo] will clear the whole channel queue if any queued [WaveInfo] is dropped prematurely. /// To ensure safety, checks within [Wave] will clear the whole channel queue if any queued [Wave] is dropped prematurely.
pub fn queue_wave(&self, wave: &mut WaveInfo) -> std::result::Result<(), NdspError> { pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), NdspError> {
match wave.get_status() { match wave.status() {
WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)), WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)),
_ => (), _ => (),
} }
@ -227,7 +225,7 @@ impl Channel<'_> {
/// Refer to [libctru](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info. /// Refer to [libctru](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info.
impl Channel<'_> { impl Channel<'_> {
/// Enables/disables monopole filters. /// Enables/disables monopole filters.
pub fn iir_mono_set_enabled(&self, enable: bool) { pub fn iir_mono_set_enabled(&mut self, enable: bool) {
unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) }; unsafe { ctru_sys::ndspChnIirMonoSetEnable(self.id.into(), enable) };
} }
@ -236,7 +234,7 @@ impl Channel<'_> {
/// # Notes /// # Notes
/// ///
/// This is a lower quality filter than the Biquad alternative. /// This is a lower quality filter than the Biquad alternative.
pub fn iir_mono_set_params_high_pass_filter(&self, cut_off_freq: f32) { pub fn iir_mono_set_params_high_pass_filter(&mut self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) }; unsafe { ctru_sys::ndspChnIirMonoSetParamsHighPassFilter(self.id.into(), cut_off_freq) };
} }
@ -245,38 +243,38 @@ impl Channel<'_> {
/// # Notes /// # Notes
/// ///
/// This is a lower quality filter than the Biquad alternative. /// This is a lower quality filter than the Biquad alternative.
pub fn iir_mono_set_params_low_pass_filter(&self, cut_off_freq: f32) { pub fn iir_mono_set_params_low_pass_filter(&mut self, cut_off_freq: f32) {
unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) }; unsafe { ctru_sys::ndspChnIirMonoSetParamsLowPassFilter(self.id.into(), cut_off_freq) };
} }
/// Enables/disables biquad filters. /// Enables/disables biquad filters.
pub fn iir_biquad_set_enabled(&self, enable: bool) { pub fn iir_biquad_set_enabled(&mut self, enable: bool) {
unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) }; unsafe { ctru_sys::ndspChnIirBiquadSetEnable(self.id.into(), enable) };
} }
/// Sets the biquad to be a high pass filter. /// Sets the biquad to be a high pass filter.
pub fn iir_biquad_set_params_high_pass_filter(&self, cut_off_freq: f32, quality: f32) { pub fn iir_biquad_set_params_high_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality)
}; };
} }
/// Sets the biquad to be a low pass filter. /// Sets the biquad to be a low pass filter.
pub fn iir_biquad_set_params_low_pass_filter(&self, cut_off_freq: f32, quality: f32) { pub fn iir_biquad_set_params_low_pass_filter(&mut self, cut_off_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality)
}; };
} }
/// Sets the biquad to be a notch filter. /// Sets the biquad to be a notch filter.
pub fn iir_biquad_set_params_notch_filter(&self, notch_freq: f32, quality: f32) { pub fn iir_biquad_set_params_notch_filter(&mut self, notch_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality)
}; };
} }
/// Sets the biquad to be a band pass filter. /// Sets the biquad to be a band pass filter.
pub fn iir_biquad_set_params_band_pass_filter(&self, mid_freq: f32, quality: f32) { pub fn iir_biquad_set_params_band_pass_filter(&mut self, mid_freq: f32, quality: f32) {
unsafe { unsafe {
ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality) ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality)
}; };
@ -284,7 +282,7 @@ impl Channel<'_> {
/// Sets the biquad to be a peaking equalizer. /// Sets the biquad to be a peaking equalizer.
pub fn iir_biquad_set_params_peaking_equalizer( pub fn iir_biquad_set_params_peaking_equalizer(
&self, &mut self,
central_freq: f32, central_freq: f32,
quality: f32, quality: f32,
gain: f32, gain: f32,
@ -302,41 +300,161 @@ impl Channel<'_> {
impl AudioFormat { impl AudioFormat {
/// Returns the amount of bytes needed to store one sample /// Returns the amount of bytes needed to store one sample
///
/// Eg. /// Eg.
/// 8 bit formats return 1 (byte) /// 8 bit mono formats return 1 (byte)
/// 16 bit formats return 2 (bytes) /// 16 bit stereo (dual-channel) formats return 4 (bytes)
pub fn sample_size(self) -> u8 { pub const fn size(self) -> usize {
match self { match self {
AudioFormat::PCM8Mono => 1, Self::PCM8Mono => 1,
AudioFormat::PCM16Mono | AudioFormat::PCM8Stereo => 2, Self::PCM16Mono | Self::PCM8Stereo => 2,
AudioFormat::PCM16Stereo => 4, Self::PCM16Stereo => 4,
} }
} }
} }
impl AudioMix {
/// Creates a new [AudioMix] with all volumes set to 0.
pub fn zeroed() -> Self {
Self { raw: [0.; 12] }
}
/// Returns a reference to the raw data.
pub fn as_raw(&self) -> &[f32; 12] {
&self.raw
}
/// Returns a mutable reference to the raw data.
pub fn as_raw_mut(&mut self) -> &mut [f32; 12] {
&mut self.raw
}
/// Returns the values set for the "front" volume mix (left and right channel).
pub fn front(&self) -> (f32, f32) {
(self.raw[0], self.raw[1])
}
/// Returns the values set for the "back" volume mix (left and right channel).
pub fn back(&self) -> (f32, f32) {
(self.raw[2], self.raw[3])
}
/// Returns the values set for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
pub fn aux_front(&self, id: usize) -> (f32, f32) {
if id > 1 {
panic!("invalid auxiliary output device index")
}
let index = 4 + id * 4;
(self.raw[index], self.raw[index + 1])
}
/// Returns the values set for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1).
pub fn aux_back(&self, id: usize) -> (f32, f32) {
if id > 1 {
panic!("invalid auxiliary output device index")
}
let index = 6 + id * 4;
(self.raw[index], self.raw[index + 1])
}
/// Sets 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.
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).
///
/// # Notes
///
/// [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).
///
/// # Notes
///
/// [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")
}
let index = 4 + id * 4;
self.raw[index] = left;
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).
///
/// # Notes
///
/// [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")
}
let index = 6 + id * 4;
self.raw[index] = left;
self.raw[index + 1] = right;
}
}
/// 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 {
fn default() -> Self {
let mut mix = AudioMix::zeroed();
mix.set_front(1.0, 1.0);
mix
}
}
impl From<[f32; 12]> for AudioMix {
fn from(value: [f32; 12]) -> Self {
Self { raw: value }
}
}
impl fmt::Display for NdspError { impl fmt::Display for NdspError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
Self::InvalidChannel(id) => write!(f, "Audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23."), Self::InvalidChannel(id) => write!(f, "audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23"),
Self::ChannelAlreadyInUse(id) => write!(f, "Audio Channel with ID {id} is already being used. Drop the other instance if you want to use it here."), Self::ChannelAlreadyInUse(id) => write!(f, "audio Channel with ID {id} is already being used. Drop the other instance if you want to use it here"),
Self::WaveBusy(id) => write!(f, "The selected WaveInfo is busy playing on channel {id}."), Self::WaveBusy(id) => write!(f, "the selected Wave is busy playing on channel {id}"),
Self::SampleCountOutOfBounds(samples_requested, max_samples) => write!(f, "The sample count requested is too big. Requested amount was {samples_requested} while the maximum sample count is {max_samples}."), Self::SampleCountOutOfBounds(samples_requested, max_samples) => write!(f, "the sample count requested is too big (requested = {samples_requested}, maximum = {max_samples})"),
} }
} }
} }
impl error::Error for NdspError {} impl error::Error for NdspError {}
impl<'ndsp> Drop for Channel<'ndsp> {
fn drop(&mut self) {
self.reset();
}
}
impl Drop for Ndsp { impl Drop for Ndsp {
fn drop(&mut self) { fn drop(&mut self) {
for i in 0..NUMBER_OF_CHANNELS { for i in 0..NUMBER_OF_CHANNELS {
self.channel(i).unwrap().clear_queue(); self.channel(i).unwrap().reset();
} }
} }
} }
from_impl!(InterpolationType, ctru_sys::ndspInterpType);
from_impl!(OutputMode, ctru_sys::ndspOutputMode);
from_impl!(AudioFormat, u16);

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

@ -1,8 +1,8 @@
use super::{AudioFormat, NdspError}; use super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator; use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf] /// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf].
pub struct WaveInfo { pub struct Wave {
/// Data block of the audio wave (and its format information). /// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat, audio_format: AudioFormat,
@ -11,9 +11,9 @@ pub struct WaveInfo {
played_on_channel: Option<u8>, played_on_channel: Option<u8>,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
/// Enum representing the playback status of a [WaveInfo]. /// Enum representing the playback status of a [Wave].
pub enum WaveStatus { pub enum WaveStatus {
Free = ctru_sys::NDSP_WBUF_FREE as u8, Free = ctru_sys::NDSP_WBUF_FREE as u8,
Queued = ctru_sys::NDSP_WBUF_QUEUED as u8, Queued = ctru_sys::NDSP_WBUF_QUEUED as u8,
@ -21,14 +21,14 @@ pub enum WaveStatus {
Done = ctru_sys::NDSP_WBUF_DONE as u8, Done = ctru_sys::NDSP_WBUF_DONE as u8,
} }
impl WaveInfo { 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 and a some info.
pub fn new( pub fn new(
buffer: Box<[u8], LinearAllocator>, buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat, audio_format: AudioFormat,
looping: bool, looping: bool,
) -> Self { ) -> Self {
let sample_count: usize = buffer.len() / (audio_format.sample_size() as usize); let sample_count = buffer.len() / audio_format.size();
// Signal to the DSP processor the buffer's RAM sector. // Signal to the DSP processor the buffer's RAM sector.
// This step may seem delicate, but testing reports failure most of the time, while still having no repercussions on the resulting audio. // This step may seem delicate, but testing reports failure most of the time, while still having no repercussions on the resulting audio.
@ -69,10 +69,10 @@ impl WaveInfo {
/// ///
/// # Errors /// # Errors
/// ///
/// This function will return an error if the [WaveInfo] is currently busy, /// This function will return an error if the [Wave] is currently busy,
/// with the id to the channel in which it's queued. /// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> {
match self.get_status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { WaveStatus::Playing | WaveStatus::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
} }
@ -81,7 +81,7 @@ impl WaveInfo {
} }
/// Return this wave's playback status. /// Return this wave's playback status.
pub fn get_status(&self) -> WaveStatus { pub fn status(&self) -> WaveStatus {
self.raw_data.status.try_into().unwrap() self.raw_data.status.try_into().unwrap()
} }
@ -90,12 +90,12 @@ impl WaveInfo {
/// # Notes /// # Notes
/// ///
/// This value varies depending on [Self::set_sample_count]. /// This value varies depending on [Self::set_sample_count].
pub fn get_sample_count(&self) -> u32 { pub fn sample_count(&self) -> usize {
self.raw_data.nsamples self.raw_data.nsamples as usize
} }
/// Get the format of the audio data. /// Get the format of the audio data.
pub fn get_format(&self) -> AudioFormat { pub fn format(&self) -> AudioFormat {
self.audio_format self.audio_format
} }
@ -117,25 +117,22 @@ impl WaveInfo {
/// # Errors /// # Errors
/// ///
/// This function will return an error if the sample size exceeds the buffer's capacity /// This function will return an error if the sample size exceeds the buffer's capacity
/// or if the WaveInfo is currently queued. /// or if the [Wave] is currently queued.
pub fn set_sample_count(&mut self, sample_count: u32) -> Result<(), NdspError> { pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> {
match self.get_status() { match self.status() {
WaveStatus::Playing | WaveStatus::Queued => { WaveStatus::Playing | WaveStatus::Queued => {
return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); return Err(NdspError::WaveBusy(self.played_on_channel.unwrap()));
} }
_ => (), _ => (),
} }
let max_count: usize = self.buffer.len() / (self.audio_format.sample_size() as usize); let max_count = self.buffer.len() / self.audio_format.size();
if sample_count > max_count as u32 { if sample_count > max_count {
return Err(NdspError::SampleCountOutOfBounds( return Err(NdspError::SampleCountOutOfBounds(sample_count, max_count));
sample_count,
max_count as u32,
));
} }
self.raw_data.nsamples = sample_count; self.raw_data.nsamples = sample_count as u32;
Ok(()) Ok(())
} }
@ -150,16 +147,16 @@ impl TryFrom<u8> for WaveStatus {
1 => Ok(Self::Queued), 1 => Ok(Self::Queued),
2 => Ok(Self::Playing), 2 => Ok(Self::Playing),
3 => Ok(Self::Done), 3 => Ok(Self::Done),
_ => Err("Invalid WaveInfo Status code"), _ => Err("Invalid Wave Status code"),
} }
} }
} }
impl Drop for WaveInfo { impl Drop for Wave {
fn drop(&mut self) { fn drop(&mut self) {
// This was the only way I found I could check for improper drops of `WaveInfos`. // This was the only way I found I could check for improper drops of `Wave`.
// A panic was considered, but it would cause issues with drop order against `Ndsp`. // A panic was considered, but it would cause issues with drop order against `Ndsp`.
match self.get_status() { match self.status() {
WaveStatus::Free | WaveStatus::Done => (), WaveStatus::Free | WaveStatus::Done => (),
// If the status flag is "unfinished" // If the status flag is "unfinished"
_ => { _ => {

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

@ -1,56 +1,80 @@
//! Process Services (PS) module. This is used for miscellaneous utility tasks, but //! 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 particularly important because it is used to generate random data, which
//! is required for common things like [`HashMap`](std::collections::HashMap). //! is required for common things like [`HashMap`](std::collections::HashMap).
//! As such, it is initialized by default in `ctru::init` instead of having a safety handler
//! See also <https://www.3dbrew.org/wiki/Process_Services> //! See also <https://www.3dbrew.org/wiki/Process_Services>
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum AESAlgorithm { pub enum AESAlgorithm {
CbcEnc, CbcEnc = ctru_sys::PS_ALGORITHM_CBC_ENC,
CbcDec, CbcDec = ctru_sys::PS_ALGORITHM_CBC_DEC,
CtrEnc, CtrEnc = ctru_sys::PS_ALGORITHM_CTR_ENC,
CtrDec, CtrDec = ctru_sys::PS_ALGORITHM_CTR_DEC,
CcmEnc, CcmEnc = ctru_sys::PS_ALGORITHM_CCM_ENC,
CcmDec, CcmDec = ctru_sys::PS_ALGORITHM_CCM_DEC,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)] #[repr(u32)]
pub enum AESKeyType { pub enum AESKeyType {
Keyslot0D, Keyslot0D = ctru_sys::PS_KEYSLOT_0D,
Keyslot2D, Keyslot2D = ctru_sys::PS_KEYSLOT_2D,
Keyslot31, Keyslot2E = ctru_sys::PS_KEYSLOT_2E,
Keyslot38, Keyslot31 = ctru_sys::PS_KEYSLOT_31,
Keyslot32, Keyslot32 = ctru_sys::PS_KEYSLOT_32,
Keyslot39Dlp, Keyslot36 = ctru_sys::PS_KEYSLOT_36,
Keyslot2E, Keyslot38 = ctru_sys::PS_KEYSLOT_38,
KeyslotInvalid, Keyslot39Dlp = ctru_sys::PS_KEYSLOT_39_DLP,
Keyslot36, Keyslot39Nfc = ctru_sys::PS_KEYSLOT_39_NFC,
Keyslot39Nfc, KeyslotInvalid = ctru_sys::PS_KEYSLOT_INVALID,
} }
pub fn local_friend_code_seed() -> crate::Result<u64> { pub struct Ps(());
impl Ps {
pub fn new() -> Result<Self> {
unsafe {
ResultCode(ctru_sys::psInit())?;
Ok(Ps(()))
}
}
pub fn local_friend_code_seed(&self) -> crate::Result<u64> {
let mut seed: u64 = 0; let mut seed: u64 = 0;
ResultCode(unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) })?; ResultCode(unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) })?;
Ok(seed) Ok(seed)
} }
pub fn device_id() -> crate::Result<u32> { pub fn device_id(&self) -> crate::Result<u32> {
let mut id: u32 = 0; let mut id: u32 = 0;
ResultCode(unsafe { ctru_sys::PS_GetDeviceId(&mut id) })?; ResultCode(unsafe { ctru_sys::PS_GetDeviceId(&mut id) })?;
Ok(id) Ok(id)
} }
pub fn generate_random_bytes(out: &mut [u8]) -> crate::Result<()> { pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> {
ResultCode(unsafe { ResultCode(unsafe {
ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32) ctru_sys::PS_GenerateRandomBytes(out.as_mut_ptr().cast(), out.len())
})?; })?;
Ok(()) Ok(())
}
} }
impl Drop for Ps {
fn drop(&mut self) {
unsafe {
ctru_sys::psExit();
}
}
}
from_impl!(AESAlgorithm, ctru_sys::PS_AESAlgorithm);
from_impl!(AESKeyType, ctru_sys::PS_AESKeyType);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::HashMap; use std::collections::HashMap;

6
ctru-rs/src/romfs.rs → ctru-rs/src/services/romfs.rs

@ -1,3 +1,5 @@
//! Read-Only Memory FileSystem
//!
//! This module only gets compiled if the configured RomFS directory is found and the `romfs` //! This module only gets compiled if the configured RomFS directory is found and the `romfs`
//! feature is enabled. //! feature is enabled.
//! //!
@ -23,7 +25,7 @@ pub struct RomFS {
static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0); static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0);
impl RomFS { impl RomFS {
pub fn init() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&ROMFS_ACTIVE, &ROMFS_ACTIVE,
true, true,
@ -48,7 +50,7 @@ mod tests {
#[test] #[test]
fn romfs_counter() { fn romfs_counter() {
let _romfs = RomFS::init().unwrap(); let _romfs = RomFS::new().unwrap();
let value = *ROMFS_ACTIVE.lock().unwrap(); let value = *ROMFS_ACTIVE.lock().unwrap();
assert_eq!(value, 1); assert_eq!(value, 1);

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

@ -1,3 +1,5 @@
//! Network Socket
use libc::memalign; use libc::memalign;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::Mutex; use std::sync::Mutex;
@ -6,8 +8,10 @@ use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use crate::Error; use crate::Error;
/// Soc service. Initializing this service will enable the use of network sockets and utilities /// Network socket service
/// such as those found in `std::net`. The service will be closed when this struct is is dropped. ///
/// Initializing this service will enable the use of network sockets and utilities
/// such as those found in `std::net`. The service will close once this struct gets dropped.
pub struct Soc { pub struct Soc {
_service_handler: ServiceReference, _service_handler: ServiceReference,
sock_3dslink: libc::c_int, sock_3dslink: libc::c_int,
@ -21,7 +25,7 @@ impl Soc {
/// # Errors /// # Errors
/// ///
/// This function will return an error if the `Soc` service is already initialized /// This function will return an error if the `Soc` service is already initialized
pub fn init() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
Self::init_with_buffer_size(0x100000) Self::init_with_buffer_size(0x100000)
} }
@ -103,8 +107,8 @@ mod tests {
#[test] #[test]
fn soc_duplicate() { fn soc_duplicate() {
let _soc = Soc::init().unwrap(); let _soc = Soc::new().unwrap();
assert!(matches!(Soc::init(), Err(Error::ServiceAlreadyActive))) assert!(matches!(Soc::new(), Err(Error::ServiceAlreadyActive)))
} }
} }

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

@ -1,3 +1,5 @@
//! SSLC (TLS) service
// TODO: Implement remaining functions // TODO: Implement remaining functions
use crate::error::ResultCode; use crate::error::ResultCode;
@ -5,24 +7,13 @@ use crate::error::ResultCode;
pub struct SslC(()); pub struct SslC(());
impl SslC { impl SslC {
/// Initialize sslc /// Initialize the service
pub fn init() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
unsafe { unsafe {
ResultCode(ctru_sys::sslcInit(0))?; ResultCode(ctru_sys::sslcInit(0))?;
Ok(SslC(())) Ok(SslC(()))
} }
} }
/// Fill `buf` with `buf.len()` random bytes
pub fn generate_random_data(&self, buf: &mut [u8]) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::sslcGenerateRandomData(
buf.as_ptr() as _,
buf.len() as u32,
))?;
Ok(())
}
}
} }
impl Drop for SslC { impl Drop for SslC {

41
ctru-rs/src/test_runner.rs

@ -6,24 +6,19 @@ use std::io;
use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts}; use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts};
use crate::console::Console; use crate::prelude::*;
use crate::gfx::Gfx;
use crate::services::hid::{Hid, KeyPad};
use crate::services::Apt;
/// A custom runner to be used with `#[test_runner]`. This simple implementation /// A custom runner to be used with `#[test_runner]`. This simple implementation
/// runs all tests in series, "failing" on the first one to panic (really, the /// runs all tests in series, "failing" on the first one to panic (really, the
/// panic is just treated the same as any normal application panic). /// panic is just treated the same as any normal application panic).
pub(crate) fn run(tests: &[&TestDescAndFn]) { pub(crate) fn run(tests: &[&TestDescAndFn]) {
crate::init(); let gfx = Gfx::new().unwrap();
let mut hid = Hid::new().unwrap();
let gfx = Gfx::init().unwrap(); let apt = Apt::new().unwrap();
let hid = Hid::init().unwrap();
let apt = Apt::init().unwrap();
let mut top_screen = gfx.top_screen.borrow_mut(); let mut top_screen = gfx.top_screen.borrow_mut();
top_screen.set_wide_mode(true); top_screen.set_wide_mode(true);
let _console = Console::init(top_screen); let _console = Console::new(top_screen);
let opts = TestOpts { let opts = TestOpts {
force_run_in_process: true, force_run_in_process: true,
@ -44,12 +39,10 @@ pub(crate) fn run(tests: &[&TestDescAndFn]) {
println!("Press START to exit."); println!("Press START to exit.");
while apt.main_loop() { while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank(); gfx.wait_for_vblank();
hid.scan_input(); hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) { if hid.keys_down().contains(KeyPad::START) {
break; break;
} }
} }
@ -78,25 +71,3 @@ fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
_ => panic!("non-static tests passed to test::test_main_static"), _ => panic!("non-static tests passed to test::test_main_static"),
} }
} }
/// The following functions are stubs needed to link the test library,
/// but do nothing because we don't actually need them for the runner to work.
mod link_fix {
#[no_mangle]
extern "C" fn execvp(
_argc: *const libc::c_char,
_argv: *mut *const libc::c_char,
) -> libc::c_int {
-1
}
#[no_mangle]
extern "C" fn pipe(_fildes: *mut libc::c_int) -> libc::c_int {
-1
}
#[no_mangle]
extern "C" fn sigemptyset(_arg1: *mut libc::sigset_t) -> ::libc::c_int {
-1
}
}

9
ctru-sys/Cargo.toml

@ -1,10 +1,13 @@
[package] [package]
name = "ctru-sys" name = "ctru-sys"
version = "0.4.1" version = "21.2.0+2.1.2-1"
authors = ["Ronald Kinard <furyhunter600@gmail.com>"] authors = [ "Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>" ]
license = "https://en.wikipedia.org/wiki/Zlib_License" license = "Zlib"
links = "ctru" links = "ctru"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
libc = { version = "0.2.121", default-features = false } libc = { version = "0.2.121", default-features = false }
[build-dependencies]
which = "4.4.0"

39
ctru-sys/bindgen.sh

@ -1,7 +1,26 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euxo pipefail set -euo pipefail
echo "Determining libctru version..."
pacman=dkp-pacman
if ! command -v $pacman &>/dev/null; then
pacman=pacman
if ! command -v $pacman &>/dev/null; then
echo >&2 "ERROR: Unable to automatically determine libctru version!"
exit 1
fi
fi
LIBCTRU_VERSION="$($pacman -Qi libctru | grep Version | cut -d: -f 2 | tr -d ' ')"
CTRU_SYS_VERSION="$(
printf '%s' "$LIBCTRU_VERSION" |
cut -d- -f1 |
sed -E 's/^([0-9]+)\.([0-9.]+)$/\1\2/'
)"
echo "Generating bindings.rs..."
bindgen "$DEVKITPRO/libctru/include/3ds.h" \ bindgen "$DEVKITPRO/libctru/include/3ds.h" \
--rust-target nightly \ --rust-target nightly \
--use-core \ --use-core \
@ -11,16 +30,16 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \
--ctypes-prefix "::libc" \ --ctypes-prefix "::libc" \
--no-prepend-enum-name \ --no-prepend-enum-name \
--generate "functions,types,vars" \ --generate "functions,types,vars" \
--blacklist-type "u(8|16|32|64)" \ --blocklist-type "u(8|16|32|64)" \
--blacklist-type "__builtin_va_list" \ --blocklist-type "__builtin_va_list" \
--blacklist-type "__va_list" \ --blocklist-type "__va_list" \
--opaque-type "MiiData" \ --opaque-type "MiiData" \
--with-derive-default \ --with-derive-default \
-- \ -- \
--target=arm-none-eabi \ --target=arm-none-eabi \
--sysroot=$DEVKITARM/arm-none-eabi \ --sysroot="$DEVKITARM/arm-none-eabi" \
-isystem$DEVKITARM/arm-none-eabi/include \ -isystem"$DEVKITARM/arm-none-eabi/include" \
-I$DEVKITPRO/libctru/include \ -I"$DEVKITPRO/libctru/include" \
-mfloat-abi=hard \ -mfloat-abi=hard \
-march=armv6k \ -march=armv6k \
-mtune=mpcore \ -mtune=mpcore \
@ -29,6 +48,10 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \
-D__3DS__ \ -D__3DS__ \
> src/bindings.rs > src/bindings.rs
cargo run --package docstring-to-rustdoc -- src/bindings.rs echo "Updating docstrings in bindings.rs..."
cargo run --quiet --package docstring-to-rustdoc -- src/bindings.rs
echo "Formatting generated files..."
cargo fmt --all cargo fmt --all
echo "Generated bindings for ctru-sys version \"${CTRU_SYS_VERSION}.x+${LIBCTRU_VERSION}\""

71
ctru-sys/build.rs

@ -1,4 +1,6 @@
use std::env; use std::env;
use std::error::Error;
use std::process::{Command, Output, Stdio};
fn main() { fn main() {
let dkp_path = env::var("DEVKITPRO").unwrap(); let dkp_path = env::var("DEVKITPRO").unwrap();
@ -14,4 +16,73 @@ fn main() {
_ => "ctru", _ => "ctru",
} }
); );
match check_libctru_version() {
Ok((maj, min, patch)) => {
eprintln!("using libctru version {maj}.{min}.{patch}");
// These are accessible by the crate during build with `env!()`.
// We might consider exporting some public constants or something.
println!("cargo:rustc-env=LIBCTRU_VERSION={maj}.{min}.{patch}");
println!("cargo:rustc-env=LIBCTRU_MAJOR={maj}");
println!("cargo:rustc-env=LIBCTRU_MINOR={min}");
println!("cargo:rustc-env=LIBCTRU_PATCH={patch}");
}
Err(err) => println!("cargo:warning=failed to check libctru version: {err}"),
}
}
fn parse_version(version: &str) -> Result<(String, String, String), &str> {
let versions: Vec<_> = version
.split(|c| c == '.' || c == '-')
.map(String::from)
.collect();
match &versions[..] {
[major, minor, patch, _build] => Ok((major.clone(), minor.clone(), patch.clone())),
_ => Err("unexpected number of version segments"),
}
}
fn check_libctru_version() -> Result<(String, String, String), Box<dyn Error>> {
let pacman = which::which("dkp-pacman").or_else(|_| which::which("pacman"))?;
let Output { stdout, .. } = Command::new(&pacman)
.args(["--query", "libctru"])
.stderr(Stdio::inherit())
.output()?;
let output_str = String::from_utf8_lossy(&stdout);
let (_pkg, lib_version) = output_str
.split_once(char::is_whitespace)
.ok_or("unexpected pacman output format")?;
let lib_version = lib_version.trim();
let cargo_pkg_version = env::var("CARGO_PKG_VERSION").unwrap();
let (_, crate_built_version) = cargo_pkg_version
.split_once('+')
.expect("crate version should have '+' delimeter");
if lib_version != crate_built_version {
return Err(format!(
"libctru version is {lib_version} but this crate was built for {crate_built_version}"
))?;
}
let Output { stdout, .. } = Command::new(pacman)
.args(["--query", "--list", "libctru"])
.stderr(Stdio::inherit())
.output()?;
for line in String::from_utf8_lossy(&stdout).split('\n') {
let Some((_pkg, file)) = line.split_once(char::is_whitespace)
else { continue };
println!("cargo:rerun-if-changed={file}");
}
let (lib_major, lib_minor, lib_patch) = parse_version(lib_version)?;
Ok((lib_major, lib_minor, lib_patch))
} }

2
ctru-sys/docstring-to-rustdoc/Cargo.toml

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
doxygen-rs = { git = "https://github.com/Techie-Pi/doxygen-rs.git", version = "0.2.2", rev = "573f483ad63ab4662650961998d3ed093a703983" } doxygen-rs = "0.3.1"

1
ctru-sys/src/.gitattributes vendored

@ -0,0 +1 @@
bindings.rs linguist-generated=true

9755
ctru-sys/src/bindings.rs generated

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save