Added fakesigning and fakesigning detection, command is in CLI

This commit is contained in:
Campbell 2025-03-25 18:28:37 -04:00
parent 1bcc004af7
commit 839e33b911
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
11 changed files with 369 additions and 51 deletions

45
Cargo.lock generated
View File

@ -13,6 +13,15 @@ dependencies = [
"cpufeatures", "cpufeatures",
] ]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.18" version = "0.6.18"
@ -237,6 +246,12 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.1" version = "1.21.1"
@ -261,6 +276,35 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "rustii" name = "rustii"
version = "0.1.0" version = "0.1.0"
@ -271,6 +315,7 @@ dependencies = [
"clap", "clap",
"glob", "glob",
"hex", "hex",
"regex",
"sha1", "sha1",
] ]

View File

@ -30,4 +30,5 @@ aes = "0"
hex = "0" hex = "0"
sha1 = "0" sha1 = "0"
glob = "0" glob = "0"
regex = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }

View File

@ -1,41 +1,43 @@
// Sample file for testing rustii library stuff. // Sample file for testing rustii library stuff.
use std::fs; use std::fs;
use rustii::title::{tmd, ticket, content, crypto, wad}; use rustii::title::{content, crypto, wad};
use rustii::title; use rustii::title;
fn main() { fn main() {
let data = fs::read("sm.wad").unwrap(); let data = fs::read("sm.wad").unwrap();
let title = title::Title::from_bytes(&data).unwrap(); let mut title = title::Title::from_bytes(&data).unwrap();
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();
println!("size of tmd: {:?}", wad.tmd().len()); println!("size of tmd: {:?}", wad.tmd().len());
let tmd = tmd::TMD::from_bytes(&wad.tmd()).unwrap(); println!("num content records: {:?}", title.tmd.content_records.len());
println!("num content records: {:?}", tmd.content_records.len()); println!("first record data: {:?}", title.tmd.content_records.first().unwrap());
println!("first record data: {:?}", tmd.content_records.first().unwrap()); if !title.tmd.is_fakesigned() {
assert_eq!(wad.tmd(), tmd.to_bytes().unwrap()); title.tmd.fakesign().unwrap();
}
println!("TMD is fakesigned: {:?}",title.tmd.is_fakesigned());
let tik = ticket::Ticket::from_bytes(&wad.ticket()).unwrap(); println!("title version from ticket is: {:?}", title.ticket.title_version);
println!("title version from ticket is: {:?}", tik.title_version); println!("title key (enc): {:?}", title.ticket.title_key);
println!("title key (enc): {:?}", tik.title_key); println!("title key (dec): {:?}", title.ticket.dec_title_key());
println!("title key (dec): {:?}", tik.dec_title_key()); if !title.ticket.is_fakesigned() {
assert_eq!(wad.ticket(), tik.to_bytes().unwrap()); title.ticket.fakesign().unwrap();
}
println!("ticket is fakesigned: {:?}", title.ticket.is_fakesigned());
println!("title is fakesigned: {:?}", title.is_fakesigned());
let content_region = content::ContentRegion::from_bytes(&wad.content(), tmd.content_records).unwrap(); let content_region = content::ContentRegion::from_bytes(&wad.content(), title.tmd.content_records).unwrap();
assert_eq!(wad.content(), content_region.to_bytes().unwrap()); assert_eq!(wad.content(), content_region.to_bytes().unwrap());
println!("content OK"); println!("content OK");
let content_dec = content_region.get_content_by_index(0, tik.dec_title_key()).unwrap(); let content_dec = content_region.get_content_by_index(0, title.ticket.dec_title_key()).unwrap();
println!("content dec from index: {:?}", content_dec); println!("content dec from index: {:?}", content_dec);
let content = content_region.get_enc_content_by_index(0).unwrap(); let content = content_region.get_enc_content_by_index(0).unwrap();
assert_eq!(content, crypto::encrypt_content(&content_dec, tik.dec_title_key(), 0, content_region.content_records[0].content_size)); assert_eq!(content, crypto::encrypt_content(&content_dec, title.ticket.dec_title_key(), 0, content_region.content_records[0].content_size));
println!("content re-encrypted OK"); println!("content re-encrypted OK");
println!("wad header: {:?}", wad.header); println!("wad header: {:?}", wad.header);
let repacked = wad.to_bytes().unwrap();
assert_eq!(repacked, data);
println!("wad packed OK");
} }

