479 lines
13 KiB
C
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;
|
|
}
|