65816-llvm-mos/runtime/src/resource.c
Scott Duensing 09f7405362 Updates
2026-06-03 16:08:42 -05:00

479 lines
13 KiB
C

// resource.c - Apple IIgs Resource Manager - real implementation.
//
// Replaces the Phase 3.4 stub. Reads .rsrc resource forks via the
// stdio surface (fopen/fread/fseek/fclose) and caches loaded payloads
// by (type, id) so repeated loadResource() calls return the same
// handle. Read-only - no AddResource / DetachResource / partial-load.
//
// File format (Apple IIgs Toolbox Reference Vol 3, ch.42):
// bytes 0..23 : ResourceMapHeaderT (little-endian fields)
// bytes ... : payload blobs at offsets recorded in the index
// bytes at rmToIndex : rmIndexUsed * ResourceIndexEntryT entries
//
// Handle convention: we return a `void **` whose dereference yields the
// resource bytes. The handle storage lives in this file's static
// table; the bytes themselves are malloc'd at first load and freed at
// releaseResource(verb=1) or closeResourceFile().
#include "iigs/resource.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// --- Prototypes ---
static int freeHandleSlot(int slot);
static int findHandleByPtr(void **handle);
static int findHandleByTypeId(IigsResTypeT type, IigsResIdT id);
static int findHandleSlot(void);
static int findOpenFileSlot(void);
static int loadIndex(int fileSlot);
static void *readPayload(int fileSlot, uint32_t offset, uint32_t size);
static int readU16(FILE *f, uint16_t *out);
static int readU32(FILE *f, uint32_t *out);
static int readMapHeader(FILE *f, ResourceMapHeaderT *hdr);
// --- Internal types ---
typedef struct {
int inUse;
FILE *fp;
ResourceMapHeaderT hdr;
ResourceIndexEntryT *index; // malloc'd; rmIndexUsed entries
uint16_t refNum; // 1..N, matches slot+1
} ResourceFileT;
typedef struct {
int inUse;
int fileSlot; // which ResourceFileT owns it
IigsResTypeT type;
IigsResIdT id;
void *data; // payload bytes
uint32_t size;
void *masterPtr; // master ptr cell -> &data
} HandleSlotT;
// --- State ---
// Declared volatile to defeat the GlobalOpt i1-narrowing pass that
// otherwise produces an `i1, zext` load the W65816 backend can't select.
// (See MEMORY.md: feedback_i1_load_custom.md.)
static volatile int gResourceReady = 0;
static ResourceFileT gFiles[IIGS_RES_MAX_FILES];
static HandleSlotT gHandles[IIGS_RES_MAX_HANDLES];
int closeResourceFile(ResourceRefNumT refNum) {
if (refNum == 0 || refNum > IIGS_RES_MAX_FILES) {
return RES_ERR_BAD_HANDLE;
}
int slot = (int)refNum - 1;
if (!gFiles[slot].inUse) {
return RES_ERR_BAD_HANDLE;
}
// Free every cached handle owned by this file.
for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
if (gHandles[i].inUse && gHandles[i].fileSlot == slot) {
freeHandleSlot(i);
}
}
if (gFiles[slot].index) {
free(gFiles[slot].index);
gFiles[slot].index = (ResourceIndexEntryT *)0;
}
if (gFiles[slot].fp) {
fclose(gFiles[slot].fp);
gFiles[slot].fp = (FILE *)0;
}
gFiles[slot].inUse = 0;
return RES_OK;
}
static int findHandleByPtr(void **handle) {
if (!handle) {
return -1;
}
for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
if (gHandles[i].inUse && (void **)&gHandles[i].data == handle) {
return i;
}
}
return -1;
}
static int findHandleByTypeId(IigsResTypeT type, IigsResIdT id) {
for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
if (gHandles[i].inUse && gHandles[i].type == type && gHandles[i].id == id) {
return i;
}
}
return -1;
}
static int findHandleSlot(void) {
for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
if (!gHandles[i].inUse) {
return i;
}
}
return -1;
}
static int findOpenFileSlot(void) {
for (int i = 0; i < IIGS_RES_MAX_FILES; i++) {
if (!gFiles[i].inUse) {
return i;
}
}
return -1;
}
static int freeHandleSlot(int slot) {
if (slot < 0 || slot >= IIGS_RES_MAX_HANDLES) {
return RES_ERR_BAD_HANDLE;
}
if (!gHandles[slot].inUse) {
return RES_ERR_BAD_HANDLE;
}
if (gHandles[slot].data) {
free(gHandles[slot].data);
gHandles[slot].data = (void *)0;
}
gHandles[slot].inUse = 0;
gHandles[slot].fileSlot = -1;
gHandles[slot].type = 0;
gHandles[slot].id = 0;
gHandles[slot].size = 0;
return RES_OK;
}
uint32_t getResourceSize(void **handle) {
int slot = findHandleByPtr(handle);
if (slot < 0) {
return 0;
}
return gHandles[slot].size;
}
// Convenience wrapper kept for backwards compat with the old probe.
// Scans the cache + open files for (type, id) and reports the size.
uint32_t iigsGetResourceSize(IigsResTypeT resType, IigsResIdT resId, int *err) {
if (!gResourceReady) {
if (err) {
*err = RES_ERR_NOT_STARTED;
}
return 0;
}
int hSlot = findHandleByTypeId(resType, resId);
if (hSlot >= 0) {
if (err) {
*err = RES_OK;
}
return gHandles[hSlot].size;
}
// Not cached - scan every open file's index for the entry.
for (int f = 0; f < IIGS_RES_MAX_FILES; f++) {
if (!gFiles[f].inUse || !gFiles[f].index) {
continue;
}
uint32_t n = gFiles[f].hdr.rmIndexUsed;
for (uint32_t i = 0; i < n; i++) {
ResourceIndexEntryT *e = &gFiles[f].index[i];
if (e->rType == resType && e->rID == resId) {
if (err) {
*err = RES_OK;
}
return e->rSize;
}
}
}
if (err) {
*err = RES_ERR_NOT_FOUND;
}
return 0;
}
// Convenience wrapper kept for backwards compat with the old probe.
void **iigsLoadResource(IigsResTypeT resType, IigsResIdT resId, int *err) {
return loadResource(resType, resId, err);
}
// Reads the 20-byte rIndex table for a freshly-opened file. Returns
// RES_OK or an RES_ERR_* code. Caller has populated gFiles[slot].hdr.
static int loadIndex(int fileSlot) {
ResourceFileT *rf = &gFiles[fileSlot];
uint32_t n = rf->hdr.rmIndexUsed;
if (n == 0) {
rf->index = (ResourceIndexEntryT *)0;
return RES_OK;
}
// Sanity-check against malloc'ing absurd amounts.
if (n > 1024) {
return RES_ERR_TOOLBOX;
}
ResourceIndexEntryT *idx = (ResourceIndexEntryT *)malloc(sizeof(ResourceIndexEntryT) * n);
if (!idx) {
return RES_ERR_NO_MEM;
}
if (fseek(rf->fp, (long)rf->hdr.rmToIndex, 0) != 0) {
free(idx);
return RES_ERR_TOOLBOX;
}
for (uint32_t i = 0; i < n; i++) {
uint16_t t;
uint32_t id;
uint32_t off;
uint16_t attr;
uint32_t sz;
uint32_t h;
if (readU16(rf->fp, &t) != 0 ||
readU32(rf->fp, &id) != 0 ||
readU32(rf->fp, &off) != 0 ||
readU16(rf->fp, &attr) != 0 ||
readU32(rf->fp, &sz) != 0 ||
readU32(rf->fp, &h) != 0) {
free(idx);
return RES_ERR_TOOLBOX;
}
idx[i].rType = t;
idx[i].rID = id;
idx[i].rOffset = off;
idx[i].rAttr = attr;
idx[i].rSize = sz;
idx[i].rHandle = h;
}
rf->index = idx;
return RES_OK;
}
void **loadResource(IigsResTypeT type, IigsResIdT id, int *err) {
if (!gResourceReady) {
if (err) {
*err = RES_ERR_NOT_STARTED;
}
return (void **)0;
}
// Cache hit?
int hSlot = findHandleByTypeId(type, id);
if (hSlot >= 0) {
if (err) {
*err = RES_OK;
}
return (void **)&gHandles[hSlot].data;
}
// Cache miss - find the resource in any open file.
for (int f = 0; f < IIGS_RES_MAX_FILES; f++) {
if (!gFiles[f].inUse || !gFiles[f].index) {
continue;
}
uint32_t n = gFiles[f].hdr.rmIndexUsed;
for (uint32_t i = 0; i < n; i++) {
ResourceIndexEntryT *e = &gFiles[f].index[i];
if (e->rType != type || e->rID != id) {
continue;
}
int slot = findHandleSlot();
if (slot < 0) {
if (err) {
*err = RES_ERR_NO_MEM;
}
return (void **)0;
}
void *bytes = readPayload(f, e->rOffset, e->rSize);
if (!bytes) {
if (err) {
*err = RES_ERR_TOOLBOX;
}
return (void **)0;
}
gHandles[slot].inUse = 1;
gHandles[slot].fileSlot = f;
gHandles[slot].type = type;
gHandles[slot].id = id;
gHandles[slot].data = bytes;
gHandles[slot].size = e->rSize;
if (err) {
*err = RES_OK;
}
return (void **)&gHandles[slot].data;
}
}
if (err) {
*err = RES_ERR_NOT_FOUND;
}
return (void **)0;
}
ResourceRefNumT openResourceFile(const char *path, uint8_t accessByte, uint16_t fileType, int *err) {
(void)accessByte;
(void)fileType;
if (!path) {
if (err) {
*err = RES_ERR_NOT_FOUND;
}
return 0;
}
int slot = findOpenFileSlot();
if (slot < 0) {
if (err) {
*err = RES_ERR_NO_MEM;
}
return 0;
}
FILE *fp = fopen(path, "rb");
if (!fp) {
if (err) {
*err = RES_ERR_NOT_FOUND;
}
return 0;
}
ResourceFileT *rf = &gFiles[slot];
if (readMapHeader(fp, &rf->hdr) != 0) {
fclose(fp);
if (err) {
*err = RES_ERR_TOOLBOX;
}
return 0;
}
rf->fp = fp;
rf->inUse = 1;
rf->refNum = (uint16_t)(slot + 1);
rf->index = (ResourceIndexEntryT *)0;
int rc = loadIndex(slot);
if (rc != RES_OK) {
fclose(fp);
rf->fp = (FILE *)0;
rf->inUse = 0;
if (err) {
*err = rc;
}
return 0;
}
gResourceReady = 1;
if (err) {
*err = RES_OK;
}
return rf->refNum;
}
// Allocates and reads `size` bytes at `offset` from the file at
// `fileSlot`. Returns NULL on any error.
static void *readPayload(int fileSlot, uint32_t offset, uint32_t size) {
if (size == 0) {
return (void *)0;
}
void *buf = malloc(size);
if (!buf) {
return (void *)0;
}
FILE *fp = gFiles[fileSlot].fp;
if (fseek(fp, (long)offset, 0) != 0) {
free(buf);
return (void *)0;
}
size_t got = fread(buf, 1, size, fp);
if (got != size) {
free(buf);
return (void *)0;
}
return buf;
}
// Reads a little-endian uint16 from `f`. Returns 0 on success.
static int readU16(FILE *f, uint16_t *out) {
uint8_t b[2];
if (fread(b, 1, 2, f) != 2) {
return -1;
}
*out = (uint16_t)(b[0] | ((uint16_t)b[1] << 8));
return 0;
}
// Reads a little-endian uint32 from `f`. Returns 0 on success.
static int readU32(FILE *f, uint32_t *out) {
uint8_t b[4];
if (fread(b, 1, 4, f) != 4) {
return -1;
}
*out = (uint32_t)b[0] |
((uint32_t)b[1] << 8) |
((uint32_t)b[2] << 16) |
((uint32_t)b[3] << 24);
return 0;
}
// Reads the 24-byte rResourceMap header at offset 0.
static int readMapHeader(FILE *f, ResourceMapHeaderT *hdr) {
if (fseek(f, 0L, 0) != 0) {
return -1;
}
if (readU16(f, &hdr->rmVersion) != 0) return -1;
if (readU32(f, &hdr->rmToIndex) != 0) return -1;
if (readU16(f, &hdr->rmFileNum) != 0) return -1;
if (readU16(f, &hdr->rmID) != 0) return -1;
if (readU32(f, &hdr->rmIndexSize) != 0) return -1;
if (readU32(f, &hdr->rmIndexUsed) != 0) return -1;
if (readU16(f, &hdr->rmFreeListSize) != 0) return -1;
if (readU16(f, &hdr->rmFreeListUsed) != 0) return -1;
if (readU16(f, &hdr->rmPad) != 0) return -1;
return 0;
}
int releaseResource(int verb, void **handle) {
int slot = findHandleByPtr(handle);
if (slot < 0) {
return RES_ERR_BAD_HANDLE;
}
if (verb == 0) {
// Soft release: keep cached payload. Real toolbox would decrement
// a use-count; we just succeed.
return RES_OK;
}
return freeHandleSlot(slot);
}
int resourceProbeInit(void) {
// Zero the tables. Safe to call repeatedly - subsequent calls do
// not touch already-open files.
if (!gResourceReady) {
for (int i = 0; i < IIGS_RES_MAX_FILES; i++) {
gFiles[i].inUse = 0;
gFiles[i].fp = (FILE *)0;
gFiles[i].index = (ResourceIndexEntryT *)0;
gFiles[i].refNum = 0;
}
for (int i = 0; i < IIGS_RES_MAX_HANDLES; i++) {
gHandles[i].inUse = 0;
gHandles[i].fileSlot = -1;
gHandles[i].data = (void *)0;
gHandles[i].size = 0;
}
gResourceReady = 1;
}
return RES_OK;
}
int resourceRuntimeEnabled(void) {
return gResourceReady;
}