#!/usr/bin/env python3
"""Extract 0x4b2980-0x4b2d8b from section_dump.txt and disassemble (x86-32).
Dump line format:  ' 4b2980 558bec83 c4d85356 578b5d08 d94510d8  U.....SVW.]..E..'
address (hex, no 0x) then up to 4 groups of 4 bytes (8 hex chars each)."""
import re, sys
from capstone import Cs, CS_ARCH_X86, CS_MODE_32

DUMP = r"C:\git\bt411\reference\decomp\section_dump.txt"
START = 0x4b2980
END   = 0x4b2d8c   # exclusive

# named symbols for annotation
SYMS = {
    0x4b2d8c: "Gyroscope::ApplyDamageImpulse",
    0x4b2de4: "Gyroscope::ApplyDamageTorque",
    0x4b2e50: "Gyroscope::ApplyVerticalImpulse",
    0x408440: "vec3_copy_A", 0x408e90: "vec3_copy_B",
    0x4085ec: "vec3_add", 0x408644: "vec3_sub",
    0x4086ac: "vec3_scale", 0x4092fc: "vec3_scale_B",
    0x4086d0: "vec3_mul_cw", 0x408744: "vec3_x_mat34_rows",
    0x40aadc: "mat34_identity", 0x40ab44: "mat34_from_placement",
    0x409a00: "quat_from_rot", 0x4091f4: "eps_cmp_A", 0x4084fc: "eps_cmp_B",
    0x409390: "lerp", 0x40b104: "mat34_affine_mult",
}

GYRO = {
    0x1D8:"exageration",0x1DC:"eyePosition",0x1E8:"springConstant",0x1F4:"dampingConstant",
    0x200:"eyeClampUpper",0x20C:"eyeClampLower",0x218:"posSpring",0x224:"negSpring",
    0x230:"eyeForce",0x23C:"eyeVelocity",0x248:"eyeWork",0x254:"spare0",
    0x258:"externalPitchPtr",0x25C:"workMatrix",0x28C:"placePos",0x298:"placeQuat",
    0x2A8:"placeRot",0x2B4:"bodyOrientation",0x2C0:"rotationSpringConstant",
    0x2CC:"rotationDampingConstant",0x2D8:"bodyClampUpper",0x2E4:"bodyClampLower",
    0x2F0:"rotationPosSpring",0x2FC:"rotationNegSpring",0x308:"bodyForce",
    0x314:"bodyVelocity",0x320:"bodyWork",0x32C:"damageMultiplier",0x340:"damageResponse",
    0x3A8:"swayBias",0x3BC:"swayAngle",0x3C8:"eyeJointNode",0x3CC:"mechJointNode",
}

def gyro_annot(off):
    best = None
    for k,v in GYRO.items():
        if k <= off:
            if best is None or k > best[0]:
                best = (k,v)
    if best and off - best[0] < 0x50:
        d = off - best[0]
        return f"{best[1]}" + (f"+0x{d:x}" if d else "")
    return None

mem = {}
pat = re.compile(r'^\s*([0-9a-f]{6,8})\s((?:[0-9a-f]{2,8}\s?)+)')
with open(DUMP) as f:
    for line in f:
        m = pat.match(line)
        if not m: continue
        addr = int(m.group(1), 16)
        if addr + 16 < START or addr > END: continue
        hexpart = m.group(2).split()
        b = bytes.fromhex(''.join(hexpart))
        for i, byte in enumerate(b):
            mem[addr+i] = byte

code = bytes(mem.get(a, 0x90) for a in range(START, END))
missing = [a for a in range(START, END) if a not in mem]
if missing:
    print(f"WARNING: {len(missing)} missing bytes, first at {missing[0]:#x}", file=sys.stderr)

md = Cs(CS_ARCH_X86, CS_MODE_32)
md.detail = True
# jump table at 0x4b2b10 (5 dwords) -- disassemble around it
segs = [(START, 0x4b2b10), (0x4b2b24, END)]
insns = []
for s,e in segs:
    insns.extend(md.disasm(bytes(mem[a] for a in range(s,e)), s))
    if e == 0x4b2b10:
        import struct
        for i in range(5):
            d = struct.unpack('<I', bytes(mem[0x4b2b10+4*i+j] for j in range(4)))[0]
            print(f"; jumptable[{i}] = {d:#010x}")
for ins in insns:
    annot = ""
    # annotate call targets
    if ins.mnemonic == 'call' and ins.op_str.startswith('0x'):
        tgt = int(ins.op_str, 16)
        if tgt in SYMS: annot = f" ; {SYMS[tgt]}"
        else: annot = " ; FUN_%08x" % tgt
    # annotate gyro member offsets in memory operands
    for m2 in re.finditer(r'\+ 0x([0-9a-f]+)\]', ins.op_str):
        off = int(m2.group(1), 16)
        g = gyro_annot(off)
        if g: annot += f" ; [{g}]"
    print(f"{ins.address:08x}: {ins.mnemonic:8s} {ins.op_str}{annot}")
