Browse Source

Merge branch 'master' into feature/split-top-screen

pull/76/head
Ian Chamberlain 2 years ago
parent
commit
690e145a4e
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 4
      Cargo.toml
  2. 1
      README.md
  3. 4
      ctru-rs/Cargo.toml
  4. 2
      ctru-rs/build.rs
  5. 2
      ctru-rs/examples/camera-image.rs
  6. 14
      ctru-rs/examples/file-explorer.rs
  7. 2
      ctru-rs/examples/hashmaps.rs
  8. 46
      ctru-rs/examples/linear-memory.rs
  9. 43
      ctru-rs/examples/mii-selector.rs
  10. 10
      ctru-rs/examples/network-sockets.rs
  11. 2
      ctru-rs/examples/software-keyboard.rs
  12. 4
      ctru-rs/examples/time-rtc.rs
  13. 186
      ctru-rs/src/applets/mii_selector.rs
  14. 1
      ctru-rs/src/applets/mod.rs
  15. 49
      ctru-rs/src/error.rs
  16. 5
      ctru-rs/src/lib.rs
  17. 42
      ctru-rs/src/linear.rs
  18. 466
      ctru-rs/src/mii.rs
  19. 7
      ctru-rs/src/romfs.rs
  20. 14
      ctru-rs/src/services/apt.rs
  21. 364
      ctru-rs/src/services/cam.rs
  22. 43
      ctru-rs/src/services/cfgu.rs
  23. 2
      ctru-rs/src/services/fs.rs
  24. 7
      ctru-rs/src/services/hid.rs
  25. 22
      ctru-rs/src/services/ps.rs
  26. 6
      ctru-rs/src/services/soc.rs
  27. 17
      ctru-rs/src/services/sslc.rs
  28. 4
      ctru-sys/bindgen.sh
  29. 7
      ctru-sys/docstring-to-rustdoc/Cargo.toml
  30. 31
      ctru-sys/docstring-to-rustdoc/src/main.rs
  31. 67
      ctru-sys/src/bin/docstring-to-rustdoc.rs
  32. 14498
      ctru-sys/src/bindings.rs

4
Cargo.toml

@ -1,6 +1,6 @@
[workspace] [workspace]
members = ["ctru-rs", "ctru-sys"] members = ["ctru-rs", "ctru-sys", "ctru-sys/docstring-to-rustdoc"]
[patch.'https://github.com/Meziu/ctru-rs'] [patch.'https://github.com/rust3ds/ctru-rs']
# Make sure all dependencies use the local ctru-sys package # Make sure all dependencies use the local ctru-sys package
ctru-sys = { path = "ctru-sys" } ctru-sys = { path = "ctru-sys" }

1
README.md

@ -37,4 +37,3 @@ applies to every file in the tree, unless otherwise noted.
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. 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.
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. 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.

4
ctru-rs/Cargo.toml

@ -14,8 +14,8 @@ name = "ctru"
cfg-if = "1.0" cfg-if = "1.0"
ctru-sys = { path = "../ctru-sys", version = "0.4" } ctru-sys = { path = "../ctru-sys", version = "0.4" }
const-zero = "0.1.0" const-zero = "0.1.0"
linker-fix-3ds = { git = "https://github.com/Meziu/rust-linker-fix-3ds.git" } linker-fix-3ds = { git = "https://github.com/rust3ds/rust-linker-fix-3ds.git" }
pthread-3ds = { git = "https://github.com/Meziu/pthread-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" }
libc = "0.2.121" libc = "0.2.121"
bitflags = "1.0.0" bitflags = "1.0.0"
widestring = "0.2.2" widestring = "0.2.2"

2
ctru-rs/build.rs

@ -28,5 +28,5 @@ fn main() {
println!("cargo:rustc-cfg=romfs_exists"); println!("cargo:rustc-cfg=romfs_exists");
} }
println!("cargo:rerun-if-changed={}", manifest_dir); println!("cargo:rerun-if-changed={manifest_dir}");
} }

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

