use crate::{ assetbundle::AssetBundle, object::{Value, parser::FromValue}, serialized_file::ExternalsContext, }; use anyhow::{Context, Result, anyhow, bail}; use log::debug; use serde::Serialize; use std::{ io::{Read, Seek}, marker::PhantomData, sync::Arc, }; #[derive(Debug, Serialize)] pub struct PPtr { #[serde(skip, default)] pub(crate) _class: PhantomData, pub class: String, #[serde(skip)] pub ecx: Arc, pub file_id: i32, pub path_id: i64, } impl FromValue for PPtr { fn from_value(v: Value) -> Result { let Value::Object { class, fields, ecx } = v else { bail!("PPtr expected but not an object") }; let inner = class .strip_prefix("PPtr<") .ok_or(anyhow!("not a PPtr"))? .strip_suffix(">") .ok_or(anyhow!("PPtr '>' missing"))?; Ok(PPtr { class: inner.to_owned(), _class: PhantomData, ecx, file_id: fields["m_FileID"] .as_i32() .ok_or(anyhow!("PPtr m_FileID is not i32"))?, path_id: fields["m_PathID"] .as_i64() .ok_or(anyhow!("PPtr m_FileID is not i64"))?, }) } } impl PPtr { pub fn cast(self) -> PPtr { PPtr { _class: PhantomData, class: self.class, ecx: self.ecx.clone(), file_id: self.file_id, path_id: self.path_id, } } pub fn is_null(&self) -> bool { self.path_id == 0 && self.file_id == 0 } pub fn load(&self, bundle: &mut AssetBundle) -> Result { if self.is_null() { bail!("attempted to load null PPtr") } debug!( "loading PPtr<{}> file_id={} path_id={}", self.class, self.file_id, self.path_id ); let path = if self.file_id == 0 { &self.ecx.name } else { &self.ecx.externals[self.file_id as usize - 1].path_name }; debug!("ref path {path:?}"); if let Some(path) = path.strip_prefix("archive:") { let path = path.split("/").last().unwrap_or(path); let ni = bundle .fs .header .nodes() .iter() .find(|n| n.name == path) .ok_or(anyhow!("cannot find {path:?} in bundle"))? .clone(); let file = bundle.get_fs_file(&ni).unwrap(); let mut file = file.lock().unwrap(); let ob = file .objects .iter() .find(|o| o.path_id == self.path_id) .ok_or(anyhow!("object with path_id = {} not found", self.path_id))? .clone(); file.read_object(ob)?.parse() } else if *path == bundle.default_resources.ecx.name { let ob = bundle .default_resources .objects .iter() .find(|o| o.path_id == self.path_id) .unwrap() .clone(); bundle .default_resources .read_object(ob) .context("reading object from default res file")? .parse() } else { unreachable!("{path:?}") } } }