@seer-project/pipeline
Node-only utilities for the offline data extraction pipeline.
Pre-1.0 — expect breaking changes. Seer is at
0.x, and under semver that means no compatibility promise: a minor bump may rename exports or change signatures. Pin an exact version if you need reproducible builds, and read the changelog before upgrading. Details: https://seer.shaid.net/start-here/project-status/.
Handles the conversion of original game data files (executables, ROMs, resource archives) into web-native formats (JSON + PNG) via a configurable two-stage pipeline. Node.js only — never imported by browser-bundled code.
Installation
Section titled “Installation”npm install @seer-project/pipelineRequires pngjs ^7.0.0 as a peer dependency (for PNG writing).
npx seer <command> [options]| Command | Description |
|---|---|
extract [--game <id>] [--platform <id>] [--data-dir <path>] |
Run the offline extraction pipeline |
hex-dump <file> [offset] [length] |
Inspect binary file contents as hex + ASCII |
doctor [--data-dir <path>] |
Sanity-check the resolved config against disk |
--help, -h |
Show usage |
Config file
Section titled “Config file”The CLI reads seer.config.ts (or .js / .mjs) from the project root.
The config file exports a GameConfig[] array defining games and their
platforms:
import { defineGameConfig } from '@seer-project/pipeline';
export default defineGameConfig([{ id: 'mygame', displayName: 'My Game', platforms: [{ platform: 'amiga', dataDirs: ['mygame/amiga'], executable: 'mygame', expectedFiles: ['mygame'], supported: true, assetDir: 'mygame', exportGameData: async (cfg, dataDir) => { /* ... */ }, buildAssets: async (cfg, dataDir) => { /* ... */ }, }],}]);Pipeline stages
Section titled “Pipeline stages”- Stage 1 —
exportGameData: Parse game executables/data tables and write raw JSON todata/extracted/<game>/. - Stage 2 —
buildAssets: Decode resource files into web-native PNG + JSON assets inpublic/assets/<game>/<platform>/.
Programmatic API
Section titled “Programmatic API”import { runPipeline } from '@seer-project/pipeline';import { defineGameConfig } from '@seer-project/pipeline';
const configs = defineGameConfig([/* ... */]);const results = await runPipeline(configs, { game: 'mygame', platform: 'amiga' });Config helpers
Section titled “Config helpers”| Function | Description |
|---|---|
defineGameConfig(config) |
Typed wrapper for GameConfig[] |
flattenConfigs(configs) |
Flatten nested GameConfig[] to PlatformConfig[] |
resolveDataDir(platformConfig, dataDir?) |
Find the data directory on disk |
findFileCI(dir, name) |
Case-insensitive filename lookup |
resType(platformConfig, logical) |
Map a logical resource type (e.g. 'imag') to its platform-specific code via platformConfig.typeCodes, uppercased fallback if unmapped |
File I/O
Section titled “File I/O”| Function | Description |
|---|---|
readBinary(path) |
Read a file as Uint8Array |
writeJson(path, data, pretty?) |
Write data as formatted JSON |
writePNG(path, rgba, width, height) |
Write an RGBA PNG image |
writeIndexedPNG(path, indices, width, height, opts?) |
Write a palette-indexed PNG (index value in the R channel — not resolved colors). opts.transparentIndex (default 0) picks which index renders transparent; pass null to make every index opaque |
writeWav(path, channels, opts) |
Write PCM samples as a RIFF/WAVE file. channels is one array per channel ([mono] or [left, right]); opts.bits: 8 expects raw Uint8Array samples copied byte-for-byte, opts.bits: 16 (default) expects normalized Float32Array samples in [-1, 1], quantized to 16-bit PCM |
resolveDataFile(dataDir, candidates) |
First matching filename from a list of casing candidates, or the first candidate if none exist |
scanFilesByExtension(dir, ext) |
Find files by extension, case-insensitive, sorted |
Decompression
Section titled “Decompression”| Function | Description |
|---|---|
decompressLZEXE(input) |
Decompress an LZEXE v0.91-compressed DOS executable, returning { data, bodyOffset } — a faithful port of the classic unlzexe.c. Common on DOS-era executables; throws if input isn’t v0.91-shaped. |
import { decompressLZEXE } from '@seer-project/pipeline';
const compressed = readBinary('GAME.EXE');const { data, bodyOffset } = decompressLZEXE(compressed);Testing
Section titled “Testing”npm testnpm run lintLicensing & Commercial Use
Section titled “Licensing & Commercial Use”Seer exists to reverse-engineer other people’s work, and that is only possible because the preservation and romhacking communities published what they found instead of keeping it. The licence is chosen so that keeps happening: build on Seer and your work stays open too, so the next person gets the same head start.
- AGPL-3.0-or-later — free for personal, educational and open-source use. Note that the AGPL extends copyleft to network use: run a public web app or hosted service on this and you must publish your application’s source under the AGPL.
- Commercial licence — waives that requirement so a proprietary or closed-source product can keep its codebase private. Flat-fee and subscription terms are available, and custom terms are negotiable.
If the copyleft doesn’t fit what you’re building, we would much rather have the
conversation than have you walk away — email
dr.shaid@gmail.com with the subject
[Commercial License Request - Project Name].
Full details: https://seer.shaid.net/start-here/licensing/.