#!/usr/bin/env python3
"""
GBA Function Matcher - Matches C code against original ROM bytes.
Uses agbcc (GCC 2.95) to compile C and compares byte-by-byte.

Key insight: agbcc's code generation depends heavily on C types.
- Struct member access (p->field) generates different code than array access (p[i])
- Pointer type matters (int* vs struct S* vs volatile int*)
- Function must have observable side effects to prevent dead code elimination
"""
import os
import re
import struct
import subprocess
import sys
import tempfile

ROM_PATH = "bit Generations - Orbital (Japan) (En).gba"
ROM_BASE = 0x08000000
AGBCC = "agbcc/agbcc"
GCC = "arm-gnu-toolchain-13.3.rel1-darwin-arm64-arm-none-eabi/bin/arm-none-eabi-gcc"
OBJCOPY = "arm-gnu-toolchain-13.3.rel1-darwin-arm64-arm-none-eabi/bin/arm-none-eabi-objcopy"

def read_rom():
    with open(ROM_PATH, "rb") as f:
        return f.read()

def try_compile_and_match(c_code, addr, size):
    """Try to compile C code and match against ROM bytes"""
    rom = read_rom()
    offset = addr - ROM_BASE
    orig = rom[offset:offset+size]
    
    with tempfile.NamedTemporaryFile(suffix=".c", mode="w", delete=False) as f:
        f.write(c_code)
        c_path = f.name
    s_path = c_path.replace(".c", ".s")
    o_path = c_path.replace(".c", ".o")
    bin_path = c_path.replace(".c", ".bin")
    
    try:
        # Compile with agbcc
        result = subprocess.run([AGBCC, "-O2", c_path], capture_output=True, text=True, timeout=30)
        if not os.path.exists(s_path):
            return None, None, "COMPILE_FAIL"
        
        # Assemble with gcc (supports Thumb16 properly)
        result = subprocess.run([GCC, "-mthumb", "-mcpu=arm7tdmi", "-c", s_path, "-o", o_path],
                              capture_output=True, text=True, timeout=30)
        if result.returncode != 0 or not os.path.exists(o_path):
            return None, None, "ASM_FAIL"
        
        # Extract binary
        result = subprocess.run([OBJCOPY, "-O", "binary", "-j", ".text", o_path, bin_path],
                              capture_output=True, text=True, timeout=30)
        if not os.path.exists(bin_path):
            return None, None, "OBJCOPY_FAIL"
        
        with open(bin_path, "rb") as f:
            binary = f.read()
        
        # Trim to exact size
        if len(binary) > size:
            binary = binary[:size]
        
        matching = sum(1 for a, b in zip(binary, orig) if a == b)
        rate = matching / size if size > 0 else 0
        
        return binary, orig, rate
    except Exception as e:
        return None, None, f"ERROR: {e}"
    finally:
        for p in [c_path, s_path, o_path, bin_path]:
            try:
                if os.path.exists(p):
                    os.unlink(p)
            except:
                pass

