# -*- coding: utf-8 -*-
"""
q_pack_inv_4 -- DIMENSION 1 pass 4: the DESERIALIZER ATLAS and the pending-pack UI.

HYPOTHESES
  (a) Every JSON deserializer in CardsDLL either calls the FNV hasher 0x180180d00
      directly or the wrapper FUN_180141ee0. Walking each such function's CMP/SUB
      immediates in [1,0x38c] and mapping them through the atom NAME TABLE at
      0x1802d2760 yields a complete key-set atlas. This settles, exhaustively,
      whether any parser dispatches on packList(0x20d), and where unopened(0x35d),
      starterPack(0x2e5) and unopenedPacks(0x35e) are read.
      (Pass 3's raw 4-byte immediate scan was USELESS: nearly every hit was a stack
      displacement such as uStack_524, not an atom compare. This pass fixes that by
      going through the instruction listing and only accepting CMP/SUB operands.)
  (b) FUT game-hub tile type 0x1c is the "CentralUnclaimedPack" tile with
      DESTINATION GOTO_STORE_MYPACK; something must decide to emit tile 0x1c.
  (c) model->vtbl[0x4e0] is the unopenedPacks-total setter; the vtable is reachable
      from the singleton writer FUN_18011d780.

CONTROLS
  * The atlas MUST report atom 0x2cd (squad) for FUN_18014cc60 and atom 0x35e
    (unopenedPacks) for FUN_18013ec10 -- both hand-verified in pass 1. If either is
    missing the CMP/SUB walk is broken.
  * The atom NAME TABLE lookup is controlled with index 0x2e5 -> "starterPack".
  * Every decompile prints len() first.
"""
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)
    except Exception:
        return "?"
    if not (0x180001000 <= p <= 0x1802EFC08):
        return "?"
    try:
        return rd_str(p, 60)
    except Exception:
        return "?"


try:
    print("CONTROL atom_name(0x2e5) = %r  (expect 'starterPack')" % atom_name(0x2E5))

    hashers = set()
    for tgt in (0x180180D00, 0x180141EE0):
        for (fr, ty, fn, en) in xrefs_to(tgt):
            if en:
                hashers.add(en)
    print("deserializer candidates (call FNV 0x180180d00 or wrapper 0x180141ee0): %d"
          % len(hashers))

    atlas = {}
    for e in sorted(hashers):
        f = func(e)
        if f is None:
            continue
        atoms = set()
        for ad in f.getBody().getAddresses(True):
            ins = listing.getInstructionAt(ad)
            if ins is None:
                continue
            m = str(ins.getMnemonicString()).upper()
            if m not in ("CMP", "SUB", "MOV", "LEA"):
                continue
            for i in range(ins.getNumOperands()):
                objs = ins.getOpObjects(i)
                for o in objs:
                    try:
                        v = int(o.getValue())
                    except Exception:
                        continue
                    if 1 <= v <= 0x38C and m in ("CMP", "SUB"):
                        atoms.add(v)
        atlas[e] = atoms

    print("\n########## DESERIALIZER ATLAS ##########")
    for e in sorted(atlas):
        ats = sorted(atlas[e])
        print("\n%#x  (%d compare-immediates in atom range)" % (e, len(ats)))
        print("   " + ", ".join("%#x=%s" % (a, atom_name(a)) for a in ats))

    print("\n########## CONTROLS ON THE ATLAS ##########")
    print("FUN_18014cc60 has 0x2cd(squad): %s"
          % (0x2CD in atlas.get(0x18014CC60, set())))
    print("FUN_18013ec10 has 0x35e(unopenedPacks): %s"
          % (0x35E in atlas.get(0x18013EC10, set())))

    print("\n########## WHICH FUNCTIONS TOUCH THE PACK-INVENTORY ATOMS ##########")
    for a in (0x20D, 0x35D, 0x35E, 0x2E5, 0x5D, 0x24B, 0x27B, 0x260, 0x262, 0x264,
              0x20C, 0x16E, 0xEC, 0x1DD):
        owners = [e for e in atlas if a in atlas[e]]
        print("  atom %#x %-20s -> %s"
              % (a, atom_name(a), [hex(x) for x in sorted(owners)] or "NONE"))

    print("\n########## PENDING-PACK UI ##########")
    for lit in (b"GOTO_STORE_MYPACK\x00", b"FUT_GH_UNCLAIMED_PACK_0\x00",
                b"mypacks\x00", b"CentralUnclaimedPack\x00"):
        for h in find_all(lit, blocks=(".rdata", ".data")):
            print("\n  literal %r @ %#x" % (lit[:-1], h))
            for (fr, ty, fn, en) in xrefs_to(h):
                print("      %#x %-10s %s @%#x" % (fr, ty, fn, en))

    print("\n--- callers of the hub-tile builder FUN_1800b2680 ---")
    for (fr, ty, fn, en) in xrefs_to(0x1800B2680):
        print("   %#x %-10s %s @%#x" % (fr, ty, fn, en))

    DECS = [
        (0x18011D780, "singleton_writer"),
        (0x18013BD40, "purchaseditems_body"),
        (0x1800150D0, "mypacks_ui_1800150d0"),
        (0x180014580, "mypacks_ui_180014580"),
        (0x1800147F0, "mypacks_ui_1800147f0"),
        (0x180014DF0, "mypacks_ui_180014df0"),
    ]
    for va, tag in DECS:
        print("\n########## %s %#x ##########" % (tag, va))
        s = dec(va)
        print("len=%d" % len(s))
        print(s)
        open(OUT + "/d1_%s.txt" % tag, "w").write(s)

    print("\n########## misc strings ##########")
    for va in (0x18021DE58,):
        print("  %#x = %r" % (va, rd_str(va, 80)))

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