Skip to content

EngineReuse Runtime

M T edited this page Oct 4, 2026 · 1 revision

EngineReuse Runtime

native/EngineReuse is the small C runtime that every translated function compiles against. It defines the guest CPU (EngineCPU), guest memory access, the stack, exact EFLAGS arithmetic, the x87 unit, the per-instruction step hook, the failure path, and the hook list that decides which translated calls must go through the dispatcher. It sits between the generated code (see Static Translation Pipeline) and the host (see EngineHost Overview): generated code calls only these inline helpers plus engine_dispatch, and the host supplies memory, imports and overrides through a handful of callbacks. The directory also contains a separate, sandboxed invocation API (engine_runtime.c) used by the runtime contract test.

Source files

File Role
native/EngineReuse/engine_cpu.h EngineCPU, engine_fail, engine_step, memory and stack helpers, divide/multiply, CPUID/RDTSC, FS base, the complete x87 model
native/EngineReuse/engine_flags.h EFLAGS bit constants and width-correct flag helpers (add/sub/logic/inc/dec/neg/shifts/imul/shld/shrd/rotates)
native/EngineReuse/engine_registers.h eax..edi, eflags, sub-register and segment-selector macros used by generated code
native/EngineReuse/engine_hooks.h ENGINE_HOOK_ADDRESSES, ENGINE_TRACE_HOOK_ADDRESSES, engine_hooked, ENGINE_DIRECT
native/EngineReuse/engine_arm64_fenv_prototype.h ARM64 FPCR/FPSR inline assembly for non-nearest x87 rounding (see x87 Floating Point)
native/EngineReuse/engine_runtime.h, engine_runtime.c Sandboxed, transactional halo_engine_* API over explicit memory regions
native/EngineReuse/module.modulemap Clang module HaloEngineRuntime exporting engine_runtime.h (not referenced by any build file in this tree)
native/Tests/EngineRuntimeContract.c Contract test for engine_runtime.c
native/EngineHost/tests/test_engine_direct_calls.c ENGINE_DIRECT behavior
native/EngineHost/tests/test_dispatch_interest.py Hook list completeness check
Host side (for context): host.c, overrides.c engine_dispatch_external, engine_dispatch_override, engine_record, engine_pc_trace, engine_flat_base

The guest CPU: EngineCPU

engine_cpu.h:21-35:

Field Type Meaning
gpr[8] uint32_t EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI (x86 encoding order)
flags uint32_t EFLAGS. Status bits are exact; DF (0x400) drives string ops; other bits are carried
pc uint32_t guest address of the current instruction, or the return address just popped by ret
fp_reg[8] double x87 physical register file; ST(i) is fp_reg[(fp_top + i) & 7]
fp_valid uint8_t logical tag bitmap: bit i set means ST(i) is not empty
fp_top uint8_t x87 TOP
fp_control, fp_status uint16_t x87 control word and status word (TOP is merged in on read)
address EngineAddress read translation callback (non-flat builds)
write_address EngineAddress write translation callback; when NULL, address is used for writes too
failure EngineFailure called by engine_fail; must not return
context void * owner data for the callbacks
instruction_limit, instruction_count uint64_t budget, only enforced with ENGINE_STEP_FULL=1
fs_base uint32_t guest address of this thread's TEB; 0 means ENGINE_DEFAULT_FS_BASE (0x7FFDE000)

There is one EngineCPU per guest thread. The host creates the main thread's in host_run and one per CreateThread in host_initialize_guest_thread (fresh stack, fresh TEB, flags = 0x202, engine_fp_init); see Threading and Synchronization. All threads share the one flat guest address space.

Generated code never names cpu->gpr[...] directly; engine_registers.h maps eax..edi to cpu->gpr[0..7] and eflags to cpu->flags, and defines LO8, HI8, LO16, SET_LO8, SET_HI8, SET_LO16, BSWAP32 (__builtin_bswap32) and constant segment selectors (_seg_cs = 0x1B, _seg_ds/_seg_es/_seg_ss = 0x23, _seg_fs = 0x3B, _seg_gs = 0) "so that rare or spuriously-decoded segment ops ... compile". The header must be included after engine_cpu.h and after any helper declarations, because short names such as esp would otherwise rewrite C identifiers.

Memory

Access helpers

