From 839e33b9118e1a910ca683d09c6467ca1d599f61 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 25 Mar 2025 18:28:37 -0400 Subject: [PATCH] Added fakesigning and fakesigning detection, command is in CLI --- Cargo.lock | 45 +++++++++++++ Cargo.toml | 1 + src/bin/playground/main.rs | 38 ++++++----- src/bin/rustii/filetypes.rs | 87 ++++++++++++++++++++++++ src/bin/rustii/main.rs | 17 ++++- src/bin/rustii/title/fakesign.rs | 60 +++++++++++++++++ src/bin/rustii/title/mod.rs | 1 + src/title/mod.rs | 15 +++++ src/title/ticket.rs | 36 ++++++++++ src/title/tmd.rs | 112 +++++++++++++++++++++++-------- src/title/wad.rs | 8 ++- 11 files changed, 369 insertions(+), 51 deletions(-) create mode 100644 src/bin/rustii/filetypes.rs create mode 100644 src/bin/rustii/title/fakesign.rs diff --git a/Cargo.lock b/Cargo.lock index f4079f3..7005340 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,15 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.18" @@ -237,6 +246,12 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "once_cell" version = "1.21.1" @@ -261,6 +276,35 @@ dependencies = [ "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]] name = "rustii" version = "0.1.0" @@ -271,6 +315,7 @@ dependencies = [ "clap", "glob", "hex", + "regex", "sha1", ] diff --git a/Cargo.toml b/Cargo.toml index a185eff..c6e1ee5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,5 @@ aes = "0" hex = "0" sha1 = "0" glob = "0" +regex = "1" clap = { version = "4", features = ["derive"] } diff --git a/src/bin/playground/main.rs b/src/bin/playground/main.rs index b853fa8..9ff8162 100644 --- a/src/bin/playground/main.rs +++ b/src/bin/playground/main.rs @@ -1,41 +1,43 @@ // Sample file for testing rustii library stuff. use std::fs; -use rustii::title::{tmd, ticket, content, crypto, wad}; +use rustii::title::{content, crypto, wad}; use rustii::title; fn main() { 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)); let wad = wad::WAD::from_bytes(&data).unwrap(); println!("size of tmd: {:?}", wad.tmd().len()); - let tmd = tmd::TMD::from_bytes(&wad.tmd()).unwrap(); - println!("num content records: {:?}", tmd.content_records.len()); - println!("first record data: {:?}", tmd.content_records.first().unwrap()); - assert_eq!(wad.tmd(), tmd.to_bytes().unwrap()); + println!("num content records: {:?}", title.tmd.content_records.len()); + println!("first record data: {:?}", title.tmd.content_records.first().unwrap()); + if !title.tmd.is_fakesigned() { + 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: {:?}", tik.title_version); - println!("title key (enc): {:?}", tik.title_key); - println!("title key (dec): {:?}", tik.dec_title_key()); - assert_eq!(wad.ticket(), tik.to_bytes().unwrap()); + println!("title version from ticket is: {:?}", title.ticket.title_version); + println!("title key (enc): {:?}", title.ticket.title_key); + println!("title key (dec): {:?}", title.ticket.dec_title_key()); + if !title.ticket.is_fakesigned() { + 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()); 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); 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!("wad header: {:?}", wad.header); - - let repacked = wad.to_bytes().unwrap(); - assert_eq!(repacked, data); - println!("wad packed OK"); } diff --git a/src/bin/rustii/filetypes.rs b/src/bin/rustii/filetypes.rs new file mode 100644 index 0000000..4da2b69 --- /dev/null +++ b/src/bin/rustii/filetypes.rs @@ -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 { + 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); + } +} diff --git a/src/bin/rustii/main.rs b/src/bin/rustii/main.rs index 4a39bec..fbce101 100644 --- a/src/bin/rustii/main.rs +++ b/src/bin/rustii/main.rs @@ -4,9 +4,10 @@ // Base for the rustii CLI that handles argument parsing and directs execution to the proper module. mod title; -use clap::{Subcommand, Parser}; -use title::wad; +mod filetypes; +use clap::{Subcommand, Parser}; +use title::{wad, fakesign}; #[derive(Parser)] #[command(version, about, long_about = None)] @@ -23,6 +24,13 @@ enum Commands { #[command(subcommand)] command: Option, }, + /// Fakesign a TMD, Ticket, or WAD (trucha bug) + Fakesign { + /// Fakesign a TMD, Ticket, or WAD (trucha bug) + input: String, + #[arg(short, long)] + output: Option, + } } fn main() { @@ -39,7 +47,10 @@ fn main() { }, &None => { /* This is handled by clap */} } + }, + Some(Commands::Fakesign { input, output }) => { + fakesign::fakesign(input, output) } None => {} } -} \ No newline at end of file +} diff --git a/src/bin/rustii/title/fakesign.rs b/src/bin/rustii/title/fakesign.rs new file mode 100644 index 0000000..2bef80f --- /dev/null +++ b/src/bin/rustii/title/fakesign.rs @@ -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) { + 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!"); + } + } +} diff --git a/src/bin/rustii/title/mod.rs b/src/bin/rustii/title/mod.rs index 99a2536..0650d30 100644 --- a/src/bin/rustii/title/mod.rs +++ b/src/bin/rustii/title/mod.rs @@ -1,4 +1,5 @@ // title/mod.rs from rustii (c) 2025 NinjaCheetah & Contributors // https://github.com/NinjaCheetah/rustii +pub mod fakesign; pub mod wad; diff --git a/src/title/mod.rs b/src/title/mod.rs index 22464dd..00e446a 100644 --- a/src/title/mod.rs +++ b/src/title/mod.rs @@ -20,6 +20,8 @@ pub enum TitleError { BadTMD, BadContent, InvalidWAD, + TMDError(tmd::TMDError), + TicketError(ticket::TicketError), WADError(wad::WADError), IOError(std::io::Error), } @@ -31,6 +33,8 @@ impl fmt::Display for TitleError { TitleError::BadTMD => "The provided TMD data was invalid.", TitleError::BadContent => "The provided content 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::IOError(_) => "The provided Title data was invalid.", }; @@ -85,6 +89,17 @@ impl 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, content::ContentError> { let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?; Ok(content) diff --git a/src/title/ticket.rs b/src/title/ticket.rs index 1c5387b..10337ee 100644 --- a/src/title/ticket.rs +++ b/src/title/ticket.rs @@ -7,11 +7,13 @@ use std::error::Error; use std::fmt; use std::io::{Cursor, Read, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use sha1::{Sha1, Digest}; use crate::title::crypto::decrypt_title_key; #[derive(Debug)] pub enum TicketError { UnsupportedVersion, + CannotFakesign, IOError(std::io::Error), } @@ -19,6 +21,7 @@ impl fmt::Display for TicketError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let description = match *self { 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.", }; f.write_str(description) @@ -184,4 +187,37 @@ impl Ticket { 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") } + + 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(()) + } } diff --git a/src/title/tmd.rs b/src/title/tmd.rs index a2cbb94..c4da1f0 100644 --- a/src/title/tmd.rs +++ b/src/title/tmd.rs @@ -3,8 +3,29 @@ // // 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 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(Clone)] @@ -46,55 +67,55 @@ pub struct TMD { impl TMD { /// Creates a new TMD instance from the binary data of a TMD file. - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { let mut buf = Cursor::new(data); - let signature_type = buf.read_u32::()?; + let signature_type = buf.read_u32::().map_err(TMDError::IOError)?; 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? 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]; - buf.read_exact(&mut signature_issuer)?; - let tmd_version = buf.read_u8()?; - let ca_crl_version = buf.read_u8()?; - let signer_crl_version = buf.read_u8()?; - let is_vwii = buf.read_u8()?; + buf.read_exact(&mut signature_issuer).map_err(TMDError::IOError)?; + let tmd_version = buf.read_u8().map_err(TMDError::IOError)?; + let ca_crl_version = buf.read_u8().map_err(TMDError::IOError)?; + let signer_crl_version = buf.read_u8().map_err(TMDError::IOError)?; + let is_vwii = buf.read_u8().map_err(TMDError::IOError)?; 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]; - buf.read_exact(&mut title_id)?; + buf.read_exact(&mut title_id).map_err(TMDError::IOError)?; let mut title_type = [0u8; 4]; - buf.read_exact(&mut title_type)?; - let group_id = buf.read_u16::()?; + buf.read_exact(&mut title_type).map_err(TMDError::IOError)?; + let group_id = buf.read_u16::().map_err(TMDError::IOError)?; // Same here... let mut padding2 = [0u8; 2]; - buf.read_exact(&mut padding2)?; - let region = buf.read_u16::()?; + buf.read_exact(&mut padding2).map_err(TMDError::IOError)?; + let region = buf.read_u16::().map_err(TMDError::IOError)?; let mut ratings = [0u8; 16]; - buf.read_exact(&mut ratings)?; + buf.read_exact(&mut ratings).map_err(TMDError::IOError)?; // ...and here... 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]; - buf.read_exact(&mut ipc_mask)?; + buf.read_exact(&mut ipc_mask).map_err(TMDError::IOError)?; // ...and here. let mut reserved2 = [0u8; 18]; - buf.read_exact(&mut reserved2)?; - let access_rights = buf.read_u32::()?; - let title_version = buf.read_u16::()?; - let num_contents = buf.read_u16::()?; - let boot_index = buf.read_u16::()?; - let minor_version = buf.read_u16::()?; + buf.read_exact(&mut reserved2).map_err(TMDError::IOError)?; + let access_rights = buf.read_u32::().map_err(TMDError::IOError)?; + let title_version = buf.read_u16::().map_err(TMDError::IOError)?; + let num_contents = buf.read_u16::().map_err(TMDError::IOError)?; + let boot_index = buf.read_u16::().map_err(TMDError::IOError)?; + let minor_version = buf.read_u16::().map_err(TMDError::IOError)?; // 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); for _ in 0..num_contents { - let content_id = buf.read_u32::()?; - let index = buf.read_u16::()?; - let content_type = buf.read_u16::()?; - let content_size = buf.read_u64::()?; + let content_id = buf.read_u32::().map_err(TMDError::IOError)?; + let index = buf.read_u16::().map_err(TMDError::IOError)?; + let content_type = buf.read_u16::().map_err(TMDError::IOError)?; + let content_size = buf.read_u64::().map_err(TMDError::IOError)?; 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_id, index, @@ -167,4 +188,37 @@ impl TMD { } 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(()) + } } diff --git a/src/title/wad.rs b/src/title/wad.rs index cf89b66..c3a576b 100644 --- a/src/title/wad.rs +++ b/src/title/wad.rs @@ -9,10 +9,14 @@ use std::str; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use crate::title::{tmd, ticket, content}; +use crate::title::ticket::TicketError; +use crate::title::tmd::TMDError; #[derive(Debug)] pub enum WADError { BadType, + TMDError(TMDError), + TicketError(TicketError), IOError(std::io::Error), } @@ -20,6 +24,8 @@ impl fmt::Display for WADError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let description = match *self { 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.", }; f.write_str(description) @@ -68,7 +74,7 @@ impl WADHeader { pub fn from_body(body: &WADBody) -> Result { // 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. - 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() { "0000000100000001" => WADType::ImportBoot, _ => WADType::Installable,