use std::ffi::CString; use std::io::{Cursor, Read, Seek, Write}; use std::path::Path; use bitflags::bitflags; use color_eyre::eyre::Context; use color_eyre::{eyre, Result}; use futures::future::join_all; use serde::Serialize; use crate::binary::sync::*; use crate::filetype::*; use crate::murmur::{HashGroup, IdString64, Murmur64}; #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] pub enum BundleFileType { Animation, AnimationCurves, Apb, BakedLighting, Bik, BlendSet, Bones, Chroma, CommonPackage, Config, Crypto, Data, Entity, Flow, Font, Ies, Ini, Input, Ivf, Keys, Level, Lua, Material, Mod, MouseCursor, NavData, NetworkConfig, OddleNet, Package, Particles, PhysicsProperties, RenderConfig, RtPipeline, Scene, Shader, ShaderLibrary, ShaderLibraryGroup, ShadingEnvionmentMapping, ShadingEnvironment, Slug, SlugAlbum, SoundEnvironment, SpuJob, StateMachine, StaticPVS, Strings, SurfaceProperties, Texture, TimpaniBank, TimpaniMaster, Tome, Ugg, Unit, Upb, VectorField, Wav, WwiseBank, WwiseDep, WwiseEvent, WwiseMetadata, WwiseStream, Xml, Unknown(Murmur64), } impl BundleFileType { pub fn ext_name(&self) -> String { match self { BundleFileType::AnimationCurves => String::from("animation_curves"), BundleFileType::Animation => String::from("animation"), BundleFileType::Apb => String::from("apb"), BundleFileType::BakedLighting => String::from("baked_lighting"), BundleFileType::Bik => String::from("bik"), BundleFileType::BlendSet => String::from("blend_set"), BundleFileType::Bones => String::from("bones"), BundleFileType::Chroma => String::from("chroma"), BundleFileType::CommonPackage => String::from("common_package"), BundleFileType::Config => String::from("config"), BundleFileType::Crypto => String::from("crypto"), BundleFileType::Data => String::from("data"), BundleFileType::Entity => String::from("entity"), BundleFileType::Flow => String::from("flow"), BundleFileType::Font => String::from("font"), BundleFileType::Ies => String::from("ies"), BundleFileType::Ini => String::from("ini"), BundleFileType::Input => String::from("input"), BundleFileType::Ivf => String::from("ivf"), BundleFileType::Keys => String::from("keys"), BundleFileType::Level => String::from("level"), BundleFileType::Lua => String::from("lua"), BundleFileType::Material => String::from("material"), BundleFileType::Mod => String::from("mod"), BundleFileType::MouseCursor => String::from("mouse_cursor"), BundleFileType::NavData => String::from("nav_data"), BundleFileType::NetworkConfig => String::from("network_config"), BundleFileType::OddleNet => String::from("oodle_net"), BundleFileType::Package => String::from("package"), BundleFileType::Particles => String::from("particles"), BundleFileType::PhysicsProperties => String::from("physics_properties"), BundleFileType::RenderConfig => String::from("render_config"), BundleFileType::RtPipeline => String::from("rt_pipeline"), BundleFileType::Scene => String::from("scene"), BundleFileType::ShaderLibraryGroup => String::from("shader_library_group"), BundleFileType::ShaderLibrary => String::from("shader_library"), BundleFileType::Shader => String::from("shader"), BundleFileType::ShadingEnvionmentMapping => String::from("shading_environment_mapping"), BundleFileType::ShadingEnvironment => String::from("shading_environment"), BundleFileType::SlugAlbum => String::from("slug_album"), BundleFileType::Slug => String::from("slug"), BundleFileType::SoundEnvironment => String::from("sound_environment"), BundleFileType::SpuJob => String::from("spu_job"), BundleFileType::StateMachine => String::from("state_machine"), BundleFileType::StaticPVS => String::from("static_pvs"), BundleFileType::Strings => String::from("strings"), BundleFileType::SurfaceProperties => String::from("surface_properties"), BundleFileType::Texture => String::from("texture"), BundleFileType::TimpaniBank => String::from("timpani_bank"), BundleFileType::TimpaniMaster => String::from("timpani_master"), BundleFileType::Tome => String::from("tome"), BundleFileType::Ugg => String::from("ugg"), BundleFileType::Unit => String::from("unit"), BundleFileType::Upb => String::from("upb"), BundleFileType::VectorField => String::from("vector_field"), BundleFileType::Wav => String::from("wav"), BundleFileType::WwiseBank => String::from("wwise_bank"), BundleFileType::WwiseDep => String::from("wwise_dep"), BundleFileType::WwiseEvent => String::from("wwise_event"), BundleFileType::WwiseMetadata => String::from("wwise_metadata"), BundleFileType::WwiseStream => String::from("wwise_stream"), BundleFileType::Xml => String::from("xml"), BundleFileType::Unknown(s) => format!("{s:016X}"), } } pub fn decompiled_ext_name(&self) -> String { match self { BundleFileType::Texture => String::from("dds"), BundleFileType::WwiseBank => String::from("bnk"), BundleFileType::WwiseStream => String::from("ogg"), _ => self.ext_name(), } } pub fn hash(&self) -> Murmur64 { Murmur64::from(*self) } } impl std::str::FromStr for BundleFileType { type Err = color_eyre::Report; fn from_str(s: &str) -> Result { let val = match s { "animation_curves" => BundleFileType::AnimationCurves, "animation" => BundleFileType::Animation, "apb" => BundleFileType::Apb, "baked_lighting" => BundleFileType::BakedLighting, "bik" => BundleFileType::Bik, "blend_set" => BundleFileType::BlendSet, "bones" => BundleFileType::Bones, "chroma" => BundleFileType::Chroma, "common_package" => BundleFileType::CommonPackage, "config" => BundleFileType::Config, "crypto" => BundleFileType::Crypto, "data" => BundleFileType::Data, "entity" => BundleFileType::Entity, "flow" => BundleFileType::Flow, "font" => BundleFileType::Font, "ies" => BundleFileType::Ies, "ini" => BundleFileType::Ini, "input" => BundleFileType::Input, "ivf" => BundleFileType::Ivf, "keys" => BundleFileType::Keys, "level" => BundleFileType::Level, "lua" => BundleFileType::Lua, "material" => BundleFileType::Material, "mod" => BundleFileType::Mod, "mouse_cursor" => BundleFileType::MouseCursor, "nav_data" => BundleFileType::NavData, "network_config" => BundleFileType::NetworkConfig, "oodle_net" => BundleFileType::OddleNet, "package" => BundleFileType::Package, "particles" => BundleFileType::Particles, "physics_properties" => BundleFileType::PhysicsProperties, "render_config" => BundleFileType::RenderConfig, "rt_pipeline" => BundleFileType::RtPipeline, "scene" => BundleFileType::Scene, "shader_library_group" => BundleFileType::ShaderLibraryGroup, "shader_library" => BundleFileType::ShaderLibrary, "shader" => BundleFileType::Shader, "shading_environment_mapping" => BundleFileType::ShadingEnvionmentMapping, "shading_environment" => BundleFileType::ShadingEnvironment, "slug_album" => BundleFileType::SlugAlbum, "slug" => BundleFileType::Slug, "sound_environment" => BundleFileType::SoundEnvironment, "spu_job" => BundleFileType::SpuJob, "state_machine" => BundleFileType::StateMachine, "static_pvs" => BundleFileType::StaticPVS, "strings" => BundleFileType::Strings, "surface_properties" => BundleFileType::SurfaceProperties, "texture" => BundleFileType::Texture, "timpani_bank" => BundleFileType::TimpaniBank, "timpani_master" => BundleFileType::TimpaniMaster, "tome" => BundleFileType::Tome, "ugg" => BundleFileType::Ugg, "unit" => BundleFileType::Unit, "upb" => BundleFileType::Upb, "vector_field" => BundleFileType::VectorField, "wav" => BundleFileType::Wav, "wwise_bank" => BundleFileType::WwiseBank, "wwise_dep" => BundleFileType::WwiseDep, "wwise_event" => BundleFileType::WwiseEvent, "wwise_metadata" => BundleFileType::WwiseMetadata, "wwise_stream" => BundleFileType::WwiseStream, "xml" => BundleFileType::Xml, s => eyre::bail!("Unknown type string '{}'", s), }; Ok(val) } } impl Serialize for BundleFileType { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let value = self.ext_name(); value.serialize(serializer) } } impl From for BundleFileType { fn from(value: Murmur64) -> Self { Self::from(Into::::into(value)) } } impl From for BundleFileType { fn from(hash: u64) -> BundleFileType { match hash { 0x931e336d7646cc26 => BundleFileType::Animation, 0xdcfb9e18fff13984 => BundleFileType::AnimationCurves, 0x3eed05ba83af5090 => BundleFileType::Apb, 0x7ffdb779b04e4ed1 => BundleFileType::BakedLighting, 0xaa5965f03029fa18 => BundleFileType::Bik, 0xe301e8af94e3b5a3 => BundleFileType::BlendSet, 0x18dead01056b72e9 => BundleFileType::Bones, 0xb7893adf7567506a => BundleFileType::Chroma, 0xfe9754bd19814a47 => BundleFileType::CommonPackage, 0x82645835e6b73232 => BundleFileType::Config, 0x69108ded1e3e634b => BundleFileType::Crypto, 0x8fd0d44d20650b68 => BundleFileType::Data, 0x9831ca893b0d087d => BundleFileType::Entity, 0x92d3ee038eeb610d => BundleFileType::Flow, 0x9efe0a916aae7880 => BundleFileType::Font, 0x8f7d5a2c0f967655 => BundleFileType::Ies, 0xd526a27da14f1dc5 => BundleFileType::Ini, 0x2bbcabe5074ade9e => BundleFileType::Input, 0xfa4a8e091a91201e => BundleFileType::Ivf, 0xa62f9297dc969e85 => BundleFileType::Keys, 0x2a690fd348fe9ac5 => BundleFileType::Level, 0xa14e8dfa2cd117e2 => BundleFileType::Lua, 0xeac0b497876adedf => BundleFileType::Material, 0x3fcdd69156a46417 => BundleFileType::Mod, 0xb277b11fe4a61d37 => BundleFileType::MouseCursor, 0x169de9566953d264 => BundleFileType::NavData, 0x3b1fa9e8f6bac374 => BundleFileType::NetworkConfig, 0xb0f2c12eb107f4d8 => BundleFileType::OddleNet, 0xad9c6d9ed1e5e77a => BundleFileType::Package, 0xa8193123526fad64 => BundleFileType::Particles, 0xbf21403a3ab0bbb1 => BundleFileType::PhysicsProperties, 0x27862fe24795319c => BundleFileType::RenderConfig, 0x9ca183c2d0e76dee => BundleFileType::RtPipeline, 0x9d0a795bfe818d19 => BundleFileType::Scene, 0xcce8d5b5f5ae333f => BundleFileType::Shader, 0xe5ee32a477239a93 => BundleFileType::ShaderLibrary, 0x9e5c3cc74575aeb5 => BundleFileType::ShaderLibraryGroup, 0x250e0a11ac8e26f8 => BundleFileType::ShadingEnvionmentMapping, 0xfe73c7dcff8a7ca5 => BundleFileType::ShadingEnvironment, 0xa27b4d04a9ba6f9e => BundleFileType::Slug, 0xe9fc9ea7042e5ec0 => BundleFileType::SlugAlbum, 0xd8b27864a97ffdd7 => BundleFileType::SoundEnvironment, 0xf97af9983c05b950 => BundleFileType::SpuJob, 0xa486d4045106165c => BundleFileType::StateMachine, 0xe3f0baa17d620321 => BundleFileType::StaticPVS, 0x0d972bab10b40fd3 => BundleFileType::Strings, 0xad2d3fa30d9ab394 => BundleFileType::SurfaceProperties, 0xcd4238c6a0c69e32 => BundleFileType::Texture, 0x99736be1fff739a4 => BundleFileType::TimpaniBank, 0x00a3e6c59a2b9c6c => BundleFileType::TimpaniMaster, 0x19c792357c99f49b => BundleFileType::Tome, 0x712d6e3dd1024c9c => BundleFileType::Ugg, 0xe0a48d0be9a7453f => BundleFileType::Unit, 0xa99510c6e86dd3c2 => BundleFileType::Upb, 0xf7505933166d6755 => BundleFileType::VectorField, 0x786f65c00a816b19 => BundleFileType::Wav, 0x535a7bd3e650d799 => BundleFileType::WwiseBank, 0xaf32095c82f2b070 => BundleFileType::WwiseDep, 0xaabdd317b58dfc8a => BundleFileType::WwiseEvent, 0xd50a8b7e1c82b110 => BundleFileType::WwiseMetadata, 0x504b55235d21440e => BundleFileType::WwiseStream, 0x76015845a6003765 => BundleFileType::Xml, _ => BundleFileType::Unknown(Murmur64::from(hash)), } } } impl From for u64 { fn from(t: BundleFileType) -> u64 { match t { BundleFileType::Animation => 0x931e336d7646cc26, BundleFileType::AnimationCurves => 0xdcfb9e18fff13984, BundleFileType::Apb => 0x3eed05ba83af5090, BundleFileType::BakedLighting => 0x7ffdb779b04e4ed1, BundleFileType::Bik => 0xaa5965f03029fa18, BundleFileType::BlendSet => 0xe301e8af94e3b5a3, BundleFileType::Bones => 0x18dead01056b72e9, BundleFileType::Chroma => 0xb7893adf7567506a, BundleFileType::CommonPackage => 0xfe9754bd19814a47, BundleFileType::Config => 0x82645835e6b73232, BundleFileType::Crypto => 0x69108ded1e3e634b, BundleFileType::Data => 0x8fd0d44d20650b68, BundleFileType::Entity => 0x9831ca893b0d087d, BundleFileType::Flow => 0x92d3ee038eeb610d, BundleFileType::Font => 0x9efe0a916aae7880, BundleFileType::Ies => 0x8f7d5a2c0f967655, BundleFileType::Ini => 0xd526a27da14f1dc5, BundleFileType::Input => 0x2bbcabe5074ade9e, BundleFileType::Ivf => 0xfa4a8e091a91201e, BundleFileType::Keys => 0xa62f9297dc969e85, BundleFileType::Level => 0x2a690fd348fe9ac5, BundleFileType::Lua => 0xa14e8dfa2cd117e2, BundleFileType::Material => 0xeac0b497876adedf, BundleFileType::Mod => 0x3fcdd69156a46417, BundleFileType::MouseCursor => 0xb277b11fe4a61d37, BundleFileType::NavData => 0x169de9566953d264, BundleFileType::NetworkConfig => 0x3b1fa9e8f6bac374, BundleFileType::OddleNet => 0xb0f2c12eb107f4d8, BundleFileType::Package => 0xad9c6d9ed1e5e77a, BundleFileType::Particles => 0xa8193123526fad64, BundleFileType::PhysicsProperties => 0xbf21403a3ab0bbb1, BundleFileType::RenderConfig => 0x27862fe24795319c, BundleFileType::RtPipeline => 0x9ca183c2d0e76dee, BundleFileType::Scene => 0x9d0a795bfe818d19, BundleFileType::Shader => 0xcce8d5b5f5ae333f, BundleFileType::ShaderLibrary => 0xe5ee32a477239a93, BundleFileType::ShaderLibraryGroup => 0x9e5c3cc74575aeb5, BundleFileType::ShadingEnvionmentMapping => 0x250e0a11ac8e26f8, BundleFileType::ShadingEnvironment => 0xfe73c7dcff8a7ca5, BundleFileType::Slug => 0xa27b4d04a9ba6f9e, BundleFileType::SlugAlbum => 0xe9fc9ea7042e5ec0, BundleFileType::SoundEnvironment => 0xd8b27864a97ffdd7, BundleFileType::SpuJob => 0xf97af9983c05b950, BundleFileType::StateMachine => 0xa486d4045106165c, BundleFileType::StaticPVS => 0xe3f0baa17d620321, BundleFileType::Strings => 0x0d972bab10b40fd3, BundleFileType::SurfaceProperties => 0xad2d3fa30d9ab394, BundleFileType::Texture => 0xcd4238c6a0c69e32, BundleFileType::TimpaniBank => 0x99736be1fff739a4, BundleFileType::TimpaniMaster => 0x00a3e6c59a2b9c6c, BundleFileType::Tome => 0x19c792357c99f49b, BundleFileType::Ugg => 0x712d6e3dd1024c9c, BundleFileType::Unit => 0xe0a48d0be9a7453f, BundleFileType::Upb => 0xa99510c6e86dd3c2, BundleFileType::VectorField => 0xf7505933166d6755, BundleFileType::Wav => 0x786f65c00a816b19, BundleFileType::WwiseBank => 0x535a7bd3e650d799, BundleFileType::WwiseDep => 0xaf32095c82f2b070, BundleFileType::WwiseEvent => 0xaabdd317b58dfc8a, BundleFileType::WwiseMetadata => 0xd50a8b7e1c82b110, BundleFileType::WwiseStream => 0x504b55235d21440e, BundleFileType::Xml => 0x76015845a6003765, BundleFileType::Unknown(hash) => hash.into(), } } } impl From for Murmur64 { fn from(t: BundleFileType) -> Murmur64 { let hash: u64 = t.into(); Murmur64::from(hash) } } impl std::fmt::Display for BundleFileType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.ext_name()) } } #[derive(Debug)] struct BundleFileHeader { variant: u32, unknown_1: u8, size: usize, len_data_file_name: usize, } pub struct BundleFileVariant { property: u32, data: Vec, data_file_name: Option, // Seems to be related to whether there is a data path. unknown_1: u8, } impl BundleFileVariant { // We will need a parameter for `property` eventually, so the `Default` impl would need to go // eventually anyways. #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { // TODO: Hard coded for, as long as we don't support bundle properties property: 0, data: Vec::new(), data_file_name: None, unknown_1: 0, } } pub fn set_data(&mut self, data: Vec) { self.data = data; } pub fn size(&self) -> usize { self.data.len() } pub fn property(&self) -> u32 { self.property } pub fn data(&self) -> &[u8] { &self.data } pub fn data_file_name(&self) -> Option<&String> { self.data_file_name.as_ref() } #[tracing::instrument(skip_all)] fn read_header(r: &mut R) -> Result where R: Read + Seek, { let variant = r.read_u32()?; let unknown_1 = r.read_u8()?; let size = r.read_u32()? as usize; r.skip_u8(1)?; let len_data_file_name = r.read_u32()? as usize; Ok(BundleFileHeader { size, unknown_1, variant, len_data_file_name, }) } #[tracing::instrument(skip_all)] fn write_header(&self, w: &mut W, props: Properties) -> Result<()> where W: Write + Seek, { w.write_u32(self.property)?; w.write_u8(self.unknown_1)?; let len_data_file_name = self.data_file_name.as_ref().map(|s| s.len()).unwrap_or(0); if props.contains(Properties::DATA) { w.write_u32(len_data_file_name as u32)?; w.write_u8(1)?; w.write_u32(0)?; } else { w.write_u32(self.data.len() as u32)?; w.write_u8(1)?; w.write_u32(len_data_file_name as u32)?; } Ok(()) } } bitflags! { #[derive(Default)] pub struct Properties: u32 { const DATA = 0b100; } } pub struct BundleFile { file_type: BundleFileType, name: IdString64, variants: Vec, props: Properties, } impl BundleFile { pub fn new(name: String, file_type: BundleFileType) -> Self { Self { file_type, name: name.into(), variants: Vec::new(), props: Properties::empty(), } } pub fn add_variant(&mut self, variant: BundleFileVariant) { self.variants.push(variant) } #[tracing::instrument(name = "File::read", skip(ctx, r))] pub fn from_reader(ctx: &crate::Context, r: &mut R, props: Properties) -> Result where R: Read + Seek, { let file_type = BundleFileType::from(r.read_u64()?); let hash = Murmur64::from(r.read_u64()?); let name = ctx.lookup_hash(hash, HashGroup::Filename); let header_count = r.read_u32()? as usize; tracing::trace!(header_count); let mut headers = Vec::with_capacity(header_count); r.skip_u32(0)?; for i in 0..header_count { let span = tracing::debug_span!("Read file header", i); let _enter = span.enter(); let header = BundleFileVariant::read_header(r) .wrap_err_with(|| format!("failed to read header {i}"))?; // TODO: Figure out how `header.unknown_1` correlates to `properties::DATA` // if props.contains(Properties::DATA) { // tracing::debug!("props: {props:?} | unknown_1: {}", header.unknown_1) // } headers.push(header); } let mut variants = Vec::with_capacity(header_count); for (i, header) in headers.into_iter().enumerate() { let span = tracing::debug_span!( "Read file data {}", i, size = header.size, len_data_file_name = header.len_data_file_name ); let _enter = span.enter(); let (data, data_file_name) = if props.contains(Properties::DATA) { let data = vec![]; let s = r .read_string_len(header.size) .wrap_err("failed to read data file name")?; (data, Some(s)) } else { let mut data = vec![0; header.size]; r.read_exact(&mut data) .wrap_err_with(|| format!("failed to read file {i}"))?; let data_file_name = if header.len_data_file_name > 0 { let s = r .read_string_len(header.len_data_file_name) .wrap_err("failed to read data file name")?; Some(s) } else { None }; (data, data_file_name) }; let variant = BundleFileVariant { property: header.variant, data, data_file_name, unknown_1: header.unknown_1, }; variants.push(variant); } Ok(Self { variants, file_type, name, props, }) } #[tracing::instrument(name = "File::to_binary", skip_all)] pub fn to_binary(&self) -> Result> { let mut w = Cursor::new(Vec::new()); w.write_u64(self.file_type.hash().into())?; w.write_u64(self.name.to_murmur64().into())?; w.write_u32(self.variants.len() as u32)?; // TODO: Figure out what this is w.write_u32(0x0)?; for variant in self.variants.iter() { w.write_u32(variant.property())?; w.write_u8(variant.unknown_1)?; let len_data_file_name = variant.data_file_name().map(|s| s.len()).unwrap_or(0); if self.props.contains(Properties::DATA) { w.write_u32(len_data_file_name as u32)?; w.write_u8(1)?; w.write_u32(0)?; } else { w.write_u32(variant.size() as u32)?; w.write_u8(1)?; w.write_u32(len_data_file_name as u32)?; } } for variant in self.variants.iter() { w.write_all(&variant.data)?; if let Some(s) = &variant.data_file_name { w.write_all(s.as_bytes())?; } } Ok(w.into_inner()) } #[tracing::instrument(name = "File::from_sjson", skip(sjson))] pub async fn from_sjson( name: String, file_type: BundleFileType, sjson: S, root: P, ) -> Result where P: AsRef + std::fmt::Debug, S: AsRef, { match file_type { BundleFileType::Lua => { let sjson = CString::new(sjson.as_ref()).wrap_err("failed to build CString from SJSON")?; lua::compile(name, sjson) } BundleFileType::Unknown(_) => { eyre::bail!("Unknown file type. Cannot compile from SJSON"); } _ => { eyre::bail!( "Compiling file type {} is not yet supported", file_type.ext_name() ) } } } pub fn props(&self) -> Properties { self.props } pub fn base_name(&self) -> &IdString64 { &self.name } pub fn name(&self, decompiled: bool, variant: Option) -> String { let mut s = self.name.display().to_string(); s.push('.'); if let Some(variant) = variant { s.push_str(&variant.to_string()); s.push('.'); } if decompiled { s.push_str(&self.file_type.decompiled_ext_name()); } else { s.push_str(&self.file_type.ext_name()); } s } pub fn matches_name(&self, name: S) -> bool where S: Into, { let name = name.into(); if self.name == name { return true; } if let IdString64::String(name) = name { self.name(false, None) == name || self.name(true, None) == name } else { false } } pub fn file_type(&self) -> BundleFileType { self.file_type } pub fn variants(&self) -> &Vec { &self.variants } pub fn variants_mut(&mut self) -> impl Iterator { self.variants.iter_mut() } pub fn raw(&self) -> Result> { let files = self .variants .iter() .map(|variant| { let name = if self.variants.len() > 1 { self.name(false, Some(variant.property())) } else { self.name(false, None) }; UserFile { data: variant.data().to_vec(), name: Some(name), } }) .collect(); Ok(files) } #[tracing::instrument(name = "File::decompiled", skip_all)] pub async fn decompiled(&self, ctx: &crate::Context) -> Result> { let file_type = self.file_type(); if tracing::enabled!(tracing::Level::DEBUG) { tracing::debug!( name = self.name(true, None), variants = self.variants.len(), "Attempting to decompile" ); } if file_type == BundleFileType::Strings { return strings::decompile(ctx, &self.variants); } let tasks = self.variants.iter().map(|variant| async move { let data = variant.data(); let name = if self.variants.len() > 1 { self.name(true, Some(variant.property())) } else { self.name(true, None) }; let res = match file_type { BundleFileType::Lua => lua::decompile(ctx, data).await, BundleFileType::Package => package::decompile(ctx, name.clone(), data), _ => { tracing::debug!("Can't decompile, unknown file type"); Ok(vec![UserFile::with_name(data.to_vec(), name.clone())]) } }; let res = res.wrap_err_with(|| format!("failed to decompile file {name}")); match res { Ok(files) => files, Err(err) => { tracing::error!("{:?}", err); vec![] } } }); let results = join_all(tasks).await; Ok(results.into_iter().flatten().collect()) } } impl PartialEq for BundleFile { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.file_type == other.file_type } } pub struct UserFile { // TODO: Might be able to avoid some allocations with a Cow here data: Vec, name: Option, } impl UserFile { pub fn new(data: Vec) -> Self { Self { data, name: None } } pub fn with_name(data: Vec, name: String) -> Self { Self { data, name: Some(name), } } pub fn data(&self) -> &[u8] { &self.data } pub fn name(&self) -> Option<&String> { self.name.as_ref() } }