Add web-based .packed explorer, updated parser and ghidra untility script
This commit is contained in:
parent
8e0df74541
commit
58407ecc9f
35 changed files with 3897 additions and 353 deletions
14
scrapper_web/scrapper/.gitignore
vendored
Normal file
14
scrapper_web/scrapper/.gitignore
vendored
Normal 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
|
31
scrapper_web/scrapper/Cargo.toml
Normal file
31
scrapper_web/scrapper/Cargo.toml
Normal 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"]
|
23
scrapper_web/scrapper/README.md
Normal file
23
scrapper_web/scrapper/README.md
Normal 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
|
||||
```
|
155
scrapper_web/scrapper/src/lib.rs
Normal file
155
scrapper_web/scrapper/src/lib.rs
Normal 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(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue