Add Disc::new_stream/new_stream_with_options

Allows opening a disc image from a custom stream,
rather than a filesystem path.
This commit is contained in:
Luke Street 2024-10-02 21:50:59 -06:00
parent 5ad514d59c
commit 370d03fa9a
13 changed files with 222 additions and 127 deletions

View File

@ -61,7 +61,7 @@ impl Node {
/// For directories, this is the parent node index in the FST. /// For directories, this is the parent node index in the FST.
#[inline] #[inline]
pub fn offset(&self, is_wii: bool) -> u64 { pub fn offset(&self, is_wii: bool) -> u64 {
if is_wii && self.kind == 0 { if is_wii && self.is_file() {
self.offset.get() as u64 * 4 self.offset.get() as u64 * 4
} else { } else {
self.offset.get() as u64 self.offset.get() as u64
@ -108,7 +108,7 @@ impl<'a> Fst<'a> {
/// Get the name of a node. /// Get the name of a node.
#[allow(clippy::missing_inline_in_public_items)] #[allow(clippy::missing_inline_in_public_items)]
pub fn get_name(&self, node: &Node) -> Result<Cow<str>, String> { pub fn get_name(&self, node: &Node) -> Result<Cow<'a, str>, String> {
let name_buf = self.string_table.get(node.name_offset() as usize..).ok_or_else(|| { let name_buf = self.string_table.get(node.name_offset() as usize..).ok_or_else(|| {
format!( format!(
"FST: name offset {} out of bounds (string table size: {})", "FST: name offset {} out of bounds (string table size: {})",
@ -128,7 +128,7 @@ impl<'a> Fst<'a> {
/// Finds a particular file or directory by path. /// Finds a particular file or directory by path.
#[allow(clippy::missing_inline_in_public_items)] #[allow(clippy::missing_inline_in_public_items)]
pub fn find(&self, path: &str) -> Option<(usize, &Node)> { pub fn find(&self, path: &str) -> Option<(usize, &'a Node)> {
let mut split = path.trim_matches('/').split('/'); let mut split = path.trim_matches('/').split('/');
let mut current = split.next()?; let mut current = split.next()?;
let mut idx = 1; let mut idx = 1;

View File

@ -11,6 +11,7 @@ use super::{
PartitionMeta, BI2_SIZE, BOOT_SIZE, SECTOR_SIZE, PartitionMeta, BI2_SIZE, BOOT_SIZE, SECTOR_SIZE,
}; };
use crate::{ use crate::{
disc::streams::OwnedFileStream,
io::block::{Block, BlockIO}, io::block::{Block, BlockIO},
util::read::{read_box, read_box_slice, read_vec}, util::read::{read_box, read_box_slice, read_vec},
Result, ResultContext, Result, ResultContext,
@ -132,6 +133,16 @@ impl PartitionBase for PartitionGC {
} }
FileStream::new(self, node.offset(false), node.length()) FileStream::new(self, node.offset(false), node.length())
} }
fn into_open_file(self: Box<Self>, node: &Node) -> io::Result<OwnedFileStream> {
if !node.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Node is not a file".to_string(),
));
}
OwnedFileStream::new(self, node.offset(false), node.length())
}
} }
pub(crate) fn read_part_meta( pub(crate) fn read_part_meta(

View File

@ -23,7 +23,7 @@ pub(crate) mod streams;
pub(crate) mod wii; pub(crate) mod wii;
pub use fst::{Fst, Node, NodeKind}; pub use fst::{Fst, Node, NodeKind};
pub use streams::FileStream; pub use streams::{FileStream, OwnedFileStream, WindowedStream};
pub use wii::{SignedHeader, Ticket, TicketLimit, TmdHeader}; pub use wii::{SignedHeader, Ticket, TicketLimit, TmdHeader};
/// Size in bytes of a disc sector. /// Size in bytes of a disc sector.
@ -308,6 +308,35 @@ pub trait PartitionBase: DynClone + BufRead + Seek + Send + Sync {
/// } /// }
/// ``` /// ```
fn open_file(&mut self, node: &Node) -> io::Result<FileStream>; fn open_file(&mut self, node: &Node) -> io::Result<FileStream>;
/// Consumes the partition instance and returns a windowed stream.
///
/// # Examples
///
/// ```no_run
/// use std::io::Read;
///
/// use nod::{Disc, PartitionKind, OwnedFileStream};
///
/// fn main() -> nod::Result<()> {
/// let disc = Disc::new("path/to/file.iso")?;
/// let mut partition = disc.open_partition_kind(PartitionKind::Data)?;
/// let meta = partition.meta()?;
/// let fst = meta.fst()?;
/// if let Some((_, node)) = fst.find("/disc.tgc") {
/// let file: OwnedFileStream = partition
/// .clone() // Clone the Box<dyn PartitionBase>
/// .into_open_file(node) // Get an OwnedFileStream
/// .expect("Failed to open file stream");
/// // Open the inner disc image using the owned stream
/// let inner_disc = Disc::new_stream(Box::new(file))
/// .expect("Failed to open inner disc");
/// // ...
/// }
/// Ok(())
/// }
/// ```
fn into_open_file(self: Box<Self>, node: &Node) -> io::Result<OwnedFileStream>;
} }
dyn_clone::clone_trait_object!(PartitionBase); dyn_clone::clone_trait_object!(PartitionBase);

View File

@ -7,30 +7,39 @@ use std::{
use super::PartitionBase; use super::PartitionBase;
/// A file read stream for a [`PartitionBase`]. /// A file read stream borrowing a [`PartitionBase`].
pub struct FileStream<'a> { pub type FileStream<'a> = WindowedStream<&'a mut dyn PartitionBase>;
base: &'a mut dyn PartitionBase,
/// A file read stream owning a [`PartitionBase`].
pub type OwnedFileStream = WindowedStream<Box<dyn PartitionBase>>;
/// A read stream with a fixed window.
#[derive(Clone)]
pub struct WindowedStream<T>
where T: BufRead + Seek
{
base: T,
pos: u64, pos: u64,
begin: u64, begin: u64,
end: u64, end: u64,
} }
impl FileStream<'_> { impl<T> WindowedStream<T>
/// Creates a new file stream with offset and size. where T: BufRead + Seek
{
/// Creates a new windowed stream with offset and size.
/// ///
/// Seeks underlying stream immediately. /// Seeks underlying stream immediately.
#[inline] #[inline]
pub(crate) fn new( pub fn new(mut base: T, offset: u64, size: u64) -> io::Result<Self> {
base: &mut dyn PartitionBase,
offset: u64,
size: u64,
) -> io::Result<FileStream> {
base.seek(SeekFrom::Start(offset))?; base.seek(SeekFrom::Start(offset))?;
Ok(FileStream { base, pos: offset, begin: offset, end: offset + size }) Ok(Self { base, pos: offset, begin: offset, end: offset + size })
} }
} }
impl<'a> Read for FileStream<'a> { impl<T> Read for WindowedStream<T>
where T: BufRead + Seek
{
#[inline] #[inline]
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> { fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
let buf = self.fill_buf()?; let buf = self.fill_buf()?;
@ -41,7 +50,9 @@ impl<'a> Read for FileStream<'a> {
} }
} }
impl<'a> BufRead for FileStream<'a> { impl<T> BufRead for WindowedStream<T>
where T: BufRead + Seek
{
#[inline] #[inline]
fn fill_buf(&mut self) -> io::Result<&[u8]> { fn fill_buf(&mut self) -> io::Result<&[u8]> {
let limit = self.end.saturating_sub(self.pos); let limit = self.end.saturating_sub(self.pos);
@ -60,7 +71,9 @@ impl<'a> BufRead for FileStream<'a> {
} }
} }
impl<'a> Seek for FileStream<'a> { impl<T> Seek for WindowedStream<T>
where T: BufRead + Seek
{
#[inline] #[inline]
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let mut pos = match pos { let mut pos = match pos {

View File

@ -14,6 +14,7 @@ use super::{
}; };
use crate::{ use crate::{
array_ref, array_ref,
disc::streams::OwnedFileStream,
io::{ io::{
aes_decrypt, aes_decrypt,
block::{Block, BlockIO, PartitionInfo}, block::{Block, BlockIO, PartitionInfo},
@ -497,4 +498,14 @@ impl PartitionBase for PartitionWii {
} }
FileStream::new(self, node.offset(true), node.length()) FileStream::new(self, node.offset(true), node.length())
} }
fn into_open_file(self: Box<Self>, node: &Node) -> io::Result<OwnedFileStream> {
if !node.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Node is not a file".to_string(),
));
}
OwnedFileStream::new(self, node.offset(true), node.length())
}
} }

View File

@ -1,4 +1,9 @@
use std::{cmp::min, fs, fs::File, io, path::Path}; use std::{
cmp::min,
fs, io,
io::{Read, Seek},
path::Path,
};
use dyn_clone::DynClone; use dyn_clone::DynClone;
use zerocopy::transmute_ref; use zerocopy::transmute_ref;
@ -10,11 +15,18 @@ use crate::{
wii::{WiiPartitionHeader, HASHES_SIZE, SECTOR_DATA_SIZE}, wii::{WiiPartitionHeader, HASHES_SIZE, SECTOR_DATA_SIZE},
SECTOR_SIZE, SECTOR_SIZE,
}, },
io::{aes_decrypt, aes_encrypt, KeyBytes, MagicBytes}, io::{aes_decrypt, aes_encrypt, split::SplitFileReader, KeyBytes, MagicBytes},
util::{lfg::LaggedFibonacci, read::read_from}, util::{lfg::LaggedFibonacci, read::read_from},
DiscHeader, DiscMeta, Error, PartitionHeader, PartitionKind, Result, ResultContext, DiscHeader, DiscMeta, Error, PartitionHeader, PartitionKind, Result, ResultContext,
}; };
/// Required trait bounds for reading disc images.
pub trait DiscStream: Read + Seek + DynClone + Send + Sync {}
impl<T> DiscStream for T where T: Read + Seek + DynClone + Send + Sync + ?Sized {}
dyn_clone::clone_trait_object!(DiscStream);
/// Block I/O trait for reading disc images. /// Block I/O trait for reading disc images.
pub trait BlockIO: DynClone + Send + Sync { pub trait BlockIO: DynClone + Send + Sync {
/// Reads a block from the disc image. /// Reads a block from the disc image.
@ -78,7 +90,27 @@ pub trait BlockIO: DynClone + Send + Sync {
dyn_clone::clone_trait_object!(BlockIO); dyn_clone::clone_trait_object!(BlockIO);
/// Creates a new [`BlockIO`] instance. /// Creates a new [`BlockIO`] instance from a stream.
pub fn new(mut stream: Box<dyn DiscStream>) -> Result<Box<dyn BlockIO>> {
let magic: MagicBytes = read_from(stream.as_mut()).context("Reading magic bytes")?;
stream.seek(io::SeekFrom::Start(0)).context("Seeking to start")?;
let io: Box<dyn BlockIO> = match magic {
crate::io::ciso::CISO_MAGIC => crate::io::ciso::DiscIOCISO::new(stream)?,
#[cfg(feature = "compress-zlib")]
crate::io::gcz::GCZ_MAGIC => crate::io::gcz::DiscIOGCZ::new(stream)?,
crate::io::nfs::NFS_MAGIC => todo!(),
crate::io::wbfs::WBFS_MAGIC => crate::io::wbfs::DiscIOWBFS::new(stream)?,
crate::io::wia::WIA_MAGIC | crate::io::wia::RVZ_MAGIC => {
crate::io::wia::DiscIOWIA::new(stream)?
}
crate::io::tgc::TGC_MAGIC => crate::io::tgc::DiscIOTGC::new(stream)?,
_ => crate::io::iso::DiscIOISO::new(stream)?,
};
check_block_size(io.as_ref())?;
Ok(io)
}
/// Creates a new [`BlockIO`] instance from a filesystem path.
pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> { pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
let path_result = fs::canonicalize(filename); let path_result = fs::canonicalize(filename);
if let Err(err) = path_result { if let Err(err) = path_result {
@ -92,16 +124,16 @@ pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
if !meta.unwrap().is_file() { if !meta.unwrap().is_file() {
return Err(Error::DiscFormat(format!("Input is not a file: {}", filename.display()))); return Err(Error::DiscFormat(format!("Input is not a file: {}", filename.display())));
} }
let magic: MagicBytes = { let mut stream = Box::new(SplitFileReader::new(filename)?);
let mut file = let magic: MagicBytes = read_from(stream.as_mut())
File::open(path).with_context(|| format!("Opening file {}", filename.display()))?; .with_context(|| format!("Reading magic bytes from {}", filename.display()))?;
read_from(&mut file) stream
.with_context(|| format!("Reading magic bytes from {}", filename.display()))? .seek(io::SeekFrom::Start(0))
}; .with_context(|| format!("Seeking to start of {}", filename.display()))?;
let io: Box<dyn BlockIO> = match magic { let io: Box<dyn BlockIO> = match magic {
crate::io::ciso::CISO_MAGIC => crate::io::ciso::DiscIOCISO::new(path)?, crate::io::ciso::CISO_MAGIC => crate::io::ciso::DiscIOCISO::new(stream)?,
#[cfg(feature = "compress-zlib")] #[cfg(feature = "compress-zlib")]
crate::io::gcz::GCZ_MAGIC => crate::io::gcz::DiscIOGCZ::new(path)?, crate::io::gcz::GCZ_MAGIC => crate::io::gcz::DiscIOGCZ::new(stream)?,
crate::io::nfs::NFS_MAGIC => match path.parent() { crate::io::nfs::NFS_MAGIC => match path.parent() {
Some(parent) if parent.is_dir() => { Some(parent) if parent.is_dir() => {
crate::io::nfs::DiscIONFS::new(path.parent().unwrap())? crate::io::nfs::DiscIONFS::new(path.parent().unwrap())?
@ -110,13 +142,18 @@ pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
return Err(Error::DiscFormat("Failed to locate NFS parent directory".to_string())); return Err(Error::DiscFormat("Failed to locate NFS parent directory".to_string()));
} }
}, },
crate::io::wbfs::WBFS_MAGIC => crate::io::wbfs::DiscIOWBFS::new(path)?, crate::io::wbfs::WBFS_MAGIC => crate::io::wbfs::DiscIOWBFS::new(stream)?,
crate::io::wia::WIA_MAGIC | crate::io::wia::RVZ_MAGIC => { crate::io::wia::WIA_MAGIC | crate::io::wia::RVZ_MAGIC => {
crate::io::wia::DiscIOWIA::new(path)? crate::io::wia::DiscIOWIA::new(stream)?
} }
crate::io::tgc::TGC_MAGIC => crate::io::tgc::DiscIOTGC::new(path)?, crate::io::tgc::TGC_MAGIC => crate::io::tgc::DiscIOTGC::new(stream)?,
_ => crate::io::iso::DiscIOISO::new(path)?, _ => crate::io::iso::DiscIOISO::new(stream)?,
}; };
check_block_size(io.as_ref())?;
Ok(io)
}
fn check_block_size(io: &dyn BlockIO) -> Result<()> {
if io.block_size_internal() < SECTOR_SIZE as u32 if io.block_size_internal() < SECTOR_SIZE as u32
&& SECTOR_SIZE as u32 % io.block_size_internal() != 0 && SECTOR_SIZE as u32 % io.block_size_internal() != 0
{ {
@ -133,7 +170,7 @@ pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
SECTOR_SIZE SECTOR_SIZE
))); )));
} }
Ok(io) Ok(())
} }
/// Wii partition information. /// Wii partition information.

View File

@ -2,7 +2,6 @@ use std::{
io, io,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
mem::size_of, mem::size_of,
path::Path,
}; };
use zerocopy::{little_endian::*, AsBytes, FromBytes, FromZeroes}; use zerocopy::{little_endian::*, AsBytes, FromBytes, FromZeroes};
@ -10,9 +9,8 @@ use zerocopy::{little_endian::*, AsBytes, FromBytes, FromZeroes};
use crate::{ use crate::{
disc::SECTOR_SIZE, disc::SECTOR_SIZE,
io::{ io::{
block::{Block, BlockIO, PartitionInfo}, block::{Block, BlockIO, DiscStream, PartitionInfo},
nkit::NKitHeader, nkit::NKitHeader,
split::SplitFileReader,
Format, MagicBytes, Format, MagicBytes,
}, },
static_assert, static_assert,
@ -36,18 +34,16 @@ static_assert!(size_of::<CISOHeader>() == SECTOR_SIZE);
#[derive(Clone)] #[derive(Clone)]
pub struct DiscIOCISO { pub struct DiscIOCISO {
inner: SplitFileReader, inner: Box<dyn DiscStream>,
header: CISOHeader, header: CISOHeader,
block_map: [u16; CISO_MAP_SIZE], block_map: [u16; CISO_MAP_SIZE],
nkit_header: Option<NKitHeader>, nkit_header: Option<NKitHeader>,
} }
impl DiscIOCISO { impl DiscIOCISO {
pub fn new(filename: &Path) -> Result<Box<Self>> { pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?;
// Read header // Read header
let header: CISOHeader = read_from(&mut inner).context("Reading CISO header")?; let header: CISOHeader = read_from(inner.as_mut()).context("Reading CISO header")?;
if header.magic != CISO_MAGIC { if header.magic != CISO_MAGIC {
return Err(Error::DiscFormat("Invalid CISO magic".to_string())); return Err(Error::DiscFormat("Invalid CISO magic".to_string()));
} }
@ -64,18 +60,18 @@ impl DiscIOCISO {
} }
} }
let file_size = SECTOR_SIZE as u64 + block as u64 * header.block_size.get() as u64; let file_size = SECTOR_SIZE as u64 + block as u64 * header.block_size.get() as u64;
if file_size > inner.len() { let len = inner.seek(SeekFrom::End(0)).context("Determining stream length")?;
if file_size > len {
return Err(Error::DiscFormat(format!( return Err(Error::DiscFormat(format!(
"CISO file size mismatch: expected at least {} bytes, got {}", "CISO file size mismatch: expected at least {} bytes, got {}",
file_size, file_size, len
inner.len()
))); )));
} }
// Read NKit header if present (after CISO data) // Read NKit header if present (after CISO data)
let nkit_header = if inner.len() > file_size + 4 { let nkit_header = if len > file_size + 4 {
inner.seek(SeekFrom::Start(file_size)).context("Seeking to NKit header")?; inner.seek(SeekFrom::Start(file_size)).context("Seeking to NKit header")?;
NKitHeader::try_read_from(&mut inner, header.block_size.get(), true) NKitHeader::try_read_from(inner.as_mut(), header.block_size.get(), true)
} else { } else {
None None
}; };

View File

@ -2,7 +2,6 @@ use std::{
io, io,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
mem::size_of, mem::size_of,
path::Path,
}; };
use adler::adler32_slice; use adler::adler32_slice;
@ -12,8 +11,7 @@ use zstd::zstd_safe::WriteBuf;
use crate::{ use crate::{
io::{ io::{
block::{Block, BlockIO}, block::{Block, BlockIO, DiscStream},
split::SplitFileReader,
MagicBytes, MagicBytes,
}, },
static_assert, static_assert,
@ -38,7 +36,7 @@ struct GCZHeader {
static_assert!(size_of::<GCZHeader>() == 32); static_assert!(size_of::<GCZHeader>() == 32);
pub struct DiscIOGCZ { pub struct DiscIOGCZ {
inner: SplitFileReader, inner: Box<dyn DiscStream>,
header: GCZHeader, header: GCZHeader,
block_map: Box<[U64]>, block_map: Box<[U64]>,
block_hashes: Box<[U32]>, block_hashes: Box<[U32]>,
@ -60,21 +58,19 @@ impl Clone for DiscIOGCZ {
} }
impl DiscIOGCZ { impl DiscIOGCZ {
pub fn new(filename: &Path) -> Result<Box<Self>> { pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?;
// Read header // Read header
let header: GCZHeader = read_from(&mut inner).context("Reading GCZ header")?; let header: GCZHeader = read_from(inner.as_mut()).context("Reading GCZ header")?;
if header.magic != GCZ_MAGIC { if header.magic != GCZ_MAGIC {
return Err(Error::DiscFormat("Invalid GCZ magic".to_string())); return Err(Error::DiscFormat("Invalid GCZ magic".to_string()));
} }
// Read block map and hashes // Read block map and hashes
let block_count = header.block_count.get(); let block_count = header.block_count.get();
let block_map = let block_map = read_box_slice(inner.as_mut(), block_count as usize)
read_box_slice(&mut inner, block_count as usize).context("Reading GCZ block map")?; .context("Reading GCZ block map")?;
let block_hashes = let block_hashes = read_box_slice(inner.as_mut(), block_count as usize)
read_box_slice(&mut inner, block_count as usize).context("Reading GCZ block hashes")?; .context("Reading GCZ block hashes")?;
// header + block_count * (u64 + u32) // header + block_count * (u64 + u32)
let data_offset = size_of::<GCZHeader>() as u64 + block_count as u64 * 12; let data_offset = size_of::<GCZHeader>() as u64 + block_count as u64 * 12;

View File

@ -1,28 +1,28 @@
use std::{ use std::{
io, io,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
path::Path,
}; };
use crate::{ use crate::{
disc::SECTOR_SIZE, disc::SECTOR_SIZE,
io::{ io::{
block::{Block, BlockIO, PartitionInfo}, block::{Block, BlockIO, DiscStream, PartitionInfo},
split::SplitFileReader,
Format, Format,
}, },
DiscMeta, Result, DiscMeta, Result, ResultContext,
}; };
#[derive(Clone)] #[derive(Clone)]
pub struct DiscIOISO { pub struct DiscIOISO {
inner: SplitFileReader, inner: Box<dyn DiscStream>,
stream_len: u64,
} }
impl DiscIOISO { impl DiscIOISO {
pub fn new(filename: &Path) -> Result<Box<Self>> { pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let inner = SplitFileReader::new(filename)?; let stream_len = inner.seek(SeekFrom::End(0)).context("Determining stream length")?;
Ok(Box::new(Self { inner })) inner.seek(SeekFrom::Start(0)).context("Seeking to start")?;
Ok(Box::new(Self { inner, stream_len }))
} }
} }
@ -34,16 +34,15 @@ impl BlockIO for DiscIOISO {
_partition: Option<&PartitionInfo>, _partition: Option<&PartitionInfo>,
) -> io::Result<Block> { ) -> io::Result<Block> {
let offset = block as u64 * SECTOR_SIZE as u64; let offset = block as u64 * SECTOR_SIZE as u64;
let total_size = self.inner.len(); if offset >= self.stream_len {
if offset >= total_size {
// End of file // End of file
return Ok(Block::Zero); return Ok(Block::Zero);
} }
self.inner.seek(SeekFrom::Start(offset))?; self.inner.seek(SeekFrom::Start(offset))?;
if offset + SECTOR_SIZE as u64 > total_size { if offset + SECTOR_SIZE as u64 > self.stream_len {
// If the last block is not a full sector, fill the rest with zeroes // If the last block is not a full sector, fill the rest with zeroes
let read = (total_size - offset) as usize; let read = (self.stream_len - offset) as usize;
self.inner.read_exact(&mut out[..read])?; self.inner.read_exact(&mut out[..read])?;
out[read..].fill(0); out[read..].fill(0);
} else { } else {
@ -58,7 +57,7 @@ impl BlockIO for DiscIOISO {
DiscMeta { DiscMeta {
format: Format::Iso, format: Format::Iso,
lossless: true, lossless: true,
disc_size: Some(self.inner.len()), disc_size: Some(self.stream_len),
..Default::default() ..Default::default()
} }
} }

View File

@ -2,7 +2,6 @@ use std::{
io, io,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
mem::size_of, mem::size_of,
path::Path,
}; };
use zerocopy::{big_endian::U32, AsBytes, FromBytes, FromZeroes}; use zerocopy::{big_endian::U32, AsBytes, FromBytes, FromZeroes};
@ -10,8 +9,7 @@ use zerocopy::{big_endian::U32, AsBytes, FromBytes, FromZeroes};
use crate::{ use crate::{
disc::SECTOR_SIZE, disc::SECTOR_SIZE,
io::{ io::{
block::{Block, BlockIO, PartitionInfo}, block::{Block, BlockIO, DiscStream, PartitionInfo},
split::SplitFileReader,
Format, MagicBytes, Format, MagicBytes,
}, },
util::read::{read_box_slice, read_from}, util::read::{read_box_slice, read_from},
@ -56,17 +54,19 @@ struct TGCHeader {
#[derive(Clone)] #[derive(Clone)]
pub struct DiscIOTGC { pub struct DiscIOTGC {
inner: SplitFileReader, inner: Box<dyn DiscStream>,
stream_len: u64,
header: TGCHeader, header: TGCHeader,
fst: Box<[u8]>, fst: Box<[u8]>,
} }
impl DiscIOTGC { impl DiscIOTGC {
pub fn new(filename: &Path) -> Result<Box<Self>> { pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?; let stream_len = inner.seek(SeekFrom::End(0)).context("Determining stream length")?;
inner.seek(SeekFrom::Start(0)).context("Seeking to start")?;
// Read header // Read header
let header: TGCHeader = read_from(&mut inner).context("Reading TGC header")?; let header: TGCHeader = read_from(inner.as_mut()).context("Reading TGC header")?;
if header.magic != TGC_MAGIC { if header.magic != TGC_MAGIC {
return Err(Error::DiscFormat("Invalid TGC magic".to_string())); return Err(Error::DiscFormat("Invalid TGC magic".to_string()));
} }
@ -75,7 +75,7 @@ impl DiscIOTGC {
inner inner
.seek(SeekFrom::Start(header.fst_offset.get() as u64)) .seek(SeekFrom::Start(header.fst_offset.get() as u64))
.context("Seeking to TGC FST")?; .context("Seeking to TGC FST")?;
let mut fst = read_box_slice(&mut inner, header.fst_size.get() as usize) let mut fst = read_box_slice(inner.as_mut(), header.fst_size.get() as usize)
.context("Reading TGC FST")?; .context("Reading TGC FST")?;
let root_node = Node::ref_from_prefix(&fst) let root_node = Node::ref_from_prefix(&fst)
.ok_or_else(|| Error::DiscFormat("Invalid TGC FST".to_string()))?; .ok_or_else(|| Error::DiscFormat("Invalid TGC FST".to_string()))?;
@ -89,7 +89,7 @@ impl DiscIOTGC {
} }
} }
Ok(Box::new(Self { inner, header, fst })) Ok(Box::new(Self { inner, stream_len, header, fst }))
} }
} }
@ -101,16 +101,15 @@ impl BlockIO for DiscIOTGC {
_partition: Option<&PartitionInfo>, _partition: Option<&PartitionInfo>,
) -> io::Result<Block> { ) -> io::Result<Block> {
let offset = self.header.header_offset.get() as u64 + block as u64 * SECTOR_SIZE as u64; let offset = self.header.header_offset.get() as u64 + block as u64 * SECTOR_SIZE as u64;
let total_size = self.inner.len(); if offset >= self.stream_len {
if offset >= total_size {
// End of file // End of file
return Ok(Block::Zero); return Ok(Block::Zero);
} }
self.inner.seek(SeekFrom::Start(offset))?; self.inner.seek(SeekFrom::Start(offset))?;
if offset + SECTOR_SIZE as u64 > total_size { if offset + SECTOR_SIZE as u64 > self.stream_len {
// If the last block is not a full sector, fill the rest with zeroes // If the last block is not a full sector, fill the rest with zeroes
let read = (total_size - offset) as usize; let read = (self.stream_len - offset) as usize;
self.inner.read_exact(&mut out[..read])?; self.inner.read_exact(&mut out[..read])?;
out[read..].fill(0); out[read..].fill(0);
} else { } else {
@ -149,7 +148,7 @@ impl BlockIO for DiscIOTGC {
DiscMeta { DiscMeta {
format: Format::Tgc, format: Format::Tgc,
lossless: true, lossless: true,
disc_size: Some(self.inner.len() - self.header.header_offset.get() as u64), disc_size: Some(self.stream_len - self.header.header_offset.get() as u64),
..Default::default() ..Default::default()
} }
} }

View File

@ -2,16 +2,14 @@ use std::{
io, io,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
mem::size_of, mem::size_of,
path::Path,
}; };
use zerocopy::{big_endian::*, AsBytes, FromBytes, FromZeroes}; use zerocopy::{big_endian::*, AsBytes, FromBytes, FromZeroes};
use crate::{ use crate::{
io::{ io::{
block::{Block, BlockIO, PartitionInfo}, block::{Block, BlockIO, DiscStream, PartitionInfo},
nkit::NKitHeader, nkit::NKitHeader,
split::SplitFileReader,
DiscMeta, Format, MagicBytes, DiscMeta, Format, MagicBytes,
}, },
util::read::{read_box_slice, read_from}, util::read::{read_box_slice, read_from},
@ -35,18 +33,6 @@ impl WBFSHeader {
fn block_size(&self) -> u32 { 1 << self.block_size_shift } fn block_size(&self) -> u32 { 1 << self.block_size_shift }
// fn align_lba(&self, x: u32) -> u32 { (x + self.sector_size() - 1) & !(self.sector_size() - 1) }
//
// fn num_wii_sectors(&self) -> u32 {
// (self.num_sectors.get() / SECTOR_SIZE as u32) * self.sector_size()
// }
//
// fn max_wii_sectors(&self) -> u32 { NUM_WII_SECTORS }
//
// fn num_wbfs_sectors(&self) -> u32 {
// self.num_wii_sectors() >> (self.wbfs_sector_size_shift - 15)
// }
fn max_blocks(&self) -> u32 { NUM_WII_SECTORS >> (self.block_size_shift - 15) } fn max_blocks(&self) -> u32 { NUM_WII_SECTORS >> (self.block_size_shift - 15) }
} }
@ -55,7 +41,7 @@ const NUM_WII_SECTORS: u32 = 143432 * 2; // Double layer discs
#[derive(Clone)] #[derive(Clone)]
pub struct DiscIOWBFS { pub struct DiscIOWBFS {
inner: SplitFileReader, inner: Box<dyn DiscStream>,
/// WBFS header /// WBFS header
header: WBFSHeader, header: WBFSHeader,
/// Map of Wii LBAs to WBFS LBAs /// Map of Wii LBAs to WBFS LBAs
@ -65,14 +51,12 @@ pub struct DiscIOWBFS {
} }
impl DiscIOWBFS { impl DiscIOWBFS {
pub fn new(filename: &Path) -> Result<Box<Self>> { pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?; let header: WBFSHeader = read_from(inner.as_mut()).context("Reading WBFS header")?;
let header: WBFSHeader = read_from(&mut inner).context("Reading WBFS header")?;
if header.magic != WBFS_MAGIC { if header.magic != WBFS_MAGIC {
return Err(Error::DiscFormat("Invalid WBFS magic".to_string())); return Err(Error::DiscFormat("Invalid WBFS magic".to_string()));
} }
let file_len = inner.len(); let file_len = inner.seek(SeekFrom::End(0)).context("Determining stream length")?;
let expected_file_len = header.num_sectors.get() as u64 * header.sector_size() as u64; let expected_file_len = header.num_sectors.get() as u64 * header.sector_size() as u64;
if file_len != expected_file_len { if file_len != expected_file_len {
return Err(Error::DiscFormat(format!( return Err(Error::DiscFormat(format!(
@ -81,8 +65,11 @@ impl DiscIOWBFS {
))); )));
} }
inner
.seek(SeekFrom::Start(size_of::<WBFSHeader>() as u64))
.context("Seeking to WBFS disc table")?;
let disc_table: Box<[u8]> = let disc_table: Box<[u8]> =
read_box_slice(&mut inner, header.sector_size() as usize - size_of::<WBFSHeader>()) read_box_slice(inner.as_mut(), header.sector_size() as usize - size_of::<WBFSHeader>())
.context("Reading WBFS disc table")?; .context("Reading WBFS disc table")?;
if disc_table[0] != 1 { if disc_table[0] != 1 {
return Err(Error::DiscFormat("WBFS doesn't contain a disc".to_string())); return Err(Error::DiscFormat("WBFS doesn't contain a disc".to_string()));
@ -95,12 +82,12 @@ impl DiscIOWBFS {
inner inner
.seek(SeekFrom::Start(header.sector_size() as u64 + DISC_HEADER_SIZE as u64)) .seek(SeekFrom::Start(header.sector_size() as u64 + DISC_HEADER_SIZE as u64))
.context("Seeking to WBFS LBA table")?; // Skip header .context("Seeking to WBFS LBA table")?; // Skip header
let block_map: Box<[U16]> = read_box_slice(&mut inner, header.max_blocks() as usize) let block_map: Box<[U16]> = read_box_slice(inner.as_mut(), header.max_blocks() as usize)
.context("Reading WBFS LBA table")?; .context("Reading WBFS LBA table")?;
// Read NKit header if present (always at 0x10000) // Read NKit header if present (always at 0x10000)
inner.seek(SeekFrom::Start(0x10000)).context("Seeking to NKit header")?; inner.seek(SeekFrom::Start(0x10000)).context("Seeking to NKit header")?;
let nkit_header = NKitHeader::try_read_from(&mut inner, header.block_size(), true); let nkit_header = NKitHeader::try_read_from(inner.as_mut(), header.block_size(), true);
Ok(Box::new(Self { inner, header, block_map, nkit_header })) Ok(Box::new(Self { inner, header, block_map, nkit_header }))
} }

View File

@ -2,7 +2,6 @@ use std::{
io, io,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
mem::size_of, mem::size_of,
path::Path,
}; };
use zerocopy::{big_endian::*, AsBytes, FromBytes, FromZeroes}; use zerocopy::{big_endian::*, AsBytes, FromBytes, FromZeroes};
@ -14,9 +13,8 @@ use crate::{
SECTOR_SIZE, SECTOR_SIZE,
}, },
io::{ io::{
block::{Block, BlockIO, PartitionInfo}, block::{Block, BlockIO, DiscStream, PartitionInfo},
nkit::NKitHeader, nkit::NKitHeader,
split::SplitFileReader,
Compression, Format, HashBytes, KeyBytes, MagicBytes, Compression, Format, HashBytes, KeyBytes, MagicBytes,
}, },
static_assert, static_assert,
@ -502,7 +500,7 @@ impl Decompressor {
} }
pub struct DiscIOWIA { pub struct DiscIOWIA {
inner: SplitFileReader, inner: Box<dyn DiscStream>,
header: WIAFileHeader, header: WIAFileHeader,
disc: WIADisc, disc: WIADisc,
partitions: Box<[WIAPartition]>, partitions: Box<[WIAPartition]>,
@ -549,17 +547,16 @@ fn verify_hash(buf: &[u8], expected: &HashBytes) -> Result<()> {
} }
impl DiscIOWIA { impl DiscIOWIA {
pub fn new(filename: &Path) -> Result<Box<Self>> { pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?;
// Load & verify file header // Load & verify file header
let header: WIAFileHeader = read_from(&mut inner).context("Reading WIA/RVZ file header")?; let header: WIAFileHeader =
read_from(inner.as_mut()).context("Reading WIA/RVZ file header")?;
header.validate()?; header.validate()?;
let is_rvz = header.is_rvz(); let is_rvz = header.is_rvz();
// log::debug!("Header: {:?}", header); // log::debug!("Header: {:?}", header);
// Load & verify disc header // Load & verify disc header
let mut disc_buf: Vec<u8> = read_vec(&mut inner, header.disc_size.get() as usize) let mut disc_buf: Vec<u8> = read_vec(inner.as_mut(), header.disc_size.get() as usize)
.context("Reading WIA/RVZ disc header")?; .context("Reading WIA/RVZ disc header")?;
verify_hash(&disc_buf, &header.disc_hash)?; verify_hash(&disc_buf, &header.disc_hash)?;
disc_buf.resize(size_of::<WIADisc>(), 0); disc_buf.resize(size_of::<WIADisc>(), 0);
@ -576,14 +573,14 @@ impl DiscIOWIA {
// log::debug!("Disc: {:?}", disc); // log::debug!("Disc: {:?}", disc);
// Read NKit header if present (after disc header) // Read NKit header if present (after disc header)
let nkit_header = NKitHeader::try_read_from(&mut inner, disc.chunk_size.get(), false); let nkit_header = NKitHeader::try_read_from(inner.as_mut(), disc.chunk_size.get(), false);
// Load & verify partition headers // Load & verify partition headers
inner inner
.seek(SeekFrom::Start(disc.partition_offset.get())) .seek(SeekFrom::Start(disc.partition_offset.get()))
.context("Seeking to WIA/RVZ partition headers")?; .context("Seeking to WIA/RVZ partition headers")?;
let partitions: Box<[WIAPartition]> = let partitions: Box<[WIAPartition]> =
read_box_slice(&mut inner, disc.num_partitions.get() as usize) read_box_slice(inner.as_mut(), disc.num_partitions.get() as usize)
.context("Reading WIA/RVZ partition headers")?; .context("Reading WIA/RVZ partition headers")?;
verify_hash(partitions.as_ref().as_bytes(), &disc.partition_hash)?; verify_hash(partitions.as_ref().as_bytes(), &disc.partition_hash)?;
// log::debug!("Partitions: {:?}", partitions); // log::debug!("Partitions: {:?}", partitions);
@ -597,7 +594,7 @@ impl DiscIOWIA {
.seek(SeekFrom::Start(disc.raw_data_offset.get())) .seek(SeekFrom::Start(disc.raw_data_offset.get()))
.context("Seeking to WIA/RVZ raw data headers")?; .context("Seeking to WIA/RVZ raw data headers")?;
let mut reader = decompressor let mut reader = decompressor
.wrap((&mut inner).take(disc.raw_data_size.get() as u64)) .wrap(inner.as_mut().take(disc.raw_data_size.get() as u64))
.context("Creating WIA/RVZ decompressor")?; .context("Creating WIA/RVZ decompressor")?;
read_box_slice(&mut reader, disc.num_raw_data.get() as usize) read_box_slice(&mut reader, disc.num_raw_data.get() as usize)
.context("Reading WIA/RVZ raw data headers")? .context("Reading WIA/RVZ raw data headers")?
@ -621,7 +618,7 @@ impl DiscIOWIA {
.seek(SeekFrom::Start(disc.group_offset.get())) .seek(SeekFrom::Start(disc.group_offset.get()))
.context("Seeking to WIA/RVZ group headers")?; .context("Seeking to WIA/RVZ group headers")?;
let mut reader = decompressor let mut reader = decompressor
.wrap((&mut inner).take(disc.group_size.get() as u64)) .wrap(inner.as_mut().take(disc.group_size.get() as u64))
.context("Creating WIA/RVZ decompressor")?; .context("Creating WIA/RVZ decompressor")?;
if is_rvz { if is_rvz {
read_box_slice(&mut reader, disc.num_groups.get() as usize) read_box_slice(&mut reader, disc.num_groups.get() as usize)

View File

@ -64,11 +64,14 @@ use std::{
}; };
pub use disc::{ pub use disc::{
ApploaderHeader, DiscHeader, DolHeader, FileStream, Fst, Node, NodeKind, PartitionBase, ApploaderHeader, DiscHeader, DolHeader, FileStream, Fst, Node, NodeKind, OwnedFileStream,
PartitionHeader, PartitionKind, PartitionMeta, SignedHeader, Ticket, TicketLimit, TmdHeader, PartitionBase, PartitionHeader, PartitionKind, PartitionMeta, SignedHeader, Ticket,
BI2_SIZE, BOOT_SIZE, SECTOR_SIZE, TicketLimit, TmdHeader, WindowedStream, BI2_SIZE, BOOT_SIZE, SECTOR_SIZE,
};
pub use io::{
block::{DiscStream, PartitionInfo},
Compression, DiscMeta, Format, KeyBytes,
}; };
pub use io::{block::PartitionInfo, Compression, DiscMeta, Format, KeyBytes};
mod disc; mod disc;
mod io; mod io;
@ -170,6 +173,23 @@ impl Disc {
Ok(Disc { reader, options: options.clone() }) Ok(Disc { reader, options: options.clone() })
} }
/// Opens a disc image from a read stream.
#[inline]
pub fn new_stream(stream: Box<dyn DiscStream>) -> Result<Disc> {
Disc::new_stream_with_options(stream, &OpenOptions::default())
}
/// Opens a disc image from a read stream with custom options.
#[inline]
pub fn new_stream_with_options(
stream: Box<dyn DiscStream>,
options: &OpenOptions,
) -> Result<Disc> {
let io = io::block::new(stream)?;
let reader = disc::reader::DiscReader::new(io, options)?;
Ok(Disc { reader, options: options.clone() })
}
/// The disc's primary header. /// The disc's primary header.
#[inline] #[inline]
pub fn header(&self) -> &DiscHeader { self.reader.header() } pub fn header(&self) -> &DiscHeader { self.reader.header() }