.QVM -- Script Bytecode (VM v8.5)

1. Architectural Role & Engine Purpose

The QVM (Innerloop Virtual Machine) file format executes all mission logic, AI behavior state machines, guard alert responses, alarm networks, security cameras, weapon firing logic, and in-engine cutscenes in Project I.G.I.: I'm Going In (2000).

The Innerloop VM was derived from / inspired by the Quake virtual machine, but it is not directly related to it: QVM uses its own LOOP container, its own 49-opcode instruction set, and its own registrar-based native bridging. Despite the shared inspiration, nothing about the bytecode is Quake-compatible.

Innerloop Studios designed a stack-based virtual machine executing bytecode version VM v8.5 (encoded as major version 8, minor version 5). The binary payload is self-contained with its own symbol and string pools, allowing O(1) table lookups and deterministic execution on 32-bit x86 hardware.

2. Binary File Layout & Memory Map Diagram

The QVM file uses a contiguous 32-bit layout where the 60-byte header defines explicit byte offsets and lengths for all five payload sections:

QVM SCRIPT BYTECODE — BINARY FILE LAYOUT (VM v8.5) 0x00: Header (60 Bytes) 60 BYTES FourCC "LOOP" | Major: 8 | Minor: 5 (VM v8.5) | Table Offsets & Sizes | Reserved Padding signature[4], ver_major, ver_minor, of_itable, of_ivalue, sz_itable, of_stable, sz_ctable... OFFSET: of_itable — Identifier Table POINTERS Array of 32-bit relative pointers into Identifier String Pool sz_itable bytes • O(1) Function & Variable Symbol Indexing OFFSET: of_ivalue — Identifier String Pool SYMBOLS Contiguous null-terminated ASCII symbol names "Task_GuardPatrol", "AI_WalkToWaypoint", "g_nAlarmState" OFFSET: of_stable — String Table (STable) POINTERS Array of 32-bit relative pointers into String Value Pool sz_stable bytes • Text literal indexing for subtitles & OSD OFFSET: of_svalue — String Value Pool STRINGS Contiguous null-terminated dialogue & UI string literals "Objective Complete", "Target Lost", "SOUNDS/ALARM.WAV" OFFSET: of_ctable — Bytecode Instruction Stream (CTable) BYTECODE Sequential 8-bit opcodes (0x00 - 0x30) with inline little-endian 32-bit operands [PUSH: 0x02] [0x00000066] [CALL: 0x18] [0x00000002] [0x00000012] [0x00000045] [BRA: 0x14] [0x00000BB8] [RET: 0x13]

3. Engine Pipeline & Architecture Data Flow Diagram

At runtime, the QVM subsystem operates as an integrated virtual machine embedded directly into the native game loop of IGI.EXE. The script engine dispatches bytecode on every simulation tick:

QVM SCRIPT ENGINE — RUNTIME EXECUTION PIPELINE 1. DISK / VFS INGESTION • Read .QVM from .RES • Verify FourCC "LOOP" • Validate VM v8.5 2. VM HEAP ALLOCATION • Swizzle Table Offsets • Allocate 32-bit Stack • Init Task Contexts 3. TASK SCHEDULER • Round-Robin Queue • Check Task Timers • Cooperative Yields 4. OPCODE DISPATCH • Fetch Opcode Byte • 49 Opcode Handlers • Stack Push / Pop 5. NATIVE IGI.EXE ENGINE METHOD DISPATCH • Opcode 0x18 (CALL) reads a count, then that many int32 targets • Dispatches to native x86 C routines: AI Navigation, Guard Senses, Alarms • Pushes return value back to VM stack • Restores caller IP

4. Architectural Mechanics & Engine Implementation Details

The QVM virtual machine uses a stack-oriented evaluation model. Instructions push values onto a 32-bit operand stack and consume them during arithmetic, relational, and control flow operations:

  • Stack Width: The operand stack is composed of 32-bit slots. Both 32-bit integers and 32-bit IEEE 754 floating-point values share the same stack slots without runtime tagging.
  • O(1) Symbol Resolution: Identifiers and string literals are referenced via 0-based indices into the ITable and STable arrays. During script execution, string lookups require zero hashing or runtime scanning.
  • Native Function Bridging: When the bytecode executes a native call (CALL opcode 0x18), it indexes into an internal native registration table in IGI.EXE. Arguments are popped directly from the VM operand stack and passed into native x86 stdcall/cdecl C functions.
  • Subroutine Control Flow: Script logic flows through BRA (0x14), conditional BF (0x15) / BT (0x16) branches, JSR (0x17) and RET (0x13). CALL (0x18) stores a 32-bit count followed by that many 32-bit targets, which is how both script subroutines and native engine methods are reached.

5. Binary Header Specification (60 Bytes)

