pub mod block_reader; pub mod header; pub mod multi_reader; use anyhow::Result; use block_reader::BlockReader; use header::{BlockInfo, NodeInfo, UnityFSHeader}; use log::debug; use multi_reader::MultiReader; use std::{ io::{Error, ErrorKind, Read, Seek, SeekFrom}, sync::Arc, }; pub struct UnityFS { reader: MultiReader, blocks: Arc>, inner_seek_offset: u64, pub header: UnityFSHeader, } pub struct NodeReader { inner: T, position: u64, offset: u64, size: u64, } impl UnityFS { pub fn open(mut file: T) -> Result { let (header, blocks) = UnityFSHeader::read(&mut file)?; let inner_seek_offset = file.stream_position()?; Ok(Self { blocks: Arc::new(blocks), header, inner_seek_offset, reader: MultiReader::new(file)?, }) } pub fn find_main_file(&self) -> Option<&NodeInfo> { self.header .nodes() .iter() .find(|n| n.name.split_once(".").is_none()) } pub fn read<'a>(&'a self, node: &NodeInfo) -> Result>>> { let mut inner = self.reader.clone(); inner.seek(SeekFrom::Start(self.inner_seek_offset))?; let mut br = BlockReader::new(self.blocks.clone(), inner, self.inner_seek_offset); br.seek(SeekFrom::Start(node.offset))?; Ok(NodeReader { size: node.size, offset: node.offset, position: 0, inner: br, }) } } impl Read for NodeReader { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let bytes_left = self.size - self.position; let end = buf.len().min(bytes_left as usize); let size = self.inner.read(&mut buf[..end])?; self.position += size as u64; Ok(size) } } impl Seek for NodeReader { fn seek(&mut self, pos: SeekFrom) -> std::io::Result { match pos { SeekFrom::Current(n) if n >= 0 => { for _ in 0..n { self.read_exact(&mut [0u8])?; } Ok(self.stream_position()?) } SeekFrom::Start(n) => { debug!("seek node to {n} (off={})", self.offset); if n > self.size { return Err(Error::new(ErrorKind::NotSeekable, "seek out of bounds")); } self.position = n; self.inner.seek(SeekFrom::Start(self.offset + n)) } _ => unimplemented!(), } } fn stream_position(&mut self) -> std::io::Result { Ok(self.position) } }