mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2026-03-03 03:15:28 -05: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:
@@ -7,8 +7,15 @@ use rustwii::archive::u8;
|
|||||||
// use rustii::title::content;
|
// use rustii::title::content;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let data = fs::read("sm.wad").unwrap();
|
let data = fs::read("ios9.wad").unwrap();
|
||||||
let title = title::Title::from_bytes(&data).unwrap();
|
let mut title = title::Title::from_bytes(&data).unwrap();
|
||||||
|
|
||||||
|
let index = title::iospatcher::ios_find_module(String::from("ES:"), &title).unwrap();
|
||||||
|
println!("ES index: {}", index);
|
||||||
|
|
||||||
|
let patch_count = title::iospatcher::ios_patch_sigchecks(&mut title, index).unwrap();
|
||||||
|
println!("patches applied: {}", patch_count);
|
||||||
|
|
||||||
println!("Title ID from WAD via Title object: {}", hex::encode(title.tmd.title_id()));
|
println!("Title ID from WAD via Title object: {}", hex::encode(title.tmd.title_id()));
|
||||||
|
|
||||||
let wad = wad::WAD::from_bytes(&data).unwrap();
|
let wad = wad::WAD::from_bytes(&data).unwrap();
|
||||||
@@ -41,16 +48,9 @@ fn main() {
|
|||||||
|
|
||||||
let result = title.verify().unwrap();
|
let result = title.verify().unwrap();
|
||||||
println!("full title verified successfully: {}", result);
|
println!("full title verified successfully: {}", result);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let u8_archive = u8::U8Directory::from_bytes(fs::read("testu8.arc").unwrap().into_boxed_slice()).unwrap();
|
let u8_archive = u8::U8Directory::from_bytes(fs::read("testu8.arc").unwrap().into_boxed_slice()).unwrap();
|
||||||
println!("{:#?}", u8_archive);
|
println!("{:#?}", u8_archive);
|
||||||
// println!("files and dirs counted: {}", u8_archive.node_tree.borrow().count());
|
|
||||||
// fs::write("outfile.arc", u8_archive.to_bytes().unwrap()).unwrap();
|
|
||||||
// println!("re-written");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// let mut content_map = content::SharedContentMap::from_bytes(&fs::read("content.map").unwrap()).unwrap();
|
// let mut content_map = content::SharedContentMap::from_bytes(&fs::read("content.map").unwrap()).unwrap();
|
||||||
// content_map.add(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap();
|
// content_map.add(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// archive/ash.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// archive/ash.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for the ASH decompression command in the rustii CLI.
|
// Code for the ASH decompression command in the rustwii CLI.
|
||||||
// Might even have the compression command someday if I ever write the compression code!
|
// Might even have the compression command someday if I ever write the compression code!
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// archive/lz77.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// archive/lz77.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for the LZ77 compression/decompression commands in the rustii CLI.
|
// Code for the LZ77 compression/decompression commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// archive/theme.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// archive/theme.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for the theme building commands in the rustii CLI.
|
// Code for the theme building commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// archive/u8.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// archive/u8.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for the U8 packing/unpacking commands in the rustii CLI.
|
// Code for the U8 packing/unpacking commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// info.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// info.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for the info command in the rustii CLI.
|
// Code for the info command in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// main.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// main.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Base for the rustii CLI that handles argument parsing and directs execution to the proper module.
|
// Base for the rustwii CLI that handles argument parsing and directs execution to the proper module.
|
||||||
|
|
||||||
mod archive;
|
mod archive;
|
||||||
mod title;
|
mod title;
|
||||||
@@ -36,7 +36,7 @@ enum Commands {
|
|||||||
Fakesign {
|
Fakesign {
|
||||||
/// The path to a TMD, Ticket, or WAD
|
/// The path to a TMD, Ticket, or WAD
|
||||||
input: String,
|
input: String,
|
||||||
/// An (optional) output name; defaults to overwriting input file if not provided
|
/// An optional output path; defaults to overwriting input file if not provided
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
output: Option<String>,
|
output: Option<String>,
|
||||||
},
|
},
|
||||||
@@ -45,6 +45,25 @@ enum Commands {
|
|||||||
/// The path to a TMD, Ticket, or WAD
|
/// The path to a TMD, Ticket, or WAD
|
||||||
input: String,
|
input: String,
|
||||||
},
|
},
|
||||||
|
/// Apply patches to an IOS
|
||||||
|
IosPatch {
|
||||||
|
/// The IOS WAD to apply patches to
|
||||||
|
input: String,
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// An optional output path; default to overwriting input file if not provided
|
||||||
|
output: Option<String>,
|
||||||
|
/// Set a new IOS version (0-65535)
|
||||||
|
#[arg(short, long)]
|
||||||
|
version: Option<u16>,
|
||||||
|
/// Set the slot that this IOS will install into
|
||||||
|
#[arg(short, long)]
|
||||||
|
slot: Option<u8>,
|
||||||
|
/// Set all patched content to be non-shared
|
||||||
|
#[arg(short, long, action)]
|
||||||
|
no_shared: bool,
|
||||||
|
#[command(flatten)]
|
||||||
|
enabled_patches: title::iospatcher::EnabledPatches,
|
||||||
|
},
|
||||||
/// Compress/decompress data using LZ77 compression
|
/// Compress/decompress data using LZ77 compression
|
||||||
Lz77 {
|
Lz77 {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
@@ -118,6 +137,24 @@ fn main() -> Result<()> {
|
|||||||
Some(Commands::Info { input }) => {
|
Some(Commands::Info { input }) => {
|
||||||
info::info(input)?
|
info::info(input)?
|
||||||
},
|
},
|
||||||
|
Some(Commands::IosPatch {
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
version,
|
||||||
|
slot,
|
||||||
|
no_shared,
|
||||||
|
enabled_patches
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
title::iospatcher::patch_ios(
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
version,
|
||||||
|
slot,
|
||||||
|
no_shared,
|
||||||
|
enabled_patches,
|
||||||
|
)?
|
||||||
|
}
|
||||||
Some(Commands::Lz77 { command }) => {
|
Some(Commands::Lz77 { command }) => {
|
||||||
match command {
|
match command {
|
||||||
archive::lz77::Commands::Compress { input, output } => {
|
archive::lz77::Commands::Compress { input, output } => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// nand/emunand.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// nand/emunand.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for EmuNAND-related commands in the rustii CLI.
|
// Code for EmuNAND-related commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::{absolute, Path};
|
use std::path::{absolute, Path};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// nand/setting.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// nand/setting.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for setting.txt-related commands in the rustii CLI.
|
// Code for setting.txt-related commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// title/fakesign.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// title/fakesign.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for the fakesign command in the rustii CLI.
|
// Code for the fakesign command in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|||||||
129
src/bin/rustwii/title/iospatcher.rs
Normal file
129
src/bin/rustwii/title/iospatcher.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// title/iospatcher.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
|
//
|
||||||
|
// Code for the iospatcher command in the rustwii CLI.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use clap::Args;
|
||||||
|
use rustwii::title;
|
||||||
|
use rustwii::title::iospatcher;
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[clap(next_help_heading = "Patches")]
|
||||||
|
#[group(multiple = true, required = true)]
|
||||||
|
/// Modifications that can be made to a title, shared between the WAD and TMD commands.
|
||||||
|
pub struct EnabledPatches {
|
||||||
|
/// Patch out signature checks
|
||||||
|
#[arg(long, action)]
|
||||||
|
sig_checks: bool,
|
||||||
|
/// Patch in access to ES_Identify
|
||||||
|
#[arg(long, action)]
|
||||||
|
es_identify: bool,
|
||||||
|
/// Patch in access to /dev/flash
|
||||||
|
#[arg(long, action)]
|
||||||
|
dev_flash: bool,
|
||||||
|
/// Patch out anti-downgrade checks
|
||||||
|
#[arg(long, action)]
|
||||||
|
allow_downgrade: bool,
|
||||||
|
/// Patch out drive inquiries (EXPERIMENTAL)
|
||||||
|
#[arg(long, action)]
|
||||||
|
drive_inquiry: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn patch_ios(
|
||||||
|
input: &str,
|
||||||
|
output: &Option<String>,
|
||||||
|
version: &Option<u16>,
|
||||||
|
slot: &Option<u8>,
|
||||||
|
no_shared: &bool,
|
||||||
|
enabled_patches: &EnabledPatches,
|
||||||
|
) -> Result<()> {
|
||||||
|
let in_path = Path::new(input);
|
||||||
|
if !in_path.exists() {
|
||||||
|
bail!("Source WAD \"{}\" does not exist.", in_path.display());
|
||||||
|
}
|
||||||
|
let out_path = if output.is_some() {
|
||||||
|
PathBuf::from(output.clone().unwrap()).with_extension("wad")
|
||||||
|
} else {
|
||||||
|
in_path.to_path_buf()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ios = title::Title::from_bytes(&fs::read(in_path)?).with_context(|| "The provided WAD file could not be parsed, and is likely invalid.")?;
|
||||||
|
let tid = hex::encode(ios.tmd.title_id());
|
||||||
|
|
||||||
|
// If the TID is not a valid IOS TID, then bail.
|
||||||
|
if !tid[..8].eq("00000001") || tid[8..].eq("00000001") || tid[8..].eq("00000002") {
|
||||||
|
bail!("The provided WAD does not appear to contain an IOS! No patches can be applied.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut patches_applied = 0;
|
||||||
|
|
||||||
|
if let Some(version) = version {
|
||||||
|
ios.set_title_version(*version);
|
||||||
|
println!("Set new IOS version: {version}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(slot) = slot && *slot >= 3 {
|
||||||
|
let tid = hex::decode(format!("00000001{slot:08X}"))?;
|
||||||
|
ios.set_title_id(tid.try_into().unwrap()).expect("Failed to set IOS slot!");
|
||||||
|
println!("Set new IOS slot: {slot}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled_patches.sig_checks ||
|
||||||
|
enabled_patches.es_identify ||
|
||||||
|
enabled_patches.dev_flash ||
|
||||||
|
enabled_patches.allow_downgrade
|
||||||
|
{
|
||||||
|
let es_index = iospatcher::ios_find_module(String::from("ES:"), &ios)
|
||||||
|
.with_context(|| "The ES module could not be found. This WAD is not a valid IOS.")?;
|
||||||
|
if enabled_patches.sig_checks {
|
||||||
|
print!("Applying signature check patch... ");
|
||||||
|
let count = iospatcher::ios_patch_sigchecks(&mut ios, es_index)?;
|
||||||
|
println!("{} patch(es) applied", count);
|
||||||
|
patches_applied += count;
|
||||||
|
}
|
||||||
|
if enabled_patches.es_identify {
|
||||||
|
print!("Applying ES_Identify access patch... ");
|
||||||
|
let count = iospatcher::ios_patch_es_identify(&mut ios, es_index)?;
|
||||||
|
println!("{} patch(es) applied", count);
|
||||||
|
patches_applied += count;
|
||||||
|
}
|
||||||
|
if enabled_patches.dev_flash {
|
||||||
|
print!("Applying /dev/flash access patch... ");
|
||||||
|
let count = iospatcher::ios_patch_dev_flash(&mut ios, es_index)?;
|
||||||
|
println!("{} patch(es) applied", count);
|
||||||
|
patches_applied += count;
|
||||||
|
}
|
||||||
|
if enabled_patches.allow_downgrade {
|
||||||
|
print!("Applying allow downgrading patch... ");
|
||||||
|
let count = iospatcher::ios_patch_allow_downgrade(&mut ios, es_index)?;
|
||||||
|
println!("{} patch(es) applied", count);
|
||||||
|
patches_applied += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled_patches.drive_inquiry {
|
||||||
|
let dip_index = iospatcher::ios_find_module(String::from("DIP:"), &ios)
|
||||||
|
.with_context(|| "The DIP module could not be found. This WAD is not a valid IOS, \
|
||||||
|
or this IOS version does not use the DIP module.")?;
|
||||||
|
print!("Applying (EXPERIMENTAL) drive inquiry patch... ");
|
||||||
|
let count = iospatcher::ios_patch_drive_inquiry(&mut ios, dip_index)?;
|
||||||
|
println!("{} patch(es) applied", count);
|
||||||
|
patches_applied += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nTotal patches applied: {patches_applied}");
|
||||||
|
|
||||||
|
if patches_applied == 0 && version.is_none() && slot.is_none() {
|
||||||
|
bail!("No patchers were applied. Please make sure the specified patches are compatible \
|
||||||
|
with this IOS.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ios.fakesign()?;
|
||||||
|
fs::write(out_path, ios.to_wad()?.to_bytes()?)?;
|
||||||
|
|
||||||
|
println!("IOS successfully patched!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -6,3 +6,4 @@ pub mod nus;
|
|||||||
pub mod wad;
|
pub mod wad;
|
||||||
pub mod tmd;
|
pub mod tmd;
|
||||||
mod shared;
|
mod shared;
|
||||||
|
pub mod iospatcher;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// title/nus.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// title/nus.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for NUS-related commands in the rustii CLI.
|
// Code for NUS-related commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// title/shared.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// title/shared.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code shared between title commands in the rustii CLI.
|
// Code shared between title commands in the rustwii CLI.
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// title/tmd.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// title/tmd.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for TMD-related commands in the rustii CLI.
|
// Code for TMD-related commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs};
|
use std::{str, fs};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// title/wad.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
// title/wad.rs from ruswtii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustwii
|
// https://github.com/NinjaCheetah/rustwii
|
||||||
//
|
//
|
||||||
// Code for WAD-related commands in the rustii CLI.
|
// Code for WAD-related commands in the rustwii CLI.
|
||||||
|
|
||||||
use std::{str, fs, fmt};
|
use std::{str, fs, fmt};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|||||||
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 commonkeys;
|
||||||
pub mod content;
|
pub mod content;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
pub mod iospatcher;
|
||||||
pub mod nus;
|
pub mod nus;
|
||||||
pub mod ticket;
|
pub mod ticket;
|
||||||
pub mod tmd;
|
pub mod tmd;
|
||||||
@@ -137,6 +138,7 @@ impl Title {
|
|||||||
/// content type can be provided, with the existing values being preserved by default.
|
/// 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> {
|
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.content.set_content(content, index, cid, content_type, self.ticket.title_key_dec())?;
|
||||||
|
self.tmd.set_content_records(self.content.content_records());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +148,7 @@ impl Title {
|
|||||||
/// content records.
|
/// content records.
|
||||||
pub fn add_content(&mut self, content: &[u8], cid: u32, content_type: tmd::ContentType) -> Result<(), TitleError> {
|
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.content.add_content(content, cid, content_type, self.ticket.title_key_dec())?;
|
||||||
|
self.tmd.set_content_records(self.content.content_records());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +198,11 @@ impl Title {
|
|||||||
Ok(())
|
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) {
|
pub fn set_cert_chain(&mut self, cert_chain: cert::CertificateChain) {
|
||||||
self.cert_chain = cert_chain;
|
self.cert_chain = cert_chain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,6 +212,10 @@ impl Ticket {
|
|||||||
pub fn title_version(&self) -> u16 {
|
pub fn title_version(&self) -> u16 {
|
||||||
self.title_version
|
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.
|
/// Gets the permitted titles mask listed in the Ticket.
|
||||||
pub fn permitted_titles_mask(&self) -> [u8; 4] {
|
pub fn permitted_titles_mask(&self) -> [u8; 4] {
|
||||||
|
|||||||
@@ -393,6 +393,10 @@ impl TMD {
|
|||||||
self.title_type = new_type;
|
self.title_type = new_type;
|
||||||
Ok(())
|
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.
|
/// Gets the type of content described by a content record in a TMD.
|
||||||
pub fn content_type(&self, index: usize) -> ContentType {
|
pub fn content_type(&self, index: usize) -> ContentType {
|
||||||
|
|||||||
Reference in New Issue
Block a user