Compare commits

...

66 Commits

Author SHA1 Message Date
xenua 937f1dfd10
fork: update refs 8 months ago
Meziu cd80a338fc
Merge pull request #156 from Jhynjhiruu/feature/uds 9 months ago
Jhynjhiruu fe85aa2cfb Merge branch 'feature/uds' of github.com:Jhynjhiruu/ctru-rs into feature/uds 9 months ago
Jhynjhiruu b9b8d6baf9 Fix nits 9 months ago
Jhynjhiruu dc70dfc771
Merge branch 'master' into feature/uds 9 months ago
Meziu d308205934
Merge pull request #172 from FenrirWolf/more_swkbd_improvements 9 months ago
Fenrir ad8b328387 Use array len instead of libctru constant 9 months ago
Fenrir 476a8120ea Variable name and style fixes 9 months ago
Fenrir d8cffcff61 Remove allocation in Swkbd::configure_button 9 months ago
Fenrir efaccdd2e5 swkbd_input_text is no longer an unsafe fn 9 months ago
Ian Chamberlain 519820bc0f
Merge pull request #168 from rust3ds/testing/add-layout-tests 9 months ago
Jhynjhiruu 33a4b1ca0c
Update uds.rs 9 months ago
Jhynjhiruu 009bc7b4f1 Replace custom types with wrappers 10 months ago
Fenrir e5581b46e7 swkbd_input_text should still be an unsafe fn 10 months ago
Fenrir 01731cac0e Set hint text directly instead of using libctru fn 10 months ago
Fenrir 83ebecbb1f Use Rust strings in set_initial_text 10 months ago
FenrirWolf c63932ccbb
Merge pull request #163 from FenrirWolf/improve_filter_callback 10 months ago
Fenrir c1f0bbd9c6 Properly handle nul termination in swkbd_message_callback 10 months ago
FenrirWolf 4b1c2e0b3c
Merge pull request #162 from FenrirWolf/impl_error_applet 10 months ago
Fenrir 9396939d64 Error messages don't actually get truncated, false alarm everybody 10 months ago
Fenrir b967440eb1 properly handle nul termination in set_text 10 months ago
Fenrir 559c757d35 Refactor some code and improve documentation 10 months ago
Fenrir efbf913380 Move error parsing code into launch_unchecked 10 months ago
Fenrir ffbe6604a6 Improve docs for set_panic_hook 10 months ago
Fenrir 6da884e7b6 Use the previous panic hook instead of just the default 10 months ago
Fenrir 5cec437e1d Reuse default hook instead of custom print method 10 months ago
Fenrir d630c25926 Check for APT init errors in panic hook 10 months ago
Ian Chamberlain 432fca3c16
Enable layout tests for generated bindings 10 months ago
Ian Chamberlain eed5fc9a2e
Merge pull request #161 from adryzz/apt-new-stuff 10 months ago
Jhynjhiruu e0c8a4d8f6
Update ctru-rs/src/services/uds.rs 10 months ago
Fenrir a65b9ed577 use `if let` in panic hook instead of `match` 10 months ago
Fenrir a67c8c0bf6 Make `set_panic_hook` into a safe fn 10 months ago
Fenrir 17dc4c4c76 Add private PopUp::launch_unchecked method 10 months ago
Fenrir d32cd1525f Properly document safety concerns 10 months ago
Fenrir c6d5cdc367 Add error applet panic hook 10 months ago
Fenrir a9ded199fa Handle error codes returned by the Error applet 10 months ago
Fenrir cd30981348 Rename error applet config struct 10 months ago
Lena 80853b2238
Apply apt review suggestion 10 months ago
Fenrir 7a84ce3d70 Use `move` closures in filter callback examples 10 months ago
Fenrir d4ad0ea8b0 Move swkbd_message_callback sanity checks to the top of the fn 10 months ago
Fenrir 1ef2164822 Minor refactor for additonal clarity 10 months ago
Fenrir becdc05abd Rename variables for clarity 10 months ago
Fenrir 2f7a04b08d Add comment clarifying callback pointer safety 10 months ago
Fenrir 3d59bdd2b7 Return Cow<str> from filter callback 10 months ago
Fenrir 032acb1524 Call the filter_callback directly in swkbd_message_callback 10 months ago
Fenrir dea122ad16 Improve filter callback API 10 months ago
FenrirWolf 10163fd225
Merge pull request #165 from FenrirWolf/unsafe_blocks_in_unsafe_fn 10 months ago
Lena e6dddff3c3
add "see also" 10 months ago
Fenrir a18dfc49f7 Require unsafe blocks in unsafe fns 10 months ago
Fenrir a75ac1ffd2 Clarify comment on manually implemented enum types 10 months ago
Lena f780bcedbb
fix review issues 10 months ago
Fenrir dc2959396c Add basic error applet support 10 months ago
Lena 0c55e1ef9b
fix chainloader 10 months ago
Lena 66e5a54952
add simple doc for chainloader 10 months ago
Lena e2364ff5e1
implement chainloader 10 months ago
Lena cc2e535e96
fix apt mutability requirements 10 months ago
Lena 4f3562b523
cargo fmt 10 months ago
Lena c95cd26d5c
add low hanging sleep/homemenu apt functions 10 months ago
Jhynjhiruu 5eda3916ba Fix doc tests 11 months ago
Jhynjhiruu 433f9b0591 Fix lint 11 months ago
Jhynjhiruu 5feb906690 Implement remaining UDS functions (mostly untested) 11 months ago
Jhynjhiruu 5287023486 Add more documentation, add NetworkNodeID enum 11 months ago
Jhynjhiruu 1cdf4737f2 Explain the `username` parameter on `Uds::new()` 11 months ago
Jhynjhiruu bcb2be8cf7 I hate not having rust-analyzer working 11 months ago
Jhynjhiruu 4fd4ef08cb Fix some suggestions (and hopefully rust-analyzer) 11 months ago
Jhynjhiruu f065d0da3d Implement UDS service with example 11 months ago
  1. 5
      .github/workflows/ci.yml
  2. 2
      Cargo.toml
  3. 9
      ctru-rs/Cargo.toml
  4. 301
      ctru-rs/examples/local-networking.rs
  5. 12
      ctru-rs/examples/software-keyboard.rs
  6. 153
      ctru-rs/src/applets/error.rs
  7. 1
      ctru-rs/src/applets/mod.rs
  8. 255
      ctru-rs/src/applets/swkbd.rs
  9. 1
      ctru-rs/src/console.rs
  10. 2
      ctru-rs/src/lib.rs
  11. 4
      ctru-rs/src/linear.rs
  12. 5
      ctru-rs/src/services/am.rs
  13. 87
      ctru-rs/src/services/apt.rs
  14. 2
      ctru-rs/src/services/gfx.rs
  15. 10
      ctru-rs/src/services/ir_user.rs
  16. 1
      ctru-rs/src/services/mod.rs
  17. 1
      ctru-rs/src/services/ndsp/mod.rs
  18. 23
      ctru-rs/src/services/svc.rs
  19. 1382
      ctru-rs/src/services/uds.rs
  20. 6
      ctru-sys/Cargo.toml
  21. 4
      ctru-sys/build.rs
  22. 18
      ctru-sys/src/lib.rs

