Skip to content

Pure TypeScript implementation of StormLib for reading MPQ archives

Notifications You must be signed in to change notification settings

tmo-gg/stormlib-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stormlib-js

Pure TypeScript implementation of StormLib for reading MPQ (Mo'PaQ) archives. No native bindings, no WASM — just TypeScript.

MPQ is the archive format used by Blizzard Entertainment games including Diablo, StarCraft, Warcraft III, and World of Warcraft.

Features

  • Read-only MPQ archive support
  • MPQ V1 through V4 format support
  • Pure TypeScript — no native addons or WASM compilation required
  • Synchronous API — simple, straightforward file extraction
  • Single runtime dependency — only pako for zlib
  • Dual module output — ships both CommonJS and ESM builds with full type declarations

Compression algorithms

Algorithm Status
zlib (Deflate) Supported
PKWARE DCL (Implode) Supported
Blizzard Huffman Supported
ADPCM Mono / Stereo Supported
Sparse (RLE) Supported
BZIP2 Not supported
LZMA Not supported

BZIP2 and LZMA are rarely used in practice (only some SC2/HotS archives). If you need them, contributions are welcome.

Table formats

Table Status
Classic Hash Table Supported
Classic Block Table Supported
Hi-Block Table (V2+) Supported
HET Table (V3+) Supported
BET Table (V3+) Supported

Installation

npm install stormlib-js

Quick start

import { MpqArchive } from 'stormlib-js';

// Open an archive
const archive = MpqArchive.open('game.mpq');

// List known files (from internal listfile)
const files = archive.getFileList();
console.log(files);

// Extract a file
const data = archive.extractFile('war3map.j');
console.log(data.toString('utf-8'));

// Search with wildcards
const results = archive.findFiles('*.blp');
for (const entry of results) {
  console.log(entry.fileName, entry.fileSize);
}

// Clean up
archive.close();

API

MpqArchive

The main class for working with MPQ archives.

MpqArchive.open(path, options?)

Opens an MPQ archive from a file path.

const archive = MpqArchive.open('archive.mpq');

// With options
const archive = MpqArchive.open('archive.mpq', {
  noListfile: false,    // Skip loading internal (listfile)
  noAttributes: false,  // Skip loading internal (attributes)
  noHeaderSearch: false, // Don't search for MPQ header (assume offset 0)
  forceMpqV1: false,    // Force reading as MPQ V1
});

archive.hasFile(name)

Returns true if the file exists in the archive.

if (archive.hasFile('war3map.j')) {
  // ...
}

archive.extractFile(name)

Extracts a file and returns its contents as a Buffer.

const buf = archive.extractFile('war3map.w3i');

archive.openFile(name)

Opens a file handle for more control over reading.

const file = archive.openFile('units.doo');
console.log(file.size, file.compressedSize, file.flags);
const data = file.read();
file.close();

archive.getFileList()

Returns an array of known file names (populated from the internal listfile).

const names = archive.getFileList();
// ['war3map.j', 'war3map.w3e', ...]

archive.findFiles(mask?)

Searches for files matching a wildcard pattern. Supports * and ?.

const textures = archive.findFiles('*.blp');
for (const f of textures) {
  console.log(f.fileName, f.fileSize, f.compSize);
}

archive.enumerateFiles()

Lists all file entries including unnamed ones. Unnamed entries are given synthetic names like File00000001.xxx.

const all = archive.enumerateFiles();
console.log(`Total: ${all.length} files`);

archive.addListfile(names)

Applies an external list of file names to resolve unnamed entries. Useful for archives without an internal (listfile).

const resolved = archive.addListfile([
  'war3map.j', 'war3map.w3e', 'war3map.w3i',
]);
console.log(`${resolved} new names resolved`);

archive.getHeader()

Returns the parsed MPQ header.

const header = archive.getHeader();
console.log(`Format: V${header.wFormatVersion + 1}`);
console.log(`Sector size: ${512 << header.wSectorSize}`);

archive.close()

Closes the archive and releases the underlying file handle.

Error classes

All errors extend MpqError:

Class Description
MpqNotFoundError File not found in the archive
MpqCorruptError Archive data is corrupt
MpqUnsupportedError Unsupported feature (e.g. BZIP2)
MpqEncryptionError Decryption failure
MpqCompressionError Decompression failure
import { MpqNotFoundError } from 'stormlib-js';

try {
  archive.extractFile('nonexistent.txt');
} catch (err) {
  if (err instanceof MpqNotFoundError) {
    console.log('File does not exist');
  }
}

Supported games

This library can read MPQ archives from:

  • Diablo / Diablo II (V1, Huffman/PKWARE compression)
  • StarCraft / Brood War (V1, Huffman/PKWARE compression)
  • Warcraft III / The Frozen Throne (V1, zlib compression)
  • World of Warcraft (V2, 64-bit offsets)
  • StarCraft II (V3/V4, HET/BET tables)

Advanced usage

Low-level crypto and hashing functions are exported for specialized use cases:

import {
  hashString, jenkinsHash,
  decryptBlock, encryptBlock,
  getStormBuffer,
  MPQ_HASH_TABLE_INDEX,
  MPQ_HASH_NAME_A,
} from 'stormlib-js';

// Compute MPQ hash
const idx = hashString('(listfile)', MPQ_HASH_TABLE_INDEX);

// Jenkins hash for HET tables
const hash = jenkinsHash('war3map.j');

Development

# Install dependencies
npm install

# Run tests
npm test

# Type check
npm run typecheck

# Build
npm run build

Acknowledgments

License

MIT

About

Pure TypeScript implementation of StormLib for reading MPQ archives

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published