View File

@ -0,0 +1,87 @@
// filetypes.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii
//
// Common code for identifying Wii file types.
use std::{str, fs::File};
use std::io::Read;
use std::path::Path;
use regex::RegexBuilder;
#[derive(Debug)]
#[derive(PartialEq)]
pub enum WiiFileType {
Wad,
Tmd,
Ticket
}
pub fn identify_file_type(input: &str) -> Option<WiiFileType> {
let input = Path::new(input);
let re = RegexBuilder::new(r"tmd\.?[0-9]*").case_insensitive(true).build().unwrap();
// == TMD ==
if re.is_match(input.to_str()?) ||
input.file_name().is_some_and(|f| f.eq_ignore_ascii_case("tmd.bin")) ||
input.extension().is_some_and(|f| f.eq_ignore_ascii_case("tmd")) {
return Some(WiiFileType::Tmd);
}
// == Ticket ==
if input.extension().is_some_and(|f| f.eq_ignore_ascii_case("tik")) ||
input.file_name().is_some_and(|f| f.eq_ignore_ascii_case("ticket.bin")) ||
input.file_name().is_some_and(|f| f.eq_ignore_ascii_case("cetk")) {
return Some(WiiFileType::Ticket);
}
// == WAD ==
if input.extension().is_some_and(|f| f.eq_ignore_ascii_case("wad")) {
return Some(WiiFileType::Wad);
}
// Advanced WAD detection, where we read and compare the first 8 bytes (only if the path exists.)
if input.exists() {
let mut f = File::open(input).unwrap();
let mut magic_number = vec![0u8; 8];
f.read_exact(&mut magic_number).unwrap();
if magic_number == b"\x00\x00\x00\x20\x49\x73\x00\x00" || magic_number == b"\x00\x00\x00\x20\x69\x62\x00\x00" {
return Some(WiiFileType::Wad);
}
}
// == No match found! ==
None
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_tmd() {
assert_eq!(identify_file_type("tmd"), Some(WiiFileType::Tmd));
assert_eq!(identify_file_type("TMD"), Some(WiiFileType::Tmd));
assert_eq!(identify_file_type("tmd.bin"), Some(WiiFileType::Tmd));
assert_eq!(identify_file_type("TMD.BIN"), Some(WiiFileType::Tmd));
assert_eq!(identify_file_type("tmd.513"), Some(WiiFileType::Tmd));
assert_eq!(identify_file_type("0000000100000002.tmd"), Some(WiiFileType::Tmd));
assert_eq!(identify_file_type("0000000100000002.TMD"), Some(WiiFileType::Tmd));
}
#[test]
fn test_parse_tik() {
assert_eq!(identify_file_type("ticket.bin"), Some(WiiFileType::Ticket));
assert_eq!(identify_file_type("TICKET.BIN"), Some(WiiFileType::Ticket));
assert_eq!(identify_file_type("cetk"), Some(WiiFileType::Ticket));
assert_eq!(identify_file_type("CETK"), Some(WiiFileType::Ticket));
assert_eq!(identify_file_type("0000000100000002.tik"), Some(WiiFileType::Ticket));
assert_eq!(identify_file_type("0000000100000002.TIK"), Some(WiiFileType::Ticket));
}
#[test]
fn test_parse_wad() {
assert_eq!(identify_file_type("0000000100000002.wad"), Some(WiiFileType::Wad));
assert_eq!(identify_file_type("0000000100000002.WAD"), Some(WiiFileType::Wad));
}
#[test]
fn test_parse_no_match() {
assert_eq!(identify_file_type("somefile.txt"), None);
}
}

