lzexe
A pure-Rust decompressor for DOS EXE files packed with LZEXE — the packer written by Fabrice Bellard in 1989/1990 that shipped with many early DOS games (id Software, Apogee, Softdisk, and others). LZEXE uses the LZSS (Lempel-Ziv-Storer-Szymanski) algorithm internally; this crate is unrelated to LZW.
Supports LZEXE 0.90, 0.91, and 0.91e.
Library usage
let compressed = read.unwrap;
// Transparently handles both compressed and uncompressed EXEs.
// Returns Ok(input.to_vec()) for uncompressed files — no allocation
// until the LZEXE signature is confirmed.
let data = decompress.unwrap;
Check without decompressing:
if is_compressed
unlzexe CLI
A drop-in replacement for the classic DOS UNLZEXE utility:
unlzexe input.exe [output.exe]
If output.exe is omitted the decompressed file is written to the same
directory as the input with the same name. Exit codes: 0 = success,
1 = error, 2 = input is not LZEXE-compressed.
Install from crates.io:
cargo install lzexe
Output format
The returned Vec<u8> is a valid MZ EXE with a reconstructed header followed
by the decompressed load image. Absolute file offsets in the output match those
of the original uncompressed binary, so asset extractors that rely on fixed
offsets work without modification.
All EXE header fields are recovered from the packed file's info block and the packer's header transformation formulas (see lzexe.pas):
| Field | Recovery method |
|---|---|
| IP, CS, SP, SS | Stored verbatim in the LZEXE info block at CS:0 |
| minalloc | compressed_minalloc − (decalage + dcmpsizepar + 9) |
| maxalloc | Same subtraction; 0xFFFF if the compressed value was clamped |
| Relocation table | Delta-decoded from the packed reloc stream at CS:0x158 (v0.91) or CS:0x19D (v0.90) |
No dependencies
Zero runtime dependencies. no_std-compatible (requires alloc).
Testing
The integration tests use the official LZEXE 0.91 binaries (LZEXE.EXE and
UPACKEXE.EXE) as fixtures — both are self-compressed with LZEXE 0.91 and
distributed by Bellard under the MIT license. Golden output files are committed
alongside them for byte-exact regression testing.
Additional synthetic test vectors from camoto-project/gamecompjs (GPL-3.0) cover all three LZEXE versions: 0.90, 0.91, and 0.91e.
cargo test
Correctness is verified independently of our own output by checking that the
decompressed binaries contain version strings that appear verbatim in the
Pascal source ("LZEXE.EXE Version 0.91", "UPACKEXE").
References
- LZEXE homepage — Fabrice Bellard's original tool and source release (MIT license, May 2025)
- LZEXE 0.91 source (zip) — official Pascal + ASM source; primary reference for this implementation
- LZEXE decompression internals — step-by-step breakdown of the decompressor by Scott Smitelli
- Modding Wiki: LZEXE — version signatures and variant notes
- gamecompjs cmp-lzexe.js — JavaScript reference implementation by Adam Nielsen (GPL-3.0); referenced for relocation table decoding
- UNLZEXE — the classic DOS-era decompressor utility