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.
#[inline]
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
} else {
self.offset.get() as u64
@ -108,7 +108,7 @@ impl<'a> Fst<'a> {
/// Get the name of a node.
#[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(|| {
format!(
"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.
#[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 current = split.next()?;
let mut idx = 1;

View File

@ -11,6 +11,7 @@ use super::{
PartitionMeta, BI2_SIZE, BOOT_SIZE, SECTOR_SIZE,
};
use crate::{
disc::streams::OwnedFileStream,
io::block::{Block, BlockIO},
util::read::{read_box, read_box_slice, read_vec},
Result, ResultContext,
@ -132,6 +133,16 @@ impl PartitionBase for PartitionGC {
}
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(

View File

@ -23,7 +23,7 @@ pub(crate) mod streams;
pub(crate) mod wii;
pub use fst::{Fst, Node, NodeKind};
pub use streams::FileStream;
pub use streams::{FileStream, OwnedFileStream, WindowedStream};
pub use wii::{SignedHeader, Ticket, TicketLimit, TmdHeader};
/// 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>;
/// 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);

View File

@ -7,30 +7,39 @@ use std::{
use super::PartitionBase;
/// A file read stream for a [`PartitionBase`].
pub struct FileStream<'a> {
base: &'a mut dyn PartitionBase,
/// A file read stream borrowing a [`PartitionBase`].
pub type FileStream<'a> = WindowedStream<&'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,
begin: u64,
end: u64,
}
impl FileStream<'_> {
/// Creates a new file stream with offset and size.
impl<T> WindowedStream<T>
where T: BufRead + Seek
{
/// Creates a new windowed stream with offset and size.
///
/// Seeks underlying stream immediately.
#[inline]
pub(crate) fn new(
base: &mut dyn PartitionBase,
offset: u64,
size: u64,
) -> io::Result<FileStream> {
pub fn new(mut base: T, offset: u64, size: u64) -> io::Result<Self> {
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]
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
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]
fn fill_buf(&mut self) -> io::Result<&[u8]> {
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]
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let mut pos = match pos {

View File

@ -14,6 +14,7 @@ use super::{
};
use crate::{
array_ref,
disc::streams::OwnedFileStream,
io::{
aes_decrypt,
block::{Block, BlockIO, PartitionInfo},
@ -497,4 +498,14 @@ impl PartitionBase for PartitionWii {
}
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 zerocopy::transmute_ref;
@ -10,11 +15,18 @@ use crate::{
wii::{WiiPartitionHeader, HASHES_SIZE, SECTOR_DATA_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},
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.
pub trait BlockIO: DynClone + Send + Sync {
/// Reads a block from the disc image.
@ -78,7 +90,27 @@ pub trait BlockIO: DynClone + Send + Sync {
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>> {
let path_result = fs::canonicalize(filename);
if let Err(err) = path_result {
@ -92,16 +124,16 @@ pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
if !meta.unwrap().is_file() {
return Err(Error::DiscFormat(format!("Input is not a file: {}", filename.display())));
}
let magic: MagicBytes = {
let mut file =
File::open(path).with_context(|| format!("Opening file {}", filename.display()))?;
read_from(&mut file)
.with_context(|| format!("Reading magic bytes from {}", filename.display()))?
};
let mut stream = Box::new(SplitFileReader::new(filename)?);
let magic: MagicBytes = read_from(stream.as_mut())
.with_context(|| format!("Reading magic bytes from {}", filename.display()))?;
stream
.seek(io::SeekFrom::Start(0))
.with_context(|| format!("Seeking to start of {}", filename.display()))?;
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")]
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() {
Some(parent) if parent.is_dir() => {
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()));
}
},
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::DiscIOWIA::new(path)?
crate::io::wia::DiscIOWIA::new(stream)?
}
crate::io::tgc::TGC_MAGIC => crate::io::tgc::DiscIOTGC::new(path)?,
_ => crate::io::iso::DiscIOISO::new(path)?,
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)
}
fn check_block_size(io: &dyn BlockIO) -> Result<()> {
if io.block_size_internal() < SECTOR_SIZE as u32
&& SECTOR_SIZE as u32 % io.block_size_internal() != 0
{
@ -133,7 +170,7 @@ pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
SECTOR_SIZE
)));
}
Ok(io)
Ok(())
}
/// Wii partition information.

View File

@ -2,7 +2,6 @@ use std::{
io,
io::{Read, Seek, SeekFrom},
mem::size_of,
path::Path,
};
use zerocopy::{little_endian::*, AsBytes, FromBytes, FromZeroes};
@ -10,9 +9,8 @@ use zerocopy::{little_endian::*, AsBytes, FromBytes, FromZeroes};
use crate::{
disc::SECTOR_SIZE,
io::{
block::{Block, BlockIO, PartitionInfo},
block::{Block, BlockIO, DiscStream, PartitionInfo},
nkit::NKitHeader,
split::SplitFileReader,
Format, MagicBytes,
},
static_assert,
@ -36,18 +34,16 @@ static_assert!(size_of::<CISOHeader>() == SECTOR_SIZE);
#[derive(Clone)]
pub struct DiscIOCISO {
inner: SplitFileReader,
inner: Box<dyn DiscStream>,
header: CISOHeader,
block_map: [u16; CISO_MAP_SIZE],
nkit_header: Option<NKitHeader>,
}
impl DiscIOCISO {
pub fn new(filename: &Path) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?;
pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
// 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 {
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;
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!(
"CISO file size mismatch: expected at least {} bytes, got {}",
file_size,
inner.len()
file_size, len
)));
}
// 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")?;
NKitHeader::try_read_from(&mut inner, header.block_size.get(), true)
NKitHeader::try_read_from(inner.as_mut(), header.block_size.get(), true)
} else {
None
};

View File

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

View File

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

View File

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

View File

@ -2,16 +2,14 @@ use std::{
io,
io::{Read, Seek, SeekFrom},
mem::size_of,
path::Path,
};
use zerocopy::{big_endian::*, AsBytes, FromBytes, FromZeroes};
use crate::{
io::{
block::{Block, BlockIO, PartitionInfo},
block::{Block, BlockIO, DiscStream, PartitionInfo},
nkit::NKitHeader,
split::SplitFileReader,
DiscMeta, Format, MagicBytes,
},
util::read::{read_box_slice, read_from},
@ -35,18 +33,6 @@ impl WBFSHeader {
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) }
}
@ -55,7 +41,7 @@ const NUM_WII_SECTORS: u32 = 143432 * 2; // Double layer discs
#[derive(Clone)]
pub struct DiscIOWBFS {
inner: SplitFileReader,
inner: Box<dyn DiscStream>,
/// WBFS header
header: WBFSHeader,
/// Map of Wii LBAs to WBFS LBAs
@ -65,14 +51,12 @@ pub struct DiscIOWBFS {
}
impl DiscIOWBFS {
pub fn new(filename: &Path) -> Result<Box<Self>> {
let mut inner = SplitFileReader::new(filename)?;
let header: WBFSHeader = read_from(&mut inner).context("Reading WBFS header")?;
pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let header: WBFSHeader = read_from(inner.as_mut()).context("Reading WBFS header")?;
if header.magic != WBFS_MAGIC {
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;
if file_len != expected_file_len {
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]> =
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")?;
if disc_table[0] != 1 {
return Err(Error::DiscFormat("WBFS doesn't contain a disc".to_string()));
@ -95,12 +82,12 @@ impl DiscIOWBFS {
inner
.seek(SeekFrom::Start(header.sector_size() as u64 + DISC_HEADER_SIZE as u64))
.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")?;
// Read NKit header if present (always at 0x10000)
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 }))
}

View File

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

View File

@ -64,11 +64,14 @@ use std::{
};
pub use disc::{
ApploaderHeader, DiscHeader, DolHeader, FileStream, Fst, Node, NodeKind, PartitionBase,
PartitionHeader, PartitionKind, PartitionMeta, SignedHeader, Ticket, TicketLimit, TmdHeader,
BI2_SIZE, BOOT_SIZE, SECTOR_SIZE,
ApploaderHeader, DiscHeader, DolHeader, FileStream, Fst, Node, NodeKind, OwnedFileStream,
PartitionBase, PartitionHeader, PartitionKind, PartitionMeta, SignedHeader, Ticket,
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 io;
@ -170,6 +173,23 @@ impl Disc {
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.
#[inline]
pub fn header(&self) -> &DiscHeader { self.reader.header() }