"""q_cd_route_2 -- the ClubSearch (FutStickerBookSearch) query-string builder.

HYPOTHESIS
  The club item list is the "StickerBookSearch" request (RS4 literal at 0x180221e48).
  Its literal block holds "%s%s=%s" 0x180221e78, "%s%s=%d" 0x180221e80, "%s%s="
  0x180221e94, "2017", "desc", "asc", so the query string is assembled key by key with
  a separator+name+value printf, and the key names come out of the atom reverse
  lookup FUN_180180cd0 exactly as they do in the /stats/%s builder FUN_18012f4f0
  (already decompiled: 6 cases, atoms 0x87 club / 0x389 year / 0xbd country /
  0x189 league / 0x1d7 newcards / 0xa5 consumables).
  A RIP-displacement scan of .text on the on-disk PE puts every use of those three
  printf formats in 0x18012deb0..0x18012e5bd, one contiguous region, plus two strays
  at 0x180169357/0x1801693ac and one at 0x180163a1c.

CONTROL
  FUN_18012f4f0 (/stats/%s) decompiled cleanly in q_cd_route_1 through the same
  helpers, so a NO FUNC or an empty body here is a fact about this address, not about
  the harness.  Also included: the two enum reverse-lookups FUN_180166300 (table
  0x180229ab0) and the sibling at 0x180166340 (table 0x180229c30), and their callers,
  because those tables are the candidate ?type= vocabularies and a table with no
  caller inside a URL builder proves nothing about the wire.

  Absence discipline: the atom-id switch in a builder is a `switch` with case labels,
  so it is enumerated by reading the decompile in full, not by grepping "== 0x".
"""
import traceback

try:
    def show(label, ea, full=True):
        f = func(ea)
        print("=" * 78)
        print("### %s  @ %s  -> %s" % (label, hex(ea), f.getName() if f else "NO FUNC"))
        if f is None:
            return None
        print("    entry %s  body %s" % (f.getEntryPoint(), f.getBody()))
        src = dec(f)
        print("    len(src) = %d" % len(src))
        if full:
            print(src)
        return f

    seen = set()
    for ea in (0x18012deb0, 0x18012df5c, 0x18012e087, 0x18012e19f, 0x18012e202,
               0x18012e256, 0x18012e2d0, 0x18012e322, 0x18012e5bd,
               0x18012dfb0, 0x18012e003, 0x18012e36d, 0x18012e3b8, 0x18012e403,
               0x18012e44e, 0x18012e499, 0x18012e0db):
        f = fm.getFunctionContaining(addr(ea))
        if f is None:
            print("### query-fmt use @ %s -> NO FUNC" % hex(ea))
            continue
        k = int(f.getEntryPoint().getOffset())
        if k in seen:
            continue
        seen.add(k)
        show("query-fmt user (via %s)" % hex(ea), k)

    for ea in (0x180169357, 0x180163a1c):
        f = fm.getFunctionContaining(addr(ea))
        if f is None:
            print("### stray fmt use @ %s -> NO FUNC" % hex(ea))
            continue
        k = int(f.getEntryPoint().getOffset())
        if k not in seen:
            seen.add(k)
            show("stray query-fmt user (via %s)" % hex(ea), k)

    show("enum revlookup table 0x180229ab0", 0x180166300)
    show("enum revlookup table 0x180229c30", 0x180166340)
    for ea in (0x180166300, 0x180166340):
        f = fm.getFunctionContaining(addr(ea))
        if f is None:
            continue
        print("--- callers of %s:" % hex(ea))
        for c in sorted(set(int(x.getEntryPoint().getOffset()) for x in f.getCallingFunctions(mon))):
            print("      %s  %s" % (hex(c), fm.getFunctionAt(addr(c)).getName()))

    show("StaffStats suffix (/stats/staff user)", 0x18012b086)
except Exception:
    traceback.print_exc()
