323 lines
9.4 KiB
C
323 lines
9.4 KiB
C
// joeysprite: host-side compiler that turns raw tile data into a
|
|
// `.spr` file ready to be loaded at runtime by spriteLoadFile.
|
|
//
|
|
// Usage:
|
|
// joeysprite --target {iigs,amiga,atarist,dos}
|
|
// --width-tiles N --height-tiles M
|
|
// input.tiles output.spr
|
|
//
|
|
// `input.tiles` is widthTiles * heightTiles * 32 bytes, laid out
|
|
// tile-major as the runtime SpriteT.tileData expects: tile (0,0)
|
|
// first 32 bytes, tile (1,0) next 32, ... tile (widthTiles-1, 0),
|
|
// then tile (0,1), and so on. Inside each tile, rows are stored
|
|
// top-to-bottom and each row is 4 bytes (8 pixels at 4bpp packed,
|
|
// high nibble = left pixel).
|
|
//
|
|
// Output `.spr` format (target-native byte order, see DESIGN.md
|
|
// §12 for details):
|
|
// header (4 bytes): widthTiles, heightTiles, codeSize lo/hi
|
|
// offsets (JOEY_SPRITE_SHIFT_COUNT * 3 * uint16_t):
|
|
// [draw_s0, save_s0, restore_s0, draw_s1, save_s1, restore_s1]
|
|
// Save and restore offsets are written as 0 (uniform memcpy on
|
|
// load; never compiled).
|
|
// code (codeSize bytes): emitted machine code per shift, in order.
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "joey/sprite.h"
|
|
#include "spriteEmitter.h"
|
|
#include "spriteInternal.h"
|
|
|
|
|
|
typedef enum {
|
|
TARGET_IIGS,
|
|
TARGET_AMIGA,
|
|
TARGET_ATARIST,
|
|
TARGET_DOS,
|
|
TARGET_INVALID
|
|
} TargetE;
|
|
|
|
|
|
// ----- Constants -----
|
|
|
|
#define MAX_SCRATCH_BYTES (16u * 1024u)
|
|
#define SPR_HEADER_SIZE 4
|
|
// Save/restore offsets are reserved (0) for now -- the runtime
|
|
// memcpy interpreter handles them.
|
|
#define SHIFT_OPS 3
|
|
#define OFFSET_TABLE_BYTES (JOEY_SPRITE_SHIFT_COUNT * SHIFT_OPS * 2u)
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static int compileToSpr(const SpriteT *sp, TargetE target, const char *outPath);
|
|
static uint16_t emitForTarget(uint8_t *out, const SpriteT *sp, uint8_t shift, TargetE target);
|
|
static int loadTileData(const char *path, uint8_t **outBytes, uint32_t *outSize);
|
|
static TargetE parseTarget(const char *name);
|
|
static int usage(const char *prog);
|
|
static int writeLE16(FILE *fp, uint16_t v);
|
|
|
|
|
|
// ----- Internal helpers (alphabetical) -----
|
|
|
|
static int compileToSpr(const SpriteT *sp, TargetE target, const char *outPath) {
|
|
uint8_t *scratch;
|
|
uint8_t *codeBuf;
|
|
uint16_t shiftLengths[JOEY_SPRITE_SHIFT_COUNT];
|
|
uint32_t totalCodeSize;
|
|
uint8_t shift;
|
|
uint8_t op;
|
|
uint16_t written;
|
|
uint16_t cursor;
|
|
uint16_t offset;
|
|
FILE *fp;
|
|
int rc;
|
|
|
|
scratch = (uint8_t *)malloc(MAX_SCRATCH_BYTES);
|
|
if (scratch == NULL) {
|
|
fprintf(stderr, "joeysprite: out of memory\n");
|
|
return 2;
|
|
}
|
|
|
|
totalCodeSize = 0;
|
|
for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
|
|
written = emitForTarget(scratch, sp, shift, target);
|
|
shiftLengths[shift] = written;
|
|
totalCodeSize += written;
|
|
}
|
|
if (totalCodeSize > 0xFFFFu) {
|
|
fprintf(stderr, "joeysprite: emitted %u code bytes; max is 65535\n",
|
|
(unsigned)totalCodeSize);
|
|
free(scratch);
|
|
return 2;
|
|
}
|
|
|
|
codeBuf = (uint8_t *)malloc(totalCodeSize);
|
|
if (codeBuf == NULL) {
|
|
fprintf(stderr, "joeysprite: out of memory for code buffer\n");
|
|
free(scratch);
|
|
return 2;
|
|
}
|
|
|
|
cursor = 0;
|
|
for (shift = 0; shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
|
|
written = emitForTarget(codeBuf + cursor, sp, shift, target);
|
|
cursor = (uint16_t)(cursor + written);
|
|
}
|
|
|
|
fp = fopen(outPath, "wb");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "joeysprite: cannot open %s for writing\n", outPath);
|
|
free(codeBuf);
|
|
free(scratch);
|
|
return 2;
|
|
}
|
|
|
|
rc = 0;
|
|
if (fputc(sp->widthTiles, fp) == EOF) rc = 2;
|
|
if (fputc(sp->heightTiles, fp) == EOF) rc = 2;
|
|
if (rc == 0 && writeLE16(fp, (uint16_t)totalCodeSize) != 0) rc = 2;
|
|
|
|
// Offset table: cumulative draw offsets + zeros for save/restore.
|
|
offset = 0;
|
|
for (shift = 0; rc == 0 && shift < JOEY_SPRITE_SHIFT_COUNT; shift++) {
|
|
for (op = 0; op < SHIFT_OPS; op++) {
|
|
uint16_t value;
|
|
if (op == SPRITE_OP_DRAW) {
|
|
value = offset;
|
|
} else {
|
|
value = 0;
|
|
}
|
|
if (writeLE16(fp, value) != 0) {
|
|
rc = 2;
|
|
break;
|
|
}
|
|
}
|
|
offset = (uint16_t)(offset + shiftLengths[shift]);
|
|
}
|
|
|
|
if (rc == 0) {
|
|
if (fwrite(codeBuf, 1, totalCodeSize, fp) != totalCodeSize) {
|
|
rc = 2;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
free(codeBuf);
|
|
free(scratch);
|
|
|
|
if (rc == 0) {
|
|
printf("joeysprite: %u code bytes -> %s (target=%s, %ux%u tiles)\n",
|
|
(unsigned)totalCodeSize, outPath,
|
|
target == TARGET_IIGS ? "iigs" :
|
|
target == TARGET_AMIGA ? "amiga" :
|
|
target == TARGET_ATARIST ? "atarist" : "dos",
|
|
sp->widthTiles, sp->heightTiles);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
static uint16_t emitForTarget(uint8_t *out, const SpriteT *sp, uint8_t shift, TargetE target) {
|
|
switch (target) {
|
|
case TARGET_DOS:
|
|
return spriteEmitDrawX86(out, sp, shift);
|
|
case TARGET_AMIGA:
|
|
case TARGET_ATARIST:
|
|
return spriteEmitDraw68k(out, sp, shift);
|
|
case TARGET_IIGS:
|
|
return spriteEmitDrawIigs(out, sp, shift);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int loadTileData(const char *path, uint8_t **outBytes, uint32_t *outSize) {
|
|
FILE *fp;
|
|
long fileSize;
|
|
uint8_t *buf;
|
|
size_t read;
|
|
|
|
fp = fopen(path, "rb");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "joeysprite: cannot open %s\n", path);
|
|
return 2;
|
|
}
|
|
if (fseek(fp, 0L, SEEK_END) != 0) {
|
|
fclose(fp);
|
|
return 2;
|
|
}
|
|
fileSize = ftell(fp);
|
|
if (fileSize <= 0) {
|
|
fprintf(stderr, "joeysprite: %s is empty\n", path);
|
|
fclose(fp);
|
|
return 2;
|
|
}
|
|
if (fseek(fp, 0L, SEEK_SET) != 0) {
|
|
fclose(fp);
|
|
return 2;
|
|
}
|
|
buf = (uint8_t *)malloc((size_t)fileSize);
|
|
if (buf == NULL) {
|
|
fclose(fp);
|
|
return 2;
|
|
}
|
|
read = fread(buf, 1, (size_t)fileSize, fp);
|
|
fclose(fp);
|
|
if (read != (size_t)fileSize) {
|
|
free(buf);
|
|
return 2;
|
|
}
|
|
*outBytes = buf;
|
|
*outSize = (uint32_t)fileSize;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static TargetE parseTarget(const char *name) {
|
|
if (strcmp(name, "iigs") == 0) return TARGET_IIGS;
|
|
if (strcmp(name, "amiga") == 0) return TARGET_AMIGA;
|
|
if (strcmp(name, "atarist") == 0) return TARGET_ATARIST;
|
|
if (strcmp(name, "dos") == 0) return TARGET_DOS;
|
|
return TARGET_INVALID;
|
|
}
|
|
|
|
|
|
static int usage(const char *prog) {
|
|
fprintf(stderr,
|
|
"usage: %s --target {iigs,amiga,atarist,dos} \\\n"
|
|
" --width-tiles N --height-tiles M \\\n"
|
|
" input.tiles output.spr\n", prog);
|
|
return 2;
|
|
}
|
|
|
|
|
|
// 65816 / x86 / 68k all expect target-native byte order in the .spr
|
|
// header offsets, but the file format is little-endian (matches the
|
|
// runtime spriteFromCompiledMem parser, which reads byte-by-byte).
|
|
static int writeLE16(FILE *fp, uint16_t v) {
|
|
if (fputc((int)(v & 0xFFu), fp) == EOF) return -1;
|
|
if (fputc((int)((v >> 8) & 0xFFu), fp) == EOF) return -1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ----- main -----
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *targetName;
|
|
const char *inPath;
|
|
const char *outPath;
|
|
long widthTiles;
|
|
long heightTiles;
|
|
int i;
|
|
TargetE target;
|
|
uint8_t *tileBytes;
|
|
uint32_t tileSize;
|
|
uint32_t expectedTileSize;
|
|
SpriteT sp;
|
|
int rc;
|
|
|
|
targetName = NULL;
|
|
widthTiles = 0;
|
|
heightTiles = 0;
|
|
inPath = NULL;
|
|
outPath = NULL;
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--target") == 0 && i + 1 < argc) {
|
|
targetName = argv[++i];
|
|
} else if (strcmp(argv[i], "--width-tiles") == 0 && i + 1 < argc) {
|
|
widthTiles = strtol(argv[++i], NULL, 10);
|
|
} else if (strcmp(argv[i], "--height-tiles") == 0 && i + 1 < argc) {
|
|
heightTiles = strtol(argv[++i], NULL, 10);
|
|
} else if (inPath == NULL) {
|
|
inPath = argv[i];
|
|
} else if (outPath == NULL) {
|
|
outPath = argv[i];
|
|
} else {
|
|
return usage(argv[0]);
|
|
}
|
|
}
|
|
if (targetName == NULL || widthTiles <= 0 || widthTiles > 255 ||
|
|
heightTiles <= 0 || heightTiles > 255 ||
|
|
inPath == NULL || outPath == NULL) {
|
|
return usage(argv[0]);
|
|
}
|
|
|
|
target = parseTarget(targetName);
|
|
if (target == TARGET_INVALID) {
|
|
fprintf(stderr, "joeysprite: unknown --target %s\n", targetName);
|
|
return usage(argv[0]);
|
|
}
|
|
|
|
rc = loadTileData(inPath, &tileBytes, &tileSize);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
expectedTileSize = (uint32_t)(widthTiles * heightTiles * 32);
|
|
if (tileSize != expectedTileSize) {
|
|
fprintf(stderr,
|
|
"joeysprite: %s is %u bytes; expected %u (%ld * %ld tiles * 32 bytes)\n",
|
|
inPath, (unsigned)tileSize, (unsigned)expectedTileSize,
|
|
widthTiles, heightTiles);
|
|
free(tileBytes);
|
|
return 2;
|
|
}
|
|
|
|
sp.tileData = tileBytes;
|
|
sp.widthTiles = (uint8_t)widthTiles;
|
|
sp.heightTiles = (uint8_t)heightTiles;
|
|
sp.ownsTileData = false;
|
|
sp.slot = NULL;
|
|
memset(sp.routineOffsets, 0, sizeof(sp.routineOffsets));
|
|
sp.flags = SPRITE_FLAGS_NONE;
|
|
|
|
rc = compileToSpr(&sp, target, outPath);
|
|
free(tileBytes);
|
|
return rc;
|
|
}
|