"""Final closer: is FUN_180173e00 (the massInfo callback) the ONLY route to the
capacity applier, and what is the constructor default of model+0x1fd1c?

If vt+0x998 has exactly one call site, then pileSizeClientData inside userMassInfo is
the ONLY way to set TRANSFER LIST capacity, and no other endpoint can be blamed or
used.

SEARCH FORM -- the trap that produced the "applier is unreachable" error before:
a virtual call is `ff /2` with a disp, and Ghidra does not resolve it, so a
direct-call/xref search finds NOTHING. I enumerate the ModRM byte myself for
disp32 form (ff 90..97 excluding 94 which needs SIB) over the whole .text.
CONTROL: the same scan for vt+0x988 (FUN_18011dc50) must reproduce the two known
sites 0x18011e21a and 0x180173f0b. If it does not, the scan is wrong and neither
result may be used.

Also: constructor default. I find the writers of 0x1fd1c by raw disp32 (0x1fd1c is
far too large for disp8, so the 4 literal bytes appear in every encoding) and print
each with its containing function -- the same form-independent search that located
the +0x1fd2e writer.
"""
import struct, traceback

try:
    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))

    def vcalls(slot):
        """every `call [reg+slot]` in disp32 form: ff 90..97 (skip 94=SIB) + imm32"""
        out = []
        d = struct.pack("<I", slot)
        for modrm in list(range(0x90, 0x98)):
            if modrm == 0x94:
                continue
            pat = bytes([0xFF, modrm]) + d
            j = TEXT.find(pat)
            while j != -1:
                out.append((TB + j, modrm))
                j = TEXT.find(pat, j + 1)
        # rex-prefixed forms are identical bytes after the rex, which the scan above
        # already lands on because it does not anchor to the rex byte
        return sorted(out)

    for slot, note in [(0x988, "CONTROL: gate applier FUN_18011dc50, known sites "
                              "0x18011e21a and 0x180173f0b"),
                       (0x998, "the CAPACITY applier FUN_18011dbf0"),
                       (0xa58, "TRADE_PILE_SIZE reader")]:
        hits = vcalls(slot)
        print("\n=== call [reg+%#x]: %d site(s)   %s" % (slot, len(hits), note))
        for a, m in hits:
            f = fm.getFunctionContaining(addr(a))
            print("    %#x  modrm=%#x  in %s" % (a, m, f.getName() if f else "?"))

    print("\n\n=== raw disp32 scan: every reference to +0x1fd1c ===")
    pat = struct.pack("<I", 0x1fd1c)
    j = TEXT.find(pat)
    while j != -1:
        a = TB + j
        f = fm.getFunctionContaining(addr(a))
        print("    %#x in %s   bytes[-6..+10]=%s"
              % (a, f.getName() if f else "?", read_bytes(a - 6, 16).hex()))
        j = TEXT.find(pat, j + 1)
except Exception:
    traceback.print_exc()
