use std::{ io, io::{Read, Seek, SeekFrom}, mem::size_of, }; use zerocopy::{big_endian::U32, FromBytes, Immutable, IntoBytes, KnownLayout}; use crate::{ disc::SECTOR_SIZE, io::{ block::{Block, BlockIO, DiscStream, PartitionInfo, TGC_MAGIC}, Format, MagicBytes, }, util::read::{read_box_slice, read_from}, DiscHeader, DiscMeta, Error, Node, PartitionHeader, Result, ResultContext, }; /// TGC header (big endian) #[derive(Clone, Debug, PartialEq, FromBytes, IntoBytes, Immutable, KnownLayout)] #[repr(C, align(4))] struct TGCHeader { /// Magic bytes magic: MagicBytes, /// TGC version version: U32, /// Offset to the start of the GCM header header_offset: U32, /// Size of the GCM header header_size: U32, /// Offset to the FST fst_offset: U32, /// Size of the FST fst_size: U32, /// Maximum size of the FST across discs fst_max_size: U32, /// Offset to the DOL dol_offset: U32, /// Size of the DOL dol_size: U32, /// Offset to user data user_offset: U32, /// Size of user data user_size: U32, /// Offset to the banner banner_offset: U32, /// Size of the banner banner_size: U32, /// Original user data offset in the GCM gcm_user_offset: U32, } #[derive(Clone)] pub struct DiscIOTGC { inner: Box, stream_len: u64, header: TGCHeader, fst: Box<[u8]>, } impl DiscIOTGC { pub fn new(mut inner: Box) -> Result> { let stream_len = inner.seek(SeekFrom::End(0)).context("Determining stream length")?; inner.seek(SeekFrom::Start(0)).context("Seeking to start")?; // Read header let header: TGCHeader = read_from(inner.as_mut()).context("Reading TGC header")?; if header.magic != TGC_MAGIC { return Err(Error::DiscFormat("Invalid TGC magic".to_string())); } // Read FST and adjust offsets inner .seek(SeekFrom::Start(header.fst_offset.get() as u64)) .context("Seeking to TGC FST")?; let mut fst = read_box_slice(inner.as_mut(), header.fst_size.get() as usize) .context("Reading TGC FST")?; let (root_node, _) = Node::ref_from_prefix(&fst) .map_err(|_| Error::DiscFormat("Invalid TGC FST".to_string()))?; let node_count = root_node.length() as usize; let (nodes, _) = <[Node]>::mut_from_prefix_with_elems(&mut fst, node_count) .map_err(|_| Error::DiscFormat("Invalid TGC FST".to_string()))?; for node in nodes { if node.is_file() { node.offset = node.offset - header.gcm_user_offset + (header.user_offset - header.header_offset); } } Ok(Box::new(Self { inner, stream_len, header, fst })) } } impl BlockIO for DiscIOTGC { fn read_block_internal( &mut self, out: &mut [u8], block: u32, _partition: Option<&PartitionInfo>, ) -> io::Result { let offset = self.header.header_offset.get() as u64 + block as u64 * SECTOR_SIZE as u64; if offset >= self.stream_len { // End of file return Ok(Block::Zero); } self.inner.seek(SeekFrom::Start(offset))?; if offset + SECTOR_SIZE as u64 > self.stream_len { // If the last block is not a full sector, fill the rest with zeroes let read = (self.stream_len - offset) as usize; self.inner.read_exact(&mut out[..read])?; out[read..].fill(0); } else { self.inner.read_exact(out)?; } // Adjust internal GCM header if block == 0 { let partition_header = PartitionHeader::mut_from_bytes( &mut out[size_of::() ..size_of::() + size_of::()], ) .unwrap(); partition_header.dol_offset = self.header.dol_offset - self.header.header_offset; partition_header.fst_offset = self.header.fst_offset - self.header.header_offset; } // Copy modified FST to output if offset + out.len() as u64 > self.header.fst_offset.get() as u64 && offset < self.header.fst_offset.get() as u64 + self.header.fst_size.get() as u64 { let out_offset = (self.header.fst_offset.get() as u64).saturating_sub(offset) as usize; let fst_offset = offset.saturating_sub(self.header.fst_offset.get() as u64) as usize; let copy_len = (out.len() - out_offset).min(self.header.fst_size.get() as usize - fst_offset); out[out_offset..out_offset + copy_len] .copy_from_slice(&self.fst[fst_offset..fst_offset + copy_len]); } Ok(Block::Raw) } fn block_size_internal(&self) -> u32 { SECTOR_SIZE as u32 } fn meta(&self) -> DiscMeta { DiscMeta { format: Format::Tgc, lossless: true, disc_size: Some(self.stream_len - self.header.header_offset.get() as u64), ..Default::default() } } }