Skip to content

invoker-bot/node-stormlib

Repository files navigation

node-stormlib

Node.js bindings for Blizzard MPQ archives, powered by StormLib.

status license node native

node-stormlib exposes a small Node.js API for reading Blizzard MPQ archives, including Warcraft III .w3m and .w3x maps. The native addon links against the vendored StormLib source tree.

Features

Capability API
Read archive metadata getArchiveInfo()
List files with StormLib masks listFiles()
Check for a file hasFile()
Inspect one file before reading getFileInfo()
Read archive files as Buffer objects readFile()
Read files from a worker thread readFileAsync()
Stream archive files in chunks createReadStream()
Extract archive files to disk extractFile()
Create new MPQ archives createArchive()
Add compressed local files addFile()
Write compressed Buffer contents writeFile()
Compact writable archives compactArchive()

Requirements

  • Node.js 18 or newer
  • npm
  • CMake
  • A C++ compiler supported by CMake.js, such as Visual Studio Build Tools on Windows

Installation

npm install

Published packages use prebuilds/<platform>-<arch>/NodeStorm.node when a matching addon is available. If no prebuild matches, the install script compiles build/Release/NodeStorm.node with CMake.js. Set NODE_STORMLIB_BUILD_FROM_SOURCE=1 to force a local build.

Quick Start

const storm = require('node-stormlib')

const archivePath = 'test/fixtures/maps/SaveAndLoad.w3x'

const info = storm.getArchiveInfo(archivePath)
const files = storm.listFiles(archivePath, 'war3map.*')
const fileInfo = storm.getFileInfo(archivePath, 'war3map.w3i')
const mapInfo = storm.readFile(archivePath, 'war3map.w3i')

console.log(info.fileCount)
console.log(files.map(file => file.name))
console.log(fileInfo.size)
console.log(mapInfo.length)

CLI

Installing the package exposes an mpq command for common archive tasks.

npm install -g node-stormlib
mpq --help
mpq info map.w3x
mpq list map.w3x "war3map.*"
mpq extract map.w3x war3map.w3i out/war3map.w3i --root out
mpq unpack map.w3x out/map --max-bytes 10485760
mpq unpack map.w3x out/map --no-limits

Create and compress archives from files or directories:

mpq create out/map.mpq --root out --overwrite --max-files 64
mpq add out/map.mpq assets/war3map.w3i war3map.w3i --source-root assets --compression zlib
mpq pack assets out/assets.mpq --root out --overwrite --compression zlib
mpq compact out/assets.mpq --root out

Useful aliases include mpq ls, mpq x, mpq compress, and mpq decompress. Bulk extraction validates archive entry names before writing. Read and write commands default to a 512 MiB per-file limit, and list/unpack default to 10,000 entries. Use --max-bytes, --max-entries, or --no-limits to tune those limits explicitly.

API Reference

getArchiveInfo(archivePath)

Opens an MPQ archive and returns basic metadata.

{
  path: 'map.w3x',
  fileCount: 15,
  maxFileCount: 64,
  sectorSize: 4096,
  archiveSize: 15432
}

listFiles(archivePath, mask = '*', options)

Returns matching archive entries. The optional mask uses StormLib wildcard matching, for example *.w3i or war3map.*.

const files = storm.listFiles('map.w3x', '*.w3i')
const firstTen = storm.listFiles('map.w3x', '*', { maxEntries: 10 })

Each entry includes:

Field Description
name Full archive path
plainName Basename reported by StormLib
size Uncompressed byte size
compressedSize Stored byte size
flags MPQ file flags
locale File locale
hashIndex Hash table index
blockIndex Block table index

hasFile(archivePath, fileName)

Returns true when fileName exists in the archive.

storm.hasFile('map.w3x', 'war3map.w3i')

getFileInfo(archivePath, fileName)

Returns metadata for one archive file without reading the file contents.

const file = storm.getFileInfo('map.w3x', 'war3map.w3i')
console.log(file.size, file.compressedSize, file.flags)