View File

@ -4,9 +4,10 @@
// Base for the rustii CLI that handles argument parsing and directs execution to the proper module. // Base for the rustii CLI that handles argument parsing and directs execution to the proper module.
mod title; mod title;
use clap::{Subcommand, Parser}; mod filetypes;
use title::wad;
use clap::{Subcommand, Parser};
use title::{wad, fakesign};
#[derive(Parser)] #[derive(Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -23,6 +24,13 @@ enum Commands {
#[command(subcommand)] #[command(subcommand)]
command: Option<wad::Commands>, command: Option<wad::Commands>,
}, },
/// Fakesign a TMD, Ticket, or WAD (trucha bug)
Fakesign {
/// Fakesign a TMD, Ticket, or WAD (trucha bug)
input: String,
#[arg(short, long)]
output: Option<String>,
}
} }
fn main() { fn main() {
@ -39,7 +47,10 @@ fn main() {
}, },
&None => { /* This is handled by clap */} &None => { /* This is handled by clap */}
} }
},
Some(Commands::Fakesign { input, output }) => {
fakesign::fakesign(input, output)
} }
None => {} None => {}
} }
} }

View File

@ -0,0 +1,60 @@
// title/fakesign.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii
//
// Code for dedicated fakesigning-related commands in the rustii CLI.
use std::{str, fs};
use std::path::{Path, PathBuf};
use crate::filetypes::{WiiFileType, identify_file_type};
use rustii::{title, title::tmd, title::ticket};
pub fn fakesign(input: &str, output: &Option<String>) {
let in_path = Path::new(input);
if !in_path.exists() {
panic!("Error: Input file does not exist.");
}
match identify_file_type(input) {
Some(WiiFileType::Wad) => {
let out_path = if output.is_some() {
PathBuf::from(output.clone().unwrap().as_str()).with_extension("wad")
} else {
PathBuf::from(input)
};
// Load WAD into a Title instance, then fakesign it.
let mut title = title::Title::from_bytes(fs::read(in_path).unwrap().as_slice()).expect("could not read WAD file");
title.fakesign().expect("could not fakesign WAD");
// Write output file.
fs::write(out_path, title.to_wad().unwrap().to_bytes().expect("could not create output WAD")).expect("could not write output WAD file");
println!("WAD fakesigned!");
},
Some(WiiFileType::Tmd) => {
let out_path = if output.is_some() {
PathBuf::from(output.clone().unwrap().as_str()).with_extension("tmd")
} else {
PathBuf::from(input)
};
// Load TMD into a TMD instance, then fakesign it.
let mut tmd = tmd::TMD::from_bytes(fs::read(in_path).unwrap().as_slice()).expect("could not read TMD file");
tmd.fakesign().expect("could not fakesign TMD");
// Write output file.
fs::write(out_path, tmd.to_bytes().expect("could not create output TMD")).expect("could not write output TMD file");
println!("TMD fakesigned!");
},
Some(WiiFileType::Ticket) => {
let out_path = if output.is_some() {
PathBuf::from(output.clone().unwrap().as_str()).with_extension("tik")
} else {
PathBuf::from(input)
};
// Load Ticket into a Ticket instance, then fakesign it.
let mut ticket = ticket::Ticket::from_bytes(fs::read(in_path).unwrap().as_slice()).expect("could not read Ticket file");
ticket.fakesign().expect("could not fakesign Ticket");
// Write output file.
fs::write(out_path, ticket.to_bytes().expect("could not create output Ticket")).expect("could not write output Ticket file");
println!("Ticket fakesigned!");
},
None => {
panic!("Error: You can only fakesign TMDs, Tickets, and WADs!");
}
}
}

View File

@ -1,4 +1,5 @@
// title/mod.rs from rustii (c) 2025 NinjaCheetah & Contributors // title/mod.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii // https://github.com/NinjaCheetah/rustii
pub mod fakesign;
pub mod wad; pub mod wad;

View File

