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>
128 lines
4.6 KiB
C++
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);
|
|
}
|