"""DIMENSION 2 batch 2. HYPOTHESES H1 The request-descriptor table around 0x1802cb230 maps action name -> uppercase name -> url template / method / factory. Decoding one row decodes all of them, and gives DiscardCard / DiscardACard / DiscardCardByRes / MoveCard / SwapCard their routes and their request+response classes. H2 FutDiscardCardServerResponse stores totalCredits at obj+0x28 and the last discarded id at obj+0x30. Whoever reads +0x28 decides whether the coin credit is server-authored or client-computed. H3 FUN_18011a830() is the FUT client-model singleton; vtable slot 0xa30 removes an item by id (called once per discarded id) and slot 0x160 hands out the pack-reveal item collection that createPack post-processes. H4 TO_TRADEPILE (FUN_1801be6a0) and 'Tradepile' (FUN_18010c3b0) are the send-to-transfer-list paths. CONTROLS: class_deser with the FULL literal names, which is what the -4 rule needs. FutSquadSaveServerResponse -> 0x180171a60, FutSquadListServerResponse -> 0x180172140, FutCreateMatchServerResponse -> 0x180120380. """ import traceback, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres" def dump(name, text): with open(os.path.join(OUT, name), "w") as f: f.write(text) print("[wrote %s %d chars]" % (name, len(text))) def show(label, va): s = dec(va) print("-" * 74) print("%s %#x fname=%s len(src)=%d" % (label, va, fname(va), len(s))) print(s) return s try: print("=" * 78) print("SECTION 0 -- CONTROLS with the full RS4 literal name") for c in ("FutSquadSaveServerResponse", "FutSquadListServerResponse", "FutCreateMatchServerResponse"): print(" ", c, [(hex(a), hex(v), hex(e)) for a, v, e in class_deser(c)]) print("=" * 78) print("SECTION 0b -- targets, with vtable + factory") for c in ("FutDiscardCardServerResponse", "FutDiscardCardByResServerResponse", "FutMoveCardByResServerResponse", "FutCreatePackServerResponse", "FutViewCardsServerResponse"): print(" ", c, [(hex(a), hex(v), hex(e)) for a, v, e in class_deser(c)]) print("=" * 78) print("SECTION 1 -- the action-descriptor table around 0x1802cb230") base = 0x1802CB000 for i in range(0, 0x600, 8): va = base + i try: q = qword(va) except Exception: continue note = "" if 0x1801E5000 <= q <= 0x180290000: try: t = rd_str(q, 48) if t and all(0x20 <= ord(ch) < 0x7F for ch in t): note = "STR %r" % t except Exception: pass if not note and 0x180001000 <= q < 0x1801E5000: f = fm.getFunctionAt(addr(q)) note = "FUNC %s" % (f.getName() if f else "(mid)") print(" %#x : %#018x %s" % (va, q, note)) print("=" * 78) print("SECTION 2 -- readers of the FutDiscardCard response fields") print("xrefs to deser 0x180127300:") for r in xrefs_to(0x180127300): print(" ", [hex(r[0]), r[1], r[2], hex(r[3])]) print("=" * 78) print("SECTION 3 -- TO_TRADEPILE / Tradepile owners") show("FUN_1801be6a0 (TO_TRADEPILE)", 0x1801BE6A0) show("FUN_18010c3b0 (Tradepile)", 0x18010C3B0) print("=" * 78) print("SECTION 4 -- the FUT model singleton") show("FUN_18011a830 (singleton getter)", 0x18011A830) print("=" * 78) print("SECTION 5 -- string search for the Scaleform action names, exact literal") for lit in (b"CardsDiscardCard\x00", b"CardsDiscardCardList\x00", b"CardsDiscardCardByRes\x00", b"GetCardDuplicate\x00", b"CardsSwapCards\x00", b"AddCardBackToTradePile\x00", b"RemoveFromTradePile\x00", b"RemoveAllSoldFromTradePile\x00", b"TradePileFull\x00", b"GetTradePileResults\x00", b"CardsMoveCard\x00", b"CardsSendToClub\x00"): hits = find_all(lit) print(" %-30s hits=%s" % (lit.decode().strip("\x00"), [hex(h) for h in hits])) for h in hits: for r in xrefs_to(h): print(" ref %#x %s %s %#x" % (r[0], r[1], r[2], r[3])) for r in xrefs_to(h - 4): print(" ref-4 %#x %s %s %#x" % (r[0], r[1], r[2], r[3])) except Exception: traceback.print_exc()