#![allow(non_snake_case)]
use anyhow::Result;
use rabex::files::SerializedFile;
use rabex::files::bundlefile::{BundleFileReader, ExtractionConfig};
use rabex::objects::{ClassId, ClassIdType, TypedPPtr};
use rabex::tpk::TpkTypeTreeBlob;
use rabex::typetree::TypeTreeProvider;
use rabex::typetree::typetree_cache::TypeTreeCache;
use rustc_hash::FxHashMap;
use serde_derive::{Deserialize, Serialize};
use std::fs::File;
use std::io::{Cursor, Read, Seek, SeekFrom};
fn main() -> Result<()> {
let paths: Vec<String> = std::env::args().skip(1).collect();
if paths.is_empty() {
anyhow::bail!("Expected path(s) to unity bundle argument");
}
let tpk = &TypeTreeCache::new(TpkTypeTreeBlob::embedded());
for path in &paths {
if paths.len() > 1 {
println!("=== {path} ===");
}
let data = &mut File::open(&path)?;
if let Ok(mut reader) =
BundleFileReader::from_reader(&mut *data, &ExtractionConfig::default())
{
while let Some(mut file) = reader.next() {
let mut data = Cursor::new(file.read()?);
let hierarchy = get_all(&mut data, tpk)?;
println!("{}", serde_json::to_string_pretty(&hierarchy)?);
}
} else {
data.seek(SeekFrom::Start(0))?;
let hierarchy = get_all(data, tpk)?;
println!("{}", serde_json::to_string_pretty(&hierarchy)?);
}
}
Ok(())
}
fn get_all(
data: &mut (impl Read + Seek),
tpk: &impl TypeTreeProvider,
) -> Result<Vec<HierarchyObject>> {
let file = SerializedFile::from_reader(data)?;
let transforms = file
.objects_of::<Transform>(tpk)
.map(|obj| Ok((obj.info.m_PathID, obj.read(data)?)))
.collect::<Result<FxHashMap<_, _>>>()?;
let mut all = transforms
.values()
.filter(|transform| transform.m_Father.is_null())
.map(|root| get(root, &file, tpk, data, 0))
.collect::<Result<Vec<_>>>()?;
all.sort_by(|a, b| a.name.cmp(&b.name));
Ok(all)
}
fn get(
root: &Transform,
file: &SerializedFile,
tpk: &impl TypeTreeProvider,
reader: &mut (impl Read + Seek),
indent: usize,
) -> Result<HierarchyObject> {
let go = root.m_GameObject.deref_local(&file, tpk)?.read(reader)?;
let mut children = root
.m_Children
.iter()
.map(|child| {
let child = child.deref_local(&file, tpk)?.read(reader)?;
get(&child, file, tpk, reader, indent + 1)
})
.collect::<Result<Vec<_>>>()?;
children.sort_by(|a, b| a.name.cmp(&b.name));
Ok(HierarchyObject {
name: go.m_Name,
children,
})
}
#[derive(Debug, Serialize, Clone)]
pub struct HierarchyObject {
name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
children: Vec<HierarchyObject>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Transform {
pub m_GameObject: TypedPPtr<GameObject>,
pub m_Children: Vec<TypedPPtr<Transform>>,
pub m_Father: TypedPPtr<Transform>,
}
impl ClassIdType for Transform {
const CLASS_ID: ClassId = ClassId::Transform;
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GameObject {
pub m_Name: String,
}
impl ClassIdType for GameObject {
const CLASS_ID: ClassId = ClassId::GameObject;
}