From b547cf0502bc82c49f7f8149376d97e9bd1e7ff6 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 12 May 2025 18:06:21 -0500 Subject: [PATCH 01/26] :wip: feat(ios/es): Start on implementing E-Ticket system ioctls --- src/ios.rs | 7 +- src/ios/es.rs | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 src/ios/es.rs diff --git a/src/ios.rs b/src/ios.rs index 490ad93..3e9ef9a 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -11,9 +11,14 @@ pub mod dolphin; /// Filesystem IOS Device /// -/// '/dev/fs'' device helper functions. +/// '/dev/fs' device helper functions. pub mod fs; +/// E-Ticket System IOS Device +/// +/// `/dev/es` device hellper functions. +pub mod es; + #[repr(u32)] /// Interprocess Control / IOS File Mode pub enum Mode { diff --git a/src/ios/es.rs b/src/ios/es.rs new file mode 100644 index 0000000..6f02f74 --- /dev/null +++ b/src/ios/es.rs @@ -0,0 +1,218 @@ +/// E-Ticket System Supported Ioctls +pub enum Ioctl { + AddTicket, + AddTitleStart, + AddContentStart, + AddContentData, + AddContentFinish, + AddTitleFinish, + GetDeviceId, + Launch, + OpenActiveTitleContent, + ReadContent, + CloseContent, + GetOwnedTitleCount, + GetOwnedTitles, + GetTitleCount, + GetTitles, + GetViewCount, + GetViews, + GetTitleMetadataViewCount, + GetTitleMetadataViews, + GetConsumption, + Deletetitle, + DeleteTicket, + DiskInterfaceGetTitleMetadataViewSize, + DiskInterfaceGetTitleMetadataView, + DiskInterfaceGetTicketView, + DiskInterfaceVerify, + GetTitleDir, + GetDeviceCertificate, + ImportBoot, + GetTitleId, + SetUid, + DeleteTitleContent, + SeekContent, + OpenContent, + LauchBackwardsCompatibility, + ExportTitleInitalize, + ExportContentBegin, + ExportContentData, + ExportContentEnd, + ExportTitleDone, + AddTitleMetadata, + Encrypt, + Decrypt, + GetBoot2Version, + AddTitleCancel, + Sign, + VerifySign, + GetStoredContentCount, + GetStoredContents, + GetStoredTitleMetadataSize, + GetStoredTitleMetadata, + GetSharedContentCount, + GetSharedContents, + DeleteSharedContents, + DiskInterfaceGetTitleMetadataSize, + DiskInterfaceGetTitleMetadata, + DiskInterfaceVerifyWithView, + SetupStreamKey, + DeleteStreamKey, + DeleteContent, + // Invalid3F + GetVersion0TicketFromView, + // Unknown41, + // Unknown42, + GetTicketSizeFromView, + GetTicketFromView, + CheckKoreaRegion, +} + +impl From for i32 { + fn from(value: Ioctl) -> Self { + match value { + Ioctl::AddTicket => todo!(), + Ioctl::AddTitleStart => todo!(), + Ioctl::AddContentStart => todo!(), + Ioctl::AddContentData => todo!(), + Ioctl::AddContentFinish => todo!(), + Ioctl::AddTitleFinish => todo!(), + Ioctl::GetDeviceId => todo!(), + Ioctl::Launch => todo!(), + Ioctl::OpenActiveTitleContent => todo!(), + Ioctl::ReadContent => todo!(), + Ioctl::CloseContent => todo!(), + Ioctl::GetOwnedTitleCount => todo!(), + Ioctl::GetOwnedTitles => todo!(), + Ioctl::GetTitleCount => 14, //0xE + Ioctl::GetTitles => 15, //0xF + Ioctl::GetViewCount => todo!(), + Ioctl::GetViews => todo!(), + Ioctl::GetTitleMetadataViewCount => todo!(), + Ioctl::GetTitleMetadataViews => todo!(), + Ioctl::GetConsumption => todo!(), + Ioctl::Deletetitle => todo!(), + Ioctl::DeleteTicket => todo!(), + Ioctl::DiskInterfaceGetTitleMetadataViewSize => todo!(), + Ioctl::DiskInterfaceGetTitleMetadataView => todo!(), + Ioctl::DiskInterfaceGetTicketView => todo!(), + Ioctl::DiskInterfaceVerify => todo!(), + Ioctl::GetTitleDir => todo!(), + Ioctl::GetDeviceCertificate => todo!(), + Ioctl::ImportBoot => todo!(), + Ioctl::GetTitleId => todo!(), + Ioctl::SetUid => todo!(), + Ioctl::DeleteTitleContent => todo!(), + Ioctl::SeekContent => todo!(), + Ioctl::OpenContent => todo!(), + Ioctl::LauchBackwardsCompatibility => todo!(), + Ioctl::ExportTitleInitalize => todo!(), + Ioctl::ExportContentBegin => todo!(), + Ioctl::ExportContentData => todo!(), + Ioctl::ExportContentEnd => todo!(), + Ioctl::ExportTitleDone => todo!(), + Ioctl::AddTitleMetadata => todo!(), + Ioctl::Encrypt => todo!(), + Ioctl::Decrypt => todo!(), + Ioctl::GetBoot2Version => todo!(), + Ioctl::AddTitleCancel => todo!(), + Ioctl::Sign => todo!(), + Ioctl::VerifySign => todo!(), + Ioctl::GetStoredContentCount => todo!(), + Ioctl::GetStoredContents => todo!(), + Ioctl::GetStoredTitleMetadataSize => todo!(), + Ioctl::GetStoredTitleMetadata => todo!(), + Ioctl::GetSharedContentCount => todo!(), + Ioctl::GetSharedContents => todo!(), + Ioctl::DeleteSharedContents => todo!(), + Ioctl::DiskInterfaceGetTitleMetadataSize => todo!(), + Ioctl::DiskInterfaceGetTitleMetadata => todo!(), + Ioctl::DiskInterfaceVerifyWithView => todo!(), + Ioctl::SetupStreamKey => todo!(), + Ioctl::DeleteStreamKey => todo!(), + Ioctl::DeleteContent => todo!(), + Ioctl::GetVersion0TicketFromView => todo!(), + Ioctl::GetTicketSizeFromView => todo!(), + Ioctl::GetTicketFromView => todo!(), + Ioctl::CheckKoreaRegion => todo!(), + } + } +} + +static DEV_ES: &CStr = c"/dev/es"; + +use core::ffi::CStr; + +use alloc::vec::Vec; + +use crate::ios; + +pub fn get_title_count() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; 4]; + ios::ioctlv::<0, 1, 1>(es, Ioctl::GetTitleCount, &[], &mut [&mut out_buf])?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(out_buf)) +} + +pub fn get_titles(title_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + // TODO: Avoid allocation + let mut out_buf = alloc::vec![0u8; title_count as usize * core::mem::size_of::()]; + + let count: [u8; 4] = title_count.to_be_bytes(); + ios::ioctlv::<1, 1, 2>(es, Ioctl::GetTitles, &[&count], &mut [&mut out_buf[..]])?; + + let _ = ios::close(es); + + // TODO: Avoid allocation + Ok(out_buf + .chunks_exact(core::mem::size_of::()) + .map(|bytes| u64::from_be_bytes(bytes.try_into().expect("should fit"))) + .collect()) +} + +pub fn get_stored_title_metadata_size(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let in_buf = title_id.to_be_bytes(); + let mut out_buf = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetStoredTitleMetadataSize, + &[&in_buf], + &mut [&mut out_buf], + )?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes( + out_buf.try_into().map_err(|_| ios::Error::Invalid)?, + )) +} + +// TODO: Proper enuming since there are different signature types and differing sizes for them +pub fn get_stored_title_metadata(title_id: u64, size: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let title_buf = title_id.to_be_bytes(); + let size_buf = size.to_be_bytes(); + let size_usize: usize = usize::try_from(size).map_err(|_| ios::Error::Invalid)?; + // TODO: Avoid allocation + let mut out_buf = alloc::vec![0u8; size_usize]; + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::GetStoredTitleMetadata, + &[&title_buf, &size_buf], + &mut [&mut out_buf[..]], + )?; + + let _ = ios::close(es); + + Ok(out_buf) +} From adfd90a835058887a5909a1c494b4f9d0d53004d Mon Sep 17 00:00:00 2001 From: ProfElements Date: Fri, 16 May 2025 03:19:46 -0500 Subject: [PATCH 02/26] :bug: fix(ios/es): Get some of the Ioctls their proper number, fix names and add missing Ioctls --- src/ios/es.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 6f02f74..113b446 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -15,12 +15,14 @@ pub enum Ioctl { GetOwnedTitles, GetTitleCount, GetTitles, + GetTitleContentsCount, + GetTitleContents, GetViewCount, GetViews, - GetTitleMetadataViewCount, - GetTitleMetadataViews, + GetTicketViewCount, + GetTicketViews, GetConsumption, - Deletetitle, + DeleteTitle, DeleteTicket, DiskInterfaceGetTitleMetadataViewSize, DiskInterfaceGetTitleMetadataView, @@ -78,21 +80,23 @@ impl From for i32 { Ioctl::AddContentData => todo!(), Ioctl::AddContentFinish => todo!(), Ioctl::AddTitleFinish => todo!(), - Ioctl::GetDeviceId => todo!(), + Ioctl::GetDeviceId => 7, Ioctl::Launch => todo!(), Ioctl::OpenActiveTitleContent => todo!(), Ioctl::ReadContent => todo!(), Ioctl::CloseContent => todo!(), - Ioctl::GetOwnedTitleCount => todo!(), - Ioctl::GetOwnedTitles => todo!(), - Ioctl::GetTitleCount => 14, //0xE - Ioctl::GetTitles => 15, //0xF + Ioctl::GetOwnedTitleCount => 12, + Ioctl::GetOwnedTitles => 13, + Ioctl::GetTitleCount => 14, + Ioctl::GetTitles => 15, + Ioctl::GetTitleContentsCount => 16, + Ioctl::GetTitleContents => 17, Ioctl::GetViewCount => todo!(), Ioctl::GetViews => todo!(), - Ioctl::GetTitleMetadataViewCount => todo!(), - Ioctl::GetTitleMetadataViews => todo!(), + Ioctl::GetTicketViewCount => todo!(), + Ioctl::GetTicketViews => todo!(), Ioctl::GetConsumption => todo!(), - Ioctl::Deletetitle => todo!(), + Ioctl::DeleteTitle => todo!(), Ioctl::DeleteTicket => todo!(), Ioctl::DiskInterfaceGetTitleMetadataViewSize => todo!(), Ioctl::DiskInterfaceGetTitleMetadataView => todo!(), From ee8bbf962fb7fcc4b227d83875bf9c98668d7a01 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Fri, 16 May 2025 03:20:12 -0500 Subject: [PATCH 03/26] :sparkles: feat(ios/es): Add more Ioctl helpers --- src/ios/es.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/src/ios/es.rs b/src/ios/es.rs index 113b446..51253d8 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -152,6 +152,47 @@ use alloc::vec::Vec; use crate::ios; +pub fn get_device_id() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; 4]; + ios::ioctlv::<0, 1, 1>(es, Ioctl::GetDeviceId, &[], &mut [&mut out_buf])?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(out_buf)) +} + +pub fn get_owned_title_count() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; 4]; + ios::ioctlv::<0, 1, 1>(es, Ioctl::GetOwnedTitleCount, &[], &mut [&mut out_buf])?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(out_buf)) +} + +pub fn get_owned_titles(title_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + //TODO: Avoid allocation + let mut out_buf = alloc::vec![0u8; core::mem::size_of::() * title_count as usize]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetOwnedTitles, + &[&title_count.to_be_bytes()], + &mut [out_buf.as_mut_slice()], + )?; + + // TODO: Avoid allocation + Ok(out_buf + .chunks_exact(core::mem::size_of::()) + .map(|bytes| u64::from_be_bytes(bytes.try_into().expect("should fit"))) + .collect()) +} + pub fn get_title_count() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -181,6 +222,78 @@ pub fn get_titles(title_count: u32) -> Result, ios::Error> { .collect()) } +pub fn get_title_contents_count(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetTitleContentsCount, + &[&title_id.to_be_bytes()], + &mut [&mut out_buf], + )?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(out_buf)) +} + +pub fn get_title_counts(title_id: u64, content_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + //TODO: avoid allocation + let mut out_buf = alloc::vec![0u8; core::mem::size_of::() * content_count as usize]; + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::GetTitleContents, + &[&title_id.to_be_bytes(), &content_count.to_be_bytes()], + &mut [out_buf.as_mut_slice()], + )?; + + let _ = ios::close(es); + + //TODO: avoid allocation + Ok(out_buf + .chunks_exact(core::mem::size_of::()) + .map(|bytes| u32::from_be_bytes(bytes.try_into().expect("should fit"))) + .collect()) +} + +pub fn get_ticket_view_count(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetTicketViewCount, + &[&title_id.to_be_bytes()], + &mut [&mut out_buf], + )?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(out_buf)) +} + +// TODO: actually returns a Vec but I haven't made teh `TicketView` struct yet and +// don't want to do structs till the end of impling all these +pub fn get_ticket_views(title_id: u64, view_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + const TICKET_VIEW_SIZE: usize = 216; // 0xD8 + let mut out_buf = alloc::vec![0u8; TICKET_VIEW_SIZE * view_count as usize]; + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::GetTicketViews, + &[&title_id.to_be_bytes(), &view_count.to_be_bytes()], + &mut [out_buf.as_mut_slice()], + )?; + + let _ = ios::close(es); + + Ok(out_buf) +} + pub fn get_stored_title_metadata_size(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; From 9fb3ddf0fda219e7b8926ab95a012df5df0dc542 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 19 May 2025 01:40:07 -0500 Subject: [PATCH 04/26] :sparkles: feat(ios/es): Add even more Ioctl helpers --- src/ios/es.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 51253d8..1467083 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -17,10 +17,10 @@ pub enum Ioctl { GetTitles, GetTitleContentsCount, GetTitleContents, - GetViewCount, - GetViews, GetTicketViewCount, GetTicketViews, + GetTitleMetadataViewSize, + GetTitleMetadataView, GetConsumption, DeleteTitle, DeleteTicket, @@ -90,12 +90,11 @@ impl From for i32 { Ioctl::GetTitleCount => 14, Ioctl::GetTitles => 15, Ioctl::GetTitleContentsCount => 16, - Ioctl::GetTitleContents => 17, - Ioctl::GetViewCount => todo!(), - Ioctl::GetViews => todo!(), - Ioctl::GetTicketViewCount => todo!(), - Ioctl::GetTicketViews => todo!(), - Ioctl::GetConsumption => todo!(), + Ioctl::GetTicketViewCount => 17, + Ioctl::GetTicketViews => 18, + Ioctl::GetTitleMetadataViewSize => 20, + Ioctl::GetTitleMetadataView => 21, + Ioctl::GetConsumption => 22, Ioctl::DeleteTitle => todo!(), Ioctl::DeleteTicket => todo!(), Ioctl::DiskInterfaceGetTitleMetadataViewSize => todo!(), @@ -140,6 +139,7 @@ impl From for i32 { Ioctl::GetTicketSizeFromView => todo!(), Ioctl::GetTicketFromView => todo!(), Ioctl::CheckKoreaRegion => todo!(), + Ioctl::GetTitleContents => todo!(), } } } @@ -294,6 +294,81 @@ pub fn get_ticket_views(title_id: u64, view_count: u32) -> Result, ios:: Ok(out_buf) } +pub fn get_title_metadata_view_size(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let title_id_in = title_id.to_be_bytes(); + let mut out_buf = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetTitleMetadataViewSize, + &[&title_id_in], + &mut [&mut out_buf], + )?; + + let _ = ios::close(es); + Ok(u32::from_be_bytes(out_buf)) +} + +//TODO: Return `TitleMetadataView` instead of owned allocation +pub fn get_title_metadata_view(title_id: u64, size: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let title_id_in_buf = title_id.to_be_bytes(); + let size_in_buf = size.to_be_bytes(); + + let size = usize::try_from(size).map_err(|_| ios::Error::Invalid)?; + let mut out_buf = alloc::vec![0u8; size]; + + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::GetTitleMetadataView, + &[&title_id_in_buf, &size_in_buf], + &mut [out_buf.as_mut_slice()], + )?; + + let _ = ios::close(es); + + Ok(out_buf) +} + +pub fn get_consumption_count(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let title_id_in_buf = title_id.to_be_bytes(); + let mut out_buf = [0u8; 4]; + ios::ioctlv::<1, 2, 3>( + es, + Ioctl::GetConsumption, + &[&title_id_in_buf], + &mut [&mut [], &mut out_buf], + )?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(out_buf)) +} + +pub fn get_consumption(title_id: u64, limit_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + const TIKLIMIT_SIZE: usize = 8; + let limit_count = usize::try_from(limit_count).map_err(|_| ios::Error::Invalid)?; + + let title_id_in_buf = title_id.to_be_bytes(); + let mut limit_out_buf = alloc::vec![0u8; TIKLIMIT_SIZE * limit_count]; + ios::ioctlv::<1, 2, 3>( + es, + Ioctl::GetConsumption, + &[&title_id_in_buf], + &mut [limit_out_buf.as_mut_slice(), &mut []], + )?; + + let _ = ios::close(es); + + Ok(limit_out_buf) +} + pub fn get_stored_title_metadata_size(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; From a504cd2a4cd1e3e7523d5ac9c4f27311dc1a729c Mon Sep 17 00:00:00 2001 From: ProfElements Date: Tue, 27 May 2025 02:08:04 -0500 Subject: [PATCH 05/26] :sparkles: feat(ios/es): Add `AddTicket` IOCTL --- src/ios/es.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 1467083..b8aad7a 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -74,7 +74,7 @@ pub enum Ioctl { impl From for i32 { fn from(value: Ioctl) -> Self { match value { - Ioctl::AddTicket => todo!(), + Ioctl::AddTicket => 1, Ioctl::AddTitleStart => todo!(), Ioctl::AddContentStart => todo!(), Ioctl::AddContentData => todo!(), @@ -152,6 +152,25 @@ use alloc::vec::Vec; use crate::ios; +pub fn add_ticket( + signed_ticket: &[u8], + signed_certs: &[u8], + signed_crl: &[u8], +) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<3, 0, 3>( + es, + Ioctl::AddTicket, + &[signed_ticket, signed_certs, signed_crl], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + pub fn get_device_id() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; From a6050c6359cec42d4bbd2150057558c625c443d3 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Tue, 27 May 2025 19:17:03 -0500 Subject: [PATCH 06/26] :sparkles: feat(ios): Add `ioctlv_reboot` and `ioctlv_reboot_background`. I needed them for `launch_title` from the ES --- src/ios.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/src/ios.rs b/src/ios.rs index 3e9ef9a..1c40b3e 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -370,3 +370,145 @@ pub fn ioctlv< val => Err(Error::UnknownErrorCode(val)), } } + +/// Attempts to call ioctl using a file descriptor with multiple input and output buffers +/// Reboots into a new `IOS` upon execution +/// +/// Attempts to call `ioctl` using `fd` with `bufs_in` and `bufs_out` +/// +/// # Errors +/// See [`Error`] +/// +pub fn ioctlv_reboot< + const COUNT_IN: usize, + const COUNT_OUT: usize, + //Invariant: This must be COUNT_IN + COUNT_OUT (waiting for `generic_const_exprs` to be + //stabilizied) + const COUNT_IN_OUT: usize, +>( + fd: FileDescriptor, + ioctl: impl Into, + buf_ins: &[&[u8]], + buf_outs: &mut [&mut [u8]], +) -> Result<(), Error> { + type Ioctlv = ogc_sys::_ioctlv; + debug_assert!(buf_ins.len() == COUNT_IN); + debug_assert!(buf_outs.len() == COUNT_OUT); + debug_assert!(COUNT_IN + COUNT_OUT == COUNT_IN_OUT); + + let mut ioctls = [Ioctlv { + data: core::ptr::null_mut(), + len: 0, + }; COUNT_IN_OUT]; + //SAFETY: I promise that i don't modify the contents of in buffers up to COUNT_IN + for (i, buf_in) in buf_ins.iter().enumerate() { + ioctls[i] = Ioctlv { + data: buf_in.as_ptr().cast_mut().cast(), + len: buf_in + .len() + .try_into() + .map_err(|_| Error::BufferTooLong(buf_in.len()))?, + } + } + + for (i, buf_out) in buf_outs.iter_mut().enumerate() { + ioctls[COUNT_IN + i] = Ioctlv { + data: buf_out.as_mut_ptr().cast(), + len: buf_out + .len() + .try_into() + .map_err(|_| Error::BufferTooLong(buf_out.len()))?, + } + } + + match unsafe { + ogc_sys::IOS_IoctlvReboot( + fd.0, + ioctl.into(), + COUNT_IN + .try_into() + .map_err(|_| Error::TooManyInputs(COUNT_IN))?, + COUNT_OUT + .try_into() + .map_err(|_| Error::TooManyOutputs(COUNT_OUT))?, + ioctls.as_ptr().cast_mut(), + ) + } { + val if { val == -4 || val == -5 || val == -6 || val == -8 || val == -22 } => { + Err(Error::try_from(val).map_err(|()| Error::UnknownErrorCode(val))?) + } + val if { val >= 0 } => Ok(()), + val => Err(Error::UnknownErrorCode(val)), + } +} + +/// Attempts to call ioctl using a file descriptor with multiple input and output buffers +/// Restarts `IOS` in the background upon execution +/// +/// Attempts to call `ioctl` using `fd` with `bufs_in` and `bufs_out` +/// +/// # Errors +/// See [`Error`] +/// +pub fn ioctlv_reboot_background< + const COUNT_IN: usize, + const COUNT_OUT: usize, + //Invariant: This must be COUNT_IN + COUNT_OUT (waiting for `generic_const_exprs` to be + //stabilizied) + const COUNT_IN_OUT: usize, +>( + fd: FileDescriptor, + ioctl: impl Into, + buf_ins: &[&[u8]], + buf_outs: &mut [&mut [u8]], +) -> Result<(), Error> { + type Ioctlv = ogc_sys::_ioctlv; + debug_assert!(buf_ins.len() == COUNT_IN); + debug_assert!(buf_outs.len() == COUNT_OUT); + debug_assert!(COUNT_IN + COUNT_OUT == COUNT_IN_OUT); + + let mut ioctls = [Ioctlv { + data: core::ptr::null_mut(), + len: 0, + }; COUNT_IN_OUT]; + //SAFETY: I promise that i don't modify the contents of in buffers up to COUNT_IN + for (i, buf_in) in buf_ins.iter().enumerate() { + ioctls[i] = Ioctlv { + data: buf_in.as_ptr().cast_mut().cast(), + len: buf_in + .len() + .try_into() + .map_err(|_| Error::BufferTooLong(buf_in.len()))?, + } + } + + for (i, buf_out) in buf_outs.iter_mut().enumerate() { + ioctls[COUNT_IN + i] = Ioctlv { + data: buf_out.as_mut_ptr().cast(), + len: buf_out + .len() + .try_into() + .map_err(|_| Error::BufferTooLong(buf_out.len()))?, + } + } + + match unsafe { + ogc_sys::IOS_IoctlvRebootBackground( + fd.0, + ioctl.into(), + COUNT_IN + .try_into() + .map_err(|_| Error::TooManyInputs(COUNT_IN))?, + COUNT_OUT + .try_into() + .map_err(|_| Error::TooManyOutputs(COUNT_OUT))?, + ioctls.as_ptr().cast_mut(), + ) + } { + val if { val == -4 || val == -5 || val == -6 || val == -8 || val == -22 } => { + Err(Error::try_from(val).map_err(|()| Error::UnknownErrorCode(val))?) + } + val if { val >= 0 } => Ok(()), + val => Err(Error::UnknownErrorCode(val)), + } +} From 2d91b70436a8067a8b80cf31f1dc75a1f9dbab42 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Tue, 27 May 2025 19:17:50 -0500 Subject: [PATCH 07/26] :sparkles: feat(ios/es): Add Even more IOCTL helpers. Im not thinking much right now, will double check if they make sense later --- src/ios/es.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index b8aad7a..bd92bcd 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -75,10 +75,10 @@ impl From for i32 { fn from(value: Ioctl) -> Self { match value { Ioctl::AddTicket => 1, - Ioctl::AddTitleStart => todo!(), - Ioctl::AddContentStart => todo!(), - Ioctl::AddContentData => todo!(), - Ioctl::AddContentFinish => todo!(), + Ioctl::AddTitleStart => 2, + Ioctl::AddContentStart => 3, + Ioctl::AddContentData => 4, + Ioctl::AddContentFinish => 5, Ioctl::AddTitleFinish => todo!(), Ioctl::GetDeviceId => 7, Ioctl::Launch => todo!(), @@ -171,6 +171,85 @@ pub fn add_ticket( Ok(()) } +pub fn add_title_start( + signed_title_meta: &[u8], + signed_certs: &[u8], + signed_crl: &[u8], +) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<4, 0, 4>( + es, + Ioctl::AddTitleStart, + &[ + signed_title_meta, + signed_certs, + signed_crl, + &1u32.to_be_bytes(), + ], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn add_content_start(title_id: u64, content_id: u32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<2, 0, 2>( + es, + Ioctl::AddContentStart, + &[&title_id.to_be_bytes(), &content_id.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn add_content_data(content_fd: i32, data: &[u8]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<2, 0, 2>( + es, + Ioctl::AddContentData, + &[&content_fd.to_be_bytes(), data], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn add_content_finish(content_id: u32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>( + es, + Ioctl::AddContentFinish, + &[&content_id.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn add_title_finish() -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<0, 0, 0>(es, Ioctl::AddTitleFinish, &[], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + pub fn get_device_id() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -182,6 +261,21 @@ pub fn get_device_id() -> Result { Ok(u32::from_be_bytes(out_buf)) } +pub fn launch_title(title_id: u64, ticket_view: &[u8]) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv_reboot::<2, 0, 2>( + es, + Ioctl::Launch, + &[&title_id.to_be_bytes(), ticket_view], + &mut [], + )?; + + loop {} +} + +pub fn open_active_title_content(content_idx: u32) -> Result<(), ios::Error> {} + pub fn get_owned_title_count() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; From 68f383f54f0f11bd2403c039b3977b67bfe4f289 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sat, 7 Jun 2025 18:19:01 -0500 Subject: [PATCH 08/26] :sparkles: feat(ios/es): Another one. More helpers --- src/ios/es.rs | 49 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index bd92bcd..8e4977c 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -79,12 +79,12 @@ impl From for i32 { Ioctl::AddContentStart => 3, Ioctl::AddContentData => 4, Ioctl::AddContentFinish => 5, - Ioctl::AddTitleFinish => todo!(), + Ioctl::AddTitleFinish => 6, Ioctl::GetDeviceId => 7, - Ioctl::Launch => todo!(), - Ioctl::OpenActiveTitleContent => todo!(), - Ioctl::ReadContent => todo!(), - Ioctl::CloseContent => todo!(), + Ioctl::Launch => 8, + Ioctl::OpenActiveTitleContent => 9, + Ioctl::ReadContent => 10, + Ioctl::CloseContent => 11, Ioctl::GetOwnedTitleCount => 12, Ioctl::GetOwnedTitles => 13, Ioctl::GetTitleCount => 14, @@ -274,7 +274,44 @@ pub fn launch_title(title_id: u64, ticket_view: &[u8]) -> Result loop {} } -pub fn open_active_title_content(content_idx: u32) -> Result<(), ios::Error> {} +pub fn open_active_title_content(content_idx: u32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>( + es, + Ioctl::OpenActiveTitleContent, + &[&content_idx.to_be_bytes()], + &mut [], + )?; + + Ok(()) +} + +pub fn read_content(content_file_descriptor: i32, out_buf: &mut [u8]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::ReadContent, + &[&content_file_descriptor.to_be_bytes()], + &mut [out_buf], + )?; + + Ok(()) +} + +pub fn close_content(content_file_descriptor: i32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>( + es, + Ioctl::CloseContent, + &[&content_file_descriptor.to_be_bytes()], + &mut [], + )?; + + Ok(()) +} pub fn get_owned_title_count() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; From 4d1124030f8c05db453b0af7dcb8ed503be9cbb0 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sat, 7 Jun 2025 18:21:03 -0500 Subject: [PATCH 09/26] :bug: fix(ios/es): `Launch` never returns so use never type. I need the feature for that --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 82c4f39..244c6a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ #![feature(allocator_api)] #![feature(asm_experimental_arch)] #![feature(generic_const_exprs)] +#![feature(never_type)] extern crate alloc; From 15db3b8f5c1e6b9b9fac516709d094c7f307a527 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sun, 8 Jun 2025 16:18:56 -0500 Subject: [PATCH 10/26] :sparkles: feat(ios/es): Working through 'em. More IOCTL helpers --- src/ios/es.rs | 91 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 24 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 8e4977c..26ab94a 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -95,8 +95,8 @@ impl From for i32 { Ioctl::GetTitleMetadataViewSize => 20, Ioctl::GetTitleMetadataView => 21, Ioctl::GetConsumption => 22, - Ioctl::DeleteTitle => todo!(), - Ioctl::DeleteTicket => todo!(), + Ioctl::DeleteTitle => 23, + Ioctl::DeleteTicket => 24, Ioctl::DiskInterfaceGetTitleMetadataViewSize => todo!(), Ioctl::DiskInterfaceGetTitleMetadataView => todo!(), Ioctl::DiskInterfaceGetTicketView => todo!(), @@ -106,7 +106,7 @@ impl From for i32 { Ioctl::ImportBoot => todo!(), Ioctl::GetTitleId => todo!(), Ioctl::SetUid => todo!(), - Ioctl::DeleteTitleContent => todo!(), + Ioctl::DeleteTitleContent => 34, Ioctl::SeekContent => todo!(), Ioctl::OpenContent => todo!(), Ioctl::LauchBackwardsCompatibility => todo!(), @@ -128,7 +128,7 @@ impl From for i32 { Ioctl::GetStoredTitleMetadata => todo!(), Ioctl::GetSharedContentCount => todo!(), Ioctl::GetSharedContents => todo!(), - Ioctl::DeleteSharedContents => todo!(), + Ioctl::DeleteSharedContents => 56, Ioctl::DiskInterfaceGetTitleMetadataSize => todo!(), Ioctl::DiskInterfaceGetTitleMetadata => todo!(), Ioctl::DiskInterfaceVerifyWithView => todo!(), @@ -499,26 +499,6 @@ pub fn get_consumption_count(title_id: u64) -> Result { Ok(u32::from_be_bytes(out_buf)) } -pub fn get_consumption(title_id: u64, limit_count: u32) -> Result, ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; - - const TIKLIMIT_SIZE: usize = 8; - let limit_count = usize::try_from(limit_count).map_err(|_| ios::Error::Invalid)?; - - let title_id_in_buf = title_id.to_be_bytes(); - let mut limit_out_buf = alloc::vec![0u8; TIKLIMIT_SIZE * limit_count]; - ios::ioctlv::<1, 2, 3>( - es, - Ioctl::GetConsumption, - &[&title_id_in_buf], - &mut [limit_out_buf.as_mut_slice(), &mut []], - )?; - - let _ = ios::close(es); - - Ok(limit_out_buf) -} - pub fn get_stored_title_metadata_size(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -558,3 +538,66 @@ pub fn get_stored_title_metadata(title_id: u64, size: u32) -> Result, io Ok(out_buf) } + +pub fn get_consumption(title_id: u64, limit_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + const TIKLIMIT_SIZE: usize = 8; + let limit_count = usize::try_from(limit_count).map_err(|_| ios::Error::Invalid)?; + + let title_id_in_buf = title_id.to_be_bytes(); + let mut limit_out_buf = alloc::vec![0u8; TIKLIMIT_SIZE * limit_count]; + ios::ioctlv::<1, 2, 3>( + es, + Ioctl::GetConsumption, + &[&title_id_in_buf], + &mut [limit_out_buf.as_mut_slice(), &mut []], + )?; + + let _ = ios::close(es); + + Ok(limit_out_buf) +} + +pub fn delete_title(title_id: u64) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>(es, Ioctl::DeleteTitle, &[&title_id.to_be_bytes()], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn delete_ticket(ticket_view: &[u8]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>(es, Ioctl::DeleteTicket, &[ticket_view], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn delete_title_content(title_id: u64) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>( + es, + Ioctl::DeleteTitleContent, + &[&title_id.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>(es, Ioctl::DeleteSharedContents, &[sha1_hash], &mut [])?; + + Ok(()) +} From fcd1c4194b08ae1db387e45f1c06ae483f851b5d Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 9 Jun 2025 16:43:55 -0500 Subject: [PATCH 11/26] :sparkles: feat(ios/es): ioctling it up --- src/ios/es.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 26ab94a..4f94ea9 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -97,9 +97,9 @@ impl From for i32 { Ioctl::GetConsumption => 22, Ioctl::DeleteTitle => 23, Ioctl::DeleteTicket => 24, - Ioctl::DiskInterfaceGetTitleMetadataViewSize => todo!(), - Ioctl::DiskInterfaceGetTitleMetadataView => todo!(), - Ioctl::DiskInterfaceGetTicketView => todo!(), + Ioctl::DiskInterfaceGetTitleMetadataViewSize => 25, + Ioctl::DiskInterfaceGetTitleMetadataView => 26, + Ioctl::DiskInterfaceGetTicketView => 27, Ioctl::DiskInterfaceVerify => todo!(), Ioctl::GetTitleDir => todo!(), Ioctl::GetDeviceCertificate => todo!(), @@ -148,7 +148,7 @@ static DEV_ES: &CStr = c"/dev/es"; use core::ffi::CStr; -use alloc::vec::Vec; +use alloc::{ffi::CString, vec::Vec}; use crate::ios; @@ -579,6 +579,119 @@ pub fn delete_ticket(ticket_view: &[u8]) -> Result<(), ios::Error> { Ok(()) } +pub fn disk_interface_get_title_metadata_view_size( + signed_title_meta: &[u8], +) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut size_buf: [u8; 4] = [0u8; 4]; + + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::DiskInterfaceGetTitleMetadataViewSize, + &[signed_title_meta], + &mut [&mut size_buf], + )?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes(size_buf)) +} + +pub fn disk_interface_get_title_metadata_view( + signed_title_meta: &[u8], + tmd_view_size: u32, +) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = alloc::vec![0u8; tmd_view_size as usize]; + + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::DiskInterfaceGetTitleMetadataView, + &[signed_title_meta, &tmd_view_size.to_be_bytes()], + &mut [out_buf.as_mut_slice()], + )?; + + let _ = ios::close(es); + + Ok(out_buf) +} + +const TICKET_VIEW_SIZE: usize = 216; // 0xD8 +pub fn disk_interface_get_ticket_view( + signed_ticket: &[u8], +) -> Result<[u8; TICKET_VIEW_SIZE], ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + let mut out_buf = [0u8; TICKET_VIEW_SIZE]; + + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::DiskInterfaceGetTicketView, + &[signed_ticket], + &mut [out_buf.as_mut_slice()], + )?; + + let _ = ios::close(es); + + Ok(out_buf) +} + +/// pub fn disk_interface_verify + +pub fn get_data_directory(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; 32]; + + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetTitleDir, + &[&title_id.to_be_bytes()], + &mut [&mut out_buf], + )?; + + CStr::from_bytes_until_nul(&out_buf) + .map(CString::from) + .map_err(|_| ios::Error::Invalid) +} + +const DEVICE_CERT_SIZE: usize = 384; +pub fn get_device_certificate() -> Result<[u8; DEVICE_CERT_SIZE], ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut out_buf = [0u8; DEVICE_CERT_SIZE]; + + ios::ioctlv::<0, 1, 1>(es, Ioctl::GetDeviceCertificate, &[], &mut [&mut out_buf])?; + + let _ = ios::close(es); + + Ok(out_buf) +} + +/// pub fn import_boot + +pub fn get_title_id() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut title_id = [0u8; 8]; + ios::ioctlv::<0, 1, 1>(es, Ioctl::GetTitleId, &[], &mut [&mut title_id])?; + + let _ = ios::close(es); + + Ok(u64::from_be_bytes(title_id)) +} + +pub fn set_uid(uid: u64) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>(es, Ioctl::SetUid, &[&uid.to_be_bytes()], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + pub fn delete_title_content(title_id: u64) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -594,6 +707,13 @@ pub fn delete_title_content(title_id: u64) -> Result<(), ios::Error> { Ok(()) } +pub fn seek_content( + content_file_descriptor: i32, + seek_mode: ios::SeekMode, + offset: i32, +) -> Result<(), ios::Error> { +} + pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; From 25adb230fcc67a8c667f7da7eb4accea52baab4a Mon Sep 17 00:00:00 2001 From: ProfElements Date: Tue, 10 Jun 2025 20:28:31 -0500 Subject: [PATCH 12/26] :wip: chore(ios/es): Replace `todo!()` with IOCTL magic numbers --- src/ios/es.rs | 82 +++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 4f94ea9..c19cb7d 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -90,8 +90,9 @@ impl From for i32 { Ioctl::GetTitleCount => 14, Ioctl::GetTitles => 15, Ioctl::GetTitleContentsCount => 16, - Ioctl::GetTicketViewCount => 17, - Ioctl::GetTicketViews => 18, + Ioctl::GetTitleContents => 17, + Ioctl::GetTicketViewCount => 18, + Ioctl::GetTicketViews => 19, Ioctl::GetTitleMetadataViewSize => 20, Ioctl::GetTitleMetadataView => 21, Ioctl::GetConsumption => 22, @@ -100,46 +101,45 @@ impl From for i32 { Ioctl::DiskInterfaceGetTitleMetadataViewSize => 25, Ioctl::DiskInterfaceGetTitleMetadataView => 26, Ioctl::DiskInterfaceGetTicketView => 27, - Ioctl::DiskInterfaceVerify => todo!(), - Ioctl::GetTitleDir => todo!(), - Ioctl::GetDeviceCertificate => todo!(), - Ioctl::ImportBoot => todo!(), - Ioctl::GetTitleId => todo!(), - Ioctl::SetUid => todo!(), + Ioctl::DiskInterfaceVerify => 28, + Ioctl::GetTitleDir => 29, + Ioctl::GetDeviceCertificate => 30, + Ioctl::ImportBoot => 31, + Ioctl::GetTitleId => 32, + Ioctl::SetUid => 33, Ioctl::DeleteTitleContent => 34, - Ioctl::SeekContent => todo!(), - Ioctl::OpenContent => todo!(), - Ioctl::LauchBackwardsCompatibility => todo!(), - Ioctl::ExportTitleInitalize => todo!(), - Ioctl::ExportContentBegin => todo!(), - Ioctl::ExportContentData => todo!(), - Ioctl::ExportContentEnd => todo!(), - Ioctl::ExportTitleDone => todo!(), - Ioctl::AddTitleMetadata => todo!(), - Ioctl::Encrypt => todo!(), - Ioctl::Decrypt => todo!(), - Ioctl::GetBoot2Version => todo!(), - Ioctl::AddTitleCancel => todo!(), - Ioctl::Sign => todo!(), - Ioctl::VerifySign => todo!(), - Ioctl::GetStoredContentCount => todo!(), - Ioctl::GetStoredContents => todo!(), - Ioctl::GetStoredTitleMetadataSize => todo!(), - Ioctl::GetStoredTitleMetadata => todo!(), - Ioctl::GetSharedContentCount => todo!(), - Ioctl::GetSharedContents => todo!(), + Ioctl::SeekContent => 35, + Ioctl::OpenContent => 36, + Ioctl::LauchBackwardsCompatibility => 37, + Ioctl::ExportTitleInitalize => 38, + Ioctl::ExportContentBegin => 39, + Ioctl::ExportContentData => 40, + Ioctl::ExportContentEnd => 41, + Ioctl::ExportTitleDone => 42, + Ioctl::AddTitleMetadata => 43, + Ioctl::Encrypt => 44, + Ioctl::Decrypt => 45, + Ioctl::GetBoot2Version => 46, + Ioctl::AddTitleCancel => 47, + Ioctl::Sign => 48, + Ioctl::VerifySign => 49, + Ioctl::GetStoredContentCount => 50, + Ioctl::GetStoredContents => 51, + Ioctl::GetStoredTitleMetadataSize => 32, + Ioctl::GetStoredTitleMetadata => 53, + Ioctl::GetSharedContentCount => 54, + Ioctl::GetSharedContents => 55, Ioctl::DeleteSharedContents => 56, - Ioctl::DiskInterfaceGetTitleMetadataSize => todo!(), - Ioctl::DiskInterfaceGetTitleMetadata => todo!(), - Ioctl::DiskInterfaceVerifyWithView => todo!(), - Ioctl::SetupStreamKey => todo!(), - Ioctl::DeleteStreamKey => todo!(), - Ioctl::DeleteContent => todo!(), - Ioctl::GetVersion0TicketFromView => todo!(), - Ioctl::GetTicketSizeFromView => todo!(), - Ioctl::GetTicketFromView => todo!(), - Ioctl::CheckKoreaRegion => todo!(), - Ioctl::GetTitleContents => todo!(), + Ioctl::DiskInterfaceGetTitleMetadataSize => 57, + Ioctl::DiskInterfaceGetTitleMetadata => 58, + Ioctl::DiskInterfaceVerifyWithView => 59, + Ioctl::SetupStreamKey => 60, + Ioctl::DeleteStreamKey => 61, + Ioctl::DeleteContent => 62, + Ioctl::GetVersion0TicketFromView => 64, + Ioctl::GetTicketSizeFromView => 67, + Ioctl::GetTicketFromView => 68, + Ioctl::CheckKoreaRegion => 69, } } } @@ -388,7 +388,7 @@ pub fn get_title_contents_count(title_id: u64) -> Result { Ok(u32::from_be_bytes(out_buf)) } -pub fn get_title_counts(title_id: u64, content_count: u32) -> Result, ios::Error> { +pub fn get_title_contents(title_id: u64, content_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; //TODO: avoid allocation From 250d8c285c3f90329b83677a14189b4df0eef9f5 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Thu, 12 Jun 2025 01:29:50 -0500 Subject: [PATCH 13/26] sparkles: feat(ios/es): Add more ioctls --- src/ios/es.rs | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/src/ios/es.rs b/src/ios/es.rs index c19cb7d..6662a81 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -712,6 +712,171 @@ pub fn seek_content( seek_mode: ios::SeekMode, offset: i32, ) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<3, 0, 3>( + es, + Ioctl::SeekContent, + &[ + &content_file_descriptor.to_be_bytes(), + &offset.to_be_bytes(), + &i32::from(seek_mode).to_be_bytes(), + ], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn open_title_content( + title_id: u64, + ticket_views: &[u8], + content_idx: u32, +) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<3, 0, 3>( + es, + Ioctl::OpenContent, + &[ + &title_id.to_be_bytes(), + ticket_views, + &content_idx.to_be_bytes(), + ], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +//pub fn launch_backwards_compat() -> Result {} + +pub fn export_title_init(title_id: u64, exported_tmd_buf: &mut [u8]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::ExportTitleInitalize, + &[&title_id.to_be_bytes()], + &mut [exported_tmd_buf], + )?; + + let _ = ios::close(es); + Ok(()) +} + +pub fn export_content_begin(title_id: u64, content_id: u32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<2, 0, 2>( + es, + Ioctl::ExportContentBegin, + &[&title_id.to_be_bytes(), &content_id.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn export_content_data( + content_file_descriptor: i32, + data: &mut [u8], +) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::ExportContentData, + &[&content_file_descriptor.to_be_bytes()], + &mut [data], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn export_content_end(content_file_descriptor: i32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>( + es, + Ioctl::ExportContentEnd, + &[&content_file_descriptor.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn export_title_done() -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<0, 0, 0>(es, Ioctl::ExportTitleDone, &[], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn add_tmd(title_meta: &[u8]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>(es, Ioctl::AddTitleMetadata, &[title_meta], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn encrypt( + keynum: u32, + iv: &mut [u8; 16], + source: &[u8], + destination: &mut [u8], +) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + let iv_copy = iv.clone(); + + ios::ioctlv::<3, 2, 5>( + es, + Ioctl::Encrypt, + &[&keynum.to_be_bytes(), &iv_copy, source], + &mut [iv, destination], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn decrypt( + keynum: u32, + iv: &mut [u8; 16], + source: &[u8], + destination: &mut [u8], +) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + let iv_copy = iv.clone(); + + ios::ioctlv::<3, 2, 5>( + es, + Ioctl::Decrypt, + &[&keynum.to_be_bytes(), &iv_copy, source], + &mut [iv, destination], + )?; + + let _ = ios::close(es); + + Ok(()) } pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { From 98d8c4ee5acfcd9d1517557f6fc2268c264c3fda Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sun, 22 Jun 2025 01:02:04 -0500 Subject: [PATCH 14/26] :wip: feat(ios/es): More IOCTLS --- src/ios/es.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/ios/es.rs b/src/ios/es.rs index 6662a81..70892e1 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -879,6 +879,89 @@ pub fn decrypt( Ok(()) } +pub fn get_boot_2_version() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut boot_version = [0u8; 4]; + ios::ioctlv::<0, 1, 1>(es, Ioctl::GetBoot2Version, &[], &mut [&mut boot_version])?; + + let _ = ios::close(es); + + let boot_version = u32::from_be_bytes(boot_version); + Ok(boot_version) +} + +pub fn cancel_add_title() -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<0, 0, 0>(es, Ioctl::AddTitleCancel, &[], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn sign(data: &[u8]) -> Result<([u8; 60], [u8; 0x180]), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut cert = [0u8; 0x180]; + let mut signature = [0u8; 60]; + ios::ioctlv::<1, 2, 3>(es, Ioctl::Sign, &[data], &mut [&mut signature, &mut cert])?; + + let _ = ios::close(es); + Ok((signature, cert)) +} + +pub fn verify_sign(data_sha1: &[u8], signature: &[u8], certs: &[u8]) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<3, 0, 3>( + es, + Ioctl::VerifySign, + &[data_sha1, signature, certs], + &mut [], + )?; + + let _ = ios::close(es); + Ok(()) +} + +pub fn get_stored_contents_count(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut title_count = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetStoredContentCount, + &[&title_id.to_be_bytes()], + &mut [&mut title_count], + )?; + + let _ = ios::close(es); + + let title_count = u32::from_be_bytes(title_count); + Ok(title_count) +} + +pub fn get_stored_contents(title_id: u64, content_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut content_ids = alloc::vec![0u8; content_count as usize * core::mem::size_of::()]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetStoredContents, + &[&title_id.to_be_bytes()], + &mut [&mut content_ids], + )?; + + let _ = ios::close(es); + + Ok(content_ids + .chunks_exact(4) + .map(|bytes| u32::from_be_bytes(bytes.try_into().unwrap())) + .collect()) +} + pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; From da127615eca0dee98d547e53ed9311d3c14517b7 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Tue, 1 Jul 2025 02:38:48 -0500 Subject: [PATCH 15/26] :wip: feat(ios/es): THE REST OF THEM --- src/ios/es.rs | 255 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 215 insertions(+), 40 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 70892e1..529ed49 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -499,46 +499,6 @@ pub fn get_consumption_count(title_id: u64) -> Result { Ok(u32::from_be_bytes(out_buf)) } -pub fn get_stored_title_metadata_size(title_id: u64) -> Result { - let es = ios::open(DEV_ES, ios::Mode::None)?; - - let in_buf = title_id.to_be_bytes(); - let mut out_buf = [0u8; 4]; - ios::ioctlv::<1, 1, 2>( - es, - Ioctl::GetStoredTitleMetadataSize, - &[&in_buf], - &mut [&mut out_buf], - )?; - - let _ = ios::close(es); - - Ok(u32::from_be_bytes( - out_buf.try_into().map_err(|_| ios::Error::Invalid)?, - )) -} - -// TODO: Proper enuming since there are different signature types and differing sizes for them -pub fn get_stored_title_metadata(title_id: u64, size: u32) -> Result, ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; - - let title_buf = title_id.to_be_bytes(); - let size_buf = size.to_be_bytes(); - let size_usize: usize = usize::try_from(size).map_err(|_| ios::Error::Invalid)?; - // TODO: Avoid allocation - let mut out_buf = alloc::vec![0u8; size_usize]; - ios::ioctlv::<2, 1, 3>( - es, - Ioctl::GetStoredTitleMetadata, - &[&title_buf, &size_buf], - &mut [&mut out_buf[..]], - )?; - - let _ = ios::close(es); - - Ok(out_buf) -} - pub fn get_consumption(title_id: u64, limit_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -962,6 +922,79 @@ pub fn get_stored_contents(title_id: u64, content_count: u32) -> Result .collect()) } +pub fn get_stored_title_metadata_size(title_id: u64) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let in_buf = title_id.to_be_bytes(); + let mut out_buf = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetStoredTitleMetadataSize, + &[&in_buf], + &mut [&mut out_buf], + )?; + + let _ = ios::close(es); + + Ok(u32::from_be_bytes( + out_buf.try_into().map_err(|_| ios::Error::Invalid)?, + )) +} + +// TODO: Proper enuming since there are different signature types and differing sizes for them +pub fn get_stored_title_metadata(title_id: u64, size: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let title_buf = title_id.to_be_bytes(); + let size_buf = size.to_be_bytes(); + let size_usize: usize = usize::try_from(size).map_err(|_| ios::Error::Invalid)?; + // TODO: Avoid allocation + let mut out_buf = alloc::vec![0u8; size_usize]; + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::GetStoredTitleMetadata, + &[&title_buf, &size_buf], + &mut [&mut out_buf[..]], + )?; + + let _ = ios::close(es); + + Ok(out_buf) +} + +pub fn get_shared_contents_count() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut shared_contents_count: [u8; 4] = [0u8; 4]; + ios::ioctlv::<0, 1, 1>( + es, + Ioctl::GetSharedContentCount, + &[], + &mut [&mut shared_contents_count], + )?; + + let _ = ios::close(es); + + let shared_contents_count = u32::from_be_bytes(shared_contents_count); + Ok(shared_contents_count) +} + +pub fn get_shared_contents(shared_contents_count: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut sha1_hashes = alloc::vec![u8; 20 * shared_contents_count as usize]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetSharedContents, + &[&shared_contents_count.to_be_bytes()], + &mut [&mut sha1_hashes], + )?; + + let _ = ios::close(es); + + Ok(sha1_hashes) +} + pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -969,3 +1002,145 @@ pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { Ok(()) } + +pub fn disk_interface_get_title_metadata_size() -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut tmd_size: [u8; 4] = [0u8; 4]; + ios::ioctlv::<0, 1, 1>( + es, + Ioctl::DiskInterfaceGetTitleMetadataSize, + &[], + &mut [&mut tmd_size], + )?; + + let _ = ios::close(es); + + let tmd_size = u32::from_be_bytes(tmd_size); + Ok(tmd_size) +} + +pub fn disk_interface_get_title_metadata(size: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut tmd = alloc::vec![0u8; size as usize]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::DiskInterfaceGetTitleMetadata, + &[&size.to_be_bytes()], + &mut [&mut tmd], + )?; + + let _ = ios::close(es); + + Ok(tmd) +} + +/// pub fn disk_interface_verify_with_view + +pub fn setup_stream_key(tik_view: &[u8], tmd: &[u8]) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut handle = [0u8; 4]; + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::SetupStreamKey, + &[&tik_view, &tmd], + &mut [&mut handle], + )?; + + let _ = ios::close(es); + + let handle = u32::from_be_bytes(handle); + Ok(handle) +} + +pub fn delete_stream_key(handle: u32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<1, 0, 1>( + es, + Ioctl::DeleteStreamKey, + &[&handle.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +pub fn delete_content(title_id: u64, content_id: u32) -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<2, 0, 2>( + es, + Ioctl::DeleteContent, + &[&title_id.to_be_bytes(), &content_id.to_be_bytes()], + &mut [], + )?; + + let _ = ios::close(es); + + Ok(()) +} + +const TICKET_SIZE: usize = 0x2A4; +pub fn get_version_0_ticket_from_view(tik_view: &[u8]) -> Result<[u8; TICKET_SIZE], ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut ticket = [0u8; TICKET_SIZE]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetVersion0TicketFromView, + &[&tik_view], + &mut [&mut ticket], + )?; + + let _ = ios::close(es); + + Ok(ticket) +} + +pub fn get_ticket_size_from_view(tik_view: &[u8]) -> Result { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut size = [0u8; 4]; + ios::ioctlv::<1, 1, 2>( + es, + Ioctl::GetTicketSizeFromView, + &[&tik_view], + &mut [&mut size], + )?; + + let _ = ios::close(es); + + let size = u32::from_be_bytes(size); + Ok(size) +} + +pub fn get_ticket_from_view(tik_view: &[u8], size: u32) -> Result, ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + let mut ticket = alloc::vec![0u8; size as usize]; + ios::ioctlv::<2, 1, 3>( + es, + Ioctl::GetTicketFromView, + &[&tik_view, &size.to_be_bytes()], + &mut [&mut ticket], + )?; + + let _ = ios::close(es); + + Ok(ticket) +} + +pub fn check_korea_region() -> Result<(), ios::Error> { + let es = ios::open(DEV_ES, ios::Mode::None)?; + + ios::ioctlv::<0, 0, 0>(es, Ioctl::CheckKoreaRegion, &[], &mut [])?; + + let _ = ios::close(es); + + Ok(()) +} From 2ae94c076577c07d46ecc397633c88c1b94baa9f Mon Sep 17 00:00:00 2001 From: ProfElements Date: Tue, 1 Jul 2025 03:00:42 -0500 Subject: [PATCH 16/26] :bug: fix(ios/es): Make sure it builds --- src/ios/es.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 529ed49..93a06b1 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -982,7 +982,7 @@ pub fn get_shared_contents_count() -> Result { pub fn get_shared_contents(shared_contents_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; - let mut sha1_hashes = alloc::vec![u8; 20 * shared_contents_count as usize]; + let mut sha1_hashes = alloc::vec![0u8; 20 * shared_contents_count as usize]; ios::ioctlv::<1, 1, 2>( es, Ioctl::GetSharedContents, From 2b1b80220da58c6dacce60b2daf00815745cc543 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sun, 6 Jul 2025 02:18:44 -0500 Subject: [PATCH 17/26] :memo: docs(ios/es): Add simple `Ioctl` docs --- src/ios/es.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/ios/es.rs b/src/ios/es.rs index 93a06b1..fa06cce 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -1,73 +1,140 @@ /// E-Ticket System Supported Ioctls pub enum Ioctl { + /// Add Ticket to NAND AddTicket, + /// Add Title Start AddTitleStart, + /// Add Title Content Start AddContentStart, + /// Add Title Content Data AddContentData, + /// Add Title Content Finish AddContentFinish, + /// Add Title Finish AddTitleFinish, + /// Get Device ID GetDeviceId, + /// Launch Title Launch, + /// Open Active Title Content OpenActiveTitleContent, + /// Read Content ReadContent, + /// Close Content File Descriptor CloseContent, + /// Get Owned Title Count GetOwnedTitleCount, + /// Get Owned Title IDs GetOwnedTitles, + /// Get Title Count GetTitleCount, + /// Get Title IDs GetTitles, + /// Get Title Content Count GetTitleContentsCount, + /// Get Title Content IDs GetTitleContents, + /// Get Title Ticket View Count GetTicketViewCount, + // Get Title Ticket Views GetTicketViews, + /// Get Title Metadata View Size GetTitleMetadataViewSize, + /// Get Title Metadata View GetTitleMetadataView, + /// Get current `ES` context consumption GetConsumption, + /// Delete Title DeleteTitle, + /// Delete Ticket DeleteTicket, + /// Get Title Metadata View Size DiskInterfaceGetTitleMetadataViewSize, + /// Get Title Metadata View DiskInterfaceGetTitleMetadataView, + /// Get Ticket View DiskInterfaceGetTicketView, + /// Verify Title DiskInterfaceVerify, + /// Get Data Directory GetTitleDir, + /// Get Device Certificate GetDeviceCertificate, + /// Import Boot 2 ImportBoot, + /// Get Current Title ID, GetTitleId, + /// Set `ES` context User ID SetUid, + /// Delete Title Content DeleteTitleContent, + /// Seek Title Content SeekContent, + /// Open Title Content OpenContent, + /// Launch Backwards Compatibility (Gamecube Mode) LauchBackwardsCompatibility, + /// Export Title Initialize ExportTitleInitalize, + /// Export Content Begin ExportContentBegin, + /// Export Content Data ExportContentData, + /// Export Content End ExportContentEnd, + /// Export Title Done / Export Title End ExportTitleDone, + /// Add Title Metadata AddTitleMetadata, + /// Encrypt Encrypt, + /// Decrpyt Decrypt, + /// Get Boot 2 Version GetBoot2Version, + /// Add Title Cancel AddTitleCancel, + /// Sign Sign, + /// Verify Sign VerifySign, + /// Get Stored Contents Count GetStoredContentCount, + /// Get Stored Contents GetStoredContents, + /// Get Stored Title Metadata Size GetStoredTitleMetadataSize, + /// Get Stored Title Metadata GetStoredTitleMetadata, + /// Get Shared Contents Count GetSharedContentCount, + /// Get Shared Contents GetSharedContents, + /// Delete Shared Contents DeleteSharedContents, + /// Disk Interface Get Title Metadata Size DiskInterfaceGetTitleMetadataSize, + + /// Disk Interface Get Title Metadata DiskInterfaceGetTitleMetadata, + /// Disk Interface Verify With View DiskInterfaceVerifyWithView, + /// Setup Stream Key SetupStreamKey, + /// Delete Stream Key DeleteStreamKey, + /// Delete Content DeleteContent, // Invalid3F + /// Get Version 0 Ticket From View GetVersion0TicketFromView, // Unknown41, // Unknown42, + /// Get Ticket Size From Ticket View GetTicketSizeFromView, + /// Get Ticket from Ticket View GetTicketFromView, + /// Check Korea Region CheckKoreaRegion, } From 4a8048da4361100fb90a6b8420a232e5d06994b0 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sun, 6 Jul 2025 02:19:40 -0500 Subject: [PATCH 18/26] :memo: docs(ios/es): Add some `Ioctl` func docs --- src/ios/es.rs | 53 +++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index fa06cce..80b3fee 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -217,8 +217,11 @@ use core::ffi::CStr; use alloc::{ffi::CString, vec::Vec}; -use crate::ios; +use crate::ios::{self, FileDescriptor}; +/// [`Ioctl::AddTicket`] +/// +/// Add ticket, certificates and certificate revoke list to system pub fn add_ticket( signed_ticket: &[u8], signed_certs: &[u8], @@ -238,12 +241,16 @@ pub fn add_ticket( Ok(()) } +/// [`Ioctl::AddTitleStart`] +/// +/// Add title metadata, certificates and certifacte revoke list to system +/// Needs to be canceled with the same file descriptor that this function is called with pub fn add_title_start( + es: FileDescriptor signed_title_meta: &[u8], signed_certs: &[u8], signed_crl: &[u8], ) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; ios::ioctlv::<4, 0, 4>( es, @@ -257,13 +264,14 @@ pub fn add_title_start( &mut [], )?; - let _ = ios::close(es); - Ok(()) } -pub fn add_content_start(title_id: u64, content_id: u32) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; +/// [`Ioctl::AddContentStart`] +/// +/// Return content file descriptor for `title_id` and `content_id` +/// Needs to be finished with the same file descriptor that this function is called with +pub fn add_content_start(es: FileDescriptor, title_id: u64, content_id: u32) -> Result<(), ios::Error> { ios::ioctlv::<2, 0, 2>( es, @@ -272,13 +280,14 @@ pub fn add_content_start(title_id: u64, content_id: u32) -> Result<(), ios::Erro &mut [], )?; - let _ = ios::close(es); Ok(()) } -pub fn add_content_data(content_fd: i32, data: &[u8]) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; +/// [`Ioctl::AddContentData`] +/// +/// Add data to content file descriptor +pub fn add_content_data(es: FileDescriptor, content_fd: i32, data: &[u8]) -> Result<(), ios::Error> { ios::ioctlv::<2, 0, 2>( es, @@ -287,33 +296,31 @@ pub fn add_content_data(content_fd: i32, data: &[u8]) -> Result<(), ios::Error> &mut [], )?; - let _ = ios::close(es); - Ok(()) } -pub fn add_content_finish(content_id: u32) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; +/// [`Ioctl::AddContentFinish`] +/// +/// Finish adding content data to content file descriptor +pub fn add_content_finish(es: FileDescriptor, content_fd: u32) -> Result<(), ios::Error> { ios::ioctlv::<1, 0, 1>( es, Ioctl::AddContentFinish, - &[&content_id.to_be_bytes()], + &[&content_fd.to_be_bytes()], &mut [], )?; - let _ = ios::close(es); - Ok(()) } -pub fn add_title_finish() -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; +/// [`Ioctl::AddTitleFinish`] +/// +/// Finish adding title to system +pub fn add_title_finish(es: FileDescriptor) -> Result<(), ios::Error> { ios::ioctlv::<0, 0, 0>(es, Ioctl::AddTitleFinish, &[], &mut [])?; - let _ = ios::close(es); - Ok(()) } @@ -664,7 +671,7 @@ pub fn disk_interface_get_ticket_view( Ok(out_buf) } -/// pub fn disk_interface_verify +// pub fn disk_interface_verify pub fn get_data_directory(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -696,7 +703,7 @@ pub fn get_device_certificate() -> Result<[u8; DEVICE_CERT_SIZE], ios::Error> { Ok(out_buf) } -/// pub fn import_boot +// pub fn import_boot pub fn get_title_id() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1103,7 +1110,7 @@ pub fn disk_interface_get_title_metadata(size: u32) -> Result, ios::Erro Ok(tmd) } -/// pub fn disk_interface_verify_with_view +// pub fn disk_interface_verify_with_view pub fn setup_stream_key(tik_view: &[u8], tmd: &[u8]) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; From f5094550c52097bd98817ace6d8b9860c1344b5f Mon Sep 17 00:00:00 2001 From: ProfElements Date: Sun, 6 Jul 2025 02:20:56 -0500 Subject: [PATCH 19/26] :wip: chore(ios/es): fix build --- src/ios/es.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 80b3fee..4fff10b 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -246,12 +246,11 @@ pub fn add_ticket( /// Add title metadata, certificates and certifacte revoke list to system /// Needs to be canceled with the same file descriptor that this function is called with pub fn add_title_start( - es: FileDescriptor + es: FileDescriptor, signed_title_meta: &[u8], signed_certs: &[u8], signed_crl: &[u8], ) -> Result<(), ios::Error> { - ios::ioctlv::<4, 0, 4>( es, Ioctl::AddTitleStart, @@ -267,12 +266,15 @@ pub fn add_title_start( Ok(()) } -/// [`Ioctl::AddContentStart`] +/// [`Ioctl::AddContentStart`] /// /// Return content file descriptor for `title_id` and `content_id` -/// Needs to be finished with the same file descriptor that this function is called with -pub fn add_content_start(es: FileDescriptor, title_id: u64, content_id: u32) -> Result<(), ios::Error> { - +/// Needs to be finished with the same file descriptor that this function is called with +pub fn add_content_start( + es: FileDescriptor, + title_id: u64, + content_id: u32, +) -> Result<(), ios::Error> { ios::ioctlv::<2, 0, 2>( es, Ioctl::AddContentStart, @@ -280,15 +282,17 @@ pub fn add_content_start(es: FileDescriptor, title_id: u64, content_id: u32) -> &mut [], )?; - Ok(()) } -/// [`Ioctl::AddContentData`] +/// [`Ioctl::AddContentData`] /// -/// Add data to content file descriptor -pub fn add_content_data(es: FileDescriptor, content_fd: i32, data: &[u8]) -> Result<(), ios::Error> { - +/// Add data to content file descriptor +pub fn add_content_data( + es: FileDescriptor, + content_fd: i32, + data: &[u8], +) -> Result<(), ios::Error> { ios::ioctlv::<2, 0, 2>( es, Ioctl::AddContentData, @@ -299,11 +303,10 @@ pub fn add_content_data(es: FileDescriptor, content_fd: i32, data: &[u8]) -> Res Ok(()) } -/// [`Ioctl::AddContentFinish`] +/// [`Ioctl::AddContentFinish`] /// /// Finish adding content data to content file descriptor pub fn add_content_finish(es: FileDescriptor, content_fd: u32) -> Result<(), ios::Error> { - ios::ioctlv::<1, 0, 1>( es, Ioctl::AddContentFinish, @@ -318,7 +321,6 @@ pub fn add_content_finish(es: FileDescriptor, content_fd: u32) -> Result<(), ios /// /// Finish adding title to system pub fn add_title_finish(es: FileDescriptor) -> Result<(), ios::Error> { - ios::ioctlv::<0, 0, 0>(es, Ioctl::AddTitleFinish, &[], &mut [])?; Ok(()) From 4a9b12afe3e716bbb55e9473d290ee789897b47f Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 7 Jul 2025 02:53:02 -0500 Subject: [PATCH 20/26] :memo: docs(ios/es): Add more documentation --- src/ios/es.rs | 60 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 4fff10b..a3d19ab 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -326,6 +326,9 @@ pub fn add_title_finish(es: FileDescriptor) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::GetDeviceId`] +/// +/// Get device id pub fn get_device_id() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -337,6 +340,9 @@ pub fn get_device_id() -> Result { Ok(u32::from_be_bytes(out_buf)) } +/// [`Ioctl::Launch`] +/// +/// Launch system title pub fn launch_title(title_id: u64, ticket_view: &[u8]) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -350,9 +356,10 @@ pub fn launch_title(title_id: u64, ticket_view: &[u8]) -> Result loop {} } -pub fn open_active_title_content(content_idx: u32) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; - +/// [`Ioctl::OpenActiveTitleContent`] +/// +/// Open content from current title +pub fn open_active_title_content(es: FileDescriptor, content_idx: u32) -> Result<(), ios::Error> { ios::ioctlv::<1, 0, 1>( es, Ioctl::OpenActiveTitleContent, @@ -363,9 +370,14 @@ pub fn open_active_title_content(content_idx: u32) -> Result<(), ios::Error> { Ok(()) } -pub fn read_content(content_file_descriptor: i32, out_buf: &mut [u8]) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; - +/// [`Ioctl::ReadContent`] +/// +/// Read data provided from `content_file_descriptor` into `out_buf` +pub fn read_content( + es: FileDescriptor, + content_file_descriptor: i32, + out_buf: &mut [u8], +) -> Result<(), ios::Error> { ios::ioctlv::<1, 1, 2>( es, Ioctl::ReadContent, @@ -375,10 +387,10 @@ pub fn read_content(content_file_descriptor: i32, out_buf: &mut [u8]) -> Result< Ok(()) } - -pub fn close_content(content_file_descriptor: i32) -> Result<(), ios::Error> { - let es = ios::open(DEV_ES, ios::Mode::None)?; - +/// [`Ioctl::CloseContent`] +/// +/// Close content +pub fn close_content(es: FileDescriptor, content_file_descriptor: i32) -> Result<(), ios::Error> { ios::ioctlv::<1, 0, 1>( es, Ioctl::CloseContent, @@ -389,6 +401,9 @@ pub fn close_content(content_file_descriptor: i32) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::GetOwnedTitleCount`] +/// +/// Get owned title count pub fn get_owned_title_count() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -399,7 +414,9 @@ pub fn get_owned_title_count() -> Result { Ok(u32::from_be_bytes(out_buf)) } - +/// [`Ioctl::GetOwnedTitles`] +/// +/// Get ids for owned titles pub fn get_owned_titles(title_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -418,7 +435,9 @@ pub fn get_owned_titles(title_count: u32) -> Result, ios::Error> { .map(|bytes| u64::from_be_bytes(bytes.try_into().expect("should fit"))) .collect()) } - +/// [`Ioctl::GetTitleCount`] +/// +/// Get title count pub fn get_title_count() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -430,6 +449,9 @@ pub fn get_title_count() -> Result { Ok(u32::from_be_bytes(out_buf)) } +/// [`Ioctl::GetTitles`] +/// +/// Get ids for all titles pub fn get_titles(title_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -448,6 +470,9 @@ pub fn get_titles(title_count: u32) -> Result, ios::Error> { .collect()) } +/// [`Ioctl::GetTitleContentsCount`] +/// +/// Get title contents count for `title_id` pub fn get_title_contents_count(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -463,7 +488,9 @@ pub fn get_title_contents_count(title_id: u64) -> Result { Ok(u32::from_be_bytes(out_buf)) } - +/// [`Ioctl::GetTitleContents`] +/// +/// Get content ids of title with `title_id` pub fn get_title_contents(title_id: u64, content_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -485,6 +512,9 @@ pub fn get_title_contents(title_id: u64, content_count: u32) -> Result, .collect()) } +/// [`Ioctl::GetTicketViewCount`] +/// +/// Get ticket view count of title with `title_id` pub fn get_ticket_view_count(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -503,6 +533,10 @@ pub fn get_ticket_view_count(title_id: u64) -> Result { // TODO: actually returns a Vec but I haven't made teh `TicketView` struct yet and // don't want to do structs till the end of impling all these +// +/// [`Ioctl::GetTicketViews`] +/// +/// Get ticket views for title with `title_id` pub fn get_ticket_views(title_id: u64, view_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; From db11c27d08beda19101331d93a2e682c83a8fd90 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Wed, 9 Jul 2025 01:09:19 -0500 Subject: [PATCH 21/26] :memo: docs(ios/es): Add more more docs --- src/ios/es.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index a3d19ab..7af0e90 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -553,7 +553,9 @@ pub fn get_ticket_views(title_id: u64, view_count: u32) -> Result, ios:: Ok(out_buf) } - +/// [`Ioctl::GetTitleMetadataViewSize`] +/// +/// Get title metadata view size for title with `title_id` pub fn get_title_metadata_view_size(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -571,6 +573,10 @@ pub fn get_title_metadata_view_size(title_id: u64) -> Result { } //TODO: Return `TitleMetadataView` instead of owned allocation +// +/// [`Ioctl::GetTitleMetadataView`] +/// +/// Get title metadata view for title with `title_id` pub fn get_title_metadata_view(title_id: u64, size: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -592,6 +598,7 @@ pub fn get_title_metadata_view(title_id: u64, size: u32) -> Result, ios: Ok(out_buf) } +/// Get tiklimit consumption count pub fn get_consumption_count(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -609,6 +616,9 @@ pub fn get_consumption_count(title_id: u64) -> Result { Ok(u32::from_be_bytes(out_buf)) } +/// [`Ioctl::GetConsumption`] +/// +/// Get tiklimit consumption pub fn get_consumption(title_id: u64, limit_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -629,6 +639,9 @@ pub fn get_consumption(title_id: u64, limit_count: u32) -> Result, ios:: Ok(limit_out_buf) } +/// [`Ioctl::DeleteTitle`] +/// +/// Delete title from system pub fn delete_title(title_id: u64) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -639,6 +652,9 @@ pub fn delete_title(title_id: u64) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl:::DeleteTicket`] +/// +/// Delete ticket from system pub fn delete_ticket(ticket_view: &[u8]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; From f8cbdc9f99c1014d9e0eb6259539b40eafd881bd Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 14 Jul 2025 18:44:14 -0500 Subject: [PATCH 22/26] :memo: docs(ios/es): Add docs --- src/ios/es.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 7af0e90..59f694d 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -665,6 +665,9 @@ pub fn delete_ticket(ticket_view: &[u8]) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::DiskInterfaceGetTitleMetadataViewSize`] +/// +/// Get current disk's title metadata view size pub fn disk_interface_get_title_metadata_view_size( signed_title_meta: &[u8], ) -> Result { @@ -684,6 +687,9 @@ pub fn disk_interface_get_title_metadata_view_size( Ok(u32::from_be_bytes(size_buf)) } +/// [`Ioctl::DiskInterfaceGetTitleMetadataView`] +/// +/// Get current disk's title metadata view pub fn disk_interface_get_title_metadata_view( signed_title_meta: &[u8], tmd_view_size: u32, @@ -705,6 +711,9 @@ pub fn disk_interface_get_title_metadata_view( } const TICKET_VIEW_SIZE: usize = 216; // 0xD8 +/// [`Ioctl::DiskInterfaceGetTicketView`] +/// +/// Get current disk's ticket view pub fn disk_interface_get_ticket_view( signed_ticket: &[u8], ) -> Result<[u8; TICKET_VIEW_SIZE], ios::Error> { @@ -725,6 +734,9 @@ pub fn disk_interface_get_ticket_view( // pub fn disk_interface_verify +/// [`Ioctl::GetTitleDir`] +/// +/// Get title with `title_id`'s data directory pub fn get_data_directory(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -743,6 +755,9 @@ pub fn get_data_directory(title_id: u64) -> Result { } const DEVICE_CERT_SIZE: usize = 384; +/// [`Ioctl::GetDeviceCertificate`] +/// +/// Get this device's certificate pub fn get_device_certificate() -> Result<[u8; DEVICE_CERT_SIZE], ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -756,7 +771,9 @@ pub fn get_device_certificate() -> Result<[u8; DEVICE_CERT_SIZE], ios::Error> { } // pub fn import_boot - +/// [`Ioctl::GetTitleId`] +/// +/// Get currently running title's title_id pub fn get_title_id() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -768,6 +785,9 @@ pub fn get_title_id() -> Result { Ok(u64::from_be_bytes(title_id)) } +/// [`Ioctl::SetUid`] +/// +/// Set E-Ticket system user id pub fn set_uid(uid: u64) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -778,6 +798,9 @@ pub fn set_uid(uid: u64) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::DeleteTitleContent`] +/// +/// Delete title with `title_id` contents pub fn delete_title_content(title_id: u64) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -793,6 +816,9 @@ pub fn delete_title_content(title_id: u64) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::SeekContent`] +/// +/// Seek contents to `offset` using `seek_mode` pub fn seek_content( content_file_descriptor: i32, seek_mode: ios::SeekMode, @@ -816,6 +842,9 @@ pub fn seek_content( Ok(()) } +/// [`Ioctl::OpenContent`] +/// +/// Open title with `title_id` contents at `content_idx` using `ticket_views` pub fn open_title_content( title_id: u64, ticket_views: &[u8], @@ -841,6 +870,9 @@ pub fn open_title_content( //pub fn launch_backwards_compat() -> Result {} +/// [`Ioctl::ExportTitleInitalize`] +/// +/// Export title with `title_id` metadata into `export_tmd_buf` pub fn export_title_init(title_id: u64, exported_tmd_buf: &mut [u8]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -855,6 +887,9 @@ pub fn export_title_init(title_id: u64, exported_tmd_buf: &mut [u8]) -> Result<( Ok(()) } +/// [`Ioctl::ExportContentBegin`] +/// +/// Open title with `title_id`'s content with `content_id` pub fn export_content_begin(title_id: u64, content_id: u32) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -870,6 +905,10 @@ pub fn export_content_begin(title_id: u64, content_id: u32) -> Result<(), ios::E Ok(()) } +/// [`Ioctl::ExportContentData`] +/// +/// Export content data into `data` using the `content_file_descriptor` provived by +/// [`Ioctl::ExportContentBegin`] pub fn export_content_data( content_file_descriptor: i32, data: &mut [u8], @@ -887,7 +926,9 @@ pub fn export_content_data( Ok(()) } - +/// [`Ioctl::ExportContentEnd`] +/// +/// Close `content_file_descriptor` provided by [`Ioctl::ExportContentBegin`] pub fn export_content_end(content_file_descriptor: i32) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -903,6 +944,7 @@ pub fn export_content_end(content_file_descriptor: i32) -> Result<(), ios::Error Ok(()) } +/// [`Ioctl::ExportTitleDone`] pub fn export_title_done() -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -913,6 +955,9 @@ pub fn export_title_done() -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::AddTitleMetadata`] +/// +/// Add title metadata to system pub fn add_tmd(title_meta: &[u8]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; From 0985d49dfcf7242054e1e46fbbca3550d7ab9620 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 21 Jul 2025 22:05:08 -0500 Subject: [PATCH 23/26] :memo: docs(ios/es): Add the rest of the docs --- src/ios/es.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 59f694d..6886438 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -36,7 +36,7 @@ pub enum Ioctl { GetTitleContents, /// Get Title Ticket View Count GetTicketViewCount, - // Get Title Ticket Views + /// Get Title Ticket Views GetTicketViews, /// Get Title Metadata View Size GetTitleMetadataViewSize, @@ -968,8 +968,28 @@ pub fn add_tmd(title_meta: &[u8]) -> Result<(), ios::Error> { Ok(()) } +#[repr(u32)] +/// Key used to encrypt and decrypt date +/// +/// Usually called `keynum` in libogc +pub enum Key { + /// NAND Key + NandFs = 2, + /// Common Key + Common = 4, + /// Backup Key + Backup = 5, + /// SD Card Contents Key + SdCard = 6, + /// Korean Key + Korean = 11, +} + +/// [`Ioctl::Encrypt`] +/// +/// Encrypt `source` with `iv` and `key` outputing to `destination` pub fn encrypt( - keynum: u32, + keynum: Key, iv: &mut [u8; 16], source: &[u8], destination: &mut [u8], @@ -980,7 +1000,7 @@ pub fn encrypt( ios::ioctlv::<3, 2, 5>( es, Ioctl::Encrypt, - &[&keynum.to_be_bytes(), &iv_copy, source], + &[&(keynum as u32).to_be_bytes(), &iv_copy, source], &mut [iv, destination], )?; @@ -989,8 +1009,11 @@ pub fn encrypt( Ok(()) } +/// [`Ioctl::Decrypt`] +/// +/// Decrypt `source` with `iv` and `key` outputing to `destination` pub fn decrypt( - keynum: u32, + keynum: Key, iv: &mut [u8; 16], source: &[u8], destination: &mut [u8], @@ -1001,7 +1024,7 @@ pub fn decrypt( ios::ioctlv::<3, 2, 5>( es, Ioctl::Decrypt, - &[&keynum.to_be_bytes(), &iv_copy, source], + &[&(keynum as u32).to_be_bytes(), &iv_copy, source], &mut [iv, destination], )?; @@ -1010,6 +1033,9 @@ pub fn decrypt( Ok(()) } +/// [`Ioctl::GetBoot2Version`] +/// +/// Get boot 2 version pub fn get_boot_2_version() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1022,6 +1048,9 @@ pub fn get_boot_2_version() -> Result { Ok(boot_version) } +/// [`Ioctl::AddTitleCancel`] +/// +/// Cancel add title to nand pub fn cancel_add_title() -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1032,6 +1061,9 @@ pub fn cancel_add_title() -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::Sign`] +/// +/// Sign provided `data` returning a signature and certificate pub fn sign(data: &[u8]) -> Result<([u8; 60], [u8; 0x180]), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1043,6 +1075,9 @@ pub fn sign(data: &[u8]) -> Result<([u8; 60], [u8; 0x180]), ios::Error> { Ok((signature, cert)) } +/// [`Ioctl::VerifySign`] +/// +/// Taking in `data_sha1`, `signature` and `certs` verify if properly signed pub fn verify_sign(data_sha1: &[u8], signature: &[u8], certs: &[u8]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1057,6 +1092,9 @@ pub fn verify_sign(data_sha1: &[u8], signature: &[u8], certs: &[u8]) -> Result<( Ok(()) } +/// [`Ioctl::GetStoredContentCount`] +/// +/// Get count of contents stored on the NAND pub fn get_stored_contents_count(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1074,6 +1112,9 @@ pub fn get_stored_contents_count(title_id: u64) -> Result { Ok(title_count) } +/// [`Ioctl::GetStoredContents`] +/// +/// Get contents stored on the NAND pub fn get_stored_contents(title_id: u64, content_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1093,6 +1134,9 @@ pub fn get_stored_contents(title_id: u64, content_count: u32) -> Result .collect()) } +/// [`Ioctl::GetStoredTitleMetadataSize`] +/// +/// Get stored title metadata size of `title_id` title pub fn get_stored_title_metadata_size(title_id: u64) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1113,6 +1157,10 @@ pub fn get_stored_title_metadata_size(title_id: u64) -> Result } // TODO: Proper enuming since there are different signature types and differing sizes for them +// +/// [`Ioctl::GetStoredTitleMetadata`] +/// +/// Get stored title metadata of `title_id` title pub fn get_stored_title_metadata(title_id: u64, size: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1133,6 +1181,9 @@ pub fn get_stored_title_metadata(title_id: u64, size: u32) -> Result, io Ok(out_buf) } +/// [`Ioctl::GetSharedContentCount`] +/// +/// Get shared contents count on NAND pub fn get_shared_contents_count() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1150,6 +1201,9 @@ pub fn get_shared_contents_count() -> Result { Ok(shared_contents_count) } +/// [`Ioctl::GetSharedContents`] +/// +/// Get shared contents sha1 hashes on NAND pub fn get_shared_contents(shared_contents_count: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1166,6 +1220,9 @@ pub fn get_shared_contents(shared_contents_count: u32) -> Result, ios::E Ok(sha1_hashes) } +/// [`Ioctl::DeleteSharedContents`] +/// +/// Delete shared content based on the provided `sha1_hash` pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1174,6 +1231,9 @@ pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::DiskInterfaceGetTitleMetadataSize`] +/// +/// Get disk title metadata size pub fn disk_interface_get_title_metadata_size() -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1191,6 +1251,9 @@ pub fn disk_interface_get_title_metadata_size() -> Result { Ok(tmd_size) } +/// [`Ioctl::DiskInterfaceGetTitleMetadata`] +/// +/// Get disk title metadata pub fn disk_interface_get_title_metadata(size: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1209,6 +1272,9 @@ pub fn disk_interface_get_title_metadata(size: u32) -> Result, ios::Erro // pub fn disk_interface_verify_with_view +/// [`Ioctl::SetupStreamKey`] +/// +/// Setup stream key pub fn setup_stream_key(tik_view: &[u8], tmd: &[u8]) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1226,6 +1292,9 @@ pub fn setup_stream_key(tik_view: &[u8], tmd: &[u8]) -> Result Ok(handle) } +/// [`Ioctl::DeleteStreamKey`] +/// +/// Delete stream key pub fn delete_stream_key(handle: u32) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1241,6 +1310,9 @@ pub fn delete_stream_key(handle: u32) -> Result<(), ios::Error> { Ok(()) } +/// [`Ioctl::DeleteContent`] +/// +/// Delete `title_id` title's content using `content_id` pub fn delete_content(title_id: u64, content_id: u32) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1257,6 +1329,9 @@ pub fn delete_content(title_id: u64, content_id: u32) -> Result<(), ios::Error> } const TICKET_SIZE: usize = 0x2A4; +/// [`Ioctl::GetVersion0TicketFromView`] +/// +/// Get version ticket from provided `tik_view` pub fn get_version_0_ticket_from_view(tik_view: &[u8]) -> Result<[u8; TICKET_SIZE], ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1273,6 +1348,9 @@ pub fn get_version_0_ticket_from_view(tik_view: &[u8]) -> Result<[u8; TICKET_SIZ Ok(ticket) } +/// [`Ioctl::GetTicketFromView`] +/// +/// Get ticket size from provided `tik_view` pub fn get_ticket_size_from_view(tik_view: &[u8]) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1290,6 +1368,9 @@ pub fn get_ticket_size_from_view(tik_view: &[u8]) -> Result { Ok(size) } +/// [`Ioctl::GetTicketFromView`] +/// +/// Get ticket from provided `tik_view` pub fn get_ticket_from_view(tik_view: &[u8], size: u32) -> Result, ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; @@ -1306,6 +1387,9 @@ pub fn get_ticket_from_view(tik_view: &[u8], size: u32) -> Result, ios:: Ok(ticket) } +/// [`Ioctl::CheckKoreaRegion`] +/// +/// Check if the console's region is Korea pub fn check_korea_region() -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; From f5831377882cb17eb5771b138f117c1cdfee7ddd Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 21 Jul 2025 22:18:43 -0500 Subject: [PATCH 24/26] :bug: fix(ios/es): Actually return the `content_file_descriptor` for funcs that return them --- src/ios/es.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 6886438..9b89631 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -274,15 +274,15 @@ pub fn add_content_start( es: FileDescriptor, title_id: u64, content_id: u32, -) -> Result<(), ios::Error> { - ios::ioctlv::<2, 0, 2>( +) -> Result { + let fd = ios::ioctlv::<2, 0, 2>( es, Ioctl::AddContentStart, &[&title_id.to_be_bytes(), &content_id.to_be_bytes()], &mut [], )?; - Ok(()) + Ok(fd) } /// [`Ioctl::AddContentData`] @@ -359,15 +359,15 @@ pub fn launch_title(title_id: u64, ticket_view: &[u8]) -> Result /// [`Ioctl::OpenActiveTitleContent`] /// /// Open content from current title -pub fn open_active_title_content(es: FileDescriptor, content_idx: u32) -> Result<(), ios::Error> { - ios::ioctlv::<1, 0, 1>( +pub fn open_active_title_content(es: FileDescriptor, content_idx: u32) -> Result { + let fd = ios::ioctlv::<1, 0, 1>( es, Ioctl::OpenActiveTitleContent, &[&content_idx.to_be_bytes()], &mut [], )?; - Ok(()) + Ok(fd) } /// [`Ioctl::ReadContent`] @@ -849,10 +849,10 @@ pub fn open_title_content( title_id: u64, ticket_views: &[u8], content_idx: u32, -) -> Result<(), ios::Error> { +) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; - ios::ioctlv::<3, 0, 3>( + let fd = ios::ioctlv::<3, 0, 3>( es, Ioctl::OpenContent, &[ @@ -865,7 +865,7 @@ pub fn open_title_content( let _ = ios::close(es); - Ok(()) + Ok(fd) } //pub fn launch_backwards_compat() -> Result {} @@ -890,10 +890,10 @@ pub fn export_title_init(title_id: u64, exported_tmd_buf: &mut [u8]) -> Result<( /// [`Ioctl::ExportContentBegin`] /// /// Open title with `title_id`'s content with `content_id` -pub fn export_content_begin(title_id: u64, content_id: u32) -> Result<(), ios::Error> { +pub fn export_content_begin(title_id: u64, content_id: u32) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; - ios::ioctlv::<2, 0, 2>( + let fd = ios::ioctlv::<2, 0, 2>( es, Ioctl::ExportContentBegin, &[&title_id.to_be_bytes(), &content_id.to_be_bytes()], @@ -902,7 +902,7 @@ pub fn export_content_begin(title_id: u64, content_id: u32) -> Result<(), ios::E let _ = ios::close(es); - Ok(()) + Ok(fd) } /// [`Ioctl::ExportContentData`] @@ -1227,7 +1227,6 @@ pub fn delete_shared_content(sha1_hash: &[u8; 20]) -> Result<(), ios::Error> { let es = ios::open(DEV_ES, ios::Mode::None)?; ios::ioctlv::<1, 0, 1>(es, Ioctl::DeleteSharedContents, &[sha1_hash], &mut [])?; - Ok(()) } From 03c9c64aceb52ea059ca082a81f0ca6be5496445 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 21 Jul 2025 22:19:31 -0500 Subject: [PATCH 25/26] :bug: fix(ios/es): Make `ios::ioctlv` return i32 so that ES can get its descriptors --- src/ios.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ios.rs b/src/ios.rs index 1c40b3e..27882a9 100644 --- a/src/ios.rs +++ b/src/ios.rs @@ -319,7 +319,7 @@ pub fn ioctlv< ioctl: impl Into, buf_ins: &[&[u8]], buf_outs: &mut [&mut [u8]], -) -> Result<(), Error> { +) -> Result { type Ioctlv = ogc_sys::_ioctlv; debug_assert!(buf_ins.len() == COUNT_IN); debug_assert!(buf_outs.len() == COUNT_OUT); @@ -366,7 +366,7 @@ pub fn ioctlv< val if { val == -4 || val == -5 || val == -6 || val == -8 || val == -22 } => { Err(Error::try_from(val).map_err(|()| Error::UnknownErrorCode(val))?) } - val if { val >= 0 } => Ok(()), + val if { val >= 0 } => Ok(val), val => Err(Error::UnknownErrorCode(val)), } } From ee04b2343cb5c12561f6cfdb6469c385be648bf8 Mon Sep 17 00:00:00 2001 From: ProfElements Date: Mon, 21 Jul 2025 22:19:31 -0500 Subject: [PATCH 26/26] :sparkles: feat(mmio): Swap `u16` with `AlignedPhysPtrLow`, and `AlignedPhysPtrHigh`. Add simple fifo mmio write funcs --- src/ios/es.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ios/es.rs b/src/ios/es.rs index 9b89631..3270a87 100644 --- a/src/ios/es.rs +++ b/src/ios/es.rs @@ -890,7 +890,7 @@ pub fn export_title_init(title_id: u64, exported_tmd_buf: &mut [u8]) -> Result<( /// [`Ioctl::ExportContentBegin`] /// /// Open title with `title_id`'s content with `content_id` -pub fn export_content_begin(title_id: u64, content_id: u32) -> Result { +pub fn export_content_begin(title_id: u64, content_id: u32) -> Result { let es = ios::open(DEV_ES, ios::Mode::None)?; let fd = ios::ioctlv::<2, 0, 2>(