#!/usr/bin/env python3 """Neutralize the shipped-in debug crash macros in BTL4OPT.EXE. The Rel 4.10 BattleTech binary is a DEBUG build (DEBUG_LEVEL 1; the binary even contains "// this file has not been approved for release"). Two families of deliberate "crash to the debugger" macros are compiled in, both faulting by writing to 0xFFFFFFFF (Exception 0E). On the real pod a clean cockpit harness rarely tripped them; through emulation + serial passthrough they fire on benign hiccups and halt the whole game. 1. DISABLE_AND_DIE -- the RIO serial driver's `DIE_ON_ERROR equ 1` macro (CODE/RP/MUNGA_L4/PCSPAK.ASM): push eax; push edx mov edx, 0FFFFFFFFh ; crash loudly mov eax, mov [edx], eax e.g. error 3 = a RIO packet byte > 0x7F (PCSPAK.ASM:1630), tripped by a serial glitch during a board reset. Release build (DIE_ON_ERROR equ 0) compiles it out and recovers (`and al,07Fh`). 2. Verify_Failed / Fail_To_Debugger -- the general C++ assertion routines (CODE/RP/MUNGA/VERIFY.HPP; Verify(c) -> Verify_Failed on !c). After printing message/file/line they crash: or edx, 0FFFFFFFFh xor ecx, ecx mov [edx], ecx The routine's own re-entry guard already jumps *past* this to the clean pop/ret, so NOPing the crash makes every failed assertion "print and continue" -- the DEBUGOFF behavior. (Failing gauge-init Verifys were halting the game before the secondary displays could come up.) This tool finds every site of both idioms by exact signature and NOPs them, writing a .orig backup first. Idempotent. Usage: patch_btl4opt.py """ import os import re import sys DIE = re.compile( rb'\x50\x52\xBA\xFF\xFF\xFF\xFF\xB8(.)\x00\x00\x00\x89\x02', re.DOTALL) VERIFY = re.compile(rb'\x83\xCA\xFF\x33\xC9\x89\x0A') # or edx,-1;xor ecx;mov[edx],ecx def main(): if len(sys.argv) != 2: raise SystemExit(__doc__) path = sys.argv[1] data = bytearray(open(path, 'rb').read()) die = list(DIE.finditer(bytes(data))) ver = list(VERIFY.finditer(bytes(data))) if not die and not ver: print("no crash sites found (already patched?)") return bak = path + '.orig' if not os.path.exists(bak): open(bak, 'wb').write(data) print(f"backup written: {bak}") for m in die: va = 0x401000 + m.start() - 0xe00 # CODE VA 0x401000 @ file 0xe00 print(f" NOP VA 0x{va:06x} DISABLE_AND_DIE (code {m.group(1)[0]})") data[m.start():m.end()] = b'\x90' * (m.end() - m.start()) for m in ver: va = 0x401000 + m.start() - 0xe00 print(f" NOP VA 0x{va:06x} Verify_Failed crash tail") data[m.start():m.end()] = b'\x90' * (m.end() - m.start()) open(path, 'wb').write(data) print(f"patched {len(die)} DISABLE_AND_DIE + {len(ver)} Verify sites") if __name__ == "__main__": main()