277 lines
8.1 KiB
C
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;
|
|
}
|