5
.github/workflows/ci.yml

@ -43,11 +43,16 @@ jobs:
- name: Check formatting - name: Check formatting
run: cargo fmt --all --verbose -- --check run: cargo fmt --all --verbose -- --check
# We always run the next two steps, so that even if formatting fails we
# still get compilation errors for the same run (mainly for matchers).
- name: Cargo check ctru-sys (without tests) - name: Cargo check ctru-sys (without tests)
run: cargo 3ds clippy --package ctru-sys --color=always --verbose run: cargo 3ds clippy --package ctru-sys --color=always --verbose
if: success() || failure()
- name: Cargo check ctru-rs (including tests) - name: Cargo check ctru-rs (including tests)
run: cargo 3ds clippy --package ctru-rs --color=always --verbose --all-targets run: cargo 3ds clippy --package ctru-rs --color=always --verbose --all-targets
if: success() || failure()
test: test:
strategy: strategy:

2
Cargo.toml

@ -3,7 +3,7 @@ members = ["ctru-rs", "ctru-sys"]
default-members = ["ctru-rs", "ctru-sys"] default-members = ["ctru-rs", "ctru-sys"]
resolver = "2" resolver = "2"
[patch.'https://github.com/rust3ds/ctru-rs'] [patch.'https://git.xenua.me/rust3ds/ctru-rs']
# Make sure all dependencies use the local packages. This is needed for things # Make sure all dependencies use the local packages. This is needed for things
# like pthread-3ds that rely on ctru-sys, and test-runner which relies on ctru-rs # like pthread-3ds that rely on ctru-sys, and test-runner which relies on ctru-rs
ctru-rs = { path = "ctru-rs" } ctru-rs = { path = "ctru-rs" }

9
ctru-rs/Cargo.toml

@ -3,7 +3,7 @@ name = "ctru-rs"
version = "0.7.1" version = "0.7.1"
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"] authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "A safe wrapper around libctru" description = "A safe wrapper around libctru"
repository = "https://github.com/rust3ds/ctru-rs" repository = "https://git.xenua.me/rust3ds/ctru-rs"
documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru" documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru"
keywords = ["3ds", "libctru"] keywords = ["3ds", "libctru"]
categories = ["os", "api-bindings", "hardware-support"] categories = ["os", "api-bindings", "hardware-support"]
@ -20,10 +20,11 @@ name = "ctru"
cfg-if = "1.0" cfg-if = "1.0"
ctru-sys = { path = "../ctru-sys", version = "0.5.0" } ctru-sys = { path = "../ctru-sys", version = "0.5.0" }
const-zero = "0.1.0" const-zero = "0.1.0"
shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } shim-3ds = { git = "https://git.xenua.me/rust3ds/shim-3ds.git" }
pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } pthread-3ds = { git = "https://git.xenua.me/rust3ds/pthread-3ds.git" }
libc = "0.2.121" libc = "0.2.121"
bitflags = "2.3.3" bitflags = "2.3.3"
macaddr = "1.0.1"
widestring = "1.0.2" widestring = "1.0.2"
[build-dependencies] [build-dependencies]
@ -35,7 +36,7 @@ cfg-if = "1.0.0"
ferris-says = "0.2.1" ferris-says = "0.2.1"
futures = "0.3" futures = "0.3"
lewton = "0.10.2" lewton = "0.10.2"
test-runner = { git = "https://github.com/rust3ds/test-runner.git" } test-runner = { git = "https://git.xenua.me/rust3ds/test-runner.git" }
time = "0.3.7" time = "0.3.7"
tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] } tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] }

301
ctru-rs/examples/local-networking.rs

