Ported wad convert command from WiiPy

This commit is contained in:
2025-04-02 19:51:19 -04:00
parent 97fe838b8c
commit 3fd701cac6
7 changed files with 169 additions and 18 deletions

View File

@@ -92,7 +92,13 @@ fn print_tmd_info(tmd: tmd::TMD, cert: Option<cert::Certificate>) {
}
},
},
Err(_) => "Invalid (Modified TMD)"
Err(_) => {
if tmd.is_fakesigned() {
"Fakesigned"
} else {
"Invalid (Modified TMD)"
}
}
};
println!(" Signature: {}", signing_str);
} else {
@@ -120,12 +126,11 @@ fn print_ticket_info(ticket: ticket::Ticket, cert: Option<cert::Certificate>) {
} else {
println!(" Title ID: {}", hex::encode(ticket.title_id).to_uppercase());
}
if hex::encode(ticket.title_id)[..8].eq("00000001") {
if hex::encode(ticket.title_id).eq("0000000100000001") {
println!(" Title Version: {} (boot2v{})", ticket.title_version, ticket.title_version);
} else {
println!(" Title Version: {} ({})", ticket.title_version, versions::dec_to_standard(ticket.title_version, &hex::encode(ticket.title_id), Some(ticket.common_key_index == 2)).unwrap());
}
let converted_ver = versions::dec_to_standard(ticket.title_version, &hex::encode(ticket.title_id), None);
if hex::encode(ticket.title_id).eq("0000000100000001") {
println!(" Title Version: {} (boot2v{})", ticket.title_version, ticket.title_version);
} else if hex::encode(ticket.title_id)[..8].eq("00000001") && converted_ver.is_some() {
println!(" Title Version: {} ({})", ticket.title_version, converted_ver.unwrap());
} else {
println!(" Title Version: {}", ticket.title_version);
}
@@ -167,7 +172,13 @@ fn print_ticket_info(ticket: ticket::Ticket, cert: Option<cert::Certificate>) {
}
},
},
Err(_) => "Invalid (Modified Ticket)"
Err(_) => {
if ticket.is_fakesigned() {
"Fakesigned"
} else {
"Invalid (Modified Ticket)"
}
}
};
println!(" Signature: {}", signing_str);
} else {
@@ -214,7 +225,13 @@ fn print_wad_info(wad: wad::WAD) {
}
},
},
Err(_) => "Illegitimate (Modified TMD + Ticket)"
Err(_) => {
if title.is_fakesigned() {
"Fakesigned"
} else {
"Illegitimate (Modified TMD + Ticket)"
}
}
};
println!(" Signing Status: {}", signing_str);
println!();

View File

