dm2-tools
v0.2.1
Published
WebAssembly bindings exposing the Dungeon Master II parsers to JavaScript via wasm-bindgen.
Downloads
489
Maintainers
Readme
dm2-wasm
Browser bindings for the Dungeon Master II asset parsers in this workspace.
It wraps the dm2-saves, dm2-dungeon, dm2-graphics, dm2-music, and
dm2-ftl crates behind a small #[wasm_bindgen] API so a JS/TypeScript
front end can parse, convert, and re-encode game files entirely client-side
— no server round-trip, no game data ever leaves the browser.
The crate is a thin JS-marshalling layer. Every export takes bytes
(Uint8Array) or text (string) and returns bytes, text, or a boolean;
none of it does its own parsing — that all lives in the wrapped crates.
Install
npm install dm2-toolsThe npm package is called dm2-tools (matching the Python distribution)
while the crate is dm2-wasm; build-npm.sh below reconciles the two. It is
built for the bundler target, so Vite, webpack and friends import it with
no explicit initialisation step:
import { saveToJson } from "dm2-tools";Build
For the npm package:
crates/dm2-wasm/build-npm.sh # add --dev to skip wasm-optThat wraps wasm-pack build --target bundler --release and patches the
generated package.json — see the comments in the script for why both are
needed. The result lands in crates/dm2-wasm/pkg, ready for npm publish.
For a page with no bundler at all, build the web target directly:
wasm-pack build crates/dm2-wasm --target web--target web produces an ES module (pkg/dm2_wasm.js + pkg/dm2_wasm_bg.wasm)
that a page can import directly, at the cost of an explicit init() call —
see the usage example below. Add --dev to either while iterating, to skip
the wasm-opt optimization pass and get a much faster, unoptimized build.
The crate also builds and tests as an ordinary native Rust crate
(cargo build -p dm2-wasm, cargo test -p dm2-wasm) — the #[wasm_bindgen]
attributes compile inertly off the wasm32 target, so the underlying parsers
stay covered by the workspace's normal native test suite.
JS API
All exports throw a JS Error (via JsError) on failure — wrap calls in
try/catch. Byte parameters/returns are Uint8Array; on the JS side pass
a Uint8Array for any &[u8] parameter below.
| Export | Params | Returns | Purpose |
| --- | --- | --- | --- |
| saveToJson | bytes: Uint8Array | string | Detect + parse a savegame and dump it to the self-contained, lossless JSON representation (round-trips byte-exact via saveFromJson). |
| saveFromJson | json: string | Uint8Array | Rebuild savegame bytes from a saveToJson dump. No external files needed. |
| saveInfoJson | bytes: Uint8Array | string | Detect + parse a savegame and dump it to a lossy, human-readable gameplay-only JSON view (inventory, stats, party state). |
| dungeonToJson | bytes: Uint8Array | string | Detect + parse a dungeon.dat and dump it to the self-contained, lossless JSON representation (round-trips byte-exact via dungeonFromJson). |
| dungeonFromJson | json: string | Uint8Array | Rebuild dungeon.dat bytes from a dungeonToJson dump. No external files needed. |
| dungeonSkprojectJson | bytes: Uint8Array | string | Detect + parse a dungeon.dat and dump it to the lossy, skproject-compatible JSON representation. |
| graphicsHeaderJson | bytes: Uint8Array | string | Parse a graphics.dat header (auto-detecting byte order) and dump a small JSON summary (format version, byte order, entry count). |
| graphicsDecodePng | bytes: Uint8Array, cls1: number, cls2: number, cls4: number | Uint8Array | Parse a graphics.dat archive and decode the image at class tuple (cls1, cls2, 0x01, cls4) to a truecolor PNG. Read-only decoder. |
| musicDetect | bytes: Uint8Array | string | Sniff a standalone music file's container format: "hmp", "mod", "modplayer41a", or "smf". |
| musicHmpToSmf | bytes: Uint8Array | Uint8Array | Convert a DOS HMI/HMP music stream to a Standard MIDI File. Read-only decoder. |
| musicRenderModWav | bytes: Uint8Array | Uint8Array | Render an Amiga ProTracker module (packed with The Player 4.1A, or plain) to a stereo WAV file. Read-only decoder. |
| musicSndToWav | bytes: Uint8Array | Uint8Array | Decode a Mac resource-fork 'snd ' resource payload to a mono WAV file. Read-only decoder. |
| ftlInfoJson | bytes: Uint8Array | string | Parse an FTL 68k module container (Sega CD / Amiga) and dump a small JSON summary (hunk sizes, checksum status). Read-only decoder. |
| ftlRoundtripOk | bytes: Uint8Array | boolean | Parse an FTL 68k module and re-emit it, checking the result is byte-exact with the input. Read-only decoder. |
Only the save and dungeon pairs are read/write; graphics, music, and FTL exports are read-only decoders with no matching "from JSON"/encode entry point.
Usage example
A minimal round-trip savegame edit, entirely in the browser:
import { saveToJson, saveFromJson } from "dm2-tools";
// `saveBytes` is a Uint8Array read from a file input, drag-and-drop, etc.
const dumpJson = saveToJson(saveBytes);
const dump = JSON.parse(dumpJson);
// Edit whatever field the JSON dump exposes, e.g. the first champion's
// current hit points:
dump.gameplay.champions[0].cur_hp = 999;
const editedBytes = saveFromJson(JSON.stringify(dump));
// `editedBytes` is a Uint8Array — byte-exact `SkSave#.dat` content aside
// from the edited field, ready to offer back for download.Against a web-target build from a checkout the same code needs the module
initialised first — import init, { saveToJson } from "./pkg/dm2_wasm.js"
followed by await init() — after which the calls are identical.
The dump/reconstruct pair is self-contained: saveToJson's output carries
everything saveFromJson needs, so no other file has to be fetched or
uploaded alongside it. The same pattern applies to dungeonToJson /
dungeonFromJson for dungeon.dat files.
