# -*- 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()