The recovered system: fire channels = LBE4ControlsManager buttonGroups (0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port already called -- it needed only the TriggerState attribute (id 0x13 PINNED to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an input feed. Keyboard/harness now push press/release edges into the button groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger = 4 weapons). Myomers @4b9550/@4b95b8 misattribution corrected (they are MechWeapon ConfigureMappables/ChooseButton). Verified 2-node: kill through the authentic chain (12 hits vs ~36 pre-groups). Config-mode session (regrouping UI) = the remaining stage, KB-scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
92 lines
3.6 KiB
Python
92 lines
3.6 KiB
Python
#!/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}")
|