#!/usr/bin/env python3
"""offderiv — mechanical validator for PS5 kernel offset tables (etaHEN g_sym_map format).

Checks a candidate offset table against invariants derived empirically from
all 15 shipped etaHEN offset tables. A table that fails ANY check is wrong
before it ever boots.

Usage:
    ./validate_offsets.py                    # validate all tables shipped in etaHEN
    ./validate_offsets.py candidate.h        # validate one candidate table
"""
import re, sys, glob, os

SHIPPED_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "reference", "shipped")

# Invariants: (symbol_a, symbol_b, c)  ->  table[a] == table[b] + c
# All hold with zero exceptions across the 15 shipped etaHEN tables.
INVARIANTS = [
    ("KERNEL_SYM_DMPML4I",    "KERNEL_SYM_DMPDPI",               -4),
    ("KERNEL_SYM_DMPML4I",    "KERNEL_SYM_PML4PML4I",           676),
    ("KERNEL_SYM_DMPML4I",    "KERNEL_SYM_PMAP_STORE",          648),
    ("KERNEL_SYM_DMPDPI",     "KERNEL_SYM_PML4PML4I",           680),
    ("KERNEL_SYM_DMPDPI",     "KERNEL_SYM_PMAP_STORE",          652),
    ("KERNEL_SYM_PML4PML4I",  "KERNEL_SYM_PMAP_STORE",          -28),
    ("KERNEL_PATCH_HAS_MMAP_SELF_CAPABILITY",
     "KERNEL_PATCH_IS_ALLOWED_TO_MMAP_SELF",                    -16),
]

# Absolute constants (every firmware, every table)
CONSTANTS = {
    "KERNEL_SYM_CODE_CAVE":          278528,
    "KERNEL_SYM_GADGET_JMP_PTR_RSI": 270336,
}

def parse_table(path):
    txt = open(path, encoding="utf-8", errors="replace").read()
    names = [n for n in re.findall(r"//\s*(\w+)", txt) if not n.startswith("OFFSETS")]
    vals = [int(x, 16) for x in re.findall(r"0x[0-9A-Fa-f]+", txt)]
    return dict(zip(names, vals))

def validate(t, label):
    errs, checks = [], 0
    for a, b, c in INVARIANTS:
        if a in t and b in t:
            checks += 1
            if t[a] != t[b] + c:
                errs.append(f"VIOLATION  {a}={t[a]:#x} != {b}={t[b]:#x} + {c}")
    for sym, const in CONSTANTS.items():
        if sym in t:
            checks += 1
            if t[sym] != const:
                errs.append(f"VIOLATION  {sym}={t[sym]:#x} != {const:#x}")
    print(f"[{'FAIL' if errs else 'PASS'}] {label}  ({checks} invariant checks)")
    for e in errs:
        print("        " + e)
    return not errs

def main():
    targets = sys.argv[1:] or sorted(glob.glob(os.path.join(SHIPPED_DIR, "*.h")))
    if not targets:
        print("no tables found"); sys.exit(2)
    ok = 0
    for p in targets:
        try:
            ok += bool(validate(parse_table(p), os.path.basename(p)))
        except Exception as e:
            print(f"[FAIL] {p}: {e}")
    print(f"\n{ok}/{len(targets)} tables passed all invariants")
    sys.exit(0 if ok == len(targets) else 1)

if __name__ == "__main__":
    main()