OffsetSizeTypeFieldDescription
0x004char[4]signatureMagic FourCC: "LOOP" (0x4C4F4F50 in little-endian)
0x044uint32_tver_majorMajor version: strictly 8
0x084uint32_tver_minorMinor version: strictly 5 (VM v8.5)
0x0C4uint32_tof_itableByte offset to Identifier Table (ITable)
0x104uint32_tof_ivalueByte offset to Identifier String Pool
0x144uint32_tsz_itableSize of Identifier Table in bytes
0x184uint32_tsz_ivalueSize of Identifier String Pool in bytes
0x1C4uint32_tof_stableByte offset to String Table (STable)
0x204uint32_tof_svalueByte offset to String Value Pool
0x244uint32_tsz_stableSize of String Table in bytes
0x284uint32_tsz_svalueSize of String Value Pool in bytes
0x2C4uint32_tof_ctableByte offset to Bytecode Section (CTable)
0x304uint32_tsz_ctableSize of Bytecode Section in bytes
0x344uint32_tunknown_1Purpose unknown; not used by the parser (read and stored)
0x384uint32_tunknown_2Purpose unknown; not used by the parser (read and stored)

6. C++ Struct Definitions

// QVM Header Definition (60 Bytes)
#pragma pack(push, 1)
struct QvmHeader {
    char     signature[4];  // "LOOP"
    uint32_t ver_major;     // 8
    uint32_t ver_minor;     // 5
    uint32_t of_itable;     // Offset to Identifier Table
    uint32_t of_ivalue;     // Offset to Identifier Strings
    uint32_t sz_itable;     // Size of Identifier Table
    uint32_t sz_ivalue;     // Size of Identifier String Pool
    uint32_t of_stable;     // Offset to String Table
    uint32_t of_svalue;     // Offset to String Values
    uint32_t sz_stable;     // Size of String Table
    uint32_t sz_svalue;     // Size of String Value Pool
    uint32_t of_ctable;     // Offset to Bytecode Section
    uint32_t sz_ctable;     // Size of Bytecode Section
    uint32_t unknown_1;     // Unknown, unused by parser
    uint32_t unknown_2;     // Unknown, unused by parser
};
static_assert(sizeof(QvmHeader) == 60, "QvmHeader must be 60 bytes");
#pragma pack(pop)

7. VM Instruction Set (49 Opcodes)

The VM executes 49 distinct opcodes encoded as single bytes (0x00 – 0x30):

OpcodeMnemonicOperandDescription
0x00BRKNoneBreak / debug trap
0x01NOPNoneNo operation
0x02PUSHuint32 (4B)Push 32-bit integer immediate onto operand stack
0x03PUSHBuint8 (1B)Push 8-bit integer immediate
0x04PUSHWuint16 (2B)Push 16-bit word immediate
0x05PUSHFfloat32 (4B)Push 32-bit float immediate
0x06PUSHAuint32 (4B)Push address (4-byte code offset)
0x07PUSHSuint32 (4B)Push string (4-byte string-pool index)
0x08PUSHSIuint32 (4B)Push string-index immediate
0x09PUSHSIBuint8 (1B)Push string-index immediate, 8-bit
0x0APUSHSIWuint16 (2B)Push string-index immediate, 16-bit
0x0BPUSHIuint32 (4B)Push integer immediate (4-byte value)
0x0CPUSHIIuint32 (4B)Push indirect integer
0x0DPUSHIIBuint8 (1B)Push indirect integer, 8-bit
0x0EPUSHIIWuint16 (2B)Push indirect integer, 16-bit
0x0FPUSH0NonePush constant zero
0x10PUSH1NonePush constant one
0x11PUSHMNonePush marked/multiple stack values
0x12POPNonePop and discard top of operand stack
0x13RETNoneReturn from subroutine, restore caller IP
0x14BRAint32 (4B)Unconditional branch to target code offset
0x15BFint32 (4B)Branch if top of stack is false
0x16BTint32 (4B)Branch if top of stack is true
0x17JSRuint32 (4B)Jump to subroutine (4-byte code offset)
0x18CALLuint32 count + count<02D7> int32Call script subroutine or native engine method
0x19ADDNoneInteger addition: pop B, pop A, push (A + B)
0x1ASUBNoneInteger subtraction: pop B, pop A, push (A - B)
0x1BMULNoneInteger multiplication: pop B, pop A, push (A * B)
0x1CDIVNoneInteger division: pop B, pop A, push (A / B)
0x1DSHLNoneBitwise shift left: pop B, pop A, push (A << B)
0x1ESHRNoneBitwise shift right: pop B, pop A, push (A >> B)
0x1FANDNoneBitwise AND: pop B, pop A, push (A & B)
0x20ORNoneBitwise OR: pop B, pop A, push (A | B)
0x21XORNoneBitwise XOR: pop B, pop A, push (A ^ B)
0x22LANDNoneLogical AND: pop B, pop A, push (A && B)
0x23LORNoneLogical OR: pop B, pop A, push (A || B)
0x24EQNoneRelational equal: pop B, pop A, push (A == B)
0x25NENoneRelational not equal: pop B, pop A, push (A != B)
0x26LTNoneRelational less than: pop B, pop A, push (A < B)
0x27LENoneRelational less or equal: pop B, pop A, push (A <= B)
0x28GTNoneRelational greater than: pop B, pop A, push (A > B)
0x29GENoneRelational greater or equal: pop B, pop A, push (A >= B)
0x2AASSIGNNoneAssign top of stack to referenced variable
0x2BPLUSNoneUnary plus: pop A, push (+A)
0x2CMINUSNoneUnary minus: pop A, push (-A)
0x2DINVNoneBitwise invert: pop A, push (~A)
0x2ENOTNoneLogical NOT: pop A, push (!A)
0x2FBLKNoneBlock scope marker
0x30ILLEGALNoneIllegal/undefined opcode (parse boundary)