#!/usr/bin/env python3
"""
Patch MCU firmware binary to bypass CAN check ONLY on the deinit path.

Single patch:
  0x124E8: cbz r0, 0x124F2 → nop
  This is the caller's NULL-pointer check after CAN deinit.
  Safe during boot (only affects deinit, not init path).
  
Usage: python3 patch_bin.py
"""
import sys

BIN_FILE = '/Users/admin/workspace/jyq/mcubin/mcu_app.bin'
PATCHED_BIN = '/Users/admin/workspace/jyq/update/mcu_app_patched.bin'
PATCHED_HEX = '/Users/admin/workspace/jyq/update/mcu_app_patched.hex'

def checksum_hex(record_bytes):
    s = sum(record_bytes) & 0xFF
    return ((~s) + 1) & 0xFF

def bin_to_hex(bin_data):
    lines = []
    addr = 0
    pos = 0
    remaining = len(bin_data)
    while remaining > 0:
        chunk = min(16, remaining)
        ext_addr = (addr >> 16) & 0xFFFF
        if ext_addr > 0:
            rec = bytes([0x02, 0x00, 0x00, 0x04, (ext_addr >> 8) & 0xFF, ext_addr & 0xFF])
            lines.append(f":{rec.hex().upper()}{checksum_hex(rec):02X}")
        offset = addr & 0xFFFF
        rec = bytes([chunk, (offset >> 8) & 0xFF, offset & 0xFF, 0x00]) + bin_data[pos:pos+chunk]
        lines.append(f":{rec.hex().upper()}{checksum_hex(rec):02X}")
        if ext_addr > 0:
            rec = bytes([0x02, 0x00, 0x00, 0x04, 0x00, 0x00])
            lines.append(f":{rec.hex().upper()}{checksum_hex(rec):02X}")
        addr += chunk
        pos += chunk
        remaining -= chunk
    lines.append(":00000001FF")
    return lines

# Only ONE patch: safe deinit-path bypass
PATCHES = [
    (0x124E8, bytes([0x00, 0xBF]), bytes([0x18, 0xB1]),
     "Caller NULL check: cbz→nop (safe, deinit only)"),
]

def main():
    with open(BIN_FILE, 'rb') as f:
        bin_data = bytearray(f.read())
    print(f"Read {len(bin_data)} bytes from {BIN_FILE}")

    applied = []
    for addr, new_bytes, orig_expected, desc in PATCHES:
        actual = bytes(bin_data[addr:addr+len(new_bytes)])
        if actual == new_bytes:
            print(f"  [SKIP] 0x{addr:05X}: already patched ({desc})")
            applied.append((addr, new_bytes, desc))
            continue
        if actual != orig_expected:
            print(f"  [WARN] 0x{addr:05X}: expected {orig_expected.hex()}, got {actual.hex()} ({desc})")
        for i, b in enumerate(new_bytes):
            bin_data[addr + i] = b
        print(f"  [OK] 0x{addr:05X}: {actual.hex()} -> {new_bytes.hex()} ({desc})")
        applied.append((addr, new_bytes, desc))

    if not applied:
        print("No patches applied!")
        sys.exit(1)

    with open(PATCHED_BIN, 'wb') as f:
        f.write(bin_data)
    print(f"\nWrote {len(bin_data)} bytes to {PATCHED_BIN}")

    hex_lines = bin_to_hex(bin_data)
    with open(PATCHED_HEX, 'w') as f:
        for line in hex_lines:
            f.write(line + '\n')
    print(f"Wrote {len(hex_lines)} lines to {PATCHED_HEX}")

    print("\n=== Verification ===")
    for addr, new_bytes, desc in applied:
        actual = bytes(bin_data[addr:addr+len(new_bytes)])
        ok = "OK" if actual == new_bytes else "FAIL"
        print(f"  0x{addr:05X}: {actual.hex()} {ok} ({desc})")

if __name__ == '__main__':
    main()