@ -20,6 +20,8 @@ pub enum TitleError {
BadTMD, BadTMD,
BadContent, BadContent,
InvalidWAD, InvalidWAD,
TMDError(tmd::TMDError),
TicketError(ticket::TicketError),
WADError(wad::WADError), WADError(wad::WADError),
IOError(std::io::Error), IOError(std::io::Error),
} }
@ -31,6 +33,8 @@ impl fmt::Display for TitleError {
TitleError::BadTMD => "The provided TMD data was invalid.", TitleError::BadTMD => "The provided TMD data was invalid.",
TitleError::BadContent => "The provided content data was invalid.", TitleError::BadContent => "The provided content data was invalid.",
TitleError::InvalidWAD => "The provided WAD data was invalid.", TitleError::InvalidWAD => "The provided WAD data was invalid.",
TitleError::TMDError(_) => "An error occurred while processing TMD data.",
TitleError::TicketError(_) => "An error occurred while processing ticket data.",
TitleError::WADError(_) => "A WAD could not be built from the provided data.", TitleError::WADError(_) => "A WAD could not be built from the provided data.",
TitleError::IOError(_) => "The provided Title data was invalid.", TitleError::IOError(_) => "The provided Title data was invalid.",
}; };
@ -85,6 +89,17 @@ impl Title {
Ok(title) Ok(title)
} }
pub fn is_fakesigned(&self) -> bool {
self.tmd.is_fakesigned() && self.ticket.is_fakesigned()
}
pub fn fakesign(&mut self) -> Result<(), TitleError> {
// Run the fakesign methods on the TMD and Ticket.
self.tmd.fakesign().map_err(TitleError::TMDError)?;
self.ticket.fakesign().map_err(TitleError::TicketError)?;
Ok(())
}
pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> { pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> {
let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?; let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?;
Ok(content) Ok(content)

View File

@ -7,11 +7,13 @@ use std::error::Error;
use std::fmt; use std::fmt;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Sha1, Digest};
use crate::title::crypto::decrypt_title_key; use crate::title::crypto::decrypt_title_key;
#[derive(Debug)] #[derive(Debug)]
pub enum TicketError { pub enum TicketError {
UnsupportedVersion, UnsupportedVersion,
CannotFakesign,
IOError(std::io::Error), IOError(std::io::Error),
} }
@ -19,6 +21,7 @@ impl fmt::Display for TicketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self { let description = match *self {
TicketError::UnsupportedVersion => "The provided Ticket is not a supported version (only v0 is supported).", TicketError::UnsupportedVersion => "The provided Ticket is not a supported version (only v0 is supported).",
TicketError::CannotFakesign => "The Ticket data could not be fakesigned.",
TicketError::IOError(_) => "The provided Ticket data was invalid.", TicketError::IOError(_) => "The provided Ticket data was invalid.",
}; };
f.write_str(description) f.write_str(description)
@ -184,4 +187,37 @@ impl Ticket {
let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default(); let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default();
issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006") issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006")
} }
pub fn is_fakesigned(&self) -> bool {
// Can't be fakesigned without a null signature.
if self.signature != [0; 256] {
return false;
}
// Test the hash of the Ticket body to make sure it starts with 00.
let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap();
hasher.update(&ticket_body[320..]);
let result = hasher.finalize();
if result[0] != 0 {
return false;
}
true
}
pub fn fakesign(&mut self) -> Result<(), TicketError> {
// Erase the signature.
self.signature = [0; 256];
let mut current_int: u16 = 0;
let mut test_hash: [u8; 20] = [255; 20];
while test_hash[0] != 0 {
if current_int == 255 { return Err(TicketError::CannotFakesign); }
current_int += 1;
self.unknown2 = current_int.to_be_bytes();
let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap();
hasher.update(&ticket_body[320..]);
test_hash = <[u8; 20]>::from(hasher.finalize());
}
Ok(())
}
} }

View File

