"""Q5: who consumes cardtype 9 / the club subtypes?

Two prongs.

A. FUN_180162c90 calls BOTH code->string helpers (type at 0x180162cec, subtype at
   0x180163071), so it is the request builder for club?type=...&... Decompile it in
   full: it names the query parameters and shows which enum feeds which parameter.
   This answers Q3 directly.

B. THE ABSENCE TRAP GUARD. Rather than grep for "== 0x91", scan EVERY instruction in
   .text for a scalar operand in the club-subtype set {0x1e,0x1f,0x91..0x96} and
   group by containing function, regardless of mnemonic (cmp / sub / mov / lea /
   switch-index arithmetic all count), then report functions that carry three or more
   DISTINCT members of the set. A dispatch written as a sub-ladder or a jump table
   still shows up because the constants themselves must exist somewhere -- and if a
   jump table is used, the case labels live in .rdata, so also scan .rdata/.data for
   the byte-pattern of a switch index table.

CONTROL for the scan: FUN_1800d8330 is known to contain all eight of 0x1e,0x1f,
0x91..0x96 as switch case labels. If the scan does not list FUN_1800d8330, the scan
is broken and any "no other consumer" conclusion is void.
"""
import traceback

try:
    src = dec(0x180162C90)
    print("=== A: FUN_180162c90 request builder, len=%d ===" % len(src))
    print(src)

    print()
    print("=== B: scalar scan over .text for {0x1e,0x1f,0x91..0x96} ===")
    TARGET = {0x1E, 0x1F, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96}
    per = {}
    block = None
    for b in mem.getBlocks():
        if b.getName() == ".text":
            block = b
            break
    it = listing.getInstructions(block.getStart(), True)
    n = 0
    while it.hasNext():
        ins = it.next()
        if ins.getAddress().getOffset() > block.getEnd().getOffset():
            break
        n += 1
        for i in range(ins.getNumOperands()):
            for o in ins.getOpObjects(i):
                try:
                    v = int(o.getValue())
                except Exception:
                    continue
                if v in TARGET:
                    f = fm.getFunctionContaining(ins.getAddress())
                    key = (f.getName(), int(f.getEntryPoint().getOffset())) if f else ("?", 0)
                    per.setdefault(key, {}).setdefault(v, []).append(
                        int(ins.getAddress().getOffset()))
    print("  instructions scanned: %d" % n)
    rank = sorted(per.items(), key=lambda kv: -len(kv[1]))
    for (name, ent), d in rank:
        if len(d) < 3:
            continue
        print("  %-28s %#x  distinct=%d  %s" %
              (name, ent, len(d), sorted("%#x" % k for k in d)))
    print("  --- control present? FUN_1800d8330 ---")
    for (name, ent), d in per.items():
        if ent == 0x1800D8330:
            print("     YES: distinct=%d %s" % (len(d), sorted("%#x" % k for k in d)))
except Exception:
    traceback.print_exc()
