npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@emdzej/inpax-parser

v0.8.1

Published

INPA IPO bytecode parser

Readme

@emdzej/inpax-parser

Binary IPO file parser for BMW INPA / NCSEXPERT bytecode. Reads both the modern v5.x format (INPA) and the older v1.x format (NCSEXPERT) into a single canonical in-memory representation.

Installation

npm install @emdzej/inpax-parser @emdzej/inpax-core

Usage

import { IpoParser } from '@emdzej/inpax-parser';
import { readFileSync } from 'fs';

const bytes = new Uint8Array(readFileSync('script.ipo'));
const ipo = new IpoParser(bytes).parse();

console.log(`Version ${ipo.header.versionHi}.${ipo.header.versionLo}`);
console.log(`Functions: ${ipo.functions.size}`);
console.log(`Constants: ${ipo.constants.values.length}`);

for (const [id, fn] of ipo.functions) {
  console.log(`  fn[${id}] ${fn.header.name} — ${fn.instructions.length} instructions`);
}

The result is an IpoFile (from @emdzej/inpax-core/types) with header, globals, constants, and Maps of functions, screens, menus, and state machines indexed by block ID.

v5.x vs v1.x — what gets normalised

The parser canonicalises both formats into v5.x's wider vocabulary so downstream consumers (interpreter, dispatcher, disassembler) do not need version-aware code paths.

Value type bytes — v1.x's 5-type table (0x01 BOOL / 0x02 INT (s16) / 0x03 REAL / 0x04 STRING / 0x05 LONG) is translated into v5.x's ValueType enum at parse time. Constants and globals both go through their respective NCSEXPERT-derived maps. v1.x globals also accept 0x00 Void (reserved slot 0) and 0x06 (handle → ULong).

Opcode bytes — v5.x inserted a new LOGTABLE opcode at 0x10, which shifted the four trailing opcodes by one slot:

| v1.x byte | v1.x op | v5.x byte (canonical) | |---|---|---| | 0x0D | RET | 0x0E | | 0x0E | FRAME | 0x0F | | 0x0F | CALLE | 0x0D | | 0x10 | PUSHIMM | 0x11 |

When header.versionHi === 1, the parser remaps these four opcode bytes. The first 12 opcodes (0x010x0C) and all ALU sub-codes (0x600x71) are identical between versions and pass through unchanged.

Instruction.raw preserves the original 32-bit on-disk word, so tooling that needs to render the file faithfully (e.g. a "show me what's actually in the bytes" disassembler view) can do so. Only Instruction.opcode carries the canonical-v5.x byte.

See docs/ipo-format-versions.md in the repository for the complete reverse-engineering notes — authoritative anchors include NCSEXPERT's CInterpreter::DoInterpret at FUN_0045d830 and INPA's INPA_VM_Interpret at 0x004607d7.

File structure

Both v1.x and v5.x share the same outer layout:

┌──────────────────────────┐
│ version_hi, version_lo   │  2 bytes
├──────────────────────────┤
│ "TEST-Infotext\n"        │  magic + LF
├──────────────────────────┤
│ Block 1                  │  see below
│ Block 2                  │
│ …                        │
└──────────────────────────┘

Each block carries a header:

type        u8                    block-type byte
name        \n-terminated string  for tooling / debug
blockId     u16 LE                referenced by CALL etc.
flags       u16 LE
arg1        \n-terminated string
arg2        \n-terminated string
marker      u8                    0 or 1
size        u16 LE                element count (instructions / consts)
…body…

The body interpretation depends on type: globals (0x11) store type bytes only; constants (0x12) store (type, value) pairs; function-style blocks (0x05, 0x210x25, 0x03) store size × 4-byte instructions.

API surface

  • IpoParser — the parser class. Constructor accepts Uint8Array or ArrayBufferLike. Call .parse() to get the IpoFile.
  • All result types (IpoFile, IpoHeader, BlockHeader, GlobalsBlock, ConstantsBlock, FunctionBlock, ScreenBlock, Instruction, StackEntry, ValueType, BlockType) come from @emdzej/inpax-core.

Errors

The parser throws on:

  • Bad magic ("TEST-Infotext" not present after the version bytes)
  • Unknown v1.x constant type byte outside 0x010x05
  • Unknown v1.x global type byte outside 0x000x06

Unrecognised block-type bytes are skipped (advancing by the header-declared size) rather than thrown — INPA / NCSEXPERT both emit blocks the other can't read, so strict rejection would be overly fragile.

License

MIT