XP Β· VX Β· VX Ace Β· MV Β· MZ β all five engine generations, one command, no install.
$ rpgm-decrypt ./MyGame ./decrypted
[key] found encryptionKey in www/js/System.json
+ Title.png_ detected as MV
> Title.png_ -> decrypted/www/img/Title.png [MV]
+ Battle.ogg_ detected as MV
> Battle.ogg_ -> decrypted/www/audio/Battle.ogg [MV]
...
=== summary ===
scanned: 1873
decrypted: 1869
failed: 0
key source: www/js/System.json
duration: 4.2s
by format: MV=1869Need machine-readable output for a script? Add --report-format json:
{
"scanned": 1873,
"decrypted": 1869,
"failed": 0,
"key_source": "www/js/System.json",
"per_format": { "MV": 1869 },
"duration_s": 4.2
}Tip
You don't install anything. Download one file, run it. No Python, no .NET, no Node β the binary carries everything it needs inside.
Important
Use it on content you're allowed to touch. Recovering your own assets, a lost encryption key, an authorized translation, or game preservation β all fine. Decrypting someone else's work to steal it is not. The license is permissive; your responsibility isn't.
| Feature | In plain words | |
|---|---|---|
| π§© | All 5 formats | .rgssad / .rgss2a / .rgss3a (XP/VX/VX Ace) and MV/MZ (.png_, .ogg_, .rpgmvp, .pak) β one tool, not five. |
| π | Finds the key for you | Reads System.json / scans rpg_core.js automatically. No key to hunt down by hand. |
| π¦ | One self-contained binary | Copy it to a clean machine and it just runs. Zero dependencies. |
| π‘οΈ | Safe by construction | Hostile/corrupt input never crashes it (proven by fuzzing) and can't escape your output folder (Zip-Slip blocked). |
| π€ | Script-friendly | --report-format json + NDJSON logs pipe straight into jq. |
| π§ͺ | Provably correct core | Key functions carry formal contracts (Gospel); core safety properties are machine-checked (Why3 / Z3). |
1. Download the binary for your OS from the Releases page.
2. Run it on a game folder:
# simplest form β output goes next to the game folder
rpgm-decrypt ./MyGame
# pick your own output folder
rpgm-decrypt ./MyGame ./decrypted
# only the decrypted assets, not a full game copy
rpgm-decrypt ./MyGame --assets-only
# look but don't write anything (see what it WOULD do)
rpgm-decrypt ./MyGame --dry-runThat's it. By default you get a full, ready-to-run copy of the game: every
file is mirrored into the output folder, the encrypted assets are decrypted in
place (.rpgmvp/.png_ β .png, etc.), and System.json's encryption flags
are cleared β so the copy just runs, with all the .json/.txt/script files
left intact. Want only the decrypted media instead? Add --assets-only.
Not comfortable with the command line? The Windows download includes a small
helper, decrypt.bat:
- Download
rpgm-decrypt-windows-x64.zipand extract the whole folder (keeprpgm-decrypt.exe,zlib1.dllanddecrypt.battogether). - Drag your game folder onto
decrypt.bat. - Done β a full, ready-to-run decrypted copy of the game appears in a new
folder next to your game, named
<your-game>-decrypted. Everything else (.json,.txt, scripts, the exe) is copied across untouched, so you can run or edit the copy directly.
No install, no typing. (zlib1.dll is just the standard compression library
Windows needs to read .pak archives β keep it next to the .exe.)
Prefer clicking to typing? There is a separate, optional GUI build β
rpgm-decrypt-gui-windows-x64.zip β that wraps the same OCaml decrypter behind a
native GTK3 window. It is a self-contained hybrid: the OCaml binary does the
decrypting, the Ruby GUI just drives it and shows live progress.
Download rpgm-decrypt-gui-windows-x64.zip from the
Releases page, extract it (the archive unpacks into a single
rpgm-decrypt-gui\ folder β put it anywhere you can write, like Desktop or
Documents), and double-click rpgm-decrypt-gui.exe. Everything ships inside:
rpgm-decrypt.exe, zlib1.dll, the GTK3 runtime and a README.txt quick
start. No Ruby, no GTK, no other installs needed.
The GUI is versioned on its own line (independent of the core): the current release is GUI 1.0.0, and the version is shown under the window title.
Tip
Windows SmartScreen may show "Windows protected your PC" because the exe is unsigned. Click More info β Run anyway. This is normal for open-source builds without a paid code-signing certificate.
The GUI has two modes:
| Mode | What it does |
|---|---|
| Whole game | Decrypt a whole game folder (full mirror copy, or Assets-only). Same result as the CLI. Live progress bar + log. |
| Single file | List the game's files, pick one encrypted asset (.png_ / .rpgmvp / .ogg_ / β¦), decrypt just that file, and restore it in place β replace the encrypted original with the open one (the original is saved as <file>.bak first). Great for poking at a single sprite/audio without decrypting the whole game. |
The window also exposes the core's --dry-run mode (scan and report without
writing anything), a Stop button that terminates a running job,
--password-file candidate key lists, a confirmation before merging into a
non-empty output folder, an Open output folder shortcut, and Save log
to dump the progress log to a file.
Note
Point-decrypt is a convenience, not a re-encrypter. It decrypts the game
into a temporary folder, grabs the one file you asked for, then deletes the
rest β so the OCaml core is never modified. "Restore in place" puts the
open (decrypted) file back; if you want the game re-encrypted you'd use the
CLI normally. Archives (.rgssad / .rgss2a / .rgss3a) are not supported in
point mode β use Whole game for those.
Tip
The GUI and the CLI ship as separate assets. Keep using the CLI (or
decrypt.bat) if you just want a full decrypted copy; grab the GUI when you
want a window and per-file control.
Note
First launch asks you to star β the repository. The GUI is free and open source; on the first run it shows a one-time "support the project" screen: sign in with GitHub (a 3-step device flow β the code opens in your browser, no password is typed into the app), star the repository, and the GUI unlocks forever on that computer β the activation is stored locally and works offline from then on. Identity is verified against the signed-in account, so starring with someone else's nickname is not possible.
When you don't pass a key, rpgm-decrypt looks for it the way the engine stores it:
1. www/js/System.json β read "encryptionKey" (the normal case)
2. www/data/System.json β same, alternate layout
3. www/js/rpg_core.js β extract the 32-hex key literal (never runs the JS)
4. *.js sweep β last resort, scan every script for the key
Note
Can't find it automatically (custom build, stripped files)? Hand it the key:
rpgm-decrypt ./MyGame --password deadbeef00112233445566778899aabb
rpgm-decrypt ./MyGame --password-file keys.txt # try a list, first match wins
rpgm-decrypt ./MyGame --vxace-seed 1a2b3c4d # verify VX Ace archive seed| Flag | Default | What it does |
|---|---|---|
--password <hex32> |
β | One key, 32 hex chars (16 bytes). |
--password-file <path> |
β | Newline-separated candidate keys; first that works wins. |
--vxace-seed <8hex> |
β | Verify the embedded RPG Maker VX Ace seed; MV/MZ still need --password. |
--assets-only |
off | Write only the decrypted assets, not a full game copy. |
--log-format human|json |
human |
Per-file progress on stderr (json = one NDJSON event per line). |
--report-format human|json |
human |
Final summary on stdout. |
--dry-run |
off | Walk + detect + classify, but write nothing. |
--quiet |
off | Hide per-file progress, show only the summary. |
-h, --help |
β | Show help. |
--version |
β | Version + supported formats. |
| Code | Meaning |
|---|---|
0 |
β Everything decrypted. |
2 |
|
3 |
π½ I/O error (can't read input / write output). |
4 |
π No key could be recovered. |
5 |
π‘ Partial β at least one file failed (details in the report). |
Most decrypters are a quick script. This one is engineered as a real product β that's the part a senior engineer notices:
| Discipline | What it means here |
|---|---|
| 𧬠Pure functional core | Narrow .mli per module, 103 behavioural checks + 12 QCheck property tests, mutation-tested (7/7 historical mutants killed). |
| π₯ Fuzzed against chaos | 21M+ mutated/garbage inputs, 0 crashes β plus coverage-guided afl-fuzz in CI. It does not fall over on broken files. |
| π Property-based tests | Invariants, not examples: "XOR is its own inverse for any key & data", "a path can never escape the output root". |
| π Formally verified core | The parser carries Gospel contracts; its key safety properties β bounds, little-endian decode, and the no-path-escape (Zip-Slip) invariant β are machine-checked by Why3 / Z3. (details) |
| π§Ή Zero-warning, formatted, clean-room | Builds warning-as-error, ocamlformat-canonical, Apache-2.0, no decompiled code. |
| π CI on every push | Linux + Windows build/test, security scanners (SAST, Secret scanning), fuzzing, verification. |
Caution
The decrypter is built for robustness on adversarial input on purpose: it parses file formats produced by other programs, so it treats every byte as untrusted. That's why the parser is fuzzed and formally verified rather than just "tested".
Generated by bisect_ppx (OCaml's coverage tool) over the historical 92 behavioural
checks + 12 QCheck properties. The committed snapshot predates the additional
regression checks now in test/test.ml; regenerate it before using the
percentages as a current measurement. The numbers split into two tiers: the
core decryption logic (XOR crypto, key derivation, the archive parsers) is
80β100% covered, while the orchestration / I-O modules that talk to the
disk/JSON/regex (report, key_discovery, log) sit at 36β52%.
| File | Coverage | Covered / Total |
|---|---|---|
lib/vxace_key.ml |
100.0% | 35 / 35 |
lib/io.ml |
100.0% | 4 / 4 |
lib/crypto.ml |
95.4% | 104 / 109 |
lib/rgssad_core.ml |
87.0% | 67 / 77 |
lib/walk.ml |
81.1% | 30 / 37 |
lib/vxace.ml |
80.4% | 37 / 46 |
lib/mv.ml |
75.7% | 56 / 74 |
lib/dispatch.ml |
63.2% | 55 / 87 |
lib/mz.ml |
63.0% | 17 / 27 |
lib/report.ml |
51.8% | 184 / 355 |
lib/key_discovery.ml |
50.3% | 80 / 159 |
lib/types.ml |
45.8% | 11 / 24 |
lib/log.ml |
36.4% | 24 / 66 |
lib/xp.ml |
33.3% | 1 / 3 |
lib/vx.ml |
33.3% | 1 / 3 |
| Project | 63.8% | 706 / 1106 |
xp.ml/vx.mlare thin wrappers overrgssad_core(one-lineparsefunctions); their low counts reflect the wrapper delegation, not untested logic β the shared parser is 87.9% covered.
Why the pure core is 80β100% but I/O is 36β41% (in plain words)
Think of the tool in two layers:
-
The pure core β the actual decryption: XOR math, key derivation, the archive parsers, the file-format classifiers. This is pure logic: bytes in, bytes out, no disk, no network. It is exactly what the test suite exercises (103 behavioural checks + 12 property tests feed it thousands of crafted inputs in-process), so the crypto/parser core ends up 80β100% covered. This is the part that must be correct β and it is the part that is also fuzzed and formally verified.
-
The I/O modules β
report.run(walks the game folder, writes the decrypted mirror tree),key_discovery(readsSystem.json, scansrpg_core.js),log(formats output). These only do real work when pointed at an actual RPG Maker game on disk β hundreds of files, real JSON, real directory structures. That kind of load can't run inside the in-process test suite, and we deliberately do not commit real game files into the repo (licensing cleanliness β seeCONTRIBUTING.md). So the suite exercises only the fast/synthetic paths of these modules, leaving their full end-to-end branches uncounted. Hence 36β52%.
This is not a gap in the decryption correctness β the core that does the decrypting is nearly fully covered, fuzzed, and proven. It is a measurement artifact: the I/O layer's real workload needs real games, which live outside the repo. A CI run against a real (private, sanitized) game fixture would lift these numbers; it is just not something we keep in version control.
Full browsable report: ocaml/coverage/html/index.html Β·
machine-readable: ocaml/coverage/coverage.cobertura.xml Β·
raw summary: ocaml/coverage/summary.txt.
How coverage is generated
opam install bisect_ppx # OCaml β€ 5.3 (use a separate switch on 5.5)
cd ocaml
dune build --instrument-with bisect_ppx --force test/test.exe test/prop/prop.exe
BISECT_FILE=$PWD/_build/cov dune exec --instrument-with bisect_ppx --force test/test.exe
BISECT_FILE=$PWD/_build/cov dune exec --instrument-with bisect_ppx --force test/prop/prop.exe
bisect-ppx-report summary --per-file _build/*.coverage
bisect-ppx-report cobertura coverage.cobertura.xml _build/*.coverage
bisect-ppx-report html _build/*.coverage -o coverage/htmlThe ocaml-coverage GitLab CI job (when: manual) reproduces this and
exposes the report as a downloadable artifact. bisect_ppx 2.8.x emits
html / cobertura / coveralls / summary (it dropped native LCOV
output; Cobertura XML is the machine-readable interchange format here).
A directed smoke campaign: 7 single-edit mutants on the verified core, each
rebuilt and run against the historical 92 behavioural checks + 12 QCheck properties.
First wave found 2 real coverage gaps (high bytes of read_u32_le, the
ogg extension map); after targeted test additions, all 7 are killed.
| # | Mutant | Result |
|---|---|---|
| M1 | crypto: xor key index i mod klen β i mod (klen+1) |
π΄ killed |
| M2 | crypto: xor output size n β n+1 |
π΄ killed |
| M3 | vxace_key: derive_master_key +3 β +4 |
π΄ killed |
| M4 | rgssad_core: read_u32_le byte1 lsl 8 β lsl 16 |
π΄ killed |
| M5 | report: safe_join full = root β full = full |
π΄ killed |
| M6 | dispatch: choose_output_extension png β .pngx |
π΄ killed |
| M7 | dispatch: choose_output_extension ogg β .png |
π΄ killed |
Full report with the survivor analysis and the test additions that closed the
gaps: ocaml/MUTATION_REPORT.md.
OCaml (flagship β single native binary)
cd ocaml
dune build --profile release
./_build/default/bin/main.exe --versionFor a portable, statically-linked Linux binary, build on Alpine (musl) with
dune build --profile static.
Run the tests
# OCaml: 103 behavioural checks + 12 QCheck property tests
cd ocaml && dune exec --profile release test/test.exe
# QCheck properties (needs `opam install qcheck`)
cd ocaml && dune exec --profile release test/prop/prop.exeTest fixtures are generated synthetically at test time β we never commit real RPG Maker game bytes, to keep the repo licensing-clean.
Project layout
ocaml/ OCaml flagship β single native binary
lib/ pure core + narrow .mli (Gospel specs)
bin/ CLI front-end
test/ test/prop/ behavioural checks + QCheck properties
fuzz/ afl-instrumented fuzz target
proofs/ Why3 + Z3 deductive verification (Zip-Slip, bounds)
coverage/ bisect_ppx coverage report (HTML + Cobertura XML)
MUTATION_REPORT.md mutation testing campaign (7/7 killed)
gui/ Windows GUI (Ruby + GTK3) β hybrid front-end over the OCaml binary
main.rb GTK3 window: "Whole game" + point-decrypt "Single file"
build_windows.ps1 OCRA bundle β rpgm-decrypt-gui-windows-x64.zip
.gitlab-ci.yml test + fuzz + verification farm
.github/workflows/ non-release verification + tag-triggered releases
Apache-2.0 β free to use, modify, and ship. See LICENSE.
Clean-room implementation: built from public format documentation and community wikis, no decompiled engine code. Every magic-byte constant is cited in the source.
Found a game it can't crack? Open an issue with the game layout and the failing file β real bugs get fixed.
Built turn-key, spec-to-shipped. Every claim here is backed by a test, a fuzz run, or a proof.