def get_function_asm(addr, size):
    """Get the agbcc assembly for a function"""
    rom = read_rom()
    offset = addr - ROM_BASE
    orig = rom[offset:offset+size]
    
    # Simple Thumb16 disassembly
    lines = []
    i = 0
    while i < size - 1:
        hw = struct.unpack_from('<H', orig, i)[0]
        addr_here = addr + i
        
        # Common instructions
        if hw == 0x4770:
            lines.append(f"    bx lr")
        elif hw == 0x46C0:
            lines.append(f"    nop")
        elif (hw & 0xFF00) == 0xB500:
            regs = []
            if hw & 0x80: regs.append("lr")
            if hw & 0x40: regs.append("r7")
            if hw & 0x20: regs.append("r6")
            if hw & 0x10: regs.append("r5")
            if hw & 0x08: regs.append("r4")
            lines.append(f"    push {{{', '.join(regs)}}}")
        elif (hw & 0xFF00) == 0xBD00:
            regs = []
            if hw & 0x80: regs.append("pc")
            if hw & 0x40: regs.append("r7")
            if hw & 0x20: regs.append("r6")
            if hw & 0x10: regs.append("r5")
            if hw & 0x08: regs.append("r4")
            lines.append(f"    pop {{{', '.join(regs)}}}")
        elif (hw & 0xF800) == 0x2000:
            rd = (hw >> 8) & 7
            imm = hw & 0xFF
            lines.append(f"    movs r{rd}, #{imm}")
        elif (hw & 0xF800) == 0x3000:
            rd = (hw >> 8) & 7
            imm = hw & 0xFF
            lines.append(f"    adds r{rd}, #{imm}")
        elif (hw & 0xF800) == 0x3800:
            rd = (hw >> 8) & 7
            imm = hw & 0xFF
            lines.append(f"    subs r{rd}, #{imm}")
        elif (hw & 0xF800) == 0x2800:
            rd = (hw >> 8) & 7
            imm = hw & 0xFF
            lines.append(f"    cmp r{rd}, #{imm}")
        elif (hw & 0xF800) == 0x6000:
            rd = hw & 7
            rn = (hw >> 3) & 7
            imm = ((hw >> 6) & 0x1F) * 4
            lines.append(f"    str r{rd}, [r{rn}, #{imm}]")
        elif (hw & 0xF800) == 0x6800:
            rd = hw & 7
            rn = (hw >> 3) & 7
            imm = ((hw >> 6) & 0x1F) * 4
            lines.append(f"    ldr r{rd}, [r{rn}, #{imm}]")
        elif (hw & 0xFFC0) == 0x4400:
            rd = ((hw >> 4) & 8) | (hw & 7)
            rs = (hw >> 3) & 0xF
            lines.append(f"    mov r{rd}, r{rs}")
        elif (hw & 0xFFC0) == 0x1800:
            rd = hw & 7
            rn = (hw >> 3) & 7
            rs = (hw >> 6) & 7
            lines.append(f"    adds r{rd}, r{rn}, r{rs}")
        elif (hw & 0xFFC0) == 0x1A00:
            rd = hw & 7
            rn = (hw >> 3) & 7
            rs = (hw >> 6) & 7
            lines.append(f"    subs r{rd}, r{rn}, r{rs}")
        elif (hw & 0xFFC0) == 0x4280:
            rd = hw & 7
            rs = (hw >> 3) & 7
            lines.append(f"    cmp r{rd}, r{rs}")
        elif (hw & 0xFFC0) == 0x4300:
            rd = hw & 7
            rs = (hw >> 3) & 7
            lines.append(f"    orrs r{rd}, r{rs}")
        elif (hw & 0xFFC0) == 0x4040:
            rd = hw & 7
            rs = (hw >> 3) & 7
            lines.append(f"    eors r{rd}, r{rs}")
        elif (hw & 0xFFC0) == 0x4080:
            rd = hw & 7
            rs = (hw >> 3) & 7
            lines.append(f"    lsls r{rd}, r{rs}")
        elif (hw & 0xFFC0) == 0x40C0:
            rd = hw & 7
            rs = (hw >> 3) & 7
            lines.append(f"    lsrs r{rd}, r{rs}")
        elif (hw & 0xFF00) == 0xD000:
            cond = (hw >> 8) & 0xF
            offset_val = hw & 0xFF
            if offset_val & 0x80: offset_val -= 256
            target = addr_here + 4 + offset_val * 2
            conds = ["eq","ne","cs","cc","mi","pl","vs","vc","hi","ls","ge","lt","gt","le","al"]
            c = conds[cond] if cond < len(conds) else f"x{cond}"
            lines.append(f"    b{c} 0x{target:08X}")
        elif (hw & 0xF800) == 0xE000:
            offset_val = hw & 0x7FF
            if offset_val & 0x400: offset_val -= 2048
            target = addr_here + 4 + offset_val * 2
            lines.append(f"    b 0x{target:08X}")
        else:
            lines.append(f"    .hword 0x{hw:04X}")
        
        i += 2
    
    if size % 2 == 1:
        lines.append(f"    .byte 0x{orig[size-1]:02X}")
    
    return '\n'.join(lines)