@ -0,0 +1,301 @@
//! Local networking example.
//!
//! This example showcases local networking using the UDS module.
use ctru::prelude::*;
use ctru::services::uds::*;
fn handle_status_event(uds: &Uds, prev_node_mask: u16) -> ctru::Result<u16> {
println!("Connection status event signalled");
let status = uds.connection_status()?;
println!("Status: {status:#02X?}");
let left = prev_node_mask & (status.node_bitmask() ^ prev_node_mask);
let joined = status.node_bitmask() & (status.node_bitmask() ^ prev_node_mask);
for i in 0..16 {
if left & (1 << i) != 0 {
println!("Node {} disconnected", i + 1);
}
}
for i in 0..16 {
if joined & (1 << i) != 0 {
println!(
"Node {} connected: {:?}",
i + 1,
uds.node_info(NodeID::Node(i + 1))
);
}
}
Ok(status.node_bitmask())
}
fn main() -> Result<(), Error> {
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!("Local networking demo");
let mut uds = Uds::new(None).unwrap();
println!("UDS initialised");
enum State {
Initialised,
Scanning,
DrawList,
List,
Connect,
Connected,
Create,
Created,
}
let mut state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
let mut networks = vec![];
let mut selected_network = 0;
let mut mode = ConnectionType::Client;
let mut channel = 0;
let data_channel = 1;
let mut prev_node_mask = 0;
while apt.main_loop() {
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
match state {
State::Initialised => {
if hid.keys_down().contains(KeyPad::A) {
state = State::Scanning;
console.clear();
prev_node_mask = 0;
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
console.clear();
prev_node_mask = 0;
}
}
State::Scanning => {
println!("Scanning...");
let nwks = uds.scan(b"HBW\x10", None, None);
match nwks {
Ok(n) => {
if n.is_empty() {
state = State::Initialised;
console.clear();
println!("Scanned successfully; no networks found");
println!("Press A to start scanning or B to create a new network");
} else {
networks = n;
selected_network = 0;
state = State::DrawList;
}
}
Err(e) => {
state = State::Initialised;
console.clear();
eprintln!("Error while scanning: {e}");
println!("Press A to start scanning or B to create a new network");
}
}
}
State::DrawList => {
console.clear();
println!(
"Scanned successfully; {} network{} found",
networks.len(),
if networks.len() == 1 { "" } else { "s" }
);
println!("D-Pad to select, A to connect as client, R + A to connect as spectator, B to create a new network");
for (index, n) in networks.iter().enumerate() {
println!(
"{} Username: {}",
if index == selected_network { ">" } else { " " },
n.nodes()[0].unwrap().username()
);
}
state = State::List;
}
State::List => {
if hid.keys_down().contains(KeyPad::UP) && selected_network > 0 {
selected_network -= 1;
state = State::DrawList;
} else if hid.keys_down().contains(KeyPad::DOWN)
&& selected_network < networks.len() - 1
{
selected_network += 1;
state = State::DrawList;
} else if hid.keys_down().contains(KeyPad::A) {
state = State::Connect;
mode = if hid.keys_held().contains(KeyPad::R) {
ConnectionType::Spectator
} else {
ConnectionType::Client
};
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
}
}
State::Connect => {
let appdata = uds.network_appdata(&networks[selected_network], None)?;
println!("App data: {:02X?}", appdata);
if let Err(e) = uds.connect_network(
&networks[selected_network],
b"udsdemo passphrase c186093cd2652741\0",
mode,
data_channel,
) {
console.clear();
eprintln!("Error while connecting to network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
} else {
channel = uds.channel()?;
println!("Connected using channel {}", channel);
let appdata = uds.appdata(None)?;
println!("App data: {:02X?}", appdata);
if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}
println!("Press A to stop data transfer");
state = State::Connected;
}
}
State::Connected => {
let packet = uds.pull_packet();
match packet {
Ok(p) => {
if let Some((pkt, node)) = p {
println!(
"{:02X}{:02X}{:02X}{:02X} from {:?}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}
if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}
if hid.keys_down().contains(KeyPad::A) {
uds.disconnect_network()?;
state = State::Initialised;
console.clear();
println!("Press A to start scanning or B to create a new network");
} else if !hid.keys_down().is_empty() || !hid.keys_up().is_empty() {
let transfer_data = hid.keys_held().bits();
if mode != ConnectionType::Spectator {
uds.send_packet(
&transfer_data.to_le_bytes(),
NodeID::Broadcast,
data_channel,
SendFlags::Default,
)?;
}
}
}
Err(e) => {
uds.disconnect_network()?;
console.clear();
eprintln!("Error while grabbing packet from network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
State::Create => {
console.clear();
println!("Creating network...");
match uds.create_network(
b"HBW\x10",
None,
None,
b"udsdemo passphrase c186093cd2652741\0",
data_channel,
) {
Ok(_) => {
let appdata = [0x69u8, 0x8a, 0x05, 0x5c]
.into_iter()
.chain((*b"Test appdata.").into_iter())
.chain(std::iter::repeat(0).take(3))
.collect::<Vec<_>>();
uds.set_appdata(&appdata)?;
println!("Press A to stop data transfer");
state = State::Created;
}
Err(e) => {
console.clear();
eprintln!("Error while creating network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
State::Created => {
let packet = uds.pull_packet();
match packet {
Ok(p) => {
if let Some((pkt, node)) = p {
println!(
"{:02X}{:02X}{:02X}{:02X} from {:?}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}
if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}
if hid.keys_down().contains(KeyPad::A) {
uds.destroy_network()?;
state = State::Initialised;
console.clear();
println!("Press A to start scanning or B to create a new network");
} else if !hid.keys_down().is_empty() || !hid.keys_up().is_empty() {
let transfer_data = hid.keys_held().bits();
uds.send_packet(
&transfer_data.to_le_bytes(),
NodeID::Broadcast,
data_channel,
SendFlags::Default,
)?;
}
}
Err(e) => {
uds.destroy_network()?;
console.clear();
eprintln!("Error while grabbing packet from network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
}
}
Ok(())
}

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

@ -5,8 +5,6 @@
use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard}; use ctru::applets::swkbd::{Button, CallbackResult, SoftwareKeyboard};
use ctru::prelude::*; use ctru::prelude::*;
use std::ffi::CString;
fn main() { fn main() {
let apt = Apt::new().unwrap(); let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap(); let mut hid = Hid::new().unwrap();
@ -21,13 +19,9 @@ fn main() {
// Custom filter callback to handle the given input. // Custom filter callback to handle the given input.
// Using this callback it's possible to integrate the applet // Using this callback it's possible to integrate the applet
// with custom error messages when the input is incorrect. // with custom error messages when the input is incorrect.
keyboard.set_filter_callback(Some(Box::new(|str| { keyboard.set_filter_callback(Some(Box::new(move |str| {
// The string is guaranteed to contain valid Unicode text, so we can safely unwrap and use it as a normal `&str`. if str.contains("boo") {
if str.to_str().unwrap().contains("boo") { return (CallbackResult::Retry, Some("Ah, you scared me!".into()));
return (
CallbackResult::Retry,
Some(CString::new("Ah, you scared me!").unwrap()),
);
} }
(CallbackResult::Ok, None) (CallbackResult::Ok, None)

153
ctru-rs/src/applets/error.rs

@ -0,0 +1,153 @@
//! Error applet.
//!
//! This applet displays error text as a pop-up message on the lower screen.
use crate::services::{apt::Apt, gfx::Gfx};
use ctru_sys::errorConf;
/// Configuration struct to set up the Error applet.
#[doc(alias = "errorConf")]
pub struct PopUp {
state: Box<errorConf>,
}
/// Determines whether the Error applet will use word wrapping when displaying a message.
#[doc(alias = "errorType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum WordWrap {
/// Error text is centered in the error applet window and does not use word wrapping.
Disabled = ctru_sys::ERROR_TEXT,
/// Error text starts at the top of the error applet window and uses word wrapping.
Enabled = ctru_sys::ERROR_TEXT_WORD_WRAP,
}
/// Error returned by an unsuccessful [`PopUp::launch()`].
#[doc(alias = "errorReturnCode")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i8)]
pub enum Error {
/// Unknown error occurred.
Unknown = ctru_sys::ERROR_UNKNOWN,
/// Operation not supported.
NotSupported = ctru_sys::ERROR_NOT_SUPPORTED,
/// Home button pressed while [`PopUp`] was running.
HomePressed = ctru_sys::ERROR_HOME_BUTTON,
/// Power button pressed while [`PopUp`] was running.
PowerPressed = ctru_sys::ERROR_POWER_BUTTON,
/// Reset button pressed while [`PopUp`] was running.
ResetPressed = ctru_sys::ERROR_SOFTWARE_RESET,
}
impl PopUp {
/// Initializes the error applet with the provided word wrap setting.
#[doc(alias = "errorInit")]
pub fn new(word_wrap: WordWrap) -> Self {
let mut state = Box::<errorConf>::default();
unsafe { ctru_sys::errorInit(state.as_mut(), word_wrap as _, 0) };
Self { state }
}
/// Sets the error text to display.
///
/// # Notes
///
/// The text will be converted to UTF-16 for display with the applet, and the message will be truncated if it exceeds
/// 1900 UTF-16 code units in length after conversion.
#[doc(alias = "errorText")]
pub fn set_text(&mut self, text: &str) {
for (idx, code_unit) in text
.encode_utf16()
.take(self.state.Text.len() - 1)
.chain(std::iter::once(0))
.enumerate()
{
self.state.Text[idx] = code_unit;
}
}
/// Launches the error applet.
#[doc(alias = "errorDisp")]
pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(), Error> {
unsafe { self.launch_unchecked() }
}
/// Launches the error applet without requiring an [`Apt`] or [`Gfx`] handle.
///
/// # Safety
///
/// Potentially leads to undefined behavior if the aforementioned services are not actually active when the applet launches.
unsafe fn launch_unchecked(&mut self) -> Result<(), Error> {
unsafe { ctru_sys::errorDisp(self.state.as_mut()) };
match self.state.returnCode {
ctru_sys::ERROR_NONE | ctru_sys::ERROR_SUCCESS => Ok(()),
ctru_sys::ERROR_NOT_SUPPORTED => Err(Error::NotSupported),
ctru_sys::ERROR_HOME_BUTTON => Err(Error::HomePressed),
ctru_sys::ERROR_POWER_BUTTON => Err(Error::PowerPressed),
ctru_sys::ERROR_SOFTWARE_RESET => Err(Error::ResetPressed),
_ => Err(Error::Unknown),
}
}
}
/// Sets a custom [panic hook](https://doc.rust-lang.org/std/panic/fn.set_hook.html) that uses the error applet to display panic messages.
///
/// You can also choose to have the previously registered panic hook called along with the error applet popup, which can be useful
/// if you want to use output redirection to display panic messages over `3dslink` or `GDB`.
///
/// You can use [`std::panic::take_hook`](https://doc.rust-lang.org/std/panic/fn.take_hook.html) to unregister the panic hook
/// set by this function.
///
/// # Notes
///
/// * If the [`Gfx`] service is not initialized during a panic, the error applet will not be displayed and the old panic hook will be called.
pub fn set_panic_hook(call_old_hook: bool) {
use crate::services::gfx::GFX_ACTIVE;
use std::sync::TryLockError;
let old_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
// If we get a `WouldBlock` error, we know that the `Gfx` service has been initialized.
// Otherwise fallback to using the old panic hook.
if let (Err(TryLockError::WouldBlock), Ok(_apt)) = (GFX_ACTIVE.try_lock(), Apt::new()) {
if call_old_hook {
old_hook(panic_info);
}
let thread = std::thread::current();
let name = thread.name().unwrap_or("<unnamed>");
let message = format!("thread '{name}' {panic_info}");
let mut popup = PopUp::new(WordWrap::Enabled);
popup.set_text(&message);
unsafe {
let _ = popup.launch_unchecked();
}
} else {
old_hook(panic_info);
}
}));
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotSupported => write!(f, "operation not supported"),
Self::HomePressed => write!(f, "home button pressed while error applet was running"),
Self::PowerPressed => write!(f, "power button pressed while error applet was running"),
Self::ResetPressed => write!(f, "reset button pressed while error applet was running"),
Self::Unknown => write!(f, "an unknown error occurred"),
}
}
}
impl std::error::Error for Error {}

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

@ -8,5 +8,6 @@
//! //!
//! Applets block execution of the thread that launches them as long as the user doesn't close the applet. //! Applets block execution of the thread that launches them as long as the user doesn't close the applet.
pub mod error;
pub mod mii_selector; pub mod mii_selector;
pub mod swkbd; pub mod swkbd;

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

@ -6,27 +6,25 @@
use crate::services::{apt::Apt, gfx::Gfx}; use crate::services::{apt::Apt, gfx::Gfx};
use ctru_sys::{ use ctru_sys::{
aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle, aptLaunchLibraryApplet, aptSetMessageCallback, envGetAptAppId, svcCloseHandle,
svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdExtra, svcCreateMemoryBlock, APT_SendParameter, SwkbdButton, SwkbdDictWord, SwkbdLearningData,
SwkbdLearningData, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, SwkbdState, SwkbdStatusData, APPID_SOFTWARE_KEYBOARD, APTCMD_MESSAGE, NS_APPID,
NS_APPID,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use std::ffi::{CStr, CString}; use std::borrow::Cow;
use std::fmt::Display; use std::fmt::Display;
use std::iter::once; use std::iter::once;
use std::str; use std::str;
type CallbackFunction = dyn Fn(&CStr) -> (CallbackResult, Option<CString>); type CallbackFunction = dyn Fn(&str) -> (CallbackResult, Option<Cow<'static, str>>);
/// Configuration structure to setup the Software Keyboard applet. /// Configuration structure to setup the Software Keyboard applet.
#[doc(alias = "SwkbdState")] #[doc(alias = "SwkbdState")]
pub struct SoftwareKeyboard { pub struct SoftwareKeyboard {
state: Box<SwkbdState>, state: Box<SwkbdState>,
callback: Option<Box<CallbackFunction>>, filter_callback: Option<Box<CallbackFunction>>,
error_message: Option<CString>, initial_text: Option<Cow<'static, str>>,
initial_text: Option<CString>,
} }
/// Configuration structure to setup the Parental Lock applet. /// Configuration structure to setup the Parental Lock applet.
@ -212,9 +210,9 @@ bitflags! {
} }
// Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard. // Internal book-keeping struct used to send data to `aptSetMessageCallback` when calling the software keyboard.
// We only need this because libctru doesn't keep a pointer to the shared memory block in `SwkbdExtra` for whatever reason #[derive(Copy, Clone)]
struct MessageCallbackData { struct MessageCallbackData {
extra: *mut SwkbdExtra, filter_callback: *const Box<CallbackFunction>,
swkbd_shared_mem_ptr: *mut libc::c_void, swkbd_shared_mem_ptr: *mut libc::c_void,
} }
@ -243,8 +241,7 @@ impl SoftwareKeyboard {
ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1); ctru_sys::swkbdInit(state.as_mut(), keyboard_type.into(), buttons.into(), -1);
Self { Self {
state, state,
callback: None, filter_callback: None,
error_message: None,
initial_text: None, initial_text: None,
} }
} }
@ -272,24 +269,15 @@ impl SoftwareKeyboard {
/// # } /// # }
/// ``` /// ```
#[doc(alias = "swkbdInputText")] #[doc(alias = "swkbdInputText")]
pub fn launch(&mut self, _apt: &Apt, _gfx: &Gfx) -> Result<(String, Button), Error> { pub fn launch(&mut self, apt: &Apt, gfx: &Gfx) -> Result<(String, Button), Error> {
let mut output = String::new(); let mut output = String::new();
unsafe { match self.swkbd_input_text(&mut output, apt, gfx) {
// The filter callback gets reset every time the SoftwareKeyboard is used. ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()),
ctru_sys::swkbdSetFilterCallback( ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)),
self.state.as_mut(), ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)),
Some(Self::internal_callback), ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)),
(self as *mut Self).cast(), _ => unreachable!(),
);
match self.swkbd_input_text(&mut output) {
ctru_sys::SWKBD_BUTTON_NONE => Err(self.state.result.into()),
ctru_sys::SWKBD_BUTTON_LEFT => Ok((output, Button::Left)),
ctru_sys::SWKBD_BUTTON_MIDDLE => Ok((output, Button::Middle)),
ctru_sys::SWKBD_BUTTON_RIGHT => Ok((output, Button::Right)),
_ => unreachable!(),
}
} }
} }
@ -355,17 +343,13 @@ impl SoftwareKeyboard {
/// # fn main() { /// # fn main() {
/// # /// #
/// use std::borrow::Cow; /// use std::borrow::Cow;
/// use std::ffi::CString;
/// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult}; /// use ctru::applets::swkbd::{SoftwareKeyboard, CallbackResult};
/// ///
/// let mut keyboard = SoftwareKeyboard::default(); /// let mut keyboard = SoftwareKeyboard::default();
/// ///
/// keyboard.set_filter_callback(Some(Box::new(|str| { /// keyboard.set_filter_callback(Some(Box::new(move |str| {
/// if str.to_str().unwrap().contains("boo") { /// if str.contains("boo") {
/// return ( /// return (CallbackResult::Retry, Some("Ah, you scared me!".into()));
/// CallbackResult::Retry,
/// Some(CString::new("Ah, you scared me!").unwrap()),
/// );
/// } /// }
/// ///
/// (CallbackResult::Ok, None) /// (CallbackResult::Ok, None)
@ -373,45 +357,7 @@ impl SoftwareKeyboard {
/// # /// #
/// # } /// # }
pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) { pub fn set_filter_callback(&mut self, callback: Option<Box<CallbackFunction>>) {
self.callback = callback; self.filter_callback = callback;
}
/// Internal function called by the filter callback.
extern "C" fn internal_callback(
user: *mut libc::c_void,
pp_message: *mut *const libc::c_char,
text: *const libc::c_char,
_text_size: libc::size_t,
) -> ctru_sys::SwkbdCallbackResult {
let this: *mut SoftwareKeyboard = user.cast();
unsafe {
// Reset any leftover error message.
(*this).error_message = None;
let text = CStr::from_ptr(text);
let result = {
// Run the callback if still available.
if let Some(callback) = &mut (*this).callback {
let (res, cstr) = callback(text);
// Due to how `libctru` operates, the user is expected to keep the error message alive until
// the end of the Software Keyboard prompt. We ensure that happens by saving it within the configuration.
(*this).error_message = cstr;
if let Some(newstr) = &(*this).error_message {
*pp_message = newstr.as_ptr();
}
res
} else {
CallbackResult::Ok
}
};
result.into()
}
} }
/// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled. /// Configure the maximum number of digits that can be entered in the keyboard when the [`Filters::DIGITS`] flag is enabled.
@ -444,6 +390,10 @@ impl SoftwareKeyboard {
/// ///
/// The initial text is the text already written when you open the software keyboard. /// The initial text is the text already written when you open the software keyboard.
/// ///
/// # Notes
///
/// Passing [`None`] will clear the initial text.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -453,30 +403,25 @@ impl SoftwareKeyboard {
/// use ctru::applets::swkbd::SoftwareKeyboard; /// use ctru::applets::swkbd::SoftwareKeyboard;
/// let mut keyboard = SoftwareKeyboard::default(); /// let mut keyboard = SoftwareKeyboard::default();
/// ///
/// keyboard.set_initial_text(Some("Write here what you like!")); /// keyboard.set_initial_text(Some("Write here what you like!".into()));
/// # /// #
/// # } /// # }
#[doc(alias = "swkbdSetInitialText")] #[doc(alias = "swkbdSetInitialText")]
pub fn set_initial_text(&mut self, text: Option<&str>) { pub fn set_initial_text(&mut self, text: Option<Cow<'static, str>>) {
if let Some(text) = text { self.initial_text = text;
let initial_text = CString::new(text).unwrap();
unsafe {
ctru_sys::swkbdSetInitialText(self.state.as_mut(), initial_text.as_ptr());
}
self.initial_text = Some(initial_text);
} else {
unsafe { ctru_sys::swkbdSetInitialText(self.state.as_mut(), std::ptr::null()) };
self.initial_text = None;
}
} }
/// Set the hint text for this software keyboard. /// Set the hint text for this software keyboard.
/// ///
/// The hint text is the text shown in gray before any text gets written in the input box. /// The hint text is the text shown in gray before any text gets written in the input box.
/// ///
/// # Notes
///
/// Passing [`None`] will clear the hint text.
///
/// The hint text will be converted to UTF-16 when passed to the software keyboard, and the text will be truncated
/// if the length exceeds 64 code units after conversion.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -486,14 +431,22 @@ impl SoftwareKeyboard {
/// use ctru::applets::swkbd::SoftwareKeyboard; /// use ctru::applets::swkbd::SoftwareKeyboard;
/// let mut keyboard = SoftwareKeyboard::default(); /// let mut keyboard = SoftwareKeyboard::default();
/// ///
/// keyboard.set_hint_text("Write here what you like!"); /// keyboard.set_hint_text(Some("Write here what you like!"));
/// # /// #
/// # } /// # }
#[doc(alias = "swkbdSetHintText")] #[doc(alias = "swkbdSetHintText")]
pub fn set_hint_text(&mut self, text: &str) { pub fn set_hint_text(&mut self, text: Option<&str>) {
unsafe { if let Some(text) = text {
let nul_terminated: String = text.chars().chain(once('\0')).collect(); for (idx, code_unit) in text
ctru_sys::swkbdSetHintText(self.state.as_mut(), nul_terminated.as_ptr()); .encode_utf16()
.take(self.state.hint_text.len() - 1)
.chain(once(0))
.enumerate()
{
self.state.hint_text[idx] = code_unit;
}
} else {
self.state.hint_text[0] = 0;
} }
} }
@ -588,15 +541,18 @@ impl SoftwareKeyboard {
/// # } /// # }
#[doc(alias = "swkbdSetButton")] #[doc(alias = "swkbdSetButton")]
pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) { pub fn configure_button(&mut self, button: Button, text: &str, submit: bool) {
unsafe { let button_text = &mut self.state.button_text[button as usize];
let nul_terminated: String = text.chars().chain(once('\0')).collect();
ctru_sys::swkbdSetButton( for (idx, code_unit) in text
self.state.as_mut(), .encode_utf16()
button.into(), .take(button_text.len() - 1)
nul_terminated.as_ptr(), .chain(once(0))
submit, .enumerate()
); {
button_text[idx] = code_unit;
} }
self.state.button_submits_text[button as usize] = submit;
} }
/// Configure the maximum number of UTF-16 code units that can be entered into the software /// Configure the maximum number of UTF-16 code units that can be entered into the software
@ -630,10 +586,9 @@ impl SoftwareKeyboard {
self.state.valid_input = ValidInput::FixedLen.into(); self.state.valid_input = ValidInput::FixedLen.into();
} }
// A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to // A reimplementation of `swkbdInputText` from `libctru/source/applets/swkbd.c`. Allows us to fix various
// get text from the software keyboard and put it directly into a `String` without requiring // API nits and get rid of awkward type conversions when interacting with the Software Keyboard.
// an intermediate fixed-size buffer fn swkbd_input_text(&mut self, output: &mut String, _apt: &Apt, _gfx: &Gfx) -> SwkbdButton {
fn swkbd_input_text(&mut self, output: &mut String) -> SwkbdButton {
use ctru_sys::{ use ctru_sys::{
MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE, MEMPERM_READ, MEMPERM_WRITE, R_FAILED, SWKBD_BUTTON_LEFT, SWKBD_BUTTON_MIDDLE,
SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0, SWKBD_BUTTON_NONE, SWKBD_BUTTON_RIGHT, SWKBD_D0_CLICK, SWKBD_D1_CLICK0,
@ -642,7 +597,7 @@ impl SoftwareKeyboard {
}; };
let swkbd = self.state.as_mut(); let swkbd = self.state.as_mut();
let mut extra = unsafe { swkbd.__bindgen_anon_1.extra }; let extra = unsafe { swkbd.__bindgen_anon_1.extra };
// Calculate shared mem size // Calculate shared mem size
let mut shared_mem_size = 0; let mut shared_mem_size = 0;
@ -714,19 +669,17 @@ impl SoftwareKeyboard {
} }
// Copy stuff to shared mem // Copy stuff to shared mem
if !extra.initial_text.is_null() { if let Some(initial_text) = self.initial_text.as_deref() {
swkbd.initial_text_offset = 0; swkbd.initial_text_offset = 0;
unsafe { let mut initial_text_cursor = swkbd_shared_mem_ptr.cast();
let utf16_iter =
str::from_utf8_unchecked(CStr::from_ptr(extra.initial_text).to_bytes())
.encode_utf16()
.take(swkbd.max_text_len as _)
.chain(once(0));
let mut initial_text_cursor = swkbd_shared_mem_ptr.cast();
for code_unit in utf16_iter { for code_unit in initial_text
.encode_utf16()
.take(swkbd.max_text_len as _)
.chain(once(0))
{
unsafe {
*initial_text_cursor = code_unit; *initial_text_cursor = code_unit;
initial_text_cursor = initial_text_cursor.add(1); initial_text_cursor = initial_text_cursor.add(1);
} }
@ -766,7 +719,7 @@ impl SoftwareKeyboard {
}; };
} }
if extra.callback.is_some() { if self.filter_callback.is_some() {
swkbd.filter_flags |= SWKBD_FILTER_CALLBACK; swkbd.filter_flags |= SWKBD_FILTER_CALLBACK;
} else { } else {
swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK; swkbd.filter_flags &= !SWKBD_FILTER_CALLBACK;
@ -776,16 +729,19 @@ impl SoftwareKeyboard {
unsafe { unsafe {
swkbd.__bindgen_anon_1.reserved.fill(0); swkbd.__bindgen_anon_1.reserved.fill(0);
let mut callback_data = MessageCallbackData { // We need to pass a thin pointer to the boxed closure over FFI. Since we know that the message callback will finish before
extra: std::ptr::addr_of_mut!(extra), // `self` is allowed to be moved again, we can safely use a pointer to the local value contained in `self.filter_callback`
// The cast here is also sound since the pointer will only be read from if `self.filter_callback.is_some()` returns true.
let mut data = MessageCallbackData {
filter_callback: std::ptr::addr_of!(self.filter_callback).cast(),
swkbd_shared_mem_ptr, swkbd_shared_mem_ptr,
}; };
if extra.callback.is_some() { if self.filter_callback.is_some() {
aptSetMessageCallback( aptSetMessageCallback(
Some(Self::swkbd_message_callback), Some(Self::swkbd_message_callback),
std::ptr::addr_of_mut!(callback_data).cast(), std::ptr::addr_of_mut!(data).cast(),
); )
} }
aptLaunchLibraryApplet( aptLaunchLibraryApplet(
@ -795,7 +751,7 @@ impl SoftwareKeyboard {
swkbd_shared_mem_handle, swkbd_shared_mem_handle,
); );
if extra.callback.is_some() { if self.filter_callback.is_some() {
aptSetMessageCallback(None, std::ptr::null_mut()); aptSetMessageCallback(None, std::ptr::null_mut());
} }
@ -846,63 +802,46 @@ impl SoftwareKeyboard {
} }
// A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`. // A reimplementation of `swkbdMessageCallback` from `libctru/source/applets/swkbd.c`.
// This function sets up and then calls the callback set by `swkbdSetFilterCallback` // This function sets up and then calls the filter callback
#[deny(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn swkbd_message_callback( unsafe extern "C" fn swkbd_message_callback(
user: *mut libc::c_void, user: *mut libc::c_void,
sender: NS_APPID, sender: NS_APPID,
msg: *mut libc::c_void, msg: *mut libc::c_void,
msg_size: libc::size_t, msg_size: libc::size_t,
) { ) {
let data = unsafe { &mut *user.cast::<MessageCallbackData>() };
let swkbd = unsafe { &mut *msg.cast::<SwkbdState>() };
let extra = unsafe { &mut *data.extra };
if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD if sender != ctru_sys::APPID_SOFTWARE_KEYBOARD
|| msg_size != std::mem::size_of::<SwkbdState>() || msg_size != std::mem::size_of::<SwkbdState>()
{ {
return; return;
} }
let swkbd = unsafe { &mut *msg.cast::<SwkbdState>() };
let data = unsafe { *user.cast::<MessageCallbackData>() };
let text16 = unsafe { let text16 = unsafe {
widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts( widestring::Utf16Str::from_slice_unchecked(std::slice::from_raw_parts(
data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(), data.swkbd_shared_mem_ptr.add(swkbd.text_offset as _).cast(),
swkbd.text_length as usize + 1, swkbd.text_length as _,
)) ))
}; };
let text8 = text16.to_string(); let text8 = text16.to_string();
let mut retmsg = std::ptr::null(); let filter_callback = unsafe { &**data.filter_callback };
if let Some(cb) = extra.callback { let (result, retmsg) = filter_callback(&text8);
swkbd.callback_result = unsafe {
cb(
extra.callback_user,
&mut retmsg,
text8.as_ptr(),
text8.len(),
)
} as _
};
let retmsg = if !retmsg.is_null() { swkbd.callback_result = result as _;
unsafe {
let len = libc::strlen(retmsg) + 1;
std::str::from_utf8_unchecked(std::slice::from_raw_parts(retmsg, len))
}
} else {
"\0"
};
let callback_msg = &mut swkbd.callback_msg; if let Some(msg) = retmsg.as_deref() {
for (idx, code_unit) in msg
for (idx, code_unit) in retmsg .encode_utf16()
.encode_utf16() .take(swkbd.callback_msg.len() - 1)
.take(callback_msg.len() - 1) .chain(once(0))
.enumerate() .enumerate()
{ {
callback_msg[idx] = code_unit; swkbd.callback_msg[idx] = code_unit;
}
} }
let _ = unsafe { let _ = unsafe {

1
ctru-rs/src/console.rs

@ -6,7 +6,6 @@
//! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications. //! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications.
use std::cell::{RefMut, UnsafeCell}; use std::cell::{RefMut, UnsafeCell};
use std::default::Default;
use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole}; use ctru_sys::{consoleClear, consoleInit, consoleSelect, consoleSetWindow, PrintConsole};

2
ctru-rs/src/lib.rs

@ -18,9 +18,11 @@
#![crate_type = "rlib"] #![crate_type = "rlib"]
#![crate_name = "ctru"] #![crate_name = "ctru"]
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unsafe_op_in_unsafe_fn)]
#![feature(custom_test_frameworks)] #![feature(custom_test_frameworks)]
#![feature(try_trait_v2)] #![feature(try_trait_v2)]
#![feature(allocator_api)] #![feature(allocator_api)]
#![feature(new_uninit)]
#![test_runner(test_runner::run_gdb)] // TODO: does this make sense to have configurable? #![test_runner(test_runner::run_gdb)] // TODO: does this make sense to have configurable?
#![doc( #![doc(
html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"

4
ctru-rs/src/linear.rs

@ -42,6 +42,8 @@ unsafe impl Allocator for LinearAllocator {
#[doc(alias = "linearFree")] #[doc(alias = "linearFree")]
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) { unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
ctru_sys::linearFree(ptr.as_ptr().cast()); unsafe {
ctru_sys::linearFree(ptr.as_ptr().cast());
}
} }
} }

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

@ -51,6 +51,11 @@ impl<'a> Title<'a> {
pub fn version(&self) -> u16 { pub fn version(&self) -> u16 {
self.version self.version
} }
/// Returns this title's media type
pub fn media_type(&self) -> MediaType {
self.mediatype
}
} }
/// Handle to the Application Manager service. /// Handle to the Application Manager service.

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

@ -82,6 +82,48 @@ impl Apt {
Ok(()) Ok(())
} }
} }
/// Set if the console is allowed to enter sleep mode.
///
/// You can check whether the console is allowed to sleep with [Apt::is_sleep_allowed].
#[doc(alias = "aptSetSleepAllowed")]
pub fn set_sleep_allowed(&mut self, allowed: bool) {
unsafe {
ctru_sys::aptSetSleepAllowed(allowed);
}
}
/// Check if the console is allowed to enter sleep mode.
///
/// You can set whether the console is allowed to sleep with [Apt::set_sleep_allowed].
#[doc(alias = "aptIsSleepAllowed")]
pub fn is_sleep_allowed(&self) -> bool {
unsafe { ctru_sys::aptIsSleepAllowed() }
}
/// Set if the console is allowed to enter the home menu.
///
/// You can check whether the console is allowed to enter the home menu with [Apt::is_home_allowed].
#[doc(alias = "aptSetHomeAllowed")]
pub fn set_home_allowed(&mut self, allowed: bool) {
unsafe {
ctru_sys::aptSetHomeAllowed(allowed);
}
}
/// Check if the console is allowed to enter the home menu.
///
/// You can set whether the console is allowed to enter the home menu with [Apt::set_home_allowed].
#[doc(alias = "aptIsHomeAllowed")]
pub fn is_home_allowed(&self) -> bool {
unsafe { ctru_sys::aptIsHomeAllowed() }
}
/// Immediately jumps to the home menu.
#[doc(alias = "aptJumpToHomeMenu")]
pub fn jump_to_home_menu(&mut self) {
unsafe { ctru_sys::aptJumpToHomeMenu() }
}
} }
impl Drop for Apt { impl Drop for Apt {
@ -90,3 +132,48 @@ impl Drop for Apt {
unsafe { ctru_sys::aptExit() }; unsafe { ctru_sys::aptExit() };
} }
} }
/// Can launch other applications when the current one exits.
pub struct Chainloader<'a> {
_apt: &'a Apt,
}
impl<'a> Chainloader<'a> {
/// Gets a handle to the chainloader
pub fn new(apt: &'a Apt) -> Self {
Self { _apt: apt }
}
/// Checks if the chainloader is set
#[doc(alias = "aptIsChainload")]
pub fn is_set(&self) -> bool {
// static funtion not exported
unsafe { (ctru_sys::envGetSystemRunFlags() & ctru_sys::RUNFLAG_APTCHAINLOAD) != 0 }
}
/// Clears the chainloader state.
#[doc(alias = "aptClearChainloader")]
pub fn clear(&mut self) {
unsafe { ctru_sys::aptClearChainloader() }
}
/// Configures the chainloader to launch a specific application.
///
/// See also [`Title`](crate::services::am::Title]
#[doc(alias = "aptSetChainloader")]
pub fn set(&mut self, title: &super::am::Title<'_>) {
unsafe { ctru_sys::aptSetChainloader(title.id(), title.media_type() as u8) }
}
/// Configures the chainloader to launch the previous application.
#[doc(alias = "aptSetChainloaderToCaller")]
pub fn set_to_caller(&mut self) {
unsafe { ctru_sys::aptSetChainloaderToCaller() }
}
/// Configures the chainloader to relaunch the current application (i.e. soft-reset)
#[doc(alias = "aptSetChainloaderToSelf")]
pub fn set_to_self(&mut self) {
unsafe { ctru_sys::aptSetChainloaderToSelf() }
}
}

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

@ -239,7 +239,7 @@ pub struct Gfx {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static GFX_ACTIVE: Mutex<()> = Mutex::new(()); pub(crate) static GFX_ACTIVE: Mutex<()> = Mutex::new(());
impl Gfx { impl Gfx {
/// Initialize a new default service handle. /// Initialize a new default service handle.

10
ctru-rs/src/services/ir_user.rs

@ -150,7 +150,7 @@ impl IrUser {
shared_mem.shared_memory_layout, shared_mem.shared_memory_layout,
); );
Ok(()) Ok::<_, Error>(())
})() })()
.unwrap(); .unwrap();
}, },
@ -347,9 +347,11 @@ impl IrUser {
let mut shared_mem_guard = IR_USER_STATE.lock().unwrap(); let mut shared_mem_guard = IR_USER_STATE.lock().unwrap();
let shared_mem = shared_mem_guard.as_mut().unwrap(); let shared_mem = shared_mem_guard.as_mut().unwrap();
shared_mem unsafe {
.service_handle shared_mem
.send_service_request(request, expected_response_len) .service_handle
.send_service_request(request, expected_response_len)
}
} }
} }

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

