fs2port/port/tools/dumpStations.c
2026-05-13 21:32:05 -05:00

277 lines
8.1 KiB
C

// Offline scenery station dumper.
//
// Loads an A2.SD* scenery file and runs the port's actual scenery
// VM (port/sceneryVm.c) over it -- same dispatcher, same advance
// counts, same SceneryOpInvalid termination -- to collect every ADF
// ($05), NAV ($1D), and COM ($1E) station record reachable from
// every entry point in the FS2 content region.
//
// Differs from extractstations.c (the original hand-rolled walker)
// in two ways:
//
// 1. Uses the same code path the running game would, so any future
// changes to the dispatcher are picked up here automatically.
// 2. Records are dispatched through a callback instead of a global
// array, so the tool can dedupe / filter inline.
//
// Stubs out renderer/palette since we don't draw anything on a station
// scan -- those calls only fire on $06 (DrawLine) and $12 (SetColor)
// records and have no bearing on station extraction.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sceneryVm.h"
#include "palette.h"
typedef struct CollectedT {
SceneryStationT station;
char nameBuf[16];
} CollectedT;
#define MAX_COLLECTED 8192
static CollectedT collected[MAX_COLLECTED];
static int collectedCount;
// BCD validation -- same rules as extractstations.c, lifted from
// chunk5 DecodeBCDFreqString and chunk3 LookupADFStation.
static int adfFreqValid(uint16_t freq);
static int bcdNibblesValid(uint8_t b);
static int comFreqValid(uint16_t freq);
static int navFreqValid(uint16_t freq);
static void onStation(SceneryStateT *state, const SceneryStationT *s);
static int processFile(const char *path);
static int bcdNibblesValid(uint8_t b) {
return ((b & 0x0F) <= 0x09) && ((b >> 4) <= 0x09);
}
static int adfFreqValid(uint16_t freq) {
uint8_t lo = (uint8_t)(freq & 0xFF);
uint8_t hi = (uint8_t)(freq >> 8);
if (!bcdNibblesValid(lo)) {
return 0;
}
if ((hi & 0xF0) != 0x00 || (hi & 0x0F) > 0x09) {
return 0;
}
int khz = (hi & 0x0F) * 100 + ((lo >> 4) * 10) + (lo & 0x0F);
return (khz >= 200 && khz <= 999);
}
static int navFreqValid(uint16_t freq) {
uint8_t lo = (uint8_t)(freq & 0xFF);
uint8_t hi = (uint8_t)(freq >> 8);
if (!bcdNibblesValid(lo) || !bcdNibblesValid(hi)) {
return 0;
}
if (hi == 0x10) {
return lo >= 0x80;
}
if (hi == 0x11) {
return lo <= 0x79;
}
return 0;
}
static int comFreqValid(uint16_t freq) {
uint8_t lo = (uint8_t)(freq & 0xFF);
uint8_t hi = (uint8_t)(freq >> 8);
if (!bcdNibblesValid(lo) || !bcdNibblesValid(hi)) {
return 0;
}
if (hi == 0x11) {
return lo >= 0x80;
}
if (hi == 0x12) {
return 1;
}
if (hi == 0x13) {
return lo <= 0x69;
}
return 0;
}
static void onStation(SceneryStateT *state, const SceneryStationT *s) {
(void)state;
if (collectedCount >= MAX_COLLECTED) {
return;
}
switch (s->type) {
case SCENERY_STATION_ADF:
if (!adfFreqValid(s->freq)) return;
break;
case SCENERY_STATION_NAV:
if (!navFreqValid(s->freq)) return;
break;
case SCENERY_STATION_COM:
if (!comFreqValid(s->freq)) return;
break;
}
CollectedT *c = &collected[collectedCount++];
c->station = *s;
c->nameBuf[0] = '\0';
if (s->name != NULL) {
size_t n = strlen(s->name);
if (n >= sizeof(c->nameBuf)) {
n = sizeof(c->nameBuf) - 1;
}
memcpy(c->nameBuf, s->name, n);
c->nameBuf[n] = '\0';
c->station.name = c->nameBuf;
}
}
static int processFile(const char *path) {
FILE *f = fopen(path, "rb");
if (f == NULL) {
fprintf(stderr, "cannot open %s\n", path);
return 0;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *buf = malloc((size_t)sz);
if (buf == NULL || fread(buf, 1, (size_t)sz, f) != (size_t)sz) {
fclose(f);
free(buf);
return 0;
}
fclose(f);
// Use a heap-allocated state to keep the 200KB visited array
// off the stack.
SceneryStateT *state = malloc(sizeof(SceneryStateT));
if (state == NULL) {
free(buf);
return 0;
}
sceneryInit(state, buf, (uint32_t)sz, NULL);
sceneryAttachStationCb(state, onStation, NULL);
int before = collectedCount;
// Walk every byte in the FS2 content region. The cycle guard
// inside sceneryRun makes this O(file size).
for (uint32_t entry = 0x2000; entry < (uint32_t)sz; entry++) {
sceneryWalkFrom(state, entry);
}
int adf = 0;
int nav = 0;
int com = 0;
for (int i = before; i < collectedCount; i++) {
switch (collected[i].station.type) {
case SCENERY_STATION_ADF: adf++; break;
case SCENERY_STATION_NAV: nav++; break;
case SCENERY_STATION_COM: com++; break;
}
}
printf("%-40s ADF=%d NAV=%d COM=%d\n", path, adf, nav, com);
free(state);
free(buf);
return 1;
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "usage: %s file.SD [file.SD ...]\n", argv[0]);
return 1;
}
int adf = 0;
int nav = 0;
int com = 0;
for (int i = 1; i < argc; i++) {
if (!processFile(argv[i])) {
continue;
}
}
for (int i = 0; i < collectedCount; i++) {
switch (collected[i].station.type) {
case SCENERY_STATION_ADF: adf++; break;
case SCENERY_STATION_NAV: nav++; break;
case SCENERY_STATION_COM: com++; break;
}
}
printf("\ntotal: %d ADF, %d NAV, %d COM\n", adf, nav, com);
for (int i = 0; i < collectedCount; i++) {
const SceneryStationT *s = &collected[i].station;
printf(" %c freq=$%04X x=%d y=%d z=%d %s\n",
(char)s->type, s->freq, s->x, s->y, s->z,
s->name != NULL ? s->name : "");
}
return 0;
}
// --- Stubs so we don't have to link the SDL-driven renderer ---
void rendererSetDrawColor(struct RenderStateT *state, ColorE color) {
(void)state;
(void)color;
}
void rendererSetHiresColor(struct RenderStateT *state, uint8_t hiresCode) {
(void)state;
(void)hiresCode;
}
void rendererDrawLine(struct RenderStateT *state, int16_t x1, int16_t y1, int16_t x2, int16_t y2) {
(void)state;
(void)x1;
(void)y1;
(void)x2;
(void)y2;
}
void rendererDrawColorSpan(struct RenderStateT *state, int16_t xRight, int16_t length, int16_t y) {
(void)state;
(void)xRight;
(void)length;
(void)y;
}
void rendererFillPolygon(struct RenderStateT *state, const int16_t *xs, const int16_t *ys, int count) {
(void)state;
(void)xs;
(void)ys;
(void)count;
}
ColorE paletteFromSceneryCode(uint8_t code) {
(void)code;
return COLOR_BLACK;
}
// sceneryAttachCamera (in sceneryVm.c) calls into camera.c -- stub
// since station scans don't need a real camera matrix.
void cameraGet2x3Matrix(const struct CameraT *cam, int8_t outRowX[3], int8_t outRowZ[3]) {
(void)cam;
outRowX[0] = 0;
outRowX[1] = 0;
outRowX[2] = 0;
outRowZ[0] = 0;
outRowZ[1] = 0;
outRowZ[2] = 0;
}