Added basic doc strings to all major structs and functions
Some checks failed
Build rustii / build-linux-x86_64 (push) Has been cancelled
Build rustii / build-macos-arm64 (push) Has been cancelled
Build rustii / build-macos-x86_64 (push) Has been cancelled
Build rustii / build-windows-x86_64 (push) Has been cancelled

This commit is contained in:
Campbell 2025-03-28 14:02:03 -04:00
parent edf3af0f7c
commit e147a953a5
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
12 changed files with 91 additions and 19 deletions

6
src/archive/mod.rs Normal file
View File

@ -0,0 +1,6 @@
// archive/mod.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii
//
// Root for all archive-related modules.
pub mod u8;

5
src/archive/u8.rs Normal file
View File

@ -0,0 +1,5 @@
// archive/u8.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii
//
// Implements the structures and methods required for parsing U8 archives.

View File

@ -1,5 +1,7 @@
// lib.rs from rustii (c) 2025 NinjaCheetah & Contributors // lib.rs from rustii (c) 2025 NinjaCheetah & Contributors
// https://github.com/NinjaCheetah/rustii // https://github.com/NinjaCheetah/rustii
//
// Root level module that imports the feature modules.
pub mod archive;
pub mod title; pub mod title;

View File

@ -49,6 +49,7 @@ pub enum CertificateKeyType {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// A structure that represents the components of a Wii signing certificate.
pub struct Certificate { pub struct Certificate {
signer_key_type: CertificateKeyType, signer_key_type: CertificateKeyType,
signature: Vec<u8>, signature: Vec<u8>,
@ -123,7 +124,7 @@ impl Certificate {
}) })
} }
/// Dumps the data in a Certificate back into binary data that can be written to a file. /// Dumps the data in a Certificate instance back into binary data that can be written to a file.
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> { pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
match self.signer_key_type { match self.signer_key_type {
@ -154,24 +155,29 @@ impl Certificate {
Ok(buf) Ok(buf)
} }
/// Gets the name of the certificate used to sign a certificate as a string.
pub fn signature_issuer(&self) -> String { pub fn signature_issuer(&self) -> String {
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned() String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
} }
/// Gets the name of a certificate's child certificate as a string.
pub fn child_cert_identity(&self) -> String { pub fn child_cert_identity(&self) -> String {
String::from_utf8_lossy(&self.child_cert_identity).trim_end_matches('\0').to_owned() String::from_utf8_lossy(&self.child_cert_identity).trim_end_matches('\0').to_owned()
} }
/// Gets the modulus of the public key contained in a certificate.
pub fn pub_key_modulus(&self) -> Vec<u8> { pub fn pub_key_modulus(&self) -> Vec<u8> {
self.pub_key_modulus.clone() self.pub_key_modulus.clone()
} }
/// Gets the exponent of the public key contained in a certificate.
pub fn pub_key_exponent(&self) -> u32 { pub fn pub_key_exponent(&self) -> u32 {
self.pub_key_exponent self.pub_key_exponent
} }
} }
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents the components of the Wii's signing certificate chain.
pub struct CertificateChain { pub struct CertificateChain {
ca_cert: Certificate, ca_cert: Certificate,
tmd_cert: Certificate, tmd_cert: Certificate,
@ -179,6 +185,9 @@ pub struct CertificateChain {
} }
impl CertificateChain { impl CertificateChain {
/// Creates a new CertificateChain instance from the binary data of an entire certificate chain.
/// This chain must contain a CA certificate, a TMD certificate, and a Ticket certificate or
/// else this method will return an error.
pub fn from_bytes(data: &[u8]) -> Result<CertificateChain, CertificateError> { pub fn from_bytes(data: &[u8]) -> Result<CertificateChain, CertificateError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let mut offset: u64 = 0; let mut offset: u64 = 0;
@ -238,6 +247,10 @@ impl CertificateChain {
}) })
} }
/// Creates a new CertificateChain instance from three separate Certificate instances each
/// containing one of the three certificates stored in the chain. You must provide a CA
/// certificate, a TMD certificate, and a Ticket certificate, or this method will return an
/// error.
pub fn from_certs(ca_cert: Certificate, tmd_cert: Certificate, ticket_cert: Certificate) -> Result<Self, CertificateError> { pub fn from_certs(ca_cert: Certificate, tmd_cert: Certificate, ticket_cert: Certificate) -> Result<Self, CertificateError> {
if String::from_utf8_lossy(&ca_cert.signature_issuer).trim_end_matches('\0').ne("Root") { if String::from_utf8_lossy(&ca_cert.signature_issuer).trim_end_matches('\0').ne("Root") {
return Err(CertificateError::IncorrectCertificate("CA".to_owned())); return Err(CertificateError::IncorrectCertificate("CA".to_owned()));
@ -255,6 +268,7 @@ impl CertificateChain {
}) })
} }
/// Dumps the entire CertificateChain back into binary data that can be written to a file.
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> { pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
buf.write_all(&self.ca_cert().to_bytes()?)?; buf.write_all(&self.ca_cert().to_bytes()?)?;
@ -316,8 +330,7 @@ pub fn verify_child_cert(ca_cert: &Certificate, child_cert: &Certificate) -> Res
return Err(CertificateError::NonMatchingCertificates) return Err(CertificateError::NonMatchingCertificates)
} }
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let cert_body = child_cert.to_bytes().unwrap(); hasher.update(&child_cert.to_bytes().map_err(CertificateError::IOError)?[320..]);
hasher.update(&cert_body[320..]);
let cert_hash = hasher.finalize().as_slice().to_owned(); let cert_hash = hasher.finalize().as_slice().to_owned();
let public_key_modulus = BigUint::from_bytes_be(&ca_cert.pub_key_modulus()); let public_key_modulus = BigUint::from_bytes_be(&ca_cert.pub_key_modulus());
let public_key_exponent = BigUint::from(ca_cert.pub_key_exponent()); let public_key_exponent = BigUint::from(ca_cert.pub_key_exponent());
@ -339,8 +352,7 @@ pub fn verify_tmd(tmd_cert: &Certificate, tmd: &tmd::TMD) -> Result<bool, Certif
return Err(CertificateError::NonMatchingCertificates) return Err(CertificateError::NonMatchingCertificates)
} }
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let tmd_body = tmd.to_bytes().unwrap(); hasher.update(&tmd.to_bytes().map_err(CertificateError::IOError)?[320..]);
hasher.update(&tmd_body[320..]);
let tmd_hash = hasher.finalize().as_slice().to_owned(); let tmd_hash = hasher.finalize().as_slice().to_owned();
let public_key_modulus = BigUint::from_bytes_be(&tmd_cert.pub_key_modulus()); let public_key_modulus = BigUint::from_bytes_be(&tmd_cert.pub_key_modulus());
let public_key_exponent = BigUint::from(tmd_cert.pub_key_exponent()); let public_key_exponent = BigUint::from(tmd_cert.pub_key_exponent());
@ -362,15 +374,13 @@ pub fn verify_ticket(ticket_cert: &Certificate, ticket: &ticket::Ticket) -> Resu
return Err(CertificateError::NonMatchingCertificates) return Err(CertificateError::NonMatchingCertificates)
} }
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let tmd_body = ticket.to_bytes().unwrap(); hasher.update(&ticket.to_bytes().map_err(CertificateError::IOError)?[320..]);
hasher.update(&tmd_body[320..]); let ticket_hash = hasher.finalize().as_slice().to_owned();
let tmd_hash = hasher.finalize().as_slice().to_owned();
let public_key_modulus = BigUint::from_bytes_be(&ticket_cert.pub_key_modulus()); let public_key_modulus = BigUint::from_bytes_be(&ticket_cert.pub_key_modulus());
let public_key_exponent = BigUint::from(ticket_cert.pub_key_exponent()); let public_key_exponent = BigUint::from(ticket_cert.pub_key_exponent());
let root_key = RsaPublicKey::new(public_key_modulus, public_key_exponent).unwrap(); let root_key = RsaPublicKey::new(public_key_modulus, public_key_exponent).unwrap();
match root_key.verify(Pkcs1v15Sign::new::<Sha1>(), &tmd_hash, ticket.signature.as_slice()) { match root_key.verify(Pkcs1v15Sign::new::<Sha1>(), &ticket_hash, ticket.signature.as_slice()) {
Ok(_) => Ok(true), Ok(_) => Ok(true),
Err(_) => Ok(false), Err(_) => Ok(false),
} }
} }

