feat: Implement decompilation for package files
This commit is contained in:
parent
987a6ade9b
commit
905734019e
11 changed files with 179 additions and 35 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -67,6 +67,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecount"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -403,6 +409,12 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
|
@ -430,6 +442,27 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom_locate"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605"
|
||||||
|
dependencies = [
|
||||||
|
"bytecount",
|
||||||
|
"memchr",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
@ -633,8 +666,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_sjson"
|
name = "serde_sjson"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
"nom_locate",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ libloading = "0.7.4"
|
||||||
nanorand = "0.7.0"
|
nanorand = "0.7.0"
|
||||||
pin-project-lite = "0.2.9"
|
pin-project-lite = "0.2.9"
|
||||||
serde = { version = "1.0.147", features = ["derive"] }
|
serde = { version = "1.0.147", features = ["derive"] }
|
||||||
serde_sjson = { path = "../../lib/serde_sjson", version = "0.1.0" }
|
serde_sjson = { path = "../../lib/serde_sjson", version = "*" }
|
||||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
||||||
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
||||||
tracing = { version = "0.1.37", features = ["async-await"] }
|
tracing = { version = "0.1.37", features = ["async-await"] }
|
||||||
|
|
|
@ -2,10 +2,8 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use color_eyre::{
|
use color_eyre::eyre::{self, Context, Result};
|
||||||
eyre::{self, Context, Result},
|
use color_eyre::{Help, Report, SectionExt};
|
||||||
Help, Report, SectionExt,
|
|
||||||
};
|
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
use sdk::Bundle;
|
use sdk::Bundle;
|
||||||
|
@ -165,21 +163,51 @@ pub(crate) async fn run(ctx: Arc<RwLock<sdk::Context>>, matches: &ArgMatches) ->
|
||||||
}))
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let files: Vec<_> = bundles
|
let files: Vec<_> = {
|
||||||
.iter()
|
let iter = bundles.iter().flat_map(|bundle| bundle.files());
|
||||||
.flat_map(|bundle| bundle.files())
|
|
||||||
.filter(|file| {
|
// Short-curcit the iteration if there is nothing to filter by
|
||||||
let name = file.base_name();
|
if includes.is_empty() && excludes.is_empty() {
|
||||||
|
iter.collect()
|
||||||
|
} else {
|
||||||
|
iter.filter(|file| {
|
||||||
|
let name = file.name(false);
|
||||||
|
let decompiled_name = file.name(true);
|
||||||
|
|
||||||
// When there is no `includes`, all files are included
|
// When there is no `includes`, all files are included
|
||||||
let is_included = includes.is_empty() || includes.iter().any(|glob| glob.matches(name));
|
let is_included = includes.is_empty()
|
||||||
|
|| includes
|
||||||
|
.iter()
|
||||||
|
.any(|glob| glob.matches(&name) || glob.matches(&decompiled_name));
|
||||||
// When there is no `excludes`, no file is excluded
|
// When there is no `excludes`, no file is excluded
|
||||||
let is_excluded =
|
let is_excluded = !excludes.is_empty()
|
||||||
!excludes.is_empty() && excludes.iter().any(|glob| glob.matches(name));
|
&& excludes
|
||||||
|
.iter()
|
||||||
|
.any(|glob| glob.matches(&name) || glob.matches(&decompiled_name));
|
||||||
|
|
||||||
is_included && !is_excluded
|
is_included && !is_excluded
|
||||||
})
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if tracing::enabled!(tracing::Level::DEBUG) {
|
||||||
|
let includes: Vec<_> = includes.iter().map(|pattern| pattern.as_str()).collect();
|
||||||
|
let excludes: Vec<_> = excludes.iter().map(|pattern| pattern.as_str()).collect();
|
||||||
|
let bundle_files: Vec<_> = bundles
|
||||||
|
.iter()
|
||||||
|
.flat_map(|bundle| bundle.files())
|
||||||
|
.map(|file| file.name(false))
|
||||||
.collect();
|
.collect();
|
||||||
|
let filtered: Vec<_> = files.iter().map(|file| file.name(false)).collect();
|
||||||
|
tracing::debug!(
|
||||||
|
?includes,
|
||||||
|
?excludes,
|
||||||
|
files = ?bundle_files,
|
||||||
|
?filtered,
|
||||||
|
"Built file list to extract"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let should_decompile = matches.get_flag("decompile");
|
let should_decompile = matches.get_flag("decompile");
|
||||||
let should_flatten = matches.get_flag("flatten");
|
let should_flatten = matches.get_flag("flatten");
|
||||||
|
|
|
@ -13,7 +13,7 @@ libloading = "0.7.4"
|
||||||
nanorand = "0.7.0"
|
nanorand = "0.7.0"
|
||||||
pin-project-lite = "0.2.9"
|
pin-project-lite = "0.2.9"
|
||||||
serde = { version = "1.0.147", features = ["derive"] }
|
serde = { version = "1.0.147", features = ["derive"] }
|
||||||
serde_sjson = { path = "../../lib/serde_sjson", version = "0.1.0" }
|
serde_sjson = { path = "../../lib/serde_sjson", version = "*" }
|
||||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
||||||
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
||||||
tracing = { version = "0.1.37", features = ["async-await"] }
|
tracing = { version = "0.1.37", features = ["async-await"] }
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use std::io::Cursor;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use color_eyre::{Help, Result, SectionExt};
|
use color_eyre::{Help, Result, SectionExt};
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncWrite, AsyncWriteExt};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -10,7 +12,7 @@ use crate::context::lookup_hash;
|
||||||
use crate::filetype::*;
|
use crate::filetype::*;
|
||||||
use crate::murmur::{HashGroup, Murmur64};
|
use crate::murmur::{HashGroup, Murmur64};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum BundleFileType {
|
pub enum BundleFileType {
|
||||||
Animation,
|
Animation,
|
||||||
AnimationCurves,
|
AnimationCurves,
|
||||||
|
@ -162,6 +164,16 @@ impl BundleFileType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for BundleFileType {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let value = self.ext_name();
|
||||||
|
value.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u64> for BundleFileType {
|
impl From<u64> for BundleFileType {
|
||||||
fn from(value: u64) -> Self {
|
fn from(value: u64) -> Self {
|
||||||
Self::from(Murmur64::from(value))
|
Self::from(Murmur64::from(value))
|
||||||
|
@ -357,7 +369,7 @@ impl BundleFileVariant {
|
||||||
self.header.size
|
self.header.size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data(&self) -> &Vec<u8> {
|
pub fn data(&self) -> &[u8] {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,7 +504,7 @@ impl BundleFile {
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
.map(|variant| UserFile {
|
.map(|variant| UserFile {
|
||||||
data: variant.data().clone(),
|
data: variant.data().to_vec(),
|
||||||
name: Some(self.name(false)),
|
name: Some(self.name(false)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -500,7 +512,7 @@ impl BundleFile {
|
||||||
Ok(files)
|
Ok(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(name = "File::decompiled", skip_all)]
|
||||||
pub async fn decompiled(&self, ctx: Arc<RwLock<crate::Context>>) -> Result<Vec<UserFile>> {
|
pub async fn decompiled(&self, ctx: Arc<RwLock<crate::Context>>) -> Result<Vec<UserFile>> {
|
||||||
let file_type = self.file_type();
|
let file_type = self.file_type();
|
||||||
|
|
||||||
|
@ -516,14 +528,17 @@ impl BundleFile {
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
let data = variant.data();
|
||||||
|
|
||||||
let res = match file_type {
|
let res = match file_type {
|
||||||
BundleFileType::Lua => lua::decompile(ctx, variant.data()).await,
|
BundleFileType::Lua => lua::decompile(ctx, data).await,
|
||||||
|
BundleFileType::Package => {
|
||||||
|
let mut c = Cursor::new(data);
|
||||||
|
package::decompile(ctx, &mut c).await
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
tracing::debug!("Can't decompile, unknown file type");
|
tracing::debug!("Can't decompile, unknown file type");
|
||||||
Ok(vec![UserFile::with_name(
|
Ok(vec![UserFile::with_name(data.to_vec(), self.name(true))])
|
||||||
variant.data.clone(),
|
|
||||||
self.name(true),
|
|
||||||
)])
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -533,7 +548,7 @@ impl BundleFile {
|
||||||
let err = err
|
let err = err
|
||||||
.wrap_err("failed to decompile file")
|
.wrap_err("failed to decompile file")
|
||||||
.with_section(|| self.name(true).header("File:"));
|
.with_section(|| self.name(true).header("File:"));
|
||||||
tracing::error!("{}", err);
|
tracing::error!("{:?}", err);
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use crate::oodle::CHUNK_SIZE;
|
||||||
|
|
||||||
pub(crate) mod file;
|
pub(crate) mod file;
|
||||||
|
|
||||||
use file::BundleFile;
|
pub use file::BundleFile;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
enum BundleFormat {
|
enum BundleFormat {
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod lua;
|
pub mod lua;
|
||||||
|
pub mod package;
|
||||||
|
|
65
lib/sdk/src/filetype/package.rs
Normal file
65
lib/sdk/src/filetype/package.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use color_eyre::eyre::Context;
|
||||||
|
use color_eyre::Result;
|
||||||
|
use serde::Serialize;
|
||||||
|
use tokio::io::{AsyncRead, AsyncSeek};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::binary::*;
|
||||||
|
use crate::bundle::file::{BundleFileType, UserFile};
|
||||||
|
use crate::context::lookup_hash;
|
||||||
|
use crate::murmur::{HashGroup, Murmur64};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Package(HashMap<BundleFileType, Vec<String>>);
|
||||||
|
|
||||||
|
impl Deref for Package {
|
||||||
|
type Target = HashMap<BundleFileType, Vec<String>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Package {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Package {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(HashMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn decompile<R>(ctx: Arc<RwLock<crate::Context>>, data: &mut R) -> Result<Vec<UserFile>>
|
||||||
|
where
|
||||||
|
R: AsyncRead + AsyncSeek + std::marker::Unpin,
|
||||||
|
{
|
||||||
|
// TODO: Figure out what this is
|
||||||
|
let unknown = read_u32(data).await?;
|
||||||
|
if unknown != 0x2b {
|
||||||
|
tracing::warn!("Unknown u32 header. Expected 0x2b, got: {unknown:#08X} ({unknown})");
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_count = read_u32(data).await? as usize;
|
||||||
|
let mut package = Package::new();
|
||||||
|
|
||||||
|
for i in 0..file_count {
|
||||||
|
let t = BundleFileType::from(read_u64(data).await?);
|
||||||
|
let hash = Murmur64::from(read_u64(data).await?);
|
||||||
|
let name = lookup_hash(ctx.clone(), hash, HashGroup::Filename).await;
|
||||||
|
|
||||||
|
tracing::trace!(index = i, r"type" = ?t, %hash, name, "Package entry");
|
||||||
|
|
||||||
|
package.entry(t).or_insert_with(Vec::new).push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = serde_sjson::to_string(&package.0).wrap_err("failed to serialize Package to SJSON")?;
|
||||||
|
Ok(vec![UserFile::new(s.into_bytes())])
|
||||||
|
}
|
|
@ -8,6 +8,6 @@ pub mod murmur;
|
||||||
mod oodle;
|
mod oodle;
|
||||||
|
|
||||||
pub use bundle::decompress;
|
pub use bundle::decompress;
|
||||||
pub use bundle::Bundle;
|
pub use bundle::{Bundle, BundleFile};
|
||||||
pub use context::Context;
|
pub use context::Context;
|
||||||
pub use oodle::Oodle;
|
pub use oodle::Oodle;
|
||||||
|
|
|
@ -27,10 +27,10 @@ fn _swap_bytes_u64(value: u64) -> u64 {
|
||||||
u64::from_le_bytes(value.to_be_bytes())
|
u64::from_le_bytes(value.to_be_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||||
pub struct Murmur64(u64);
|
pub struct Murmur64(u64);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||||
pub struct Murmur32(u32);
|
pub struct Murmur32(u32);
|
||||||
|
|
||||||
impl Deref for Murmur64 {
|
impl Deref for Murmur64 {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit b53949d5df34f3407e09cdc21da426e64b976832
|
Subproject commit e037ef76591387f27d3a7aa31536b3d97c4c9efe
|
Loading…
Add table
Reference in a new issue