#!/usr/bin/env python3 """Cache the full Ghidra function list (address, size, name) to dumps/functions.json. Ghidra/ReVa is a shared resource -- pull the list once, then work offline. Usage: uv run python3 tools/cache_functions.py [--refresh] """ import json import os import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) REPO = os.path.dirname(HERE) OUT = os.path.join(REPO, "dumps", "functions.json") PROG = "/Sword of the Stars.exe" def call(tool, args): r = subprocess.run( ["uv", "run", "python3", os.path.join(HERE, "reva_call.py"), tool, json.dumps(args)], capture_output=True, text=True, cwd=REPO, timeout=300) if r.returncode != 0: sys.exit(f"reva_call {tool} failed: {r.stderr[:400]}") return r.stdout def main(): if os.path.exists(OUT) and "--refresh" not in sys.argv: print(f"{OUT} exists; use --refresh to re-pull") return funcs = {} start = 0 while True: raw = call("get-functions", { "programPath": PROG, "startIndex": start, "filterDefaultNames": False}) lines = [ln for ln in raw.splitlines() if ln.strip()] header = json.loads(lines[0]) for ln in lines[1:]: try: f = json.loads(ln) except json.JSONDecodeError: continue funcs[f["address"]] = [f["name"], f.get("sizeInBytes", 0)] nxt = header.get("nextStartIndex") total = header.get("totalCount") print(f" {len(funcs)}/{total}", file=sys.stderr) if nxt is None or nxt <= start or len(funcs) >= total: break start = nxt os.makedirs(os.path.dirname(OUT), exist_ok=True) with open(OUT, "w") as fh: json.dump(funcs, fh) print(f"wrote {len(funcs)} functions to {OUT}") if __name__ == "__main__": main()