Rename PartitionHeader -> BootHeader & various fixes

This commit is contained in:
Luke Street 2025-03-04 22:54:09 -07:00
parent 73eebfe90b
commit fb3542f445
22 changed files with 694 additions and 402 deletions

View File

@ -11,11 +11,11 @@ use zerocopy::{FromZeros, IntoBytes};
use crate::{ use crate::{
disc::{ disc::{
fst::{Fst, FstBuilder}, fst::{Fst, FstBuilder},
DiscHeader, PartitionHeader, BI2_SIZE, BOOT_SIZE, GCN_MAGIC, MINI_DVD_SIZE, SECTOR_SIZE, BootHeader, DiscHeader, BI2_SIZE, BOOT_SIZE, GCN_MAGIC, MINI_DVD_SIZE, SECTOR_SIZE,
WII_MAGIC, WII_MAGIC,
}, },
read::DiscStream, read::DiscStream,
util::{align_up_64, array_ref, array_ref_mut, lfg::LaggedFibonacci}, util::{array_ref, array_ref_mut, lfg::LaggedFibonacci, Align},
Error, Result, ResultContext, Error, Result, ResultContext,
}; };
@ -33,7 +33,7 @@ pub struct FileInfo {
pub struct GCPartitionBuilder { pub struct GCPartitionBuilder {
disc_header: Box<DiscHeader>, disc_header: Box<DiscHeader>,
partition_header: Box<PartitionHeader>, boot_header: Box<BootHeader>,
user_files: Vec<FileInfo>, user_files: Vec<FileInfo>,
overrides: PartitionOverrides, overrides: PartitionOverrides,
junk_files: Vec<String>, junk_files: Vec<String>,
@ -97,7 +97,7 @@ impl GCPartitionBuilder {
} }
Self { Self {
disc_header, disc_header,
partition_header: PartitionHeader::new_box_zeroed().unwrap(), boot_header: BootHeader::new_box_zeroed().unwrap(),
user_files: Vec::new(), user_files: Vec::new(),
overrides, overrides,
junk_files: Vec::new(), junk_files: Vec::new(),
@ -110,8 +110,8 @@ impl GCPartitionBuilder {
} }
#[inline] #[inline]
pub fn set_partition_header(&mut self, partition_header: Box<PartitionHeader>) { pub fn set_boot_header(&mut self, boot_header: Box<BootHeader>) {
self.partition_header = partition_header; self.boot_header = boot_header;
} }
pub fn add_file(&mut self, info: FileInfo) -> Result<()> { pub fn add_file(&mut self, info: FileInfo) -> Result<()> {
@ -139,8 +139,8 @@ impl GCPartitionBuilder {
layout.locate_sys_files(sys_file_callback)?; layout.locate_sys_files(sys_file_callback)?;
layout.apply_overrides(&self.overrides)?; layout.apply_overrides(&self.overrides)?;
let write_info = layout.layout_files()?; let write_info = layout.layout_files()?;
let disc_size = layout.partition_header.user_offset.get() as u64 let disc_size =
+ layout.partition_header.user_size.get() as u64; layout.boot_header.user_offset.get() as u64 + layout.boot_header.user_size.get() as u64;
let junk_id = layout.junk_id(); let junk_id = layout.junk_id();
Ok(GCPartitionWriter::new(write_info, disc_size, junk_id, self.disc_header.disc_num)) Ok(GCPartitionWriter::new(write_info, disc_size, junk_id, self.disc_header.disc_num))
} }
@ -148,7 +148,7 @@ impl GCPartitionBuilder {
struct GCPartitionLayout { struct GCPartitionLayout {
disc_header: Box<DiscHeader>, disc_header: Box<DiscHeader>,
partition_header: Box<PartitionHeader>, boot_header: Box<BootHeader>,
user_files: Vec<FileInfo>, user_files: Vec<FileInfo>,
apploader_file: Option<FileInfo>, apploader_file: Option<FileInfo>,
dol_file: Option<FileInfo>, dol_file: Option<FileInfo>,
@ -162,7 +162,7 @@ impl GCPartitionLayout {
fn new(builder: &GCPartitionBuilder) -> Self { fn new(builder: &GCPartitionBuilder) -> Self {
GCPartitionLayout { GCPartitionLayout {
disc_header: builder.disc_header.clone(), disc_header: builder.disc_header.clone(),
partition_header: builder.partition_header.clone(), boot_header: builder.boot_header.clone(),
user_files: builder.user_files.clone(), user_files: builder.user_files.clone(),
apploader_file: None, apploader_file: None,
dol_file: None, dol_file: None,
@ -194,9 +194,7 @@ impl GCPartitionLayout {
))); )));
} }
self.disc_header.as_mut_bytes().copy_from_slice(&data[..size_of::<DiscHeader>()]); self.disc_header.as_mut_bytes().copy_from_slice(&data[..size_of::<DiscHeader>()]);
self.partition_header self.boot_header.as_mut_bytes().copy_from_slice(&data[size_of::<DiscHeader>()..]);
.as_mut_bytes()
.copy_from_slice(&data[size_of::<DiscHeader>()..]);
*handled = true; *handled = true;
continue; continue;
} }
@ -228,7 +226,7 @@ impl GCPartitionLayout {
// Locate other system files // Locate other system files
let is_wii = self.disc_header.is_wii(); let is_wii = self.disc_header.is_wii();
for (info, handled) in self.user_files.iter().zip(handled.iter_mut()) { for (info, handled) in self.user_files.iter().zip(handled.iter_mut()) {
let dol_offset = self.partition_header.dol_offset(is_wii); let dol_offset = self.boot_header.dol_offset(is_wii);
if (dol_offset != 0 && info.offset == Some(dol_offset)) || info.name == "sys/main.dol" { if (dol_offset != 0 && info.offset == Some(dol_offset)) || info.name == "sys/main.dol" {
let mut info = info.clone(); let mut info = info.clone();
if info.alignment.is_none() { if info.alignment.is_none() {
@ -239,7 +237,7 @@ impl GCPartitionLayout {
continue; continue;
} }
let fst_offset = self.partition_header.fst_offset(is_wii); let fst_offset = self.boot_header.fst_offset(is_wii);
if (fst_offset != 0 && info.offset == Some(fst_offset)) || info.name == "sys/fst.bin" { if (fst_offset != 0 && info.offset == Some(fst_offset)) || info.name == "sys/fst.bin" {
let mut data = Vec::with_capacity(info.size as usize); let mut data = Vec::with_capacity(info.size as usize);
file_callback(&mut data, &info.name) file_callback(&mut data, &info.name)
@ -293,7 +291,7 @@ impl GCPartitionLayout {
if let Some(audio_stream_buf_size) = overrides.audio_stream_buf_size { if let Some(audio_stream_buf_size) = overrides.audio_stream_buf_size {
self.disc_header.audio_stream_buf_size = audio_stream_buf_size; self.disc_header.audio_stream_buf_size = audio_stream_buf_size;
} }
let set_bi2 = self.raw_bi2.is_none() && overrides.region.is_some(); let set_bi2 = self.raw_bi2.is_none() || overrides.region.is_some();
let raw_bi2 = self.raw_bi2.get_or_insert_with(|| { let raw_bi2 = self.raw_bi2.get_or_insert_with(|| {
<[u8]>::new_box_zeroed_with_elems(BI2_SIZE).expect("Failed to allocate BI2") <[u8]>::new_box_zeroed_with_elems(BI2_SIZE).expect("Failed to allocate BI2")
}); });
@ -383,11 +381,11 @@ impl GCPartitionLayout {
} }
} }
let raw_fst = builder.finalize(); let raw_fst = builder.finalize();
if raw_fst.len() != self.partition_header.fst_size(is_wii) as usize { if raw_fst.len() != self.boot_header.fst_size(is_wii) as usize {
return Err(Error::Other(format!( return Err(Error::Other(format!(
"FST size mismatch: {} != {}", "FST size mismatch: {} != {}",
raw_fst.len(), raw_fst.len(),
self.partition_header.fst_size(is_wii) self.boot_header.fst_size(is_wii)
))); )));
} }
Ok(Arc::from(raw_fst)) Ok(Arc::from(raw_fst))
@ -411,7 +409,7 @@ impl GCPartitionLayout {
let mut boot = <[u8]>::new_box_zeroed_with_elems(BOOT_SIZE)?; let mut boot = <[u8]>::new_box_zeroed_with_elems(BOOT_SIZE)?;
boot[..size_of::<DiscHeader>()].copy_from_slice(self.disc_header.as_bytes()); boot[..size_of::<DiscHeader>()].copy_from_slice(self.disc_header.as_bytes());
boot[size_of::<DiscHeader>()..].copy_from_slice(self.partition_header.as_bytes()); boot[size_of::<DiscHeader>()..].copy_from_slice(self.boot_header.as_bytes());
write_info.push(WriteInfo { write_info.push(WriteInfo {
kind: WriteKind::Static(Arc::from(boot), "[BOOT]"), kind: WriteKind::Static(Arc::from(boot), "[BOOT]"),
size: BOOT_SIZE as u64, size: BOOT_SIZE as u64,
@ -433,21 +431,21 @@ impl GCPartitionLayout {
// Update DOL and FST offsets if not set // Update DOL and FST offsets if not set
let is_wii = self.disc_header.is_wii(); let is_wii = self.disc_header.is_wii();
let mut dol_offset = self.partition_header.dol_offset(is_wii); let mut dol_offset = self.boot_header.dol_offset(is_wii);
if dol_offset == 0 { if dol_offset == 0 {
dol_offset = align_up_64(last_offset, dol_file.alignment.unwrap() as u64); dol_offset = last_offset.align_up(dol_file.alignment.unwrap() as u64);
self.partition_header.set_dol_offset(dol_offset, is_wii); self.boot_header.set_dol_offset(dol_offset, is_wii);
} }
let mut fst_offset = self.partition_header.fst_offset(is_wii); let mut fst_offset = self.boot_header.fst_offset(is_wii);
if fst_offset == 0 { if fst_offset == 0 {
// TODO handle DOL in user data // TODO handle DOL in user data
fst_offset = align_up_64(dol_offset + dol_file.size, 128); fst_offset = (dol_offset + dol_file.size).align_up(128);
self.partition_header.set_fst_offset(fst_offset, is_wii); self.boot_header.set_fst_offset(fst_offset, is_wii);
} }
let fst_size = self.calculate_fst_size()?; let fst_size = self.calculate_fst_size()?;
self.partition_header.set_fst_size(fst_size, is_wii); self.boot_header.set_fst_size(fst_size, is_wii);
if self.partition_header.fst_max_size(is_wii) < fst_size { if self.boot_header.fst_max_size(is_wii) < fst_size {
self.partition_header.set_fst_max_size(fst_size, is_wii); self.boot_header.set_fst_max_size(fst_size, is_wii);
} }
if dol_offset < fst_offset { if dol_offset < fst_offset {
@ -474,10 +472,10 @@ impl GCPartitionLayout {
let mut last_offset = self.layout_system_data(&mut system_write_info)?; let mut last_offset = self.layout_system_data(&mut system_write_info)?;
// Layout user data // Layout user data
let mut user_offset = self.partition_header.user_offset.get() as u64; let mut user_offset = self.boot_header.user_offset.get() as u64;
if user_offset == 0 { if user_offset == 0 {
user_offset = align_up_64(last_offset, SECTOR_SIZE as u64); user_offset = last_offset.align_up(SECTOR_SIZE as u64);
self.partition_header.user_offset.set(user_offset as u32); self.boot_header.user_offset.set(user_offset as u32);
} else if user_offset < last_offset { } else if user_offset < last_offset {
return Err(Error::Other(format!( return Err(Error::Other(format!(
"User offset {:#X} is before FST {:#X}", "User offset {:#X} is before FST {:#X}",
@ -488,7 +486,7 @@ impl GCPartitionLayout {
for info in &self.user_files { for info in &self.user_files {
let offset = info let offset = info
.offset .offset
.unwrap_or_else(|| align_up_64(last_offset, info.alignment.unwrap_or(32) as u64)); .unwrap_or_else(|| last_offset.align_up(info.alignment.unwrap_or(32) as u64));
write_info.push(WriteInfo { write_info.push(WriteInfo {
kind: WriteKind::File(info.name.clone()), kind: WriteKind::File(info.name.clone()),
offset, offset,
@ -504,7 +502,7 @@ impl GCPartitionLayout {
write_info.push(WriteInfo { write_info.push(WriteInfo {
kind: WriteKind::Static(fst_data, "[FST]"), kind: WriteKind::Static(fst_data, "[FST]"),
size: fst_size, size: fst_size,
offset: self.partition_header.fst_offset(is_wii), offset: self.boot_header.fst_offset(is_wii),
}); });
// Add system files to write info // Add system files to write info
write_info.extend(system_write_info); write_info.extend(system_write_info);
@ -512,17 +510,17 @@ impl GCPartitionLayout {
sort_files(&mut write_info)?; sort_files(&mut write_info)?;
// Update user size if not set // Update user size if not set
if self.partition_header.user_size.get() == 0 { if self.boot_header.user_size.get() == 0 {
let user_end = if self.disc_header.is_wii() { let user_end = if self.disc_header.is_wii() {
align_up_64(last_offset, SECTOR_SIZE as u64) last_offset.align_up(SECTOR_SIZE as u64)
} else { } else {
MINI_DVD_SIZE MINI_DVD_SIZE
}; };
self.partition_header.user_size.set((user_end - user_offset) as u32); self.boot_header.user_size.set((user_end - user_offset) as u32);
} }
// Insert junk data // Insert junk data
let write_info = insert_junk_data(write_info, &self.partition_header); let write_info = insert_junk_data(write_info, &self.boot_header);
Ok(write_info) Ok(write_info)
} }
@ -534,11 +532,11 @@ impl GCPartitionLayout {
pub(crate) fn insert_junk_data( pub(crate) fn insert_junk_data(
write_info: Vec<WriteInfo>, write_info: Vec<WriteInfo>,
partition_header: &PartitionHeader, boot_header: &BootHeader,
) -> Vec<WriteInfo> { ) -> Vec<WriteInfo> {
let mut new_write_info = Vec::with_capacity(write_info.len()); let mut new_write_info = Vec::with_capacity(write_info.len());
let fst_end = partition_header.fst_offset(false) + partition_header.fst_size(false); let fst_end = boot_header.fst_offset(false) + boot_header.fst_size(false);
let file_gap = find_file_gap(&write_info, fst_end); let file_gap = find_file_gap(&write_info, fst_end);
let mut last_file_end = 0; let mut last_file_end = 0;
for info in write_info { for info in write_info {
@ -550,7 +548,7 @@ pub(crate) fn insert_junk_data(
// FST, and the junk data in between the inner and outer rim files. This attempts to // FST, and the junk data in between the inner and outer rim files. This attempts to
// determine the correct alignment, but is not 100% accurate. // determine the correct alignment, but is not 100% accurate.
let junk_start = if file_gap == Some(last_file_end) { let junk_start = if file_gap == Some(last_file_end) {
align_up_64(last_file_end, 4) last_file_end.align_up(4)
} else { } else {
aligned_end aligned_end
}; };
@ -565,8 +563,7 @@ pub(crate) fn insert_junk_data(
new_write_info.push(info); new_write_info.push(info);
} }
let aligned_end = gcm_align(last_file_end); let aligned_end = gcm_align(last_file_end);
let user_end = let user_end = boot_header.user_offset.get() as u64 + boot_header.user_size.get() as u64;
partition_header.user_offset.get() as u64 + partition_header.user_size.get() as u64;
if aligned_end < user_end && aligned_end >= fst_end { if aligned_end < user_end && aligned_end >= fst_end {
new_write_info.push(WriteInfo { new_write_info.push(WriteInfo {
kind: WriteKind::Junk, kind: WriteKind::Junk,

View File

@ -6,8 +6,10 @@ use zerocopy::FromBytes;
use crate::{ use crate::{
disc::{ disc::{
fst::Fst, wii::WiiPartitionHeader, DiscHeader, PartitionHeader, BOOT_SIZE, SECTOR_SIZE, fst::Fst, wii::WiiPartitionHeader, BootHeader, DebugHeader, DiscHeader, BB2_OFFSET,
BOOT_SIZE, SECTOR_SIZE,
}, },
util::array_ref,
Error, Result, Error, Result,
}; };
@ -308,7 +310,7 @@ pub struct PartitionInfo {
pub has_encryption: bool, pub has_encryption: bool,
/// Whether the partition data hashes are present /// Whether the partition data hashes are present
pub has_hashes: bool, pub has_hashes: bool,
/// Disc and partition header (boot.bin) /// Disc and boot header (boot.bin)
pub raw_boot: Arc<[u8; BOOT_SIZE]>, pub raw_boot: Arc<[u8; BOOT_SIZE]>,
/// File system table (fst.bin), or `None` if partition is invalid /// File system table (fst.bin), or `None` if partition is invalid
pub raw_fst: Option<Arc<[u8]>>, pub raw_fst: Option<Arc<[u8]>>,
@ -330,15 +332,26 @@ impl PartitionInfo {
/// A view into the disc header. /// A view into the disc header.
#[inline] #[inline]
pub fn disc_header(&self) -> &DiscHeader { pub fn disc_header(&self) -> &DiscHeader {
DiscHeader::ref_from_bytes(&self.raw_boot[..size_of::<DiscHeader>()]) DiscHeader::ref_from_bytes(array_ref![self.raw_boot, 0, size_of::<DiscHeader>()])
.expect("Invalid disc header alignment") .expect("Invalid disc header alignment")
} }
/// A view into the partition header. /// A view into the debug header.
#[inline] #[inline]
pub fn partition_header(&self) -> &PartitionHeader { pub fn debug_header(&self) -> &DebugHeader {
PartitionHeader::ref_from_bytes(&self.raw_boot[size_of::<DiscHeader>()..]) DebugHeader::ref_from_bytes(array_ref![
.expect("Invalid partition header alignment") self.raw_boot,
size_of::<DiscHeader>(),
size_of::<DebugHeader>()
])
.expect("Invalid debug header alignment")
}
/// A view into the boot header.
#[inline]
pub fn boot_header(&self) -> &BootHeader {
BootHeader::ref_from_bytes(array_ref![self.raw_boot, BB2_OFFSET, size_of::<BootHeader>()])
.expect("Invalid boot header alignment")
} }
/// A view into the file system table (FST). /// A view into the file system table (FST).

View File

@ -14,6 +14,7 @@ use crate::{
Result, Result,
}; };
#[derive(Clone)]
pub enum DirectDiscReaderMode { pub enum DirectDiscReaderMode {
Raw, Raw,
Partition { disc_header: Arc<DiscHeader>, data_start_sector: u32, key: KeyBytes }, Partition { disc_header: Arc<DiscHeader>, data_start_sector: u32, key: KeyBytes },
@ -31,6 +32,19 @@ pub struct DirectDiscReader {
mode: DirectDiscReaderMode, mode: DirectDiscReaderMode,
} }
impl Clone for DirectDiscReader {
fn clone(&self) -> Self {
Self {
io: self.io.clone(),
block: Block::default(),
block_buf: <[u8]>::new_box_zeroed_with_elems(self.block_buf.len()).unwrap(),
block_decrypted: false,
pos: 0,
mode: self.mode.clone(),
}
}
}
impl DirectDiscReader { impl DirectDiscReader {
pub fn new(inner: Box<dyn BlockReader>) -> Result<Box<Self>> { pub fn new(inner: Box<dyn BlockReader>) -> Result<Box<Self>> {
let block_size = inner.block_size() as usize; let block_size = inner.block_size() as usize;

View File

@ -1,6 +1,6 @@
use std::{ use std::{
io, io,
io::{BufRead, Read, Seek, SeekFrom}, io::{BufRead, Seek, SeekFrom},
mem::size_of, mem::size_of,
sync::Arc, sync::Arc,
}; };
@ -10,11 +10,11 @@ use zerocopy::{FromBytes, FromZeros, IntoBytes};
use crate::{ use crate::{
disc::{ disc::{
preloader::{fetch_sector_group, Preloader, SectorGroup, SectorGroupRequest}, preloader::{fetch_sector_group, Preloader, SectorGroup, SectorGroupRequest},
ApploaderHeader, DiscHeader, DolHeader, PartitionHeader, BI2_SIZE, BOOT_SIZE, ApploaderHeader, BootHeader, DolHeader, BB2_OFFSET, BI2_SIZE, BOOT_SIZE, SECTOR_GROUP_SIZE,
SECTOR_GROUP_SIZE, SECTOR_SIZE, SECTOR_SIZE,
}, },
io::block::BlockReader, io::block::BlockReader,
read::{PartitionEncryption, PartitionMeta, PartitionReader}, read::{DiscStream, PartitionEncryption, PartitionMeta, PartitionReader},
util::{ util::{
impl_read_for_bufread, impl_read_for_bufread,
read::{read_arc, read_arc_slice, read_from}, read::{read_arc, read_arc_slice, read_from},
@ -69,12 +69,12 @@ impl BufRead for PartitionReaderGC {
let abs_sector = (self.pos / SECTOR_SIZE as u64) as u32; let abs_sector = (self.pos / SECTOR_SIZE as u64) as u32;
let group_idx = abs_sector / 64; let group_idx = abs_sector / 64;
let abs_group_sector = group_idx * 64;
let max_groups = self.disc_size.div_ceil(SECTOR_GROUP_SIZE as u64) as u32; let max_groups = self.disc_size.div_ceil(SECTOR_GROUP_SIZE as u64) as u32;
let request = SectorGroupRequest { let request = SectorGroupRequest {
group_idx, group_idx,
partition_idx: None, partition_idx: None,
mode: PartitionEncryption::Original, mode: PartitionEncryption::Original,
force_rehash: false,
}; };
// Load sector group // Load sector group
@ -82,7 +82,7 @@ impl BufRead for PartitionReaderGC {
fetch_sector_group(request, max_groups, &mut self.sector_group, &self.preloader)?; fetch_sector_group(request, max_groups, &mut self.sector_group, &self.preloader)?;
// Calculate the number of consecutive sectors in the group // Calculate the number of consecutive sectors in the group
let group_sector = abs_sector - abs_group_sector; let group_sector = abs_sector - sector_group.start_sector;
let consecutive_sectors = sector_group.consecutive_sectors(group_sector); let consecutive_sectors = sector_group.consecutive_sectors(group_sector);
if consecutive_sectors == 0 { if consecutive_sectors == 0 {
return Ok(&[]); return Ok(&[]);
@ -90,7 +90,7 @@ impl BufRead for PartitionReaderGC {
let num_sectors = group_sector + consecutive_sectors; let num_sectors = group_sector + consecutive_sectors;
// Read from sector group buffer // Read from sector group buffer
let group_start = abs_group_sector as u64 * SECTOR_SIZE as u64; let group_start = sector_group.start_sector as u64 * SECTOR_SIZE as u64;
let offset = (self.pos - group_start) as usize; let offset = (self.pos - group_start) as usize;
let end = let end =
(num_sectors as u64 * SECTOR_SIZE as u64).min(self.disc_size - group_start) as usize; (num_sectors as u64 * SECTOR_SIZE as u64).min(self.disc_size - group_start) as usize;
@ -131,12 +131,14 @@ impl PartitionReader for PartitionReaderGC {
} }
pub(crate) fn read_dol( pub(crate) fn read_dol(
reader: &mut dyn PartitionReader, // TODO: replace with &dyn mut DiscStream when trait_upcasting is stabilized
partition_header: &PartitionHeader, // https://github.com/rust-lang/rust/issues/65991
reader: &mut (impl DiscStream + ?Sized),
boot_header: &BootHeader,
is_wii: bool, is_wii: bool,
) -> Result<Arc<[u8]>> { ) -> Result<Arc<[u8]>> {
reader reader
.seek(SeekFrom::Start(partition_header.dol_offset(is_wii))) .seek(SeekFrom::Start(boot_header.dol_offset(is_wii)))
.context("Seeking to DOL offset")?; .context("Seeking to DOL offset")?;
let dol_header: DolHeader = read_from(reader).context("Reading DOL header")?; let dol_header: DolHeader = read_from(reader).context("Reading DOL header")?;
let dol_size = (dol_header.text_offs.iter().zip(&dol_header.text_sizes)) let dol_size = (dol_header.text_offs.iter().zip(&dol_header.text_sizes))
@ -150,30 +152,28 @@ pub(crate) fn read_dol(
Ok(Arc::from(raw_dol)) Ok(Arc::from(raw_dol))
} }
pub(crate) fn read_fst<R>( pub(crate) fn read_fst(
reader: &mut R, // TODO: replace with &dyn mut DiscStream when trait_upcasting is stabilized
partition_header: &PartitionHeader, // https://github.com/rust-lang/rust/issues/65991
reader: &mut (impl DiscStream + ?Sized),
boot_header: &BootHeader,
is_wii: bool, is_wii: bool,
) -> Result<Arc<[u8]>> ) -> Result<Arc<[u8]>> {
where
R: Read + Seek + ?Sized,
{
reader reader
.seek(SeekFrom::Start(partition_header.fst_offset(is_wii))) .seek(SeekFrom::Start(boot_header.fst_offset(is_wii)))
.context("Seeking to FST offset")?; .context("Seeking to FST offset")?;
let raw_fst: Arc<[u8]> = read_arc_slice(reader, partition_header.fst_size(is_wii) as usize) let raw_fst: Arc<[u8]> = read_arc_slice(reader, boot_header.fst_size(is_wii) as usize)
.with_context(|| { .with_context(|| {
format!( format!(
"Reading partition FST (offset {}, size {})", "Reading partition FST (offset {}, size {})",
partition_header.fst_offset(is_wii), boot_header.fst_offset(is_wii),
partition_header.fst_size(is_wii) boot_header.fst_size(is_wii)
) )
})?; })?;
Ok(raw_fst) Ok(raw_fst)
} }
pub(crate) fn read_apploader<R>(reader: &mut R) -> Result<Arc<[u8]>> pub(crate) fn read_apploader(reader: &mut dyn DiscStream) -> Result<Arc<[u8]>> {
where R: Read + Seek + ?Sized {
reader reader
.seek(SeekFrom::Start(BOOT_SIZE as u64 + BI2_SIZE as u64)) .seek(SeekFrom::Start(BOOT_SIZE as u64 + BI2_SIZE as u64))
.context("Seeking to apploader offset")?; .context("Seeking to apploader offset")?;
@ -190,14 +190,10 @@ where R: Read + Seek + ?Sized {
Ok(Arc::from(raw_apploader)) Ok(Arc::from(raw_apploader))
} }
pub(crate) fn read_part_meta( pub(crate) fn read_part_meta(reader: &mut dyn DiscStream, is_wii: bool) -> Result<PartitionMeta> {
reader: &mut dyn PartitionReader,
is_wii: bool,
) -> Result<PartitionMeta> {
// boot.bin // boot.bin
let raw_boot: Arc<[u8; BOOT_SIZE]> = read_arc(reader).context("Reading boot.bin")?; let raw_boot: Arc<[u8; BOOT_SIZE]> = read_arc(reader).context("Reading boot.bin")?;
let partition_header = let boot_header = BootHeader::ref_from_bytes(&raw_boot[BB2_OFFSET..]).unwrap();
PartitionHeader::ref_from_bytes(&raw_boot[size_of::<DiscHeader>()..]).unwrap();
// bi2.bin // bi2.bin
let raw_bi2: Arc<[u8; BI2_SIZE]> = read_arc(reader).context("Reading bi2.bin")?; let raw_bi2: Arc<[u8; BI2_SIZE]> = read_arc(reader).context("Reading bi2.bin")?;
@ -206,10 +202,10 @@ pub(crate) fn read_part_meta(
let raw_apploader = read_apploader(reader)?; let raw_apploader = read_apploader(reader)?;
// fst.bin // fst.bin
let raw_fst = read_fst(reader, partition_header, is_wii)?; let raw_fst = read_fst(reader, boot_header, is_wii)?;
// main.dol // main.dol
let raw_dol = read_dol(reader, partition_header, is_wii)?; let raw_dol = read_dol(reader, boot_header, is_wii)?;
Ok(PartitionMeta { Ok(PartitionMeta {
raw_boot, raw_boot,

View File

@ -42,7 +42,10 @@ impl GroupHashes {
pub const NUM_H0_HASHES: usize = SECTOR_DATA_SIZE / HASHES_SIZE; pub const NUM_H0_HASHES: usize = SECTOR_DATA_SIZE / HASHES_SIZE;
#[instrument(skip_all)] #[instrument(skip_all)]
pub fn hash_sector_group(sector_group: &[u8; SECTOR_GROUP_SIZE]) -> Box<GroupHashes> { pub fn hash_sector_group(
sector_group: &[u8; SECTOR_GROUP_SIZE],
ignore_existing: bool,
) -> Box<GroupHashes> {
let mut result = GroupHashes::new_box_zeroed().unwrap(); let mut result = GroupHashes::new_box_zeroed().unwrap();
for (h2_index, h2_hash) in result.h2_hashes.iter_mut().enumerate() { for (h2_index, h2_hash) in result.h2_hashes.iter_mut().enumerate() {
let out_h1_hashes = array_ref_mut![result.h1_hashes, h2_index * 8, 8]; let out_h1_hashes = array_ref_mut![result.h1_hashes, h2_index * 8, 8];
@ -50,7 +53,9 @@ pub fn hash_sector_group(sector_group: &[u8; SECTOR_GROUP_SIZE]) -> Box<GroupHas
let sector = h1_index + h2_index * 8; let sector = h1_index + h2_index * 8;
let out_h0_hashes = let out_h0_hashes =
array_ref_mut![result.h0_hashes, sector * NUM_H0_HASHES, NUM_H0_HASHES]; array_ref_mut![result.h0_hashes, sector * NUM_H0_HASHES, NUM_H0_HASHES];
if array_ref![sector_group, sector * SECTOR_SIZE, 20].iter().any(|&v| v != 0) { if !ignore_existing
&& array_ref![sector_group, sector * SECTOR_SIZE, 20].iter().any(|&v| v != 0)
{
// Hash block already present, use it // Hash block already present, use it
out_h0_hashes.as_mut_bytes().copy_from_slice(array_ref![ out_h0_hashes.as_mut_bytes().copy_from_slice(array_ref![
sector_group, sector_group,

View File

@ -27,10 +27,13 @@ pub const WII_MAGIC: MagicBytes = [0x5D, 0x1C, 0x9E, 0xA3];
/// Magic bytes for GameCube discs. Located at offset 0x1C. /// Magic bytes for GameCube discs. Located at offset 0x1C.
pub const GCN_MAGIC: MagicBytes = [0xC2, 0x33, 0x9F, 0x3D]; pub const GCN_MAGIC: MagicBytes = [0xC2, 0x33, 0x9F, 0x3D];
/// Size in bytes of the disc header and partition header. (boot.bin) /// Offset in bytes of the boot block within a disc partition.
pub const BOOT_SIZE: usize = size_of::<DiscHeader>() + size_of::<PartitionHeader>(); pub const BB2_OFFSET: usize = 0x420;
/// Size in bytes of the debug and region information. (bi2.bin) /// Size in bytes of the disc header, debug block and boot block. (boot.bin)
pub const BOOT_SIZE: usize = 0x440;
/// Size in bytes of the DVD Boot Info (debug and region information, bi2.bin)
pub const BI2_SIZE: usize = 0x2000; pub const BI2_SIZE: usize = 0x2000;
/// The size of a single-layer MiniDVD. (1.4 GB) /// The size of a single-layer MiniDVD. (1.4 GB)
@ -114,20 +117,28 @@ impl DiscHeader {
pub fn has_partition_encryption(&self) -> bool { self.no_partition_encryption == 0 } pub fn has_partition_encryption(&self) -> bool { self.no_partition_encryption == 0 }
} }
/// A header describing the contents of a disc partition. /// The debug block of a disc partition.
/// ///
/// **GameCube**: Always follows the disc header. /// Located at offset 0x400 (following the disc header) within each partition.
///
/// **Wii**: Follows the disc header within each partition.
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)] #[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))] #[repr(C, align(4))]
pub struct PartitionHeader { pub struct DebugHeader {
/// Debug monitor offset /// Debug monitor offset
pub debug_mon_offset: U32, pub debug_mon_offset: U32,
/// Debug monitor load address /// Debug monitor load address
pub debug_load_address: U32, pub debug_load_address: U32,
/// Padding /// Padding
_pad1: [u8; 0x18], _pad1: [u8; 0x18],
}
static_assert!(size_of::<DebugHeader>() == 0x20);
/// The boot block (BB2) of a disc partition.
///
/// Located at offset 0x420 (following the debug block) within each partition.
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct BootHeader {
/// Offset to main DOL (Wii: >> 2) /// Offset to main DOL (Wii: >> 2)
pub dol_offset: U32, pub dol_offset: U32,
/// Offset to file system table (Wii: >> 2) /// Offset to file system table (Wii: >> 2)
@ -146,9 +157,12 @@ pub struct PartitionHeader {
_pad2: [u8; 4], _pad2: [u8; 4],
} }
static_assert!(size_of::<PartitionHeader>() == 0x40); static_assert!(size_of::<BootHeader>() == 0x20);
static_assert!(
size_of::<DiscHeader>() + size_of::<DebugHeader>() + size_of::<BootHeader>() == BOOT_SIZE
);
impl PartitionHeader { impl BootHeader {
/// Offset within the partition to the main DOL. /// Offset within the partition to the main DOL.
#[inline] #[inline]
pub fn dol_offset(&self, is_wii: bool) -> u64 { pub fn dol_offset(&self, is_wii: bool) -> u64 {

View File

@ -1,5 +1,6 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::{Display, Formatter},
io, io,
num::NonZeroUsize, num::NonZeroUsize,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
@ -19,7 +20,9 @@ use zerocopy::FromZeros;
use crate::{ use crate::{
common::PartitionInfo, common::PartitionInfo,
disc::{ disc::{
hashes::hash_sector_group, wii::HASHES_SIZE, DiscHeader, SECTOR_GROUP_SIZE, SECTOR_SIZE, hashes::{hash_sector_group, GroupHashes},
wii::HASHES_SIZE,
DiscHeader, SECTOR_GROUP_SIZE, SECTOR_SIZE,
}, },
io::{ io::{
block::{Block, BlockKind, BlockReader}, block::{Block, BlockKind, BlockReader},
@ -30,6 +33,7 @@ use crate::{
aes::{decrypt_sector, encrypt_sector}, aes::{decrypt_sector, encrypt_sector},
array_ref_mut, array_ref_mut,
}, },
IoResultContext,
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -37,14 +41,27 @@ pub struct SectorGroupRequest {
pub group_idx: u32, pub group_idx: u32,
pub partition_idx: Option<u8>, pub partition_idx: Option<u8>,
pub mode: PartitionEncryption, pub mode: PartitionEncryption,
pub force_rehash: bool,
}
impl Display for SectorGroupRequest {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.partition_idx {
Some(idx) => write!(f, "Partition {} group {}", idx, self.group_idx),
None => write!(f, "Group {}", self.group_idx),
}
}
} }
#[derive(Clone)] #[derive(Clone)]
pub struct SectorGroup { pub struct SectorGroup {
pub request: SectorGroupRequest, pub request: SectorGroupRequest,
pub start_sector: u32,
pub data: Bytes, pub data: Bytes,
pub sector_bitmap: u64, pub sector_bitmap: u64,
pub io_duration: Option<Duration>, pub io_duration: Option<Duration>,
#[allow(unused)] // TODO WIA hash exceptions
pub group_hashes: Option<Arc<GroupHashes>>,
} }
impl SectorGroup { impl SectorGroup {
@ -208,9 +225,12 @@ fn preloader_thread(
let end = Instant::now(); let end = Instant::now();
last_request_end = Some(end); last_request_end = Some(end);
let req_time = end - start; let req_time = end - start;
stat_tx if stat_tx
.send(PreloaderThreadStats { thread_id, wait_time, req_time, io_time }) .send(PreloaderThreadStats { thread_id, wait_time, req_time, io_time })
.expect("Failed to send preloader stats"); .is_err()
{
break;
}
} }
}) })
.expect("Failed to spawn preloader thread") .expect("Failed to spawn preloader thread")
@ -329,6 +349,18 @@ impl Clone for SectorGroupLoader {
} }
} }
#[derive(Default)]
struct LoadedSectorGroup {
/// Start sector of the group
start_sector: u32,
/// Bitmap of sectors that were read
sector_bitmap: u64,
/// Total duration of I/O operations
io_duration: Option<Duration>,
/// Calculated sector group hashes
group_hashes: Option<Arc<GroupHashes>>,
}
impl SectorGroupLoader { impl SectorGroupLoader {
pub fn new( pub fn new(
io: Box<dyn BlockReader>, io: Box<dyn BlockReader>,
@ -344,13 +376,21 @@ impl SectorGroupLoader {
let mut sector_group_buf = BytesMut::zeroed(SECTOR_GROUP_SIZE); let mut sector_group_buf = BytesMut::zeroed(SECTOR_GROUP_SIZE);
let out = array_ref_mut![sector_group_buf, 0, SECTOR_GROUP_SIZE]; let out = array_ref_mut![sector_group_buf, 0, SECTOR_GROUP_SIZE];
let (sector_bitmap, io_duration) = if request.partition_idx.is_some() { let LoadedSectorGroup { start_sector, sector_bitmap, io_duration, group_hashes } =
self.load_partition_group(request, out)? if request.partition_idx.is_some() {
} else { self.load_partition_group(request, out)?
self.load_raw_group(request, out)? } else {
}; self.load_raw_group(request, out)?
};
Ok(SectorGroup { request, data: sector_group_buf.freeze(), sector_bitmap, io_duration }) Ok(SectorGroup {
request,
start_sector,
data: sector_group_buf.freeze(),
sector_bitmap,
io_duration,
group_hashes,
})
} }
/// Load a sector group from a partition. /// Load a sector group from a partition.
@ -360,16 +400,16 @@ impl SectorGroupLoader {
&mut self, &mut self,
request: SectorGroupRequest, request: SectorGroupRequest,
sector_group_buf: &mut [u8; SECTOR_GROUP_SIZE], sector_group_buf: &mut [u8; SECTOR_GROUP_SIZE],
) -> io::Result<(u64, Option<Duration>)> { ) -> io::Result<LoadedSectorGroup> {
let Some(partition) = let Some(partition) =
request.partition_idx.and_then(|idx| self.partitions.get(idx as usize)) request.partition_idx.and_then(|idx| self.partitions.get(idx as usize))
else { else {
return Ok((0, None)); return Ok(LoadedSectorGroup::default());
}; };
let abs_group_sector = partition.data_start_sector + request.group_idx * 64; let abs_group_sector = partition.data_start_sector + request.group_idx * 64;
if abs_group_sector >= partition.data_end_sector { if abs_group_sector >= partition.data_end_sector {
return Ok((0, None)); return Ok(LoadedSectorGroup::default());
} }
// Bitmap of sectors that were read // Bitmap of sectors that were read
@ -382,6 +422,8 @@ impl SectorGroupLoader {
let mut hash_exceptions = Vec::<WIAException>::new(); let mut hash_exceptions = Vec::<WIAException>::new();
// Total duration of I/O operations // Total duration of I/O operations
let mut io_duration = None; let mut io_duration = None;
// Calculated sector group hashes
let mut group_hashes = None;
// Read sector group // Read sector group
for sector in 0..64 { for sector in 0..64 {
@ -397,7 +439,10 @@ impl SectorGroupLoader {
// Read new block // Read new block
if !self.block.contains(abs_sector) { if !self.block.contains(abs_sector) {
self.block = self.io.read_block(self.block_buf.as_mut(), abs_sector)?; self.block = self
.io
.read_block(self.block_buf.as_mut(), abs_sector)
.io_with_context(|| format!("Reading block for sector {abs_sector}"))?;
if let Some(duration) = self.block.io_duration { if let Some(duration) = self.block.io_duration {
*io_duration.get_or_insert_with(Duration::default) += duration; *io_duration.get_or_insert_with(Duration::default) += duration;
} }
@ -408,16 +453,21 @@ impl SectorGroupLoader {
} }
// Add hash exceptions // Add hash exceptions
self.block.append_hash_exceptions(abs_sector, sector, &mut hash_exceptions)?; self.block
.append_hash_exceptions(abs_sector, sector, &mut hash_exceptions)
.io_with_context(|| format!("Appending hash exceptions for sector {abs_sector}"))?;
// Read new sector into buffer // Read new sector into buffer
let (encrypted, has_hashes) = self.block.copy_sector( let (encrypted, has_hashes) = self
sector_data, .block
self.block_buf.as_mut(), .copy_sector(
abs_sector, sector_data,
partition.disc_header(), self.block_buf.as_mut(),
Some(partition), abs_sector,
)?; partition.disc_header(),
Some(partition),
)
.io_with_context(|| format!("Copying sector {abs_sector} from block"))?;
if !encrypted { if !encrypted {
decrypted_sectors |= 1 << sector; decrypted_sectors |= 1 << sector;
} }
@ -428,7 +478,9 @@ impl SectorGroupLoader {
} }
// Recover hashes // Recover hashes
if request.mode != PartitionEncryption::ForceDecryptedNoHashes && hash_recovery_sectors != 0 if request.force_rehash
|| (request.mode != PartitionEncryption::ForceDecryptedNoHashes
&& hash_recovery_sectors != 0)
{ {
// Decrypt any encrypted sectors // Decrypt any encrypted sectors
if decrypted_sectors != u64::MAX { if decrypted_sectors != u64::MAX {
@ -443,16 +495,19 @@ impl SectorGroupLoader {
} }
// Recover hashes // Recover hashes
let group_hashes = hash_sector_group(sector_group_buf); let hashes = hash_sector_group(sector_group_buf, request.force_rehash);
// Apply hashes // Apply hashes
for sector in 0..64 { for sector in 0..64 {
let sector_data = let sector_data =
array_ref_mut![sector_group_buf, sector * SECTOR_SIZE, SECTOR_SIZE]; array_ref_mut![sector_group_buf, sector * SECTOR_SIZE, SECTOR_SIZE];
if (hash_recovery_sectors >> sector) & 1 == 1 { if (hash_recovery_sectors >> sector) & 1 == 1 {
group_hashes.apply(sector_data, sector); hashes.apply(sector_data, sector);
} }
} }
// Persist hashes
group_hashes = Some(Arc::from(hashes));
} }
// Apply hash exceptions // Apply hash exceptions
@ -508,7 +563,12 @@ impl SectorGroupLoader {
} }
} }
Ok((sector_bitmap, io_duration)) Ok(LoadedSectorGroup {
start_sector: abs_group_sector,
sector_bitmap,
io_duration,
group_hashes,
})
} }
/// Loads a non-partition sector group. /// Loads a non-partition sector group.
@ -516,7 +576,7 @@ impl SectorGroupLoader {
&mut self, &mut self,
request: SectorGroupRequest, request: SectorGroupRequest,
sector_group_buf: &mut [u8; SECTOR_GROUP_SIZE], sector_group_buf: &mut [u8; SECTOR_GROUP_SIZE],
) -> io::Result<(u64, Option<Duration>)> { ) -> io::Result<LoadedSectorGroup> {
let abs_group_sector = request.group_idx * 64; let abs_group_sector = request.group_idx * 64;
// Bitmap of sectors that were read // Bitmap of sectors that were read
@ -534,7 +594,10 @@ impl SectorGroupLoader {
// Read new block // Read new block
if !self.block.contains(abs_sector) { if !self.block.contains(abs_sector) {
self.block = self.io.read_block(self.block_buf.as_mut(), abs_sector)?; self.block = self
.io
.read_block(self.block_buf.as_mut(), abs_sector)
.io_with_context(|| format!("Reading block for sector {abs_sector}"))?;
if let Some(duration) = self.block.io_duration { if let Some(duration) = self.block.io_duration {
*io_duration.get_or_insert_with(Duration::default) += duration; *io_duration.get_or_insert_with(Duration::default) += duration;
} }
@ -544,17 +607,24 @@ impl SectorGroupLoader {
} }
// Read new sector into buffer // Read new sector into buffer
self.block.copy_sector( self.block
sector_data, .copy_sector(
self.block_buf.as_mut(), sector_data,
abs_sector, self.block_buf.as_mut(),
self.disc_header.as_ref(), abs_sector,
None, self.disc_header.as_ref(),
)?; None,
)
.io_with_context(|| format!("Copying sector {abs_sector} from block"))?;
sector_bitmap |= 1 << sector; sector_bitmap |= 1 << sector;
} }
Ok((sector_bitmap, io_duration)) Ok(LoadedSectorGroup {
start_sector: abs_group_sector,
sector_bitmap,
io_duration,
group_hashes: None,
})
} }
} }

View File

@ -5,6 +5,7 @@ use std::{
}; };
use bytes::Bytes; use bytes::Bytes;
use polonius_the_crab::{polonius, polonius_return};
use tracing::warn; use tracing::warn;
use zerocopy::{FromBytes, IntoBytes}; use zerocopy::{FromBytes, IntoBytes};
@ -21,13 +22,13 @@ use crate::{
PartitionReaderWii, WiiPartEntry, WiiPartGroup, WiiPartitionHeader, REGION_OFFSET, PartitionReaderWii, WiiPartEntry, WiiPartGroup, WiiPartitionHeader, REGION_OFFSET,
REGION_SIZE, WII_PART_GROUP_OFF, REGION_SIZE, WII_PART_GROUP_OFF,
}, },
DiscHeader, PartitionHeader, BOOT_SIZE, DL_DVD_SIZE, MINI_DVD_SIZE, SECTOR_GROUP_SIZE, BootHeader, DiscHeader, BB2_OFFSET, BOOT_SIZE, DL_DVD_SIZE, MINI_DVD_SIZE,
SECTOR_SIZE, SL_DVD_SIZE, SECTOR_GROUP_SIZE, SECTOR_SIZE, SL_DVD_SIZE,
}, },
io::block::BlockReader, io::block::BlockReader,
read::{DiscMeta, DiscOptions, PartitionEncryption, PartitionOptions, PartitionReader}, read::{DiscMeta, DiscOptions, PartitionEncryption, PartitionOptions, PartitionReader},
util::{ util::{
impl_read_for_bufread, array_ref, impl_read_for_bufread,
read::{read_arc, read_from, read_vec}, read::{read_arc, read_from, read_vec},
}, },
Error, Result, ResultContext, Error, Result, ResultContext,
@ -210,15 +211,18 @@ impl DiscReader {
} }
} }
/// A reference to the disc's boot header (BB2) for GameCube discs.
/// For Wii discs, use the boot header from the appropriate [PartitionInfo].
#[inline] #[inline]
pub fn partition_header(&self) -> Option<&PartitionHeader> { pub fn boot_header(&self) -> Option<&BootHeader> {
match &self.disc_data { match &self.disc_data {
DiscReaderData::GameCube { .. } => Some( DiscReaderData::GameCube { .. } => Some(
PartitionHeader::ref_from_bytes( BootHeader::ref_from_bytes(array_ref![
&self.raw_boot[size_of::<DiscHeader>() self.raw_boot,
..size_of::<DiscHeader>() + size_of::<PartitionHeader>()], BB2_OFFSET,
) size_of::<BootHeader>()
.expect("Invalid partition header alignment"), ])
.expect("Invalid boot header alignment"),
), ),
_ => None, _ => None,
} }
@ -306,48 +310,62 @@ impl DiscReader {
} }
} }
pub fn fill_buf_internal(&mut self) -> io::Result<Bytes> { pub fn load_sector_group(
if self.pos >= self.size { &mut self,
return Ok(Bytes::new()); abs_sector: u32,
} force_rehash: bool,
) -> io::Result<(&SectorGroup, bool)> {
// Read from modified disc header let (request, max_groups) = if let Some(partition) =
if self.pos < size_of::<DiscHeader>() as u64 {
if let Some(alt_disc_header) = &self.alt_disc_header {
return Ok(Bytes::copy_from_slice(
&alt_disc_header.as_bytes()[self.pos as usize..],
));
}
}
// Build sector group request
let abs_sector = (self.pos / SECTOR_SIZE as u64) as u32;
let (request, abs_group_sector, max_groups) = if let Some(partition) =
self.orig_partitions().iter().find(|part| part.data_contains_sector(abs_sector)) self.orig_partitions().iter().find(|part| part.data_contains_sector(abs_sector))
{ {
let group_idx = (abs_sector - partition.data_start_sector) / 64; let group_idx = (abs_sector - partition.data_start_sector) / 64;
let abs_group_sector = partition.data_start_sector + group_idx * 64;
let max_groups = (partition.data_end_sector - partition.data_start_sector).div_ceil(64); let max_groups = (partition.data_end_sector - partition.data_start_sector).div_ceil(64);
let request = SectorGroupRequest { let request = SectorGroupRequest {
group_idx, group_idx,
partition_idx: Some(partition.index as u8), partition_idx: Some(partition.index as u8),
mode: self.mode, mode: self.mode,
force_rehash,
}; };
(request, abs_group_sector, max_groups) (request, max_groups)
} else { } else {
let group_idx = abs_sector / 64; let group_idx = abs_sector / 64;
let abs_group_sector = group_idx * 64;
let max_groups = self.size.div_ceil(SECTOR_GROUP_SIZE as u64) as u32; let max_groups = self.size.div_ceil(SECTOR_GROUP_SIZE as u64) as u32;
let request = SectorGroupRequest { group_idx, partition_idx: None, mode: self.mode }; let request = SectorGroupRequest {
(request, abs_group_sector, max_groups) group_idx,
partition_idx: None,
mode: self.mode,
force_rehash,
};
(request, max_groups)
}; };
// Load sector group // Load sector group
let (sector_group, _updated) = let (sector_group, updated) =
fetch_sector_group(request, max_groups, &mut self.sector_group, &self.preloader)?; fetch_sector_group(request, max_groups, &mut self.sector_group, &self.preloader)?;
Ok((sector_group, updated))
}
pub fn fill_buf_internal(&mut self) -> io::Result<Bytes> {
let pos = self.pos;
let size = self.size;
if pos >= size {
return Ok(Bytes::new());
}
// Read from modified disc header
if pos < size_of::<DiscHeader>() as u64 {
if let Some(alt_disc_header) = &self.alt_disc_header {
return Ok(Bytes::copy_from_slice(&alt_disc_header.as_bytes()[pos as usize..]));
}
}
// Load sector group
let abs_sector = (pos / SECTOR_SIZE as u64) as u32;
let (sector_group, _updated) = self.load_sector_group(abs_sector, false)?;
// Calculate the number of consecutive sectors in the group // Calculate the number of consecutive sectors in the group
let group_sector = abs_sector - abs_group_sector; let group_sector = abs_sector - sector_group.start_sector;
let consecutive_sectors = sector_group.consecutive_sectors(group_sector); let consecutive_sectors = sector_group.consecutive_sectors(group_sector);
if consecutive_sectors == 0 { if consecutive_sectors == 0 {
return Ok(Bytes::new()); return Ok(Bytes::new());
@ -355,54 +373,37 @@ impl DiscReader {
let num_sectors = group_sector + consecutive_sectors; let num_sectors = group_sector + consecutive_sectors;
// Read from sector group buffer // Read from sector group buffer
let group_start = abs_group_sector as u64 * SECTOR_SIZE as u64; let group_start = sector_group.start_sector as u64 * SECTOR_SIZE as u64;
let offset = (self.pos - group_start) as usize; let offset = (pos - group_start) as usize;
let end = (num_sectors as u64 * SECTOR_SIZE as u64).min(self.size - group_start) as usize; let end = (num_sectors as u64 * SECTOR_SIZE as u64).min(size - group_start) as usize;
Ok(sector_group.data.slice(offset..end)) Ok(sector_group.data.slice(offset..end))
} }
} }
impl BufRead for DiscReader { impl BufRead for DiscReader {
fn fill_buf(&mut self) -> io::Result<&[u8]> { fn fill_buf(&mut self) -> io::Result<&[u8]> {
if self.pos >= self.size { let pos = self.pos;
let size = self.size;
if pos >= size {
return Ok(&[]); return Ok(&[]);
} }
// Read from modified disc header let mut this = self;
if self.pos < size_of::<DiscHeader>() as u64 { polonius!(|this| -> io::Result<&'polonius [u8]> {
if let Some(alt_disc_header) = &self.alt_disc_header { // Read from modified disc header
return Ok(&alt_disc_header.as_bytes()[self.pos as usize..]); if pos < size_of::<DiscHeader>() as u64 {
if let Some(alt_disc_header) = &this.alt_disc_header {
polonius_return!(Ok(&alt_disc_header.as_bytes()[pos as usize..]));
}
} }
} });
// Build sector group request
let abs_sector = (self.pos / SECTOR_SIZE as u64) as u32;
let (request, abs_group_sector, max_groups) = if let Some(partition) =
self.orig_partitions().iter().find(|part| part.data_contains_sector(abs_sector))
{
let group_idx = (abs_sector - partition.data_start_sector) / 64;
let abs_group_sector = partition.data_start_sector + group_idx * 64;
let max_groups = (partition.data_end_sector - partition.data_start_sector).div_ceil(64);
let request = SectorGroupRequest {
group_idx,
partition_idx: Some(partition.index as u8),
mode: self.mode,
};
(request, abs_group_sector, max_groups)
} else {
let group_idx = abs_sector / 64;
let abs_group_sector = group_idx * 64;
let max_groups = self.size.div_ceil(SECTOR_GROUP_SIZE as u64) as u32;
let request = SectorGroupRequest { group_idx, partition_idx: None, mode: self.mode };
(request, abs_group_sector, max_groups)
};
// Load sector group // Load sector group
let (sector_group, _updated) = let abs_sector = (pos / SECTOR_SIZE as u64) as u32;
fetch_sector_group(request, max_groups, &mut self.sector_group, &self.preloader)?; let (sector_group, _updated) = this.load_sector_group(abs_sector, false)?;
// Calculate the number of consecutive sectors in the group // Calculate the number of consecutive sectors in the group
let group_sector = abs_sector - abs_group_sector; let group_sector = abs_sector - sector_group.start_sector;
let consecutive_sectors = sector_group.consecutive_sectors(group_sector); let consecutive_sectors = sector_group.consecutive_sectors(group_sector);
if consecutive_sectors == 0 { if consecutive_sectors == 0 {
return Ok(&[]); return Ok(&[]);
@ -410,9 +411,9 @@ impl BufRead for DiscReader {
let num_sectors = group_sector + consecutive_sectors; let num_sectors = group_sector + consecutive_sectors;
// Read from sector group buffer // Read from sector group buffer
let group_start = abs_group_sector as u64 * SECTOR_SIZE as u64; let group_start = sector_group.start_sector as u64 * SECTOR_SIZE as u64;
let offset = (self.pos - group_start) as usize; let offset = (pos - group_start) as usize;
let end = (num_sectors as u64 * SECTOR_SIZE as u64).min(self.size - group_start) as usize; let end = (num_sectors as u64 * SECTOR_SIZE as u64).min(size - group_start) as usize;
Ok(&sector_group.data[offset..end]) Ok(&sector_group.data[offset..end])
} }
@ -490,37 +491,43 @@ fn read_partition_info(
data_start_sector, data_start_sector,
key, key,
}); });
let raw_boot: Arc<[u8; BOOT_SIZE]> = let raw_boot: Arc<[u8; BOOT_SIZE]> = read_arc(reader).context("Reading boot data")?;
read_arc(reader).context("Reading partition headers")?;
let partition_disc_header = let partition_disc_header =
DiscHeader::ref_from_bytes(&raw_boot[..size_of::<DiscHeader>()]) DiscHeader::ref_from_bytes(array_ref![raw_boot, 0, size_of::<DiscHeader>()])
.expect("Invalid disc header alignment"); .expect("Invalid disc header alignment");
let partition_header = let boot_header = BootHeader::ref_from_bytes(&raw_boot[BB2_OFFSET..])
PartitionHeader::ref_from_bytes(&raw_boot[size_of::<DiscHeader>()..]) .expect("Invalid boot header alignment");
.expect("Invalid partition header alignment");
let raw_fst = if partition_disc_header.is_wii() { let raw_fst = if partition_disc_header.is_wii() {
let raw_fst = read_fst(reader, partition_header, true)?; let raw_fst = read_fst(reader, boot_header, true)?;
let fst = Fst::new(&raw_fst)?; match Fst::new(&raw_fst) {
let max_fst_offset = fst Ok(fst) => {
.nodes let max_fst_offset = fst
.iter() .nodes
.filter_map(|n| match n.kind() { .iter()
NodeKind::File => Some(n.offset(true) + n.length() as u64), .filter_map(|n| match n.kind() {
_ => None, NodeKind::File => Some(n.offset(true) + n.length() as u64),
}) _ => None,
.max() })
.unwrap_or(0); .max()
if max_fst_offset > data_size { .unwrap_or(0);
if data_size == 0 { if max_fst_offset > data_size {
// Guess data size for decrypted partitions if data_size == 0 {
data_end_sector = max_fst_offset.div_ceil(SECTOR_SIZE as u64) as u32; // Guess data size for decrypted partitions
} else { data_end_sector =
return Err(Error::DiscFormat(format!( max_fst_offset.div_ceil(SECTOR_SIZE as u64) as u32;
"Partition {group_idx}:{part_idx} FST exceeds data size", } else {
))); return Err(Error::DiscFormat(format!(
"Partition {group_idx}:{part_idx} FST exceeds data size",
)));
}
}
Some(raw_fst)
}
Err(e) => {
warn!("Partition {group_idx}:{part_idx} FST is not valid: {e}");
None
} }
} }
Some(raw_fst)
} else { } else {
warn!("Partition {group_idx}:{part_idx} is not valid"); warn!("Partition {group_idx}:{part_idx} is not valid");
None None

View File

@ -378,6 +378,7 @@ impl BufRead for PartitionReaderWii {
} else { } else {
PartitionEncryption::ForceDecryptedNoHashes PartitionEncryption::ForceDecryptedNoHashes
}, },
force_rehash: false,
}; };
// Load sector group // Load sector group

View File

@ -107,7 +107,8 @@ pub const WBFS_MAGIC: MagicBytes = *b"WBFS";
pub const WIA_MAGIC: MagicBytes = *b"WIA\x01"; pub const WIA_MAGIC: MagicBytes = *b"WIA\x01";
pub const RVZ_MAGIC: MagicBytes = *b"RVZ\x01"; pub const RVZ_MAGIC: MagicBytes = *b"RVZ\x01";
pub fn detect<R: Read + ?Sized>(stream: &mut R) -> io::Result<Option<Format>> { pub fn detect<R>(stream: &mut R) -> io::Result<Option<Format>>
where R: Read + ?Sized {
let data: [u8; 0x20] = match read_from(stream) { let data: [u8; 0x20] = match read_from(stream) {
Ok(magic) => magic, Ok(magic) => magic,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None), Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),

View File

@ -15,14 +15,14 @@ use crate::{
gcn::{read_dol, read_fst}, gcn::{read_dol, read_fst},
reader::DiscReader, reader::DiscReader,
writer::{DataCallback, DiscWriter}, writer::{DataCallback, DiscWriter},
DiscHeader, PartitionHeader, SECTOR_SIZE, BootHeader, DiscHeader, BB2_OFFSET, SECTOR_SIZE,
}, },
io::block::{Block, BlockKind, BlockReader, TGC_MAGIC}, io::block::{Block, BlockKind, BlockReader, TGC_MAGIC},
read::{DiscMeta, DiscStream, PartitionOptions, PartitionReader}, read::{DiscMeta, DiscStream, PartitionOptions, PartitionReader},
util::{ util::{
align_up_32, array_ref, array_ref,
read::{read_arc, read_arc_slice, read_from, read_with_zero_fill}, read::{read_arc, read_arc_slice, read_from, read_with_zero_fill},
static_assert, static_assert, Align,
}, },
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions}, write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
Error, Result, ResultContext, Error, Result, ResultContext,
@ -82,19 +82,21 @@ impl BlockReaderTGC {
} }
let disc_size = (header.gcm_files_start.get() + header.user_size.get()) as u64; let disc_size = (header.gcm_files_start.get() + header.user_size.get()) as u64;
// Read disc header and partition header // Read GCM header
inner inner
.seek(SeekFrom::Start(header.header_offset.get() as u64)) .seek(SeekFrom::Start(header.header_offset.get() as u64))
.context("Seeking to GCM header")?; .context("Seeking to GCM header")?;
let raw_header = let raw_header =
read_arc::<[u8; GCM_HEADER_SIZE], _>(inner.as_mut()).context("Reading GCM header")?; read_arc::<[u8; GCM_HEADER_SIZE], _>(inner.as_mut()).context("Reading GCM header")?;
let (disc_header, remain) = DiscHeader::ref_from_prefix(raw_header.as_ref()) let disc_header =
.expect("Invalid disc header alignment"); DiscHeader::ref_from_bytes(array_ref![raw_header, 0, size_of::<DiscHeader>()])
.expect("Invalid disc header alignment");
let disc_header = disc_header.clone(); let disc_header = disc_header.clone();
let (partition_header, _) = let boot_header =
PartitionHeader::ref_from_prefix(remain).expect("Invalid partition header alignment"); BootHeader::ref_from_bytes(array_ref![raw_header, BB2_OFFSET, size_of::<BootHeader>()])
let partition_header = partition_header.clone(); .expect("Invalid boot header alignment");
let boot_header = boot_header.clone();
// Read DOL // Read DOL
inner.seek(SeekFrom::Start(header.dol_offset.get() as u64)).context("Seeking to DOL")?; inner.seek(SeekFrom::Start(header.dol_offset.get() as u64)).context("Seeking to DOL")?;
@ -116,12 +118,12 @@ impl BlockReaderTGC {
write_info.push(WriteInfo { write_info.push(WriteInfo {
kind: WriteKind::Static(raw_dol, "sys/main.dol"), kind: WriteKind::Static(raw_dol, "sys/main.dol"),
size: header.dol_size.get() as u64, size: header.dol_size.get() as u64,
offset: partition_header.dol_offset(false), offset: boot_header.dol_offset(false),
}); });
write_info.push(WriteInfo { write_info.push(WriteInfo {
kind: WriteKind::Static(raw_fst.clone(), "sys/fst.bin"), kind: WriteKind::Static(raw_fst.clone(), "sys/fst.bin"),
size: header.fst_size.get() as u64, size: header.fst_size.get() as u64,
offset: partition_header.fst_offset(false), offset: boot_header.fst_offset(false),
}); });
// Collect files // Collect files
@ -136,7 +138,7 @@ impl BlockReaderTGC {
}); });
} }
write_info.sort_unstable_by(|a, b| a.offset.cmp(&b.offset).then(a.size.cmp(&b.size))); write_info.sort_unstable_by(|a, b| a.offset.cmp(&b.offset).then(a.size.cmp(&b.size)));
let write_info = insert_junk_data(write_info, &partition_header); let write_info = insert_junk_data(write_info, &boot_header);
let file_callback = FileCallbackTGC::new(inner, raw_fst, header); let file_callback = FileCallbackTGC::new(inner, raw_fst, header);
let disc_id = *array_ref![disc_header.game_id, 0, 4]; let disc_id = *array_ref![disc_header.game_id, 0, 4];
@ -225,14 +227,13 @@ impl DiscWriterTGC {
// Read GCM header // Read GCM header
let mut raw_header = <[u8; GCM_HEADER_SIZE]>::new_box_zeroed()?; let mut raw_header = <[u8; GCM_HEADER_SIZE]>::new_box_zeroed()?;
inner.read_exact(raw_header.as_mut()).context("Reading GCM header")?; inner.read_exact(raw_header.as_mut()).context("Reading GCM header")?;
let (_, remain) = DiscHeader::ref_from_prefix(raw_header.as_ref()) let boot_header =
.expect("Invalid disc header alignment"); BootHeader::ref_from_bytes(array_ref![raw_header, BB2_OFFSET, size_of::<BootHeader>()])
let (partition_header, _) = .expect("Invalid boot header alignment");
PartitionHeader::ref_from_prefix(remain).expect("Invalid partition header alignment");
// Read DOL // Read DOL
let raw_dol = read_dol(inner.as_mut(), partition_header, false)?; let raw_dol = read_dol(inner.as_mut(), boot_header, false)?;
let raw_fst = read_fst(inner.as_mut(), partition_header, false)?; let raw_fst = read_fst(inner.as_mut(), boot_header, false)?;
// Parse FST // Parse FST
let fst = Fst::new(&raw_fst)?; let fst = Fst::new(&raw_fst)?;
@ -249,11 +250,10 @@ impl DiscWriterTGC {
// Layout system files // Layout system files
let gcm_header_offset = SECTOR_SIZE as u32; let gcm_header_offset = SECTOR_SIZE as u32;
let fst_offset = gcm_header_offset + GCM_HEADER_SIZE as u32; let fst_offset = gcm_header_offset + GCM_HEADER_SIZE as u32;
let dol_offset = align_up_32(fst_offset + partition_header.fst_size.get(), 32); let dol_offset = (fst_offset + boot_header.fst_size.get()).align_up(32);
let user_size = let user_size =
partition_header.user_offset.get() + partition_header.user_size.get() - gcm_files_start; boot_header.user_offset.get() + boot_header.user_size.get() - gcm_files_start;
let user_end = let user_end = (dol_offset + raw_dol.len() as u32 + user_size).align_up(SECTOR_SIZE as u32);
align_up_32(dol_offset + raw_dol.len() as u32 + user_size, SECTOR_SIZE as u32);
let user_offset = user_end - user_size; let user_offset = user_end - user_size;
let header = TGCHeader { let header = TGCHeader {
@ -262,8 +262,8 @@ impl DiscWriterTGC {
header_offset: gcm_header_offset.into(), header_offset: gcm_header_offset.into(),
header_size: (GCM_HEADER_SIZE as u32).into(), header_size: (GCM_HEADER_SIZE as u32).into(),
fst_offset: fst_offset.into(), fst_offset: fst_offset.into(),
fst_size: partition_header.fst_size, fst_size: boot_header.fst_size,
fst_max_size: partition_header.fst_max_size, fst_max_size: boot_header.fst_max_size,
dol_offset: dol_offset.into(), dol_offset: dol_offset.into(),
dol_size: (raw_dol.len() as u32).into(), dol_size: (raw_dol.len() as u32).into(),
user_offset: user_offset.into(), user_offset: user_offset.into(),

View File

@ -19,7 +19,7 @@ use crate::{
reader::DiscReader, reader::DiscReader,
wii::{HASHES_SIZE, SECTOR_DATA_SIZE}, wii::{HASHES_SIZE, SECTOR_DATA_SIZE},
writer::{par_process, read_block, BlockProcessor, BlockResult, DataCallback, DiscWriter}, writer::{par_process, read_block, BlockProcessor, BlockResult, DataCallback, DiscWriter},
DiscHeader, PartitionHeader, SECTOR_SIZE, BootHeader, DiscHeader, SECTOR_SIZE,
}, },
io::{ io::{
block::{Block, BlockKind, BlockReader, RVZ_MAGIC, WIA_MAGIC}, block::{Block, BlockKind, BlockReader, RVZ_MAGIC, WIA_MAGIC},
@ -28,15 +28,15 @@ use crate::{
read::{DiscMeta, DiscStream}, read::{DiscMeta, DiscStream},
util::{ util::{
aes::decrypt_sector_data_b2b, aes::decrypt_sector_data_b2b,
align_up_32, align_up_64, array_ref, array_ref_mut, array_ref, array_ref_mut,
compress::{Compressor, DecompressionKind, Decompressor}, compress::{Compressor, DecompressionKind, Decompressor},
digest::{sha1_hash, xxh64_hash, DigestManager}, digest::{sha1_hash, xxh64_hash, DigestManager},
lfg::{LaggedFibonacci, SEED_SIZE, SEED_SIZE_BYTES}, lfg::{LaggedFibonacci, SEED_SIZE, SEED_SIZE_BYTES},
read::{read_arc_slice, read_from, read_vec}, read::{read_arc_slice, read_from, read_vec},
static_assert, static_assert, Align,
}, },
write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions}, write::{DiscFinalization, DiscWriterWeight, FormatOptions, ProcessOptions},
Error, Result, ResultContext, Error, IoResultContext, Result, ResultContext,
}; };
const WIA_VERSION: u32 = 0x01000000; const WIA_VERSION: u32 = 0x01000000;
@ -395,7 +395,7 @@ pub struct WIARawData {
} }
impl WIARawData { impl WIARawData {
pub fn start_offset(&self) -> u64 { self.raw_data_offset.get() & !(SECTOR_SIZE as u64 - 1) } 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 start_sector(&self) -> u32 { (self.start_offset() / SECTOR_SIZE as u64) as u32 }
@ -457,19 +457,21 @@ pub struct RVZGroup {
pub rvz_packed_size: U32, pub rvz_packed_size: U32,
} }
const COMPRESSED_BIT: u32 = 1 << 31;
impl RVZGroup { impl RVZGroup {
#[inline] #[inline]
pub fn data_size(&self) -> u32 { self.data_size_and_flag.get() & 0x7FFFFFFF } pub fn data_size(&self) -> u32 { self.data_size_and_flag.get() & !COMPRESSED_BIT }
#[inline] #[inline]
pub fn is_compressed(&self) -> bool { self.data_size_and_flag.get() & 0x80000000 != 0 } pub fn is_compressed(&self) -> bool { self.data_size_and_flag.get() & COMPRESSED_BIT != 0 }
} }
impl From<&WIAGroup> for RVZGroup { impl From<&WIAGroup> for RVZGroup {
fn from(value: &WIAGroup) -> Self { fn from(value: &WIAGroup) -> Self {
Self { Self {
data_offset: value.data_offset, data_offset: value.data_offset,
data_size_and_flag: U32::new(value.data_size.get() | 0x80000000), data_size_and_flag: U32::new(value.data_size.get() | COMPRESSED_BIT),
rvz_packed_size: U32::new(0), rvz_packed_size: U32::new(0),
} }
} }
@ -886,23 +888,40 @@ impl BlockReader for BlockReaderWIA {
|| !group.is_compressed(); || !group.is_compressed();
let mut exception_lists = vec![]; let mut exception_lists = vec![];
if info.in_partition && uncompressed_exception_lists { if info.in_partition && uncompressed_exception_lists {
exception_lists = read_exception_lists(&mut group_data, chunk_size, true)?; 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 mut decompressed = if group.is_compressed() {
let mut decompressed = BytesMut::zeroed(chunk_size as usize); let max_decompressed_size =
let len = self.decompressor.decompress(group_data.as_ref(), decompressed.as_mut())?; self.decompressor.get_content_size(group_data.as_ref())?.unwrap_or_else(|| {
if info.in_partition && !uncompressed_exception_lists {
// Add room for potential hash exceptions. See [WIAExceptionList] for details on the
// maximum number of exceptions.
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.truncate(len);
decompressed.freeze() decompressed.freeze()
} else { } else {
group_data group_data
}; };
if info.in_partition && !uncompressed_exception_lists { if info.in_partition && !uncompressed_exception_lists {
exception_lists = read_exception_lists(&mut decompressed, chunk_size, false)?; exception_lists = read_exception_lists(&mut decompressed, chunk_size, false)
.io_context("Reading compressed exception lists")?;
} }
if group.rvz_packed_size.get() > 0 { if group.rvz_packed_size.get() > 0 {
// Decode RVZ packed data // Decode RVZ packed data
rvz_unpack(&mut decompressed, out, &info)?; rvz_unpack(&mut decompressed, out, &info).io_context("Unpacking RVZ group data")?;
} else { } else {
// Read and decompress data // Read and decompress data
if decompressed.remaining() != info.size as usize { if decompressed.remaining() != info.size as usize {
@ -975,9 +994,9 @@ fn rvz_unpack(data: &mut impl Buf, out: &mut [u8], info: &GroupInfo) -> io::Resu
while data.remaining() >= 4 { while data.remaining() >= 4 {
let size = data.get_u32(); let size = data.get_u32();
let remain = out.len() - read; let remain = out.len() - read;
if size & 0x80000000 != 0 { if size & COMPRESSED_BIT != 0 {
// Junk data // Junk data
let size = size & 0x7FFFFFFF; let size = size & !COMPRESSED_BIT;
if size as usize > remain { if size as usize > remain {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::InvalidData, io::ErrorKind::InvalidData,
@ -1057,14 +1076,13 @@ impl JunkInfo {
start_sector: u32, start_sector: u32,
end_sector: u32, end_sector: u32,
disc_header: &DiscHeader, disc_header: &DiscHeader,
partition_header: Option<&PartitionHeader>, boot_header: Option<&BootHeader>,
fst: Option<Fst>, fst: Option<Fst>,
) -> Self { ) -> Self {
let is_wii = disc_header.is_wii(); let is_wii = disc_header.is_wii();
let mut file_ends = BTreeSet::new(); let mut file_ends = BTreeSet::new();
if let Some(partition_header) = partition_header { if let Some(boot_header) = boot_header {
file_ends file_ends.insert(boot_header.fst_offset(is_wii) + boot_header.fst_size(is_wii));
.insert(partition_header.fst_offset(is_wii) + partition_header.fst_size(is_wii));
} }
if let Some(fst) = fst { if let Some(fst) = fst {
for entry in fst.nodes.iter().filter(|n| n.is_file()) { for entry in fst.nodes.iter().filter(|n| n.is_file()) {
@ -1145,7 +1163,7 @@ impl BlockProcessor for BlockProcessorWIA {
}; };
let uncompressed_size = let uncompressed_size =
align_up_32(hash_exception_data.len() as u32, 4) + group_data.len() as u32; (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) if hash_exception_data.as_ref().iter().all(|&b| b == 0)
&& group_data.as_ref().iter().all(|&b| b == 0) && group_data.as_ref().iter().all(|&b| b == 0)
{ {
@ -1167,7 +1185,7 @@ impl BlockProcessor for BlockProcessorWIA {
if is_rvz { if is_rvz {
if let Some(packed_data) = self.try_rvz_pack(group_data.as_ref(), &info) { if let Some(packed_data) = self.try_rvz_pack(group_data.as_ref(), &info) {
meta.data_size = meta.data_size =
align_up_32(hash_exception_data.len() as u32, 4) + packed_data.len() as u32; (hash_exception_data.len() as u32).align_up(4) + packed_data.len() as u32;
meta.rvz_packed_size = packed_data.len() as u32; meta.rvz_packed_size = packed_data.len() as u32;
group_data = packed_data; group_data = packed_data;
} }
@ -1185,9 +1203,9 @@ impl BlockProcessor for BlockProcessorWIA {
let compressed_size = self.compressor.buffer.len() as u32; let compressed_size = self.compressor.buffer.len() as u32;
// For WIA, we must always store compressed data. // For WIA, we must always store compressed data.
// For RVZ, only store compressed data if it's smaller than uncompressed. // For RVZ, only store compressed data if it's smaller than uncompressed.
if !is_rvz || align_up_32(compressed_size, 4) < meta.data_size { if !is_rvz || compressed_size.align_up(4) < meta.data_size {
// Align resulting block data to 4 bytes // Align resulting block data to 4 bytes
let mut buf = BytesMut::zeroed(align_up_32(compressed_size, 4) as usize); let mut buf = BytesMut::zeroed(compressed_size.align_up(4) as usize);
buf[..compressed_size as usize].copy_from_slice(&self.compressor.buffer); buf[..compressed_size as usize].copy_from_slice(&self.compressor.buffer);
meta.is_compressed = true; meta.is_compressed = true;
// Data size does not include end alignment // Data size does not include end alignment
@ -1214,9 +1232,9 @@ impl BlockProcessor for BlockProcessorWIA {
} }
// Store uncompressed group, aligned to 4 bytes after hash exceptions and at the end // Store uncompressed group, aligned to 4 bytes after hash exceptions and at the end
let mut buf = BytesMut::zeroed(align_up_32(meta.data_size, 4) as usize); 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()); buf[..hash_exception_data.len()].copy_from_slice(hash_exception_data.as_ref());
let offset = align_up_32(hash_exception_data.len() as u32, 4) as usize; 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()); buf[offset..offset + group_data.len()].copy_from_slice(group_data.as_ref());
meta.data_hash = xxh64_hash(buf.as_ref()); meta.data_hash = xxh64_hash(buf.as_ref());
Ok(BlockResult { block_idx: group_idx, disc_data, block_data: buf.freeze(), meta }) Ok(BlockResult { block_idx: group_idx, disc_data, block_data: buf.freeze(), meta })
@ -1336,7 +1354,7 @@ impl BlockProcessorWIA {
sector, sector,
); );
} }
*array_ref_mut![out, offset, 4] = (len as u32 | 0x80000000).to_be_bytes(); *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()); array_ref_mut![out, offset + 4, SEED_SIZE_BYTES].copy_from_slice(seed_out.as_bytes());
} }
@ -1528,13 +1546,11 @@ impl DiscWriterWIA {
let partition_data_start = partition.data_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 partition_end = partition.data_end_sector as u64 * SECTOR_SIZE as u64;
let partition_header = partition.partition_header(); let boot_header = partition.boot_header();
let management_data_end = align_up_64( let management_data_end =
partition_offset_to_raw( partition_offset_to_raw(boot_header.fst_offset(true) + boot_header.fst_size(true))
partition_header.fst_offset(true) + partition_header.fst_size(true), // Align to 2 MiB
), .align_up(0x200000);
0x200000, // Align to 2 MiB
);
let management_end_sector = ((partition_data_start + management_data_end) let management_end_sector = ((partition_data_start + management_data_end)
.min(partition_end) .min(partition_end)
/ SECTOR_SIZE as u64) as u32; / SECTOR_SIZE as u64) as u32;
@ -1629,7 +1645,7 @@ impl DiscWriterWIA {
partition.data_start_sector, partition.data_start_sector,
partition.data_end_sector, partition.data_end_sector,
partition.disc_header(), partition.disc_header(),
Some(partition.partition_header()), Some(partition.boot_header()),
partition.fst(), partition.fst(),
)); ));
} }
@ -1637,7 +1653,7 @@ impl DiscWriterWIA {
0, 0,
disc_size.div_ceil(SECTOR_SIZE as u64) as u32, disc_size.div_ceil(SECTOR_SIZE as u64) as u32,
disc_header, disc_header,
inner.partition_header(), inner.boot_header(),
inner.fst(), inner.fst(),
)); ));
@ -1713,7 +1729,7 @@ impl DiscWriter for DiscWriterWIA {
groups[group_idx as usize] = RVZGroup { groups[group_idx as usize] = RVZGroup {
data_offset: data_offset.into(), data_offset: data_offset.into(),
data_size_and_flag: (group.meta.data_size data_size_and_flag: (group.meta.data_size
| if group.meta.is_compressed { 0x80000000 } else { 0 }) | if group.meta.is_compressed { COMPRESSED_BIT } else { 0 })
.into(), .into(),
rvz_packed_size: group.meta.rvz_packed_size.into(), rvz_packed_size: group.meta.rvz_packed_size.into(),
}; };

View File

@ -160,7 +160,7 @@ pub enum Error {
#[error("disc format error: {0}")] #[error("disc format error: {0}")]
DiscFormat(String), DiscFormat(String),
/// A general I/O error. /// A general I/O error.
#[error("I/O error: {0}")] #[error("{0}")]
Io(String, #[source] std::io::Error), Io(String, #[source] std::io::Error),
/// An unknown error. /// An unknown error.
#[error("error: {0}")] #[error("error: {0}")]
@ -225,3 +225,34 @@ where E: ErrorContext
self.map_err(|e| e.context(f())) self.map_err(|e| e.context(f()))
} }
} }
pub(crate) trait IoErrorContext {
fn io_context(self, context: impl Into<String>) -> std::io::Error;
}
impl IoErrorContext for std::io::Error {
#[inline]
fn io_context(self, context: impl Into<String>) -> std::io::Error {
std::io::Error::new(self.kind(), self.context(context))
}
}
pub(crate) trait IoResultContext<T> {
fn io_context(self, context: impl Into<String>) -> std::io::Result<T>;
fn io_with_context<F>(self, f: F) -> std::io::Result<T>
where F: FnOnce() -> String;
}
impl<T> IoResultContext<T> for std::io::Result<T> {
#[inline]
fn io_context(self, context: impl Into<String>) -> std::io::Result<T> {
self.map_err(|e| e.io_context(context))
}
#[inline]
fn io_with_context<F>(self, f: F) -> std::io::Result<T>
where F: FnOnce() -> String {
self.map_err(|e| e.io_context(f()))
}
}

View File

@ -14,10 +14,11 @@ use crate::{
disc::{ disc::{
fst::{Fst, Node}, fst::{Fst, Node},
wii::{ContentMetadata, Ticket, TmdHeader, H3_TABLE_SIZE, REGION_SIZE}, wii::{ContentMetadata, Ticket, TmdHeader, H3_TABLE_SIZE, REGION_SIZE},
ApploaderHeader, DiscHeader, DolHeader, PartitionHeader, BI2_SIZE, BOOT_SIZE, ApploaderHeader, BootHeader, DebugHeader, DiscHeader, DolHeader, BB2_OFFSET, BI2_SIZE,
BOOT_SIZE,
}, },
io::block, io::block,
util::WindowedReader, util::{array_ref, WindowedReader},
Result, Result,
}; };
@ -198,7 +199,7 @@ pub struct DiscMeta {
} }
/// An open disc partition. /// An open disc partition.
pub trait PartitionReader: DynClone + BufRead + Seek + Send + Sync { pub trait PartitionReader: BufRead + DiscStream {
/// Whether this is a Wii partition. (GameCube otherwise) /// Whether this is a Wii partition. (GameCube otherwise)
fn is_wii(&self) -> bool; fn is_wii(&self) -> bool;
@ -306,7 +307,7 @@ dyn_clone::clone_trait_object!(PartitionReader);
/// Extra disc partition data. (DOL, FST, etc.) /// Extra disc partition data. (DOL, FST, etc.)
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PartitionMeta { pub struct PartitionMeta {
/// Disc and partition header (boot.bin) /// Disc and boot header (boot.bin)
pub raw_boot: Arc<[u8; BOOT_SIZE]>, pub raw_boot: Arc<[u8; BOOT_SIZE]>,
/// Debug and region information (bi2.bin) /// Debug and region information (bi2.bin)
pub raw_bi2: Arc<[u8; BI2_SIZE]>, pub raw_bi2: Arc<[u8; BI2_SIZE]>,
@ -329,16 +330,27 @@ pub struct PartitionMeta {
impl PartitionMeta { impl PartitionMeta {
/// A view into the disc header. /// A view into the disc header.
#[inline] #[inline]
pub fn header(&self) -> &DiscHeader { pub fn disc_header(&self) -> &DiscHeader {
DiscHeader::ref_from_bytes(&self.raw_boot[..size_of::<DiscHeader>()]) DiscHeader::ref_from_bytes(array_ref![self.raw_boot, 0, size_of::<DiscHeader>()])
.expect("Invalid header alignment") .expect("Invalid disc header alignment")
} }
/// A view into the partition header. /// A view into the debug header.
#[inline] #[inline]
pub fn partition_header(&self) -> &PartitionHeader { pub fn debug_header(&self) -> &DebugHeader {
PartitionHeader::ref_from_bytes(&self.raw_boot[size_of::<DiscHeader>()..]) DebugHeader::ref_from_bytes(array_ref![
.expect("Invalid partition header alignment") self.raw_boot,
size_of::<DiscHeader>(),
size_of::<DebugHeader>()
])
.expect("Invalid debug header alignment")
}
/// A view into the boot header.
#[inline]
pub fn boot_header(&self) -> &BootHeader {
BootHeader::ref_from_bytes(array_ref![self.raw_boot, BB2_OFFSET, size_of::<BootHeader>()])
.expect("Invalid boot header alignment")
} }
/// A view into the apploader header. /// A view into the apploader header.

View File

@ -10,6 +10,7 @@ use crate::{
pub struct Decompressor { pub struct Decompressor {
pub kind: DecompressionKind, pub kind: DecompressionKind,
#[allow(unused)] // if compression features are disabled
pub cache: DecompressorCache, pub cache: DecompressorCache,
} }
@ -38,7 +39,13 @@ impl Decompressor {
pub fn decompress(&mut self, buf: &[u8], out: &mut [u8]) -> io::Result<usize> { pub fn decompress(&mut self, buf: &[u8], out: &mut [u8]) -> io::Result<usize> {
match &self.kind { match &self.kind {
DecompressionKind::None => { DecompressionKind::None => {
out.copy_from_slice(buf); if buf.len() > out.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Decompressed data too large: {} > {}", buf.len(), out.len()),
));
}
out[..buf.len()].copy_from_slice(buf);
Ok(buf.len()) Ok(buf.len())
} }
#[cfg(feature = "compress-zlib")] #[cfg(feature = "compress-zlib")]
@ -130,6 +137,18 @@ impl Decompressor {
} }
} }
} }
pub fn get_content_size(&self, buf: &[u8]) -> io::Result<Option<usize>> {
match &self.kind {
DecompressionKind::None => Ok(Some(buf.len())),
#[cfg(feature = "compress-zstd")]
DecompressionKind::Zstandard => zstd_safe::get_frame_content_size(buf)
.map(|n| n.map(|n| n as usize))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
#[allow(unreachable_patterns)] // if compression features are disabled
_ => Ok(None),
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -227,6 +246,14 @@ impl Compressor {
pub fn compress(&mut self, buf: &[u8]) -> io::Result<bool> { pub fn compress(&mut self, buf: &[u8]) -> io::Result<bool> {
self.buffer.clear(); self.buffer.clear();
match self.kind { match self.kind {
Compression::None => {
if self.buffer.capacity() >= buf.len() {
self.buffer.extend_from_slice(buf);
Ok(true)
} else {
Ok(false)
}
}
#[cfg(feature = "compress-zlib")] #[cfg(feature = "compress-zlib")]
Compression::Deflate(level) => { Compression::Deflate(level) => {
let compressor = match &mut self.cache { let compressor = match &mut self.cache {
@ -288,6 +315,8 @@ impl Compressor {
_ => { _ => {
let mut ctx = zstd_safe::CCtx::create(); let mut ctx = zstd_safe::CCtx::create();
ctx.init(level as i32).map_err(zstd_util::map_error_code)?; ctx.init(level as i32).map_err(zstd_util::map_error_code)?;
ctx.set_parameter(zstd_safe::CParameter::ContentSizeFlag(true))
.map_err(zstd_util::map_error_code)?;
self.cache = CompressorCache::Zstandard(ctx); self.cache = CompressorCache::Zstandard(ctx);
match &mut self.cache { match &mut self.cache {
CompressorCache::Zstandard(compressor) => compressor, CompressorCache::Zstandard(compressor) => compressor,
@ -302,6 +331,7 @@ impl Compressor {
Err(e) => Err(zstd_util::map_error_code(e)), Err(e) => Err(zstd_util::map_error_code(e)),
} }
} }
#[allow(unreachable_patterns)] // if compression is disabled
_ => Err(io::Error::new( _ => Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
format!("Unsupported compression: {:?}", self.kind), format!("Unsupported compression: {:?}", self.kind),

View File

@ -127,18 +127,36 @@ where T: Div<Output = T> + Rem<Output = T> + Copy {
(x / y, x % y) (x / y, x % y)
} }
#[inline] pub(crate) trait Align {
pub(crate) fn align_up_32(n: u32, align: u32) -> u32 { (n + align - 1) & !(align - 1) } fn align_up(self, align: Self) -> Self;
#[inline] fn align_down(self, align: Self) -> Self;
pub(crate) fn align_up_64(n: u64, align: u64) -> u64 { (n + align - 1) & !(align - 1) } }
macro_rules! impl_align {
($ty:ident) => {
impl Align for $ty {
#[inline(always)]
fn align_up(self, align: Self) -> Self { (self + (align - 1)) & !(align - 1) }
#[inline(always)]
fn align_down(self, align: Self) -> Self { self & !(align - 1) }
}
};
}
impl_align!(u8);
impl_align!(u16);
impl_align!(u32);
impl_align!(u64);
impl_align!(usize);
/// Creates a fixed-size array reference from a slice. /// Creates a fixed-size array reference from a slice.
macro_rules! array_ref { macro_rules! array_ref {
($slice:expr, $offset:expr, $size:expr) => {{ ($slice:expr, $offset:expr, $size:expr) => {{
#[inline(always)] #[inline(always)]
fn to_array<T>(slice: &[T]) -> &[T; $size] { fn to_array<T>(slice: &[T]) -> &[T; $size] {
unsafe { &*(slice.as_ptr() as *const [_; $size]) } unsafe { &*(slice as *const [T] as *const [T; $size]) }
} }
to_array(&$slice[$offset..$offset + $size]) to_array(&$slice[$offset..$offset + $size])
}}; }};
@ -150,7 +168,7 @@ macro_rules! array_ref_mut {
($slice:expr, $offset:expr, $size:expr) => {{ ($slice:expr, $offset:expr, $size:expr) => {{
#[inline(always)] #[inline(always)]
fn to_array<T>(slice: &mut [T]) -> &mut [T; $size] { fn to_array<T>(slice: &mut [T]) -> &mut [T; $size] {
unsafe { &mut *(slice.as_ptr() as *mut [_; $size]) } unsafe { &mut *(slice as *mut [T] as *mut [T; $size]) }
} }
to_array(&mut $slice[$offset..$offset + $size]) to_array(&mut $slice[$offset..$offset + $size])
}}; }};

View File

@ -16,6 +16,11 @@ categories = ["command-line-utilities", "parser-implementations"]
build = "build.rs" build = "build.rs"
[features] [features]
default = ["compress-bzip2", "compress-lzma", "compress-zlib", "compress-zstd"]
compress-bzip2 = ["nod/compress-bzip2"]
compress-lzma = ["nod/compress-lzma"]
compress-zlib = ["nod/compress-zlib"]
compress-zstd = ["nod/compress-zstd"]
openssl = ["nod/openssl"] openssl = ["nod/openssl"]
openssl-vendored = ["nod/openssl-vendored"] openssl-vendored = ["nod/openssl-vendored"]
tracy = ["dep:tracing-tracy"] tracy = ["dep:tracing-tracy"]
@ -28,7 +33,7 @@ enable-ansi-support = "0.2"
hex = { version = "0.4", features = ["serde"] } hex = { version = "0.4", features = ["serde"] }
indicatif = "0.17" indicatif = "0.17"
md-5 = { workspace = true } md-5 = { workspace = true }
nod = { version = "2.0.0-alpha", path = "../nod" } nod = { version = "2.0.0-alpha", path = "../nod", default-features = false }
num_cpus = "1.16" num_cpus = "1.16"
quick-xml = { version = "0.37", features = ["serialize"] } quick-xml = { version = "0.37", features = ["serialize"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -5,7 +5,7 @@
<name>Non-Redump - Nintendo - Nintendo GameCube</name> <name>Non-Redump - Nintendo - Nintendo GameCube</name>
<description>Non-Redump - Nintendo - Nintendo GameCube</description> <description>Non-Redump - Nintendo - Nintendo GameCube</description>
<subset>Non-Redump</subset> <subset>Non-Redump</subset>
<version>20240602-041318</version> <version>20240904-155318</version>
<author>bikerspade, Gefflon, Hiccup, NovaAurora, rarenight, relax, Seventy7, togemet2</author> <author>bikerspade, Gefflon, Hiccup, NovaAurora, rarenight, relax, Seventy7, togemet2</author>
<homepage>No-Intro</homepage> <homepage>No-Intro</homepage>
<url>https://www.no-intro.org</url> <url>https://www.no-intro.org</url>
@ -94,12 +94,6 @@
<description>Major League Baseball 2K6 (USA) (Beta) (2006-05-18)</description> <description>Major League Baseball 2K6 (USA) (Beta) (2006-05-18)</description>
<rom name="Major League Baseball 2K6 (USA) (Beta) (2006-05-18).iso" size="1459978240" crc="2e97a802" md5="707168f95134d4847278472b84c7657b" sha1="d18ee85aeb558bb7d357bd0fc2a6c6a4bda2467c" serial="G62ET2"/> <rom name="Major League Baseball 2K6 (USA) (Beta) (2006-05-18).iso" size="1459978240" crc="2e97a802" md5="707168f95134d4847278472b84c7657b" sha1="d18ee85aeb558bb7d357bd0fc2a6c6a4bda2467c" serial="G62ET2"/>
</game> </game>
<game name="Mega Man X - Command Mission (USA) (Beta)" id="0038">
<category>Games</category>
<category>Preproduction</category>
<description>Mega Man X - Command Mission (USA) (Beta)</description>
<rom name="Mega Man X - Command Mission (USA) (Beta).iso" size="1459978240" crc="7b88690e" md5="801aeb70186915ac0a53494006ff74f8" sha1="24d72340954cd62e142a6d11a6582fc5f40ba994" sha256="de74aab25978ee3523e7de522d9da9e7f65690eb161094163d342c423d55d25c" serial="GXRE08"/>
</game>
<game name="Metal Gear Solid - The Twin Snakes (USA) (Disc 2) (Beta)" id="0014"> <game name="Metal Gear Solid - The Twin Snakes (USA) (Disc 2) (Beta)" id="0014">
<description>Metal Gear Solid - The Twin Snakes (USA) (Disc 2) (Beta)</description> <description>Metal Gear Solid - The Twin Snakes (USA) (Disc 2) (Beta)</description>
<rom name="Metal Gear Solid - The Twin Snakes (USA) (Disc 2) (Beta).iso" size="1459978240" crc="85409e81" md5="ac1181d6223c1b82596b41ddac9e40e7" sha1="523b0e82b84c91946da05a48841914df6729e747" serial="GGSEA4"/> <rom name="Metal Gear Solid - The Twin Snakes (USA) (Disc 2) (Beta).iso" size="1459978240" crc="85409e81" md5="ac1181d6223c1b82596b41ddac9e40e7" sha1="523b0e82b84c91946da05a48841914df6729e747" serial="GGSEA4"/>

View File

@ -3,9 +3,9 @@
<datafile> <datafile>
<header> <header>
<name>Nintendo - GameCube</name> <name>Nintendo - GameCube</name>
<description>Nintendo - GameCube - Discs (1992) (2024-06-02 00-38-06)</description> <description>Nintendo - GameCube - Discs (2001) (2024-12-01 23-54-46)</description>
<version>2024-06-02 00-38-06</version> <version>2024-12-01 23-54-46</version>
<date>2024-06-02 00-38-06</date> <date>2024-12-01 23-54-46</date>
<author>redump.org</author> <author>redump.org</author>
<homepage>redump.org</homepage> <homepage>redump.org</homepage>
<url>http://redump.org/</url> <url>http://redump.org/</url>
@ -2085,10 +2085,10 @@
<description>Nickelodeon Tak 2 - The Staff of Dreams (USA)</description> <description>Nickelodeon Tak 2 - The Staff of Dreams (USA)</description>
<rom name="Nickelodeon Tak 2 - The Staff of Dreams (USA).iso" size="1459978240" crc="ca5ada0f" md5="0d9a7c74d1ff98dd5ad867bd67377bb1" sha1="bc36de899604771551b93638ce204da92e0b7582"/> <rom name="Nickelodeon Tak 2 - The Staff of Dreams (USA).iso" size="1459978240" crc="ca5ada0f" md5="0d9a7c74d1ff98dd5ad867bd67377bb1" sha1="bc36de899604771551b93638ce204da92e0b7582"/>
</game> </game>
<game name="Nintendo GameCube Preview Disc - May 2003 (USA)"> <game name="Nintendo GameCube Preview Disc - May 2003 (USA, Canada)">
<category>Demos</category> <category>Demos</category>
<description>Nintendo GameCube Preview Disc - May 2003 (USA)</description> <description>Nintendo GameCube Preview Disc - May 2003 (USA, Canada)</description>
<rom name="Nintendo GameCube Preview Disc - May 2003 (USA).iso" size="1459978240" crc="d45c8c07" md5="73e092844193533bfd6d5c27034446a3" sha1="43047f599583e1b0357881d21cba2e3958c57822"/> <rom name="Nintendo GameCube Preview Disc - May 2003 (USA, Canada).iso" size="1459978240" crc="d45c8c07" md5="73e092844193533bfd6d5c27034446a3" sha1="43047f599583e1b0357881d21cba2e3958c57822"/>
</game> </game>
<game name="Interactive Multi-Game Demo Disc - July 2002 (USA)"> <game name="Interactive Multi-Game Demo Disc - July 2002 (USA)">
<category>Demos</category> <category>Demos</category>
@ -2395,10 +2395,10 @@
<description>Need for Speed - Underground (USA)</description> <description>Need for Speed - Underground (USA)</description>
<rom name="Need for Speed - Underground (USA).iso" size="1459978240" crc="01c154ba" md5="c20f02454166bc8fa08f84307871ac7d" sha1="d6882a712509e8bba75ee8837bcac65f8352b1d1"/> <rom name="Need for Speed - Underground (USA).iso" size="1459978240" crc="01c154ba" md5="c20f02454166bc8fa08f84307871ac7d" sha1="d6882a712509e8bba75ee8837bcac65f8352b1d1"/>
</game> </game>
<game name="Nickelodeon SpongeBob SquarePants - The Movie (USA) (Rev 1)"> <game name="Nickelodeon The SpongeBob SquarePants Movie (USA) (Rev 1)">
<category>Games</category> <category>Games</category>
<description>Nickelodeon SpongeBob SquarePants - The Movie (USA) (Rev 1)</description> <description>Nickelodeon The SpongeBob SquarePants Movie (USA) (Rev 1)</description>
<rom name="Nickelodeon SpongeBob SquarePants - The Movie (USA) (Rev 1).iso" size="1459978240" crc="77be3cc6" md5="9fbad7186b2168190082a54fa20d63b7" sha1="2aea159b1e98a220a6e4534f0f017b8722ce4caa"/> <rom name="Nickelodeon The SpongeBob SquarePants Movie (USA) (Rev 1).iso" size="1459978240" crc="77be3cc6" md5="9fbad7186b2168190082a54fa20d63b7" sha1="2aea159b1e98a220a6e4534f0f017b8722ce4caa"/>
</game> </game>
<game name="Star Wars - Rogue Squadron III - Rebel Strike (USA) (En,Fr,De,Es,It)"> <game name="Star Wars - Rogue Squadron III - Rebel Strike (USA) (En,Fr,De,Es,It)">
<category>Games</category> <category>Games</category>
@ -2760,15 +2760,15 @@
<description>Sonic Adventure DX - Director's Cut (Europe) (En,Ja,Fr,De,Es) (Rev 1)</description> <description>Sonic Adventure DX - Director's Cut (Europe) (En,Ja,Fr,De,Es) (Rev 1)</description>
<rom name="Sonic Adventure DX - Director's Cut (Europe) (En,Ja,Fr,De,Es) (Rev 1).iso" size="1459978240" crc="9ba7f3af" md5="2136ed4b6a27dc64066c8a689918dce0" sha1="99ce5f51388919a95d571a730c50362ba14277c3"/> <rom name="Sonic Adventure DX - Director's Cut (Europe) (En,Ja,Fr,De,Es) (Rev 1).iso" size="1459978240" crc="9ba7f3af" md5="2136ed4b6a27dc64066c8a689918dce0" sha1="99ce5f51388919a95d571a730c50362ba14277c3"/>
</game> </game>
<game name="GoldenEye - Rogue Agent (France) (Disc 1)"> <game name="GoldenEye - Au Service du Mal (France) (Disc 1)">
<category>Games</category> <category>Games</category>
<description>GoldenEye - Rogue Agent (France) (Disc 1)</description> <description>GoldenEye - Au Service du Mal (France) (Disc 1)</description>
<rom name="GoldenEye - Rogue Agent (France) (Disc 1).iso" size="1459978240" crc="4564bb01" md5="08110ba8ac059a8f735bf951bee12edd" sha1="107e792ae93495179f5bd657da166947751e37cc"/> <rom name="GoldenEye - Au Service du Mal (France) (Disc 1).iso" size="1459978240" crc="4564bb01" md5="08110ba8ac059a8f735bf951bee12edd" sha1="107e792ae93495179f5bd657da166947751e37cc"/>
</game> </game>
<game name="GoldenEye - Rogue Agent (France) (Disc 2)"> <game name="GoldenEye - Au Service du Mal (France) (Disc 2)">
<category>Games</category> <category>Games</category>
<description>GoldenEye - Rogue Agent (France) (Disc 2)</description> <description>GoldenEye - Au Service du Mal (France) (Disc 2)</description>
<rom name="GoldenEye - Rogue Agent (France) (Disc 2).iso" size="1459978240" crc="dba8aa7f" md5="57f801c71548bd7a6e281b0b028a4ac6" sha1="7bb6875321b6ea90146bfb71bb730f6317c2a172"/> <rom name="GoldenEye - Au Service du Mal (France) (Disc 2).iso" size="1459978240" crc="dba8aa7f" md5="57f801c71548bd7a6e281b0b028a4ac6" sha1="7bb6875321b6ea90146bfb71bb730f6317c2a172"/>
</game> </game>
<game name="Lost Kingdoms (Europe) (En,Fr)"> <game name="Lost Kingdoms (Europe) (En,Fr)">
<category>Games</category> <category>Games</category>
@ -3265,10 +3265,10 @@
<description>Mega Man X - Command Mission (USA)</description> <description>Mega Man X - Command Mission (USA)</description>
<rom name="Mega Man X - Command Mission (USA).iso" size="1459978240" crc="82115bd2" md5="ddaf03e8bb5b7ca43ae2f2d77087f917" sha1="98fafeb1fbe04dfec9a4ff1d0c159627bb288aad"/> <rom name="Mega Man X - Command Mission (USA).iso" size="1459978240" crc="82115bd2" md5="ddaf03e8bb5b7ca43ae2f2d77087f917" sha1="98fafeb1fbe04dfec9a4ff1d0c159627bb288aad"/>
</game> </game>
<game name="Skies of Arcadia Legends (USA)"> <game name="Skies of Arcadia - Legends (USA)">
<category>Games</category> <category>Games</category>
<description>Skies of Arcadia Legends (USA)</description> <description>Skies of Arcadia - Legends (USA)</description>
<rom name="Skies of Arcadia Legends (USA).iso" size="1459978240" crc="23e347b6" md5="3e7fa5033c4a2704434fb6ba98195ecd" sha1="46105320553c858f25fafc5fd357566b505a4940"/> <rom name="Skies of Arcadia - Legends (USA).iso" size="1459978240" crc="23e347b6" md5="3e7fa5033c4a2704434fb6ba98195ecd" sha1="46105320553c858f25fafc5fd357566b505a4940"/>
</game> </game>
<game name="Harvest Moon - Another Wonderful Life (USA)"> <game name="Harvest Moon - Another Wonderful Life (USA)">
<category>Games</category> <category>Games</category>
@ -4380,10 +4380,10 @@
<description>World Soccer Winning Eleven 6 - Final Evolution (Japan)</description> <description>World Soccer Winning Eleven 6 - Final Evolution (Japan)</description>
<rom name="World Soccer Winning Eleven 6 - Final Evolution (Japan).iso" size="1459978240" crc="07f76bcc" md5="db339f4d36b698e0c18de99a91c0c165" sha1="4d0a7142474ad5a4d6e7245f252eab308f783fbe"/> <rom name="World Soccer Winning Eleven 6 - Final Evolution (Japan).iso" size="1459978240" crc="07f76bcc" md5="db339f4d36b698e0c18de99a91c0c165" sha1="4d0a7142474ad5a4d6e7245f252eab308f783fbe"/>
</game> </game>
<game name="Skies of Arcadia Legends (Europe) (En,Fr,De,Es)"> <game name="Skies of Arcadia - Legends (Europe) (En,Fr,De,Es)">
<category>Games</category> <category>Games</category>
<description>Skies of Arcadia Legends (Europe) (En,Fr,De,Es)</description> <description>Skies of Arcadia - Legends (Europe) (En,Fr,De,Es)</description>
<rom name="Skies of Arcadia Legends (Europe) (En,Fr,De,Es).iso" size="1459978240" crc="a8e18c76" md5="bd814992f1e39d4147c775ff8b32d022" sha1="30ee1d7777fe51bcd7c4deeb867651d5b6e96e41"/> <rom name="Skies of Arcadia - Legends (Europe) (En,Fr,De,Es).iso" size="1459978240" crc="a8e18c76" md5="bd814992f1e39d4147c775ff8b32d022" sha1="30ee1d7777fe51bcd7c4deeb867651d5b6e96e41"/>
</game> </game>
<game name="Serious Sam - Next Encounter (Europe) (En,Fr,De)"> <game name="Serious Sam - Next Encounter (Europe) (En,Fr,De)">
<category>Games</category> <category>Games</category>
@ -6555,10 +6555,10 @@
<description>2002 FIFA World Cup (Europe) (Fr,Nl)</description> <description>2002 FIFA World Cup (Europe) (Fr,Nl)</description>
<rom name="2002 FIFA World Cup (Europe) (Fr,Nl).iso" size="1459978240" crc="5980022f" md5="54411489d800faf171f5ff203b1a0c46" sha1="b7e6aa5d84e35fe1de1879c9dcd12c0c1987e275"/> <rom name="2002 FIFA World Cup (Europe) (Fr,Nl).iso" size="1459978240" crc="5980022f" md5="54411489d800faf171f5ff203b1a0c46" sha1="b7e6aa5d84e35fe1de1879c9dcd12c0c1987e275"/>
</game> </game>
<game name="Nickelodeon SpongeBob SquarePants - The Movie (Europe) (Fr,Nl)"> <game name="Nickelodeon The SpongeBob SquarePants Movie (Europe) (Fr,Nl)">
<category>Games</category> <category>Games</category>
<description>Nickelodeon SpongeBob SquarePants - The Movie (Europe) (Fr,Nl)</description> <description>Nickelodeon The SpongeBob SquarePants Movie (Europe) (Fr,Nl)</description>
<rom name="Nickelodeon SpongeBob SquarePants - The Movie (Europe) (Fr,Nl).iso" size="1459978240" crc="084ea086" md5="5c8a6eeeb849e20c59bc245a56d4f174" sha1="09606bce7bc063d257f006973f1b5c70e8e9cd42"/> <rom name="Nickelodeon The SpongeBob SquarePants Movie (Europe) (Fr,Nl).iso" size="1459978240" crc="084ea086" md5="5c8a6eeeb849e20c59bc245a56d4f174" sha1="09606bce7bc063d257f006973f1b5c70e8e9cd42"/>
</game> </game>
<game name="FIFA 06 (Netherlands)"> <game name="FIFA 06 (Netherlands)">
<category>Games</category> <category>Games</category>
@ -7070,10 +7070,10 @@
<description>Nickelodeon SpongeBob SquarePants - Creature from the Krusty Krab (Europe)</description> <description>Nickelodeon SpongeBob SquarePants - Creature from the Krusty Krab (Europe)</description>
<rom name="Nickelodeon SpongeBob SquarePants - Creature from the Krusty Krab (Europe).iso" size="1459978240" crc="5dcf1734" md5="10b725bed9a3be326a776db800682b47" sha1="53238fd27c00b2f51b19e2fcf4f3bc8ae34aa612"/> <rom name="Nickelodeon SpongeBob SquarePants - Creature from the Krusty Krab (Europe).iso" size="1459978240" crc="5dcf1734" md5="10b725bed9a3be326a776db800682b47" sha1="53238fd27c00b2f51b19e2fcf4f3bc8ae34aa612"/>
</game> </game>
<game name="Nickelodeon SpongeBob SquarePants - The Movie (Europe)"> <game name="Nickelodeon The SpongeBob SquarePants Movie (Europe)">
<category>Games</category> <category>Games</category>
<description>Nickelodeon SpongeBob SquarePants - The Movie (Europe)</description> <description>Nickelodeon The SpongeBob SquarePants Movie (Europe)</description>
<rom name="Nickelodeon SpongeBob SquarePants - The Movie (Europe).iso" size="1459978240" crc="334aac70" md5="62bc84bfcad66e8d24f83873b247b628" sha1="f13f66669639b95d2c63f51cc3deea1b334998e5"/> <rom name="Nickelodeon The SpongeBob SquarePants Movie (Europe).iso" size="1459978240" crc="334aac70" md5="62bc84bfcad66e8d24f83873b247b628" sha1="f13f66669639b95d2c63f51cc3deea1b334998e5"/>
</game> </game>
<game name="MC Groovz Dance Craze (Europe) (En,Fr,De,Es,It)"> <game name="MC Groovz Dance Craze (Europe) (En,Fr,De,Es,It)">
<category>Games</category> <category>Games</category>
@ -7205,10 +7205,10 @@
<description>Gekkan Nintendo Tentou Demo 2002.7.1 (Japan)</description> <description>Gekkan Nintendo Tentou Demo 2002.7.1 (Japan)</description>
<rom name="Gekkan Nintendo Tentou Demo 2002.7.1 (Japan).iso" size="1459978240" crc="86dacab6" md5="84936411bf7768bcdac3be8f401ad8eb" sha1="4d7d77f67288902ba17bf6f42d19679e45cbcb54"/> <rom name="Gekkan Nintendo Tentou Demo 2002.7.1 (Japan).iso" size="1459978240" crc="86dacab6" md5="84936411bf7768bcdac3be8f401ad8eb" sha1="4d7d77f67288902ba17bf6f42d19679e45cbcb54"/>
</game> </game>
<game name="Gekkan Nintendo Tentou Demo 2002.7.10 (Japan)"> <game name="Gekkan Nintendo Tentou Demo 2002.7.10 Zoukan-gou (Japan)">
<category>Demos</category> <category>Demos</category>
<description>Gekkan Nintendo Tentou Demo 2002.7.10 (Japan)</description> <description>Gekkan Nintendo Tentou Demo 2002.7.10 Zoukan-gou (Japan)</description>
<rom name="Gekkan Nintendo Tentou Demo 2002.7.10 (Japan).iso" size="1459978240" crc="878c6dea" md5="f185e3c31a81519ee0c0b2539ebcd442" sha1="8b863d521ce96c85c9de60a9d95ccf10e6d8edb6"/> <rom name="Gekkan Nintendo Tentou Demo 2002.7.10 Zoukan-gou (Japan).iso" size="1459978240" crc="878c6dea" md5="f185e3c31a81519ee0c0b2539ebcd442" sha1="8b863d521ce96c85c9de60a9d95ccf10e6d8edb6"/>
</game> </game>
<game name="Rune II - Koruten no Kagi no Himitsu (Japan) (Taikenban)"> <game name="Rune II - Koruten no Kagi no Himitsu (Japan) (Taikenban)">
<category>Demos</category> <category>Demos</category>
@ -7725,10 +7725,10 @@
<description>Family Stadium 2003 (Japan)</description> <description>Family Stadium 2003 (Japan)</description>
<rom name="Family Stadium 2003 (Japan).iso" size="1459978240" crc="69b9e609" md5="d6fe5f8e1b6c915c6812b073ac23aada" sha1="d49244a31f95a77c296fba70b8b6550b7e017efc"/> <rom name="Family Stadium 2003 (Japan).iso" size="1459978240" crc="69b9e609" md5="d6fe5f8e1b6c915c6812b073ac23aada" sha1="d49244a31f95a77c296fba70b8b6550b7e017efc"/>
</game> </game>
<game name="Gakuen Toshi Vara Noir Roses (Japan)"> <game name="Gakuen Toshi Varanoir - Roses (Japan)">
<category>Games</category> <category>Games</category>
<description>Gakuen Toshi Vara Noir Roses (Japan)</description> <description>Gakuen Toshi Varanoir - Roses (Japan)</description>
<rom name="Gakuen Toshi Vara Noir Roses (Japan).iso" size="1459978240" crc="cf6d0aa5" md5="364f6513ce08ebbfc312e7a05a6b4225" sha1="d403770cba0c71520df8fd853275508130ac04f4"/> <rom name="Gakuen Toshi Varanoir - Roses (Japan).iso" size="1459978240" crc="cf6d0aa5" md5="364f6513ce08ebbfc312e7a05a6b4225" sha1="d403770cba0c71520df8fd853275508130ac04f4"/>
</game> </game>
<game name="Gekitou Pro Yakyuu - Mizushima Shinji All Stars vs. Pro Yakyuu (Japan)"> <game name="Gekitou Pro Yakyuu - Mizushima Shinji All Stars vs. Pro Yakyuu (Japan)">
<category>Games</category> <category>Games</category>
@ -8660,10 +8660,10 @@
<description>SSX on Tour (Europe) (En,Fr,De)</description> <description>SSX on Tour (Europe) (En,Fr,De)</description>
<rom name="SSX on Tour (Europe) (En,Fr,De).iso" size="1459978240" crc="886defd6" md5="e86b6fc0db336d2e55c08b23d76ab554" sha1="2a6055689d79602d92a5bbbcf9d5a9613301d802"/> <rom name="SSX on Tour (Europe) (En,Fr,De).iso" size="1459978240" crc="886defd6" md5="e86b6fc0db336d2e55c08b23d76ab554" sha1="2a6055689d79602d92a5bbbcf9d5a9613301d802"/>
</game> </game>
<game name="2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Disc 1) (Bob L'eponge - Le Film)"> <game name="2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Fr,Nl) (Disc 1) (Bob L'eponge - Le Film)">
<category>Games</category> <category>Games</category>
<description>2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Disc 1) (Bob L'eponge - Le Film)</description> <description>2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Fr,Nl) (Disc 1) (Bob L'eponge - Le Film)</description>
<rom name="2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Disc 1) (Bob L'eponge - Le Film).iso" size="1459978240" crc="3cb489d7" md5="592585ee660990d4953e8d6b9f961d3c" sha1="e52e6b4d76461a9041dfbcc95ecef8b1d1ecc051"/> <rom name="2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Fr,Nl) (Disc 1) (Bob L'eponge - Le Film).iso" size="1459978240" crc="3cb489d7" md5="592585ee660990d4953e8d6b9f961d3c" sha1="e52e6b4d76461a9041dfbcc95ecef8b1d1ecc051"/>
</game> </game>
<game name="2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Disc 2) (Tak 2 - Le Sceptre des Reves)"> <game name="2 Games in 1 - Bob L'eponge - Le Film + Tak 2 - Le Sceptre des Reves (France) (Disc 2) (Tak 2 - Le Sceptre des Reves)">
<category>Games</category> <category>Games</category>
@ -9325,10 +9325,10 @@
<description>Disney-Pixar Nemo-reul Chajaseo (Korea)</description> <description>Disney-Pixar Nemo-reul Chajaseo (Korea)</description>
<rom name="Disney-Pixar Nemo-reul Chajaseo (Korea).iso" size="1459978240" crc="312420a2" md5="7ee187f776e1cbed1f8836e645560d46" sha1="340ef094dfe69c0253e1539911f7f508d91108fa"/> <rom name="Disney-Pixar Nemo-reul Chajaseo (Korea).iso" size="1459978240" crc="312420a2" md5="7ee187f776e1cbed1f8836e645560d46" sha1="340ef094dfe69c0253e1539911f7f508d91108fa"/>
</game> </game>
<game name="Action Replay for GameCube (USA) (En,Fr,De,Es,It,Pt) (Unl) (v1.08)"> <game name="Action Replay for GameCube (USA, Europe) (En,Fr,De,Es,It,Pt) (Unl) (v1.08)">
<category>Applications</category> <category>Applications</category>
<description>Action Replay for GameCube (USA) (En,Fr,De,Es,It,Pt) (Unl) (v1.08)</description> <description>Action Replay for GameCube (USA, Europe) (En,Fr,De,Es,It,Pt) (Unl) (v1.08)</description>
<rom name="Action Replay for GameCube (USA) (En,Fr,De,Es,It,Pt) (Unl) (v1.08).iso" size="1459978240" crc="9cb75b81" md5="ba5aadafb4b364679e6950d339aaedfd" sha1="8d480dc45b47e11c1bb5f7f500e2c78b08a1153a"/> <rom name="Action Replay for GameCube (USA, Europe) (En,Fr,De,Es,It,Pt) (Unl) (v1.08).iso" size="1459978240" crc="9cb75b81" md5="ba5aadafb4b364679e6950d339aaedfd" sha1="8d480dc45b47e11c1bb5f7f500e2c78b08a1153a"/>
</game> </game>
<game name="Mario Kart - Double Dash!! (Korea)"> <game name="Mario Kart - Double Dash!! (Korea)">
<category>Games</category> <category>Games</category>
@ -9480,10 +9480,10 @@
<description>Cube CD 14 (33) (UK) (Unl)</description> <description>Cube CD 14 (33) (UK) (Unl)</description>
<rom name="Cube CD 14 (33) (UK) (Unl).iso" size="1459978240" crc="b21fe659" md5="82109cff554d0ecadb0fffb7030dc5a8" sha1="884052c0eb3f0d515ca0c7b173e0a5dfd8071a35"/> <rom name="Cube CD 14 (33) (UK) (Unl).iso" size="1459978240" crc="b21fe659" md5="82109cff554d0ecadb0fffb7030dc5a8" sha1="884052c0eb3f0d515ca0c7b173e0a5dfd8071a35"/>
</game> </game>
<game name="Action Replay Max (Europe) (En,Fr,De,Es,It,Pt) (Unl)"> <game name="Action Replay Max (USA, Europe) (En,Fr,De,Es,It,Pt) (Unl)">
<category>Applications</category> <category>Applications</category>
<description>Action Replay Max (Europe) (En,Fr,De,Es,It,Pt) (Unl)</description> <description>Action Replay Max (USA, Europe) (En,Fr,De,Es,It,Pt) (Unl)</description>
<rom name="Action Replay Max (Europe) (En,Fr,De,Es,It,Pt) (Unl).iso" size="1459978240" crc="989e3a76" md5="335e62cdeb07ef3e15d1a3bfceaa28d2" sha1="b339b3f828abe509bfbb4a745322eebea54e105d"/> <rom name="Action Replay Max (USA, Europe) (En,Fr,De,Es,It,Pt) (Unl).iso" size="1459978240" crc="989e3a76" md5="335e62cdeb07ef3e15d1a3bfceaa28d2" sha1="b339b3f828abe509bfbb4a745322eebea54e105d"/>
</game> </game>
<game name="Capcom vs. SNK 2 EO (Japan) (Tentou Taikenban)"> <game name="Capcom vs. SNK 2 EO (Japan) (Tentou Taikenban)">
<category>Demos</category> <category>Demos</category>
@ -9880,10 +9880,10 @@
<description>FIFA Soccer 07 (Latin America)</description> <description>FIFA Soccer 07 (Latin America)</description>
<rom name="FIFA Soccer 07 (Latin America).iso" size="1459978240" crc="eb1be753" md5="f0f6029d90191ac10a92938597735f37" sha1="64ba3862474dc9708d7263e0136299f684f38ee6"/> <rom name="FIFA Soccer 07 (Latin America).iso" size="1459978240" crc="eb1be753" md5="f0f6029d90191ac10a92938597735f37" sha1="64ba3862474dc9708d7263e0136299f684f38ee6"/>
</game> </game>
<game name="Pickles (USA) (Proto)"> <game name="Pickles (USA) (Proto 1)">
<category>Preproduction</category> <category>Preproduction</category>
<description>Pickles (USA) (Proto)</description> <description>Pickles (USA) (Proto 1)</description>
<rom name="Pickles (USA) (Proto).iso" size="1459978240" crc="5542f6e9" md5="3cdde8d4002b4268c573cf2c2a9009c1" sha1="7011e5c016f3e87b9786d542cb555b111c8293ee"/> <rom name="Pickles (USA) (Proto 1).iso" size="1459978240" crc="5542f6e9" md5="3cdde8d4002b4268c573cf2c2a9009c1" sha1="7011e5c016f3e87b9786d542cb555b111c8293ee"/>
</game> </game>
<game name="18 Wheeler - American Pro Trucker (USA) (Beta) (2001-11-12)"> <game name="18 Wheeler - American Pro Trucker (USA) (Beta) (2001-11-12)">
<category>Preproduction</category> <category>Preproduction</category>
@ -9970,4 +9970,49 @@
<description>Evolution Worlds (USA) (Beta)</description> <description>Evolution Worlds (USA) (Beta)</description>
<rom name="Evolution Worlds (USA) (Beta).iso" size="1459978240" crc="74e3fbee" md5="f5a5d5d02b8ce369b53e5efcaf2c5d71" sha1="061ba70fe611a7c36a3a99dc52839fc275724f19"/> <rom name="Evolution Worlds (USA) (Beta).iso" size="1459978240" crc="74e3fbee" md5="f5a5d5d02b8ce369b53e5efcaf2c5d71" sha1="061ba70fe611a7c36a3a99dc52839fc275724f19"/>
</game> </game>
<game name="Pickles (USA) (Proto 2)">
<category>Preproduction</category>
<description>Pickles (USA) (Proto 2)</description>
<rom name="Pickles (USA) (Proto 2).iso" size="1459978240" crc="7fa9a839" md5="313057403f079b96926335202a6ca40c" sha1="9038eb0c7c013421a9be5a94fcd3d27c7f2f8767"/>
</game>
<game name="Advance Connector GC-you (Japan) (Unl)">
<category>Applications</category>
<description>Advance Connector GC-you (Japan) (Unl)</description>
<rom name="Advance Connector GC-you (Japan) (Unl).iso" size="1459978240" crc="93418d2a" md5="9ad3900dbde0c1b588212dce88730b7a" sha1="cf3f68552c099aa2830c2dd74fc3e90d3518f82f"/>
</game>
<game name="Xeno Crisis (Japan) (En,Ja,Fr,De,Es,It,Nl,Pt) (Unl)">
<category>Games</category>
<description>Xeno Crisis (Japan) (En,Ja,Fr,De,Es,It,Nl,Pt) (Unl)</description>
<rom name="Xeno Crisis (Japan) (En,Ja,Fr,De,Es,It,Nl,Pt) (Unl).iso" size="115898368" crc="0c6d4075" md5="4244e7887f60c1336d711fc4a1fd13e0" sha1="46acc5a470af00a3adf916ba879e382d1540b1c5"/>
</game>
<game name="Gekkan Nintendo Tentou Demo 2002.8.1 (Japan)">
<category>Demos</category>
<description>Gekkan Nintendo Tentou Demo 2002.8.1 (Japan)</description>
<rom name="Gekkan Nintendo Tentou Demo 2002.8.1 (Japan).iso" size="1459978240" crc="06588854" md5="405c464ef39eb55f63fd6cd0a947de86" sha1="cd7d1418c2aba5ee614fdd22a46b1f48cbbc54cf"/>
</game>
<game name="NBA Live 2003 (France)">
<category>Games</category>
<description>NBA Live 2003 (France)</description>
<rom name="NBA Live 2003 (France).iso" size="1459978240" crc="ca606a11" md5="b300251ad4be896443143f7dac64e9dc" sha1="f8117becd57e358afed78b35e79800df094bbd3e"/>
</game>
<game name="Advance Game Port (USA) (Unl) (Rev 2)">
<category>Applications</category>
<description>Advance Game Port (USA) (Unl) (Rev 2)</description>
<rom name="Advance Game Port (USA) (Unl) (Rev 2).iso" size="1459978240" crc="0e8bb8d0" md5="097275ac8c60735001d3c0623529fcfc" sha1="d4ac3fed9cc08a19cdad5e46cc5870e909723691"/>
</game>
<game name="SD Media Launcher for GameCube &amp; Wii (Japan) (Unl)">
<category>Applications</category>
<description>SD Media Launcher for GameCube &amp; Wii (Japan) (Unl)</description>
<rom name="SD Media Launcher for GameCube &amp; Wii (Japan) (Unl).iso" size="1459978240" crc="7b642b57" md5="4e260af6d573045186b8794587a466a5" sha1="cf5c5e8b9e073a3bbf993cce3910aa0814d9eb96"/>
</game>
<game name="Mega Man X - Command Mission (USA) (Beta) (2004-07-23)">
<category>Preproduction</category>
<description>Mega Man X - Command Mission (USA) (Beta) (2004-07-23)</description>
<rom name="Mega Man X - Command Mission (USA) (Beta) (2004-07-23).iso" size="1459978240" crc="7b88690e" md5="801aeb70186915ac0a53494006ff74f8" sha1="24d72340954cd62e142a6d11a6582fc5f40ba994"/>
</game>
<game name="Mario Party 4 Event-you Disc (Japan)">
<category>Demos</category>
<description>Mario Party 4 Event-you Disc (Japan)</description>
<rom name="Mario Party 4 Event-you Disc (Japan).iso" size="1459978240" crc="4d3a81a8" md5="fe10c951d2345f94b0633d4861c65598" sha1="de8c7b6f1ca3e7b4bf29a3fa3d12c4bfc42dfad4"/>
</game>
</datafile> </datafile>

View File

@ -3,9 +3,9 @@
<datafile> <datafile>
<header> <header>
<name>Nintendo - Wii</name> <name>Nintendo - Wii</name>
<description>Nintendo - Wii - Discs (3770) (2024-02-13 21-52-18)</description> <description>Nintendo - Wii - Discs (3775) (2024-11-29 20-05-00)</description>
<version>2024-02-13 21-52-18</version> <version>2024-11-29 20-05-00</version>
<date>2024-02-13 21-52-18</date> <date>2024-11-29 20-05-00</date>
<author>redump.org</author> <author>redump.org</author>
<homepage>redump.org</homepage> <homepage>redump.org</homepage>
<url>http://redump.org/</url> <url>http://redump.org/</url>
@ -400,10 +400,10 @@
<description>Wii Music (USA) (En,Fr,Es)</description> <description>Wii Music (USA) (En,Fr,Es)</description>
<rom name="Wii Music (USA) (En,Fr,Es).iso" size="4699979776" crc="59d46fe4" md5="ce0d062df2a18fcbb145e52634679ca6" sha1="50c784119b4e5cfed244a744183c9d5214227840"/> <rom name="Wii Music (USA) (En,Fr,Es).iso" size="4699979776" crc="59d46fe4" md5="ce0d062df2a18fcbb145e52634679ca6" sha1="50c784119b4e5cfed244a744183c9d5214227840"/>
</game> </game>
<game name="Trauma Center - New Blood (USA)"> <game name="Trauma Center - New Blood (USA) (En,Ja)">
<category>Games</category> <category>Games</category>
<description>Trauma Center - New Blood (USA)</description> <description>Trauma Center - New Blood (USA) (En,Ja)</description>
<rom name="Trauma Center - New Blood (USA).iso" size="4699979776" crc="1b9fc0a4" md5="4ece4485e318a2fa2aab5cbf5aab1b18" sha1="3778e0c4d0b74be925c894813c9f6e2c6078dda5"/> <rom name="Trauma Center - New Blood (USA) (En,Ja).iso" size="4699979776" crc="1b9fc0a4" md5="4ece4485e318a2fa2aab5cbf5aab1b18" sha1="3778e0c4d0b74be925c894813c9f6e2c6078dda5"/>
</game> </game>
<game name="Kororinpa - Marble Mania (USA)"> <game name="Kororinpa - Marble Mania (USA)">
<category>Games</category> <category>Games</category>
@ -13390,10 +13390,10 @@
<description>Nickelodeon Dora the Explorer - Dora Saves the Snow Princess (Europe) (En,Fr,Nl)</description> <description>Nickelodeon Dora the Explorer - Dora Saves the Snow Princess (Europe) (En,Fr,Nl)</description>
<rom name="Nickelodeon Dora the Explorer - Dora Saves the Snow Princess (Europe) (En,Fr,Nl).iso" size="4699979776" crc="bc05608d" md5="b08b2d0a6a7262cd43069ca94fc2cb57" sha1="cfb57ba6736728b181e84826f11d20428bddcb13"/> <rom name="Nickelodeon Dora the Explorer - Dora Saves the Snow Princess (Europe) (En,Fr,Nl).iso" size="4699979776" crc="bc05608d" md5="b08b2d0a6a7262cd43069ca94fc2cb57" sha1="cfb57ba6736728b181e84826f11d20428bddcb13"/>
</game> </game>
<game name="Food Network - Cook or Be Cooked (USA)"> <game name="Food Network - Cook or Be Cooked! (USA)">
<category>Games</category> <category>Games</category>
<description>Food Network - Cook or Be Cooked (USA)</description> <description>Food Network - Cook or Be Cooked! (USA)</description>
<rom name="Food Network - Cook or Be Cooked (USA).iso" size="4699979776" crc="30a9d897" md5="ce5ec10957e7f90bdf1419237ed612f7" sha1="8dfc46d40fde135de2fb24d1e056bcf53001c714"/> <rom name="Food Network - Cook or Be Cooked! (USA).iso" size="4699979776" crc="30a9d897" md5="ce5ec10957e7f90bdf1419237ed612f7" sha1="8dfc46d40fde135de2fb24d1e056bcf53001c714"/>
</game> </game>
<game name="Get Fit with Mel B (USA) (En,Fr,Es)"> <game name="Get Fit with Mel B (USA) (En,Fr,Es)">
<category>Games</category> <category>Games</category>
@ -17300,10 +17300,10 @@
<description>Horrid Henry - Missions of Mischief (Europe) (En,Fr,De,Es,It)</description> <description>Horrid Henry - Missions of Mischief (Europe) (En,Fr,De,Es,It)</description>
<rom name="Horrid Henry - Missions of Mischief (Europe) (En,Fr,De,Es,It).iso" size="4699979776" crc="58596d71" md5="c5225b3248eebb54f0256cbf3c518e34" sha1="cd1bbc1540bf9773c56572617dc4e7bc0874d8b7"/> <rom name="Horrid Henry - Missions of Mischief (Europe) (En,Fr,De,Es,It).iso" size="4699979776" crc="58596d71" md5="c5225b3248eebb54f0256cbf3c518e34" sha1="cd1bbc1540bf9773c56572617dc4e7bc0874d8b7"/>
</game> </game>
<game name="I'm a Celebrity...Get Me Out of Here! (Europe)"> <game name="I'm a Celebrity...Get Me Out of Here! (UK)">
<category>Games</category> <category>Games</category>
<description>I'm a Celebrity...Get Me Out of Here! (Europe)</description> <description>I'm a Celebrity...Get Me Out of Here! (UK)</description>
<rom name="I'm a Celebrity...Get Me Out of Here! (Europe).iso" size="4699979776" crc="96f013c7" md5="c272b7ad6ea6f7e170f9603092f9cc5b" sha1="34c56074bce0e4ba2bc36696954d21f8e38b0602"/> <rom name="I'm a Celebrity...Get Me Out of Here! (UK).iso" size="4699979776" crc="96f013c7" md5="c272b7ad6ea6f7e170f9603092f9cc5b" sha1="34c56074bce0e4ba2bc36696954d21f8e38b0602"/>
</game> </game>
<game name="Musiic Party - Rock the House (UK) (En,Fr,De,Es,It)"> <game name="Musiic Party - Rock the House (UK) (En,Fr,De,Es,It)">
<category>Games</category> <category>Games</category>
@ -17945,10 +17945,10 @@
<description>Transformers - The Game (Korea)</description> <description>Transformers - The Game (Korea)</description>
<rom name="Transformers - The Game (Korea).iso" size="4699979776" crc="65b28e90" md5="caa4ba68d9590cb14f268d6b859de799" sha1="f0f1c9e40010a5ea40c6119fc2d1d06ebd0e35e9"/> <rom name="Transformers - The Game (Korea).iso" size="4699979776" crc="65b28e90" md5="caa4ba68d9590cb14f268d6b859de799" sha1="f0f1c9e40010a5ea40c6119fc2d1d06ebd0e35e9"/>
</game> </game>
<game name="FreeLoader for Nintendo Wii (USA) (Unl)"> <game name="FreeLoader for Nintendo Wii (USA) (Unl) (Rev 1)">
<category>Applications</category> <category>Applications</category>
<description>FreeLoader for Nintendo Wii (USA) (Unl)</description> <description>FreeLoader for Nintendo Wii (USA) (Unl) (Rev 1)</description>
<rom name="FreeLoader for Nintendo Wii (USA) (Unl).iso" size="1459978240" crc="1cc40417" md5="7ef5176eee10d71f6094bae0821d0b44" sha1="3cbab4236fe31abc15e253adadf963ba8fa7261d"/> <rom name="FreeLoader for Nintendo Wii (USA) (Unl) (Rev 1).iso" size="1459978240" crc="1cc40417" md5="7ef5176eee10d71f6094bae0821d0b44" sha1="3cbab4236fe31abc15e253adadf963ba8fa7261d"/>
</game> </game>
<game name="FreeLoader for Nintendo Wii (Japan) (Unl)"> <game name="FreeLoader for Nintendo Wii (Japan) (Unl)">
<category>Applications</category> <category>Applications</category>
@ -18450,10 +18450,10 @@
<description>Fishing Master World Tour (USA) (Beta) (2008-11-14)</description> <description>Fishing Master World Tour (USA) (Beta) (2008-11-14)</description>
<rom name="Fishing Master World Tour (USA) (Beta) (2008-11-14).iso" size="4707319808" crc="e030529b" md5="f4530ea0a779d4f47f1e9682d6cb8d45" sha1="87d23879d8320f6d8d4d84df796587bbd3ef6ed1"/> <rom name="Fishing Master World Tour (USA) (Beta) (2008-11-14).iso" size="4707319808" crc="e030529b" md5="f4530ea0a779d4f47f1e9682d6cb8d45" sha1="87d23879d8320f6d8d4d84df796587bbd3ef6ed1"/>
</game> </game>
<game name="Food Network - Cook or Be Cooked (USA) (Beta) (2009-07-20)"> <game name="Food Network - Cook or Be Cooked! (USA) (Beta) (2009-07-20)">
<category>Preproduction</category> <category>Preproduction</category>
<description>Food Network - Cook or Be Cooked (USA) (Beta) (2009-07-20)</description> <description>Food Network - Cook or Be Cooked! (USA) (Beta) (2009-07-20)</description>
<rom name="Food Network - Cook or Be Cooked (USA) (Beta) (2009-07-20).iso" size="4707319808" crc="d5aae4a9" md5="3d4e8632d621dfdc142c364e2f645822" sha1="6d10aa3438a655a4e348b82a4e78ff6120690bc5"/> <rom name="Food Network - Cook or Be Cooked! (USA) (Beta) (2009-07-20).iso" size="4707319808" crc="d5aae4a9" md5="3d4e8632d621dfdc142c364e2f645822" sha1="6d10aa3438a655a4e348b82a4e78ff6120690bc5"/>
</game> </game>
<game name="Fragile Dreams - Farewell Ruins of the Moon (USA) (Beta) (2009-12-10)"> <game name="Fragile Dreams - Farewell Ruins of the Moon (USA) (Beta) (2009-12-10)">
<category>Preproduction</category> <category>Preproduction</category>
@ -18810,10 +18810,10 @@
<description>Lara Croft Tomb Raider - Anniversary (USA) (Beta) (2007-09-20)</description> <description>Lara Croft Tomb Raider - Anniversary (USA) (Beta) (2007-09-20)</description>
<rom name="Lara Croft Tomb Raider - Anniversary (USA) (Beta) (2007-09-20).iso" size="4707319808" crc="61ef67d0" md5="0bf1f1a9941519c76c5f1e957c6118cd" sha1="3c897f945fa9ee1a681f07654a9484c23bdf63fc"/> <rom name="Lara Croft Tomb Raider - Anniversary (USA) (Beta) (2007-09-20).iso" size="4707319808" crc="61ef67d0" md5="0bf1f1a9941519c76c5f1e957c6118cd" sha1="3c897f945fa9ee1a681f07654a9484c23bdf63fc"/>
</game> </game>
<game name="Trauma Center - New Blood (USA) (Beta) (2007-10-02)"> <game name="Trauma Center - New Blood (USA) (En,Ja) (Beta) (2007-10-02)">
<category>Preproduction</category> <category>Preproduction</category>
<description>Trauma Center - New Blood (USA) (Beta) (2007-10-02)</description> <description>Trauma Center - New Blood (USA) (En,Ja) (Beta) (2007-10-02)</description>
<rom name="Trauma Center - New Blood (USA) (Beta) (2007-10-02).iso" size="4707319808" crc="6f01fefc" md5="7c38e763310b81ac68df7eacd4f58a11" sha1="5fed4b32ba9d06d1fbc21d9771b111f2279d1a16"/> <rom name="Trauma Center - New Blood (USA) (En,Ja) (Beta) (2007-10-02).iso" size="4707319808" crc="6f01fefc" md5="7c38e763310b81ac68df7eacd4f58a11" sha1="5fed4b32ba9d06d1fbc21d9771b111f2279d1a16"/>
</game> </game>
<game name="Wacky World of Sports (USA) (Beta) (2009-03-25)"> <game name="Wacky World of Sports (USA) (Beta) (2009-03-25)">
<category>Preproduction</category> <category>Preproduction</category>
@ -18860,4 +18860,29 @@
<description>Yu-Gi-Oh! 5D's - Duel Transer (USA) (Beta) (2010-07-15)</description> <description>Yu-Gi-Oh! 5D's - Duel Transer (USA) (Beta) (2010-07-15)</description>
<rom name="Yu-Gi-Oh! 5D's - Duel Transer (USA) (Beta) (2010-07-15).iso" size="4707319808" crc="fc013893" md5="572d467f9186b5f7203bfb2723ab2423" sha1="df639ddde0fdae538931443e18db80dface559fd"/> <rom name="Yu-Gi-Oh! 5D's - Duel Transer (USA) (Beta) (2010-07-15).iso" size="4707319808" crc="fc013893" md5="572d467f9186b5f7203bfb2723ab2423" sha1="df639ddde0fdae538931443e18db80dface559fd"/>
</game> </game>
<game name="Deca Sporta - Wiiro Jeulgineun Sports 10 Jongmok! (Korea)">
<category>Games</category>
<description>Deca Sporta - Wiiro Jeulgineun Sports 10 Jongmok! (Korea)</description>
<rom name="Deca Sporta - Wiiro Jeulgineun Sports 10 Jongmok! (Korea).iso" size="4699979776" crc="a5643872" md5="89b7a6b6952c0d1c2a4d3a45e91b2c0f" sha1="58454d77b159066845d3774853eeb87145c5e742"/>
</game>
<game name="Wii Fit (Japan) (Taikenban)">
<category>Demos</category>
<description>Wii Fit (Japan) (Taikenban)</description>
<rom name="Wii Fit (Japan) (Taikenban).iso" size="4699979776" crc="840639c5" md5="6428ece705a4f8e8528d72d3228ce02f" sha1="7edfe8a76c90fd8abc5d60c2643ef7791d080223"/>
</game>
<game name="FreeLoader for Nintendo Wii (USA) (Unl)">
<category>Applications</category>
<description>FreeLoader for Nintendo Wii (USA) (Unl)</description>
<rom name="FreeLoader for Nintendo Wii (USA) (Unl).iso" size="1459978240" crc="20261177" md5="5d7f821663974428804dae7df56f789d" sha1="70c05e6713e6f7acbc65929628b4ab35bb94eb7d"/>
</game>
<game name="Food Network - Cook or Be Cooked! (USA) (Demo)">
<category>Demos</category>
<description>Food Network - Cook or Be Cooked! (USA) (Demo)</description>
<rom name="Food Network - Cook or Be Cooked! (USA) (Demo).iso" size="4699979776" crc="16705e31" md5="6b8659f8ec82a16e9442df2c589cf601" sha1="3c623f447cb6cbf2c7abd9ce18f232cc2f00ff95"/>
</game>
<game name="Disney Sing It (Russia) (En,Ru)">
<category>Games</category>
<description>Disney Sing It (Russia) (En,Ru)</description>
<rom name="Disney Sing It (Russia) (En,Ru).iso" size="4699979776" crc="23644f21" md5="1d86f24d8f618c34e466a82f1f4a27e7" sha1="201c9ad4442830879b538e54c1fcc8e137fa7a92"/>
</game>
</datafile> </datafile>

View File

@ -13,7 +13,8 @@ use nod::{
build::gc::{FileCallback, FileInfo, GCPartitionBuilder, PartitionOverrides}, build::gc::{FileCallback, FileInfo, GCPartitionBuilder, PartitionOverrides},
common::PartitionKind, common::PartitionKind,
disc::{ disc::{
fst::Fst, DiscHeader, PartitionHeader, BI2_SIZE, BOOT_SIZE, MINI_DVD_SIZE, SECTOR_SIZE, fst::Fst, BootHeader, DiscHeader, BB2_OFFSET, BI2_SIZE, BOOT_SIZE, MINI_DVD_SIZE,
SECTOR_SIZE,
}, },
read::{ read::{
DiscOptions, DiscReader, PartitionEncryption, PartitionMeta, PartitionOptions, DiscOptions, DiscReader, PartitionEncryption, PartitionMeta, PartitionOptions,
@ -114,11 +115,12 @@ pub fn run(args: Args) -> nod::Result<()> {
// Build metadata // Build metadata
let mut file_infos = Vec::new(); let mut file_infos = Vec::new();
let boot_data: Box<[u8; BOOT_SIZE]> = read_fixed(&boot_path)?; let boot_data: Box<[u8; BOOT_SIZE]> = read_fixed(&boot_path)?;
let header = DiscHeader::ref_from_bytes(&boot_data[..size_of::<DiscHeader>()]) let header = DiscHeader::ref_from_bytes(array_ref![boot_data, 0, size_of::<DiscHeader>()])
.expect("Failed to read disc header"); .expect("Failed to read disc header");
let junk_id = get_junk_id(header); let junk_id = get_junk_id(header);
let partition_header = PartitionHeader::ref_from_bytes(&boot_data[size_of::<DiscHeader>()..]) let boot_header =
.expect("Failed to read partition header"); BootHeader::ref_from_bytes(array_ref![boot_data, BB2_OFFSET, size_of::<BootHeader>()])
.expect("Failed to read boot header");
let fst_path = args.dir.join("sys/fst.bin"); let fst_path = args.dir.join("sys/fst.bin");
let fst_data = read_all(&fst_path)?; let fst_data = read_all(&fst_path)?;
let fst = Fst::new(&fst_data).expect("Failed to parse FST"); let fst = Fst::new(&fst_data).expect("Failed to parse FST");
@ -138,8 +140,8 @@ pub fn run(args: Args) -> nod::Result<()> {
offset: BOOT_SIZE as u64 + BI2_SIZE as u64, offset: BOOT_SIZE as u64 + BI2_SIZE as u64,
length: apploader_size, length: apploader_size,
}); });
let fst_offset = partition_header.fst_offset(false); let fst_offset = boot_header.fst_offset(false);
let dol_offset = partition_header.dol_offset(false); let dol_offset = boot_header.dol_offset(false);
if dol_offset < fst_offset { if dol_offset < fst_offset {
file_infos.push(FileWriteInfo { file_infos.push(FileWriteInfo {
name: "sys/main.dol".to_string(), name: "sys/main.dol".to_string(),
@ -162,7 +164,7 @@ pub fn run(args: Args) -> nod::Result<()> {
return Err(nod::Error::DiscFormat("DOL not found in FST".to_string())); return Err(nod::Error::DiscFormat("DOL not found in FST".to_string()));
} }
} }
let fst_size = partition_header.fst_size(false); let fst_size = boot_header.fst_size(false);
file_infos.push(FileWriteInfo { file_infos.push(FileWriteInfo {
name: "sys/fst.bin".to_string(), name: "sys/fst.bin".to_string(),
offset: fst_offset, offset: fst_offset,
@ -213,21 +215,15 @@ pub fn run(args: Args) -> nod::Result<()> {
let mut out = File::create(&args.out) let mut out = File::create(&args.out)
.with_context(|| format!("Failed to create {}", args.out.display()))?; .with_context(|| format!("Failed to create {}", args.out.display()))?;
info!("Writing disc image to {} ({} files)", args.out.display(), file_infos.len()); info!("Writing disc image to {} ({} files)", args.out.display(), file_infos.len());
let crc = write_files( let crc =
&mut out, write_files(&mut out, &file_infos, header, boot_header, junk_id, |out, name| match name {
&file_infos,
header,
partition_header,
junk_id,
|out, name| match name {
"sys/boot.bin" => out.write_all(boot_data.as_ref()), "sys/boot.bin" => out.write_all(boot_data.as_ref()),
"sys/fst.bin" => out.write_all(fst_data.as_ref()), "sys/fst.bin" => out.write_all(fst_data.as_ref()),
path => { path => {
let mut in_file = File::open(args.dir.join(path))?; let mut in_file = File::open(args.dir.join(path))?;
io::copy(&mut in_file, out).map(|_| ()) io::copy(&mut in_file, out).map(|_| ())
} }
}, })?;
)?;
out.flush().context("Failed to flush output file")?; out.flush().context("Failed to flush output file")?;
info!("Generated disc image in {:?} (CRC32: {:08X})", start.elapsed(), crc); info!("Generated disc image in {:?} (CRC32: {:08X})", start.elapsed(), crc);
let redump_entry = redump::find_by_crc32(crc); let redump_entry = redump::find_by_crc32(crc);
@ -265,14 +261,14 @@ fn write_files<W>(
w: &mut W, w: &mut W,
file_infos: &[FileWriteInfo], file_infos: &[FileWriteInfo],
header: &DiscHeader, header: &DiscHeader,
partition_header: &PartitionHeader, boot_header: &BootHeader,
junk_id: Option<[u8; 4]>, junk_id: Option<[u8; 4]>,
mut callback: impl FnMut(&mut HashStream<&mut W>, &str) -> io::Result<()>, mut callback: impl FnMut(&mut HashStream<&mut W>, &str) -> io::Result<()>,
) -> nod::Result<u32> ) -> nod::Result<u32>
where where
W: Write + ?Sized, W: Write + ?Sized,
{ {
let fst_end = partition_header.fst_offset(false) + partition_header.fst_size(false); let fst_end = boot_header.fst_offset(false) + boot_header.fst_size(false);
let file_gap = find_file_gap(file_infos, fst_end); let file_gap = find_file_gap(file_infos, fst_end);
let mut lfg = LaggedFibonacci::default(); let mut lfg = LaggedFibonacci::default();
let mut out = HashStream::new(w); let mut out = HashStream::new(w);
@ -461,9 +457,9 @@ fn in_memory_test(
// Build metadata // Build metadata
let mut file_infos = Vec::new(); let mut file_infos = Vec::new();
let header = meta.header(); let header = meta.disc_header();
let junk_id = get_junk_id(header); let junk_id = get_junk_id(header);
let partition_header = meta.partition_header(); let boot_header = meta.boot_header();
let fst = meta.fst()?; let fst = meta.fst()?;
file_infos.push(FileWriteInfo { file_infos.push(FileWriteInfo {
@ -481,8 +477,8 @@ fn in_memory_test(
offset: BOOT_SIZE as u64 + BI2_SIZE as u64, offset: BOOT_SIZE as u64 + BI2_SIZE as u64,
length: meta.raw_apploader.len() as u64, length: meta.raw_apploader.len() as u64,
}); });
let fst_offset = partition_header.fst_offset(false); let fst_offset = boot_header.fst_offset(false);
let dol_offset = partition_header.dol_offset(false); let dol_offset = boot_header.dol_offset(false);
if dol_offset < fst_offset { if dol_offset < fst_offset {
file_infos.push(FileWriteInfo { file_infos.push(FileWriteInfo {
name: "sys/main.dol".to_string(), name: "sys/main.dol".to_string(),
@ -505,7 +501,7 @@ fn in_memory_test(
return Err(nod::Error::Other("DOL not found in FST".to_string())); return Err(nod::Error::Other("DOL not found in FST".to_string()));
} }
} }
let fst_size = partition_header.fst_size(false); let fst_size = boot_header.fst_size(false);
file_infos.push(FileWriteInfo { file_infos.push(FileWriteInfo {
name: "sys/fst.bin".to_string(), name: "sys/fst.bin".to_string(),
offset: fst_offset, offset: fst_offset,

View File

@ -145,8 +145,10 @@ fn main() {
result = result.and_then(|_| run(args.command)); result = result.and_then(|_| run(args.command));
if let Err(e) = result { if let Err(e) = result {
eprintln!("Failed: {}", e); eprintln!("Failed: {}", e);
if let Some(source) = e.source() { let mut source = e.source();
eprintln!("Caused by: {}", source); while let Some(e) = source {
eprintln!("Caused by: {}", e);
source = e.source();
} }
std::process::exit(1); std::process::exit(1);
} }