#!/usr/bin/env python3
"""Apply the UART touch-bypass patch to a Smatfarm GD32F130 firmware dump.

Usage:
    python3 apply_patch.py smatfarm_dump.bin uart_patch.bin smatfarm_PATCHED.bin

smatfarm_dump.bin  -- your own firmware, dumped via:
                       openocd ... -c "dump_image smatfarm_dump.bin 0x08000000 0x10000"
uart_patch.bin      -- built from uart_patch.c, see the howto for the exact
                       compile/link commands (must produce exactly this file)
smatfarm_PATCHED.bin -- output, ready to flash back with flash write_image

This only overwrites the dead touch-detection function (0x08000678-0x080006f4,
124 bytes) and NOP-pads any unused remainder. Every other byte of your dump
is carried through unchanged.
"""
import sys

TARGET_FLASH_ADDR = 0x08000678
TARGET_FUNC_END   = 0x080006f4
FLASH_BASE        = 0x08000000
EXPECTED_SIZE     = 65536

def main():
    if len(sys.argv) != 4:
        print(__doc__)
        sys.exit(1)

    orig_path, patch_path, out_path = sys.argv[1:4]

    with open(orig_path, "rb") as f:
        fw = bytearray(f.read())
    with open(patch_path, "rb") as f:
        patch = f.read()

    if len(fw) != EXPECTED_SIZE:
        print(f"warning: expected a {EXPECTED_SIZE}-byte dump, got {len(fw)} -- "
              f"make sure you dumped the full 64KB flash region, continuing anyway")

    off_start = TARGET_FLASH_ADDR - FLASH_BASE
    off_end   = TARGET_FUNC_END - FLASH_BASE
    region_size = off_end - off_start

    if len(patch) > region_size:
        print(f"error: patch is {len(patch)} bytes, only {region_size} bytes "
              f"available at the target address -- patch must be rebuilt smaller")
        sys.exit(1)

    original_bytes = fw[off_start:off_start + 16]
    if original_bytes[:2] != b"\x10\xb5":
        print("warning: the bytes at the target address don't start with the "
              "expected push{r4,lr} (10 b5) -- this dump may be from a "
              "different firmware version than this patch was built for. "
              "Proceeding, but verify the result before flashing.")

    fw[off_start:off_start + len(patch)] = patch
    pad_start = off_start + len(patch)
    pad_len = off_end - pad_start
    nop_pattern = (b"\x00\xbf" * ((pad_len // 2) + 1))[:pad_len]
    fw[pad_start:off_end] = nop_pattern

    with open(out_path, "wb") as f:
        f.write(fw)

    print(f"wrote {out_path}: {len(fw)} bytes, patched region "
          f"0x{TARGET_FLASH_ADDR:08x}-0x{TARGET_FUNC_END:08x} "
          f"({len(patch)} bytes of code, {pad_len} bytes NOP padding)")

if __name__ == "__main__":
    main()
