"""D4 Q1b/Q2: find the READERS of the settings gate byte at FutDataManagerImpl+0x1fd45
(the byte written from settings field [0x1d], which is the packOpeningAnimationEnabled
arm found by q_pack_reveal_1).

HYPOTHESIS: some accessor reads [reg + 0x1fd45] and is called from the pack-reveal
path. If no IS_* key exists (the publisher FUN_18006cc60 does not mention it), the
byte must be read by a direct getter instead.

CONTROLS: the same byte-displacement scan for 0x1fd3a (friendlySeasonsEnabled) and
0x1fd3d (enableDraftMode) must find the accessors that FUN_18006cc60 calls through
vtable slots +0x2b0 and +0x2c8 respectively. If the scan cannot reproduce those, the
scan technique is wrong and the 0x1fd45 result means nothing.

Also dumps PACK/REVEAL/WALKOUT/ANIM strings using find_all's REAL signature
(block NAMES, not block objects -- q_pack_reveal_1 passed objects and silently got 0).
"""
import traceback, struct

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


def w(name, text):
    with open(OUT + name, "w") as f:
        f.write(text)
    print("WROTE %s%s (%d chars)" % (OUT, name, len(text)))


def disp_scan(off):
    """functions containing the little-endian dword `off` inside .text."""
    pat = struct.pack("<I", off)
    hits = find_all(pat, (".text",))
    out = {}
    for h in hits:
        f = fm.getFunctionContaining(addr(h))
        if f:
            out.setdefault(int(f.getEntryPoint().getOffset()), []).append(h)
    return out


try:
    for label, off in (("CONTROL friendlySeasons 0x1fd3a", 0x1FD3A),
                       ("CONTROL draftMode      0x1fd3d", 0x1FD3D),
                       ("TARGET  field[0x1d]    0x1fd45", 0x1FD45),
                       ("neighbour field[0x1c]  0x1fd44", 0x1FD44),
                       ("neighbour field[0x1e]  0x1fd46", 0x1FD46)):
        d = disp_scan(off)
        print("=== %s : %d functions ===" % (label, len(d)))
        for ent, hs in sorted(d.items()):
            f = func(ent)
            print("   %#x  %s   hits=%s" % (ent, f.getName(), [hex(x) for x in hs]))

    # vtable of FutDataManagerImpl around the slots the publisher uses, to map
    # slot -> accessor -> byte offset
    print("=== strings ===")
    pats = [b"PACK", b"Pack", b"WALKOUT", b"Walkout", b"walkout", b"REVEAL",
            b"Reveal", b"ANIMATION", b"Animation", b"nimation"]
    seen = {}
    for p in pats:
        for h in find_all(p, (".text", ".rdata", ".data")):
            start = h
            for k in range(1, 128):
                try:
                    c = mem.getByte(addr(h - k)) & 0xFF
                except Exception:
                    break
                if c < 0x20 or c > 0x7E:
                    start = h - k + 1
                    break
            s = rd_str(start)
            if s and 3 < len(s) < 200:
                seen.setdefault(s, start)
    lines = ["%#x  %s" % (v, k) for k, v in sorted(seen.items(), key=lambda kv: kv[1])]
    print("%d distinct strings" % len(lines))
    w("d4_strings.txt", "\n".join(lines))
    for l in lines:
        print(l)

except Exception:
    traceback.print_exc()