View File

@ -6,6 +6,8 @@ const KOREAN_KEY: &str = "63b82bb4f4614e2e13f2fefbba4c9b7e";
const VWII_KEY: &str = "30bfc76e7c19afbb23163330ced7c28d"; const VWII_KEY: &str = "30bfc76e7c19afbb23163330ced7c28d";
const DEV_COMMON_KEY: &str = "a1604a6a7123b529ae8bec32c816fcaa"; const DEV_COMMON_KEY: &str = "a1604a6a7123b529ae8bec32c816fcaa";
/// Returns the common key for the specified index. Providing Some(true) for the optional argument
/// is_dev will make index 0 return the development common key instead of the retail common key.
pub fn get_common_key(index: u8, is_dev: Option<bool>) -> [u8; 16] { pub fn get_common_key(index: u8, is_dev: Option<bool>) -> [u8; 16] {
// Match the Korean and vWii keys, and if they don't match then fall back on the common key. // Match the Korean and vWii keys, and if they don't match then fall back on the common key.
// The is_dev argument is an option, and if it's set to false or None, then the regular // The is_dev argument is an option, and if it's set to false or None, then the regular

View File

@ -31,6 +31,7 @@ impl fmt::Display for ContentError {
impl Error for ContentError {} impl Error for ContentError {}
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents the block of data containing the content of a digital Wii title.
pub struct ContentRegion { pub struct ContentRegion {
pub content_records: Vec<ContentRecord>, pub content_records: Vec<ContentRecord>,
pub content_region_size: u32, pub content_region_size: u32,
@ -97,6 +98,7 @@ impl ContentRegion {
}) })
} }
/// Dumps the entire ContentRegion back into binary data that can be written to a file.
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> { pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
for i in 0..self.num_contents { for i in 0..self.num_contents {
@ -108,11 +110,13 @@ impl ContentRegion {
Ok(buf) Ok(buf)
} }
/// Gets the encrypted content file from the ContentRegion at the specified index.
pub fn get_enc_content_by_index(&self, index: usize) -> Result<Vec<u8>, ContentError> { pub fn get_enc_content_by_index(&self, index: usize) -> Result<Vec<u8>, ContentError> {
let content = self.contents.get(index).ok_or(ContentError::IndexNotFound)?; let content = self.contents.get(index).ok_or(ContentError::IndexNotFound)?;
Ok(content.clone()) Ok(content.clone())
} }
/// Gets the decrypted content file from the ContentRegion at the specified index.
pub fn get_content_by_index(&self, index: usize, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> { pub fn get_content_by_index(&self, index: usize, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> {
let content = self.get_enc_content_by_index(index)?; let content = self.get_enc_content_by_index(index)?;
// Verify the hash of the decrypted content against its record. // Verify the hash of the decrypted content against its record.
@ -127,6 +131,7 @@ impl ContentRegion {
Ok(content_dec) Ok(content_dec)
} }
/// Gets the encrypted content file from the ContentRegion with the specified Content ID.
pub fn get_enc_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, ContentError> { pub fn get_enc_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, ContentError> {
let index = self.content_records.iter().position(|x| x.content_id == cid); let index = self.content_records.iter().position(|x| x.content_id == cid);
if let Some(index) = index { if let Some(index) = index {
@ -137,6 +142,7 @@ impl ContentRegion {
} }
} }
/// Gets the decrypted content file from the ContentRegion with the specified Content ID.
pub fn get_content_by_cid(&self, cid: u32, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> { pub fn get_content_by_cid(&self, cid: u32, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> {
let index = self.content_records.iter().position(|x| x.content_id == cid); let index = self.content_records.iter().position(|x| x.content_id == cid);
if let Some(index) = index { if let Some(index) = index {
@ -147,6 +153,9 @@ impl ContentRegion {
} }
} }
/// Loads existing content into the specified index of a ContentRegion instance. This content
/// must be decrypted and needs to match the size and hash listed in the content record at that
/// index.
pub fn load_content(&mut self, content: &[u8], index: usize, title_key: [u8; 16]) -> Result<(), ContentError> { pub fn load_content(&mut self, content: &[u8], index: usize, title_key: [u8; 16]) -> Result<(), ContentError> {
if index >= self.content_records.len() { if index >= self.content_records.len() {
return Err(ContentError::IndexNotFound); return Err(ContentError::IndexNotFound);

View File

@ -14,7 +14,7 @@ fn title_id_to_iv(title_id: [u8; 8]) -> [u8; 16] {
iv.as_slice().try_into().unwrap() iv.as_slice().try_into().unwrap()
} }
// Decrypt a Title Key using the specified common key. /// Decrypts a Title Key using the specified common key and the corresponding Title ID.
pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] { pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
let iv = title_id_to_iv(title_id); let iv = title_id_to_iv(title_id);
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>; type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
@ -24,7 +24,7 @@ pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id
title_key title_key
} }
// Encrypt a Title Key using the specified common key. /// Encrypts a Title Key using the specified common key and the corresponding Title ID.
pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] { pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
let iv = title_id_to_iv(title_id); let iv = title_id_to_iv(title_id);
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>; type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
@ -34,7 +34,7 @@ pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id
title_key title_key
} }
// Decrypt content using a Title Key. /// Decrypt content using the corresponding Title Key and content index.
pub fn decrypt_content(data: &[u8], title_key: [u8; 16], index: u16) -> Vec<u8> { pub fn decrypt_content(data: &[u8], title_key: [u8; 16], index: u16) -> Vec<u8> {
let mut iv = Vec::from(index.to_be_bytes()); let mut iv = Vec::from(index.to_be_bytes());
iv.resize(16, 0); iv.resize(16, 0);
@ -45,7 +45,7 @@ pub fn decrypt_content(data: &[u8], title_key: [u8; 16], index: u16) -> Vec<u8>
buf buf
} }
// Encrypt content using a Title Key. /// Encrypt content using the corresponding Title Key and content index.
pub fn encrypt_content(data: &[u8], title_key: [u8; 16], index: u16, size: u64) -> Vec<u8> { pub fn encrypt_content(data: &[u8], title_key: [u8; 16], index: u16, size: u64) -> Vec<u8> {
let mut iv = Vec::from(index.to_be_bytes()); let mut iv = Vec::from(index.to_be_bytes());
iv.resize(16, 0); iv.resize(16, 0);

View File

@ -50,6 +50,7 @@ impl fmt::Display for TitleError {
impl Error for TitleError {} impl Error for TitleError {}
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents the components of a digital Wii title.
pub struct Title { pub struct Title {
pub cert_chain: cert::CertificateChain, pub cert_chain: cert::CertificateChain,
crl: Vec<u8>, crl: Vec<u8>,
@ -60,6 +61,7 @@ pub struct Title {
} }
impl Title { impl Title {
/// Creates a new Title instance from an existing WAD instance.
pub fn from_wad(wad: &wad::WAD) -> Result<Title, TitleError> { pub fn from_wad(wad: &wad::WAD) -> Result<Title, TitleError> {
let cert_chain = cert::CertificateChain::from_bytes(&wad.cert_chain()).map_err(|_| TitleError::BadCertChain)?; let cert_chain = cert::CertificateChain::from_bytes(&wad.cert_chain()).map_err(|_| TitleError::BadCertChain)?;
let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(|_| TitleError::BadTicket)?; let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(|_| TitleError::BadTicket)?;
@ -76,6 +78,7 @@ impl Title {
Ok(title) Ok(title)
} }
/// Converts a Title instance into a WAD, which can be used to export the Title back to a file.
pub fn to_wad(&self) -> Result<wad::WAD, TitleError> { pub fn to_wad(&self) -> Result<wad::WAD, TitleError> {
// Create a new WAD from the data in the Title. // Create a new WAD from the data in the Title.
let wad = wad::WAD::from_parts( let wad = wad::WAD::from_parts(
@ -89,16 +92,19 @@ impl Title {
Ok(wad) Ok(wad)
} }
/// Creates a new Title instance from the binary data of a WAD file.
pub fn from_bytes(bytes: &[u8]) -> Result<Title, TitleError> { pub fn from_bytes(bytes: &[u8]) -> Result<Title, TitleError> {
let wad = wad::WAD::from_bytes(bytes).map_err(|_| TitleError::InvalidWAD)?; let wad = wad::WAD::from_bytes(bytes).map_err(|_| TitleError::InvalidWAD)?;
let title = Title::from_wad(&wad)?; let title = Title::from_wad(&wad)?;
Ok(title) Ok(title)
} }
/// Gets whether the TMD and Ticket of a Title are both fakesigned.
pub fn is_fakesigned(&self) -> bool { pub fn is_fakesigned(&self) -> bool {
self.tmd.is_fakesigned() && self.ticket.is_fakesigned() self.tmd.is_fakesigned() && self.ticket.is_fakesigned()
} }
/// Fakesigns the TMD and Ticket of a Title.
pub fn fakesign(&mut self) -> Result<(), TitleError> { pub fn fakesign(&mut self) -> Result<(), TitleError> {
// Run the fakesign methods on the TMD and Ticket. // Run the fakesign methods on the TMD and Ticket.
self.tmd.fakesign().map_err(TitleError::TMDError)?; self.tmd.fakesign().map_err(TitleError::TMDError)?;
@ -106,11 +112,13 @@ impl Title {
Ok(()) Ok(())
} }
/// Gets the decrypted content file from the Title at the specified index.
pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> { pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> {
let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?; let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?;
Ok(content) Ok(content)
} }
/// Gets the decrypted content file from the Title with the specified Content ID.
pub fn get_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, content::ContentError> { pub fn get_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, content::ContentError> {
let content = self.content.get_content_by_cid(cid, self.ticket.dec_title_key())?; let content = self.content.get_content_by_cid(cid, self.ticket.dec_title_key())?;
Ok(content) Ok(content)

View File

@ -41,6 +41,7 @@ pub struct TitleLimit {
} }
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents a Wii Ticket file.
pub struct Ticket { pub struct Ticket {
pub signature_type: u32, pub signature_type: u32,
pub signature: [u8; 256], pub signature: [u8; 256],
@ -67,6 +68,7 @@ pub struct Ticket {
} }
impl Ticket { impl Ticket {
/// Creates a new Ticket instance from the binary data of a Ticket file.
pub fn from_bytes(data: &[u8]) -> Result<Self, TicketError> { pub fn from_bytes(data: &[u8]) -> Result<Self, TicketError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let signature_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?; let signature_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?;
@ -145,6 +147,7 @@ impl Ticket {
}) })
} }
/// Dumps the data in a Ticket instance back into binary data that can be written to a file.
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> { pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
buf.write_u32::<BigEndian>(self.signature_type)?; buf.write_u32::<BigEndian>(self.signature_type)?;
@ -176,18 +179,21 @@ impl Ticket {
Ok(buf) Ok(buf)
} }
/// Gets the decrypted version of the Title Key stored in a Ticket.
pub fn dec_title_key(&self) -> [u8; 16] { pub fn dec_title_key(&self) -> [u8; 16] {
// Get the dev status of this Ticket so decrypt_title_key knows the right common key. // Get the dev status of this Ticket so decrypt_title_key knows the right common key.
let is_dev = self.is_dev(); let is_dev = self.is_dev();
decrypt_title_key(self.title_key, self.common_key_index, self.title_id, Some(is_dev)) decrypt_title_key(self.title_key, self.common_key_index, self.title_id, Some(is_dev))
} }
/// Gets whether a Ticket was signed for development (true) or retail (false).
pub fn is_dev(&self) -> bool { pub fn is_dev(&self) -> bool {
// Parse the signature issuer to determine if this is a dev Ticket or not. // Parse the signature issuer to determine if this is a dev Ticket or not.
let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default(); 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") issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006")
} }
/// Gets whether a Ticket is fakesigned using the strncmp (trucha) bug or not.
pub fn is_fakesigned(&self) -> bool { pub fn is_fakesigned(&self) -> bool {
// Can't be fakesigned without a null signature. // Can't be fakesigned without a null signature.
if self.signature != [0; 256] { if self.signature != [0; 256] {
@ -204,6 +210,7 @@ impl Ticket {
true true
} }
/// Fakesigns a Ticket for use with the strncmp (trucha) bug.
pub fn fakesign(&mut self) -> Result<(), TicketError> { pub fn fakesign(&mut self) -> Result<(), TicketError> {
// Erase the signature. // Erase the signature.
self.signature = [0; 256]; self.signature = [0; 256];
@ -221,6 +228,7 @@ impl Ticket {
Ok(()) Ok(())
} }
/// Gets the name of the certificate used to sign a Ticket as a string.
pub fn signature_issuer(&self) -> String { pub fn signature_issuer(&self) -> String {
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned() String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
} }

View File

@ -83,6 +83,7 @@ pub enum AccessRight {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// A structure that represents the metadata of a content file in a digital Wii title.
pub struct ContentRecord { pub struct ContentRecord {
pub content_id: u32, pub content_id: u32,
pub index: u16, pub index: u16,
@ -92,6 +93,7 @@ pub struct ContentRecord {
} }
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents a Wii TMD (Title Metadata) file.
pub struct TMD { pub struct TMD {
pub signature_type: u32, pub signature_type: u32,
pub signature: [u8; 256], pub signature: [u8; 256],
@ -257,6 +259,7 @@ impl TMD {
Ok(buf) Ok(buf)
} }
/// Gets whether a TMD is fakesigned using the strncmp (trucha) bug or not.
pub fn is_fakesigned(&self) -> bool { pub fn is_fakesigned(&self) -> bool {
// Can't be fakesigned without a null signature. // Can't be fakesigned without a null signature.
if self.signature != [0; 256] { if self.signature != [0; 256] {
@ -273,6 +276,7 @@ impl TMD {
true true
} }
/// Fakesigns a TMD for use with the strncmp (trucha) bug.
pub fn fakesign(&mut self) -> Result<(), TMDError> { pub fn fakesign(&mut self) -> Result<(), TMDError> {
// Erase the signature. // Erase the signature.
self.signature = [0; 256]; self.signature = [0; 256];
@ -290,6 +294,7 @@ impl TMD {
Ok(()) Ok(())
} }
/// Gets the 3-letter code of the region a TMD was created for.
pub fn region(&self) -> &str { pub fn region(&self) -> &str {
match self.region { match self.region {
0 => "JPN", 0 => "JPN",
@ -301,6 +306,7 @@ impl TMD {
} }
} }
/// Gets the type of title described by a TMD.
pub fn title_type(&self) -> TitleType { pub fn title_type(&self) -> TitleType {
match hex::encode(self.title_id)[..8].to_string().as_str() { match hex::encode(self.title_id)[..8].to_string().as_str() {
"00000001" => TitleType::System, "00000001" => TitleType::System,
@ -314,6 +320,7 @@ impl TMD {
} }
} }
/// Gets the type of content described by a content record in a TMD.
pub fn content_type(&self, index: usize) -> ContentType { pub fn content_type(&self, index: usize) -> ContentType {
// Find possible content indices, because the provided one could exist while the indices // Find possible content indices, because the provided one could exist while the indices
// are out of order, which could cause problems finding the content. // are out of order, which could cause problems finding the content.
@ -331,6 +338,7 @@ impl TMD {
} }
} }
/// Gets whether a specified access right is enabled in a TMD.
pub fn check_access_right(&self, right: AccessRight) -> bool { pub fn check_access_right(&self, right: AccessRight) -> bool {
match right { match right {
AccessRight::AHB => (self.access_rights & (1 << 0)) != 0, AccessRight::AHB => (self.access_rights & (1 << 0)) != 0,
@ -338,6 +346,7 @@ impl TMD {
} }
} }
/// Gets the name of the certificate used to sign a TMD as a string.
pub fn signature_issuer(&self) -> String { pub fn signature_issuer(&self) -> String {
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned() String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
} }

View File

@ -69,6 +69,10 @@ fn wii_menu_versions_map(vwii: Option<bool>) -> HashMap<u16, String> {
menu_versions menu_versions
} }
/// Converts the decimal version of a title (vXXX) into a more standard format for applicable
/// titles. For the Wii Menu, this uses the optional vwii argument and a hash table to determine
/// the user-friendly version number, as there is no way to directly derive it from the decimal
/// format.
pub fn dec_to_standard(version: u16, title_id: &str, vwii: Option<bool>) -> Option<String> { pub fn dec_to_standard(version: u16, title_id: &str, vwii: Option<bool>) -> Option<String> {
if title_id == "0000000100000002" { if title_id == "0000000100000002" {
let map = wii_menu_versions_map(vwii); let map = wii_menu_versions_map(vwii);

View File

@ -41,12 +41,14 @@ pub enum WADType {
} }
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents an entire WAD file as a separate header and body.
pub struct WAD { pub struct WAD {
pub header: WADHeader, pub header: WADHeader,
pub body: WADBody, pub body: WADBody,
} }
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents the header of a WAD file.
pub struct WADHeader { pub struct WADHeader {
pub header_size: u32, pub header_size: u32,
pub wad_type: WADType, pub wad_type: WADType,
@ -61,6 +63,7 @@ pub struct WADHeader {
} }
#[derive(Debug)] #[derive(Debug)]
/// A structure that represent the data contained in the body of a WAD file.
pub struct WADBody { pub struct WADBody {
cert_chain: Vec<u8>, cert_chain: Vec<u8>,
crl: Vec<u8>, crl: Vec<u8>,
@ -71,6 +74,7 @@ pub struct WADBody {
} }
impl WADHeader { impl WADHeader {
/// Creates a new WADHeader instance from the binary data of a WAD file's header.
pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> { pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> {
// Generates a new WADHeader from a populated WADBody object. // 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. // Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD.
@ -103,6 +107,7 @@ impl WADHeader {
} }
impl WADBody { impl WADBody {
/// Creates a new WADBody instance from instances of the components stored in a WAD file.
pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD, pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
content: &content::ContentRegion, meta: &[u8]) -> Result<WADBody, WADError> { content: &content::ContentRegion, meta: &[u8]) -> Result<WADBody, WADError> {
let body = WADBody { let body = WADBody {
@ -118,6 +123,7 @@ impl WADBody {
} }
impl WAD { impl WAD {
/// Creates a new WAD instance from the binary data of a WAD file.
pub fn from_bytes(data: &[u8]) -> Result<WAD, WADError> { pub fn from_bytes(data: &[u8]) -> Result<WAD, WADError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let header_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?; let header_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?;
@ -196,6 +202,8 @@ impl WAD {
Ok(wad) Ok(wad)
} }
/// Creates a new WAD instance from instances of the components stored in a WAD file. This
/// first creates a WADBody from the components, then generates a new WADHeader from them.
pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD, pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
content: &content::ContentRegion, meta: &[u8]) -> Result<WAD, WADError> { content: &content::ContentRegion, meta: &[u8]) -> Result<WAD, WADError> {
let body = WADBody::from_parts(cert_chain, crl, ticket, tmd, content, meta)?; let body = WADBody::from_parts(cert_chain, crl, ticket, tmd, content, meta)?;
@ -207,6 +215,7 @@ impl WAD {
Ok(wad) Ok(wad)
} }
/// Dumps the data in a WAD instance back into binary data that can be written to a file.
pub fn to_bytes(&self) -> Result<Vec<u8>, WADError> { pub fn to_bytes(&self) -> Result<Vec<u8>, WADError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
buf.write_u32::<BigEndian>(self.header.header_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.header_size).map_err(WADError::IOError)?;