diff --git a/src/bin/rustii/info.rs b/src/bin/rustii/info.rs index 86d4473..2672320 100644 --- a/src/bin/rustii/info.rs +++ b/src/bin/rustii/info.rs @@ -11,14 +11,14 @@ use crate::filetypes::{WiiFileType, identify_file_type}; fn print_tmd_info(tmd: tmd::TMD) { // Print all important keys from the TMD. println!("Title Info"); - println!(" Title ID: {}", hex::encode(tmd.title_id)); + println!(" Title ID: {}", hex::encode(tmd.title_id).to_uppercase()); println!(" Title Version: {}", tmd.title_version); println!(" TMD Version: {}", tmd.tmd_version); if hex::encode(tmd.ios_tid) == "0000000000000000" { println!(" Required IOS: N/A"); } else if hex::encode(tmd.ios_tid) != "0000000100000001" { - println!(" Required IOS: IOS{} ({})", tmd.ios_tid.last().unwrap(), hex::encode(tmd.ios_tid)); + println!(" Required IOS: IOS{} ({})", tmd.ios_tid.last().unwrap(), hex::encode(tmd.ios_tid).to_uppercase()); } let signature_issuer = String::from_utf8(Vec::from(tmd.signature_issuer)).unwrap_or_default(); if signature_issuer.contains("CP00000004") { @@ -40,11 +40,11 @@ fn print_tmd_info(tmd: tmd::TMD) { else { println!(" Certificate Info: {} (Unknown)", signature_issuer); } - println!(" Region: WIP"); - println!(" Title Type: WIP"); + println!(" Region: {}", tmd.region()); + println!(" Title Type: {}", tmd.title_type()); println!(" vWii Title: {}", tmd.is_vwii != 0); - println!(" DVD Video Access: WIP"); - println!(" AHB Access: WIP"); + println!(" DVD Video Access: {}", tmd.check_access_right(tmd::AccessRight::DVDVideo)); + println!(" AHB Access: {}", tmd.check_access_right(tmd::AccessRight::AHB)); println!(" Fakesigned: {}", tmd.is_fakesigned()); println!("\nContent Info"); println!(" Total Contents: {}", tmd.num_contents); @@ -53,7 +53,7 @@ fn print_tmd_info(tmd: tmd::TMD) { for content in tmd.content_records { println!(" Content Index: {}", content.index); println!(" Content ID: {:08X}", content.content_id); - println!(" Content Type: WIP"); + println!(" Content Type: {}", content.content_type); println!(" Content Size: {} bytes", content.content_size); println!(" Content Hash: {}", hex::encode(content.content_hash)); } @@ -62,7 +62,7 @@ fn print_tmd_info(tmd: tmd::TMD) { fn print_ticket_info(ticket: ticket::Ticket) { // Print all important keys from the Ticket. println!("Ticket Info"); - println!(" Title ID: {}", hex::encode(ticket.title_id)); + println!(" Title ID: {}", hex::encode(ticket.title_id).to_uppercase()); println!(" Title Version: {}", ticket.title_version); println!(" Ticket Version: {}", ticket.ticket_version); let signature_issuer = String::from_utf8(Vec::from(ticket.signature_issuer)).unwrap_or_default(); diff --git a/src/title/tmd.rs b/src/title/tmd.rs index 2dc76a8..a72195a 100644 --- a/src/title/tmd.rs +++ b/src/title/tmd.rs @@ -6,12 +6,14 @@ use std::error::Error; use std::fmt; use std::io::{Cursor, Read, Write}; +use std::ops::Index; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use sha1::{Sha1, Digest}; #[derive(Debug)] pub enum TMDError { CannotFakesign, + InvalidContentType(u16), IOError(std::io::Error), } @@ -19,6 +21,7 @@ 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::InvalidContentType(_) => "The TMD contains content with an invalid type.", TMDError::IOError(_) => "The provided TMD data was invalid.", }; f.write_str(description) @@ -27,12 +30,63 @@ impl fmt::Display for TMDError { impl Error for TMDError {} -#[derive(Debug)] -#[derive(Clone)] +pub enum TitleType { + System, + Game, + Channel, + SystemChannel, + GameChannel, + DLC, + HiddenChannel, + Unknown, +} + +impl fmt::Display for TitleType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TitleType::System => write!(f, "System"), + TitleType::Game => write!(f, "Game"), + TitleType::Channel => write!(f, "Channel"), + TitleType::SystemChannel => write!(f, "SystemChannel"), + TitleType::GameChannel => write!(f, "GameChannel"), + TitleType::DLC => write!(f, "DLC"), + TitleType::HiddenChannel => write!(f, "HiddenChannel"), + TitleType::Unknown => write!(f, "Unknown"), + } + } +} + +#[derive(Debug, Clone)] +pub enum ContentType { + Normal, + Development, + HashTree, + DLC, + Shared, +} + +impl fmt::Display for ContentType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ContentType::Normal => write!(f, "Normal"), + ContentType::Development => write!(f, "Development/Unknown"), + ContentType::HashTree => write!(f, "Hash Tree"), + ContentType::DLC => write!(f, "DLC"), + ContentType::Shared => write!(f, "Shared"), + } + } +} + +pub enum AccessRight { + AHB, + DVDVideo, +} + +#[derive(Debug, Clone)] pub struct ContentRecord { pub content_id: u32, pub index: u16, - pub content_type: u16, + pub content_type: ContentType, pub content_size: u64, pub content_hash: [u8; 20], } @@ -52,7 +106,7 @@ pub struct TMD { pub title_type: [u8; 4], pub group_id: u16, padding2: [u8; 2], - pub region: u16, + region: u16, pub ratings: [u8; 16], reserved1: [u8; 12], pub ipc_mask: [u8; 12], @@ -112,7 +166,15 @@ impl TMD { for _ in 0..num_contents { let content_id = buf.read_u32::().map_err(TMDError::IOError)?; let index = buf.read_u16::().map_err(TMDError::IOError)?; - let content_type = buf.read_u16::().map_err(TMDError::IOError)?; + let type_int = buf.read_u16::().map_err(TMDError::IOError)?; + let content_type = match type_int { + 1 => ContentType::Normal, + 2 => ContentType::Development, + 3 => ContentType::HashTree, + 16385 => ContentType::DLC, + 32769 => ContentType::Shared, + _ => return Err(TMDError::InvalidContentType(type_int)) + }; let content_size = buf.read_u64::().map_err(TMDError::IOError)?; let mut content_hash = [0u8; 20]; buf.read_exact(&mut content_hash).map_err(TMDError::IOError)?; @@ -182,7 +244,13 @@ impl TMD { for content in &self.content_records { buf.write_u32::(content.content_id)?; buf.write_u16::(content.index)?; - buf.write_u16::(content.content_type)?; + match content.content_type { + ContentType::Normal => { buf.write_u16::(1)?; }, + ContentType::Development => { buf.write_u16::(2)?; }, + ContentType::HashTree => { buf.write_u16::(3)?; }, + ContentType::DLC => { buf.write_u16::(16385)?; }, + ContentType::Shared => { buf.write_u16::(32769)?; } + } buf.write_u64::(content.content_size)?; buf.write_all(&content.content_hash)?; } @@ -221,4 +289,52 @@ impl TMD { } Ok(()) } + + pub fn region(&self) -> &str { + match self.region { + 0 => "JPN", + 1 => "USA", + 2 => "EUR", + 3 => "None", + 4 => "KOR", + _ => "Unknown", + } + } + + pub fn title_type(&self) -> TitleType { + 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, + } + } + + pub fn content_type(&self, index: usize) -> ContentType { + // Find possible content indices, because the provided one could exist while the indices + // are out of order, which could cause problems finding the content. + let mut content_indices = Vec::new(); + for record in &self.content_records { + content_indices.push(record.index); + } + let target_index = content_indices.index(index); + match self.content_records[*target_index as usize].content_type { + ContentType::Normal => ContentType::Normal, + ContentType::Development => ContentType::Development, + ContentType::HashTree => ContentType::HashTree, + ContentType::DLC => ContentType::DLC, + ContentType::Shared => ContentType::Shared, + } + } + + pub fn check_access_right(&self, right: AccessRight) -> bool { + match right { + AccessRight::AHB => (self.access_rights & (1 << 0)) != 0, + AccessRight::DVDVideo => (self.access_rights & (1 << 1)) != 0, + } + } }