2021-08-25 08:22:17 -07:00
|
|
|
use std::{
|
|
|
|
fs::File,
|
|
|
|
io,
|
|
|
|
io::{Read, Seek, SeekFrom},
|
2021-09-07 21:26:22 -07:00
|
|
|
path::{Component, Path, PathBuf},
|
2021-08-25 08:22:17 -07:00
|
|
|
};
|
2021-08-23 06:48:35 -07:00
|
|
|
|
|
|
|
use aes::{Aes128, NewBlockCipher};
|
|
|
|
use binread::{derive_binread, prelude::*};
|
|
|
|
use block_modes::{block_padding::NoPadding, BlockMode, Cbc};
|
|
|
|
|
2021-08-25 08:22:17 -07:00
|
|
|
use crate::{disc::BUFFER_SIZE, io::DiscIO, streams::ReadStream, Error, Result};
|
2021-08-23 06:48:35 -07:00
|
|
|
|
|
|
|
type Aes128Cbc = Cbc<Aes128, NoPadding>;
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, BinRead)]
|
|
|
|
pub(crate) struct LBARange {
|
|
|
|
pub(crate) start_block: u32,
|
|
|
|
pub(crate) num_blocks: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive_binread]
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
#[br(magic = b"EGGS", assert(end_magic == * b"SGGE"))]
|
|
|
|
pub(crate) struct NFSHeader {
|
|
|
|
pub(crate) version: u32,
|
|
|
|
pub(crate) unk1: u32,
|
|
|
|
pub(crate) unk2: u32,
|
|
|
|
pub(crate) lba_range_count: u32,
|
|
|
|
#[br(count = 61)]
|
|
|
|
pub(crate) lba_ranges: Vec<LBARange>,
|
|
|
|
#[br(temp)]
|
|
|
|
pub(crate) end_magic: [u8; 4],
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub(crate) struct FBO {
|
|
|
|
pub(crate) file: u32,
|
|
|
|
pub(crate) block: u32,
|
|
|
|
pub(crate) l_block: u32,
|
|
|
|
pub(crate) offset: u32,
|
|
|
|
}
|
|
|
|
|
2021-08-25 08:22:17 -07:00
|
|
|
impl Default for FBO {
|
|
|
|
fn default() -> Self {
|
|
|
|
FBO { file: u32::MAX, block: u32::MAX, l_block: u32::MAX, offset: u32::MAX }
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NFSHeader {
|
|
|
|
pub(crate) fn calculate_num_files(&self) -> u32 {
|
2021-08-25 08:22:17 -07:00
|
|
|
let total_block_count = self
|
|
|
|
.lba_ranges
|
|
|
|
.iter()
|
|
|
|
.take(self.lba_range_count as usize)
|
2021-08-23 06:48:35 -07:00
|
|
|
.fold(0u32, |acc, range| acc + range.num_blocks);
|
|
|
|
(((total_block_count as u64) * 0x8000u64 + (0x200u64 + 0xF9FFFFFu64)) / 0xFA00000u64) as u32
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn logical_to_fbo(&self, offset: u64) -> FBO {
|
|
|
|
let block_div = (offset / 0x8000) as u32;
|
|
|
|
let block_off = (offset % 0x8000) as u32;
|
|
|
|
let mut block = u32::MAX;
|
|
|
|
let mut physical_block = 0u32;
|
|
|
|
for range in self.lba_ranges.iter().take(self.lba_range_count as usize) {
|
|
|
|
if block_div >= range.start_block && block_div - range.start_block < range.num_blocks {
|
|
|
|
block = physical_block + (block_div - range.start_block);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
physical_block += range.num_blocks;
|
|
|
|
}
|
|
|
|
if block == u32::MAX {
|
2021-08-25 08:22:17 -07:00
|
|
|
FBO::default()
|
2021-08-23 06:48:35 -07:00
|
|
|
} else {
|
2021-08-25 08:22:17 -07:00
|
|
|
FBO { file: block / 8000, block: block % 8000, l_block: block_div, offset: block_off }
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct DiscIONFS {
|
|
|
|
pub(crate) directory: PathBuf,
|
|
|
|
pub(crate) key: [u8; 16],
|
|
|
|
pub(crate) header: Option<NFSHeader>,
|
|
|
|
}
|
|
|
|
|
2021-09-01 13:10:22 -07:00
|
|
|
impl DiscIONFS {
|
|
|
|
pub(crate) fn new(directory: &Path) -> Result<DiscIONFS> {
|
|
|
|
let mut disc_io =
|
|
|
|
DiscIONFS { directory: directory.to_owned(), key: [0; 16], header: Option::None };
|
|
|
|
disc_io.validate_files()?;
|
|
|
|
Result::Ok(disc_io)
|
|
|
|
}
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct NFSReadStream<'a> {
|
|
|
|
disc_io: &'a DiscIONFS,
|
|
|
|
file: Option<File>,
|
|
|
|
crypto: Aes128,
|
|
|
|
// Physical address - all UINT32_MAX indicates logical zero block
|
|
|
|
phys_addr: FBO,
|
|
|
|
// Logical address
|
|
|
|
offset: u64,
|
|
|
|
// Active file stream and its offset as set in the system.
|
|
|
|
// Block is typically one ahead of the presently decrypted block.
|
|
|
|
cur_file: u32,
|
|
|
|
cur_block: u32,
|
|
|
|
buf: [u8; BUFFER_SIZE],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> NFSReadStream<'a> {
|
|
|
|
fn set_cur_file(&mut self, cur_file: u32) -> Result<()> {
|
|
|
|
if cur_file >= self.disc_io.header.as_ref().unwrap().calculate_num_files() {
|
|
|
|
return Result::Err(Error::DiscFormat("Out of bounds NFS file access".to_string()));
|
|
|
|
}
|
|
|
|
self.cur_file = cur_file;
|
|
|
|
self.cur_block = u32::MAX;
|
|
|
|
self.file = Option::from(File::open(self.disc_io.get_nfs(cur_file)?)?);
|
|
|
|
Result::Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_cur_block(&mut self, cur_block: u32) -> io::Result<()> {
|
|
|
|
self.cur_block = cur_block;
|
2021-08-25 08:22:17 -07:00
|
|
|
self.file
|
|
|
|
.as_ref()
|
|
|
|
.unwrap()
|
|
|
|
.seek(SeekFrom::Start(self.cur_block as u64 * BUFFER_SIZE as u64 + 0x200u64))?;
|
2021-08-23 06:48:35 -07:00
|
|
|
io::Result::Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_phys_addr(&mut self, phys_addr: FBO) -> Result<()> {
|
|
|
|
// If we're just changing the offset, nothing else needs to be done
|
|
|
|
if self.phys_addr.file == phys_addr.file && self.phys_addr.block == phys_addr.block {
|
|
|
|
self.phys_addr.offset = phys_addr.offset;
|
|
|
|
return Result::Ok(());
|
|
|
|
}
|
|
|
|
self.phys_addr = phys_addr;
|
|
|
|
|
|
|
|
// Set logical zero block
|
|
|
|
if phys_addr.file == u32::MAX {
|
|
|
|
self.buf.fill(0u8);
|
|
|
|
return Result::Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make necessary file and block current with system
|
|
|
|
if phys_addr.file != self.cur_file {
|
|
|
|
self.set_cur_file(phys_addr.file)?;
|
|
|
|
}
|
|
|
|
if phys_addr.block != self.cur_block {
|
|
|
|
self.set_cur_block(phys_addr.block)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read block, handling 0x200 overlap case
|
|
|
|
if phys_addr.block == 7999 {
|
|
|
|
self.file.as_ref().unwrap().read(&mut self.buf[..BUFFER_SIZE - 0x200])?;
|
|
|
|
self.set_cur_file(self.cur_file + 1)?;
|
|
|
|
self.file.as_ref().unwrap().read(&mut self.buf[BUFFER_SIZE - 0x200..])?;
|
|
|
|
self.cur_block = 0;
|
|
|
|
} else {
|
|
|
|
self.file.as_ref().unwrap().read(&mut self.buf)?;
|
|
|
|
self.cur_block += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt
|
2021-08-25 08:22:17 -07:00
|
|
|
#[rustfmt::skip]
|
2021-08-23 06:48:35 -07:00
|
|
|
let iv: [u8; 16] = [
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
(phys_addr.l_block & 0xFF) as u8,
|
2021-08-23 13:51:05 -07:00
|
|
|
((phys_addr.l_block >> 8) & 0xFF) as u8,
|
|
|
|
((phys_addr.l_block >> 16) & 0xFF) as u8,
|
|
|
|
((phys_addr.l_block >> 24) & 0xFF) as u8,
|
2021-08-23 06:48:35 -07:00
|
|
|
];
|
2021-08-25 08:22:17 -07:00
|
|
|
Aes128Cbc::new(self.crypto.clone(), &iv.into()).decrypt(&mut self.buf)?;
|
2021-08-23 06:48:35 -07:00
|
|
|
|
|
|
|
Result::Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_logical_addr(&mut self, addr: u64) -> Result<()> {
|
|
|
|
self.set_phys_addr(self.disc_io.header.as_ref().unwrap().logical_to_fbo(addr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Read for NFSReadStream<'a> {
|
|
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
|
|
let mut rem = buf.len();
|
|
|
|
let mut read: usize = 0;
|
|
|
|
while rem > 0 {
|
|
|
|
let mut read_size = rem;
|
2021-08-25 08:22:17 -07:00
|
|
|
let block_offset: usize =
|
|
|
|
if self.phys_addr.offset == u32::MAX { 0 } else { self.phys_addr.offset as usize };
|
2021-08-23 06:48:35 -07:00
|
|
|
if read_size + block_offset > BUFFER_SIZE {
|
|
|
|
read_size = BUFFER_SIZE - block_offset
|
|
|
|
}
|
|
|
|
buf[read..read + read_size]
|
|
|
|
.copy_from_slice(&mut self.buf[block_offset..block_offset + read_size]);
|
|
|
|
read += read_size;
|
|
|
|
rem -= read_size;
|
|
|
|
self.offset += read_size as u64;
|
2021-08-25 08:22:17 -07:00
|
|
|
self.set_logical_addr(self.offset).map_err(|v| match v {
|
|
|
|
Error::Io(_, v) => v,
|
|
|
|
_ => io::Error::from(io::ErrorKind::Other),
|
|
|
|
})?;
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
|
|
|
io::Result::Ok(read)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Seek for NFSReadStream<'a> {
|
|
|
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
|
|
|
self.offset = match pos {
|
|
|
|
SeekFrom::Start(v) => v,
|
2021-08-23 07:13:40 -07:00
|
|
|
SeekFrom::End(v) => (self.stable_stream_len()? as i64 + v) as u64,
|
2021-08-23 06:48:35 -07:00
|
|
|
SeekFrom::Current(v) => (self.offset as i64 + v) as u64,
|
|
|
|
};
|
2021-08-25 08:22:17 -07:00
|
|
|
self.set_logical_addr(self.offset).map_err(|v| match v {
|
|
|
|
Error::Io(_, v) => v,
|
|
|
|
_ => io::Error::from(io::ErrorKind::Other),
|
|
|
|
})?;
|
2021-08-23 06:48:35 -07:00
|
|
|
io::Result::Ok(self.offset)
|
|
|
|
}
|
|
|
|
|
2021-08-25 08:22:17 -07:00
|
|
|
fn stream_position(&mut self) -> io::Result<u64> { io::Result::Ok(self.offset) }
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
|
|
|
|
2021-08-23 07:13:40 -07:00
|
|
|
impl<'a> ReadStream for NFSReadStream<'a> {
|
2021-08-25 08:22:17 -07:00
|
|
|
fn stable_stream_len(&mut self) -> io::Result<u64> { todo!() }
|
2021-09-01 13:10:22 -07:00
|
|
|
|
|
|
|
fn as_dyn(&mut self) -> &mut dyn ReadStream { self }
|
2021-08-23 07:13:40 -07:00
|
|
|
}
|
2021-08-23 06:48:35 -07:00
|
|
|
|
|
|
|
impl DiscIO for DiscIONFS {
|
|
|
|
fn begin_read_stream(&self, offset: u64) -> io::Result<Box<dyn ReadStream + '_>> {
|
|
|
|
io::Result::Ok(Box::from(NFSReadStream {
|
|
|
|
disc_io: self,
|
|
|
|
file: Option::None,
|
|
|
|
crypto: Aes128::new(&self.key.into()),
|
2021-08-25 08:22:17 -07:00
|
|
|
phys_addr: FBO::default(),
|
2021-08-23 06:48:35 -07:00
|
|
|
offset,
|
|
|
|
cur_file: u32::MAX,
|
|
|
|
cur_block: u32::MAX,
|
|
|
|
buf: [0; BUFFER_SIZE],
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn has_wii_crypto(&self) -> bool { false }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DiscIONFS {
|
|
|
|
fn get_path<P: AsRef<Path>>(&self, path: P) -> PathBuf {
|
|
|
|
let mut buf = self.directory.clone();
|
2021-09-07 21:26:22 -07:00
|
|
|
for component in path.as_ref().components() {
|
|
|
|
match component {
|
|
|
|
Component::ParentDir => {
|
|
|
|
buf.pop();
|
|
|
|
}
|
|
|
|
_ => buf.push(component),
|
|
|
|
}
|
|
|
|
}
|
2021-08-23 06:48:35 -07:00
|
|
|
buf
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_nfs(&self, num: u32) -> Result<PathBuf> {
|
|
|
|
let path = self.get_path(format!("hif_{:06}.nfs", num));
|
|
|
|
if path.exists() {
|
|
|
|
Result::Ok(path)
|
|
|
|
} else {
|
|
|
|
Result::Err(Error::DiscFormat(format!("Failed to locate {}", path.to_string_lossy())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn validate_files(&mut self) -> Result<()> {
|
|
|
|
{
|
|
|
|
// Load key file
|
2021-09-07 21:26:22 -07:00
|
|
|
let primary_key_path =
|
|
|
|
self.get_path(["..", "code", "htk.bin"].iter().collect::<PathBuf>());
|
|
|
|
let secondary_key_path = self.get_path("htk.bin");
|
|
|
|
let mut key_path = primary_key_path.canonicalize();
|
|
|
|
if key_path.is_err() {
|
|
|
|
key_path = secondary_key_path.canonicalize();
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
2021-09-07 21:26:22 -07:00
|
|
|
if key_path.is_err() {
|
2021-08-23 06:48:35 -07:00
|
|
|
return Result::Err(Error::DiscFormat(format!(
|
|
|
|
"Failed to locate {} or {}",
|
2021-09-07 21:26:22 -07:00
|
|
|
primary_key_path.to_string_lossy(),
|
|
|
|
secondary_key_path.to_string_lossy()
|
2021-08-23 06:48:35 -07:00
|
|
|
)));
|
|
|
|
}
|
2021-09-07 21:26:22 -07:00
|
|
|
let resolved_path = key_path.unwrap();
|
|
|
|
File::open(resolved_path.as_path())
|
2021-08-25 08:22:17 -07:00
|
|
|
.map_err(|v| {
|
2021-09-07 21:26:22 -07:00
|
|
|
Error::Io(format!("Failed to open {}", resolved_path.to_string_lossy()), v)
|
2021-08-25 08:22:17 -07:00
|
|
|
})?
|
2021-08-23 06:48:35 -07:00
|
|
|
.read(&mut self.key)
|
2021-08-25 08:22:17 -07:00
|
|
|
.map_err(|v| {
|
2021-09-07 21:26:22 -07:00
|
|
|
Error::Io(format!("Failed to read {}", resolved_path.to_string_lossy()), v)
|
2021-08-25 08:22:17 -07:00
|
|
|
})?;
|
2021-08-23 06:48:35 -07:00
|
|
|
}
|
|
|
|
{
|
|
|
|
// Load header from first file
|
|
|
|
let header: NFSHeader = File::open(self.get_nfs(0)?)?.read_be()?;
|
|
|
|
// Ensure remaining files exist
|
|
|
|
for i in 1..header.calculate_num_files() {
|
|
|
|
self.get_nfs(i)?;
|
|
|
|
}
|
|
|
|
self.header = Option::from(header)
|
|
|
|
}
|
|
|
|
Result::Ok(())
|
|
|
|
}
|
|
|
|
}
|