├── .gitignore ├── benches ├── benchmarks │ ├── mod.rs │ ├── test.wz │ ├── node_parsing.rs │ └── node_lookup.rs └── bench_main.rs ├── tests ├── test.img ├── test.wz ├── test_need_iv.wz ├── wz_img_test.rs └── wz_file_test.rs ├── src ├── ms │ ├── mod.rs │ ├── utils.rs │ ├── chacha20_reader.rs │ ├── snow2_reader.rs │ ├── ms_image.rs │ ├── header.rs │ └── file.rs ├── util │ ├── mod.rs │ ├── maple_crypto_constants.rs │ ├── walk.rs │ ├── color.rs │ ├── wz_mutable_key.rs │ ├── resolver.rs │ └── node_util.rs ├── lib.rs ├── property │ ├── video.rs │ ├── raw_data.rs │ ├── vector.rs │ ├── lua.rs │ ├── sound.rs │ ├── string.rs │ └── mod.rs ├── node_name.rs ├── header.rs ├── version.rs ├── object.rs ├── directory.rs ├── wz_image.rs ├── file.rs └── node_cast.rs ├── examples ├── single_file.rs ├── parse_ms_file.rs ├── parallel_parse_wz_image.rs ├── parse_single_img_file.rs ├── extracting_lua.rs ├── wz_to_json.rs ├── extracting_sounds.rs ├── extracting_pngs.rs ├── pet_equip_matcher.rs ├── id_to_name.rs └── name_to_id.rs ├── LICENSE.txt ├── .github └── workflows │ └── publish.yml ├── README.md └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.wz 3 | .vscode 4 | !tests/**/*.wz 5 | !benches/**/*.wz -------------------------------------------------------------------------------- /benches/benchmarks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod node_lookup; 2 | pub mod node_parsing; 3 | -------------------------------------------------------------------------------- /tests/test.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spd789562/wz-reader-rs/HEAD/tests/test.img -------------------------------------------------------------------------------- /tests/test.wz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spd789562/wz-reader-rs/HEAD/tests/test.wz -------------------------------------------------------------------------------- /tests/test_need_iv.wz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spd789562/wz-reader-rs/HEAD/tests/test_need_iv.wz -------------------------------------------------------------------------------- /benches/benchmarks/test.wz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spd789562/wz-reader-rs/HEAD/benches/benchmarks/test.wz -------------------------------------------------------------------------------- /src/ms/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod file; 2 | pub mod header; 3 | pub mod ms_image; 4 | pub mod snow2_decryptor; 5 | pub mod utils; 6 | pub mod chacha20_reader; 7 | pub mod snow2_reader; -------------------------------------------------------------------------------- /benches/bench_main.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | mod benchmarks; 4 | 5 | criterion_main! { 6 | benchmarks::node_lookup::benches, 7 | benchmarks::node_parsing::benches 8 | } 9 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod color; 2 | pub mod maple_crypto_constants; 3 | pub mod node_util; 4 | pub mod parse_property; 5 | pub(crate) mod resolver; 6 | pub mod walk; 7 | pub mod wz_mutable_key; 8 | 9 | pub use parse_property::*; 10 | pub use resolver::*; 11 | pub use walk::*; 12 | pub use wz_mutable_key::*; 13 | -------------------------------------------------------------------------------- /src/ms/utils.rs: -------------------------------------------------------------------------------- 1 | 2 | #[inline] 3 | pub fn sum_str(string: &str) -> usize { 4 | string.as_bytes().iter().map(|&b| b as usize).sum::() 5 | } 6 | 7 | #[inline] 8 | pub fn get_ascii_file_name

