mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2025-06-05 23:11:02 -04:00
Added WAD packing command to rustii CLI
Also added lots of required library magic to make WAD packing possible. This includes the high-level Title object from libWiiPy, the ability to set content in a WAD, the ability to generate a WADHeader from a WADBody, and more.
This commit is contained in:
parent
6ab9993dd9
commit
62f6e6c0ec
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,3 +21,5 @@ target/
|
||||
*.tmd
|
||||
*.tik
|
||||
*.cert
|
||||
*.footer
|
||||
*.app
|
||||
|
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -197,6 +197,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@ -263,6 +269,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"cbc",
|
||||
"clap",
|
||||
"glob",
|
||||
"hex",
|
||||
"sha1",
|
||||
]
|
||||
|
@ -29,4 +29,5 @@ cbc = "0"
|
||||
aes = "0"
|
||||
hex = "0"
|
||||
sha1 = "0"
|
||||
glob = "0"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
@ -2,9 +2,13 @@
|
||||
|
||||
use std::fs;
|
||||
use rustii::title::{tmd, ticket, content, crypto, wad};
|
||||
use rustii::title;
|
||||
|
||||
fn main() {
|
||||
let data = fs::read("sm.wad").unwrap();
|
||||
let title = title::Title::from_bytes(&data).unwrap();
|
||||
println!("Title ID from WAD via Title object: {}", hex::encode(title.tmd.title_id));
|
||||
|
||||
let wad = wad::WAD::from_bytes(&data).unwrap();
|
||||
println!("size of tmd: {:?}", wad.tmd().len());
|
||||
let tmd = tmd::TMD::from_bytes(&wad.tmd()).unwrap();
|
||||
|
@ -3,10 +3,12 @@
|
||||
//
|
||||
// Code for WAD-related commands in the rustii CLI.
|
||||
|
||||
use clap::Subcommand;
|
||||
use std::{str, fs};
|
||||
use std::path::Path;
|
||||
use rustii::title::{tmd, ticket, wad, content};
|
||||
use std::path::{Path, PathBuf};
|
||||
use clap::Subcommand;
|
||||
use glob::glob;
|
||||
use rustii::title::{tmd, ticket, content, wad};
|
||||
use rustii::title;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[command(arg_required_else_help = true)]
|
||||
@ -24,35 +26,93 @@ pub enum Commands {
|
||||
}
|
||||
|
||||
pub fn pack_wad(input: &str, output: &str) {
|
||||
print!("packing");
|
||||
let in_path = Path::new(input);
|
||||
if !in_path.exists() {
|
||||
panic!("Error: Source directory does not exist.");
|
||||
}
|
||||
// Read TMD file (only accept one file).
|
||||
let tmd_files: Vec<PathBuf> = glob(&format!("{}/*.tmd", in_path.display()))
|
||||
.expect("failed to read glob pattern")
|
||||
.filter_map(|f| f.ok()).collect();
|
||||
if tmd_files.is_empty() {
|
||||
panic!("Error: No TMD file found in the source directory.");
|
||||
} else if tmd_files.len() > 1 {
|
||||
panic!("Error: More than one TMD file found in the source directory.")
|
||||
}
|
||||
let tmd = tmd::TMD::from_bytes(&fs::read(&tmd_files[0]).expect("could not read TMD file")).unwrap();
|
||||
// Read Ticket file (only accept one file).
|
||||
let ticket_files: Vec<PathBuf> = glob(&format!("{}/*.tik", in_path.display()))
|
||||
.expect("failed to read glob pattern")
|
||||
.filter_map(|f| f.ok()).collect();
|
||||
if ticket_files.is_empty() {
|
||||
panic!("Error: No Ticket file found in the source directory.");
|
||||
} else if ticket_files.len() > 1 {
|
||||
panic!("Error: More than one Ticket file found in the source directory.")
|
||||
}
|
||||
let tik = ticket::Ticket::from_bytes(&fs::read(&ticket_files[0]).expect("could not read Ticket file")).unwrap();
|
||||
// Read cert chain (only accept one file).
|
||||
let cert_files: Vec<PathBuf> = glob(&format!("{}/*.cert", in_path.display()))
|
||||
.expect("failed to read glob pattern")
|
||||
.filter_map(|f| f.ok()).collect();
|
||||
if cert_files.is_empty() {
|
||||
panic!("Error: No cert file found in the source directory.");
|
||||
} else if cert_files.len() > 1 {
|
||||
panic!("Error: More than one Cert file found in the source directory.")
|
||||
}
|
||||
let cert_chain = fs::read(&cert_files[0]).expect("could not read cert chain file");
|
||||
// Read footer, if one exists (only accept one file).
|
||||
let footer_files: Vec<PathBuf> = glob(&format!("{}/*.footer", in_path.display()))
|
||||
.expect("failed to read glob pattern")
|
||||
.filter_map(|f| f.ok()).collect();
|
||||
let mut footer: Vec<u8> = Vec::new();
|
||||
if footer_files.len() == 1 {
|
||||
footer = fs::read(&footer_files[0]).unwrap();
|
||||
}
|
||||
// Iterate over expected content and read it into a content region.
|
||||
let mut content_region = content::ContentRegion::new(tmd.content_records.clone()).expect("could not create content region");
|
||||
for content in tmd.content_records.clone() {
|
||||
let data = fs::read(format!("{}/{:08X}.app", in_path.display(), content.index)).expect("could not read required content");
|
||||
content_region.load_content(&data, content.index as usize, tik.dec_title_key()).expect("failed to load content into ContentRegion");
|
||||
}
|
||||
let wad = wad::WAD::from_parts(&cert_chain, &[], &tik, &tmd, &content_region, &footer).expect("failed to create WAD");
|
||||
// Write out WAD file.
|
||||
let mut out_path = PathBuf::from(output);
|
||||
match out_path.extension() {
|
||||
Some(ext) => {
|
||||
if ext != "wad" {
|
||||
out_path.set_extension("wad");
|
||||
}
|
||||
},
|
||||
None => {
|
||||
out_path.set_extension("wad");
|
||||
}
|
||||
}
|
||||
fs::write(out_path, wad.to_bytes().unwrap()).expect("could not write to wad file");
|
||||
println!("WAD file packed!");
|
||||
}
|
||||
|
||||
pub fn unpack_wad(input: &str, output: &str) {
|
||||
let wad_file = fs::read(input).expect("could not read WAD");
|
||||
let wad = wad::WAD::from_bytes(&wad_file).expect("could not parse WAD");
|
||||
let tmd = tmd::TMD::from_bytes(&wad.tmd()).expect("could not parse TMD");
|
||||
let tik = ticket::Ticket::from_bytes(&wad.ticket()).expect("could not parse Ticket");
|
||||
let cert_data = &wad.cert_chain();
|
||||
let meta_data = &wad.meta();
|
||||
let title = title::Title::from_bytes(&wad_file).unwrap();
|
||||
let tid = hex::encode(title.tmd.title_id);
|
||||
// Create output directory if it doesn't exist.
|
||||
if !Path::new(output).exists() {
|
||||
fs::create_dir(output).expect("could not create output directory");
|
||||
}
|
||||
let out_path = Path::new(output);
|
||||
// Write out all WAD components.
|
||||
let tmd_file_name = format!("{}.tmd", hex::encode(tmd.title_id));
|
||||
fs::write(Path::join(out_path, tmd_file_name), tmd.to_bytes().unwrap()).expect("could not write TMD file");
|
||||
let ticket_file_name = format!("{}.tik", hex::encode(tmd.title_id));
|
||||
fs::write(Path::join(out_path, ticket_file_name), tik.to_bytes().unwrap()).expect("could not write Ticket file");
|
||||
let cert_file_name = format!("{}.cert", hex::encode(tmd.title_id));
|
||||
fs::write(Path::join(out_path, cert_file_name), cert_data).expect("could not write Cert file");
|
||||
let meta_file_name = format!("{}.footer", hex::encode(tmd.title_id));
|
||||
fs::write(Path::join(out_path, meta_file_name), meta_data).expect("could not write footer file");
|
||||
let tmd_file_name = format!("{}.tmd", tid);
|
||||
fs::write(Path::join(out_path, tmd_file_name), title.tmd.to_bytes().unwrap()).expect("could not write TMD file");
|
||||
let ticket_file_name = format!("{}.tik", tid);
|
||||
fs::write(Path::join(out_path, ticket_file_name), title.ticket.to_bytes().unwrap()).expect("could not write Ticket file");
|
||||
let cert_file_name = format!("{}.cert", tid);
|
||||
fs::write(Path::join(out_path, cert_file_name), title.cert_chain()).expect("could not write Cert file");
|
||||
let meta_file_name = format!("{}.footer", tid);
|
||||
fs::write(Path::join(out_path, meta_file_name), title.meta()).expect("could not write footer file");
|
||||
// Iterate over contents, decrypt them, and write them out.
|
||||
let content_region = content::ContentRegion::from_bytes(&wad.content(), tmd.content_records).unwrap();
|
||||
for i in 0..tmd.num_contents {
|
||||
let content_file_name = format!("{:08X}.app", content_region.content_records[i as usize].index);
|
||||
let dec_content = content_region.get_content_by_index(i as usize, tik.dec_title_key()).unwrap();
|
||||
for i in 0..title.tmd.num_contents {
|
||||
let content_file_name = format!("{:08X}.app", title.content.content_records[i as usize].index);
|
||||
let dec_content = title.get_content_by_index(i as usize).unwrap();
|
||||
fs::write(Path::join(out_path, content_file_name), dec_content).unwrap();
|
||||
}
|
||||
println!("WAD file unpacked!");
|
||||
|
@ -8,7 +8,7 @@ use std::fmt;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use sha1::{Sha1, Digest};
|
||||
use crate::title::tmd::ContentRecord;
|
||||
use crate::title::crypto::decrypt_content;
|
||||
use crate::title::crypto;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ContentError {
|
||||
@ -40,6 +40,8 @@ pub struct ContentRegion {
|
||||
}
|
||||
|
||||
impl ContentRegion {
|
||||
/// Creates a ContentRegion instance that can be used to parse and edit content stored in a
|
||||
/// digital Wii title from the content area of a WAD and the ContentRecords from a TMD.
|
||||
pub fn from_bytes(data: &[u8], content_records: Vec<ContentRecord>) -> Result<Self, std::io::Error> {
|
||||
let content_region_size = data.len() as u32;
|
||||
let num_contents = content_records.len() as u16;
|
||||
@ -76,6 +78,24 @@ impl ContentRegion {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a ContentRegion instance from the ContentRecords of a TMD that contains no actual
|
||||
/// content. This can be used to load existing content from files.
|
||||
pub fn new(content_records: Vec<ContentRecord>) -> Result<Self, ContentError> {
|
||||
let content_region_size: u64 = content_records.iter().map(|x| (x.content_size + 63) & !63).sum();
|
||||
let content_region_size = content_region_size as u32;
|
||||
let num_contents = content_records.len() as u16;
|
||||
let content_start_offsets: Vec<u64> = Vec::new();
|
||||
let mut contents: Vec<Vec<u8>> = Vec::new();
|
||||
contents.resize(num_contents as usize, Vec::new());
|
||||
Ok(ContentRegion {
|
||||
content_records,
|
||||
content_region_size,
|
||||
num_contents,
|
||||
content_start_offsets,
|
||||
contents,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
for i in 0..self.num_contents {
|
||||
@ -95,7 +115,7 @@ impl ContentRegion {
|
||||
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)?;
|
||||
// Verify the hash of the decrypted content against its record.
|
||||
let mut content_dec = decrypt_content(&content, title_key, self.content_records[index].index);
|
||||
let mut content_dec = crypto::decrypt_content(&content, title_key, self.content_records[index].index);
|
||||
content_dec.resize(self.content_records[index].content_size as usize, 0);
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(content_dec.clone());
|
||||
@ -125,4 +145,21 @@ impl ContentRegion {
|
||||
Err(ContentError::CIDNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_content(&mut self, content: &[u8], index: usize, title_key: [u8; 16]) -> Result<(), ContentError> {
|
||||
if index >= self.content_records.len() {
|
||||
return Err(ContentError::IndexNotFound);
|
||||
}
|
||||
// Hash the content we're trying to load to ensure it matches the hash expected in the
|
||||
// matching record.
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(content);
|
||||
let result = hasher.finalize();
|
||||
if result[..] != self.content_records[index].content_hash {
|
||||
return Err(ContentError::BadHash);
|
||||
}
|
||||
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;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ pub fn encrypt_content(data: &[u8], title_key: [u8; 16], index: u16, size: u64)
|
||||
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
||||
let encryptor = Aes128CbcEnc::new(&title_key.into(), iv.as_slice().into());
|
||||
let mut buf = data.to_owned();
|
||||
let size = (size + 15) & !15;
|
||||
buf.resize(size as usize, 0);
|
||||
encryptor.encrypt_padded_mut::<ZeroPadding>(&mut buf, size as usize).unwrap();
|
||||
buf.resize(size as usize, 0);
|
||||
buf
|
||||
}
|
||||
|
123
src/title/mod.rs
123
src/title/mod.rs
@ -1,5 +1,7 @@
|
||||
// title/mod.rs from rustii (c) 2025 NinjaCheetah & Contributors
|
||||
// https://github.com/NinjaCheetah/rustii
|
||||
//
|
||||
// Root for all title-related modules and implementation of the high-level Title object.
|
||||
|
||||
pub mod commonkeys;
|
||||
pub mod content;
|
||||
@ -7,3 +9,124 @@ pub mod crypto;
|
||||
pub mod ticket;
|
||||
pub mod tmd;
|
||||
pub mod wad;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TitleError {
|
||||
BadTicket,
|
||||
BadTMD,
|
||||
BadContent,
|
||||
InvalidWAD,
|
||||
WADError(wad::WADError),
|
||||
IOError(std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for TitleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let description = match *self {
|
||||
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::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)]
|
||||
pub struct Title {
|
||||
cert_chain: Vec<u8>,
|
||||
crl: Vec<u8>,
|
||||
pub ticket: ticket::Ticket,
|
||||
pub tmd: tmd::TMD,
|
||||
pub content: content::ContentRegion,
|
||||
meta: Vec<u8>
|
||||
}
|
||||
|
||||
impl Title {
|
||||
pub fn from_wad(wad: &wad::WAD) -> Result<Title, TitleError> {
|
||||
let ticket = ticket::Ticket::from_bytes(&wad.ticket()).map_err(|_| TitleError::BadTicket)?;
|
||||
let tmd = tmd::TMD::from_bytes(&wad.tmd()).map_err(|_| TitleError::BadTMD)?;
|
||||
let content = content::ContentRegion::from_bytes(&wad.content(), tmd.content_records.clone()).map_err(|_| TitleError::BadContent)?;
|
||||
let title = Title {
|
||||
cert_chain: wad.cert_chain(),
|
||||
crl: wad.crl(),
|
||||
ticket,
|
||||
tmd,
|
||||
content,
|
||||
meta: wad.meta(),
|
||||
};
|
||||
Ok(title)
|
||||
}
|
||||
|
||||
pub fn to_wad(&self) -> Result<wad::WAD, TitleError> {
|
||||
// Create a new WAD from the data in the Title.
|
||||
let wad = wad::WAD::from_parts(
|
||||
&self.cert_chain,
|
||||
&self.crl,
|
||||
&self.ticket,
|
||||
&self.tmd,
|
||||
&self.content,
|
||||
&self.meta
|
||||
).map_err(TitleError::WADError)?;
|
||||
Ok(wad)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Title, TitleError> {
|
||||
let wad = wad::WAD::from_bytes(bytes).map_err(|_| TitleError::InvalidWAD)?;
|
||||
let title = Title::from_wad(&wad)?;
|
||||
Ok(title)
|
||||
}
|
||||
|
||||
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())?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
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())?;
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
pub fn cert_chain(&self) -> Vec<u8> {
|
||||
self.cert_chain.clone()
|
||||
}
|
||||
|
||||
pub fn set_cert_chain(&mut self, cert_chain: &[u8]) {
|
||||
self.cert_chain = cert_chain.to_vec();
|
||||
}
|
||||
|
||||
pub fn crl(&self) -> Vec<u8> {
|
||||
self.crl.clone()
|
||||
}
|
||||
|
||||
pub fn set_crl(&mut self, crl: &[u8]) {
|
||||
self.crl = crl.to_vec();
|
||||
}
|
||||
|
||||
pub fn set_ticket(&mut self, ticket: ticket::Ticket) {
|
||||
self.ticket = ticket;
|
||||
}
|
||||
|
||||
pub fn set_tmd(&mut self, tmd: tmd::TMD) {
|
||||
self.tmd = tmd;
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, content: content::ContentRegion) {
|
||||
self.content = content;
|
||||
}
|
||||
|
||||
pub fn meta(&self) -> Vec<u8> {
|
||||
self.meta.clone()
|
||||
}
|
||||
|
||||
pub fn set_meta(&mut self, meta: &[u8]) {
|
||||
self.meta = meta.to_vec();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use std::io::{Cursor, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone)]
|
||||
pub struct ContentRecord {
|
||||
pub content_id: u32,
|
||||
pub index: u16,
|
||||
@ -44,6 +45,7 @@ pub struct TMD {
|
||||
}
|
||||
|
||||
impl TMD {
|
||||
/// Creates a new TMD instance from the binary data of a TMD file.
|
||||
pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> {
|
||||
let mut buf = Cursor::new(data);
|
||||
let signature_type = buf.read_u32::<BigEndian>()?;
|
||||
@ -129,6 +131,7 @@ impl TMD {
|
||||
})
|
||||
}
|
||||
|
||||
/// Dumps the data in a TMD back into binary data that can be written to a file.
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, std::io::Error> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
buf.write_u32::<BigEndian>(self.signature_type)?;
|
||||
|
101
src/title/wad.rs
101
src/title/wad.rs
@ -8,6 +8,7 @@ use std::fmt;
|
||||
use std::str;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use crate::title::{tmd, ticket, content};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WADError {
|
||||
@ -28,7 +29,7 @@ impl fmt::Display for WADError {
|
||||
impl Error for WADError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WADTypes {
|
||||
pub enum WADType {
|
||||
Installable,
|
||||
ImportBoot
|
||||
}
|
||||
@ -42,7 +43,7 @@ pub struct WAD {
|
||||
#[derive(Debug)]
|
||||
pub struct WADHeader {
|
||||
pub header_size: u32,
|
||||
pub wad_type: WADTypes,
|
||||
pub wad_type: WADType,
|
||||
pub wad_version: u16,
|
||||
cert_chain_size: u32,
|
||||
crl_size: u32,
|
||||
@ -63,6 +64,53 @@ pub struct WADBody {
|
||||
meta: Vec<u8>,
|
||||
}
|
||||
|
||||
impl WADHeader {
|
||||
pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> {
|
||||
// Generates a new WADHeader from a populated WADBody object.
|
||||
// Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD.
|
||||
let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::IOError)?;
|
||||
let wad_type = match hex::encode(tmd.title_id).as_str() {
|
||||
"0000000100000001" => WADType::ImportBoot,
|
||||
_ => WADType::Installable,
|
||||
};
|
||||
// Find the sizes of all components of the Title.
|
||||
let cert_chain_size = body.cert_chain.len() as u32;
|
||||
let crl_size = body.crl.len() as u32;
|
||||
let ticket_size = body.ticket.len() as u32;
|
||||
let tmd_size = body.tmd.len() as u32;
|
||||
let content_size = body.content.len() as u32;
|
||||
let meta_size = body.meta.len() as u32;
|
||||
let header = WADHeader {
|
||||
header_size: 32,
|
||||
wad_type,
|
||||
wad_version: 0, // This is always officially a zero.
|
||||
cert_chain_size,
|
||||
crl_size,
|
||||
ticket_size,
|
||||
tmd_size,
|
||||
content_size,
|
||||
meta_size,
|
||||
padding: [0; 32],
|
||||
};
|
||||
Ok(header)
|
||||
}
|
||||
}
|
||||
|
||||
impl WADBody {
|
||||
pub fn from_parts(cert_chain: &[u8], crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
|
||||
content: &content::ContentRegion, meta: &[u8]) -> Result<WADBody, WADError> {
|
||||
let body = WADBody {
|
||||
cert_chain: cert_chain.to_vec(),
|
||||
crl: crl.to_vec(),
|
||||
ticket: ticket.to_bytes().map_err(WADError::IOError)?,
|
||||
tmd: tmd.to_bytes().map_err(WADError::IOError)?,
|
||||
content: content.to_bytes().map_err(WADError::IOError)?,
|
||||
meta: meta.to_vec(),
|
||||
};
|
||||
Ok(body)
|
||||
}
|
||||
}
|
||||
|
||||
impl WAD {
|
||||
pub fn from_bytes(data: &[u8]) -> Result<WAD, WADError> {
|
||||
let mut buf = Cursor::new(data);
|
||||
@ -71,8 +119,8 @@ impl WAD {
|
||||
buf.read_exact(&mut wad_type).map_err(WADError::IOError)?;
|
||||
let wad_type = match str::from_utf8(&wad_type) {
|
||||
Ok(wad_type) => match wad_type {
|
||||
"Is" => WADTypes::Installable,
|
||||
"ib" => WADTypes::ImportBoot,
|
||||
"Is" => WADType::Installable,
|
||||
"ib" => WADType::ImportBoot,
|
||||
_ => return Err(WADError::BadType),
|
||||
},
|
||||
Err(_) => return Err(WADError::BadType),
|
||||
@ -142,12 +190,23 @@ impl WAD {
|
||||
Ok(wad)
|
||||
}
|
||||
|
||||
pub fn from_parts(cert_chain: &[u8], crl: &[u8], ticket: &ticket::Ticket, tmd: &tmd::TMD,
|
||||
content: &content::ContentRegion, meta: &[u8]) -> Result<WAD, WADError> {
|
||||
let body = WADBody::from_parts(cert_chain, crl, ticket, tmd, content, meta)?;
|
||||
let header = WADHeader::from_body(&body)?;
|
||||
let wad = WAD {
|
||||
header,
|
||||
body,
|
||||
};
|
||||
Ok(wad)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, WADError> {
|
||||
let mut buf = Vec::new();
|
||||
buf.write_u32::<BigEndian>(self.header.header_size).map_err(WADError::IOError)?;
|
||||
match self.header.wad_type {
|
||||
WADTypes::Installable => { buf.write("Is".as_bytes()).map_err(WADError::IOError)?; },
|
||||
WADTypes::ImportBoot => { buf.write("ib".as_bytes()).map_err(WADError::IOError)?; },
|
||||
WADType::Installable => { buf.write("Is".as_bytes()).map_err(WADError::IOError)?; },
|
||||
WADType::ImportBoot => { buf.write("ib".as_bytes()).map_err(WADError::IOError)?; },
|
||||
}
|
||||
buf.write_u16::<BigEndian>(self.header.wad_version).map_err(WADError::IOError)?;
|
||||
buf.write_u32::<BigEndian>(self.header.cert_chain_size).map_err(WADError::IOError)?;
|
||||
@ -178,23 +237,53 @@ impl WAD {
|
||||
self.body.cert_chain.clone()
|
||||
}
|
||||
|
||||
pub fn set_cert_chain(&mut self, cert_chain: &[u8]) {
|
||||
self.body.cert_chain = cert_chain.to_vec();
|
||||
self.header.cert_chain_size = cert_chain.len() as u32;
|
||||
}
|
||||
|
||||
pub fn crl(&self) -> Vec<u8> {
|
||||
self.body.crl.clone()
|
||||
}
|
||||
|
||||
pub fn set_crl(&mut self, crl: &[u8]) {
|
||||
self.body.crl = crl.to_vec();
|
||||
self.header.crl_size = crl.len() as u32;
|
||||
}
|
||||
|
||||
pub fn ticket(&self) -> Vec<u8> {
|
||||
self.body.ticket.clone()
|
||||
}
|
||||
|
||||
pub fn set_ticket(&mut self, ticket: &[u8]) {
|
||||
self.body.ticket = ticket.to_vec();
|
||||
self.header.ticket_size = ticket.len() as u32;
|
||||
}
|
||||
|
||||
pub fn tmd(&self) -> Vec<u8> {
|
||||
self.body.tmd.clone()
|
||||
}
|
||||
|
||||
pub fn set_tmd(&mut self, tmd: &[u8]) {
|
||||
self.body.tmd = tmd.to_vec();
|
||||
self.header.tmd_size = tmd.len() as u32;
|
||||
}
|
||||
|
||||
pub fn content(&self) -> Vec<u8> {
|
||||
self.body.content.clone()
|
||||
}
|
||||
|
||||
pub fn set_content(&mut self, content: &[u8]) {
|
||||
self.body.content = content.to_vec();
|
||||
self.header.content_size = content.len() as u32;
|
||||
}
|
||||
|
||||
pub fn meta(&self) -> Vec<u8> {
|
||||
self.body.meta.clone()
|
||||
}
|
||||
|
||||
pub fn set_meta(&mut self, meta: &[u8]) {
|
||||
self.body.meta = meta.to_vec();
|
||||
self.header.meta_size = meta.len() as u32;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user