engine_cpu.h:60-87. Generated code uses engine_read_u8/16/32/64(cpu, addr) and engine_write_u8/16/32/64(cpu, addr, value), plus engine_read_f32/f64/f80 and engine_write_f32/f64/f80 for floating values. Every access goes through memcpy, so unaligned guest accesses are well-defined C.

Two address translation modes are selected at compile time:

Mode engine_address / engine_write_address Used by
ENGINE_FLAT_MEMORY defined engine_flat_base + a; no checks at all the game (macOS host and visionOS app), most host tests
not defined rejects a + n > 2^32 ("address wrap"/"write address wrap"), calls cpu->address or cpu->write_address, fails with "unmapped guest address" or "unmapped or readonly guest write" on NULL engine_runtime.c, EngineRuntimeContract.c, test_fpu.py

In flat mode engine_flat_base is defined by the host: host_run maps 0x100000000 bytes read/write anonymous memory and makes the first 0x10000 bytes PROT_NONE to catch null dereferences. A guest address is therefore a plain offset, and an invalid access becomes a host SIGSEGV/SIGBUS, which the host's signal handler reports with the guest pc and esp. Guest memory layout (stack, heap, page ranges, TEB/PEB) is described on Guest Memory and Heap.

FS segment

ENGINE_FS_BASE is (cpu->fs_base ? cpu->fs_base : 0x7FFDE000u) (line 300). The lifter rewrites every FS- or GS-relative operand to (ENGINE_FS_BASE + offset), so mov eax, fs:[0x18] reads the TEB self pointer that the host wrote into guest memory. 0x7FFDE000 is the same address the host uses as MAIN_TEB_BASE.

Stack

engine_push/engine_pop adjust gpr[4] (ESP) by 2 or 4 and store/load guest memory; any other size fails with "invalid push width"/"invalid pop width". Calls push real return addresses, so the guest stack has exactly the layout the original code expects (stack-walking code, [esp+N] argument access, ret N cleanup, SEH frames, setjmp buffers).

Integer helpers

Helper Lines Behavior
engine_divide(c, divisor, bits, sign) 99-120 8-bit: AX / src into AL (quotient) and AH (remainder); 16/32-bit: DX:AX or EDX:EAX. Fails on zero divisor ("integer division by zero"), INT64_MIN / -1, and quotient overflow ("integer quotient overflow"), where hardware raises #DE. Flags left unchanged ("undefined ... retained deterministically")
engine_mul1(c, src, bits, sign) 302-322 one-operand MUL/IMUL into AX, DX:AX or EDX:EAX; sets CF and OF together when the upper half is significant
engine_bsf, engine_bsr 323-324 __builtin_ctz / 31 - __builtin_clz; the lifter handles the zero-source case
engine_cpuid(c) 325-333 leaf 0: max leaf 1, vendor GenuineIntel; leaf 1: signature 0x686 (family 6), EDX = FPU, TSC, CX8, CMOV, FXSR (no MMX, SSE, SSE2, 3DNow!); other leaves return zeros. The point is to make the game and its CRT pick x87 code paths, which the translator models exactly
engine_rdtsc(c) 334-337 clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) * 3 into EDX:EAX ("~3 GHz tick")

EFLAGS

engine_flags.h holds the arithmetic for every flag-producing instruction the lifter supports. Each helper takes uint32_t *flags (always &eflags, i.e. &cpu->flags), the operands and the width (8, 16 or 32), and returns the width-masked result.

There is no lazy-flags scheme: each producer computes all of its defined flags at the instruction that produces them, and each consumer (jcc, setcc, cmovcc, fcmov, adc/sbb, rcl/rcr) reads the bits from cpu->flags. That is what makes flags survive calls, block boundaries and unrelated register writes (the flags.py docstring contrasts this with the upstream operand-history heuristic).

