"""D2 QUICK SELL, batch 4: settle where the "level" query key comes from.

MEASURED FACT (live memory, pid 134663, read-only): the fcc_discardcoins table
has 141 rows keyed (cardtype, level, rare) with level in {1,2,3} only -- there is
NO level==0 row. Three live club items decode exactly:
    cardtype 1, level 3, rare 1 -> price 800 ; rating 94 -> 752 ; rating 75 -> 600
    cardtype 6, level 1, rare 0 -> price   5 ; rating 55 ->   3
    cardtype 6, level 3, rare 0 -> price  40 ; rating 95 ->  38
So the query MUST have been issued with level = 3 / 1 / 3, never 0.

But batch 2's instruction scan of FUN_18013fe00 found EXACTLY ONE reference to
[RBP + 0x1b4] (the slot the "level" argument is loaded from) and it is a READ at
0x180141099; the only write covering that slot appeared to be the 16-byte
MOVDQA at 0x18013ffa1. Something is wrong with that conclusion -- this is exactly
the absence trap. Find the write.

HYPOTHESES TO TEST, in order
  H1 the MOVDQA source is not _DAT_1801f66a0, or that constant is not
     56 01 00 00 | 00 00 00 00 | ...
  H2 part of the atom switch lives outside the address set Ghidra assigned to
     FUN_18013fe00, so the body-only instruction walk missed an arm. Test by
     walking the whole address range 0x18013fe00..0x180141400 instruction by
     instruction, ignoring function boundaries.
  H3 the slot is written through a register-based pointer (LEA RAX,[RBP+0x160]
     style) rather than an RBP displacement.

CONTROL: the same range-walk must find the KNOWN write to [RBP + 0x1ac]
(cardtype) at 0x180140e16 and the KNOWN write to [RBP + 0x1b8] (rareflag) at
0x180140cc5. Both are plain RBP-displacement MOVs, the same syntactic form as
the write being hunted, so finding them proves the walk sees this form.
"""
import traceback

try:
    print("##### H1: the initialiser constant #####")
    for a in (0x1801f66a0, 0x1801f66b0):
        print("  %#x = %s" % (a, read_bytes(a, 16).hex()))
    print("  asm 0x18013fe00..0x18013fff0:")
    p = 0x18013fe00
    while p < 0x18013fff0:
        i = listing.getInstructionAt(addr(p))
        if i is None:
            p += 1
            continue
        print("    %#x  %s" % (p, str(i)))
        p += i.getLength()

    print("\n##### H2/H3: whole-range instruction walk 0x18013fe00..0x180141400 #####")
    want = ("0x1b4", "0x1b0", "0x1ac", "0x1b8", "0x214", "0x198", "0x19c")
    p = 0x18013fe00
    n = 0
    hits = {w: [] for w in want}
    lea160 = []
    while p < 0x180141400:
        i = listing.getInstructionAt(addr(p))
        if i is None:
            p += 1
            continue
        t = str(i)
        n += 1
        for w in want:
            if w in t:
                hits[w].append((p, t))
        if "RBP + 0x160]" in t or "RBP + 0x1" in t and t.startswith("LEA"):
            lea160.append((p, t))
        p += i.getLength()
    print("  walked %d instructions" % n)
    for w in want:
        print("  --- '%s' : %d ---" % (w, len(hits[w])))
        for a, t in hits[w]:
            print("      %#x %s" % (a, t))
    print("  --- LEA of frame slots ---")
    for a, t in lea160:
        print("      %#x %s" % (a, t))

    print("\n##### the atom-0x191 (level) question #####")
    # find every immediate 0x191 anywhere in the range, in any form
    p = 0x18013fe00
    while p < 0x180141400:
        i = listing.getInstructionAt(addr(p))
        if i is None:
            p += 1
            continue
        t = str(i)
        if "0x191" in t or "0x18a" in t or "0x192" in t:
            print("    %#x %s" % (p, t))
        p += i.getLength()

    print("\n##### callers of FUN_18013fe00 (maybe one pre-fills level) #####")
    for frm, typ, fn, ent in xrefs_to(0x18013fe00):
        print("   %#x %s %s %#x" % (frm, typ, fn, ent))

except Exception:
    traceback.print_exc()
