use crate::error::Error;
use crate::smdh::ApplicationTitle;
use crate::smdh::SMDHRead;
use crate::smdh::SMDH;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::str;
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
#[derive(Debug)]
pub struct CIAHeader {
pub archive_header_size: u32,
pub type_: u16,
pub version: u16,
pub certificate_chain_size: u32,
pub ticket_size: u32,
pub tmd_file_size: u32,
pub meta_size: u32,
pub content_size: u64,
pub content_index: Vec<u8>,
}
pub struct CIA<T: Read + Seek> {
pub header: CIAHeader,
io: T,
}
pub trait CIARead {
fn read_cia_header(&mut self) -> Result<CIAHeader, Error>;
}
impl<T: Read + Seek> CIA<T> {
pub fn new(mut io: T) -> Result<Self, Error> {
let header = io.read_cia_header()?;
Ok(Self { header, io })
}
}
impl<T: Read + Seek> CIARead for T {
fn read_cia_header(&mut self) -> Result<CIAHeader, Error> {
self.seek(SeekFrom::Start(0))?;
let archive_header_size = self.read_u32::<LittleEndian>()?;
let type_ = self.read_u16::<LittleEndian>()?;
let version = self.read_u16::<LittleEndian>()?;
let certificate_chain_size = self.read_u32::<LittleEndian>()?;
let ticket_size = self.read_u32::<LittleEndian>()?;
let tmd_file_size = self.read_u32::<LittleEndian>()?;
let meta_size = self.read_u32::<LittleEndian>()?;
let content_size = self.read_u64::<LittleEndian>()?;
let content_index = Vec::<u8>::default();
Ok(CIAHeader {
archive_header_size,
type_,
version,
certificate_chain_size,
ticket_size,
tmd_file_size,
meta_size,
content_size,
content_index,
})
}
}
const fn round_align_64(size: u64) -> u64 {
if size.trailing_zeros() >= 6 {
size
} else {
(size + 0x40) & !0x3f
}
}
fn trim(string: &str) -> &str {
string.trim_matches('\0').trim()
}
fn from_utf16(data: &[u8]) -> Result<String, Error> {
let data: Vec<u16> = data
.chunks(2)
.map(|e| u16::from_le_bytes(e.try_into().unwrap()))
.collect();
String::from_utf16(&data).map_err(|_| Error::Parse("could not parse utf16 string".into()))
}
impl<T: Read + Seek> SMDHRead for CIA<T> {
fn read_smdh(&mut self) -> Result<Option<SMDH>, Error> {
if self.header.meta_size == 0 {
return Ok(None);
}
let skip = round_align_64(self.header.archive_header_size.into())
+ round_align_64(self.header.certificate_chain_size.into())
+ round_align_64(self.header.ticket_size.into())
+ round_align_64(self.header.tmd_file_size.into())
+ round_align_64(self.header.content_size);
self.io.seek(SeekFrom::Start(skip + 0x400))?;
let icon_size = self.header.meta_size - 0x400;
let mut icon_data = vec![0u8; usize::try_from(icon_size).unwrap()];
self.io.read_exact(&mut icon_data)?;
let magic = str::from_utf8(&icon_data[0..4])?.to_string();
let version = u16::from_le_bytes(icon_data[4..6].try_into().unwrap());
let mut titles = Vec::<ApplicationTitle>::default();
for i in 0..16 {
titles.push(ApplicationTitle {
short_description: trim(&from_utf16(
&icon_data[0x8 + 0x200 * i..0x8 + 0x200 * i + 0x80],
)?)
.to_string(),
long_description: trim(&from_utf16(
&icon_data[0x8 + 0x200 * i + 0x80..0x8 + 0x200 * i + 0x180],
)?)
.to_string(),
publisher: trim(&from_utf16(
&icon_data[0x8 + 0x200 * i + 0x180..0x8 + 0x200 * i + 0x200],
)?)
.to_string(),
});
}
let application_settings: [u8; 0x30] = icon_data[0x2008..0x2008 + 0x30].try_into().unwrap();
let icons: [u16; 0x1680 / 2] = icon_data[0x2040..0x2040 + 0x1680]
.chunks_exact(2)
.map(|e| u16::from_le_bytes([e[0], e[1]]))
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok(Some(SMDH {
magic,
version,
titles: titles.try_into().unwrap(),
application_settings,
icons,
}))
}
}