@ -26,6 +26,7 @@ mod reference;
pub mod soc; pub mod soc;
pub mod sslc; pub mod sslc;
pub mod svc; pub mod svc;
pub mod uds;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(all(feature = "romfs", romfs_exists))] { if #[cfg(all(feature = "romfs", romfs_exists))] {

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

@ -21,7 +21,6 @@ use crate::error::ResultCode;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use std::cell::{RefCell, RefMut}; use std::cell::{RefCell, RefMut};
use std::default::Default;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::sync::Mutex; use std::sync::Mutex;

23
ctru-rs/src/services/svc.rs

@ -50,19 +50,28 @@ impl HandleExt for Handle {
) -> crate::Result<Vec<u32>> { ) -> crate::Result<Vec<u32>> {
// Copy over the request // Copy over the request
let cmd_buffer_ptr = unsafe { ctru_sys::getThreadCommandBuffer() }; let cmd_buffer_ptr = unsafe { ctru_sys::getThreadCommandBuffer() };
std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len());
// Send the request unsafe {
ResultCode(ctru_sys::svcSendSyncRequest(self))?; std::ptr::copy_nonoverlapping(request.as_ptr(), cmd_buffer_ptr, request.len());
// Send the request
ResultCode(ctru_sys::svcSendSyncRequest(self))?;
// Handle the result returned by the service // Handle the result returned by the service
let result = unsafe { std::ptr::read(cmd_buffer_ptr.add(1)) }; let result = std::ptr::read(cmd_buffer_ptr.add(1));
ResultCode(result as ctru_sys::Result)?; ResultCode(result as ctru_sys::Result)?;
}
// Copy back the response // Copy back the response
request.clear(); request.clear();
request.resize(expected_response_len, 0); request.resize(expected_response_len, 0);
std::ptr::copy_nonoverlapping(cmd_buffer_ptr, request.as_mut_ptr(), expected_response_len); unsafe {
std::ptr::copy_nonoverlapping(
cmd_buffer_ptr,
request.as_mut_ptr(),
expected_response_len,
);
}
Ok(request) Ok(request)
} }

