mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2026-03-17 06:47:49 -04:00
Added iospatcher to lib and CLI
Everything but the no-shared flag is working right now. Getting no-shared to work properly will take a little more work because right now there's nothing to guarantee that the content records are synced between the TMD and content objects in a title. This means that updating any content in a title will result in the records being out of sync and the written TMD will not match the actual state of the content when it was dumped. To mitigate this, I intend on making the content records in the content struct a reference to the content records in the TMD, so that they are the same object and therefore always in sync.
This commit is contained in:
136
src/title/iospatcher.rs
Normal file
136
src/title/iospatcher.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// title/iospatcher.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||
// https://github.com/NinjaCheetah/rustwii
|
||||
//
|
||||
// Module for applying patches to IOSes using a Title.
|
||||
|
||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||
use thiserror::Error;
|
||||
use crate::title;
|
||||
use crate::title::content;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IOSPatcherError {
|
||||
#[error("this title is not an IOS")]
|
||||
NotIOS,
|
||||
#[error("the required module \"{0}\" could not be found, this may not be a valid IOS")]
|
||||
ModuleNotFound(String),
|
||||
#[error("failed to get IOS content")]
|
||||
Content(#[from] content::ContentError),
|
||||
#[error("failed to set content in Title")]
|
||||
Title(#[from] title::TitleError),
|
||||
#[error("IOS content is invalid")]
|
||||
IO(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub fn ios_find_module(module_keyword: String, ios: &title::Title) -> Result<usize, IOSPatcherError> {
|
||||
let content_records = ios.tmd.content_records();
|
||||
let tid = hex::encode(ios.tmd.title_id());
|
||||
|
||||
// If the TID is not a valid IOS TID, then return NotIOS. It's possible that this could catch
|
||||
// some modified IOSes that currently have a non-IOS TID but that's weird and if you're doing
|
||||
// that please stop.
|
||||
if !tid[..8].eq("00000001") || tid[8..].eq("00000001") || tid[8..].eq("00000002") {
|
||||
return Err(IOSPatcherError::NotIOS);
|
||||
}
|
||||
|
||||
// Find the module's keyword in the content, and return the (true) index of the content that
|
||||
// it was found in.
|
||||
let keyword = module_keyword.as_bytes();
|
||||
for record in content_records {
|
||||
let content_decrypted = ios.get_content_by_index(record.index as usize)?;
|
||||
let offset = content_decrypted
|
||||
.windows(keyword.len())
|
||||
.position(|window| window == keyword);
|
||||
if offset.is_some() {
|
||||
return Ok(record.index as usize);
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't return early by finding the offset, then return a ModuleNotFound error.
|
||||
Err(IOSPatcherError::ModuleNotFound(module_keyword))
|
||||
}
|
||||
|
||||
fn ios_apply_patches(
|
||||
target_content: &mut Cursor<Vec<u8>>,
|
||||
find_seq: Vec<Vec<u8>>,
|
||||
replace_seq: Vec<Vec<u8>>
|
||||
) -> Result<i32, IOSPatcherError> {
|
||||
let mut patch_count = 0;
|
||||
for idx in 0..find_seq.len() {
|
||||
let offset = target_content.get_ref()
|
||||
.windows(find_seq[idx].len())
|
||||
.position(|window| window == find_seq[idx]);
|
||||
if let Some(offset) = offset {
|
||||
target_content.seek(SeekFrom::Start(offset as u64))?;
|
||||
target_content.write_all(&replace_seq[idx])?;
|
||||
patch_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(patch_count)
|
||||
}
|
||||
|
||||
pub fn ios_patch_sigchecks(ios: &mut title::Title, es_index: usize) -> Result<i32, IOSPatcherError> {
|
||||
let target_content = ios.get_content_by_index(es_index)?;
|
||||
let mut buf = Cursor::new(target_content);
|
||||
|
||||
let find_seq = vec![vec![0x20, 0x07, 0x23, 0xa2], vec![0x20, 0x07, 0x4b, 0x0b]];
|
||||
let replace_seq: Vec<Vec<u8>> = vec![vec![0x20, 0x00, 0x23, 0xa2], vec![0x20, 0x00, 0x4b, 0x0b]];
|
||||
let patch_count = ios_apply_patches(&mut buf, find_seq, replace_seq)?;
|
||||
|
||||
ios.set_content(buf.get_ref(), es_index, None, None)?;
|
||||
|
||||
Ok(patch_count)
|
||||
}
|
||||
|
||||
pub fn ios_patch_es_identify(ios: &mut title::Title, es_index: usize) -> Result<i32, IOSPatcherError> {
|
||||
let target_content = ios.get_content_by_index(es_index)?;
|
||||
let mut buf = Cursor::new(target_content);
|
||||
|
||||
let find_seq = vec![vec![0x28, 0x03, 0xd1, 0x23]];
|
||||
let replace_seq = vec![vec![0x28, 0x03, 0x00, 0x00]];
|
||||
let patch_count = ios_apply_patches(&mut buf, find_seq, replace_seq)?;
|
||||
|
||||
ios.set_content(buf.get_ref(), es_index, None, None)?;
|
||||
|
||||
Ok(patch_count)
|
||||
}
|
||||
|
||||
pub fn ios_patch_dev_flash(ios: &mut title::Title, es_index: usize) -> Result<i32, IOSPatcherError> {
|
||||
let target_content = ios.get_content_by_index(es_index)?;
|
||||
let mut buf = Cursor::new(target_content);
|
||||
|
||||
let find_seq = vec![vec![0x42, 0x8b, 0xd0, 0x01, 0x25, 0x66]];
|
||||
let replace_seq = vec![vec![0x42, 0x8b, 0xe0, 0x01, 0x25, 0x66]];
|
||||
let patch_count = ios_apply_patches(&mut buf, find_seq, replace_seq)?;
|
||||
|
||||
ios.set_content(buf.get_ref(), es_index, None, None)?;
|
||||
|
||||
Ok(patch_count)
|
||||
}
|
||||
|
||||
pub fn ios_patch_allow_downgrade(ios: &mut title::Title, es_index: usize) -> Result<i32, IOSPatcherError> {
|
||||
let target_content = ios.get_content_by_index(es_index)?;
|
||||
let mut buf = Cursor::new(target_content);
|
||||
|
||||
let find_seq = vec![vec![0xd2, 0x01, 0x4e, 0x56]];
|
||||
let replace_seq = vec![vec![0xe0, 0x01, 0x4e, 0x56]];
|
||||
let patch_count = ios_apply_patches(&mut buf, find_seq, replace_seq)?;
|
||||
|
||||
ios.set_content(buf.get_ref(), es_index, None, None)?;
|
||||
|
||||
Ok(patch_count)
|
||||
}
|
||||
|
||||
pub fn ios_patch_drive_inquiry(ios: &mut title::Title, dip_index: usize) -> Result<i32, IOSPatcherError> {
|
||||
let target_content = ios.get_content_by_index(dip_index)?;
|
||||
let mut buf = Cursor::new(target_content);
|
||||
|
||||
let find_seq = vec![vec![0x49, 0x4c, 0x23, 0x90, 0x68, 0x0a]];
|
||||
let replace_seq = vec![vec![0x20, 0x00, 0xe5, 0x38, 0x68, 0x0a]];
|
||||
let patch_count = ios_apply_patches(&mut buf, find_seq, replace_seq)?;
|
||||
|
||||
ios.set_content(buf.get_ref(), dip_index, None, None)?;
|
||||
|
||||
Ok(patch_count)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ pub mod cert;
|
||||
pub mod commonkeys;
|
||||
pub mod content;
|
||||
pub mod crypto;
|
||||
pub mod iospatcher;
|
||||
pub mod nus;
|
||||
pub mod ticket;
|
||||
pub mod tmd;
|
||||
@@ -137,6 +138,7 @@ impl Title {
|
||||
/// content type can be provided, with the existing values being preserved by default.
|
||||
pub fn set_content(&mut self, content: &[u8], index: usize, cid: Option<u32>, content_type: Option<tmd::ContentType>) -> Result<(), TitleError> {
|
||||
self.content.set_content(content, index, cid, content_type, self.ticket.title_key_dec())?;
|
||||
self.tmd.set_content_records(self.content.content_records());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -146,6 +148,7 @@ impl Title {
|
||||
/// content records.
|
||||
pub fn add_content(&mut self, content: &[u8], cid: u32, content_type: tmd::ContentType) -> Result<(), TitleError> {
|
||||
self.content.add_content(content, cid, content_type, self.ticket.title_key_dec())?;
|
||||
self.tmd.set_content_records(self.content.content_records());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -195,6 +198,11 @@ impl Title {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_title_version(&mut self, version: u16) {
|
||||
self.tmd.set_title_version(version);
|
||||
self.ticket.set_title_version(version);
|
||||
}
|
||||
|
||||
pub fn set_cert_chain(&mut self, cert_chain: cert::CertificateChain) {
|
||||
self.cert_chain = cert_chain;
|
||||
}
|
||||
|
||||
@@ -212,6 +212,10 @@ impl Ticket {
|
||||
pub fn title_version(&self) -> u16 {
|
||||
self.title_version
|
||||
}
|
||||
|
||||
pub fn set_title_version(&mut self, version: u16) {
|
||||
self.title_version = version;
|
||||
}
|
||||
|
||||
/// Gets the permitted titles mask listed in the Ticket.
|
||||
pub fn permitted_titles_mask(&self) -> [u8; 4] {
|
||||
|
||||
@@ -393,6 +393,10 @@ impl TMD {
|
||||
self.title_type = new_type;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_title_version(&mut self, version: u16) {
|
||||
self.title_version = version;
|
||||
}
|
||||
|
||||
/// Gets the type of content described by a content record in a TMD.
|
||||
pub fn content_type(&self, index: usize) -> ContentType {
|
||||
|
||||
Reference in New Issue
Block a user