diff --git a/ctru-rs/examples/output-3dslink.rs b/ctru-rs/examples/output-3dslink.rs new file mode 100644 index 0000000..9c66c9b --- /dev/null +++ b/ctru-rs/examples/output-3dslink.rs @@ -0,0 +1,45 @@ +//! 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}; +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/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 5705d06..13d0dc9 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -4,12 +4,14 @@ 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. #[non_exhaustive] pub struct Soc { _service_handler: ServiceReference, + sock_3dslink: libc::c_int, } static SOC_ACTIVE: Lazy> = Lazy::new(|| Mutex::new(0)); @@ -52,7 +54,10 @@ impl Soc { }, )?; - Ok(Self { _service_handler }) + Ok(Self { + _service_handler, + sock_3dslink: -1, + }) } /// IP Address of the Nintendo 3DS system. @@ -60,12 +65,45 @@ 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 { + return Err(Error::OutputAlreadyRedirected); + } + + if !stdout && !stderr { + return Ok(()); + } + + self.sock_3dslink = unsafe { ctru_sys::link3dsConnectToHost(stdout, stderr) }; + if self.sock_3dslink < 0 { + Err(Error::from_errno()) + } else { + Ok(()) + } + } +} + +impl Drop for Soc { + fn drop(&mut self) { + if self.sock_3dslink >= 0 { + unsafe { + libc::close(self.sock_3dslink); + } + } + } } #[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 +}