1382
ctru-rs/src/services/uds.rs

File diff suppressed because it is too large Load Diff

6
ctru-sys/Cargo.toml

@ -3,7 +3,7 @@ name = "ctru-sys"
version = "0.5.0" version = "0.5.0"
authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"] authors = ["Rust3DS Org", "Ronald Kinard <furyhunter600@gmail.com>"]
description = "Raw bindings to libctru" description = "Raw bindings to libctru"
repository = "https://github.com/rust3ds/ctru-rs" repository = "https://git.xenua.me/rust3ds/ctru-rs"
documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru_sys" documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru_sys"
keywords = ["3ds", "libctru"] keywords = ["3ds", "libctru"]
categories = ["os", "external-ffi-bindings", "no-std", "hardware-support"] categories = ["os", "external-ffi-bindings", "no-std", "hardware-support"]
@ -23,8 +23,8 @@ itertools = "0.11.0"
which = "4.4.0" which = "4.4.0"
[dev-dependencies] [dev-dependencies]
shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } shim-3ds = { git = "https://git.xenua.me/rust3ds/shim-3ds.git" }
test-runner = { git = "https://github.com/rust3ds/test-runner.git" } test-runner = { git = "https://git.xenua.me/rust3ds/test-runner.git" }
[package.metadata.docs.rs] [package.metadata.docs.rs]
default-target = "armv6k-nintendo-3ds" default-target = "armv6k-nintendo-3ds"

