From 9834b3d175cc0120758cb339c9b81705f16b918a Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 05:55:03 -0400 Subject: [PATCH] gen_addresses: per-lane fragment dir; duplicate names are a hard error --- ghidra/addresses.d/README.md | 18 ++++++++++++++++++ tools/gen_addresses.py | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 ghidra/addresses.d/README.md diff --git a/ghidra/addresses.d/README.md b/ghidra/addresses.d/README.md new file mode 100644 index 0000000..147c70f --- /dev/null +++ b/ghidra/addresses.d/README.md @@ -0,0 +1,18 @@ +# Per-lane address fragments + +`addresses.json` is a single shared file. When several lanes run concurrently they edit the +same lines, and three times on 2026-09-08 one lane's `git add` swept another's in-flight +entries into the wrong commit. Nothing was lost, but authorship and atomicity were. + +A lane may instead drop its own file here: + +```json +{ "entries": [ { "name": "...", "addr": "0x...", "convention": "...", + "prototype": "...", "status": "verified", "source": "findings/..." } ] } +``` + +Name it after the lane (`lane-d.json`). `tools/gen_addresses.py` merges every fragment in +sorted order after `addresses.json`. A **duplicate name across files is a hard error**, not +last-wins — two lanes disagreeing about an address is exactly the thing we must not paper over. + +The integrator folds fragments back into `addresses.json` once the lane's work is merged. diff --git a/tools/gen_addresses.py b/tools/gen_addresses.py index eda3c3d..7f0c588 100755 --- a/tools/gen_addresses.py +++ b/tools/gen_addresses.py @@ -1,10 +1,24 @@ #!/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 json, os, subprocess, sys, datetime +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/.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).",