#!/usr/bin/env python3
"""Decode the running-sum atom ladders in /hub parser FUN_180139610 and name each
atom from docs/fut_atoms.tsv.

The dispatch is `sub ecx,d0 / sub ecx,d1 / .../ cmp ecx,dN`: the atom that each
branch handles is the CUMULATIVE sum of the deltas up to and including that step
(a jz after each sub tests atom==running_sum). Plus there are direct `cmp esi,imm`.
"""
import subprocess, re

DLL = "/tmp/fut/cardsdll.dll"
TSV = "/home/alex/Documents/OpenFUT/fifa17-recon/docs/fut_atoms.tsv"
FUNC, STOP = 0x180139610, 0x18013e600

atoms = {}
for line in open(TSV):
    p = line.rstrip("\n").split("\t")
    if len(p) >= 3:
        try: atoms[int(p[1], 16)] = p[2]
        except ValueError: pass

out = subprocess.check_output(
    ["objdump", "-d", "-M", "intel",
     "--start-address=%#x" % FUNC, "--stop-address=%#x" % STOP, DLL], text=True)

# linear list of (addr, mnem, dest_reg, imm) for sub/cmp on 32-bit regs, stop at int3 pad
seq = []
int3 = 0
for ln in out.splitlines():
    parts = ln.split("\t")
    if len(parts) < 3:
        continue
    addr_s = parts[0].strip().rstrip(":")
    try:
        addr = int(addr_s, 16)
    except ValueError:
        continue
    instr = parts[2].strip()
    bits = instr.split(None, 1)
    mnem = bits[0]
    ops = bits[1].strip() if len(bits) > 1 else ""
    if mnem == "int3":
        int3 += 1
        if int3 >= 4: break
        continue
    int3 = 0
    mo = re.match(r"(e?[a-d]x|e?si|e?di|e?bp|r\d+d?),\s*(0x[0-9a-f]+)$", ops)
    if mnem in ("sub", "cmp") and mo:
        seq.append((addr, mnem, mo.group(1), int(mo.group(2), 16)))

# walk ladders: consecutive sub/cmp on the SAME register form one ladder; the running
# sum at each element is the atom that element dispatches. A `cmp` closes the ladder.
found = {}          # atom -> (addr, kind)
i = 0
while i < len(seq):
    addr, mnem, reg, imm = seq[i]
    # a ladder starts on a sub
    if mnem == "sub":
        run = 0
        j = i
        while j < len(seq) and seq[j][2] == reg and seq[j][1] in ("sub", "cmp"):
            run += seq[j][3]
            found.setdefault(run, (seq[j][0], "ladder"))
            if seq[j][1] == "cmp":
                j += 1
                break
            j += 1
        i = j
    else:
        # a lone cmp reg,imm on an atom-holding reg is a direct atom test
        if 0 < imm <= 0x400:
            found.setdefault(imm, (addr, "direct"))
        i += 1

TOKENS = {0x1, 0x6, 0x7, 0x9, 0xa, 0xb, 0xc, 0xd}   # SAX token enum, not atoms
print("Atoms dispatched by hub parser FUN_%#x:" % FUNC)
print("=" * 70)
for a in sorted(found):
    if a in TOKENS:
        continue
    tag = "  <-- TOKEN?" if a < 0x10 else ""
    print("  %#06x  %-28s (%s @ %#x)%s" %
          (a, atoms.get(a, "?"), found[a][1], found[a][0], tag))

print("\nKnown tile counters for reference: 0x33=auctionCount, 0x90=clubPlayers")
print("\nName-based tile-count candidates:")
KEYS = ("sell","sold","trade","auction","pile","list","count","num","offer",
        "won","outbid","target","watch","transfer","active","unassigned")
for a in sorted(found):
    if a in TOKENS: continue
    n = atoms.get(a, "").lower()
    if any(k in n for k in KEYS):
        print("  %#06x  %s" % (a, atoms.get(a, "?")))
