"""D5 last pass: the endpoint table (call-id -> URL), CreatePack's URL builder, who sets the CreatePack purchase MODE, and the PurchaseItems response defaults. HYPOTHESES H9: every FUT ServerCall carries a numeric id (CreatePack=0x4b, PurchaseItems=0x4c, passed to FUN_18016be60) and a table near 0x18021e100 maps id -> URL format string ("ut/%s/store", "ut/%s/purchased", ...). H10: FUN_180124ad0 is CreatePack's URL builder and it emits the "store/transaction" path we already see on the wire. H11: the mode field at +0x20 of the CreatePack call (0=COINS,1=MTX,2=POINTS,4=preorder) is set by whoever constructs it; callers of FUN_1801623d0 will show the choice. CONTROL: dec(0x180162530) reprinted (known: writes packId/useCredits/usePreOrder/currency). """ 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(0x180162530) P("CONTROL dec(0x180162530) len=%d has_MTX=%s has_COINS=%s" % (len(s), '"MTX"' in s, '"COINS"' in s)) P("") P("=== endpoint table 0x18021e080..0x18021e900 ===") a = 0x18021e080 while a < 0x18021e900: 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 0x180000000 <= q < 0x181000000 and f is None: try: t = rd_str(q, 60) if t and all(32 <= ord(c) < 127 for c in t): extra = "->str %r" % t except Exception: pass P(" %#x -> %#x %s %s" % (a, q, f.getName() if f else "", extra)) a += 8 P("") P("=== createpack req vtable 0x180214d40..0x180214e10 ===") a = 0x180214d40 while a < 0x180214e10: q = qword(a) f = fm.getFunctionAt(addr(q)) if 0x180000000 <= q < 0x181000000 else None P(" +%03x %#x -> %#x %s" % (a - 0x180214d40, a, q, f.getName() if f else "")) a += 8 P("") for callee, tag in ((0x1801623d0, "createpack_call_ctor"), (0x1801263a0, "purchaseitems_call_ctor"), (0x18016be60, "servercall_base_ctor"), (0x180124ad0, "maybe_createpack_url")): try: cs = callers(callee) except Exception as e: P("CALLERS %s ERR %s" % (tag, e)); continue P("CALLERS of %-26s %#x : %s" % (tag, callee, [("%#x" % c, n) for c, n in cs][:30])) try: xs = xrefs_to(callee) P(" xrefs: %s" % [("%#x" % f2, t, n) for f2, t, n, e2 in xs][:30]) except Exception: pass w("d5_q5_notes.txt", "\n".join(buf) + "\n") TG = {"createpack_url_180124ad0": 0x180124ad0, "createpack_x_180162c90": 0x180162c90, "servercall_base_ctor_18016be60": 0x18016be60, "purchaseitems_resp_ctor_18002d460": 0x18002d460, "fp_18002ee50": 0x18002ee50, "fp_18002ed20": 0x18002ed20, "fp_18002f1b0": 0x18002f1b0, "fp_18002f0b0": 0x18002f0b0, "fp_18002f920": 0x18002f920, "generic_180068320": 0x180068320, "packtypes_url_180123430": 0x180123430} for tag, va in sorted(TG.items()): try: src = dec(va) except Exception as e: src = "// ERR %s" % e w("d5_q5_dec_%s.txt" % tag, "// %s %#x len=%d\n" % (tag, va, len(src)) + src) # who constructs the createpack call -> the mode ctor_callers = set() for c, n in callers(0x1801623d0): ctor_callers.add(int(c)) for frm, typ, fn, ent in xrefs_to(0x1801623d0): if ent: ctor_callers.add(int(ent)) txt = [] for e in sorted(ctor_callers): src = dec(e) txt.append("// ===== caller of createpack ctor %#x %s len=%d\n%s" % (e, fname(e), len(src), src)) w("d5_q5_createpack_callers.txt", "\n".join(txt) if txt else "// none found\n") w("d5_q5_notes.txt", "\n".join(buf) + "\n") print("DONE") except Exception: traceback.print_exc()