diff options
Diffstat (limited to 'src/unityfs.rs')
-rw-r--r-- | src/unityfs.rs | 327 |
1 files changed, 0 insertions, 327 deletions
diff --git a/src/unityfs.rs b/src/unityfs.rs deleted file mode 100644 index 05ad922..0000000 --- a/src/unityfs.rs +++ /dev/null @@ -1,327 +0,0 @@ -use crate::helper::{AlignExt, ReadExt}; -use anyhow::{Result, anyhow, bail}; -use humansize::DECIMAL; -use log::{debug, info, trace}; -use std::io::{Cursor, Error, ErrorKind, Read, Seek, SeekFrom}; - -pub struct UnityFS<T> { - nodes: Vec<NodeInfo>, - reader: BlocksReader<T>, - pub file_version: u32, - pub player_version: String, - pub unity_version: String, -} - -#[derive(Debug, Clone)] -pub struct NodeInfo { - pub name: String, - pub size: u64, - offset: u64, - _status: u32, -} - -struct BlockInfo { - comp_size: u32, - decomp_size: u32, - comp_scheme: CompressionScheme, -} - -pub struct NodeReader<'a, T> { - inner: &'a mut BlocksReader<T>, - position: u64, - offset: u64, - size: u64, -} - -impl<T: Read + Seek> UnityFS<T> { - pub fn open(mut file: T) -> Result<Self> { - let signature = file.read_cstr()?; - if signature.as_str() != "UnityFS" { - bail!("unknown signature {signature:?}") - } - - let file_version = file.read_u32_be()?; - let player_version = file.read_cstr()?; - let unity_version = file.read_cstr()?; - let size = file.read_u64_be()?; - let blockindex_comp_size = file.read_u32_be()?; - let blockindex_decomp_size = file.read_u32_be()?; - let flags = file.read_u32_be()?; - - let meta_comp_scheme = CompressionScheme::from_flag_num(flags as u8).ok_or(anyhow!( - "unknown block compression 0x{:02x}", - (flags & 0x3f) as u8 - ))?; - let blockindex_eof = flags & 0x80 != 0; - let blockindex_has_directory = flags & 0x40 != 0; - let blockindex_need_padding = flags & 0x200 != 0; - - info!("File Version: {file_version:?}"); - info!("Player Version: {player_version:?}"); - info!("Unity Version: {unity_version:?}"); - debug!("size={size:?}"); - debug!("meta_comp_size={blockindex_comp_size:?}"); - debug!("meta_decomp_size={blockindex_decomp_size:?}"); - debug!("flags={flags:?}"); - debug!("meta_comp_scheme={meta_comp_scheme:?}"); - debug!("blockindex_eof={blockindex_eof:?}"); - debug!("blockindex_has_directory={blockindex_has_directory:?}"); - debug!("blockindex_need_padding={blockindex_need_padding:?}"); - - let mut blockindex = { - let restore_position = if blockindex_eof { - let pos = file.stream_position()?; - file.seek(SeekFrom::End(-(blockindex_comp_size as i64)))?; - Some(pos) - } else { - None - }; - - let mut blockindex = vec![0u8; blockindex_comp_size as usize]; - file.read_exact(&mut blockindex)?; - - if let Some(pos) = restore_position { - file.seek(SeekFrom::Start(pos))?; - } - let blockindex = - meta_comp_scheme.decompress(blockindex, blockindex_decomp_size as usize)?; - Cursor::new(blockindex) - }; - - file.align(16)?; - - blockindex.read_u128_be()?; - - let num_blocks = blockindex.read_u32_be()?; - info!("File has {num_blocks} blocks"); - let mut blocks = Vec::new(); - for _ in 0..num_blocks { - let decomp_size = blockindex.read_u32_be()?; - let comp_size = blockindex.read_u32_be()?; - let flags = blockindex.read_u16_be()?; - let comp_scheme = CompressionScheme::from_flag_num(flags as u8) - .ok_or(anyhow!("unknown block compression 0x{:02x}", flags & 0x3f))?; - blocks.push(BlockInfo { - comp_size, - decomp_size, - comp_scheme, - }) - } - - let num_nodes = blockindex.read_u32_be()?; - debug!("num_nodes={num_nodes:?}"); - let mut nodes = Vec::new(); - for _ in 0..num_nodes { - let offset = blockindex.read_u64_be()?; - let size = blockindex.read_u64_be()?; - let status = blockindex.read_u32_be()?; - let name = blockindex.read_cstr()?; - info!( - "found node {name:?} (size={}, status={status})", - humansize::format_size(size, DECIMAL) - ); - nodes.push(NodeInfo { - offset, - size, - _status: status, - name, - }) - } - - let position = file.stream_position()?; - - Ok(Self { - file_version, - player_version, - unity_version, - nodes, - reader: BlocksReader::new(blocks, file, position), - }) - } - - pub fn nodes(&self) -> &[NodeInfo] { - &self.nodes - } - - pub fn find_main_file(&self) -> Option<&NodeInfo> { - self.nodes().iter().find(|n| { - !n.name.ends_with(".resource") - && !n.name.ends_with(".resS") - && !n.name.ends_with(".sharedAssets") - }) - } - - pub fn read<'a>(&'a mut self, node: &NodeInfo) -> std::io::Result<NodeReader<'a, T>> { - self.reader.seek(SeekFrom::Start(node.offset))?; - Ok(NodeReader { - size: node.size, - offset: node.offset, - position: 0, - inner: &mut self.reader, - }) - } -} - -struct BlocksReader<T> { - blocks: Vec<BlockInfo>, - inner: T, - inner_seek_offset: u64, - nblock_index: usize, - cblock_data: Vec<u8>, - cblock_off: usize, -} -impl<T: Read> BlocksReader<T> { - pub fn new(blocks: Vec<BlockInfo>, inner: T, inner_seek_offset: u64) -> Self { - Self { - blocks, - inner, - inner_seek_offset, - nblock_index: 0, - cblock_data: Vec::new(), - cblock_off: 0, - } - } - pub fn load_next_block(&mut self) -> std::io::Result<()> { - trace!("loading block {}", self.nblock_index); - let block = &self.blocks[self.nblock_index]; - let mut comp_buf = vec![0; block.comp_size as usize]; - self.inner.read_exact(&mut comp_buf)?; - let decomp_buf = block - .comp_scheme - .decompress(comp_buf, block.decomp_size as usize) - .map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidData, - format!("decompression failure: {e}"), - ) - })?; - self.nblock_index += 1; - self.cblock_data = decomp_buf; - self.cblock_off = 0; - Ok(()) - } -} -impl<T: Read> Read for BlocksReader<T> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - if self.cblock_off >= self.cblock_data.len() { - self.load_next_block()?; - } - let size = (self.cblock_data.len() - self.cblock_off).min(buf.len()); - buf[..size].copy_from_slice(&self.cblock_data[self.cblock_off..self.cblock_off + size]); - self.cblock_off += size; - Ok(size) - } -} -impl<T: Seek + Read> Seek for BlocksReader<T> { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> { - let SeekFrom::Start(pos) = pos else { - unimplemented!() - }; - debug!("seek decomp to {pos}"); - let mut comp_off = self.inner_seek_offset; - let mut decomp_off = 0; - let mut target_block = None; - for (i, b) in self.blocks.iter().enumerate() { - if pos <= decomp_off + b.decomp_size as u64 { - target_block = Some(i); - break; - } - decomp_off += b.decomp_size as u64; - comp_off += b.comp_size as u64; - } - - let Some(i) = target_block else { - return Err(std::io::Error::new( - ErrorKind::UnexpectedEof, - "seek out of bounds", - )); - }; - - let block_off = pos - decomp_off; - debug!("target is block={i} offset={block_off}"); - if self.nblock_index == i + 1 { - debug!("intra-block seek") - } else { - debug!("seek comp to {comp_off}"); - self.inner.seek(SeekFrom::Start(comp_off))?; - self.nblock_index = i; - self.load_next_block()?; - } - self.cblock_off = block_off as usize; - - Ok(pos) - } -} - -impl<T: Read> Read for NodeReader<'_, T> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - 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<T: Seek + Read> Seek for NodeReader<'_, T> { - fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> { - 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<u64> { - Ok(self.position) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -enum CompressionScheme { - None, - Lzma, - Lz4, - Lz4hc, - Lzham, -} -impl CompressionScheme { - pub fn from_flag_num(n: u8) -> Option<CompressionScheme> { - Some(match n & 0x3f { - 0 => CompressionScheme::None, - 1 => CompressionScheme::Lzma, - 2 => CompressionScheme::Lz4, - 3 => CompressionScheme::Lz4hc, - 4 => CompressionScheme::Lzham, - _ => return None, - }) - } - pub fn decompress(&self, block: Vec<u8>, decomp_size: usize) -> Result<Vec<u8>> { - match self { - CompressionScheme::None => Ok(block), - CompressionScheme::Lzma => { - let mut r = lzma::Reader::from(Cursor::new(block))?; - let mut buf = Vec::new(); - r.read_to_end(&mut buf)?; - Ok(buf) - } - CompressionScheme::Lz4hc | CompressionScheme::Lz4 => { - Ok(lz4_flex::block::decompress(&block, decomp_size)?) - } - // CompressionScheme::LZ4HC | CompressionScheme::LZ4 => { - // Ok(lz4::block::decompress(&block, Some(decomp_size as i32))?) - // } - CompressionScheme::Lzham => todo!(), - } - } -} |