1use std::{
2 fs, io,
3 io::{Read, Seek},
4 path::Path,
5};
6
7use dyn_clone::DynClone;
8use zerocopy::transmute_ref;
9
10use crate::{
11 array_ref,
12 disc::{
13 hashes::HashTable,
14 wii::{WiiPartitionHeader, HASHES_SIZE, SECTOR_DATA_SIZE},
15 DiscHeader, PartitionHeader, PartitionKind, GCN_MAGIC, SECTOR_SIZE, WII_MAGIC,
16 },
17 io::{
18 aes_decrypt, aes_encrypt, split::SplitFileReader, DiscMeta, Format, KeyBytes, MagicBytes,
19 },
20 util::{lfg::LaggedFibonacci, read::read_from},
21 Error, Result, ResultContext,
22};
23
24pub trait DiscStream: Read + Seek + DynClone + Send + Sync {}
26
27impl<T> DiscStream for T where T: Read + Seek + DynClone + Send + Sync + ?Sized {}
28
29dyn_clone::clone_trait_object!(DiscStream);
30
31pub trait BlockIO: DynClone + Send + Sync {
33 fn read_block_internal(
35 &mut self,
36 out: &mut [u8],
37 block: u32,
38 partition: Option<&PartitionInfo>,
39 ) -> io::Result<Block>;
40
41 fn read_block(
43 &mut self,
44 out: &mut [u8],
45 block: u32,
46 partition: Option<&PartitionInfo>,
47 ) -> io::Result<Block> {
48 let block_size_internal = self.block_size_internal();
49 let block_size = self.block_size();
50 if block_size_internal == block_size {
51 self.read_block_internal(out, block, partition)
52 } else {
53 let mut offset = 0usize;
54 let mut result = None;
55 let mut block_idx =
56 ((block as u64 * block_size as u64) / block_size_internal as u64) as u32;
57 while offset < block_size as usize {
58 let block = self.read_block_internal(
59 &mut out[offset..offset + block_size_internal as usize],
60 block_idx,
61 partition,
62 )?;
63 if result.is_none() {
64 result = Some(block);
65 } else if result != Some(block) {
66 if block == Block::Zero {
67 out[offset..offset + block_size_internal as usize].fill(0);
68 } else {
69 return Err(io::Error::new(
70 io::ErrorKind::InvalidData,
71 "Inconsistent block types in split block",
72 ));
73 }
74 }
75 offset += block_size_internal as usize;
76 block_idx += 1;
77 }
78 Ok(result.unwrap_or_default())
79 }
80 }
81
82 fn block_size_internal(&self) -> u32;
84
85 fn block_size(&self) -> u32 { self.block_size_internal().max(SECTOR_SIZE as u32) }
87
88 fn meta(&self) -> DiscMeta;
90}
91
92dyn_clone::clone_trait_object!(BlockIO);
93
94pub fn new(mut stream: Box<dyn DiscStream>) -> Result<Box<dyn BlockIO>> {
96 let io: Box<dyn BlockIO> = match detect(stream.as_mut()).context("Detecting file type")? {
97 Some(Format::Iso) => crate::io::iso::DiscIOISO::new(stream)?,
98 Some(Format::Ciso) => crate::io::ciso::DiscIOCISO::new(stream)?,
99 Some(Format::Gcz) => {
100 #[cfg(feature = "compress-zlib")]
101 {
102 crate::io::gcz::DiscIOGCZ::new(stream)?
103 }
104 #[cfg(not(feature = "compress-zlib"))]
105 return Err(Error::DiscFormat("GCZ support is disabled".to_string()));
106 }
107 Some(Format::Nfs) => {
108 return Err(Error::DiscFormat("NFS requires a filesystem path".to_string()))
109 }
110 Some(Format::Wbfs) => crate::io::wbfs::DiscIOWBFS::new(stream)?,
111 Some(Format::Wia | Format::Rvz) => crate::io::wia::DiscIOWIA::new(stream)?,
112 Some(Format::Tgc) => crate::io::tgc::DiscIOTGC::new(stream)?,
113 None => return Err(Error::DiscFormat("Unknown disc format".to_string())),
114 };
115 check_block_size(io.as_ref())?;
116 Ok(io)
117}
118
119pub fn open(filename: &Path) -> Result<Box<dyn BlockIO>> {
121 let path_result = fs::canonicalize(filename);
122 if let Err(err) = path_result {
123 return Err(Error::Io(format!("Failed to open {}", filename.display()), err));
124 }
125 let path = path_result.as_ref().unwrap();
126 let meta = fs::metadata(path);
127 if let Err(err) = meta {
128 return Err(Error::Io(format!("Failed to open {}", filename.display()), err));
129 }
130 if !meta.unwrap().is_file() {
131 return Err(Error::DiscFormat(format!("Input is not a file: {}", filename.display())));
132 }
133 let mut stream = Box::new(SplitFileReader::new(filename)?);
134 let io: Box<dyn BlockIO> = match detect(stream.as_mut()).context("Detecting file type")? {
135 Some(Format::Iso) => crate::io::iso::DiscIOISO::new(stream)?,
136 Some(Format::Ciso) => crate::io::ciso::DiscIOCISO::new(stream)?,
137 Some(Format::Gcz) => {
138 #[cfg(feature = "compress-zlib")]
139 {
140 crate::io::gcz::DiscIOGCZ::new(stream)?
141 }
142 #[cfg(not(feature = "compress-zlib"))]
143 return Err(Error::DiscFormat("GCZ support is disabled".to_string()));
144 }
145 Some(Format::Nfs) => match path.parent() {
146 Some(parent) if parent.is_dir() => {
147 crate::io::nfs::DiscIONFS::new(path.parent().unwrap())?
148 }
149 _ => {
150 return Err(Error::DiscFormat("Failed to locate NFS parent directory".to_string()));
151 }
152 },
153 Some(Format::Tgc) => crate::io::tgc::DiscIOTGC::new(stream)?,
154 Some(Format::Wbfs) => crate::io::wbfs::DiscIOWBFS::new(stream)?,
155 Some(Format::Wia | Format::Rvz) => crate::io::wia::DiscIOWIA::new(stream)?,
156 None => return Err(Error::DiscFormat("Unknown disc format".to_string())),
157 };
158 check_block_size(io.as_ref())?;
159 Ok(io)
160}
161
162pub const CISO_MAGIC: MagicBytes = *b"CISO";
163pub const GCZ_MAGIC: MagicBytes = [0x01, 0xC0, 0x0B, 0xB1];
164pub const NFS_MAGIC: MagicBytes = *b"EGGS";
165pub const TGC_MAGIC: MagicBytes = [0xae, 0x0f, 0x38, 0xa2];
166pub const WBFS_MAGIC: MagicBytes = *b"WBFS";
167pub const WIA_MAGIC: MagicBytes = *b"WIA\x01";
168pub const RVZ_MAGIC: MagicBytes = *b"RVZ\x01";
169
170pub fn detect<R: Read + ?Sized>(stream: &mut R) -> io::Result<Option<Format>> {
171 let data: [u8; 0x20] = match read_from(stream) {
172 Ok(magic) => magic,
173 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
174 Err(e) => return Err(e),
175 };
176 let out = match *array_ref!(data, 0, 4) {
177 CISO_MAGIC => Some(Format::Ciso),
178 GCZ_MAGIC => Some(Format::Gcz),
179 NFS_MAGIC => Some(Format::Nfs),
180 TGC_MAGIC => Some(Format::Tgc),
181 WBFS_MAGIC => Some(Format::Wbfs),
182 WIA_MAGIC => Some(Format::Wia),
183 RVZ_MAGIC => Some(Format::Rvz),
184 _ if *array_ref!(data, 0x18, 4) == WII_MAGIC || *array_ref!(data, 0x1C, 4) == GCN_MAGIC => {
185 Some(Format::Iso)
186 }
187 _ => None,
188 };
189 Ok(out)
190}
191
192fn check_block_size(io: &dyn BlockIO) -> Result<()> {
193 if io.block_size_internal() < SECTOR_SIZE as u32
194 && SECTOR_SIZE as u32 % io.block_size_internal() != 0
195 {
196 return Err(Error::DiscFormat(format!(
197 "Sector size {} is not divisible by block size {}",
198 SECTOR_SIZE,
199 io.block_size_internal(),
200 )));
201 }
202 if io.block_size() % SECTOR_SIZE as u32 != 0 {
203 return Err(Error::DiscFormat(format!(
204 "Block size {} is not a multiple of sector size {}",
205 io.block_size(),
206 SECTOR_SIZE
207 )));
208 }
209 Ok(())
210}
211
212#[derive(Debug, Clone)]
214pub struct PartitionInfo {
215 pub index: usize,
217 pub kind: PartitionKind,
219 pub start_sector: u32,
221 pub data_start_sector: u32,
223 pub data_end_sector: u32,
225 pub key: KeyBytes,
227 pub header: Box<WiiPartitionHeader>,
229 pub disc_header: Box<DiscHeader>,
231 pub partition_header: Box<PartitionHeader>,
233 pub hash_table: Option<HashTable>,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
239pub enum Block {
240 Raw,
242 PartDecrypted {
244 has_hashes: bool,
246 },
247 Junk,
249 #[default]
251 Zero,
252}
253
254impl Block {
255 pub(crate) fn decrypt(
257 self,
258 out: &mut [u8; SECTOR_SIZE],
259 data: &[u8],
260 abs_sector: u32,
261 partition: &PartitionInfo,
262 ) -> io::Result<()> {
263 let part_sector = abs_sector - partition.data_start_sector;
264 match self {
265 Block::Raw => {
266 out.copy_from_slice(block_sector::<SECTOR_SIZE>(data, abs_sector)?);
267 decrypt_sector(out, partition);
268 }
269 Block::PartDecrypted { has_hashes } => {
270 out.copy_from_slice(block_sector::<SECTOR_SIZE>(data, abs_sector)?);
271 if !has_hashes {
272 rebuild_hash_block(out, part_sector, partition);
273 }
274 }
275 Block::Junk => {
276 generate_junk(out, part_sector, Some(partition), &partition.disc_header);
277 rebuild_hash_block(out, part_sector, partition);
278 }
279 Block::Zero => {
280 out.fill(0);
281 rebuild_hash_block(out, part_sector, partition);
282 }
283 }
284 Ok(())
285 }
286
287 pub(crate) fn encrypt(
289 self,
290 out: &mut [u8; SECTOR_SIZE],
291 data: &[u8],
292 abs_sector: u32,
293 partition: &PartitionInfo,
294 ) -> io::Result<()> {
295 let part_sector = abs_sector - partition.data_start_sector;
296 match self {
297 Block::Raw => {
298 out.copy_from_slice(block_sector::<SECTOR_SIZE>(data, abs_sector)?);
299 }
300 Block::PartDecrypted { has_hashes } => {
301 out.copy_from_slice(block_sector::<SECTOR_SIZE>(data, abs_sector)?);
302 if !has_hashes {
303 rebuild_hash_block(out, part_sector, partition);
304 }
305 encrypt_sector(out, partition);
306 }
307 Block::Junk => {
308 generate_junk(out, part_sector, Some(partition), &partition.disc_header);
309 rebuild_hash_block(out, part_sector, partition);
310 encrypt_sector(out, partition);
311 }
312 Block::Zero => {
313 out.fill(0);
314 rebuild_hash_block(out, part_sector, partition);
315 encrypt_sector(out, partition);
316 }
317 }
318 Ok(())
319 }
320
321 pub(crate) fn copy_raw(
323 self,
324 out: &mut [u8; SECTOR_SIZE],
325 data: &[u8],
326 abs_sector: u32,
327 disc_header: &DiscHeader,
328 ) -> io::Result<()> {
329 match self {
330 Block::Raw => {
331 out.copy_from_slice(block_sector::<SECTOR_SIZE>(data, abs_sector)?);
332 }
333 Block::PartDecrypted { .. } => {
334 return Err(io::Error::new(
335 io::ErrorKind::InvalidData,
336 "Cannot copy decrypted data as raw",
337 ));
338 }
339 Block::Junk => generate_junk(out, abs_sector, None, disc_header),
340 Block::Zero => out.fill(0),
341 }
342 Ok(())
343 }
344}
345
346#[inline(always)]
347fn block_sector<const N: usize>(data: &[u8], sector_idx: u32) -> io::Result<&[u8; N]> {
348 if data.len() % N != 0 {
349 return Err(io::Error::new(
350 io::ErrorKind::InvalidData,
351 format!("Expected block size {} to be a multiple of {}", data.len(), N),
352 ));
353 }
354 let rel_sector = sector_idx % (data.len() / N) as u32;
355 let offset = rel_sector as usize * N;
356 data.get(offset..offset + N)
357 .ok_or_else(|| {
358 io::Error::new(
359 io::ErrorKind::InvalidData,
360 format!(
361 "Sector {} out of range (block size {}, sector size {})",
362 rel_sector,
363 data.len(),
364 N
365 ),
366 )
367 })
368 .map(|v| unsafe { &*(v as *const [u8] as *const [u8; N]) })
369}
370
371fn generate_junk(
372 out: &mut [u8; SECTOR_SIZE],
373 sector: u32,
374 partition: Option<&PartitionInfo>,
375 disc_header: &DiscHeader,
376) {
377 let (pos, offset) = if partition.is_some() {
378 (sector as u64 * SECTOR_DATA_SIZE as u64, HASHES_SIZE)
379 } else {
380 (sector as u64 * SECTOR_SIZE as u64, 0)
381 };
382 out[..offset].fill(0);
383 let mut lfg = LaggedFibonacci::default();
384 lfg.fill_sector_chunked(
385 &mut out[offset..],
386 *array_ref![disc_header.game_id, 0, 4],
387 disc_header.disc_num,
388 pos,
389 );
390}
391
392fn rebuild_hash_block(out: &mut [u8; SECTOR_SIZE], part_sector: u32, partition: &PartitionInfo) {
393 let Some(hash_table) = partition.hash_table.as_ref() else {
394 return;
395 };
396 let sector_idx = part_sector as usize;
397 let h0_hashes: &[u8; 0x26C] =
398 transmute_ref!(array_ref![hash_table.h0_hashes, sector_idx * 31, 31]);
399 out[0..0x26C].copy_from_slice(h0_hashes);
400 let h1_hashes: &[u8; 0xA0] =
401 transmute_ref!(array_ref![hash_table.h1_hashes, sector_idx & !7, 8]);
402 out[0x280..0x320].copy_from_slice(h1_hashes);
403 let h2_hashes: &[u8; 0xA0] =
404 transmute_ref!(array_ref![hash_table.h2_hashes, (sector_idx / 8) & !7, 8]);
405 out[0x340..0x3E0].copy_from_slice(h2_hashes);
406}
407
408fn encrypt_sector(out: &mut [u8; SECTOR_SIZE], partition: &PartitionInfo) {
409 aes_encrypt(&partition.key, [0u8; 16], &mut out[..HASHES_SIZE]);
410 let iv = *array_ref![out, 0x3D0, 16];
412 aes_encrypt(&partition.key, iv, &mut out[HASHES_SIZE..]);
413}
414
415fn decrypt_sector(out: &mut [u8; SECTOR_SIZE], partition: &PartitionInfo) {
416 let iv = *array_ref![out, 0x3D0, 16];
418 aes_decrypt(&partition.key, [0u8; 16], &mut out[..HASHES_SIZE]);
419 aes_decrypt(&partition.key, iv, &mut out[HASHES_SIZE..]);
420}