"""D3 THE THIRD CONDITION: who writes massinfo-response+0x17c?

FOUND: at the END of the userMassInfo deserializer FUN_180174630 (the token==10
tail, i.e. after the whole body is parsed) there is

    if (*(char *)(param_1 + 0x17c) != '\\0') { *(undefined4 *)(param_1 + 0x50) = 0; }

and param_1+0x50 is the settings sub-struct's field [10] -- the very field that
FUN_18011dc50 copies to model+0x1fd2e (IS_TRADING_ENABLED). Atom 0x336
(tradingEnabled) writes that same field via FUN_18013c6d0(param_1+0x28).

So a single byte at response+0x17c can veto tradingEnabled AFTER it is parsed.

HYPOTHESIS: +0x17c is a userInfo-level boolean on the same response struct
(the massinfo response carries userInfo, squad and settings) whose atom we are
sending, or whose CONSTRUCTOR DEFAULT is non-zero, and it is the third condition.

METHOD: 0x17c cannot be encoded as a disp8, so every access to [reg+0x17c] must
carry the literal disp32 bytes 7c 01 00 00. Scanning for that displacement is
form-independent -- it catches mov/movzx/cmp/setcc/lea in every encoding, which
is what the "== 0x" grep trap requires.

CONTROL: the same scan must return the KNOWN read at 0x1801748xx inside
FUN_180174630. If the known read does not appear, the scan is broken and no
absence it reports means anything.

Also decompiles FUN_180174580 and dumps descriptor vtable 0x18022d000 to find
the response-struct constructor, so the default value of +0x17c can be read.
"""
import traceback

try:
    TEXT_LO, TEXT_HI = 0x180001000, 0x1801e5000
    text = read_bytes(TEXT_LO, TEXT_HI - TEXT_LO)
    print("text bytes: %d" % len(text))

    def dispscan(disp):
        pat = int(disp).to_bytes(4, "little")
        out, i = [], text.find(pat)
        while i != -1:
            va = TEXT_LO + i
            f = fm.getFunctionContaining(addr(va))
            out.append((va, f.getName() if f else "?",
                        int(f.getEntryPoint().getOffset()) if f else 0,
                        text[max(0, i - 8):i + 8].hex()))
            i = text.find(pat, i + 1)
        return out

    for disp in (0x17C, 0x50, 0x180):
        hits = dispscan(disp)
        print("=" * 78)
        print("### disp32 +%#x : %d sites" % (disp, len(hits)))
        if disp == 0x17C:
            for va, nm, ent, ctx in hits:
                print("    %#x  %-24s (%#x)  ctx=%s" % (va, nm, ent, ctx))
        else:
            print("    (too many to be useful; count only)")

    print()
    print("#" * 78)
    print("### descriptor vtable 0x18022d000")
    for slot, t, nm in vtable(0x18022D000, 24):
        print("   +%#05x  %#018x  %s" % (slot, t, nm))

    print()
    print("#" * 78)
    for label, a in [("FUN_180174580 descriptor builder", 0x180174580)]:
        src = dec(a)
        print("### %s len=%d" % (label, len(src)))
        print(src)
except Exception:
    traceback.print_exc()
