#!/usr/bin/env python3
"""Verify (or derive) SUWSF AOB patterns against a decrypted game dump.

- Reads a game's SUWSF.ini, extracts every [Patch:*] Pattern, and reports how many
  matches each has in the dump produced by dump_decrypted.py.
- 1 clean match  -> pattern is good, ship it.
- 0 matches      -> pattern needs deriving for this game (see notes printed).
- many matches   -> pattern too loose; tighten or set Match to a specific index.

Usage:
    python3 tools/aob.py <dump.bin> [games/<Game>/SUWSF.ini]
"""
import re, sys, os

DUMP = sys.argv[1] if len(sys.argv) > 1 else ""
INI = sys.argv[2] if len(sys.argv) > 2 else \
    os.path.join(os.path.dirname(__file__), "..", "games", "GundamBreaker4", "SUWSF.ini")


def parse_pattern(pat):
    """'0F ?? 3F' -> (regex bytes). ?? = wildcard byte."""
    out = bytearray()
    mask = []
    for tok in pat.split():
        if tok in ("??", "?"):
            out.append(0)
            mask.append(False)
        else:
            out.append(int(tok, 16))
            mask.append(True)
    # build a regex over raw bytes
    rx = b""
    for b, m in zip(out, mask):
        rx += re.escape(bytes([b])) if m else b"[\\x00-\\xff]"
    return re.compile(rx, re.DOTALL)


def find_all(data, rx, limit=50):
    hits, pos = [], 0
    while len(hits) < limit:
        m = rx.search(data, pos)
        if not m:
            break
        hits.append(m.start())
        pos = m.start() + 1
    return hits


def load_patches(ini_path):
    patches = []
    cur = None
    with open(ini_path) as f:
        for raw in f:
            line = raw.strip()
            if line.startswith("[") and "Patch" in line:
                cur = {"name": line.strip("[]")}
                patches.append(cur)
            elif line.startswith("[") :
                cur = None
            elif cur is not None and "=" in line and not line.startswith(";"):
                k, _, v = line.partition("=")
                cur[k.strip()] = v.strip().strip('"')
    return patches


def main():
    if not DUMP or not os.path.exists(DUMP):
        print(f"[!] dump not found: {DUMP}\n    Run tools/dump_decrypted.py while the game is running.")
        sys.exit(1)
    data = open(DUMP, "rb").read()
    print(f"[+] dump: {len(data):,} bytes")
    patches = load_patches(os.path.abspath(INI))
    print(f"[+] {len(patches)} patches in {os.path.abspath(INI)}\n")
    for p in patches:
        pat = p.get("Pattern")
        if not pat:
            continue
        rx = parse_pattern(pat)
        hits = find_all(data, rx)
        enabled = p.get("Enabled", "true").lower()
        status = "OK " if len(hits) == 1 else ("MISS" if not hits else "MANY")
        print(f"[{status}] {p['name']:32s} enabled={enabled:5s} matches={len(hits)}  {pat[:40]}...")
        for h in hits[:4]:
            ctx = data[h:h + 24].hex(" ")
            print(f"        @rva 0x{h:x}: {ctx}")
    print("\nLegend: OK=exactly 1 match (ship it) | MISS=derive new pattern | MANY=tighten/Match=n")


if __name__ == "__main__":
    main()
