"""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("<Q", vt)
        P("=== code refs to vtable ptr %#x (%s) ===" % (vt, tag))
        for frm, typ, fn, ent in xrefs_to(vt):
            P("   from %#x %-12s %s @ %#x" % (frm, typ, fn, ent))

    for callee, tag in ((0x180162530, "createpack_req_ser"),
                        (0x180126440, "purchaseitems_req_ser"),
                        (0x180162770, "createpack_resp_factory"),
                        (0x180126820, "purchaseitems_resp_factory"),
                        (0x18016c060, "http_status_mapper"),
                        (0x180166a30, "state_enum_to_str")):
        try:
            cs = callers(callee)
        except Exception as e:
            P("CALLERS %s ERR %s" % (tag, e)); continue
        P("CALLERS of %-28s %#x : %s" % (tag, callee, cs))

    # ---------- D. offset scan for pack-record readers ----------
    P("")
    P("=== disp32 offset scan in .text ===")
    def scan(off):
        pat = struct.pack("<I", off)
        hits = find_all(pat, (".text",))
        fs = {}
        for h in hits:
            f = fm.getFunctionContaining(addr(h))
            if f:
                fs.setdefault(int(f.getEntryPoint().getOffset()), 0)
                fs[int(f.getEntryPoint().getOffset())] += 1
        return fs

    packoffs = [0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, 0x158]
    tables = {}
    for o in packoffs + [0x28]:
        tables[o] = scan(o)
        P("  offset %#05x -> %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()
