diff --git a/.gitignore b/.gitignore index c34b8d3..54f1838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ target Cargo.lock .cargo + +# IDE files +.idea diff --git a/Cargo.toml b/Cargo.toml index 65c4a2f..80b876e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,10 @@ [workspace] members = ["ctru-rs", "ctru-sys"] + +[patch.crates-io] +# We have some changes not in the upstream +libc = { git = "https://github.com/Meziu/libc.git" } + +[patch.'https://github.com/Meziu/ctru-rs'] +# Make sure all dependencies use the local ctru-sys package +ctru-sys = { path = "ctru-sys" } \ No newline at end of file diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index 1b303f3..4807f32 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -25,7 +25,9 @@ toml = "0.5" [dev-dependencies] ferris-says = "0.2.1" +futures = "0.3" time = "0.3.7" +tokio = { version = "1.16", features = ["rt", "time", "sync", "macros"] } [features] default = ["romfs"] diff --git a/ctru-rs/examples/futures-basic.rs b/ctru-rs/examples/futures-basic.rs new file mode 100644 index 0000000..c48964b --- /dev/null +++ b/ctru-rs/examples/futures-basic.rs @@ -0,0 +1,73 @@ +//! This example runs a basic future executor from the `futures` library. +//! Every 60 frames (about 1 second) it prints "Tick" to the console. +//! The executor runs on a separate thread. Internally it yields when it has no more work to do, +//! allowing other threads to run. +//! The example also implements clean shutdown by using a oneshot channel to end the future, thus +//! ending the executor and the thread it runs on. + +use ctru::console::Console; +use ctru::services::hid::KeyPad; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; +use futures::StreamExt; + +fn main() { + ctru::init(); + let gfx = Gfx::default(); + 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()); + + // Give ourselves up to 30% of the system core's time + apt.set_app_cpu_time_limit(30) + .expect("Failed to enable system core"); + + println!("Starting executor..."); + + let (exit_sender, mut exit_receiver) = futures::channel::oneshot::channel(); + let (mut timer_sender, mut timer_receiver) = futures::channel::mpsc::channel(0); + let executor_thread = ctru::thread::Builder::new() + .affinity(1) + .spawn(move || { + let mut executor = futures::executor::LocalPool::new(); + + executor.run_until(async move { + loop { + futures::select! { + _ = exit_receiver => break, + _ = timer_receiver.next() => { + println!("Tick"); + } + } + } + }); + }) + .expect("Failed to create executor thread"); + + println!("Executor started!"); + + let mut frame_count = 0; + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + println!("Shutting down..."); + let _ = exit_sender.send(()); + let _ = executor_thread.join(); + break; + } + + frame_count += 1; + + if frame_count == 60 { + if let Err(e) = timer_sender.try_send(()) { + println!("Error sending timer message: {e}"); + } + frame_count = 0; + } + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/futures-tokio.rs b/ctru-rs/examples/futures-tokio.rs new file mode 100644 index 0000000..60753a2 --- /dev/null +++ b/ctru-rs/examples/futures-tokio.rs @@ -0,0 +1,72 @@ +use ctru::console::Console; +use ctru::services::hid::KeyPad; +use ctru::services::ps::Ps; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; +use std::time::Duration; + +fn main() { + ctru::init(); + let gfx = Gfx::default(); + let hid = Hid::init().expect("Couldn't obtain HID controller"); + let apt = Apt::init().expect("Couldn't obtain APT controller"); + let _ps = Ps::init().expect("Couldn't initialize PS service"); + let _console = Console::init(gfx.top_screen.borrow_mut()); + + // Give ourselves up to 30% of the system core's time + apt.set_app_cpu_time_limit(30) + .expect("Failed to enable system core"); + + println!("Starting runtime..."); + + let (exit_sender, mut exit_receiver) = tokio::sync::oneshot::channel(); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_time() + .build() + .expect("Couldn't build runtime"); + + let runtime_thread = ctru::thread::Builder::new() + // Run on the system core + .affinity(1) + // Use a bigger stack size. Default is 0x1000 but we'd easily overflow that. + .stack_size(0x200000) + .spawn(move || { + runtime.block_on(async move { + let mut wake_time = tokio::time::Instant::now() + Duration::from_secs(1); + let mut iteration = 0; + loop { + let sleep_future = tokio::time::sleep_until(wake_time); + + tokio::select! { + // Use the first available future instead of randomizing + biased; + + _ = sleep_future => { + println!("Tick {}", iteration); + iteration += 1; + wake_time += Duration::from_secs(1); + } + _ = &mut exit_receiver => break, + } + } + }); + }) + .expect("Failed to create runtime thread"); + + println!("Runtime started!"); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + println!("Shutting down..."); + let _ = exit_sender.send(()); + let _ = runtime_thread.join(); + break; + } + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs new file mode 100644 index 0000000..d0aab20 --- /dev/null +++ b/ctru-rs/examples/hashmaps.rs @@ -0,0 +1,39 @@ +use ctru::console::Console; +use ctru::gfx::Gfx; +use ctru::services::apt::Apt; +use ctru::services::hid::{Hid, KeyPad}; +use ctru::services::ps::Ps; + +fn main() { + // Initialize services + ctru::init(); + let apt = Apt::init().unwrap(); + let hid = Hid::init().unwrap(); + let gfx = Gfx::default(); + let _console = Console::init(gfx.top_screen.borrow_mut()); + + // HashMaps generate hashes thanks to the 3DS' criptografically secure generator. + // Sadly, this generator is only active when activating the `Ps` service. + // To do this, we have to make sure the `Ps` service handle is alive for the whole + // run time (or at least, when `HashMaps` are used). + // Not having a living `Ps` instance when using `HashMap`s results in a panic + let _ps = Ps::init().unwrap(); + + let mut map = std::collections::HashMap::new(); + map.insert("A Key!", 102); + map.insert("Another key?", 543); + map.remove("A Key!"); + + println!("{:#?}", map); + + while apt.main_loop() { + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + + hid.scan_input(); + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + } +} diff --git a/ctru-rs/examples/thread-locals.rs b/ctru-rs/examples/thread-locals.rs new file mode 100644 index 0000000..937855a --- /dev/null +++ b/ctru-rs/examples/thread-locals.rs @@ -0,0 +1,66 @@ +use ctru::console::Console; +use ctru::services::hid::KeyPad; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; +use std::cell::RefCell; + +std::thread_local! { + static MY_LOCAL: RefCell<&'static str> = RefCell::new("initial value"); +} + +fn main() { + ctru::init(); + let gfx = Gfx::default(); + gfx.top_screen.borrow_mut().set_wide_mode(true); + 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()); + + // Give ourselves up to 30% of the system core's time + apt.set_app_cpu_time_limit(30) + .expect("Failed to enable system core"); + + MY_LOCAL.with(|local| { + println!("Initial value on main thread: {}", local.borrow()); + *local.borrow_mut() = "second value"; + }); + MY_LOCAL.with(|local| { + println!("Value on main thread after mutation: {}", local.borrow()); + }); + + ctru::thread::Builder::new() + .affinity(1) + .spawn(move || { + MY_LOCAL.with(|local| { + println!("Initial value on second thread: {}", local.borrow()); + *local.borrow_mut() = "third value"; + }); + MY_LOCAL.with(|local| { + println!("Value on second thread after mutation: {}", local.borrow()); + }); + }) + .expect("Failed to create thread") + .join() + .expect("Failed to join on thread"); + + MY_LOCAL.with(|local| { + println!( + "Value on main thread after second thread exits: {}", + local.borrow() + ); + }); + + println!("Press Start to exit"); + + while apt.main_loop() { + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/src/services/ps.rs b/ctru-rs/src/services/ps.rs index ec9b2d5..9a664e8 100644 --- a/ctru-rs/src/services/ps.rs +++ b/ctru-rs/src/services/ps.rs @@ -8,6 +8,30 @@ #[non_exhaustive] pub struct Ps; +#[repr(u32)] +pub enum AESAlgorithm { + CbcEnc, + CbcDec, + CtrEnc, + CtrDec, + CcmEnc, + CcmDec, +} + +#[repr(u32)] +pub enum AESKeyType { + Keyslot0D, + Keyslot2D, + Keyslot31, + Keyslot38, + Keyslot32, + Keyslot39Dlp, + Keyslot2E, + KeyslotInvalid, + Keyslot36, + Keyslot39Nfc, +} + impl Ps { /// Initialize the PS module. pub fn init() -> crate::Result { @@ -18,6 +42,38 @@ impl Ps { Ok(Self) } } + + pub fn local_friend_code_seed(&self) -> crate::Result { + let mut seed: u64 = 0; + + let r = unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) }; + if r < 0 { + Err(r.into()) + } else { + Ok(seed) + } + } + + pub fn device_id(&self) -> crate::Result { + let mut id: u32 = 0; + + let r = unsafe { ctru_sys::PS_GetDeviceId(&mut id) }; + if r < 0 { + Err(r.into()) + } else { + Ok(id) + } + } + + pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> { + let r = + unsafe { ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32) }; + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } } impl Drop for Ps { diff --git a/ctru-rs/src/thread.rs b/ctru-rs/src/thread.rs index 225b1f8..14d7a56 100644 --- a/ctru-rs/src/thread.rs +++ b/ctru-rs/src/thread.rs @@ -1040,7 +1040,10 @@ mod thread_info { } pub fn set(thread: Thread) { - CTRU_THREAD_INFO.with(|c| assert!(c.borrow().is_none())); - CTRU_THREAD_INFO.with(move |c| *c.borrow_mut() = Some(ThreadInfo { thread })); + CTRU_THREAD_INFO.with(move |c| { + let mut thread_info = c.borrow_mut(); + assert!(thread_info.is_none()); + *thread_info = Some(ThreadInfo { thread }); + }); } } diff --git a/ctru-sys/Cargo.toml b/ctru-sys/Cargo.toml index 46bed0b..3cde33c 100644 --- a/ctru-sys/Cargo.toml +++ b/ctru-sys/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "ctru-sys" -version = "0.4.0" +version = "0.4.1" authors = ["Ronald Kinard "] license = "https://en.wikipedia.org/wiki/Zlib_License" +links = "ctru" edition = "2021" [dependencies] diff --git a/ctru-sys/bindgen.sh b/ctru-sys/bindgen.sh index 247f6b3..3a25578 100755 --- a/ctru-sys/bindgen.sh +++ b/ctru-sys/bindgen.sh @@ -26,5 +26,5 @@ bindgen "$DEVKITPRO/libctru/include/3ds.h" \ -mtune=mpcore \ -mfpu=vfp \ -DARM11 \ - -D_3DS \ + -D__3DS__ \ > src/bindings.rs diff --git a/ctru-sys/src/bindings.rs b/ctru-sys/src/bindings.rs index 612cf41..5e6aafa 100644 --- a/ctru-sys/src/bindings.rs +++ b/ctru-sys/src/bindings.rs @@ -215,8 +215,12 @@ pub const CUR_PROCESS_HANDLE: u32 = 4294934529; pub const ARBITRATION_SIGNAL_ALL: i32 = -1; pub const CUR_THREAD_HANDLE: u32 = 4294934528; pub const SYSCLOCK_SOC: u32 = 16756991; +pub const SYSCLOCK_SYS: u32 = 33513982; +pub const SYSCLOCK_SDMMC: u32 = 67027964; pub const SYSCLOCK_ARM9: u32 = 134055928; pub const SYSCLOCK_ARM11: u32 = 268111856; +pub const SYSCLOCK_ARM11_LGR1: u32 = 536223712; +pub const SYSCLOCK_ARM11_LGR2: u32 = 804335568; pub const SYSCLOCK_ARM11_NEW: u32 = 804335568; pub const CPU_TICKS_PER_MSEC: f64 = 268111.856; pub const CPU_TICKS_PER_USEC: f64 = 268.111856; @@ -277,6 +281,8 @@ pub const CONSOLE_BLINK_FAST: u32 = 32; pub const CONSOLE_COLOR_REVERSE: u32 = 64; pub const CONSOLE_CONCEAL: u32 = 128; pub const CONSOLE_CROSSED_OUT: u32 = 256; +pub const CONSOLE_FG_CUSTOM: u32 = 512; +pub const CONSOLE_BG_CUSTOM: u32 = 1024; pub const __GNUCLIKE_ASM: u32 = 3; pub const __GNUCLIKE___TYPEOF: u32 = 1; pub const __GNUCLIKE___OFFSETOF: u32 = 1; @@ -1216,6 +1222,7 @@ pub const MIISELECTOR_GUESTMII_SLOTS: u32 = 6; pub const MIISELECTOR_USERMII_SLOTS: u32 = 100; pub const MIISELECTOR_GUESTMII_NAME_LEN: u32 = 12; pub const ARCHIVE_DIRITER_MAGIC: u32 = 1751347809; +pub const LINK3DS_COMM_PORT: u32 = 17491; pub type __int8_t = ::libc::c_schar; pub type __uint8_t = ::libc::c_uchar; pub type __int16_t = ::libc::c_short; @@ -3446,7 +3453,10 @@ extern "C" { fb_b: *const ::libc::c_void, stride: u32_, mode: u32_, - ); + ) -> bool; +} +extern "C" { + pub fn gspIsPresentPending(screen: ::libc::c_uint) -> bool; } extern "C" { pub fn gspSetEventCallback( @@ -3628,8 +3638,8 @@ pub struct PrintConsole { pub windowWidth: ::libc::c_int, pub windowHeight: ::libc::c_int, pub tabSize: ::libc::c_int, - pub fg: ::libc::c_int, - pub bg: ::libc::c_int, + pub fg: u16_, + pub bg: u16_, pub flags: ::libc::c_int, pub PrintChar: ConsolePrint, pub consoleInitialised: bool, @@ -4040,12 +4050,26 @@ extern "C" { extern "C" { pub fn mappableFree(mem: *mut ::libc::c_void); } +pub const VRAM_ALLOC_A: vramAllocPos = 1; +pub const VRAM_ALLOC_B: vramAllocPos = 2; +pub const VRAM_ALLOC_ANY: vramAllocPos = 3; +pub type vramAllocPos = ::libc::c_uint; extern "C" { pub fn vramAlloc(size: size_t) -> *mut ::libc::c_void; } +extern "C" { + pub fn vramAllocAt(size: size_t, pos: vramAllocPos) -> *mut ::libc::c_void; +} extern "C" { pub fn vramMemAlign(size: size_t, alignment: size_t) -> *mut ::libc::c_void; } +extern "C" { + pub fn vramMemAlignAt( + size: size_t, + alignment: size_t, + pos: vramAllocPos, + ) -> *mut ::libc::c_void; +} extern "C" { pub fn vramRealloc(mem: *mut ::libc::c_void, size: size_t) -> *mut ::libc::c_void; } @@ -4414,6 +4438,13 @@ impl Default for FS_Path { } } } +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct FS_SdMmcSpeedInfo { + pub highSpeedModeEnabled: bool, + pub usesHighestClockRate: bool, + pub sdClkCtrl: u16_, +} pub type FS_Archive = u64_; extern "C" { pub fn fsInit() -> Result; @@ -4553,10 +4584,10 @@ extern "C" { pub fn FSUSER_GetNandCid(out: *mut u8_, length: u32_) -> Result; } extern "C" { - pub fn FSUSER_GetSdmcSpeedInfo(speedInfo: *mut u32_) -> Result; + pub fn FSUSER_GetSdmcSpeedInfo(speedInfo: *mut FS_SdMmcSpeedInfo) -> Result; } extern "C" { - pub fn FSUSER_GetNandSpeedInfo(speedInfo: *mut u32_) -> Result; + pub fn FSUSER_GetNandSpeedInfo(speedInfo: *mut FS_SdMmcSpeedInfo) -> Result; } extern "C" { pub fn FSUSER_GetSdmcLog(out: *mut u8_, length: u32_) -> Result; @@ -5027,6 +5058,19 @@ pub struct AM_TWLPartitionInfo { pub titlesCapacity: u64_, pub titlesFreeSpace: u64_, } +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct AM_ContentInfo { + pub index: u16_, + pub type_: u16_, + pub contentId: u32_, + pub size: u64_, + pub flags: u8_, + pub padding: [u8_; 7usize], +} +pub const AM_CONTENT_DOWNLOADED: AM_ContentInfoFlags = 1; +pub const AM_CONTENT_OWNED: AM_ContentInfoFlags = 2; +pub type AM_ContentInfoFlags = ::libc::c_uint; extern "C" { pub fn amInit() -> Result; } @@ -5325,6 +5369,23 @@ extern "C" { extern "C" { pub fn AM_DeleteAllTwlTitles() -> Result; } +extern "C" { + pub fn AMAPP_GetDLCContentInfoCount( + count: *mut u32_, + mediatype: FS_MediaType, + titleID: u64_, + ) -> Result; +} +extern "C" { + pub fn AMAPP_ListDLCContentInfos( + contentInfoRead: *mut u32_, + mediatype: FS_MediaType, + titleID: u64_, + contentInfoCount: u32_, + offset: u32_, + contentInfos: *mut AM_ContentInfo, + ) -> Result; +} extern "C" { pub fn ampxiInit(servhandle: Handle) -> Result; } @@ -7305,10 +7366,10 @@ extern "C" { pub fn FSPXI_GetNandCid(serviceHandle: Handle, out: *mut ::libc::c_void, size: u32_) -> Result; } extern "C" { - pub fn FSPXI_GetSdmcSpeedInfo(serviceHandle: Handle, out: *mut u32_) -> Result; + pub fn FSPXI_GetSdmcSpeedInfo(serviceHandle: Handle, out: *mut FS_SdMmcSpeedInfo) -> Result; } extern "C" { - pub fn FSPXI_GetNandSpeedInfo(serviceHandle: Handle, out: *mut u32_) -> Result; + pub fn FSPXI_GetNandSpeedInfo(serviceHandle: Handle, out: *mut FS_SdMmcSpeedInfo) -> Result; } extern "C" { pub fn FSPXI_GetSdmcLog(serviceHandle: Handle, out: *mut ::libc::c_void, size: u32_) -> Result; @@ -10314,6 +10375,9 @@ extern "C" { extern "C" { pub fn ptmuExit(); } +extern "C" { + pub fn ptmuGetSessionHandle() -> *mut Handle; +} extern "C" { pub fn PTMU_GetShellState(out: *mut u8_) -> Result; } @@ -10361,6 +10425,9 @@ extern "C" { extern "C" { pub fn ptmSysmExit(); } +extern "C" { + pub fn ptmSysmGetSessionHandle() -> *mut Handle; +} extern "C" { pub fn PTMSYSM_RequestSleep() -> Result; } @@ -10379,6 +10446,9 @@ extern "C" { extern "C" { pub fn PTMSYSM_Awaken() -> Result; } +extern "C" { + pub fn PTMSYSM_SetUserTime(msY2k: s64) -> Result; +} extern "C" { pub fn PTMSYSM_InvalidateSystemTime() -> Result; } @@ -10400,6 +10470,30 @@ extern "C" { extern "C" { pub fn PTMSYSM_RebootAsync(timeout: u64_) -> Result; } +extern "C" { + pub fn ptmGetsInit() -> Result; +} +extern "C" { + pub fn ptmGetsExit(); +} +extern "C" { + pub fn ptmGetsGetSessionHandle() -> *mut Handle; +} +extern "C" { + pub fn PTMGETS_GetSystemTime(outMsY2k: *mut s64) -> Result; +} +extern "C" { + pub fn ptmSetsInit() -> Result; +} +extern "C" { + pub fn ptmSetsExit(); +} +extern "C" { + pub fn ptmSetsGetSessionHandle() -> *mut Handle; +} +extern "C" { + pub fn PTMSETS_SetSystemTime(msY2k: s64) -> Result; +} pub const WAIT_NONE: PXIDEV_WaitType = 0; pub const WAIT_SLEEP: PXIDEV_WaitType = 1; pub const WAIT_IREQ_RETURN: PXIDEV_WaitType = 2; @@ -13543,3 +13637,9 @@ extern "C" { extern "C" { pub fn gdbHioDevSystem(command: *const ::libc::c_char) -> ::libc::c_int; } +extern "C" { + pub static mut __3dslink_host: in_addr; +} +extern "C" { + pub fn link3dsConnectToHost(redirStdout: bool, redirStderr: bool) -> ::libc::c_int; +}