pmx-vm
v1.0.0
Published
TypeScript runtime for the PX/PMX language - load, decrypt and execute .pmx bytecode.
Downloads
391
Maintainers
Readme
pmx-vm
TypeScript runtime for the PX/PMX language — a small Pawn/SA:MP-flavored
scripting language that compiles to encrypted .pmx bytecode. pmx-vm loads
those .pmx files (AES-256-GCM with a key derived from a passphrase),
verifies them, and executes them in a sandboxed stack VM that runs on
Node.js 18+ and Bun.
Status: pre-1.0. The bytecode format is versioned; this release is
v5.
Install
npm install pmx-vm
# or
bun add pmx-vmQuick start
import {
loadPmx,
registerBuiltins,
ref,
PmxScriptException,
VM,
type Ref,
type StructValue,
} from "pmx-vm";
const vm = new VM();
registerBuiltins(vm); // print / println / printf / format / strlen / strcat / exception
// Custom natives the script can call:
vm.registerNative("LogEvent", (tag: string, payload: string) => {
console.log(`[${tag}] ${payload}`);
return 1n;
});
// `&out` parameters arrive as a Ref<T> box that the host fills:
vm.registerNative("MakeNonce", (out: Ref<string>) => {
out.value = crypto.randomUUID();
return 1n;
});
// Whole-array struct refs are also supported (Ref<StructValue[]>).
vm.registerNative("ListUsers", (list: Ref<StructValue[]>) => {
list.value = [
{ name: "ada", score: 100n, alive: 1n },
{ name: "grace", score: 90n, alive: 1n },
];
return 1n;
});
const script = await loadPmx("./showcase.pmx", { vm, key: "demo" });
script.start(); // runs main() if present
// Call a public function and read a Ref<string> back:
const out = ref("");
script.callPublic("DumpInto", [out]);
console.log(out.value);
// Catch script-thrown exceptions:
try {
script.callPublic("Boom", [42n, "kapow"]);
} catch (e) {
if (e instanceof PmxScriptException) {
console.log(e.code, e.message);
}
}What the VM gives you
- Encrypted bytecode loader — AES-256-GCM payload, key derived from a passphrase via SHA-256. Tamper-evident through the GCM auth tag.
- 64-bit stack VM —
cell,float,string, andbooltypes, withBigInt-backed integers and IEEE-754 floats stored as cell-bits. - Native function bridge — register host functions by name; they receive
bigint | number | stringarguments and may return any of the same. - Reference parameters (
&) — for both PX-to-PX calls and host-callable publics. Scalars use a tinyRef<T>box; struct rows useRef<StructValue>(a JSON-shaped record keyed by struct tag strings); whole 2-D struct arrays useRef<StructValue[]>with lenient writeback. serialize/deserializeintrinsics — opcodes that round-trip struct rows and 2-D struct arrays through JSON, with strict and lenient modes.PmxScriptException— script-thrown exceptions surface as a typed JavaScript error with.code: numberand.message: string.- Per-call instruction limit — defaults to 1 000 000 ops, configurable
per-
VMto defend against infinite loops in untrusted bytecode.
Public API
// Loading
loadPmx(path: string, opts: LoadOptions): Promise<Script>;
loadPmxBytes(bytes: Uint8Array, opts: LoadOptions): Promise<Script>;
// Core
class VM {
registerNative(name: string, fn: NativeFn): void;
// ...
}
class Script {
start(): Value; // runs main()
callPublic(name: string, args: (Value | Ref)[]): Value;
serializeStruct(structName: string, base: number): StructValue;
globalLayout(): { size: number; base: number }[];
// ...
}
// Built-ins
registerBuiltins(vm: VM): void; // print/println/printf/format/strlen/strcat/exception
// Helpers / types
ref<T>(initial: T): Ref<T>;
type Ref<T> = { value: T };
type Value = bigint | number | string;
type StructValue = Record<string, Value>;
type NativeFn = (...args: NativeArg[]) => Value | void;
class PmxScriptException extends Error { code: number; }
// Decoder primitives (advanced)
decodePayload(buf: Uint8Array): Module;
parseHeader(buf: Uint8Array): { version: number; encrypted: boolean; iv: Uint8Array; payloadLen: number; };
const PMX_VERSION: number;Compiling .pmx files
pmx-vm only runs bytecode; it does not compile from source. Use the
pxc Go compiler (separate project) to produce .pmx:
pxc build -o ./script.pmx --key demo -I . ./script.pxThe bytecode version embedded in the file must match PMX_VERSION exposed
by this package; otherwise loading throws.
Requirements
- Node.js >=18 (uses
node:fs/promisesandglobalThis.crypto.subtle) - Or Bun (any recent version).
License
Apache-2.0 © lenerotex
