Files
psxrecomp/tools/new_project_layout/sync_symbols.py
T
Alex Vanderveen 480fcad652 new build wizard
2026-08-07 01:43:04 -04:00

115 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Sync game-root symbols.toml → psx_symbols.h (PSX_FN_* macros).
Progressive naming for PSX titles (partial decomp). Catalog freely; set
emit=true only when an entry is safe for AOT / host hooks.
Usage (from game repo root):
python3 tools/sync_symbols.py
python3 tools/sync_symbols.py --check
"""
from __future__ import annotations
import argparse
import pathlib
import re
import sys
try:
import tomllib # Python 3.11+
except ModuleNotFoundError: # pragma: no cover
try:
import tomli as tomllib # type: ignore
except ModuleNotFoundError:
print("error: need Python 3.11+ tomllib or pip install tomli", file=sys.stderr)
raise SystemExit(2)
ROOT = pathlib.Path.cwd()
SYMBOLS_PATH = ROOT / "symbols.toml"
HEADER_PATH = ROOT / "psx_symbols.h"
def _parse_int(value) -> int:
if isinstance(value, int):
return value
if isinstance(value, str):
return int(value, 0)
raise TypeError(f"expected int, got {type(value)!r}")
def load_funcs(path: pathlib.Path) -> list[dict]:
data = tomllib.loads(path.read_text(encoding="utf-8"))
funcs = []
for raw in data.get("func", []):
pc = _parse_int(raw["pc"]) & 0xFFFFFFFF
name = str(raw["name"])
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
raise SystemExit(f"invalid func name {name!r} (need C identifier)")
funcs.append({
"pc": pc,
"name": name,
"emit": bool(raw.get("emit", False)),
"status": str(raw.get("status", "guessed")),
"note": str(raw.get("note", "")),
})
funcs.sort(key=lambda f: f["pc"])
return funcs
def render_header(funcs: list[dict], *, game: str) -> str:
lines = [
"/* Auto-generated by tools/sync_symbols.py — do not edit.",
f" * Source: symbols.toml ({game})",
" * Progressive symbols: discover → label → manipulate via PSX_FN_*.",
" */",
"#pragma once",
"",
]
for f in funcs:
note = f["note"].replace("*/", "* /")
lines.append(
f"/* {f['status']}: {note} */" if note else f"/* {f['status']} */"
)
lines.append(f"#define PSX_FN_{f['name']} 0x{f['pc']:08X}u")
lines.append(f"#define func_{f['pc']:08X} {f['name']} /* alias */")
if f["emit"]:
lines.append(f"/* emit=true — promote in recompiler/map when ready */")
lines.append("")
if not funcs:
lines.append("/* (no [[func]] entries yet) */")
lines.append("")
return "\n".join(lines)
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--check", action="store_true", help="exit 1 if header would change")
ap.add_argument("--symbols", type=pathlib.Path, default=SYMBOLS_PATH)
ap.add_argument("--header", type=pathlib.Path, default=HEADER_PATH)
ap.add_argument("--game", default="", help="title label for header comment")
args = ap.parse_args()
if not args.symbols.is_file():
print(f"error: missing {args.symbols}", file=sys.stderr)
return 1
game = args.game or args.symbols.parent.name
funcs = load_funcs(args.symbols)
text = render_header(funcs, game=game)
if args.check:
if not args.header.is_file() or args.header.read_text(encoding="utf-8") != text:
print(f"out of date: {args.header}", file=sys.stderr)
return 1
print(f"ok: {args.header}")
return 0
args.header.write_text(text, encoding="utf-8")
print(f"wrote {args.header} ({len(funcs)} funcs)")
return 0
if __name__ == "__main__":
raise SystemExit(main())