(file_name: P)-> String 9 | where 10 | P: AsRef, 11 | { 12 | file_name.as_ref().file_name().unwrap().to_str().unwrap().to_ascii_lowercase() 13 | } 14 | -------------------------------------------------------------------------------- /benches/benchmarks/node_parsing.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, Criterion}; 2 | use wz_reader::{version::WzMapleVersion, WzNode}; 3 | 4 | fn bench(c: &mut Criterion) { 5 | c.bench_function("node parsing", |b| { 6 | b.iter(|| { 7 | let node = WzNode::from_wz_file_full( 8 | "./benches/benchmarks/test.wz", 9 | Some(WzMapleVersion::BMS), 10 | Some(123), 11 | None, 12 | None, 13 | ) 14 | .unwrap() 15 | .into_lock(); 16 | assert!(node.write().unwrap().parse(&node).is_ok()); 17 | }) 18 | }); 19 | } 20 | 21 | criterion_group!(benches, bench); 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod directory; 2 | pub mod file; 3 | mod header; 4 | pub mod ms; 5 | pub mod node; 6 | mod node_cast; 7 | mod node_name; 8 | mod object; 9 | pub mod property; 10 | pub mod reader; 11 | pub mod util; 12 | pub mod version; 13 | pub mod wz_image; 14 | 15 | pub use directory::WzDirectory; 16 | pub use file::WzFile; 17 | pub use header::*; 18 | pub use ms::file::MsFile; 19 | pub use ms::ms_image::MsImage; 20 | pub use node::{WzNode, WzNodeArc, WzNodeArcVec}; 21 | pub use node_cast::*; 22 | pub use node_name::*; 23 | pub use object::*; 24 | pub use reader::{Reader, SharedWzMutableKey, WzReader, WzSliceReader}; 25 | pub use wz_image::{ 26 | WzImage, WZ_IMAGE_HEADER_BYTE_WITHOUT_OFFSET, WZ_IMAGE_HEADER_BYTE_WITH_OFFSET, 27 | }; 28 | -------------------------------------------------------------------------------- /examples/single_file.rs: -------------------------------------------------------------------------------- 1 | use wz_reader::util::walk_node; 2 | use wz_reader::{WzNode, WzNodeArc}; 3 | 4 | // usage: 5 | // cargo run --example single_file -- "path/to/some.wz" 6 | // cargo run --example single_file -- "D:\Path\To\some.wz" 7 | fn main() { 8 | let path = std::env::args_os() 9 | .nth(1) 10 | .expect("Need .wz file as argument"); 11 | /* resolve single wz file */ 12 | /* 13 | when you know exactly know what version is, consider using WzNode::from_wz_file_full 14 | */ 15 | let node: WzNodeArc = WzNode::from_wz_file(path, None).unwrap().into(); 16 | 17 | walk_node(&node, true, &|node: &WzNodeArc| { 18 | println!("{}", node.read().unwrap().get_full_path()); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/property/video.rs: -------------------------------------------------------------------------------- 1 | use crate::WzReader; 2 | use std::ops::Range; 3 | use std::sync::Arc; 4 | 5 | #[derive(Debug, Clone, Default)] 6 | pub struct WzVideo { 7 | pub reader: Arc, 8 | offset: usize, 9 | length: usize, 10 | } 11 | 12 | impl WzVideo { 13 | pub fn new(reader: &Arc, offset: usize, length: usize) -> Self { 14 | Self { 15 | reader: Arc::clone(reader), 16 | offset, 17 | length, 18 | } 19 | } 20 | #[inline] 21 | fn get_buffer_range(&self) -> Range { 22 | self.offset..self.offset + self.length 23 | } 24 | #[inline] 25 | pub fn get_buffer(&self) -> &[u8] { 26 | let range = self.get_buffer_range(); 27 | self.reader.get_slice(range) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/property/raw_data.rs: -------------------------------------------------------------------------------- 1 | use crate::WzReader; 2 | use std::ops::Range; 3 | use std::sync::Arc; 4 | 5 | #[derive(Debug, Clone, Default)] 6 | pub struct WzRawData { 7 | pub reader: Arc, 8 | offset: usize, 9 | length: usize, 10 | } 11 | 12 | impl WzRawData { 13 | pub fn new(reader: &Arc, offset: usize, length: usize) -> Self { 14 | Self { 15 | reader: Arc::clone(reader), 16 | offset, 17 | length, 18 | } 19 | } 20 | #[inline] 21 | fn get_buffer_range(&self) -> Range { 22 | self.offset..self.offset + self.length 23 | } 24 | #[inline] 25 | pub fn get_buffer(&self) -> &[u8] { 26 | let range = self.get_buffer_range(); 27 | self.reader.get_slice(range) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/parse_ms_file.rs: -------------------------------------------------------------------------------- 1 | use wz_reader::util::walk_node; 2 | use wz_reader::{WzNode, WzNodeArc}; 3 | 4 | type Error = Box; 5 | type Result = std::result::Result; 6 | 7 | // usage: 8 | // cargo run --example parse_ms_file -- "path/to/file.ms" 9 | // cargo run --example parse_ms_file -- "D:\Path\To\file.ms" 10 | fn main() -> Result<()> { 11 | let args = std::env::args_os().collect::>(); 12 | let base_path = args.get(1).expect("missing ms file path"); 13 | 14 | let ms_file_node = WzNode::from_ms_file(base_path, None)?.into_lock(); 15 | 16 | ms_file_node.write().unwrap().parse(&ms_file_node)?; 17 | 18 | walk_node(&ms_file_node, true, &|node: &WzNodeArc| { 19 | if node.read().unwrap().name.contains("text.txt") { 20 | println!("{}", node.read().unwrap().get_full_path()); 21 | } 22 | }); 23 | 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /examples/parallel_parse_wz_image.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use wz_reader::{WzNode, WzNodeArc}; 3 | 4 | // usage: 5 | // cargo run --example parallel_parse_wz_image -- "path/to/some.wz" 6 | // cargo run --example parallel_parse_wz_image -- "D:\Path\To\some.wz" 7 | fn main() { 8 | let path = std::env::args_os() 9 | .nth(1) 10 | .expect("Need path to .wz as first argument"); 11 | let node: WzNodeArc = WzNode::from_wz_file(path, None).unwrap().into(); 12 | 13 | let mut node_write = node.write().unwrap(); 14 | 15 | node_write.parse(&node).unwrap(); 16 | 17 | let handles = node_write 18 | .children 19 | .values() 20 | .map(|node| { 21 | let node = node.clone(); 22 | thread::spawn(move || { 23 | node.write().unwrap().parse(&node).unwrap(); 24 | }) 25 | }) 26 | .collect::>(); 27 | 28 | for t in handles { 29 | t.join().unwrap(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Leo Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/parse_single_img_file.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use wz_reader::property::get_image; 4 | use wz_reader::util::walk_node; 5 | use wz_reader::{WzNode, WzNodeArc, WzNodeCast}; 6 | 7 | // usage: 8 | // cargo run --example parse_single_img_file -- "path/to/some.img" 9 | // cargo run --example parse_single_img_file -- "D:\Path\To\some.img" 10 | fn main() { 11 | let mut args = std::env::args_os().skip(1); 12 | let path = args.next().expect("Need .wz file as 1st argument"); 13 | let out_dir: PathBuf = args.next().expect("Need out dir as 2nd argument").into(); 14 | /* resolve single img file */ 15 | let node: WzNodeArc = WzNode::from_img_file(path, None, None).unwrap().into(); 16 | 17 | walk_node(&node, true, &|node: &WzNodeArc| { 18 | let node_read = node.read().unwrap(); 19 | if node_read.try_as_png().is_some() { 20 | let image = get_image(&node).unwrap().into_rgba8(); 21 | let save_name = [&node_read.get_full_path().replace("/", "-"), ".png"].concat(); 22 | let out_path = out_dir.join(save_name); 23 | image.save(out_path).unwrap(); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | test_linux: 10 | name: Test on Linux 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: dtolnay/rust-toolchain@stable 15 | - name: 'Build and test' 16 | run: cargo test 17 | publish: 18 | name: Publish to crates.io 19 | needs: test_linux 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: dtolnay/rust-toolchain@stable 24 | 25 | - name: cargo-release Cache 26 | id: cargo_release_cache 27 | uses: actions/cache@v3 28 | with: 29 | path: ~/.cargo/bin/cargo-release 30 | key: ${{ runner.os }}-cargo-release 31 | - run: cargo install cargo-release 32 | if: steps.cargo_release_cache.outputs.cache-hit != 'true' 33 | 34 | - name: cargo login 35 | run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }} 36 | 37 | - name: 'Publish' 38 | run: cargo release publish --execute --no-confirm --no-verify --allow-branch HEAD -------------------------------------------------------------------------------- /src/util/maple_crypto_constants.rs: -------------------------------------------------------------------------------- 1 | pub const MAPLESTORY_USERKEY_DEFAULT: [u8; 128] = [ 2 | // 16 * 8 3 | 0x13, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 4 | 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 5 | 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 6 | 0xB4, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 7 | 0x1B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 8 | 0x0F, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 9 | 0x33, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 10 | 0x52, 0x00, 0x00, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xC7, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 11 | ]; 12 | 13 | pub const WZ_GMSIV: [u8; 4] = [0x4D, 0x23, 0xC7, 0x2B]; 14 | pub const WZ_MSEAIV: [u8; 4] = [0xB9, 0x7D, 0x63, 0xE9]; 15 | 16 | #[inline] 17 | pub fn get_trimmed_user_key(user_key: &[u8]) -> [u8; 32] { 18 | let mut trimmed_key = [0; 32]; 19 | 20 | for i in (0..128).step_by(16) { 21 | let index = i / 4; 22 | trimmed_key[index] = user_key[i]; 23 | } 24 | 25 | trimmed_key 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wz-reader-rs 2 | Maplestory *.Wz file reading written in rust, try port similer code from [WzComparerR2.WzLib](https://github.com/Kagamia/WzComparerR2/tree/master/WzComparerR2.WzLib) and [MapleLib](https://github.com/lastbattle/MapleLib) 3 | 4 | It a rust learning project, performance maybe not as good as C#. 5 | 6 | ## Dependencies 7 | - Image 8 | * flate2 9 | * image 10 | - Char Decryption 11 | * aes 12 | * ecb 13 | - Data 14 | * hashbrown - Hashmap 15 | * memmap2 16 | - Others 17 | * rayon 18 | * scroll 19 | * thiserror 20 | 21 | ## Minimum supported Rust version 22 | 23 | wz_reader's MSRV is 1.70.0 24 | 25 | ## Example 26 | ```rust 27 | use wz_reader::util::{resolve_base, walk_node}; 28 | // NodeCast trait provide try_as_* functions to casting WzNode 29 | use wz_reader::NodeCast; 30 | 31 | fn main() { 32 | // resolve wz files 33 | let base_node = resolve_base(r"D:\MapleStory\Data\Base.wz", None).unwrap(); 34 | 35 | // try to parsing every nodes on the way 36 | walk_node(&base_node, true, &|node| { 37 | let node_read = node.read().unwrap(); 38 | 39 | if let Some(sound_node) = node_read.try_as_sound() { 40 | let path = std::path::Path::new("./sounds").join(node_read.name.as_str()); 41 | if sound_node.save(path).is_err() { 42 | println!("failed to extract sound: {}", node_read.get_full_path()); 43 | } 44 | } 45 | }); 46 | } 47 | ``` 48 | 49 | You can find more example usage in the [examples](./examples) folder. 50 | 51 | ## License 52 | 53 | This project is licensed under the [MIT license](./LICENSE.txt). -------------------------------------------------------------------------------- /examples/extracting_lua.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use wz_reader::util::{resolve_base, walk_node}; 4 | use wz_reader::version::WzMapleVersion; 5 | use wz_reader::WzNodeCast; 6 | 7 | // usage: 8 | // cargo run --example extracting_lua -- "path/to/Base.wz" "output" 9 | // cargo run --example extracting_lua -- "D:\Path\To\Base.wz" "./output" 10 | fn main() { 11 | let args = std::env::args().collect::>(); 12 | let base_path = args.get(1).expect("missing base path"); 13 | let out_path = args.get(2).expect("missing target name"); 14 | let base_node = resolve_base(&base_path, Some(WzMapleVersion::BMS)).unwrap(); 15 | 16 | let start = std::time::Instant::now(); 17 | 18 | let script_node = base_node 19 | .read() 20 | .unwrap() 21 | .at_path("Etc/Script") 22 | .expect("script node not found"); 23 | 24 | walk_node(&script_node, true, &|node| { 25 | let node = node.read().unwrap(); 26 | if let Some(lua_node) = node.try_as_lua() { 27 | let result = lua_node.extract_lua(); 28 | if let Ok(lua_text) = result { 29 | let lua_file_name = node.parent.upgrade().unwrap().read().unwrap().name.clone(); 30 | let lua_save_path = format!("{}/{}", out_path, lua_file_name); 31 | let mut file = File::create(lua_save_path).unwrap(); 32 | file.write_all(lua_text.as_bytes()).unwrap(); 33 | } else { 34 | println!("failed to extract lua from {:?}", node.get_full_path()); 35 | } 36 | } 37 | }); 38 | 39 | println!("total time: {:?}", start.elapsed()); 40 | } 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wz_reader" 3 | description = "A wz file reader to resolve wz file with thread safe" 4 | readme = "README.md" 5 | homepage = "https://github.com/spd789562/wz-reader-rs" 6 | repository = "https://github.com/spd789562/wz-reader-rs" 7 | documentation = "https://docs.rs/wz_reader" 8 | categories = ["parsing"] 9 | keywords = ["wz", "maplestory", "wzlib"] 10 | version = "0.0.16" 11 | edition = "2021" 12 | license-file = "LICENSE.txt" 13 | 14 | [dependencies] 15 | aes = "0.8.4" 16 | ecb = "0.1.2" 17 | flate2 = { version = "1.1.4", default-features = false } 18 | hashbrown = "0.14.3" 19 | image = { version = "0.25.0", default-features = false } 20 | memmap2 = "0.9.4" 21 | rayon = { version = "1.9.0", optional = true } 22 | scroll = "0.12.0" 23 | thiserror = "1.0.57" 24 | serde = { version = "1.0", features = ["derive"], optional = true } 25 | serde_json = { version = "1.0", optional = true } 26 | c2-chacha = "0.3.3" 27 | 28 | [dev-dependencies] 29 | serde_json = { version = "1.0" } 30 | tempfile = "3" 31 | criterion = "0.5" 32 | axum = "0.7.5" 33 | tokio = { version = "1.0", features = ["full"] } 34 | 35 | [features] 36 | default = ["rayon", "zlib-ng"] 37 | json = ["serde", "dep:serde_json"] 38 | serde = ["dep:serde", "hashbrown/serde"] 39 | rayon = ["dep:rayon", "image/rayon"] 40 | zlib-ng = ["flate2/zlib-ng"] 41 | 42 | [[bench]] 43 | name = "bench_main" 44 | harness = false 45 | 46 | [[example]] 47 | name = "with_axum" 48 | required-features = ["json", "image/default-formats"] 49 | 50 | [[example]] 51 | name = "wz_to_json" 52 | required-features = ["json"] 53 | 54 | [[example]] 55 | name = "parse_single_img_file" 56 | required-features = ["image/png"] 57 | 58 | [[example]] 59 | name = "extracting_pngs" 60 | required-features = ["image/png"] 61 | -------------------------------------------------------------------------------- /src/node_name.rs: -------------------------------------------------------------------------------- 1 | use hashbrown::Equivalent; 2 | use std::fmt::Display; 3 | use std::ops::Deref; 4 | use std::sync::Arc; 5 | 6 | #[cfg(feature = "serde")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// A wrapper around `Arc` use for WzNode's name and hashmap key. 10 | #[cfg_attr(feature = "serde", derive(Deserialize))] 11 | #[cfg_attr(feature = "serde", serde(from = "String"))] 12 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 13 | pub struct WzNodeName(Arc); 14 | 15 | impl Equivalent for str { 16 | #[inline] 17 | fn equivalent(&self, key: &WzNodeName) -> bool { 18 | self == key.as_str() 19 | } 20 | } 21 | 22 | impl From<&str> for WzNodeName { 23 | #[inline] 24 | fn from(s: &str) -> Self { 25 | WzNodeName(Arc::from(s)) 26 | } 27 | } 28 | 29 | impl From for WzNodeName { 30 | #[inline] 31 | fn from(s: String) -> Self { 32 | WzNodeName(Arc::from(s)) 33 | } 34 | } 35 | 36 | impl Deref for WzNodeName { 37 | type Target = str; 38 | 39 | fn deref(&self) -> &Self::Target { 40 | &self.0 41 | } 42 | } 43 | 44 | impl Display for WzNodeName { 45 | #[inline] 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | write!(f, "{}", self.0) 48 | } 49 | } 50 | 51 | impl Default for WzNodeName { 52 | #[inline] 53 | fn default() -> Self { 54 | "".into() 55 | } 56 | } 57 | 58 | impl WzNodeName { 59 | #[inline] 60 | pub fn new(s: &str) -> Self { 61 | s.into() 62 | } 63 | #[inline] 64 | pub fn as_str(&self) -> &str { 65 | &self.0 66 | } 67 | } 68 | 69 | #[cfg(feature = "serde")] 70 | impl Serialize for WzNodeName { 71 | /// I don't known how to directly into &str, so impl this 72 | fn serialize(&self, serializer: S) -> Result 73 | where 74 | S: serde::Serializer, 75 | { 76 | serializer.serialize_str(self.as_str()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/property/vector.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Add, Div, Mul, Sub}; 3 | 4 | #[cfg(feature = "serde")] 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 8 | #[derive(Debug, Clone, Copy, PartialEq)] 9 | pub struct Vector2D(pub i32, pub i32); 10 | 11 | impl Vector2D { 12 | pub fn new(x: i32, y: i32) -> Vector2D { 13 | Vector2D(x, y) 14 | } 15 | pub fn distance(&self, other: &Vector2D) -> f64 { 16 | let x = (other.0 - self.0) as f64; 17 | let y = (other.1 - self.1) as f64; 18 | (x * x + y * y).sqrt() 19 | } 20 | } 21 | 22 | impl fmt::Display for Vector2D { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | write!(f, "({}, {})", self.0, self.1) 25 | } 26 | } 27 | 28 | impl Add for Vector2D { 29 | type Output = Vector2D; 30 | 31 | fn add(self, other: Vector2D) -> Vector2D { 32 | Vector2D(self.0 + other.0, self.1 + other.1) 33 | } 34 | } 35 | 36 | impl Sub for Vector2D { 37 | type Output = Vector2D; 38 | 39 | fn sub(self, other: Vector2D) -> Vector2D { 40 | Vector2D(self.0 - other.0, self.1 - other.1) 41 | } 42 | } 43 | 44 | impl Mul for Vector2D { 45 | type Output = Vector2D; 46 | 47 | fn mul(self, other: Vector2D) -> Vector2D { 48 | Vector2D(self.0 * other.0, self.1 * other.1) 49 | } 50 | } 51 | 52 | impl Div for Vector2D { 53 | type Output = Vector2D; 54 | 55 | fn div(self, other: Vector2D) -> Vector2D { 56 | Vector2D(self.0 / other.0, self.1 / other.1) 57 | } 58 | } 59 | 60 | #[cfg(feature = "serde")] 61 | #[cfg(test)] 62 | mod test { 63 | use super::*; 64 | use serde_json; 65 | 66 | #[test] 67 | fn test_vector2d_serde() { 68 | let vector = Vector2D::new(1, 2); 69 | let json = serde_json::to_string(&vector).unwrap(); 70 | assert_eq!(json, r#"[1,2]"#); 71 | 72 | let vector: Vector2D = serde_json::from_str(r#"[1,2]"#).unwrap(); 73 | assert_eq!(vector, Vector2D::new(1, 2)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/wz_to_json.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{Map, Value}; 2 | use wz_reader::{util::node_util, WzNode, WzNodeArc, WzObjectType}; 3 | 4 | fn walk_node_and_to_json(node_arc: &WzNodeArc, json: &mut Map) { 5 | node_util::parse_node(node_arc).unwrap(); 6 | let node = node_arc.read().unwrap(); 7 | match &node.object_type { 8 | WzObjectType::Value(value_type) => { 9 | json.insert(node.name.to_string(), value_type.clone().into()); 10 | } 11 | WzObjectType::Directory(_) 12 | | WzObjectType::Image(_) 13 | | WzObjectType::File(_) 14 | | WzObjectType::Property(_) => { 15 | let mut child_json = Map::new(); 16 | if node.children.len() != 0 { 17 | for value in node.children.values() { 18 | walk_node_and_to_json(value, &mut child_json); 19 | } 20 | json.insert(node.name.to_string(), Value::Object(child_json)); 21 | } 22 | } 23 | _ => {} 24 | } 25 | } 26 | 27 | // usage: 28 | // cargo run --example wz_to_json -- "path/to/some.wz" "ouput/path" 29 | // cargo run --example wz_to_json -- "D:\Path\To\some.wz" ".\output" 30 | fn main() { 31 | let mut args = std::env::args_os().skip(1); 32 | let path = args.next().expect("Need path to wz file as 1st arg"); 33 | let out = args.next().expect("Need out json dir as 2nd arg"); 34 | 35 | /* resolve single wz file */ 36 | let node: WzNodeArc = WzNode::from_wz_file(&path, None).unwrap().into(); 37 | 38 | let mut node_write = node.write().unwrap(); 39 | 40 | let file_name = node_write.name.to_string(); 41 | 42 | node_write.parse(&node).unwrap(); 43 | 44 | let mut json = Map::new(); 45 | 46 | for value in node_write.children.values() { 47 | walk_node_and_to_json(value, &mut json); 48 | } 49 | 50 | let json_string = serde_json::to_string_pretty(&Value::Object(json)).unwrap(); 51 | 52 | let out_path = std::path::Path::new(&out).join([file_name.as_str(), ".json"].concat()); 53 | 54 | std::fs::write(out_path, json_string).unwrap(); 55 | } 56 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | use super::reader::Error; 2 | use scroll::{ctx, Endian, Pread, LE}; 3 | 4 | type Result = std::result::Result; 5 | 6 | /// Wz file's header. 7 | #[derive(Debug, Clone, Copy, Default)] 8 | pub struct WzHeader<'a> { 9 | pub ident: &'a str, 10 | pub fsize: u64, 11 | /// when wz file's content actually start 12 | pub fstart: usize, 13 | pub copyright: &'a str, 14 | } 15 | 16 | impl<'a> ctx::TryFromCtx<'a, Endian> for WzHeader<'a> { 17 | type Error = Error; 18 | fn try_from_ctx(src: &'a [u8], _: Endian) -> std::result::Result<(Self, usize), Self::Error> { 19 | Self::read_from_buf(src) 20 | } 21 | } 22 | 23 | impl WzHeader<'_> { 24 | #[inline] 25 | pub fn get_header_slice(buf: &[u8]) -> &[u8] { 26 | let fstart = Self::get_wz_fstart(buf).unwrap() as usize; 27 | &buf[0..fstart] 28 | } 29 | #[inline] 30 | pub fn get_ident(buf: &[u8]) -> Result<&str> { 31 | buf[0..4].pread::<&str>(0).map_err(Error::from) 32 | } 33 | #[inline] 34 | pub fn get_wz_fsize(buf: &[u8]) -> Result { 35 | buf.pread_with::(4, LE).map_err(Error::from) 36 | } 37 | #[inline] 38 | pub fn get_wz_fstart(buf: &[u8]) -> Result { 39 | buf.pread_with::(12, LE).map_err(Error::from) 40 | } 41 | #[inline] 42 | pub fn get_wz_copyright(buf: &[u8]) -> Result<&str> { 43 | let fstart = Self::get_wz_fstart(buf)? as usize; 44 | buf[16..fstart - 17].pread::<&str>(0).map_err(Error::from) 45 | } 46 | pub fn read_from_buf(buf: &[u8]) -> Result<(WzHeader, usize)> { 47 | let ident = Self::get_ident(buf)?; 48 | 49 | let fsize = Self::get_wz_fsize(buf)?; 50 | 51 | let fstart = Self::get_wz_fstart(buf)? as usize; 52 | 53 | let copyright = Self::get_wz_copyright(buf)?; 54 | 55 | let offset = fstart - 17; 56 | 57 | Ok(( 58 | WzHeader { 59 | ident, 60 | fsize, 61 | fstart, 62 | copyright, 63 | }, 64 | offset, 65 | )) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/extracting_sounds.rs: -------------------------------------------------------------------------------- 1 | use wz_reader::util::{resolve_base, resolve_root_wz_file_dir, walk_node}; 2 | use wz_reader::{WzNode, WzNodeArc, WzNodeCast}; 3 | 4 | // usage: 5 | // cargo run --example extracting_sounds -- (single|base|folder) "path/to/Base.wz" "output/path" 6 | // cargo run --example extracting_sounds -- single "D:\Path\To\Base.wz" "./output" 7 | // cargo run --example extracting_sounds -- base "D:\Path\To\Base.wz" "./output" 8 | // cargo run --example extracting_sounds -- folder "D:\Path\To\Base.wz" "./output" 9 | fn main() { 10 | let mut args = std::env::args_os().skip(1); 11 | let method = args 12 | .next() 13 | .expect("Need method (single/base/folder) as 1st arg"); 14 | let path = args.next().expect("Need path to wz file as 2nd arg"); 15 | let out = args.next().expect("Need out dir as 3rd arg"); 16 | let save_sound_fn = |node: &WzNodeArc| { 17 | let node_read = node.read().unwrap(); 18 | if let Some(sound) = node_read.try_as_sound() { 19 | let path = std::path::Path::new(&out).join(node_read.name.as_str()); 20 | if sound.save(path).is_err() { 21 | println!("failed to extract sound: {}", node_read.get_full_path()); 22 | } 23 | } 24 | }; 25 | 26 | match method.as_encoded_bytes() { 27 | b"single" => { 28 | /* resolve single wz file */ 29 | let node: WzNodeArc = WzNode::from_wz_file(path, None).unwrap().into(); 30 | 31 | walk_node(&node, true, &save_sound_fn); 32 | } 33 | b"base" => { 34 | /* resolve from base.wz */ 35 | let base_node = resolve_base(&path, None).unwrap(); 36 | 37 | /* it's same as below method */ 38 | let sound_node = base_node.read().unwrap().at("Sound").unwrap(); 39 | walk_node(&sound_node, true, &save_sound_fn); 40 | } 41 | b"folder" => { 42 | /* resolve whole wz folder */ 43 | let root_node = resolve_root_wz_file_dir(&path, None).unwrap(); 44 | 45 | walk_node(&root_node, true, &save_sound_fn); 46 | } 47 | _ => eprintln!("Invalid method"), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/extracting_pngs.rs: -------------------------------------------------------------------------------- 1 | use wz_reader::property::get_image; 2 | use wz_reader::util::{resolve_base, resolve_root_wz_file_dir, walk_node}; 3 | use wz_reader::{WzNode, WzNodeArc, WzNodeCast}; 4 | 5 | // usage: 6 | // cargo run --example extracting_png --features "image/png" -- "path/to/Base.wz" "output/path" 7 | // cargo run --example extracting_png --features "image/png" -- single "D:\Path\To\Base.wz" ".\output" 8 | // cargo run --example extracting_png --features "image/png" -- base "D:\Path\To\Base.wz" ".\output" 9 | // cargo run --example extracting_png --features "image/png" -- folder "D:\Path\To\Base.wz" ".\output" 10 | fn main() { 11 | let mut args = std::env::args_os().skip(1); 12 | let method = args 13 | .next() 14 | .expect("Need method (single/base/folder) as 1st arg"); 15 | let path = args.next().expect("Need path to wz file as 2nd arg"); 16 | let out = args.next().expect("Need out dir as 3rd arg"); 17 | let save_image_fn = |node: &WzNodeArc| { 18 | let node_read = node.read().unwrap(); 19 | if node_read.try_as_png().is_some() { 20 | let image = get_image(&node).unwrap(); 21 | /* the name of image is easily got conflect */ 22 | let save_name = node_read.get_full_path().replace("/", "-"); 23 | /* resolving image will auto resolve image from _inlink and _outlink */ 24 | image.save(format!("{out}/{save_name}.png")).unwrap(); 25 | } 26 | }; 27 | match method.as_encoded_bytes() { 28 | b"single" => { 29 | /* resolve single wz file */ 30 | let node: WzNodeArc = WzNode::from_wz_file(path, None).unwrap().into(); 31 | 32 | walk_node(&node, true, &save_image_fn); 33 | } 34 | b"base" => { 35 | /* resolve from base.wz */ 36 | let base_node = resolve_base(&path, None).unwrap(); 37 | 38 | /* this will take millions years */ 39 | walk_node(&base_node, true, &save_image_fn); 40 | } 41 | b"folder" => { 42 | /* resolve whole wz folder */ 43 | let root_node = resolve_root_wz_file_dir(&path, None).unwrap(); 44 | 45 | walk_node(&root_node, true, &save_image_fn); 46 | } 47 | _ => eprintln!("Invalid method"), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/util/walk.rs: -------------------------------------------------------------------------------- 1 | use crate::{WzNodeArc, WzObjectType}; 2 | 3 | /// recursively walk a wz node, passing `&WzNodeArc` to `f`. 4 | /// with `force_parse` it will parse every node along the way, 5 | /// and only unparse `WzImage` after `f` is called to release memory. 6 | pub fn walk_node(node: &WzNodeArc, force_parse: bool, f: &dyn Fn(&WzNodeArc)) { 7 | if force_parse { 8 | // ignore the error 9 | let _ = node.write().unwrap().parse(node); 10 | } 11 | 12 | f(node); 13 | 14 | for child in node.read().unwrap().children.values() { 15 | walk_node(child, force_parse, f); 16 | } 17 | 18 | if force_parse && matches!(node.read().unwrap().object_type, WzObjectType::Image(_)) { 19 | if let Ok(mut node) = node.write() { 20 | node.unparse(); 21 | } 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod test { 27 | use super::*; 28 | use crate::{property::WzSubProperty, WzNode, WzObjectType}; 29 | 30 | fn generate_mock_node() -> WzNodeArc { 31 | let root = WzNode::from_str( 32 | "root", 33 | WzObjectType::Property(WzSubProperty::Property), 34 | None, 35 | ) 36 | .into_lock(); 37 | 38 | let child1 = WzNode::from_str("child1", 1, Some(&root)).into_lock(); 39 | let child2 = WzNode::from_str("child2", 2, Some(&root)).into_lock(); 40 | 41 | WzNode::from_str("child11", 11, Some(&child1)).into_lock(); 42 | WzNode::from_str("child12", 12, Some(&child1)).into_lock(); 43 | 44 | WzNode::from_str("child21", 21, Some(&child2)).into_lock(); 45 | WzNode::from_str("child22", 22, Some(&child2)).into_lock(); 46 | 47 | root 48 | } 49 | 50 | #[test] 51 | fn test_walk_node() { 52 | let root = generate_mock_node(); 53 | 54 | let pathes = std::collections::HashSet::from([ 55 | "root", 56 | "root/child1", 57 | "root/child1/child11", 58 | "root/child1/child12", 59 | "root/child2", 60 | "root/child2/child21", 61 | "root/child2/child22", 62 | ]); 63 | 64 | walk_node(&root, false, &|node| { 65 | let node_read = node.read().unwrap(); 66 | assert!(pathes.contains(node_read.get_full_path().as_str())); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/pet_equip_matcher.rs: -------------------------------------------------------------------------------- 1 | use rayon::iter::*; 2 | use std::sync::Mutex; 3 | use wz_reader::property::string; 4 | use wz_reader::util::resolve_base; 5 | use wz_reader::WzNodeCast; 6 | 7 | // usage: 8 | // cargo run --example pet_equip_matcher -- "path/to/Base.wz" "pet_id" 9 | // cargo run --example pet_equip_matcher -- "D:\Path\To\Base.wz" "5000042" 10 | fn main() { 11 | let args = std::env::args().collect::>(); 12 | let base_path = args.get(1).expect("missing base path"); 13 | let target_pet_id = args.get(2).expect("missing target pet id"); 14 | let base_node = resolve_base(&base_path, None).unwrap(); 15 | 16 | let start = std::time::Instant::now(); 17 | 18 | let pet_equip = base_node 19 | .read() 20 | .unwrap() 21 | .at_path("Character/PetEquip") 22 | .expect("pet equip path not found"); 23 | 24 | let pet_equip_read = pet_equip.read().unwrap(); 25 | 26 | let pet_equip_items = pet_equip_read.children.values().collect::>(); 27 | 28 | let result_items: Mutex> = Mutex::new(Vec::new()); 29 | 30 | pet_equip_items.par_iter().for_each(|node| { 31 | let node = node.read().unwrap(); 32 | if let Some(image) = node.try_as_image() { 33 | if image.at_path(target_pet_id).is_ok() { 34 | let name = node.name.clone().replace(".img", ""); 35 | 36 | if &name == "01802000" { 37 | return; 38 | } 39 | 40 | let mut result_items = result_items.lock().unwrap(); 41 | 42 | let striped_name = name.strip_prefix('0').map(|striped| striped.to_string()); 43 | 44 | result_items.push(striped_name.unwrap_or(name)); 45 | } 46 | } 47 | }); 48 | 49 | let mut ids = result_items.into_inner().unwrap(); 50 | 51 | ids.sort(); 52 | 53 | let string_node = base_node 54 | .read() 55 | .unwrap() 56 | .at_path("String/Eqp.img") 57 | .expect("string node not found"); 58 | 59 | let string_node_read = string_node.read().unwrap(); 60 | 61 | let string_img_node = string_node_read 62 | .try_as_image() 63 | .expect("string node is not wz image"); 64 | 65 | let pet_equip_node = string_img_node 66 | .at_path("Eqp/PetEquip") 67 | .expect("pet equip node not found"); 68 | 69 | let names: Vec<_> = ids 70 | .par_iter() 71 | .map(|id| { 72 | let pet_equip_node = pet_equip_node.read().unwrap(); 73 | let pet_equip_item = pet_equip_node 74 | .at(id) 75 | .expect(format!("pet equip item {} not found", id).as_str()); 76 | let pet_equip_item = pet_equip_item.read().unwrap(); 77 | let name = pet_equip_item.at("name").expect("name not found"); 78 | string::resolve_string_from_node(&name) 79 | }) 80 | .collect(); 81 | 82 | println!("{:?}", names); 83 | 84 | println!("finding time: {:?}", start.elapsed()); 85 | } 86 | -------------------------------------------------------------------------------- /examples/id_to_name.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | use std::sync::Mutex; 3 | use wz_reader::property::string; 4 | use wz_reader::util::{resolve_base, walk_node}; 5 | use wz_reader::WzNodeCast; 6 | 7 | // usage: 8 | // cargo run --example id_to_name -- "path/to/Base.wz" "id" 9 | // cargo run --example id_to_name -- "D:\Path\To\Base.wz" "5000042" 10 | fn main() { 11 | let args = std::env::args_os().collect::>(); 12 | let base_path = args.get(1).expect("missing base path"); 13 | let target_id = args.get(2).expect("missing target id"); 14 | let target_id = target_id.to_str().expect("invalid target id format"); 15 | let base_node = resolve_base(&base_path, None).unwrap(); 16 | 17 | let start = std::time::Instant::now(); 18 | 19 | let string_nodes = { 20 | let mut nodes = vec![]; 21 | let base_node = base_node.read().unwrap(); 22 | 23 | base_node 24 | .at_path("String/Cash.img") 25 | .map(|node| nodes.push(node)); 26 | base_node 27 | .at_path("String/Consume.img") 28 | .map(|node| nodes.push(node)); 29 | base_node 30 | .at_path("String/Eqp.img") 31 | .map(|node| nodes.push(node)); 32 | base_node 33 | .at_path("String/Map.img") 34 | .map(|node| nodes.push(node)); 35 | base_node 36 | .at_path("String/Mob.img") 37 | .map(|node| nodes.push(node)); 38 | base_node 39 | .at_path("String/Npc.img") 40 | .map(|node| nodes.push(node)); 41 | base_node 42 | .at_path("String/Pet.img") 43 | .map(|node| nodes.push(node)); 44 | base_node 45 | .at_path("String/Skill.img") 46 | .map(|node| nodes.push(node)); 47 | base_node 48 | .at_path("String/Ins.img") 49 | .map(|node| nodes.push(node)); 50 | 51 | nodes 52 | }; 53 | 54 | let result = Mutex::new(Vec::new()); 55 | 56 | string_nodes.par_iter().for_each(|node| { 57 | let parse_success = { 58 | let mut node_write = node.write().unwrap(); 59 | node_write.parse(node).is_ok() 60 | }; 61 | 62 | if parse_success { 63 | node.read() 64 | .unwrap() 65 | .children 66 | .values() 67 | .collect::>() 68 | .par_iter() 69 | .for_each(|node| { 70 | walk_node(node, false, &|node| { 71 | let node = node.read().unwrap(); 72 | /* the id of item always be sub_property, so just ignore other node */ 73 | if node.try_as_sub_property().is_none() { 74 | return; 75 | } 76 | /* sometime id will contain 0 perfix(why?) so use end_with instead of eq */ 77 | if node.name.ends_with(target_id) { 78 | let name_node = node.at("name").or_else(|| node.at("mapName")); 79 | if let Some(name_node) = name_node { 80 | let name = string::resolve_string_from_node(&name_node); 81 | if let Ok(name) = name { 82 | let mut result = result.lock().unwrap(); 83 | result.push(name); 84 | } 85 | } 86 | } 87 | }) 88 | }) 89 | }; 90 | }); 91 | 92 | println!("{:?}", result.into_inner().unwrap()); 93 | 94 | println!("finding time: {:?}", start.elapsed()); 95 | } 96 | -------------------------------------------------------------------------------- /examples/name_to_id.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | use std::sync::Mutex; 3 | use wz_reader::property::string; 4 | use wz_reader::util::{resolve_base, walk_node}; 5 | use wz_reader::WzNodeCast; 6 | 7 | // usage: 8 | // cargo run --example id_to_name -- "path/to/Base.wz" "name" 9 | // cargo run --example id_to_name -- "D:\Path\To\Base.wz" "symbol" 10 | fn main() { 11 | let args = std::env::args_os().collect::>(); 12 | let base_path = args.get(1).expect("missing base path"); 13 | let item_name = args.get(2).expect("missing target name"); 14 | let item_name = item_name.to_str().expect("invalid item name"); 15 | let base_node = resolve_base(&base_path, None).unwrap(); 16 | 17 | let start = std::time::Instant::now(); 18 | 19 | let string_nodes = { 20 | let mut nodes = vec![]; 21 | let base_node = base_node.read().unwrap(); 22 | 23 | base_node 24 | .at_path("String/Cash.img") 25 | .map(|node| nodes.push(node)); 26 | base_node 27 | .at_path("String/Consume.img") 28 | .map(|node| nodes.push(node)); 29 | base_node 30 | .at_path("String/Eqp.img") 31 | .map(|node| nodes.push(node)); 32 | base_node 33 | .at_path("String/Map.img") 34 | .map(|node| nodes.push(node)); 35 | base_node 36 | .at_path("String/Mob.img") 37 | .map(|node| nodes.push(node)); 38 | base_node 39 | .at_path("String/Npc.img") 40 | .map(|node| nodes.push(node)); 41 | base_node 42 | .at_path("String/Pet.img") 43 | .map(|node| nodes.push(node)); 44 | base_node 45 | .at_path("String/Skill.img") 46 | .map(|node| nodes.push(node)); 47 | base_node 48 | .at_path("String/Ins.img") 49 | .map(|node| nodes.push(node)); 50 | 51 | nodes 52 | }; 53 | 54 | let result = Mutex::new(Vec::new()); 55 | 56 | string_nodes.par_iter().for_each(|node| { 57 | let parse_success = { 58 | let mut node_write = node.write().unwrap(); 59 | node_write.parse(node).is_ok() 60 | }; 61 | 62 | if parse_success { 63 | node.read() 64 | .unwrap() 65 | .children 66 | .values() 67 | .collect::>() 68 | .par_iter() 69 | .for_each(|node| { 70 | walk_node(node, false, &|node| { 71 | let node_read = node.read().unwrap(); 72 | /* name are always string node */ 73 | if node_read.try_as_string().is_some() { 74 | let name = string::resolve_string_from_node(&node); 75 | if let Ok(name) = name { 76 | if &name == item_name { 77 | let mut result = result.lock().unwrap(); 78 | /* get actual id */ 79 | let id = node_read 80 | .parent 81 | .upgrade() 82 | .unwrap() 83 | .read() 84 | .unwrap() 85 | .name 86 | .clone(); 87 | result.push(id); 88 | } 89 | } 90 | } 91 | }) 92 | }) 93 | }; 94 | }); 95 | 96 | println!("{:?}", result.into_inner().unwrap()); 97 | 98 | println!("finding time: {:?}", start.elapsed()); 99 | } 100 | -------------------------------------------------------------------------------- /src/ms/chacha20_reader.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use c2_chacha::stream_cipher::{NewStreamCipher, SyncStreamCipher}; 3 | use c2_chacha::Ietf; 4 | use scroll::{Pread, LE}; 5 | 6 | use crate::reader::Error; 7 | 8 | pub(crate) const MS_CHACHA20_KEY_SIZE: usize = 32; 9 | pub(crate) const MS_CHACHA20_NONCE_SIZE: usize = 12; 10 | pub(crate) const MS_CHACHA20_VERSION: u8 = 4; 11 | pub(crate) const MS_CHACHA20_KEY_BASE: [u8; 32] = [ 12 | 0x7B, 0x2F, 0x35, 0x48, 0x43, 0x95, 0x02, 0xB9, 0xAE, 0x91, 0xA6, 0xE1, 0xD8, 0xD6, 0x24, 0xB4, 13 | 0x33, 0x10, 0x1D, 0x3D, 0xC1, 0xBB, 0xC6, 0xF4, 0xA5, 0xFE, 0xB3, 0x69, 0x6B, 0x56, 0xE4, 0x75, 14 | ]; 15 | 16 | const READER_CHUNK_SIZE: usize = 64; 17 | 18 | pub(crate) struct ChaCha20Reader<'a> { 19 | data: &'a [u8], 20 | pub offset: usize, 21 | decryptor: Ietf, 22 | buffer_offset: usize, 23 | buffer: [u8; READER_CHUNK_SIZE], 24 | } 25 | 26 | impl<'a> ChaCha20Reader<'a> { 27 | pub fn new( 28 | data: &'a [u8], 29 | key: &[u8; MS_CHACHA20_KEY_SIZE], 30 | nonce: &[u8; MS_CHACHA20_NONCE_SIZE], 31 | ) -> Self { 32 | Self { 33 | data, 34 | offset: 0, 35 | buffer_offset: READER_CHUNK_SIZE, 36 | buffer: [0; READER_CHUNK_SIZE], 37 | decryptor: Ietf::new_var(key, nonce).unwrap(), 38 | } 39 | } 40 | pub fn read_u32(&mut self) -> Result { 41 | let mut buffer = [0_u8; 4]; 42 | buffer.copy_from_slice(&self.data[self.offset..self.offset + 4]); 43 | self.apply_keystream(&mut buffer); 44 | 45 | buffer.pread_with::(0, LE).map_err(Error::from) 46 | } 47 | pub fn read_i32(&mut self) -> Result { 48 | let mut buffer = [0_u8; 4]; 49 | buffer.copy_from_slice(&self.data[self.offset..self.offset + 4]); 50 | self.apply_keystream(&mut buffer); 51 | 52 | buffer.pread_with::(0, LE).map_err(Error::from) 53 | } 54 | pub fn read_utf16_string(&mut self, len: usize) -> Result { 55 | let utf16_vec = self 56 | .read_bytes(len)? 57 | .chunks_exact(2) 58 | .map(|u| u16::from_le_bytes([u[0], u[1]])) 59 | .collect::>(); 60 | 61 | String::from_utf16(&utf16_vec).map_err(Error::from) 62 | } 63 | pub fn read_bytes(&mut self, len: usize) -> Result, Error> { 64 | let mut buffer = self.data[self.offset..self.offset + len].to_vec(); 65 | self.apply_keystream(&mut buffer); 66 | Ok(buffer) 67 | } 68 | pub fn write_bytes_to(&mut self, dest_buffer: &mut [u8], len: usize) -> Result<(), Error> { 69 | if len > dest_buffer.len() { 70 | return Err(Error::DecryptError(len)); 71 | } 72 | 73 | dest_buffer.copy_from_slice(&self.data[self.offset..self.offset + len]); 74 | self.apply_keystream(dest_buffer); 75 | 76 | Ok(()) 77 | } 78 | pub fn apply_keystream(&mut self, data: &mut [u8]) { 79 | let mut remain = data.len(); 80 | let mut start = 0; 81 | while remain > 0 { 82 | if self.buffer_offset >= READER_CHUNK_SIZE { 83 | self.buffer 84 | .copy_from_slice(&self.data[self.offset..self.offset + READER_CHUNK_SIZE]); 85 | self.decryptor.apply_keystream(&mut self.buffer); 86 | self.offset += READER_CHUNK_SIZE; 87 | self.buffer_offset = 0; 88 | } 89 | 90 | let read_count = remain.min(READER_CHUNK_SIZE - self.buffer_offset); 91 | 92 | data[start..start + read_count] 93 | .copy_from_slice(&self.buffer[self.buffer_offset..self.buffer_offset + read_count]); 94 | start += read_count; 95 | remain -= read_count; 96 | self.buffer_offset += read_count; 97 | } 98 | 99 | // this should only trigger in == 100 | if self.buffer_offset >= READER_CHUNK_SIZE { 101 | self.decryptor.state.state.set_stream_param(0, 0); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | use crate::util::maple_crypto_constants::{WZ_GMSIV, WZ_MSEAIV}; 2 | use crate::util::wz_mutable_key::WzMutableKey; 3 | use crate::{reader, Reader, WzHeader, WzSliceReader}; 4 | use std::sync::{Arc, RwLock}; 5 | 6 | pub fn get_iv_by_maple_version(version: WzMapleVersion) -> [u8; 4] { 7 | match version { 8 | WzMapleVersion::GMS => WZ_GMSIV, 9 | WzMapleVersion::EMS => WZ_MSEAIV, 10 | _ => [0; 4], 11 | } 12 | } 13 | 14 | /// MapleStory version, use to determine the IV for decryption 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 16 | pub enum WzMapleVersion { 17 | /// Global MapleStory (old) 18 | GMS, 19 | 20 | /// 新楓之谷 / 冒险岛Online / 메이플스토리 / MapleSEA / EMS (old) 21 | EMS, 22 | 23 | /// BMS / GMS / MapleSEA / メイプルストーリー / 메이플스토리 24 | BMS, 25 | 26 | CLASSIC, 27 | 28 | GENERATE, 29 | 30 | /* from zlz.dll */ 31 | GETFROMZLZ, 32 | 33 | CUSTOM, 34 | 35 | UNKNOWN, 36 | } 37 | 38 | /// Verify IV from wz image 39 | pub fn verify_iv_from_wz_img(buf: &[u8], iv: &[u8; 4]) -> bool { 40 | let reader = WzSliceReader::new(buf, &Arc::new(RwLock::new(WzMutableKey::from_iv(*iv)))); 41 | 42 | reader.pos.set(1); 43 | 44 | reader.read_wz_string().unwrap_or_default() == "Property" 45 | } 46 | 47 | /// Try to guess IV from wz image use fixed value. Currently will try GMS, EMS, BMS. 48 | pub fn guess_iv_from_wz_img(buf: &[u8]) -> Option<[u8; 4]> { 49 | // not support other then WZ_IMAGE_HEADER_BYTE_WITHOUT_OFFSET 50 | if buf[0] != 0x73 { 51 | return None; 52 | } 53 | 54 | let guess_versions = [ 55 | WzMapleVersion::GMS, 56 | WzMapleVersion::EMS, 57 | WzMapleVersion::BMS, 58 | ]; 59 | 60 | for version in guess_versions.iter() { 61 | let iv = get_iv_by_maple_version(*version); 62 | if verify_iv_from_wz_img(buf, &iv) { 63 | return Some(iv); 64 | } 65 | } 66 | 67 | None 68 | } 69 | 70 | pub fn verify_iv_from_wz_file(buf: &[u8], iv: &[u8; 4]) -> Result<(), reader::Error> { 71 | let reader = WzSliceReader::new(buf, &Arc::new(RwLock::new(WzMutableKey::from_iv(*iv)))); 72 | 73 | let fstart = WzHeader::get_wz_fstart(buf)? as usize; 74 | 75 | reader.seek(fstart + 2); 76 | 77 | let entry_count = reader.read_wz_int()?; 78 | 79 | if !(0..=1000000).contains(&entry_count) { 80 | return Err(reader::Error::DecryptError(reader.pos.get())); 81 | } 82 | 83 | for _ in 0..entry_count { 84 | let dir_byte = reader.read_u8()?; 85 | 86 | match dir_byte { 87 | 1 => { 88 | reader.skip(4 + 4 + 2); 89 | continue; 90 | } 91 | 2 => { 92 | let str_offset = reader.read_i32()?; 93 | 94 | let offset = reader.header.fstart + str_offset as usize; 95 | // just check string can be valid string(instead of parse string lossy), so can prove the iv is valid 96 | let meta = reader.read_wz_string_meta_at(offset + 1)?; 97 | reader.try_resolve_wz_string_meta( 98 | &meta.string_type, 99 | meta.offset, 100 | meta.length as usize, 101 | )?; 102 | } 103 | 3 | 4 => { 104 | // just check string can be valid string(instead of parse string lossy), so can prove the iv is valid 105 | let meta = reader.read_wz_string_meta()?; 106 | reader.try_resolve_wz_string_meta( 107 | &meta.string_type, 108 | meta.offset, 109 | meta.length as usize, 110 | )?; 111 | } 112 | _ => return Err(reader::Error::DecryptError(reader.pos.get())), 113 | } 114 | 115 | reader.read_wz_int()?; 116 | reader.read_wz_int()?; 117 | reader.skip(4); 118 | } 119 | 120 | Ok(()) 121 | } 122 | 123 | pub fn guess_iv_from_wz_file(buf: &[u8]) -> Option<[u8; 4]> { 124 | let guess_versions = [ 125 | WzMapleVersion::BMS, 126 | WzMapleVersion::GMS, 127 | WzMapleVersion::EMS, 128 | ]; 129 | 130 | for version in guess_versions.iter() { 131 | let iv = get_iv_by_maple_version(*version); 132 | if verify_iv_from_wz_file(buf, &iv).is_ok() { 133 | return Some(iv); 134 | } 135 | } 136 | 137 | None 138 | } 139 | -------------------------------------------------------------------------------- /src/property/lua.rs: -------------------------------------------------------------------------------- 1 | use crate::util::maple_crypto_constants::{WZ_GMSIV, WZ_MSEAIV}; 2 | use crate::util::WzMutableKey; 3 | use crate::WzReader; 4 | use std::sync::Arc; 5 | use thiserror::Error; 6 | 7 | #[derive(Debug, Error)] 8 | pub enum WzLuaParseError { 9 | #[error("Lua decode fail")] 10 | StringDecodeFail(#[from] std::string::FromUtf8Error), 11 | 12 | #[error("Unknown Lua Iv")] 13 | UnknownLuaIv, 14 | 15 | #[error("Not a Lua property")] 16 | NotLuaProperty, 17 | } 18 | 19 | /// WzLua use to store lua information and extraction method. 20 | #[derive(Debug, Clone, Default)] 21 | pub struct WzLua { 22 | reader: Arc, 23 | offset: usize, 24 | length: usize, 25 | } 26 | 27 | impl WzLua { 28 | pub fn new(reader: &Arc, offset: usize, length: usize) -> Self { 29 | Self { 30 | reader: Arc::clone(reader), 31 | offset, 32 | length, 33 | } 34 | } 35 | 36 | /// extract lua string from wz file 37 | pub fn extract_lua(&self) -> Result { 38 | let data = self 39 | .reader 40 | .get_slice(self.offset..self.length + self.offset); 41 | 42 | let mut keys = self 43 | .get_mtb_keys_from_guess_lua_iv() 44 | .ok_or(WzLuaParseError::UnknownLuaIv)?; 45 | 46 | let len = data.len(); 47 | 48 | let mut decoded = data.to_vec(); 49 | 50 | keys.ensure_key_size(len).unwrap(); 51 | 52 | keys.decrypt_slice(&mut decoded); 53 | 54 | String::from_utf8(decoded).map_err(WzLuaParseError::from) 55 | } 56 | 57 | /// try to guess the iv from encrypted data 58 | fn get_mtb_keys_from_guess_lua_iv(&self) -> Option { 59 | let len = std::cmp::min(64, self.length); 60 | let test_data = self.reader.get_slice(self.offset..self.offset + len); 61 | 62 | let ivs = [WZ_MSEAIV, WZ_GMSIV, [0, 0, 0, 0]]; 63 | 64 | for iv in ivs { 65 | let mut decoded = test_data.to_vec(); 66 | let mut keys = WzMutableKey::from_iv(iv); 67 | 68 | keys.ensure_key_size(len).unwrap(); 69 | 70 | keys.decrypt_slice(&mut decoded); 71 | 72 | if String::from_utf8(decoded).is_ok() { 73 | return Some(keys); 74 | } 75 | } 76 | 77 | None 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod test { 83 | use super::*; 84 | use memmap2::MmapMut; 85 | use std::fs::OpenOptions; 86 | use tempfile; 87 | 88 | fn generate_encrypted_text(text: &str, iv: [u8; 4]) -> Vec { 89 | let mut keys = WzMutableKey::from_iv(iv); 90 | let mut data = text.as_bytes().to_vec(); 91 | 92 | keys.ensure_key_size(data.len()).unwrap(); 93 | 94 | keys.decrypt_slice(&mut data); 95 | 96 | data 97 | } 98 | 99 | fn setup_lua(iv: [u8; 4]) -> Result { 100 | let lua_text = "print(1234567)"; 101 | let len = lua_text.len(); 102 | let dir = tempfile::tempdir()?; 103 | let file_path = dir.path().join("test.lua"); 104 | 105 | let file = OpenOptions::new() 106 | .read(true) 107 | .write(true) 108 | .create(true) 109 | .open(file_path)?; 110 | 111 | file.set_len(len as u64)?; 112 | 113 | let mut map = unsafe { MmapMut::map_mut(&file)? }; 114 | 115 | let encrypted = generate_encrypted_text(lua_text, iv); 116 | 117 | (&mut map[..len]).copy_from_slice(&encrypted); 118 | 119 | let reader = Arc::new(WzReader::new(map.make_read_only()?).with_iv(iv)); 120 | 121 | Ok(WzLua::new(&reader, 0, len)) 122 | } 123 | 124 | #[test] 125 | fn should_guess_gms() { 126 | let lua = setup_lua(WZ_GMSIV).unwrap(); 127 | 128 | let text = lua.extract_lua(); 129 | 130 | assert!(text.is_ok()); 131 | assert_eq!(text.unwrap(), "print(1234567)"); 132 | } 133 | 134 | #[test] 135 | fn should_guess_msea() { 136 | let lua = setup_lua(WZ_MSEAIV).unwrap(); 137 | 138 | let text = lua.extract_lua(); 139 | 140 | assert!(text.is_ok()); 141 | assert_eq!(text.unwrap(), "print(1234567)"); 142 | } 143 | 144 | #[test] 145 | fn should_guess_none_iv() { 146 | let lua = setup_lua([0, 0, 0, 0]).unwrap(); 147 | 148 | let text = lua.extract_lua(); 149 | 150 | assert!(text.is_ok()); 151 | assert_eq!(text.unwrap(), "print(1234567)"); 152 | } 153 | 154 | #[test] 155 | fn should_error_unknown_iv() { 156 | let lua = setup_lua([1, 2, 3, 4]).unwrap(); 157 | 158 | let text = lua.extract_lua(); 159 | 160 | assert!(text.is_err()); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/ms/snow2_reader.rs: -------------------------------------------------------------------------------- 1 | use scroll::{Pread, LE}; 2 | 3 | use super::snow2_decryptor::Snow2Decryptor; 4 | use crate::reader::Error; 5 | 6 | pub(crate) const MS_SNOW2_KEY_SIZE: usize = 16; 7 | pub(crate) const MS_SNOW2_VERSION: u8 = 2; 8 | 9 | pub(crate) struct Snow2Reader<'a> { 10 | data: &'a [u8], 11 | pub offset: usize, 12 | decryptor: Snow2Decryptor, 13 | pub buffer: [u8; 4], 14 | pub buffer_len: usize, 15 | } 16 | 17 | impl<'a> Snow2Reader<'a> { 18 | pub fn new(data: &'a [u8], snow_key: [u8; MS_SNOW2_KEY_SIZE]) -> Self { 19 | Self { 20 | data, 21 | offset: 0, 22 | decryptor: Snow2Decryptor::new(snow_key), 23 | buffer: [0_u8; 4], 24 | buffer_len: 0, 25 | } 26 | } 27 | #[allow(dead_code)] 28 | pub fn read_u32(&mut self) -> Result { 29 | let decrypted = self 30 | .decryptor 31 | .decrypt_block(&self.data.pread_with::(self.offset, LE)?); 32 | self.offset += 4; 33 | if self.buffer_len > 0 { 34 | let decrypted_bytes = decrypted.to_le_bytes(); 35 | let mut buffer = [0_u8; 4]; 36 | buffer[..self.buffer_len].copy_from_slice(&self.buffer[..self.buffer_len]); 37 | buffer[self.buffer_len..].copy_from_slice(&decrypted_bytes[..4 - self.buffer_len]); 38 | let result = buffer.pread_with::(0, LE)?; 39 | self.buffer[..(4 - self.buffer_len)] 40 | .copy_from_slice(&decrypted_bytes[self.buffer_len..]); 41 | Ok(result) 42 | } else { 43 | Ok(decrypted) 44 | } 45 | } 46 | pub fn read_i32(&mut self) -> Result { 47 | let decrypted = self 48 | .decryptor 49 | .decrypt_block(&self.data.pread_with::(self.offset, LE)?); 50 | self.offset += 4; 51 | 52 | let decrypted_bytes = decrypted.to_le_bytes(); 53 | 54 | if self.buffer_len > 0 { 55 | // merge buffer and decrypted_bytes then read u32 56 | let mut buffer = [0_u8; 4]; 57 | buffer[..self.buffer_len].copy_from_slice(&self.buffer[..self.buffer_len]); 58 | buffer[self.buffer_len..].copy_from_slice(&decrypted_bytes[..4 - self.buffer_len]); 59 | let result = buffer.pread_with::(0, LE)?; 60 | // deal with remaining bytes 61 | self.buffer[..(4 - self.buffer_len)] 62 | .copy_from_slice(&decrypted_bytes[self.buffer_len..]); 63 | 64 | Ok(result) 65 | } else { 66 | Ok(i32::from_le_bytes(decrypted_bytes)) 67 | } 68 | } 69 | pub fn read_utf16_string(&mut self, len: usize) -> Result { 70 | let string_vec = self.read_bytes(len)?; 71 | let utf16_vec = string_vec 72 | .chunks_exact(2) 73 | .map(|u| u16::from_le_bytes([u[0], u[1]])) 74 | .collect::>(); 75 | 76 | String::from_utf16(&utf16_vec).map_err(Error::from) 77 | } 78 | pub fn read_bytes(&mut self, len: usize) -> Result, Error> { 79 | let mut vec = vec![0_u8; len]; 80 | 81 | self.write_bytes_to(&mut vec, len)?; 82 | 83 | Ok(vec) 84 | } 85 | pub fn write_bytes_to(&mut self, dest_buffer: &mut [u8], len: usize) -> Result<(), Error> { 86 | let mut remaining_len = len as i32; 87 | 88 | /* first: deal remain buffer from previous */ 89 | if self.buffer_len > 0 { 90 | if len < self.buffer_len { 91 | (&mut dest_buffer[0..len]).copy_from_slice(&self.buffer[0..len]); 92 | self.buffer_len -= len; 93 | 94 | // move the remaining bytes to the beginning of the buffer 95 | let mut temp_buffer = [0_u8; 4]; 96 | temp_buffer.copy_from_slice(&self.buffer[len..]); 97 | 98 | self.buffer.copy_from_slice(&temp_buffer); 99 | 100 | return Ok(()); 101 | } 102 | (&mut dest_buffer[0..self.buffer_len]) 103 | .copy_from_slice(&self.buffer[0..self.buffer_len]); 104 | // self.buffer.fill(0); 105 | remaining_len -= self.buffer_len as i32; 106 | self.buffer_len = 0; 107 | } 108 | 109 | while remaining_len > 0 { 110 | let decrypted = self 111 | .decryptor 112 | .decrypt_block(&self.data.pread_with::(self.offset, LE)?); 113 | let bytes = decrypted.to_le_bytes(); 114 | let start = len - remaining_len as usize; 115 | let dest_range = start..std::cmp::min(start + 4, len); 116 | let data_len = dest_range.len(); 117 | (&mut dest_buffer[dest_range]).copy_from_slice(&bytes[..data_len]); 118 | self.offset += 4; 119 | remaining_len -= 4; 120 | 121 | if remaining_len < 0 { 122 | let remaining_bytes = &bytes[data_len..]; 123 | self.buffer_len = remaining_bytes.len(); 124 | self.buffer[..self.buffer_len].copy_from_slice(&remaining_bytes); 125 | } 126 | } 127 | 128 | Ok(()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /benches/benchmarks/node_lookup.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, Criterion}; 2 | use std::sync::Arc; 3 | use wz_reader::version::WzMapleVersion; 4 | use wz_reader::{property::WzSubProperty, WzImage, WzNode, WzNodeArc, WzNodeCast, WzObjectType}; 5 | 6 | fn create_int_node(num: i32, parent: &WzNodeArc) -> WzNodeArc { 7 | WzNode::new(&format!("{}", num).into(), num, Some(parent)).into_lock() 8 | } 9 | 10 | fn thin_setup() -> (WzNodeArc, String) { 11 | let root = WzNode::new( 12 | &"root".into(), 13 | WzObjectType::Property(WzSubProperty::Property), 14 | None, 15 | ) 16 | .into_lock(); 17 | 18 | let (_, mut path) = (0..99).fold((Arc::clone(&root), String::from("")), |node, num| { 19 | let child = create_int_node(num, &node.0); 20 | node.0 21 | .write() 22 | .unwrap() 23 | .children 24 | .insert(num.to_string().into(), Arc::clone(&child)); 25 | (child, format!("{}/{}", node.1, num)) 26 | }); 27 | 28 | path.remove(0); 29 | 30 | (root, path) 31 | } 32 | 33 | fn fat_setup() -> (WzNodeArc, String) { 34 | let root = WzNode::new( 35 | &"root".into(), 36 | WzObjectType::Property(WzSubProperty::Property), 37 | None, 38 | ) 39 | .into_lock(); 40 | 41 | let (_, mut path) = (0..=500).fold((Arc::clone(&root), String::from("")), |node, _| { 42 | let first = create_int_node(0, &node.0); 43 | node.0 44 | .write() 45 | .unwrap() 46 | .children 47 | .insert("0".into(), Arc::clone(&first)); 48 | 49 | let last = (1..=500).fold(first, |_, num| { 50 | let child = create_int_node(num, &node.0); 51 | node.0 52 | .write() 53 | .unwrap() 54 | .children 55 | .insert(num.to_string().into(), Arc::clone(&child)); 56 | child 57 | }); 58 | (last, format!("{}/{}", node.1, 500)) 59 | }); 60 | 61 | path.remove(0); 62 | 63 | (root, path) 64 | } 65 | 66 | fn lookup(node: &WzNodeArc, look_path: &str) { 67 | assert!(node.read().unwrap().at_path(look_path).is_some()); 68 | } 69 | fn parse_and_lookup(node: &WzNodeArc, look_path: &str) { 70 | assert!(node.write().unwrap().parse(node).is_ok()); 71 | assert!(node.read().unwrap().at_path(look_path).is_some()); 72 | } 73 | fn direct_lookup(node: &WzImage, look_path: &str) { 74 | assert!(node.at_path(look_path).is_ok()); 75 | } 76 | 77 | fn thin_bench(c: &mut Criterion) { 78 | let (node, path) = thin_setup(); 79 | c.bench_function("thin node lookup", |b| { 80 | b.iter(|| lookup(black_box(&node), black_box(&path))) 81 | }); 82 | } 83 | 84 | fn fat_bench(c: &mut Criterion) { 85 | let (node, path) = fat_setup(); 86 | c.bench_function("fat node lookup", |b| { 87 | b.iter(|| lookup(black_box(&node), black_box(&path))) 88 | }); 89 | } 90 | 91 | fn parse_and_access_bench(c: &mut Criterion) { 92 | let node = WzNode::from_wz_file_full( 93 | "./benches/benchmarks/test.wz", 94 | Some(WzMapleVersion::BMS), 95 | Some(123), 96 | None, 97 | None, 98 | ) 99 | .unwrap() 100 | .into_lock(); 101 | assert!(node.write().unwrap().parse(&node).is_ok()); 102 | let image_node = node.read().unwrap().at("wz_img.img").unwrap(); 103 | c.bench_function("parse and access lookup", |b| { 104 | b.iter(|| { 105 | parse_and_lookup(black_box(&image_node), "1/1/1/1/1/1/1/1/1/1/1"); 106 | }) 107 | }); 108 | } 109 | 110 | fn access_after_parsing_bench(c: &mut Criterion) { 111 | let node = WzNode::from_wz_file_full( 112 | "./benches/benchmarks/test.wz", 113 | Some(WzMapleVersion::BMS), 114 | Some(123), 115 | None, 116 | None, 117 | ) 118 | .unwrap() 119 | .into_lock(); 120 | assert!(node.write().unwrap().parse(&node).is_ok()); 121 | let image_node = node.read().unwrap().at("wz_img.img").unwrap(); 122 | image_node.write().unwrap().parse(&image_node).unwrap(); 123 | c.bench_function("access after parsing lookup", |b| { 124 | b.iter(|| { 125 | lookup(black_box(&image_node), "1/1/1/1/1/1/1/1/1/1/1"); 126 | }) 127 | }); 128 | } 129 | 130 | fn direct_access_bench(c: &mut Criterion) { 131 | let node = WzNode::from_wz_file_full( 132 | "./benches/benchmarks/test.wz", 133 | Some(WzMapleVersion::BMS), 134 | Some(123), 135 | None, 136 | None, 137 | ) 138 | .unwrap() 139 | .into_lock(); 140 | assert!(node.write().unwrap().parse(&node).is_ok()); 141 | let image_node = node.read().unwrap().at("wz_img.img").unwrap(); 142 | let image_node = image_node.read().unwrap(); 143 | let image_node = image_node.try_as_image().unwrap(); 144 | c.bench_function("direct access lookup", |b| { 145 | b.iter(|| { 146 | direct_lookup(black_box(&image_node), "1/1/1/1/1/1/1/1/1/1/1"); 147 | }) 148 | }); 149 | } 150 | 151 | criterion_group!( 152 | benches, 153 | thin_bench, 154 | fat_bench, 155 | parse_and_access_bench, 156 | access_after_parsing_bench, 157 | direct_access_bench 158 | ); 159 | -------------------------------------------------------------------------------- /src/util/color.rs: -------------------------------------------------------------------------------- 1 | use image::{Rgb, Rgba}; 2 | 3 | pub trait SimpleColor 4 | where 5 | Self: Sized, 6 | { 7 | fn create(r: u8, g: u8, b: u8) -> Self; 8 | fn white() -> Self; 9 | fn black() -> Self; 10 | #[inline] 11 | fn from_rgb565(color: u16) -> Self { 12 | let r = ((color & 0xF800) >> 11) as u8; 13 | let g = ((color & 0x07E0) >> 5) as u8; 14 | let b = (color & 0x001F) as u8; 15 | 16 | Self::create(r << 3 | r >> 2, g << 2 | g >> 4, b << 3 | b >> 2) 17 | } 18 | fn r(&self) -> u8; 19 | fn g(&self) -> u8; 20 | fn b(&self) -> u8; 21 | } 22 | 23 | pub trait SimpleColorAlpha 24 | where 25 | Self: Sized, 26 | { 27 | fn create_alpha(r: u8, g: u8, b: u8, a: u8) -> Self; 28 | fn transparent() -> Self; 29 | #[inline] 30 | fn from_argb1555(color: u16) -> Self { 31 | let a = if (color & 0x8000) != 0 { 255 } else { 0 }; 32 | let r = ((color & 0x7C00) >> 10) as u8; 33 | let g = ((color & 0x03E0) >> 5) as u8; 34 | let b = (color & 0x001F) as u8; 35 | 36 | Self::create_alpha(r << 3 | r >> 2, g << 3 | g >> 2, b << 3 | b >> 2, a) 37 | } 38 | fn a(&self) -> u8; 39 | } 40 | 41 | impl SimpleColor for Rgba { 42 | #[inline] 43 | fn create(r: u8, g: u8, b: u8) -> Self { 44 | Rgba([r, g, b, 255]) 45 | } 46 | #[inline] 47 | fn white() -> Self { 48 | Rgba([255, 255, 255, 255]) 49 | } 50 | #[inline] 51 | fn black() -> Self { 52 | Rgba([0, 0, 0, 255]) 53 | } 54 | #[inline] 55 | fn r(&self) -> u8 { 56 | self[0] 57 | } 58 | #[inline] 59 | fn g(&self) -> u8 { 60 | self[1] 61 | } 62 | #[inline] 63 | fn b(&self) -> u8 { 64 | self[2] 65 | } 66 | } 67 | 68 | impl SimpleColorAlpha for Rgba { 69 | #[inline] 70 | fn create_alpha(r: u8, g: u8, b: u8, a: u8) -> Self { 71 | Rgba([r, g, b, a]) 72 | } 73 | #[inline] 74 | fn transparent() -> Self { 75 | Rgba([0, 0, 0, 0]) 76 | } 77 | #[inline] 78 | fn a(&self) -> u8 { 79 | self[3] 80 | } 81 | } 82 | 83 | impl SimpleColor for Rgb { 84 | #[inline] 85 | fn create(r: u8, g: u8, b: u8) -> Self { 86 | Rgb([r, g, b]) 87 | } 88 | #[inline] 89 | fn white() -> Self { 90 | Rgb([255, 255, 255]) 91 | } 92 | #[inline] 93 | fn black() -> Self { 94 | Rgb([0, 0, 0]) 95 | } 96 | #[inline] 97 | fn r(&self) -> u8 { 98 | self[0] 99 | } 100 | #[inline] 101 | fn g(&self) -> u8 { 102 | self[1] 103 | } 104 | #[inline] 105 | fn b(&self) -> u8 { 106 | self[2] 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod test { 112 | use super::*; 113 | use image::{Rgb, Rgba}; 114 | 115 | const RED: u32 = 0xFF0000; 116 | const GREEN: u32 = 0x00FF00; 117 | const BLUE: u32 = 0x0000FF; 118 | const GRAY: u32 = 0x808080; 119 | 120 | const ARED: u32 = 0xFF000000; 121 | const AGREEN: u32 = 0x00FF0000; 122 | const ABLUE: u32 = 0x0000FF00; 123 | const AGRAY: u32 = 0x80808000; 124 | const TGRAY: u32 = 0x808080FF; // Transparent 125 | const HGRAY: u32 = 0x80808080; // Half Transparent 126 | 127 | fn create_rgb565_from_rgb(color: u32) -> u16 { 128 | (((color & 0xf80000) >> 8) + ((color & 0xfc00) >> 5) + ((color & 0xf8) >> 3)) as u16 129 | } 130 | 131 | fn create_argb1555_from_rgba(color: u32) -> u16 { 132 | let r = (((color & 0xff000000) >> 24) as u16 * 31 + 127) / 255; 133 | let g = (((color & 0x00ff0000) >> 16) as u16 * 31 + 127) / 255; 134 | let b = (((color & 0x0000ff00) >> 8) as u16 * 31 + 127) / 255; 135 | 136 | let a = if color & 0xff > 0 { 0 } else { 1 }; 137 | 138 | dbg!((a << 15) | (r << 10) | (g << 5) | b) 139 | } 140 | 141 | #[test] 142 | fn rgb_from_rgb565() { 143 | let red = Rgb::from_rgb565(create_rgb565_from_rgb(RED)); 144 | assert_eq!(red, Rgb([255, 0, 0])); 145 | 146 | let green = Rgb::from_rgb565(create_rgb565_from_rgb(GREEN)); 147 | assert_eq!(green, Rgb([0, 255, 0])); 148 | 149 | let blue = Rgb::from_rgb565(create_rgb565_from_rgb(BLUE)); 150 | assert_eq!(blue, Rgb([0, 0, 255])); 151 | 152 | let gray = Rgb::from_rgb565(create_rgb565_from_rgb(GRAY)); 153 | assert_eq!(gray, Rgb([132, 130, 132])); 154 | } 155 | 156 | #[test] 157 | fn rgba_from_rgba1555() { 158 | let red = Rgba::from_argb1555(create_argb1555_from_rgba(ARED)); 159 | assert_eq!(red, Rgba([255, 0, 0, 255])); 160 | 161 | let green = Rgba::from_argb1555(create_argb1555_from_rgba(AGREEN)); 162 | assert_eq!(green, Rgba([0, 255, 0, 255])); 163 | 164 | let blue = Rgba::from_argb1555(create_argb1555_from_rgba(ABLUE)); 165 | assert_eq!(blue, Rgba([0, 0, 255, 255])); 166 | 167 | let gray = Rgba::from_argb1555(create_argb1555_from_rgba(AGRAY)); 168 | assert_eq!(gray, Rgba([132, 132, 132, 255])); 169 | 170 | let tgray = Rgba::from_argb1555(create_argb1555_from_rgba(TGRAY)); 171 | assert_eq!(tgray, Rgba([132, 132, 132, 0])); 172 | 173 | // helf gray should turn to complete transparent 174 | let hgray = Rgba::from_argb1555(create_argb1555_from_rgba(HGRAY)); 175 | assert_eq!(hgray, Rgba([132, 132, 132, 0])); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/property/sound.rs: -------------------------------------------------------------------------------- 1 | use crate::reader::{read_i32_at, WzReader}; 2 | use std::fs::File; 3 | use std::path::PathBuf; 4 | use std::sync::Arc; 5 | use std::{ 6 | io::{Seek, Write}, 7 | ops::Range, 8 | }; 9 | 10 | #[cfg(feature = "serde")] 11 | use serde::{Deserialize, Serialize}; 12 | 13 | use thiserror::Error; 14 | 15 | #[derive(Debug, Error)] 16 | pub enum WzSoundError { 17 | #[error("Unsupported sound format")] 18 | UnsupportedFormat, 19 | 20 | #[error(transparent)] 21 | SaveError(#[from] std::io::Error), 22 | 23 | #[error("Not a Sound property")] 24 | NotSoundProperty, 25 | } 26 | 27 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 28 | #[derive(Debug, Clone, PartialEq, Default)] 29 | pub enum WzSoundType { 30 | Mp3, 31 | Wav, 32 | #[default] 33 | Binary, 34 | } 35 | 36 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 37 | #[derive(Debug, Clone, Default)] 38 | pub struct WzSound { 39 | #[cfg_attr(feature = "serde", serde(skip))] 40 | reader: Arc, 41 | #[cfg_attr(feature = "serde", serde(skip))] 42 | offset: usize, 43 | #[cfg_attr(feature = "serde", serde(skip))] 44 | length: u32, 45 | #[cfg_attr(feature = "serde", serde(skip))] 46 | header_offset: usize, 47 | #[cfg_attr(feature = "serde", serde(skip))] 48 | header_size: usize, 49 | pub duration: u32, 50 | pub sound_type: WzSoundType, 51 | } 52 | 53 | const WAV_HEADER: [u8; 44] = [ 54 | 0x52, 0x49, 0x46, 0x46, //"RIFF" 55 | 0, 0, 0, 0, //ChunkSize 56 | 0x57, 0x41, 0x56, 0x45, //"WAVE" 57 | 0x66, 0x6d, 0x74, 0x20, //"fmt " 58 | 0x10, 0, 0, 0, //chunk1Size 59 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // copy16char 60 | 0x64, 0x61, 0x74, 0x61, //"data" 61 | 0, 0, 0, 0, //chunk2Size 62 | ]; 63 | 64 | fn get_frequency_header(header: &[u8]) -> u32 { 65 | if header.len() <= 0x3c { 66 | 0 67 | } else { 68 | read_i32_at(header, 0x38).unwrap() as u32 69 | } 70 | } 71 | 72 | pub fn get_sound_type_from_header(header: &[u8], file_size: u32, duration: u32) -> WzSoundType { 73 | let frequency = get_frequency_header(header); 74 | if header.len() == 0x46 { 75 | if frequency == file_size && duration == 1000 { 76 | WzSoundType::Binary 77 | } else { 78 | WzSoundType::Wav 79 | } 80 | } else { 81 | WzSoundType::Mp3 82 | } 83 | } 84 | 85 | impl WzSound { 86 | pub fn new( 87 | reader: &Arc, 88 | offset: usize, 89 | length: u32, 90 | header_offset: usize, 91 | header_size: usize, 92 | duration: u32, 93 | sound_type: WzSoundType, 94 | ) -> Self { 95 | Self { 96 | reader: Arc::clone(reader), 97 | offset, 98 | length, 99 | header_offset, 100 | header_size, 101 | duration, 102 | sound_type, 103 | } 104 | } 105 | #[inline] 106 | fn get_buffer_range(&self) -> Range { 107 | self.offset..self.offset + self.length as usize 108 | } 109 | #[inline] 110 | fn get_header_range(&self) -> Range { 111 | self.header_offset..self.header_offset + self.header_size 112 | } 113 | pub fn get_wav_header(&self) -> Vec { 114 | let header = self.reader.get_slice(self.get_header_range()); 115 | let chunk_size = (self.length + 36).to_le_bytes(); 116 | let u8_16_from_header = &header[0x34..0x34 + 16]; 117 | let chunk2_size = self.length.to_le_bytes(); 118 | 119 | let mut wav_header = WAV_HEADER.to_vec(); 120 | 121 | wav_header[4..8].copy_from_slice(&chunk_size); 122 | wav_header[20..36].copy_from_slice(u8_16_from_header); 123 | wav_header[40..44].copy_from_slice(&chunk2_size); 124 | 125 | wav_header 126 | } 127 | /// Write the sound to a writer. Will inculde the header if the sound is a wav file. 128 | pub fn write_to(&self, writer: &mut W) -> Result<(), WzSoundError> 129 | where 130 | W: Write + Seek, 131 | { 132 | let buffer = self.reader.get_slice(self.get_buffer_range()); 133 | match self.sound_type { 134 | WzSoundType::Wav => { 135 | let wav_header = self.get_wav_header(); 136 | writer.write_all(&wav_header)?; 137 | writer.write_all(buffer)?; 138 | } 139 | _ => { 140 | writer.write_all(buffer)?; 141 | } 142 | } 143 | 144 | Ok(()) 145 | } 146 | #[inline] 147 | pub fn get_buffer(&self) -> Vec { 148 | let buffer = self.reader.get_slice(self.get_buffer_range()); 149 | match self.sound_type { 150 | WzSoundType::Wav => { 151 | let mut wav_buffer = Vec::with_capacity(44 + buffer.len()); 152 | wav_buffer.extend_from_slice(&self.get_wav_header()); 153 | wav_buffer.extend_from_slice(buffer); 154 | wav_buffer 155 | } 156 | _ => buffer.to_vec(), 157 | } 158 | } 159 | #[inline] 160 | pub fn save(&self, path: PathBuf) -> Result<(), WzSoundError> { 161 | let mut file = match self.sound_type { 162 | WzSoundType::Wav => File::create(path.with_extension("wav"))?, 163 | WzSoundType::Mp3 => File::create(path.with_extension("mp3"))?, 164 | _ => File::create(path)?, 165 | }; 166 | 167 | self.write_to(&mut file)?; 168 | 169 | Ok(()) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/object.rs: -------------------------------------------------------------------------------- 1 | use crate::property::{ 2 | Vector2D, WzLua, WzPng, WzRawData, WzSound, WzString, WzSubProperty, WzValue, WzVideo, 3 | }; 4 | use crate::{MsFile, MsImage, WzDirectory, WzFile, WzImage}; 5 | 6 | #[cfg(feature = "serde")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// All variants of `WzObjectType`. 10 | /// 11 | /// `WzObjectType` implement most of the From trait for the types that can be converted to it. 12 | /// 13 | /// # Example 14 | /// 15 | /// ``` 16 | /// # use wz_reader::WzObjectType; 17 | /// # use wz_reader::property::WzValue; 18 | /// let wz_int: WzObjectType = 1.into(); 19 | /// 20 | /// assert!(matches!(wz_int, WzObjectType::Value(WzValue::Int(1)))); 21 | /// ``` 22 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 23 | #[cfg_attr(feature = "serde", serde(tag = "type", content = "data"))] 24 | #[derive(Debug, Clone)] 25 | pub enum WzObjectType { 26 | File(Box), 27 | MsFile(Box), 28 | Image(Box), 29 | MsImage(Box), 30 | Directory(Box), 31 | #[cfg_attr(feature = "serde", serde(untagged))] 32 | Property(WzSubProperty), 33 | #[cfg_attr(feature = "serde", serde(untagged))] 34 | Value(WzValue), 35 | } 36 | 37 | macro_rules! from_impl_wz_files { 38 | ($from_type:ident, $ot_variant:ident) => { 39 | impl From<$from_type> for WzObjectType { 40 | fn from(i: $from_type) -> Self { 41 | WzObjectType::$ot_variant(Box::new(i)) 42 | } 43 | } 44 | }; 45 | } 46 | macro_rules! from_impl_wz_value { 47 | ($from_type:ident, $variant:ident) => { 48 | impl From<$from_type> for WzObjectType { 49 | fn from(i: $from_type) -> Self { 50 | WzObjectType::Value(WzValue::$variant(i)) 51 | } 52 | } 53 | }; 54 | } 55 | macro_rules! from_impl_wz_property { 56 | ($from_type:ident, $variant:ident) => { 57 | impl From<$from_type> for WzObjectType { 58 | fn from(i: $from_type) -> Self { 59 | WzObjectType::Property(WzSubProperty::$variant(Box::new(i))) 60 | } 61 | } 62 | }; 63 | } 64 | 65 | from_impl_wz_files!(WzFile, File); 66 | from_impl_wz_files!(WzDirectory, Directory); 67 | from_impl_wz_files!(WzImage, Image); 68 | from_impl_wz_files!(MsFile, MsFile); 69 | from_impl_wz_files!(MsImage, MsImage); 70 | 71 | from_impl_wz_value!(i16, Short); 72 | from_impl_wz_value!(i32, Int); 73 | from_impl_wz_value!(i64, Long); 74 | from_impl_wz_value!(f32, Float); 75 | from_impl_wz_value!(f64, Double); 76 | from_impl_wz_value!(WzString, String); 77 | from_impl_wz_value!(Vector2D, Vector); 78 | from_impl_wz_value!(WzRawData, RawData); 79 | from_impl_wz_value!(WzVideo, Video); 80 | from_impl_wz_value!(WzLua, Lua); 81 | 82 | from_impl_wz_property!(WzPng, PNG); 83 | from_impl_wz_property!(WzSound, Sound); 84 | 85 | #[cfg(feature = "serde")] 86 | #[cfg(test)] 87 | mod test { 88 | use super::*; 89 | 90 | #[cfg(feature = "serde")] 91 | use serde_json; 92 | 93 | #[cfg(feature = "serde")] 94 | #[test] 95 | fn test_serialize_main_wz_object() { 96 | let file: WzObjectType = WzFile::default().into(); 97 | let dir: WzObjectType = WzDirectory::default().into(); 98 | let image: WzObjectType = WzImage::default().into(); 99 | 100 | let file_json = serde_json::to_string(&file).unwrap(); 101 | let dir_json = serde_json::to_string(&dir).unwrap(); 102 | let image_json = serde_json::to_string(&image).unwrap(); 103 | 104 | assert_eq!( 105 | file_json, 106 | r#"{"type":"File","data":{"path":"","patch_version":0,"wz_version_header":0,"wz_with_encrypt_version_header":false,"hash":0}}"# 107 | ); 108 | assert_eq!(dir_json, r#"{"type":"Directory","data":{}}"#); 109 | assert_eq!(image_json, r#"{"type":"Image","data":{}}"#); 110 | } 111 | 112 | #[cfg(feature = "serde")] 113 | #[test] 114 | fn test_serialize_sub_and_value_wz_object() { 115 | let sub: WzObjectType = WzPng::default().into(); 116 | let value: WzObjectType = 1.into(); 117 | 118 | let sub_json = serde_json::to_string(&sub).unwrap(); 119 | let value_json = serde_json::to_string(&value).unwrap(); 120 | 121 | // not need to test every property, just need to check is actually using untagged 122 | assert_eq!(sub_json, r#"{"type":"PNG","data":{"width":0,"height":0}}"#); 123 | assert_eq!(value_json, r#"{"type":"Int","data":1}"#); 124 | } 125 | 126 | #[cfg(feature = "serde")] 127 | #[test] 128 | fn test_deserialize_main_wz_object() { 129 | let file_json = r#"{"type":"File","data":{"path":"","patch_version":0,"wz_version_header":0,"wz_with_encrypt_version_header":false,"hash":0}}"#; 130 | let dir_json = r#"{"type":"Directory","data":{}}"#; 131 | let image_json = r#"{"type":"Image","data":{}}"#; 132 | 133 | let file: WzObjectType = serde_json::from_str(file_json).unwrap(); 134 | let dir: WzObjectType = serde_json::from_str(dir_json).unwrap(); 135 | let image: WzObjectType = serde_json::from_str(image_json).unwrap(); 136 | 137 | assert!(matches!(file, WzObjectType::File(_))); 138 | assert!(matches!(dir, WzObjectType::Directory(_))); 139 | assert!(matches!(image, WzObjectType::Image(_))); 140 | } 141 | 142 | #[cfg(feature = "serde")] 143 | #[test] 144 | fn test_deserialize_sub_and_value_wz_object() { 145 | let sub_json = r#"{"type":"PNG","data":{"width":0,"height":0}}"#; 146 | let value_json = r#"{"type":"Int","data":1}"#; 147 | 148 | let sub: WzObjectType = serde_json::from_str(sub_json).unwrap(); 149 | let value: WzObjectType = serde_json::from_str(value_json).unwrap(); 150 | 151 | assert!(matches!(sub, WzObjectType::Property(_))); 152 | assert!(matches!(value, WzObjectType::Value(_))); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/util/wz_mutable_key.rs: -------------------------------------------------------------------------------- 1 | use super::maple_crypto_constants::{get_trimmed_user_key, MAPLESTORY_USERKEY_DEFAULT, WZ_MSEAIV}; 2 | use crate::reader::read_i32_at; 3 | use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, BlockSizeUser, KeyInit}; 4 | use aes::Aes256; 5 | 6 | const BATCH_SIZE: f64 = 4096_f64; 7 | 8 | /// A string decryption util. 9 | #[derive(Debug)] 10 | pub struct WzMutableKey { 11 | pub iv: [u8; 4], 12 | keys: Vec, 13 | aes_key: [u8; 32], 14 | /// iv == 0, without decrypt 15 | pub without_decrypt: bool, 16 | } 17 | 18 | impl WzMutableKey { 19 | pub fn new(iv: [u8; 4], aes_key: [u8; 32]) -> Self { 20 | Self { 21 | iv, 22 | keys: vec![], 23 | aes_key, 24 | without_decrypt: read_i32_at(&iv, 0).unwrap_or(0) == 0, 25 | } 26 | } 27 | pub fn new_lua() -> Self { 28 | Self { 29 | iv: WZ_MSEAIV, 30 | keys: vec![], 31 | aes_key: get_trimmed_user_key(&MAPLESTORY_USERKEY_DEFAULT), 32 | without_decrypt: false, 33 | } 34 | } 35 | pub fn from_iv(iv: [u8; 4]) -> Self { 36 | Self { 37 | iv, 38 | keys: vec![], 39 | aes_key: get_trimmed_user_key(&MAPLESTORY_USERKEY_DEFAULT), 40 | without_decrypt: read_i32_at(&iv, 0).unwrap_or(0) == 0, 41 | } 42 | } 43 | /// force get key at index, will expand key size if not enough. 44 | pub fn at(&mut self, index: usize) -> &u8 { 45 | if self.keys.len() <= index { 46 | self.ensure_key_size(index + 1).unwrap(); 47 | } 48 | &self.keys[index] 49 | } 50 | #[inline] 51 | /// get key at index, return `None` if doesn't exist. 52 | pub fn try_at(&self, index: usize) -> Option<&u8> { 53 | self.keys.get(index) 54 | } 55 | #[inline] 56 | pub fn get_range(&self, range: std::ops::Range) -> &[u8] { 57 | &self.keys[range] 58 | } 59 | #[inline] 60 | pub fn is_enough(&self, size: usize) -> bool { 61 | self.keys.len() >= size 62 | } 63 | /// decrypt data in place, make sure has enough key size. 64 | pub fn decrypt_slice(&self, data: &mut [u8]) { 65 | if self.without_decrypt { 66 | return; 67 | } 68 | let keys = &self.keys[0..data.len()]; 69 | data.iter_mut() 70 | .zip(keys) 71 | .for_each(|(byte, key)| *byte ^= key); 72 | } 73 | /// ensure keys has enough size to do decryption 74 | pub fn ensure_key_size(&mut self, size: usize) -> Result<(), String> { 75 | if self.is_enough(size) || self.without_decrypt { 76 | return Ok(()); 77 | } 78 | 79 | let size = (((size as f64) / BATCH_SIZE).ceil() * BATCH_SIZE) as usize; 80 | 81 | if self.keys.capacity() < size { 82 | self.keys.reserve(size - self.keys.capacity()); 83 | } 84 | 85 | // initialize the first block 86 | if self.keys.is_empty() { 87 | self.keys.resize(32, 0); 88 | 89 | let mut block = [0_u8; 16]; 90 | for (index, item) in block.iter_mut().enumerate() { 91 | *item = self.iv[index % 4]; 92 | } 93 | ecb::Encryptor::::new(&self.aes_key.into()) 94 | .encrypt_padded_b2b_mut::(&block, &mut self.keys) 95 | .map_err(|_| "Failed to encrypt block")?; 96 | 97 | self.keys.truncate(16); 98 | } 99 | 100 | let start_index = self.keys.len(); 101 | 102 | // fill enouth 0 for later, 103 | if self.keys.len() < size { 104 | // + 16 is prevent encryption not enough to padding 105 | self.keys.resize(size + 16, 0); 106 | } 107 | 108 | let block_size = aes::Aes256::block_size(); 109 | 110 | for i in (start_index..size).step_by(16) { 111 | let (in_buf, out_buf) = self.keys.split_at_mut(i); 112 | ecb::Encryptor::::new(&self.aes_key.into()) 113 | // im not sure why this will actually append block_size * 2 to out_buf, so will be trimed at the end 114 | .encrypt_padded_b2b_mut::(&in_buf[i - block_size..], out_buf) 115 | .map_err(|_| "Failed to encrypt block")?; 116 | } 117 | 118 | if self.keys.len() > size { 119 | self.keys.truncate(size); 120 | } 121 | 122 | Ok(()) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod test { 128 | use super::*; 129 | 130 | #[test] 131 | fn test_expand_key() { 132 | let mut key = WzMutableKey::new_lua(); 133 | 134 | assert!(key.ensure_key_size(16).is_ok()); 135 | assert_eq!(key.keys.len(), 4096); 136 | 137 | assert!(key.ensure_key_size(4200).is_ok()); 138 | assert_eq!(key.keys.len(), 4096 * 2); 139 | 140 | assert!(key.ensure_key_size(4096 * 4 + 5).is_ok()); 141 | assert_eq!(key.keys.len(), 4096 * 5); 142 | } 143 | 144 | #[test] 145 | fn test_force_at() { 146 | let mut key = WzMutableKey::new_lua(); 147 | 148 | let _ = key.at(1); 149 | 150 | assert_eq!(key.keys.len(), 4096); 151 | 152 | let _ = key.at(4000); 153 | 154 | assert_eq!(key.keys.len(), 4096); 155 | 156 | let _ = key.at(4097); 157 | 158 | assert_eq!(key.keys.len(), 4096 * 2); 159 | } 160 | 161 | #[test] 162 | fn test_at() { 163 | let mut key = WzMutableKey::new_lua(); 164 | 165 | assert!(key.try_at(1).is_none()); 166 | assert!(key.try_at(100).is_none()); 167 | assert!(key.try_at(10000).is_none()); 168 | 169 | assert!(key.ensure_key_size(10000).is_ok()); 170 | 171 | assert!(key.try_at(1).is_some()); 172 | assert!(key.try_at(100).is_some()); 173 | assert!(key.try_at(10000).is_some()); 174 | assert!(key.try_at(20000).is_none()); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/util/resolver.rs: -------------------------------------------------------------------------------- 1 | use crate::{version::WzMapleVersion, SharedWzMutableKey, WzNode, WzNodeArc, WzNodeCast}; 2 | use std::fs::DirEntry; 3 | use std::io; 4 | use std::path::Path; 5 | 6 | /// Get a wz file path by directory, like `Map` -> `Map/Map.wz`. 7 | pub fn get_root_wz_file_path(dir: &DirEntry) -> Option { 8 | let dir_name = dir.file_name(); 9 | let mut inner_wz_name = dir_name.to_str().unwrap().to_string(); 10 | inner_wz_name.push_str(".wz"); 11 | let inner_wz_path = dir.path().join(inner_wz_name); 12 | 13 | if inner_wz_path.try_exists().is_ok() { 14 | return Some(inner_wz_path.to_str().unwrap().to_string()); 15 | } 16 | 17 | None 18 | } 19 | 20 | /// Resolve series of wz files in a directory, and merge *_nnn.wz files into one WzFile. 21 | pub fn resolve_root_wz_file_dir_full( 22 | dir: impl AsRef, 23 | version: Option, 24 | patch_version: Option, 25 | parent: Option<&WzNodeArc>, 26 | default_keys: Option<&SharedWzMutableKey>, 27 | ) -> Result { 28 | let root_node: WzNodeArc = 29 | WzNode::from_wz_file_full(&dir, version, patch_version, parent, default_keys) 30 | .unwrap() 31 | .into(); 32 | let wz_dir = dir.as_ref().parent().unwrap(); 33 | 34 | { 35 | let mut root_node_write = root_node.write().unwrap(); 36 | 37 | root_node_write.parse(&root_node).unwrap(); 38 | 39 | for entry in wz_dir.read_dir()? { 40 | let entry = entry?; 41 | let file_type = entry.file_type()?; 42 | let name = entry.file_name(); 43 | 44 | if file_type.is_dir() && root_node_write.at(name.to_str().unwrap()).is_some() { 45 | if let Some(file_path) = get_root_wz_file_path(&entry) { 46 | let dir_node = resolve_root_wz_file_dir_full( 47 | &file_path, 48 | version, 49 | patch_version, 50 | Some(&root_node), 51 | default_keys, 52 | )?; 53 | 54 | /* replace the original one */ 55 | root_node_write 56 | .children 57 | .insert(name.to_str().unwrap().into(), dir_node); 58 | } 59 | } else if file_type.is_file() { 60 | // check is XXX_nnn.wz 61 | let file_path = entry.path(); 62 | let file_name = file_path.file_stem().unwrap().to_str().unwrap(); 63 | 64 | let splited = file_name.split('_').collect::>(); 65 | 66 | if splited.len() < 2 { 67 | continue; 68 | } 69 | 70 | if splited.last().unwrap().parse::().is_err() { 71 | continue; 72 | } 73 | 74 | let node = WzNode::from_wz_file_full( 75 | file_path.to_str().unwrap(), 76 | version, 77 | patch_version, 78 | None, 79 | default_keys, 80 | ) 81 | .unwrap() 82 | .into_lock(); 83 | 84 | let mut node_write = node.write().unwrap(); 85 | 86 | if node_write.parse(&root_node).is_ok() { 87 | root_node_write.children.reserve(node_write.children.len()); 88 | for (name, child) in node_write.children.drain() { 89 | root_node_write.children.insert(name, child); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | Ok(root_node) 97 | } 98 | 99 | /// resolve_root_wz_file_dir_full with less arguments for easier use 100 | pub fn resolve_root_wz_file_dir( 101 | dir: impl AsRef, 102 | parent: Option<&WzNodeArc>, 103 | ) -> Result { 104 | resolve_root_wz_file_dir_full(dir, None, None, parent, None) 105 | } 106 | 107 | /// Construct `WzNode` tree from `Base.wz` 108 | pub fn resolve_base( 109 | path: impl AsRef, 110 | version: Option, 111 | ) -> Result { 112 | if !path.as_ref().ends_with("Base.wz") { 113 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a Base.wz")); 114 | } 115 | 116 | let base_node = resolve_root_wz_file_dir_full(&path, version, None, None, None)?; 117 | 118 | let (patch_version, keys) = { 119 | let node_read = base_node.read().unwrap(); 120 | let file = node_read.try_as_file().unwrap(); 121 | 122 | // reusing the keys from Base.wz 123 | (file.wz_file_meta.patch_version, file.reader.keys.clone()) 124 | }; 125 | 126 | { 127 | let mut base_write = base_node.write().unwrap(); 128 | 129 | let first_parent = path.as_ref().parent().unwrap(); 130 | 131 | // if a Base.wz in under Base folder, then should up to parent to find Map, Item and other stuff 132 | // if not, the other stuff just at same folder as Base.wz 133 | let wz_root_path = if first_parent.file_stem().unwrap() == "Base" { 134 | first_parent.parent().unwrap() 135 | } else { 136 | first_parent 137 | }; 138 | 139 | for item in wz_root_path.read_dir()? { 140 | let dir = item?; 141 | let path = dir.path(); 142 | let file_name = path.file_stem().unwrap(); 143 | 144 | let is_valid = path.extension().unwrap_or_default() == "wz" || path.is_dir(); 145 | 146 | // we only allow the thing is listed in Base.wz 147 | let has_dir = base_write.at(file_name.to_str().unwrap()).is_some(); 148 | 149 | if has_dir && is_valid { 150 | let wz_path = if dir.file_type()?.is_dir() { 151 | get_root_wz_file_path(&dir) 152 | } else { 153 | Some(path.to_str().unwrap().to_string()) 154 | }; 155 | 156 | if let Some(file_path) = wz_path { 157 | let dir_node = resolve_root_wz_file_dir_full( 158 | &file_path, 159 | version, 160 | Some(patch_version), 161 | Some(&base_node), 162 | Some(&keys), 163 | // None, 164 | )?; 165 | 166 | /* replace the original one */ 167 | base_write 168 | .children 169 | .insert(file_name.to_str().unwrap().into(), dir_node); 170 | } 171 | } 172 | } 173 | } 174 | 175 | Ok(base_node) 176 | } 177 | -------------------------------------------------------------------------------- /src/directory.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | reader, Reader, WzImage, WzNode, WzNodeArc, WzNodeArcVec, WzNodeName, WzObjectType, WzReader, 3 | }; 4 | use std::sync::Arc; 5 | 6 | #[cfg(feature = "serde")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, thiserror::Error)] 10 | pub enum Error { 11 | #[error("Lua parse error")] 12 | LuaParseError, 13 | #[error("parse as wz image failed, pos at {0}")] 14 | ParseError(usize), 15 | #[error("New Wz image header found. b = {0}, offset = {1}")] 16 | UnknownWzDirectoryType(u8, usize), 17 | #[error("Invalid wz version used for decryption, try parsing other version numbers.")] 18 | InvalidWzVersion, 19 | #[error("Entry count overflow, Invalid wz version used for decryption, try parsing other version numbers.")] 20 | InvalidEntryCount, 21 | #[error("Binary reading error")] 22 | ReaderError(#[from] reader::Error), 23 | } 24 | 25 | #[derive(Debug)] 26 | enum WzDirectoryType { 27 | UnknownType, 28 | /// directory type and name maybe at some where alse 29 | RetrieveStringFromOffset, 30 | WzDirectory, 31 | WzImage, 32 | NewUnknownType, 33 | } 34 | 35 | fn get_wz_directory_type_from_byte(byte: u8) -> WzDirectoryType { 36 | match byte { 37 | 1 => WzDirectoryType::UnknownType, 38 | 2 => WzDirectoryType::RetrieveStringFromOffset, 39 | 3 => WzDirectoryType::WzDirectory, 40 | 4 => WzDirectoryType::WzImage, 41 | _ => WzDirectoryType::NewUnknownType, 42 | } 43 | } 44 | 45 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 46 | #[derive(Debug, Clone, Default)] 47 | pub struct WzDirectory { 48 | #[cfg_attr(feature = "serde", serde(skip))] 49 | pub reader: Arc, 50 | #[cfg_attr(feature = "serde", serde(skip))] 51 | pub offset: usize, 52 | #[cfg_attr(feature = "serde", serde(skip))] 53 | pub block_size: usize, 54 | #[cfg_attr(feature = "serde", serde(skip))] 55 | pub hash: usize, 56 | #[cfg_attr(feature = "serde", serde(skip))] 57 | pub is_parsed: bool, 58 | } 59 | 60 | impl WzDirectory { 61 | pub fn new(offset: usize, block_size: usize, reader: &Arc, is_parsed: bool) -> Self { 62 | Self { 63 | reader: reader.clone(), 64 | offset, 65 | block_size, 66 | hash: 0, 67 | is_parsed, 68 | } 69 | } 70 | pub fn with_hash(mut self, hash: usize) -> Self { 71 | self.hash = hash; 72 | self 73 | } 74 | 75 | pub fn verify_hash(&self) -> Result<(), Error> { 76 | let reader = self.reader.create_slice_reader(); 77 | 78 | reader.seek(self.offset); 79 | 80 | let entry_count = reader.read_wz_int()?; 81 | 82 | if !(0..=1000000).contains(&entry_count) { 83 | return Err(Error::InvalidEntryCount); 84 | } 85 | 86 | for _ in 0..entry_count { 87 | let dir_byte = reader.read_u8()?; 88 | let dir_type = get_wz_directory_type_from_byte(dir_byte); 89 | 90 | match dir_type { 91 | WzDirectoryType::UnknownType => { 92 | reader.skip(4 + 4 + 2); 93 | continue; 94 | } 95 | WzDirectoryType::RetrieveStringFromOffset => { 96 | // skip read string offset 97 | reader.skip(4); 98 | } 99 | WzDirectoryType::WzDirectory | WzDirectoryType::WzImage => { 100 | reader.read_wz_string()?; 101 | } 102 | WzDirectoryType::NewUnknownType => { 103 | return Err(Error::UnknownWzDirectoryType(dir_byte, reader.pos.get())) 104 | } 105 | } 106 | 107 | let fsize = reader.read_wz_int()?; 108 | reader.read_wz_int()?; 109 | let offset = reader.read_wz_offset(self.hash, None)?; 110 | let buf_start = offset; 111 | 112 | let buf_end = buf_start + fsize as usize; 113 | 114 | if !reader.is_valid_pos(buf_end) { 115 | return Err(Error::InvalidWzVersion); 116 | } 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | pub fn resolve_children(&self, parent: &WzNodeArc) -> Result { 123 | let reader = self.reader.create_slice_reader(); 124 | 125 | reader.seek(self.offset); 126 | 127 | let entry_count = reader.read_wz_int()?; 128 | 129 | if !(0..=1000000).contains(&entry_count) { 130 | return Err(Error::InvalidEntryCount); 131 | } 132 | 133 | let mut nodes: WzNodeArcVec = Vec::with_capacity(entry_count as usize); 134 | 135 | for _ in 0..entry_count { 136 | let dir_byte = reader.read_u8()?; 137 | let mut dir_type = get_wz_directory_type_from_byte(dir_byte); 138 | 139 | let fname: WzNodeName; 140 | 141 | match dir_type { 142 | WzDirectoryType::UnknownType => { 143 | /* unknown, just skip this chunk */ 144 | reader.skip(4 + 4 + 2); 145 | continue; 146 | } 147 | WzDirectoryType::RetrieveStringFromOffset => { 148 | let str_offset = reader.read_i32()?; 149 | 150 | let offset = reader.header.fstart + str_offset as usize; 151 | 152 | dir_type = get_wz_directory_type_from_byte(reader.read_u8_at(offset)?); 153 | fname = reader.read_wz_string_at_offset(offset + 1)?.into(); 154 | } 155 | WzDirectoryType::WzDirectory | WzDirectoryType::WzImage => { 156 | fname = reader.read_wz_string()?.into(); 157 | } 158 | WzDirectoryType::NewUnknownType => { 159 | println!("NewUnknownType: {}", dir_byte); 160 | return Err(Error::UnknownWzDirectoryType(dir_byte, reader.pos.get())); 161 | } 162 | } 163 | 164 | let fsize = reader.read_wz_int()?; 165 | let _checksum = reader.read_wz_int()?; 166 | let offset = reader.read_wz_offset(self.hash, None)?; 167 | let buf_start = offset; 168 | 169 | let buf_end = buf_start + fsize as usize; 170 | 171 | if !reader.is_valid_pos(buf_end) { 172 | return Err(Error::InvalidWzVersion); 173 | } 174 | 175 | match dir_type { 176 | WzDirectoryType::WzDirectory => { 177 | let wz_dir = WzDirectory::new(offset, fsize as usize, &self.reader, false) 178 | .with_hash(self.hash); 179 | 180 | let obj_node = WzNode::new(&fname, wz_dir, Some(parent)); 181 | 182 | nodes.push((fname, obj_node.into_lock())); 183 | } 184 | WzDirectoryType::WzImage => { 185 | let wz_image = WzImage::new(&fname, offset, fsize as usize, &self.reader); 186 | 187 | let obj_node = WzNode::new(&fname, wz_image, Some(parent)); 188 | 189 | nodes.push((fname, obj_node.into_lock())); 190 | } 191 | _ => { 192 | // should never be here 193 | } 194 | } 195 | } 196 | 197 | for (_, node) in nodes.iter() { 198 | let mut write = node.write().unwrap(); 199 | if let WzObjectType::Directory(dir) = &mut write.object_type { 200 | let children = dir.resolve_children(node)?; 201 | 202 | for (name, child) in children { 203 | write.children.insert(name, child); 204 | } 205 | } 206 | } 207 | 208 | Ok(nodes) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/ms/ms_image.rs: -------------------------------------------------------------------------------- 1 | use crate::{WzImage, WzReader}; 2 | use c2_chacha::stream_cipher::{NewStreamCipher, SyncStreamCipher}; 3 | use c2_chacha::Ietf; 4 | use std::cmp; 5 | use std::sync::Arc; 6 | 7 | use super::chacha20_reader::{MS_CHACHA20_KEY_BASE, MS_CHACHA20_KEY_SIZE, MS_CHACHA20_NONCE_SIZE}; 8 | use super::snow2_decryptor::Snow2Decryptor; 9 | 10 | #[cfg(feature = "serde")] 11 | use serde::{Deserialize, Serialize}; 12 | #[derive(Debug, Clone, Default)] 13 | pub struct MsEntryMeta { 14 | pub key_salt: String, 15 | pub entry_name: String, 16 | pub check_sum: i32, 17 | pub flags: i32, 18 | pub start_pos: i32, 19 | pub size: i32, 20 | pub size_aligned: i32, 21 | pub unk1: i32, 22 | pub unk2: i32, 23 | pub entry_key: [u8; 16], 24 | pub unk3: i32, // for ms file v2 25 | pub unk4: i32, // for ms file v2 26 | pub version: u8, 27 | } 28 | 29 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 30 | #[cfg_attr(feature = "serde", serde(default))] 31 | #[derive(Debug, Clone, Default)] 32 | pub struct MsImage { 33 | #[cfg_attr(feature = "serde", serde(skip))] 34 | pub reader: Arc, 35 | #[cfg_attr(feature = "serde", serde(skip))] 36 | pub offset: usize, 37 | #[cfg_attr(feature = "serde", serde(skip))] 38 | pub block_size: usize, 39 | #[cfg_attr(feature = "serde", serde(skip))] 40 | pub meta: MsEntryMeta, 41 | } 42 | 43 | impl MsImage { 44 | pub fn new(meta: MsEntryMeta, reader: &Arc) -> Self { 45 | Self { 46 | reader: Arc::clone(reader), 47 | offset: meta.start_pos as usize, 48 | block_size: meta.size_aligned as usize, 49 | meta, 50 | } 51 | } 52 | 53 | /// make a WzImage from the MsImage, this process will allocate a new buffer instead of using MsFile's buffer 54 | pub fn to_wz_image(&self) -> WzImage { 55 | match self.meta.version { 56 | 1 => self.to_wz_image_v1(), 57 | 2 => self.to_wz_image_v2(), 58 | _ => panic!("Invalid MsImage version: {}", self.meta.version), 59 | } 60 | } 61 | 62 | fn to_wz_image_v1(&self) -> WzImage { 63 | // calc snow key for entry 64 | let mut key_hash = 0x811C9DC5; 65 | for b in self.meta.key_salt.chars() { 66 | key_hash = (key_hash ^ b as u32).wrapping_mul(0x1000193); 67 | } 68 | 69 | // extract each digit from key_hash, like 1234 -> [1,2,3,4] 70 | let key_hash_digits: Vec = key_hash.to_string().chars().map(|c| c as u8 - 48).collect(); 71 | 72 | let mut img_key = [0_u8; 16]; 73 | 74 | let bytes = self.meta.entry_name.as_bytes(); 75 | 76 | for i in 0..16 { 77 | let char = bytes[i % bytes.len()]; 78 | let digit = key_hash_digits[i % key_hash_digits.len()] % 2; 79 | let digit2 = (key_hash_digits[(i + 1) % key_hash_digits.len()] + i as u8) % 5; 80 | let ekey_idx = key_hash_digits[(i + 2) % key_hash_digits.len()] + i as u8; 81 | let ekey = self.meta.entry_key[(ekey_idx % self.meta.entry_key.len() as u8) as usize]; 82 | 83 | // it kinda hard to read 84 | // i + char * (digit + ekey + digit2) 85 | img_key[i] = (i as u8) 86 | .wrapping_add(char.wrapping_mul(digit.wrapping_add(ekey).wrapping_add(digit2))); 87 | } 88 | 89 | let mut image_buffer = self 90 | .reader 91 | .get_slice(self.offset..self.offset + self.block_size) 92 | .to_owned(); 93 | 94 | // decrypt initial 1024 bytes twice 95 | { 96 | let mut snow_decryptor = Snow2Decryptor::new(img_key); 97 | let min_len = cmp::min(1024, image_buffer.len()); 98 | snow_decryptor.decrypt_slice(&mut image_buffer[..min_len]); 99 | } 100 | 101 | let mut snow_decryptor = Snow2Decryptor::new(img_key); 102 | 103 | snow_decryptor.decrypt_slice(&mut image_buffer[..]); 104 | 105 | // create a new allocate reader,but only grab the size of the image, not size_aligned 106 | let image_reader = WzReader::from_buff(&image_buffer[..self.meta.size as usize]); 107 | 108 | WzImage { 109 | reader: Arc::new(image_reader), 110 | name: self.meta.entry_name.clone().into(), 111 | offset: 0, 112 | block_size: self.meta.size as usize, 113 | is_parsed: false, 114 | } 115 | } 116 | 117 | fn to_wz_image_v2(&self) -> WzImage { 118 | // calc chacha20 key for entry 119 | let mut key_hash: u32 = 0x811C9DC5; 120 | 121 | for b in self.meta.key_salt.chars() { 122 | key_hash = (key_hash ^ b as u32).wrapping_mul(0x1000193); 123 | } 124 | let key_hash2 = key_hash >> 1; 125 | let key_hash3 = key_hash2 ^ 0x6C; 126 | let _key_hash4 = key_hash3 << 2; // not used 127 | // extract each digit from key_hash, like 1234 -> [1,2,3,4] 128 | let key_hash_digits: Vec = key_hash.to_string().chars().map(|c| c as u8 - 48).collect(); 129 | 130 | // key 131 | let mut img_key = [0_u8; MS_CHACHA20_KEY_SIZE]; 132 | for i in 0..MS_CHACHA20_KEY_SIZE { 133 | let char = self 134 | .meta 135 | .entry_name 136 | .chars() 137 | .nth(i % self.meta.entry_name.len()) 138 | .unwrap() as u8; 139 | let digit = key_hash_digits[i % key_hash_digits.len()] % 2; 140 | 141 | let ekey_idx = ((key_hash_digits[(i + 2) % key_hash_digits.len()] + i as u8) 142 | % self.meta.entry_key.len() as u8) as usize; 143 | let ekey = self.meta.entry_key[ekey_idx]; 144 | 145 | let digit2 = (key_hash_digits[(i + 1) % key_hash_digits.len()] + i as u8) % 5; 146 | 147 | img_key[i] = (i as u8) 148 | .wrapping_add(char.wrapping_mul(digit.wrapping_add(ekey).wrapping_add(digit2))); 149 | img_key[i] ^= MS_CHACHA20_KEY_BASE[i]; 150 | } 151 | 152 | // nonce and counter 153 | let mut key_hash_data = [0_u8; 12]; 154 | key_hash_data[0..4].copy_from_slice(&key_hash.to_le_bytes()); 155 | key_hash_data[4..8].copy_from_slice(&key_hash2.to_le_bytes()); 156 | key_hash_data[8..12].copy_from_slice(&key_hash3.to_le_bytes()); 157 | 158 | let (mut a, mut b, mut c, mut d) = (0_i32, 0_i32, 90_i32, 0_i32); 159 | for i in 0..12 { 160 | key_hash_data[i as usize] ^= 161 | (d + (11 * (i as u32 / 11) as i32) + (c ^ (i as u32 >> 2) as i32) + (a ^ b)) as u8; 162 | d = d.wrapping_sub(1); 163 | a = a.wrapping_add(8); 164 | b = b.wrapping_add(17); 165 | c = c.wrapping_add(43); 166 | } 167 | let mut nonce = [0_u8; MS_CHACHA20_NONCE_SIZE]; 168 | nonce[4..].copy_from_slice(&key_hash_data[0..8]); 169 | let counter = u32::from_le_bytes(key_hash_data[8..12].try_into().unwrap()); 170 | 171 | let mut image_buffer = self 172 | .reader 173 | .get_slice(self.offset..self.offset + self.block_size) 174 | .to_owned(); 175 | 176 | // only decrypt initial 1024 bytes 177 | { 178 | let mut chacha20_cipher = Ietf::new_var(&img_key, &nonce).unwrap(); 179 | chacha20_cipher 180 | .state 181 | .state 182 | .set_stream_param(0, counter as u64); 183 | let min_len = cmp::min(1024, image_buffer.len()); 184 | chacha20_cipher.apply_keystream(&mut image_buffer[..min_len]); 185 | } 186 | 187 | // create a new allocate reader,but only grab the size of the image, not size_aligned 188 | let image_reader = WzReader::from_buff(&image_buffer[..self.meta.size as usize]); 189 | 190 | WzImage { 191 | reader: Arc::new(image_reader), 192 | name: self.meta.entry_name.clone().into(), 193 | offset: 0, 194 | block_size: self.meta.size as usize, 195 | is_parsed: false, 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/wz_image.rs: -------------------------------------------------------------------------------- 1 | use crate::property::{WzLua, WzRawData}; 2 | use crate::version::{guess_iv_from_wz_img, verify_iv_from_wz_img}; 3 | use crate::{reader, util, WzNode, WzNodeArc, WzNodeArcVec, WzNodeName, WzReader}; 4 | use std::sync::Arc; 5 | 6 | #[cfg(feature = "serde")] 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, thiserror::Error)] 10 | pub enum Error { 11 | #[error(transparent)] 12 | FileError(#[from] std::io::Error), 13 | #[error("Lua parse error")] 14 | LuaParseError, 15 | #[error("parse as wz image failed, pos at {0}")] 16 | ParseError(usize), 17 | #[error("New Wz image header found. b = {0}, offset = {1}")] 18 | UnknownImageHeader(u8, usize), 19 | #[error("Wrong Wz Version")] 20 | WrongVersion, 21 | #[error("Unable to guess version")] 22 | UnableToGuessVersion, 23 | #[error(transparent)] 24 | ParsePropertyListError(#[from] util::WzPropertyParseError), 25 | #[error("Binary reading error")] 26 | ReaderError(#[from] reader::Error), 27 | #[error("Not a Image object")] 28 | NotImageObject, 29 | } 30 | 31 | pub const WZ_IMAGE_HEADER_BYTE_WITHOUT_OFFSET: u8 = 0x73; 32 | pub const WZ_IMAGE_HEADER_BYTE_WITH_OFFSET: u8 = 0x1B; 33 | 34 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 35 | #[cfg_attr(feature = "serde", serde(default))] 36 | #[derive(Debug, Clone, Default)] 37 | pub struct WzImage { 38 | #[cfg_attr(feature = "serde", serde(skip))] 39 | pub reader: Arc, 40 | #[cfg_attr(feature = "serde", serde(skip))] 41 | pub name: WzNodeName, 42 | #[cfg_attr(feature = "serde", serde(skip))] 43 | pub offset: usize, 44 | #[cfg_attr(feature = "serde", serde(skip))] 45 | pub block_size: usize, 46 | #[cfg_attr(feature = "serde", serde(skip))] 47 | pub is_parsed: bool, 48 | } 49 | 50 | impl WzImage { 51 | pub fn new( 52 | name: &WzNodeName, 53 | offset: usize, 54 | block_size: usize, 55 | reader: &Arc, 56 | ) -> Self { 57 | Self { 58 | reader: Arc::clone(reader), 59 | name: name.clone(), 60 | offset, 61 | block_size, 62 | is_parsed: false, 63 | } 64 | } 65 | pub fn from_file

