"""D5 Q3/Q4/Q5: who READS the pack availability fields and the currency funds, what the two store ServerCall vtables look like, and what the HTTP error path does. HYPOTHESES H4 (sold out): the pack record fields state(+0xB0), start(+0xB4), end(+0xB8), quantity(+0xBC), purchaseLimit(+0xC0), purchaseCount(+0xC4), saleType(+0xC8) are read together by one availability predicate in the store UI. Offsets derived from the stack layout of FUN_18013af30 (base local_268, size 0x158). H5 (error path): FUN_18016c060 is the generic HTTP-status -> FUT-error mapper and FUN_1801267b0 only special-cases 409 + "User already has a transaction" -> 0x70. CONTROL: the same offset-scan run for offset 0x28 (a control offset that appears everywhere) must return far more functions than the pack offsets, proving the scan is not silently returning nothing. Also dec(0x180171a60) printed as a live control. """ import traceback, os, struct OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres" def w(name, text): p = os.path.join(OUT, name) with open(p, "w") as f: f.write(text) print("WROTE %s (%d bytes)" % (p, len(text))) try: buf = [] def P(*a): s = " ".join(str(x) for x in a) print(s); buf.append(s) s = dec(0x180171a60) P("CONTROL dec(0x180171a60) len=%d sax=%s" % (len(s), "FUN_1801c7f10" in s)) # ---------- A. vtable dumps ---------- def dumpvt(lo, hi, tag): P("") P("=== %s %#x..%#x ===" % (tag, lo, hi)) a = lo while a < hi: try: q = qword(a) except Exception as e: P(" %#x ERR %s" % (a, e)); a += 8; continue f = fm.getFunctionAt(addr(q)) if 0x180000000 <= q < 0x181000000 else None extra = "" if f is None: try: raw = read_bytes(a, 8) if all(32 <= b < 127 or b == 0 for b in raw) and raw[0] != 0: extra = "inline-ascii %r" % raw except Exception: pass if 0x180000000 <= q < 0x181000000: try: t = rd_str(q, 50) if t and all(32 <= ord(c) < 127 for c in t): extra += " ->str %r" % t except Exception: pass P(" +%03x %#x -> %#x %s %s" % (a - lo, a, q, f.getName() if f else "", extra)) a += 8 dumpvt(0x1802202f0, 0x1802203a8, "PurchaseItems ServerCall region") dumpvt(0x180228250, 0x180228330, "CreatePack ServerCall region") dumpvt(0x1801f0440, 0x1801f04b0, "first-party CARDPACK descriptor") dumpvt(0x18021dd40, 0x18021de30, "StoreGetPackTypes region") # ---------- B. service / viewmodel strings ---------- P("") for va, nm in ((0x1802345d8, "FutComponentServicesImpl::FutStoreServiceImpl"), (0x1801ee8d8, "futstoreviewmodel"), (0x1801f4e48, "PurchasePack"), (0x180205560, "PURCHASE_FAILED"), (0x180205678, "PURCHASE_SUCCESS"), (0x1802150e8, "PACK_EXISTS_IN_PURCHASED_PILE"), (0x180215108, "PACK_PURCHASE_FAILED")): try: xs = xrefs_to(va) except Exception as e: P("XREF %s ERR %s" % (nm, e)); continue P("XREFS %-46s %#x : %s" % (nm, va, [("%#x" % f, n, "%#x" % e2) for f, t, n, e2 in xs])) # ---------- C. who constructs the two ServerCalls ---------- P("") for vt, tag in ((0x180228270, "CreatePack call vtable"), (0x1802202f8, "PurchaseItems call vtable"), (0x18021dd90, "packtypes?"),): pat = struct.pack(" %d funcs" % (o, len(tables[o]))) score = {} for o in (0xB0, 0xBC, 0xC0, 0xC4, 0xC8): for e in tables[o]: score.setdefault(e, set()).add(o) cands = sorted((e for e, s2 in score.items() if len(s2) >= 3), key=lambda e: -len(score[e])) P(" candidates with >=3 of {B0,BC,C0,C4,C8}: %d" % len(cands)) for e in cands[:40]: P(" %#x %-30s offs=%s" % (e, fname(e), sorted("%#x" % x for x in score[e]))) w("d5_q3_notes.txt", "\n".join(buf) + "\n") # ---------- E. decompiles ---------- TG = {"http_status_mapper_18016c060": 0x18016c060, "state_enum_to_str_180166a30": 0x180166a30, "createpack_req_ser_180162530": 0x180162530, "purchaseitems_req_ser_180126440": 0x180126440} for tag, va in sorted(TG.items()): try: src = dec(va) except Exception as e: src = "// ERR %s" % e w("d5_q3_dec_%s.txt" % tag, "// %s %#x len=%d\n" % (tag, va, len(src)) + src) txt = [] for e in cands[:14]: src = dec(e) txt.append("// ===== %#x %s offs=%s len=%d\n%s" % (e, fname(e), sorted("%#x" % x for x in score[e]), len(src), src)) w("d5_q3_packreaders.txt", "\n".join(txt)) w("d5_q3_notes.txt", "\n".join(buf) + "\n") print("DONE") except Exception: traceback.print_exc()