4
ctru-sys/build.rs

@ -73,12 +73,14 @@ fn main() {
.use_core() .use_core()
.trust_clang_mangling(false) .trust_clang_mangling(false)
.must_use_type("Result") .must_use_type("Result")
.layout_tests(false) .layout_tests(true)
.ctypes_prefix("::libc") .ctypes_prefix("::libc")
.prepend_enum_name(false) .prepend_enum_name(false)
.blocklist_type("u(8|16|32|64)") .blocklist_type("u(8|16|32|64)")
.blocklist_type("__builtin_va_list") .blocklist_type("__builtin_va_list")
.blocklist_type("__va_list") .blocklist_type("__va_list")
.blocklist_type("errorReturnCode")
.blocklist_type("errorScreenFlag")
.opaque_type("MiiData") .opaque_type("MiiData")
.derive_default(true) .derive_default(true)
.wrap_static_fns(true) .wrap_static_fns(true)

18
ctru-sys/src/lib.rs

@ -16,6 +16,24 @@
pub mod result; pub mod result;
pub use result::*; pub use result::*;
// Fun fact: C compilers are allowed to represent enums as the smallest integer type that can hold all of its variants,
// meaning that enums are allowed to be the size of a `c_short` or a `c_char` rather than the size of a `c_int`.
// Libctru's `errorConf` struct contains two enums that depend on this narrowing property for size and alignment purposes,
// and since `bindgen` generates all enums with `c_int` sizing, we have to blocklist those types and manually define them
// here with the proper size.
pub type errorReturnCode = libc::c_schar;
pub const ERROR_UNKNOWN: errorReturnCode = -1;
pub const ERROR_NONE: errorReturnCode = 0;
pub const ERROR_SUCCESS: errorReturnCode = 1;
pub const ERROR_NOT_SUPPORTED: errorReturnCode = 2;
pub const ERROR_HOME_BUTTON: errorReturnCode = 10;
pub const ERROR_SOFTWARE_RESET: errorReturnCode = 11;
pub const ERROR_POWER_BUTTON: errorReturnCode = 12;
pub type errorScreenFlag = libc::c_char;
pub const ERROR_NORMAL: errorScreenFlag = 0;
pub const ERROR_STEREO: errorScreenFlag = 1;
include!(concat!(env!("OUT_DIR"), "/bindings.rs")); include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
/// In lieu of a proper errno function exposed by libc /// In lieu of a proper errno function exposed by libc

Loading…
Cancel
Save