"""What does the hub body actually contain? Read the sub-parser the hub root delegates to.

CHAIN ESTABLISHED SO FAR:
  FutGetHubDataServerResponse literal 0x18022ce40 -> vtable 0x18022cd48 -> deser
  0x1801738b0 (control FutSquadSave -> 0x180171a60 MATCHED in the same run).
  0x1801738b0 spends TWO tokenizer calls and then tail-delegates to FUN_180139610.
  That is the /purchased shape: root spends 2, the sub-parser spends the 3rd and owns
  the key loop.

  We answer GET /hub with a FLAT two-key body {"clubPlayers":205,"auctionCount":0}.
  q_hub_1 scanned 152 deserializers for a direct comparison against clubPlayers (0x90)
  and found ZERO, with a passing control. auctionCount (0x33) got 7 hits, one of which
  is FUN_180139610 itself.

WHY THAT IS NOT YET AN ANSWER. A source grep for `== 0x90` cannot see running-sum
sub/dec ladder dispatch, which is common in this binary, so zero hits is suggestive and
not conclusive. Read the ladder instead of grepping it.

QUESTIONS
  Q1 print FUN_180139610 IN FULL and enumerate every atom its ladder handles, including
     any expressed as a running-sum sub/dec chain rather than an equality test
  Q2 is clubPlayers 0x90 among them, in ANY dispatch form
  Q3 does it consume a token before its loop (making the total 3, and therefore eating
     the first key/value pair of a flat body) or does it start looping immediately
  Q4 FUN_1801c8210(parser, 3, 1) is called by the hub root and not by the other roots
     read so far. Find out what it configures, since it may change the token semantics

CONTROL: FUN_180139610 must show auctionCount 0x33 somewhere, since the q_hub_1 grep
already found it there. If the atom enumeration below cannot see 0x33, the enumeration
is broken and its verdict on 0x90 is void.
"""
import traceback

SUB = 0x180139610
CFG = 0x1801C8210
A_CLUB = 0x90
A_AUCTION = 0x33

try:
    for va, title in ((SUB, "hub body sub-parser"), (CFG, "parser config called by the hub root")):
        f = func(va)
        src = dec(va)
        print("=" * 78)
        print("%#x  %s   body %d bytes / decompile %d chars (IN FULL)"
              % (va, title, f.getBody().getNumAddresses() if f else -1, len(src)))
        print("=" * 78)
        print(src)
        print()

    # Enumerate atoms mechanically from the instruction stream, which unlike a source
    # grep also catches sub/dec ladder steps.
    print("=" * 78)
    print("ATOMS REACHABLE IN %#x, read from the instruction stream" % SUB)
    print("=" * 78)
    f = func(SUB)
    imms, runsum = [], 0
    for ad in f.getBody().getAddresses(True):
        ins = listing.getInstructionAt(ad)
        if ins is None:
            continue
        m = ins.getMnemonicString().lower()
        txt = str(ins)
        if m in ("cmp", "sub", "dec", "add", "mov"):
            for i in range(ins.getNumOperands()):
                for o in ins.getOpObjects(i):
                    try:
                        v = int(o.getValue())
                    except Exception:
                        continue
                    if 0 < v <= 0x400:
                        imms.append((int(ad.getOffset()), m, v, txt))
    print("  %d candidate immediates in range 1..0x400" % len(imms))
    # running-sum reconstruction: consecutive sub/dec on the same register accumulate
    acc = 0
    print("\n  addr        mnem  imm     running-sum  disasm")
    for a, m, v, txt in imms:
        if m in ("sub", "dec"):
            acc += v
            print("  %#010x  %-4s  %#-6x %#-11x %s" % (a, m, v, acc, txt))
        else:
            print("  %#010x  %-4s  %#-6x %-11s %s" % (a, m, v, "", txt))

    seen = {v for _, m, v, _ in imms}
    sums = set()
    acc = 0
    for _, m, v, _ in imms:
        if m in ("sub", "dec"):
            acc += v
            sums.add(acc)
    print("\n  distinct raw immediates : %s" % " ".join("%#x" % v for v in sorted(seen)))
    print("  distinct running sums   : %s" % " ".join("%#x" % v for v in sorted(sums)))
    print("\n  CONTROL auctionCount %#x present? %s"
          % (A_AUCTION, "YES" if (A_AUCTION in seen or A_AUCTION in sums) else "NO -- enumeration broken, verdict void"))
    print("  clubPlayers %#x present?          %s"
          % (A_CLUB, "YES" if (A_CLUB in seen or A_CLUB in sums) else "NO"))

    print("\n  callees of %#x:" % SUB)
    for a, n in callees(SUB):
        print("     %#x  %s" % (a, n))

except Exception:
    traceback.print_exc()
