mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2025-06-05 23:11:02 -04:00
Added basic doc strings to all major structs and functions
Some checks failed
Build rustii / build-linux-x86_64 (push) Has been cancelled
Build rustii / build-macos-arm64 (push) Has been cancelled
Build rustii / build-macos-x86_64 (push) Has been cancelled
Build rustii / build-windows-x86_64 (push) Has been cancelled
Some checks failed
Build rustii / build-linux-x86_64 (push) Has been cancelled
Build rustii / build-macos-arm64 (push) Has been cancelled
Build rustii / build-macos-x86_64 (push) Has been cancelled
Build rustii / build-windows-x86_64 (push) Has been cancelled
This commit is contained in:
parent
edf3af0f7c
commit
e147a953a5
6
src/archive/mod.rs
Normal file
6
src/archive/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// archive/mod.rs from rustii (c) 2025 NinjaCheetah & Contributors
|
||||||
|
// https://github.com/NinjaCheetah/rustii
|
||||||
|
//
|
||||||
|
// Root for all archive-related modules.
|
||||||
|
|
||||||
|
pub mod u8;
|
5
src/archive/u8.rs
Normal file
5
src/archive/u8.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// archive/u8.rs from rustii (c) 2025 NinjaCheetah & Contributors
|
||||||
|
// https://github.com/NinjaCheetah/rustii
|
||||||
|
//
|
||||||
|
// Implements the structures and methods required for parsing U8 archives.
|
||||||
|
|
@ -1,5 +1,7 @@
|
|||||||
// lib.rs from rustii (c) 2025 NinjaCheetah & Contributors
|
// lib.rs from rustii (c) 2025 NinjaCheetah & Contributors
|
||||||
// https://github.com/NinjaCheetah/rustii
|
// https://github.com/NinjaCheetah/rustii
|
||||||
|
//
|
||||||
|
// Root level module that imports the feature modules.
|
||||||
|
|
||||||
|
pub mod archive;
|
||||||
pub mod title;
|
pub mod title;
|
||||||
|
@ -49,6 +49,7 @@ pub enum CertificateKeyType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// A structure that represents the components of a Wii signing certificate.
|
||||||
pub struct Certificate {
|
pub struct Certificate {
|
||||||
signer_key_type: CertificateKeyType,
|
signer_key_type: CertificateKeyType,
|
||||||
signature: Vec<u8>,
|
signature: Vec<u8>,
|
||||||
@ -123,7 +124,7 @@ impl Certificate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dumps the data in a Certificate back into binary data that can be written to a file.
|
/// Dumps the data in a Certificate instance back into binary data that can be written to a file.
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
match self.signer_key_type {
|
match self.signer_key_type {
|
||||||
@ -154,24 +155,29 @@ impl Certificate {
|
|||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the name of the certificate used to sign a certificate as a string.
|
||||||
pub fn signature_issuer(&self) -> String {
|
pub fn signature_issuer(&self) -> String {
|
||||||
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the name of a certificate's child certificate as a string.
|
||||||
pub fn child_cert_identity(&self) -> String {
|
pub fn child_cert_identity(&self) -> String {
|
||||||
String::from_utf8_lossy(&self.child_cert_identity).trim_end_matches('\0').to_owned()
|
String::from_utf8_lossy(&self.child_cert_identity).trim_end_matches('\0').to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the modulus of the public key contained in a certificate.
|
||||||
pub fn pub_key_modulus(&self) -> Vec<u8> {
|
pub fn pub_key_modulus(&self) -> Vec<u8> {
|
||||||
self.pub_key_modulus.clone()
|
self.pub_key_modulus.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the exponent of the public key contained in a certificate.
|
||||||
pub fn pub_key_exponent(&self) -> u32 {
|
pub fn pub_key_exponent(&self) -> u32 {
|
||||||
self.pub_key_exponent
|
self.pub_key_exponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents the components of the Wii's signing certificate chain.
|
||||||
pub struct CertificateChain {
|
pub struct CertificateChain {
|
||||||
ca_cert: Certificate,
|
ca_cert: Certificate,
|
||||||
tmd_cert: Certificate,
|
tmd_cert: Certificate,
|
||||||
@ -179,6 +185,9 @@ pub struct CertificateChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CertificateChain {
|
impl CertificateChain {
|
||||||
|
/// Creates a new CertificateChain instance from the binary data of an entire certificate chain.
|
||||||
|
/// This chain must contain a CA certificate, a TMD certificate, and a Ticket certificate or
|
||||||
|
/// else this method will return an error.
|
||||||
pub fn from_bytes(data: &[u8]) -> Result<CertificateChain, CertificateError> {
|
pub fn from_bytes(data: &[u8]) -> Result<CertificateChain, CertificateError> {
|
||||||
let mut buf = Cursor::new(data);
|
let mut buf = Cursor::new(data);
|
||||||
let mut offset: u64 = 0;
|
let mut offset: u64 = 0;
|
||||||
@ -238,6 +247,10 @@ impl CertificateChain {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new CertificateChain instance from three separate Certificate instances each
|
||||||
|
/// containing one of the three certificates stored in the chain. You must provide a CA
|
||||||
|
/// certificate, a TMD certificate, and a Ticket certificate, or this method will return an
|
||||||
|
/// error.
|
||||||
pub fn from_certs(ca_cert: Certificate, tmd_cert: Certificate, ticket_cert: Certificate) -> Result<Self, CertificateError> {
|
pub fn from_certs(ca_cert: Certificate, tmd_cert: Certificate, ticket_cert: Certificate) -> Result<Self, CertificateError> {
|
||||||
if String::from_utf8_lossy(&ca_cert.signature_issuer).trim_end_matches('\0').ne("Root") {
|
if String::from_utf8_lossy(&ca_cert.signature_issuer).trim_end_matches('\0').ne("Root") {
|
||||||
return Err(CertificateError::IncorrectCertificate("CA".to_owned()));
|
return Err(CertificateError::IncorrectCertificate("CA".to_owned()));
|
||||||
@ -255,6 +268,7 @@ impl CertificateChain {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dumps the entire CertificateChain back into binary data that can be written to a file.
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
buf.write_all(&self.ca_cert().to_bytes()?)?;
|
buf.write_all(&self.ca_cert().to_bytes()?)?;
|
||||||
@ -316,8 +330,7 @@ pub fn verify_child_cert(ca_cert: &Certificate, child_cert: &Certificate) -> Res
|
|||||||
return Err(CertificateError::NonMatchingCertificates)
|
return Err(CertificateError::NonMatchingCertificates)
|
||||||
}
|
}
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
let cert_body = child_cert.to_bytes().unwrap();
|
hasher.update(&child_cert.to_bytes().map_err(CertificateError::IOError)?[320..]);
|
||||||
hasher.update(&cert_body[320..]);
|
|
||||||
let cert_hash = hasher.finalize().as_slice().to_owned();
|
let cert_hash = hasher.finalize().as_slice().to_owned();
|
||||||
let public_key_modulus = BigUint::from_bytes_be(&ca_cert.pub_key_modulus());
|
let public_key_modulus = BigUint::from_bytes_be(&ca_cert.pub_key_modulus());
|
||||||
let public_key_exponent = BigUint::from(ca_cert.pub_key_exponent());
|
let public_key_exponent = BigUint::from(ca_cert.pub_key_exponent());
|
||||||
@ -339,8 +352,7 @@ pub fn verify_tmd(tmd_cert: &Certificate, tmd: &tmd::TMD) -> Result<bool, Certif
|
|||||||
return Err(CertificateError::NonMatchingCertificates)
|
return Err(CertificateError::NonMatchingCertificates)
|
||||||
}
|
}
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
let tmd_body = tmd.to_bytes().unwrap();
|
hasher.update(&tmd.to_bytes().map_err(CertificateError::IOError)?[320..]);
|
||||||
hasher.update(&tmd_body[320..]);
|
|
||||||
let tmd_hash = hasher.finalize().as_slice().to_owned();
|
let tmd_hash = hasher.finalize().as_slice().to_owned();
|
||||||
let public_key_modulus = BigUint::from_bytes_be(&tmd_cert.pub_key_modulus());
|
let public_key_modulus = BigUint::from_bytes_be(&tmd_cert.pub_key_modulus());
|
||||||
let public_key_exponent = BigUint::from(tmd_cert.pub_key_exponent());
|
let public_key_exponent = BigUint::from(tmd_cert.pub_key_exponent());
|
||||||
@ -362,15 +374,13 @@ pub fn verify_ticket(ticket_cert: &Certificate, ticket: &ticket::Ticket) -> Resu
|
|||||||
return Err(CertificateError::NonMatchingCertificates)
|
return Err(CertificateError::NonMatchingCertificates)
|
||||||
}
|
}
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
let tmd_body = ticket.to_bytes().unwrap();
|
hasher.update(&ticket.to_bytes().map_err(CertificateError::IOError)?[320..]);
|
||||||
hasher.update(&tmd_body[320..]);
|
let ticket_hash = hasher.finalize().as_slice().to_owned();
|
||||||
let tmd_hash = hasher.finalize().as_slice().to_owned();
|
|
||||||
let public_key_modulus = BigUint::from_bytes_be(&ticket_cert.pub_key_modulus());
|
let public_key_modulus = BigUint::from_bytes_be(&ticket_cert.pub_key_modulus());
|
||||||
let public_key_exponent = BigUint::from(ticket_cert.pub_key_exponent());
|
let public_key_exponent = BigUint::from(ticket_cert.pub_key_exponent());
|
||||||
let root_key = RsaPublicKey::new(public_key_modulus, public_key_exponent).unwrap();
|
let root_key = RsaPublicKey::new(public_key_modulus, public_key_exponent).unwrap();
|
||||||
match root_key.verify(Pkcs1v15Sign::new::<Sha1>(), &tmd_hash, ticket.signature.as_slice()) {
|
match root_key.verify(Pkcs1v15Sign::new::<Sha1>(), &ticket_hash, ticket.signature.as_slice()) {
|
||||||
Ok(_) => Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(_) => Ok(false),
|
Err(_) => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ const KOREAN_KEY: &str = "63b82bb4f4614e2e13f2fefbba4c9b7e";
|
|||||||
const VWII_KEY: &str = "30bfc76e7c19afbb23163330ced7c28d";
|
const VWII_KEY: &str = "30bfc76e7c19afbb23163330ced7c28d";
|
||||||
const DEV_COMMON_KEY: &str = "a1604a6a7123b529ae8bec32c816fcaa";
|
const DEV_COMMON_KEY: &str = "a1604a6a7123b529ae8bec32c816fcaa";
|
||||||
|
|
||||||
|
/// Returns the common key for the specified index. Providing Some(true) for the optional argument
|
||||||
|
/// is_dev will make index 0 return the development common key instead of the retail common key.
|
||||||
pub fn get_common_key(index: u8, is_dev: Option<bool>) -> [u8; 16] {
|
pub fn get_common_key(index: u8, is_dev: Option<bool>) -> [u8; 16] {
|
||||||
// Match the Korean and vWii keys, and if they don't match then fall back on the common key.
|
// Match the Korean and vWii keys, and if they don't match then fall back on the common key.
|
||||||
// The is_dev argument is an option, and if it's set to false or None, then the regular
|
// The is_dev argument is an option, and if it's set to false or None, then the regular
|
||||||
|
@ -31,6 +31,7 @@ impl fmt::Display for ContentError {
|
|||||||
impl Error for ContentError {}
|
impl Error for ContentError {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents the block of data containing the content of a digital Wii title.
|
||||||
pub struct ContentRegion {
|
pub struct ContentRegion {
|
||||||
pub content_records: Vec<ContentRecord>,
|
pub content_records: Vec<ContentRecord>,
|
||||||
pub content_region_size: u32,
|
pub content_region_size: u32,
|
||||||
@ -97,6 +98,7 @@ impl ContentRegion {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dumps the entire ContentRegion back into binary data that can be written to a file.
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
for i in 0..self.num_contents {
|
for i in 0..self.num_contents {
|
||||||
@ -107,12 +109,14 @@ impl ContentRegion {
|
|||||||
}
|
}
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the encrypted content file from the ContentRegion at the specified index.
|
||||||
pub fn get_enc_content_by_index(&self, index: usize) -> Result<Vec<u8>, ContentError> {
|
pub fn get_enc_content_by_index(&self, index: usize) -> Result<Vec<u8>, ContentError> {
|
||||||
let content = self.contents.get(index).ok_or(ContentError::IndexNotFound)?;
|
let content = self.contents.get(index).ok_or(ContentError::IndexNotFound)?;
|
||||||
Ok(content.clone())
|
Ok(content.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the decrypted content file from the ContentRegion at the specified index.
|
||||||
pub fn get_content_by_index(&self, index: usize, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> {
|
pub fn get_content_by_index(&self, index: usize, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> {
|
||||||
let content = self.get_enc_content_by_index(index)?;
|
let content = self.get_enc_content_by_index(index)?;
|
||||||
// Verify the hash of the decrypted content against its record.
|
// Verify the hash of the decrypted content against its record.
|
||||||
@ -127,6 +131,7 @@ impl ContentRegion {
|
|||||||
Ok(content_dec)
|
Ok(content_dec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the encrypted content file from the ContentRegion with the specified Content ID.
|
||||||
pub fn get_enc_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, ContentError> {
|
pub fn get_enc_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, ContentError> {
|
||||||
let index = self.content_records.iter().position(|x| x.content_id == cid);
|
let index = self.content_records.iter().position(|x| x.content_id == cid);
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
@ -136,7 +141,8 @@ impl ContentRegion {
|
|||||||
Err(ContentError::CIDNotFound)
|
Err(ContentError::CIDNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the decrypted content file from the ContentRegion with the specified Content ID.
|
||||||
pub fn get_content_by_cid(&self, cid: u32, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> {
|
pub fn get_content_by_cid(&self, cid: u32, title_key: [u8; 16]) -> Result<Vec<u8>, ContentError> {
|
||||||
let index = self.content_records.iter().position(|x| x.content_id == cid);
|
let index = self.content_records.iter().position(|x| x.content_id == cid);
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
@ -147,6 +153,9 @@ impl ContentRegion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads existing content into the specified index of a ContentRegion instance. This content
|
||||||
|
/// must be decrypted and needs to match the size and hash listed in the content record at that
|
||||||
|
/// index.
|
||||||
pub fn load_content(&mut self, content: &[u8], index: usize, title_key: [u8; 16]) -> Result<(), ContentError> {
|
pub fn load_content(&mut self, content: &[u8], index: usize, title_key: [u8; 16]) -> Result<(), ContentError> {
|
||||||
if index >= self.content_records.len() {
|
if index >= self.content_records.len() {
|
||||||
return Err(ContentError::IndexNotFound);
|
return Err(ContentError::IndexNotFound);
|
||||||
|
@ -14,7 +14,7 @@ fn title_id_to_iv(title_id: [u8; 8]) -> [u8; 16] {
|
|||||||
iv.as_slice().try_into().unwrap()
|
iv.as_slice().try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt a Title Key using the specified common key.
|
/// Decrypts a Title Key using the specified common key and the corresponding Title ID.
|
||||||
pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
|
pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
|
||||||
let iv = title_id_to_iv(title_id);
|
let iv = title_id_to_iv(title_id);
|
||||||
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
|
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
|
||||||
@ -24,7 +24,7 @@ pub fn decrypt_title_key(title_key_enc: [u8; 16], common_key_index: u8, title_id
|
|||||||
title_key
|
title_key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt a Title Key using the specified common key.
|
/// Encrypts a Title Key using the specified common key and the corresponding Title ID.
|
||||||
pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
|
pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id: [u8; 8], is_dev: Option<bool>) -> [u8; 16] {
|
||||||
let iv = title_id_to_iv(title_id);
|
let iv = title_id_to_iv(title_id);
|
||||||
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
||||||
@ -34,7 +34,7 @@ pub fn encrypt_title_key(title_key_dec: [u8; 16], common_key_index: u8, title_id
|
|||||||
title_key
|
title_key
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt content using a Title Key.
|
/// Decrypt content using the corresponding Title Key and content index.
|
||||||
pub fn decrypt_content(data: &[u8], title_key: [u8; 16], index: u16) -> Vec<u8> {
|
pub fn decrypt_content(data: &[u8], title_key: [u8; 16], index: u16) -> Vec<u8> {
|
||||||
let mut iv = Vec::from(index.to_be_bytes());
|
let mut iv = Vec::from(index.to_be_bytes());
|
||||||
iv.resize(16, 0);
|
iv.resize(16, 0);
|
||||||
@ -45,7 +45,7 @@ pub fn decrypt_content(data: &[u8], title_key: [u8; 16], index: u16) -> Vec<u8>
|
|||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt content using a Title Key.
|
/// Encrypt content using the corresponding Title Key and content index.
|
||||||
pub fn encrypt_content(data: &[u8], title_key: [u8; 16], index: u16, size: u64) -> Vec<u8> {
|
pub fn encrypt_content(data: &[u8], title_key: [u8; 16], index: u16, size: u64) -> Vec<u8> {
|
||||||
let mut iv = Vec::from(index.to_be_bytes());
|
let mut iv = Vec::from(index.to_be_bytes());
|
||||||
iv.resize(16, 0);
|
iv.resize(16, 0);
|
||||||
|
@ -50,6 +50,7 @@ impl fmt::Display for TitleError {
|
|||||||
impl Error for TitleError {}
|
impl Error for TitleError {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents the components of a digital Wii title.
|
||||||
pub struct Title {
|
pub struct Title {
|
||||||
pub cert_chain: cert::CertificateChain,
|
pub cert_chain: cert::CertificateChain,
|
||||||
crl: Vec<u8>,
|
crl: Vec<u8>,
|
||||||
@ -60,6 +61,7 @@ pub struct Title {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Title {
|
impl Title {
|
||||||
|
/// Creates a new Title instance from an existing WAD instance.
|
||||||
pub fn from_wad(wad: &wad::WAD) -> Result<Title, TitleError> {
|
pub fn from_wad(wad: &wad::WAD) -> Result<Title, TitleError> {
|
||||||
let cert_chain = cert::CertificateChain::from_bytes(&wad.cert_chain()).map_err(|_| TitleError::BadCertChain)?;
|
let cert_chain = cert::CertificateChain::from_bytes(&wad.cert_chain()).map_err(|_| TitleError::BadCertChain)?;
|
||||||
let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(|_| TitleError::BadTicket)?;
|
let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(|_| TitleError::BadTicket)?;
|
||||||
@ -76,6 +78,7 @@ impl Title {
|
|||||||
Ok(title)
|
Ok(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a Title instance into a WAD, which can be used to export the Title back to a file.
|
||||||
pub fn to_wad(&self) -> Result<wad::WAD, TitleError> {
|
pub fn to_wad(&self) -> Result<wad::WAD, TitleError> {
|
||||||
// Create a new WAD from the data in the Title.
|
// Create a new WAD from the data in the Title.
|
||||||
let wad = wad::WAD::from_parts(
|
let wad = wad::WAD::from_parts(
|
||||||
@ -89,16 +92,19 @@ impl Title {
|
|||||||
Ok(wad)
|
Ok(wad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new Title instance from the binary data of a WAD file.
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Title, TitleError> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<Title, TitleError> {
|
||||||
let wad = wad::WAD::from_bytes(bytes).map_err(|_| TitleError::InvalidWAD)?;
|
let wad = wad::WAD::from_bytes(bytes).map_err(|_| TitleError::InvalidWAD)?;
|
||||||
let title = Title::from_wad(&wad)?;
|
let title = Title::from_wad(&wad)?;
|
||||||
Ok(title)
|
Ok(title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether the TMD and Ticket of a Title are both fakesigned.
|
||||||
pub fn is_fakesigned(&self) -> bool {
|
pub fn is_fakesigned(&self) -> bool {
|
||||||
self.tmd.is_fakesigned() && self.ticket.is_fakesigned()
|
self.tmd.is_fakesigned() && self.ticket.is_fakesigned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fakesigns the TMD and Ticket of a Title.
|
||||||
pub fn fakesign(&mut self) -> Result<(), TitleError> {
|
pub fn fakesign(&mut self) -> Result<(), TitleError> {
|
||||||
// Run the fakesign methods on the TMD and Ticket.
|
// Run the fakesign methods on the TMD and Ticket.
|
||||||
self.tmd.fakesign().map_err(TitleError::TMDError)?;
|
self.tmd.fakesign().map_err(TitleError::TMDError)?;
|
||||||
@ -106,11 +112,13 @@ impl Title {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the decrypted content file from the Title at the specified index.
|
||||||
pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> {
|
pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> {
|
||||||
let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?;
|
let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?;
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the decrypted content file from the Title with the specified Content ID.
|
||||||
pub fn get_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, content::ContentError> {
|
pub fn get_content_by_cid(&self, cid: u32) -> Result<Vec<u8>, content::ContentError> {
|
||||||
let content = self.content.get_content_by_cid(cid, self.ticket.dec_title_key())?;
|
let content = self.content.get_content_by_cid(cid, self.ticket.dec_title_key())?;
|
||||||
Ok(content)
|
Ok(content)
|
||||||
|
@ -41,6 +41,7 @@ pub struct TitleLimit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents a Wii Ticket file.
|
||||||
pub struct Ticket {
|
pub struct Ticket {
|
||||||
pub signature_type: u32,
|
pub signature_type: u32,
|
||||||
pub signature: [u8; 256],
|
pub signature: [u8; 256],
|
||||||
@ -67,6 +68,7 @@ pub struct Ticket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Ticket {
|
impl Ticket {
|
||||||
|
/// Creates a new Ticket instance from the binary data of a Ticket file.
|
||||||
pub fn from_bytes(data: &[u8]) -> Result<Self, TicketError> {
|
pub fn from_bytes(data: &[u8]) -> Result<Self, TicketError> {
|
||||||
let mut buf = Cursor::new(data);
|
let mut buf = Cursor::new(data);
|
||||||
let signature_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?;
|
let signature_type = buf.read_u32::<BigEndian>().map_err(TicketError::IOError)?;
|
||||||
@ -145,6 +147,7 @@ impl Ticket {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dumps the data in a Ticket instance back into binary data that can be written to a file.
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
buf.write_u32::<BigEndian>(self.signature_type)?;
|
buf.write_u32::<BigEndian>(self.signature_type)?;
|
||||||
@ -176,18 +179,21 @@ impl Ticket {
|
|||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the decrypted version of the Title Key stored in a Ticket.
|
||||||
pub fn dec_title_key(&self) -> [u8; 16] {
|
pub fn dec_title_key(&self) -> [u8; 16] {
|
||||||
// Get the dev status of this Ticket so decrypt_title_key knows the right common key.
|
// Get the dev status of this Ticket so decrypt_title_key knows the right common key.
|
||||||
let is_dev = self.is_dev();
|
let is_dev = self.is_dev();
|
||||||
decrypt_title_key(self.title_key, self.common_key_index, self.title_id, Some(is_dev))
|
decrypt_title_key(self.title_key, self.common_key_index, self.title_id, Some(is_dev))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether a Ticket was signed for development (true) or retail (false).
|
||||||
pub fn is_dev(&self) -> bool {
|
pub fn is_dev(&self) -> bool {
|
||||||
// Parse the signature issuer to determine if this is a dev Ticket or not.
|
// Parse the signature issuer to determine if this is a dev Ticket or not.
|
||||||
let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default();
|
let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default();
|
||||||
issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006")
|
issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether a Ticket is fakesigned using the strncmp (trucha) bug or not.
|
||||||
pub fn is_fakesigned(&self) -> bool {
|
pub fn is_fakesigned(&self) -> bool {
|
||||||
// Can't be fakesigned without a null signature.
|
// Can't be fakesigned without a null signature.
|
||||||
if self.signature != [0; 256] {
|
if self.signature != [0; 256] {
|
||||||
@ -204,6 +210,7 @@ impl Ticket {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fakesigns a Ticket for use with the strncmp (trucha) bug.
|
||||||
pub fn fakesign(&mut self) -> Result<(), TicketError> {
|
pub fn fakesign(&mut self) -> Result<(), TicketError> {
|
||||||
// Erase the signature.
|
// Erase the signature.
|
||||||
self.signature = [0; 256];
|
self.signature = [0; 256];
|
||||||
@ -221,6 +228,7 @@ impl Ticket {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the name of the certificate used to sign a Ticket as a string.
|
||||||
pub fn signature_issuer(&self) -> String {
|
pub fn signature_issuer(&self) -> String {
|
||||||
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@ pub enum AccessRight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// A structure that represents the metadata of a content file in a digital Wii title.
|
||||||
pub struct ContentRecord {
|
pub struct ContentRecord {
|
||||||
pub content_id: u32,
|
pub content_id: u32,
|
||||||
pub index: u16,
|
pub index: u16,
|
||||||
@ -92,6 +93,7 @@ pub struct ContentRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents a Wii TMD (Title Metadata) file.
|
||||||
pub struct TMD {
|
pub struct TMD {
|
||||||
pub signature_type: u32,
|
pub signature_type: u32,
|
||||||
pub signature: [u8; 256],
|
pub signature: [u8; 256],
|
||||||
@ -257,6 +259,7 @@ impl TMD {
|
|||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether a TMD is fakesigned using the strncmp (trucha) bug or not.
|
||||||
pub fn is_fakesigned(&self) -> bool {
|
pub fn is_fakesigned(&self) -> bool {
|
||||||
// Can't be fakesigned without a null signature.
|
// Can't be fakesigned without a null signature.
|
||||||
if self.signature != [0; 256] {
|
if self.signature != [0; 256] {
|
||||||
@ -273,6 +276,7 @@ impl TMD {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fakesigns a TMD for use with the strncmp (trucha) bug.
|
||||||
pub fn fakesign(&mut self) -> Result<(), TMDError> {
|
pub fn fakesign(&mut self) -> Result<(), TMDError> {
|
||||||
// Erase the signature.
|
// Erase the signature.
|
||||||
self.signature = [0; 256];
|
self.signature = [0; 256];
|
||||||
@ -290,6 +294,7 @@ impl TMD {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the 3-letter code of the region a TMD was created for.
|
||||||
pub fn region(&self) -> &str {
|
pub fn region(&self) -> &str {
|
||||||
match self.region {
|
match self.region {
|
||||||
0 => "JPN",
|
0 => "JPN",
|
||||||
@ -301,6 +306,7 @@ impl TMD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the type of title described by a TMD.
|
||||||
pub fn title_type(&self) -> TitleType {
|
pub fn title_type(&self) -> TitleType {
|
||||||
match hex::encode(self.title_id)[..8].to_string().as_str() {
|
match hex::encode(self.title_id)[..8].to_string().as_str() {
|
||||||
"00000001" => TitleType::System,
|
"00000001" => TitleType::System,
|
||||||
@ -314,6 +320,7 @@ impl TMD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the type of content described by a content record in a TMD.
|
||||||
pub fn content_type(&self, index: usize) -> ContentType {
|
pub fn content_type(&self, index: usize) -> ContentType {
|
||||||
// Find possible content indices, because the provided one could exist while the indices
|
// Find possible content indices, because the provided one could exist while the indices
|
||||||
// are out of order, which could cause problems finding the content.
|
// are out of order, which could cause problems finding the content.
|
||||||
@ -331,13 +338,15 @@ impl TMD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets whether a specified access right is enabled in a TMD.
|
||||||
pub fn check_access_right(&self, right: AccessRight) -> bool {
|
pub fn check_access_right(&self, right: AccessRight) -> bool {
|
||||||
match right {
|
match right {
|
||||||
AccessRight::AHB => (self.access_rights & (1 << 0)) != 0,
|
AccessRight::AHB => (self.access_rights & (1 << 0)) != 0,
|
||||||
AccessRight::DVDVideo => (self.access_rights & (1 << 1)) != 0,
|
AccessRight::DVDVideo => (self.access_rights & (1 << 1)) != 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the name of the certificate used to sign a TMD as a string.
|
||||||
pub fn signature_issuer(&self) -> String {
|
pub fn signature_issuer(&self) -> String {
|
||||||
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
String::from_utf8_lossy(&self.signature_issuer).trim_end_matches('\0').to_owned()
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,10 @@ fn wii_menu_versions_map(vwii: Option<bool>) -> HashMap<u16, String> {
|
|||||||
menu_versions
|
menu_versions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the decimal version of a title (vXXX) into a more standard format for applicable
|
||||||
|
/// titles. For the Wii Menu, this uses the optional vwii argument and a hash table to determine
|
||||||
|
/// the user-friendly version number, as there is no way to directly derive it from the decimal
|
||||||
|
/// format.
|
||||||
pub fn dec_to_standard(version: u16, title_id: &str, vwii: Option<bool>) -> Option<String> {
|
pub fn dec_to_standard(version: u16, title_id: &str, vwii: Option<bool>) -> Option<String> {
|
||||||
if title_id == "0000000100000002" {
|
if title_id == "0000000100000002" {
|
||||||
let map = wii_menu_versions_map(vwii);
|
let map = wii_menu_versions_map(vwii);
|
||||||
|
@ -41,12 +41,14 @@ pub enum WADType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents an entire WAD file as a separate header and body.
|
||||||
pub struct WAD {
|
pub struct WAD {
|
||||||
pub header: WADHeader,
|
pub header: WADHeader,
|
||||||
pub body: WADBody,
|
pub body: WADBody,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represents the header of a WAD file.
|
||||||
pub struct WADHeader {
|
pub struct WADHeader {
|
||||||
pub header_size: u32,
|
pub header_size: u32,
|
||||||
pub wad_type: WADType,
|
pub wad_type: WADType,
|
||||||
@ -61,6 +63,7 @@ pub struct WADHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
/// A structure that represent the data contained in the body of a WAD file.
|
||||||
pub struct WADBody {
|
pub struct WADBody {
|
||||||
cert_chain: Vec<u8>,
|
cert_chain: Vec<u8>,
|
||||||
crl: Vec<u8>,
|
crl: Vec<u8>,
|
||||||
@ -71,6 +74,7 @@ pub struct WADBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WADHeader {
|
impl WADHeader {
|
||||||
|
/// Creates a new WADHeader instance from the binary data of a WAD file's header.
|
||||||
pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> {
|
pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> {
|
||||||
// Generates a new WADHeader from a populated WADBody object.
|
// Generates a new WADHeader from a populated WADBody object.
|
||||||
// Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD.
|
// Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD.
|
||||||
@ -103,6 +107,7 @@ impl WADHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WADBody {
|
impl WADBody {
|
||||||
|
/// Creates a new WADBody instance from instances of the components stored in a WAD file.
|
||||||
pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
|
pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
|
||||||
content: &content::ContentRegion, meta: &[u8]) -> Result<WADBody, WADError> {
|
content: &content::ContentRegion, meta: &[u8]) -> Result<WADBody, WADError> {
|
||||||
let body = WADBody {
|
let body = WADBody {
|
||||||
@ -118,6 +123,7 @@ impl WADBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WAD {
|
impl WAD {
|
||||||
|
/// Creates a new WAD instance from the binary data of a WAD file.
|
||||||
pub fn from_bytes(data: &[u8]) -> Result<WAD, WADError> {
|
pub fn from_bytes(data: &[u8]) -> Result<WAD, WADError> {
|
||||||
let mut buf = Cursor::new(data);
|
let mut buf = Cursor::new(data);
|
||||||
let header_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?;
|
let header_size = buf.read_u32::<BigEndian>().map_err(WADError::IOError)?;
|
||||||
@ -196,6 +202,8 @@ impl WAD {
|
|||||||
Ok(wad)
|
Ok(wad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new WAD instance from instances of the components stored in a WAD file. This
|
||||||
|
/// first creates a WADBody from the components, then generates a new WADHeader from them.
|
||||||
pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
|
pub fn from_parts(cert_chain: &cert::CertificateChain, crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
|
||||||
content: &content::ContentRegion, meta: &[u8]) -> Result<WAD, WADError> {
|
content: &content::ContentRegion, meta: &[u8]) -> Result<WAD, WADError> {
|
||||||
let body = WADBody::from_parts(cert_chain, crl, ticket, tmd, content, meta)?;
|
let body = WADBody::from_parts(cert_chain, crl, ticket, tmd, content, meta)?;
|
||||||
@ -206,7 +214,8 @@ impl WAD {
|
|||||||
};
|
};
|
||||||
Ok(wad)
|
Ok(wad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dumps the data in a WAD instance back into binary data that can be written to a file.
|
||||||
pub fn to_bytes(&self) -> Result<Vec<u8>, WADError> {
|
pub fn to_bytes(&self) -> Result<Vec<u8>, WADError> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
buf.write_u32::<BigEndian>(self.header.header_size).map_err(WADError::IOError)?;
|
buf.write_u32::<BigEndian>(self.header.header_size).map_err(WADError::IOError)?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user