mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 02:56:36 -04:00
115 lines
3.6 KiB
Python
Executable File
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())
|