From b2e4c5eaa695d508bebce3af3993bb050be3e29d Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 3 Jul 2026 17:48:07 -0500 Subject: [PATCH] Gauges: fix out-of-memory crash (HEAPSIZE), draw instrument panels Enabling gauges crashed (null-this, illegal 0x478). Root cause was NOT video: the MUNGA main heap (fixed 6MB default) exhausted loading gauge images. The heap size comes from getenv("HEAPSIZE") (BTL4OPT.EXE @0x401076; default 0x600000); the pod's PARAMETR.BAT sets it (:POD=15MB, :REVIEW/:LOOP=32MB) but our minimal setenv launch never did. memsize is irrelevant (it's the game's own heap, not DOS memory -- verified memsize=127 still gave 6MB). Pods have 32MB RAM so 15MB fits. Fix (no binary patch): set HEAPSIZE=15000000 + L4GAUGE=640x480x16 in gauge.conf. Game now runs sustained with gauges on (500+ frames); the VESA 640x480x16 mode switches fine on our emulated S3 and the cockpit instrument panels draw (DISPLAY/PROGRAM/ENG DATA/TRIGGER CONFIG...). patch_btl4opt.py also gained Verify_Failed (DEBUG-build assertion) neutral- ization -- release-equivalent robustness, separate from the heap fix. Known-open (GAUGE-NOTES.md): the framebuffer renders only a top strip, garbled -- the game bank-switches via a hardcoded far pointer in L4GAUGE.INI (C000:2616, the STB Horizon+ card's VESA WinFuncPtr) that isn't valid on our emulated S3, so only the first bank(s) page in. Fixing bank-switching (or an LFB mode) is next; then the full buffer can be split into the six displays. Co-Authored-By: Claude Opus 4.8 --- emulator/.gitignore | 1 + emulator/GAUGE-NOTES.md | 71 +++++++++++++++++++++++++++++++++++++ emulator/gauge-display.png | Bin 0 -> 16531 bytes emulator/gauge.conf | 5 +++ emulator/gaugelog.conf | 25 +++++++++++++ emulator/gaugemem.conf | 25 +++++++++++++ emulator/patch_btl4opt.py | 66 +++++++++++++++++++++------------- 7 files changed, 168 insertions(+), 25 deletions(-) create mode 100644 emulator/GAUGE-NOTES.md create mode 100644 emulator/gauge-display.png create mode 100644 emulator/gaugelog.conf create mode 100644 emulator/gaugemem.conf diff --git a/emulator/.gitignore b/emulator/.gitignore index f4f642d..b5c6593 100644 --- a/emulator/.gitignore +++ b/emulator/.gitignore @@ -11,6 +11,7 @@ image/ !game-live-gl.png !game-live-textured.png !game-live-lit.png +!gauge-display.png dbx_out.txt vpx*.txt sweep_*.txt diff --git a/emulator/GAUGE-NOTES.md b/emulator/GAUGE-NOTES.md new file mode 100644 index 0000000..97978b6 --- /dev/null +++ b/emulator/GAUGE-NOTES.md @@ -0,0 +1,71 @@ +# Gauge / secondary-display path (cockpit instruments) + +The cockpit's six secondary displays (5 mono + 1 color) show instruments, +driven by the PC's own SVGA video (an STB Horizon+ / S3-class card on the +pod) in **VESA mode 0x111 = 640×480×16**, which the VDB splits across the +monitors (see RIO-NOTES.md / HISTORY.md). Enabled by `L4GAUGE=640x480x16` +(pod `PARAMETR.BAT :POD`) or `setenv.bat … g`. Config layout, instrument +images, and gauge scripts live in `ALPHA_1/REL410/BT/GAUGE/` +(`L4GAUGE.INI`, `L4GAUGE.CFG`, hundreds of `.GIM`/`.PCC`/`.PCX`). + +## Fixed: the gauge crash was out-of-memory, not video + +Enabling gauges crashed the game (`Exception 0E … illegal address 0x478`, +a null-`this`). Root cause, from the game's own debug log: + +``` +Requested 2808 bytes from MUNGA Heap +Largest block remaining is 1100 bytes +Heap size was set to 6291432 bytes +d:\tesla_bt\munga\HEAP.CPP(342): No remaining block large enough! +Failing to debugger +``` + +The MUNGA main heap (`UserHeap MainStorage`, fixed size, no growth) exhausted +loading the gauge images. It reads its size from the **`HEAPSIZE` environment +variable** (BTL4OPT.EXE @VA 0x401076: `getenv("HEAPSIZE")`; else default +`0x600000` = 6 MB). The pod's `PARAMETR.BAT` sets it (`:POD` → 15000000, +`:REVIEW`/`:LOOP` → 32000000); our minimal `setenv.bat` launch never did, so +the game used the 6 MB default and starved. **`memsize` does not affect it — +it is the game's own heap, not DOS memory** (verified: `memsize=127` still +gave 6 MB). The pod machines have **32 MB RAM**, so a 15 MB heap fits. + +Fix (no binary patch): `set HEAPSIZE=15000000` before launch (see +`gauge.conf`). The game then runs sustained with gauges on (500+ frames), the +VESA mode switches (DOSBox screen blanks into graphics), and it draws the +instrument panels — the VESA 640×480×16 mode works fine on our emulated S3. + +Separately, `patch_btl4opt.py` now also neutralizes the general `Verify_Failed` +debug-assertion crash (this is a `DEBUG_LEVEL 1` build); not required for the +heap fix but makes the debug build behave like release under emulation. + +## Open: bank-switching garbles the framebuffer + +The gauge framebuffer currently renders **only a top strip, garbled** — the +rest black. 640×480×16 is 600 KB accessed through a 64 KB window; the game +pages it by **calling a hardcoded far pointer** from `L4GAUGE.INI`: + +``` +[640x480x16] +mode=273 ;0111h +pageFcnPtr=796182 ;C000:2616 <- STB Horizon+ VESA bank-switch routine +``` + +`C000:2616` is the STB card's VESA `WinFuncPtr`. On our emulated S3 that +address is not the bank-switch function, so paging fails and only the first +bank(s) (~top 50–100 lines) render. Until this is fixed there is no complete +framebuffer to divide into the six displays. + +Fix options (next): +1. **Point `pageFcnPtr` at our card's real `WinFuncPtr`** — query VESA + GetModeInfo (int 10h/4F01) for mode 0x111 under our S3 and put that far + pointer in `L4GAUGE.INI` (`special`/`pageFcnPtr` fields), or force the + game to use the queried pointer instead of the baked-in one. +2. **Use a linear-framebuffer (LFB) mode** (VESA 2.0, mode 0x111|0x4000) so + no bank switching is needed — requires the SVGA16 paging path to target + the LFB. +3. **Route bank switches through int 10h/4F05** (portable VESA windowing) + instead of the direct pointer — a small game patch. + +Once the full 640×480 buffer renders, splitting it into the six VDB display +regions is straightforward (the VDB device already models the splitter). diff --git a/emulator/gauge-display.png b/emulator/gauge-display.png new file mode 100644 index 0000000000000000000000000000000000000000..48b76c840042e3e82d863c51bc81eb8b670739be GIT binary patch literal 16531 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 Fe*n66$*TYW literal 0 HcmV?d00001 diff --git a/emulator/gauge.conf b/emulator/gauge.conf index bd9559d..b4ce4ed 100644 --- a/emulator/gauge.conf +++ b/emulator/gauge.conf @@ -17,6 +17,11 @@ cd \REL410\BT set VIDEOFORMAT=svga set BLASTER=A220 I5 D1 H5 P330 T6 set TEMP=c:\ +rem The pod's PARAMETR.BAT :POD path sets these; the game's HEAPSIZE default +rem is only 6MB (too small once gauges load -> MUNGA heap exhaustion). Match +rem the pod: 15MB heap + the 640x480x16 gauge mode. +set HEAPSIZE=15000000 +set L4GAUGE=640x480x16 rem arg4=g enables the gauge/secondary displays -> exercises the VDB splitter call setenv.bat r s n g 32rtm.exe -x diff --git a/emulator/gaugelog.conf b/emulator/gaugelog.conf new file mode 100644 index 0000000..518f67e --- /dev/null +++ b/emulator/gaugelog.conf @@ -0,0 +1,25 @@ +[sdl] +output=opengl +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=dynamic +cputype=pentium +cycles=max +[serial] +serial1=disabled +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +c: +cd \REL410\BT +set VIDEOFORMAT=svga +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +call setenv.bat r s n g +32rtm.exe -x +btl4opt.exe -egg test.egg > c:\gaugelog.txt +32rtm.exe -u +echo ALPHA1-RUN-DONE +pause diff --git a/emulator/gaugemem.conf b/emulator/gaugemem.conf new file mode 100644 index 0000000..ff3b00f --- /dev/null +++ b/emulator/gaugemem.conf @@ -0,0 +1,25 @@ +[sdl] +output=opengl +[dosbox] +memsize=127 +machine=svga_s3 +[cpu] +core=dynamic +cputype=pentium +cycles=max +[serial] +serial1=disabled +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +c: +cd \REL410\BT +set VIDEOFORMAT=svga +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +call setenv.bat r s n g +32rtm.exe -x +btl4opt.exe -egg test.egg > c:\gaugelog.txt +32rtm.exe -u +echo ALPHA1-RUN-DONE +pause diff --git a/emulator/patch_btl4opt.py b/emulator/patch_btl4opt.py index 9bf6538..4a57001 100644 --- a/emulator/patch_btl4opt.py +++ b/emulator/patch_btl4opt.py @@ -1,33 +1,46 @@ #!/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: +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. - push eax; push edx - mov edx, 0FFFFFFFFh ; crash loudly - mov eax, - mov [edx], eax ; write to 0xFFFFFFFF -> Exception 0E +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`). -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. +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 DISABLE_AND_DIE site by its exact 14-byte signature and -replaces it with NOPs -- exactly the release-build behavior -- writing a -.orig backup first. Idempotent. +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 -SIG = re.compile( +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(): @@ -35,22 +48,25 @@ def main(): 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?)") + 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 - 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] + for m in die: va = 0x401000 + m.start() - 0xe00 # CODE VA 0x401000 @ file 0xe00 - print(f" NOP VA 0x{va:06x} (error code {code})") + 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(sites)} sites") + print(f"patched {len(die)} DISABLE_AND_DIE + {len(ver)} Verify sites") if __name__ == "__main__":