mirror of
https://github.com/encounter/nod-rs.git
synced 2025-06-30 18:33:42 +00:00
Documentation updates
This commit is contained in:
parent
9d8cd980b8
commit
4b4564207a
116
README.md
116
README.md
@ -81,17 +81,21 @@ Opening a disc image and reading a file:
|
|||||||
```rust
|
```rust
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use nod::{
|
||||||
|
common::PartitionKind,
|
||||||
|
read::{DiscOptions, DiscReader, PartitionOptions},
|
||||||
|
};
|
||||||
|
|
||||||
// Open a disc image and the first data partition.
|
// Open a disc image and the first data partition.
|
||||||
let disc = nod::Disc::new("path/to/file.iso")
|
let disc =
|
||||||
.expect("Failed to open disc");
|
DiscReader::new("path/to/file.iso", &DiscOptions::default()).expect("Failed to open disc");
|
||||||
let mut partition = disc.open_partition_kind(nod::PartitionKind::Data)
|
let mut partition = disc
|
||||||
|
.open_partition_kind(PartitionKind::Data, &PartitionOptions::default())
|
||||||
.expect("Failed to open data partition");
|
.expect("Failed to open data partition");
|
||||||
|
|
||||||
// Read partition metadata and the file system table.
|
// Read partition metadata and the file system table.
|
||||||
let meta = partition.meta()
|
let meta = partition.meta().expect("Failed to read partition metadata");
|
||||||
.expect("Failed to read partition metadata");
|
let fst = meta.fst().expect("File system table is invalid");
|
||||||
let fst = meta.fst()
|
|
||||||
.expect("File system table is invalid");
|
|
||||||
|
|
||||||
// Find a file by path and read it into a string.
|
// Find a file by path and read it into a string.
|
||||||
if let Some((_, node)) = fst.find("/MP3/Worlds.txt") {
|
if let Some((_, node)) = fst.find("/MP3/Worlds.txt") {
|
||||||
@ -108,16 +112,98 @@ if let Some((_, node)) = fst.find("/MP3/Worlds.txt") {
|
|||||||
Converting a disc image to raw ISO:
|
Converting a disc image to raw ISO:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Enable `rebuild_encryption` to ensure the output is a valid ISO.
|
use nod::read::{DiscOptions, DiscReader, PartitionEncryption};
|
||||||
let options = nod::OpenOptions { rebuild_encryption: true, ..Default::default() };
|
|
||||||
let mut disc = nod::Disc::new_with_options("path/to/file.rvz", &options)
|
|
||||||
.expect("Failed to open disc");
|
|
||||||
|
|
||||||
// Read directly from the open disc and write to the output file.
|
let options = DiscOptions {
|
||||||
let mut out = std::fs::File::create("output.iso")
|
partition_encryption: PartitionEncryption::Original,
|
||||||
|
// Use 4 threads to preload data as the disc is read. This can speed up sequential reads,
|
||||||
|
// especially when the disc image format uses compression.
|
||||||
|
preloader_threads: 4,
|
||||||
|
};
|
||||||
|
// Open a disc image.
|
||||||
|
let mut disc = DiscReader::new("path/to/file.rvz", &options).expect("Failed to open disc");
|
||||||
|
|
||||||
|
// Create a new output file.
|
||||||
|
let mut out = std::fs::File::create("output.iso").expect("Failed to create output file");
|
||||||
|
// Read directly from the DiscReader and write to the output file.
|
||||||
|
// NOTE: Any copy method that accepts `Read` and `Write` can be used here,
|
||||||
|
// such as `std::io::copy`. This example utilizes `BufRead` for efficiency,
|
||||||
|
// since `DiscReader` has its own internal buffer.
|
||||||
|
nod::util::buf_copy(&mut disc, &mut out).expect("Failed to write data");
|
||||||
|
```
|
||||||
|
|
||||||
|
Converting a disc image to RVZ:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Seek, Write};
|
||||||
|
use nod::common::{Compression, Format};
|
||||||
|
use nod::read::{DiscOptions, DiscReader, PartitionEncryption};
|
||||||
|
use nod::write::{DiscWriter, DiscWriterWeight, FormatOptions, ProcessOptions};
|
||||||
|
|
||||||
|
let open_options = DiscOptions {
|
||||||
|
partition_encryption: PartitionEncryption::Original,
|
||||||
|
// Use 4 threads to preload data as the disc is read. This can speed up sequential reads,
|
||||||
|
// especially when the disc image format uses compression.
|
||||||
|
preloader_threads: 4,
|
||||||
|
};
|
||||||
|
// Open a disc image.
|
||||||
|
let disc = DiscReader::new("path/to/file.iso", &open_options)
|
||||||
|
.expect("Failed to open disc");
|
||||||
|
// Create a new output file.
|
||||||
|
let mut output_file = File::create("output.rvz")
|
||||||
.expect("Failed to create output file");
|
.expect("Failed to create output file");
|
||||||
std::io::copy(&mut disc, &mut out)
|
|
||||||
.expect("Failed to write data");
|
let options = FormatOptions {
|
||||||
|
format: Format::Rvz,
|
||||||
|
compression: Compression::Zstandard(19),
|
||||||
|
block_size: Format::Rvz.default_block_size(),
|
||||||
|
};
|
||||||
|
// Create a disc writer with the desired output format.
|
||||||
|
let mut writer = DiscWriter::new(disc, &options)
|
||||||
|
.expect("Failed to create writer");
|
||||||
|
|
||||||
|
// Ideally we'd base this on the actual number of CPUs available.
|
||||||
|
// This is just an example.
|
||||||
|
let num_threads = match writer.weight() {
|
||||||
|
DiscWriterWeight::Light => 0,
|
||||||
|
DiscWriterWeight::Medium => 4,
|
||||||
|
DiscWriterWeight::Heavy => 12,
|
||||||
|
};
|
||||||
|
let process_options = ProcessOptions {
|
||||||
|
processor_threads: num_threads,
|
||||||
|
// Enable checksum calculation for the _original_ disc data.
|
||||||
|
// Digests will be stored in the output file for verification, if supported.
|
||||||
|
// They will also be returned in the finalization result.
|
||||||
|
digest_crc32: true,
|
||||||
|
digest_md5: false, // MD5 is slow, skip it
|
||||||
|
digest_sha1: true,
|
||||||
|
digest_xxh64: true,
|
||||||
|
};
|
||||||
|
// Start processing the disc image.
|
||||||
|
let finalization = writer.process(
|
||||||
|
|data, _progress, _total| {
|
||||||
|
output_file.write_all(data.as_ref())?;
|
||||||
|
// One could display progress here, if desired.
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
&process_options
|
||||||
|
)
|
||||||
|
.expect("Failed to process disc image");
|
||||||
|
|
||||||
|
// Some disc writers calculate data during processing.
|
||||||
|
// If the finalization returns header data, seek to the beginning of the file and write it.
|
||||||
|
if !finalization.header.is_empty() {
|
||||||
|
output_file.rewind()
|
||||||
|
.expect("Failed to seek");
|
||||||
|
output_file.write_all(finalization.header.as_ref())
|
||||||
|
.expect("Failed to write header");
|
||||||
|
}
|
||||||
|
output_file.flush().expect("Failed to flush output file");
|
||||||
|
|
||||||
|
// Display the calculated digests.
|
||||||
|
println!("CRC32: {:08X}", finalization.crc32.unwrap());
|
||||||
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -16,17 +16,9 @@ use crate::{
|
|||||||
wii::{HASHES_SIZE, SECTOR_DATA_SIZE},
|
wii::{HASHES_SIZE, SECTOR_DATA_SIZE},
|
||||||
},
|
},
|
||||||
util::{aes::decrypt_sector_b2b, array_ref, array_ref_mut, lfg::LaggedFibonacci},
|
util::{aes::decrypt_sector_b2b, array_ref, array_ref_mut, lfg::LaggedFibonacci},
|
||||||
write::{DiscFinalization, DiscWriterWeight, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A callback for writing disc data.
|
|
||||||
///
|
|
||||||
/// The callback should write all data to the output stream before returning, or return an error if
|
|
||||||
/// writing fails. The second and third arguments are the current bytes processed and the total
|
|
||||||
/// bytes to process, respectively. For most formats, this has no relation to the written disc size,
|
|
||||||
/// but can be used to display progress.
|
|
||||||
pub type DataCallback<'a> = dyn FnMut(Bytes, u64, u64) -> io::Result<()> + 'a;
|
|
||||||
|
|
||||||
/// A trait for writing disc images.
|
/// A trait for writing disc images.
|
||||||
pub trait DiscWriter: DynClone {
|
pub trait DiscWriter: DynClone {
|
||||||
/// Processes the disc writer to completion.
|
/// Processes the disc writer to completion.
|
||||||
|
@ -15,8 +15,8 @@ use crate::{
|
|||||||
SECTOR_SIZE,
|
SECTOR_SIZE,
|
||||||
reader::DiscReader,
|
reader::DiscReader,
|
||||||
writer::{
|
writer::{
|
||||||
BlockProcessor, BlockResult, CheckBlockResult, DataCallback, DiscWriter, check_block,
|
BlockProcessor, BlockResult, CheckBlockResult, DiscWriter, check_block, par_process,
|
||||||
par_process, read_block,
|
read_block,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
io::{
|
io::{
|
||||||
@ -31,7 +31,7 @@ use crate::{
|
|||||||
read::{box_to_bytes, read_arc_at},
|
read::{box_to_bytes, read_arc_at},
|
||||||
static_assert,
|
static_assert,
|
||||||
},
|
},
|
||||||
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CISO_MAP_SIZE: usize = SECTOR_SIZE - 8;
|
pub const CISO_MAP_SIZE: usize = SECTOR_SIZE - 8;
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
|||||||
disc::{
|
disc::{
|
||||||
SECTOR_SIZE,
|
SECTOR_SIZE,
|
||||||
reader::DiscReader,
|
reader::DiscReader,
|
||||||
writer::{BlockProcessor, BlockResult, DataCallback, DiscWriter, par_process, read_block},
|
writer::{BlockProcessor, BlockResult, DiscWriter, par_process, read_block},
|
||||||
},
|
},
|
||||||
io::block::{Block, BlockKind, BlockReader, GCZ_MAGIC},
|
io::block::{Block, BlockKind, BlockReader, GCZ_MAGIC},
|
||||||
read::{DiscMeta, DiscStream},
|
read::{DiscMeta, DiscStream},
|
||||||
@ -25,7 +25,7 @@ use crate::{
|
|||||||
read::{read_arc_slice_at, read_at},
|
read::{read_arc_slice_at, read_at},
|
||||||
static_assert,
|
static_assert,
|
||||||
},
|
},
|
||||||
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// GCZ header (little endian)
|
/// GCZ header (little endian)
|
||||||
|
@ -3,15 +3,11 @@ use std::{io, io::BufRead};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Result, ResultContext,
|
Result, ResultContext,
|
||||||
common::Format,
|
common::Format,
|
||||||
disc::{
|
disc::{SECTOR_SIZE, reader::DiscReader, writer::DiscWriter},
|
||||||
SECTOR_SIZE,
|
|
||||||
reader::DiscReader,
|
|
||||||
writer::{DataCallback, DiscWriter},
|
|
||||||
},
|
|
||||||
io::block::{Block, BlockKind, BlockReader},
|
io::block::{Block, BlockKind, BlockReader},
|
||||||
read::{DiscMeta, DiscStream},
|
read::{DiscMeta, DiscStream},
|
||||||
util::digest::DigestManager,
|
util::digest::DigestManager,
|
||||||
write::{DiscFinalization, DiscWriterWeight, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
|||||||
fst::Fst,
|
fst::Fst,
|
||||||
gcn::{read_dol, read_fst},
|
gcn::{read_dol, read_fst},
|
||||||
reader::DiscReader,
|
reader::DiscReader,
|
||||||
writer::{DataCallback, DiscWriter},
|
writer::DiscWriter,
|
||||||
},
|
},
|
||||||
io::block::{Block, BlockKind, BlockReader, TGC_MAGIC},
|
io::block::{Block, BlockKind, BlockReader, TGC_MAGIC},
|
||||||
read::{DiscMeta, DiscStream, PartitionOptions, PartitionReader},
|
read::{DiscMeta, DiscStream, PartitionOptions, PartitionReader},
|
||||||
@ -25,7 +25,7 @@ use crate::{
|
|||||||
read::{read_arc_at, read_arc_slice_at, read_at, read_with_zero_fill},
|
read::{read_arc_at, read_arc_slice_at, read_at, read_with_zero_fill},
|
||||||
static_assert,
|
static_assert,
|
||||||
},
|
},
|
||||||
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// TGC header (big endian)
|
/// TGC header (big endian)
|
||||||
|
@ -15,8 +15,8 @@ use crate::{
|
|||||||
SECTOR_SIZE,
|
SECTOR_SIZE,
|
||||||
reader::DiscReader,
|
reader::DiscReader,
|
||||||
writer::{
|
writer::{
|
||||||
BlockProcessor, BlockResult, CheckBlockResult, DataCallback, DiscWriter, check_block,
|
BlockProcessor, BlockResult, CheckBlockResult, DiscWriter, check_block, par_process,
|
||||||
par_process, read_block,
|
read_block,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
io::{
|
io::{
|
||||||
@ -30,7 +30,7 @@ use crate::{
|
|||||||
lfg::LaggedFibonacci,
|
lfg::LaggedFibonacci,
|
||||||
read::{read_arc_slice_at, read_at, read_box_slice_at},
|
read::{read_arc_slice_at, read_at, read_box_slice_at},
|
||||||
},
|
},
|
||||||
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
#[derive(Debug, Clone, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
|
||||||
|
@ -20,7 +20,7 @@ use crate::{
|
|||||||
fst::Fst,
|
fst::Fst,
|
||||||
reader::DiscReader,
|
reader::DiscReader,
|
||||||
wii::{HASHES_SIZE, SECTOR_DATA_SIZE},
|
wii::{HASHES_SIZE, SECTOR_DATA_SIZE},
|
||||||
writer::{BlockProcessor, BlockResult, DataCallback, DiscWriter, par_process, read_block},
|
writer::{BlockProcessor, BlockResult, DiscWriter, par_process, read_block},
|
||||||
},
|
},
|
||||||
io::{
|
io::{
|
||||||
block::{Block, BlockKind, BlockReader, RVZ_MAGIC, WIA_MAGIC},
|
block::{Block, BlockKind, BlockReader, RVZ_MAGIC, WIA_MAGIC},
|
||||||
@ -40,7 +40,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
static_assert,
|
static_assert,
|
||||||
},
|
},
|
||||||
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
write::{DataCallback, DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WIA_VERSION: u32 = 0x01000000;
|
const WIA_VERSION: u32 = 0x01000000;
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
#![allow(clippy::new_ret_no_self)]
|
#![allow(clippy::new_ret_no_self)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
//! Library for traversing & reading Nintendo Optical Disc (GameCube and Wii) images.
|
//! Library for reading and writing Nintendo Optical Disc (GameCube and Wii) images.
|
||||||
//!
|
//!
|
||||||
//! Originally based on the C++ library [nod](https://github.com/AxioDL/nod),
|
//! Originally based on the C++ library [nod](https://github.com/AxioDL/nod),
|
||||||
//! but does not currently support authoring.
|
//! but with extended format support and many additional features.
|
||||||
//!
|
//!
|
||||||
//! Currently supported file formats:
|
//! Currently supported file formats:
|
||||||
//! - ISO (GCM)
|
//! - ISO (GCM)
|
||||||
//! - WIA / RVZ
|
//! - WIA / RVZ
|
||||||
//! - WBFS (+ NKit 2 lossless)
|
//! - WBFS (+ NKit 2 lossless)
|
||||||
//! - CISO (+ NKit 2 lossless)
|
//! - CISO (+ NKit 2 lossless)
|
||||||
//! - NFS (Wii U VC)
|
//! - NFS (Wii U VC, read-only)
|
||||||
//! - GCZ
|
//! - GCZ
|
||||||
//! - TGC
|
//! - TGC
|
||||||
//!
|
//!
|
||||||
@ -75,11 +75,16 @@
|
|||||||
//! Converting a disc image to RVZ:
|
//! Converting a disc image to RVZ:
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! use std::fs::File;
|
//! use std::{
|
||||||
//! use std::io::{Seek, Write};
|
//! fs::File,
|
||||||
//! use nod::common::{Compression, Format};
|
//! io::{Seek, Write},
|
||||||
//! use nod::read::{DiscOptions, DiscReader, PartitionEncryption};
|
//! };
|
||||||
//! use nod::write::{DiscWriter, DiscWriterWeight, FormatOptions, ProcessOptions};
|
//!
|
||||||
|
//! use nod::{
|
||||||
|
//! common::{Compression, Format},
|
||||||
|
//! read::{DiscOptions, DiscReader, PartitionEncryption},
|
||||||
|
//! write::{DiscWriter, DiscWriterWeight, FormatOptions, ProcessOptions},
|
||||||
|
//! };
|
||||||
//!
|
//!
|
||||||
//! let open_options = DiscOptions {
|
//! let open_options = DiscOptions {
|
||||||
//! partition_encryption: PartitionEncryption::Original,
|
//! partition_encryption: PartitionEncryption::Original,
|
||||||
@ -88,11 +93,9 @@
|
|||||||
//! preloader_threads: 4,
|
//! preloader_threads: 4,
|
||||||
//! };
|
//! };
|
||||||
//! // Open a disc image.
|
//! // Open a disc image.
|
||||||
//! let disc = DiscReader::new("path/to/file.iso", &open_options)
|
//! let disc = DiscReader::new("path/to/file.iso", &open_options).expect("Failed to open disc");
|
||||||
//! .expect("Failed to open disc");
|
|
||||||
//! // Create a new output file.
|
//! // Create a new output file.
|
||||||
//! let mut output_file = File::create("output.rvz")
|
//! let mut output_file = File::create("output.rvz").expect("Failed to create output file");
|
||||||
//! .expect("Failed to create output file");
|
|
||||||
//!
|
//!
|
||||||
//! let options = FormatOptions {
|
//! let options = FormatOptions {
|
||||||
//! format: Format::Rvz,
|
//! format: Format::Rvz,
|
||||||
@ -100,8 +103,7 @@
|
|||||||
//! block_size: Format::Rvz.default_block_size(),
|
//! block_size: Format::Rvz.default_block_size(),
|
||||||
//! };
|
//! };
|
||||||
//! // Create a disc writer with the desired output format.
|
//! // Create a disc writer with the desired output format.
|
||||||
//! let mut writer = DiscWriter::new(disc, &options)
|
//! let mut writer = DiscWriter::new(disc, &options).expect("Failed to create writer");
|
||||||
//! .expect("Failed to create writer");
|
|
||||||
//!
|
//!
|
||||||
//! // Ideally we'd base this on the actual number of CPUs available.
|
//! // Ideally we'd base this on the actual number of CPUs available.
|
||||||
//! // This is just an example.
|
//! // This is just an example.
|
||||||
@ -121,29 +123,29 @@
|
|||||||
//! digest_xxh64: true,
|
//! digest_xxh64: true,
|
||||||
//! };
|
//! };
|
||||||
//! // Start processing the disc image.
|
//! // Start processing the disc image.
|
||||||
//! let finalization = writer.process(
|
//! let finalization = writer
|
||||||
|
//! .process(
|
||||||
//! |data, _progress, _total| {
|
//! |data, _progress, _total| {
|
||||||
//! output_file.write_all(data.as_ref())?;
|
//! output_file.write_all(data.as_ref())?;
|
||||||
//! // One could display progress here, if desired.
|
//! // One could display progress here, if desired.
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! },
|
//! },
|
||||||
//! &process_options
|
//! &process_options,
|
||||||
//! )
|
//! )
|
||||||
//! .expect("Failed to process disc image");
|
//! .expect("Failed to process disc image");
|
||||||
//!
|
//!
|
||||||
//! // Some disc writers calculate data during processing.
|
//! // Some disc writers calculate data during processing.
|
||||||
//! // If the finalization returns header data, seek to the beginning of the file and write it.
|
//! // If the finalization returns header data, seek to the beginning of the file and write it.
|
||||||
//! if !finalization.header.is_empty() {
|
//! if !finalization.header.is_empty() {
|
||||||
//! output_file.rewind()
|
//! output_file.rewind().expect("Failed to seek");
|
||||||
//! .expect("Failed to seek");
|
//! output_file.write_all(finalization.header.as_ref()).expect("Failed to write header");
|
||||||
//! output_file.write_all(finalization.header.as_ref())
|
|
||||||
//! .expect("Failed to write header");
|
|
||||||
//! }
|
//! }
|
||||||
//! output_file.flush().expect("Failed to flush output file");
|
//! output_file.flush().expect("Failed to flush output file");
|
||||||
//!
|
//!
|
||||||
//! // Display the calculated digests.
|
//! // Display the calculated digests.
|
||||||
//! println!("CRC32: {:08X}", finalization.crc32.unwrap());
|
//! println!("CRC32: {:08X}", finalization.crc32.unwrap());
|
||||||
//! // ...
|
//! // ...
|
||||||
|
//! ```
|
||||||
|
|
||||||
pub mod build;
|
pub mod build;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
@ -167,16 +167,15 @@ where T: Read + Seek + Send
|
|||||||
///
|
///
|
||||||
/// This is the primary entry point for reading disc images.
|
/// This is the primary entry point for reading disc images.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DiscReader {
|
#[repr(transparent)]
|
||||||
inner: disc::reader::DiscReader,
|
pub struct DiscReader(disc::reader::DiscReader);
|
||||||
}
|
|
||||||
|
|
||||||
impl DiscReader {
|
impl DiscReader {
|
||||||
/// Opens a disc image from a file path.
|
/// Opens a disc image from a file path.
|
||||||
pub fn new<P: AsRef<Path>>(path: P, options: &DiscOptions) -> Result<DiscReader> {
|
pub fn new<P: AsRef<Path>>(path: P, options: &DiscOptions) -> Result<DiscReader> {
|
||||||
let io = block::open(path.as_ref())?;
|
let io = block::open(path.as_ref())?;
|
||||||
let inner = disc::reader::DiscReader::new(io, options)?;
|
let inner = disc::reader::DiscReader::new(io, options)?;
|
||||||
Ok(DiscReader { inner })
|
Ok(DiscReader(inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a disc image from a [`DiscStream`]. This allows low-overhead, multithreaded
|
/// Opens a disc image from a [`DiscStream`]. This allows low-overhead, multithreaded
|
||||||
@ -185,8 +184,8 @@ impl DiscReader {
|
|||||||
/// See [`DiscStream`] for more information.
|
/// See [`DiscStream`] for more information.
|
||||||
pub fn new_stream(stream: Box<dyn DiscStream>, options: &DiscOptions) -> Result<DiscReader> {
|
pub fn new_stream(stream: Box<dyn DiscStream>, options: &DiscOptions) -> Result<DiscReader> {
|
||||||
let io = block::new(stream)?;
|
let io = block::new(stream)?;
|
||||||
let reader = disc::reader::DiscReader::new(io, options)?;
|
let inner = disc::reader::DiscReader::new(io, options)?;
|
||||||
Ok(DiscReader { inner: reader })
|
Ok(DiscReader(inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a disc image from a [`Read`] + [`Seek`] stream that can be cloned.
|
/// Opens a disc image from a [`Read`] + [`Seek`] stream that can be cloned.
|
||||||
@ -217,27 +216,27 @@ impl DiscReader {
|
|||||||
|
|
||||||
/// The disc's primary header.
|
/// The disc's primary header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn header(&self) -> &DiscHeader { self.inner.header() }
|
pub fn header(&self) -> &DiscHeader { self.0.header() }
|
||||||
|
|
||||||
/// The Wii disc's region information.
|
/// The Wii disc's region information.
|
||||||
///
|
///
|
||||||
/// **GameCube**: This will return `None`.
|
/// **GameCube**: This will return `None`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn region(&self) -> Option<&[u8; REGION_SIZE]> { self.inner.region() }
|
pub fn region(&self) -> Option<&[u8; REGION_SIZE]> { self.0.region() }
|
||||||
|
|
||||||
/// Returns extra metadata included in the disc file format, if any.
|
/// Returns extra metadata included in the disc file format, if any.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn meta(&self) -> DiscMeta { self.inner.meta() }
|
pub fn meta(&self) -> DiscMeta { self.0.meta() }
|
||||||
|
|
||||||
/// The disc's size in bytes, or an estimate if not stored by the format.
|
/// The disc's size in bytes, or an estimate if not stored by the format.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn disc_size(&self) -> u64 { self.inner.disc_size() }
|
pub fn disc_size(&self) -> u64 { self.0.disc_size() }
|
||||||
|
|
||||||
/// A list of Wii partitions on the disc.
|
/// A list of Wii partitions on the disc.
|
||||||
///
|
///
|
||||||
/// **GameCube**: This will return an empty slice.
|
/// **GameCube**: This will return an empty slice.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn partitions(&self) -> &[PartitionInfo] { self.inner.partitions() }
|
pub fn partitions(&self) -> &[PartitionInfo] { self.0.partitions() }
|
||||||
|
|
||||||
/// Opens a decrypted partition read stream for the specified partition index.
|
/// Opens a decrypted partition read stream for the specified partition index.
|
||||||
///
|
///
|
||||||
@ -248,7 +247,7 @@ impl DiscReader {
|
|||||||
index: usize,
|
index: usize,
|
||||||
options: &PartitionOptions,
|
options: &PartitionOptions,
|
||||||
) -> Result<Box<dyn PartitionReader>> {
|
) -> Result<Box<dyn PartitionReader>> {
|
||||||
self.inner.open_partition(index, options)
|
self.0.open_partition(index, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a decrypted partition read stream for the first partition matching
|
/// Opens a decrypted partition read stream for the first partition matching
|
||||||
@ -261,28 +260,28 @@ impl DiscReader {
|
|||||||
kind: PartitionKind,
|
kind: PartitionKind,
|
||||||
options: &PartitionOptions,
|
options: &PartitionOptions,
|
||||||
) -> Result<Box<dyn PartitionReader>> {
|
) -> Result<Box<dyn PartitionReader>> {
|
||||||
self.inner.open_partition_kind(kind, options)
|
self.0.open_partition_kind(kind, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn into_inner(self) -> disc::reader::DiscReader { self.inner }
|
pub(crate) fn into_inner(self) -> disc::reader::DiscReader { self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufRead for DiscReader {
|
impl BufRead for DiscReader {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fill_buf(&mut self) -> io::Result<&[u8]> { self.inner.fill_buf() }
|
fn fill_buf(&mut self) -> io::Result<&[u8]> { self.0.fill_buf() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn consume(&mut self, amt: usize) { self.inner.consume(amt) }
|
fn consume(&mut self, amt: usize) { self.0.consume(amt) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for DiscReader {
|
impl Read for DiscReader {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.inner.read(buf) }
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.0.read(buf) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Seek for DiscReader {
|
impl Seek for DiscReader {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> { self.inner.seek(pos) }
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> { self.0.seek(pos) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra metadata about the underlying disc file format.
|
/// Extra metadata about the underlying disc file format.
|
||||||
|
@ -14,7 +14,7 @@ pub(crate) mod digest;
|
|||||||
pub mod lfg;
|
pub mod lfg;
|
||||||
pub(crate) mod read;
|
pub(crate) mod read;
|
||||||
|
|
||||||
/// Copies from a buffered reader to a writer without extra allocations.
|
/// Copies from a [`BufRead`] to a [`Write`] without allocating a buffer.
|
||||||
pub fn buf_copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
|
pub fn buf_copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
|
||||||
where
|
where
|
||||||
R: BufRead + ?Sized,
|
R: BufRead + ?Sized,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
//! [`DiscWriter`] and associated types.
|
//! [`DiscWriter`] and associated types.
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -70,13 +72,20 @@ pub struct ProcessOptions {
|
|||||||
pub digest_xxh64: bool,
|
pub digest_xxh64: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A callback for writing disc data.
|
||||||
|
///
|
||||||
|
/// The callback should write all data to the output stream before returning, or return an error if
|
||||||
|
/// writing fails. The second and third arguments are the current bytes processed and the total
|
||||||
|
/// bytes to process, respectively. For most formats, this has no relation to the written disc size,
|
||||||
|
/// but can be used to display progress.
|
||||||
|
pub type DataCallback<'a> = dyn FnMut(Bytes, u64, u64) -> io::Result<()> + 'a;
|
||||||
|
|
||||||
/// A constructed disc writer.
|
/// A constructed disc writer.
|
||||||
///
|
///
|
||||||
/// This is the primary entry point for writing disc images.
|
/// This is the primary entry point for writing disc images.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DiscWriter {
|
#[repr(transparent)]
|
||||||
inner: Box<dyn disc::writer::DiscWriter>,
|
pub struct DiscWriter(Box<dyn disc::writer::DiscWriter>);
|
||||||
}
|
|
||||||
|
|
||||||
impl DiscWriter {
|
impl DiscWriter {
|
||||||
/// Creates a new disc writer with the specified format options.
|
/// Creates a new disc writer with the specified format options.
|
||||||
@ -101,31 +110,33 @@ impl DiscWriter {
|
|||||||
Format::Wia | Format::Rvz => crate::io::wia::DiscWriterWIA::new(reader, &options)?,
|
Format::Wia | Format::Rvz => crate::io::wia::DiscWriterWIA::new(reader, &options)?,
|
||||||
format => return Err(Error::Other(format!("Unsupported write format: {format}"))),
|
format => return Err(Error::Other(format!("Unsupported write format: {format}"))),
|
||||||
};
|
};
|
||||||
Ok(DiscWriter { inner })
|
Ok(DiscWriter(inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the disc writer to completion, calling the data callback, in order, for each block
|
/// Processes the disc writer to completion, calling the data callback, in order, for each block
|
||||||
/// of data to write to the output file. The callback should write all data before returning, or
|
/// of data to write to the output file. The callback should write all data before returning, or
|
||||||
/// return an error if writing fails.
|
/// return an error if writing fails.
|
||||||
|
///
|
||||||
|
/// See [`DataCallback`] for more information.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn process(
|
pub fn process(
|
||||||
&self,
|
&self,
|
||||||
mut data_callback: impl FnMut(Bytes, u64, u64) -> std::io::Result<()>,
|
mut data_callback: impl FnMut(Bytes, u64, u64) -> io::Result<()>,
|
||||||
options: &ProcessOptions,
|
options: &ProcessOptions,
|
||||||
) -> Result<DiscFinalization> {
|
) -> Result<DiscFinalization> {
|
||||||
self.inner.process(&mut data_callback, options)
|
self.0.process(&mut data_callback, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the progress upper bound for the disc writer. For most formats, this has no
|
/// Returns the progress upper bound for the disc writer. For most formats, this has no
|
||||||
/// relation to the written disc size, but can be used to display progress.
|
/// relation to the written disc size, but can be used to display progress.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn progress_bound(&self) -> u64 { self.inner.progress_bound() }
|
pub fn progress_bound(&self) -> u64 { self.0.progress_bound() }
|
||||||
|
|
||||||
/// Returns the weight of the disc writer, which can help determine the number of threads to
|
/// Returns the weight of the disc writer, which can help determine the number of threads to
|
||||||
/// dedicate for output processing. This may depend on the format's configuration, such as
|
/// dedicate for output processing. This may depend on the format's configuration, such as
|
||||||
/// whether compression is enabled.
|
/// whether compression is enabled.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn weight(&self) -> DiscWriterWeight { self.inner.weight() }
|
pub fn weight(&self) -> DiscWriterWeight { self.0.weight() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data returned by the disc writer after processing.
|
/// Data returned by the disc writer after processing.
|
||||||
|
@ -2,7 +2,7 @@ use std::{
|
|||||||
fs,
|
fs,
|
||||||
fs::File,
|
fs::File,
|
||||||
io,
|
io,
|
||||||
io::{BufRead, Read, Seek, SeekFrom, Write},
|
io::{Read, Seek, SeekFrom, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str::from_utf8,
|
str::from_utf8,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
@ -21,7 +21,7 @@ use nod::{
|
|||||||
DiscOptions, DiscReader, PartitionEncryption, PartitionMeta, PartitionOptions,
|
DiscOptions, DiscReader, PartitionEncryption, PartitionMeta, PartitionOptions,
|
||||||
PartitionReader,
|
PartitionReader,
|
||||||
},
|
},
|
||||||
util::lfg::LaggedFibonacci,
|
util::{buf_copy, lfg::LaggedFibonacci},
|
||||||
write::{DiscWriter, FormatOptions, ProcessOptions},
|
write::{DiscWriter, FormatOptions, ProcessOptions},
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
@ -745,23 +745,3 @@ where W: Write
|
|||||||
|
|
||||||
fn stream_position(&mut self) -> io::Result<u64> { Ok(self.position) }
|
fn stream_position(&mut self) -> io::Result<u64> { Ok(self.position) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies from a buffered reader to a writer without extra allocations.
|
|
||||||
fn buf_copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
|
|
||||||
where
|
|
||||||
R: BufRead + ?Sized,
|
|
||||||
W: Write + ?Sized,
|
|
||||||
{
|
|
||||||
let mut copied = 0;
|
|
||||||
loop {
|
|
||||||
let buf = reader.fill_buf()?;
|
|
||||||
let len = buf.len();
|
|
||||||
if len == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
writer.write_all(buf)?;
|
|
||||||
reader.consume(len);
|
|
||||||
copied += len as u64;
|
|
||||||
}
|
|
||||||
Ok(copied)
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user