kwaj-lzh
v0.1.0
Published
Zero-dependency decompressor for the Microsoft KWAJ and SZDD container formats (the expand.exe family of compressed install-media files), including KWAJ compression method 3 (LZH). Works in the browser and Node.
Maintainers
Readme
kwaj-lzh
Zero-dependency decompressor for the Microsoft KWAJ and SZDD container
formats — the COMPRESS.EXE / EXPAND.EXE family used for the compressed files
on old MS-DOS and 16-bit Windows install media (the ones with a trailing
underscore in the extension, e.g. SETUP.EX_, DRIVER.DL_, HELP.HL_).
Includes KWAJ compression method 3 (LZH) — LZSS with static Huffman coding — which most other JavaScript libraries do not implement.
- Pure TypeScript, no dependencies
- Runs in the browser and Node.js — every function takes and returns a
Uint8Array - Ships ESM with type definitions
Install
npm install kwaj-lzhUsage
Auto-detect the container by its signature:
import { decompress } from 'kwaj-lzh';
const compressed = new Uint8Array(/* bytes of a .EX_ / SZDD / KWAJ file */);
const original = decompress(compressed); // Uint8ArrayNode — read a compressed file and write the expanded one:
import { readFile, writeFile } from 'node:fs/promises';
import { decompress } from 'kwaj-lzh';
const input = await readFile('SETUP.EX_');
await writeFile('SETUP.EXE', decompress(input));Browser — let the user drop a file in:
import { decompress } from 'kwaj-lzh';
const buf = await file.arrayBuffer();
const original = decompress(new Uint8Array(buf));If you already know the format, call it directly and skip detection:
import { decompressKwaj, decompressSzdd, readKwajHeader } from 'kwaj-lzh';
const header = readKwajHeader(bytes); // { method, dataOffset, flags, length? }
const out = decompressKwaj(bytes);Supported formats
| Container | Method | Supported |
| --------- | ----------------- | --------- |
| KWAJ | 0 — none (store) | ✅ |
| KWAJ | 1 — XOR | ✅ |
| KWAJ | 2 — SZDD (LZSS) | ✅ |
| KWAJ | 3 — LZH | ✅ |
| KWAJ | 4 — MSZIP | ❌ (throws UnsupportedCompressionError) |
| SZDD | LZSS (mode A) | ✅ |
API
decompress(bytes: Uint8Array): Uint8Array— detect KWAJ or SZDD and decompress.decompressKwaj(bytes): Uint8Array/readKwajHeader(bytes): KwajHeader/hasKwajSignature(bytes): booleandecompressSzdd(bytes): Uint8Array/readSzddHeader(bytes): SzddHeader/hasSzddSignature(bytes): booleanlzssDecompress(bytes, start, mode)and theLZSS_MODE_*constants, for raw LZSS streams.- Errors:
FormatError(bad signature / malformed input),UnsupportedCompressionError(recognized but unimplemented method).
Notes
- KWAJ MSZIP (method 4) is deflate-based and is not implemented yet.
- SZDD output is truncated to the exact length recorded in the header.
Credits
This is an original, clean-room TypeScript port of the publicly documented KWAJ
and SZDD wire formats. The reference used for the format details is libmspack
by Stuart Caie — specifically its mspack/kwajd.c (KWAJ, incl. the method-3 LZH
codec by Jeff Johnson) and mspack/szddd.c decompressors:
- Source repo: https://github.com/kyz/libmspack
- Project page: https://www.cabextract.org.uk/libmspack/
libmspack is licensed LGPL-2.1. Only its wire format was reproduced here — no libmspack source code is copied or machine-translated into this project, which is an independent implementation licensed under MIT.
