207 lines
8.7 KiB
C
207 lines
8.7 KiB
C
// Walk the FS2.1 ProDOS scenery file and emit each section as a
|
|
// separate clean bytecode blob. No 6502 emulation, no 64K dump
|
|
// scaffolding. Just file -> per-section files.
|
|
//
|
|
// A "section" in chunk5 terms is a sequence of scenery opcodes loaded
|
|
// by a $0D HEADER. We don't have an explicit section table, so we
|
|
// scan for plausible section starts ($13 cull or $21 cull at sector
|
|
// boundaries) and walk forward until we hit a stream terminator
|
|
// ($79+, $80+, or an invalid opcode). For each one we emit:
|
|
//
|
|
// out_section_<offset>.bin -- raw bytes from start to terminator
|
|
//
|
|
// This is a best-effort static dump. The output isn't guaranteed to
|
|
// be a complete chunk5 section (some have $0B back-jumps that
|
|
// reference yet other regions of the file), but it gives us clean
|
|
// chunks to inspect, diff, and load into the port without the boot
|
|
// machinery overhead.
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
static int isTerminator(uint8_t op) {
|
|
// chunk5 dispatcher: opcodes with bit 7 set or >= $46 fall off
|
|
// the SceneryOpcodeTable and reset the stream.
|
|
if (op & 0x80) return 1;
|
|
if (op >= 0x46) return 1;
|
|
// Specific $08/$0C/$0F/$10/$15-17/$1F/$26-27/$2A/$2C-2E/$30/
|
|
// $34/$36-3F/$43-45 are SceneryOpInvalid and also terminate.
|
|
switch (op) {
|
|
case 0x08: case 0x0C: case 0x0F: case 0x10:
|
|
case 0x15: case 0x16: case 0x17:
|
|
case 0x1F:
|
|
case 0x26: case 0x27: case 0x2A:
|
|
case 0x2C: case 0x2D: case 0x2E:
|
|
case 0x30:
|
|
case 0x34:
|
|
case 0x36: case 0x37: case 0x38: case 0x39:
|
|
case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
|
|
case 0x43: case 0x44: case 0x45:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Static-walk advance for each opcode. Returns 0 if we should stop
|
|
// (terminator or unknown that needs more state). Best-effort -- no
|
|
// full state machine.
|
|
static int advanceLength(uint8_t op, const uint8_t *p, size_t avail) {
|
|
switch (op) {
|
|
case 0x00: case 0x01: case 0x02:
|
|
case 0x40: case 0x41:
|
|
return 5; // emit-vertex 5-byte
|
|
case 0x42:
|
|
return 6; // refresh-cache: op + idx + 4 vertex bytes
|
|
case 0x03:
|
|
return 6; // SceneryOpCall64K_2
|
|
case 0x04:
|
|
// CullByOutcodeList: walks vertex idx bytes
|
|
// until terminator with bit 7 set.
|
|
{
|
|
size_t off = 3;
|
|
while (off < avail) {
|
|
if (p[off++] & 0x80) break;
|
|
}
|
|
return (int)off;
|
|
}
|
|
case 0x05: // ADF station
|
|
return 9;
|
|
case 0x06: // DrawLine
|
|
return 5;
|
|
case 0x07: // EnterLocalFrame
|
|
return 14;
|
|
case 0x09: case 0x0A: // SKIP3
|
|
return 3;
|
|
case 0x0B: // REL_JUMP -- can't follow statically; stop
|
|
return 0;
|
|
case 0x0D: // HEADER
|
|
return 6;
|
|
case 0x0E:
|
|
return 1;
|
|
case 0x11: // SKIP_1
|
|
return 1;
|
|
case 0x12: // SetColor
|
|
return 2;
|
|
case 0x13: case 0x14: // JumpIfBeyondXY/XYZ -- consume but don't follow
|
|
return 9;
|
|
case 0x18: // SUB_INVOKE -- can't follow statically; stop
|
|
return 0;
|
|
case 0x19: // RETURN
|
|
return 0;
|
|
case 0x1A: // WriteWord
|
|
return 5;
|
|
case 0x1B: case 0x1C: // ModeWhite / DayOnly
|
|
return 1;
|
|
case 0x1D: // NAV station
|
|
return 11;
|
|
case 0x1E: // COM record (variable length, byte 1 = total)
|
|
if (avail < 2) return 0;
|
|
return p[1];
|
|
case 0x20: // CullN_1
|
|
return 9;
|
|
case 0x21: // CullN_2
|
|
return 15;
|
|
case 0x22: // CullN_3
|
|
return 21;
|
|
case 0x23: // JumpIfBitsClear
|
|
return 7;
|
|
case 0x24: // PushOriginWithStash
|
|
return 8;
|
|
case 0x25: // StoreImmWord
|
|
return 5;
|
|
case 0x28: // JumpIfWordCompare
|
|
return 8;
|
|
case 0x29: // CopyToD2
|
|
return 1;
|
|
case 0x2B: // EmitCurve (best-effort)
|
|
return 12;
|
|
case 0x2F: // ResetState
|
|
return 1;
|
|
case 0x31: case 0x32: case 0x33:
|
|
case 0x35:
|
|
return 2;
|
|
}
|
|
return 0; // unknown / can't advance
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc != 3) {
|
|
fprintf(stderr, "usage: %s <fs2.1 file> <out dir>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
FILE *f = fopen(argv[1], "rb");
|
|
if (f == NULL) { perror(argv[1]); return 1; }
|
|
fseek(f, 0, SEEK_END);
|
|
long size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
uint8_t *buf = malloc(size);
|
|
fread(buf, 1, size, f);
|
|
fclose(f);
|
|
|
|
printf("FS2.1: %ld bytes\n", size);
|
|
|
|
int sectionsFound = 0;
|
|
int reachedSection = 0;
|
|
long off = 0;
|
|
while (off < size) {
|
|
// Plausible section start: $13 ($14) cull or $21
|
|
// ($20/$22) cull at a 256-byte boundary.
|
|
uint8_t op = buf[off];
|
|
int sectorBoundary = (off & 0xFF) == 0;
|
|
int looksLikeSection = sectorBoundary && (
|
|
op == 0x13 || op == 0x14 ||
|
|
op == 0x20 || op == 0x21 || op == 0x22);
|
|
if (!looksLikeSection) {
|
|
off++;
|
|
continue;
|
|
}
|
|
|
|
// Walk forward, counting bytes until terminator.
|
|
long start = off;
|
|
long polygonOps = 0;
|
|
long opCount = 0;
|
|
while (off < size) {
|
|
uint8_t cur = buf[off];
|
|
if (isTerminator(cur)) break;
|
|
int len = advanceLength(cur, &buf[off], (size_t)(size - off));
|
|
if (len <= 0) {
|
|
// $0B / $18 / $19 -- can't follow.
|
|
break;
|
|
}
|
|
opCount++;
|
|
if (cur == 0x00 || cur == 0x40 || cur == 0x41) {
|
|
polygonOps++;
|
|
}
|
|
if (off + len > size) break;
|
|
off += len;
|
|
}
|
|
|
|
long sectionLen = off - start;
|
|
if (sectionLen >= 16 && polygonOps > 0) {
|
|
char name[256];
|
|
snprintf(name, sizeof(name),
|
|
"%s/section_%05lX_polyops%ld_oplen%ld.bin",
|
|
argv[2], start, polygonOps, opCount);
|
|
FILE *out = fopen(name, "wb");
|
|
if (out != NULL) {
|
|
fwrite(&buf[start], 1, sectionLen, out);
|
|
fclose(out);
|
|
printf(" $%05lX: %ld bytes, %ld ops, %ld polygon emits -> %s\n",
|
|
start, sectionLen, opCount, polygonOps, name);
|
|
if (polygonOps >= 5) reachedSection++;
|
|
}
|
|
sectionsFound++;
|
|
}
|
|
if (off == start) off++; // avoid infinite loop on stuck cursor
|
|
}
|
|
|
|
printf("\n%d candidate sections found, %d with >=5 polygon emits\n",
|
|
sectionsFound, reachedSection);
|
|
free(buf);
|
|
return 0;
|
|
}
|