@ -83,7 +83,7 @@ fn main() {
.expect("Failed to take picture"); .expect("Failed to take picture");
} }
let img = convert_image_to_rgb8(&buf, 0, 0, WIDTH as usize, HEIGHT as usize); let img = convert_image_to_rgb8(&buf, 0, 0, WIDTH, HEIGHT);
unsafe { unsafe {
gfx.top_screen gfx.top_screen

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

@ -97,7 +97,7 @@ impl<'a> FileExplorer<'a> {
} }
} }
Err(e) => { Err(e) => {
println!("Failed to read {}: {}", self.path.display(), e) println!("Failed to read {}: {e}", self.path.display())
} }
}; };
@ -111,7 +111,7 @@ impl<'a> FileExplorer<'a> {
for (i, entry) in dir_listing.enumerate() { for (i, entry) in dir_listing.enumerate() {
match entry { match entry {
Ok(entry) => { Ok(entry) => {
println!("{:2} - {}", i, entry.file_name().to_string_lossy()); println!("{i:2} - {}", entry.file_name().to_string_lossy());
self.entries.push(entry); self.entries.push(entry);
if (i + 1) % 20 == 0 { if (i + 1) % 20 == 0 {
@ -119,7 +119,7 @@ impl<'a> FileExplorer<'a> {
} }
} }
Err(e) => { Err(e) => {
println!("{} - Error: {}", i, e); println!("{i} - Error: {e}");
} }
} }
} }
@ -133,7 +133,7 @@ impl<'a> FileExplorer<'a> {
println!("{0:->80}", ""); println!("{0:->80}", "");
} }
Err(err) => { Err(err) => {
println!("Error reading file: {}", err); println!("Error reading file: {err}");
} }
} }
} }
@ -176,7 +176,7 @@ impl<'a> FileExplorer<'a> {
unreachable!() unreachable!()
} }
Err(e) => { Err(e) => {
panic!("Error: {:?}", e) panic!("Error: {e:?}")
} }
} }
} }
@ -185,7 +185,7 @@ impl<'a> FileExplorer<'a> {
let next_path_index: usize = match next_path_index.parse() { let next_path_index: usize = match next_path_index.parse() {
Ok(index) => index, Ok(index) => index,
Err(e) => { Err(e) => {
println!("Number parsing error: {}", e); println!("Number parsing error: {e}");
return; return;
} }
}; };
@ -206,7 +206,7 @@ impl<'a> FileExplorer<'a> {
fn set_exact_path(&mut self, new_path_str: String) { fn set_exact_path(&mut self, new_path_str: String) {
let new_path = Path::new(&new_path_str); let new_path = Path::new(&new_path_str);
if !new_path.is_dir() { if !new_path.is_dir() {
println!("Not a directory: {}", new_path_str); println!("Not a directory: {new_path_str}");
return; return;
} }

2
ctru-rs/examples/hashmaps.rs

@ -17,7 +17,7 @@ fn main() {
map.insert("Another key?", 543); map.insert("Another key?", 543);
map.remove("A Key!"); map.remove("A Key!");
println!("{:#?}", map); println!("{map:#?}");
while apt.main_loop() { while apt.main_loop() {
gfx.flush_buffers(); gfx.flush_buffers();

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

@ -0,0 +1,46 @@
#![feature(allocator_api)]
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());
let linear_space_before = LinearAllocator::free_space();
// Normal `Box` on the heap
let heap_box = Box::new(1492);
// `Box` living on the linear memory sector
let linear_box: Box<i32, LinearAllocator> = Box::new_in(2022, LinearAllocator);
println!("This value is from the heap: {heap_box}");
println!("This value is from the LINEAR memory: {linear_box}");
println!("\nLINEAR space free before allocation: {linear_space_before}");
println!(
"LINEAR space free after allocation: {}",
LinearAllocator::free_space()
);
println!("\x1b[29;16HPress Start to exit");
// Main loop
while apt.main_loop() {
//Scan all the inputs. This should be done once for each frame
hid.scan_input();
if hid.keys_down().contains(KeyPad::KEY_START) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}
}

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

@ -0,0 +1,43 @@
use ctru::applets::mii_selector::MiiSelector;
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());
let mut mii_selector = MiiSelector::init();
mii_selector.set_initial_index(3);
mii_selector.blacklist_user_mii(0.into());
mii_selector.set_title("Great Mii Selector!");
let result = mii_selector.launch().unwrap();
println!("Is Mii selected?: {:?}", result.is_mii_selected);
println!("Mii type: {:?}", result.mii_type);
println!("Name: {:?}", result.mii_data.name);
println!("Author: {:?}", result.mii_data.author_name);
println!(
"Does the Mii have moles?: {:?}",
result.mii_data.mole_details.is_enabled
);
// 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) {
break;
}
// Flush and swap framebuffers
gfx.flush_buffers();
gfx.swap_buffers();
//Wait for VBlank
gfx.wait_for_vblank();
}
}

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

@ -25,19 +25,19 @@ fn main() {
match server.accept() { match server.accept() {
Ok((mut stream, socket_addr)) => { Ok((mut stream, socket_addr)) => {
println!("Got connection from {}", socket_addr); println!("Got connection from {socket_addr}");
let mut buf = [0u8; 4096]; let mut buf = [0u8; 4096];
match stream.read(&mut buf) { match stream.read(&mut buf) {
Ok(_) => { Ok(_) => {
let req_str = String::from_utf8_lossy(&buf); let req_str = String::from_utf8_lossy(&buf);
println!("{}", req_str); println!("{req_str}");
} }
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock { if e.kind() == io::ErrorKind::WouldBlock {
println!("Note: Reading the connection returned ErrorKind::WouldBlock.") println!("Note: Reading the connection returned ErrorKind::WouldBlock.")
} else { } else {
println!("Unable to read stream: {}", e) println!("Unable to read stream: {e}")
} }
} }
} }
@ -45,7 +45,7 @@ fn main() {
let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n"; let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";
if let Err(e) = stream.write(response) { if let Err(e) = stream.write(response) {
println!("Error writing http response: {}", e); println!("Error writing http response: {e}");
} }
stream.shutdown(Shutdown::Both).unwrap(); stream.shutdown(Shutdown::Both).unwrap();
@ -53,7 +53,7 @@ fn main() {
Err(e) => match e.kind() { Err(e) => match e.kind() {
std::io::ErrorKind::WouldBlock => {} std::io::ErrorKind::WouldBlock => {}
_ => { _ => {
println!("Error accepting connection: {}", e); println!("Error accepting connection: {e}");
std::thread::sleep(Duration::from_secs(2)); std::thread::sleep(Duration::from_secs(2));
} }
}, },

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

@ -29,7 +29,7 @@ fn main() {
// Raise the software keyboard. You can perform different actions depending on which // Raise the software keyboard. You can perform different actions depending on which
// software button the user pressed // software button the user pressed
match keyboard.get_utf8(&mut text) { match keyboard.get_utf8(&mut text) {
Ok(Button::Right) => println!("You entered: {}", text), Ok(Button::Right) => println!("You entered: {text}"),
Ok(Button::Left) => println!("Cancelled"), Ok(Button::Left) => println!("Cancelled"),
Ok(Button::Middle) => println!("How did you even press this?"), Ok(Button::Middle) => println!("How did you even press this?"),
Err(_) => println!("Oh noes, an error happened!"), Err(_) => println!("Oh noes, an error happened!"),

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

@ -33,8 +33,8 @@ fn main() {
let day = cur_time.day(); let day = cur_time.day();
let year = cur_time.year(); let year = cur_time.year();
println!("\x1b[1;1H{:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds); println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}");
println!("{} {} {} {}", weekday, month, day, year); println!("{weekday} {month} {day} {year}");
// Flush and swap framebuffers // Flush and swap framebuffers
gfx.flush_buffers(); gfx.flush_buffers();

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

@ -0,0 +1,186 @@
//! Mii Selector applet
//!
//! This module contains the methods to launch the Mii Selector.
use crate::mii::MiiData;
use bitflags::bitflags;
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 {
Index(u32),
All,
}
/// The type of a Mii with their respective data
#[derive(Debug, Clone)]
pub enum MiiType {
Guest { index: u32, name: String },
User,
}
bitflags! {
/// Options for the Mii Selector
pub struct Options: u32 {
/// Show the cancel button
const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL;
/// Make guest Miis selectable
const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS;
/// Show on the top screen
const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP;
/// Start on the guest's page
const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART;
}
}
/// An instance of the Mii Selector
///
/// # Example
/// ```
/// use ctru::applets::mii_selector::MiiSelector;
///
/// let mut mii_selector = MiiSelector::init();
/// mii_selector.set_title("Example Mii selector");
///
/// let result = mii_selector.launch().unwrap();
/// ```
#[derive(Clone, Debug)]
pub struct MiiSelector {
config: Box<ctru_sys::MiiSelectorConf>,
}
/// Return value from a MiiSelector's launch
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct MiiSelectorReturn {
pub mii_data: MiiData,
pub is_mii_selected: bool,
pub mii_type: MiiType,
}
/// Error type for the Mii selector
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiLaunchError {
InvalidChecksum,
}
impl MiiSelector {
/// Initializes a Mii Selector
pub fn init() -> Self {
let mut config = Box::<ctru_sys::MiiSelectorConf>::default();
unsafe {
ctru_sys::miiSelectorInit(config.as_mut());
}
Self { config }
}
/// Set the title of the Mii Selector.
///
/// This function would panic if the given ``&str`` contains NUL bytes.
pub fn set_title(&mut self, text: &str) {
// This can only fail if the text contains NUL bytes in the string... which seems
// unlikely and is documented
let c_text = CString::new(text).expect("Failed to convert the title text into a CString");
unsafe {
ctru_sys::miiSelectorSetTitle(self.config.as_mut(), c_text.as_ptr());
}
}
/// Set the options of the Mii Selector
pub fn set_options(&mut self, options: Options) {
unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits) }
}
/// Whitelist a guest Mii
pub fn whitelist_guest_mii(&mut self, mii_index: MiiConfigIndex) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::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) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::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) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::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) {
let index = match mii_index {
MiiConfigIndex::Index(i) => i,
MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS,
};
unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) }
}
/// Set where the cursor will be.
/// If there's no Mii at that index, the cursor will start at the Mii with the index 0
pub fn set_initial_index(&mut self, index: u32) {
// 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
}
/// Launch the Mii Selector.
/// Returns an error when the checksum of the Mii is invalid.
pub fn launch(&mut self) -> Result<MiiSelectorReturn, MiiLaunchError> {
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)
}
}
}
impl From<ctru_sys::MiiSelectorReturn> for MiiSelectorReturn {
fn from(ret: ctru_sys::MiiSelectorReturn) -> Self {
let raw_mii_data = ret.mii;
let mut guest_mii_name = ret.guest_mii_name;
MiiSelectorReturn {
mii_data: raw_mii_data.into(),
is_mii_selected: ret.no_mii_selected == 0,
mii_type: if ret.guest_mii_index != 0xFFFFFFFF {
MiiType::Guest {
index: ret.guest_mii_index,
name: {
let utf16_be = &mut guest_mii_name;
utf16_be.reverse();
String::from_utf16(utf16_be.as_slice()).unwrap()
},
}
} else {
MiiType::User
},
}
}
}
impl From<u32> for MiiConfigIndex {
fn from(v: u32) -> Self {
Self::Index(v)
}
}

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

@ -1 +1,2 @@
pub mod mii_selector;
pub mod swkbd; pub mod swkbd;

49
ctru-rs/src/error.rs

@ -1,11 +1,48 @@
use std::error; use std::error;
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::ops::{ControlFlow, FromResidual, Try};
use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY}; use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY};
pub type Result<T> = ::std::result::Result<T, Error>; pub type Result<T> = ::std::result::Result<T, Error>;
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
#[repr(transparent)]
pub(crate) struct ResultCode(pub ctru_sys::Result);
impl Try for ResultCode {
type Output = ();
type Residual = Error;
fn from_output(_: Self::Output) -> Self {
Self(0)
}
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
if self.0 < 0 {
ControlFlow::Break(self.into())
} else {
ControlFlow::Continue(())
}
}
}
impl FromResidual for ResultCode {
fn from_residual(e: <Self as Try>::Residual) -> Self {
match e {
Error::Os(result) => Self(result),
_ => unreachable!(),
}
}
}
impl<T> FromResidual<Error> for Result<T> {
fn from_residual(e: Error) -> Self {
Err(e)
}
}
/// The error type returned by all libctru functions. /// The error type returned by all libctru functions.
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
@ -39,12 +76,18 @@ impl From<ctru_sys::Result> for Error {
} }
} }
impl From<ResultCode> for Error {
fn from(err: ResultCode) -> Self {
Self::Os(err.0)
}
}
impl fmt::Debug for Error { impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
&Self::Os(err) => f &Self::Os(err) => f
.debug_struct("Error") .debug_struct("Error")
.field("raw", &format_args!("{:#08X}", err)) .field("raw", &format_args!("{err:#08X}"))
.field("description", &R_DESCRIPTION(err)) .field("description", &R_DESCRIPTION(err))
.field("module", &R_MODULE(err)) .field("module", &R_MODULE(err))
.field("summary", &R_SUMMARY(err)) .field("summary", &R_SUMMARY(err))
@ -63,8 +106,8 @@ impl fmt::Debug for Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
&Self::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), &Self::Os(err) => write!(f, "libctru result code: 0x{err:08X}"),
Self::Libc(err) => write!(f, "{}", err), Self::Libc(err) => write!(f, "{err}"),
Self::ServiceAlreadyActive => write!(f, "Service already active"), Self::ServiceAlreadyActive => write!(f, "Service already active"),
Self::OutputAlreadyRedirected => { Self::OutputAlreadyRedirected => {
write!(f, "output streams are already redirected to 3dslink") write!(f, "output streams are already redirected to 3dslink")

5
ctru-rs/src/lib.rs

@ -2,6 +2,9 @@
#![crate_name = "ctru"] #![crate_name = "ctru"]
#![feature(test)] #![feature(test)]
#![feature(custom_test_frameworks)] #![feature(custom_test_frameworks)]
#![feature(try_trait_v2)]
#![feature(allocator_api)]
#![feature(nonnull_slice_from_raw_parts)]
#![test_runner(test_runner::run)] #![test_runner(test_runner::run)]
extern "C" fn services_deinit() { extern "C" fn services_deinit() {
@ -75,6 +78,8 @@ pub mod applets;
pub mod console; pub mod console;
pub mod error; pub mod error;
pub mod gfx; pub mod gfx;
pub mod linear;
pub mod mii;
pub mod prelude; pub mod prelude;
pub mod services; pub mod services;

42
ctru-rs/src/linear.rs

@ -0,0 +1,42 @@
//! Linear memory allocator
//!
//! Linear memory is a sector of the 3DS' RAM that binds virtual addresses exactly to the physical address.
//! As such, it is used for fast and safe memory sharing between services (and is especially needed for GPU and DSP).
//!
//! Resources:<br>
//! <https://github.com/devkitPro/libctru/blob/master/libctru/source/allocator/linear.cpp><br>
//! <https://www.3dbrew.org/wiki/Memory_layout>
use std::alloc::{AllocError, Allocator, Layout};
use std::ptr::NonNull;
// Implementing an `std::alloc::Allocator` type is the best way to handle this case, since it gives
// us full control over the normal `std` implementations (like `Box`). The only issue is that this is another unstable feature to add.
// Sadly the linear memory allocator included in `libctru` doesn't implement `linearRealloc` at the time of these additions,
// but the default fallback of the `std` will take care of that for us.
/// [`std::alloc::Allocator`] struct for LINEAR memory
/// To use this struct the main crate must activate the `allocator_api` unstable feature.
pub struct LinearAllocator;
impl LinearAllocator {
/// Returns the amount of free space left in the LINEAR sector
pub fn free_space() -> u32 {
unsafe { ctru_sys::linearSpaceFree() }
}
}
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) };
NonNull::new(pointer.cast())
.map(|ptr| NonNull::slice_from_raw_parts(ptr, layout.size()))
.ok_or(AllocError)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
ctru_sys::linearFree(ptr.as_ptr().cast());
}
}

