36 lines
2.5 KiB
Python
Executable file
36 lines
2.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Emit include/generated/sots_addresses.h for sots-engine from ghidra/addresses.json.
|
|
Only facts (name, RVA, convention, prototype, status) — never decompiler text."""
|
|
import glob, json, os, subprocess, sys, datetime
|
|
root = os.path.join(os.path.dirname(__file__), "..")
|
|
j = json.load(open(os.path.join(root, "ghidra", "addresses.json")))
|
|
base = int(j["image_base"], 16)
|
|
|
|
# Per-lane fragments. addresses.json is a single shared file, so concurrent lanes kept
|
|
# sweeping each other's in-flight entries into the wrong commit. A lane may instead write
|
|
# ghidra/addresses.d/<lane>.json ({"entries": [...]}) — its own file, no shared-line edits.
|
|
# Fragments are merged here in sorted order; a duplicate name is an error, not a silent
|
|
# last-wins, because two lanes disagreeing about an address is exactly what we must not paper over.
|
|
seen = {e["name"]: "addresses.json" for e in j["entries"]}
|
|
for frag_path in sorted(glob.glob(os.path.join(root, "ghidra", "addresses.d", "*.json"))):
|
|
frag_name = os.path.basename(frag_path)
|
|
for e in json.load(open(frag_path))["entries"]:
|
|
if e["name"] in seen:
|
|
sys.exit(f"duplicate address entry {e['name']!r}: in {seen[e['name']]} and {frag_name}")
|
|
seen[e["name"]] = frag_name
|
|
j["entries"].append(e)
|
|
try: rev = subprocess.check_output(["git","-C",root,"rev-parse","--short","HEAD"]).decode().strip()
|
|
except Exception: rev = "unknown"
|
|
out = [ "// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).",
|
|
f"// Source: sots-re ghidra/addresses.json @ {rev}, generated {datetime.date.today()} by tools/gen_addresses.py",
|
|
"// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).",
|
|
"#pragma once", "#include <cstdint>", "", "namespace sots::addr {", f"constexpr uint32_t IMAGE_BASE = 0x{base:08x};", "" ]
|
|
for e in j["entries"]:
|
|
# Two entry shapes: an "addr" (absolute VA -> RVA) or an "offset" (a field offset inside a
|
|
# struct, emitted verbatim). Offsets are facts about layout, not load addresses.
|
|
val = int(e["offset"], 16) if "offset" in e else int(e["addr"], 16) - base
|
|
out.append(f"// {e['convention']:8s} {e['prototype']} [{e['status']}]")
|
|
out.append(f"constexpr uint32_t {e['name']} = 0x{val:08x};")
|
|
out += ["", "} // namespace sots::addr", ""]
|
|
dest = sys.argv[1] if len(sys.argv) > 1 else os.path.join(root, "ghidra", "generated", "sots_addresses.h")
|
|
os.makedirs(os.path.dirname(dest), exist_ok=True); open(dest, "w").write("\n".join(out)); print("wrote", dest, f"({len(j['entries'])} entries)")
|