diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc new file mode 100644 index 0000000..b091620 --- /dev/null +++ b/CHANGELOG.adoc @@ -0,0 +1,7 @@ += Changelog + +== Unreleased + +=== Added + +- implement decompilation for `.strings` files diff --git a/crates/dtmt/src/cmd/dictionary.rs b/crates/dtmt/src/cmd/dictionary.rs index d9709f8..dc09ad6 100644 --- a/crates/dtmt/src/cmd/dictionary.rs +++ b/crates/dtmt/src/cmd/dictionary.rs @@ -14,6 +14,7 @@ use tokio_stream::StreamExt; pub enum HashGroup { Filename, Filetype, + Strings, Other, } @@ -22,6 +23,7 @@ impl From for sdk::murmur::HashGroup { match value { HashGroup::Filename => sdk::murmur::HashGroup::Filename, HashGroup::Filetype => sdk::murmur::HashGroup::Filetype, + HashGroup::Strings => sdk::murmur::HashGroup::Strings, HashGroup::Other => sdk::murmur::HashGroup::Other, } } diff --git a/lib/sdk/src/bundle/file.rs b/lib/sdk/src/bundle/file.rs index f1d68c3..1f5f675 100644 --- a/lib/sdk/src/bundle/file.rs +++ b/lib/sdk/src/bundle/file.rs @@ -359,6 +359,10 @@ impl BundleFileVariant { self.header.size } + pub fn kind(&self) -> u32 { + self.header.variant + } + pub fn data(&self) -> &[u8] { &self.data } @@ -543,6 +547,11 @@ impl BundleFile { ); } + if file_type == BundleFileType::Strings { + let ctx = ctx.read().await; + return strings::decompile(&ctx, &self.variants); + } + let tasks = self.variants.iter().map(|variant| { let ctx = ctx.clone(); diff --git a/lib/sdk/src/filetype/mod.rs b/lib/sdk/src/filetype/mod.rs index e1a2715..c62c503 100644 --- a/lib/sdk/src/filetype/mod.rs +++ b/lib/sdk/src/filetype/mod.rs @@ -1,2 +1,3 @@ pub mod lua; pub mod package; +pub mod strings; diff --git a/lib/sdk/src/filetype/strings.rs b/lib/sdk/src/filetype/strings.rs new file mode 100644 index 0000000..dc48e0b --- /dev/null +++ b/lib/sdk/src/filetype/strings.rs @@ -0,0 +1,84 @@ +use std::collections::HashMap; +use std::io::{Cursor, Read}; + +use color_eyre::{Report, Result}; + +use crate::binary::sync::ReadExt; +use crate::bundle::file::{BundleFileVariant, UserFile}; +use crate::murmur::HashGroup; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, serde::Serialize)] +#[serde(untagged)] +pub enum Language { + #[serde(rename = "en")] + English, + #[serde(serialize_with = "Language::serialize_unnamed")] + Unnamed(u32), +} + +impl Language { + fn serialize_unnamed(field: &u32, ser: S) -> Result + where + S: serde::Serializer, + { + ser.serialize_str(&format!("lang_{}", field)) + } +} + +#[derive(serde::Serialize)] +pub struct Strings(HashMap>); + +fn read_string(r: R) -> Result +where + R: Read, +{ + r.bytes() + .take_while(|b| b.as_ref().map(|b| *b != 0).unwrap_or(false)) + .map(|b| b.map_err(Report::new)) + .collect::>() + .and_then(|bytes| String::from_utf8(bytes).map_err(Report::new)) +} + +impl Strings { + #[tracing::instrument(skip_all, fields(languages = variants.len()))] + pub fn from_variants(ctx: &crate::Context, variants: &Vec) -> Result { + let mut map: HashMap> = HashMap::new(); + + for (i, variant) in variants.iter().enumerate() { + let _span = tracing::trace_span!("variant {}", i); + let mut r = Cursor::new(variant.data()); + let _header = r.read_u32()?; + let count = r.read_u32()? as usize; + + for _ in 0..count { + let name = ctx.lookup_hash_short(r.read_u32()?, HashGroup::Strings); + let address = r.read_u32()? as u64; + + let pos = r.position(); + + r.set_position(address); + let s = read_string(&mut r)?; + r.set_position(pos); + + map.entry(name) + .or_default() + .insert(Language::Unnamed(variant.kind()), s); + } + } + + Ok(Self(map)) + } + + #[tracing::instrument(skip_all)] + pub fn to_sjson(&self) -> Result { + serde_sjson::to_string(&self.0).map_err(Report::new) + } +} + +#[tracing::instrument(skip_all)] +pub fn decompile(ctx: &crate::Context, variants: &Vec) -> Result> { + let strings = Strings::from_variants(ctx, variants)?; + let content = strings.to_sjson()?; + + Ok(vec![UserFile::new(content.into_bytes())]) +} diff --git a/lib/sdk/src/murmur/dictionary.rs b/lib/sdk/src/murmur/dictionary.rs index a293f6a..abf90b7 100644 --- a/lib/sdk/src/murmur/dictionary.rs +++ b/lib/sdk/src/murmur/dictionary.rs @@ -11,6 +11,7 @@ use super::{murmurhash64, Murmur32, Murmur64, SEED}; pub enum HashGroup { Filename, Filetype, + Strings, Other, } @@ -25,6 +26,7 @@ impl std::fmt::Display for HashGroup { match self { HashGroup::Filename => write!(f, "filename"), HashGroup::Filetype => write!(f, "filetype"), + HashGroup::Strings => write!(f, "strings"), HashGroup::Other => write!(f, "other"), } }