// Asset loader: parses .jas blobs into JoeyAssetT, with companion // helpers for file I/O and palette application. // // The .jas format is little-endian byte-stream so the same binary // works on every target -- 6502/x86 (LE) read it directly, 68k (BE) // reassembles via byte-by-byte decode in jasDecodeWord. #include #include #include #include "joey/asset.h" #include "joey/palette.h" #define JAS_HEADER_SIZE 44 #define JAS_PIXELS_OFFSET JAS_HEADER_SIZE #define JAS_PALETTE_OFFSET 12 #define JAS_PALETTE_ENTRIES 16 #define JAS_MAX_BYTES (1L << 24) // 16MB sanity cap on file size // ----- Prototypes ----- static uint16_t jasDecodeWord(const uint8_t *p); static bool jasMagicMatches(const uint8_t *data); // ----- Internal helpers ----- static uint16_t jasDecodeWord(const uint8_t *p) { return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); } static bool jasMagicMatches(const uint8_t *data) { return data[0] == 'J' && data[1] == 'A' && data[2] == 'S' && data[3] == '1'; } // ----- Public API (alphabetical) ----- void joeyAssetApplyPalette(SurfaceT *dst, uint8_t paletteIndex, const JoeyAssetT *asset) { if (dst == NULL || asset == NULL) { return; } if (!asset->hasPalette) { return; } paletteSet(dst, paletteIndex, asset->palette); } void joeyAssetFree(JoeyAssetT *asset) { if (asset == NULL) { return; } // The const cast is intentional: loader-owned assets carry // pixels we allocated. Static / embedded assets point at .rodata // and the contract documents that they must not be passed here. free((void *)asset->pixels); free(asset); } JoeyAssetT *joeyAssetFromMem(const uint8_t *data, uint32_t length) { JoeyAssetT *asset; uint8_t *pixelBuf; uint32_t rowBytes; uint32_t pixelLen; uint16_t i; if (data == NULL || length < JAS_HEADER_SIZE) { return NULL; } if (!jasMagicMatches(data)) { return NULL; } asset = (JoeyAssetT *)malloc(sizeof(JoeyAssetT)); if (asset == NULL) { return NULL; } memset(asset, 0, sizeof(*asset)); asset->width = jasDecodeWord(data + 4); asset->height = jasDecodeWord(data + 6); asset->hasPalette = data[8] != 0; for (i = 0; i < JAS_PALETTE_ENTRIES; i++) { asset->palette[i] = jasDecodeWord(data + JAS_PALETTE_OFFSET + (i * 2)); } if (asset->width == 0 || asset->height == 0) { free(asset); return NULL; } rowBytes = (uint32_t)((asset->width + 1) >> 1); pixelLen = rowBytes * (uint32_t)asset->height; if (length < (JAS_PIXELS_OFFSET + pixelLen)) { free(asset); return NULL; } pixelBuf = (uint8_t *)malloc(pixelLen); if (pixelBuf == NULL) { free(asset); return NULL; } memcpy(pixelBuf, data + JAS_PIXELS_OFFSET, pixelLen); asset->pixels = pixelBuf; return asset; } JoeyAssetT *joeyAssetLoadFile(const char *path) { FILE *fp; uint8_t *buf; long size; size_t readBytes; JoeyAssetT *asset; if (path == NULL) { return NULL; } fp = fopen(path, "rb"); if (fp == NULL) { return NULL; } if (fseek(fp, 0L, SEEK_END) != 0) { fclose(fp); return NULL; } size = ftell(fp); if (size <= 0 || size > JAS_MAX_BYTES) { fclose(fp); return NULL; } if (fseek(fp, 0L, SEEK_SET) != 0) { fclose(fp); return NULL; } buf = (uint8_t *)malloc((size_t)size); if (buf == NULL) { fclose(fp); return NULL; } readBytes = fread(buf, 1, (size_t)size, fp); fclose(fp); if (readBytes != (size_t)size) { free(buf); return NULL; } asset = joeyAssetFromMem(buf, (uint32_t)size); free(buf); return asset; }