"""DIMENSION 2 batch 1: what happens to cards after the reveal. HYPOTHESES H1 duplicateItemIdList (atom 0xec) in createPackResponse (deser 0x180162880) lands in a store offset that some UI/flow code reads. Find the offset, then find every reader. H2 Quick Sell == "Discard" in this codebase (strings CardsDiscardCard / CardsDiscardCardList / CardsDiscardCardByRes, RS4:FutDiscardCardServerResponse). FutDiscardCard deser 0x180127300 parses items/totalCredits/id. Question is whether the coin credit is taken from totalCredits (server) or recomputed from the item's discardValue (client). H3 Send-to-transfer-list from the reveal reuses FutMoveCard (PUT ut/%s/item) with a pile change rather than a new endpoint. H4 CardsDiscardCardList is the bulk variant and is ONE request carrying a list. CONTROLS (must resolve, else class_deser is misbehaving this run): FutSquadSave -> 0x180171a60, FutSquadList -> 0x180172140, FutCreateMatch -> 0x180120380 """ import traceback, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres" def dump(name, text): p = os.path.join(OUT, name) with open(p, "w") as f: f.write(text) print("[wrote %s %d chars]" % (p, len(text))) try: print("=" * 78) print("SECTION 0 -- class_deser CONTROLS") for c in ("FutSquadSave", "FutSquadList", "FutCreateMatch"): print(" ", c, [hex(x[0]) for x in class_deser(c)]) print("=" * 78) print("SECTION 0b -- class_deser TARGETS") for c in ("FutDiscardCardServerResponse", "FutDiscardCardByResServerResponse", "FutMoveCardServerResponse", "FutMoveCardByResServerResponse", "FutSwapCardServerResponse", "FutCreatePackServerResponse", "FutISStartServerResponse"): print(" ", c, [hex(x[0]) for x in class_deser(c)]) print("=" * 78) print("SECTION 1 -- createPackResponse deserializer 0x180162880 FULL") s = dec(0x180162880) print("len(src) =", len(s)) print(s) dump("d2_createpack_deser.txt", s) print("=" * 78) print("SECTION 2 -- nested int-list parser 0x180138e10 FULL " "(duplicateItemIdList element parser per ENDPOINT_MAP)") s = dec(0x180138E10) print("len(src) =", len(s)) print(s) dump("d2_138e10_intlist.txt", s) print("=" * 78) print("SECTION 3 -- FutDiscardCard deser 0x180127300 FULL") s = dec(0x180127300) print("len(src) =", len(s)) print(s) dump("d2_discardcard_deser.txt", s) print("=" * 78) print("SECTION 3b -- FutDiscardCardByRes deser 0x1801279c0 FULL") s = dec(0x1801279C0) print("len(src) =", len(s)) print(s) dump("d2_discardcardbyres_deser.txt", s) print("=" * 78) print("SECTION 4 -- string xrefs for the action names") names = { "CardsDiscardCard": 0x1801EF6C5, "CardsDiscardCardList": 0x1801EF6DD, "CardsDiscardCardByRes": 0x1801EF6F5, "DiscardCard": 0x18021EEA8, "DISCARDCARD": 0x18021EEB8, "DiscardCardByRes": 0x18021EEC8, "DiscardACard": 0x18021EEF8, "DISCARDACARD": 0x18021EF08, "MoveCard": 0x18021EF18, "MOVECARD": 0x18021EF28, "SwapCard": 0x18021EF58, "CardsSwapCards": 0x1801EF6B5, "GetCardDuplicate": 0x1801F3ADF, "TO_TRADEPILE": 0x1802391A8, "Tradepile": 0x18021CF08, "tradepile_lc": 0x18022FB20, "tradePile_cc": 0x18022FB30, "discardValue_key": 0x180230BE8, "duplicateItemId": 0x180230D08, "duplicateItemIdList": 0x180230D18, "duplicateItemLoans": 0x180230D30, "fcc_discardcoins": 0x1802231F4, "swap_key": 0x18022F7EC, "swapPlayerDefIds": 0x18022F7F8, "AddCardBackToTradePile": 0x1801F3C15, "RemoveFromTradePile": 0x1801EFADA, "RemoveAllSoldFromTradePile": 0x1801EFAF9, } for n, va in sorted(names.items()): got = rd_str(va, 60) # the -4 rule may apply to some; report the literal we actually see xs = xrefs_to(va) print("%-28s %#x str=%r xrefs=%d" % (n, va, got, len(xs))) for frm, typ, fn, ent in xs[:12]: print(" from %#x %s in %s (%#x)" % (frm, typ, fn, ent)) print("=" * 78) print("SECTION 5 -- where is atom 0xd7 discardValue handled? " "search .text for the immediate 0xd7 near the item deser") print("item element deser 0x18013fe00:") s = dec(0x18013FE00) print("len(src) =", len(s)) dump("d2_item_elem_deser.txt", s) print(s[:6000]) print("... [full copy written to d2_item_elem_deser.txt]") except Exception: traceback.print_exc()