Add web-based .packed explorer, updated parser and ghidra untility script

This commit is contained in:
Daniel S. 2023-05-07 21:29:21 +02:00
parent 8e0df74541
commit 58407ecc9f
35 changed files with 3897 additions and 353 deletions

14
scrapper_web/scrapper/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

View file

@ -0,0 +1,31 @@
[package]
name = "scrapper"
version = "0.1.0"
authors = []
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[profile.release]
lto = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aes = "0.8.2"
anyhow = "1.0.69"
binrw = "0.11.1"
cbc = "0.1.2"
console_error_panic_hook = "0.1.7"
derivative = "2.2.0"
js-sys = "0.3.61"
pelite = "0.10.0"
serde = { version = "1.0.152", features = ["derive"] }
serde-wasm-bindgen = "0.4.5"
wasm-bindgen = "0.2.83"
wasm-bindgen-file-reader = "1.0.0"
web-sys = { version = "0.3.61", features = ["File", "BlobPropertyBag", "Blob", "Url"] }
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-O4"]

View file

@ -0,0 +1,23 @@
# scrapper
## Usage
[rsw-rs doc](https://github.com/lencx/rsw-rs)
```bash
# install rsw
cargo install rsw
# --- help ---
# rsw help
rsw -h
# new help
rsw new -h
# --- usage ---
# dev
rsw watch
# production
rsw build
```

View file

@ -0,0 +1,155 @@
use binrw::{binread, BinReaderExt};
use serde::Serialize;
use std::collections::BTreeMap;
use std::io::{Read, Seek, SeekFrom};
use wasm_bindgen::prelude::*;
use wasm_bindgen_file_reader::WebSysFile;
use web_sys::{Blob, File};
type JsResult<T> = Result<T,JsValue>;
#[binread]
#[derive(Serialize, Debug)]
struct ScrapFile {
#[br(temp)]
name_len: u32,
#[br(count = name_len)]
#[br(map = |s: Vec<u8>| String::from_utf8_lossy(&s).to_string())]
path: String,
size: u32,
offset: u32,
}
#[binread]
#[br(magic = b"BFPK", little)]
#[derive(Serialize, Debug)]
struct PackedHeader {
version: u32,
#[br(temp)]
num_files: u32,
#[br(count= num_files)]
files: Vec<ScrapFile>,
}
#[derive(Serialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
enum DirectoryTree {
File {
size: u32,
offset: u32,
file_index: u8,
},
Directory {
entries: BTreeMap<String, DirectoryTree>,
},
}
#[wasm_bindgen(inspectable)]
pub struct MultiPack {
files: Vec<(String,WebSysFile)>,
tree: DirectoryTree,
}
fn blob_url(buffer: &[u8]) -> JsResult<String> {
let uint8arr =
js_sys::Uint8Array::new(&unsafe { js_sys::Uint8Array::view(buffer) }.into());
let array = js_sys::Array::new();
array.push(&uint8arr.buffer());
let blob = Blob::new_with_u8_array_sequence_and_options(
&array,
web_sys::BlobPropertyBag::new().type_("application/octet-stream"),
)
.unwrap();
web_sys::Url::create_object_url_with_blob(&blob)
}
#[wasm_bindgen]
impl MultiPack {
#[wasm_bindgen(constructor)]
pub fn parse(files: Vec<File>) -> Self {
let mut tree = DirectoryTree::default();
let mut web_files = vec![];
for (file_index, file) in files.into_iter().enumerate() {
let file_name = file.name();
let mut fh = WebSysFile::new(file);
let header = fh.read_le::<PackedHeader>().unwrap();
tree.merge(&header.files, file_index.try_into().unwrap());
web_files.push((file_name,fh));
}
Self {
tree,
files: web_files,
}
}
#[wasm_bindgen]
pub fn tree(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.tree).unwrap()
}
#[wasm_bindgen]
pub fn download(
&mut self,
file_index: u8,
offset: u32,
size: u32,
) -> Result<JsValue, JsValue> {
let Some((_,file)) = self.files.get_mut(file_index as usize) else {
return Err("File not found".into());
};
let mut buffer = vec![0u8; size as usize];
file.seek(SeekFrom::Start(offset as u64))
.map_err(|e| format!("Failed to seek file: {e}"))?;
file.read(&mut buffer)
.map_err(|e| format!("Failed to read from file: {e}"))?;
Ok(blob_url(&buffer)?.into())
}
}
impl Default for DirectoryTree {
fn default() -> Self {
Self::Directory {
entries: Default::default(),
}
}
}
impl DirectoryTree {
fn add_child(&mut self, name: &str, node: Self) -> &mut Self {
match self {
Self::File { .. } => panic!("Can't add child to file!"),
Self::Directory {
entries
} => entries.entry(name.to_owned()).or_insert(node),
}
}
fn merge(&mut self, files: &[ScrapFile], file_index: u8) {
for file in files {
let mut folder = &mut *self;
let path: Vec<_> = file.path.split('/').collect();
if let Some((filename, path)) = path.as_slice().split_last() {
for part in path {
let DirectoryTree::Directory { entries } = folder else {
unreachable!();
};
folder = entries.entry(part.to_string()).or_default();
}
folder.add_child(
filename,
DirectoryTree::File {
size: file.size,
offset: file.offset,
file_index,
},
);
}
}
}
}
#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
console_error_panic_hook::set_once();
Ok(())
}