Files
TeslaRel410/emulator/vpx-device/vpxlog.cpp
T
CydandClaude Fable 5 e3c090695d Phase 1 complete: captured VPX boot conversation from shipped binary
Built DOSBox-X from source with a custom VPX link-adapter logging
device (vpx-device/vpxlog.cpp) and captured the game's outbound boot
sequence. Findings:

- Register map confirmed against LINKIO.C: outputData 0x151,
  outputStatus 0x153 (polled bit0), resetRoot 0x160, analyseRoot 0x161.
- Reset preamble captured exactly (analyse=0, reset 0->1->0 + status inits).
- The game streams 85298 bytes to outputData that are BYTE-FOR-BYTE
  identical to VRENDMON.BTL; first byte 0xF0 = transputer boot-from-link
  primary-bootstrap length. Protocol stage 1 (reset + monitor download)
  fully characterized; no hidden bulk/interrupt path.
- Production cockpit dump ALPHA_1 added (git-ignored): its BT is v1.1.0.6
  with a VRENDMON.BTL byte-identical to the captured stream, so this
  result reflects the exact cockpit software. ALPHA_1 is the reference
  image going forward (carries RP + production pod/network boot chain).

Adds analyze_capture.py, capture.conf, PHASE1-RESULTS.md. DOSBox-X
source tree and capture artifacts are git-ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:46:20 -05:00

128 lines
4.6 KiB
C++

/* VPX link-adapter logging device (Tesla Rel 4.10 emulation, Phase 1)
*
* Impersonates the INMOS C012 transputer link adapter the Division VPX board
* hung off the ISA bus, at its documented register map (host source:
* sda4/DPL3/LINKIO.C, setLA):
*
* base+0 (0x150) inputData R byte board->host
* base+1 (0x151) outputData W byte host->board
* base+2 (0x152) inputStatus R bit0 = inbound byte available
* base+3 (0x153) outputStatus R bit0 = ready to accept outbound byte
* base+16 (0x160) resetRoot W board reset strobe / ok_to_fifo
* base+17 (0x161) analyseRoot W board analyse strobe
*
* Phase 1 goal is DISCOVERY, not emulation: log every access so we can capture
* the game's outbound boot conversation (reset -> transputer monitor download
* -> i860 code/data segments) from the shipped binary. Status reads are
* answered so the game keeps writing:
* - outputStatus -> always ready (bit0=1) so OUTBYTE proceeds
* - inputStatus -> no data (bit0=0) so the game doesn't read phantom input
* - inputData -> 0xFF (logged) if the game reads anyway
*
* Enabled only when the environment variable VPXLOG is set (harmless when off).
* Log file: $VPXLOG if it names a path, else "vpxlog.txt" in the CWD.
*/
#include "dosbox.h"
#include "inout.h"
#include "logging.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const io_port_t VPX_BASE = 0x150;
static FILE *vpx_fp = NULL;
static bool vpx_on = false;
static unsigned long vpx_seq = 0;
/* Coalesce runs of identical status polls so the log shows the conversation,
* not millions of "read status = ready" lines. */
static io_port_t last_port = 0xFFFF;
static unsigned last_val = 0xFFFFFFFF;
static bool last_write = false;
static unsigned long last_run = 0;
static const char *reg_name(io_port_t port) {
switch (port - VPX_BASE) {
case 0: return "inputData";
case 1: return "outputData";
case 2: return "inputStatus";
case 3: return "outputStatus";
case 16: return "resetRoot";
case 17: return "analyseRoot";
default: return "?";
}
}
static void vpx_flush_run(void) {
if (last_run == 0 || vpx_fp == NULL) return;
if (last_run == 1) {
fprintf(vpx_fp, "%8lu %s %-13s 0x%02X\n",
vpx_seq++, last_write ? "W" : "R", reg_name(last_port),
last_val & 0xFF);
} else {
fprintf(vpx_fp, "%8lu %s %-13s 0x%02X x%lu\n",
vpx_seq++, last_write ? "W" : "R", reg_name(last_port),
last_val & 0xFF, last_run);
}
fflush(vpx_fp);
last_run = 0;
}
static void vpx_record(io_port_t port, unsigned val, bool write) {
if (vpx_fp == NULL) return;
/* Collapse identical consecutive accesses (typically status polls). */
if (port == last_port && val == last_val && write == last_write) {
last_run++;
return;
}
vpx_flush_run();
last_port = port; last_val = val; last_write = write; last_run = 1;
/* Payload writes and reset/analyse strobes are the interesting events;
* emit them immediately (don't wait for a differing access to flush). */
io_port_t off = port - VPX_BASE;
if (write && (off == 1 || off == 16 || off == 17))
vpx_flush_run();
}
static Bitu vpx_read(Bitu port, Bitu /*iolen*/) {
io_port_t off = (io_port_t)port - VPX_BASE;
Bitu ret;
switch (off) {
case 2: ret = 0x00; break; /* inputStatus: no inbound data */
case 3: ret = 0x01; break; /* outputStatus: always ready to send */
default: ret = 0xFF; break; /* inputData / unknown: open-bus */
}
vpx_record((io_port_t)port, (unsigned)ret, false);
return ret;
}
static void vpx_write(Bitu port, Bitu val, Bitu /*iolen*/) {
vpx_record((io_port_t)port, (unsigned)val, true);
}
void VPXLOG_Init(void) {
const char *env = getenv("VPXLOG");
if (env == NULL || env[0] == '\0') return;
const char *path = (strchr(env, '/') || strchr(env, '\\') ||
strchr(env, '.')) ? env : "vpxlog.txt";
vpx_fp = fopen(path, "w");
if (vpx_fp == NULL) {
LOG_MSG("VPXLOG: could not open log file '%s'", path);
return;
}
vpx_on = true;
/* Cover 0x150..0x153 and 0x160..0x161 with one 18-port range. */
IO_RegisterReadHandler(VPX_BASE, vpx_read, IO_MB, 18);
IO_RegisterWriteHandler(VPX_BASE, vpx_write, IO_MB, 18);
fprintf(vpx_fp, "# VPX link-adapter access log, base 0x%03X\n", VPX_BASE);
fprintf(vpx_fp, "# seq dir register value [run-length]\n");
fflush(vpx_fp);
LOG_MSG("VPXLOG: logging VPX link adapter at 0x%03X to '%s'", VPX_BASE, path);
}