use std::{
borrow::Cow,
collections::{BTreeSet, HashMap, hash_map::Entry},
io,
io::{Seek, SeekFrom},
mem::size_of,
sync::Arc,
time::Instant,
};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use tracing::{debug, instrument, warn};
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, big_endian::*};
use crate::{
Error, IoResultContext, Result, ResultContext,
common::{Compression, Format, HashBytes, KeyBytes, MagicBytes},
disc::{
BootHeader, DiscHeader, SECTOR_SIZE,
fst::Fst,
reader::DiscReader,
wii::{HASHES_SIZE, SECTOR_DATA_SIZE},
writer::{BlockProcessor, BlockResult, DiscWriter, par_process, read_block},
},
io::{
block::{Block, BlockKind, BlockReader, RVZ_MAGIC, WIA_MAGIC},
nkit::NKitHeader,
},
read::{DiscMeta, DiscStream},
util::{
Align,
aes::decrypt_sector_data_b2b,
array_ref, array_ref_mut,
compress::{Compressor, DecompressionKind},
digest::{DigestManager, sha1_hash, xxh64_hash},
lfg::{LaggedFibonacci, SEED_SIZE, SEED_SIZE_BYTES},
read::{
read_arc_slice_at, read_at, read_box_slice_at, read_into_arc_slice,
read_into_box_slice, read_vec_at,
},
static_assert,
},
write::{DataCallback, DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
};
const WIA_VERSION: u32 = 0x01000000;
const WIA_VERSION_WRITE_COMPATIBLE: u32 = 0x01000000;
const WIA_VERSION_READ_COMPATIBLE: u32 = 0x00080000;
const RVZ_VERSION: u32 = 0x01000000;
const RVZ_VERSION_WRITE_COMPATIBLE: u32 = 0x00030000;
const RVZ_VERSION_READ_COMPATIBLE: u32 = 0x00030000;
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct WIAFileHeader {
pub magic: MagicBytes,
pub version: U32,
pub version_compatible: U32,
pub disc_size: U32,
pub disc_hash: HashBytes,
pub iso_file_size: U64,
pub wia_file_size: U64,
pub file_head_hash: HashBytes,
}
static_assert!(size_of::<WIAFileHeader>() == 0x48);
impl WIAFileHeader {
pub fn validate(&self) -> Result<()> {
if self.magic != WIA_MAGIC && self.magic != RVZ_MAGIC {
return Err(Error::DiscFormat(format!("Invalid WIA/RVZ magic: {:#X?}", self.magic)));
}
let is_rvz = self.magic == RVZ_MAGIC;
let version = if is_rvz { RVZ_VERSION } else { WIA_VERSION };
let version_read_compat =
if is_rvz { RVZ_VERSION_READ_COMPATIBLE } else { WIA_VERSION_READ_COMPATIBLE };
if version < self.version_compatible.get() || version_read_compat > self.version.get() {
return Err(Error::DiscFormat(format!(
"Unsupported WIA/RVZ version: {:#X}",
self.version.get()
)));
}
let bytes = self.as_bytes();
verify_hash(&bytes[..bytes.len() - size_of::<HashBytes>()], &self.file_head_hash)?;
if self.version_compatible.get() < 0x30000 {
return Err(Error::DiscFormat(format!(
"WIA/RVZ version {:#X} is not supported",
self.version_compatible
)));
}
Ok(())
}
pub fn is_rvz(&self) -> bool { self.magic == RVZ_MAGIC }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DiscKind {
GameCube,
Wii,
}
impl From<DiscKind> for u32 {
fn from(value: DiscKind) -> Self {
match value {
DiscKind::GameCube => 1,
DiscKind::Wii => 2,
}
}
}
impl From<DiscKind> for U32 {
fn from(value: DiscKind) -> Self { u32::from(value).into() }
}
impl TryFrom<u32> for DiscKind {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
1 => Ok(Self::GameCube),
2 => Ok(Self::Wii),
v => Err(Error::DiscFormat(format!("Invalid disc type {}", v))),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WIACompression {
None,
Purge,
Bzip2,
Lzma,
Lzma2,
Zstandard,
}
impl From<WIACompression> for u32 {
fn from(value: WIACompression) -> Self {
match value {
WIACompression::None => 0,
WIACompression::Purge => 1,
WIACompression::Bzip2 => 2,
WIACompression::Lzma => 3,
WIACompression::Lzma2 => 4,
WIACompression::Zstandard => 5,
}
}
}
impl From<WIACompression> for U32 {
fn from(value: WIACompression) -> Self { u32::from(value).into() }
}
impl TryFrom<u32> for WIACompression {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(Self::None),
1 => Ok(Self::Purge),
2 => Ok(Self::Bzip2),
3 => Ok(Self::Lzma),
4 => Ok(Self::Lzma2),
5 => Ok(Self::Zstandard),
v => Err(Error::DiscFormat(format!("Invalid compression type {}", v))),
}
}
}
const DISC_HEAD_SIZE: usize = 0x80;
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct WIADisc {
pub disc_type: U32,
pub compression: U32,
pub compression_level: I32,
pub chunk_size: U32,
pub disc_head: [u8; DISC_HEAD_SIZE],
pub num_partitions: U32,
pub partition_type_size: U32,
pub partition_offset: U64,
pub partition_hash: HashBytes,
pub num_raw_data: U32,
pub raw_data_offset: U64,
pub raw_data_size: U32,
pub num_groups: U32,
pub group_offset: U64,
pub group_size: U32,
pub compr_data_len: u8,
pub compr_data: [u8; 7],
}
static_assert!(size_of::<WIADisc>() == 0xDC);
impl WIADisc {
pub fn validate(&self, is_rvz: bool) -> Result<()> {
DiscKind::try_from(self.disc_type.get())?;
WIACompression::try_from(self.compression.get())?;
let chunk_size = self.chunk_size.get();
if is_rvz {
if chunk_size < SECTOR_SIZE as u32 || !chunk_size.is_power_of_two() {
return Err(Error::DiscFormat(format!(
"Invalid RVZ chunk size: {:#X}",
chunk_size
)));
}
} else if chunk_size < 0x200000 || chunk_size % 0x200000 != 0 {
return Err(Error::DiscFormat(format!("Invalid WIA chunk size: {:#X}", chunk_size)));
}
if self.partition_type_size.get() != size_of::<WIAPartition>() as u32 {
return Err(Error::DiscFormat(format!(
"WIA/RVZ partition type size is {}, expected {}",
self.partition_type_size.get(),
size_of::<WIAPartition>()
)));
}
Ok(())
}
pub fn compression(&self) -> WIACompression {
WIACompression::try_from(self.compression.get()).unwrap()
}
}
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct WIAPartitionData {
pub first_sector: U32,
pub num_sectors: U32,
pub group_index: U32,
pub num_groups: U32,
}
static_assert!(size_of::<WIAPartitionData>() == 0x10);
impl WIAPartitionData {
pub fn start_sector(&self) -> u32 { self.first_sector.get() }
pub fn end_sector(&self) -> u32 { self.first_sector.get() + self.num_sectors.get() }
pub fn contains_sector(&self, sector: u32) -> bool {
let start = self.first_sector.get();
sector >= start && sector < start + self.num_sectors.get()
}
pub fn contains_group(&self, group: u32) -> bool {
let start = self.group_index.get();
group >= start && group < start + self.num_groups.get()
}
}
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct WIAPartition {
pub partition_key: KeyBytes,
pub partition_data: [WIAPartitionData; 2],
}
static_assert!(size_of::<WIAPartition>() == 0x30);
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct WIARawData {
pub raw_data_offset: U64,
pub raw_data_size: U64,
pub group_index: U32,
pub num_groups: U32,
}
impl WIARawData {
pub fn start_offset(&self) -> u64 { self.raw_data_offset.get().align_down(SECTOR_SIZE as u64) }
pub fn start_sector(&self) -> u32 { (self.start_offset() / SECTOR_SIZE as u64) as u32 }
pub fn end_offset(&self) -> u64 { self.raw_data_offset.get() + self.raw_data_size.get() }
pub fn end_sector(&self) -> u32 {
self.end_offset().div_ceil(SECTOR_SIZE as u64) as u32
}
pub fn contains_sector(&self, sector: u32) -> bool {
sector >= self.start_sector() && sector < self.end_sector()
}
pub fn contains_group(&self, group: u32) -> bool {
let start = self.group_index.get();
group >= start && group < start + self.num_groups.get()
}
}
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct WIAGroup {
pub data_offset: U32,
pub data_size: U32,
}
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct RVZGroup {
pub data_offset: U32,
pub data_size_and_flag: U32,
pub rvz_packed_size: U32,
}
const COMPRESSED_BIT: u32 = 1 << 31;
impl RVZGroup {
#[inline]
pub fn data_size(&self) -> u32 { self.data_size_and_flag.get() & !COMPRESSED_BIT }
#[inline]
pub fn is_compressed(&self) -> bool { self.data_size_and_flag.get() & COMPRESSED_BIT != 0 }
}
impl From<&WIAGroup> for RVZGroup {
fn from(value: &WIAGroup) -> Self {
Self {
data_offset: value.data_offset,
data_size_and_flag: U32::new(value.data_size.get() | COMPRESSED_BIT),
rvz_packed_size: U32::new(0),
}
}
}
impl From<&RVZGroup> for WIAGroup {
fn from(value: &RVZGroup) -> Self {
Self { data_offset: value.data_offset, data_size: value.data_size().into() }
}
}
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(2))]
pub struct WIAException {
pub offset: U16,
pub hash: HashBytes,
}
pub type WIAExceptionList = Box<[WIAException]>;
pub struct BlockReaderWIA {
inner: Box<dyn DiscStream>,
header: WIAFileHeader,
disc: WIADisc,
partitions: Arc<[WIAPartition]>,
raw_data: Arc<[WIARawData]>,
groups: Arc<[RVZGroup]>,
nkit_header: Option<NKitHeader>,
decompressor: DecompressionKind,
}
impl Clone for BlockReaderWIA {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
header: self.header.clone(),
disc: self.disc.clone(),
partitions: self.partitions.clone(),
raw_data: self.raw_data.clone(),
groups: self.groups.clone(),
nkit_header: self.nkit_header.clone(),
decompressor: self.decompressor.clone(),
}
}
}
fn verify_hash(buf: &[u8], expected: &HashBytes) -> Result<()> {
let out = sha1_hash(buf);
if out != *expected {
let mut got_bytes = [0u8; 40];
let got = base16ct::lower::encode_str(&out, &mut got_bytes).unwrap(); let mut expected_bytes = [0u8; 40];
let expected = base16ct::lower::encode_str(expected, &mut expected_bytes).unwrap(); return Err(Error::DiscFormat(format!(
"WIA/RVZ hash mismatch: {}, expected {}",
got, expected
)));
}
Ok(())
}
impl BlockReaderWIA {
pub fn new(mut inner: Box<dyn DiscStream>) -> Result<Box<Self>> {
let header: WIAFileHeader =
read_at(inner.as_mut(), 0).context("Reading WIA/RVZ file header")?;
header.validate()?;
let is_rvz = header.is_rvz();
debug!("Header: {:?}", header);
let mut disc_buf: Vec<u8> = read_vec_at(
inner.as_mut(),
header.disc_size.get() as usize,
size_of::<WIAFileHeader>() as u64,
)
.context("Reading WIA/RVZ disc header")?;
verify_hash(&disc_buf, &header.disc_hash)?;
disc_buf.resize(size_of::<WIADisc>(), 0);
let disc = WIADisc::read_from_bytes(disc_buf.as_slice()).unwrap();
disc.validate(is_rvz)?;
debug!("Disc: {:?}", disc);
let nkit_header = NKitHeader::try_read_from(
inner.as_mut(),
size_of::<WIAFileHeader>() as u64 + header.disc_size.get() as u64,
disc.chunk_size.get(),
false,
);
let partitions: Arc<[WIAPartition]> = read_arc_slice_at(
inner.as_mut(),
disc.num_partitions.get() as usize,
disc.partition_offset.get(),
)
.context("Reading WIA/RVZ partition headers")?;
verify_hash(partitions.as_ref().as_bytes(), &disc.partition_hash)?;
debug!("Partitions: {:?}", partitions);
let decompressor = DecompressionKind::from_wia(&disc)?;
let raw_data: Arc<[WIARawData]> = {
let compressed_data = read_box_slice_at::<u8, _>(
inner.as_mut(),
disc.raw_data_size.get() as usize,
disc.raw_data_offset.get(),
)
.context("Reading WIA/RVZ raw data headers")?;
read_into_arc_slice(disc.num_raw_data.get() as usize, |out| {
decompressor
.decompress(&compressed_data, out)
.context("Decompressing WIA/RVZ raw data headers")
.map(|_| ())
})?
};
for (idx, rd) in raw_data.iter().enumerate() {
let start_offset = rd.start_offset();
let end_offset = rd.end_offset();
let is_last = idx == raw_data.len() - 1;
if (start_offset % SECTOR_SIZE as u64) != 0
|| (!is_last && (end_offset % SECTOR_SIZE as u64) != 0)
{
return Err(Error::DiscFormat(format!(
"WIA/RVZ raw data {} not aligned to sector: {:#X}..{:#X}",
idx, start_offset, end_offset
)));
}
}
debug!("Num raw data: {}", raw_data.len());
let groups = {
let compressed_data = read_box_slice_at::<u8, _>(
inner.as_mut(),
disc.group_size.get() as usize,
disc.group_offset.get(),
)
.context("Reading WIA/RVZ group headers")?;
if is_rvz {
read_into_arc_slice(disc.num_groups.get() as usize, |out| {
decompressor
.decompress(&compressed_data, out)
.context("Decompressing WIA/RVZ group headers")
.map(|_| ())
})?
} else {
let wia_groups =
read_into_box_slice::<WIAGroup, _>(disc.num_groups.get() as usize, |out| {
decompressor
.decompress(&compressed_data, out)
.context("Decompressing WIA/RVZ group headers")
.map(|_| ())
})?;
wia_groups.iter().map(RVZGroup::from).collect()
}
};
debug!("Num groups: {}", groups.len());
Ok(Box::new(Self {
header,
disc,
partitions,
raw_data,
groups,
inner,
nkit_header,
decompressor,
}))
}
}
fn read_exception_lists(
bytes: &mut Bytes,
chunk_size: u32,
align: bool,
) -> io::Result<Vec<WIAExceptionList>> {
let initial_remaining = bytes.remaining();
let num_exception_list = (chunk_size as usize).div_ceil(0x200000);
let mut exception_lists = vec![WIAExceptionList::default(); num_exception_list];
for exception_list in exception_lists.iter_mut() {
if bytes.remaining() < 2 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Reading WIA/RVZ exception list count",
));
}
let num_exceptions = bytes.get_u16();
if bytes.remaining() < num_exceptions as usize * size_of::<WIAException>() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Reading WIA/RVZ exception list",
));
}
let mut exceptions =
<[WIAException]>::new_box_zeroed_with_elems(num_exceptions as usize).unwrap();
bytes.copy_to_slice(exceptions.as_mut_bytes());
if !exceptions.is_empty() {
debug!("Exception list: {:?}", exceptions);
}
*exception_list = exceptions;
}
if align {
let rem = (initial_remaining - bytes.remaining()) % 4;
if rem != 0 {
bytes.advance(4 - rem);
}
}
Ok(exception_lists)
}
struct GroupInfo {
index: u32,
sector: u32,
num_sectors: u32,
size: u32,
section_offset: u64,
in_partition: bool,
partition_key: Option<KeyBytes>,
}
impl GroupInfo {
fn from_partition(index: u32, disc: &WIADisc, p: &WIAPartition, pd: &WIAPartitionData) -> Self {
let sectors_per_chunk = disc.chunk_size.get() / SECTOR_SIZE as u32;
let rel_group_idx = index - pd.group_index.get();
let sector = pd.start_sector() + rel_group_idx * sectors_per_chunk;
let num_sectors = (pd.end_sector() - sector).min(sectors_per_chunk);
let size = num_sectors * SECTOR_DATA_SIZE as u32;
let partition_offset =
(sector - p.partition_data[0].start_sector()) as u64 * SECTOR_DATA_SIZE as u64;
let partition_key = (disc.disc_head[0x61] == 0).then_some(p.partition_key);
Self {
index,
sector,
num_sectors,
size,
section_offset: partition_offset,
in_partition: true,
partition_key,
}
}
fn from_raw_data(index: u32, disc: &WIADisc, rd: &WIARawData) -> Self {
let chunk_size = disc.chunk_size.get();
let sectors_per_chunk = chunk_size / SECTOR_SIZE as u32;
let rel_group_idx = index - rd.group_index.get();
let sector = rd.start_sector() + rel_group_idx * sectors_per_chunk;
let size =
(rd.end_offset() - (sector as u64 * SECTOR_SIZE as u64)).min(chunk_size as u64) as u32;
let num_sectors = size.div_ceil(SECTOR_SIZE as u32);
let partition_offset = sector as u64 * SECTOR_SIZE as u64;
Self {
index,
sector,
num_sectors,
size,
section_offset: partition_offset,
in_partition: false,
partition_key: None,
}
}
}
fn find_group_info(
idx: u32,
disc: &WIADisc,
partitions: &[WIAPartition],
raw_data: &[WIARawData],
) -> Option<GroupInfo> {
partitions
.iter()
.find_map(|p| {
p.partition_data.iter().find_map(|pd| {
pd.contains_group(idx).then(|| GroupInfo::from_partition(idx, disc, p, pd))
})
})
.or_else(|| {
raw_data.iter().find_map(|rd| {
rd.contains_group(idx).then(|| GroupInfo::from_raw_data(idx, disc, rd))
})
})
}
fn find_group_info_for_sector(
sector: u32,
disc: &WIADisc,
partitions: &[WIAPartition],
raw_data: &[WIARawData],
) -> Option<GroupInfo> {
let sectors_per_chunk = disc.chunk_size.get() / SECTOR_SIZE as u32;
partitions
.iter()
.find_map(|p| {
p.partition_data.iter().find_map(|pd| {
pd.contains_sector(sector).then(|| {
let rel_group_idx = (sector - pd.start_sector()) / sectors_per_chunk;
GroupInfo::from_partition(pd.group_index.get() + rel_group_idx, disc, p, pd)
})
})
})
.or_else(|| {
raw_data.iter().find_map(|rd| {
rd.contains_sector(sector).then(|| {
let rel_group_idx = (sector - rd.start_sector()) / sectors_per_chunk;
GroupInfo::from_raw_data(rd.group_index.get() + rel_group_idx, disc, rd)
})
})
})
}
impl BlockReader for BlockReaderWIA {
#[instrument(name = "BlockReaderWIA::read_block", skip_all)]
fn read_block(&mut self, out: &mut [u8], sector: u32) -> io::Result<Block> {
let Some(info) =
find_group_info_for_sector(sector, &self.disc, &self.partitions, &self.raw_data)
else {
return Ok(Block::sector(sector, BlockKind::None));
};
if info.size as usize > out.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Output buffer too small for WIA/RVZ group data: {} < {}",
out.len(),
info.size
),
));
}
let Some(group) = self.groups.get(info.index as usize) else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Couldn't find WIA/RVZ group index {}", info.index),
));
};
if group.data_size() == 0 {
return Ok(Block::sectors(info.sector, info.num_sectors, BlockKind::Zero));
}
let group_data_start = group.data_offset.get() as u64 * 4;
let mut group_data = BytesMut::zeroed(group.data_size() as usize);
let io_start = Instant::now();
self.inner.read_exact_at(group_data.as_mut(), group_data_start)?;
let io_duration = io_start.elapsed();
let mut group_data = group_data.freeze();
let chunk_size = self.disc.chunk_size.get();
let uncompressed_exception_lists =
matches!(self.disc.compression(), WIACompression::None | WIACompression::Purge)
|| !group.is_compressed();
let mut exception_lists = vec![];
if info.in_partition && uncompressed_exception_lists {
exception_lists = read_exception_lists(&mut group_data, chunk_size, true)
.io_context("Reading uncompressed exception lists")?;
}
let mut decompressed = if group.is_compressed() {
let max_decompressed_size =
self.decompressor.get_content_size(group_data.as_ref())?.unwrap_or_else(|| {
if info.in_partition && !uncompressed_exception_lists {
chunk_size as usize
+ (2 + 3328 * size_of::<WIAException>())
* (chunk_size as usize).div_ceil(0x200000)
} else {
chunk_size as usize
}
});
let mut decompressed = BytesMut::zeroed(max_decompressed_size);
let len = self
.decompressor
.decompress(group_data.as_ref(), decompressed.as_mut())
.io_context("Decompressing group data")?;
decompressed.truncate(len);
decompressed.freeze()
} else {
group_data
};
if info.in_partition && !uncompressed_exception_lists {
exception_lists = read_exception_lists(&mut decompressed, chunk_size, false)
.io_context("Reading compressed exception lists")?;
}
if group.rvz_packed_size.get() > 0 {
rvz_unpack(&mut decompressed, out, &info).io_context("Unpacking RVZ group data")?;
} else {
if decompressed.remaining() != info.size as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"WIA/RVZ group {} data size mismatch: {} != {}",
info.index,
decompressed.remaining(),
info.size
),
));
}
decompressed.copy_to_slice(&mut out[..info.size as usize]);
}
if !decompressed.is_empty() {
return Err(io::Error::other("Failed to consume all group data"));
}
if info.sector == 0 {
*array_ref_mut![out, 0, DISC_HEAD_SIZE] = self.disc.disc_head;
}
let mut block = if info.in_partition {
let mut block =
Block::sectors(info.sector, info.num_sectors, BlockKind::PartDecrypted {
hash_block: false,
});
block.hash_exceptions = exception_lists.into_boxed_slice();
block
} else {
Block::sectors(info.sector, info.num_sectors, BlockKind::Raw)
};
block.io_duration = Some(io_duration);
Ok(block)
}
fn block_size(&self) -> u32 { self.disc.chunk_size.get() }
fn meta(&self) -> DiscMeta {
let level = self.disc.compression_level.get();
let mut result = DiscMeta {
format: if self.header.is_rvz() { Format::Rvz } else { Format::Wia },
block_size: Some(self.disc.chunk_size.get()),
compression: match self.disc.compression() {
WIACompression::None | WIACompression::Purge => Compression::None,
WIACompression::Bzip2 => Compression::Bzip2(level as u8),
WIACompression::Lzma => Compression::Lzma(level as u8),
WIACompression::Lzma2 => Compression::Lzma2(level as u8),
WIACompression::Zstandard => Compression::Zstandard(level as i8),
},
decrypted: true,
needs_hash_recovery: true,
lossless: true,
disc_size: Some(self.header.iso_file_size.get()),
..Default::default()
};
if let Some(nkit_header) = &self.nkit_header {
nkit_header.apply(&mut result);
}
result
}
}
#[instrument(name = "rvz_unpack", skip_all)]
fn rvz_unpack(data: &mut impl Buf, out: &mut [u8], info: &GroupInfo) -> io::Result<()> {
let mut read = 0;
let mut lfg = LaggedFibonacci::default();
while data.remaining() >= 4 {
let size = data.get_u32();
let remain = out.len() - read;
if size & COMPRESSED_BIT != 0 {
let size = size & !COMPRESSED_BIT;
if size as usize > remain {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("RVZ packed junk size too large: {} > {}", size, remain),
));
}
lfg.init_with_buf(data)?;
lfg.skip(((info.section_offset + read as u64) % SECTOR_SIZE as u64) as usize);
lfg.fill(&mut out[read..read + size as usize]);
read += size as usize;
} else {
if size as usize > remain {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("RVZ packed data size too large: {} > {}", size, remain),
));
}
data.copy_to_slice(&mut out[read..read + size as usize]);
read += size as usize;
}
}
if read != info.size as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("RVZ packed data size mismatch: {} != {}", read, info.size),
));
}
Ok(())
}
struct BlockProcessorWIA {
inner: DiscReader,
header: WIAFileHeader,
disc: WIADisc,
partitions: Arc<[WIAPartition]>,
raw_data: Arc<[WIARawData]>,
compressor: Compressor,
lfg: LaggedFibonacci,
junk_info: Arc<[JunkInfo]>,
}
impl Clone for BlockProcessorWIA {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
header: self.header.clone(),
disc: self.disc.clone(),
partitions: self.partitions.clone(),
raw_data: self.raw_data.clone(),
compressor: self.compressor.clone(),
lfg: LaggedFibonacci::default(),
junk_info: self.junk_info.clone(),
}
}
}
#[derive(Default)]
struct BlockMetaWIA {
is_compressed: bool,
data_size: u32, rvz_packed_size: u32,
data_hash: u64,
}
#[derive(Clone)]
struct JunkInfo {
start_sector: u32,
end_sector: u32,
file_ends: BTreeSet<u64>,
disc_id: [u8; 4],
disc_num: u8,
}
impl JunkInfo {
fn from_fst(
start_sector: u32,
end_sector: u32,
disc_header: &DiscHeader,
boot_header: Option<&BootHeader>,
fst: Option<Fst>,
) -> Self {
let is_wii = disc_header.is_wii();
let mut file_ends = BTreeSet::new();
if let Some(boot_header) = boot_header {
file_ends.insert(boot_header.fst_offset(is_wii) + boot_header.fst_size(is_wii));
}
if let Some(fst) = fst {
for entry in fst.nodes.iter().filter(|n| n.is_file()) {
file_ends.insert(entry.offset(is_wii) + entry.length() as u64);
}
}
Self {
start_sector,
end_sector,
file_ends,
disc_id: *array_ref![disc_header.game_id, 0, 4],
disc_num: disc_header.disc_num,
}
}
}
impl BlockProcessor for BlockProcessorWIA {
type BlockMeta = BlockMetaWIA;
#[instrument(name = "BlockProcessorWIA::process_block", skip_all)]
fn process_block(&mut self, group_idx: u32) -> io::Result<BlockResult<Self::BlockMeta>> {
let info = find_group_info(
group_idx,
&self.disc,
self.partitions.as_ref(),
self.raw_data.as_ref(),
)
.ok_or_else(|| {
io::Error::other(format!("Couldn't find partition or raw data for group {}", group_idx))
})?;
self.inner.seek(SeekFrom::Start(info.sector as u64 * SECTOR_SIZE as u64))?;
let (_, disc_data) = read_block(&mut self.inner, info.num_sectors as usize * SECTOR_SIZE)?;
let is_rvz = self.header.is_rvz();
let chunk_size = self.disc.chunk_size.get() as u64;
let (mut group_data, hash_exception_data) = if info.in_partition {
if info.size % SECTOR_DATA_SIZE as u32 != 0 {
return Err(io::Error::other("Partition group size not aligned to sector"));
}
let mut buf = BytesMut::zeroed(info.size as usize);
if let Some(key) = info.partition_key {
for i in 0..info.num_sectors as usize {
decrypt_sector_data_b2b(
array_ref![disc_data, i * SECTOR_SIZE, SECTOR_SIZE],
array_ref_mut![buf, i * SECTOR_DATA_SIZE, SECTOR_DATA_SIZE],
&key,
);
}
} else {
for i in 0..info.num_sectors as usize {
*array_ref_mut![buf, i * SECTOR_DATA_SIZE, SECTOR_DATA_SIZE] =
*array_ref![disc_data, i * SECTOR_SIZE + HASHES_SIZE, SECTOR_DATA_SIZE];
}
}
let num_exception_list = (chunk_size as usize).div_ceil(0x200000); let mut exceptions_buf = BytesMut::with_capacity(num_exception_list * 2);
for _ in 0..num_exception_list {
exceptions_buf.put_u16(0); }
(buf.freeze(), exceptions_buf.freeze())
} else {
(disc_data.clone(), Bytes::new())
};
let uncompressed_size =
(hash_exception_data.len() as u32).align_up(4) + group_data.len() as u32;
if hash_exception_data.as_ref().iter().all(|&b| b == 0)
&& group_data.as_ref().iter().all(|&b| b == 0)
{
return Ok(BlockResult {
block_idx: group_idx,
disc_data,
block_data: Bytes::new(),
meta: BlockMetaWIA::default(),
});
}
let mut meta = BlockMetaWIA {
is_compressed: false,
data_size: uncompressed_size,
rvz_packed_size: 0,
data_hash: 0,
};
if is_rvz {
if let Some(packed_data) = self.try_rvz_pack(group_data.as_ref(), &info) {
meta.data_size =
(hash_exception_data.len() as u32).align_up(4) + packed_data.len() as u32;
meta.rvz_packed_size = packed_data.len() as u32;
group_data = packed_data;
}
}
if self.compressor.kind != Compression::None {
let mut buf = BytesMut::with_capacity(hash_exception_data.len() + group_data.len());
buf.put_slice(hash_exception_data.as_ref());
buf.put_slice(group_data.as_ref());
if self
.compressor
.compress(buf.as_ref())
.map_err(|e| io::Error::other(format!("Failed to compress group: {}", e)))?
{
let compressed_size = self.compressor.buffer.len() as u32;
if !is_rvz || compressed_size.align_up(4) < meta.data_size {
let mut buf = BytesMut::zeroed(compressed_size.align_up(4) as usize);
buf[..compressed_size as usize].copy_from_slice(&self.compressor.buffer);
meta.is_compressed = true;
meta.data_size = compressed_size;
meta.data_hash = xxh64_hash(buf.as_ref());
return Ok(BlockResult {
block_idx: group_idx,
disc_data,
block_data: buf.freeze(),
meta,
});
}
} else if !is_rvz {
return Err(io::Error::other(format!(
"Failed to compress group {}: len {}, capacity {}",
group_idx,
self.compressor.buffer.len(),
self.compressor.buffer.capacity()
)));
}
}
let mut buf = BytesMut::zeroed(meta.data_size.align_up(4) as usize);
buf[..hash_exception_data.len()].copy_from_slice(hash_exception_data.as_ref());
let offset = (hash_exception_data.len() as u32).align_up(4) as usize;
buf[offset..offset + group_data.len()].copy_from_slice(group_data.as_ref());
meta.data_hash = xxh64_hash(buf.as_ref());
Ok(BlockResult { block_idx: group_idx, disc_data, block_data: buf.freeze(), meta })
}
}
impl BlockProcessorWIA {
#[instrument(name = "BlockProcessorWIA::try_rvz_pack", skip_all)]
fn try_rvz_pack(&mut self, data: &[u8], info: &GroupInfo) -> Option<Bytes> {
let Some(junk_info) = self
.junk_info
.iter()
.find(|r| info.sector >= r.start_sector && info.sector < r.end_sector)
else {
warn!("No junk info found for sector {}", info.sector);
return None;
};
let mut junk_areas = vec![];
let mut lfg_buf = [0u8; SECTOR_SIZE];
let mut lfg_sector = u32::MAX;
let mut offset = info.section_offset;
let mut data_offset = 0;
while data_offset < data.len() {
let sector = (offset / SECTOR_SIZE as u64) as u32;
let sector_offset = (offset % SECTOR_SIZE as u64) as usize;
if sector != lfg_sector {
self.lfg.init_with_seed(
junk_info.disc_id,
junk_info.disc_num,
sector as u64 * SECTOR_SIZE as u64,
);
self.lfg.fill(&mut lfg_buf);
lfg_sector = sector;
}
let zeroes = data[data_offset..].iter().take_while(|&&b| b == 0).count();
if zeroes > 0 {
let lfg_zeroes = lfg_buf[sector_offset..].iter().take_while(|&&b| b == 0).count();
if zeroes > lfg_zeroes {
if self.compressor.kind == Compression::None && zeroes > SEED_SIZE_BYTES + 4 {
debug!("Packing {} zero bytes in group {}", zeroes, info.index);
junk_areas.push((data_offset, u32::MAX, zeroes));
}
offset += zeroes as u64;
data_offset += zeroes;
continue;
}
}
let len = (SECTOR_SIZE - sector_offset).min(data.len() - data_offset);
let sector_end = offset + len as u64;
let num_match = data[data_offset..data_offset + len]
.iter()
.zip(&lfg_buf[sector_offset..sector_offset + len])
.take_while(|(a, b)| a == b)
.count();
if num_match > SEED_SIZE_BYTES + 4 {
debug!("Matched {} junk bytes at offset {:#X}", num_match, offset);
junk_areas.push((data_offset, sector, num_match));
offset += num_match as u64;
data_offset += num_match;
}
if offset < sector_end {
let next_offset = junk_info
.file_ends
.range(offset + 1..sector_end)
.next()
.cloned()
.unwrap_or(sector_end);
let skip = (next_offset - offset) as usize;
offset = next_offset;
data_offset += skip;
}
}
fn write_raw_data(
data: &[u8],
out: &mut [u8],
offset: usize,
len: usize,
data_offset: usize,
) {
*array_ref_mut![out, offset, 4] = (len as u32).to_be_bytes();
out[offset + 4..offset + 4 + len]
.copy_from_slice(&data[data_offset..data_offset + len]);
}
fn write_junk_data(
out: &mut [u8],
offset: usize,
len: usize,
sector: u32,
junk_info: &JunkInfo,
) {
let mut seed_out = [0u32; SEED_SIZE];
if sector != u32::MAX {
LaggedFibonacci::generate_seed_be(
&mut seed_out,
junk_info.disc_id,
junk_info.disc_num,
sector,
);
}
*array_ref_mut![out, offset, 4] = (len as u32 | COMPRESSED_BIT).to_be_bytes();
array_ref_mut![out, offset + 4, SEED_SIZE_BYTES].copy_from_slice(seed_out.as_bytes());
}
if !junk_areas.is_empty() {
let mut packed_data_len = 0;
let mut last_data_offset = 0;
for &(data_offset, _, num_match) in &junk_areas {
if data_offset > last_data_offset {
packed_data_len += 4 + data_offset - last_data_offset;
}
packed_data_len += 4 + SEED_SIZE_BYTES;
last_data_offset = data_offset + num_match;
}
if last_data_offset < data.len() {
packed_data_len += 4 + data.len() - last_data_offset;
}
let mut packed_data = BytesMut::zeroed(packed_data_len);
let mut packed_data_offset = 0;
last_data_offset = 0;
for &(data_offset, sector, len) in &junk_areas {
if data_offset > last_data_offset {
let len = data_offset - last_data_offset;
write_raw_data(
data,
packed_data.as_mut(),
packed_data_offset,
len,
last_data_offset,
);
packed_data_offset += 4 + len;
}
write_junk_data(packed_data.as_mut(), packed_data_offset, len, sector, junk_info);
packed_data_offset += 4 + SEED_SIZE_BYTES;
last_data_offset = data_offset + len;
}
if last_data_offset < data.len() {
let len = data.len() - last_data_offset;
write_raw_data(
data,
packed_data.as_mut(),
packed_data_offset,
len,
last_data_offset,
);
packed_data_offset += 4 + len;
last_data_offset += len;
}
assert_eq!(packed_data_offset, packed_data_len);
assert_eq!(last_data_offset, data.len());
let packed_data = packed_data.freeze();
return Some(packed_data);
}
None
}
}
#[derive(Clone)]
pub struct DiscWriterWIA {
inner: DiscReader,
header: WIAFileHeader,
disc: WIADisc,
partitions: Arc<[WIAPartition]>,
raw_data: Arc<[WIARawData]>,
group_count: u32,
data_start: u32,
is_rvz: bool,
compression: Compression,
initial_header_data: Bytes, junk_info: Arc<[JunkInfo]>,
}
#[inline]
fn partition_offset_to_raw(partition_offset: u64) -> u64 {
(partition_offset / SECTOR_DATA_SIZE as u64) * SECTOR_SIZE as u64
}
pub const RVZ_DEFAULT_CHUNK_SIZE: u32 = 0x20000; pub const WIA_DEFAULT_CHUNK_SIZE: u32 = 0x200000;
pub const RVZ_DEFAULT_COMPRESSION: Compression = Compression::Zstandard(0);
pub const WIA_DEFAULT_COMPRESSION: Compression = Compression::Lzma(0);
impl DiscWriterWIA {
pub fn new(inner: DiscReader, options: &FormatOptions) -> Result<Box<dyn DiscWriter>> {
let is_rvz = options.format == Format::Rvz;
let chunk_size = options.block_size;
let disc_header = inner.header();
let disc_size = inner.disc_size();
let mut num_partitions = 0;
let mut num_raw_data = 1;
let partition_info = inner.partitions();
for partition in partition_info {
if !partition.has_hashes {
continue;
}
num_partitions += 1;
num_raw_data += 2;
}
let header = WIAFileHeader {
magic: if is_rvz { RVZ_MAGIC } else { WIA_MAGIC },
version: if is_rvz { RVZ_VERSION } else { WIA_VERSION }.into(),
version_compatible: if is_rvz {
RVZ_VERSION_WRITE_COMPATIBLE
} else {
WIA_VERSION_WRITE_COMPATIBLE
}
.into(),
disc_size: (size_of::<WIADisc>() as u32).into(),
disc_hash: Default::default(),
iso_file_size: disc_size.into(),
wia_file_size: Default::default(),
file_head_hash: Default::default(),
};
let mut header_data = BytesMut::new();
header_data.put_slice(header.as_bytes());
let (compression, level) = match compression_to_wia(options.compression) {
Some(v) => v,
None => {
return Err(Error::Other(format!(
"Unsupported compression for WIA/RVZ: {}",
options.compression
)));
}
};
let compr_data = compr_data(options.compression).context("Building compression data")?;
let mut disc = WIADisc {
disc_type: if disc_header.is_wii() { DiscKind::Wii } else { DiscKind::GameCube }.into(),
compression: compression.into(),
compression_level: level.into(),
chunk_size: chunk_size.into(),
disc_head: *array_ref![disc_header.as_bytes(), 0, DISC_HEAD_SIZE],
num_partitions: num_partitions.into(),
partition_type_size: (size_of::<WIAPartition>() as u32).into(),
partition_offset: Default::default(),
partition_hash: Default::default(),
num_raw_data: num_raw_data.into(),
raw_data_offset: Default::default(),
raw_data_size: Default::default(),
num_groups: Default::default(),
group_offset: Default::default(),
group_size: Default::default(),
compr_data_len: compr_data.len() as u8,
compr_data: Default::default(),
};
disc.compr_data[..compr_data.len()].copy_from_slice(compr_data.as_ref());
disc.validate(is_rvz)?;
header_data.put_slice(disc.as_bytes());
let nkit_header = NKitHeader {
version: 2,
size: Some(disc_size),
crc32: Some(Default::default()),
md5: Some(Default::default()),
sha1: Some(Default::default()),
xxh64: Some(Default::default()),
junk_bits: None,
encrypted: false,
};
let mut w = header_data.writer();
nkit_header.write_to(&mut w).context("Writing NKit header")?;
let mut header_data = w.into_inner();
let mut partitions = <[WIAPartition]>::new_box_zeroed_with_elems(num_partitions as usize)?;
let mut raw_data = <[WIARawData]>::new_box_zeroed_with_elems(num_raw_data as usize)?;
raw_data[0].raw_data_offset = (DISC_HEAD_SIZE as u64).into();
let mut raw_data_idx = 0;
let mut group_idx = 0;
for (partition, wia_partition) in
partition_info.iter().filter(|p| p.has_hashes).zip(partitions.iter_mut())
{
let partition_start = partition.start_sector as u64 * SECTOR_SIZE as u64;
let partition_data_start = partition.data_start_sector as u64 * SECTOR_SIZE as u64;
let partition_end = partition.data_end_sector as u64 * SECTOR_SIZE as u64;
let boot_header = partition.boot_header();
let management_data_end =
partition_offset_to_raw(boot_header.fst_offset(true) + boot_header.fst_size(true))
.align_up(0x200000);
let management_end_sector = ((partition_data_start + management_data_end)
.min(partition_end)
/ SECTOR_SIZE as u64) as u32;
{
let cur_raw_data = &mut raw_data[raw_data_idx];
let raw_data_size = partition_start - cur_raw_data.raw_data_offset.get();
let raw_data_groups = raw_data_size.div_ceil(chunk_size as u64) as u32;
cur_raw_data.raw_data_size = raw_data_size.into();
cur_raw_data.group_index = group_idx.into();
cur_raw_data.num_groups = raw_data_groups.into();
group_idx += raw_data_groups;
raw_data_idx += 1;
}
{
let cur_raw_data = &mut raw_data[raw_data_idx];
let raw_data_size = partition_data_start - partition_start;
let raw_data_groups = raw_data_size.div_ceil(chunk_size as u64) as u32;
cur_raw_data.raw_data_offset = partition_start.into();
cur_raw_data.raw_data_size = raw_data_size.into();
cur_raw_data.group_index = group_idx.into();
cur_raw_data.num_groups = raw_data_groups.into();
group_idx += raw_data_groups;
raw_data_idx += 1;
}
wia_partition.partition_key = partition.key;
let management_num_sectors = management_end_sector - partition.data_start_sector;
let management_num_groups = (management_num_sectors as u64 * SECTOR_SIZE as u64)
.div_ceil(chunk_size as u64) as u32;
wia_partition.partition_data[0] = WIAPartitionData {
first_sector: partition.data_start_sector.into(),
num_sectors: management_num_sectors.into(),
group_index: group_idx.into(),
num_groups: management_num_groups.into(),
};
group_idx += management_num_groups;
let data_num_sectors = partition.data_end_sector - management_end_sector;
let data_num_groups =
(data_num_sectors as u64 * SECTOR_SIZE as u64).div_ceil(chunk_size as u64) as u32;
wia_partition.partition_data[1] = WIAPartitionData {
first_sector: management_end_sector.into(),
num_sectors: data_num_sectors.into(),
group_index: group_idx.into(),
num_groups: data_num_groups.into(),
};
group_idx += data_num_groups;
let next_raw_data = &mut raw_data[raw_data_idx];
next_raw_data.raw_data_offset = partition_end.into();
}
disc.partition_hash = sha1_hash(partitions.as_bytes());
{
let cur_raw_data = &mut raw_data[raw_data_idx];
let raw_data_size = disc_size - cur_raw_data.raw_data_offset.get();
let raw_data_groups = raw_data_size.div_ceil(chunk_size as u64) as u32;
cur_raw_data.raw_data_size = raw_data_size.into();
cur_raw_data.group_index = group_idx.into();
cur_raw_data.num_groups = raw_data_groups.into();
group_idx += raw_data_groups;
}
disc.num_groups = group_idx.into();
let raw_data_size = size_of::<WIARawData>() as u32 * num_raw_data;
let group_size =
if is_rvz { size_of::<RVZGroup>() } else { size_of::<WIAGroup>() } as u32 * group_idx;
header_data.put_slice(partitions.as_bytes());
header_data.put_bytes(0, raw_data_size as usize);
header_data.put_bytes(0, group_size as usize);
let rem = header_data.len() % 4;
if rem != 0 {
header_data.put_bytes(0, 4 - rem);
}
let mut junk_info = Vec::<JunkInfo>::with_capacity(partitions.len() + 1);
for partition in inner.partitions() {
junk_info.push(JunkInfo::from_fst(
partition.data_start_sector,
partition.data_end_sector,
partition.disc_header(),
Some(partition.boot_header()),
partition.fst(),
));
}
junk_info.push(JunkInfo::from_fst(
0,
disc_size.div_ceil(SECTOR_SIZE as u64) as u32,
disc_header,
inner.boot_header(),
inner.fst(),
));
let data_start = header_data.len() as u32;
Ok(Box::new(Self {
inner,
header,
disc,
partitions: Arc::from(partitions),
raw_data: Arc::from(raw_data),
group_count: group_idx,
data_start,
is_rvz,
compression: options.compression,
initial_header_data: header_data.freeze(),
junk_info: Arc::from(junk_info),
}))
}
}
impl DiscWriter for DiscWriterWIA {
fn process(
&self,
data_callback: &mut DataCallback,
options: &ProcessOptions,
) -> Result<DiscFinalization> {
let disc_size = self.inner.disc_size();
data_callback(self.initial_header_data.clone(), 0, disc_size)
.context("Failed to write WIA/RVZ header")?;
let chunk_size = self.disc.chunk_size.get();
let compressor_buf_size = if self.is_rvz {
chunk_size as usize
} else {
compress_bound(self.compression, chunk_size as usize)
};
let mut compressor = Compressor::new(self.compression, compressor_buf_size);
let digest = DigestManager::new(options);
let mut input_position = 0;
let mut file_position = self.data_start as u64;
let mut groups = <[RVZGroup]>::new_box_zeroed_with_elems(self.group_count as usize)?;
let mut group_hashes = HashMap::<u64, u32>::new();
let mut reuse_size = 0;
par_process(
BlockProcessorWIA {
inner: self.inner.clone(),
header: self.header.clone(),
disc: self.disc.clone(),
partitions: self.partitions.clone(),
raw_data: self.raw_data.clone(),
compressor: compressor.clone(),
lfg: LaggedFibonacci::default(),
junk_info: self.junk_info.clone(),
},
self.group_count,
#[cfg(feature = "threading")]
options.processor_threads,
|group| -> Result<()> {
input_position += group.disc_data.len() as u64;
digest.send(group.disc_data);
let group_idx = group.block_idx;
if file_position % 4 != 0 {
return Err(Error::Other("File position not aligned to 4".to_string()));
}
let data_offset = (file_position / 4) as u32;
groups[group_idx as usize] = RVZGroup {
data_offset: data_offset.into(),
data_size_and_flag: (group.meta.data_size
| if group.meta.is_compressed { COMPRESSED_BIT } else { 0 })
.into(),
rvz_packed_size: group.meta.rvz_packed_size.into(),
};
if group.meta.data_size == 0 {
return Ok(());
}
match group_hashes.entry(group.meta.data_hash) {
Entry::Occupied(e) => {
debug!("Reusing group data offset {} for group {}", e.get(), group_idx);
groups[group_idx as usize].data_offset = (*e.get()).into();
reuse_size += group.block_data.len();
return Ok(());
}
Entry::Vacant(e) => {
e.insert(data_offset);
}
}
if group.block_data.len() % 4 != 0 {
return Err(Error::Other("Group data size not aligned to 4".to_string()));
}
file_position += group.block_data.len() as u64;
data_callback(group.block_data, input_position, disc_size)
.with_context(|| format!("Failed to write group {group_idx}"))?;
Ok(())
},
)?;
debug!("Saved {} bytes with group data reuse", reuse_size);
let digest_results = digest.finish();
let mut nkit_header = NKitHeader {
version: 2,
size: Some(disc_size),
crc32: None,
md5: None,
sha1: None,
xxh64: None,
junk_bits: None,
encrypted: false,
};
nkit_header.apply_digests(&digest_results);
let mut nkit_header_data = Vec::new();
nkit_header.write_to(&mut nkit_header_data).context("Writing NKit header")?;
let mut header = self.header.clone();
let mut disc = self.disc.clone();
compressor.buffer = Vec::with_capacity(self.data_start as usize);
if !compressor.compress(self.raw_data.as_bytes()).context("Compressing raw data")? {
return Err(Error::Other("Failed to compress raw data".to_string()));
}
let compressed_raw_data = compressor.buffer.clone();
disc.raw_data_size = (compressed_raw_data.len() as u32).into();
let groups_data = if self.is_rvz {
Cow::Borrowed(groups.as_bytes())
} else {
let mut groups_buf = Vec::with_capacity(groups.len() * size_of::<WIAGroup>());
for group in &groups {
if compressor.kind != Compression::None
&& !group.is_compressed()
&& group.data_size() > 0
{
return Err(Error::Other("Uncompressed group in compressed WIA".to_string()));
}
if group.rvz_packed_size.get() > 0 {
return Err(Error::Other("RVZ packed group in WIA".to_string()));
}
groups_buf.extend_from_slice(WIAGroup::from(group).as_bytes());
}
Cow::Owned(groups_buf)
};
if !compressor.compress(groups_data.as_ref()).context("Compressing groups")? {
return Err(Error::Other("Failed to compress groups".to_string()));
}
let compressed_groups = compressor.buffer;
disc.group_size = (compressed_groups.len() as u32).into();
let mut header_offset = size_of::<WIAFileHeader>() as u32
+ size_of::<WIADisc>() as u32
+ nkit_header_data.len() as u32;
disc.partition_offset = (header_offset as u64).into();
header_offset += size_of_val(self.partitions.as_ref()) as u32;
disc.raw_data_offset = (header_offset as u64).into();
header_offset += compressed_raw_data.len() as u32;
disc.group_offset = (header_offset as u64).into();
header_offset += compressed_groups.len() as u32;
if header_offset > self.data_start {
return Err(Error::Other("Header offset exceeds max".to_string()));
}
header.disc_hash = sha1_hash(disc.as_bytes());
header.wia_file_size = file_position.into();
let header_bytes = header.as_bytes();
header.file_head_hash =
sha1_hash(&header_bytes[..size_of::<WIAFileHeader>() - size_of::<HashBytes>()]);
let mut header_data = BytesMut::with_capacity(header_offset as usize);
header_data.put_slice(header.as_bytes());
header_data.put_slice(disc.as_bytes());
header_data.put_slice(&nkit_header_data);
header_data.put_slice(self.partitions.as_bytes());
header_data.put_slice(&compressed_raw_data);
header_data.put_slice(&compressed_groups);
if header_data.len() as u32 != header_offset {
return Err(Error::Other("Header offset mismatch".to_string()));
}
let mut finalization =
DiscFinalization { header: header_data.freeze(), ..Default::default() };
finalization.apply_digests(&digest_results);
Ok(finalization)
}
fn progress_bound(&self) -> u64 { self.inner.disc_size() }
fn weight(&self) -> DiscWriterWeight {
if self.disc.compression() == WIACompression::None {
DiscWriterWeight::Medium
} else {
DiscWriterWeight::Heavy
}
}
}
fn compression_to_wia(compression: Compression) -> Option<(WIACompression, i32)> {
match compression {
Compression::None => Some((WIACompression::None, 0)),
Compression::Bzip2(level) => Some((WIACompression::Bzip2, level as i32)),
Compression::Lzma(level) => Some((WIACompression::Lzma, level as i32)),
Compression::Lzma2(level) => Some((WIACompression::Lzma2, level as i32)),
Compression::Zstandard(level) => Some((WIACompression::Zstandard, level as i32)),
_ => None,
}
}
fn compr_data(compression: Compression) -> io::Result<Box<[u8]>> {
match compression {
#[cfg(feature = "compress-lzma")]
Compression::Lzma(level) => {
Ok(Box::new(crate::util::compress::lzma_api::lzma_props_encode_preset(level as u32)?))
}
#[cfg(feature = "compress-lzma")]
Compression::Lzma2(level) => {
Ok(Box::new(crate::util::compress::lzma_api::lzma2_props_encode_preset(level as u32)?))
}
_ => Ok(Box::default()),
}
}
fn compress_bound(compression: Compression, size: usize) -> usize {
match compression {
Compression::None => size,
Compression::Bzip2(_) => {
size.div_ceil(4) + size
}
Compression::Lzma(_) => {
size.div_ceil(10) + size + 64000
}
Compression::Lzma2(_) => {
size.div_ceil(1000) + size + 1000
}
#[cfg(feature = "compress-zstd")]
Compression::Zstandard(_) => crate::util::compress::zstd_api::compress_bound(size),
_ => unimplemented!("CompressionKind::compress_bound {:?}", compression),
}
}