# -*- coding: utf-8 -*- """ q_pack_inv_2 -- DIMENSION 1 (pack inventory) pass 2. HYPOTHESES (a) FUN_18014cc60 really is the FutCreateUserServerResponse deserializer, so its atom->type mapping (bonusPacks=BOOL, login=OBJECT->userInfo deser, starterPack=ARRAY-of-ITEM) supersedes ENDPOINT_MAP.md's typing. (b) userInfo.unopenedPacks pushes (preOrderPacks+recoveredPacks) into a global model through singleton FUN_18011a830 -> vtbl[0x4e0]. That setter's member is readable by a UI surface; find the setter, the member offset, and the readers. (c) atom 0x20d "packList" is a real key somewhere. If any deserializer dispatches on it, that is the pack-inventory response we have never modelled. (d) ut/%s/purchased (FutGetPurchasedItemsServerResponse) is the only route that lists already-owned-but-unrevealed things. CONTROLS * class_deser on FULL class names must return the three known-good deserializers: FutSquadSaveServerResponse -> 0x180171a60 FutSquadListServerResponse -> 0x180172140 FutCreateMatchServerResponse -> 0x180120380 (pass 1 called class_deser("FutSquadSave") and got nothing -- the literal is the FULL name, there is no bare "FutSquadSave\\0" in the image. Not a harness bug.) * The atom-immediate scanner is controlled with atom 0x2cd (squad), which MUST be found inside FUN_18014cc60, and 0x35e (unopenedPacks) inside FUN_18013ec10. * Every decompile prints len(src) first and is dumped whole. """ import struct import traceback OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres" ATOM_NAMES = { 0x5d: "bonusPacks", 0xbc: "count", 0x1a5: "login", 0x20c: "packContentInfo", 0x20d: "packList", 0x24b: "preOrderPacks", 0x262: "purchased", 0x27b: "recoveredPacks", 0x2cd: "squad", 0x2e5: "starterPack", 0x35d: "unopened", 0x35e: "unopenedPacks", 0x36d: "userData", } try: print("########## CONTROL BLOCK ##########") want = {"FutSquadSaveServerResponse": 0x180171a60, "FutSquadListServerResponse": 0x180172140, "FutCreateMatchServerResponse": 0x180120380} for nm, w in want.items(): got = sorted(set(x[0] for x in class_deser(nm))) print("CONTROL class_deser(%-32s) -> %s expect %#x %s" % (nm, [hex(g) for g in got], w, "PASS" if w in got else "FAIL")) print("\n--- class_deser on the classes this dimension needs ---") for nm in ("FutCreateUserServerResponse", "FutGetPurchasedItemsServerResponse", "FutStoreGetPackTypesServerResponse", "FutGetUserInfoServerResponse", "FutStoreVoucherRefreshResponse", "FutGetUserActionServerResponse", "FutUpdateUserActionServerResponse"): got = sorted(set(x[0] for x in class_deser(nm))) print(" %-42s -> %s" % (nm, [hex(g) for g in got])) print("\n########## ATOM IMMEDIATE SCAN (.text) ##########") print("(a hit is a 32-bit LE immediate equal to the atom; noisy by nature, so") print(" only functions that ALSO call the FNV hasher 0x180180d00 are flagged HOT)") hashers = set() for (fr, ty, fn, en) in xrefs_to(0x180180d00): if en: hashers.add(en) print("functions calling FNV 0x180180d00: %d" % len(hashers)) for atom in sorted(ATOM_NAMES): pat = struct.pack("