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