Implement thiserror for library errors

This commit is contained in:
Campbell 2025-04-03 19:09:56 -04:00
parent 0bda6dabf3
commit c2169f84c4
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
6 changed files with 207 additions and 276 deletions

View File

@ -3,44 +3,33 @@
// //
// Implements the structures and methods required for validating the signatures of Wii titles. // Implements the structures and methods required for validating the signatures of Wii titles.
use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Write, SeekFrom, Seek}; use std::io::{Cursor, Read, Write, SeekFrom, Seek};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use rsa::pkcs8::DecodePublicKey; use rsa::pkcs8::DecodePublicKey;
use rsa::pkcs1v15::Pkcs1v15Sign; use rsa::pkcs1v15::Pkcs1v15Sign;
use rsa::{RsaPublicKey, BigUint}; use rsa::{RsaPublicKey, BigUint};
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use thiserror::Error;
use crate::title::{tmd, ticket}; use crate::title::{tmd, ticket};
#[derive(Debug)] #[derive(Debug, Error)]
pub enum CertificateError { pub enum CertificateError {
#[error("certificate appears to be signed with invalid key type `{0}`")]
InvalidSignatureKeyType(u32), InvalidSignatureKeyType(u32),
#[error("certificate appears to contain key with invalid type `{0}`")]
InvalidContainedKeyType(u32), InvalidContainedKeyType(u32),
#[error("certificate chain contains an unknown certificate")]
UnknownCertificate, UnknownCertificate,
#[error("certificate chain is missing required certificate `{0}`")]
MissingCertificate(String), MissingCertificate(String),
#[error("attempted to load incorrect certificate `{0}`")]
IncorrectCertificate(String), IncorrectCertificate(String),
#[error("the data you are attempting to verify was not signed with the provided certificate")]
NonMatchingCertificates, NonMatchingCertificates,
IOError(std::io::Error), #[error("certificate data is not in a valid format")]
IO(#[from] std::io::Error),
} }
impl fmt::Display for CertificateError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
CertificateError::InvalidSignatureKeyType(_) => "The key type this certificate appears to be signed with is not valid.",
CertificateError::InvalidContainedKeyType(_) => "The key type contained in this certificate is not valid.",
CertificateError::UnknownCertificate => "An unknown certificate was found in the certificate chain.",
CertificateError::MissingCertificate(_) => "A required certificate was not found in the certificate chain.",
CertificateError::IncorrectCertificate(_) => "A provided certificate did not match the expected type.",
CertificateError::NonMatchingCertificates => "The provided certificate does not match the data you are attempting to verify with it.",
CertificateError::IOError(_) => "The provided certificate data was invalid.",
};
f.write_str(description)
}
}
impl Error for CertificateError {}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CertificateKeyType { pub enum CertificateKeyType {
Rsa4096, Rsa4096,
@ -65,7 +54,7 @@ impl Certificate {
/// Creates a new Certificate instance from the binary data of a certificate file. /// Creates a new Certificate instance from the binary data of a certificate file.
pub fn from_bytes(data: &[u8]) -> Result<Self, CertificateError> { pub fn from_bytes(data: &[u8]) -> Result<Self, CertificateError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let signer_key_type_int = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; let signer_key_type_int = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
let signer_key_type = match signer_key_type_int { let signer_key_type = match signer_key_type_int {
0x00010000 => CertificateKeyType::Rsa4096, 0x00010000 => CertificateKeyType::Rsa4096,
0x00010001 => CertificateKeyType::Rsa2048, 0x00010001 => CertificateKeyType::Rsa2048,
@ -78,12 +67,12 @@ impl Certificate {
CertificateKeyType::ECC => 60, CertificateKeyType::ECC => 60,
}; };
let mut signature = vec![0u8; signature_len]; let mut signature = vec![0u8; signature_len];
buf.read_exact(&mut signature).map_err(CertificateError::IOError)?; buf.read_exact(&mut signature).map_err(CertificateError::IO)?;
// Skip past padding at the end of the signature. // Skip past padding at the end of the signature.
buf.seek(SeekFrom::Start(0x40 + signature_len as u64)).map_err(CertificateError::IOError)?; buf.seek(SeekFrom::Start(0x40 + signature_len as u64)).map_err(CertificateError::IO)?;
let mut signature_issuer = [0u8; 64]; let mut signature_issuer = [0u8; 64];
buf.read_exact(&mut signature_issuer).map_err(CertificateError::IOError)?; buf.read_exact(&mut signature_issuer).map_err(CertificateError::IO)?;
let pub_key_type_int = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; let pub_key_type_int = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
let pub_key_type = match pub_key_type_int { let pub_key_type = match pub_key_type_int {
0x00000000 => CertificateKeyType::Rsa4096, 0x00000000 => CertificateKeyType::Rsa4096,
0x00000001 => CertificateKeyType::Rsa2048, 0x00000001 => CertificateKeyType::Rsa2048,
@ -91,25 +80,25 @@ impl Certificate {
_ => return Err(CertificateError::InvalidContainedKeyType(pub_key_type_int)) _ => return Err(CertificateError::InvalidContainedKeyType(pub_key_type_int))
}; };
let mut child_cert_identity = [0u8; 64]; let mut child_cert_identity = [0u8; 64];
buf.read_exact(&mut child_cert_identity).map_err(CertificateError::IOError)?; buf.read_exact(&mut child_cert_identity).map_err(CertificateError::IO)?;
let pub_key_id = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; let pub_key_id = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
let mut pub_key_modulus: Vec<u8>; let mut pub_key_modulus: Vec<u8>;
let mut pub_key_exponent: u32 = 0; let mut pub_key_exponent: u32 = 0;
// The key size and exponent are different based on the key type. ECC has no exponent. // The key size and exponent are different based on the key type. ECC has no exponent.
match pub_key_type { match pub_key_type {
CertificateKeyType::Rsa4096 => { CertificateKeyType::Rsa4096 => {
pub_key_modulus = vec![0u8; 512]; pub_key_modulus = vec![0u8; 512];
buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IOError)?; buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IO)?;
pub_key_exponent = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; pub_key_exponent = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
}, },
CertificateKeyType::Rsa2048 => { CertificateKeyType::Rsa2048 => {
pub_key_modulus = vec![0u8; 256]; pub_key_modulus = vec![0u8; 256];
buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IOError)?; buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IO)?;
pub_key_exponent = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; pub_key_exponent = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
}, },
CertificateKeyType::ECC => { CertificateKeyType::ECC => {
pub_key_modulus = vec![0u8; 60]; pub_key_modulus = vec![0u8; 60];
buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IOError)?; buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IO)?;
} }
} }
Ok(Certificate { Ok(Certificate {
@ -196,16 +185,16 @@ impl CertificateChain {
let mut ticket_cert: Option<Certificate> = None; let mut ticket_cert: Option<Certificate> = None;
// Iterate 3 times, because the chain should contain 3 certs. // Iterate 3 times, because the chain should contain 3 certs.
for _ in 0..3 { for _ in 0..3 {
buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IOError)?; buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IO)?;
let signer_key_type = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; let signer_key_type = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
let signature_len = match signer_key_type { let signature_len = match signer_key_type {
0x00010000 => 512, // 0x200 0x00010000 => 512, // 0x200
0x00010001 => 256, // 0x100 0x00010001 => 256, // 0x100
0x00010002 => 60, 0x00010002 => 60,
_ => return Err(CertificateError::InvalidSignatureKeyType(signer_key_type)) _ => return Err(CertificateError::InvalidSignatureKeyType(signer_key_type))
}; };
buf.seek(SeekFrom::Start(offset + 0x80 + signature_len)).map_err(CertificateError::IOError)?; buf.seek(SeekFrom::Start(offset + 0x80 + signature_len)).map_err(CertificateError::IO)?;
let pub_key_type = buf.read_u32::<BigEndian>().map_err(CertificateError::IOError)?; let pub_key_type = buf.read_u32::<BigEndian>().map_err(CertificateError::IO)?;
let pub_key_len = match pub_key_type { let pub_key_len = match pub_key_type {
0x00000000 => 568, // 0x238 0x00000000 => 568, // 0x238
0x00000001 => 312, // 0x138 0x00000001 => 312, // 0x138
@ -215,10 +204,10 @@ impl CertificateChain {
// Cert size is the base length (0xC8) + the signature length + the public key length. // Cert size is the base length (0xC8) + the signature length + the public key length.
// Like a lot of values, it needs to be rounded to the nearest multiple of 64. // Like a lot of values, it needs to be rounded to the nearest multiple of 64.
let cert_size = (0xC8 + signature_len + pub_key_len + 63) & !63; let cert_size = (0xC8 + signature_len + pub_key_len + 63) & !63;
buf.seek(SeekFrom::End(0)).map_err(CertificateError::IOError)?; buf.seek(SeekFrom::End(0)).map_err(CertificateError::IO)?;
buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IOError)?; buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IO)?;
let mut cert_buf = vec![0u8; cert_size as usize]; let mut cert_buf = vec![0u8; cert_size as usize];
buf.read_exact(&mut cert_buf).map_err(CertificateError::IOError)?; buf.read_exact(&mut cert_buf).map_err(CertificateError::IO)?;
let cert = Certificate::from_bytes(&cert_buf)?; let cert = Certificate::from_bytes(&cert_buf)?;
let issuer_name = String::from_utf8_lossy(&cert.signature_issuer).trim_end_matches('\0').to_owned(); let issuer_name = String::from_utf8_lossy(&cert.signature_issuer).trim_end_matches('\0').to_owned();
if issuer_name.eq("Root") { if issuer_name.eq("Root") {
@ -310,7 +299,7 @@ pub fn verify_ca_cert(ca_cert: &Certificate) -> Result<bool, CertificateError> {
return Err(CertificateError::UnknownCertificate); return Err(CertificateError::UnknownCertificate);
}; };
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let cert_body = ca_cert.to_bytes().unwrap(); let cert_body = ca_cert.to_bytes()?;
hasher.update(&cert_body[576..]); hasher.update(&cert_body[576..]);
let cert_hash = hasher.finalize().as_slice().to_owned(); let cert_hash = hasher.finalize().as_slice().to_owned();
match root_key.verify(Pkcs1v15Sign::new::<Sha1>(), &cert_hash, ca_cert.signature.as_slice()) { match root_key.verify(Pkcs1v15Sign::new::<Sha1>(), &cert_hash, ca_cert.signature.as_slice()) {
@ -330,7 +319,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();
hasher.update(&child_cert.to_bytes().map_err(CertificateError::IOError)?[320..]); hasher.update(&child_cert.to_bytes().map_err(CertificateError::IO)?[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());
@ -352,7 +341,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();
hasher.update(&tmd.to_bytes().map_err(CertificateError::IOError)?[320..]); hasher.update(&tmd.to_bytes().map_err(CertificateError::IO)?[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());
@ -374,7 +363,7 @@ 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();
hasher.update(&ticket.to_bytes().map_err(CertificateError::IOError)?[320..]); hasher.update(&ticket.to_bytes().map_err(CertificateError::IO)?[320..]);
let ticket_hash = hasher.finalize().as_slice().to_owned(); let ticket_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());

View File

@ -3,33 +3,24 @@
// //
// Implements content parsing and editing. // Implements content parsing and editing.
use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use sha1::{Sha1, Digest}; use sha1::{Sha1, Digest};
use thiserror::Error;
use crate::title::tmd::ContentRecord; use crate::title::tmd::ContentRecord;
use crate::title::crypto; use crate::title::crypto;
#[derive(Debug)] #[derive(Debug, Error)]
pub enum ContentError { pub enum ContentError {
IndexNotFound, #[error("requested index {index} is out of range (must not exceed {max})")]
CIDNotFound, IndexOutOfRange { index: usize, max: usize },
BadHash, #[error("content with requested Content ID {0} could not be found")]
CIDNotFound(u32),
#[error("content's hash did not match the expected value (was {hash}, expected {expected})")]
BadHash { hash: String, expected: String },
#[error("content data is not in a valid format")]
IO(#[from] std::io::Error),
} }
impl fmt::Display for ContentError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
ContentError::IndexNotFound => "The specified content index does not exist.",
ContentError::CIDNotFound => "The specified Content ID does not exist.",
ContentError::BadHash => "The content hash does not match the expected hash.",
};
f.write_str(description)
}
}
impl Error for ContentError {}
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents the block of data containing the content of a digital Wii title. /// A structure that represents the block of data containing the content of a digital Wii title.
pub struct ContentRegion { pub struct ContentRegion {
@ -43,7 +34,7 @@ pub struct ContentRegion {
impl ContentRegion { impl ContentRegion {
/// Creates a ContentRegion instance that can be used to parse and edit content stored in a /// Creates a ContentRegion instance that can be used to parse and edit content stored in a
/// digital Wii title from the content area of a WAD and the ContentRecords from a TMD. /// digital Wii title from the content area of a WAD and the ContentRecords from a TMD.
pub fn from_bytes(data: &[u8], content_records: Vec<ContentRecord>) -> Result<Self, std::io::Error> { pub fn from_bytes(data: &[u8], content_records: Vec<ContentRecord>) -> Result<Self, ContentError> {
let content_region_size = data.len() as u32; let content_region_size = data.len() as u32;
let num_contents = content_records.len() as u16; let num_contents = content_records.len() as u16;
// Calculate the starting offsets of each content. // Calculate the starting offsets of each content.
@ -112,7 +103,7 @@ impl ContentRegion {
/// Gets the encrypted content file from the ContentRegion at the specified index. /// 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::IndexOutOfRange { index, max: self.content_records.len() - 1 })?;
Ok(content.clone()) Ok(content.clone())
} }
@ -126,7 +117,7 @@ impl ContentRegion {
hasher.update(content_dec.clone()); hasher.update(content_dec.clone());
let result = hasher.finalize(); let result = hasher.finalize();
if result[..] != self.content_records[index].content_hash { if result[..] != self.content_records[index].content_hash {
return Err(ContentError::BadHash); return Err(ContentError::BadHash { hash: hex::encode(result), expected: hex::encode(self.content_records[index].content_hash) });
} }
Ok(content_dec) Ok(content_dec)
} }
@ -135,10 +126,10 @@ impl ContentRegion {
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 {
let content = self.get_enc_content_by_index(index).map_err(|_| ContentError::CIDNotFound)?; let content = self.get_enc_content_by_index(index).map_err(|_| ContentError::CIDNotFound(cid))?;
Ok(content) Ok(content)
} else { } else {
Err(ContentError::CIDNotFound) Err(ContentError::CIDNotFound(cid))
} }
} }
@ -149,7 +140,7 @@ impl ContentRegion {
let content_dec = self.get_content_by_index(index, title_key)?; let content_dec = self.get_content_by_index(index, title_key)?;
Ok(content_dec) Ok(content_dec)
} else { } else {
Err(ContentError::CIDNotFound) Err(ContentError::CIDNotFound(cid))
} }
} }
@ -158,7 +149,7 @@ impl ContentRegion {
/// index. /// 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::IndexOutOfRange { index, max: self.content_records.len() - 1 });
} }
// Hash the content we're trying to load to ensure it matches the hash expected in the // Hash the content we're trying to load to ensure it matches the hash expected in the
// matching record. // matching record.
@ -166,7 +157,7 @@ impl ContentRegion {
hasher.update(content); hasher.update(content);
let result = hasher.finalize(); let result = hasher.finalize();
if result[..] != self.content_records[index].content_hash { if result[..] != self.content_records[index].content_hash {
return Err(ContentError::BadHash); return Err(ContentError::BadHash { hash: hex::encode(result), expected: hex::encode(self.content_records[index].content_hash) });
} }
let content_enc = crypto::encrypt_content(content, title_key, self.content_records[index].index, self.content_records[index].content_size); let content_enc = crypto::encrypt_content(content, title_key, self.content_records[index].index, self.content_records[index].content_size);
self.contents[index] = content_enc; self.contents[index] = content_enc;

View File

@ -12,43 +12,28 @@ pub mod tmd;
pub mod versions; pub mod versions;
pub mod wad; pub mod wad;
use std::error::Error; use thiserror::Error;
use std::fmt;
#[derive(Debug)] #[derive(Debug, Error)]
pub enum TitleError { pub enum TitleError {
BadCertChain, #[error("the data for required Title component `{0}` was invalid")]
BadTicket, InvalidData(String),
BadTMD, #[error("WAD data is not in a valid format")]
BadContent,
InvalidWAD, InvalidWAD,
CertificateError(cert::CertificateError), #[error("certificate processing error")]
TMDError(tmd::TMDError), CertificateError(#[from] cert::CertificateError),
TicketError(ticket::TicketError), #[error("TMD processing error")]
WADError(wad::WADError), TMD(#[from] tmd::TMDError),
IOError(std::io::Error), #[error("Ticket processing error")]
Ticket(#[from] ticket::TicketError),
#[error("content processing error")]
Content(#[from] content::ContentError),
#[error("WAD processing error")]
WAD(#[from] wad::WADError),
#[error("WAD data is not in a valid format")]
IO(#[from] std::io::Error),
} }
impl fmt::Display for TitleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
TitleError::BadCertChain => "The provided certificate chain data was invalid.",
TitleError::BadTicket => "The provided Ticket data was invalid.",
TitleError::BadTMD => "The provided TMD data was invalid.",
TitleError::BadContent => "The provided content data was invalid.",
TitleError::InvalidWAD => "The provided WAD data was invalid.",
TitleError::CertificateError(_) => "An error occurred while processing certificate data.",
TitleError::TMDError(_) => "An error occurred while processing TMD data.",
TitleError::TicketError(_) => "An error occurred while processing ticket data.",
TitleError::WADError(_) => "A WAD could not be built from the provided data.",
TitleError::IOError(_) => "The provided Title data was invalid.",
};
f.write_str(description)
}
}
impl Error for TitleError {}
#[derive(Debug)] #[derive(Debug)]
/// A structure that represents the components of a digital Wii title. /// A structure that represents the components of a digital Wii title.
pub struct Title { pub struct Title {
@ -63,10 +48,10 @@ pub struct Title {
impl Title { impl Title {
/// Creates a new Title instance from an existing WAD instance. /// 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::CertificateError)?;
let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(|_| TitleError::BadTicket)?; let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(TitleError::Ticket)?;
let tmd = tmd::TMD::from_bytes(&wad.tmd()).map_err(|_| TitleError::BadTMD)?; let tmd = tmd::TMD::from_bytes(&wad.tmd()).map_err(TitleError::TMD)?;
let content = content::ContentRegion::from_bytes(&wad.content(), tmd.content_records.clone()).map_err(|_| TitleError::BadContent)?; let content = content::ContentRegion::from_bytes(&wad.content(), tmd.content_records.clone()).map_err(TitleError::Content)?;
let title = Title { let title = Title {
cert_chain, cert_chain,
crl: wad.crl(), crl: wad.crl(),
@ -88,7 +73,7 @@ impl Title {
&self.tmd, &self.tmd,
&self.content, &self.content,
&self.meta &self.meta
).map_err(TitleError::WADError)?; ).map_err(TitleError::WAD)?;
Ok(wad) Ok(wad)
} }
@ -107,8 +92,8 @@ impl Title {
/// Fakesigns the TMD and Ticket of a Title. /// 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::TMD)?;
self.ticket.fakesign().map_err(TitleError::TicketError)?; self.ticket.fakesign().map_err(TitleError::Ticket)?;
Ok(()) Ok(())
} }
@ -130,8 +115,8 @@ impl Title {
let mut title_size: usize = 0; let mut title_size: usize = 0;
// Get the TMD and Ticket size by dumping them and measuring their length for the most // Get the TMD and Ticket size by dumping them and measuring their length for the most
// accurate results. // accurate results.
title_size += self.tmd.to_bytes().map_err(|x| TitleError::TMDError(tmd::TMDError::IOError(x)))?.len(); title_size += self.tmd.to_bytes().map_err(|x| TitleError::TMD(tmd::TMDError::IO(x)))?.len();
title_size += self.ticket.to_bytes().map_err(|x| TitleError::TicketError(ticket::TicketError::IOError(x)))?.len(); title_size += self.ticket.to_bytes().map_err(|x| TitleError::Ticket(ticket::TicketError::IO(x)))?.len();
for record in &self.tmd.content_records { for record in &self.tmd.content_records {
if matches!(record.content_type, tmd::ContentType::Shared) { if matches!(record.content_type, tmd::ContentType::Shared) {
if absolute == Some(true) { if absolute == Some(true) {

View File

@ -3,38 +3,25 @@
// //
// Implements the structures and methods required for Ticket parsing and editing. // Implements the structures and methods required for Ticket parsing and editing.
use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Sha1, Digest}; use sha1::{Sha1, Digest};
use thiserror::Error;
use crate::title::crypto::decrypt_title_key; use crate::title::crypto::decrypt_title_key;
#[derive(Debug)] #[derive(Debug, Error)]
pub enum TicketError { pub enum TicketError {
UnsupportedVersion, #[error("Ticket is version `{0}` but only v0 is supported")]
UnsupportedVersion(u8),
#[error("Ticket data could not be fakesigned")]
CannotFakesign, CannotFakesign,
IssuerTooLong, #[error("signature issuer string must not exceed 64 characters (was {0})")]
IOError(std::io::Error), IssuerTooLong(usize),
#[error("Ticket data is not in a valid format")]
IO(#[from] std::io::Error),
} }
impl fmt::Display for TicketError { #[derive(Debug, Copy, Clone)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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)
}
}
impl Error for TicketError {}
#[derive(Debug)]
#[derive(Copy)]
#[derive(Clone)]
pub struct TitleLimit { pub struct TitleLimit {
// The type of limit being applied (time, launch count, etc.) // The type of limit being applied (time, launch count, etc.)
pub limit_type: u32, pub limit_type: u32,
@ -73,53 +60,53 @@ impl Ticket {
/// Creates a new Ticket instance from the binary data of a Ticket file. /// 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::IO)?;
let mut signature = [0u8; 256]; let mut signature = [0u8; 256];
buf.read_exact(&mut signature).map_err(TicketError::IOError)?; buf.read_exact(&mut signature).map_err(TicketError::IO)?;
// Maybe this can be read differently? // Maybe this can be read differently?
let mut padding1 = [0u8; 60]; let mut padding1 = [0u8; 60];
buf.read_exact(&mut padding1).map_err(TicketError::IOError)?; buf.read_exact(&mut padding1).map_err(TicketError::IO)?;
let mut signature_issuer = [0u8; 64]; let mut signature_issuer = [0u8; 64];
buf.read_exact(&mut signature_issuer).map_err(TicketError::IOError)?; buf.read_exact(&mut signature_issuer).map_err(TicketError::IO)?;
let mut ecdh_data = [0u8; 60]; let mut ecdh_data = [0u8; 60];
buf.read_exact(&mut ecdh_data).map_err(TicketError::IOError)?; buf.read_exact(&mut ecdh_data).map_err(TicketError::IO)?;
let ticket_version = buf.read_u8().map_err(TicketError::IOError)?; let ticket_version = buf.read_u8().map_err(TicketError::IO)?;
// v1 Tickets are NOT supported (just like in libWiiPy). // v1 Tickets are NOT supported (just like in libWiiPy).
if ticket_version != 0 { if ticket_version != 0 {
return Err(TicketError::UnsupportedVersion); return Err(TicketError::UnsupportedVersion(ticket_version));
} }
let mut reserved1 = [0u8; 2]; let mut reserved1 = [0u8; 2];
buf.read_exact(&mut reserved1).map_err(TicketError::IOError)?; buf.read_exact(&mut reserved1).map_err(TicketError::IO)?;
let mut title_key = [0u8; 16]; let mut title_key = [0u8; 16];
buf.read_exact(&mut title_key).map_err(TicketError::IOError)?; buf.read_exact(&mut title_key).map_err(TicketError::IO)?;
let mut unknown1 = [0u8; 1]; let mut unknown1 = [0u8; 1];
buf.read_exact(&mut unknown1).map_err(TicketError::IOError)?; buf.read_exact(&mut unknown1).map_err(TicketError::IO)?;
let mut ticket_id = [0u8; 8]; let mut ticket_id = [0u8; 8];
buf.read_exact(&mut ticket_id).map_err(TicketError::IOError)?; buf.read_exact(&mut ticket_id).map_err(TicketError::IO)?;
let mut console_id = [0u8; 4]; let mut console_id = [0u8; 4];
buf.read_exact(&mut console_id).map_err(TicketError::IOError)?; buf.read_exact(&mut console_id).map_err(TicketError::IO)?;
let mut title_id = [0u8; 8]; let mut title_id = [0u8; 8];
buf.read_exact(&mut title_id).map_err(TicketError::IOError)?; buf.read_exact(&mut title_id).map_err(TicketError::IO)?;
let mut unknown2 = [0u8; 2]; let mut unknown2 = [0u8; 2];
buf.read_exact(&mut unknown2).map_err(TicketError::IOError)?; buf.read_exact(&mut unknown2).map_err(TicketError::IO)?;
let title_version = buf.read_u16::<BigEndian>().map_err(TicketError::IOError)?; let title_version = buf.read_u16::<BigEndian>().map_err(TicketError::IO)?;
let mut permitted_titles_mask = [0u8; 4]; let mut permitted_titles_mask = [0u8; 4];
buf.read_exact(&mut permitted_titles_mask).map_err(TicketError::IOError)?; buf.read_exact(&mut permitted_titles_mask).map_err(TicketError::IO)?;
let mut permit_mask = [0u8; 4]; let mut permit_mask = [0u8; 4];
buf.read_exact(&mut permit_mask).map_err(TicketError::IOError)?; buf.read_exact(&mut permit_mask).map_err(TicketError::IO)?;
let title_export_allowed = buf.read_u8().map_err(TicketError::IOError)?; let title_export_allowed = buf.read_u8().map_err(TicketError::IO)?;
let common_key_index = buf.read_u8().map_err(TicketError::IOError)?; let common_key_index = buf.read_u8().map_err(TicketError::IO)?;
let mut unknown3 = [0u8; 48]; let mut unknown3 = [0u8; 48];
buf.read_exact(&mut unknown3).map_err(TicketError::IOError)?; buf.read_exact(&mut unknown3).map_err(TicketError::IO)?;
let mut content_access_permission = [0u8; 64]; let mut content_access_permission = [0u8; 64];
buf.read_exact(&mut content_access_permission).map_err(TicketError::IOError)?; buf.read_exact(&mut content_access_permission).map_err(TicketError::IO)?;
let mut padding2 = [0u8; 2]; let mut padding2 = [0u8; 2];
buf.read_exact(&mut padding2).map_err(TicketError::IOError)?; buf.read_exact(&mut padding2).map_err(TicketError::IO)?;
// Build the array of title limits. // Build the array of title limits.
let mut title_limits: Vec<TitleLimit> = Vec::new(); let mut title_limits: Vec<TitleLimit> = Vec::new();
for _ in 0..8 { for _ in 0..8 {
let limit_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?; let limit_type = buf.read_u32::<BigEndian>().map_err(TicketError::IO)?;
let limit_max = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?; let limit_max = buf.read_u32::<BigEndian>().map_err(TicketError::IO)?;
title_limits.push(TitleLimit { limit_type, limit_max }); title_limits.push(TitleLimit { limit_type, limit_max });
} }
let title_limits = title_limits.try_into().unwrap(); let title_limits = title_limits.try_into().unwrap();
@ -223,7 +210,7 @@ impl Ticket {
current_int += 1; current_int += 1;
self.unknown2 = current_int.to_be_bytes(); self.unknown2 = current_int.to_be_bytes();
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap(); let ticket_body = self.to_bytes()?;
hasher.update(&ticket_body[320..]); hasher.update(&ticket_body[320..]);
test_hash = <[u8; 20]>::from(hasher.finalize()); test_hash = <[u8; 20]>::from(hasher.finalize());
} }
@ -238,7 +225,7 @@ impl Ticket {
/// Sets a new name for the certificate used to sign a Ticket. /// Sets a new name for the certificate used to sign a Ticket.
pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TicketError> { pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TicketError> {
if signature_issuer.len() > 64 { if signature_issuer.len() > 64 {
return Err(TicketError::IssuerTooLong); return Err(TicketError::IssuerTooLong(signature_issuer.len()));
} }
let mut issuer = signature_issuer.into_bytes(); let mut issuer = signature_issuer.into_bytes();
issuer.resize(64, 0); issuer.resize(64, 0);

View File

@ -3,35 +3,25 @@
// //
// Implements the structures and methods required for TMD parsing and editing. // Implements the structures and methods required for TMD parsing and editing.
use std::error::Error;
use std::fmt; use std::fmt;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use std::ops::Index; use std::ops::Index;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Sha1, Digest}; use sha1::{Sha1, Digest};
use thiserror::Error;
#[derive(Debug)] #[derive(Debug, Error)]
pub enum TMDError { pub enum TMDError {
#[error("TMD data could not be fakesigned")]
CannotFakesign, CannotFakesign,
IssuerTooLong, #[error("signature issuer string must not exceed 64 characters (was {0})")]
IssuerTooLong(usize),
#[error("TMD data contains content record with invalid type `{0}`")]
InvalidContentType(u16), InvalidContentType(u16),
IOError(std::io::Error), #[error("TMD data is not in a valid format")]
IO(#[from] std::io::Error),
} }
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.",
};
f.write_str(description)
}
}
impl Error for TMDError {}
pub enum TitleType { pub enum TitleType {
System, System,
Game, Game,
@ -127,50 +117,50 @@ impl TMD {
/// Creates a new TMD instance from the binary data of a TMD file. /// Creates a new TMD instance from the binary data of a TMD file.
pub fn from_bytes(data: &[u8]) -> Result<Self, TMDError> { pub fn from_bytes(data: &[u8]) -> Result<Self, TMDError> {
let mut buf = Cursor::new(data); let mut buf = Cursor::new(data);
let signature_type = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?; let signature_type = buf.read_u32::<BigEndian>().map_err(TMDError::IO)?;
let mut signature = [0u8; 256]; let mut signature = [0u8; 256];
buf.read_exact(&mut signature).map_err(TMDError::IOError)?; buf.read_exact(&mut signature).map_err(TMDError::IO)?;
// Maybe this can be read differently? // Maybe this can be read differently?
let mut padding1 = [0u8; 60]; let mut padding1 = [0u8; 60];
buf.read_exact(&mut padding1).map_err(TMDError::IOError)?; buf.read_exact(&mut padding1).map_err(TMDError::IO)?;
let mut signature_issuer = [0u8; 64]; let mut signature_issuer = [0u8; 64];
buf.read_exact(&mut signature_issuer).map_err(TMDError::IOError)?; buf.read_exact(&mut signature_issuer).map_err(TMDError::IO)?;
let tmd_version = buf.read_u8().map_err(TMDError::IOError)?; let tmd_version = buf.read_u8().map_err(TMDError::IO)?;
let ca_crl_version = buf.read_u8().map_err(TMDError::IOError)?; let ca_crl_version = buf.read_u8().map_err(TMDError::IO)?;
let signer_crl_version = buf.read_u8().map_err(TMDError::IOError)?; let signer_crl_version = buf.read_u8().map_err(TMDError::IO)?;
let is_vwii = buf.read_u8().map_err(TMDError::IOError)?; let is_vwii = buf.read_u8().map_err(TMDError::IO)?;
let mut ios_tid = [0u8; 8]; let mut ios_tid = [0u8; 8];
buf.read_exact(&mut ios_tid).map_err(TMDError::IOError)?; buf.read_exact(&mut ios_tid).map_err(TMDError::IO)?;
let mut title_id = [0u8; 8]; let mut title_id = [0u8; 8];
buf.read_exact(&mut title_id).map_err(TMDError::IOError)?; buf.read_exact(&mut title_id).map_err(TMDError::IO)?;
let mut title_type = [0u8; 4]; let mut title_type = [0u8; 4];
buf.read_exact(&mut title_type).map_err(TMDError::IOError)?; buf.read_exact(&mut title_type).map_err(TMDError::IO)?;
let group_id = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let group_id = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
// Same here... // Same here...
let mut padding2 = [0u8; 2]; let mut padding2 = [0u8; 2];
buf.read_exact(&mut padding2).map_err(TMDError::IOError)?; buf.read_exact(&mut padding2).map_err(TMDError::IO)?;
let region = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let region = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
let mut ratings = [0u8; 16]; let mut ratings = [0u8; 16];
buf.read_exact(&mut ratings).map_err(TMDError::IOError)?; buf.read_exact(&mut ratings).map_err(TMDError::IO)?;
// ...and here... // ...and here...
let mut reserved1 = [0u8; 12]; let mut reserved1 = [0u8; 12];
buf.read_exact(&mut reserved1).map_err(TMDError::IOError)?; buf.read_exact(&mut reserved1).map_err(TMDError::IO)?;
let mut ipc_mask = [0u8; 12]; let mut ipc_mask = [0u8; 12];
buf.read_exact(&mut ipc_mask).map_err(TMDError::IOError)?; buf.read_exact(&mut ipc_mask).map_err(TMDError::IO)?;
// ...and here. // ...and here.
let mut reserved2 = [0u8; 18]; let mut reserved2 = [0u8; 18];
buf.read_exact(&mut reserved2).map_err(TMDError::IOError)?; buf.read_exact(&mut reserved2).map_err(TMDError::IO)?;
let access_rights = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?; let access_rights = buf.read_u32::<BigEndian>().map_err(TMDError::IO)?;
let title_version = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let title_version = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
let num_contents = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let num_contents = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
let boot_index = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let boot_index = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
let minor_version = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let minor_version = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
// Build content records by iterating over the rest of the data num_contents times. // Build content records by iterating over the rest of the data num_contents times.
let mut content_records = Vec::with_capacity(num_contents as usize); let mut content_records = Vec::with_capacity(num_contents as usize);
for _ in 0..num_contents { for _ in 0..num_contents {
let content_id = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?; let content_id = buf.read_u32::<BigEndian>().map_err(TMDError::IO)?;
let index = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let index = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
let type_int = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?; let type_int = buf.read_u16::<BigEndian>().map_err(TMDError::IO)?;
let content_type = match type_int { let content_type = match type_int {
1 => ContentType::Normal, 1 => ContentType::Normal,
2 => ContentType::Development, 2 => ContentType::Development,
@ -179,9 +169,9 @@ impl TMD {
32769 => ContentType::Shared, 32769 => ContentType::Shared,
_ => return Err(TMDError::InvalidContentType(type_int)) _ => return Err(TMDError::InvalidContentType(type_int))
}; };
let content_size = buf.read_u64::<BigEndian>().map_err(TMDError::IOError)?; let content_size = buf.read_u64::<BigEndian>().map_err(TMDError::IO)?;
let mut content_hash = [0u8; 20]; let mut content_hash = [0u8; 20];
buf.read_exact(&mut content_hash).map_err(TMDError::IOError)?; buf.read_exact(&mut content_hash).map_err(TMDError::IO)?;
content_records.push(ContentRecord { content_records.push(ContentRecord {
content_id, content_id,
index, index,
@ -289,7 +279,7 @@ impl TMD {
current_int += 1; current_int += 1;
self.minor_version = current_int; self.minor_version = current_int;
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap(); let ticket_body = self.to_bytes()?;
hasher.update(&ticket_body[320..]); hasher.update(&ticket_body[320..]);
test_hash = <[u8; 20]>::from(hasher.finalize()); test_hash = <[u8; 20]>::from(hasher.finalize());
} }
@ -356,7 +346,7 @@ impl TMD {
/// Sets a new name for the certificate used to sign a TMD. /// Sets a new name for the certificate used to sign a TMD.
pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TMDError> { pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TMDError> {
if signature_issuer.len() > 64 { if signature_issuer.len() > 64 {
return Err(TMDError::IssuerTooLong); return Err(TMDError::IssuerTooLong(signature_issuer.len()));
} }
let mut issuer = signature_issuer.into_bytes(); let mut issuer = signature_issuer.into_bytes();
issuer.resize(64, 0); issuer.resize(64, 0);

View File

@ -3,37 +3,26 @@
// //
// Implements the structures and methods required for WAD parsing and editing. // Implements the structures and methods required for WAD parsing and editing.
use std::error::Error;
use std::fmt;
use std::str; use std::str;
use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use thiserror::Error;
use crate::title::{cert, tmd, ticket, content}; use crate::title::{cert, tmd, ticket, content};
use crate::title::ticket::TicketError; use crate::title::ticket::TicketError;
use crate::title::tmd::TMDError; use crate::title::tmd::TMDError;
#[derive(Debug)] #[derive(Debug, Error)]
pub enum WADError { pub enum WADError {
BadType, #[error("WAD is invalid type `{0}`")]
TMDError(TMDError), BadType(String),
TicketError(TicketError), #[error("TMD processing error")]
IOError(std::io::Error), TMD(#[from] TMDError),
#[error("Ticket processing error")]
Ticket(#[from] TicketError),
#[error("WAD data is not in a valid format")]
IO(#[from] std::io::Error),
} }
impl fmt::Display for WADError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
WADError::BadType => "An invalid WAD type was specified.",
WADError::TMDError(_) => "An error occurred while loading TMD data.",
WADError::TicketError(_) => "An error occurred while loading Ticket data.",
WADError::IOError(_) => "The provided WAD data was invalid.",
};
f.write_str(description)
}
}
impl Error for WADError {}
#[derive(Debug)] #[derive(Debug)]
pub enum WADType { pub enum WADType {
Installable, Installable,
@ -78,7 +67,7 @@ impl WADHeader {
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.
let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::TMDError)?; let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::TMD)?;
let wad_type = match hex::encode(tmd.title_id).as_str() { let wad_type = match hex::encode(tmd.title_id).as_str() {
"0000000100000001" => WADType::ImportBoot, "0000000100000001" => WADType::ImportBoot,
_ => WADType::Installable, _ => WADType::Installable,
@ -111,11 +100,11 @@ impl WADBody {
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 {
cert_chain: cert_chain.to_bytes().map_err(WADError::IOError)?, cert_chain: cert_chain.to_bytes().map_err(WADError::IO)?,
crl: crl.to_vec(), crl: crl.to_vec(),
ticket: ticket.to_bytes().map_err(WADError::IOError)?, ticket: ticket.to_bytes().map_err(WADError::IO)?,
tmd: tmd.to_bytes().map_err(WADError::IOError)?, tmd: tmd.to_bytes().map_err(WADError::IO)?,
content: content.to_bytes().map_err(WADError::IOError)?, content: content.to_bytes().map_err(WADError::IO)?,
meta: meta.to_vec(), meta: meta.to_vec(),
}; };
Ok(body) Ok(body)
@ -126,27 +115,27 @@ impl WAD {
/// Creates a new WAD instance from the binary data of a WAD file. /// 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::IO)?;
let mut wad_type = [0u8; 2]; let mut wad_type = [0u8; 2];
buf.read_exact(&mut wad_type).map_err(WADError::IOError)?; buf.read_exact(&mut wad_type).map_err(WADError::IO)?;
let wad_type = match str::from_utf8(&wad_type) { let wad_type = match str::from_utf8(&wad_type) {
Ok(wad_type) => match wad_type { Ok(wad_type) => match wad_type {
"Is" => WADType::Installable, "Is" => WADType::Installable,
"ib" => WADType::ImportBoot, "ib" => WADType::ImportBoot,
_ => return Err(WADError::BadType), _ => return Err(WADError::BadType(wad_type.to_string())),
}, },
Err(_) => return Err(WADError::BadType), Err(_) => return Err(WADError::BadType(String::new())),
}; };
let wad_version = buf.read_u16::<BigEndian>().map_err(WADError::IOError)?; let wad_version = buf.read_u16::<BigEndian>().map_err(WADError::IO)?;
let cert_chain_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?; let cert_chain_size = buf.read_u32::<BigEndian>().map_err(WADError::IO)?;
let crl_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?; let crl_size = buf.read_u32::<BigEndian>().map_err(WADError::IO)?;
let ticket_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?; let ticket_size = buf.read_u32::<BigEndian>().map_err(WADError::IO)?;
let tmd_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?; let tmd_size = buf.read_u32::<BigEndian>().map_err(WADError::IO)?;
// Round the content size to the nearest 16. // Round the content size to the nearest 16.
let content_size = (buf.read_u32::<BigEndian>().map_err(WADError::IOError)? + 15) & !15; let content_size = (buf.read_u32::<BigEndian>().map_err(WADError::IO)? + 15) & !15;
let meta_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?; let meta_size = buf.read_u32::<BigEndian>().map_err(WADError::IO)?;
let mut padding = [0u8; 32]; let mut padding = [0u8; 32];
buf.read_exact(&mut padding).map_err(WADError::IOError)?; buf.read_exact(&mut padding).map_err(WADError::IO)?;
// Build header so we can use that data to read the WAD data. // Build header so we can use that data to read the WAD data.
let header = WADHeader { let header = WADHeader {
header_size, header_size,
@ -168,24 +157,24 @@ impl WAD {
let content_offset = (tmd_offset + header.tmd_size + 63) & !63; let content_offset = (tmd_offset + header.tmd_size + 63) & !63;
let meta_offset = (content_offset + header.content_size + 63) & !63; let meta_offset = (content_offset + header.content_size + 63) & !63;
// Read cert chain data. // Read cert chain data.
buf.seek(SeekFrom::Start(cert_chain_offset as u64)).map_err(WADError::IOError)?; buf.seek(SeekFrom::Start(cert_chain_offset as u64)).map_err(WADError::IO)?;
let mut cert_chain = vec![0u8; header.cert_chain_size as usize]; let mut cert_chain = vec![0u8; header.cert_chain_size as usize];
buf.read_exact(&mut cert_chain).map_err(WADError::IOError)?; buf.read_exact(&mut cert_chain).map_err(WADError::IO)?;
buf.seek(SeekFrom::Start(crl_offset as u64)).map_err(WADError::IOError)?; buf.seek(SeekFrom::Start(crl_offset as u64)).map_err(WADError::IO)?;
let mut crl = vec![0u8; header.crl_size as usize]; let mut crl = vec![0u8; header.crl_size as usize];
buf.read_exact(&mut crl).map_err(WADError::IOError)?; buf.read_exact(&mut crl).map_err(WADError::IO)?;
buf.seek(SeekFrom::Start(ticket_offset as u64)).map_err(WADError::IOError)?; buf.seek(SeekFrom::Start(ticket_offset as u64)).map_err(WADError::IO)?;
let mut ticket = vec![0u8; header.ticket_size as usize]; let mut ticket = vec![0u8; header.ticket_size as usize];
buf.read_exact(&mut ticket).map_err(WADError::IOError)?; buf.read_exact(&mut ticket).map_err(WADError::IO)?;
buf.seek(SeekFrom::Start(tmd_offset as u64)).map_err(WADError::IOError)?; buf.seek(SeekFrom::Start(tmd_offset as u64)).map_err(WADError::IO)?;
let mut tmd = vec![0u8; header.tmd_size as usize]; let mut tmd = vec![0u8; header.tmd_size as usize];
buf.read_exact(&mut tmd).map_err(WADError::IOError)?; buf.read_exact(&mut tmd).map_err(WADError::IO)?;
buf.seek(SeekFrom::Start(content_offset as u64)).map_err(WADError::IOError)?; buf.seek(SeekFrom::Start(content_offset as u64)).map_err(WADError::IO)?;
let mut content = vec![0u8; header.content_size as usize]; let mut content = vec![0u8; header.content_size as usize];
buf.read_exact(&mut content).map_err(WADError::IOError)?; buf.read_exact(&mut content).map_err(WADError::IO)?;
buf.seek(SeekFrom::Start(meta_offset as u64)).map_err(WADError::IOError)?; buf.seek(SeekFrom::Start(meta_offset as u64)).map_err(WADError::IO)?;
let mut meta = vec![0u8; header.meta_size as usize]; let mut meta = vec![0u8; header.meta_size as usize];
buf.read_exact(&mut meta).map_err(WADError::IOError)?; buf.read_exact(&mut meta).map_err(WADError::IO)?;
let body = WADBody { let body = WADBody {
cert_chain, cert_chain,
crl, crl,
@ -218,32 +207,32 @@ impl WAD {
/// Dumps the data in a WAD instance back into binary data that can be written to a file. /// 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::IO)?;
match self.header.wad_type { match self.header.wad_type {
WADType::Installable => { buf.write("Is".as_bytes()).map_err(WADError::IOError)?; }, WADType::Installable => { buf.write("Is".as_bytes()).map_err(WADError::IO)?; },
WADType::ImportBoot => { buf.write("ib".as_bytes()).map_err(WADError::IOError)?; }, WADType::ImportBoot => { buf.write("ib".as_bytes()).map_err(WADError::IO)?; },
} }
buf.write_u16::<BigEndian>(self.header.wad_version).map_err(WADError::IOError)?; buf.write_u16::<BigEndian>(self.header.wad_version).map_err(WADError::IO)?;
buf.write_u32::<BigEndian>(self.header.cert_chain_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.cert_chain_size).map_err(WADError::IO)?;
buf.write_u32::<BigEndian>(self.header.crl_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.crl_size).map_err(WADError::IO)?;
buf.write_u32::<BigEndian>(self.header.ticket_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.ticket_size).map_err(WADError::IO)?;
buf.write_u32::<BigEndian>(self.header.tmd_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.tmd_size).map_err(WADError::IO)?;
buf.write_u32::<BigEndian>(self.header.content_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.content_size).map_err(WADError::IO)?;
buf.write_u32::<BigEndian>(self.header.meta_size).map_err(WADError::IOError)?; buf.write_u32::<BigEndian>(self.header.meta_size).map_err(WADError::IO)?;
buf.write_all(&self.header.padding).map_err(WADError::IOError)?; buf.write_all(&self.header.padding).map_err(WADError::IO)?;
// Pad up to nearest multiple of 64. This also needs to happen after each section of data. // Pad up to nearest multiple of 64. This also needs to happen after each section of data.
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
buf.write_all(&self.body.cert_chain).map_err(WADError::IOError)?; buf.write_all(&self.body.cert_chain).map_err(WADError::IO)?;
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
buf.write_all(&self.body.crl).map_err(WADError::IOError)?; buf.write_all(&self.body.crl).map_err(WADError::IO)?;
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
buf.write_all(&self.body.ticket).map_err(WADError::IOError)?; buf.write_all(&self.body.ticket).map_err(WADError::IO)?;
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
buf.write_all(&self.body.tmd).map_err(WADError::IOError)?; buf.write_all(&self.body.tmd).map_err(WADError::IO)?;
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
buf.write_all(&self.body.content).map_err(WADError::IOError)?; buf.write_all(&self.body.content).map_err(WADError::IO)?;
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
buf.write_all(&self.body.meta).map_err(WADError::IOError)?; buf.write_all(&self.body.meta).map_err(WADError::IO)?;
buf.resize((buf.len() + 63) & !63, 0); buf.resize((buf.len() + 63) & !63, 0);
Ok(buf) Ok(buf)
} }