use crate::error::Error;
use crate::ncch::NCCH;
use crate::ncch::NCCHRead;
use std::fmt;
use std::fmt::Display;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use byteorder::ByteOrder;
use byteorder::LittleEndian;
use byteorder::ReadBytesExt;
#[derive(Debug)]
pub enum NCCHPartitionCryptoType {
None,
Twl,
Ctr,
Snake,
}
impl TryFrom<u8> for NCCHPartitionCryptoType {
type Error = Error;
fn try_from(data: u8) -> Result<Self, Error> {
match data {
0 => Ok(Self::None),
1 => Ok(Self::Twl),
2 => Ok(Self::Ctr),
3 => Ok(Self::Snake),
_ => Err(Error::Parse(
"unable to parse NCCH partition crypt type".into(),
)),
}
}
}
impl Display for NCCHPartitionCryptoType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Twl => write!(f, "TWL"),
Self::Ctr => write!(f, "CTR"),
Self::Snake => write!(f, "SNAKE"),
}
}
}
#[derive(Debug)]
pub enum SavegameKeyYMethod {
HashedKeyY,
NewKeyY1,
NewKeyY2,
}
impl TryFrom<u8> for SavegameKeyYMethod {
type Error = Error;
fn try_from(data: u8) -> Result<Self, Error> {
match data {
0 => Ok(Self::HashedKeyY),
1 => Ok(Self::NewKeyY1),
0x0A => Ok(Self::NewKeyY2),
_ => Err(Error::Parse("failed to parse savegame key Y method".into())),
}
}
}
#[derive(Debug)]
pub struct NCCHPartitionCryptoFlags {
pub savegame_keyy_method: SavegameKeyYMethod,
pub use_keyy_hash: bool,
pub use_keyy_hash_method: bool,
}
impl TryFrom<[u8; 8]> for NCCHPartitionCryptoFlags {
type Error = Error;
fn try_from(data: [u8; 8]) -> Result<Self, Error> {
Ok(Self {
savegame_keyy_method: data[1].try_into()?,
use_keyy_hash: data[3] != 0,
use_keyy_hash_method: data[7] != 0,
})
}
}
#[derive(Debug)]
pub struct NCCHPartitionFlags {
pub backup_write_wait_time: u8,
pub media_card_device: MediaCardDevice,
pub media_platform: MediaPlatform,
pub media_type: MediaType,
pub media_unit_size: u32,
}
#[derive(Debug)]
pub enum MediaPlatform {
Ctr,
Snake,
}
impl Display for MediaPlatform {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Ctr => write!(f, "Nintendo 3DS"),
Self::Snake => write!(f, "development Nintendo 3DS"),
}
}
}
impl TryFrom<u8> for MediaPlatform {
type Error = Error;
fn try_from(data: u8) -> Result<Self, Error> {
match data {
1 => Ok(Self::Ctr),
_ => Err(Error::Parse("could not parse media platform".into())),
}
}
}
#[derive(Debug)]
pub enum NCCHPartitionFilesystemType {
None,
Normal,
Firm,
AgbFirmSave,
}
impl Display for NCCHPartitionFilesystemType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Normal => write!(f, "normal"),
Self::Firm => write!(f, "FIRM"),
Self::AgbFirmSave => write!(f, "AGB_FIRM save"),
}
}
}
impl TryFrom<u8> for NCCHPartitionFilesystemType {
type Error = Error;
fn try_from(data: u8) -> Result<Self, Error> {
match data {
0 => Ok(Self::None),
1 => Ok(Self::Normal),
3 => Ok(Self::Firm),
4 => Ok(Self::AgbFirmSave),
_ => Err(Error::Parse(
"could not parse the NCCH FS partition type".into(),
)),
}
}
}
#[derive(Debug)]
pub struct NCCHPartitionHeader {
pub index: usize,
pub offset: u32,
pub size: u32,
pub title_id: u64,
pub crypt_type: NCCHPartitionCryptoType,
pub filesystem_type: NCCHPartitionFilesystemType,
}
#[derive(Debug)]
pub enum MediaCardDevice {
Null,
NORFlash,
None,
BT,
}
#[derive(Debug)]
pub enum MediaType {
InnerDevice,
Card1,
Card2,
ExtendedDevice,
}
impl Display for MediaType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InnerDevice => write!(f, "inner device"),
Self::Card1 => write!(f, "card1"),
Self::Card2 => write!(f, "card2"),
Self::ExtendedDevice => write!(f, "extended device"),
}
}
}
impl TryFrom<u8> for MediaType {
type Error = Error;
fn try_from(data: u8) -> Result<Self, Error> {
match data {
0 => Ok(Self::InnerDevice),
1 => Ok(Self::Card1),
2 => Ok(Self::Card2),
3 => Ok(Self::ExtendedDevice),
_ => Err(Error::Parse("could not parse media type".into())),
}
}
}
impl Display for MediaCardDevice {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Null => write!(f, "null"),
Self::NORFlash => write!(f, "NOR flash"),
Self::None => write!(f, "none"),
Self::BT => write!(f, "BT"),
}
}
}
impl TryFrom<u8> for MediaCardDevice {
type Error = Error;
fn try_from(data: u8) -> Result<Self, Error> {
match data {
0 => Ok(Self::Null),
1 => Ok(Self::NORFlash),
2 => Ok(Self::None),
3 => Ok(Self::BT),
_ => Err(Self::Error::Parse(
"could not parse media card device".into(),
)),
}
}
}
impl TryFrom<[u8; 8]> for NCCHPartitionFlags {
type Error = Error;
fn try_from(data: [u8; 8]) -> Result<Self, Error> {
let media_card_device: MediaCardDevice = if data[3] == 0 {
data[7].try_into()?
} else {
data[3].try_into()?
};
Ok(Self {
backup_write_wait_time: data[0],
media_card_device,
media_platform: data[4].try_into()?,
media_type: data[5].try_into()?,
media_unit_size: 0x200 * (1 << data[6]),
})
}
}
#[derive(Debug, Copy, Clone)]
pub struct NCCHPartitionEntry {
pub offset: u32,
pub size: u32,
}
#[derive(Debug)]
pub struct NCSD {
pub signature: [u8; 0x100],
pub magic: [u8; 4],
pub size: u32,
pub main_title_id: u64,
pub exheader_hash: [u8; 0x20],
pub header_size: u32,
pub sector_zero_offset: u32,
pub partition_flags: NCCHPartitionFlags,
pub partition_crypto_flags: NCCHPartitionCryptoFlags,
pub ncch: Vec<(NCCHPartitionHeader, NCCH)>,
}
impl From<[u8; 8]> for NCCHPartitionEntry {
fn from(data: [u8; 8]) -> Self {
let offset = LittleEndian::read_u32(&data[..4]);
let size = LittleEndian::read_u32(&data[4..8]);
Self { offset, size }
}
}
pub trait NCSDRead {
fn read_ncsd(&mut self) -> Result<NCSD, Error>;
}
impl<T: Read + Seek> NCSDRead for T {
fn read_ncsd(&mut self) -> Result<NCSD, Error> {
self.seek(SeekFrom::Start(0))?;
let mut signature = [0u8; 0x100];
self.read_exact(&mut signature)?;
let mut magic = [0u8; 4];
self.read_exact(&mut magic)?;
let size = self.read_u32::<LittleEndian>()?;
let main_title_id = self.read_u64::<LittleEndian>()?;
let mut partition_fs_type = [0u8; 0x8];
self.read_exact(&mut partition_fs_type)?;
let mut partition_crypt_type = [0u8; 0x8];
self.read_exact(&mut partition_crypt_type)?;
let mut partition_table = [NCCHPartitionEntry { offset: 0, size: 0 }; 8];
for entry in &mut partition_table {
let mut data = [0u8; 8];
self.read_exact(&mut data)?;
*entry = data.into();
}
self.seek(SeekFrom::Start(0x160))?;
let mut exheader_hash = [0u8; 0x20];
self.read_exact(&mut exheader_hash)?;
let header_size = self.read_u32::<LittleEndian>()?;
let sector_zero_offset = self.read_u32::<LittleEndian>()?;
let mut partition_flags = [0u8; 8];
self.read_exact(&mut partition_flags)?;
let partition_crypto_flags: NCCHPartitionCryptoFlags = partition_flags.try_into()?;
let partition_flags: NCCHPartitionFlags = partition_flags.try_into()?;
let mut partition_title_id_table = [0u64; 8];
for elem in &mut partition_title_id_table {
*elem = self.read_u64::<LittleEndian>()?;
}
let mut ncch = vec![];
for index in 0..8 {
if partition_table[index].size == 0 {
continue;
}
let ncch_partition = self.read_ncch(partition_table[index])?.unwrap();
ncch.push((
NCCHPartitionHeader {
index,
offset: partition_table[index].offset,
size: partition_table[index].size,
title_id: partition_title_id_table[index],
filesystem_type: partition_fs_type[index].try_into()?,
crypt_type: partition_crypt_type[index].try_into()?,
},
ncch_partition,
));
}
Ok(NCSD {
signature,
magic,
size,
main_title_id,
exheader_hash,
header_size,
sector_zero_offset,
partition_flags,
partition_crypto_flags,
ncch,
})
}
}
impl NCSD {
#[must_use]
pub fn find_exefs_ncch(&self) -> Option<&NCCH> {
if let Some(ncch) = self.ncch.iter().find(|&ncch| ncch.1.exefs_size != 0) {
return Some(&ncch.1);
}
None
}
}