use crate::{ classes::pptr::PPtr, serialized_file::SerializedFile, unityfs::{UnityFS, header::NodeInfo}, }; use anyhow::{Context, Result, anyhow}; use log::debug; use std::{ collections::HashMap, fs::File, io::{BufReader, Cursor, Read, Seek}, marker::PhantomData, path::Path, sync::{Arc, Mutex}, }; /// High-level wrapper around UnityFS, SerializedFile and all the classes. pub struct AssetBundle { pub fs: UnityFS, pub ser_files: HashMap>>>>, pub main_file: NodeInfo, pub default_resources: SerializedFile>, pub full_decomp: bool, } pub trait ReadSeek: Read + Seek + 'static {} impl ReadSeek for T {} impl AssetBundle { pub fn open(inner: T, support_dir: impl AsRef) -> Result { let fs = UnityFS::open(inner).context("opening UnityFS")?; let main_file = fs .find_main_file() .ok_or(anyhow!("AssetBundle seems to lack main file"))? .clone(); debug!("opening default resource file"); let default_resources = SerializedFile::read( BufReader::new(File::open( support_dir.as_ref().join("unity default resources"), )?), "Library/unity default resources".to_owned(), )?; debug!("detected {:?} as main file", main_file.name); Ok(Self { fs, main_file, default_resources, full_decomp: false, ser_files: HashMap::new(), }) } pub fn get_fs_file( &mut self, nodeinfo: &NodeInfo, ) -> Result>>>> { if !self.ser_files.contains_key(&nodeinfo.name) { let mut node = self.fs.read(nodeinfo)?; let node_box = if self.full_decomp { let mut buf = Vec::new(); node.read_to_end(&mut buf)?; Box::new(Cursor::new(buf)) as Box } else { Box::new(node) as Box }; let file = SerializedFile::read(node_box, format!("archive:/{}", nodeinfo.name))?; self.ser_files .insert(nodeinfo.name.clone(), Arc::new(Mutex::new(file))); } Ok(self.ser_files.get(&nodeinfo.name).unwrap().clone()) } pub fn all_toplevel(&mut self) -> Vec { let main = self.get_fs_file(&self.main_file.clone()).unwrap(); let main = main.lock().unwrap(); let shared_assets = main.find_fs_shared_assets(&self.fs); let main_obs = main .objects .iter() .map(|o| (main.ecx.clone(), o.to_owned())) .collect::>(); let shared_obs = if let Some(shared_assets) = shared_assets { let shared = self.get_fs_file(&shared_assets).unwrap(); let shared = shared.lock().unwrap(); shared .objects .iter() .map(|o| (shared.ecx.clone(), o.to_owned())) .collect() } else { Vec::new() }; main_obs .into_iter() .chain(shared_obs) .map(|(ecx, o)| PPtr { class: "".to_string(), ecx, file_id: 0, path_id: o.path_id, _class: PhantomData, }) .collect() } pub fn all_toplevel_of_class(&mut self, class_name: &str) -> Vec { let main = self.get_fs_file(&self.main_file.clone()).unwrap(); let main = main.lock().unwrap(); let shared_assets = main.find_fs_shared_assets(&self.fs); let main_obs = main .all_objects_of_class(class_name) .map(|o| (main.ecx.clone(), o.to_owned())) .collect::>(); let shared_obs = if let Some(shared_assets) = shared_assets { let shared = self.get_fs_file(&shared_assets).unwrap(); let shared = shared.lock().unwrap(); shared .all_objects_of_class(class_name) .map(|o| (shared.ecx.clone(), o.to_owned())) .collect() } else { Vec::new() }; main_obs .into_iter() .chain(shared_obs) .map(|(ecx, o)| PPtr { class: "".to_string(), ecx, file_id: 0, path_id: o.path_id, _class: PhantomData, }) .collect() } }