#!/usr/bin/env python3 """Round-trip verifier for the .instance decompiler. Regenerates each `.instance` and compares the page name and every key against the authored source. Keys the source carries but the decompiler does not emit are reported separately, so an unhandled key can never be mistaken for a pass. python3 verify_instance.py [--show CHASSIS] """ import argparse, collections, glob, os, re, sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import instance, subsystems REC = "/home/rich/Repositories/FS_Ours_extracted/core/mechs" SRC = "/home/rich/Repositories/firestorm/Gameleap/mw4/Content/Mechs" NUM = re.compile(r'^-?(?:\d+\.?\d*|\.\d+)$') def canon(v): v = str(v).strip() if NUM.match(v): return f"{float(v):.5g}" return re.sub(r'^content[\\/]', '', v.replace("/", "\\"), flags=re.I).lower() def source_page(path): txt = re.sub(r'//[^\n]*', '', open(path, "rb").read().decode("latin-1")) m = re.search(r'^\[([^\]]+)\]', txt, re.M) kv = [(k, v.strip()) for k, v in re.findall(r'^([A-Za-z]\w*)=([^\r\n]*)', txt, re.M)] return (m.group(1) if m else None), kv def main(): ap = argparse.ArgumentParser() ap.add_argument("--show") args = ap.parse_args() manifest = subsystems.load_manifest(instance.MANIFEST) t = collections.Counter() bad = collections.Counter() unhandled = collections.Counter() examples = [] name_bad = [] for d in sorted(glob.glob(REC + "/*")): ch = os.path.basename(d) src = [p for p in glob.glob(SRC + "/*/*.instance") if os.path.basename(p).lower() == ch.lower() + ".instance"] if not src or not glob.glob(os.path.join(d, "*.instance")): continue t["chassis"] += 1 want_name, want = source_page(src[0]) got_name, got = instance.decompile(d, manifest) if args.show and args.show.lower() == ch.lower(): sys.stdout.write(instance.emit(got_name, got)) return if (want_name or "").lower() != (got_name or "").lower(): name_bad.append((ch, want_name, got_name)) gmap = {k.lower(): v for k, v in got} for key, wv in want: if key.lower() not in gmap: unhandled[key] += 1 continue t["keys"] += 1 if canon(wv) == canon(gmap[key.lower()]): t["ok"] += 1 else: bad[key] += 1 if len(examples) < 12: examples.append((ch, key, wv, gmap[key.lower()])) print(f"chassis : {t['chassis']}") print(f"page names : {t['chassis'] - len(name_bad)}/{t['chassis']} match") print(f"keys compared : {t['keys']} exact: {t['ok']} wrong: {t['keys'] - t['ok']}") if unhandled: print("\nkeys present in source but NOT emitted:") for k, n in unhandled.most_common(): print(f" {k:26s} x{n}") if name_bad: print("\npage name mismatches:") for e in name_bad[:5]: print(f" {e[0]:14s} want={e[1]!r} got={e[2]!r}") if bad: print("\nvalue mismatches:") for k, n in bad.most_common(15): print(f" {k:26s} x{n}") print("\nexamples (chassis, key, source, decoded):") for e in examples: print(f" {e[0]:14s} {e[1]:22s} want={e[2]!r:32s} got={e[3]!r}") if __name__ == "__main__": main()