Added fakesigning and fakesigning detection, command is in CLI

This commit is contained in:
2025-03-25 18:28:37 -04:00
parent 1bcc004af7
commit 839e33b911
11 changed files with 369 additions and 51 deletions

View File

@@ -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");
}

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.
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<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() {
@@ -39,7 +47,10 @@ fn main() {
},
&None => { /* This is handled by clap */}
}
},
Some(Commands::Fakesign { input, output }) => {
fakesign::fakesign(input, output)
}
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
// https://github.com/NinjaCheetah/rustii
pub mod fakesign;
pub mod wad;