Disassembled BTL4OPT.EXE (PE-in-DOS32RTM) to crack the last unknowns: - velocirender_sync (@0x48D220): sends action 0x2D with a data token and checks cmp token, reply.node[0] (the 'unexpected action' text only prints the action; the real check is the echoed token). Device now echoes the token in node[0]. Sync passes. - Speculative-poll gating: only feed a reply after consecutive empty polls (a blocking inRecord spin), not single handle_iserver_stuff() checks. - Frame-ack: velocirender_frameack expects action 9 (vr_draw_scene); device tracks outstanding draw_scene and replies 9. Frame-ack passes. The game now passes sync, loads renderer config, builds the scene (create/flush/list_add/draw_scene), completes frame-ack, and enters its own content loader (L4VIDEO.cpp) loading BattleTech models. Remaining issues are content packaging/paths and an empty DPL config -- not VPX protocol. The board emulation is functionally complete for boot + scene setup + frame-ack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
168 lines
9.2 KiB
Markdown
168 lines
9.2 KiB
Markdown
# Phase 2 — Boot Protocol Emulation: Progress
|
||
|
||
**Status: boot handshake + i860 download + FIFO transport solved; blocked on one
|
||
unknown constant in `velocirender_sync`. Phase 5 (RIO) validated on real hardware.**
|
||
|
||
## Update 2 (2026-07-02): FIFO transport working; RIO validated
|
||
|
||
- **Phase 5 — RIO passthrough validated with real hardware.** A physical RIO
|
||
was attached to the host's COM1 (Prolific USB-serial). With
|
||
`serial1=directserial realport:COM1` (see `rio.conf`) and `L4CONTROLS=RIO`,
|
||
the game's 1996 RIO driver (`L4RIO.CPP`, 9600 baud) talks to the real board:
|
||
the `RIO never came back from check request?` error **disappears**, and the
|
||
**expected activity was observed on the RIO board**. The cockpit control path
|
||
works end-to-end, unmodified, through DOSBox-X serial passthrough.
|
||
|
||
- **FIFO fast path decoded and implemented (send side).** The board is
|
||
dual-register: the render protocol switches to a FIFO channel after the i860
|
||
boots. From `OUTSW.ASM` + the capture: each message writes an `outsw` tag byte
|
||
to `outputData`, then pumps the payload as 16-bit words to the FIFO data port,
|
||
which DOSBox-X sees as byte writes to **0x154 (low) / 0x155 (high)**. The
|
||
payload begins with the 4-byte action. The device now parses this and
|
||
extracts the action of each FIFO message (confirmed: `vr_init`=0 with the
|
||
`/device~…` args string, then `vr_create`=1 with node-type `0x2D`).
|
||
|
||
- **Handler-specific reply actions (from board source).** `velocirender_receive`
|
||
reads replies on the **slow path** (already served); the device echoes the
|
||
sent action, with per-handler exceptions found in `VRENDER/VR_REMOT.C`:
|
||
`velocirender_init` and `velocirender_statistics` do `*data = 1`, so their
|
||
reply action is **1**, not the sent action. The device implements this map.
|
||
|
||
- **Remaining blocker — one constant.** The game reaches `velocirender_sync`,
|
||
which sends `vr_init` (action 0) and checks the reply action against a
|
||
specific value that is **neither 0 (echo) nor 1 (the DPL3 board's `init`
|
||
return)** — so the Rel 4.10 board returns a different value than the DPL3
|
||
dev-tree source. Determining it needs the Rel 4.10 LIBDPL/VRENDER source or a
|
||
disassembly of `BTL4OPT.EXE` around the format string at file offset
|
||
**0x107772** (`"unexpected action %d returned in velocirender_sync"`). The
|
||
device has an experiment hook (`VPX_INIT_REPLY=<n>`) to try candidate values.
|
||
|
||
Original Update 1 analysis (handshake + i860 download) follows.
|
||
|
||
---
|
||
|
||
**Status: substantial progress; boot handshake + i860 download solved; render
|
||
transport (FIFO fast path) identified as the remaining sub-protocol.**
|
||
|
||
The VPX device (`vpx-device/vpxlog.cpp`, env `VPX_RESPOND=1`) now drives the
|
||
production **BattleTech v4.10** (ALPHA_1 image) through the entire transputer/i860
|
||
boot sequence — far past the Phase 1 wall — and reaches the live renderer
|
||
protocol. Each layer below was solved by reading the normative sources on the
|
||
drive (`sda4/DPL3/VR_COMMS.C`, `DPL_HOST.C`, `VRENDER/VR_REMOT.C`, `LINKIO.C`,
|
||
`OUTSW.ASM`) and iterating against the running binary.
|
||
|
||
## Solved
|
||
|
||
### 1. iserver boot handshake
|
||
After the monitor (`VRENDMON.BTL`) download, `boot_xputer()` calls
|
||
`startup_handshake()`, which does **3 iserver transactions** (Phil's comment in
|
||
`VR_COMMS.C`: "the C run-time boots up and does 3 iserver transactions"). Each
|
||
is a `receive_protocol()` read of a 4-byte little-endian length/route word
|
||
(bit31=iserver, low16=payload length) + payload, answered by `iserver_action()`;
|
||
only success is checked. The device feeds **3 well-formed iserver "version"
|
||
requests** (tag 42, no request-content dependency) and the handshake completes.
|
||
|
||
### 2. i860 renderer download
|
||
`boot_860()` then streams the i860 image (`VRNOSTEX.MNG`) as framed renderer
|
||
messages on the slow byte path: `vr_860args` (21), `vr_860code` (18, ×118),
|
||
`vr_860data` (19, ×37), `vr_860bss` (20, ×9). The device parses the outbound
|
||
frame stream (`[length_word:4][action:4][data]`, `nb = length_word & 0xffff`)
|
||
and absorbs them, staying byte-aligned throughout.
|
||
|
||
### 3. The echo-action reply model (validated against board source)
|
||
Renderer request/reply calls (`velocirender_create`/`delete`/`flush`/`sync` in
|
||
`DPL_HOST.C`) send an action and require the **same action echoed back**. The
|
||
board side confirms this exactly: `VRENDER/VR_REMOT.C remote_velocirender()`
|
||
replies with its `data` buffer whose first word is still the received action
|
||
(`reply()` → `dN_send`). The device tracks the last outbound action and echoes
|
||
it; this is the general model for every render call, not a one-off.
|
||
|
||
## The remaining sub-protocol: the FIFO fast path
|
||
|
||
After the i860 boots, the host stops using the slow byte path and switches to a
|
||
**FIFO fast path** for the render protocol. This is why the current build still
|
||
stops at `velocirender_sync` — the device only implements the slow path
|
||
(0x150–0x153).
|
||
|
||
Discovered mechanics (from the capture + `OUTSW.ASM` + `LINKIO.C`):
|
||
|
||
- The VPX board is **dual-register**: beyond the C012 slow link
|
||
(0x150 inputData / 0x151 outputData / 0x152 inputStatus / 0x153 outputStatus),
|
||
there is a **FIFO data port** reached at **0x154/0x155** (observed) and an
|
||
`ok_to_fifo` status at **0x160**.
|
||
- `send_FIFO_protocol()` / `velocirender_transmit()` (when `use_fifo`) call the
|
||
assembly `outsw()` (`OUTSW.ASM`): it writes a `0x40` "use fifo" tag byte, then
|
||
`add dx,3` and `REP OUTSW` — pumping the message as **16-bit words** into the
|
||
FIFO port. In the capture these land as alternating byte writes to 0x154/0x155
|
||
and decode to the init/args string (e.g. `/device~0x150~/video~svga~…`).
|
||
- The host switches to `use_fifo` once the board asserts `ok_to_fifo`
|
||
(`fifo_ok_status()` reads 0x160 bit0); see the commented wait loop in
|
||
`boot_xputer()`.
|
||
|
||
### Next step (concrete)
|
||
Extend the device to implement the FIFO transport:
|
||
1. Answer `ok_to_fifo` (0x160 bit0) at the right time so the host enables `use_fifo`.
|
||
2. Accept FIFO writes: the `0x40` tag, then word-stream data at the FIFO port
|
||
(0x154/0x155). Reassemble into `[length_word][action][data]` messages
|
||
(same framing as the slow path).
|
||
3. Serve FIFO reads for replies: `velocirender_receive()` in FIFO mode reads the
|
||
reply (length_word + `[action][node]`) from the FIFO/status ports. Echo the
|
||
sent action (model already validated), returning meaningful node handles for
|
||
`vr_create` so later `vr_delete`/geometry ops have valid remotes.
|
||
4. Then the render loop (`vr_draw_scene` + frame-ack, `velocirender_frameack()`)
|
||
— the Phase 3 boundary, where geometry/material/texture commands can be
|
||
decoded into the OpenGL backend (formats already implemented in
|
||
`restoration/divformats.py`).
|
||
|
||
## Reproduce
|
||
|
||
```
|
||
set VPXLOG=C:\VWE\TeslaRel410\emulator\vpxresp.txt
|
||
set VPX_RESPOND=1
|
||
emulator\src\src\dosbox-x.exe -conf emulator\respond.conf
|
||
```
|
||
|
||
Console progression now: `BattleTech v4.10` → `BTL4Application` → RIO check →
|
||
(handshake, i860 download succeed) → `unexpected action N in velocirender_sync`
|
||
(the FIFO-path render handshake, the next target). The log's `#` note lines
|
||
trace the device's decisions (handshake requests fed, frames parsed, actions
|
||
echoed, mystery ports resolved to 0x154/0x155).
|
||
|
||
## Update 3 (2026-07-03): full VPX protocol working — game reaches content load
|
||
|
||
Disassembling `BTL4OPT.EXE` (a PE-in-DOS32RTM binary; CODE @VA 0x401000,
|
||
`velocirender_sync` @0x48D220) cracked the last protocol unknowns and the
|
||
emulated board now carries the game through the **entire** boot + render
|
||
protocol into its own game logic:
|
||
|
||
1. **velocirender_sync** (was the blocker): from the disassembly, it sends
|
||
action **0x2D** (a Rel4.10 sync/ping outside the DPL3 enum, which is why my
|
||
`<24` filter dropped it) with data `[token,0]`, then checks
|
||
`cmp token, reply.node[0]` at 0x48D271 — the "unexpected action %d" message
|
||
only *prints* the received action; the real test is the echoed **data
|
||
token**. Device now replies to 0x2D with `node[0] = token`. **Sync passes.**
|
||
|
||
2. **Speculative-poll gating**: `velocirender_transmit` calls
|
||
`handle_iserver_stuff()` which polls inputStatus once and proceeds if
|
||
not-ready. Feeding a reply into those polls corrupted alignment. The device
|
||
now only feeds a reply after several *consecutive* empty polls (a blocking
|
||
`inRecord` spin), not single speculative checks.
|
||
|
||
3. **Frame-ack**: `velocirender_frameack` expects a message with action
|
||
**9 (vr_draw_scene)**. The device tracks an outstanding `draw_scene` send and
|
||
replies 9 to the frame-ack. **Frame-ack passes.**
|
||
|
||
With these, the game now: passes sync, loads the renderer config, builds the
|
||
scene (create/flush/list_add/draw_scene), completes frame-ack, and enters its
|
||
own content loader (`L4VIDEO.cpp`), where it tries to load BattleTech models
|
||
(`buttee.bgf`, `mslr.bgf`, …). The remaining errors are **content packaging /
|
||
paths** (the minimal `test.egg` lacks these objects) and an **empty renderer
|
||
config** (`dpldflt.ini`) — *not* VPX protocol issues.
|
||
|
||
**The VPX board emulation is functionally complete for boot + scene setup +
|
||
frame-ack.** Next: (a) run against a full mission/content set (a real `.egg`
|
||
or the pod's content tree) so objects load; (b) provide a proper DPL config;
|
||
(c) Phase 3 — decode the geometry/material/texture commands already flowing over
|
||
the FIFO into the OpenGL backend (formats in `restoration/divformats.py`) to put
|
||
actual pixels on screen.
|