466
ctru-rs/src/mii.rs

@ -0,0 +1,466 @@
//! Mii Data
//!
//! This module contains the structs that represent all the data of a Mii.
//! This data is given by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector)
/// Represents the region lock of the console
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RegionLock {
None,
Japan,
USA,
Europe,
}
/// Represent the charset of the console
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Charset {
JapanUSAEurope,
China,
Korea,
Taiwan,
}
/// Represents the options of the Mii
#[derive(Copy, Clone, Debug)]
pub struct MiiDataOptions {
pub is_copying_allowed: bool,
pub is_profanity_flag_enabled: bool,
pub region_lock: RegionLock,
pub charset: Charset,
}
/// Represents the position that the Mii has on the selector
#[derive(Copy, Clone, Debug)]
pub struct SelectorPosition {
pub page_index: u8,
pub slot_index: u8,
}
/// Represents the kind of origin console
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OriginConsole {
Wii,
DSi,
/// Both New 3DS and Old 3DS
N3DS,
WiiUSwitch,
}
/// Represents the identity of the origin console
#[derive(Copy, Clone, Debug)]
pub struct ConsoleIdentity {
pub origin_console: OriginConsole,
}
/// Represents the sex of the Mii
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MiiSex {
Male,
Female,
}
/// Represents the details of the Mii
#[derive(Copy, Clone, Debug)]
pub struct Details {
pub sex: MiiSex,
pub birthday_month: u8,
pub birthday_day: u8,
pub shirt_color: u8,
pub is_favorite: bool,
}
/// Represents the face style of the Mii
#[derive(Copy, Clone, Debug)]
pub struct FaceStyle {
pub is_sharing_enabled: bool,
pub shape: u8,
pub skin_color: u8,
}
/// Represents the face details of the Mii
#[derive(Copy, Clone, Debug)]
pub struct FaceDetails {
pub style: FaceStyle,
pub wrinkles: u8,
pub makeup: u8,
}
/// Represents the hair details of the Mii
#[derive(Copy, Clone, Debug)]
pub struct HairDetails {
pub style: u8,
pub color: u8,
pub is_flipped: bool,
}
/// Represents the eye details of the Mii
#[derive(Copy, Clone, Debug)]
pub struct EyeDetails {
pub style: u8,
pub color: u8,
pub scale: u8,
pub y_scale: u8,
pub rotation: u8,
/// Spacing between the eyes
pub x_spacing: u8,
pub y_position: u8,
}
/// Represents the eyebrow details of the Mii
#[derive(Copy, Clone, Debug)]
pub struct EyebrowDetails {
pub style: u8,
pub color: u8,
pub scale: u8,
pub y_scale: u8,
pub rotation: u8,
/// Spacing between the eyebrows
pub x_spacing: u8,
pub y_position: u8,
}
/// Represents the details of the nose
#[derive(Copy, Clone, Debug)]
pub struct NoseDetails {
pub style: u8,
pub scale: u8,
pub y_position: u8,
}
/// Represents the details of the mouth
#[derive(Copy, Clone, Debug)]
pub struct MouthDetails {
pub style: u8,
pub color: u8,
pub scale: u8,
pub y_scale: u8,
}
/// Represents the details of the mustache
#[derive(Copy, Clone, Debug)]
pub struct MustacheDetails {
pub mouth_y_position: u8,
pub mustache_style: u8,
}
/// Represents the details of the beard
#[derive(Copy, Clone, Debug)]
pub struct BeardDetails {
pub style: u8,
pub color: u8,
pub scale: u8,
pub y_position: u8,
}
/// Represents the details of the glass
#[derive(Copy, Clone, Debug)]
pub struct GlassDetails {
pub style: u8,
pub color: u8,
pub scale: u8,
pub y_position: u8,
}
/// Represents the details of the mole
#[derive(Copy, Clone, Debug)]
pub struct MoleDetails {
pub is_enabled: bool,
pub scale: u8,
pub x_position: u8,
pub y_position: u8,
}
/// Represents all the data of a Mii
///
/// Some values are not ordered _like_ the Mii Editor UI. The mapped values can be seen here:
/// <https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values>
///
/// This struct is returned by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector)
#[derive(Clone, Debug)]
pub struct MiiData {
pub options: MiiDataOptions,
pub selector_position: SelectorPosition,
pub console_identity: ConsoleIdentity,
/// Unique system ID, not dependant on the MAC address
pub system_id: [u8; 8],
pub mac_address: [u8; 6],
pub details: Details,
pub name: String,
pub height: u8,
pub width: u8,
pub face_details: FaceDetails,
pub hair_details: HairDetails,
pub eye_details: EyeDetails,
pub eyebrow_details: EyebrowDetails,
pub nose_details: NoseDetails,
pub mouth_details: MouthDetails,
pub mustache_details: MustacheDetails,
pub beard_details: BeardDetails,
pub glass_details: GlassDetails,
pub mole_details: MoleDetails,
pub author_name: String,
}
impl From<ctru_sys::MiiData> for MiiData {
fn from(mii_data: ctru_sys::MiiData) -> Self {
let raw_mii_data = mii_data._bindgen_opaque_blob;
// Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii
let raw_options = vec_bit(raw_mii_data[0x1]);
let raw_position = vec_bit(raw_mii_data[0x2]);
let raw_device = vec_bit(raw_mii_data[0x3]);
let system_id = [
raw_mii_data[0x4],
raw_mii_data[0x5],
raw_mii_data[0x6],
raw_mii_data[0x7],
raw_mii_data[0x8],
raw_mii_data[0x9],
raw_mii_data[0xA],
raw_mii_data[0xB],
];
let mac_address = [
raw_mii_data[0x10],
raw_mii_data[0x11],
raw_mii_data[0x12],
raw_mii_data[0x13],
raw_mii_data[0x14],
raw_mii_data[0x15],
];
let raw_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x18, 0x19])
.try_into()
.unwrap();
let raw_utf16_name = &raw_mii_data[0x1A..0x2D];
let height = raw_mii_data[0x2E];
let width = raw_mii_data[0x2F];
let raw_face_style = vec_bit(raw_mii_data[0x30]);
let raw_face_details = vec_bit(raw_mii_data[0x31]);
let raw_hair_details = vec_bit(raw_mii_data[0x33]);
let raw_eye_details: [bool; 32] =
get_and_concat_vec_bit(&raw_mii_data, &[0x34, 0x35, 0x36, 0x37])
.try_into()
.unwrap();
let raw_eyebrow_details: [bool; 32] =
get_and_concat_vec_bit(&raw_mii_data, &[0x38, 0x39, 0x3A, 0x3B])
.try_into()
.unwrap();
let raw_nose_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x3C, 0x3D])
.try_into()
.unwrap();
let raw_mouth_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x3E, 0x3F])
.try_into()
.unwrap();
let raw_mustache_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x40, 0x41])
.try_into()
.unwrap();
let raw_beard_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x42, 0x42])
.try_into()
.unwrap();
let raw_glass_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x44, 0x45])
.try_into()
.unwrap();
let raw_mole_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x46, 0x47])
.try_into()
.unwrap();
let raw_utf16_author = &raw_mii_data[0x48..0x5C];
let name = utf16_byte_pairs_to_string(raw_utf16_name);
let author_name = utf16_byte_pairs_to_string(raw_utf16_author);
let options = MiiDataOptions {
is_copying_allowed: raw_options[0],
is_profanity_flag_enabled: raw_options[1],
region_lock: {
match (raw_options[3], raw_options[2]) {
(false, false) => RegionLock::None,
(false, true) => RegionLock::Japan,
(true, false) => RegionLock::USA,
(true, true) => RegionLock::Europe,
}
},
charset: {
match (raw_options[5], raw_options[4]) {
(false, false) => Charset::JapanUSAEurope,
(false, true) => Charset::China,
(true, false) => Charset::Korea,
(true, true) => Charset::Taiwan,
}
},
};
let selector_position = SelectorPosition {
page_index: partial_u8_bits_to_u8(&raw_position[0..=3]),
slot_index: partial_u8_bits_to_u8(&raw_position[4..=7]),
};
let console_identity = ConsoleIdentity {
origin_console: {
match (raw_device[6], raw_device[5], raw_device[4]) {
(false, false, true) => OriginConsole::Wii,
(false, true, false) => OriginConsole::DSi,
(false, true, true) => OriginConsole::N3DS,
_ => OriginConsole::WiiUSwitch,
}
},
};
let details = Details {
sex: {
match raw_details[0] {
true => MiiSex::Female,
false => MiiSex::Male,
}
},
birthday_month: partial_u8_bits_to_u8(&raw_details[1..=4]),
birthday_day: partial_u8_bits_to_u8(&raw_details[5..=9]),
shirt_color: partial_u8_bits_to_u8(&raw_details[10..=13]),
is_favorite: raw_details[14],
};
let face_details = FaceDetails {
style: FaceStyle {
is_sharing_enabled: !raw_face_style[0],
shape: partial_u8_bits_to_u8(&raw_face_style[1..=4]),
skin_color: partial_u8_bits_to_u8(&raw_face_style[5..=7]),
},
wrinkles: partial_u8_bits_to_u8(&raw_face_details[0..=3]),
makeup: partial_u8_bits_to_u8(&raw_face_details[4..=7]),
};
let hair_details = HairDetails {
style: raw_mii_data[0x32],
color: partial_u8_bits_to_u8(&raw_hair_details[0..=2]),
is_flipped: raw_hair_details[3],
};
let eye_details = EyeDetails {
style: partial_u8_bits_to_u8(&raw_eye_details[0..=5]),
color: partial_u8_bits_to_u8(&raw_eye_details[6..=8]),
scale: partial_u8_bits_to_u8(&raw_eye_details[9..=12]),
y_scale: partial_u8_bits_to_u8(&raw_eye_details[13..=15]),
rotation: partial_u8_bits_to_u8(&raw_eye_details[16..=20]),
x_spacing: partial_u8_bits_to_u8(&raw_eye_details[21..=24]),
y_position: partial_u8_bits_to_u8(&raw_eye_details[25..=29]),
};
let eyebrow_details = EyebrowDetails {
style: partial_u8_bits_to_u8(&raw_eyebrow_details[0..=4]),
color: partial_u8_bits_to_u8(&raw_eyebrow_details[5..=7]),
scale: partial_u8_bits_to_u8(&raw_eyebrow_details[8..=11]),
// Bits are skipped here, following the 3dbrew wiki:
// https://www.3dbrew.org/wiki/Mii#Mii_format offset 0x38
y_scale: partial_u8_bits_to_u8(&raw_eyebrow_details[12..=14]),
rotation: partial_u8_bits_to_u8(&raw_eyebrow_details[16..=19]),
x_spacing: partial_u8_bits_to_u8(&raw_eyebrow_details[21..=24]),
y_position: partial_u8_bits_to_u8(&raw_eyebrow_details[25..=29]),
};
let nose_details = NoseDetails {
style: partial_u8_bits_to_u8(&raw_nose_details[0..=4]),
scale: partial_u8_bits_to_u8(&raw_nose_details[5..=8]),
y_position: partial_u8_bits_to_u8(&raw_nose_details[9..=13]),
};
let mouth_details = MouthDetails {
style: partial_u8_bits_to_u8(&raw_mouth_details[0..=5]),
color: partial_u8_bits_to_u8(&raw_mouth_details[6..=8]),
scale: partial_u8_bits_to_u8(&raw_mouth_details[9..=12]),
y_scale: partial_u8_bits_to_u8(&raw_mouth_details[13..=15]),
};
let mustache_details = MustacheDetails {
mouth_y_position: partial_u8_bits_to_u8(&raw_mustache_details[0..=4]),
mustache_style: partial_u8_bits_to_u8(&raw_mustache_details[5..=7]),
};
let beard_details = BeardDetails {
style: partial_u8_bits_to_u8(&raw_beard_details[0..=2]),
color: partial_u8_bits_to_u8(&raw_beard_details[3..=5]),
scale: partial_u8_bits_to_u8(&raw_beard_details[6..=9]),
y_position: partial_u8_bits_to_u8(&raw_beard_details[10..=14]),
};
let glass_details = GlassDetails {
style: partial_u8_bits_to_u8(&raw_glass_details[0..=3]),
color: partial_u8_bits_to_u8(&raw_glass_details[4..=6]),
scale: partial_u8_bits_to_u8(&raw_glass_details[7..=10]),
y_position: partial_u8_bits_to_u8(&raw_glass_details[11..=15]),
};
let mole_details = MoleDetails {
is_enabled: raw_mole_details[0],
scale: partial_u8_bits_to_u8(&raw_mole_details[1..=4]),
x_position: partial_u8_bits_to_u8(&raw_mole_details[5..=9]),
y_position: partial_u8_bits_to_u8(&raw_mole_details[10..=14]),
};
MiiData {
options,
selector_position,
console_identity,
system_id,
mac_address,
details,
name,
height,
width,
face_details,
hair_details,
eye_details,
eyebrow_details,
nose_details,
mouth_details,
mustache_details,
beard_details,
glass_details,
mole_details,
author_name,
}
}
}
// Methods to handle "_bits_", ``bitvec`` cannot compile to 32-bit targets, so I had to create a few
// helper methods
/// Transforms a u8 into a [bool; 8]
fn vec_bit(data: u8) -> [bool; 8] {
(0..8)
.map(|i| (data & (1 << i)) != 0)
.collect::<Vec<bool>>()
.try_into()
.unwrap()
}
/// Transforms a [bool; 8] into an u8
fn vec_bit_to_u8(data: [bool; 8]) -> u8 {
data.into_iter()
.fold(0, |result, bit| (result << 1) ^ u8::from(bit))
}
/// Given a series of LE bits, they are filled until a full LE u8 is reached
fn partial_u8_bits_to_u8(data: &[bool]) -> u8 {
let leading_zeroes_to_add = 8 - data.len();
let leading_zeroes = vec![false; leading_zeroes_to_add];
vec_bit_to_u8([data, &leading_zeroes].concat().try_into().unwrap())
}
/// UTF-16 Strings are give in pairs of bytes (u8), this converts them into an _actual_ string
fn utf16_byte_pairs_to_string(data: &[u8]) -> String {
let raw_utf16_composed = data
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect::<Vec<u16>>();
String::from_utf16_lossy(raw_utf16_composed.as_slice()).replace('\0', "")
}
/// Gets the values from the slice and concatenates them
fn get_and_concat_vec_bit(data: &[u8], get_values: &[usize]) -> Vec<bool> {
get_values.iter().flat_map(|v| vec_bit(data[*v])).collect()
}

