From 8c7cd48dff53a5cf07a0bf1e24acc15dc10ba1bc Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 25 Mar 2025 20:19:59 -0400 Subject: [PATCH] Add WIP info command to rustii CLI, currently has a lot of blanks --- src/bin/rustii/info.rs | 140 +++++++++++++++++++++++++++++++ src/bin/rustii/main.rs | 14 +++- src/bin/rustii/title/fakesign.rs | 4 +- src/title/ticket.rs | 2 +- src/title/tmd.rs | 2 +- src/title/wad.rs | 12 +++ 6 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 src/bin/rustii/info.rs diff --git a/src/bin/rustii/info.rs b/src/bin/rustii/info.rs new file mode 100644 index 0000000..86d4473 --- /dev/null +++ b/src/bin/rustii/info.rs @@ -0,0 +1,140 @@ +// info.rs from rustii (c) 2025 NinjaCheetah & Contributors +// https://github.com/NinjaCheetah/rustii +// +// Code for the info command in the rustii CLI. + +use std::{str, fs}; +use std::path::Path; +use rustii::{title, title::tmd, title::ticket, title::wad}; +use crate::filetypes::{WiiFileType, identify_file_type}; + +fn print_tmd_info(tmd: tmd::TMD) { + // Print all important keys from the TMD. + println!("Title Info"); + println!(" Title ID: {}", hex::encode(tmd.title_id)); + println!(" Title Version: {}", tmd.title_version); + println!(" TMD Version: {}", tmd.tmd_version); + if hex::encode(tmd.ios_tid) == "0000000000000000" { + println!(" Required IOS: N/A"); + } + else if hex::encode(tmd.ios_tid) != "0000000100000001" { + println!(" Required IOS: IOS{} ({})", tmd.ios_tid.last().unwrap(), hex::encode(tmd.ios_tid)); + } + let signature_issuer = String::from_utf8(Vec::from(tmd.signature_issuer)).unwrap_or_default(); + if signature_issuer.contains("CP00000004") { + println!(" Certificate: CP00000004 (Retail)"); + println!(" Certificate Issuer: Root-CA00000001 (Retail)"); + } + else if signature_issuer.contains("CP00000007") { + println!(" Certificate: CP00000007 (Development)"); + println!(" Certificate Issuer: Root-CA00000002 (Development)"); + } + else if signature_issuer.contains("CP00000005") { + println!(" Certificate: CP00000005 (Development/Unknown)"); + println!(" Certificate Issuer: Root-CA00000002 (Development)"); + } + else if signature_issuer.contains("CP10000000") { + println!(" Certificate: CP10000000 (Arcade)"); + println!(" Certificate Issuer: Root-CA10000000 (Arcade)"); + } + else { + println!(" Certificate Info: {} (Unknown)", signature_issuer); + } + println!(" Region: WIP"); + println!(" Title Type: WIP"); + println!(" vWii Title: {}", tmd.is_vwii != 0); + println!(" DVD Video Access: WIP"); + println!(" AHB Access: WIP"); + println!(" Fakesigned: {}", tmd.is_fakesigned()); + println!("\nContent Info"); + println!(" Total Contents: {}", tmd.num_contents); + println!(" Boot Content Index: {}", tmd.boot_index); + println!(" Content Records:"); + for content in tmd.content_records { + println!(" Content Index: {}", content.index); + println!(" Content ID: {:08X}", content.content_id); + println!(" Content Type: WIP"); + println!(" Content Size: {} bytes", content.content_size); + println!(" Content Hash: {}", hex::encode(content.content_hash)); + } +} + +fn print_ticket_info(ticket: ticket::Ticket) { + // Print all important keys from the Ticket. + println!("Ticket Info"); + println!(" Title ID: {}", hex::encode(ticket.title_id)); + println!(" Title Version: {}", ticket.title_version); + println!(" Ticket Version: {}", ticket.ticket_version); + let signature_issuer = String::from_utf8(Vec::from(ticket.signature_issuer)).unwrap_or_default(); + if signature_issuer.contains("XS00000003") { + println!(" Certificate: XS00000003 (Retail)"); + println!(" Certificate Issuer: Root-CA00000001 (Retail)"); + } + else if signature_issuer.contains("XS00000006") { + println!(" Certificate: XS00000006 (Development)"); + println!(" Certificate Issuer: Root-CA00000002 (Development)"); + } + else if signature_issuer.contains("XS00000004") { + println!(" Certificate: XS00000004 (Development/Unknown)"); + println!(" Certificate Issuer: Root-CA00000002 (Development)"); + } + else { + println!(" Certificate Info: {} (Unknown)", signature_issuer); + } + let key = match ticket.common_key_index { + 0 => { + if ticket.is_dev() { "Common (Development)" } + else { "Common (Retail)" } + } + 1 => "Korean", + 2 => "vWii", + _ => "Unknown (Likely Common)" + }; + println!(" Decryption Key: {}", key); + println!(" Title Key (Encrypted): {}", hex::encode(ticket.title_key)); + println!(" Title Key (Decrypted): {}", hex::encode(ticket.dec_title_key())); + println!(" Fakesigned: {}", ticket.is_fakesigned()); +} + +fn print_wad_info(wad: wad::WAD) { + println!("WAD Info"); + match wad.header.wad_type { + wad::WADType::ImportBoot => { println!(" WAD Type: boot2") }, + wad::WADType::Installable => { println!(" WAD Type: Standard Installable") }, + } + println!(" Installed Size: WIP blocks"); + println!(" Installed Size (MB): WIP MB"); + println!(" Has Meta/Footer: {}", wad.meta_size() != 0); + println!(" Has CRL: {}", wad.crl_size() != 0); + // Create a Title for signing info and TMD/Ticket info. + let title = title::Title::from_wad(&wad).unwrap(); + println!(" Fakesigned: {}", title.is_fakesigned()); + println!(); + print_ticket_info(title.ticket); + println!(); + print_tmd_info(title.tmd); +} + +pub fn info(input: &str) { + let in_path = Path::new(input); + if !in_path.exists() { + panic!("Error: Input file does not exist."); + } + match identify_file_type(input) { + Some(WiiFileType::Tmd) => { + let tmd = tmd::TMD::from_bytes(fs::read(in_path).unwrap().as_slice()).unwrap(); + print_tmd_info(tmd); + }, + Some(WiiFileType::Ticket) => { + let ticket = ticket::Ticket::from_bytes(fs::read(in_path).unwrap().as_slice()).unwrap(); + print_ticket_info(ticket); + }, + Some(WiiFileType::Wad) => { + let wad = wad::WAD::from_bytes(fs::read(in_path).unwrap().as_slice()).unwrap(); + print_wad_info(wad); + }, + None => { + println!("Error: Information cannot be displayed for this file."); + } + } +} diff --git a/src/bin/rustii/main.rs b/src/bin/rustii/main.rs index fbce101..eb7a025 100644 --- a/src/bin/rustii/main.rs +++ b/src/bin/rustii/main.rs @@ -5,6 +5,7 @@ mod title; mod filetypes; +mod info; use clap::{Subcommand, Parser}; use title::{wad, fakesign}; @@ -26,10 +27,16 @@ enum Commands { }, /// Fakesign a TMD, Ticket, or WAD (trucha bug) Fakesign { - /// Fakesign a TMD, Ticket, or WAD (trucha bug) + /// The path to a TMD, Ticket, or WAD input: String, + /// An (optional) output name; defaults to overwriting input file if not provided #[arg(short, long)] output: Option, + }, + /// Get information about a TMD, Ticket, or WAD + Info { + /// The path to a TMD, Ticket, or WAD + input: String, } } @@ -45,11 +52,14 @@ fn main() { Some(wad::Commands::Unpack { input, output }) => { wad::unpack_wad(input, output) }, - &None => { /* This is handled by clap */} + &None => { /* This is for me handled by clap */} } }, Some(Commands::Fakesign { input, output }) => { fakesign::fakesign(input, output) + }, + Some(Commands::Info { input }) => { + info::info(input) } None => {} } diff --git a/src/bin/rustii/title/fakesign.rs b/src/bin/rustii/title/fakesign.rs index 2bef80f..36a1ea6 100644 --- a/src/bin/rustii/title/fakesign.rs +++ b/src/bin/rustii/title/fakesign.rs @@ -1,12 +1,12 @@ // title/fakesign.rs from rustii (c) 2025 NinjaCheetah & Contributors // https://github.com/NinjaCheetah/rustii // -// Code for dedicated fakesigning-related commands in the rustii CLI. +// Code for the fakesign command 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}; +use crate::filetypes::{WiiFileType, identify_file_type}; pub fn fakesign(input: &str, output: &Option) { let in_path = Path::new(input); diff --git a/src/title/ticket.rs b/src/title/ticket.rs index 10337ee..e191e37 100644 --- a/src/title/ticket.rs +++ b/src/title/ticket.rs @@ -210,7 +210,7 @@ impl Ticket { 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); } + if current_int == 65535 { return Err(TicketError::CannotFakesign); } current_int += 1; self.unknown2 = current_int.to_be_bytes(); let mut hasher = Sha1::new(); diff --git a/src/title/tmd.rs b/src/title/tmd.rs index c4da1f0..2dc76a8 100644 --- a/src/title/tmd.rs +++ b/src/title/tmd.rs @@ -211,7 +211,7 @@ impl TMD { 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); } + if current_int == 65535 { return Err(TMDError::CannotFakesign); } current_int += 1; self.minor_version = current_int; let mut hasher = Sha1::new(); diff --git a/src/title/wad.rs b/src/title/wad.rs index c3a576b..f2f4dea 100644 --- a/src/title/wad.rs +++ b/src/title/wad.rs @@ -238,6 +238,8 @@ impl WAD { buf.resize((buf.len() + 63) & !63, 0); Ok(buf) } + + pub fn cert_chain_size(&self) -> u32 { self.header.cert_chain_size } pub fn cert_chain(&self) -> Vec { self.body.cert_chain.clone() @@ -247,6 +249,8 @@ impl WAD { self.body.cert_chain = cert_chain.to_vec(); self.header.cert_chain_size = cert_chain.len() as u32; } + + pub fn crl_size(&self) -> u32 { self.header.crl_size } pub fn crl(&self) -> Vec { self.body.crl.clone() @@ -256,6 +260,8 @@ impl WAD { self.body.crl = crl.to_vec(); self.header.crl_size = crl.len() as u32; } + + pub fn ticket_size(&self) -> u32 { self.header.ticket_size } pub fn ticket(&self) -> Vec { self.body.ticket.clone() @@ -265,6 +271,8 @@ impl WAD { self.body.ticket = ticket.to_vec(); self.header.ticket_size = ticket.len() as u32; } + + pub fn tmd_size(&self) -> u32 { self.header.tmd_size } pub fn tmd(&self) -> Vec { self.body.tmd.clone() @@ -274,6 +282,8 @@ impl WAD { self.body.tmd = tmd.to_vec(); self.header.tmd_size = tmd.len() as u32; } + + pub fn content_size(&self) -> u32 { self.header.content_size } pub fn content(&self) -> Vec { self.body.content.clone() @@ -283,6 +293,8 @@ impl WAD { self.body.content = content.to_vec(); self.header.content_size = content.len() as u32; } + + pub fn meta_size(&self) -> u32 { self.header.meta_size } pub fn meta(&self) -> Vec { self.body.meta.clone()