# -*- coding: utf-8 -*-
"""
q_pack_inv_6 -- DIMENSION 1 pass 6: CLASS -> DESERIALIZER map for the whole image.

HYPOTHESIS
  Pass 5 found a top-level parser FUN_18017fc20 whose ONLY root key is
  packList(0x20d). That is the pack-inventory response we have never modelled.
  Running class_deser over every RS4:Fut* literal in the image names it, and at the
  same time produces the complete class->deser table (useful far beyond this task).

CONTROLS
  * FutSquadSaveServerResponse -> 0x180171a60, FutSquadListServerResponse ->
    0x180172140, FutCreateMatchServerResponse -> 0x180120380 must all resolve.
  * class_deser is KNOWN to false-negative, so an unresolved class is reported as
    UNRESOLVED, never as "has no deserializer".
"""
import re
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres"
ATOM_TABLE = 0x1802D2760


def atom_name(i):
    try:
        p = qword(ATOM_TABLE + i * 8)
        if 0x180001000 <= p <= 0x1802EFC08:
            return rd_str(p, 60)
    except Exception:
        pass
    return "?"


try:
    names = set()
    for h in find_all(b"RS4:", blocks=(".rdata", ".data")):
        s = rd_str(h, 120)[4:]
        if s.startswith("Fut"):
            names.add(s)
        elif s.startswith(":Fut"):
            names.add(s[1:])
    print("class-name literals found: %d" % len(names))

    fwd = {}
    for nm in sorted(names):
        got = sorted(set(x[0] for x in class_deser(nm)))
        fwd[nm] = got
        print("  %-46s -> %s" % (nm, [hex(g) for g in got] or "UNRESOLVED"))

    print("\n########## CONTROLS ##########")
    for nm, w in (("FutSquadSaveServerResponse", 0x180171A60),
                  ("FutSquadListServerResponse", 0x180172140),
                  ("FutCreateMatchServerResponse", 0x180120380)):
        print("  %-32s %s" % (nm, "PASS" if w in fwd.get(nm, []) else "FAIL"))

    print("\n########## REVERSE LOOKUP for the parsers this dimension found ##########")
    for tgt in (0x18017FC20, 0x180122C50, 0x180161B00, 0x180124EE0, 0x18014CC60,
                0x1801234E0, 0x180162880, 0x1801758C0, 0x180174630, 0x180146970):
        owners = [nm for nm, v in fwd.items() if tgt in v]
        print("  %#x -> %s" % (tgt, owners or "UNRESOLVED"))

    print("\n########## packList ELEMENT deser FUN_18017f830 ##########")
    s = dec(0x18017F830)
    print("len=%d" % len(s))
    print(s)
    open(OUT + "/d1_packlist_elem_18017f830.txt", "w").write(s)
    ats = set()
    for m in re.finditer(r"(?:==|!=)\s*(0x[0-9a-fA-F]+|\d+)", s):
        v = int(m.group(1), 0)
        if 1 <= v <= 0x38C and v not in (6, 10, 0xB, 0xD, 0x38C):
            ats.add(v)
    for m in re.finditer(r"case\s+(0x[0-9a-fA-F]+|\d+)\s*:", s):
        v = int(m.group(1), 0)
        if 1 <= v <= 0x38C and v not in (6, 10, 0xB, 0xD, 0x38C):
            ats.add(v)
    print("\npackList element keys: " +
          ", ".join("%#x=%s" % (a, atom_name(a)) for a in sorted(ats)))

    print("\n########## who calls FUN_18017fc20 (the packList parser) ##########")
    for (fr, ty, fn, en) in xrefs_to(0x18017FC20):
        print("   %#x %-10s %s @%#x" % (fr, ty, fn, en))

    print("\n########## model container getter vtbl[0x940] users ##########")
    import struct as _s
    for modrm in (0x90, 0x91, 0x92, 0x93, 0x96, 0x97):
        pat = bytes([0xFF, modrm]) + _s.pack("<I", 0x940)
        for h in find_all(pat, blocks=(".text",)):
            f = fm.getFunctionContaining(addr(h))
            print("   %#x in %s @%#x" % (h, f.getName() if f else "?",
                                         int(f.getEntryPoint().getOffset()) if f else 0))

    print("\nDONE q_pack_inv_6")
except Exception:
    traceback.print_exc()
