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:
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())
|
||||
Reference in New Issue
Block a user