#!/usr/bin/env python3
"""Map every GPIO write in the RT-4D app: find calls to the low-level GPIO
helpers and back-resolve (port, mask, state) from the preceding instructions."""
import struct, re, capstone, os

BASE = 0x08002800
P = "/home/viktor/claude/rt-4d/stock-fw/rt4d_stock_v3.25_abs_0x08002800.bin"
IMG = open(P, "rb").read()
md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)

PORTS = {0x40020000: "GPIOA", 0x40020400: "GPIOB", 0x40020800: "GPIOC",
         0x40020C00: "GPIOD", 0x40021000: "GPIOE", 0x40021400: "GPIOF",
         0x40021800: "GPIOG"}
# low-level writers discovered by disassembly
HELPERS = {0x08021C6E: "gpio_set_state(port,mask,state)",
           0x08021C56: "gpio_bsrr_write(port,mask)",
           0x08021C4C: "set_bit0(reg,val)"}


def word(va):
    o = va - BASE
    return struct.unpack_from("<I", IMG, o)[0] if 0 <= o <= len(IMG) - 4 else None


def lit_of(ins):
    m = re.search(r"\[pc, #(?:0x)?([0-9a-fA-F]+)\]", ins.op_str)
    if m and ins.mnemonic.startswith("ldr"):
        return word(((ins.address + 4) & ~3) + int(m.group(1), 16))
    return None


# linear sweep, keep a sliding window to resolve register values
def scan():
    hits = []
    win = []
    for ins in md.disasm(IMG, BASE):
        win.append(ins)
        if len(win) > 14:
            win.pop(0)
        if ins.mnemonic == "bl" and ins.op_str.startswith("#"):
            tgt = int(ins.op_str[1:], 16)
            if tgt in HELPERS:
                regs = {}
                for p in win[:-1]:
                    mm = re.match(r"(r\d+)", p.op_str)
                    dst = mm.group(1) if mm else None
                    if not dst:
                        continue
                    v = lit_of(p)
                    if v is not None:
                        regs[dst] = v
                    elif p.mnemonic in ("movs", "mov.w", "mov") and "#" in p.op_str:
                        try:
                            regs[dst] = int(p.op_str.split("#")[1], 0)
                        except Exception:
                            pass
                    elif p.mnemonic == "movw" and "#" in p.op_str:
                        try:
                            regs[dst] = int(p.op_str.split("#")[1], 0)
                        except Exception:
                            pass
                hits.append((ins.address, HELPERS[tgt], regs.get("r0"), regs.get("r1"), regs.get("r2")))
    return hits


if __name__ == "__main__":
    rows = scan()
    print(f"{len(rows)} GPIO-helper call sites\n")
    print(f"{'site':<12}{'helper':<32}{'r0(port/reg)':<22}{'r1(mask/val)':<14}{'r2'}")
    for site, h, r0, r1, r2 in rows:
        pn = PORTS.get(r0, f"0x{r0:08X}" if r0 is not None else "?")
        mask = f"0x{r1:X}" if r1 is not None else "?"
        bit = ""
        if r1 and r1 and (r1 & (r1 - 1)) == 0:
            bit = f" (bit{r1.bit_length()-1})"
        print(f"0x{site:08X}  {h:<32}{pn:<22}{mask+bit:<14}{r2 if r2 is not None else ''}")