@ -3,8 +3,29 @@
// //
// Implements the structures and methods required for TMD parsing and editing. // Implements the structures and methods required for TMD parsing and editing.
use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Sha1, Digest};
#[derive(Debug)]
pub enum TMDError {
CannotFakesign,
IOError(std::io::Error),
}
impl fmt::Display for TMDError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
TMDError::CannotFakesign => "The TMD data could not be fakesigned.",
TMDError::IOError(_) => "The provided TMD data was invalid.",
};
f.write_str(description)
}
}
impl Error for TMDError {}
#[derive(Debug)] #[derive(Debug)]
#[derive(Clone)] #[derive(Clone)]
@ -46,55 +67,55 @@ pub struct TMD {
impl TMD { impl TMD {
/// Creates a new TMD instance from the binary data of a TMD file. /// Creates a new TMD instance from the binary data of a TMD file.
pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> { pub fn from_bytes(data: &[u8]) -> Result<Self, TMDError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let signature_type = buf.read_u32::<BigEndian>()?; let signature_type = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?;
let mut signature = [0u8; 256]; let mut signature = [0u8; 256];
buf.read_exact(&mut signature)?; buf.read_exact(&mut signature).map_err(TMDError::IOError)?;
// Maybe this can be read differently? // Maybe this can be read differently?
let mut padding1 = [0u8; 60]; let mut padding1 = [0u8; 60];
buf.read_exact(&mut padding1)?; buf.read_exact(&mut padding1).map_err(TMDError::IOError)?;
let mut signature_issuer = [0u8; 64]; let mut signature_issuer = [0u8; 64];
buf.read_exact(&mut signature_issuer)?; buf.read_exact(&mut signature_issuer).map_err(TMDError::IOError)?;
let tmd_version = buf.read_u8()?; let tmd_version = buf.read_u8().map_err(TMDError::IOError)?;
let ca_crl_version = buf.read_u8()?; let ca_crl_version = buf.read_u8().map_err(TMDError::IOError)?;
let signer_crl_version = buf.read_u8()?; let signer_crl_version = buf.read_u8().map_err(TMDError::IOError)?;
let is_vwii = buf.read_u8()?; let is_vwii = buf.read_u8().map_err(TMDError::IOError)?;
let mut ios_tid = [0u8; 8]; let mut ios_tid = [0u8; 8];
buf.read_exact(&mut ios_tid)?; buf.read_exact(&mut ios_tid).map_err(TMDError::IOError)?;
let mut title_id = [0u8; 8]; let mut title_id = [0u8; 8];
buf.read_exact(&mut title_id)?; buf.read_exact(&mut title_id).map_err(TMDError::IOError)?;
let mut title_type = [0u8; 4]; let mut title_type = [0u8; 4];
buf.read_exact(&mut title_type)?; buf.read_exact(&mut title_type).map_err(TMDError::IOError)?;
let group_id = buf.read_u16::<BigEndian>()?; let group_id = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
// Same here... // Same here...
let mut padding2 = [0u8; 2]; let mut padding2 = [0u8; 2];
buf.read_exact(&mut padding2)?; buf.read_exact(&mut padding2).map_err(TMDError::IOError)?;
let region = buf.read_u16::<BigEndian>()?; let region = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let mut ratings = [0u8; 16]; let mut ratings = [0u8; 16];
buf.read_exact(&mut ratings)?; buf.read_exact(&mut ratings).map_err(TMDError::IOError)?;
// ...and here... // ...and here...
let mut reserved1 = [0u8; 12]; let mut reserved1 = [0u8; 12];
buf.read_exact(&mut reserved1)?; buf.read_exact(&mut reserved1).map_err(TMDError::IOError)?;
let mut ipc_mask = [0u8; 12]; let mut ipc_mask = [0u8; 12];
buf.read_exact(&mut ipc_mask)?; buf.read_exact(&mut ipc_mask).map_err(TMDError::IOError)?;
// ...and here. // ...and here.
let mut reserved2 = [0u8; 18]; let mut reserved2 = [0u8; 18];
buf.read_exact(&mut reserved2)?; buf.read_exact(&mut reserved2).map_err(TMDError::IOError)?;
let access_rights = buf.read_u32::<BigEndian>()?; let access_rights = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?;
let title_version = buf.read_u16::<BigEndian>()?; let title_version = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let num_contents = buf.read_u16::<BigEndian>()?; let num_contents = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let boot_index = buf.read_u16::<BigEndian>()?; let boot_index = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let minor_version = buf.read_u16::<BigEndian>()?; let minor_version = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
// Build content records by iterating over the rest of the data num_contents times. // Build content records by iterating over the rest of the data num_contents times.
let mut content_records = Vec::with_capacity(num_contents as usize); let mut content_records = Vec::with_capacity(num_contents as usize);
for _ in 0..num_contents { for _ in 0..num_contents {
let content_id = buf.read_u32::<BigEndian>()?; let content_id = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?;
let index = buf.read_u16::<BigEndian>()?; let index = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let content_type = buf.read_u16::<BigEndian>()?; let content_type = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let content_size = buf.read_u64::<BigEndian>()?; let content_size = buf.read_u64::<BigEndian>().map_err(TMDError::IOError)?;
let mut content_hash = [0u8; 20]; let mut content_hash = [0u8; 20];
buf.read_exact(&mut content_hash)?; buf.read_exact(&mut content_hash).map_err(TMDError::IOError)?;
content_records.push(ContentRecord { content_records.push(ContentRecord {
content_id, content_id,
index, index,
@ -167,4 +188,37 @@ impl TMD {
} }
Ok(buf) Ok(buf)
} }
pub fn is_fakesigned(&self) -> bool {
// Can't be fakesigned without a null signature.
if self.signature != [0; 256] {
return false;
}
// Test the hash of the TMD body to make sure it starts with 00.
let mut hasher = Sha1::new();
let tmd_body = self.to_bytes().unwrap();
hasher.update(&tmd_body[320..]);
let result = hasher.finalize();
if result[0] != 0 {
return false;
}
true
}
pub fn fakesign(&mut self) -> Result<(), TMDError> {
// Erase the signature.
self.signature = [0; 256];
let mut current_int: u16 = 0;
let mut test_hash: [u8; 20] = [255; 20];
while test_hash[0] != 0 {
if current_int == 255 { return Err(TMDError::CannotFakesign); }
current_int += 1;
self.minor_version = current_int;
let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap();
hasher.update(&ticket_body[320..]);
test_hash = <[u8; 20]>::from(hasher.finalize());
}
Ok(())
}
} }

View File

@ -9,10 +9,14 @@ use std::str;
use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::title::{tmd, ticket, content}; use crate::title::{tmd, ticket, content};
use crate::title::ticket::TicketError;
use crate::title::tmd::TMDError;
#[derive(Debug)] #[derive(Debug)]
pub enum WADError { pub enum WADError {
BadType, BadType,
TMDError(TMDError),
TicketError(TicketError),
IOError(std::io::Error), IOError(std::io::Error),
} }
@ -20,6 +24,8 @@ impl fmt::Display for WADError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self { let description = match *self {
WADError::BadType => "An invalid WAD type was specified.", WADError::BadType => "An invalid WAD type was specified.",
WADError::TMDError(_) => "An error occurred while loading TMD data.",
WADError::TicketError(_) => "An error occurred while loading Ticket data.",
WADError::IOError(_) => "The provided WAD data was invalid.", WADError::IOError(_) => "The provided WAD data was invalid.",
}; };
f.write_str(description) f.write_str(description)
@ -68,7 +74,7 @@ impl WADHeader {
pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> { pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> {
// Generates a new WADHeader from a populated WADBody object. // Generates a new WADHeader from a populated WADBody object.
// Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD. // Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD.
let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::IOError)?; let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::TMDError)?;
let wad_type = match hex::encode(tmd.title_id).as_str() { let wad_type = match hex::encode(tmd.title_id).as_str() {
"0000000100000001" => WADType::ImportBoot, "0000000100000001" => WADType::ImportBoot,
_ => WADType::Installable, _ => WADType::Installable,