use std::{ffi::CStr, str::from_utf8};
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, big_endian::*};
use crate::{common::MagicBytes, util::static_assert};
pub(crate) mod direct;
pub mod fst;
pub(crate) mod gcn;
pub(crate) mod hashes;
pub(crate) mod preloader;
pub(crate) mod reader;
pub mod wii;
pub(crate) mod writer;
pub const SECTOR_SIZE: usize = 0x8000;
pub const SECTOR_GROUP_SIZE: usize = SECTOR_SIZE * 64;
pub const WII_MAGIC: MagicBytes = [0x5D, 0x1C, 0x9E, 0xA3];
pub const GCN_MAGIC: MagicBytes = [0xC2, 0x33, 0x9F, 0x3D];
pub const BB2_OFFSET: usize = 0x420;
pub const BOOT_SIZE: usize = 0x440;
pub const BI2_SIZE: usize = 0x2000;
pub const MINI_DVD_SIZE: u64 = 1_459_978_240;
pub const SL_DVD_SIZE: u64 = 4_699_979_776;
pub const DL_DVD_SIZE: u64 = 8_511_160_320;
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct DiscHeader {
pub game_id: [u8; 6],
pub disc_num: u8,
pub disc_version: u8,
pub audio_streaming: u8,
pub audio_stream_buf_size: u8,
_pad1: [u8; 14],
pub wii_magic: MagicBytes,
pub gcn_magic: MagicBytes,
pub game_title: [u8; 64],
pub no_partition_hashes: u8,
pub no_partition_encryption: u8,
_pad2: [u8; 926],
}
static_assert!(size_of::<DiscHeader>() == 0x400);
impl DiscHeader {
#[inline]
pub fn game_id_str(&self) -> &str { from_utf8(&self.game_id).unwrap_or("[invalid]") }
#[inline]
pub fn game_title_str(&self) -> &str {
CStr::from_bytes_until_nul(&self.game_title)
.ok()
.and_then(|c| c.to_str().ok())
.unwrap_or("[invalid]")
}
#[inline]
pub fn is_gamecube(&self) -> bool { self.gcn_magic == GCN_MAGIC }
#[inline]
pub fn is_wii(&self) -> bool { self.wii_magic == WII_MAGIC }
#[inline]
pub fn has_partition_hashes(&self) -> bool { self.no_partition_hashes == 0 }
#[inline]
pub fn has_partition_encryption(&self) -> bool { self.no_partition_encryption == 0 }
}
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct DebugHeader {
pub debug_mon_offset: U32,
pub debug_load_address: U32,
_pad1: [u8; 0x18],
}
static_assert!(size_of::<DebugHeader>() == 0x20);
#[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct BootHeader {
pub dol_offset: U32,
pub fst_offset: U32,
pub fst_size: U32,
pub fst_max_size: U32,
pub fst_memory_address: U32,
pub user_offset: U32,
pub user_size: U32,
_pad2: [u8; 4],
}
static_assert!(size_of::<BootHeader>() == 0x20);
static_assert!(
size_of::<DiscHeader>() + size_of::<DebugHeader>() + size_of::<BootHeader>() == BOOT_SIZE
);
impl BootHeader {
#[inline]
pub fn dol_offset(&self, is_wii: bool) -> u64 {
if is_wii { self.dol_offset.get() as u64 * 4 } else { self.dol_offset.get() as u64 }
}
#[inline]
pub fn set_dol_offset(&mut self, offset: u64, is_wii: bool) {
if is_wii {
self.dol_offset.set((offset / 4) as u32);
} else {
self.dol_offset.set(offset as u32);
}
}
#[inline]
pub fn fst_offset(&self, is_wii: bool) -> u64 {
if is_wii { self.fst_offset.get() as u64 * 4 } else { self.fst_offset.get() as u64 }
}
#[inline]
pub fn set_fst_offset(&mut self, offset: u64, is_wii: bool) {
if is_wii {
self.fst_offset.set((offset / 4) as u32);
} else {
self.fst_offset.set(offset as u32);
}
}
#[inline]
pub fn fst_size(&self, is_wii: bool) -> u64 {
if is_wii { self.fst_size.get() as u64 * 4 } else { self.fst_size.get() as u64 }
}
#[inline]
pub fn set_fst_size(&mut self, size: u64, is_wii: bool) {
if is_wii {
self.fst_size.set((size / 4) as u32);
} else {
self.fst_size.set(size as u32);
}
}
#[inline]
pub fn fst_max_size(&self, is_wii: bool) -> u64 {
if is_wii { self.fst_max_size.get() as u64 * 4 } else { self.fst_max_size.get() as u64 }
}
#[inline]
pub fn set_fst_max_size(&mut self, size: u64, is_wii: bool) {
if is_wii {
self.fst_max_size.set((size / 4) as u32);
} else {
self.fst_max_size.set(size as u32);
}
}
}
#[derive(Debug, PartialEq, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
#[repr(C, align(4))]
pub struct ApploaderHeader {
pub date: [u8; 16],
pub entry_point: U32,
pub size: U32,
pub trailer_size: U32,
_pad: [u8; 4],
}
impl ApploaderHeader {
#[inline]
pub fn date_str(&self) -> Option<&str> {
CStr::from_bytes_until_nul(&self.date).ok().and_then(|c| c.to_str().ok())
}
}
pub const DOL_MAX_TEXT_SECTIONS: usize = 7;
pub const DOL_MAX_DATA_SECTIONS: usize = 11;
#[derive(Debug, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct DolHeader {
pub text_offs: [U32; DOL_MAX_TEXT_SECTIONS],
pub data_offs: [U32; DOL_MAX_DATA_SECTIONS],
pub text_addrs: [U32; DOL_MAX_TEXT_SECTIONS],
pub data_addrs: [U32; DOL_MAX_DATA_SECTIONS],
pub text_sizes: [U32; DOL_MAX_TEXT_SECTIONS],
pub data_sizes: [U32; DOL_MAX_DATA_SECTIONS],
pub bss_addr: U32,
pub bss_size: U32,
pub entry_point: U32,
_pad: [u8; 0x1C],
}
static_assert!(size_of::<DolHeader>() == 0x100);