Files
BT411/tools/disas2.py
T
arcattackandClaude Opus 4.8 c2c4ab1f67 docs: record the gauge attribute-wave (mechanism + 4 increments) + durable RE tools
Document the cockpit-gauge attribute-pointer system in GAUGE_COMPOSITE.md
(§Attribute wave) and CLAUDE.md §10: how a Subsystem/Attribute or bare-Attribute
binding resolves (ParseAttribute -> FindSubsystem/GetName -> GetAttributePointer
-> activeAttributeIndex), what a class must publish (AttributeID enum +
AttributePointers[] + GetAttributeIndex chained + DefaultData wiring), the
DENSE-TABLE HAZARD (Build leaves gap slots uninitialized, Find strcmps every
slot -> tables must be a dense prefix), and the base-primitive vs BT-specific
widget split.  Records the 4 committed increments (HeatSink table, Mech table,
vertBar, segmentArcRatio) and the follow-ups (oneOfSeveralPixInt/map/PlayerStatus/
vehicleSubSystems, the #if0'd HeatSink-bank AmbientTemperature, the Reservoir
coolantCapacity shadow).

Promote the two reverse-engineering helpers from the session scratchpad into
tools/ (durable): disas2.py (capstone + PE parse -- recovers x87 float math
Ghidra drops; FUN_004dcd94=round, FUN_004dcd00=fabs) and vtdump.py (dump a class
vtable to find an override the assert-anchored decomp never exported, e.g.
SegmentArcRatio::Execute).  Doc references updated scratchpad/ -> tools/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:52:53 -05:00

60 lines
2.1 KiB
Python

import struct, sys
from capstone import *
PATH = r"C:\git\bt411\content\BTL4OPT.EXE"
data = open(PATH, "rb").read()
e_lfanew = struct.unpack_from("<I", data, 0x3C)[0]
coff = e_lfanew + 4
num_sec = struct.unpack_from("<H", data, coff+2)[0]
opt_size = struct.unpack_from("<H", data, coff+16)[0]
opt = coff + 20
image_base = struct.unpack_from("<I", data, opt+28)[0]
sec_tbl = opt + opt_size
secs = []
for i in range(num_sec):
off = sec_tbl + i*40
vsize, va, rawsize, rawptr = struct.unpack_from("<IIII", data, off+8)
secs.append((va, vsize, rawptr, rawsize))
def va_to_off(va):
rva = va - image_base
for sva, vsize, rawptr, rawsize in secs:
if sva <= rva < sva + max(vsize, rawsize):
return rawptr + (rva - sva)
return None
def read_va(va, n):
o = va_to_off(va)
return data[o:o+n] if o is not None else None
def fconst(va):
b = read_va(va, 4)
return struct.unpack("<f", b)[0] if b else None
FN = int(sys.argv[1], 16)
LEN = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0x140
code = read_va(FN, LEN)
md = Cs(CS_ARCH_X86, CS_MODE_32); md.detail = True
known = {"0x408328":"SinCos","0x4dcd94":"Round(ftol ST0)","0x40954c":"Quat->Euler",
"0x4700bc":"NumericDisplay::ctor","0x4700ac":"nameCopy","0x442aec":"bitMapBin.Get",
"0x442c12":"bitMapBin.Release","0x444818":"GraphicGauge::ctor","0x474855":"GaugeConnDirectOf::ctor",
"0x4c2ff8":"tiledBlit","0x402298":"operator new","0x4dcdec":"Round64"}
print(f"=== disasm @0x{FN:x} len=0x{LEN:x} ===")
for ins in md.disasm(code, FN):
line = f"0x{ins.address:x}: {ins.mnemonic:<7} {ins.op_str}"
for tok in ins.op_str.replace("[", " ").replace("]", " ").split():
if tok.startswith("0x"):
try:
a = int(tok, 16)
if 0x4b0000 <= a <= 0x520000:
fv = fconst(a)
if fv is not None:
line += f" ; [0x{a:x}]={fv:g}f"
except ValueError:
pass
if ins.mnemonic == "call" and ins.op_str in known:
line += f" ; {known[ins.op_str]}"
print(line)
if ins.mnemonic == "ret":
break