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