Constant Bit
ENGINE_EFLAGS_CF 0x0001
ENGINE_EFLAGS_RESERVED1 0x0002
ENGINE_EFLAGS_PF 0x0004
ENGINE_EFLAGS_AF 0x0010
ENGINE_EFLAGS_ZF 0x0040
ENGINE_EFLAGS_SF 0x0080
ENGINE_EFLAGS_OF 0x0800
ENGINE_EFLAGS_STATUS_MASK CF, PF, AF, ZF, SF, OF
Helper Defined flags written Notes
engine_flags_add(f, a, b, carry_in, w) all six used for ADD, ADC (carry from CF), XADD
engine_flags_sub(f, a, b, borrow_in, w) all six SUB, SBB, CMP, NEG, SCAS, CMPS, CMPXCHG
engine_flags_logic(f, result, w) PF, ZF, SF; CF=OF=0 AF normalized to 0
engine_flags_inc / _dec all but CF (CF preserved)
engine_flags_neg as SUB from 0
engine_flags_shl / _shr / _sar PF, ZF, SF, CF, OF (count 1) count masked to 5 bits; count 0 leaves flags untouched
engine_flags_imul(f, a, b, w) CF=OF=overflow two/three-operand IMUL
engine_flags_shld / _shrd CF, OF, SF, ZF, PF count masked, then reduced modulo width when larger
engine_flags_rol / _ror CF, OF count reduced modulo width
engine_flags_rcl / _rcr CF, OF rotate through CF, count modulo width+1

Architecturally undefined outputs are made deterministic (header comment lines 14-17, mirrored in UNDEFINED_FLAG_NOTES):

Case Rule
AND/OR/XOR/TEST AF cleared
nonzero SHL/SHR/SAR AF cleared
shift count >= width CF preserved
shift count > 1 OF preserved
two/three-operand IMUL PF, AF, ZF, SF preserved
DIV/IDIV flags untouched (the flags mixin documents the lowering as rejected; EngineLifter lowers DIV itself through engine_divide)

All helpers widen to uint64_t or use unsigned arithmetic before shifting, so there is no signed overflow or oversized shift; test_flags.py checks this with UBSan (receipt FLAGS-004).

The step hook and instruction budget

Every translated instruction begins with engine_step(cpu, pc) (lines 43-59):

static inline void engine_step(EngineCPU *cpu, uint32_t pc) {
    cpu->pc = pc;
#if ENGINE_STEP_FULL
    if (cpu->instruction_limit && cpu->instruction_count >= cpu->instruction_limit)
        engine_fail(cpu, "original instruction budget exhausted");
    cpu->instruction_count++;
    if (pc >= engine_trace_lo && pc < engine_trace_hi) engine_pc_trace(cpu);
#endif
}

The comment explains the split: "The lean default only records the pc (faults, shims and dispatch need it). ENGINE_STEP_FULL=1 restores the instruction budget, the instruction counter and the HALO_TRACE_LO/HI pc trace, which the bounded-invocation tests rely on; the game builds do not use them and the checks cost several host operations on every translated instruction." The game builds do not define ENGINE_STEP_FULL, so:

  • HALO_TRACE_LO/HALO_TRACE_HI are read by host.c:627 but only take effect if the translated chunks were compiled with -DENGINE_STEP_FULL=1.
  • instruction_count stays 0 in release builds (the host's heartbeat log prints it).

Failure

engine_fail is _Noreturn: it calls cpu->failure(cpu, reason) and then abort()s, because "A failure callback must leave execution; never resume a bad instruction." The host's callbacks longjmp to the frame that started the guest thread and log the reason, cpu->pc, a register dump and a backtrace. Reasons emitted by the runtime and the generated code:

Reason Source
unsupported original instruction an instruction trapped by --trap-unsupported was reached
dispatch to non-leader address the L_ENTRY switch of the function chosen by engine_dispatch has no such leader
unresolved original engine boundary no generated function at or below the address, and no host handler
unresolved import boundary address >= 0xFE000000 not handled by the host
int3 breakpoint reached, hlt executed, ud2 executed ExtraMixin
integer division by zero, integer division overflow, integer quotient overflow, invalid divide width engine_divide
invalid push width, invalid pop width stack helpers
address wrap, unmapped guest address, write address wrap, unmapped or readonly guest write non-flat memory
original instruction budget exhausted ENGINE_STEP_FULL builds
empty x87 stack register, x87 stack overflow, invalid x87 stack register, invalid x87 arithmetic, invalid x87 unary operation, invalid x87 transcendental, invalid FILD size, invalid FIST size x87 helpers (x87 Floating Point)
host reasons (unimplemented import, callback return mismatch, ...) EngineHost Overview

Dispatch

engine_dispatch(cpu, address) is generated into engine_bundle.c (code on Static Translation Pipeline). Its order of decisions:

flowchart TD
    D["engine_dispatch(cpu, address)"] --> M{"address >= 0xFE000000 ?"}
    M -- yes --> EXT["engine_dispatch_external (host)"]
    EXT -- handled --> R["return"]
    EXT -- no --> F1["engine_fail: unresolved import boundary"]
    M -- no --> OV{"engine_dispatch_override (host)<br/>handled?"}
    OV -- yes --> R
    OV -- no --> PC["cpu->pc = address<br/>engine_record(address)"]
    PC --> BS["binary search: greatest entry <= address"]
    BS -- found --> FN["engine_fns[idx](cpu)<br/>-> L_ENTRY switch on cpu->pc"]
    FN --> R
    BS -- none --> EXT2["engine_dispatch_external (host)"]
    EXT2 -- handled --> R
    EXT2 -- no --> F2["engine_fail: unresolved original engine boundary"]
Loading

The three host callbacks are weak in the bundle and strong in the host:

Callback Host definition Purpose
int engine_dispatch_external(EngineCPU*, uint32_t) host.c:495-510 Owns 0xFF000000 | index procedure handles that setup_imports wrote into the IAT and that GetProcAddress returns. Index 0 is the host return sentinel ("entry point returned"); others call the registered shim, or fail with unimplemented import. Returns 0 for anything else
int engine_dispatch_override(EngineCPU*, uint32_t) overrides.c:238-265 Native replacements and observation hooks (panorama, model capture, audio ownership, native leaves, CRT overrides). A 65,536-bit membership filter (dispatch_interest) built at startup from the hook lists and override table rejects most addresses with one bit test
void engine_record(uint32_t) host.c:50 Per-thread 256-entry ring of dispatched addresses; the last 48 are printed when the guest stops

engine_reuse_entry(cpu, entry) is the bundle's exported entry; the host calls it once with the PE entry point. Guest threads and host-to-guest callbacks call engine_dispatch directly (host.c:577-600, host.c:484-494). The override catalogue is documented on Engine Overrides and Hooks.

ENGINE_DIRECT and the hook list

The generator rewrites calls and tail calls whose target is a constant, translated entry into (engine_hooks.h:65-69):

#define ENGINE_DIRECT(cpu, address, fn) do { \
    if (engine_hooked(address)) engine_dispatch((cpu), (address)); \
    else { (cpu)->pc = (address); fn(cpu); } \
} while (0)

engine_hooked is a switch over a compile-time constant, so at each call site the test folds away and the call is either a plain C call or a dispatch. The header's comment gives the reason: dispatch "looks every call up: the override filter, the crash call record, a binary search over 8336 entries and an indirect branch. On b30 that was about a tenth of the engine thread, and 25,835 of the 32,373 translated call sites name a fixed function entry."

The consequence is a rule for contributors: any address the host intercepts in engine_dispatch_override must be listed in ENGINE_HOOK_ADDRESSES, and the translated chunks recompiled, or direct calls will bypass the hook. The Makefile makes every chunk object depend on engine_hooks.h for that reason. The current list (lines 22-37):

Addresses Purpose (header comment)
004C6E80 campaign unlock and input/control tick
00442550, 00544090, 00544120, 0048A1A0 audio / HSC
004D6FC0, 00533850, 00533730 model capture
00492430, 0052B050, 00518F40, 004924B0, 005154A0, 005537C0, 0050CC40, 00449780, 00494730, 004984C0, 0050BEA0, 0050BFB0, 0050BA80 panorama
0050F740 object pixel size, one per object per panorama frame
0050BDC0 panorama: the game's interface record
00626BA4, 00631930, 00631260 overrides: _mbstowcs, _mbtowc, _wctomb
00634D8E, 00634D50, 00634F61, 00634DCB FP intrinsic dispatchers
00449590 int32 sort, native for the render pass's callback
00553920 visible-triangle marking, native (visible_surfaces.h)
004CC0D0, 00554260, 005541B0, 00553380, 00552C20 native leaves
00552DE0 BSP material walk
005540C0, 00553C40, 00553F10 exact light/shadow gather loops

ENGINE_TRACE_HOOK_ADDRESSES (004C8800, 00477EA0, 005527F0, 005528F0, 00511F30) are boundaries that only the HALO_A10_TRACE diagnostics observe. They are treated as hooked only when the chunks are compiled with -DENGINE_TRACE_HOOKS=1; in release builds those calls go direct and the trace only sees the indirect ones. Direct calls also skip engine_record, so the crash dump's call ring contains only dispatched calls, but "the native backtrace still shows the whole chain".

Calls, returns and non-local control flow

The calling protocol uses cpu->pc as the handshake between caller and callee:

  1. The caller pushes the real return address (engine_push(cpu, ret_va, 4)).
  2. It transfers control: ENGINE_DIRECT (sets cpu->pc = entry, calls the C function) or engine_dispatch (for imports, registers, memory operands and hooked entries).
  3. The callee's C function starts at L_ENTRY (or the entry fast path), runs, and its ret does cpu->pc = engine_pop(cpu, 4); esp += n; return;.
  4. Back in the caller, if (cpu->pc != ret_va): if the new pc lies inside the caller's own address range, goto L_ENTRY re-enters the caller's leader switch at that pc; otherwise the caller itself returns, propagating the unusual pc up the C stack until a function that contains it, or the host, is reached.

Step 4 is how translated code survives guest code that does not return normally: CRT longjmp, code that adjusts the stack and returns to a different site, and imports or host shims that set cpu->pc themselves. Because the decoder makes every call return site a block leader, the containing function can always resume there. (Full SEH unwinding is not modeled; the host's RtlUnwind shim fails explicitly.)

Direct call

sequenceDiagram
    participant A as sub_A caller
    participant M as ENGINE_DIRECT
    participant B as sub_B callee
    A->>A: engine_push(cpu, RET, 4)
    A->>M: ENGINE_DIRECT(cpu, B_VA, sub_B)
    alt B_VA not in ENGINE_HOOK_ADDRESSES
        M->>B: set pc to B_VA and call sub_B(cpu)
    else hooked entry
        M->>M: engine_dispatch(cpu, B_VA), an override may run instead
    end
    B->>B: entry fast path, pc equals B_VA so goto L_B
    B->>B: body
    B->>B: ret pops RET into pc
    B-->>A: C return
    A->>A: pc equals RET, continue
    Note over A: otherwise goto L_ENTRY if pc is inside sub_A, else return
Loading

Indirect call through the IAT or a register

sequenceDiagram
    participant A as sub_A caller
    participant D as engine_dispatch
    participant H as host external or override
    participant T as engine_fns[idx]
    A->>A: target read from IAT slot, memory or register
    A->>A: engine_push(cpu, RET, 4)
    A->>D: engine_dispatch(cpu, target)
    alt target at or above 0xFE000000, an IAT magic handle
        D->>H: engine_dispatch_external runs the shim
        H->>H: RET_STDCALL pops RET and args, sets eax, sets pc to RET
    else hook or override address
        D->>H: engine_dispatch_override handles it
    else translated code
        D->>D: set pc to target, engine_record(target)
        D->>T: binary search, call the function containing target
        T->>T: L_ENTRY switch on pc, run block, ret sets pc
    end
    D-->>A: C return
    A->>A: check pc against RET
Loading

Indirect jump

An indirect jmp lowers to a switch over the current function's own leaders, so jump tables whose targets were harvested become direct gotos. Anything else is treated as a tail call: engine_dispatch(cpu, target); return;. Since cpu->pc then holds whatever the tail callee's ret popped, the C caller's return check works unchanged.

Host-to-guest calls

host_call_guest is the inverse direction (window procedures, APCs, thread starts): push arguments, push the sentinel 0xFF000000 as the return address, engine_dispatch(cpu, fn), then require cpu->pc == 0xFF000000 ("callback return mismatch" otherwise), pop cdecl arguments if needed, and restore ESP if the callback left it unbalanced.

Sandboxed runtime API (engine_runtime.c)

engine_runtime.h defines a self-contained way to run translated code against explicit memory, with transactional rollback. The game does not use it; in this tree only EngineRuntimeContract.c links it. It is compiled without ENGINE_FLAT_MEMORY, so all accesses go through its region table.

Type / function Description
HaloEngineRegion {address, byte_count, bytes, writable} a guest memory region supplied by the caller
HaloEngineState registers[8], flags, pc, fp_values[8] in logical stack order (ST(0) first, whatever fp_top is), fp_control, fp_status, fp_valid, fp_top
HaloEngineStatus OK, INVALID, MEMORY, BUSY, EXECUTION
HaloEngineResult {status, pc, reason[160]}
halo_engine_create(regions, count, result) validates every descriptor first (non-NULL bytes, non-zero length, writable 0 or 1, no 32-bit overflow, at most 65,536 regions, no overlap after sorting), then copies each region into owned memory. "No files, snapshots, emulator, network, host executable loading, or hidden runtime initialization"
halo_engine_read / halo_engine_write bounded access to one region; writes to read-only regions and cross-region spans are INVALID; BUSY while executing
halo_engine_invoke(rt, entry, state) halo_engine_invoke_bounded with a 10,000,000-instruction limit
halo_engine_invoke_bounded(rt, entry, state, limit) rejects a zero limit, a busy runtime, fp_top > 7, or a control word with any exception unmasked ((fp_control & 0x3F) != 0x3F); snapshots every writable region; loads the CPU (converting logical ST values to physical slots); calls engine_reuse_entry; on success publishes the CPU back in logical order; on engine_fail (via longjmp) restores every writable region and leaves state untouched

Address lookup is a binary search over the sorted regions; an access must lie entirely within one region, and writes require writable. Calls on one runtime must be serialized by the owner (the busy flag only detects reentry).

Compile-time switches

Macro Default Effect Set by
ENGINE_FLAT_MEMORY undefined flat engine_flat_base + a addressing Makefile, visionOS builds, most tests
ENGINE_STEP_FULL 0 instruction budget, counter and pc trace in engine_step runtime contract test (-DENGINE_STEP_FULL=1)
ENGINE_TRACE_HOOKS 0 treat trace-only addresses as hooked manual builds for HALO_A10_TRACE work
HALO_ARM64_FENV_FAST 0 inline FPCR/FPSR assembly for non-nearest x87 rounding on arm64 Makefile, visionOS builds (1)

Environment variables

Variable Default Effect Read at
HALO_TRACE_LO, HALO_TRACE_HI unset (trace range empty: lo = 0xFFFFFFFF, hi = 0) hex guest pc range for engine_pc_trace logging; both must be set; effective only in ENGINE_STEP_FULL=1 builds host.c:627

The runtime headers themselves read no environment variables.

Tests

EngineRuntimeContract.c

Built by run_source_checks.py:120-126 with -DENGINE_STEP_FULL=1 and linked with engine_runtime.c. It supplies its own engine_reuse_entry with six synthetic entries and checks:

Check Assertion
ownership changing the caller's buffer after create does not change guest memory
public access read-only write rejected; unmapped and cross-region accesses rejected
success registers, flags and pc published; a guest-side read of ST(1) and two pushes appear as fp_valid == 3, fp_top == 6, fp_values[0] == 3.25, fp_values[1] == 1.5; guest write visible
failure rollback after engine_fail, the HaloEngineState is byte-identical to before and both writable regions are restored
memory faults unmapped read gives "unmapped guest address"; read-only write gives a reason containing "readonly"
budget a limit of 2 with three engine_steps fails with "instruction budget" and rolls back; a limit of 0 is INVALID
repeatability a second successful call after failures sees the earlier committed memory and stacks four x87 values
x87 validation fp_control = 0 is rejected unchanged
logical x87 ABI for every TOP 0..7 and every valid mask 0..127, one push yields TOP-1, (valid << 1) | 1, and the logical values shifted by one, compared bit-for-bit including empty slots
create validation zero length, writable == 2, address overflow, overlap and NULL descriptors rejected

test_engine_direct_calls.c

Defines a stub engine_dispatch and checks that ENGINE_DIRECT calls an unhooked entry directly with cpu->pc set to the entry, sends hooked entries (0050BEA0 panorama renderer, 00449590 sort, 00553920 visible surfaces, 00552DE0 BSP walk) through engine_dispatch, sends the trace-only 00511F30 direct unless ENGINE_TRACE_HOOKS, and that every listed hook address is hooked while address + 1 is not. Run by run_source_checks.py.

test_dispatch_interest.py

A static source check, run first in run_source_checks.py. It parses engine_hooks.h, overrides.c and the hook include files (a10_control.inc, a10_gamepad.inc, hsc_trace.inc, audio_ownership_trace.inc, model_capture_hooks.inc, panorama_hooks.inc, panorama_overlay_scope.h, native_leaves.h, frame_pacing_hooks.inc) and asserts:

  • every address compared as address == 0x... (or a #defined name) in those files and in engine_dispatch_override, plus 004C6E80, is in one of the two hook lists;
  • behavioral hooks are not in the trace-only list (which goes direct in release);
  • every entry of the overrides[] table (except the 0xFFFFFFFF placeholder) is in ENGINE_HOOK_ADDRESSES;
  • overrides.c still builds its interest filter from both lists and the override table.

Related pages

Clone this wiki locally