From 172c3c260eb971e806ad4d3fef7c1eb38bd93f90 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 7 May 2022 16:06:51 -0400 Subject: [PATCH 1/4] Add 3dslink redirection to Soc service - Add example of usage - Fix broken thread-info compilation --- ctru-rs/examples/output-3dslink.rs | 35 ++++++++++++++++++++++++++ ctru-rs/examples/thread-info.rs | 2 +- ctru-rs/src/services/soc.rs | 40 ++++++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 ctru-rs/examples/output-3dslink.rs diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs new file mode 100644 index 0000000..fd5a526 --- /dev/null +++ b/ctru-rs/examples/output-3dslink.rs @@ -0,0 +1,35 @@ +use ctru::gfx::Gfx; +use ctru::services::apt::Apt; +use ctru::services::hid::{Hid, KeyPad}; +use ctru::services::soc::Soc; + +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 mut soc = Soc::init().expect("Couldn't obtain SOC controller"); + + soc.redirect_to_3dslink(true, true) + .expect("unable to redirect stdout/err to 3dslink server"); + + println!("Hello 3dslink!"); + eprintln!("Press Start on the device to disconnect and exit."); + + // Main loop + while apt.main_loop() { + //Scan all the inputs. This should be done once for each frame + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/thread-info.rs b/ctru-rs/examples/thread-info.rs index 47ae486..337c49e 100644 --- a/ctru-rs/examples/thread-info.rs +++ b/ctru-rs/examples/thread-info.rs @@ -10,7 +10,7 @@ use std::os::horizon::thread::BuilderExt; fn main() { ctru::init(); - let gfx = Gfx::default(); + 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()); diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index 5705d06..e058516 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -10,6 +10,7 @@ use crate::services::ServiceReference; #[non_exhaustive] pub struct Soc { _service_handler: ServiceReference, + sock_3dslink: libc::c_int, } static SOC_ACTIVE: Lazy> = Lazy::new(|| Mutex::new(0)); @@ -52,7 +53,10 @@ impl Soc { }, )?; - Ok(Self { _service_handler }) + Ok(Self { + _service_handler, + sock_3dslink: -1, + }) } /// IP Address of the Nintendo 3DS system. @@ -60,6 +64,38 @@ impl Soc { let raw_id = unsafe { libc::gethostid() }; Ipv4Addr::from(raw_id.to_ne_bytes()) } + + /// Redirect output streams (i.e. [`println`] and [`eprintln`]) to the `3dslink` server. + /// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0. + /// + /// # Errors + /// + /// Returns an error if a connection cannot be established to the server, or + /// output was already previously redirected. + pub fn redirect_to_3dslink(&mut self, stdout: bool, stderr: bool) -> crate::Result<()> { + if self.sock_3dslink >= 0 { + // TODO AlreadyRedirected or something + return Err(crate::Error::ServiceAlreadyActive); + } + + let sock = unsafe { ctru_sys::link3dsConnectToHost(stdout, stderr) }; + if sock < 0 { + Err(sock.into()) + } else { + self.sock_3dslink = sock; + Ok(()) + } + } +} + +impl Drop for Soc { + fn drop(&mut self) { + if self.sock_3dslink >= 0 { + unsafe { + libc::closesocket(self.sock_3dslink); + } + } + } } #[cfg(test)] @@ -69,7 +105,7 @@ mod tests { #[test] fn soc_duplicate() { - let _soc = Soc::init().unwrap(); + // let _soc = Soc::init().unwrap(); assert!(matches!(Soc::init(), Err(Error::ServiceAlreadyActive))) } From cdb7e4550075f124b9aab8e42396bbb64865fc83 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 7 May 2022 16:13:49 -0400 Subject: [PATCH 2/4] Add doc comment for usage + clear screen --- ctru-rs/examples/output-3dslink.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs index fd5a526..36ae187 100644 --- a/ctru-rs/examples/output-3dslink.rs +++ b/ctru-rs/examples/output-3dslink.rs @@ -1,3 +1,13 @@ +//! Use the `3dslink --server` option for redirecting output from the 3DS back +//! to the device that sent the executable. +//! +//! For now, `cargo 3ds run` does not support this flag, so to run this example +//! it must be sent manually, like this: +//! ```sh +//! cargo 3ds build --example output-3dslink +//! 3dslink --server target/armv6k-nintendo-3ds/debug/examples/output-3dslink.3dsx +//! ``` + use ctru::gfx::Gfx; use ctru::services::apt::Apt; use ctru::services::hid::{Hid, KeyPad}; @@ -14,6 +24,8 @@ fn main() { soc.redirect_to_3dslink(true, true) .expect("unable to redirect stdout/err to 3dslink server"); + print!("\x1b[2J\x1b[0;0H"); // Clear screen + move to 0,0 + println!("Hello 3dslink!"); eprintln!("Press Start on the device to disconnect and exit."); From 89c6d84a0ec35d87dc326099adcd7ce36c8bda79 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 7 May 2022 22:45:49 -0400 Subject: [PATCH 3/4] Restore previous test behavior --- ctru-rs/src/services/soc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index e058516..bcbdcc8 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -105,7 +105,7 @@ mod tests { #[test] fn soc_duplicate() { - // let _soc = Soc::init().unwrap(); + let _soc = Soc::init().unwrap(); assert!(matches!(Soc::init(), Err(Error::ServiceAlreadyActive))) } From 042d5602cee08b7b4d7b9d0839fce7f303ae6a3e Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 9 May 2022 21:32:15 -0400 Subject: [PATCH 4/4] Add a new enum variant for already redirected Also add libc errno error helper and an errno function in ctru_sys. --- ctru-rs/examples/output-3dslink.rs | 2 -- ctru-rs/src/error.rs | 39 +++++++++++++++++++++++++----- ctru-rs/src/services/soc.rs | 18 ++++++++------ ctru-sys/src/lib.rs | 7 ++++++ 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs index 36ae187..9c66c9b 100644 --- a/ctru-rs/examples/output-3dslink.rs +++ b/ctru-rs/examples/output-3dslink.rs @@ -24,8 +24,6 @@ fn main() { soc.redirect_to_3dslink(true, true) .expect("unable to redirect stdout/err to 3dslink server"); - print!("\x1b[2J\x1b[0;0H"); // Clear screen + move to 0,0 - println!("Hello 3dslink!"); eprintln!("Press Start on the device to disconnect and exit."); diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 96ade26..047f811 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -1,4 +1,5 @@ use std::error; +use std::ffi::CStr; use std::fmt; use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY}; @@ -9,7 +10,27 @@ pub type Result = ::std::result::Result; #[non_exhaustive] pub enum Error { Os(ctru_sys::Result), + Libc(String), ServiceAlreadyActive, + OutputAlreadyRedirected, +} + +impl Error { + /// Create an [`Error`] out of the last set value in `errno`. This can be used + /// to get a human-readable error string from calls to `libc` functions. + pub(crate) fn from_errno() -> Self { + let error_str = unsafe { + let errno = ctru_sys::errno(); + let str_ptr = libc::strerror(errno); + + // Safety: strerror should always return a valid string, + // even if the error number is unknown + CStr::from_ptr(str_ptr) + }; + + // Copy out of the error string, since it may be changed by other libc calls later + Self::Libc(error_str.to_string_lossy().into()) + } } impl From for Error { @@ -20,8 +41,8 @@ impl From for Error { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Os(err) => f + match self { + &Self::Os(err) => f .debug_struct("Error") .field("raw", &format_args!("{:#08X}", err)) .field("description", &R_DESCRIPTION(err)) @@ -29,7 +50,9 @@ impl fmt::Debug for Error { .field("summary", &R_SUMMARY(err)) .field("level", &R_LEVEL(err)) .finish(), - Error::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").finish(), + Self::Libc(err) => f.debug_tuple("Libc").field(err).finish(), + Self::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").finish(), + Self::OutputAlreadyRedirected => f.debug_tuple("OutputAlreadyRedirected").finish(), } } } @@ -39,9 +62,13 @@ impl fmt::Debug for Error { // https://github.com/devkitPro/libctru/blob/master/libctru/include/3ds/result.h impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), - Error::ServiceAlreadyActive => write!(f, "Service already active"), + match self { + &Self::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), + Self::Libc(err) => write!(f, "{}", err), + Self::ServiceAlreadyActive => write!(f, "Service already active"), + Self::OutputAlreadyRedirected => { + write!(f, "output streams are already redirected to 3dslink") + } } } } diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index bcbdcc8..13d0dc9 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -4,6 +4,7 @@ use std::net::Ipv4Addr; use std::sync::Mutex; use crate::services::ServiceReference; +use crate::Error; /// Soc service. Initializing this service will enable the use of network sockets and utilities /// such as those found in `std::net`. The service will be closed when this struct is is dropped. @@ -74,15 +75,17 @@ impl Soc { /// output was already previously redirected. pub fn redirect_to_3dslink(&mut self, stdout: bool, stderr: bool) -> crate::Result<()> { if self.sock_3dslink >= 0 { - // TODO AlreadyRedirected or something - return Err(crate::Error::ServiceAlreadyActive); + return Err(Error::OutputAlreadyRedirected); } - let sock = unsafe { ctru_sys::link3dsConnectToHost(stdout, stderr) }; - if sock < 0 { - Err(sock.into()) + if !stdout && !stderr { + return Ok(()); + } + + self.sock_3dslink = unsafe { ctru_sys::link3dsConnectToHost(stdout, stderr) }; + if self.sock_3dslink < 0 { + Err(Error::from_errno()) } else { - self.sock_3dslink = sock; Ok(()) } } @@ -92,7 +95,7 @@ impl Drop for Soc { fn drop(&mut self) { if self.sock_3dslink >= 0 { unsafe { - libc::closesocket(self.sock_3dslink); + libc::close(self.sock_3dslink); } } } @@ -101,7 +104,6 @@ impl Drop for Soc { #[cfg(test)] mod tests { use super::*; - use crate::Error; #[test] fn soc_duplicate() { diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 454a380..595cbd3 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -10,3 +10,10 @@ mod bindings; pub use bindings::*; pub use result::*; + +/// In lieu of a proper errno function exposed by libc +/// (), this will retrieve the +/// last error set in the global reentrancy struct. +pub unsafe fn errno() -> s32 { + (*__getreent())._errno +}