# -*- coding: utf-8 -*-
"""
q_pack_inv_7 -- DIMENSION 1 pass 7: who READS unopened(0xcd) and the unopenedPacks total.

HYPOTHESES
  (a) The store pack element built by FUN_18013af30 is 0x158 bytes; `unopened`
      (atom 0x35d) lands at byte offset 0xcd and `useDefaultImage` at 0xcc.
      Any x86 access with disp32 == 0xcd inside .text is a candidate reader.
  (b) The FUT model singleton (DAT_1802e6398, written by FUN_18011d780) has a
      vtable whose slot +0x4e0 is the unopenedPacks-total setter. Find the vtable
      by locating the ctor that both calls FUN_18011d780 and stores a vtable ptr.
  (c) displayGroup(0xd9) is an OBJECT {priority(0x250):int, value(0x377):string};
      value lands at element offset 0x00 and priority at 0x34, and the My Packs UI
      FUN_1800150d0 compares offset 0 against the literal "mypacks".

CONTROLS
  * The disp32==0xcc scan MUST return FUN_1800150d0 (hand-verified: it reads
    *(char *)((longlong)puVar10 + 0xcc) to pick the pack background image).
  * The disp32 scanner is also run for 0x34 and must return FUN_1800150d0 too.
  * class_deser("FutSquadSaveServerResponse") must still be 0x180171a60.
"""
import struct
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres"


def disp32_readers(disp):
    """functions containing a modrm with mod=10 and this disp32."""
    pat = struct.pack("<I", disp)
    out = {}
    for h in find_all(pat, blocks=(".text",)):
        try:
            prev = read_bytes(h - 1, 1)[0]
        except Exception:
            continue
        if not (0x80 <= prev <= 0xBF):
            continue
        f = fm.getFunctionContaining(addr(h))
        if f is None:
            continue
        e = int(f.getEntryPoint().getOffset())
        out.setdefault(e, []).append(h)
    return out


try:
    got = sorted(set(x[0] for x in class_deser("FutSquadSaveServerResponse")))
    print("CONTROL FutSquadSaveServerResponse -> %s %s"
          % ([hex(g) for g in got], "PASS" if 0x180171A60 in got else "FAIL"))

    for disp, tag, ctl in ((0xCC, "useDefaultImage (CONTROL)", 0x1800150D0),
                           (0xCD, "unopened", None),
                           (0x34, "displayGroup.priority (CONTROL)", 0x1800150D0)):
        r = disp32_readers(disp)
        print("\n### disp32 %#x  %s : %d functions" % (disp, tag, len(r)))
        if ctl is not None:
            print("    CONTROL %#x present: %s" % (ctl, "PASS" if ctl in r else "FAIL"))
        for e in sorted(r):
            f = fm.getFunctionContaining(addr(e))
            print("    %#x %-30s sites=%s"
                  % (e, f.getName(), [hex(x) for x in r[e][:6]]))

    print("\n########## MODEL SINGLETON CTOR / VTABLE ##########")
    ctors = set()
    for (fr, ty, fn, en) in xrefs_to(0x18011D780):
        print("  caller of FUN_18011d780: %#x %s @%#x" % (fr, fn, en))
        if en:
            ctors.add(en)
    for e in sorted(ctors):
        s = dec(e)
        print("\n--- ctor candidate %#x len=%d" % (e, len(s)))
        print(s[:4000])
        open(OUT + "/d1_modelctor_%x.txt" % e, "w").write(s)

    print("\n########## VTABLE SCAN in .rdata for tables >= 0x950 bytes ##########")
    LO, HI = 0x180001000, 0x1801E4F62
    blk = None
    for b in mem.getBlocks():
        if b.getName() == ".rdata":
            blk = b
    start = int(blk.getStart().getOffset())
    end = int(blk.getEnd().getOffset())
    data = read_bytes(start, end - start + 1)
    n = len(data) // 8
    qs = struct.unpack_from("<%dQ" % n, data, 0)
    i = 0
    found = []
    while i < n:
        if LO <= qs[i] <= HI:
            j = i
            while j < n and LO <= qs[j] <= HI:
                j += 1
            if (j - i) * 8 >= 0x950:
                found.append((start + i * 8, (j - i) * 8))
            i = j
        else:
            i += 1
    print("candidate vtables >= 0x950 bytes: %d" % len(found))
    for va, sz in found:
        f4e0 = qword(va + 0x4E0)
        f940 = qword(va + 0x940) if sz > 0x940 else 0
        n4e0 = fm.getFunctionAt(addr(f4e0))
        n940 = fm.getFunctionAt(addr(f940)) if f940 else None
        print("  vtable %#x size %#x   [+0x4e0]=%#x %s   [+0x940]=%#x %s"
              % (va, sz, f4e0, n4e0.getName() if n4e0 else "?",
                 f940, n940.getName() if n940 else "?"))
        if n4e0 is not None:
            s = dec(f4e0)
            print("     --- [+0x4e0] len=%d\n%s" % (len(s), s))
            open(OUT + "/d1_vt%x_slot4e0_%x.txt" % (va, f4e0), "w").write(s)

    print("\n########## pack-element consumers ##########")
    for va, tag in ((0x180014380, "find_group"), (0x18002C3C0, "tile_from_packelem"),
                    (0x180012950, "group_ctor")):
        s = dec(va)
        print("\n--- %s %#x len=%d" % (tag, va, len(s)))
        print(s)
        open(OUT + "/d1_%s.txt" % tag, "w").write(s)

    print("\nDONE q_pack_inv_7")
except Exception:
    traceback.print_exc()
