mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2025-06-05 23:11:02 -04:00
Ported wad convert command from WiiPy
This commit is contained in:
parent
97fe838b8c
commit
3fd701cac6
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -72,6 +72,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@ -502,6 +508,7 @@ name = "rustii"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"cbc",
|
||||
"clap",
|
||||
|
@ -33,3 +33,4 @@ sha1 = { version = "0", features = ["oid"]}
|
||||
glob = "0"
|
||||
regex = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
|
@ -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") {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
} 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!();
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -14,6 +14,7 @@ use crate::title::crypto::decrypt_title_key;
|
||||
pub enum TicketError {
|
||||
UnsupportedVersion,
|
||||
CannotFakesign,
|
||||
IssuerTooLong,
|
||||
IOError(std::io::Error),
|
||||
}
|
||||
|
||||
@ -22,6 +23,7 @@ impl fmt::Display for TicketError {
|
||||
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::IssuerTooLong => "Signature issuer length must not exceed 64 characers.",
|
||||
TicketError::IOError(_) => "The provided Ticket data was invalid.",
|
||||
};
|
||||
f.write_str(description)
|
||||
@ -232,4 +234,15 @@ impl Ticket {
|
||||
pub fn signature_issuer(&self) -> String {
|
||||
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
||||
}
|
||||
|
||||
/// Sets a new name for the certificate used to sign a Ticket.
|
||||
pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TicketError> {
|
||||
if signature_issuer.len() > 64 {
|
||||
return Err(TicketError::IssuerTooLong);
|
||||
}
|
||||
let mut issuer = signature_issuer.into_bytes();
|
||||
issuer.resize(64, 0);
|
||||
self.signature_issuer = issuer.try_into().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use sha1::{Sha1, Digest};
|
||||
#[derive(Debug)]
|
||||
pub enum TMDError {
|
||||
CannotFakesign,
|
||||
IssuerTooLong,
|
||||
InvalidContentType(u16),
|
||||
IOError(std::io::Error),
|
||||
}
|
||||
@ -21,6 +22,7 @@ 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::IssuerTooLong => "Signature issuer length must not exceed 64 characters.",
|
||||
TMDError::InvalidContentType(_) => "The TMD contains content with an invalid type.",
|
||||
TMDError::IOError(_) => "The provided TMD data was invalid.",
|
||||
};
|
||||
@ -350,4 +352,20 @@ impl TMD {
|
||||
pub fn signature_issuer(&self) -> String {
|
||||
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
||||
}
|
||||
|
||||
/// Sets a new name for the certificate used to sign a TMD.
|
||||
pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TMDError> {
|
||||
if signature_issuer.len() > 64 {
|
||||
return Err(TMDError::IssuerTooLong);
|
||||
}
|
||||
let mut issuer = signature_issuer.into_bytes();
|
||||
issuer.resize(64, 0);
|
||||
self.signature_issuer = issuer.try_into().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets whether this TMD describes a vWii title or not.
|
||||
pub fn is_vwii(&self) -> bool {
|
||||
self.is_vwii == 1
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user