// 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_.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 #include #include #include 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 \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; }