"""Q1/Q2/Q4 decisive: which ATOM writes the struct field the capacity applier reads,
and is pileSizeClientData consumed anywhere.

FUN_18011dbf0(model, S) does model+0x1fd1c = S[+0x08] and model+0x1fd20 = S[+0x0c].
So I need the settings/response struct S and which atom arm writes S+0x08.

HYPOTHESIS: atom 0x1c0 maximumTradePileSize writes S+0x08 and 0x1bf
maxAuctionsAllowed (or a watchlist atom) writes S+0x0c.

ABSENCE-TRAP GUARD: a jump-table switch never contains the case value as an
immediate, so a literal scan for 0x1c0 would produce a FALSE ABSENCE. I therefore
do BOTH: (a) full decompile of the deserialiser and its callers, printed in FULL
with len(), and (b) a raw immediate scan in every common encoding. A disagreement
between the two is itself the finding.

CONTROL: atom 0x361 (untradeable) and 0x336 are PROVEN to have arms in the settings
range switch. Whatever form I find them in is the form I must search for 0x1c0.
"""
import struct, traceback

CONSTS = {0x1bf: "maxAuctionsAllowed", 0x1c0: "maximumTradePileSize",
          0x227: "pileSizeClientData", 0x333: "tradePile", 0x381: "watchlist",
          0x361: "CONTROL untradeable", 0x336: "CONTROL"}

try:
    for a, tag in [
        (0x180173e00, "settings completion callback -- calls BOTH appliers"),
        (0x180174580, "builds the request descriptor"),
    ]:
        src = dec(a)
        print("\n\n########## %#x %s (len=%d) FULL ##########" % (a, tag, len(src)))
        print(src)
except Exception:
    traceback.print_exc()

try:
    print("\n\n########## caller of vt+0x988 at 0x18011e21a ##########")
    f = fm.getFunctionContaining(addr(0x18011e21a))
    print("containing:", f.getName() if f else "?", hex(int(f.getEntryPoint().getOffset())) if f else "")
    if f:
        s = dec(int(f.getEntryPoint().getOffset()))
        print("len=%d" % len(s)); print(s)
except Exception:
    traceback.print_exc()

try:
    print("\n\n########## raw immediate scan for the atom constants ##########")
    def sect(name):
        for b in mem.getBlocks():
            if b.getName() == name:
                return int(b.getStart().getOffset()), int(b.getEnd().getOffset()) - int(b.getStart().getOffset()) + 1
    TB, TS = sect(".text")
    TEXT = read_bytes(TB, TS)
    print("text len", len(TEXT))
    for v, nm in sorted(CONSTS.items()):
        pats = {
            "cmp eax,imm32 (3d)": b"\x3d" + struct.pack("<I", v),
            "cmp r/m32,imm32 (81 f8..ff)": None,
            "sub eax,imm32 (2d)": b"\x2d" + struct.pack("<I", v),
            "mov r32,imm32 (b8..bf)": None,
            "cmp r/m16,imm16 (66 81 f9)": b"\x66\x81\xf9" + struct.pack("<H", v),
            "cmp r/m16,imm16 (66 3d)": b"\x66\x3d" + struct.pack("<H", v),
            "bare imm32 le": struct.pack("<I", v),
            "bare imm16 le": struct.pack("<H", v),
        }
        print("\n--- %#05x %s" % (v, nm))
        for label, pat in pats.items():
            if pat is None:
                continue
            hits = []
            j = TEXT.find(pat)
            while j != -1 and len(hits) < 40:
                hits.append(TB + j)
                j = TEXT.find(pat, j + 1)
            if hits:
                fns = {}
                for h in hits:
                    fn = fm.getFunctionContaining(addr(h))
                    fns.setdefault(fn.getName() if fn else "?", []).append(hex(h))
                print("   %-30s %3d hit(s)" % (label, len(hits)))
                for k, vv in sorted(fns.items()):
                    print("        %-22s %s" % (k, vv[:8]))
except Exception:
    traceback.print_exc()
