mirror of
https://github.com/NinjaCheetah/rustii.git
synced 2026-03-17 06:47:49 -04:00
Added rustii CLI wad edit command and required library features
This required a LOT more backend work than I expected. But hey, some of this stuff is being done better than it was in libWiiPy/WiiPy, so that's a win in my book. When changing both the Title ID and type of a WAD, the updated TID will only be written once (which also means the Title Key will only be re-encrypted once). This is an improvement over WiiPy where it will be updated as part of both changes. Some TMD fields have been made private and moved to getter/setter methods only as they are actually in use now and should only be set through the correct means.
This commit is contained in:
@@ -171,13 +171,6 @@ impl Title {
|
||||
Ok(title_size)
|
||||
}
|
||||
|
||||
/// Gets the installed size of the title, in blocks. Use the optional parameter "absolute" to
|
||||
/// set whether shared content should be included in this total or not.
|
||||
pub fn title_size_blocks(&self, absolute: Option<bool>) -> Result<usize, TitleError> {
|
||||
let title_size_bytes = self.title_size(absolute)?;
|
||||
Ok((title_size_bytes as f64 / 131072.0).ceil() as usize)
|
||||
}
|
||||
|
||||
/// Verifies entire certificate chain, and then the TMD and Ticket. Returns true if the title
|
||||
/// is entirely valid, or false if any component of the verification fails.
|
||||
pub fn verify(&self) -> Result<bool, TitleError> {
|
||||
@@ -194,6 +187,14 @@ impl Title {
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Sets a new Title ID for the Title. This will re-encrypt the Title Key in the Ticket, since
|
||||
/// the Title ID is used as the IV for decrypting the Title Key.
|
||||
pub fn set_title_id(&mut self, title_id: [u8; 8]) -> Result<(), TitleError> {
|
||||
self.tmd.set_title_id(title_id)?;
|
||||
self.ticket.set_title_id(title_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_cert_chain(&mut self, cert_chain: cert::CertificateChain) {
|
||||
self.cert_chain = cert_chain;
|
||||
@@ -227,3 +228,8 @@ impl Title {
|
||||
self.meta = meta.to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts bytes to the Wii's storage unit, blocks.
|
||||
pub fn bytes_to_blocks(size_bytes: usize) -> usize {
|
||||
(size_bytes as f64 / 131072.0).ceil() as usize
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ pub fn download_contents(tmd: &tmd::TMD, wiiu_endpoint: bool) -> Result<Vec<Vec<
|
||||
let content_ids: Vec<u32> = tmd.content_records.borrow().iter().map(|record| { record.content_id }).collect();
|
||||
let mut contents: Vec<Vec<u8>> = Vec::new();
|
||||
for id in content_ids {
|
||||
contents.push(download_content(tmd.title_id, id, wiiu_endpoint)?);
|
||||
contents.push(download_content(tmd.title_id(), id, wiiu_endpoint)?);
|
||||
}
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::io::{Cursor, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use sha1::{Sha1, Digest};
|
||||
use thiserror::Error;
|
||||
use crate::title::crypto;
|
||||
use crate::title::crypto::decrypt_title_key;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -43,7 +44,7 @@ pub struct Ticket {
|
||||
unknown1: [u8; 1],
|
||||
pub ticket_id: [u8; 8],
|
||||
pub console_id: [u8; 4],
|
||||
pub title_id: [u8; 8],
|
||||
title_id: [u8; 8],
|
||||
unknown2: [u8; 2],
|
||||
pub title_version: u16,
|
||||
pub permitted_titles_mask: [u8; 4],
|
||||
@@ -232,4 +233,18 @@ impl Ticket {
|
||||
self.signature_issuer = issuer.try_into().unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the Title ID of the Ticket.
|
||||
pub fn title_id(&self) -> [u8; 8] {
|
||||
self.title_id
|
||||
}
|
||||
|
||||
/// Sets a new Title ID for the Ticket. This will re-encrypt the Title Key, since the Title ID
|
||||
/// is used as the IV for decrypting the Title Key.
|
||||
pub fn set_title_id(&mut self, title_id: [u8; 8]) -> Result<(), TicketError> {
|
||||
let new_enc_title_key = crypto::encrypt_title_key(self.dec_title_key(), self.common_key_index, title_id, self.is_dev());
|
||||
self.title_key = new_enc_title_key;
|
||||
self.title_id = title_id;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,27 @@ pub enum TMDError {
|
||||
CannotFakesign,
|
||||
#[error("signature issuer string must not exceed 64 characters (was {0})")]
|
||||
IssuerTooLong(usize),
|
||||
#[error("invalid IOS Title ID, IOSes must have a Title ID beginning with 00000001 (type 'System')")]
|
||||
InvalidIOSTitleID,
|
||||
#[error("invalid IOS version `{0}`, IOS version must be in the range 3-255")]
|
||||
InvalidIOSVersion(u32),
|
||||
#[error("TMD data contains content record with invalid type `{0}`")]
|
||||
InvalidContentType(u16),
|
||||
#[error("encountered unknown title type `{0}`")]
|
||||
InvalidTitleType(String),
|
||||
#[error("TMD data is not in a valid format")]
|
||||
IO(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
pub enum TitleType {
|
||||
System,
|
||||
Game,
|
||||
Channel,
|
||||
SystemChannel,
|
||||
GameChannel,
|
||||
DLC,
|
||||
HiddenChannel,
|
||||
Unknown,
|
||||
System = 0x00000001,
|
||||
Game = 0x00010000,
|
||||
Channel = 0x00010001,
|
||||
SystemChannel = 0x00010002,
|
||||
GameChannel = 0x00010004,
|
||||
DLC = 0x00010005,
|
||||
HiddenChannel = 0x00010008,
|
||||
}
|
||||
|
||||
impl fmt::Display for TitleType {
|
||||
@@ -45,7 +51,6 @@ impl fmt::Display for TitleType {
|
||||
TitleType::GameChannel => write!(f, "GameChannel"),
|
||||
TitleType::DLC => write!(f, "DLC"),
|
||||
TitleType::HiddenChannel => write!(f, "HiddenChannel"),
|
||||
TitleType::Unknown => write!(f, "Unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,9 +102,9 @@ pub struct TMD {
|
||||
pub ca_crl_version: u8,
|
||||
pub signer_crl_version: u8,
|
||||
pub is_vwii: u8,
|
||||
pub ios_tid: [u8; 8],
|
||||
pub title_id: [u8; 8],
|
||||
pub title_type: [u8; 4],
|
||||
ios_tid: [u8; 8],
|
||||
title_id: [u8; 8],
|
||||
title_type: [u8; 4],
|
||||
pub group_id: u16,
|
||||
padding2: [u8; 2],
|
||||
region: u16,
|
||||
@@ -301,19 +306,26 @@ impl TMD {
|
||||
}
|
||||
|
||||
/// Gets the type of title described by a TMD.
|
||||
pub fn title_type(&self) -> TitleType {
|
||||
pub fn title_type(&self) -> Result<TitleType, TMDError> {
|
||||
match hex::encode(self.title_id)[..8].to_string().as_str() {
|
||||
"00000001" => TitleType::System,
|
||||
"00010000" => TitleType::Game,
|
||||
"00010001" => TitleType::Channel,
|
||||
"00010002" => TitleType::SystemChannel,
|
||||
"00010004" => TitleType::GameChannel,
|
||||
"00010005" => TitleType::DLC,
|
||||
"00010008" => TitleType::HiddenChannel,
|
||||
_ => TitleType::Unknown,
|
||||
"00000001" => Ok(TitleType::System),
|
||||
"00010000" => Ok(TitleType::Game),
|
||||
"00010001" => Ok(TitleType::Channel),
|
||||
"00010002" => Ok(TitleType::SystemChannel),
|
||||
"00010004" => Ok(TitleType::GameChannel),
|
||||
"00010005" => Ok(TitleType::DLC),
|
||||
"00010008" => Ok(TitleType::HiddenChannel),
|
||||
_ => Err(TMDError::InvalidTitleType(hex::encode(self.title_id)[..8].to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the type of title described by a TMD.
|
||||
pub fn set_title_type(&mut self, new_type: TitleType) -> Result<(), TMDError> {
|
||||
let new_type: [u8; 4] = (new_type as u32).to_be_bytes();
|
||||
self.title_type = new_type;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the type of content described by a content record in a TMD.
|
||||
pub fn content_type(&self, index: usize) -> ContentType {
|
||||
// Find possible content indices, because the provided one could exist while the indices
|
||||
@@ -353,8 +365,39 @@ impl TMD {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets whether this TMD describes a vWii title or not.
|
||||
/// Gets whether a TMD describes a vWii title or not.
|
||||
pub fn is_vwii(&self) -> bool {
|
||||
self.is_vwii == 1
|
||||
}
|
||||
|
||||
/// Gets the Title ID of a TMD.
|
||||
pub fn title_id(&self) -> [u8; 8] {
|
||||
self.title_id
|
||||
}
|
||||
|
||||
/// Sets a new Title ID for a TMD.
|
||||
pub fn set_title_id(&mut self, title_id: [u8; 8]) -> Result<(), TMDError> {
|
||||
self.title_id = title_id;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the Title ID of the IOS required by a TMD.
|
||||
pub fn ios_tid(&self) -> [u8; 8] {
|
||||
self.ios_tid
|
||||
}
|
||||
|
||||
/// Sets the Title ID of the IOS required by a TMD. The Title ID must be in the valid range of
|
||||
/// IOS versions, from 0000000100000003 to 00000001000000FF.
|
||||
pub fn set_ios_tid(&mut self, ios_tid: [u8; 8]) -> Result<(), TMDError> {
|
||||
let tid_high = &ios_tid[0..4];
|
||||
if hex::encode(tid_high) != "00000001" {
|
||||
return Err(TMDError::InvalidIOSTitleID);
|
||||
}
|
||||
let ios_version = u32::from_be_bytes(ios_tid[4..8].try_into().unwrap());
|
||||
if !(3..=255).contains(&ios_version) {
|
||||
return Err(TMDError::InvalidIOSVersion(ios_version));
|
||||
}
|
||||
self.ios_tid = ios_tid;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl WADHeader {
|
||||
// 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::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,
|
||||
_ => WADType::Installable,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user