From c2169f84c478ce0f43b31b46fcc037d39d9966dd Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Thu, 3 Apr 2025 19:09:56 -0400 Subject: [PATCH] Implement thiserror for library errors --- src/title/cert.rs | 77 ++++++++++++--------------- src/title/content.rs | 45 +++++++--------- src/title/mod.rs | 67 +++++++++-------------- src/title/ticket.rs | 85 +++++++++++++----------------- src/title/tmd.rs | 86 +++++++++++++----------------- src/title/wad.rs | 123 ++++++++++++++++++++----------------------- 6 files changed, 207 insertions(+), 276 deletions(-) diff --git a/src/title/cert.rs b/src/title/cert.rs index 80c14d5..0245a23 100644 --- a/src/title/cert.rs +++ b/src/title/cert.rs @@ -3,44 +3,33 @@ // // 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 byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use rsa::pkcs8::DecodePublicKey; use rsa::pkcs1v15::Pkcs1v15Sign; use rsa::{RsaPublicKey, BigUint}; use sha1::{Digest, Sha1}; +use thiserror::Error; use crate::title::{tmd, ticket}; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum CertificateError { + #[error("certificate appears to be signed with invalid key type `{0}`")] InvalidSignatureKeyType(u32), + #[error("certificate appears to contain key with invalid type `{0}`")] InvalidContainedKeyType(u32), + #[error("certificate chain contains an unknown certificate")] UnknownCertificate, + #[error("certificate chain is missing required certificate `{0}`")] MissingCertificate(String), + #[error("attempted to load incorrect certificate `{0}`")] IncorrectCertificate(String), + #[error("the data you are attempting to verify was not signed with the provided certificate")] 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)] pub enum CertificateKeyType { Rsa4096, @@ -65,7 +54,7 @@ impl Certificate { /// Creates a new Certificate instance from the binary data of a certificate file. pub fn from_bytes(data: &[u8]) -> Result { let mut buf = Cursor::new(data); - let signer_key_type_int = buf.read_u32::().map_err(CertificateError::IOError)?; + let signer_key_type_int = buf.read_u32::().map_err(CertificateError::IO)?; let signer_key_type = match signer_key_type_int { 0x00010000 => CertificateKeyType::Rsa4096, 0x00010001 => CertificateKeyType::Rsa2048, @@ -78,12 +67,12 @@ impl Certificate { CertificateKeyType::ECC => 60, }; 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. - 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]; - buf.read_exact(&mut signature_issuer).map_err(CertificateError::IOError)?; - let pub_key_type_int = buf.read_u32::().map_err(CertificateError::IOError)?; + buf.read_exact(&mut signature_issuer).map_err(CertificateError::IO)?; + let pub_key_type_int = buf.read_u32::().map_err(CertificateError::IO)?; let pub_key_type = match pub_key_type_int { 0x00000000 => CertificateKeyType::Rsa4096, 0x00000001 => CertificateKeyType::Rsa2048, @@ -91,25 +80,25 @@ impl Certificate { _ => return Err(CertificateError::InvalidContainedKeyType(pub_key_type_int)) }; let mut child_cert_identity = [0u8; 64]; - buf.read_exact(&mut child_cert_identity).map_err(CertificateError::IOError)?; - let pub_key_id = buf.read_u32::().map_err(CertificateError::IOError)?; + buf.read_exact(&mut child_cert_identity).map_err(CertificateError::IO)?; + let pub_key_id = buf.read_u32::().map_err(CertificateError::IO)?; let mut pub_key_modulus: Vec; let mut pub_key_exponent: u32 = 0; // The key size and exponent are different based on the key type. ECC has no exponent. match pub_key_type { CertificateKeyType::Rsa4096 => { pub_key_modulus = vec![0u8; 512]; - buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IOError)?; - pub_key_exponent = buf.read_u32::().map_err(CertificateError::IOError)?; + buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IO)?; + pub_key_exponent = buf.read_u32::().map_err(CertificateError::IO)?; }, CertificateKeyType::Rsa2048 => { pub_key_modulus = vec![0u8; 256]; - buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IOError)?; - pub_key_exponent = buf.read_u32::().map_err(CertificateError::IOError)?; + buf.read_exact(&mut pub_key_modulus).map_err(CertificateError::IO)?; + pub_key_exponent = buf.read_u32::().map_err(CertificateError::IO)?; }, CertificateKeyType::ECC => { 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 { @@ -196,16 +185,16 @@ impl CertificateChain { let mut ticket_cert: Option = None; // Iterate 3 times, because the chain should contain 3 certs. for _ in 0..3 { - buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IOError)?; - let signer_key_type = buf.read_u32::().map_err(CertificateError::IOError)?; + buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IO)?; + let signer_key_type = buf.read_u32::().map_err(CertificateError::IO)?; let signature_len = match signer_key_type { 0x00010000 => 512, // 0x200 0x00010001 => 256, // 0x100 0x00010002 => 60, _ => return Err(CertificateError::InvalidSignatureKeyType(signer_key_type)) }; - buf.seek(SeekFrom::Start(offset + 0x80 + signature_len)).map_err(CertificateError::IOError)?; - let pub_key_type = buf.read_u32::().map_err(CertificateError::IOError)?; + buf.seek(SeekFrom::Start(offset + 0x80 + signature_len)).map_err(CertificateError::IO)?; + let pub_key_type = buf.read_u32::().map_err(CertificateError::IO)?; let pub_key_len = match pub_key_type { 0x00000000 => 568, // 0x238 0x00000001 => 312, // 0x138 @@ -215,10 +204,10 @@ impl CertificateChain { // 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. let cert_size = (0xC8 + signature_len + pub_key_len + 63) & !63; - buf.seek(SeekFrom::End(0)).map_err(CertificateError::IOError)?; - buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IOError)?; + buf.seek(SeekFrom::End(0)).map_err(CertificateError::IO)?; + buf.seek(SeekFrom::Start(offset)).map_err(CertificateError::IO)?; 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 issuer_name = String::from_utf8_lossy(&cert.signature_issuer).trim_end_matches('\0').to_owned(); if issuer_name.eq("Root") { @@ -310,7 +299,7 @@ pub fn verify_ca_cert(ca_cert: &Certificate) -> Result { return Err(CertificateError::UnknownCertificate); }; 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..]); let cert_hash = hasher.finalize().as_slice().to_owned(); match root_key.verify(Pkcs1v15Sign::new::(), &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) } 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 public_key_modulus = BigUint::from_bytes_be(&ca_cert.pub_key_modulus()); 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 Resu return Err(CertificateError::NonMatchingCertificates) } 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 public_key_modulus = BigUint::from_bytes_be(&ticket_cert.pub_key_modulus()); let public_key_exponent = BigUint::from(ticket_cert.pub_key_exponent()); diff --git a/src/title/content.rs b/src/title/content.rs index 2c73a6a..b829ada 100644 --- a/src/title/content.rs +++ b/src/title/content.rs @@ -3,33 +3,24 @@ // // Implements content parsing and editing. -use std::error::Error; -use std::fmt; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use sha1::{Sha1, Digest}; +use thiserror::Error; use crate::title::tmd::ContentRecord; use crate::title::crypto; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ContentError { - IndexNotFound, - CIDNotFound, - BadHash, + #[error("requested index {index} is out of range (must not exceed {max})")] + IndexOutOfRange { index: usize, max: usize }, + #[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)] /// A structure that represents the block of data containing the content of a digital Wii title. pub struct ContentRegion { @@ -43,7 +34,7 @@ pub struct ContentRegion { impl ContentRegion { /// 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. - pub fn from_bytes(data: &[u8], content_records: Vec) -> Result { + pub fn from_bytes(data: &[u8], content_records: Vec) -> Result { let content_region_size = data.len() as u32; let num_contents = content_records.len() as u16; // 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. pub fn get_enc_content_by_index(&self, index: usize) -> Result, 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()) } @@ -126,7 +117,7 @@ impl ContentRegion { hasher.update(content_dec.clone()); let result = hasher.finalize(); 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) } @@ -135,10 +126,10 @@ impl ContentRegion { pub fn get_enc_content_by_cid(&self, cid: u32) -> Result, ContentError> { let index = self.content_records.iter().position(|x| x.content_id == cid); 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) } 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)?; Ok(content_dec) } else { - Err(ContentError::CIDNotFound) + Err(ContentError::CIDNotFound(cid)) } } @@ -158,7 +149,7 @@ impl ContentRegion { /// index. pub fn load_content(&mut self, content: &[u8], index: usize, title_key: [u8; 16]) -> Result<(), ContentError> { 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 // matching record. @@ -166,7 +157,7 @@ impl ContentRegion { hasher.update(content); let result = hasher.finalize(); 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); self.contents[index] = content_enc; diff --git a/src/title/mod.rs b/src/title/mod.rs index de2065a..ac346b5 100644 --- a/src/title/mod.rs +++ b/src/title/mod.rs @@ -12,43 +12,28 @@ pub mod tmd; pub mod versions; pub mod wad; -use std::error::Error; -use std::fmt; +use thiserror::Error; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum TitleError { - BadCertChain, - BadTicket, - BadTMD, - BadContent, + #[error("the data for required Title component `{0}` was invalid")] + InvalidData(String), + #[error("WAD data is not in a valid format")] InvalidWAD, - CertificateError(cert::CertificateError), - TMDError(tmd::TMDError), - TicketError(ticket::TicketError), - WADError(wad::WADError), - IOError(std::io::Error), + #[error("certificate processing error")] + CertificateError(#[from] cert::CertificateError), + #[error("TMD processing error")] + TMD(#[from] tmd::TMDError), + #[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)] /// A structure that represents the components of a digital Wii title. pub struct Title { @@ -63,10 +48,10 @@ pub struct Title { impl Title { /// Creates a new Title instance from an existing WAD instance. pub fn from_wad(wad: &wad::WAD) -> Result { - 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 tmd = tmd::TMD::from_bytes(&wad.tmd()).map_err(|_| TitleError::BadTMD)?; - let content = content::ContentRegion::from_bytes(&wad.content(), tmd.content_records.clone()).map_err(|_| TitleError::BadContent)?; + 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::Ticket)?; + 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::Content)?; let title = Title { cert_chain, crl: wad.crl(), @@ -88,7 +73,7 @@ impl Title { &self.tmd, &self.content, &self.meta - ).map_err(TitleError::WADError)?; + ).map_err(TitleError::WAD)?; Ok(wad) } @@ -107,8 +92,8 @@ impl Title { /// Fakesigns the TMD and Ticket of a Title. pub fn fakesign(&mut self) -> Result<(), TitleError> { // Run the fakesign methods on the TMD and Ticket. - self.tmd.fakesign().map_err(TitleError::TMDError)?; - self.ticket.fakesign().map_err(TitleError::TicketError)?; + self.tmd.fakesign().map_err(TitleError::TMD)?; + self.ticket.fakesign().map_err(TitleError::Ticket)?; Ok(()) } @@ -130,8 +115,8 @@ impl Title { let mut title_size: usize = 0; // Get the TMD and Ticket size by dumping them and measuring their length for the most // accurate results. - title_size += self.tmd.to_bytes().map_err(|x| TitleError::TMDError(tmd::TMDError::IOError(x)))?.len(); - title_size += self.ticket.to_bytes().map_err(|x| TitleError::TicketError(ticket::TicketError::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::Ticket(ticket::TicketError::IO(x)))?.len(); for record in &self.tmd.content_records { if matches!(record.content_type, tmd::ContentType::Shared) { if absolute == Some(true) { diff --git a/src/title/ticket.rs b/src/title/ticket.rs index 0e5fe93..2ad84ac 100644 --- a/src/title/ticket.rs +++ b/src/title/ticket.rs @@ -3,38 +3,25 @@ // // 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 byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use sha1::{Sha1, Digest}; +use thiserror::Error; use crate::title::crypto::decrypt_title_key; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum TicketError { - UnsupportedVersion, + #[error("Ticket is version `{0}` but only v0 is supported")] + UnsupportedVersion(u8), + #[error("Ticket data could not be fakesigned")] CannotFakesign, - IssuerTooLong, - IOError(std::io::Error), + #[error("signature issuer string must not exceed 64 characters (was {0})")] + IssuerTooLong(usize), + #[error("Ticket data is not in a valid format")] + IO(#[from] std::io::Error), } -impl fmt::Display for TicketError { - 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)] +#[derive(Debug, Copy, Clone)] pub struct TitleLimit { // The type of limit being applied (time, launch count, etc.) pub limit_type: u32, @@ -73,53 +60,53 @@ impl Ticket { /// Creates a new Ticket instance from the binary data of a Ticket file. pub fn from_bytes(data: &[u8]) -> Result { let mut buf = Cursor::new(data); - let signature_type = buf.read_u32::().map_err(TicketError::IOError)?; + let signature_type = buf.read_u32::().map_err(TicketError::IO)?; 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? 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]; - 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]; - buf.read_exact(&mut ecdh_data).map_err(TicketError::IOError)?; - let ticket_version = buf.read_u8().map_err(TicketError::IOError)?; + buf.read_exact(&mut ecdh_data).map_err(TicketError::IO)?; + let ticket_version = buf.read_u8().map_err(TicketError::IO)?; // v1 Tickets are NOT supported (just like in libWiiPy). if ticket_version != 0 { - return Err(TicketError::UnsupportedVersion); + return Err(TicketError::UnsupportedVersion(ticket_version)); } 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]; - 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]; - buf.read_exact(&mut unknown1).map_err(TicketError::IOError)?; + buf.read_exact(&mut unknown1).map_err(TicketError::IO)?; 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]; - 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]; - 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]; - buf.read_exact(&mut unknown2).map_err(TicketError::IOError)?; - let title_version = buf.read_u16::().map_err(TicketError::IOError)?; + buf.read_exact(&mut unknown2).map_err(TicketError::IO)?; + let title_version = buf.read_u16::().map_err(TicketError::IO)?; 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]; - buf.read_exact(&mut permit_mask).map_err(TicketError::IOError)?; - let title_export_allowed = buf.read_u8().map_err(TicketError::IOError)?; - let common_key_index = buf.read_u8().map_err(TicketError::IOError)?; + buf.read_exact(&mut permit_mask).map_err(TicketError::IO)?; + let title_export_allowed = buf.read_u8().map_err(TicketError::IO)?; + let common_key_index = buf.read_u8().map_err(TicketError::IO)?; 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]; - 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]; - buf.read_exact(&mut padding2).map_err(TicketError::IOError)?; + buf.read_exact(&mut padding2).map_err(TicketError::IO)?; // Build the array of title limits. let mut title_limits: Vec = Vec::new(); for _ in 0..8 { - let limit_type = buf.read_u32::().map_err(TicketError::IOError)?; - let limit_max = buf.read_u32::().map_err(TicketError::IOError)?; + let limit_type = buf.read_u32::().map_err(TicketError::IO)?; + let limit_max = buf.read_u32::().map_err(TicketError::IO)?; title_limits.push(TitleLimit { limit_type, limit_max }); } let title_limits = title_limits.try_into().unwrap(); @@ -223,7 +210,7 @@ impl Ticket { current_int += 1; self.unknown2 = current_int.to_be_bytes(); let mut hasher = Sha1::new(); - let ticket_body = self.to_bytes().unwrap(); + let ticket_body = self.to_bytes()?; hasher.update(&ticket_body[320..]); 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. pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TicketError> { if signature_issuer.len() > 64 { - return Err(TicketError::IssuerTooLong); + return Err(TicketError::IssuerTooLong(signature_issuer.len())); } let mut issuer = signature_issuer.into_bytes(); issuer.resize(64, 0); diff --git a/src/title/tmd.rs b/src/title/tmd.rs index 6c93d45..c63d1fb 100644 --- a/src/title/tmd.rs +++ b/src/title/tmd.rs @@ -3,35 +3,25 @@ // // Implements the structures and methods required for TMD parsing and editing. -use std::error::Error; use std::fmt; use std::io::{Cursor, Read, Write}; use std::ops::Index; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use sha1::{Sha1, Digest}; +use thiserror::Error; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum TMDError { + #[error("TMD data could not be fakesigned")] 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), - 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 { System, Game, @@ -127,50 +117,50 @@ impl TMD { /// Creates a new TMD instance from the binary data of a TMD file. pub fn from_bytes(data: &[u8]) -> Result { let mut buf = Cursor::new(data); - let signature_type = buf.read_u32::().map_err(TMDError::IOError)?; + let signature_type = buf.read_u32::().map_err(TMDError::IO)?; 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? 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]; - buf.read_exact(&mut signature_issuer).map_err(TMDError::IOError)?; - let tmd_version = buf.read_u8().map_err(TMDError::IOError)?; - let ca_crl_version = buf.read_u8().map_err(TMDError::IOError)?; - let signer_crl_version = buf.read_u8().map_err(TMDError::IOError)?; - let is_vwii = buf.read_u8().map_err(TMDError::IOError)?; + buf.read_exact(&mut signature_issuer).map_err(TMDError::IO)?; + let tmd_version = buf.read_u8().map_err(TMDError::IO)?; + let ca_crl_version = buf.read_u8().map_err(TMDError::IO)?; + let signer_crl_version = buf.read_u8().map_err(TMDError::IO)?; + let is_vwii = buf.read_u8().map_err(TMDError::IO)?; 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]; - 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]; - buf.read_exact(&mut title_type).map_err(TMDError::IOError)?; - let group_id = buf.read_u16::().map_err(TMDError::IOError)?; + buf.read_exact(&mut title_type).map_err(TMDError::IO)?; + let group_id = buf.read_u16::().map_err(TMDError::IO)?; // Same here... let mut padding2 = [0u8; 2]; - buf.read_exact(&mut padding2).map_err(TMDError::IOError)?; - let region = buf.read_u16::().map_err(TMDError::IOError)?; + buf.read_exact(&mut padding2).map_err(TMDError::IO)?; + let region = buf.read_u16::().map_err(TMDError::IO)?; 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... 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]; - buf.read_exact(&mut ipc_mask).map_err(TMDError::IOError)?; + buf.read_exact(&mut ipc_mask).map_err(TMDError::IO)?; // ...and here. let mut reserved2 = [0u8; 18]; - buf.read_exact(&mut reserved2).map_err(TMDError::IOError)?; - let access_rights = buf.read_u32::().map_err(TMDError::IOError)?; - let title_version = buf.read_u16::().map_err(TMDError::IOError)?; - let num_contents = buf.read_u16::().map_err(TMDError::IOError)?; - let boot_index = buf.read_u16::().map_err(TMDError::IOError)?; - let minor_version = buf.read_u16::().map_err(TMDError::IOError)?; + buf.read_exact(&mut reserved2).map_err(TMDError::IO)?; + let access_rights = buf.read_u32::().map_err(TMDError::IO)?; + let title_version = buf.read_u16::().map_err(TMDError::IO)?; + let num_contents = buf.read_u16::().map_err(TMDError::IO)?; + let boot_index = buf.read_u16::().map_err(TMDError::IO)?; + let minor_version = buf.read_u16::().map_err(TMDError::IO)?; // 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); for _ in 0..num_contents { - let content_id = buf.read_u32::().map_err(TMDError::IOError)?; - let index = buf.read_u16::().map_err(TMDError::IOError)?; - let type_int = buf.read_u16::().map_err(TMDError::IOError)?; + let content_id = buf.read_u32::().map_err(TMDError::IO)?; + let index = buf.read_u16::().map_err(TMDError::IO)?; + let type_int = buf.read_u16::().map_err(TMDError::IO)?; let content_type = match type_int { 1 => ContentType::Normal, 2 => ContentType::Development, @@ -179,9 +169,9 @@ impl TMD { 32769 => ContentType::Shared, _ => return Err(TMDError::InvalidContentType(type_int)) }; - let content_size = buf.read_u64::().map_err(TMDError::IOError)?; + let content_size = buf.read_u64::().map_err(TMDError::IO)?; 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_id, index, @@ -289,7 +279,7 @@ impl TMD { current_int += 1; self.minor_version = current_int; let mut hasher = Sha1::new(); - let ticket_body = self.to_bytes().unwrap(); + let ticket_body = self.to_bytes()?; hasher.update(&ticket_body[320..]); 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. pub fn set_signature_issuer(&mut self, signature_issuer: String) -> Result<(), TMDError> { if signature_issuer.len() > 64 { - return Err(TMDError::IssuerTooLong); + return Err(TMDError::IssuerTooLong(signature_issuer.len())); } let mut issuer = signature_issuer.into_bytes(); issuer.resize(64, 0); diff --git a/src/title/wad.rs b/src/title/wad.rs index 8401f4b..22b4f06 100644 --- a/src/title/wad.rs +++ b/src/title/wad.rs @@ -3,37 +3,26 @@ // // Implements the structures and methods required for WAD parsing and editing. -use std::error::Error; -use std::fmt; use std::str; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use thiserror::Error; use crate::title::{cert, tmd, ticket, content}; use crate::title::ticket::TicketError; use crate::title::tmd::TMDError; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum WADError { - BadType, - TMDError(TMDError), - TicketError(TicketError), - IOError(std::io::Error), + #[error("WAD is invalid type `{0}`")] + BadType(String), + #[error("TMD processing 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)] pub enum WADType { Installable, @@ -78,7 +67,7 @@ impl WADHeader { pub fn from_body(body: &WADBody) -> Result { // 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. - 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() { "0000000100000001" => WADType::ImportBoot, _ => WADType::Installable, @@ -111,11 +100,11 @@ impl WADBody { pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD, content: &content::ContentRegion, meta: &[u8]) -> Result { 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(), - ticket: ticket.to_bytes().map_err(WADError::IOError)?, - tmd: tmd.to_bytes().map_err(WADError::IOError)?, - content: content.to_bytes().map_err(WADError::IOError)?, + ticket: ticket.to_bytes().map_err(WADError::IO)?, + tmd: tmd.to_bytes().map_err(WADError::IO)?, + content: content.to_bytes().map_err(WADError::IO)?, meta: meta.to_vec(), }; Ok(body) @@ -126,27 +115,27 @@ impl WAD { /// Creates a new WAD instance from the binary data of a WAD file. pub fn from_bytes(data: &[u8]) -> Result { let mut buf = Cursor::new(data); - let header_size = buf.read_u32::().map_err(WADError::IOError)?; + let header_size = buf.read_u32::().map_err(WADError::IO)?; 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) { Ok(wad_type) => match wad_type { "Is" => WADType::Installable, "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::().map_err(WADError::IOError)?; - let cert_chain_size = buf.read_u32::().map_err(WADError::IOError)?; - let crl_size = buf.read_u32::().map_err(WADError::IOError)?; - let ticket_size = buf.read_u32::().map_err(WADError::IOError)?; - let tmd_size = buf.read_u32::().map_err(WADError::IOError)?; + let wad_version = buf.read_u16::().map_err(WADError::IO)?; + let cert_chain_size = buf.read_u32::().map_err(WADError::IO)?; + let crl_size = buf.read_u32::().map_err(WADError::IO)?; + let ticket_size = buf.read_u32::().map_err(WADError::IO)?; + let tmd_size = buf.read_u32::().map_err(WADError::IO)?; // Round the content size to the nearest 16. - let content_size = (buf.read_u32::().map_err(WADError::IOError)? + 15) & !15; - let meta_size = buf.read_u32::().map_err(WADError::IOError)?; + let content_size = (buf.read_u32::().map_err(WADError::IO)? + 15) & !15; + let meta_size = buf.read_u32::().map_err(WADError::IO)?; 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. let header = WADHeader { header_size, @@ -168,24 +157,24 @@ impl WAD { let content_offset = (tmd_offset + header.tmd_size + 63) & !63; let meta_offset = (content_offset + header.content_size + 63) & !63; // 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]; - buf.read_exact(&mut cert_chain).map_err(WADError::IOError)?; - buf.seek(SeekFrom::Start(crl_offset as u64)).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::IO)?; let mut crl = vec![0u8; header.crl_size as usize]; - buf.read_exact(&mut crl).map_err(WADError::IOError)?; - buf.seek(SeekFrom::Start(ticket_offset as u64)).map_err(WADError::IOError)?; + buf.read_exact(&mut crl).map_err(WADError::IO)?; + buf.seek(SeekFrom::Start(ticket_offset as u64)).map_err(WADError::IO)?; let mut ticket = vec![0u8; header.ticket_size as usize]; - buf.read_exact(&mut ticket).map_err(WADError::IOError)?; - buf.seek(SeekFrom::Start(tmd_offset as u64)).map_err(WADError::IOError)?; + buf.read_exact(&mut ticket).map_err(WADError::IO)?; + buf.seek(SeekFrom::Start(tmd_offset as u64)).map_err(WADError::IO)?; let mut tmd = vec![0u8; header.tmd_size as usize]; - buf.read_exact(&mut tmd).map_err(WADError::IOError)?; - buf.seek(SeekFrom::Start(content_offset as u64)).map_err(WADError::IOError)?; + buf.read_exact(&mut tmd).map_err(WADError::IO)?; + buf.seek(SeekFrom::Start(content_offset as u64)).map_err(WADError::IO)?; let mut content = vec![0u8; header.content_size as usize]; - buf.read_exact(&mut content).map_err(WADError::IOError)?; - buf.seek(SeekFrom::Start(meta_offset as u64)).map_err(WADError::IOError)?; + buf.read_exact(&mut content).map_err(WADError::IO)?; + buf.seek(SeekFrom::Start(meta_offset as u64)).map_err(WADError::IO)?; 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 { cert_chain, 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. pub fn to_bytes(&self) -> Result, WADError> { let mut buf = Vec::new(); - buf.write_u32::(self.header.header_size).map_err(WADError::IOError)?; + buf.write_u32::(self.header.header_size).map_err(WADError::IO)?; match self.header.wad_type { - WADType::Installable => { buf.write("Is".as_bytes()).map_err(WADError::IOError)?; }, - WADType::ImportBoot => { buf.write("ib".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::IO)?; }, } - buf.write_u16::(self.header.wad_version).map_err(WADError::IOError)?; - buf.write_u32::(self.header.cert_chain_size).map_err(WADError::IOError)?; - buf.write_u32::(self.header.crl_size).map_err(WADError::IOError)?; - buf.write_u32::(self.header.ticket_size).map_err(WADError::IOError)?; - buf.write_u32::(self.header.tmd_size).map_err(WADError::IOError)?; - buf.write_u32::(self.header.content_size).map_err(WADError::IOError)?; - buf.write_u32::(self.header.meta_size).map_err(WADError::IOError)?; - buf.write_all(&self.header.padding).map_err(WADError::IOError)?; + buf.write_u16::(self.header.wad_version).map_err(WADError::IO)?; + buf.write_u32::(self.header.cert_chain_size).map_err(WADError::IO)?; + buf.write_u32::(self.header.crl_size).map_err(WADError::IO)?; + buf.write_u32::(self.header.ticket_size).map_err(WADError::IO)?; + buf.write_u32::(self.header.tmd_size).map_err(WADError::IO)?; + buf.write_u32::(self.header.content_size).map_err(WADError::IO)?; + buf.write_u32::(self.header.meta_size).map_err(WADError::IO)?; + 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. 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.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.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.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.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.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); Ok(buf) }