readFile(archivePath, fileName, options)

Reads a file from the archive and returns a Node.js Buffer.

const bytes = storm.readFile('map.w3x', 'war3map.w3i')
const capped = storm.readFile('map.w3x', 'war3map.w3i', { maxBytes: 1024 * 1024 })

readFileAsync(archivePath, fileName, options)

Reads a file on a worker thread and resolves with a Buffer.

const bytes = await storm.readFileAsync('map.w3x', 'war3map.w3i')

createReadStream(archivePath, fileName, options)

Returns a Readable that emits the archive file contents in chunks. Pass maxBytes to reject oversized files before chunk reads begin, and chunkSize to tune the read size.

storm
  .createReadStream('map.w3x', 'war3map.w3i', {
    maxBytes: 1024 * 1024,
    chunkSize: 64 * 1024,
  })
  .pipe(process.stdout)

extractFile(archivePath, fileName, outputPath, options)

Extracts a file from the archive to outputPath and returns true on success. Pass rootDir to reject writes outside an allowed directory.

storm.extractFile('map.w3x', 'war3map.w3i', 'out/war3map.w3i')
storm.extractFile('map.w3x', 'war3map.w3i', 'out/war3map.w3i', { rootDir: 'out' })

StormLib failures include a stable JavaScript error.code such as STORM_2, plus the numeric error.stormCode.

createArchive(archivePath, options)

Creates a new MPQ archive. Pass rootDir to restrict the output path and overwrite: true when replacing an existing archive is intended.

storm.createArchive('out/map.mpq', {
  rootDir: 'out',
  maxFileCount: 64,
  version: 1,
})

addFile(archivePath, sourcePath, archivedName, options)

Adds a local file to an existing archive. By default files are compressed with zlib and existing entries are replaced.

storm.addFile('out/map.mpq', 'assets/war3map.w3i', 'war3map.w3i', {
  rootDir: 'out',
  sourceRootDir: 'assets',
  maxBytes: 1024 * 1024,
  compression: storm.compression.zlib,
})

writeFile(archivePath, archivedName, data, options)

Writes a Buffer directly into an existing archive.

storm.writeFile('out/map.mpq', 'scripts/main.j', Buffer.from('function main takes nothing returns nothing'), {
  rootDir: 'out',
  compression: storm.compression.zlib,
})

compactArchive(archivePath, options)

Compacts a writable archive after edits.

storm.compactArchive('out/map.mpq', { rootDir: 'out' })

Development

npm run build
npm test
npm run test:review
  • npm run build compiles the native addon and StormLib static library.
  • npm test runs Node.js unit tests against real .w3x and .w3m fixtures.
  • npm run test:review runs the regression tests that cover prior review findings.

Repository Layout

.
|-- CMakeLists.txt              Native addon build entry
|-- index.d.ts                  TypeScript declarations
|-- index.js                    JavaScript package entry
|-- main.cc                     Node-API wrapper around StormLib
|-- native.js                   Native addon loader with prebuild fallback
|-- read-worker.js              Worker-thread helper for async reads
|-- test/fixtures/maps          MPQ map fixtures tracked by Git LFS
|-- test/open-archive.test.js   Unit tests for the public API
|-- test/review-findings.test.js Regression tests for review findings
|-- test/write-archive.test.js  Unit tests for MPQ creation and compression
`-- third_party/StormLib        Vendored StormLib source

Third-Party Code

StormLib is vendored from ladislav-zezula/StormLib at commit 448dd9a824f60d940a4627f5d2b8a7c31ee9287f. See third_party/README.md and third_party/StormLib/LICENSE for details.

Fixtures

Small Warcraft III map fixtures live in test/fixtures/maps. Binary archive formats such as .mpq, .w3m, .w3x, and .w3n are tracked with Git LFS.

License

This package is released under the MIT license.

About

A NodeJS based library to process Blizzard game MPQ format archives.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors