re: EngineCtx field registry (engine-ctx.toml) + builder
Canonical source vm-map/engine-ctx.toml (35 documented ctx fields) + engine_ctx_build.py (--build/--lint, unit-tested) -> build/engine-ctx.json + docs/engine-ctx-reference.md. Applied to Ghidra in the next task. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
43
docs/engine-ctx-reference.md
Normal file
43
docs/engine-ctx-reference.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# EngineCtx field reference
|
||||
|
||||
> Generated from `vm-map/engine-ctx.toml` by `tools/engine_ctx_build.py --build`. Do not edit.
|
||||
|
||||
Struct `EngineCtx`, size `0xa1000`. Applied to the Ghidra `/v2` image (dispatch-handler `this` = `EngineCtx *`).
|
||||
|
||||
| offset | name | type | note |
|
||||
|---|---|---|---|
|
||||
| `0x408` | `gfx_obj_registry` | `int` | gfx object registry (std::map handle->object); op 0x1a2 insert / 0x215 find |
|
||||
| `0x40c` | `sys4ini_count` | `int` | SYS4INI record count |
|
||||
| `0x410` | `archive_name_table` | `void*` | archive-name table base (arc_id*0x100 indexes it) |
|
||||
| `0x414` | `sys4ini_records` | `void*` | SYS4INI 80-byte record base {name[64],arc_id,file_number,offset,size}; record = base + id*0x50 |
|
||||
| `0x3028` | `alt_pack_table` | `int` | call-script high-byte alternate pack table (unused by corpus) |
|
||||
| `0xb558` | `gfx_dirty_a` | `int` | gfx dirty flag (anim set raises) |
|
||||
| `0xb560` | `gfx_dirty_b` | `int` | gfx dirty flag |
|
||||
| `0x14d54` | `gfx_obj_ptr_table` | `void*` | per-object pointer table (ops 0x212/0x213 write obj+0x64/0x68/0x6c) |
|
||||
| `0x14f45` | `script_frame_index` | `int` | call-script frame index (0x1e-dword frames) |
|
||||
| `0x46d14` | `query_table_46d14` | `void*` | stride-0x14 table read by op 0x216 |
|
||||
| `0x51b64` | `frame_timer` | `int` | frame timer (present updates 0x51b64/0x51b68) |
|
||||
| `0x51b78` | `anim_clock_elapsed` | `int` | global anim clock elapsed (op 0x238 zeroes) |
|
||||
| `0x51b7c` | `anim_clock_duration` | `int` | global anim clock total duration (op 0x238 sets) |
|
||||
| `0x52bd4` | `surfaces` | `void*` | surface array base [~1000 slots]; create/set-texture (0x1f8/0x1f9) allocate |
|
||||
| `0x53d14` | `cur_ctx_index` | `uint` | current gfx-object / script-context index (curCtx); indexes 0x78-byte records |
|
||||
| `0x53d28` | `frame_codebase` | `void*` | current frame codebase (PC = codebase + off*4) |
|
||||
| `0x53d2c` | `frame_pc` | `int` | current frame PC column (op = *(0x53d2c + curCtx*0x78)) |
|
||||
| `0x53d60` | `ctx_record_base` | `void*` | 0x78-byte context-record array base (coroutine/script contexts) |
|
||||
| `0x53d64` | `gfx_obj_record_array` | `void*` | gfx object-record array (field[0]=0xffffffff free; cmd-type at rec+0x24) |
|
||||
| `0x53d88` | `cmd_type_table` | `int` | per-object cmd-type column base (write *(0x53d88 + curCtx*0x78)) |
|
||||
| `0x55120` | `anti_tamper_a` | `int` | anti-tamper checksum operand |
|
||||
| `0x55124` | `anti_tamper_b` | `int` | anti-tamper checksum operand |
|
||||
| `0x5512c` | `anti_tamper_fp` | `int` | anti-tamper (import fn ptr / result) |
|
||||
| `0x55248` | `ret_stack_a` | `void*` | per-frame return stack (op 0x8f call pushes) |
|
||||
| `0x552e8` | `ret_stack_b` | `void*` | per-frame return stack (companion) |
|
||||
| `0x5f304` | `sleep_timer` | `int` | sleep timer object (op 0xc8; +8 active, +0x14 start-ms, +0x18 duration) |
|
||||
| `0x6da88` | `coroutine_yield_a` | `void*` | op 0x7b yield-state save (op1 -> +ctxidx*4) |
|
||||
| `0x6db28` | `coroutine_yield_b` | `void*` | op 0x7b yield-state save (op2 -> +ctxidx*4) |
|
||||
| `0x6dbc8` | `coroutine_runstate` | `int` | op 0x7c resume gate (run-state bit 0x2000000) |
|
||||
| `0x6dbcc` | `coroutine_resume_off` | `int` | op 0x7c resume PC offset |
|
||||
| `0x9b24c` | `dispatch_table` | `void*` | opcode->handler table base [0x400]; handler(op) = *(0x9b24c + op*4) |
|
||||
| `0xa0cc0` | `screen_w` | `int` | screen width (640) |
|
||||
| `0xa0cc4` | `screen_h` | `int` | screen height (480) |
|
||||
| `0xa0cc8` | `screen_bpp` | `int` | screen bpp (8) |
|
||||
| `0xa0ce4` | `run_state_flags` | `uint` | interpreter run-state flags (bit1 sleeping; 0x8000000 skip/fast-forward) |
|
||||
85
tools/engine_ctx_build.py
Normal file
85
tools/engine_ctx_build.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Build the EngineCtx struct artifacts from vm-map/engine-ctx.toml (single source of truth).
|
||||
|
||||
py -3.11 -X utf8 tools/engine_ctx_build.py --build # -> build/engine-ctx.json + docs/engine-ctx-reference.md
|
||||
py -3.11 -X utf8 tools/engine_ctx_build.py --lint # checks only (overlap / out-of-bounds / dup name / type)
|
||||
|
||||
The struct is APPLIED to the Ghidra /v2 image via run_script_inline reading build/engine-ctx.json
|
||||
(see docs/superpowers/plans/2026-07-09-engine-ctx-struct.md). ctx = engine context (esi / thiscall this);
|
||||
the VM global bank G[...] is a separate space (vm-map/globals.toml), never added here.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
TYPE_SIZES = {"int": 4, "uint": 4, "void*": 4}
|
||||
|
||||
|
||||
def load(toml_text):
|
||||
d = tomllib.loads(toml_text)
|
||||
fields = [{"offset": int(f["offset"]), "name": f["name"], "type": f["type"], "note": f.get("note", "")}
|
||||
for f in d.get("field", [])]
|
||||
fields.sort(key=lambda f: f["offset"])
|
||||
return {"meta": d["meta"], "fields": fields}
|
||||
|
||||
|
||||
def lint(model):
|
||||
errs, size = [], int(model["meta"]["size"])
|
||||
seen_names, prev = {}, None
|
||||
for f in model["fields"]:
|
||||
sz = TYPE_SIZES.get(f["type"])
|
||||
if sz is None:
|
||||
errs.append(f"unknown type {f['type']!r} for {f['name']}")
|
||||
sz = 4
|
||||
if f["offset"] + sz > size:
|
||||
errs.append(f"field {f['name']} @0x{f['offset']:x} out of bounds (size 0x{size:x})")
|
||||
if f["name"] in seen_names:
|
||||
errs.append(f"duplicate name {f['name']!r}")
|
||||
seen_names[f["name"]] = True
|
||||
if prev is not None and f["offset"] < prev["end"]:
|
||||
errs.append(f"overlap: {f['name']} @0x{f['offset']:x} into {prev['name']} (ends 0x{prev['end']:x})")
|
||||
prev = {"name": f["name"], "end": f["offset"] + sz}
|
||||
return errs
|
||||
|
||||
|
||||
def emit_json(model):
|
||||
return {"meta": model["meta"],
|
||||
"fields": {hex(f["offset"]): {"name": f["name"], "type": f["type"]} for f in model["fields"]}}
|
||||
|
||||
|
||||
def emit_reference_md(model):
|
||||
lines = ["# EngineCtx field reference", "",
|
||||
"> Generated from `vm-map/engine-ctx.toml` by `tools/engine_ctx_build.py --build`. Do not edit.",
|
||||
"", f"Struct `{model['meta']['struct_name']}`, size `0x{int(model['meta']['size']):x}`. "
|
||||
"Applied to the Ghidra `/v2` image (dispatch-handler `this` = `EngineCtx *`).", "",
|
||||
"| offset | name | type | note |", "|---|---|---|---|"]
|
||||
for f in model["fields"]:
|
||||
lines.append(f"| `0x{f['offset']:x}` | `{f['name']}` | `{f['type']}` | {f['note']} |")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main():
|
||||
text = (REPO / "vm-map" / "engine-ctx.toml").read_text(encoding="utf-8")
|
||||
model = load(text)
|
||||
errs = lint(model)
|
||||
if errs:
|
||||
print("LINT ERRORS:")
|
||||
for e in errs:
|
||||
print(" " + e)
|
||||
return 1
|
||||
if "--lint" in sys.argv[1:]:
|
||||
print(f"lint clean: {len(model['fields'])} fields")
|
||||
return 0
|
||||
if "--build" in sys.argv[1:]:
|
||||
(REPO / "build" / "engine-ctx.json").write_text(
|
||||
json.dumps(emit_json(model), indent=2) + "\n", encoding="utf-8")
|
||||
(REPO / "docs" / "engine-ctx-reference.md").write_text(emit_reference_md(model), encoding="utf-8")
|
||||
print(f"built {len(model['fields'])} fields -> build/engine-ctx.json + docs/engine-ctx-reference.md")
|
||||
return 0
|
||||
print(__doc__)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
61
tools/test_engine_ctx.py
Normal file
61
tools/test_engine_ctx.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Unit tests for the EngineCtx builder (tools/engine_ctx_build.py).
|
||||
|
||||
Run: py -3.11 -X utf8 tools/test_engine_ctx.py (plain runner, no pytest).
|
||||
"""
|
||||
import sys
|
||||
|
||||
from engine_ctx_build import load, lint, emit_json
|
||||
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(cond, msg):
|
||||
if not cond:
|
||||
FAILS.append(msg)
|
||||
print("FAIL:", msg)
|
||||
else:
|
||||
print("ok:", msg)
|
||||
|
||||
|
||||
GOOD = ('[meta]\nstruct_name="EngineCtx"\nsize=0x1000\n'
|
||||
'[[field]]\noffset=0x10\nname="a"\ntype="int"\nnote="x"\n'
|
||||
'[[field]]\noffset=0x20\nname="b"\ntype="void*"\nnote="y"\n')
|
||||
|
||||
|
||||
def test_load_and_emit():
|
||||
j = emit_json(load(GOOD))
|
||||
check(j["fields"]["0x10"]["name"] == "a" and j["meta"]["struct_name"] == "EngineCtx",
|
||||
"emit_json keys fields by hex offset + carries meta")
|
||||
|
||||
|
||||
def test_lint_clean():
|
||||
check(lint(load(GOOD)) == [], "clean model lints with no errors")
|
||||
|
||||
|
||||
def test_lint_catches_overlap():
|
||||
bad = ('[meta]\nstruct_name="E"\nsize=0x1000\n'
|
||||
'[[field]]\noffset=0x10\nname="a"\ntype="int"\nnote=""\n'
|
||||
'[[field]]\noffset=0x12\nname="b"\ntype="int"\nnote=""\n') # 0x10+4 > 0x12 -> overlap
|
||||
check(any("overlap" in e.lower() for e in lint(load(bad))), "lint flags overlapping fields")
|
||||
|
||||
|
||||
def test_lint_catches_oob_and_dupname():
|
||||
bad = ('[meta]\nstruct_name="E"\nsize=0x14\n'
|
||||
'[[field]]\noffset=0x10\nname="a"\ntype="int"\nnote=""\n'
|
||||
'[[field]]\noffset=0x40\nname="a"\ntype="int"\nnote=""\n') # 0x40 > size AND dup name
|
||||
errs = lint(load(bad))
|
||||
check(any("out of bounds" in e.lower() for e in errs) and any("duplicate" in e.lower() for e in errs),
|
||||
"lint flags out-of-bounds offset and duplicate name")
|
||||
|
||||
|
||||
def main():
|
||||
test_load_and_emit()
|
||||
test_lint_clean()
|
||||
test_lint_catches_overlap()
|
||||
test_lint_catches_oob_and_dupname()
|
||||
print("FAILURES:", len(FAILS))
|
||||
return 1 if FAILS else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
184
vm-map/engine-ctx.toml
Normal file
184
vm-map/engine-ctx.toml
Normal file
@@ -0,0 +1,184 @@
|
||||
# vm-map/engine-ctx.toml -- CANONICAL source for the EngineCtx struct (hand-edited).
|
||||
# Generated: build/engine-ctx.json + docs/engine-ctx-reference.md via `tools/engine_ctx_build.py --build`.
|
||||
# Applied to the Ghidra /v2 image via run_script_inline (docs/superpowers/plans/2026-07-09-engine-ctx-struct.md).
|
||||
# ctx = the engine context (esi; thiscall `this`). The VM global bank G[...] is a SEPARATE address space
|
||||
# (vm-map/globals.toml), NOT ctx offsets -- do not add globals here. Grows one [[field]] at a time as we RE more.
|
||||
[meta]
|
||||
struct_name = "EngineCtx"
|
||||
size = 0xa1000
|
||||
|
||||
[[field]]
|
||||
offset = 0x408
|
||||
name = "gfx_obj_registry"
|
||||
type = "int"
|
||||
note = "gfx object registry (std::map handle->object); op 0x1a2 insert / 0x215 find"
|
||||
[[field]]
|
||||
offset = 0x40c
|
||||
name = "sys4ini_count"
|
||||
type = "int"
|
||||
note = "SYS4INI record count"
|
||||
[[field]]
|
||||
offset = 0x410
|
||||
name = "archive_name_table"
|
||||
type = "void*"
|
||||
note = "archive-name table base (arc_id*0x100 indexes it)"
|
||||
[[field]]
|
||||
offset = 0x414
|
||||
name = "sys4ini_records"
|
||||
type = "void*"
|
||||
note = "SYS4INI 80-byte record base {name[64],arc_id,file_number,offset,size}; record = base + id*0x50"
|
||||
[[field]]
|
||||
offset = 0x3028
|
||||
name = "alt_pack_table"
|
||||
type = "int"
|
||||
note = "call-script high-byte alternate pack table (unused by corpus)"
|
||||
[[field]]
|
||||
offset = 0xb558
|
||||
name = "gfx_dirty_a"
|
||||
type = "int"
|
||||
note = "gfx dirty flag (anim set raises)"
|
||||
[[field]]
|
||||
offset = 0xb560
|
||||
name = "gfx_dirty_b"
|
||||
type = "int"
|
||||
note = "gfx dirty flag"
|
||||
[[field]]
|
||||
offset = 0x14d54
|
||||
name = "gfx_obj_ptr_table"
|
||||
type = "void*"
|
||||
note = "per-object pointer table (ops 0x212/0x213 write obj+0x64/0x68/0x6c)"
|
||||
[[field]]
|
||||
offset = 0x14f45
|
||||
name = "script_frame_index"
|
||||
type = "int"
|
||||
note = "call-script frame index (0x1e-dword frames)"
|
||||
[[field]]
|
||||
offset = 0x46d14
|
||||
name = "query_table_46d14"
|
||||
type = "void*"
|
||||
note = "stride-0x14 table read by op 0x216"
|
||||
[[field]]
|
||||
offset = 0x51b64
|
||||
name = "frame_timer"
|
||||
type = "int"
|
||||
note = "frame timer (present updates 0x51b64/0x51b68)"
|
||||
[[field]]
|
||||
offset = 0x51b78
|
||||
name = "anim_clock_elapsed"
|
||||
type = "int"
|
||||
note = "global anim clock elapsed (op 0x238 zeroes)"
|
||||
[[field]]
|
||||
offset = 0x51b7c
|
||||
name = "anim_clock_duration"
|
||||
type = "int"
|
||||
note = "global anim clock total duration (op 0x238 sets)"
|
||||
[[field]]
|
||||
offset = 0x52bd4
|
||||
name = "surfaces"
|
||||
type = "void*"
|
||||
note = "surface array base [~1000 slots]; create/set-texture (0x1f8/0x1f9) allocate"
|
||||
[[field]]
|
||||
offset = 0x53d14
|
||||
name = "cur_ctx_index"
|
||||
type = "uint"
|
||||
note = "current gfx-object / script-context index (curCtx); indexes 0x78-byte records"
|
||||
[[field]]
|
||||
offset = 0x53d28
|
||||
name = "frame_codebase"
|
||||
type = "void*"
|
||||
note = "current frame codebase (PC = codebase + off*4)"
|
||||
[[field]]
|
||||
offset = 0x53d2c
|
||||
name = "frame_pc"
|
||||
type = "int"
|
||||
note = "current frame PC column (op = *(0x53d2c + curCtx*0x78))"
|
||||
[[field]]
|
||||
offset = 0x53d60
|
||||
name = "ctx_record_base"
|
||||
type = "void*"
|
||||
note = "0x78-byte context-record array base (coroutine/script contexts)"
|
||||
[[field]]
|
||||
offset = 0x53d64
|
||||
name = "gfx_obj_record_array"
|
||||
type = "void*"
|
||||
note = "gfx object-record array (field[0]=0xffffffff free; cmd-type at rec+0x24)"
|
||||
[[field]]
|
||||
offset = 0x53d88
|
||||
name = "cmd_type_table"
|
||||
type = "int"
|
||||
note = "per-object cmd-type column base (write *(0x53d88 + curCtx*0x78))"
|
||||
[[field]]
|
||||
offset = 0x55120
|
||||
name = "anti_tamper_a"
|
||||
type = "int"
|
||||
note = "anti-tamper checksum operand"
|
||||
[[field]]
|
||||
offset = 0x55124
|
||||
name = "anti_tamper_b"
|
||||
type = "int"
|
||||
note = "anti-tamper checksum operand"
|
||||
[[field]]
|
||||
offset = 0x5512c
|
||||
name = "anti_tamper_fp"
|
||||
type = "int"
|
||||
note = "anti-tamper (import fn ptr / result)"
|
||||
[[field]]
|
||||
offset = 0x55248
|
||||
name = "ret_stack_a"
|
||||
type = "void*"
|
||||
note = "per-frame return stack (op 0x8f call pushes)"
|
||||
[[field]]
|
||||
offset = 0x552e8
|
||||
name = "ret_stack_b"
|
||||
type = "void*"
|
||||
note = "per-frame return stack (companion)"
|
||||
[[field]]
|
||||
offset = 0x5f304
|
||||
name = "sleep_timer"
|
||||
type = "int"
|
||||
note = "sleep timer object (op 0xc8; +8 active, +0x14 start-ms, +0x18 duration)"
|
||||
[[field]]
|
||||
offset = 0x6da88
|
||||
name = "coroutine_yield_a"
|
||||
type = "void*"
|
||||
note = "op 0x7b yield-state save (op1 -> +ctxidx*4)"
|
||||
[[field]]
|
||||
offset = 0x6db28
|
||||
name = "coroutine_yield_b"
|
||||
type = "void*"
|
||||
note = "op 0x7b yield-state save (op2 -> +ctxidx*4)"
|
||||
[[field]]
|
||||
offset = 0x6dbc8
|
||||
name = "coroutine_runstate"
|
||||
type = "int"
|
||||
note = "op 0x7c resume gate (run-state bit 0x2000000)"
|
||||
[[field]]
|
||||
offset = 0x6dbcc
|
||||
name = "coroutine_resume_off"
|
||||
type = "int"
|
||||
note = "op 0x7c resume PC offset"
|
||||
[[field]]
|
||||
offset = 0x9b24c
|
||||
name = "dispatch_table"
|
||||
type = "void*"
|
||||
note = "opcode->handler table base [0x400]; handler(op) = *(0x9b24c + op*4)"
|
||||
[[field]]
|
||||
offset = 0xa0cc0
|
||||
name = "screen_w"
|
||||
type = "int"
|
||||
note = "screen width (640)"
|
||||
[[field]]
|
||||
offset = 0xa0cc4
|
||||
name = "screen_h"
|
||||
type = "int"
|
||||
note = "screen height (480)"
|
||||
[[field]]
|
||||
offset = 0xa0cc8
|
||||
name = "screen_bpp"
|
||||
type = "int"
|
||||
note = "screen bpp (8)"
|
||||
[[field]]
|
||||
offset = 0xa0ce4
|
||||
name = "run_state_flags"
|
||||
type = "uint"
|
||||
note = "interpreter run-state flags (bit1 sleeping; 0x8000000 skip/fast-forward)"
|
||||
Reference in New Issue
Block a user