TypeScript library for parsing and manipulating Dark Ages (DOOMVAS v1) game client files.
Translated from eriscorp/dalib, the original C# implementation, under the MIT license.
npm install @eriscorp/dalib-ts| Format | Class(es) | Description |
|---|---|---|
.dat |
DataArchive, DataArchiveEntry |
Game data archives |
.map |
MapFile |
Map layout files |
.meta |
MetaFile, MetaFileEntry |
Metadata files |
.hpf |
HpfFile |
Tile graphics |
.spf |
SpfFile, SpfFrame |
Sprite frames |
.epf |
EpfFile, EpfFrame |
Entity / mob sprites |
.mpf |
MpfFile, MpfFrame |
Map sprites |
.efa |
EfaFile, EfaFrame |
Effect animations |
.lft |
LftFile |
Bitmap fonts (the format the client actually renders text with) |
.fnt / .hea |
FntFile, HeaFile |
Dormant fixed-cell fonts / light masks |
sotp.dat |
SotpFile |
Static-tile collision & render flags |
.tbl |
PaletteTable, ColorTable, TileAnimationTable, EffectTable |
Lookup tables |
.control |
ControlFile, Control |
UI layout files |
import { DataArchive } from '@eriscorp/dalib-ts';
const archive = DataArchive.fromFile('path/to/archive.dat');
// List all .spf entries
const sprites = archive.getEntriesByExtension('.spf');
// Get a specific entry's raw bytes
const entry = archive.get('sprite001.spf');
if (entry) {
const buffer = archive.getEntryBuffer(entry);
}import { DataArchive } from '@eriscorp/dalib-ts';
const response = await fetch('archive.dat');
const buffer = await response.arrayBuffer();
const archive = DataArchive.fromBuffer(buffer);import { DataArchive, SpfFile, Palette } from '@eriscorp/dalib-ts';
const archive = DataArchive.fromFile('seo.dat');
const spfEntry = archive.get('sprite001.spf')!;
const palEntry = archive.get('sprite001.pal')!;
const spf = SpfFile.fromBuffer(archive.getEntryBuffer(spfEntry));
const palette = Palette.fromBuffer(archive.getEntryBuffer(palEntry));Legacy sprites do not carry their own colors. PaletteResolver applies the
client's rules to find the correct palette for an entry:
import { DataArchive, PaletteResolver } from '@eriscorp/dalib-ts';
const archive = DataArchive.fromFile('legend.dat');
// The provider supplies sibling archives; return null when unavailable.
const resolver = new PaletteResolver('legend.dat', archive, name => null);
const resolved = resolver.resolve(archive.get('item001.epf')!, 0);
if (resolved) {
// resolved.palette, resolved.paletteNumber, resolved.ruleId
// resolved.luminanceBlended → render with straight (non-premultiplied) alpha
} else {
// No rule matched, or data is missing — fall back to a manual picker.
}import { renderSpfPalettized } from '@eriscorp/dalib-ts';
const frame = spf.frames[0];
const rgbaFrame = renderSpfPalettized(frame, palette);
// rgbaFrame.data is a Uint8ClampedArray — pass directly to ImageData
const imageData = new ImageData(rgbaFrame.data, rgbaFrame.width, rgbaFrame.height);
ctx.putImageData(imageData, 0, 0);SpanReader— low-level binary reader (LE/BE integers, fixed-length ASCII, byte spans)SpanWriter— low-level binary writercompressHpf/decompressHpf— HPF tile compression
crc16(data)— CRC-16 checksumcrc32(data)— CRC-32 checksum
decodeRgb555/encodeRgb555— RGB555 color codecdecodeRgb565/encodeRgb565— RGB565 color codecquantizeFrames— palette quantization for image exportcropTransparentPixels— trim transparent borders from frames
PaletteResolver— resolves the palette for entries of one open archive; returnsResolvedPalette(palette,paletteNumber,luminanceBlended,kind,ruleId) ornullmatchPaletteRule(archiveName, entryName)— the pure rule-match stage, for tests and hosts that only need theruleIdPaletteLookup.getResolvedPaletteForId(id, override?)— likegetPaletteForId, and reports the real palette number plus the luminance-blending flag
For large archives, avoid decoding everything upfront:
SpfView,EpfView,MpfView,EfaView,TilesetView
These decode individual frames on demand rather than parsing the entire file.
A handful of suites assert against the archives a real Dark Ages install ships rather than against synthetic fixtures. They gate themselves on the files being present, so they skip on CI and on any machine without a client — no binary assets are committed here.
Point DALIB_CLIENT_DIR at an install to run them:
DALIB_CLIENT_DIR="/path/to/dark ages" npm testIf you have no install, tools/extract-wise-installer.mjs unpacks one straight out of the official
installer without running it (it is a Wise self-extractor; 7z and innoextract cannot read it,
and no wine is needed):
node tools/extract-wise-installer.mjs ~/Downloads/DarkAges741single.exe "/path/to/dark ages"Every extracted file is checked against both the size and the CRC32 the installer records for it, so
a clean exit means the payload is intact. --dry-run verifies without writing.
One thing to know on a case-sensitive filesystem: the installer spells the archive Legend.dat,
while tests/paletteResolver.test.ts looks it up as legend.dat, so that suite stays skipped. Either
extract with --lowercase, or add the alias:
ln -s Legend.dat "/path/to/dark ages/legend.dat"