(path: P, wz_iv: Option<[u8; 4]>) -> Result 66 | where 67 | P: AsRef, 68 | { 69 | let name = path 70 | .as_ref() 71 | .file_name() 72 | .unwrap() 73 | .to_str() 74 | .unwrap() 75 | .to_string(); 76 | let file = std::fs::File::open(path)?; 77 | let map = unsafe { memmap2::Mmap::map(&file)? }; 78 | 79 | let wz_iv = if let Some(iv) = wz_iv { 80 | if !verify_iv_from_wz_img(&map, &iv) { 81 | return Err(Error::WrongVersion); 82 | } 83 | iv 84 | } else { 85 | guess_iv_from_wz_img(&map).ok_or(Error::UnableToGuessVersion)? 86 | }; 87 | 88 | let block_size = map.len(); 89 | let reader = WzReader::new(map).with_iv(wz_iv); 90 | 91 | Ok(WzImage { 92 | reader: Arc::new(reader), 93 | name: name.into(), 94 | offset: 0, 95 | block_size, 96 | is_parsed: false, 97 | }) 98 | } 99 | 100 | /// Direct get child node inside `WzImage` without parsing the whole `WzImage`. Sometimes 101 | /// we just need a single node in `WzImage`, but don't want to parse it and 102 | /// unparse later, it waste time and memory. 103 | pub fn at_path(&self, path: &str) -> Result { 104 | let reader = self.reader.create_slice_reader_without_hash(); 105 | 106 | reader.seek(self.offset); 107 | let header_byte = reader.read_u8()?; 108 | 109 | if header_byte != WZ_IMAGE_HEADER_BYTE_WITHOUT_OFFSET { 110 | return Err(Error::UnknownImageHeader(header_byte, reader.pos.get())); 111 | } else { 112 | let name = reader.read_wz_string()?; 113 | let value = reader.read_u16()?; 114 | if name != "Property" && value != 0 { 115 | return Err(Error::ParseError(reader.pos.get())); 116 | } 117 | } 118 | 119 | let result = util::get_node(path, &self.reader, &reader, self.offset); 120 | 121 | match result { 122 | Ok(node) => Ok(node.1), 123 | Err(e) => Err(Error::from(e)), 124 | } 125 | } 126 | 127 | /// Parse the whole `WzImage` and return all children nodes. It can be directly used when you want to keep the original order of children. 128 | pub fn resolve_children( 129 | &self, 130 | parent: Option<&WzNodeArc>, 131 | ) -> Result<(WzNodeArcVec, Vec), Error> { 132 | let reader = self.reader.create_slice_reader_without_hash(); 133 | 134 | reader.seek(self.offset); 135 | 136 | let header_byte = reader.read_u8()?; 137 | 138 | // there also has json, atlas, etc. but they all using WzString to store it, this guy is just raw text 139 | if self.name.ends_with(".txt") { 140 | let name: WzNodeName = String::from("text.txt").into(); 141 | 142 | let wz_raw_data = WzRawData::new(&self.reader, self.offset, self.block_size); 143 | let raw_data_node = WzNode::new(&name, wz_raw_data, parent); 144 | 145 | return Ok((vec![(name, raw_data_node.into_lock())], vec![])); 146 | } 147 | 148 | match header_byte { 149 | 0x1 => { 150 | if self.name.ends_with(".lua") { 151 | let len = reader.read_wz_int()?; 152 | let offset = reader.pos.get(); 153 | 154 | let name: WzNodeName = String::from("Script").into(); 155 | 156 | let wz_lua = WzLua::new(&self.reader, offset, len as usize); 157 | 158 | let lua_node = WzNode::new(&name, wz_lua, parent); 159 | 160 | return Ok((vec![(name, lua_node.into_lock())], vec![])); 161 | } 162 | return Err(Error::LuaParseError); 163 | } 164 | // a weird format raw data, it store pure text and start by "#Property" 165 | // inside the data, it looks like some kind of json 166 | // key = { key = value, key = value, kv = { key = value, key = value } } 167 | 35 => { 168 | let property_string = String::from_iter( 169 | reader 170 | .get_slice(self.offset..self.offset + 9) 171 | .iter() 172 | .map(|&b| b as char), 173 | ); 174 | if property_string != "#Property" { 175 | return Err(Error::UnknownImageHeader(header_byte, reader.pos.get())); 176 | } 177 | let name: WzNodeName = String::from("text.txt").into(); 178 | let wz_raw_data = 179 | WzRawData::new(&self.reader, self.offset + 9, self.block_size - 9); 180 | let raw_data_node = WzNode::new(&name, wz_raw_data, parent); 181 | 182 | return Ok((vec![(name, raw_data_node.into_lock())], vec![])); 183 | } 184 | WZ_IMAGE_HEADER_BYTE_WITHOUT_OFFSET => { 185 | let name = reader.read_wz_string()?; 186 | let value = reader.read_u16()?; 187 | if name != "Property" && value != 0 { 188 | return Err(Error::WrongVersion); 189 | } 190 | } 191 | _ => { 192 | return Err(Error::UnknownImageHeader(header_byte, reader.pos.get())); 193 | } 194 | } 195 | 196 | util::parse_property_list(parent, &self.reader, &reader, self.offset).map_err(Error::from) 197 | } 198 | } 199 | 200 | pub fn is_lua_image(name: &str) -> bool { 201 | name.ends_with(".lua") 202 | } 203 | pub fn is_valid_wz_image(check_byte: u8) -> bool { 204 | check_byte == WZ_IMAGE_HEADER_BYTE_WITH_OFFSET 205 | || check_byte == WZ_IMAGE_HEADER_BYTE_WITHOUT_OFFSET 206 | } 207 | -------------------------------------------------------------------------------- /src/ms/header.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use crate::reader::{self, Reader, WzReader}; 3 | use scroll::{Pread, LE}; 4 | 5 | use super::chacha20_reader::{ 6 | ChaCha20Reader, MS_CHACHA20_KEY_BASE, MS_CHACHA20_KEY_SIZE, MS_CHACHA20_NONCE_SIZE, 7 | MS_CHACHA20_VERSION, 8 | }; 9 | use super::snow2_decryptor::Snow2Decryptor; 10 | use super::snow2_reader::{MS_SNOW2_KEY_SIZE, MS_SNOW2_VERSION}; 11 | use super::utils::{get_ascii_file_name, sum_str}; 12 | 13 | #[cfg(feature = "serde")] 14 | use serde::{Deserialize, Serialize}; 15 | 16 | #[derive(Debug, thiserror::Error)] 17 | pub enum Error { 18 | #[error("Error reading binary")] 19 | ReaderError(#[from] reader::Error), 20 | #[error("Error reading binary: {0}")] 21 | ReadError(#[from] scroll::Error), 22 | #[error("Unsupported snow version, expected 2 but got {0}")] 23 | UnsupportedSnowVersion(u8), 24 | #[error("Unsupported chacha20 version, expected 4 but got {0}")] 25 | UnsupportedChacha20Version(u8), 26 | #[error("Hash mismatch, expected {0} but got {1}")] 27 | HashMismatch(i32, i32), 28 | } 29 | 30 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 31 | #[derive(Debug, Clone, Default)] 32 | pub struct MsHeader { 33 | // name: String, 34 | pub key_salt: String, 35 | pub name_with_salt: String, 36 | pub header_hash: i32, 37 | // snow version, it should only be 2 currently 38 | pub version: u8, 39 | pub entry_count: i32, 40 | 41 | // header start 42 | pub hstart: usize, 43 | // entry start 44 | pub estart: usize, 45 | 46 | pub ms_file_version: u8, 47 | } 48 | 49 | impl MsHeader { 50 | pub fn from_ms_file

