// staxibake -- generate the Space Taxi cel bank (.spr) for offline // sprite pre-compilation. Runs on the host: it links the game's shared // cel table (stCels.c) and the C64 sprite bitmaps (stC64Data.c) and // writes the cross-platform JSP1 sprite bank in kStCels order. The // per-target spritebake then compiles it to a .spc the game loads at // boot with jlSpriteBankLoadPrecompiled, so no cel is JIT-compiled on // the 65816 (which costs ~0.4 s each). // // Usage: staxibake OUTPUT.spr the cab/passenger/exhaust bank // staxibake --level LEVEL.dat OUTPUT.spr one level's hook-sprite cels // // The per-level bank holds the cels stLevelCelList names for that level // (its own VIC bitmaps in the colours its hook program uses), in list // order -- the game rebuilds the same list at level entry to key them. #include #include #include #include #include "stCels.h" #include "stC64Data.h" // JSP1 header (mirrors tools/spritebake + jlSpriteBankLoad): magic, // target byte (0 = cross-platform chunky), has-palette, w/h tiles, // cell count LE16, then a 32-byte palette block, all in 44 bytes. #define SPR_HEADER_BYTES 44u #define SPR_OFF_TARGET 4u #define SPR_OFF_HAS_PAL 5u #define SPR_OFF_WIDTH 6u #define SPR_OFF_HEIGHT 7u #define SPR_OFF_CELLS 8u static const uint8_t *levelBitmap(const StLevelT *level, uint8_t ptr); static bool writeHeader(FILE *out, uint16_t count); // A level's own sprite block, or NULL when the level does not ship it. static const uint8_t *levelBitmap(const StLevelT *level, uint8_t ptr) { uint8_t k; for (k = 0u; k < level->spriteCount; k++) { if (level->sprites[k].ptr == ptr) { return level->sprites[k].bitmap; } } return NULL; } static bool writeHeader(FILE *out, uint16_t count) { uint8_t header[SPR_HEADER_BYTES]; memset(header, 0, sizeof(header)); memcpy(header, "JSP1", 4); header[SPR_OFF_TARGET] = 0u; // cross-platform chunky header[SPR_OFF_HAS_PAL] = 0u; // the game owns its palette header[SPR_OFF_WIDTH] = (uint8_t)ST_CEL_TILES; header[SPR_OFF_HEIGHT] = (uint8_t)ST_CEL_TILES; header[SPR_OFF_CELLS] = (uint8_t)(count & 0xFFu); header[SPR_OFF_CELLS + 1u] = (uint8_t)(count >> 8); return fwrite(header, 1, SPR_HEADER_BYTES, out) == SPR_HEADER_BYTES; } int main(int argc, char **argv) { static StLevelT level; static StCelDefT levelCels[ST_LEVEL_CELS_MAX]; const StCelDefT *cels = kStCels; uint16_t count = kStCelCount; const char *outPath; FILE *out; uint16_t i; if (argc == 4 && strcmp(argv[1], "--level") == 0) { FILE *fp = fopen(argv[2], "rb"); if (fp == NULL || !stLevelParse(&level, fp)) { fprintf(stderr, "staxibake: cannot load level %s\n", argv[2]); return 1; } fclose(fp); cels = levelCels; count = stLevelCelList(&level, levelCels, ST_LEVEL_CELS_MAX); outPath = argv[3]; } else if (argc == 2) { outPath = argv[1]; } else { fprintf(stderr, "usage: staxibake OUTPUT.spr\n staxibake --level LEVEL.dat OUTPUT.spr\n"); return 2; } out = fopen(outPath, "wb"); if (out == NULL) { fprintf(stderr, "staxibake: cannot write %s\n", outPath); return 1; } if (!writeHeader(out, count)) { fprintf(stderr, "staxibake: header write failed\n"); fclose(out); return 1; } for (i = 0u; i < count; i++) { uint8_t blob[ST_CEL_BYTES]; const uint8_t *bm; if (cels == levelCels) { bm = levelBitmap(&level, cels[i].ptr); if (bm == NULL) { fprintf(stderr, "staxibake: level %s has no sprite block $%02X\n", argv[2], (unsigned)cels[i].ptr); fclose(out); return 1; } } else { bm = stC64SpriteBitmap((uint8_t)(cels[i].ptr - ST_SPRITE_PTR_FIRST)); } stCelBlob(bm, cels[i].multi, cels[i].color, cels[i].mc0, cels[i].mc1, blob); if (fwrite(blob, 1, ST_CEL_BYTES, out) != ST_CEL_BYTES) { fprintf(stderr, "staxibake: cel %u write failed\n", (unsigned)i); fclose(out); return 1; } } fclose(out); fprintf(stderr, "staxibake: wrote %u cels (%ux%u tiles) -> %s\n", (unsigned)count, (unsigned)ST_CEL_TILES, (unsigned)ST_CEL_TILES, outPath); return 0; }