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. 114
      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. 86
      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. 9943
      ctru-sys/src/bindings.rs

1
.github/CODEOWNERS

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

46
.github/actions/setup/action.yml

@ -0,0 +1,46 @@ @@ -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: @@ -12,6 +12,8 @@ on:
env:
# https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html
CARGO_UNSTABLE_SPARSE_REGISTRY: "true"
# actions-rust-lang/setup-rust-toolchain sets some default RUSTFLAGS
RUSTFLAGS: ""
jobs:
lint:
@ -22,41 +24,24 @@ jobs: @@ -22,41 +24,24 @@ jobs:
- nightly-2023-01-13
# Check for breakage on latest nightly
- nightly
# But if latest nightly fails, allow the workflow to continue
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
runs-on: ubuntu-latest
container: devkitpro/devkitarm
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
uses: actions/checkout@v2
- name: Setup default Rust toolchain
uses: actions-rs/toolchain@v1
- uses: ./.github/actions/setup
with:
components: clippy, rustfmt, rust-src
profile: minimal
toolchain: ${{ matrix.toolchain }}
default: true
- name: Install build tools for host
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 7b70b6b26c4740b9a10ab85b832ee73c41142bbb
- name: Hide duplicate warnings from lint job
if: ${{ matrix.toolchain == 'nightly' }}
run: |
echo "::remove-matcher owner=clippy::"
echo "::remove-matcher owner=rustfmt::"
- name: Check formatting
run: cargo fmt --all --verbose -- --check
@ -68,5 +53,28 @@ jobs: @@ -68,5 +53,28 @@ jobs:
# feature, but https://github.com/actions/runner/issues/2341 means we
# 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.
# and run it somehow, but exactly how remains to be seen.

4
AUTHORS.md

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

21
LICENSE

@ -0,0 +1,21 @@ @@ -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 @@ -5,35 +5,29 @@ A Rust wrapper library for smealum's [ctrulib](https://github.com/smealum/ctruli
## Structure
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
applies to every file in the tree, unless otherwise noted.
* `libctru` version `X.Y.Z-W`
* `ctru-sys` version `XY.Z.P+X.Y.Z-W`
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.
where `P` is usually 0 but may be incremented for fixes in e.g.
binding generation, `libc` dependency bump, etc.
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:
It may be possible to build this crate against a different version of `libctru`,
but you may encounter linker errors or ABI issues. A build-time Cargo warning
(displayed when built with `-vv`) will be issued if the build script detects
a mismatch or is unable to check the installed `libctru` version.
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.
## Original version
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 @@ @@ -1,7 +1,7 @@
[package]
authors = ["Ronald Kinard <furyhunter600@gmail.com>"]
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "A safe wrapper around smealum's ctrulib."
license = "https://en.wikipedia.org/wiki/Zlib_License"
license = "Zlib"
name = "ctru-rs"
version = "0.7.1"
edition = "2021"
@ -13,9 +13,9 @@ name = "ctru" @@ -13,9 +13,9 @@ name = "ctru"
[dependencies]
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"
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" }
libc = "0.2.121"
bitflags = "1.0.0"

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