(path: P, reader: &WzReader) -> Result 51 | where 52 | P: AsRef, 53 | { 54 | let file_name = get_ascii_file_name(path); 55 | 56 | let mut offset = 0; 57 | 58 | // all the code is from https://github.com/Kagamia/WzComparerR2/pull/271/files#diff-d0d53b2411f7d680fb0c7c32bbf10138be0f7e662555cbc28d27353fbd2741d0 59 | // 1. random bytes 60 | let rand_byte_count = sum_str(&file_name) % 312 + 30; 61 | let rand_bytes = reader.get_slice(offset..rand_byte_count); 62 | offset += rand_byte_count; 63 | 64 | // 2. encrypted snowKeySalt 65 | let hashed_salt_len = reader.read_u8_at(offset)?; 66 | let hashed_salt_len_i32 = reader.read_i32_at(offset)?; 67 | // plus 1 and skip 3 bytes 68 | offset += 4; 69 | let salt_len = hashed_salt_len ^ rand_bytes[0]; 70 | let salt_byte_len = (salt_len * 2) as usize; 71 | let salt_bytes = reader.get_slice(offset..offset + salt_byte_len); 72 | offset += salt_byte_len; 73 | 74 | let salt_string = (0..salt_len as usize) 75 | .map(|i| { 76 | let byte = rand_bytes[i]; 77 | let byte2 = salt_bytes[i * 2]; 78 | (byte ^ byte2) as char 79 | }) 80 | .collect::(); 81 | // 3. decrypt 9 bytes header with snow cipher 82 | // generate snow key based on filename+keySalt 83 | let file_name_with_salt = format!("{}{}", file_name, &salt_string); 84 | 85 | let file_name_with_salt_bytes = file_name_with_salt.as_bytes(); 86 | let snow_key: [u8; MS_SNOW2_KEY_SIZE] = core::array::from_fn(|i| { 87 | file_name_with_salt_bytes[i % file_name_with_salt.len()] + i as u8 88 | }); 89 | let mut snow_decryptor = Snow2Decryptor::new(snow_key); 90 | 91 | let hstart = offset; 92 | // the snow decryptor is decrypting 4 bytes at a time, so we need to decrypt 12 bytes for only 9 bytes of data 93 | let header_bytes = reader.get_slice(offset..offset + 12); 94 | 95 | let mut decrypt_data = [0_u8; 12]; 96 | decrypt_data.copy_from_slice(header_bytes); 97 | snow_decryptor.decrypt_slice(&mut decrypt_data); 98 | 99 | let hash = decrypt_data.pread_with::(0, LE)?; 100 | let version = decrypt_data[4]; 101 | let entry_count = decrypt_data.pread_with::(5, LE)?; 102 | 103 | // verify snowversion and hash 104 | const EXPECTED_VERSION: u8 = MS_SNOW2_VERSION; 105 | if version != EXPECTED_VERSION { 106 | return Err(Error::UnsupportedSnowVersion(version)); 107 | } 108 | 109 | let sum_of_salt_byte = salt_bytes 110 | .chunks_exact(2) 111 | .map(|s| u16::from_le_bytes([s[0], s[1]])) 112 | .fold(0, |acc: i32, x| acc.wrapping_add(x as i32)); 113 | 114 | let actual_hash = hashed_salt_len_i32 + version as i32 + entry_count + sum_of_salt_byte; 115 | if hash != actual_hash { 116 | return Err(Error::HashMismatch(hash, actual_hash)); 117 | } 118 | 119 | // 4. skip random bytes 120 | let estart = hstart + 9 + sum_str(&file_name) * 3 % 212 + 33; 121 | 122 | Ok(MsHeader { 123 | key_salt: salt_string, 124 | name_with_salt: file_name_with_salt, 125 | header_hash: hash, 126 | version, 127 | entry_count, 128 | hstart, 129 | estart, 130 | ms_file_version: 1, 131 | }) 132 | } 133 | 134 | pub fn from_ms_file_v2

(path: P, reader: &WzReader) -> Result 135 | where 136 | P: AsRef, 137 | { 138 | let file_name = get_ascii_file_name(path); 139 | 140 | let mut offset = 0; 141 | 142 | // all the code is from https://github.com/Kagamia/WzComparerR2/pull/347/files#diff-c14e6750eb85e2b36e81f13448c1afe29e229b58f639b6a373b5cc72352ba07b 143 | // 1. random bytes 144 | let rand_byte_count = sum_str(&file_name) % 312 + 30; 145 | let rand_bytes: Vec = reader 146 | .get_slice(offset..rand_byte_count) 147 | .iter() 148 | .map(|b| ((*b as i8) >> 1) as u8) 149 | .collect(); 150 | offset += rand_byte_count; 151 | 152 | // 2. check file version 153 | const EXPECTED_VERSION: u8 = MS_CHACHA20_VERSION; 154 | let version = reader.read_u8_at(offset)? ^ rand_bytes[0]; 155 | if version != EXPECTED_VERSION { 156 | return Err(Error::UnsupportedChacha20Version(version)); 157 | } 158 | offset += 1; 159 | 160 | // 3. encrypted chacha20 key 161 | let hashed_salt_len = reader.read_u8_at(offset)?; 162 | let _hashed_salt_len_i32 = reader.read_i32_at(offset)?; 163 | offset += 4; 164 | let salt_len = (hashed_salt_len ^ rand_bytes[0]) as usize; 165 | let salt_byte_len = (salt_len * 2) as usize; 166 | let salt_bytes = reader.get_slice(offset..offset + salt_byte_len).to_vec(); 167 | offset += salt_byte_len; 168 | 169 | let salt_string = (0..salt_len as usize) 170 | .map(|i| { 171 | let a = rand_bytes[i] ^ salt_bytes[i * 2]; 172 | let b = ((a | 0x4B) << 1) - a - 75; 173 | b as char 174 | }) 175 | .collect::(); 176 | 177 | let hstart = offset; 178 | 179 | // 4. decrypt 8 bytes header with chacha20 cipher 180 | // generate chacha20 key based on filename+keySalt 181 | let file_name_with_salt = format!("{}{}", file_name, &salt_string); 182 | let file_name_with_salt_bytes = file_name_with_salt.as_bytes(); 183 | let chacha20_key: [u8; MS_CHACHA20_KEY_SIZE] = core::array::from_fn(|i| { 184 | (file_name_with_salt_bytes[i % file_name_with_salt.len()] + i as u8) 185 | ^ MS_CHACHA20_KEY_BASE[i] 186 | }); 187 | let empty_nonce = [0; MS_CHACHA20_NONCE_SIZE]; 188 | 189 | let mut chacha20_reader = ChaCha20Reader::new( 190 | reader.get_slice(offset..offset + 128), 191 | &chacha20_key, 192 | &empty_nonce, 193 | ); 194 | let hash = chacha20_reader.read_i32()?; 195 | let entry_count = chacha20_reader.read_i32()?; 196 | 197 | // hash checking? 198 | 199 | // 5. skip random bytes 200 | let estart = hstart + 8 + sum_str(&file_name) * 3 % 212 + 64; 201 | 202 | Ok(MsHeader { 203 | key_salt: salt_string, 204 | name_with_salt: file_name_with_salt, 205 | header_hash: hash, 206 | version, 207 | entry_count, 208 | hstart, 209 | estart, 210 | ms_file_version: 2, 211 | }) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/ms/file.rs: -------------------------------------------------------------------------------- 1 | use crate::{reader, WzNode, WzNodeArc, WzNodeArcVec, WzNodeName, WzReader}; 2 | use memmap2::Mmap; 3 | use std::fs::File; 4 | use std::sync::Arc; 5 | 6 | use super::chacha20_reader::{ 7 | ChaCha20Reader, MS_CHACHA20_KEY_BASE, MS_CHACHA20_KEY_SIZE, MS_CHACHA20_NONCE_SIZE, 8 | }; 9 | use super::header::{self, MsHeader}; 10 | use super::ms_image::{MsEntryMeta, MsImage}; 11 | use super::snow2_reader::{Snow2Reader, MS_SNOW2_KEY_SIZE}; 12 | 13 | #[cfg(feature = "serde")] 14 | use serde::{Deserialize, Serialize}; 15 | 16 | #[derive(Debug, thiserror::Error)] 17 | pub enum Error { 18 | #[error(transparent)] 19 | FileError(#[from] std::io::Error), 20 | #[error("invald ms file")] 21 | InvalidMsFile, 22 | #[error("Failed, in this case the causes are undetermined.")] 23 | FailedUnknown, 24 | #[error("Binary reading error")] 25 | ReaderError(#[from] reader::Error), 26 | #[error(transparent)] 27 | HeaderReadError(#[from] header::Error), 28 | #[error("Unknown ms file, unable to parse")] 29 | UnknownMsFile, 30 | } 31 | 32 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 33 | #[derive(Debug, Clone, Default)] 34 | pub struct MsFile { 35 | #[cfg_attr(feature = "serde", serde(skip))] 36 | pub reader: Arc, 37 | #[cfg_attr(feature = "serde", serde(skip))] 38 | pub block_size: usize, 39 | #[cfg_attr(feature = "serde", serde(skip))] 40 | pub is_parsed: bool, 41 | #[cfg_attr(feature = "serde", serde(flatten))] 42 | pub header: MsHeader, 43 | } 44 | 45 | impl MsFile { 46 | pub fn from_file

(path: P) -> Result 47 | where 48 | P: AsRef, 49 | { 50 | let file: File = File::open(&path)?; 51 | let map = unsafe { Mmap::map(&file)? }; 52 | 53 | let block_size = map.len(); 54 | 55 | let reader = WzReader::new(map); 56 | 57 | let ms_header = if let Ok(header) = MsHeader::from_ms_file(&path, &reader) { 58 | header 59 | } else if let Ok(header) = MsHeader::from_ms_file_v2(&path, &reader) { 60 | header 61 | } else { 62 | return Err(Error::UnknownMsFile); 63 | }; 64 | 65 | Ok(MsFile { 66 | block_size, 67 | is_parsed: false, 68 | reader: Arc::new(reader), 69 | header: ms_header, 70 | }) 71 | } 72 | pub fn parse(&mut self, parent: &WzNodeArc) -> Result { 73 | match self.header.ms_file_version { 74 | 1 => self.parse_v1(parent), 75 | 2 => self.parse_v2(parent), 76 | _ => Err(Error::UnknownMsFile), 77 | } 78 | } 79 | 80 | fn parse_v1(&mut self, parent: &WzNodeArc) -> Result { 81 | // decrypt with another snow key 82 | let file_name_with_salt_bytes = self.header.name_with_salt.as_bytes(); 83 | let file_name_with_salt_len = file_name_with_salt_bytes.len(); 84 | let mut snow_key: [u8; 16] = [0; MS_SNOW2_KEY_SIZE]; 85 | for i in 0_u8..16_u8 { 86 | let byte = file_name_with_salt_bytes 87 | [file_name_with_salt_len - 1 - i as usize % file_name_with_salt_len]; 88 | snow_key[i as usize] = i + (i % 3 + 2).wrapping_mul(byte); 89 | } 90 | 91 | let data = self.reader.get_slice(0..self.block_size); 92 | let mut snow_reader = Snow2Reader::new(data, snow_key); 93 | 94 | snow_reader.offset = self.header.estart; 95 | 96 | let mut ms_images = Vec::with_capacity(self.header.entry_count as usize); 97 | 98 | for _ in 0..self.header.entry_count { 99 | let entry_name_len = snow_reader.read_i32()?; 100 | let entry_name = snow_reader.read_utf16_string(entry_name_len as usize * 2)?; 101 | let check_sum = snow_reader.read_i32()?; 102 | let flags = snow_reader.read_i32()?; 103 | let start_pos = snow_reader.read_i32()?; 104 | let size = snow_reader.read_i32()?; 105 | let size_aligned = snow_reader.read_i32()?; 106 | let unk1 = snow_reader.read_i32()?; 107 | let unk2 = snow_reader.read_i32()?; 108 | let mut entry_key = [0_u8; 16]; 109 | snow_reader.write_bytes_to(&mut entry_key, 16)?; 110 | 111 | let meta = MsEntryMeta { 112 | key_salt: self.header.key_salt.clone(), 113 | entry_name, 114 | check_sum, 115 | flags, 116 | start_pos, 117 | size, 118 | size_aligned, 119 | unk1, 120 | unk2, 121 | entry_key, 122 | unk3: 0, 123 | unk4: 0, 124 | version: self.header.ms_file_version, 125 | }; 126 | let image = MsImage::new(meta, &self.reader); 127 | 128 | ms_images.push(image); 129 | } 130 | 131 | let mut data_start = snow_reader.offset; 132 | // align to 1024 bytes 133 | if (data_start & 0x3FF) != 0 { 134 | data_start = data_start - (data_start & 0x3FF) + 0x400; 135 | } 136 | 137 | for image in ms_images.iter_mut() { 138 | let actual_start = data_start + image.offset * 1024; 139 | image.offset = actual_start; 140 | image.meta.start_pos = actual_start as i32; 141 | } 142 | 143 | return Ok(ms_images 144 | .drain(..) 145 | .map(|image| { 146 | let name = WzNodeName::from(image.meta.entry_name.clone()); 147 | let node = WzNode::new(&name, image, Some(parent)); 148 | (name, node.into_lock()) 149 | }) 150 | .collect()); 151 | } 152 | 153 | fn parse_v2(&mut self, parent: &WzNodeArc) -> Result { 154 | if self.header.entry_count == 0 { 155 | return Ok(vec![]); 156 | } 157 | 158 | // decrypt with another chacha20 key 159 | let file_name_with_salt_bytes = self.header.name_with_salt.as_bytes(); 160 | let file_name_with_salt_len = file_name_with_salt_bytes.len(); 161 | let chacha20_key2: [u8; MS_CHACHA20_KEY_SIZE] = core::array::from_fn(|i| { 162 | let byte = file_name_with_salt_bytes 163 | [file_name_with_salt_len - 1 - i % file_name_with_salt_len]; 164 | MS_CHACHA20_KEY_BASE[i] ^ ((i as u8) + (((i as u8) % 3 + 2).wrapping_mul(byte))) 165 | }); 166 | let empty_nonce = [0; MS_CHACHA20_NONCE_SIZE]; 167 | 168 | let mut chacha20_reader = ChaCha20Reader::new( 169 | self.reader.get_slice(0..self.block_size), 170 | &chacha20_key2, 171 | &empty_nonce, 172 | ); 173 | chacha20_reader.offset = self.header.estart; 174 | 175 | let mut ms_images = Vec::::with_capacity(self.header.entry_count as usize); 176 | 177 | for _ in 0..self.header.entry_count { 178 | let entry_name_len = chacha20_reader.read_i32()?; 179 | let entry_name = chacha20_reader.read_utf16_string(entry_name_len as usize * 2)?; 180 | let check_sum = chacha20_reader.read_i32()?; 181 | let flags = chacha20_reader.read_i32()?; 182 | let start_pos = chacha20_reader.read_i32()?; 183 | let size = chacha20_reader.read_i32()?; 184 | let size_aligned = chacha20_reader.read_i32()?; 185 | let unk1 = chacha20_reader.read_i32()?; 186 | let unk2 = chacha20_reader.read_i32()?; 187 | let mut entry_key = [0_u8; 16]; 188 | chacha20_reader.write_bytes_to(&mut entry_key, 16)?; 189 | let unk3 = chacha20_reader.read_i32()?; 190 | let unk4 = chacha20_reader.read_i32()?; 191 | 192 | let meta = MsEntryMeta { 193 | key_salt: self.header.key_salt.clone(), 194 | entry_name, 195 | check_sum, 196 | flags, 197 | start_pos, 198 | size, 199 | size_aligned, 200 | unk1, 201 | unk2, 202 | entry_key, 203 | unk3, 204 | unk4, 205 | version: self.header.ms_file_version, 206 | }; 207 | 208 | let image = MsImage::new(meta, &self.reader); 209 | 210 | ms_images.push(image); 211 | } 212 | 213 | let mut data_start = chacha20_reader.offset; 214 | // align to 1024 bytes 215 | if (data_start & 0x3FF) != 0 { 216 | data_start = data_start - (data_start & 0x3FF) + 0x400; 217 | } 218 | 219 | for image in ms_images.iter_mut() { 220 | let actual_start = data_start + image.offset * 1024; 221 | image.offset = actual_start; 222 | image.meta.start_pos = actual_start as i32; 223 | } 224 | 225 | return Ok(ms_images 226 | .drain(..) 227 | .map(|image| { 228 | let name = WzNodeName::from(image.meta.entry_name.clone()); 229 | let node = WzNode::new(&name, image, Some(parent)); 230 | (name, node.into_lock()) 231 | }) 232 | .collect()); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/property/string.rs: -------------------------------------------------------------------------------- 1 | use crate::{reader, util::WzMutableKey, Reader, WzNodeArc, WzNodeCast, WzReader}; 2 | use std::sync::{Arc, RwLock}; 3 | use thiserror::Error; 4 | 5 | #[cfg(feature = "serde")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum WzStringParseError { 10 | #[error("Error parsing WzString: {0}")] 11 | ParseError(#[from] reader::Error), 12 | 13 | #[error("Not a String property")] 14 | NotStringProperty, 15 | } 16 | 17 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 18 | #[derive(Debug, Clone, PartialEq)] 19 | pub enum WzStringType { 20 | Ascii, 21 | Unicode, 22 | Empty, 23 | } 24 | 25 | impl Default for WzStringType { 26 | fn default() -> Self { 27 | Self::Ascii 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | pub struct WzStringMeta { 33 | /// string start offset 34 | pub offset: usize, 35 | /// string length 36 | pub length: u32, 37 | pub string_type: WzStringType, 38 | } 39 | 40 | /// `WzString` only hold the string information. 41 | #[derive(Debug, Clone, Default)] 42 | pub struct WzString { 43 | reader: Arc, 44 | /// string start offset 45 | offset: usize, 46 | /// string length 47 | length: u32, 48 | pub string_type: WzStringType, 49 | } 50 | 51 | impl WzStringMeta { 52 | pub fn new(offset: usize, length: u32, string_type: WzStringType) -> Self { 53 | Self { 54 | offset, 55 | length, 56 | string_type, 57 | } 58 | } 59 | #[inline] 60 | pub fn empty() -> Self { 61 | Self { 62 | offset: 0, 63 | length: 0, 64 | string_type: WzStringType::Empty, 65 | } 66 | } 67 | #[inline] 68 | pub fn new_ascii(offset: usize, length: u32) -> Self { 69 | Self { 70 | offset, 71 | length, 72 | string_type: WzStringType::Ascii, 73 | } 74 | } 75 | #[inline] 76 | pub fn new_unicode(offset: usize, length: u32) -> Self { 77 | Self { 78 | offset, 79 | length, 80 | string_type: WzStringType::Unicode, 81 | } 82 | } 83 | } 84 | 85 | impl WzString { 86 | pub fn from_meta(meta: WzStringMeta, reader: &Arc) -> Self { 87 | Self { 88 | reader: Arc::clone(reader), 89 | offset: meta.offset, 90 | length: meta.length, 91 | string_type: meta.string_type, 92 | } 93 | } 94 | /// Create a new `WzString` it will encrypt the string with the given iv. 95 | pub fn from_str(str: &str, iv: [u8; 4]) -> Self { 96 | let mut mtbkeys = WzMutableKey::from_iv(iv); 97 | 98 | let len; 99 | 100 | let meta_type = if str.is_empty() { 101 | len = 0; 102 | WzStringType::Empty 103 | } else if str.is_ascii() { 104 | len = str.len(); 105 | WzStringType::Ascii 106 | } else { 107 | len = str.chars().count() * 2; 108 | WzStringType::Unicode 109 | }; 110 | 111 | let encrypted = encrypt_str(&mut mtbkeys, str, &meta_type); 112 | 113 | let mut reader = WzReader::from_buff(&encrypted); 114 | 115 | reader.wz_iv = iv; 116 | reader.keys = Arc::new(RwLock::new(mtbkeys)); 117 | 118 | WzString { 119 | reader: Arc::new(reader), 120 | offset: 0, 121 | length: len as u32, 122 | string_type: meta_type, 123 | } 124 | } 125 | #[inline] 126 | /// Decode string from wz file. 127 | pub fn get_string(&self) -> Result { 128 | self.reader 129 | .resolve_wz_string_meta(&self.string_type, self.offset, self.length as usize) 130 | .map_err(WzStringParseError::from) 131 | } 132 | } 133 | 134 | #[inline] 135 | /// A helper function to resolve string from `WzNodeArc`. 136 | pub fn resolve_string_from_node(node: &WzNodeArc) -> Result { 137 | node.read() 138 | .unwrap() 139 | .try_as_string() 140 | .ok_or(WzStringParseError::NotStringProperty) 141 | .and_then(|string| string.get_string()) 142 | } 143 | 144 | pub(crate) fn encrypt_str( 145 | keys: &mut WzMutableKey, 146 | str: &str, 147 | string_type: &WzStringType, 148 | ) -> Vec { 149 | match string_type { 150 | WzStringType::Empty => Vec::new(), 151 | WzStringType::Unicode => { 152 | let mut bytes = str.encode_utf16().collect::>(); 153 | 154 | keys.ensure_key_size(bytes.len() * 2).unwrap(); 155 | 156 | bytes 157 | .iter_mut() 158 | .enumerate() 159 | .flat_map(|(i, b)| { 160 | let key1 = *keys.try_at(i * 2).unwrap_or(&0) as u16; 161 | let key2 = *keys.try_at(i * 2 + 1).unwrap_or(&0) as u16; 162 | let i = (i + 0xAAAA) as u16; 163 | *b ^= i ^ key1 ^ (key2 << 8); 164 | 165 | b.to_le_bytes().to_vec() 166 | }) 167 | .collect() 168 | } 169 | WzStringType::Ascii => { 170 | let mut bytes = str.bytes().collect::>(); 171 | 172 | keys.ensure_key_size(bytes.len()).unwrap(); 173 | 174 | for (i, b) in bytes.iter_mut().enumerate() { 175 | let key = keys.try_at(i).unwrap_or(&0); 176 | let i = (i + 0xAA) as u8; 177 | 178 | *b ^= i ^ key; 179 | } 180 | 181 | bytes 182 | } 183 | } 184 | } 185 | 186 | #[cfg(feature = "serde")] 187 | impl Serialize for WzString { 188 | fn serialize(&self, serializer: S) -> Result 189 | where 190 | S: serde::Serializer, 191 | { 192 | let str = self.get_string().unwrap_or_default(); 193 | 194 | serializer.serialize_str(&str) 195 | } 196 | } 197 | #[cfg(feature = "serde")] 198 | use serde::de::{self, Deserializer, Visitor}; 199 | #[cfg(feature = "serde")] 200 | impl<'de> Deserialize<'de> for WzString { 201 | fn deserialize(deserializer: D) -> Result 202 | where 203 | D: Deserializer<'de>, 204 | { 205 | use std::fmt; 206 | struct StringVisitor; 207 | impl<'de> Visitor<'de> for StringVisitor { 208 | type Value = WzString; 209 | 210 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 211 | formatter.write_str("a string to deserialize into WzString") 212 | } 213 | 214 | fn visit_str(self, value: &str) -> Result 215 | where 216 | E: de::Error, 217 | { 218 | Ok(WzString::from_str(value, Default::default())) 219 | } 220 | } 221 | 222 | deserializer.deserialize_str(StringVisitor) 223 | } 224 | } 225 | 226 | #[cfg(test)] 227 | mod test { 228 | use super::*; 229 | use crate::WzNode; 230 | #[cfg(feature = "serde")] 231 | use serde_json; 232 | 233 | type Result = std::result::Result; 234 | 235 | #[cfg(feature = "serde")] 236 | #[test] 237 | fn test_wz_string_serde_ascii() { 238 | let encrypter_reader = WzReader::default(); 239 | let encrypted = encrypter_reader.encrypt_str("test", &WzStringType::Ascii); 240 | 241 | let reader = WzReader::from_buff(&encrypted); 242 | 243 | let string = WzString::from_meta(WzStringMeta::new_ascii(0, 4), &Arc::new(reader)); 244 | 245 | let json = serde_json::to_string(&string).unwrap(); 246 | assert_eq!(json, r#""test""#); 247 | 248 | let string: WzString = serde_json::from_str(r#""test""#).unwrap(); 249 | assert_eq!(string.string_type, WzStringType::Ascii); 250 | assert_eq!(string.get_string().unwrap(), "test"); 251 | } 252 | 253 | #[cfg(feature = "serde")] 254 | #[test] 255 | fn test_wz_string_serde_unicode() { 256 | let encrypter_reader = WzReader::default(); 257 | let encrypted = encrypter_reader.encrypt_str("測試", &WzStringType::Unicode); 258 | 259 | let reader = WzReader::from_buff(&encrypted); 260 | 261 | let string = WzString::from_meta(WzStringMeta::new_unicode(0, 4), &Arc::new(reader)); 262 | 263 | let json = serde_json::to_string(&string).unwrap(); 264 | assert_eq!(json, r#""測試""#); 265 | 266 | let string: WzString = serde_json::from_str(r#""測試""#).unwrap(); 267 | assert_eq!(string.string_type, WzStringType::Unicode); 268 | assert_eq!(string.get_string().unwrap(), "測試"); 269 | } 270 | 271 | #[test] 272 | fn test_wz_string_create_empty() -> Result<()> { 273 | let wz_string = WzString::from_str("", [0, 0, 0, 0]); 274 | 275 | assert_eq!(wz_string.length, 0); 276 | assert_eq!(wz_string.string_type, WzStringType::Empty); 277 | assert_eq!(wz_string.get_string()?, ""); 278 | 279 | Ok(()) 280 | } 281 | 282 | #[test] 283 | fn test_wz_string_create_ascii() -> Result<()> { 284 | let wz_string = WzString::from_str("test", [0, 0, 0, 0]); 285 | 286 | assert_eq!(wz_string.length, 4); 287 | assert_eq!(wz_string.string_type, WzStringType::Ascii); 288 | assert_eq!(wz_string.get_string()?, "test"); 289 | 290 | Ok(()) 291 | } 292 | 293 | #[test] 294 | fn test_wz_string_create_unicode() -> Result<()> { 295 | let wz_string = WzString::from_str("測試", [0, 0, 0, 0]); 296 | 297 | assert_eq!(wz_string.length, 4); 298 | assert_eq!(wz_string.string_type, WzStringType::Unicode); 299 | assert_eq!(wz_string.get_string()?, "測試"); 300 | 301 | Ok(()) 302 | } 303 | 304 | #[test] 305 | fn test_resolve_from_node_success() -> Result<()> { 306 | let node = 307 | WzNode::from_str("root", WzString::from_str("test", [0, 0, 0, 0]), None).into_lock(); 308 | 309 | assert_eq!(resolve_string_from_node(&node)?, "test"); 310 | 311 | Ok(()) 312 | } 313 | 314 | #[test] 315 | fn test_resolve_from_node_fail() -> Result<()> { 316 | let node = WzNode::from_str("root", 1, None).into_lock(); 317 | 318 | assert!(resolve_string_from_node(&node).is_err()); 319 | 320 | Ok(()) 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | directory, reader, version, Reader, SharedWzMutableKey, WzDirectory, WzNodeArc, WzNodeArcVec, 3 | WzNodeCast, WzObjectType, WzReader, WzSliceReader, 4 | }; 5 | use memmap2::Mmap; 6 | use std::fs::File; 7 | use std::ops::Range; 8 | use std::sync::Arc; 9 | 10 | #[cfg(feature = "serde")] 11 | use serde::{Deserialize, Serialize}; 12 | 13 | #[derive(Debug, thiserror::Error)] 14 | pub enum Error { 15 | #[error(transparent)] 16 | FileError(#[from] std::io::Error), 17 | #[error("invald wz file")] 18 | InvalidWzFile, 19 | #[error("Error with game version hash : The specified game version is incorrect and WzLib was unable to determine the version itself")] 20 | ErrorGameVerHash, 21 | #[error("Failed, in this case the causes are undetermined.")] 22 | FailedUnknown, 23 | #[error("Binary reading error")] 24 | ReaderError(#[from] reader::Error), 25 | #[error(transparent)] 26 | DirectoryError(#[from] directory::Error), 27 | #[error("[WzFile] New Wz image header found. checkByte = {0}, File Name = {1}")] 28 | UnknownImageHeader(u8, String), 29 | #[error("Unable to guess version")] 30 | UnableToGuessVersion, 31 | } 32 | 33 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 34 | #[derive(Debug, Clone, Default)] 35 | pub struct WzFileMeta { 36 | /// path of wz file 37 | pub path: String, 38 | /// the wz file's patch version, if not set, try to guess from wz file 39 | pub patch_version: i32, 40 | /// a.k.a encver 41 | pub wz_version_header: i32, 42 | /// a wz file is cantain wz_version_header(encver) in header 43 | pub wz_with_encrypt_version_header: bool, 44 | /// the hash use to calculate img offset 45 | pub hash: usize, 46 | } 47 | 48 | /// Root of the `WzNode`, represents the Wz file itself and contains `WzFileMeta` 49 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 50 | #[derive(Debug, Clone, Default)] 51 | pub struct WzFile { 52 | #[cfg_attr(feature = "serde", serde(skip))] 53 | pub reader: Arc, 54 | #[cfg_attr(feature = "serde", serde(skip))] 55 | pub offset: usize, 56 | #[cfg_attr(feature = "serde", serde(skip))] 57 | pub block_size: usize, 58 | #[cfg_attr(feature = "serde", serde(skip))] 59 | pub is_parsed: bool, 60 | #[cfg_attr(feature = "serde", serde(flatten))] 61 | pub wz_file_meta: WzFileMeta, 62 | } 63 | 64 | impl WzFile { 65 | pub fn from_file

( 66 | path: P, 67 | wz_iv: Option<[u8; 4]>, 68 | patch_version: Option, 69 | existing_key: Option<&SharedWzMutableKey>, 70 | ) -> Result 71 | where 72 | P: AsRef, 73 | { 74 | let file: File = File::open(&path)?; 75 | let map = unsafe { Mmap::map(&file)? }; 76 | 77 | let block_size = map.len(); 78 | 79 | let wz_iv = if let Some(iv) = wz_iv { 80 | // consider do version::verify_iv_from_wz_file here like WzImage does, but feel like it's not necessary 81 | iv 82 | } else { 83 | version::guess_iv_from_wz_file(&map).ok_or(Error::UnableToGuessVersion)? 84 | }; 85 | 86 | let reader = if let Some(keys) = existing_key { 87 | WzReader::new(map) 88 | .with_iv(wz_iv) 89 | .with_existing_keys(keys.clone()) 90 | } else { 91 | WzReader::new(map).with_iv(wz_iv) 92 | }; 93 | 94 | let offset = reader.get_wz_fstart().map_err(|_| Error::InvalidWzFile)? + 2; 95 | 96 | let wz_file_meta = WzFileMeta { 97 | path: path.as_ref().to_str().unwrap().to_string(), 98 | patch_version: patch_version.unwrap_or(-1), 99 | wz_version_header: 0, 100 | wz_with_encrypt_version_header: true, 101 | hash: 0, 102 | }; 103 | 104 | Ok(WzFile { 105 | offset: offset as usize, 106 | block_size, 107 | is_parsed: false, 108 | reader: Arc::new(reader), 109 | wz_file_meta, 110 | }) 111 | } 112 | pub fn parse( 113 | &mut self, 114 | parent: &WzNodeArc, 115 | patch_version: Option, 116 | ) -> Result { 117 | let reader = self.reader.clone(); 118 | 119 | let mut wz_file_meta = WzFileMeta { 120 | path: "".to_string(), 121 | patch_version: patch_version.unwrap_or(self.wz_file_meta.patch_version), 122 | wz_version_header: 0, 123 | wz_with_encrypt_version_header: true, 124 | hash: 0, 125 | }; 126 | 127 | let slice_reader = reader.create_slice_reader(); 128 | 129 | let (wz_with_encrypt_version_header, encrypt_version) = check_64bit_client(&slice_reader); 130 | 131 | wz_file_meta.wz_version_header = if wz_with_encrypt_version_header { 132 | encrypt_version as i32 133 | } else { 134 | WZ_VERSION_HEADER_64BIT_START as i32 135 | }; 136 | 137 | wz_file_meta.wz_with_encrypt_version_header = wz_with_encrypt_version_header; 138 | 139 | if wz_file_meta.patch_version == -1 { 140 | let guess_range: Range = if wz_with_encrypt_version_header { 141 | 1..2000 142 | } else { 143 | /* not hold encver in wz_file, directly try 770 - 780 */ 144 | WZ_VERSION_HEADER_64BIT_START as i32..WZ_VERSION_HEADER_64BIT_START as i32 + 10 145 | }; 146 | 147 | /* there has code in maplelib to detect version from maplestory.exe here */ 148 | 149 | for ver_to_decode in guess_range { 150 | wz_file_meta.hash = 151 | check_and_get_version_hash(wz_file_meta.wz_version_header, ver_to_decode) 152 | as usize; 153 | if let Ok(childs) = self.try_decode_with_wz_version_number( 154 | parent, 155 | &slice_reader, 156 | &wz_file_meta, 157 | ver_to_decode, 158 | ) { 159 | wz_file_meta.patch_version = ver_to_decode; 160 | self.update_wz_file_meta(wz_file_meta); 161 | self.is_parsed = true; 162 | return Ok(childs); 163 | } 164 | } 165 | 166 | return Err(Error::ErrorGameVerHash); 167 | } 168 | 169 | wz_file_meta.hash = 170 | check_and_get_version_hash(wz_file_meta.wz_version_header, wz_file_meta.patch_version) 171 | as usize; 172 | 173 | let childs = self.try_decode_with_wz_version_number( 174 | parent, 175 | &slice_reader, 176 | &wz_file_meta, 177 | wz_file_meta.patch_version, 178 | )?; 179 | self.update_wz_file_meta(wz_file_meta); 180 | self.is_parsed = true; 181 | 182 | Ok(childs) 183 | } 184 | 185 | fn try_decode_with_wz_version_number( 186 | &self, 187 | parent: &WzNodeArc, 188 | reader: &WzSliceReader, 189 | meta: &WzFileMeta, 190 | use_maplestory_patch_version: i32, 191 | ) -> Result { 192 | if meta.hash == 0 { 193 | return Err(Error::ErrorGameVerHash); 194 | } 195 | 196 | let node = WzDirectory::new(self.offset, self.block_size, &self.reader, false) 197 | .with_hash(meta.hash); 198 | 199 | node.verify_hash()?; 200 | 201 | let childs = node.resolve_children(parent)?; 202 | 203 | let first_image_node = childs 204 | .iter() 205 | .find(|(_, node)| matches!(node.read().unwrap().object_type, WzObjectType::Image(_))); 206 | 207 | if let Some((name, image_node)) = first_image_node { 208 | let offset = image_node 209 | .read() 210 | .unwrap() 211 | .try_as_image() 212 | .map(|node| node.offset) 213 | .ok_or(Error::ErrorGameVerHash)?; 214 | 215 | let check_byte = reader 216 | .read_u8_at(offset) 217 | .map_err(|_| Error::ErrorGameVerHash)?; 218 | 219 | match check_byte { 220 | 0x73 | 0x1b | 0x01 => {} 221 | _ => { 222 | /* 0x30, 0x6C, 0xBC */ 223 | println!( 224 | "UnknownImageHeader: check_byte = {}, File Name = {}", 225 | check_byte, name 226 | ); 227 | return Err(Error::UnknownImageHeader(check_byte, name.to_string())); 228 | } 229 | } 230 | } 231 | 232 | // there a special case this 2 will match 233 | if !meta.wz_with_encrypt_version_header && use_maplestory_patch_version == 113 { 234 | return Err(Error::ErrorGameVerHash); 235 | } 236 | 237 | Ok(childs) 238 | } 239 | 240 | fn update_wz_file_meta(&mut self, wz_file_meta: WzFileMeta) { 241 | self.wz_file_meta = WzFileMeta { 242 | path: std::mem::take(&mut self.wz_file_meta.path), 243 | ..wz_file_meta 244 | }; 245 | } 246 | } 247 | 248 | const WZ_VERSION_HEADER_64BIT_START: u16 = 770; 249 | 250 | fn check_64bit_client(wz_reader: &WzSliceReader) -> (bool, u16) { 251 | let encrypt_version = wz_reader.read_u16_at(wz_reader.header.fstart).unwrap(); 252 | 253 | if wz_reader.header.fsize >= 2 { 254 | if encrypt_version > 0xff { 255 | return (false, 0); 256 | } 257 | if encrypt_version == 0x80 { 258 | let prop_count = wz_reader.read_i32_at(wz_reader.header.fstart + 2).unwrap(); 259 | if prop_count > 0 && (prop_count & 0xff) == 0 && prop_count <= 0xffff { 260 | return (false, 0); 261 | } 262 | } 263 | /* the only place return actual encrypt_version */ 264 | return (true, encrypt_version); 265 | } 266 | 267 | (false, 0) 268 | } 269 | 270 | fn check_and_get_version_hash(encver: i32, patch_version: i32) -> i32 { 271 | let mut version_hash: i32 = 0; 272 | 273 | let bind_version = &patch_version.to_string(); 274 | 275 | for i in bind_version.chars() { 276 | let char_code = i.to_ascii_lowercase() as i32; 277 | 278 | // version_hash * 2^5 + char_code + 1 279 | version_hash = version_hash * 32 + char_code + 1; 280 | } 281 | 282 | if encver == patch_version { 283 | return version_hash; 284 | } 285 | 286 | let enc = 0xff 287 | ^ (version_hash >> 24) & 0xff 288 | ^ (version_hash >> 16) & 0xff 289 | ^ (version_hash >> 8) & 0xff 290 | ^ version_hash & 0xff; 291 | 292 | if enc == encver { 293 | version_hash 294 | } else { 295 | 0 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /tests/wz_img_test.rs: -------------------------------------------------------------------------------- 1 | use wz_reader::property::{Vector2D, WzValue}; 2 | use wz_reader::util::{self, node_util}; 3 | use wz_reader::version::WzMapleVersion; 4 | use wz_reader::{node, wz_image, WzNode, WzNodeArc, WzNodeCast, WzObjectType}; 5 | 6 | type Error = Box; 7 | type Result = std::result::Result; 8 | 9 | /** 10 | * the wz_img.img file structure: 11 | * wz_img.img 12 | * - conv 13 | * - 0(vec) 14 | * - 1(png) 15 | * - origin 16 | * - _inlink 17 | * - 1 18 | * - float 19 | * - double 20 | * - long 21 | * - short 22 | * - int 23 | * - 2 24 | * - nil 25 | * - string 26 | * - uol 27 | */ 28 | 29 | #[test] 30 | fn should_parsing_with_default_version() -> Result<()> { 31 | let wz_img = WzNode::from_img_file(r"tests/test.img", None, None); 32 | assert!(wz_img.is_ok()); 33 | 34 | let wz_img = wz_img?.into_lock(); 35 | 36 | assert!(node_util::parse_node(&wz_img).is_ok()); 37 | 38 | Ok(()) 39 | } 40 | 41 | #[test] 42 | fn should_parsing_with_correct_version() -> Result<()> { 43 | let wz_img = WzNode::from_img_file(r"tests/test.img", Some(WzMapleVersion::BMS), None); 44 | assert!(wz_img.is_ok()); 45 | 46 | let wz_img = wz_img?.into_lock(); 47 | 48 | assert!(node_util::parse_node(&wz_img).is_ok()); 49 | 50 | Ok(()) 51 | } 52 | 53 | #[test] 54 | fn should_error_with_wrong_version() -> Result<()> { 55 | let wz_img = WzNode::from_img_file(r"tests/test.img", Some(WzMapleVersion::EMS), None); 56 | 57 | assert!(wz_img.is_err()); 58 | 59 | Ok(()) 60 | } 61 | 62 | #[test] 63 | fn should_parsing_with_correct_iv() -> Result<()> { 64 | let wz_img = WzNode::from_img_file_with_iv(r"tests/test.img", [0, 0, 0, 0], None); 65 | assert!(wz_img.is_ok()); 66 | 67 | let wz_img = wz_img?.into_lock(); 68 | 69 | assert!(node_util::parse_node(&wz_img).is_ok()); 70 | 71 | Ok(()) 72 | } 73 | 74 | #[test] 75 | fn should_error_with_wrong_iv() -> Result<()> { 76 | let wz_img = WzNode::from_img_file_with_iv(r"tests/test.img", [1, 2, 3, 4], None); 77 | assert!(wz_img.is_err()); 78 | 79 | Ok(()) 80 | } 81 | 82 | fn check_sample_wz_img(wz_img: &WzNodeArc) -> Result<()> { 83 | assert!(node_util::parse_node(&wz_img).is_ok()); 84 | 85 | let wz_img_read = wz_img.read().unwrap(); 86 | 87 | if let Some(first_folder) = wz_img_read.at("1") { 88 | let first_folder = first_folder.read().unwrap(); 89 | 90 | assert!(first_folder.is_sub_property()); 91 | 92 | let int = first_folder.at("int"); 93 | assert!(int.is_some()); 94 | 95 | let int = int.unwrap(); 96 | let int = int.read().unwrap(); 97 | assert_eq!(int.try_as_int(), Some(&1)); 98 | 99 | let short = first_folder.at("short"); 100 | assert!(short.is_some()); 101 | 102 | let short = short.unwrap(); 103 | let short = short.read().unwrap(); 104 | assert_eq!(short.try_as_short(), Some(&2)); 105 | 106 | let long = first_folder.at("long"); 107 | assert!(long.is_some()); 108 | 109 | let long = long.unwrap(); 110 | let long = long.read().unwrap(); 111 | assert_eq!(long.try_as_long(), Some(&3)); 112 | 113 | let float = first_folder.at("float"); 114 | assert!(float.is_some()); 115 | 116 | let float = float.unwrap(); 117 | let float = float.read().unwrap(); 118 | assert_eq!(float.try_as_float(), Some(&4.1)); 119 | 120 | let double = first_folder.at("double"); 121 | assert!(double.is_some()); 122 | 123 | let double = double.unwrap(); 124 | let double = double.read().unwrap(); 125 | assert_eq!(double.try_as_double(), Some(&4.2)); 126 | } 127 | 128 | if let Some(second_folder) = wz_img_read.at("2") { 129 | let second_folder = second_folder.read().unwrap(); 130 | 131 | assert!(second_folder.is_sub_property()); 132 | 133 | let nil = second_folder.at("nil"); 134 | assert!(nil.is_some()); 135 | 136 | let nil = nil.unwrap(); 137 | let nil = nil.read().unwrap(); 138 | assert!(nil.is_null()); 139 | 140 | let string = second_folder.at("string"); 141 | assert!(string.is_some()); 142 | 143 | let string = string.unwrap(); 144 | let string = string.read().unwrap(); 145 | if let Some(string) = string.try_as_string() { 146 | let s = string.get_string(); 147 | assert!(s.is_ok()); 148 | assert_eq!(&s?, "foo"); 149 | }; 150 | 151 | let uol = second_folder.at("uol"); 152 | assert!(uol.is_some()); 153 | 154 | let uol = uol.unwrap(); 155 | let uol = uol.read().unwrap(); 156 | 157 | // uol node should be resolved 158 | assert!(uol.try_as_uol().is_none()); 159 | 160 | if let Some(str) = uol.try_as_string() { 161 | let s = str.get_string(); 162 | assert!(s.is_ok()); 163 | assert_eq!(&s?, "foo"); 164 | } 165 | } 166 | 167 | if let Some(convex_folder) = wz_img_read.at("conv") { 168 | let convex_folder = convex_folder.read().unwrap(); 169 | 170 | assert!(convex_folder.is_convex()); 171 | 172 | let vector = convex_folder.at("0"); 173 | assert!(vector.is_some()); 174 | 175 | let vector = vector.unwrap(); 176 | let vector = vector.read().unwrap(); 177 | if let Some(vec) = vector.try_as_vector2d() { 178 | assert_eq!(vec, &Vector2D(1, 1)); 179 | }; 180 | 181 | let png = convex_folder.at("1"); 182 | assert!(png.is_some()); 183 | 184 | let png = png.unwrap(); 185 | let png = png.read().unwrap(); 186 | assert!(png.try_as_png().is_some()); 187 | 188 | let vector = png.at("origin"); 189 | assert!(vector.is_some()); 190 | 191 | let vector = vector.unwrap(); 192 | let vector = vector.read().unwrap(); 193 | if let Some(vec) = vector.try_as_vector2d() { 194 | assert_eq!(vec, &Vector2D(0, 0)); 195 | }; 196 | 197 | let inlink = png.at("_inlink"); 198 | assert!(inlink.is_some()); 199 | 200 | let inlink = inlink.unwrap(); 201 | let inlink_str = if let Some(string) = inlink.read().unwrap().try_as_string() { 202 | string.get_string().unwrap() 203 | } else { 204 | String::new() 205 | }; 206 | assert_eq!(&inlink_str, "conv/png2"); 207 | } 208 | 209 | Ok(()) 210 | } 211 | 212 | #[test] 213 | fn should_parsing_wz_img_and_check_values() -> Result<()> { 214 | let wz_img = WzNode::from_img_file(r"tests/test.img", Some(WzMapleVersion::BMS), None); 215 | 216 | assert!(wz_img.is_ok()); 217 | 218 | assert!(check_sample_wz_img(&wz_img?.into_lock()).is_ok()); 219 | 220 | Ok(()) 221 | } 222 | 223 | #[test] 224 | fn should_success_using_wz_node_methods_on_childs() -> Result<()> { 225 | let wz_img = WzNode::from_img_file(r"tests/test.img", Some(WzMapleVersion::BMS), None); 226 | assert!(wz_img.is_ok()); 227 | 228 | let wz_img = wz_img?.into_lock(); 229 | 230 | assert!(node_util::parse_node(&wz_img).is_ok()); 231 | 232 | let nil_node = wz_img.read().unwrap().at_path("2/nil"); 233 | assert!(nil_node.is_some()); 234 | 235 | let wz_node_not_exist = wz_img.read().unwrap().at_path("2/not_exist"); 236 | assert!(wz_node_not_exist.is_none()); 237 | 238 | let nil_node = nil_node.unwrap(); 239 | let nil_read = nil_node.read().unwrap(); 240 | assert_eq!(nil_read.get_full_path(), "test.img/2/nil"); 241 | 242 | let nil_parent = nil_read.at_relative(".."); 243 | assert!(nil_parent.is_some()); 244 | assert_eq!( 245 | nil_parent.unwrap().read().unwrap().get_full_path(), 246 | "test.img/2" 247 | ); 248 | 249 | let vec_node = nil_read.at_path_relative("../../conv/0"); 250 | assert!(vec_node.is_some()); 251 | let vec_node = vec_node.unwrap(); 252 | assert_eq!(vec_node.read().unwrap().get_full_path(), "test.img/conv/0"); 253 | 254 | let png_node = nil_read.at_path_relative("../../conv/1"); 255 | assert!(png_node.is_some()); 256 | let png_node = png_node.unwrap(); 257 | assert_eq!(png_node.read().unwrap().get_full_path(), "test.img/conv/1"); 258 | 259 | let wz_node_not_exist = nil_read.at_path_relative("../../not_exist"); 260 | assert!(wz_node_not_exist.is_none()); 261 | 262 | let inlink = png_node.read().unwrap().at("_inlink"); 263 | assert!(inlink.is_some()); 264 | let inlink = inlink.unwrap(); 265 | let inlink_read = inlink.read().unwrap(); 266 | let inlink_string = inlink_read.try_as_string(); 267 | 268 | assert!(inlink_string.is_some()); 269 | let inlink_string = inlink_string.unwrap().get_string(); 270 | 271 | assert!(inlink_string.is_ok()); 272 | 273 | let inlink_string = inlink_string.unwrap(); 274 | let inlink_target = node_util::resolve_inlink(&inlink_string, &inlink); 275 | assert!(inlink_target.is_none()); 276 | 277 | let parent_img = png_node.read().unwrap().get_parent_wz_image(); 278 | assert!(parent_img.is_some()); 279 | let parent_img = parent_img.unwrap(); 280 | assert_eq!( 281 | parent_img.read().unwrap().get_full_path(), 282 | wz_img.read().unwrap().get_full_path() 283 | ); 284 | 285 | let force_get_next_exist_node = parent_img.read().unwrap().at_path_parsed("2/not_exist"); 286 | assert!(matches!( 287 | force_get_next_exist_node, 288 | Err(node::Error::NodeNotFound) 289 | )); 290 | 291 | let force_get_some_node = parent_img.read().unwrap().at_path_parsed("2/nil"); 292 | assert!(force_get_some_node.is_ok()); 293 | 294 | parent_img.write().unwrap().unparse(); 295 | 296 | assert_eq!(parent_img.read().unwrap().children.len(), 0); 297 | 298 | let parent_img_read = parent_img.read().unwrap(); 299 | if let WzObjectType::Image(wz_image) = &parent_img_read.object_type { 300 | let direct_access_not_exist = wz_image.at_path("2/not_exist"); 301 | 302 | assert!(matches!( 303 | direct_access_not_exist, 304 | Err(wz_image::Error::ParsePropertyListError( 305 | util::WzPropertyParseError::NodeNotFound 306 | )) 307 | )); 308 | 309 | let direct_access_nil = wz_image.at_path("2/nil"); 310 | assert!(direct_access_nil.is_ok()); 311 | 312 | let nil = direct_access_nil.unwrap(); 313 | let nil = nil.read().unwrap(); 314 | assert!(matches!( 315 | nil.object_type, 316 | WzObjectType::Value(WzValue::Null) 317 | )); 318 | } 319 | 320 | Ok(()) 321 | } 322 | 323 | #[test] 324 | fn should_success_walk_thorugh() { 325 | let wz_img = WzNode::from_img_file(r"tests/test.img", Some(WzMapleVersion::BMS), None); 326 | assert!(wz_img.is_ok()); 327 | 328 | let wz_img = wz_img.unwrap().into_lock(); 329 | 330 | let pathes = std::collections::HashSet::from([ 331 | "test.img", 332 | "test.img/conv", 333 | "test.img/conv/0", 334 | "test.img/conv/1", 335 | "test.img/conv/1/_inlink", 336 | "test.img/conv/1/origin", 337 | "test.img/1", 338 | "test.img/1/float", 339 | "test.img/1/double", 340 | "test.img/1/long", 341 | "test.img/1/short", 342 | "test.img/1/int", 343 | "test.img/2", 344 | "test.img/2/nil", 345 | "test.img/2/string", 346 | "test.img/2/uol", 347 | ]); 348 | 349 | util::walk_node(&wz_img, true, &|node| { 350 | let node_read = node.read().unwrap(); 351 | println!("{}", node_read.get_full_path()); 352 | assert!(pathes.contains(node_read.get_full_path().as_str())); 353 | }); 354 | } 355 | -------------------------------------------------------------------------------- /src/node_cast.rs: -------------------------------------------------------------------------------- 1 | use crate::property::{ 2 | Vector2D, WzLua, WzPng, WzRawData, WzSound, WzString, WzSubProperty, WzValue, WzVideo, 3 | }; 4 | use crate::{WzDirectory, WzFile, WzImage, WzNode, WzObjectType}; 5 | 6 | /// Trait for casting `WzNode` to its inner type. 7 | /// 8 | /// # Example 9 | /// 10 | /// ``` 11 | /// # use wz_reader::{WzNode, WzNodeCast}; 12 | /// let wz_int = WzNode::from_str("test", 1, None); 13 | /// 14 | /// assert!(wz_int.try_as_int().is_some()); 15 | /// assert!(wz_int.try_as_file().is_none()); 16 | /// ``` 17 | pub trait WzNodeCast { 18 | fn try_as_file(&self) -> Option<&WzFile>; 19 | fn try_as_directory(&self) -> Option<&WzDirectory>; 20 | fn try_as_image(&self) -> Option<&WzImage>; 21 | 22 | fn try_as_sub_property(&self) -> Option<&WzSubProperty>; 23 | fn try_as_value(&self) -> Option<&WzValue>; 24 | 25 | fn try_as_png(&self) -> Option<&WzPng>; 26 | fn try_as_sound(&self) -> Option<&WzSound>; 27 | fn try_as_string(&self) -> Option<&WzString>; 28 | fn is_sub_property(&self) -> bool; 29 | fn is_convex(&self) -> bool; 30 | 31 | fn try_as_lua(&self) -> Option<&WzLua>; 32 | fn try_as_raw_data(&self) -> Option<&WzRawData>; 33 | fn try_as_video(&self) -> Option<&WzVideo>; 34 | 35 | fn is_null(&self) -> bool; 36 | fn try_as_vector2d(&self) -> Option<&Vector2D>; 37 | fn try_as_short(&self) -> Option<&i16>; 38 | fn try_as_int(&self) -> Option<&i32>; 39 | fn try_as_long(&self) -> Option<&i64>; 40 | fn try_as_float(&self) -> Option<&f32>; 41 | fn try_as_double(&self) -> Option<&f64>; 42 | fn try_as_uol(&self) -> Option<&WzString>; 43 | } 44 | 45 | macro_rules! try_as { 46 | ($func_name:ident, $variant:ident, $result:ty) => { 47 | #[inline] 48 | fn $func_name(&self) -> Option<&$result> { 49 | match &self.object_type { 50 | WzObjectType::$variant(inner) => Some(inner), 51 | _ => None, 52 | } 53 | } 54 | }; 55 | } 56 | 57 | macro_rules! try_as_wz_value { 58 | ($func_name:ident, $variant:ident, $result:ident) => { 59 | #[inline] 60 | fn $func_name(&self) -> Option<&$result> { 61 | match &self.object_type { 62 | WzObjectType::Value(WzValue::$variant(inner)) => Some(inner), 63 | _ => None, 64 | } 65 | } 66 | }; 67 | } 68 | 69 | impl WzNodeCast for WzNode { 70 | try_as!(try_as_file, File, WzFile); 71 | try_as!(try_as_directory, Directory, WzDirectory); 72 | try_as!(try_as_image, Image, WzImage); 73 | 74 | try_as!(try_as_sub_property, Property, WzSubProperty); 75 | try_as!(try_as_value, Value, WzValue); 76 | 77 | #[inline] 78 | fn try_as_png(&self) -> Option<&WzPng> { 79 | match &self.object_type { 80 | WzObjectType::Property(WzSubProperty::PNG(png)) => Some(png), 81 | _ => None, 82 | } 83 | } 84 | #[inline] 85 | fn try_as_sound(&self) -> Option<&WzSound> { 86 | match &self.object_type { 87 | WzObjectType::Property(WzSubProperty::Sound(sound)) => Some(sound), 88 | _ => None, 89 | } 90 | } 91 | #[inline] 92 | fn try_as_string(&self) -> Option<&WzString> { 93 | match &self.object_type { 94 | WzObjectType::Value(WzValue::String(string)) 95 | | WzObjectType::Value(WzValue::UOL(string)) => Some(string), 96 | _ => None, 97 | } 98 | } 99 | #[inline] 100 | fn is_sub_property(&self) -> bool { 101 | matches!( 102 | &self.object_type, 103 | WzObjectType::Property(WzSubProperty::Property) 104 | ) 105 | } 106 | #[inline] 107 | fn is_convex(&self) -> bool { 108 | matches!( 109 | &self.object_type, 110 | WzObjectType::Property(WzSubProperty::Convex) 111 | ) 112 | } 113 | #[inline] 114 | fn is_null(&self) -> bool { 115 | matches!(&self.object_type, WzObjectType::Value(WzValue::Null)) 116 | } 117 | 118 | try_as_wz_value!(try_as_lua, Lua, WzLua); 119 | try_as_wz_value!(try_as_raw_data, RawData, WzRawData); 120 | try_as_wz_value!(try_as_video, Video, WzVideo); 121 | 122 | try_as_wz_value!(try_as_vector2d, Vector, Vector2D); 123 | try_as_wz_value!(try_as_short, Short, i16); 124 | try_as_wz_value!(try_as_int, Int, i32); 125 | try_as_wz_value!(try_as_long, Long, i64); 126 | try_as_wz_value!(try_as_float, Float, f32); 127 | try_as_wz_value!(try_as_double, Double, f64); 128 | try_as_wz_value!(try_as_uol, UOL, WzString); 129 | } 130 | 131 | #[cfg(test)] 132 | mod test { 133 | 134 | use super::*; 135 | use crate::property::{WzSoundType, WzStringMeta}; 136 | use crate::WzReader; 137 | use memmap2::Mmap; 138 | use std::fs::OpenOptions; 139 | use std::sync::Arc; 140 | 141 | fn setup_wz_reader() -> Result { 142 | let dir = tempfile::tempdir()?; 143 | let file_path = dir.path().join("test.wz"); 144 | 145 | let file = OpenOptions::new() 146 | .read(true) 147 | .write(true) 148 | .create(true) 149 | .open(file_path)?; 150 | 151 | file.set_len(200)?; 152 | 153 | let map = unsafe { Mmap::map(&file)? }; 154 | 155 | Ok(WzReader::new(map)) 156 | } 157 | 158 | #[test] 159 | fn try_as_file() { 160 | let reader = setup_wz_reader().unwrap(); 161 | let file = WzFile { 162 | offset: 0, 163 | block_size: 0, 164 | is_parsed: false, 165 | reader: Arc::new(reader), 166 | wz_file_meta: Default::default(), 167 | }; 168 | let node = WzNode::from_str("test", file, None); 169 | 170 | assert!(node.try_as_file().is_some()); 171 | assert!(node.try_as_directory().is_none()); 172 | } 173 | 174 | #[test] 175 | fn try_as_directory() { 176 | let reader = Arc::new(setup_wz_reader().unwrap()); 177 | let wzdir = WzDirectory::new(0, 0, &reader, false); 178 | let node = WzNode::from_str("test", wzdir, None); 179 | 180 | assert!(node.try_as_directory().is_some()); 181 | assert!(node.try_as_file().is_none()); 182 | } 183 | 184 | #[test] 185 | fn try_as_image() { 186 | let reader = Arc::new(setup_wz_reader().unwrap()); 187 | let wzimage = WzImage::new(&"test".into(), 0, 0, &reader); 188 | let node = WzNode::from_str("test", wzimage, None); 189 | 190 | assert!(node.try_as_image().is_some()); 191 | assert!(node.try_as_file().is_none()); 192 | } 193 | 194 | #[test] 195 | fn try_as_sub_property() { 196 | let node = WzNode::from_str( 197 | "test", 198 | WzObjectType::Property(WzSubProperty::Property), 199 | None, 200 | ); 201 | 202 | assert!(node.try_as_sub_property().is_some()); 203 | assert!(node.try_as_file().is_none()); 204 | } 205 | #[test] 206 | fn try_as_value() { 207 | let node = WzNode::from_str("test", WzObjectType::Value(WzValue::Null), None); 208 | 209 | assert!(node.try_as_value().is_some()); 210 | assert!(node.try_as_file().is_none()); 211 | } 212 | 213 | #[test] 214 | fn try_as_png() { 215 | let reader = Arc::new(setup_wz_reader().unwrap()); 216 | let png = WzPng::new(&reader, (1, 1), (1, 1), (0, 1), 0); 217 | let node = WzNode::from_str("test", png, None); 218 | 219 | assert!(node.try_as_png().is_some()); 220 | assert!(node.try_as_file().is_none()); 221 | } 222 | #[test] 223 | fn try_as_sound() { 224 | let reader = Arc::new(setup_wz_reader().unwrap()); 225 | let sound = WzSound::new(&reader, 0, 0, 0, 0, 0, WzSoundType::Mp3); 226 | let node = WzNode::from_str("test", sound, None); 227 | 228 | assert!(node.try_as_sound().is_some()); 229 | assert!(node.try_as_file().is_none()); 230 | } 231 | #[test] 232 | fn try_as_string() { 233 | let reader = Arc::new(setup_wz_reader().unwrap()); 234 | let string = WzString::from_meta(WzStringMeta::empty(), &reader); 235 | let node = WzNode::from_str("test", string, None); 236 | 237 | assert!(node.try_as_string().is_some()); 238 | assert!(node.try_as_file().is_none()); 239 | } 240 | 241 | #[test] 242 | fn try_as_string_uol() { 243 | let reader = Arc::new(setup_wz_reader().unwrap()); 244 | let string = WzString::from_meta(WzStringMeta::empty(), &reader); 245 | let node = WzNode::from_str("test", WzObjectType::Value(WzValue::UOL(string)), None); 246 | 247 | assert!(node.try_as_string().is_some()); 248 | assert!(node.try_as_file().is_none()); 249 | } 250 | 251 | #[test] 252 | fn try_as_uol() { 253 | let reader = Arc::new(setup_wz_reader().unwrap()); 254 | let string = WzString::from_meta(WzStringMeta::empty(), &reader); 255 | let node = WzNode::from_str("test", WzObjectType::Value(WzValue::UOL(string)), None); 256 | 257 | assert!(node.try_as_uol().is_some()); 258 | assert!(node.try_as_file().is_none()); 259 | } 260 | 261 | #[test] 262 | fn is_sub_property() { 263 | let node = WzNode::from_str( 264 | "test", 265 | WzObjectType::Property(WzSubProperty::Property), 266 | None, 267 | ); 268 | 269 | assert!(node.is_sub_property()); 270 | assert!(!node.is_convex()); 271 | } 272 | #[test] 273 | fn is_convex() { 274 | let node = WzNode::from_str("test", WzObjectType::Property(WzSubProperty::Convex), None); 275 | 276 | assert!(node.is_convex()); 277 | assert!(!node.is_sub_property()); 278 | } 279 | 280 | #[test] 281 | fn is_null() { 282 | let node = WzNode::from_str("test", WzObjectType::Value(WzValue::Null), None); 283 | 284 | assert!(node.is_null()); 285 | assert!(!node.is_sub_property()); 286 | } 287 | 288 | #[test] 289 | fn try_as_lua() { 290 | let reader = Arc::new(setup_wz_reader().unwrap()); 291 | let lua = WzLua::new(&reader, 0, 0); 292 | let node = WzNode::from_str("test", lua, None); 293 | 294 | assert!(node.try_as_lua().is_some()); 295 | assert!(node.try_as_file().is_none()); 296 | } 297 | #[test] 298 | fn try_as_raw_data() { 299 | let reader = Arc::new(setup_wz_reader().unwrap()); 300 | let raw_data = WzRawData::new(&reader, 0, 0); 301 | let node = WzNode::from_str("test", raw_data, None); 302 | 303 | assert!(node.try_as_raw_data().is_some()); 304 | assert!(node.try_as_file().is_none()); 305 | } 306 | 307 | #[test] 308 | fn try_as_vector2d() { 309 | let vec2 = Vector2D::new(2, 3); 310 | let node = WzNode::from_str("test", vec2, None); 311 | 312 | assert!(node.try_as_file().is_none()); 313 | assert_eq!(node.try_as_vector2d(), Some(&Vector2D::new(2, 3))); 314 | } 315 | #[test] 316 | fn try_as_short() { 317 | let node = WzNode::from_str("test", 1_i16, None); 318 | 319 | assert!(node.try_as_file().is_none()); 320 | assert_eq!(node.try_as_short(), Some(&1)); 321 | } 322 | #[test] 323 | fn try_as_int() { 324 | let node = WzNode::from_str("test", 1, None); 325 | 326 | assert!(node.try_as_file().is_none()); 327 | assert_eq!(node.try_as_int(), Some(&1)); 328 | } 329 | #[test] 330 | fn try_as_long() { 331 | let node = WzNode::from_str("test", 1_i64, None); 332 | 333 | assert!(node.try_as_file().is_none()); 334 | assert_eq!(node.try_as_long(), Some(&1)); 335 | } 336 | #[test] 337 | fn try_as_float() { 338 | let node = WzNode::from_str("test", 1.0_f32, None); 339 | 340 | assert!(node.try_as_file().is_none()); 341 | assert_eq!(node.try_as_float(), Some(&1.0)); 342 | } 343 | #[test] 344 | fn try_as_double() { 345 | let node = WzNode::from_str("test", 1.0_f64, None); 346 | 347 | assert!(node.try_as_file().is_none()); 348 | assert_eq!(node.try_as_double(), Some(&1.0)); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/property/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serde")] 2 | use serde::{Deserialize, Serialize}; 3 | #[cfg(feature = "json")] 4 | use serde_json::{Number, Value}; 5 | 6 | pub mod lua; 7 | pub mod png; 8 | pub mod raw_data; 9 | pub mod sound; 10 | pub mod string; 11 | pub mod vector; 12 | pub mod video; 13 | 14 | pub use lua::*; 15 | pub use png::*; 16 | pub use raw_data::*; 17 | pub use sound::*; 18 | pub use string::*; 19 | pub use vector::*; 20 | pub use video::*; 21 | 22 | // #[derive(Debug, Clone)] 23 | // pub enum WzPropertyType { 24 | // Null, 25 | // Short(i16), 26 | // Int(i32), 27 | // Long(i64), 28 | // Float(f32), 29 | // Double(f64), 30 | // String(WzStringType), 31 | 32 | // SubProperty, 33 | // Vector(Vector2D), 34 | // Convex, 35 | // Sound(WzSoundMeta), 36 | // UOL(WzStringType), 37 | // Lua, 38 | 39 | // PNG(WzPng), 40 | 41 | // RawData, 42 | // } 43 | 44 | /// A WzProperty potentially contains childrens. 45 | #[derive(Debug, Clone)] 46 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 47 | #[cfg_attr(feature = "serde", serde(tag = "type", content = "data"))] 48 | pub enum WzSubProperty { 49 | Convex, 50 | Sound(Box), 51 | PNG(Box), 52 | #[cfg_attr(feature = "serde", serde(other))] 53 | Property, 54 | } 55 | 56 | /// Some basic value, more like a primitive type. 57 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 58 | #[cfg_attr(feature = "serde", serde(tag = "type", content = "data"))] 59 | #[derive(Debug, Clone)] 60 | pub enum WzValue { 61 | #[cfg_attr(feature = "serde", serde(skip))] 62 | RawData(WzRawData), 63 | #[cfg_attr(feature = "serde", serde(skip))] 64 | Video(WzVideo), 65 | #[cfg_attr(feature = "serde", serde(skip))] 66 | Lua(WzLua), 67 | Short(i16), 68 | #[cfg_attr(feature = "serde", serde(alias = "number"))] 69 | Int(i32), 70 | Long(i64), 71 | Float(f32), 72 | Double(f64), 73 | Vector(Vector2D), 74 | UOL(WzString), 75 | String(WzString), 76 | ParsedString(String), 77 | #[cfg_attr(feature = "serde", serde(other))] 78 | Null, 79 | } 80 | 81 | impl Default for WzValue { 82 | fn default() -> Self { 83 | Self::Null 84 | } 85 | } 86 | 87 | #[cfg(feature = "json")] 88 | impl From for Value { 89 | fn from(value: WzValue) -> Self { 90 | match value { 91 | WzValue::Null => Value::Null, 92 | WzValue::RawData(_) => Value::Null, 93 | WzValue::Video(_) => Value::Null, 94 | WzValue::Lua(_) => Value::Null, 95 | WzValue::Short(value) => value.into(), 96 | WzValue::Int(value) => value.into(), 97 | WzValue::Long(value) => value.into(), 98 | WzValue::Float(value) => Value::Number(Number::from_f64(value.into()).unwrap()), 99 | WzValue::Double(value) => Value::Number(Number::from_f64(value).unwrap()), 100 | WzValue::Vector(Vector2D(x, y)) => { 101 | let mut vec = serde_json::Map::new(); 102 | vec.insert("x".to_string(), x.into()); 103 | vec.insert("y".to_string(), y.into()); 104 | Value::Object(vec) 105 | } 106 | WzValue::UOL(string) | WzValue::String(string) => { 107 | string.get_string().unwrap_or_default().into() 108 | } 109 | WzValue::ParsedString(string) => string.into(), 110 | } 111 | } 112 | } 113 | 114 | #[cfg(feature = "serde")] 115 | #[cfg(test)] 116 | mod test { 117 | use super::*; 118 | 119 | #[cfg(feature = "serde")] 120 | use serde_json; 121 | 122 | #[cfg(feature = "serde")] 123 | #[test] 124 | fn test_serialize_wz_sub_property() { 125 | let png = WzSubProperty::PNG(Box::new(WzPng::default())); 126 | let sound = WzSubProperty::Sound(Box::new(WzSound::default())); 127 | let property = WzSubProperty::Property; 128 | let convex = WzSubProperty::Convex; 129 | 130 | let png_json = serde_json::to_string(&png).unwrap(); 131 | let sound_json = serde_json::to_string(&sound).unwrap(); 132 | let property_json = serde_json::to_string(&property).unwrap(); 133 | let convex_json = serde_json::to_string(&convex).unwrap(); 134 | 135 | assert_eq!(png_json, r#"{"type":"PNG","data":{"width":0,"height":0}}"#); 136 | assert_eq!( 137 | sound_json, 138 | r#"{"type":"Sound","data":{"duration":0,"sound_type":"Binary"}}"# 139 | ); 140 | assert_eq!(property_json, r#"{"type":"Property"}"#); 141 | assert_eq!(convex_json, r#"{"type":"Convex"}"#); 142 | } 143 | 144 | #[cfg(feature = "serde")] 145 | #[test] 146 | fn test_deserialize_wz_sub_property() { 147 | let png_json = r#"{"type":"PNG","data":{"width":0,"height":0}}"#; 148 | let sound_json = r#"{"type":"Sound","data":{"duration":0,"sound_type":"Binary"}}"#; 149 | let property_json = r#"{"type":"Property"}"#; 150 | let convex_json = r#"{"type":"Convex"}"#; 151 | 152 | let png: WzSubProperty = serde_json::from_str(png_json).unwrap(); 153 | let sound: WzSubProperty = serde_json::from_str(sound_json).unwrap(); 154 | let property: WzSubProperty = serde_json::from_str(property_json).unwrap(); 155 | let convex: WzSubProperty = serde_json::from_str(convex_json).unwrap(); 156 | 157 | assert!(matches!(png, WzSubProperty::PNG(_))); 158 | assert!(matches!(sound, WzSubProperty::Sound(_))); 159 | assert!(matches!(property, WzSubProperty::Property)); 160 | assert!(matches!(convex, WzSubProperty::Convex)); 161 | } 162 | 163 | #[cfg(feature = "serde")] 164 | #[test] 165 | fn test_serialize_wz_value() { 166 | let null = WzValue::Null; 167 | let raw_data = WzValue::RawData(WzRawData::default()); 168 | let video = WzValue::Video(WzVideo::default()); 169 | let lua = WzValue::Lua(WzLua::default()); 170 | let short = WzValue::Short(1); 171 | let int = WzValue::Int(1); 172 | let long = WzValue::Long(1); 173 | let float = WzValue::Float(1.1); 174 | let double = WzValue::Double(1.1); 175 | let vector = WzValue::Vector(Vector2D(1, 1)); 176 | let uol = WzValue::UOL(WzString::from_str("1/1", [0, 0, 0, 0])); 177 | let string = WzValue::String(WzString::from_str("string", [0, 0, 0, 0])); 178 | let parsed_string = WzValue::ParsedString("string".to_string()); 179 | 180 | let null_json = serde_json::to_string(&null).unwrap(); 181 | assert!(serde_json::to_string(&raw_data).is_err()); 182 | assert!(serde_json::to_string(&lua).is_err()); 183 | assert!(serde_json::to_string(&video).is_err()); 184 | let short_json = serde_json::to_string(&short).unwrap(); 185 | let int_json = serde_json::to_string(&int).unwrap(); 186 | let long_json = serde_json::to_string(&long).unwrap(); 187 | let float_json = serde_json::to_string(&float).unwrap(); 188 | let double_json = serde_json::to_string(&double).unwrap(); 189 | let vector_json = serde_json::to_string(&vector).unwrap(); 190 | let uol_json = serde_json::to_string(&uol).unwrap(); 191 | let string_json = serde_json::to_string(&string).unwrap(); 192 | let parsed_string_json = serde_json::to_string(&parsed_string).unwrap(); 193 | 194 | assert_eq!(null_json, r#"{"type":"Null"}"#); 195 | assert_eq!(short_json, r#"{"type":"Short","data":1}"#); 196 | assert_eq!(int_json, r#"{"type":"Int","data":1}"#); 197 | assert_eq!(long_json, r#"{"type":"Long","data":1}"#); 198 | assert_eq!(float_json, r#"{"type":"Float","data":1.1}"#); 199 | assert_eq!(double_json, r#"{"type":"Double","data":1.1}"#); 200 | assert_eq!(vector_json, r#"{"type":"Vector","data":[1,1]}"#); 201 | assert_eq!(uol_json, r#"{"type":"UOL","data":"1/1"}"#); 202 | assert_eq!(string_json, r#"{"type":"String","data":"string"}"#); 203 | assert_eq!( 204 | parsed_string_json, 205 | r#"{"type":"ParsedString","data":"string"}"# 206 | ); 207 | } 208 | 209 | #[cfg(feature = "serde")] 210 | #[test] 211 | fn test_deserialize_wz_value() { 212 | let null_json = r#"{"type":"Null"}"#; 213 | let raw_data_json = r#"{"type":"RawData"}"#; 214 | let video_json = r#"{"type":"Video"}"#; 215 | let lua_json = r#"{"type":"Lua"}"#; 216 | let short_json = r#"{"type":"Short","data":1}"#; 217 | let int_json = r#"{"type":"Int","data":1}"#; 218 | let long_json = r#"{"type":"Long","data":1}"#; 219 | let float_json = r#"{"type":"Float","data":1.0}"#; 220 | let double_json = r#"{"type":"Double","data":1.0}"#; 221 | let vector_json = r#"{"type":"Vector", "data": [1, 1]}"#; 222 | let uol_json = r#"{"type":"UOL","data":"1/1"}"#; 223 | let string_json = r#"{"type":"String","data":"string"}"#; 224 | let parsed_string_json = r#"{"type":"ParsedString","data":"string"}"#; 225 | 226 | let null: WzValue = serde_json::from_str(null_json).unwrap(); 227 | let null_1: WzValue = serde_json::from_str(raw_data_json).unwrap(); 228 | let null_2: WzValue = serde_json::from_str(lua_json).unwrap(); 229 | let null_3: WzValue = serde_json::from_str(video_json).unwrap(); 230 | let short: WzValue = serde_json::from_str(short_json).unwrap(); 231 | let int: WzValue = serde_json::from_str(int_json).unwrap(); 232 | let long: WzValue = serde_json::from_str(long_json).unwrap(); 233 | let float: WzValue = serde_json::from_str(float_json).unwrap(); 234 | let double: WzValue = serde_json::from_str(double_json).unwrap(); 235 | let vector: WzValue = serde_json::from_str(vector_json).unwrap(); 236 | let uol: WzValue = serde_json::from_str(uol_json).unwrap(); 237 | let string: WzValue = serde_json::from_str(string_json).unwrap(); 238 | let parsed_string: WzValue = serde_json::from_str(parsed_string_json).unwrap(); 239 | 240 | assert!(matches!(null, WzValue::Null)); 241 | assert!(matches!(null_1, WzValue::Null)); 242 | assert!(matches!(null_2, WzValue::Null)); 243 | assert!(matches!(null_3, WzValue::Null)); 244 | assert!(matches!(short, WzValue::Short(1))); 245 | assert!(matches!(int, WzValue::Int(1))); 246 | assert!(matches!(long, WzValue::Long(1))); 247 | assert!(matches!(float, WzValue::Float(_))); 248 | assert!(matches!(double, WzValue::Double(_))); 249 | assert!(matches!(vector, WzValue::Vector(Vector2D(1, 1)))); 250 | assert!(matches!(uol, WzValue::UOL(_))); 251 | assert!(matches!(string, WzValue::String(_))); 252 | assert!(matches!(parsed_string, WzValue::ParsedString(_))); 253 | } 254 | 255 | #[cfg(feature = "json")] 256 | #[test] 257 | fn test_from_wz_value_to_serde_json_value() { 258 | let null = WzValue::Null; 259 | let raw_data = WzValue::RawData(WzRawData::default()); 260 | let lua = WzValue::Lua(WzLua::default()); 261 | let video = WzValue::Video(WzVideo::default()); 262 | let short = WzValue::Short(1); 263 | let int = WzValue::Int(1); 264 | let long = WzValue::Long(1); 265 | let float = WzValue::Float(1.1); 266 | let double = WzValue::Double(1.1); 267 | let vector = WzValue::Vector(Vector2D(1, 1)); 268 | let uol = WzValue::UOL(WzString::from_str("1/1", [0, 0, 0, 0])); 269 | let string = WzValue::String(WzString::from_str("string", [0, 0, 0, 0])); 270 | let parsed_string = WzValue::ParsedString("string".to_string()); 271 | 272 | let null_json: Value = null.into(); 273 | let raw_data_json: Value = raw_data.into(); 274 | let lua_json: Value = lua.into(); 275 | let video_json: Value = video.into(); 276 | let short_json: Value = short.into(); 277 | let int_json: Value = int.into(); 278 | let long_json: Value = long.into(); 279 | let float_json: Value = float.into(); 280 | let double_json: Value = double.into(); 281 | let vector_json: Value = vector.into(); 282 | let uol_json: Value = uol.into(); 283 | let string_json: Value = string.into(); 284 | let parsed_string_json: Value = parsed_string.into(); 285 | 286 | assert_eq!(null_json, Value::Null); 287 | assert_eq!(raw_data_json, Value::Null); 288 | assert_eq!(lua_json, Value::Null); 289 | assert_eq!(video_json, Value::Null); 290 | assert_eq!(short_json, Value::Number(Number::from(1))); 291 | assert_eq!(int_json, Value::Number(Number::from(1))); 292 | assert_eq!(long_json, Value::Number(Number::from(1))); 293 | assert_eq!( 294 | float_json, 295 | Value::Number(Number::from_f64((1.1_f32).into()).unwrap()) 296 | ); 297 | assert_eq!(double_json, Value::Number(Number::from_f64(1.1).unwrap())); 298 | assert_eq!( 299 | vector_json, 300 | Value::Object({ 301 | let mut map = serde_json::Map::new(); 302 | map.insert("x".to_string(), Value::Number(Number::from(1))); 303 | map.insert("y".to_string(), Value::Number(Number::from(1))); 304 | map 305 | }) 306 | ); 307 | assert_eq!(uol_json, Value::String("1/1".to_string())); 308 | assert_eq!(string_json, Value::String("string".to_string())); 309 | assert_eq!(parsed_string_json, Value::String("string".to_string())); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/util/node_util.rs: -------------------------------------------------------------------------------- 1 | use crate::{node::Error, WzNode, WzNodeArc, WzNodeCast}; 2 | use std::sync::Arc; 3 | 4 | #[inline] 5 | /// Just wrap around of `node.write().unwrap().parse(&node)` 6 | pub fn parse_node(node: &WzNodeArc) -> Result<(), Error> { 7 | node.write().unwrap().parse(node) 8 | } 9 | 10 | #[inline] 11 | /// Resolve a `_inlink` path, a `_inlink` path always start from a `WzImage`. 12 | pub fn resolve_inlink(path: &str, node: &WzNodeArc) -> Option { 13 | let parent_wz_image = node.read().unwrap().get_parent_wz_image()?; 14 | let parent_wz_image = parent_wz_image.read().unwrap(); 15 | parent_wz_image.at_path(path) 16 | } 17 | 18 | #[inline] 19 | /// Resolve a `_outlink` path, a `_outlink` path always start from Wz's data root(a.k.a `Base.wz`). 20 | pub fn resolve_outlink(path: &str, node: &WzNodeArc, force_parse: bool) -> Option { 21 | let parent_wz_base = node.read().unwrap().get_base_wz_file()?; 22 | 23 | if force_parse { 24 | parent_wz_base.write().unwrap().at_path_parsed(path).ok() 25 | } else { 26 | parent_wz_base.read().unwrap().at_path(path) 27 | } 28 | } 29 | 30 | #[inline] 31 | /// Make sure WzNode tree's all node has correct parent. 32 | pub fn resolve_childs_parent(node: &WzNodeArc) { 33 | let node_read = node.read().unwrap(); 34 | for child in node_read.children.values() { 35 | child.write().unwrap().parent = Arc::downgrade(node); 36 | resolve_childs_parent(child); 37 | } 38 | } 39 | 40 | /// Get resolved uol path, it will resolve `..` and `.` in path. 41 | pub fn get_resolved_uol_path(path: &str, uol_path: &str) -> String { 42 | let mut pathes: Vec<&str> = path.split('/').collect(); 43 | /* uol path always start at parent */ 44 | pathes.pop(); 45 | for p in uol_path.split('/') { 46 | if p == ".." && !pathes.is_empty() { 47 | pathes.pop(); 48 | } else { 49 | pathes.push(p); 50 | } 51 | } 52 | pathes.join("/") 53 | } 54 | 55 | /// Make a uol node become valid node, second argument is optional, 56 | /// it prevent the parent is the WzImage while it currently parsing causing the deadlock. 57 | pub fn resolve_uol(node: &WzNodeArc, wz_image: Option<&mut WzNode>) { 58 | let node_parent = node.read().unwrap().parent.upgrade().unwrap(); 59 | 60 | if let Some(ref mut uol_target_path) = node 61 | .read() 62 | .unwrap() 63 | .try_as_uol() 64 | .and_then(|s| s.get_string().ok()) 65 | { 66 | let mut pathes = uol_target_path.split('/'); 67 | 68 | let first = if let Ok(node) = node.try_read() { 69 | node.at_relative("..") 70 | } else if let Some(image_node) = &wz_image { 71 | image_node.at(pathes.next().unwrap()) 72 | } else { 73 | None 74 | }; 75 | 76 | let uol_target = if let Some(first) = first { 77 | pathes.try_fold(first, |node, name| { 78 | /* usually happen on parsing process, the WzImage is taking self a write lock 79 | so just directly using wz_image here */ 80 | if let Ok(node) = node.try_read() { 81 | return node.at_relative(name); 82 | } else if let Some(image_node) = &wz_image { 83 | return image_node.at(name); 84 | } 85 | None 86 | }) 87 | } else { 88 | None 89 | }; 90 | 91 | if let Some(target_node) = uol_target { 92 | let node_name = node.read().unwrap().name.clone(); 93 | 94 | /* when parent is locked, it means it's parent is WzImage, and it currently parsing */ 95 | if let Ok(mut parent) = node_parent.try_write() { 96 | if let Some(origin) = parent.children.get_mut(&node_name) { 97 | let _ = std::mem::replace(origin, target_node); 98 | } 99 | } else if let Some(wz_image) = wz_image { 100 | if let Some(origin) = wz_image.children.get_mut(&node_name) { 101 | let _ = std::mem::replace(origin, target_node); 102 | } 103 | } else { 104 | return; 105 | } 106 | } 107 | } 108 | } 109 | 110 | /// Get image node in the way, and return the rest of path. 111 | pub fn get_image_node_from_path<'a>( 112 | node: &'_ WzNodeArc, 113 | path: &'a str, 114 | ) -> Option<(WzNodeArc, &'a str)> { 115 | if path.is_empty() { 116 | return None; 117 | } 118 | 119 | if path.contains(".img") { 120 | let mut pathes = path.split_inclusive(".img"); 121 | let img_path = pathes.next()?; 122 | let rest_path = pathes.next()?.strip_prefix('/')?; 123 | 124 | let image_node = node.read().unwrap().at_path(img_path)?; 125 | 126 | return Some((image_node, rest_path)); 127 | } 128 | 129 | let mut node = node.clone(); 130 | let mut slash_index = 0; 131 | for split_path in path.split('/') { 132 | let target = node.read().unwrap().at(split_path); 133 | if let Some(target) = target { 134 | node = target; 135 | slash_index += split_path.len() + 1; 136 | if node.read().unwrap().try_as_image().is_some() { 137 | let rest = path.split_at(slash_index).1; 138 | return Some((node, rest)); 139 | } 140 | } else { 141 | return None; 142 | } 143 | } 144 | None 145 | } 146 | 147 | #[inline] 148 | /// get a certain node without parsing all node in the way 149 | pub fn get_node_without_parse(root: &WzNodeArc, path: &str) -> Option { 150 | let (image_node, rest_path) = get_image_node_from_path(root, path)?; 151 | let image_read = image_node.read().unwrap(); 152 | let image = image_read.try_as_image()?; 153 | 154 | if image.is_parsed { 155 | image_read.at_path(rest_path) 156 | } else { 157 | image.at_path(rest_path).ok() 158 | } 159 | } 160 | 161 | #[cfg(test)] 162 | mod test { 163 | use super::*; 164 | use crate::{ 165 | property::{resolve_string_from_node, WzString, WzValue}, 166 | WzDirectory, WzFile, WzImage, WzNode, WzObjectType, 167 | }; 168 | 169 | fn setup_node_tree() -> WzNodeArc { 170 | let root = WzNode::from_str("Base", WzFile::default(), None).into_lock(); 171 | let dir = WzNode::from_str("dir", WzDirectory::default(), Some(&root)).into_lock(); 172 | 173 | let img1 = { 174 | let mut img = WzImage::default(); 175 | img.is_parsed = true; 176 | WzNode::from_str("test1.img", img, Some(&dir)) 177 | } 178 | .into_lock(); 179 | 180 | let img2 = { 181 | let mut img = WzImage::default(); 182 | img.is_parsed = true; 183 | WzNode::from_str("test2.img", img, Some(&dir)) 184 | } 185 | .into_lock(); 186 | 187 | let img3 = { 188 | let mut img = WzImage::default(); 189 | img.is_parsed = true; 190 | WzNode::from_str("test3.img", img, Some(&dir)) 191 | } 192 | .into_lock(); 193 | 194 | let img1child1 = WzNode::from_str("1-dep1", 1, Some(&img1)).into_lock(); 195 | let img1child11 = WzNode::from_str("1-dep2", 2, Some(&img1child1)).into_lock(); 196 | let img1child2 = WzNode::from_str("2-dep1", 1, Some(&img1)).into_lock(); 197 | let img1child21 = WzNode::from_str("2-dep2", 1, Some(&img1child2)).into_lock(); 198 | let img1child21inlink = WzNode::from_str( 199 | "_inlink", 200 | WzString::from_str("1-dep1/1-dep2", [0, 0, 0, 0]), 201 | Some(&img1child21), 202 | ) 203 | .into_lock(); 204 | let img1child21outlink = WzNode::from_str( 205 | "_outlink", 206 | WzString::from_str("dir/test2.img/child1/child2", [0, 0, 0, 0]), 207 | Some(&img1child21), 208 | ) 209 | .into_lock(); 210 | let image1child21uol = WzNode::from_str( 211 | "uol", 212 | WzObjectType::Value(WzValue::UOL(WzString::from_str( 213 | "../../1-dep1/1-dep2", 214 | [0, 0, 0, 0], 215 | ))), 216 | Some(&img1child21), 217 | ) 218 | .into_lock(); 219 | 220 | let img2child1 = WzNode::from_str("child1", 1, Some(&img2)).into_lock(); 221 | let img2child2 = WzNode::from_str("child2", 2, Some(&img2child1)).into_lock(); 222 | 223 | // make those orphan but also is test3.img's child 224 | let img3child1 = WzNode::from_str("orphan1", 1, None).into_lock(); 225 | let img3child2 = WzNode::from_str("orphan2", 1, None).into_lock(); 226 | 227 | root.write().unwrap().add(&dir); 228 | 229 | dir.write().unwrap().add(&img1); 230 | dir.write().unwrap().add(&img2); 231 | dir.write().unwrap().add(&img3); 232 | 233 | img1.write().unwrap().add(&img1child1); 234 | img1child1.write().unwrap().add(&img1child11); 235 | img1.write().unwrap().add(&img1child2); 236 | img1child2.write().unwrap().add(&img1child21); 237 | img1child21.write().unwrap().add(&img1child21inlink); 238 | img1child21.write().unwrap().add(&img1child21outlink); 239 | img1child21.write().unwrap().add(&image1child21uol); 240 | 241 | img2.write().unwrap().add(&img2child1); 242 | img2child1.write().unwrap().add(&img2child2); 243 | 244 | img3.write().unwrap().add(&img3child1); 245 | img3child1.write().unwrap().add(&img3child2); 246 | 247 | root 248 | } 249 | 250 | #[test] 251 | fn test_resolve_inlink() { 252 | let root = setup_node_tree(); 253 | 254 | let node = root 255 | .read() 256 | .unwrap() 257 | .at_path("dir/test1.img/2-dep1/2-dep2/_inlink") 258 | .unwrap(); 259 | let inlink = resolve_string_from_node(&node).unwrap(); 260 | 261 | let inlink_target = resolve_inlink(&inlink, &node); 262 | 263 | assert!(inlink_target.is_some()); 264 | 265 | let inlink_target = inlink_target.unwrap(); 266 | 267 | assert_eq!(inlink_target.read().unwrap().name.as_str(), "1-dep2"); 268 | } 269 | 270 | #[test] 271 | fn test_resolve_outlink() { 272 | let root = setup_node_tree(); 273 | 274 | let node = root 275 | .read() 276 | .unwrap() 277 | .at_path("dir/test1.img/2-dep1/2-dep2/_outlink") 278 | .unwrap(); 279 | let outlink = resolve_string_from_node(&node).unwrap(); 280 | 281 | println!("{:?}", outlink); 282 | 283 | let outlink_target = resolve_outlink(&outlink, &node, false); 284 | 285 | assert!(outlink_target.is_some()); 286 | 287 | let outlink_target = outlink_target.unwrap(); 288 | 289 | assert_eq!(outlink_target.read().unwrap().name.as_str(), "child2"); 290 | } 291 | 292 | #[test] 293 | fn test_resolve_childs_parent() { 294 | let root = setup_node_tree(); 295 | 296 | let node = root.read().unwrap().at_path("dir/test3.img").unwrap(); 297 | 298 | resolve_childs_parent(&node); 299 | 300 | let child1 = node.read().unwrap().at("orphan1").unwrap(); 301 | 302 | let child1_parent = child1.read().unwrap().parent.upgrade().unwrap(); 303 | 304 | assert_eq!(child1_parent.read().unwrap().name.as_str(), "test3.img"); 305 | 306 | let child2 = child1.read().unwrap().at("orphan2").unwrap(); 307 | 308 | let child2_parent = child2.read().unwrap().parent.upgrade().unwrap(); 309 | 310 | assert_eq!(child2_parent.read().unwrap().name.as_str(), "orphan1"); 311 | } 312 | 313 | #[test] 314 | fn test_get_image_node_from_path() { 315 | let root = setup_node_tree(); 316 | 317 | let find_result = get_image_node_from_path(&root, "dir/test1.img/2-dep1/2-dep2/_outlink"); 318 | 319 | assert!(find_result.is_some()); 320 | 321 | let (node, rest) = find_result.unwrap(); 322 | 323 | assert_eq!(node.read().unwrap().name.as_str(), "test1.img"); 324 | assert_eq!(rest, "2-dep1/2-dep2/_outlink"); 325 | } 326 | 327 | #[test] 328 | fn test_get_node_without_parse() { 329 | let root = setup_node_tree(); 330 | 331 | let target_node = get_node_without_parse(&root, "dir/test1.img/2-dep1/2-dep2/_outlink"); 332 | 333 | assert!(target_node.is_some()); 334 | 335 | let target_node = target_node.unwrap(); 336 | 337 | assert_eq!(target_node.read().unwrap().name.as_str(), "_outlink"); 338 | } 339 | 340 | #[test] 341 | fn test_get_resolved_uol_path() { 342 | let path = "dir/test1.img/2-dep1/2-dep2"; 343 | let uol_path = "../1-dep1/1-dep2"; 344 | 345 | let resolved = get_resolved_uol_path(path, uol_path); 346 | 347 | assert_eq!(&resolved, "dir/test1.img/1-dep1/1-dep2"); 348 | } 349 | 350 | #[test] 351 | fn test_resolve_uol() { 352 | let root = setup_node_tree(); 353 | 354 | let uol_node = root 355 | .read() 356 | .unwrap() 357 | .at_path("dir/test1.img/2-dep1/2-dep2/uol") 358 | .unwrap(); 359 | 360 | resolve_uol(&uol_node, None); 361 | 362 | let new_uol_node = root 363 | .read() 364 | .unwrap() 365 | .at_path("dir/test1.img/2-dep1/2-dep2/uol") 366 | .unwrap(); 367 | 368 | assert_eq!(new_uol_node.read().unwrap().name.as_str(), "1-dep2"); 369 | assert_eq!( 370 | new_uol_node.read().unwrap().get_full_path(), 371 | "Base/dir/test1.img/1-dep1/1-dep2" 372 | ); 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /tests/wz_file_test.rs: -------------------------------------------------------------------------------- 1 | use wz_reader::property::{Vector2D, WzValue}; 2 | use wz_reader::util::{self, node_util}; 3 | use wz_reader::version::WzMapleVersion; 4 | use wz_reader::{node, wz_image, WzNode, WzNodeArc, WzNodeCast, WzObjectType}; 5 | 6 | type Error = Box; 7 | type Result = std::result::Result; 8 | 9 | /** 10 | * the test.wz file structure: 11 | * test 12 | * - wz_img.img 13 | * - conv 14 | * - 0(vec) 15 | * - 1(png) 16 | * - origin 17 | * - _inlink 18 | * - 1 19 | * - float 20 | * - double 21 | * - long 22 | * - short 23 | * - int 24 | * - 2 25 | * - nil 26 | * - string 27 | * - uol 28 | * - wz_dir 29 | * - wz_img_under_dir.img 30 | * - hi 31 | */ 32 | 33 | fn make_sure_wz_file_version(node: &WzNodeArc, version: i32) { 34 | let wz_file_read = node.read().unwrap(); 35 | 36 | assert!(matches!(wz_file_read.object_type, WzObjectType::File(_))); 37 | 38 | if let WzObjectType::File(file) = &wz_file_read.object_type { 39 | assert_eq!(file.wz_file_meta.patch_version, version); 40 | } 41 | } 42 | 43 | #[test] 44 | fn should_guessing_patch_version() -> Result<()> { 45 | let wz_file = WzNode::from_wz_file_full( 46 | r"tests/test.wz", 47 | Some(WzMapleVersion::BMS), 48 | None, 49 | None, 50 | None, 51 | ); 52 | assert!(wz_file.is_ok()); 53 | 54 | let wz_file = wz_file?.into_lock(); 55 | 56 | make_sure_wz_file_version(&wz_file, -1); 57 | 58 | assert!(node_util::parse_node(&wz_file).is_ok()); 59 | 60 | make_sure_wz_file_version(&wz_file, 123); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[test] 66 | fn should_parsing_with_patch_version() -> Result<()> { 67 | let wz_file = WzNode::from_wz_file_full( 68 | r"tests/test.wz", 69 | Some(WzMapleVersion::BMS), 70 | Some(123), 71 | None, 72 | None, 73 | ); 74 | assert!(wz_file.is_ok()); 75 | 76 | let wz_file = wz_file?.into_lock(); 77 | 78 | assert!(node_util::parse_node(&wz_file).is_ok()); 79 | 80 | make_sure_wz_file_version(&wz_file, 123); 81 | 82 | Ok(()) 83 | } 84 | 85 | fn check_sample_wz_dir(wz_dir: &WzNodeArc) -> Result<()> { 86 | let wz_dir = wz_dir.read().unwrap(); 87 | 88 | let sub_img = wz_dir.at("wz_img_under_dir.img"); 89 | assert!(sub_img.is_some()); 90 | 91 | let sub_img = sub_img.unwrap(); 92 | 93 | assert!(sub_img.read().unwrap().try_as_image().is_some()); 94 | 95 | assert!(node_util::parse_node(&sub_img).is_ok()); 96 | 97 | let child = sub_img.read().unwrap().at("hi"); 98 | assert!(child.is_some()); 99 | 100 | let child = child.unwrap(); 101 | let child = child.read().unwrap(); 102 | 103 | assert!(matches!(child.object_type, WzObjectType::Value(_))); 104 | 105 | if let Some(num) = child.try_as_int() { 106 | assert_eq!(num, &1); 107 | } 108 | 109 | Ok(()) 110 | } 111 | 112 | fn check_sample_wz_img(wz_img: &WzNodeArc) -> Result<()> { 113 | assert!(node_util::parse_node(wz_img).is_ok()); 114 | 115 | let wz_img_read = wz_img.read().unwrap(); 116 | 117 | if let Some(first_folder) = wz_img_read.at("1") { 118 | let first_folder = first_folder.read().unwrap(); 119 | 120 | assert!(first_folder.is_sub_property()); 121 | 122 | let int = first_folder.at("int"); 123 | assert!(int.is_some()); 124 | 125 | let int = int.unwrap(); 126 | let int = int.read().unwrap(); 127 | assert_eq!(int.try_as_int(), Some(&1)); 128 | 129 | let short = first_folder.at("short"); 130 | assert!(short.is_some()); 131 | 132 | let short = short.unwrap(); 133 | let short = short.read().unwrap(); 134 | assert_eq!(short.try_as_short(), Some(&2)); 135 | 136 | let long = first_folder.at("long"); 137 | assert!(long.is_some()); 138 | 139 | let long = long.unwrap(); 140 | let long = long.read().unwrap(); 141 | assert_eq!(long.try_as_long(), Some(&3)); 142 | 143 | let float = first_folder.at("float"); 144 | assert!(float.is_some()); 145 | 146 | let float = float.unwrap(); 147 | let float = float.read().unwrap(); 148 | assert_eq!(float.try_as_float(), Some(&4.1)); 149 | 150 | let double = first_folder.at("double"); 151 | assert!(double.is_some()); 152 | 153 | let double = double.unwrap(); 154 | let double = double.read().unwrap(); 155 | assert_eq!(double.try_as_double(), Some(&4.2)); 156 | } 157 | 158 | if let Some(second_folder) = wz_img_read.at("2") { 159 | let second_folder = second_folder.read().unwrap(); 160 | 161 | assert!(second_folder.is_sub_property()); 162 | 163 | let nil = second_folder.at("nil"); 164 | assert!(nil.is_some()); 165 | 166 | let nil = nil.unwrap(); 167 | let nil = nil.read().unwrap(); 168 | assert!(nil.is_null()); 169 | 170 | let string = second_folder.at("string"); 171 | assert!(string.is_some()); 172 | 173 | let string = string.unwrap(); 174 | let string = string.read().unwrap(); 175 | if let Some(string) = string.try_as_string() { 176 | let s = string.get_string(); 177 | assert!(s.is_ok()); 178 | assert_eq!(&s?, "foo"); 179 | } 180 | 181 | let uol = second_folder.at("uol"); 182 | assert!(uol.is_some()); 183 | 184 | let uol = uol.unwrap(); 185 | let uol = uol.read().unwrap(); 186 | 187 | // uol node should be resolved 188 | assert!(uol.try_as_uol().is_none()); 189 | 190 | if let Some(str) = uol.try_as_string() { 191 | let s = str.get_string(); 192 | assert!(s.is_ok()); 193 | assert_eq!(&s?, "foo"); 194 | } 195 | } 196 | 197 | if let Some(convex_folder) = wz_img_read.at("conv") { 198 | let convex_folder = convex_folder.read().unwrap(); 199 | 200 | assert!(convex_folder.is_convex()); 201 | 202 | let vector = convex_folder.at("0"); 203 | assert!(vector.is_some()); 204 | 205 | let vector = vector.unwrap(); 206 | if let Some(vec) = vector.read().unwrap().try_as_vector2d() { 207 | assert_eq!(vec, &Vector2D(1, 1)); 208 | } 209 | 210 | let png = convex_folder.at("1"); 211 | assert!(png.is_some()); 212 | 213 | let png = png.unwrap(); 214 | let png = png.read().unwrap(); 215 | assert!(png.try_as_png().is_some()); 216 | 217 | let vector = png.at("origin"); 218 | assert!(vector.is_some()); 219 | 220 | let vector = vector.unwrap(); 221 | if let Some(vec) = vector.read().unwrap().try_as_vector2d() { 222 | assert_eq!(vec, &Vector2D(0, 0)); 223 | } 224 | 225 | let inlink = png.at("_inlink"); 226 | assert!(inlink.is_some()); 227 | 228 | let inlink = inlink.unwrap(); 229 | let inlink_str = if let Some(string) = inlink.read().unwrap().try_as_string() { 230 | string.get_string()? 231 | } else { 232 | String::new() 233 | }; 234 | assert_eq!(&inlink_str, "conv/png2"); 235 | } 236 | 237 | Ok(()) 238 | } 239 | 240 | #[test] 241 | fn should_parsing_wz_file_and_check_values() -> Result<()> { 242 | let wz_file = WzNode::from_wz_file_full( 243 | r"tests/test.wz", 244 | Some(WzMapleVersion::BMS), 245 | Some(123), 246 | None, 247 | None, 248 | ); 249 | assert!(wz_file.is_ok()); 250 | 251 | let wz_file = wz_file?.into_lock(); 252 | 253 | assert!(node_util::parse_node(&wz_file).is_ok()); 254 | 255 | let wz_file_read = wz_file.read().unwrap(); 256 | 257 | let wz_dir = wz_file_read.at("wz_dir"); 258 | assert!(wz_dir.is_some()); 259 | 260 | assert!(check_sample_wz_dir(&wz_dir.unwrap()).is_ok()); 261 | 262 | let wz_img = wz_file_read.at("wz_img.img"); 263 | assert!(wz_img.is_some()); 264 | 265 | assert!(check_sample_wz_img(&wz_img.unwrap()).is_ok()); 266 | 267 | Ok(()) 268 | } 269 | 270 | #[test] 271 | fn should_success_using_wz_node_methods_on_childs() -> Result<()> { 272 | let wz_file = WzNode::from_wz_file_full( 273 | r"tests/test.wz", 274 | Some(WzMapleVersion::BMS), 275 | Some(123), 276 | None, 277 | None, 278 | ); 279 | assert!(wz_file.is_ok()); 280 | 281 | let wz_file = wz_file?.into_lock(); 282 | 283 | assert!(node_util::parse_node(&wz_file).is_ok()); 284 | 285 | let wz_file_read = wz_file.read().unwrap(); 286 | 287 | let wz_img = wz_file_read.at("wz_img.img"); 288 | assert!(wz_img.is_some()); 289 | 290 | let wz_node_not_exist = wz_file_read.at("not_exist"); 291 | assert!(wz_node_not_exist.is_none()); 292 | 293 | let wz_img = wz_img.unwrap(); 294 | assert!(node_util::parse_node(&wz_img).is_ok()); 295 | 296 | let nil_node = wz_file_read.at_path("wz_img.img/2/nil"); 297 | assert!(nil_node.is_some()); 298 | 299 | let wz_node_not_exist = wz_file_read.at_path("wz_img.img/2/not_exist"); 300 | assert!(wz_node_not_exist.is_none()); 301 | 302 | let nil_node = nil_node.unwrap(); 303 | let nil_read = nil_node.read().unwrap(); 304 | assert_eq!(nil_read.get_full_path(), "test/wz_img.img/2/nil"); 305 | 306 | let nil_parent = nil_read.at_relative(".."); 307 | assert!(nil_parent.is_some()); 308 | assert_eq!( 309 | nil_parent.unwrap().read().unwrap().get_full_path(), 310 | "test/wz_img.img/2" 311 | ); 312 | 313 | let vec_node = nil_read.at_path_relative("../../conv/0"); 314 | assert!(vec_node.is_some()); 315 | let vec_node = vec_node.unwrap(); 316 | assert_eq!( 317 | vec_node.read().unwrap().get_full_path(), 318 | "test/wz_img.img/conv/0" 319 | ); 320 | 321 | let png_node = nil_read.at_path_relative("../../conv/1"); 322 | assert!(png_node.is_some()); 323 | let png_node = png_node.unwrap(); 324 | assert_eq!( 325 | png_node.read().unwrap().get_full_path(), 326 | "test/wz_img.img/conv/1" 327 | ); 328 | 329 | let wz_node_not_exist = nil_read.at_path_relative("../../not_exist"); 330 | assert!(wz_node_not_exist.is_none()); 331 | 332 | let inlink = png_node.read().unwrap().at("_inlink"); 333 | assert!(inlink.is_some()); 334 | let inlink = inlink.unwrap(); 335 | let inlink_read = inlink.read().unwrap(); 336 | let inlink_string = inlink_read.try_as_string(); 337 | 338 | assert!(inlink_string.is_some()); 339 | let inlink_string = inlink_string.unwrap().get_string(); 340 | 341 | assert!(inlink_string.is_ok()); 342 | 343 | let inlink_string = inlink_string.unwrap(); 344 | let inlink_target = node_util::resolve_inlink(&inlink_string, &inlink); 345 | assert!(inlink_target.is_none()); 346 | 347 | let parent_img = png_node.read().unwrap().get_parent_wz_image(); 348 | assert!(parent_img.is_some()); 349 | let parent_img = parent_img.unwrap(); 350 | assert_eq!( 351 | parent_img.read().unwrap().get_full_path(), 352 | wz_img.read().unwrap().get_full_path() 353 | ); 354 | 355 | let force_get_next_exist_node = parent_img.read().unwrap().at_path_parsed("2/not_exist"); 356 | assert!(matches!( 357 | force_get_next_exist_node, 358 | Err(node::Error::NodeNotFound) 359 | )); 360 | 361 | let force_get_some_node = parent_img.read().unwrap().at_path_parsed("2/nil"); 362 | assert!(force_get_some_node.is_ok()); 363 | 364 | parent_img.write().unwrap().unparse(); 365 | 366 | assert_eq!(parent_img.read().unwrap().children.len(), 0); 367 | 368 | let parent_img_read = parent_img.read().unwrap(); 369 | if let WzObjectType::Image(wz_image) = &parent_img_read.object_type { 370 | let direct_access_not_exist = wz_image.at_path("2/not_exist"); 371 | 372 | assert!(matches!( 373 | direct_access_not_exist, 374 | Err(wz_image::Error::ParsePropertyListError( 375 | util::WzPropertyParseError::NodeNotFound 376 | )) 377 | )); 378 | 379 | let direct_access_nil = wz_image.at_path("2/nil"); 380 | assert!(direct_access_nil.is_ok()); 381 | 382 | let nil = direct_access_nil.unwrap(); 383 | let nil = nil.read().unwrap(); 384 | assert!(matches!( 385 | nil.object_type, 386 | WzObjectType::Value(WzValue::Null) 387 | )); 388 | } 389 | 390 | Ok(()) 391 | } 392 | 393 | #[test] 394 | fn should_success_walk_thorugh() { 395 | let wz_file = WzNode::from_wz_file_full( 396 | r"tests/test.wz", 397 | Some(WzMapleVersion::BMS), 398 | Some(123), 399 | None, 400 | None, 401 | ); 402 | assert!(wz_file.is_ok()); 403 | 404 | let wz_file = wz_file.unwrap().into_lock(); 405 | 406 | let pathes = std::collections::HashSet::from([ 407 | "test", 408 | "test/wz_img.img", 409 | "test/wz_img.img/conv", 410 | "test/wz_img.img/conv/0", 411 | "test/wz_img.img/conv/1", 412 | "test/wz_img.img/conv/1/_inlink", 413 | "test/wz_img.img/conv/1/origin", 414 | "test/wz_img.img/1", 415 | "test/wz_img.img/1/float", 416 | "test/wz_img.img/1/double", 417 | "test/wz_img.img/1/long", 418 | "test/wz_img.img/1/short", 419 | "test/wz_img.img/1/int", 420 | "test/wz_img.img/2", 421 | "test/wz_img.img/2/nil", 422 | "test/wz_img.img/2/string", 423 | "test/wz_img.img/2/uol", 424 | "test/wz_dir", 425 | "test/wz_dir/wz_img_under_dir.img", 426 | "test/wz_dir/wz_img_under_dir.img/hi", 427 | ]); 428 | 429 | util::walk_node(&wz_file, true, &|node| { 430 | let node_read = node.read().unwrap(); 431 | assert!(pathes.contains(node_read.get_full_path().as_str())); 432 | }); 433 | } 434 | 435 | #[test] 436 | fn should_guessing_iv() -> Result<()> { 437 | let wz_file = WzNode::from_wz_file(r"tests/test_need_iv.wz", None); 438 | assert!(wz_file.is_ok()); 439 | 440 | let wz_file = wz_file?.into_lock(); 441 | 442 | assert!(wz_file 443 | .read() 444 | .unwrap() 445 | .try_as_file() 446 | .map(|file| assert_eq!(file.reader.wz_iv, [0xB9, 0x7D, 0x63, 0xE9])) 447 | .is_some()); 448 | 449 | make_sure_wz_file_version(&wz_file, -1); 450 | 451 | assert!(node_util::parse_node(&wz_file).is_ok()); 452 | 453 | make_sure_wz_file_version(&wz_file, 123); 454 | 455 | Ok(()) 456 | } 457 | 458 | #[test] 459 | fn should_success_walk_thorugh_with_iv() { 460 | let wz_file = WzNode::from_wz_file_full( 461 | r"tests/test_need_iv.wz", 462 | Some(WzMapleVersion::EMS), 463 | Some(123), 464 | None, 465 | None, 466 | ); 467 | assert!(wz_file.is_ok()); 468 | 469 | let wz_file = wz_file.unwrap().into_lock(); 470 | 471 | let pathes = std::collections::HashSet::from([ 472 | "test_need_iv", 473 | "test_need_iv/wz_img.img", 474 | "test_need_iv/wz_img.img/conv", 475 | "test_need_iv/wz_img.img/conv/0", 476 | "test_need_iv/wz_img.img/conv/1", 477 | "test_need_iv/wz_img.img/conv/1/_inlink", 478 | "test_need_iv/wz_img.img/conv/1/origin", 479 | "test_need_iv/wz_img.img/1", 480 | "test_need_iv/wz_img.img/1/float", 481 | "test_need_iv/wz_img.img/1/double", 482 | "test_need_iv/wz_img.img/1/long", 483 | "test_need_iv/wz_img.img/1/short", 484 | "test_need_iv/wz_img.img/1/int", 485 | "test_need_iv/wz_img.img/2", 486 | "test_need_iv/wz_img.img/2/nil", 487 | "test_need_iv/wz_img.img/2/string", 488 | "test_need_iv/wz_img.img/2/uol", 489 | "test_need_iv/wz_dir", 490 | "test_need_iv/wz_dir/wz_img_under_dir.img", 491 | "test_need_iv/wz_dir/wz_img_under_dir.img/hi", 492 | ]); 493 | 494 | util::walk_node(&wz_file, true, &|node| { 495 | let node_read = node.read().unwrap(); 496 | println!("{}", node_read.get_full_path()); 497 | assert!(pathes.contains(node_read.get_full_path().as_str())); 498 | }); 499 | } 500 | --------------------------------------------------------------------------------