Browse Source

Merge pull request #61 from ian-h-chamberlain/feature/3dslink-output-redirection

Add 3dslink redirection to Soc service
pull/62/head
Mark Drobnak 3 years ago committed by GitHub
parent
commit
ae676a8d99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 45
      ctru-rs/examples/output-3dslink.rs
  2. 2
      ctru-rs/examples/thread-info.rs
  3. 39
      ctru-rs/src/error.rs
  4. 42
      ctru-rs/src/services/soc.rs
  5. 7
      ctru-sys/src/lib.rs

45
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();
}
}

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

@ -10,7 +10,7 @@ use std::os::horizon::thread::BuilderExt;
fn main() { fn main() {
ctru::init(); 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 hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller"); let apt = Apt::init().expect("Couldn't obtain APT controller");
let _console = Console::init(gfx.top_screen.borrow_mut()); let _console = Console::init(gfx.top_screen.borrow_mut());

39
ctru-rs/src/error.rs

@ -1,4 +1,5 @@
use std::error; use std::error;
use std::ffi::CStr;
use std::fmt; use std::fmt;
use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY}; use ctru_sys::result::{R_DESCRIPTION, R_LEVEL, R_MODULE, R_SUMMARY};
@ -9,7 +10,27 @@ pub type Result<T> = ::std::result::Result<T, Error>;
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
Os(ctru_sys::Result), Os(ctru_sys::Result),
Libc(String),
ServiceAlreadyActive, 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<ctru_sys::Result> for Error { impl From<ctru_sys::Result> for Error {
@ -20,8 +41,8 @@ impl From<ctru_sys::Result> for Error {
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 {
Error::Os(err) => f &Self::Os(err) => f
.debug_struct("Error") .debug_struct("Error")
.field("raw", &format_args!("{:#08X}", err)) .field("raw", &format_args!("{:#08X}", err))
.field("description", &R_DESCRIPTION(err)) .field("description", &R_DESCRIPTION(err))
@ -29,7 +50,9 @@ impl fmt::Debug for Error {
.field("summary", &R_SUMMARY(err)) .field("summary", &R_SUMMARY(err))
.field("level", &R_LEVEL(err)) .field("level", &R_LEVEL(err))
.finish(), .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 // https://github.com/devkitPro/libctru/blob/master/libctru/include/3ds/result.h
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 {
Error::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), &Self::Os(err) => write!(f, "libctru result code: 0x{:08X}", err),
Error::ServiceAlreadyActive => write!(f, "Service already active"), Self::Libc(err) => write!(f, "{}", err),
Self::ServiceAlreadyActive => write!(f, "Service already active"),
Self::OutputAlreadyRedirected => {
write!(f, "output streams are already redirected to 3dslink")
}
} }
} }
} }

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

@ -4,12 +4,14 @@ use std::net::Ipv4Addr;
use std::sync::Mutex; use std::sync::Mutex;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use crate::Error;
/// Soc service. Initializing this service will enable the use of network sockets and utilities /// 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. /// such as those found in `std::net`. The service will be closed when this struct is is dropped.
#[non_exhaustive] #[non_exhaustive]
pub struct Soc { pub struct Soc {
_service_handler: ServiceReference, _service_handler: ServiceReference,
sock_3dslink: libc::c_int,
} }
static SOC_ACTIVE: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(0)); static SOC_ACTIVE: Lazy<Mutex<usize>> = 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. /// IP Address of the Nintendo 3DS system.
@ -60,12 +65,45 @@ impl Soc {
let raw_id = unsafe { libc::gethostid() }; let raw_id = unsafe { libc::gethostid() };
Ipv4Addr::from(raw_id.to_ne_bytes()) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::Error;
#[test] #[test]
fn soc_duplicate() { fn soc_duplicate() {

7
ctru-sys/src/lib.rs

@ -10,3 +10,10 @@ mod bindings;
pub use bindings::*; pub use bindings::*;
pub use result::*; pub use result::*;
/// In lieu of a proper errno function exposed by libc
/// (<https://github.com/rust-lang/libc/issues/1995>), this will retrieve the
/// last error set in the global reentrancy struct.
pub unsafe fn errno() -> s32 {
(*__getreent())._errno
}

Loading…
Cancel
Save