@ -5,13 +5,13 @@ use std::f32::consts::PI; @@ -5,13 +5,13 @@ use std::f32::consts::PI;
use ctru::linear::LinearAllocator;
use ctru::prelude::*;
use ctru::services::ndsp::{
wave::{WaveInfo, WaveStatus},
AudioFormat, InterpolationType, Ndsp, OutputMode,
wave::{Wave, WaveStatus},
AudioFormat, AudioMix, InterpolationType, Ndsp, OutputMode,
};
const SAMPLE_RATE: usize = 22050;
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;
// Note Frequencies
@ -35,11 +35,12 @@ fn fill_buffer(audio_data: &mut [u8], frequency: f32) { @@ -35,11 +35,12 @@ fn fill_buffer(audio_data: &mut [u8], frequency: f32) {
}
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
let mut note: usize = 4;
@ -64,24 +65,21 @@ fn main() { @@ -64,24 +65,21 @@ fn main() {
let audio_data2 = audio_data1.clone();
let mut wave_info1 = WaveInfo::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = WaveInfo::new(audio_data2, AudioFormat::PCM16Stereo, false);
let mut wave_info1 = Wave::new(audio_data1, AudioFormat::PCM16Stereo, false);
let mut wave_info2 = Wave::new(audio_data2, AudioFormat::PCM16Stereo, false);
let mut 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`
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_sample_rate(SAMPLE_RATE as f32);
channel_zero.set_format(AudioFormat::PCM16Stereo);
// Output at 100% on the first pair of left and right channels.
let mut mix: [f32; 12] = [0f32; 12];
mix[0] = 1.0;
mix[1] = 1.0;
let mix = AudioMix::default();
channel_zero.set_mix(&mix);
channel_zero.queue_wave(&mut wave_info1).unwrap();
@ -101,23 +99,23 @@ fn main() { @@ -101,23 +99,23 @@ fn main() {
hid.scan_input();
let keys_down = hid.keys_down();
if keys_down.contains(KeyPad::KEY_START) {
if keys_down.contains(KeyPad::START) {
break;
} // 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);
} else if keys_down.intersects(KeyPad::KEY_UP) {
} else if keys_down.intersects(KeyPad::UP) {
note = std::cmp::min(note + 1, NOTEFREQ.len() - 1);
}
let mut update_params = false;
if keys_down.intersects(KeyPad::KEY_LEFT) {
if keys_down.intersects(KeyPad::LEFT) {
filter -= 1;
filter = filter.rem_euclid(filter_names.len() as _);
update_params = true;
} else if keys_down.intersects(KeyPad::KEY_RIGHT) {
} else if keys_down.intersects(KeyPad::RIGHT) {
filter += 1;
filter = filter.rem_euclid(filter_names.len() as _);
@ -141,13 +139,13 @@ fn main() { @@ -141,13 +139,13 @@ fn main() {
}
}
let current: &mut WaveInfo = if altern {
let current: &mut Wave = if altern {
&mut wave_info1
} else {
&mut wave_info2
};
let status = current.get_status();
let status = current.status();
if let WaveStatus::Done = status {
fill_buffer(current.get_buffer_mut().unwrap(), NOTEFREQ[note]);
@ -156,10 +154,6 @@ fn main() { @@ -156,10 +154,6 @@ fn main() {
altern = !altern;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}

25
ctru-rs/examples/buttons.rs

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

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

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

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

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

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

@ -13,11 +13,12 @@ use futures::StreamExt; @@ -13,11 +13,12 @@ use futures::StreamExt;
use std::os::horizon::thread::BuilderExt;
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
// Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30)
@ -67,8 +68,6 @@ fn main() { @@ -67,8 +68,6 @@ fn main() {
frame_count = 0;
}
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
}
}

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

@ -6,11 +6,12 @@ use std::os::horizon::thread::BuilderExt; @@ -6,11 +6,12 @@ use std::os::horizon::thread::BuilderExt;
use std::time::Duration;
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
// Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30)
@ -62,8 +63,6 @@ fn main() { @@ -62,8 +63,6 @@ fn main() {
break;
}
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
use ctru::gfx::{Screen, Side, TopScreen3D};
use ctru::prelude::*;
use ctru::services::gfx::{Flush, Screen, Side, Swap, TopScreen3D};
/// See `graphics-bitmap.rs` for details on how the image is generated.
///
@ -10,18 +10,18 @@ const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb"); @@ -10,18 +10,18 @@ const IMAGE: &[u8] = include_bytes!("assets/ferris.rgb");
static ZERO: &[u8] = &[0; IMAGE.len()];
fn main() {
ctru::init();
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 _console = Console::init(gfx.bottom_screen.borrow_mut());
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.bottom_screen.borrow_mut());
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);
let top_screen = TopScreen3D::from(&gfx.top_screen);
let (mut left, mut right) = top_screen.split_mut();
let mut top_screen = TopScreen3D::from(&gfx.top_screen);
let mut current_side = Side::Left;
@ -30,12 +30,14 @@ fn main() { @@ -30,12 +30,14 @@ fn main() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
let left_buf = left.get_raw_framebuffer();
let right_buf = right.get_raw_framebuffer();
let (mut left, mut right) = top_screen.split_mut();
let left_buf = left.raw_framebuffer();
let right_buf = right.raw_framebuffer();
// Clear both buffers every time, in case the user switches sides this loop
unsafe {
@ -43,7 +45,7 @@ fn main() { @@ -43,7 +45,7 @@ fn main() {
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
current_side = match current_side {
Side::Left => Side::Right,
@ -60,9 +62,10 @@ fn main() { @@ -60,9 +62,10 @@ fn main() {
buf.copy_from(IMAGE.as_ptr(), IMAGE.len());
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
drop((left, right));
top_screen.flush_buffers();
top_screen.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();

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

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

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

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

17
ctru-rs/examples/hashmaps.rs

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

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

@ -1,18 +1,18 @@ @@ -1,18 +1,18 @@
use ctru::prelude::*;
fn main() {
// Initialize services
ctru::init();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap();
let gfx = Gfx::init().unwrap();
ctru::use_panic_handler();
let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap();
// 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.
// 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
top_screen.select();
@ -27,12 +27,10 @@ fn main() { @@ -27,12 +27,10 @@ fn main() {
println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
}

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

@ -3,11 +3,12 @@ use ctru::prelude::*; @@ -3,11 +3,12 @@ use ctru::prelude::*;
use std::io::BufWriter;
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
let out = b"Hello fellow Rustaceans, I'm on the Nintendo 3DS!";
let width = 24;
@ -25,12 +26,9 @@ fn main() { @@ -25,12 +26,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();

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

@ -4,11 +4,12 @@ use ctru::linear::LinearAllocator; @@ -4,11 +4,12 @@ use ctru::linear::LinearAllocator;
use ctru::prelude::*;
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
let linear_space_before = LinearAllocator::free_space();
@ -33,12 +34,9 @@ fn main() { @@ -33,12 +34,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();

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

@ -2,14 +2,14 @@ use ctru::applets::mii_selector::MiiSelector; @@ -2,14 +2,14 @@ use ctru::applets::mii_selector::MiiSelector;
use ctru::prelude::*;
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
let mut mii_selector = MiiSelector::init();
let mut mii_selector = MiiSelector::new();
mii_selector.set_initial_index(3);
mii_selector.blacklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!");
@ -30,12 +30,9 @@ fn main() { @@ -30,12 +30,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();

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

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

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

@ -11,12 +11,13 @@ @@ -11,12 +11,13 @@
use ctru::prelude::*;
fn main() {
ctru::init();
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");
ctru::use_panic_handler();
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)
.expect("unable to redirect stdout/err to 3dslink server");
@ -29,12 +30,9 @@ fn main() { @@ -29,12 +30,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();

18
ctru-rs/examples/romfs.rs

@ -1,18 +1,19 @@ @@ -1,18 +1,19 @@
use ctru::prelude::*;
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
cfg_if::cfg_if! {
// Run this code if RomFS are wanted and available
// This never fails as `ctru-rs` examples inherit all of the `ctru` features,
// but it might if a normal user application wasn't setup correctly
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();
println!("Contents of test-file.txt: \n{f}\n");
@ -32,12 +33,9 @@ fn main() { @@ -32,12 +33,9 @@ fn main() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();

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

@ -2,41 +2,37 @@ use ctru::applets::swkbd::{Button, Swkbd}; @@ -2,41 +2,37 @@ use ctru::applets::swkbd::{Button, Swkbd};
use ctru::prelude::*;
fn main() {
ctru::init();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap();
let gfx = Gfx::init().unwrap();
let _console = Console::init(gfx.top_screen.borrow_mut());
ctru::use_panic_handler();
let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap();
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");
while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
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
// 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.
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
// software button the user pressed
match keyboard.get_utf8(&mut text) {
Ok(Button::Right) => println!("You entered: {text}"),
Ok(Button::Left) => println!("Cancelled"),
Ok(Button::Middle) => println!("How did you even press this?"),
match keyboard.get_string(2048) {
Ok((text, Button::Right)) => println!("You entered: {text}"),
Ok((_, Button::Left)) => println!("Cancelled"),
Ok((_, Button::Middle)) => println!("How did you even press this?"),
Err(_) => println!("Oh noes, an error happened!"),
}
}
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
}

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

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

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

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

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

@ -7,11 +7,12 @@ use ctru::prelude::*; @@ -7,11 +7,12 @@ use ctru::prelude::*;
use std::os::horizon::thread::BuilderExt;
fn main() {
ctru::init();
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 _console = Console::init(gfx.top_screen.borrow_mut());
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());
// Give ourselves up to 30% of the system core's time
apt.set_app_cpu_time_limit(30)
@ -44,8 +45,6 @@ fn main() { @@ -44,8 +45,6 @@ fn main() {
break;
}
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
}
}

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

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

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

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

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

@ -0,0 +1,112 @@ @@ -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 @@ @@ -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; @@ -8,14 +8,14 @@ use std::ffi::CString;
/// Index of a Mii used to configure some parameters of the Mii Selector
/// Can be either a single index, or _all_ Miis
#[derive(Debug, Clone)]
pub enum MiiConfigIndex {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Index {
Index(u32),
All,
}
/// The type of a Mii with their respective data
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MiiType {
Guest { index: u32, name: String },
User,
@ -41,7 +41,7 @@ bitflags! { @@ -41,7 +41,7 @@ bitflags! {
/// ```
/// 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");
///
/// let result = mii_selector.launch().unwrap();
@ -54,7 +54,7 @@ pub struct MiiSelector { @@ -54,7 +54,7 @@ pub struct MiiSelector {
/// Return value from a MiiSelector's launch
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct MiiSelectorReturn {
pub struct SelectionResult {
pub mii_data: MiiData,
pub is_mii_selected: bool,
pub mii_type: MiiType,
@ -62,13 +62,13 @@ pub struct MiiSelectorReturn { @@ -62,13 +62,13 @@ pub struct MiiSelectorReturn {
/// Error type for the Mii selector
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiLaunchError {
pub enum LaunchError {
InvalidChecksum,
}
impl MiiSelector {
/// Initializes a Mii Selector
pub fn init() -> Self {
pub fn new() -> Self {
let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
unsafe {
ctru_sys::miiSelectorInit(config.as_mut());
@ -94,40 +94,40 @@ impl MiiSelector { @@ -94,40 +94,40 @@ impl MiiSelector {
}
/// 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 {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
};
unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) }
}
/// 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 {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS,
};
unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) }
}
/// 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 {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
};
unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) }
}
/// 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 {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
Index::Index(i) => i,
Index::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
};
unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
@ -135,32 +135,38 @@ impl MiiSelector { @@ -135,32 +135,38 @@ impl MiiSelector {
/// 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
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
// 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.
/// 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();
unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) }
if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } {
Ok((*return_val).into())
} 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 {
let raw_mii_data = ret.mii;
let mut guest_mii_name = ret.guest_mii_name;
MiiSelectorReturn {
SelectionResult {
mii_data: raw_mii_data.into(),
is_mii_selected: ret.no_mii_selected == 0,
mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
@ -179,7 +185,7 @@ impl From<ctru_sys::MiiSelectorReturn> for MiiSelectorReturn { @@ -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 {
Self::Index(v)
}

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

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

40
ctru-rs/src/error.rs

@ -21,7 +21,11 @@ impl Try for ResultCode { @@ -21,7 +21,11 @@ impl Try for ResultCode {
}
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())
} else {
ControlFlow::Continue(())
@ -51,6 +55,12 @@ pub enum Error { @@ -51,6 +55,12 @@ pub enum Error {
Libc(String),
ServiceAlreadyActive,
OutputAlreadyRedirected,
BufferTooShort {
/// Length of the buffer provided by the user.
provided: usize,
/// Size of the requested data (in bytes).
wanted: usize,
},
}
impl Error {
@ -104,22 +114,32 @@ impl fmt::Debug for Error { @@ -104,22 +114,32 @@ impl fmt::Debug for Error {
Self::Libc(err) => f.debug_tuple("Libc").field(err).finish(),
Self::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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::ServiceAlreadyActive => write!(f, "Service already active"),
Self::ServiceAlreadyActive => write!(f, "service already active"),
Self::OutputAlreadyRedirected => {
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> { @@ -142,7 +162,7 @@ fn result_code_level_str(result: ctru_sys::Result) -> Cow<'static, str> {
RL_PERMANENT => "permanent",
RL_TEMPORARY => "temporary",
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> { @@ -167,7 +187,7 @@ fn result_code_summary_str(result: ctru_sys::Result) -> Cow<'static, str> {
RS_STATUSCHANGED => "status_changed",
RS_INTERNAL => "internal",
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> { @@ -207,7 +227,7 @@ fn result_code_description_str(result: ctru_sys::Result) -> Cow<'static, str> {
RD_NOT_AUTHORIZED => "not_authorized",
RD_TOO_LARGE => "too_large",
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> { @@ -324,6 +344,6 @@ fn result_code_module_str(result: ctru_sys::Result) -> Cow<'static, str> {
RM_NFP => "nfp",
RM_APPLICATION => "application",
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 @@ @@ -7,40 +7,35 @@
#![feature(nonnull_slice_from_raw_parts)]
#![test_runner(test_runner::run)]
extern "C" fn services_deinit() {
unsafe {
ctru_sys::psExit();
}
}
// Nothing is imported from these crates but their inclusion here assures correct linking of the missing implementations.
extern crate pthread_3ds;
extern crate shim_3ds;
#[no_mangle]
#[cfg(feature = "big-stack")]
static __stacksize__: usize = 2 * 1024 * 1024; // 2MB
/// Call this somewhere to force Rust to link some required crates
/// This is also a setup for some crate integration only available at runtime
///
/// See <https://github.com/rust-lang/rust/issues/47384>
pub fn init() {
linker_fix_3ds::init();
pthread_3ds::init();
macro_rules! from_impl {
($from_type:ty, $into_type:ty) => {
impl From<$from_type> for $into_type {
fn from(v: $from_type) -> Self {
v as $into_type
}
}
};
}
/// 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))]
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))]
@ -59,11 +54,11 @@ fn panic_hook_setup() { @@ -59,11 +54,11 @@ fn panic_hook_setup() {
if main_thread == std::thread::current().id() && console::Console::exists() {
println!("\nPress SELECT to exit the software");
match Hid::init() {
Ok(hid) => loop {
match Hid::new() {
Ok(mut hid) => loop {
hid.scan_input();
let keys = hid.keys_down();
if keys.contains(KeyPad::KEY_SELECT) {
if keys.contains(KeyPad::SELECT) {
break;
}
},
@ -77,31 +72,11 @@ fn panic_hook_setup() { @@ -77,31 +72,11 @@ fn panic_hook_setup() {
pub mod applets;
pub mod console;
pub mod error;
pub mod gfx;
pub mod linear;
pub mod mii;
pub mod prelude;
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)]
mod test_runner;

3
ctru-rs/src/linear.rs

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

3
ctru-rs/src/prelude.rs

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

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

@ -0,0 +1,107 @@ @@ -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; @@ -3,7 +3,7 @@ use crate::error::ResultCode;
pub struct Apt(());
impl Apt {
pub fn init() -> crate::Result<Apt> {
pub fn new() -> crate::Result<Apt> {
unsafe {
ResultCode(ctru_sys::aptInit())?;
Ok(Apt(()))
@ -14,7 +14,7 @@ impl Apt { @@ -14,7 +14,7 @@ impl Apt {
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 {
ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?;
Ok(())

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

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
//! CAM service
//! Camera service
//!
//! The CAM service provides access to the cameras. Cameras can return 2D images
//! in the form of byte vectors which can be used for display or other usages.
//! The CAM service provides access to the cameras. Cameras can return images
//! in the form of byte vectors which can be displayed or used in other ways.
use crate::error::ResultCode;
use crate::error::{Error, ResultCode};
use crate::services::gspgpu::FramebufferFormat;
use bitflags::bitflags;
use ctru_sys::Handle;
use std::time::Duration;
@ -21,191 +20,169 @@ pub struct Cam { @@ -21,191 +20,169 @@ pub struct Cam {
pub both_outer_cams: BothOutwardCam,
}
bitflags! {
/// A set of flags to be passed to [Camera::flip_image]
#[derive(Default)]
pub struct CamFlip: u32 {
const NONE = ctru_sys::FLIP_NONE;
const HORIZONTAL = ctru_sys::FLIP_HORIZONTAL;
const VERTICAL = ctru_sys::FLIP_VERTICAL;
const REVERSE = ctru_sys::FLIP_REVERSE;
}
/// Flag to pass to [Camera::flip_image]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FlipMode {
None = ctru_sys::FLIP_NONE,
Horizontal = ctru_sys::FLIP_HORIZONTAL,
Vertical = ctru_sys::FLIP_VERTICAL,
Reverse = ctru_sys::FLIP_REVERSE,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_view_size]
#[derive(Default)]
pub struct CamSize: u32 {
const VGA = ctru_sys::SIZE_VGA;
const QVGA = ctru_sys::SIZE_QVGA;
const QQVGA = ctru_sys::SIZE_QQVGA;
const CIF = ctru_sys::SIZE_CIF;
const QCIF = ctru_sys::SIZE_QCIF;
const DS_LCD = ctru_sys::SIZE_DS_LCD;
const DS_LCD_X4 = ctru_sys::SIZE_DS_LCDx4;
const CTR_TOP_LCD = ctru_sys::SIZE_CTR_TOP_LCD;
const CTR_BOTTOM_LCD = ctru_sys::SIZE_CTR_BOTTOM_LCD;
}
/// Flag to pass to [Camera::set_view_size]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ViewSize {
TopLCD = ctru_sys::SIZE_CTR_TOP_LCD,
/// Equivalent to QVga
BottomLCD = ctru_sys::SIZE_CTR_BOTTOM_LCD,
Vga = ctru_sys::SIZE_VGA,
QQVga = ctru_sys::SIZE_QQVGA,
Cif = ctru_sys::SIZE_CIF,
QCif = ctru_sys::SIZE_QCIF,
/// Nintendo DS Screen
DS = ctru_sys::SIZE_DS_LCD,
/// Nintendo DS Screen x4
DSX4 = ctru_sys::SIZE_DS_LCDx4,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_frame_rate]
#[derive(Default)]
pub struct CamFrameRate: u32 {
const RATE_15 = ctru_sys::FRAME_RATE_15;
const RATE_15_TO_5 = ctru_sys::FRAME_RATE_15_TO_5;
const RATE_15_TO_2 = ctru_sys::FRAME_RATE_15_TO_2;
const RATE_10 = ctru_sys::FRAME_RATE_10;
const RATE_8_5 = ctru_sys::FRAME_RATE_8_5;
const RATE_5 = ctru_sys::FRAME_RATE_5;
const RATE_20 = ctru_sys::FRAME_RATE_20;
const RATE_20_TO_5 = ctru_sys::FRAME_RATE_20_TO_5;
const RATE_30 = ctru_sys::FRAME_RATE_30;
const RATE_30_TO_5 = ctru_sys::FRAME_RATE_30_TO_5;
const RATE_15_TO_10 = ctru_sys::FRAME_RATE_15_TO_10;
const RATE_20_TO_10 = ctru_sys::FRAME_RATE_20_TO_10;
const RATE_30_TO_10 = ctru_sys::FRAME_RATE_30_TO_10;
}
/// Flag to pass to [Camera::set_frame_rate]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FrameRate {
Fps15 = ctru_sys::FRAME_RATE_15,
Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5,
Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2,
Fps10 = ctru_sys::FRAME_RATE_10,
Fps8_5 = ctru_sys::FRAME_RATE_8_5,
Fps5 = ctru_sys::FRAME_RATE_5,
Fps20 = ctru_sys::FRAME_RATE_20,
Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5,
Fps30 = ctru_sys::FRAME_RATE_30,
Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5,
Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10,
Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10,
Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_white_balance] or
/// [Camera::set_white_balance_without_base_up]
#[derive(Default)]
pub struct CamWhiteBalance: u32 {
const AUTO = ctru_sys::WHITE_BALANCE_AUTO;
const BALANCE_3200K = ctru_sys::WHITE_BALANCE_3200K;
const BALANCE_4150K = ctru_sys::WHITE_BALANCE_4150K;
const BALANCE_5200K = ctru_sys::WHITE_BALANCE_5200K;
const BALANCE_6000K = ctru_sys::WHITE_BALANCE_6000K;
const BALANCE_7000K = ctru_sys::WHITE_BALANCE_7000K;
const NORMAL = ctru_sys::WHITE_BALANCE_NORMAL;
const TUNGSTEN = ctru_sys::WHITE_BALANCE_TUNGSTEN;
const WHITE_FLUORESCENT_LIGHT = ctru_sys::WHITE_BALANCE_WHITE_FLUORESCENT_LIGHT;
const DAYLIGHT = ctru_sys::WHITE_BALANCE_DAYLIGHT;
const CLOUDY = ctru_sys::WHITE_BALANCE_CLOUDY;
const HORIZON = ctru_sys::WHITE_BALANCE_HORIZON;
const SHADE = ctru_sys::WHITE_BALANCE_SHADE;
}
/// Flag to pass to [Camera::set_white_balance] or
/// [Camera::set_white_balance_without_base_up]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum WhiteBalance {
/// Normal
Auto = ctru_sys::WHITE_BALANCE_AUTO,
/// Tungsten
Temp3200K = ctru_sys::WHITE_BALANCE_3200K,
/// Fluorescent Light
Temp4150K = ctru_sys::WHITE_BALANCE_4150K,
/// Daylight
Temp5200K = ctru_sys::WHITE_BALANCE_5200K,
/// Cloudy/Horizon
Temp6000K = ctru_sys::WHITE_BALANCE_6000K,
///Shade
Temp7000K = ctru_sys::WHITE_BALANCE_7000K,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_photo_mode]
#[derive(Default)]
pub struct CamPhotoMode: u32 {
const NORMAL = ctru_sys::PHOTO_MODE_NORMAL;
const PORTRAIT = ctru_sys::PHOTO_MODE_PORTRAIT;
const LANDSCAPE = ctru_sys::PHOTO_MODE_LANDSCAPE;
const NIGHTVIEW = ctru_sys::PHOTO_MODE_NIGHTVIEW;
const LETTER = ctru_sys::PHOTO_MODE_LETTER;
}
/// Flag to pass to [Camera::set_photo_mode]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum PhotoMode {
Normal = ctru_sys::PHOTO_MODE_NORMAL,
Portrait = ctru_sys::PHOTO_MODE_PORTRAIT,
Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE,
NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW,
Letter = ctru_sys::PHOTO_MODE_LETTER,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_effect]
#[derive(Default)]
pub struct CamEffect: u32 {
const NONE = ctru_sys::EFFECT_NONE;
const MONO = ctru_sys::EFFECT_MONO;
const SEPIA = ctru_sys::EFFECT_SEPIA;
const NEGATIVE = ctru_sys::EFFECT_NEGATIVE;
const NEGAFILM = ctru_sys::EFFECT_NEGAFILM;
const SEPIA01 = ctru_sys::EFFECT_SEPIA01;
}
/// Flag to pass to [Camera::set_effect]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Effect {
None = ctru_sys::EFFECT_NONE,
Mono = ctru_sys::EFFECT_MONO,
Sepia = ctru_sys::EFFECT_SEPIA,
Negative = ctru_sys::EFFECT_NEGATIVE,
Negafilm = ctru_sys::EFFECT_NEGAFILM,
Sepia01 = ctru_sys::EFFECT_SEPIA01,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_contrast]
#[derive(Default)]
pub struct CamContrast: u32 {
const PATTERN_01 = ctru_sys::CONTRAST_PATTERN_01;
const PATTERN_02 = ctru_sys::CONTRAST_PATTERN_02;
const PATTERN_03 = ctru_sys::CONTRAST_PATTERN_03;
const PATTERN_04 = ctru_sys::CONTRAST_PATTERN_04;
const PATTERN_05 = ctru_sys::CONTRAST_PATTERN_05;
const PATTERN_06 = ctru_sys::CONTRAST_PATTERN_06;
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;
}
/// Flag to pass to [Camera::set_contrast]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Contrast {
/// OFF
Low = ctru_sys::CONTRAST_LOW,
/// Brightness ratio: 70
Normal = ctru_sys::CONTRAST_NORMAL,
/// Brightness ratio: 90
High = ctru_sys::CONTRAST_HIGH,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_lens_correction]
#[derive(Default)]
pub struct CamLensCorrection: u32 {
const OFF = ctru_sys::LENS_CORRECTION_OFF;
const ON_70 = ctru_sys::LENS_CORRECTION_ON_70;
const ON_90 = ctru_sys::LENS_CORRECTION_ON_90;
/// Flag to pass to [Camera::set_lens_correction]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum LensCorrection {
Off = ctru_sys::LENS_CORRECTION_DARK,
Normal = ctru_sys::LENS_CORRECTION_NORMAL,
Bright = ctru_sys::LENS_CORRECTION_BRIGHT,
}
const DARK = ctru_sys::LENS_CORRECTION_DARK;
const NORMAL = ctru_sys::LENS_CORRECTION_NORMAL;
const BRIGHT = ctru_sys::LENS_CORRECTION_BRIGHT;
}
/// Flag to pass to [Camera::set_output_format]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum OutputFormat {
Yuv422 = ctru_sys::OUTPUT_YUV_422,
Rgb565 = ctru_sys::OUTPUT_RGB_565,
}
bitflags! {
/// A set of flags to be passed to [Camera::set_output_format]
#[derive(Default)]
pub struct CamOutputFormat: u32 {
const YUV_422 = ctru_sys::OUTPUT_YUV_422;
const RGB_565 = ctru_sys::OUTPUT_RGB_565;
}
/// Flag to pass to [Cam::play_shutter_sound]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ShutterSound {
Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL,
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 = ();
fn try_from(value: FramebufferFormat) -> Result<Self, Self::Error> {
match value {
FramebufferFormat::Rgb565 => Ok(CamOutputFormat::RGB_565),
FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565),
_ => Err(()),
}
}
}
impl TryFrom<CamOutputFormat> for FramebufferFormat {
impl TryFrom<OutputFormat> for FramebufferFormat {
type Error = ();
fn try_from(value: CamOutputFormat) -> Result<Self, Self::Error> {
fn try_from(value: OutputFormat) -> Result<Self, Self::Error> {
match value {
CamOutputFormat::RGB_565 => Ok(FramebufferFormat::Rgb565),
OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565),
_ => 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].
pub struct CamTrimmingParams {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrimmingParams {
x_start: i16,
y_start: i16,
x_end: i16,
y_end: i16,
}
impl CamTrimmingParams {
impl TrimmingParams {
/// Creates a new [CamTrimmingParams] and guarantees the start coordinates are less than or
/// equal to the end coordinates.
///
/// `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);
Self {
x_start,
@ -217,11 +194,11 @@ impl CamTrimmingParams { @@ -217,11 +194,11 @@ impl CamTrimmingParams {
}
/// 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);
/// 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);
/// Represents the camera on the inside of the 3DS
@ -307,7 +284,7 @@ pub trait Camera { @@ -307,7 +284,7 @@ pub trait Camera {
/// Returns the maximum amount of transfer bytes based on the view size, trimming, and other
/// modifications set to the camera
fn get_transfer_bytes(&self) -> crate::Result<u32> {
fn transfer_byte_count(&self) -> crate::Result<u32> {
unsafe {
let mut transfer_bytes = 0;
ResultCode(ctru_sys::CAMU_GetTransferBytes(
@ -336,8 +313,8 @@ pub trait Camera { @@ -336,8 +313,8 @@ pub trait Camera {
}
}
/// Sets trimming parameters based on coordinates specified inside a [CamTrimmingParams]
fn set_trimming_params(&mut self, params: CamTrimmingParams) -> crate::Result<()> {
/// Sets trimming parameters based on coordinates specified inside a [TrimmingParams]
fn set_trimming_params(&mut self, params: TrimmingParams) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetTrimmingParams(
self.port_as_raw(),
@ -350,8 +327,8 @@ pub trait Camera { @@ -350,8 +327,8 @@ pub trait Camera {
}
}
/// Returns the set [CamTrimmingParams] from the camera
fn get_trimming_params(&self) -> crate::Result<CamTrimmingParams> {
/// Returns the [TrimmingParams] set
fn trimming_params(&self) -> crate::Result<TrimmingParams> {
unsafe {
let mut x_start = 0;
let mut y_start = 0;
@ -365,7 +342,7 @@ pub trait Camera { @@ -365,7 +342,7 @@ pub trait Camera {
self.port_as_raw(),
))?;
Ok(CamTrimmingParams {
Ok(TrimmingParams {
x_start,
y_start,
x_end,
@ -378,7 +355,7 @@ pub trait Camera { @@ -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 height will be `trim_height / 2` above and below the center.
fn set_trimming_params_center(
&self,
&mut self,
trim_width: i16,
trim_height: i16,
cam_width: i16,
@ -404,27 +381,27 @@ pub trait Camera { @@ -404,27 +381,27 @@ pub trait Camera {
}
}
/// Sets the white balance mod of the camera based on the passed [CamWhiteBalance] argument
fn set_white_balance(&mut self, white_balance: CamWhiteBalance) -> crate::Result<()> {
/// Sets the white balance mod of the camera based on the passed [WhiteBalance] argument
fn set_white_balance(&mut self, white_balance: WhiteBalance) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetWhiteBalance(
self.camera_as_raw(),
white_balance.bits(),
white_balance.into(),
))?;
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
fn set_white_balance_without_base_up(
&mut self,
white_balance: CamWhiteBalance,
white_balance: WhiteBalance,
) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetWhiteBalanceWithoutBaseUp(
self.camera_as_raw(),
white_balance.bits(),
white_balance.into(),
))?;
Ok(())
}
@ -484,12 +461,12 @@ pub trait Camera { @@ -484,12 +461,12 @@ pub trait Camera {
}
}
/// Sets the flip direction of the camera's image based on the passed [CamFlip] argument
fn flip_image(&mut self, flip: CamFlip) -> crate::Result<()> {
/// Sets the flip direction of the camera's image based on the passed [FlipMode] argument
fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_FlipImage(
self.camera_as_raw(),
flip.bits(),
flip.into(),
ctru_sys::CONTEXT_A,
))?;
Ok(())
@ -530,82 +507,82 @@ pub trait Camera { @@ -530,82 +507,82 @@ pub trait Camera {
}
}
/// Sets the view size of the camera based on the passed [CamSize] argument.
fn set_view_size(&mut self, size: CamSize) -> crate::Result<()> {
/// Sets the view size of the camera based on the passed [ViewSize] argument.
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSize(
self.camera_as_raw(),
size.bits(),
size.into(),
ctru_sys::CONTEXT_A,
))?;
Ok(())
}
}
/// Sets the frame rate of the camera based on the passed [CamFrameRate] argument.
fn set_frame_rate(&mut self, frame_rate: CamFrameRate) -> crate::Result<()> {
/// Sets the frame rate of the camera based on the passed [FrameRate] argument.
fn set_frame_rate(&mut self, frame_rate: FrameRate) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetFrameRate(
self.camera_as_raw(),
frame_rate.bits(),
frame_rate.into(),
))?;
Ok(())
}
}
/// Sets the photo mode of the camera based on the passed [CamPhotoMode] argument.
fn set_photo_mode(&mut self, photo_mode: CamPhotoMode) -> crate::Result<()> {
/// Sets the photo mode of the camera based on the passed [PhotoMode] argument.
fn set_photo_mode(&mut self, photo_mode: PhotoMode) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetPhotoMode(
self.camera_as_raw(),
photo_mode.bits(),
photo_mode.into(),
))?;
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]
fn set_effect(&mut self, effect: CamEffect) -> crate::Result<()> {
fn set_effect(&mut self, effect: Effect) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetEffect(
self.camera_as_raw(),
effect.bits(),
effect.into(),
ctru_sys::CONTEXT_A,
))?;
Ok(())
}
}
/// Sets the contrast of the camera based on the passed [CamContrast] argument.
fn set_contrast(&mut self, contrast: CamContrast) -> crate::Result<()> {
/// Sets the contrast of the camera based on the passed [Contrast] argument.
fn set_contrast(&mut self, contrast: Contrast) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetContrast(
self.camera_as_raw(),
contrast.bits(),
contrast.into(),
))?;
Ok(())
}
}
/// Sets the lens correction of the camera based on the passed [CamLensCorrection] argument.
fn set_lens_correction(&mut self, lens_correction: CamLensCorrection) -> crate::Result<()> {
/// Sets the lens correction of the camera based on the passed [LensCorrection] argument.
fn set_lens_correction(&mut self, lens_correction: LensCorrection) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetLensCorrection(
self.camera_as_raw(),
lens_correction.bits(),
lens_correction.into(),
))?;
Ok(())
}
}
/// Sets the output format of the camera based on the passed [CamOutputFormat] argument.
fn set_output_format(&mut self, format: CamOutputFormat) -> crate::Result<()> {
/// Sets the output format of the camera based on the passed [OutputFormat] argument.
fn set_output_format(&mut self, format: OutputFormat) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetOutputFormat(
self.camera_as_raw(),
format.bits(),
format.into(),
ctru_sys::CONTEXT_A,
))?;
Ok(())
@ -687,7 +664,7 @@ pub trait Camera { @@ -687,7 +664,7 @@ pub trait 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 {
let mut data = ImageQualityCalibrationData::default();
ResultCode(ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0))?;
@ -704,7 +681,7 @@ pub trait Camera { @@ -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
///
@ -717,10 +694,11 @@ pub trait Camera { @@ -717,10 +694,11 @@ pub trait Camera {
/// * `timeout` - Duration to wait for the image
fn take_picture(
&mut self,
buffer: &mut [u8],
width: u16,
height: u16,
timeout: Duration,
) -> crate::Result<Vec<u8>> {
) -> crate::Result<()> {
let transfer_unit = unsafe {
let mut buf_size = 0;
ResultCode(ctru_sys::CAMU_GetMaxBytes(
@ -731,10 +709,6 @@ pub trait Camera { @@ -731,10 +709,6 @@ pub trait Camera {
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 {
ResultCode(ctru_sys::CAMU_SetTransferBytes(
self.port_as_raw(),
@ -744,6 +718,14 @@ pub trait Camera { @@ -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 {
ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?;
ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
@ -754,25 +736,30 @@ pub trait Camera { @@ -754,25 +736,30 @@ pub trait Camera {
let mut completion_handle: Handle = 0;
ResultCode(ctru_sys::CAMU_SetReceiving(
&mut completion_handle,
buf.as_mut_ptr() as *mut ::libc::c_void,
buffer.as_mut_ptr().cast(),
self.port_as_raw(),
screen_size,
screen_size as u32,
transfer_unit.try_into().unwrap(),
))?;
Ok::<Handle, i32>(completion_handle)
}?;
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,
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::svcCloseHandle(receive_event))?;
ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))?;
wait_result?;
};
Ok(buf)
Ok(())
}
}
@ -784,7 +771,7 @@ impl Cam { @@ -784,7 +771,7 @@ impl Cam {
/// This function will return an error if the service was unable to be initialized.
/// Since this service requires no special or elevated permissions, errors are
/// rare in practice.
pub fn init() -> crate::Result<Cam> {
pub fn new() -> crate::Result<Cam> {
unsafe {
ResultCode(ctru_sys::camInit())?;
Ok(Cam {
@ -796,10 +783,10 @@ impl Cam { @@ -796,10 +783,10 @@ impl Cam {
}
}
/// Plays the specified sound based on the [CamShutterSoundType] argument
pub fn play_shutter_sound(&self, sound: CamShutterSoundType) -> crate::Result<()> {
/// Plays the specified sound based on the [ShutterSound] argument
pub fn play_shutter_sound(&self, sound: ShutterSound) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.bits()))?;
ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?;
Ok(())
}
}
@ -810,3 +797,14 @@ impl Drop for Cam { @@ -810,3 +797,14 @@ impl Drop for Cam {
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 @@ @@ -4,7 +4,7 @@
use crate::error::ResultCode;
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Region {
Japan = ctru_sys::CFG_REGION_JPN,
@ -16,7 +16,7 @@ pub enum Region { @@ -16,7 +16,7 @@ pub enum Region {
Taiwan = ctru_sys::CFG_REGION_TWN,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Language {
Japanese = ctru_sys::CFG_LANGUAGE_JP,
@ -33,15 +33,15 @@ pub enum Language { @@ -33,15 +33,15 @@ pub enum Language {
TraditionalChinese = ctru_sys::CFG_LANGUAGE_TW,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum SystemModel {
Model3DS = ctru_sys::CFG_MODEL_3DS,
Model3DSXL = ctru_sys::CFG_MODEL_3DSXL,
ModelNew3DS = ctru_sys::CFG_MODEL_N3DS,
Model2DS = ctru_sys::CFG_MODEL_2DS,
ModelNew3DSXL = ctru_sys::CFG_MODEL_N3DSXL,
ModelNew2DSXL = ctru_sys::CFG_MODEL_N2DSXL,
Old3DS = ctru_sys::CFG_MODEL_3DS,
Old3DSXL = ctru_sys::CFG_MODEL_3DSXL,
New3DS = ctru_sys::CFG_MODEL_N3DS,
Old2DS = ctru_sys::CFG_MODEL_2DS,
New3DSXL = ctru_sys::CFG_MODEL_N3DSXL,
New2DSXL = ctru_sys::CFG_MODEL_N2DSXL,
}
/// Represents the configuration service. No actions can be performed
@ -61,13 +61,13 @@ impl Cfgu { @@ -61,13 +61,13 @@ impl Cfgu {
/// ctrulib services are reference counted, so this function may be called
/// as many times as desired and the service will not exit until all
/// instances of Cfgu drop out of scope.
pub fn init() -> crate::Result<Cfgu> {
pub fn new() -> crate::Result<Cfgu> {
ResultCode(unsafe { ctru_sys::cfguInit() })?;
Ok(Cfgu(()))
}
/// 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;
ResultCode(unsafe { ctru_sys::CFGU_SecureInfoGetRegion(&mut region) })?;
@ -75,7 +75,7 @@ impl Cfgu { @@ -75,7 +75,7 @@ impl Cfgu {
}
/// Gets system's model
pub fn get_model(&self) -> crate::Result<SystemModel> {
pub fn model(&self) -> crate::Result<SystemModel> {
let mut model: u8 = 0;
ResultCode(unsafe { ctru_sys::CFGU_GetSystemModel(&mut model) })?;
@ -83,7 +83,7 @@ impl Cfgu { @@ -83,7 +83,7 @@ impl Cfgu {
}
/// Gets system's language
pub fn get_language(&self) -> crate::Result<Language> {
pub fn language(&self) -> crate::Result<Language> {
let mut language: u8 = 0;
ResultCode(unsafe { ctru_sys::CFGU_GetSystemLanguage(&mut language) })?;
@ -115,19 +115,9 @@ impl Drop for Cfgu { @@ -115,19 +115,9 @@ impl Drop for Cfgu {
}
}
macro_rules! from_type_to_u8 {
($from_type:ty) => {
impl From<$from_type> for 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);
from_impl!(Region, u8);
from_impl!(Language, u8);
from_impl!(SystemModel, u8);
impl TryFrom<u8> for Region {
type Error = ();
@ -173,12 +163,12 @@ impl TryFrom<u8> for SystemModel { @@ -173,12 +163,12 @@ impl TryFrom<u8> for SystemModel {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value as u32 {
ctru_sys::CFG_MODEL_3DS => Ok(SystemModel::Model3DS),
ctru_sys::CFG_MODEL_3DSXL => Ok(SystemModel::Model3DSXL),
ctru_sys::CFG_MODEL_N3DS => Ok(SystemModel::ModelNew3DS),
ctru_sys::CFG_MODEL_2DS => Ok(SystemModel::Model2DS),
ctru_sys::CFG_MODEL_N3DSXL => Ok(SystemModel::ModelNew3DSXL),
ctru_sys::CFG_MODEL_N2DSXL => Ok(SystemModel::ModelNew2DSXL),
ctru_sys::CFG_MODEL_3DS => Ok(SystemModel::Old3DS),
ctru_sys::CFG_MODEL_3DSXL => Ok(SystemModel::Old3DSXL),
ctru_sys::CFG_MODEL_N3DS => Ok(SystemModel::New3DS),
ctru_sys::CFG_MODEL_2DS => Ok(SystemModel::Old2DS),
ctru_sys::CFG_MODEL_N3DSXL => Ok(SystemModel::New3DSXL),
ctru_sys::CFG_MODEL_N2DSXL => Ok(SystemModel::New2DSXL),
_ => Err(()),
}
}

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

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
//! Filesystem service
//!
//! 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 std::ffi::OsString;
@ -23,17 +23,13 @@ bitflags! { @@ -23,17 +23,13 @@ bitflags! {
const FS_OPEN_WRITE = 2;
const FS_OPEN_CREATE = 4;
}
}
bitflags! {
#[derive(Default)]
struct FsWrite: u32 {
const FS_WRITE_FLUSH = 1;
const FS_WRITE_UPDATE_TIME = 256;
}
}
bitflags! {
#[derive(Default)]
struct FsAttribute: u32 {
const FS_ATTRIBUTE_DIRECTORY = 1;
@ -43,39 +39,49 @@ bitflags! { @@ -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 {
Invalid,
Empty,
Binary,
ASCII,
UTF16,
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,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum ArchiveID {
RomFS,
Savedata,
Extdata,
SharedExtdata,
SystemSavedata,
Sdmc,
SdmcWriteOnly,
BossExtdata,
CardSpiFS,
ExtDataAndBossExtdata,
SystemSaveData2,
NandRW,
NandRO,
NandROWriteAccess,
SaveDataAndContent,
SaveDataAndContent2,
NandCtrFS,
TwlPhoto,
NandTwlFS,
GameCardSavedata,
UserSavedata,
DemoSavedata,
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,
}
/// Represents the filesystem service. No file IO can be performed
@ -93,7 +99,7 @@ pub struct Fs(()); @@ -93,7 +99,7 @@ pub struct Fs(());
/// ```no_run
/// use ctru::services::fs::Fs;
///
/// let fs = Fs::init().unwrap();
/// let mut fs = Fs::new().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// ```
pub struct Archive {
@ -113,47 +119,62 @@ pub struct Archive { @@ -113,47 +119,62 @@ pub struct Archive {
/// Create a new file and write bytes to it:
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::init()?;
/// let sdmc = fs.sdmc()?;
///
/// let mut file = File::create(&sdmc, "/foo.txt")?;
/// file.write_all(b"Hello, world!")?;
/// let mut fs = Fs::new()?;
/// let mut sdmc = fs.sdmc()?;
/// #
/// # Ok(())
/// # }
/// ```
///
/// Read the contents of a file into a `String`::
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::init()?;
/// let sdmc = fs.sdmc()?;
/// let mut fs = Fs::new()?;
/// let mut sdmc = fs.sdmc()?;
///
/// let mut file = File::open(&sdmc, "/foo.txt")?;
/// let mut contents = String::new();
/// file.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!");
/// #
/// # Ok(())
/// # }
/// ```
///
/// It can be more efficient to read the contents of a file with a buffered
/// `Read`er. This can be accomplished with `BufReader<R>`:
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use std::io::BufReader;
/// use std::io::prelude::*;
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::init()?;
/// let sdmc = fs.sdmc()?;
/// let mut fs = Fs::new()?;
/// let mut sdmc = fs.sdmc()?;
///
/// let file = File::open(&sdmc, "/foo.txt")?;
/// let mut buf_reader = BufReader::new(file);
/// let mut contents = String::new();
/// buf_reader.read_to_string(&mut contents)?;
/// assert_eq!(contents, "Hello, world!");
/// #
/// # Ok(())
/// # }
/// ```
pub struct File {
handle: u32,
@ -199,8 +220,8 @@ pub struct Metadata { @@ -199,8 +220,8 @@ pub struct Metadata {
/// ```no_run
/// use ctru::services::fs::{Fs, OpenOptions};
///
/// let fs = Fs::init().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
/// let file = OpenOptions::new()
/// .read(true)
/// .archive(&sdmc_archive)
@ -214,8 +235,8 @@ pub struct Metadata { @@ -214,8 +235,8 @@ pub struct Metadata {
/// ```no_run
/// use ctru::services::fs::{Fs, OpenOptions};
///
/// let fs = Fs::init().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
/// let file = OpenOptions::new()
/// .read(true)
/// .write(true)
@ -224,7 +245,7 @@ pub struct Metadata { @@ -224,7 +245,7 @@ pub struct Metadata {
/// .open("foo.txt")
/// .unwrap();
/// ```
#[derive(Clone, Default)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OpenOptions {
read: bool,
write: bool,
@ -288,7 +309,7 @@ impl Fs { @@ -288,7 +309,7 @@ impl Fs {
/// ctrulib services are reference counted, so this function may be called
/// as many times as desired and the service will not exit until all
/// instances of Fs drop out of scope.
pub fn init() -> crate::Result<Fs> {
pub fn new() -> crate::Result<Fs> {
unsafe {
let r = ctru_sys::fsInit();
if r < 0 {
@ -300,7 +321,7 @@ impl Fs { @@ -300,7 +321,7 @@ impl Fs {
}
/// 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 {
let mut handle = 0;
let id = ArchiveID::Sdmc;
@ -319,7 +340,7 @@ impl Archive { @@ -319,7 +340,7 @@ impl Archive {
/// Retrieves an Archive's [`ArchiveID`]
///
/// [`ArchiveID`]: enum.ArchiveID.html
pub fn get_id(&self) -> ArchiveID {
pub fn id(&self) -> ArchiveID {
self.id
}
}
@ -341,8 +362,8 @@ impl File { @@ -341,8 +362,8 @@ impl File {
/// ```no_run
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::init().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::open(&sdmc_archive, "/foo.txt").unwrap();
/// ```
pub fn open<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
@ -370,11 +391,11 @@ impl File { @@ -370,11 +391,11 @@ impl File {
/// ```no_run
/// use ctru::services::fs::{Fs, File};
///
/// let fs = Fs::init().unwrap();
/// let sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::create(&sdmc_archive, "/foo.txt").unwrap();
/// let mut fs = Fs::new().unwrap();
/// let mut sdmc_archive = fs.sdmc().unwrap();
/// let mut f = File::create(&mut sdmc_archive, "/foo.txt").unwrap();
/// ```
pub fn create<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<File> {
pub fn create<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<File> {
OpenOptions::new()
.write(true)
.create(true)
@ -578,11 +599,11 @@ impl OpenOptions { @@ -578,11 +599,11 @@ impl OpenOptions {
/// * Invalid combinations of open options.
///
/// [`Archive`]: struct.Archive.html
pub fn open<P: AsRef<Path>>(&self, path: P) -> IoResult<File> {
self._open(path.as_ref(), self.get_open_flags())
pub fn open<P: AsRef<Path>>(&mut self, path: P) -> IoResult<File> {
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 {
let mut file_handle = 0;
let path = to_utf16(path);
@ -618,7 +639,7 @@ impl OpenOptions { @@ -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) {
(true, false, false) => FsOpen::FS_OPEN_READ,
(false, true, false) => FsOpen::FS_OPEN_WRITE,
@ -696,7 +717,7 @@ impl<'a> DirEntry<'a> { @@ -696,7 +717,7 @@ impl<'a> DirEntry<'a> {
/// but is not limited to just these cases:
///
/// * User lacks permissions to create directory at `path`
pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
pub fn create_dir<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
unsafe {
let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -722,7 +743,7 @@ pub fn create_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { @@ -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
/// 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 mut dir = PathBuf::new();
let mut result = Ok(());
@ -758,7 +779,7 @@ pub fn metadata<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<Metadata> { @@ -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 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 {
let path = to_utf16(path.as_ref());
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<()> { @@ -776,7 +797,7 @@ pub fn remove_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
/// # Errors
///
/// 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 {
let path = to_utf16(path.as_ref());
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> { @@ -828,7 +849,7 @@ pub fn read_dir<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<ReadDir> {
///
/// * path points to a directory.
/// * The user lacks permissions to remove the file.
pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
pub fn remove_file<P: AsRef<Path>>(arch: &mut Archive, path: P) -> IoResult<()> {
unsafe {
let path = to_utf16(path.as_ref());
let fs_path = ctru_sys::fsMakePath(PathType::UTF16.into(), path.as_ptr() as _);
@ -851,7 +872,7 @@ pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> { @@ -851,7 +872,7 @@ pub fn remove_file<P: AsRef<Path>>(arch: &Archive, path: P) -> IoResult<()> {
///
/// * from does not exist.
/// * 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
P: AsRef<Path>,
Q: AsRef<Path>,
@ -1008,45 +1029,6 @@ impl Drop for Dir { @@ -1008,45 +1029,6 @@ impl Drop for Dir {
}
}
impl From<PathType> for ctru_sys::FS_PathType {
fn from(p: PathType) -> Self {
use self::PathType::*;
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,
}
}
}
from_impl!(FsMediaType, ctru_sys::FS_MediaType);
from_impl!(PathType, ctru_sys::FS_PathType);
from_impl!(ArchiveID, ctru_sys::FS_ArchiveID);

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

@ -9,11 +9,12 @@ use crate::services::gspgpu::{self, FramebufferFormat}; @@ -9,11 +9,12 @@ use crate::services::gspgpu::{self, FramebufferFormat};
use crate::services::ServiceReference;
mod private {
use super::{BottomScreen, TopScreen, TopScreenLeft, TopScreenRight};
use super::{BottomScreen, TopScreen, TopScreen3D, TopScreenLeft, TopScreenRight};
pub trait Sealed {}
impl Sealed for TopScreen {}
impl Sealed for TopScreen3D<'_> {}
impl Sealed for TopScreenLeft {}
impl Sealed for TopScreenRight {}
impl Sealed for BottomScreen {}
@ -33,34 +34,37 @@ pub trait Screen: private::Sealed { @@ -33,34 +34,37 @@ pub trait Screen: private::Sealed {
///
/// Note that the pointer of the framebuffer returned by this function can
/// change after each call to this function if double buffering is enabled.
fn get_raw_framebuffer(&mut self) -> RawFrameBuffer {
let mut width = 0;
let mut height = 0;
fn raw_framebuffer(&mut self) -> RawFrameBuffer {
let mut width: u16 = 0;
let mut height: u16 = 0;
let ptr = unsafe {
ctru_sys::gfxGetFramebuffer(self.as_raw(), self.side().into(), &mut width, &mut height)
};
RawFrameBuffer {
ptr,
width,
height,
width: width.into(),
height: height.into(),
screen: PhantomData,
}
}
/// Sets whether to use double buffering. Enabled by default.
///
/// Note that even when double buffering is disabled, one should still use the `swap_buffers`
/// method on each frame to keep the gsp configuration up to date
/// [`Swap::swap_buffers`] must be called after this function for the configuration
/// change to take effect.
fn set_double_buffering(&mut self, enabled: bool) {
unsafe { ctru_sys::gfxSetDoubleBuffering(self.as_raw(), enabled) }
}
/// Gets the framebuffer format
fn get_framebuffer_format(&self) -> FramebufferFormat {
unsafe { ctru_sys::gfxGetScreenFormat(self.as_raw()).into() }
/// Gets the framebuffer format.
fn framebuffer_format(&self) -> FramebufferFormat {
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) {
unsafe { ctru_sys::gfxSetScreenFormat(self.as_raw(), fmt.into()) }
}
@ -75,17 +79,98 @@ pub struct TopScreen { @@ -75,17 +79,98 @@ pub struct TopScreen {
/// A helper container for both sides of the top screen. Once the [`TopScreen`] is
/// converted into this, 3D mode will be enabled until this struct is dropped.
pub struct TopScreen3D<'top_screen> {
screen: &'top_screen RefCell<TopScreen>,
pub struct TopScreen3D<'screen> {
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]
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
/// bottom screen's frame buffer.
#[derive(Debug)]
#[non_exhaustive]
pub struct BottomScreen;
/// Representation of a framebuffer for one [`Side`] of the top screen, or the
@ -96,22 +181,23 @@ pub struct RawFrameBuffer<'screen> { @@ -96,22 +181,23 @@ pub struct RawFrameBuffer<'screen> {
/// Pointer to graphics data to be rendered.
pub ptr: *mut u8,
/// The width of the framebuffer in pixels.
pub width: u16,
pub width: usize,
/// 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.
screen: PhantomData<&'screen mut dyn Screen>,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
/// Side of top screen framebuffer
///
/// The top screen of the 3DS can have two separate sets of framebuffers to support its 3D functionality
pub enum Side {
/// The left framebuffer. This framebuffer is also the one used when 3D is disabled
Left,
Left = ctru_sys::GFX_LEFT,
/// 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
@ -127,10 +213,17 @@ pub struct Gfx { @@ -127,10 +213,17 @@ pub struct Gfx {
static GFX_ACTIVE: Mutex<usize> = Mutex::new(0);
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
/// 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(
top_fb_fmt: FramebufferFormat,
bottom_fb_fmt: FramebufferFormat,
@ -154,32 +247,6 @@ impl Gfx { @@ -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
///
/// Use this to synchronize your application with the refresh rate of the LCD screens
@ -190,22 +257,20 @@ impl Gfx { @@ -190,22 +257,20 @@ impl Gfx {
impl TopScreen3D<'_> {
/// Immutably borrow the two sides of the screen as `(left, right)`.
pub fn split(&self) -> (Ref<dyn Screen>, Ref<dyn Screen>) {
Ref::map_split(self.screen.borrow(), |screen| {
(&screen.left as _, &screen.right as _)
})
pub fn split(&self) -> (Ref<TopScreenLeft>, Ref<TopScreenRight>) {
Ref::map_split(self.screen.borrow(), |screen| (&screen.left, &screen.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| {
(&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> {
fn from(top_screen: &'top_screen RefCell<TopScreen>) -> Self {
impl<'screen> From<&'screen RefCell<TopScreen>> for TopScreen3D<'screen> {
fn from(top_screen: &'screen RefCell<TopScreen>) -> Self {
unsafe {
ctru_sys::gfxSet3D(true);
}
@ -231,6 +296,9 @@ impl TopScreen { @@ -231,6 +296,9 @@ impl TopScreen {
}
/// 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) {
unsafe {
ctru_sys::gfxSetWide(enable);
@ -238,11 +306,13 @@ impl TopScreen { @@ -238,11 +306,13 @@ impl TopScreen {
}
/// 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() }
}
}
// 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 {
fn as_raw(&self) -> ctru_sys::gfxScreen_t {
self.left.as_raw()
@ -283,14 +353,7 @@ impl Screen for BottomScreen { @@ -283,14 +353,7 @@ impl Screen for BottomScreen {
}
}
impl From<Side> for 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,
}
}
}
from_impl!(Side, ctru_sys::gfx3dSide_t);
#[cfg(test)]
mod tests {
@ -300,6 +363,6 @@ mod tests { @@ -300,6 +363,6 @@ mod tests {
#[test]
fn gfx_duplicate() {
// 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 @@ @@ -1,31 +1,31 @@
//! GSPGPU service
use std::convert::From;
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Event {
Psc0,
Psc1,
VBlank0,
VBlank1,
PPF,
P3D,
DMA,
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,
}
/// The different framebuffer formats supported by the 3DS
#[derive(Copy, Clone, Debug)]
/// Framebuffer formats supported by the 3DS
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum FramebufferFormat {
/// RGBA8. 4 bytes per pixel
Rgba8,
Rgba8 = ctru_sys::GSP_RGBA8_OES,
/// BGR8. 3 bytes per pixel
Bgr8,
Bgr8 = ctru_sys::GSP_BGR8_OES,
/// RGB565. 2 bytes per pixel
Rgb565,
Rgb565 = ctru_sys::GSP_RGB565_OES,
/// RGB5A1. 2 bytes per pixel
Rgb5A1,
Rgb5A1 = ctru_sys::GSP_RGB5_A1_OES,
/// RGBA4. 2 bytes per pixel
Rgba4,
Rgba4 = ctru_sys::GSP_RGBA4_OES,
}
impl FramebufferFormat {
@ -65,30 +65,5 @@ impl From<ctru_sys::GSPGPU_FramebufferFormat> for FramebufferFormat { @@ -65,30 +65,5 @@ impl From<ctru_sys::GSPGPU_FramebufferFormat> for FramebufferFormat {
}
}
impl From<FramebufferFormat> for ctru_sys::GSPGPU_FramebufferFormat {
fn from(g: FramebufferFormat) -> Self {
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,
}
}
}
from_impl!(FramebufferFormat, ctru_sys::GSPGPU_FramebufferFormat);
from_impl!(Event, ctru_sys::GSPGPU_Event);

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

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

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

@ -1,19 +1,20 @@ @@ -1,19 +1,20 @@
//! NDSP (Audio) service
pub mod wave;
use wave::{WaveInfo, WaveStatus};
use wave::{Wave, WaveStatus};
use crate::error::ResultCode;
use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut};
use std::default::Default;
use std::error;
use std::fmt;
use std::sync::Mutex;
const NUMBER_OF_CHANNELS: u8 = 24;
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum OutputMode {
Mono = ctru_sys::NDSP_OUTPUT_MONO,
@ -21,7 +22,7 @@ pub enum OutputMode { @@ -21,7 +22,7 @@ pub enum OutputMode {
Surround = ctru_sys::NDSP_OUTPUT_SURROUND,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum AudioFormat {
PCM8Mono = ctru_sys::NDSP_FORMAT_MONO_PCM8,
@ -30,7 +31,13 @@ pub enum AudioFormat { @@ -30,7 +31,13 @@ pub enum AudioFormat {
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)]
pub enum InterpolationType {
Polyphase = ctru_sys::NDSP_INTERP_POLYPHASE,
@ -38,7 +45,7 @@ pub enum InterpolationType { @@ -38,7 +45,7 @@ pub enum InterpolationType {
None = ctru_sys::NDSP_INTERP_NONE,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum NdspError {
/// Channel ID
InvalidChannel(u8),
@ -47,7 +54,7 @@ pub enum NdspError { @@ -47,7 +54,7 @@ pub enum NdspError {
/// Channel ID
WaveBusy(u8),
/// Sample amount requested, Max sample amount
SampleCountOutOfBounds(u32, u32),
SampleCountOutOfBounds(usize, usize),
}
pub struct Channel<'ndsp> {
@ -73,7 +80,7 @@ impl Ndsp { @@ -73,7 +80,7 @@ impl Ndsp {
///
/// This function will return an error if an instance of the `Ndsp` struct already exists
/// 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(
&NDSP_ACTIVE,
false,
@ -115,13 +122,13 @@ impl Ndsp { @@ -115,13 +122,13 @@ impl Ndsp {
/// Set the audio output mode. Defaults to `OutputMode::Stereo`.
pub fn set_output_mode(&mut self, mode: OutputMode) {
unsafe { ctru_sys::ndspSetOutputMode(mode as u32) };
unsafe { ctru_sys::ndspSetOutputMode(mode.into()) };
}
}
impl Channel<'_> {
/// Reset the channel
pub fn reset(&self) {
pub fn reset(&mut self) {
unsafe { ctru_sys::ndspChnReset(self.id.into()) };
}
@ -141,54 +148,45 @@ impl Channel<'_> { @@ -141,54 +148,45 @@ impl Channel<'_> {
}
// Returns the channel's id
pub fn get_id(&self) -> u8 {
pub fn id(&self) -> u8 {
self.id
}
/// Returns the channel's current sample's position.
pub fn get_sample_position(&self) -> u32 {
unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }
/// Returns the index of the currently played sample.
///
/// Because of how fast this value changes, it should only be used as a rough estimate of the current progress.
pub fn sample_position(&self) -> usize {
(unsafe { ctru_sys::ndspChnGetSamplePos(self.id.into()) }) as usize
}
/// 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()) }
}
/// 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) };
}
/// Set the channel's output format.
/// Change this setting based on the used sample's format.
pub fn set_format(&self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format as u16) };
pub fn set_format(&mut self, format: AudioFormat) {
unsafe { ctru_sys::ndspChnSetFormat(self.id.into(), format.into()) };
}
/// Set the channel's interpolation mode.
pub fn set_interpolation(&self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type as u32) };
pub fn set_interpolation(&mut self, interp_type: InterpolationType) {
unsafe { ctru_sys::ndspChnSetInterp(self.id.into(), interp_type.into()) };
}
/// Set the channel's volume mix.
///
/// # Notes
///
/// 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()) }
pub fn set_mix(&mut self, mix: &AudioMix) {
unsafe { ctru_sys::ndspChnSetMix(self.id.into(), mix.as_raw().as_ptr().cast_mut()) }
}
/// Set the channel's rate of sampling.
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) };
}
@ -197,7 +195,7 @@ impl Channel<'_> { @@ -197,7 +195,7 @@ impl Channel<'_> {
// We suggest using other wave formats when developing homebrew applications.
/// 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()) };
}
@ -206,10 +204,10 @@ impl Channel<'_> { @@ -206,10 +204,10 @@ impl Channel<'_> {
///
/// # Warning
///
/// `libctru` expects the user to manually keep the info data (in this case [WaveInfo]) alive during playback.
/// To ensure safety, checks within [WaveInfo] will clear the whole channel queue if any queued [WaveInfo] is dropped prematurely.
pub fn queue_wave(&self, wave: &mut WaveInfo) -> std::result::Result<(), NdspError> {
match wave.get_status() {
/// `libctru` expects the user to manually keep the info data (in this case [Wave]) alive during playback.
/// To ensure safety, checks within [Wave] will clear the whole channel queue if any queued [Wave] is dropped prematurely.
pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), NdspError> {
match wave.status() {
WaveStatus::Playing | WaveStatus::Queued => return Err(NdspError::WaveBusy(self.id)),
_ => (),
}
@ -227,7 +225,7 @@ impl Channel<'_> { @@ -227,7 +225,7 @@ impl Channel<'_> {
/// Refer to [libctru](https://libctru.devkitpro.org/channel_8h.html#a1da3b363c2edfd318c92276b527daae6) for more info.
impl Channel<'_> {
/// 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) };
}
@ -236,7 +234,7 @@ impl Channel<'_> { @@ -236,7 +234,7 @@ impl Channel<'_> {
/// # Notes
///
/// 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) };
}
@ -245,38 +243,38 @@ impl Channel<'_> { @@ -245,38 +243,38 @@ impl Channel<'_> {
/// # Notes
///
/// 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) };
}
/// 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) };
}
/// 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 {
ctru_sys::ndspChnIirBiquadSetParamsHighPassFilter(self.id.into(), cut_off_freq, quality)
};
}
/// 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 {
ctru_sys::ndspChnIirBiquadSetParamsLowPassFilter(self.id.into(), cut_off_freq, quality)
};
}
/// 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 {
ctru_sys::ndspChnIirBiquadSetParamsNotchFilter(self.id.into(), notch_freq, quality)
};
}
/// 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 {
ctru_sys::ndspChnIirBiquadSetParamsBandPassFilter(self.id.into(), mid_freq, quality)
};
@ -284,7 +282,7 @@ impl Channel<'_> { @@ -284,7 +282,7 @@ impl Channel<'_> {
/// Sets the biquad to be a peaking equalizer.
pub fn iir_biquad_set_params_peaking_equalizer(
&self,
&mut self,
central_freq: f32,
quality: f32,
gain: f32,
@ -302,41 +300,161 @@ impl Channel<'_> { @@ -302,41 +300,161 @@ impl Channel<'_> {
impl AudioFormat {
/// Returns the amount of bytes needed to store one sample
///
/// Eg.
/// 8 bit formats return 1 (byte)
/// 16 bit formats return 2 (bytes)
pub fn sample_size(self) -> u8 {
/// 8 bit mono formats return 1 (byte)
/// 16 bit stereo (dual-channel) formats return 4 (bytes)
pub const fn size(self) -> usize {
match self {
AudioFormat::PCM8Mono => 1,
AudioFormat::PCM16Mono | AudioFormat::PCM8Stereo => 2,
AudioFormat::PCM16Stereo => 4,
Self::PCM8Mono => 1,
Self::PCM16Mono | Self::PCM8Stereo => 2,
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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::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::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::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::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 = {samples_requested}, maximum = {max_samples})"),
}
}
}
impl error::Error for NdspError {}
impl<'ndsp> Drop for Channel<'ndsp> {
fn drop(&mut self) {
self.reset();
}
}
impl Drop for Ndsp {
fn drop(&mut self) {
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 @@ @@ -1,8 +1,8 @@
use super::{AudioFormat, NdspError};
use crate::linear::LinearAllocator;
/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf]
pub struct WaveInfo {
/// Informational struct holding the raw audio data and playback info. This corresponds to [ctru_sys::ndspWaveBuf].
pub struct Wave {
/// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
@ -11,9 +11,9 @@ pub struct WaveInfo { @@ -11,9 +11,9 @@ pub struct WaveInfo {
played_on_channel: Option<u8>,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
/// Enum representing the playback status of a [WaveInfo].
/// Enum representing the playback status of a [Wave].
pub enum WaveStatus {
Free = ctru_sys::NDSP_WBUF_FREE as u8,
Queued = ctru_sys::NDSP_WBUF_QUEUED as u8,
@ -21,14 +21,14 @@ pub enum WaveStatus { @@ -21,14 +21,14 @@ pub enum WaveStatus {
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.
pub fn new(
buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
looping: bool,
) -> 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.
// 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 { @@ -69,10 +69,10 @@ impl WaveInfo {
///
/// # 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.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> {
match self.get_status() {
match self.status() {
WaveStatus::Playing | WaveStatus::Queued => {
Err(NdspError::WaveBusy(self.played_on_channel.unwrap()))
}
@ -81,7 +81,7 @@ impl WaveInfo { @@ -81,7 +81,7 @@ impl WaveInfo {
}
/// Return this wave's playback status.
pub fn get_status(&self) -> WaveStatus {
pub fn status(&self) -> WaveStatus {
self.raw_data.status.try_into().unwrap()
}
@ -90,12 +90,12 @@ impl WaveInfo { @@ -90,12 +90,12 @@ impl WaveInfo {
/// # Notes
///
/// This value varies depending on [Self::set_sample_count].
pub fn get_sample_count(&self) -> u32 {
self.raw_data.nsamples
pub fn sample_count(&self) -> usize {
self.raw_data.nsamples as usize
}
/// Get the format of the audio data.
pub fn get_format(&self) -> AudioFormat {
pub fn format(&self) -> AudioFormat {
self.audio_format
}
@ -117,25 +117,22 @@ impl WaveInfo { @@ -117,25 +117,22 @@ impl WaveInfo {
/// # Errors
///
/// This function will return an error if the sample size exceeds the buffer's capacity
/// or if the WaveInfo is currently queued.
pub fn set_sample_count(&mut self, sample_count: u32) -> Result<(), NdspError> {
match self.get_status() {
/// or if the [Wave] is currently queued.
pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> {
match self.status() {
WaveStatus::Playing | WaveStatus::Queued => {
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 {
return Err(NdspError::SampleCountOutOfBounds(
sample_count,
max_count as u32,
));
if sample_count > max_count {
return Err(NdspError::SampleCountOutOfBounds(sample_count, max_count));
}
self.raw_data.nsamples = sample_count;
self.raw_data.nsamples = sample_count as u32;
Ok(())
}
@ -150,16 +147,16 @@ impl TryFrom<u8> for WaveStatus { @@ -150,16 +147,16 @@ impl TryFrom<u8> for WaveStatus {
1 => Ok(Self::Queued),
2 => Ok(Self::Playing),
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) {
// 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`.
match self.get_status() {
match self.status() {
WaveStatus::Free | WaveStatus::Done => (),
// If the status flag is "unfinished"
_ => {

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

@ -1,56 +1,80 @@ @@ -1,56 +1,80 @@
//! Process Services (PS) module. This is used for miscellaneous utility tasks, but
//! is particularly important because it is used to generate random data, which
//! is required for common things like [`HashMap`](std::collections::HashMap).
//! 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>
use crate::error::ResultCode;
use crate::Result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AESAlgorithm {
CbcEnc,
CbcDec,
CtrEnc,
CtrDec,
CcmEnc,
CcmDec,
CbcEnc = ctru_sys::PS_ALGORITHM_CBC_ENC,
CbcDec = ctru_sys::PS_ALGORITHM_CBC_DEC,
CtrEnc = ctru_sys::PS_ALGORITHM_CTR_ENC,
CtrDec = ctru_sys::PS_ALGORITHM_CTR_DEC,
CcmEnc = ctru_sys::PS_ALGORITHM_CCM_ENC,
CcmDec = ctru_sys::PS_ALGORITHM_CCM_DEC,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum AESKeyType {
Keyslot0D,
Keyslot2D,
Keyslot31,
Keyslot38,
Keyslot32,
Keyslot39Dlp,
Keyslot2E,
KeyslotInvalid,
Keyslot36,
Keyslot39Nfc,
Keyslot0D = ctru_sys::PS_KEYSLOT_0D,
Keyslot2D = ctru_sys::PS_KEYSLOT_2D,
Keyslot2E = ctru_sys::PS_KEYSLOT_2E,
Keyslot31 = ctru_sys::PS_KEYSLOT_31,
Keyslot32 = ctru_sys::PS_KEYSLOT_32,
Keyslot36 = ctru_sys::PS_KEYSLOT_36,
Keyslot38 = ctru_sys::PS_KEYSLOT_38,
Keyslot39Dlp = ctru_sys::PS_KEYSLOT_39_DLP,
Keyslot39Nfc = ctru_sys::PS_KEYSLOT_39_NFC,
KeyslotInvalid = ctru_sys::PS_KEYSLOT_INVALID,
}
pub fn local_friend_code_seed() -> crate::Result<u64> {
let mut seed: u64 = 0;
pub struct Ps(());
ResultCode(unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) })?;
Ok(seed)
}
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;
ResultCode(unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) })?;
Ok(seed)
}
pub fn device_id() -> crate::Result<u32> {
let mut id: u32 = 0;
pub fn device_id(&self) -> crate::Result<u32> {
let mut id: u32 = 0;
ResultCode(unsafe { ctru_sys::PS_GetDeviceId(&mut id) })?;
Ok(id)
ResultCode(unsafe { ctru_sys::PS_GetDeviceId(&mut id) })?;
Ok(id)
}
pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> {
ResultCode(unsafe {
ctru_sys::PS_GenerateRandomBytes(out.as_mut_ptr().cast(), out.len())
})?;
Ok(())
}
}
pub fn generate_random_bytes(out: &mut [u8]) -> crate::Result<()> {
ResultCode(unsafe {
ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32)
})?;
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)]
mod tests {
use std::collections::HashMap;

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

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

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

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
//! Network Socket
use libc::memalign;
use std::net::Ipv4Addr;
use std::sync::Mutex;
@ -6,8 +8,10 @@ use crate::error::ResultCode; @@ -6,8 +8,10 @@ use crate::error::ResultCode;
use crate::services::ServiceReference;
use crate::Error;
/// Soc service. Initializing this service will enable the use of network sockets and utilities
/// such as those found in `std::net`. The service will be closed when this struct is is dropped.
/// Network socket service
///
/// Initializing this service will enable the use of network sockets and utilities
/// such as those found in `std::net`. The service will close once this struct gets dropped.
pub struct Soc {
_service_handler: ServiceReference,
sock_3dslink: libc::c_int,
@ -21,7 +25,7 @@ impl Soc { @@ -21,7 +25,7 @@ impl Soc {
/// # Errors
///
/// 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)
}
@ -103,8 +107,8 @@ mod tests { @@ -103,8 +107,8 @@ mod tests {
#[test]
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 @@ @@ -1,3 +1,5 @@
//! SSLC (TLS) service
// TODO: Implement remaining functions
use crate::error::ResultCode;
@ -5,24 +7,13 @@ use crate::error::ResultCode; @@ -5,24 +7,13 @@ use crate::error::ResultCode;
pub struct SslC(());
impl SslC {
/// Initialize sslc
pub fn init() -> crate::Result<Self> {
/// Initialize the service
pub fn new() -> crate::Result<Self> {
unsafe {
ResultCode(ctru_sys::sslcInit(0))?;
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 {

41
ctru-rs/src/test_runner.rs

@ -6,24 +6,19 @@ use std::io; @@ -6,24 +6,19 @@ use std::io;
use test::{ColorConfig, OutputFormat, TestDescAndFn, TestFn, TestOpts};
use crate::console::Console;
use crate::gfx::Gfx;
use crate::services::hid::{Hid, KeyPad};
use crate::services::Apt;
use crate::prelude::*;
/// 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
/// panic is just treated the same as any normal application panic).
pub(crate) fn run(tests: &[&TestDescAndFn]) {
crate::init();
let gfx = Gfx::init().unwrap();
let hid = Hid::init().unwrap();
let apt = Apt::init().unwrap();
let gfx = Gfx::new().unwrap();
let mut hid = Hid::new().unwrap();
let apt = Apt::new().unwrap();
let mut top_screen = gfx.top_screen.borrow_mut();
top_screen.set_wide_mode(true);
let _console = Console::init(top_screen);
let _console = Console::new(top_screen);
let opts = TestOpts {
force_run_in_process: true,
@ -44,12 +39,10 @@ pub(crate) fn run(tests: &[&TestDescAndFn]) { @@ -44,12 +39,10 @@ pub(crate) fn run(tests: &[&TestDescAndFn]) {
println!("Press START to exit.");
while apt.main_loop() {
gfx.flush_buffers();
gfx.swap_buffers();
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
if hid.keys_down().contains(KeyPad::START) {
break;
}
}
@ -78,25 +71,3 @@ fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn { @@ -78,25 +71,3 @@ fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
_ => 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 @@ @@ -1,10 +1,13 @@
[package]
name = "ctru-sys"
version = "0.4.1"
authors = ["Ronald Kinard <furyhunter600@gmail.com>"]
license = "https://en.wikipedia.org/wiki/Zlib_License"
version = "21.2.0+2.1.2-1"
authors = [ "Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>" ]
license = "Zlib"
links = "ctru"
edition = "2021"
[dependencies]
libc = { version = "0.2.121", default-features = false }
[build-dependencies]
which = "4.4.0"

39
ctru-sys/bindgen.sh

@ -1,7 +1,26 @@ @@ -1,7 +1,26 @@
#!/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" \
--rust-target nightly \
--use-core \
@ -11,16 +30,16 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \ @@ -11,16 +30,16 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \
--ctypes-prefix "::libc" \
--no-prepend-enum-name \
--generate "functions,types,vars" \
--blacklist-type "u(8|16|32|64)" \
--blacklist-type "__builtin_va_list" \
--blacklist-type "__va_list" \
--blocklist-type "u(8|16|32|64)" \
--blocklist-type "__builtin_va_list" \
--blocklist-type "__va_list" \
--opaque-type "MiiData" \
--with-derive-default \
-- \
--target=arm-none-eabi \
--sysroot=$DEVKITARM/arm-none-eabi \
-isystem$DEVKITARM/arm-none-eabi/include \
-I$DEVKITPRO/libctru/include \
--sysroot="$DEVKITARM/arm-none-eabi" \
-isystem"$DEVKITARM/arm-none-eabi/include" \
-I"$DEVKITPRO/libctru/include" \
-mfloat-abi=hard \
-march=armv6k \
-mtune=mpcore \
@ -29,6 +48,10 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \ @@ -29,6 +48,10 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \
-D__3DS__ \
> 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
echo "Generated bindings for ctru-sys version \"${CTRU_SYS_VERSION}.x+${LIBCTRU_VERSION}\""

71
ctru-sys/build.rs

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
use std::env;
use std::error::Error;
use std::process::{Command, Output, Stdio};
fn main() {
let dkp_path = env::var("DEVKITPRO").unwrap();
@ -14,4 +16,73 @@ fn main() { @@ -14,4 +16,73 @@ fn main() {
_ => "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" @@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[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 @@ @@ -0,0 +1 @@
bindings.rs linguist-generated=true

9943
ctru-sys/src/bindings.rs generated

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