@@ -7,6 +7,7 @@ mod title;
mod filetypes;
mod info;
use anyhow::Result;
use clap::{Subcommand, Parser};
use title::{wad, fakesign};
@@ -40,14 +41,14 @@ enum Commands {
}
}
fn main() {
fn main() -> Result<()> {
let cli = Cli::parse();
match &cli.command {
Some(Commands::Wad { command }) => {
match command {
Some(wad::Commands::Convert { input, output }) => {
wad::convert_wad(input, output)
Some(wad::Commands::Convert { input, target, output }) => {
wad::convert_wad(input, target, output)?
},
Some(wad::Commands::Pack { input, output}) => {
wad::pack_wad(input, output)
@@ -66,4 +67,5 @@ fn main() {
}
None => {}
}
Ok(())
}

View File

@@ -3,11 +3,12 @@
//
// Code for WAD-related commands in the rustii CLI.
use std::{str, fs};
use std::{str, fs, fmt};
use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use clap::{Subcommand, Args};
use glob::glob;
use rustii::title::{cert, tmd, ticket, content, wad};
use rustii::title::{cert, crypto, tmd, ticket, content, wad};
use rustii::title;
#[derive(Subcommand)]
@@ -20,6 +21,8 @@ pub enum Commands {
/// An (optional) WAD name; defaults to <input name>_<new type>.wad
#[arg(short, long)]
output: Option<String>,
#[command(flatten)]
target: ConvertTargets,
},
/// Pack a directory into a WAD file
Pack {
@@ -38,13 +41,103 @@ pub enum Commands {
}
#[derive(Args)]
#[clap(next_help_heading = "Encryption Targets")]
#[group(multiple = false, required = true)]
struct ConvertTargets {
pub struct ConvertTargets {
/// Use the retail common key, allowing this WAD to be installed on retail consoles and Dolphin
#[arg(long)]
retail: bool,
/// Use the development common key, allowing this WAD to be installed on development consoles
#[arg(long)]
dev: bool,
/// Use the vWii key, allowing this WAD to theoretically be installed from Wii U mode if a Wii U mode WAD installer is created
#[arg(long)]
vwii: bool,
}
pub fn convert_wad(input: &str, output: &Option<String>) {
todo!();
enum Target {
Retail,
Dev,
Vwii,
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Target::Retail => write!(f, "retail"),
Target::Dev => write!(f, "development"),
Target::Vwii => write!(f, "vWii"),
}
}
}
pub fn convert_wad(input: &str, target: &ConvertTargets, output: &Option<String>) -> Result<()> {
let in_path = Path::new(input);
if !in_path.exists() {
bail!("Source WAD \"{}\" could not be found.", input);
}
// Parse the target passed to identify the encryption target.
let target = if target.dev {
Target::Dev
} else if target.vwii {
Target::Vwii
} else {
Target::Retail
};
// Get the output name now that we know the target, if one wasn't passed.
let out_path = if output.is_some() {
PathBuf::from(output.clone().unwrap()).with_extension("wad")
} else {
match target {
Target::Retail => PathBuf::from(format!("{}_retail", in_path.file_stem().unwrap().to_str().unwrap())).with_extension("wad"),
Target::Dev => PathBuf::from(format!("{}_dev", in_path.file_stem().unwrap().to_str().unwrap())).with_extension("wad"),
Target::Vwii => PathBuf::from(format!("{}_vWii", in_path.file_stem().unwrap().to_str().unwrap())).with_extension("wad"),
}
};
let mut title = title::Title::from_bytes(fs::read(in_path)?.as_slice()).with_context(|| "The provided WAD file could not be loaded, and is likely invalid.")?;
// Bail if the WAD is already using the selected encryption.
if matches!(target, Target::Dev) && title.ticket.is_dev() {
bail!("This is already a development WAD!");
} else if matches!(target, Target::Retail) && !title.ticket.is_dev() && !title.tmd.is_vwii() {
bail!("This is already a retail WAD!");
} else if matches!(target, Target::Vwii) && !title.ticket.is_dev() && title.tmd.is_vwii() {
bail!("This is already a vWii WAD!");
}
// Save the current encryption to display at the end.
let source = if title.ticket.is_dev() {
"development"
} else if title.tmd.is_vwii() {
"vWii"
} else {
"retail"
};
let title_key = title.ticket.dec_title_key();
let title_key_new: [u8; 16];
match target {
Target::Dev => {
title.tmd.set_signature_issuer(String::from("Root-CA00000002-CP00000007"))?;
title.ticket.set_signature_issuer(String::from("Root-CA00000002-XS00000006"))?;
title_key_new = crypto::encrypt_title_key(title_key, 0, title.ticket.title_id, Some(true));
title.ticket.common_key_index = 0;
},
Target::Retail => {
title.tmd.set_signature_issuer(String::from("Root-CA00000001-CP00000004"))?;
title.ticket.set_signature_issuer(String::from("Root-CA00000001-XS00000003"))?;
title_key_new = crypto::encrypt_title_key(title_key, 0, title.ticket.title_id, Some(false));
title.ticket.common_key_index = 0;
},
Target::Vwii => {
title.tmd.set_signature_issuer(String::from("Root-CA00000001-CP00000004"))?;
title.ticket.set_signature_issuer(String::from("Root-CA00000001-XS00000003"))?;
title_key_new = crypto::encrypt_title_key(title_key, 2, title.ticket.title_id, Some(false));
title.ticket.common_key_index = 2;
}
}
title.ticket.title_key = title_key_new;
title.fakesign()?;
fs::write(out_path.clone(), title.to_wad()?.to_bytes()?)?;
println!("Successfully converted {} WAD to {} WAD \"{}\"!", source, target, out_path.file_name().unwrap().to_str().unwrap());
Ok(())
}
pub fn pack_wad(input: &str, output: &str) {