mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2025-06-05 23:11:02 -04:00
Implement thiserror for library errors
This commit is contained in:
parent
0bda6dabf3
commit
c2169f84c4
@ -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());
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
123
src/title/wad.rs
123
src/title/wad.rs
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user