"""The hub response class exists after all. Resolve it and settle the clubPlayers question.

q_hub_1.py searched for RS4:FutGetHubServerResponse and found nothing, and the
pack-opening doc recorded that as "there is no hub class". Wrong search, not absent
class: the literal scan turned up `tHubDataServerResponse` at 0x18022ce61, i.e. the
class is FutGetHubData..., not FutGetHub.... This is the same absence-from-a-bad-search
trap the project has hit before, so this file re-derives it from the literal outward.

q_hub_1 also found ZERO direct `== 0x90` comparisons for clubPlayers across 152
deserializers, with a passing control (itemList in 0x180162880). That is suggestive but
it is NOT a proven absence, because dispatch here is frequently a running-sum sub/dec
ladder, which a source grep for `== 0x90` cannot see. Settle it by reading the actual
hub deserializer instead of by scanning.

QUESTIONS
  Q1 exact class literal, its vtable, its deserializer (RS4 rule: the literal is
     "RS4:<Name>", so search the full string and take vtable slot +0x08)
  Q2 how many tokens the deserializer spends before its first key read, which decides
     whether the flat two-key body we serve loses its first pair
  Q3 which atoms its ladder actually handles, read from the decompile IN FULL rather
     than grepped, so clubPlayers 0x90 and auctionCount 0x33 get a real answer

CONTROL: resolve FutSquadSave the same way in the same run. Its deserializer is known
to be 0x180171a60. If that does not come back correct, nothing else here is trustworthy.
"""
import traceback

CANDIDATES = [b"RS4:FutGetHubDataServerResponse", b"RS4:FutSquadSaveServerResponse"]
KNOWN = {"RS4:FutSquadSaveServerResponse": 0x180171A60}


def resolve(lit):
    """literal -> [(deser, vtable, factory)] via the RS4 rule."""
    out = []
    for a in find_all(lit):
        print("   literal at %#x: %r" % (a, rd_str(a)))
        for frm, typ, fn, ent in xrefs_to(a):
            if not ent:
                continue
            f = func(ent)
            if f is None:
                continue
            for ad in f.getBody().getAddresses(True):
                ins = listing.getInstructionAt(ad)
                if ins is None:
                    continue
                for r in ins.getReferencesFrom():
                    t = int(r.getToAddress().getOffset())
                    if not (0x1801E5000 <= t <= 0x1802891FF):
                        continue
                    try:
                        v0, v1 = qword(t), qword(t + 8)
                    except Exception:
                        continue
                    if fm.getFunctionAt(addr(v0)) and fm.getFunctionAt(addr(v1)):
                        out.append((v1, t, ent))
    return out


try:
    found = {}
    for lit in CANDIDATES:
        name = lit.decode()
        print("=" * 78)
        print(name)
        print("=" * 78)
        res = resolve(lit)
        uniq = sorted({d for d, _, _ in res})
        for d, vt, ent in res:
            print("   deser %#x  vtable %#x  factory %#x" % (d, vt, ent))
        print("   -> distinct deserializers: %s"
              % (" ".join("%#x" % d for d in uniq) or "NONE"))
        found[name] = uniq
        if name in KNOWN:
            exp = KNOWN[name]
            print("   CONTROL: expected %#x, %s"
                  % (exp, "MATCH" if exp in uniq else "*** MISMATCH, results are void ***"))
        print()

    for d in found.get("RS4:FutGetHubDataServerResponse", []):
        src = dec(d)
        f = func(d)
        print("=" * 78)
        print("HUB DESERIALIZER %#x   body %d bytes / decompile %d chars (IN FULL)"
              % (d, f.getBody().getNumAddresses() if f else -1, len(src)))
        print("=" * 78)
        print(src)

        toks, begin, keys = [], [], []
        for ad in f.getBody().getAddresses(True):
            ins = listing.getInstructionAt(ad)
            if ins is None:
                continue
            for r in ins.getReferencesFrom():
                t = int(r.getToAddress().getOffset())
                a = int(ad.getOffset())
                if t == 0x1801C7F10:
                    toks.append(a)
                elif t == 0x1801C8270:
                    begin.append(a)
                elif t == 0x180141EE0:
                    keys.append(a)
        print("\n  tokenizer calls : %d  at %s" % (len(toks), " ".join("%#x" % a for a in toks)))
        print("  begin-object    : %s" % (" ".join("%#x" % a for a in begin) or "NONE"))
        print("  key-reader calls: %s" % (" ".join("%#x" % a for a in keys) or "NONE"))
        if begin and keys:
            b, k = min(begin), min(keys)
            print("  tokens before first key read: %d"
                  % len([a for a in toks if b < a < k]))
        print("\n  callers: %s" % " ".join("%#x %s" % (a, n) for a, n in callers(d)[:10]))

except Exception:
    traceback.print_exc()
