Three root causes, all fixed: 1. kDamageScale was 1.0 -- _DAT_004bafbc is an x87 float80 = 1e-7, cancelling the ctor's x1e7: damagePortion = authored DamageAmount x (charge/seekV)^2 (closed form at fire time; madcat AC=25/LRM=50/ERLL=6, bhk1 PPC=12/SRM=35). The observed "0.25" was the degenerate EC=1 fallback, never authored data. 2. CheckFireEdge NaN latch: TriggerState carries ControlsButton INTS; the release value (-65) is a negative NaN. The binary's x87 unordered compare read it as "released"; IEEE-correct float compares latched the edge detector shut after the first release -- the reason the emitter discharge chain NEVER fired in-game. Fixed with bit-pattern sign compares. 3. The weapon-side submission LIVE: Emitter::FireWeapon fills damageData (amount + damageForce=target-muzzle [the gyro directional-bounce feed] + impact) -> MechWeapon::SendDamageMessage (@004b9728 real body) -> messmgr consolidation with per-weapon records + explosion bundling. The mech4 bring-up damage block + flat kShotDamage retired to diag hooks. Bonus: LODReuseHysteresis 0.82 -> 0.33 (double misread). Zone model verified byte-exact (no change). Solo end-to-end: 5-record volleys (2 PPC + 3 ERML with authored amounts), per-weapon zone granularity, explosions, kill. Heat stays bring-up scale pending the heat-calibration audit [T3]. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
import capstone, struct, sys
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
data = open(r"C:/git/bt411/content/BTL4OPT.EXE","rb").read()
|
|
CODE_VA=0x401000; CODE_RA=0xE00
|
|
# scan whole CODE section but report region; disp32 0x434 pattern
|
|
pat = b"\x34\x04\x00\x00"
|
|
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32); md.detail=False
|
|
# find CODE section size
|
|
pe = struct.unpack_from("<I", data, 0x3c)[0]
|
|
nsec = struct.unpack_from("<H", data, pe+6)[0]
|
|
opt=pe+24; szopt=struct.unpack_from("<H",data,pe+20)[0]; sec0=opt+szopt
|
|
for i in range(nsec):
|
|
off=sec0+i*40
|
|
name=data[off:off+8].rstrip(b"\0").decode()
|
|
vsz,va,rsz,ra=struct.unpack_from("<IIII",data,off+8)
|
|
if name=="CODE": code_rsz=rsz
|
|
start=0
|
|
hits=[]
|
|
while True:
|
|
idx = data.find(pat, start)
|
|
if idx<0: break
|
|
start=idx+1
|
|
if not (CODE_RA <= idx < CODE_RA+code_rsz): continue
|
|
va = CODE_VA + (idx - CODE_RA)
|
|
# try disassembling from a few candidate starts before the disp
|
|
for back in range(2,9):
|
|
chunk = data[idx-back: idx-back+16]
|
|
insns = list(md.disasm(chunk, va-back))
|
|
if insns:
|
|
i0 = insns[0]
|
|
if "0x434" in i0.op_str and i0.address+i0.size >= va+4 and i0.address+i0.size <= va+4+4:
|
|
hits.append((i0.address, f"{i0.mnemonic} {i0.op_str}", i0.size))
|
|
break
|
|
seen=set()
|
|
for a,s,sz in sorted(hits):
|
|
if a in seen: continue
|
|
seen.add(a)
|
|
region = "MECH" if 0x49e000<=a<0x4c0000 else ""
|
|
print(f"{a:08x} {region:5s} {s}")
|