wzlib-rs
MapleStory WZ file parser and writer — Rust core compiled to WebAssembly with a TypeScript wrapper.
Reads and writes .wz and .ms archives used by MapleStory — directory trees, property trees, canvas images, sound, and video — all in the browser via WASM.
Features
- Standard WZ — parse and save directory trees, IMG properties, version auto-detection, 64-bit support
- Hotfix & List WZ — headerless Data.wz and pre-Big Bang List.wz path indices
- MS archives — v220+
.msfiles: v1 (Snow2) and v2 (ChaCha20) with auto-detection, full read/write for both versions - Canvas decoding — 14 pixel formats (DXT1/3/5, BC7, BGRA4444/8888, RGB565, etc.) → RGBA8888
- Sound extraction — MP3/PCM for Web Audio playback
- Video extraction — MCV container parsing with frame metadata
- Encryption — GMS, EMS/MSEA, BMS/Classic with auto-detection
- File saving — full WZ file packaging (header + directory + images), hotfix Data.wz, and MS file construction
- Small footprint — ~100KB WASM binary (LTO,
opt-level = "s")
Quick Start
Prerequisites
Build
# Build WASM package (--features wasm is required)
# Build TypeScript wrapper
Test
Run Demo
# Open http://localhost:8080
Drop a .wz or .ms file to explore directory trees, view images, play sounds, and inspect video metadata.
Usage
import { WzParser } from "wzlib";
const parser = await WzParser.create();
// Parse a .wz file
const wzData = new Uint8Array(await fetch("Map.wz").then(r => r.arrayBuffer()));
const root = parser.parseFile(wzData, "gms", 83);
// Navigate the tree
const mob = root.resolve("Mob/100100.img");
console.log(mob?.childNames);
// Decode a canvas
const rgba = parser.decodeCanvas(compressedPng, 64, 64, 2);
const imageData = parser.toImageData(rgba, 64, 64);
ctx.putImageData(imageData, 0, 0);
// Parse a .ms file (v220+)
const msData = new Uint8Array(await fetch("Data.ms").then(r => r.arrayBuffer()));
const entries = parser.parseMsFile(msData, "Data.ms");
const imgTree = parser.parseMsImage(msData, "Data.ms", 0);
// Edit and rebuild a hotfix Data.wz
const { properties, blobs } = parser.parseHotfixForEdit(wzData, "bms");
properties.find(p => p.name === "hp").value = 9999;
const saved = parser.buildImage(properties, blobs, "bms");
// Build a .ms file from entries (version: 1 = Snow2, 2 = ChaCha20)
const msEntries = [{ name: "Mob/test.img", entryKey: [...key16] }];
const msSaved = parser.buildMsFile("output.ms", "salt", msEntries, [imageBlob], 2);
Architecture
src/ Rust WASM core
├── crypto/ AES, Snow2, ChaCha20, custom encryption, CRC32
├── wz/ WZ/MS/List file parsing + writing, MCV video, properties
├── image/ Pixel format decoders (DXT, BC7, etc.) → RGBA8888
└── wasm_api.rs wasm-bindgen exports (parse + save)
ts-wrapper/src/ TypeScript wrapper
├── wz-parser.ts High-level WzParser class
├── wz-node.ts Tree navigation (resolve, walk)
└── types.ts Shared type definitions
demo/ Browser file explorer SPA
├── js/ Modular JS (state, tree, property, search, media…)
├── index.html Entry page
└── styles.css Styles
Ported From
Harepacker-resurrected — MapleLib WzComparerR2 - WzLib