7
ctru-rs/src/romfs.rs

@ -10,6 +10,7 @@
//! romfs_dir = "romfs" //! romfs_dir = "romfs"
//! ``` //! ```
use crate::error::ResultCode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::ffi::CStr; use std::ffi::CStr;
use std::sync::Mutex; use std::sync::Mutex;
@ -30,11 +31,7 @@ impl RomFS {
true, true,
|| { || {
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap();
let r = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?;
if r < 0 {
return Err(r.into());
}
Ok(()) Ok(())
}, },
|| { || {

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

@ -1,16 +1,14 @@
use crate::error::ResultCode;
pub struct Apt(()); pub struct Apt(());
impl Apt { impl Apt {
pub fn init() -> crate::Result<Apt> { pub fn init() -> crate::Result<Apt> {
unsafe { unsafe {
let r = ctru_sys::aptInit(); ResultCode(ctru_sys::aptInit())?;
if r < 0 {
Err(r.into())
} else {
Ok(Apt(())) Ok(Apt(()))
} }
} }
}
pub fn main_loop(&self) -> bool { pub fn main_loop(&self) -> bool {
unsafe { ctru_sys::aptMainLoop() } unsafe { ctru_sys::aptMainLoop() }
@ -18,15 +16,11 @@ impl Apt {
pub fn set_app_cpu_time_limit(&self, percent: u32) -> crate::Result<()> { pub fn set_app_cpu_time_limit(&self, percent: u32) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::APT_SetAppCpuTimeLimit(percent); ResultCode(ctru_sys::APT_SetAppCpuTimeLimit(percent))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
} }
}
impl Drop for Apt { impl Drop for Apt {
fn drop(&mut self) { fn drop(&mut self) {

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

@ -3,6 +3,7 @@
//! The CAM service provides access to the cameras. Cameras can return 2D images //! 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. //! in the form of byte vectors which can be used for display or other usages.
use crate::error::ResultCode;
use crate::services::gspgpu::FramebufferFormat; use crate::services::gspgpu::FramebufferFormat;
use bitflags::bitflags; use bitflags::bitflags;
use ctru_sys::Handle; use ctru_sys::Handle;
@ -267,28 +268,14 @@ impl BothOutwardCam {
brightness_synchronization: bool, brightness_synchronization: bool,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetBrightnessSynchronization(brightness_synchronization); ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization(
if r < 0 { brightness_synchronization,
Err(r.into()) ))?;
} else {
Ok(()) Ok(())
} }
} }
} }
fn synchronize_vsync_timing(&self) -> crate::Result<()> {
unsafe {
let r =
ctru_sys::CAMU_SynchronizeVsyncTiming(ctru_sys::SELECT_OUT1, ctru_sys::SELECT_OUT2);
if r < 0 {
Err(r.into())
} else {
Ok(())
}
}
}
}
impl Camera for BothOutwardCam { impl Camera for BothOutwardCam {
fn camera_as_raw(&self) -> ctru_sys::u32_ { fn camera_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::SELECT_OUT1_OUT2 ctru_sys::SELECT_OUT1_OUT2
@ -313,72 +300,55 @@ pub trait Camera {
fn is_busy(&self) -> crate::Result<bool> { fn is_busy(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut is_busy = false; let mut is_busy = false;
let r = ctru_sys::CAMU_IsBusy(&mut is_busy, self.port_as_raw()); ResultCode(ctru_sys::CAMU_IsBusy(&mut is_busy, self.port_as_raw()))?;
if r < 0 {
Err(r.into())
} else {
Ok(is_busy) Ok(is_busy)
} }
} }
}
/// Returns the maximum amount of transfer bytes based on the view size, trimming, and other /// Returns the maximum amount of transfer bytes based on the view size, trimming, and other
/// modifications set to the camera /// modifications set to the camera
fn get_transfer_bytes(&self) -> crate::Result<u32> { fn get_transfer_bytes(&self) -> crate::Result<u32> {
unsafe { unsafe {
let mut transfer_bytes = 0; let mut transfer_bytes = 0;
let r = ctru_sys::CAMU_GetTransferBytes(&mut transfer_bytes, self.port_as_raw()); ResultCode(ctru_sys::CAMU_GetTransferBytes(
if r < 0 { &mut transfer_bytes,
Err(r.into()) self.port_as_raw(),
} else { ))?;
Ok(transfer_bytes) Ok(transfer_bytes)
} }
} }
}
/// Sets whether or not the camera should trim the image based on parameters set by /// Sets whether or not the camera should trim the image based on parameters set by
/// [Camera::set_trimming_params] /// [Camera::set_trimming_params]
fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> { fn set_trimming(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetTrimming(self.port_as_raw(), enabled); ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), enabled))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Returns whether or not trimming is currently enabled for the camera /// Returns whether or not trimming is currently enabled for the camera
fn is_trimming_enabled(&self) -> crate::Result<bool> { fn is_trimming_enabled(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut trimming = false; let mut trimming = false;
let r = ctru_sys::CAMU_IsTrimming(&mut trimming, self.port_as_raw()); ResultCode(ctru_sys::CAMU_IsTrimming(&mut trimming, self.port_as_raw()))?;
if r < 0 {
Err(r.into())
} else {
Ok(trimming) Ok(trimming)
} }
} }
}
/// Sets trimming parameters based on coordinates specified inside a [CamTrimmingParams] /// Sets trimming parameters based on coordinates specified inside a [CamTrimmingParams]
fn set_trimming_params(&mut self, params: CamTrimmingParams) -> crate::Result<()> { fn set_trimming_params(&mut self, params: CamTrimmingParams) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetTrimmingParams( ResultCode(ctru_sys::CAMU_SetTrimmingParams(
self.port_as_raw(), self.port_as_raw(),
params.x_start, params.x_start,
params.y_start, params.y_start,
params.x_end, params.x_end,
params.y_end, params.y_end,
); ))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Returns the set [CamTrimmingParams] from the camera /// Returns the set [CamTrimmingParams] from the camera
fn get_trimming_params(&self) -> crate::Result<CamTrimmingParams> { fn get_trimming_params(&self) -> crate::Result<CamTrimmingParams> {
@ -387,16 +357,14 @@ pub trait Camera {
let mut y_start = 0; let mut y_start = 0;
let mut x_end = 0; let mut x_end = 0;
let mut y_end = 0; let mut y_end = 0;
let r = ctru_sys::CAMU_GetTrimmingParams( ResultCode(ctru_sys::CAMU_GetTrimmingParams(
&mut x_start, &mut x_start,
&mut y_start, &mut y_start,
&mut x_end, &mut x_end,
&mut y_end, &mut y_end,
self.port_as_raw(), self.port_as_raw(),
); ))?;
if r < 0 {
Err(r.into())
} else {
Ok(CamTrimmingParams { Ok(CamTrimmingParams {
x_start, x_start,
y_start, y_start,
@ -405,7 +373,6 @@ pub trait Camera {
}) })
} }
} }
}
/// Sets the trimming parameters revolving around the center of the image. /// Sets the trimming parameters revolving around the center of the image.
/// The new width will be `trim_width / 2` to the left and right of the center. /// The new width will be `trim_width / 2` to the left and right of the center.
@ -418,44 +385,35 @@ pub trait Camera {
cam_height: i16, cam_height: i16,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetTrimmingParamsCenter( ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter(
self.port_as_raw(), self.port_as_raw(),
trim_width, trim_width,
trim_height, trim_height,
cam_width, cam_width,
cam_height, cam_height,
); ))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets the exposure level of the camera /// Sets the exposure level of the camera
fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> { fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetExposure(self.camera_as_raw(), exposure); ResultCode(ctru_sys::CAMU_SetExposure(self.camera_as_raw(), exposure))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets the white balance mod of the camera based on the passed [CamWhiteBalance] argument /// Sets the white balance mod of the camera based on the passed [CamWhiteBalance] argument
fn set_white_balance(&mut self, white_balance: CamWhiteBalance) -> crate::Result<()> { fn set_white_balance(&mut self, white_balance: CamWhiteBalance) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetWhiteBalance(self.camera_as_raw(), white_balance.bits()); ResultCode(ctru_sys::CAMU_SetWhiteBalance(
if r < 0 { self.camera_as_raw(),
Err(r.into()) white_balance.bits(),
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the white balance mode of the camera based on the passed [CamWhiteBalance] argument /// Sets the white balance mode of the camera based on the passed [CamWhiteBalance] argument
// TODO: Explain base up // TODO: Explain base up
@ -464,92 +422,79 @@ pub trait Camera {
white_balance: CamWhiteBalance, white_balance: CamWhiteBalance,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetWhiteBalanceWithoutBaseUp( ResultCode(ctru_sys::CAMU_SetWhiteBalanceWithoutBaseUp(
self.camera_as_raw(), self.camera_as_raw(),
white_balance.bits(), white_balance.bits(),
); ))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets the sharpness of the camera /// Sets the sharpness of the camera
fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> { fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetSharpness(self.camera_as_raw(), sharpness); ResultCode(ctru_sys::CAMU_SetSharpness(self.camera_as_raw(), sharpness))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets whether auto exposure is enabled or disabled for the camera /// Sets whether auto exposure is enabled or disabled for the camera
fn set_auto_exposure(&mut self, enabled: bool) -> crate::Result<()> { fn set_auto_exposure(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetAutoExposure(self.camera_as_raw(), enabled); ResultCode(ctru_sys::CAMU_SetAutoExposure(
if r < 0 { self.camera_as_raw(),
Err(r.into()) enabled,
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Returns true if auto exposure is enabled for the camera /// Returns true if auto exposure is enabled for the camera
fn is_auto_exposure_enabled(&self) -> crate::Result<bool> { fn is_auto_exposure_enabled(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut enabled = false; let mut enabled = false;
let r = ctru_sys::CAMU_IsAutoExposure(&mut enabled, self.camera_as_raw()); ResultCode(ctru_sys::CAMU_IsAutoExposure(
if r < 0 { &mut enabled,
Err(r.into()) self.camera_as_raw(),
} else { ))?;
Ok(enabled) Ok(enabled)
} }
} }
}
/// Sets whether auto white balance is enabled or disabled for the camera /// Sets whether auto white balance is enabled or disabled for the camera
fn set_auto_white_balance(&mut self, enabled: bool) -> crate::Result<()> { fn set_auto_white_balance(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetAutoWhiteBalance(self.camera_as_raw(), enabled); ResultCode(ctru_sys::CAMU_SetAutoWhiteBalance(
if r < 0 { self.camera_as_raw(),
Err(r.into()) enabled,
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Returns true if auto white balance is enabled for the camera /// Returns true if auto white balance is enabled for the camera
fn is_auto_white_balance_enabled(&self) -> crate::Result<bool> { fn is_auto_white_balance_enabled(&self) -> crate::Result<bool> {
unsafe { unsafe {
let mut enabled = false; let mut enabled = false;
let r = ctru_sys::CAMU_IsAutoWhiteBalance(&mut enabled, self.camera_as_raw()); ResultCode(ctru_sys::CAMU_IsAutoWhiteBalance(
if r < 0 { &mut enabled,
Err(r.into()) self.camera_as_raw(),
} else { ))?;
Ok(enabled) Ok(enabled)
} }
} }
}
/// Sets the flip direction of the camera's image based on the passed [CamFlip] argument /// Sets the flip direction of the camera's image based on the passed [CamFlip] argument
fn flip_image(&mut self, flip: CamFlip) -> crate::Result<()> { fn flip_image(&mut self, flip: CamFlip) -> crate::Result<()> {
unsafe { unsafe {
let r = ResultCode(ctru_sys::CAMU_FlipImage(
ctru_sys::CAMU_FlipImage(self.camera_as_raw(), flip.bits(), ctru_sys::CONTEXT_A); self.camera_as_raw(),
if r < 0 { flip.bits(),
Err(r.into()) ctru_sys::CONTEXT_A,
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the image resolution of the camera in detail /// Sets the image resolution of the camera in detail
/// ///
@ -571,7 +516,7 @@ pub trait Camera {
crop_1: (i16, i16), crop_1: (i16, i16),
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetDetailSize( ResultCode(ctru_sys::CAMU_SetDetailSize(
self.camera_as_raw(), self.camera_as_raw(),
width, width,
height, height,
@ -580,105 +525,92 @@ pub trait Camera {
crop_1.0, crop_1.0,
crop_1.1, crop_1.1,
ctru_sys::CONTEXT_A, ctru_sys::CONTEXT_A,
); ))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets the view size of the camera based on the passed [CamSize] argument. /// Sets the view size of the camera based on the passed [CamSize] argument.
fn set_view_size(&mut self, size: CamSize) -> crate::Result<()> { fn set_view_size(&mut self, size: CamSize) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetSize(self.camera_as_raw(), size.bits(), ctru_sys::CONTEXT_A); ResultCode(ctru_sys::CAMU_SetSize(
if r < 0 { self.camera_as_raw(),
Err(r.into()) size.bits(),
} else { ctru_sys::CONTEXT_A,
))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the frame rate of the camera based on the passed [CamFrameRate] argument. /// Sets the frame rate of the camera based on the passed [CamFrameRate] argument.
fn set_frame_rate(&mut self, frame_rate: CamFrameRate) -> crate::Result<()> { fn set_frame_rate(&mut self, frame_rate: CamFrameRate) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetFrameRate(self.camera_as_raw(), frame_rate.bits()); ResultCode(ctru_sys::CAMU_SetFrameRate(
if r < 0 { self.camera_as_raw(),
Err(r.into()) frame_rate.bits(),
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the photo mode of the camera based on the passed [CamPhotoMode] argument. /// Sets the photo mode of the camera based on the passed [CamPhotoMode] argument.
fn set_photo_mode(&mut self, photo_mode: CamPhotoMode) -> crate::Result<()> { fn set_photo_mode(&mut self, photo_mode: CamPhotoMode) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetPhotoMode(self.camera_as_raw(), photo_mode.bits()); ResultCode(ctru_sys::CAMU_SetPhotoMode(
if r < 0 { self.camera_as_raw(),
Err(r.into()) photo_mode.bits(),
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the effect of the camera based on the passed [CamEffect] argument. /// Sets the effect of the camera based on the passed [CamEffect] argument.
/// ///
/// Multiple effects can be set at once by combining the bitflags of [CamEffect] /// Multiple effects can be set at once by combining the bitflags of [CamEffect]
fn set_effect(&mut self, effect: CamEffect) -> crate::Result<()> { fn set_effect(&mut self, effect: CamEffect) -> crate::Result<()> {
unsafe { unsafe {
let r = ResultCode(ctru_sys::CAMU_SetEffect(
ctru_sys::CAMU_SetEffect(self.camera_as_raw(), effect.bits(), ctru_sys::CONTEXT_A); self.camera_as_raw(),
if r < 0 { effect.bits(),
Err(r.into()) ctru_sys::CONTEXT_A,
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the contrast of the camera based on the passed [CamContrast] argument. /// Sets the contrast of the camera based on the passed [CamContrast] argument.
fn set_contrast(&mut self, contrast: CamContrast) -> crate::Result<()> { fn set_contrast(&mut self, contrast: CamContrast) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetContrast(self.camera_as_raw(), contrast.bits()); ResultCode(ctru_sys::CAMU_SetContrast(
if r < 0 { self.camera_as_raw(),
Err(r.into()) contrast.bits(),
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the lens correction of the camera based on the passed [CamLensCorrection] argument. /// Sets the lens correction of the camera based on the passed [CamLensCorrection] argument.
fn set_lens_correction(&mut self, lens_correction: CamLensCorrection) -> crate::Result<()> { fn set_lens_correction(&mut self, lens_correction: CamLensCorrection) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetLensCorrection(self.camera_as_raw(), lens_correction.bits()); ResultCode(ctru_sys::CAMU_SetLensCorrection(
if r < 0 { self.camera_as_raw(),
Err(r.into()) lens_correction.bits(),
} else { ))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the output format of the camera based on the passed [CamOutputFormat] argument. /// Sets the output format of the camera based on the passed [CamOutputFormat] argument.
fn set_output_format(&mut self, format: CamOutputFormat) -> crate::Result<()> { fn set_output_format(&mut self, format: CamOutputFormat) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetOutputFormat( ResultCode(ctru_sys::CAMU_SetOutputFormat(
self.camera_as_raw(), self.camera_as_raw(),
format.bits(), format.bits(),
ctru_sys::CONTEXT_A, ctru_sys::CONTEXT_A,
); ))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets the region in which auto exposure should be based on. /// Sets the region in which auto exposure should be based on.
/// ///
@ -696,14 +628,16 @@ pub trait Camera {
height: i16, height: i16,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetAutoExposureWindow(self.camera_as_raw(), x, y, width, height); ResultCode(ctru_sys::CAMU_SetAutoExposureWindow(
if r < 0 { self.camera_as_raw(),
Err(r.into()) x,
} else { y,
width,
height,
))?;
Ok(()) Ok(())
} }
} }
}
/// Sets the region in which auto white balance should be based on. /// Sets the region in which auto white balance should be based on.
/// ///
@ -721,27 +655,24 @@ pub trait Camera {
height: i16, height: i16,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ResultCode(ctru_sys::CAMU_SetAutoWhiteBalanceWindow(
ctru_sys::CAMU_SetAutoWhiteBalanceWindow(self.camera_as_raw(), x, y, width, height); self.camera_as_raw(),
if r < 0 { x,
Err(r.into()) y,
} else { width,
height,
))?;
Ok(()) Ok(())
} }
} }
}
/// Sets whether the noise filter should be enabled or disabled for the camera /// Sets whether the noise filter should be enabled or disabled for the camera
fn set_noise_filter(&mut self, enabled: bool) -> crate::Result<()> { fn set_noise_filter(&mut self, enabled: bool) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetNoiseFilter(self.camera_as_raw(), enabled); ResultCode(ctru_sys::CAMU_SetNoiseFilter(self.camera_as_raw(), enabled))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Sets the image quality calibration data for the camera based on the passed in /// Sets the image quality calibration data for the camera based on the passed in
/// [ImageQualityCalibrationData] argument /// [ImageQualityCalibrationData] argument
@ -750,40 +681,28 @@ pub trait Camera {
data: ImageQualityCalibrationData, data: ImageQualityCalibrationData,
) -> crate::Result<()> { ) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetImageQualityCalibrationData(data.0); ResultCode(ctru_sys::CAMU_SetImageQualityCalibrationData(data.0))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Returns the current [ImageQualityCalibrationData] for the camera /// Returns the current [ImageQualityCalibrationData] for the camera
fn get_image_quality_calibration_data(&self) -> crate::Result<ImageQualityCalibrationData> { fn get_image_quality_calibration_data(&self) -> crate::Result<ImageQualityCalibrationData> {
unsafe { unsafe {
let mut data = ImageQualityCalibrationData::default(); let mut data = ImageQualityCalibrationData::default();
let r = ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0); ResultCode(ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0))?;
if r < 0 {
Err(r.into())
} else {
Ok(data) Ok(data)
} }
} }
}
/// Sets the camera as the current sleep camera /// Sets the camera as the current sleep camera
// TODO: Explain sleep camera // TODO: Explain sleep camera
fn set_sleep_camera(&mut self) -> crate::Result<()> { fn set_sleep_camera(&mut self) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_SetSleepCamera(self.camera_as_raw()); ResultCode(ctru_sys::CAMU_SetSleepCamera(self.camera_as_raw()))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
}
/// Requests the camera to take a picture and returns a vector containing the image bytes. /// Requests the camera to take a picture and returns a vector containing the image bytes.
/// ///
@ -804,12 +723,12 @@ pub trait Camera {
) -> crate::Result<Vec<u8>> { ) -> crate::Result<Vec<u8>> {
let transfer_unit = unsafe { let transfer_unit = unsafe {
let mut buf_size = 0; let mut buf_size = 0;
let r = ctru_sys::CAMU_GetMaxBytes(&mut buf_size, width as i16, height as i16); ResultCode(ctru_sys::CAMU_GetMaxBytes(
if r < 0 { &mut buf_size,
Err(crate::Error::from(r)) width as i16,
} else { height as i16,
Ok(buf_size) ))?;
} Ok::<u32, i32>(buf_size)
}?; }?;
let screen_size = u32::from(width) * u32::from(width) * 2; let screen_size = u32::from(width) * u32::from(width) * 2;
@ -817,83 +736,40 @@ pub trait Camera {
let mut buf = vec![0u8; usize::try_from(screen_size).unwrap()]; let mut buf = vec![0u8; usize::try_from(screen_size).unwrap()];
unsafe { unsafe {
let r = ctru_sys::CAMU_SetTransferBytes( ResultCode(ctru_sys::CAMU_SetTransferBytes(
self.port_as_raw(), self.port_as_raw(),
transfer_unit, transfer_unit,
width as i16, width as i16,
height as i16, height as i16,
); ))?;
if r < 0 {
return Err(r.into());
}
}; };
unsafe { unsafe {
let r = ctru_sys::CAMU_Activate(self.camera_as_raw()); ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?;
if r < 0 { ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
return Err(r.into()); ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?;
}
};
unsafe {
let r = ctru_sys::CAMU_ClearBuffer(self.port_as_raw());
if r < 0 {
return Err(r.into());
}
};
unsafe {
let r = ctru_sys::CAMU_StartCapture(self.port_as_raw());
if r < 0 {
return Err(r.into());
}
}; };
let receive_event = unsafe { let receive_event = unsafe {
let mut completion_handle: Handle = 0; let mut completion_handle: Handle = 0;
let r = ctru_sys::CAMU_SetReceiving( ResultCode(ctru_sys::CAMU_SetReceiving(
&mut completion_handle, &mut completion_handle,
buf.as_mut_ptr() as *mut ::libc::c_void, buf.as_mut_ptr() as *mut ::libc::c_void,
self.port_as_raw(), self.port_as_raw(),
screen_size, screen_size,
transfer_unit.try_into().unwrap(), transfer_unit.try_into().unwrap(),
); ))?;
if r < 0 { Ok::<Handle, i32>(completion_handle)
Err(crate::Error::from(r))
} else {
Ok(completion_handle)
}
}?; }?;
unsafe { unsafe {
let r = ctru_sys::svcWaitSynchronization( ResultCode(ctru_sys::svcWaitSynchronization(
receive_event, receive_event,
timeout.as_nanos().try_into().unwrap(), timeout.as_nanos().try_into().unwrap(),
); ))?;
if r < 0 { ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?;
return Err(r.into()); ResultCode(ctru_sys::svcCloseHandle(receive_event))?;
} ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))?;
};
unsafe {
let r = ctru_sys::CAMU_StopCapture(self.port_as_raw());
if r < 0 {
return Err(r.into());
}
};
unsafe {
let r = ctru_sys::svcCloseHandle(receive_event);
if r < 0 {
return Err(r.into());
}
};
unsafe {
let r = ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE);
if r < 0 {
return Err(r.into());
}
}; };
Ok(buf) Ok(buf)
@ -910,10 +786,7 @@ impl Cam {
/// rare in practice. /// rare in practice.
pub fn init() -> crate::Result<Cam> { pub fn init() -> crate::Result<Cam> {
unsafe { unsafe {
let r = ctru_sys::camInit(); ResultCode(ctru_sys::camInit())?;
if r < 0 {
Err(r.into())
} else {
Ok(Cam { Ok(Cam {
inner_cam: InwardCam, inner_cam: InwardCam,
outer_right_cam: OutwardRightCam, outer_right_cam: OutwardRightCam,
@ -922,20 +795,15 @@ impl Cam {
}) })
} }
} }
}
/// Plays the specified sound based on the [CamShutterSoundType] argument /// Plays the specified sound based on the [CamShutterSoundType] argument
pub fn play_shutter_sound(&self, sound: CamShutterSoundType) -> crate::Result<()> { pub fn play_shutter_sound(&self, sound: CamShutterSoundType) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::CAMU_PlayShutterSound(sound.bits()); ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.bits()))?;
if r < 0 {
Err(r.into())
} else {
Ok(()) Ok(())
} }
} }
} }
}
impl Drop for Cam { impl Drop for Cam {
fn drop(&mut self) { fn drop(&mut self) {

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

@ -2,6 +2,8 @@
//! //!
//! This module contains basic methods to retrieve and change configuration from the console. //! This module contains basic methods to retrieve and change configuration from the console.
use crate::error::ResultCode;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(u32)] #[repr(u32)]
pub enum Region { pub enum Region {
@ -60,79 +62,50 @@ impl Cfgu {
/// as many times as desired and the service will not exit until all /// as many times as desired and the service will not exit until all
/// instances of Cfgu drop out of scope. /// instances of Cfgu drop out of scope.
pub fn init() -> crate::Result<Cfgu> { pub fn init() -> crate::Result<Cfgu> {
unsafe { ResultCode(unsafe { ctru_sys::cfguInit() })?;
let r = ctru_sys::cfguInit();
if r < 0 {
Err(r.into())
} else {
Ok(Cfgu(())) Ok(Cfgu(()))
} }
}
}
/// Gets system region from secure info /// Gets system region from secure info
pub fn get_region(&self) -> crate::Result<Region> { pub fn get_region(&self) -> crate::Result<Region> {
let mut region: u8 = 0; let mut region: u8 = 0;
let r = unsafe { ctru_sys::CFGU_SecureInfoGetRegion(&mut region) }; ResultCode(unsafe { ctru_sys::CFGU_SecureInfoGetRegion(&mut region) })?;
if r < 0 {
Err(r.into())
} else {
// The system shouldn't give an invalid value
Ok(Region::try_from(region).unwrap()) Ok(Region::try_from(region).unwrap())
} }
}
/// Gets system's model /// Gets system's model
pub fn get_model(&self) -> crate::Result<SystemModel> { pub fn get_model(&self) -> crate::Result<SystemModel> {
let mut model: u8 = 0; let mut model: u8 = 0;
let r = unsafe { ctru_sys::CFGU_GetSystemModel(&mut model) }; ResultCode(unsafe { ctru_sys::CFGU_GetSystemModel(&mut model) })?;
if r < 0 {
Err(r.into())
} else {
// The system shouldn't give an invalid value
Ok(SystemModel::try_from(model).unwrap()) Ok(SystemModel::try_from(model).unwrap())
} }
}
/// Gets system's language /// Gets system's language
pub fn get_language(&self) -> crate::Result<Language> { pub fn get_language(&self) -> crate::Result<Language> {
let mut language: u8 = 0; let mut language: u8 = 0;
let r = unsafe { ctru_sys::CFGU_GetSystemLanguage(&mut language) }; ResultCode(unsafe { ctru_sys::CFGU_GetSystemLanguage(&mut language) })?;
if r < 0 {
Err(r.into())
} else {
// The system shouldn't give an invalid value
Ok(Language::try_from(language).unwrap()) Ok(Language::try_from(language).unwrap())
} }
}
/// Checks if NFC is supported by the console /// Checks if NFC is supported by the console
pub fn is_nfc_supported(&self) -> crate::Result<bool> { pub fn is_nfc_supported(&self) -> crate::Result<bool> {
let mut supported: bool = false; let mut supported: bool = false;
let r = unsafe { ctru_sys::CFGU_IsNFCSupported(&mut supported) }; ResultCode(unsafe { ctru_sys::CFGU_IsNFCSupported(&mut supported) })?;
if r < 0 {
Err(r.into())
} else {
Ok(supported) Ok(supported)
} }
}
/// Check if the console is from the 2DS family (2DS, New2DS, New2DSXL) /// Check if the console is from the 2DS family (2DS, New2DS, New2DSXL)
pub fn is_2ds_family(&self) -> crate::Result<bool> { pub fn is_2ds_family(&self) -> crate::Result<bool> {
let mut is_2ds_family: u8 = 0; let mut is_2ds_family: u8 = 0;
let r = unsafe { ctru_sys::CFGU_GetModelNintendo2DS(&mut is_2ds_family) }; ResultCode(unsafe { ctru_sys::CFGU_GetModelNintendo2DS(&mut is_2ds_family) })?;
if r < 0 {
Err(r.into())
} else {
Ok(is_2ds_family == 0) Ok(is_2ds_family == 0)
} }
} }
}
impl Drop for Cfgu { impl Drop for Cfgu {
fn drop(&mut self) { fn drop(&mut self) {

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

@ -669,7 +669,7 @@ impl<'a> DirEntry<'a> {
/// The full path is created by joining the original path to `read_dir` /// The full path is created by joining the original path to `read_dir`
/// with the filename of this entry. /// with the filename of this entry.
pub fn path(&self) -> PathBuf { pub fn path(&self) -> PathBuf {
self.root.join(&self.file_name()) self.root.join(self.file_name())
} }
/// Return the metadata for the file that this entry points at. /// Return the metadata for the file that this entry points at.

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

@ -4,6 +4,7 @@
//! and circle pad information. It also provides information from the sound volume slider, //! and circle pad information. It also provides information from the sound volume slider,
//! the accelerometer, and the gyroscope. //! the accelerometer, and the gyroscope.
use crate::error::ResultCode;
bitflags::bitflags! { bitflags::bitflags! {
/// A set of flags corresponding to the button and directional pad /// A set of flags corresponding to the button and directional pad
/// inputs on the 3DS /// inputs on the 3DS
@ -62,14 +63,10 @@ pub struct CirclePosition(ctru_sys::circlePosition);
impl Hid { impl Hid {
pub fn init() -> crate::Result<Hid> { pub fn init() -> crate::Result<Hid> {
unsafe { unsafe {
let r = ctru_sys::hidInit(); ResultCode(ctru_sys::hidInit())?;
if r < 0 {
Err(r.into())
} else {
Ok(Hid(())) Ok(Hid(()))
} }
} }
}
/// Scans the HID service for all user input occurring on the current /// Scans the HID service for all user input occurring on the current
/// frame. This function should be called on every frame when polling /// frame. This function should be called on every frame when polling

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

@ -4,6 +4,8 @@
//! As such, it is initialized by default in `ctru::init` instead of having a safety handler //! As such, it is initialized by default in `ctru::init` instead of having a safety handler
//! See also <https://www.3dbrew.org/wiki/Process_Services> //! See also <https://www.3dbrew.org/wiki/Process_Services>
use crate::error::ResultCode;
#[repr(u32)] #[repr(u32)]
pub enum AESAlgorithm { pub enum AESAlgorithm {
CbcEnc, CbcEnc,
@ -31,33 +33,23 @@ pub enum AESKeyType {
pub fn local_friend_code_seed() -> crate::Result<u64> { pub fn local_friend_code_seed() -> crate::Result<u64> {
let mut seed: u64 = 0; let mut seed: u64 = 0;
let r = unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) }; ResultCode(unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) })?;
if r < 0 {
Err(r.into())
} else {
Ok(seed) Ok(seed)
} }
}
pub fn device_id() -> crate::Result<u32> { pub fn device_id() -> crate::Result<u32> {
let mut id: u32 = 0; let mut id: u32 = 0;
let r = unsafe { ctru_sys::PS_GetDeviceId(&mut id) }; ResultCode(unsafe { ctru_sys::PS_GetDeviceId(&mut id) })?;
if r < 0 {
Err(r.into())
} else {
Ok(id) Ok(id)
} }
}
pub fn generate_random_bytes(out: &mut [u8]) -> crate::Result<()> { pub fn generate_random_bytes(out: &mut [u8]) -> crate::Result<()> {
let r = unsafe { ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32) }; ResultCode(unsafe {
if r < 0 { ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32)
Err(r.into()) })?;
} else {
Ok(()) Ok(())
} }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

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

@ -3,6 +3,7 @@ use once_cell::sync::Lazy;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::Mutex; use std::sync::Mutex;
use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use crate::Error; use crate::Error;
@ -38,10 +39,7 @@ impl Soc {
false, false,
|| { || {
let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32;
let r = unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) }; ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?;
if r < 0 {
return Err(r.into());
}
Ok(()) Ok(())
}, },

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

@ -1,32 +1,29 @@
// TODO: Implement remaining functions // TODO: Implement remaining functions
use crate::error::ResultCode;
pub struct SslC(()); pub struct SslC(());
impl SslC { impl SslC {
/// Initialize sslc /// Initialize sslc
pub fn init() -> crate::Result<Self> { pub fn init() -> crate::Result<Self> {
unsafe { unsafe {
let r = ctru_sys::sslcInit(0); ResultCode(ctru_sys::sslcInit(0))?;
if r < 0 {
Err(r.into())
} else {
Ok(SslC(())) Ok(SslC(()))
} }
} }
}
/// Fill `buf` with `buf.len()` random bytes /// Fill `buf` with `buf.len()` random bytes
pub fn generate_random_data(&self, buf: &mut [u8]) -> crate::Result<()> { pub fn generate_random_data(&self, buf: &mut [u8]) -> crate::Result<()> {
unsafe { unsafe {
let r = ctru_sys::sslcGenerateRandomData(buf.as_ptr() as _, buf.len() as u32); ResultCode(ctru_sys::sslcGenerateRandomData(
if r < 0 { buf.as_ptr() as _,
Err(r.into()) buf.len() as u32,
} else { ))?;
Ok(()) Ok(())
} }
} }
} }
}
impl Drop for SslC { impl Drop for SslC {
fn drop(&mut self) { fn drop(&mut self) {

4
ctru-sys/bindgen.sh

@ -29,4 +29,6 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \
-D__3DS__ \ -D__3DS__ \
> src/bindings.rs > src/bindings.rs
cargo run --bin docstring-to-rustdoc -- src/bindings.rs cargo run --package docstring-to-rustdoc -- src/bindings.rs
cargo fmt --all

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

@ -0,0 +1,7 @@
[package]
name = "docstring-to-rustdoc"
version = "0.1.0"
edition = "2021"
[dependencies]
doxygen-rs = { git = "https://github.com/Techie-Pi/doxygen-rs.git", version = "0.2.2", rev = "573f483ad63ab4662650961998d3ed093a703983" }

31
ctru-sys/docstring-to-rustdoc/src/main.rs

@ -0,0 +1,31 @@
//! This script transforms _some_ Boxygen comments to Rustdoc format
//!
//! # Usage
//!
//! `cargo run --package docstring-to-rustdoc -- [location of the bindings.rs]`
//! Example: `cargo run --package docstring-to-rustdoc -- src/bindings.rs`
//!
//! # Transformations
//!
//! Check [doxygen-rs docs](https://techie-pi.github.io/doxygen-rs/doxygen_rs/)
use std::path::Path;
use std::{env, fs, io};
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
let bindings_path = Path::new(args.get(1).expect("bindings.rs not provided in the args"));
let bindings = fs::read_to_string(bindings_path)?;
let parsed = doxygen_rs::transform_bindgen(bindings.as_str());
let old_bindings_path = bindings_path.to_str().unwrap().to_owned() + ".old";
// If something fails, the original bindings are available at ``bindings.rs.old``
fs::rename(bindings_path, &old_bindings_path)?;
fs::write(bindings_path, parsed)?;
fs::remove_file(&old_bindings_path)?;
Ok(())
}

67
ctru-sys/src/bin/docstring-to-rustdoc.rs

@ -1,67 +0,0 @@
//! This script transforms _some_ Boxygen comments to Rustdoc format
//!
//! # Usage
//!
//! `cargo run --bin docstring-to-rustdoc -- [location of the bindings.rs]`
//! Example: `cargo run --bin docstring-to-rustdoc -- src/bindings.rs`
//!
//! # Transformations
//!
//! The following are _completely_ removed, but _its contents are kept_:
//! * `@brief`
//! * `@ref`
//! * `@note`
//! * `@return`
//! * `@sa`
//! * `<`
//! * `[out]` and `[in]`
//!
//! The followings are _partially_ transformed to Rustdoc format:
//! * `@param`
use std::path::Path;
use std::{env, fs, io};
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
let bindings_path = Path::new(args.get(1).expect("bindings.rs not provided in the args"));
let bindings_string: String = fs::read_to_string(bindings_path)?;
let parsed = bindings_string
.lines()
.map(|v| {
// Only modify lines with the following structure: `` #[doc ... ] ``
if v.trim_start().starts_with("#[doc") && v.trim_end().ends_with(']') {
v.replace("@brief", "")
// Example: ``@param offset Offset of the RomFS...`` -> ``- offset Offset of the RomFS...``
// Will improve in the future
.replace("@param", "* ")
.replace("@ref", "")
.replace("@note", "")
.replace("@return", "")
.replace("@sa", "")
.replace("< ", "")
// Remove things like ``@param[out]``
.replace("[out]", "")
.replace("[in]", "")
// Trim start of the Rustdoc: ``...= " ...`` -> ``...= "...``
.replace("= \" ", "= \"")
// Double pass because _most_ annotations are at the start
.replace("= \" ", "= \"")
} else {
String::from(v)
}
})
.map(|v| v + "\n")
.collect::<String>();
let old_bindings_path = bindings_path.to_str().unwrap().to_owned() + ".old";
// If something fails, the original bindings are available at ``bindings.rs.old``
fs::rename(bindings_path, &old_bindings_path)?;
fs::write(bindings_path, parsed)?;
fs::remove_file(&old_bindings_path)?;
Ok(())
}

14498
ctru-sys/src/bindings.rs

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