Files
TeslaRel410/emulator/patch_btl4opt.py
T
CydandClaude Opus 4.8 08f71eb471 Phase 3f: lighting from wire normals; defuse shipped-in debug crash
Lighting: stride-8/9 vertices carry a normal (floats 3-5, uv at 6-7). The
backend transforms normals by the instance rotation and lights with a single
directional sun (GL_LIGHT0, smooth, two-sided, GL_COLOR_MATERIAL). Terrain
and hulls shade instead of reading flat.

LOD: the lod flush has no switch distances -- the host keeps the active LOD
at the child-list head and re-orders over the wire, so children[0] is right
and LOD changes come through as the sim runs.

The crash that halted the game once the RIO drove the sim was not ours: fault
at 0047E1D1 writing 0xFFFFFFFF is the RIO driver's DISABLE_AND_DIE debug
macro (PCSPAK.ASM, DIE_ON_ERROR=1), which deliberately faults on a serial-tx
anomaly -- error 3 = body char >0x7F during a RIO packet, i.e. a glitch on a
board reset. Release build (DIE_ON_ERROR=0) compiles it out and recovers
(and al,07Fh). patch_btl4opt.py NOPs all 12 sites by signature (with .orig
backup) = release behavior; game then survives RIO resets (1200+ frames).
(ALPHA_1 is git-ignored, so the tool ships, not the patched binary.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:13:46 -05:00

58 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""Neutralize the shipped-in debug crash macros in BTL4OPT.EXE.
The Rel 4.10 BattleTech binary was built with the RIO serial driver's
`DIE_ON_ERROR equ 1` diagnostic enabled (CODE/RP/MUNGA_L4/PCSPAK.ASM). Its
`DISABLE_AND_DIE` macro deliberately faults ("crash loudly to the debugger")
on any serial-transmit anomaly:
push eax; push edx
mov edx, 0FFFFFFFFh ; crash loudly
mov eax, <error-code>
mov [edx], eax ; write to 0xFFFFFFFF -> Exception 0E
On the real pod a clean cockpit harness never tripped these; through the
DOSBox-X serial passthrough a RIO glitch (e.g. during a board reset) hits
error 3 (PCSPAK.ASM:1630, "body char > 0x7F") and halts the whole game. The
release build (`DIE_ON_ERROR equ 0`) compiles the macro to nothing and lets
the normal recovery path (`and al,07Fh`) continue.
This tool finds every DISABLE_AND_DIE site by its exact 14-byte signature and
replaces it with NOPs -- exactly the release-build behavior -- writing a
<name>.orig backup first. Idempotent.
Usage: patch_btl4opt.py <path-to-BTL4OPT.EXE>
"""
import re
import sys
SIG = re.compile(
rb'\x50\x52\xBA\xFF\xFF\xFF\xFF\xB8(.)\x00\x00\x00\x89\x02', re.DOTALL)
def main():
if len(sys.argv) != 2:
raise SystemExit(__doc__)
path = sys.argv[1]
data = bytearray(open(path, 'rb').read())
sites = list(SIG.finditer(bytes(data)))
if not sites:
print("no DISABLE_AND_DIE sites found (already patched?)")
return
import os
bak = path + '.orig'
if not os.path.exists(bak):
open(bak, 'wb').write(data)
print(f"backup written: {bak}")
for m in sites:
code = m.group(1)[0]
va = 0x401000 + m.start() - 0xe00 # CODE VA 0x401000 @ file 0xe00
print(f" NOP VA 0x{va:06x} (error code {code})")
data[m.start():m.end()] = b'\x90' * (m.end() - m.start())
open(path, 'wb').write(data)
print(f"patched {len(sites)} sites")
if __name__ == "__main__":
main()