Mercurial > hg > dmlib
view dmres.c @ 510:43ea59887c69
Start work on making C64 formats encoding possible by changing DMDecodeOps
to DMEncDecOps and adding fields and op enums for custom encode functions, renaming,
etc. Split generic op sanity checking into a separate function in
preparation for its use in generic encoding function.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 19 Nov 2012 15:06:01 +0200 |
parents | bc1da1f4cb4b |
children | b60220fd1669 |
line wrap: on
line source
/* * dmlib * -- Resource management * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2003-2012 Tecnic Software productions (TNSP) */ #include "dmres.h" #include <time.h> DMResource *dmres_new(DMResourceLib *lib, const char *filename, int flags, size_t size) { DMResource *node = dmMalloc0(sizeof(DMResource)); if (node == NULL) return NULL; node->lib = lib; node->filename = dm_strdup(filename); node->flags = flags; node->dataSize = size; return node; } void dmres_free_res_data(DMResource *node) { if (node->rdata != NULL && node->rops != NULL && node->rops->free != NULL) { node->rops->free(node); } node->rdata = NULL; node->flags &= !DMF_LOADED_RES; } void dmres_free_raw_data(DMResource *node) { dmFree(node->data); node->data = NULL; node->flags &= !DMF_LOADED_RAW; } void dmres_purge_raw_data(DMResource *node) { if ((node->flags & DMF_PRELOAD_RAW) == 0 && (node->flags & DMF_LOADED_RAW) && node->data != NULL) dmres_free_raw_data(node); } void dmres_free(DMResource *node) { if (node != NULL) { dmres_free_res_data(node); dmres_free_raw_data(node); dmFree(node->filename); dmFree(node); } } void dmres_insert(DMResourceLib *lib, DMResource * node) { if (lib == NULL) return; node->lib = lib; if (lib->resources != NULL) { node->prev = lib->resources->prev; lib->resources->prev->next = node; lib->resources->prev = node; } else { lib->resources = node->prev = node; } node->next = NULL; } void dmres_delete(DMResourceLib *lib, DMResource * node) { if (lib == NULL) return; if (node->prev) node->prev->next = node->next; if (node->next) node->next->prev = node->prev; else lib->resources->prev = node->prev; node->prev = node->next = NULL; } DMResource * dmres_find(DMResourceLib *lib, const char *filename) { DMResource *node, *found = NULL; if (lib == NULL) return NULL; dmMutexLock(lib->mutex); for (node = lib->resources; node != NULL; node = node->next) { if (strcmp(node->filename, filename) == 0) { found = node; break; } } dmMutexUnlock(lib->mutex); return found; } #ifdef DM_USE_STDIO /* Basic stdio file routines */ static int dm_stdio_fopen(DMResource *handle) { char *rfilename = dm_strdup_printf("%s%s", DMRES_DATA_PATH, handle->filename); if (rfilename == NULL) return DMERR_MALLOC; handle->fh = fopen(rfilename, "rb"); dmFree(rfilename); handle->error = dmGetErrno(); return (handle->fh != NULL) ? DMERR_OK : DMERR_FOPEN; } static void dm_stdio_fclose(DMResource * f) { if (f->fh != NULL) { fclose(f->fh); f->fh = NULL; } } static int dm_stdio_ferror(DMResource * f) { return f->error; } static int dm_stdio_fseek(DMResource *f, const off_t pos, const int whence) { int ret = fseek(f->fh, pos, whence); f->error = dmGetErrno(); return ret; } static off_t dm_stdio_fsize(DMResource *f) { off_t savePos, fileSize; // Check if the size is cached if (f->dataSize != 0) return f->dataSize; // Get file size savePos = ftello(f->fh); if (fseeko(f->fh, 0L, SEEK_END) != 0) { f->error = dmGetErrno(); return -1; } fileSize = ftello(f->fh); if (fseeko(f->fh, savePos, SEEK_SET) != 0) { f->error = dmGetErrno(); return -1; } f->dataSize = fileSize; return fileSize; } static off_t dm_stdio_ftell(DMResource * f) { return ftell(f->fh); } static BOOL dm_stdio_feof(DMResource * f) { return feof(f->fh); } static int dm_stdio_fgetc(DMResource * f) { int ret = fgetc(f->fh); f->error = dmGetErrno(); return ret; } static int dm_stdio_fputc(int v, DMResource * f) { int ret = fputc(v, f->fh); f->error = dmGetErrno(); return ret; } static size_t dm_stdio_fread(void *ptr, size_t size, size_t nmemb, DMResource * f) { size_t ret = fread(ptr, size, nmemb, f->fh); f->error = dmGetErrno(); return ret; } static size_t dm_stdio_fwrite(void *ptr, size_t size, size_t nmemb, DMResource * f) { size_t ret = fwrite(ptr, size, nmemb, f->fh); f->error = dmGetErrno(); return ret; } static int dm_stdio_preload(DMResource *handle) { int ret = dm_stdio_fopen(handle); if (ret != DMERR_OK) return ret; dm_stdio_fsize(handle); handle->data = dmMalloc(handle->dataSize); if (handle->data == NULL) return DMERR_MALLOC; if (dm_stdio_fread(handle->data, sizeof(Uint8), handle->dataSize, handle) != handle->dataSize) return DMERR_FREAD; return DMERR_OK; } DMResourceOps dfStdioFileOps = { dm_stdio_ferror, dm_stdio_fseek, dm_stdio_fsize, dm_stdio_ftell, dm_stdio_feof, dm_stdio_fgetc, dm_stdio_fputc, dm_stdio_fread, dm_stdio_fwrite, dm_stdio_fopen, dm_stdio_fclose, dm_stdio_preload }; DMResourceOps dfStdioFHOps = { dm_stdio_ferror, dm_stdio_fseek, dm_stdio_fsize, dm_stdio_ftell, dm_stdio_feof, dm_stdio_fgetc, dm_stdio_fputc, dm_stdio_fread, dm_stdio_fwrite, NULL, NULL, NULL }; #endif // Some mingw/windows headers define these as macros, which is bad for us #ifdef __WIN32 #undef ferror #undef feof #endif /* * PACK file routines */ #ifdef DM_USE_PACKFS static int dm_pack_preload(DMResource *handle) { DMPackEntry *node; int res = DMERR_OK, cres, cdataLeft; z_stream cstream; Uint8 * cbuffer = NULL; if (handle->lib == NULL || handle->lib->packFile == NULL) return DMERR_NULLPTR; // Search PACK nodelist for file if ((node = dm_pack_find(handle->lib->packFile->entries, handle->filename)) == NULL) { dmError("Entry '%s' not found in PACK file.\n", handle->filename); res = DMERR_NOT_FOUND; goto error; } // Seek to entry if (fseek(handle->lib->packFile->file, node->offset, SEEK_SET) == -1) { dmError("Could not seek node position in PACK file.\n"); res = DMERR_FSEEK; goto error; } // Allocate a structures and buffers cbuffer = (Uint8 *) dmMalloc(DPACK_TMPSIZE); if (cbuffer == NULL) { res = DMERR_MALLOC; goto error; } // Initialize fields handle->dataOffset = 0; handle->dataSize = node->size; handle->data = (Uint8 *) dmMalloc(node->size); if (handle->data == NULL) { res = DMERR_MALLOC; goto error; } // Initialize decompression cstream.zalloc = (alloc_func) Z_NULL; cstream.zfree = (free_func) Z_NULL; cstream.opaque = (voidpf) Z_NULL; cstream.next_out = handle->data; cstream.avail_out = handle->dataSize; cdataLeft = node->length; cres = inflateInit(&(cstream)); if (cres != Z_OK) { dmError("Could not initialize zlib stream inflation.\n"); res = DMERR_INIT_FAIL; goto error; } // Uncompress the data while (cdataLeft > 0 && cstream.avail_out > 0 && cres == Z_OK) { cstream.avail_in = fread( cbuffer, sizeof(Uint8), (cdataLeft >= DPACK_TMPSIZE) ? DPACK_TMPSIZE : cdataLeft, handle->lib->packFile->file); cdataLeft -= cstream.avail_in; cstream.next_in = cbuffer; cres = inflate(&cstream, Z_FULL_FLUSH); } // Cleanup inflateEnd(&(cstream)); error: dmFree(cbuffer); return res; } static void dm_pack_fclose(DMResource * f) { f->dataSize = 0; f->dataOffset = 0; dmFree(f->data); f->data = NULL; } #endif static int dm_mem_ferror(DMResource * f) { return f->error; } static int dm_mem_fseek(DMResource * f, const off_t offset, const int whence) { off_t newPos; // Calculate the new position switch (whence) { case SEEK_SET: newPos = offset; break; case SEEK_CUR: newPos = f->dataOffset + offset; break; case SEEK_END: newPos = f->dataSize + offset; break; default: return -1; } // Set the new position f->dataOffset = newPos; // Check the new position if (newPos < 0 && (size_t) newPos >= f->dataSize) return -1; return 0; } static off_t dm_mem_fsize(DMResource * f) { return f->dataSize; } static off_t dm_mem_ftell(DMResource * f) { return f->dataOffset; } static BOOL dm_mem_feof(DMResource * f) { // Check for EOF if ((size_t) f->dataOffset <= f->dataSize) return FALSE; else return TRUE; } static int dm_mem_fgetc(DMResource * f) { // Check for EOF if ((size_t) f->dataOffset < f->dataSize) return f->data[f->dataOffset++]; else return EOF; } static size_t dm_mem_fread(void *buf, size_t size, size_t nmemb, DMResource * f) { size_t length = (size * nmemb); // Check if we can read the whole chunk if (((size_t) f->dataOffset + length) >= f->dataSize) { nmemb = (f->dataSize - f->dataOffset) / size; length = size * nmemb; } memcpy(buf, f->data + f->dataOffset, length); f->dataOffset += length; return nmemb; } static int dm_mem_fputc(int ch, DMResource * f) { // Check for EOF if ((size_t) f->dataOffset < f->dataSize) { f->data[f->dataOffset++] = ch; return ch; } else return EOF; } static size_t dm_mem_fwrite(void *buf, size_t size, size_t nmemb, DMResource * f) { size_t length = (size * nmemb); // Check if we can write the whole chunk if (((size_t) f->dataOffset + length) >= f->dataSize) { nmemb = (f->dataSize - f->dataOffset) / size; length = size * nmemb; } if (length > 0) { memcpy(f->data + f->dataOffset, buf, length); f->dataOffset += length; } return nmemb; } #ifdef DM_USE_PACKFS DMResourceOps dfPackFileOps = { dm_mem_ferror, dm_mem_fseek, dm_mem_fsize, dm_mem_ftell, dm_mem_feof, dm_mem_fgetc, NULL, dm_mem_fread, NULL, NULL, dm_pack_fclose, dm_pack_preload }; #endif DMResourceOps dfMemIOFileOps = { dm_mem_ferror, dm_mem_fseek, dm_mem_fsize, dm_mem_ftell, dm_mem_feof, dm_mem_fgetc, dm_mem_fputc, dm_mem_fread, dm_mem_fwrite, NULL, NULL, NULL }; /* FS file handling functions. These functions call the actual * functions depending on where the file is located. */ static void dmf_init_fops(DMResource *handle) { // Check fops if (handle->fops == NULL) { #ifdef DM_USE_PACKFS if (handle->lib->flags & DRF_USE_PACK) handle->fops = &dfPackFileOps; #ifdef DM_USE_STDIO else handle->fops = &dfStdioFileOps; #else handle->fops = &dfPackFileOps; #endif #else handle->fops = NULL; #endif } } int dmf_preload(DMResource *handle) { int ret = DMERR_INIT_FAIL; // Check if we want to preload raw data? if (((handle->flags & DMF_PRELOAD_RAW) || (handle->lib->flags & DRF_PRELOAD_ALL)) && (handle->flags & DMF_LOADED_RAW) == 0 && handle->fops->preload != NULL) { ret = handle->fops->preload(handle); if (ret == DMERR_OK) { handle->flags |= DMF_LOADED_RAW; } } else { if (handle->fops->fopen != NULL) ret = handle->fops->fopen(handle); else if (handle->fops->preload != NULL) { ret = handle->fops->preload(handle); if (ret == DMERR_OK) { handle->flags |= DMF_LOADED_RAW; } } } // Check if resource data is to be preloaded if (((handle->flags & DMF_PRELOAD_RES) || (handle->lib->flags & DRF_PRELOAD_RES)) && (handle->flags & DMF_LOADED_RES) == 0 && handle->rops != NULL && handle->rops->load != NULL) { ret = handle->rops->load(handle); if (ret == DMERR_OK) { // Okay, mark as loaded handle->flags |= DMF_LOADED_RES; // Check if we can purge the raw data now if ((handle->flags & DMF_PERSIST) == 0) dmres_purge_raw_data(handle); } } return ret; } DMResource *dmf_open(DMResourceLib *lib, const char *filename) { int ret; DMResource *handle; // Check master directory for resource if ((handle = dmres_find(lib, filename)) == NULL) { #ifdef DM_USE_STDIO // Hmm.. does not exist? Fall back to a stdio file handle = dmres_new(lib, filename, 0, 0); if (handle == NULL) return NULL; handle->fops = &dfStdioFileOps; dmres_insert(lib, handle); #else // Stdio not enabled, fail return NULL; #endif } // Initialize file ops dmf_init_fops(handle); // Check if the data is preloaded if (handle->flags & DMF_LOADED_RAW) { dmres_ref(handle); return handle; } // Try preloading ret = dmf_preload(handle); if (ret == DMERR_OK) { dmres_ref(handle); return handle; } return NULL; } DMResource * dmf_create_memio(DMResourceLib *lib, const char *filename, Uint8 *buf, size_t len) { DMResource *handle; // Check master directory for resource if ((handle = dmres_find(lib, filename)) == NULL) { // Hmm.. does not exist? Fall back to a stdio file handle = dmres_new(lib, filename, DMF_LOADED_RAW, len); if (handle == NULL) return NULL; handle->fops = &dfMemIOFileOps; handle->data = buf; dmres_insert(lib, handle); } // Increase refcount dmres_ref(handle); return handle; } #ifdef DM_USE_STDIO DMResource * dmf_create_stdio(const char *filename, const char *mode) { DMResource *handle = dmres_new(NULL, filename, 0, 0); if (handle == NULL) return NULL; handle->fops = &dfStdioFileOps; handle->fh = fopen(filename, mode); handle->error = dmGetErrno(); if (handle->fh != NULL) { dmres_ref(handle); return handle; } else { dmres_free(handle); return NULL; } } DMResource * dmf_create_stdio_stream(FILE *fh) { DMResource *handle = dmres_new(NULL, "", 0, 0); if (handle == NULL) return NULL; handle->fops = &dfStdioFHOps; handle->fh = fh; dmres_ref(handle); return handle; } #endif void dmf_close(DMResource * f) { if (f == NULL) return; if (f->fops->fclose != NULL) f->fops->fclose(f); dmres_unref(f); } int dmferror(DMResource * f) { f->atime = time(NULL); return f->fops->ferror(f); } int dmfseek(DMResource * f, off_t offset, int whence) { f->atime = time(NULL); return f->fops->fseek(f, offset, whence); } off_t dmfsize(DMResource * f) { f->atime = time(NULL); return f->fops->fsize(f); } off_t dmftell(DMResource * f) { f->atime = time(NULL); return f->fops->ftell(f); } BOOL dmfeof(DMResource * f) { f->atime = time(NULL); return f->fops->feof(f); } int dmfgetc(DMResource * f) { f->atime = time(NULL); return f->fops->fgetc(f); } int dmfputc(int v, DMResource * f) { f->atime = time(NULL); return f->fops->fputc(v, f); } size_t dmfread(void *ptr, size_t size, size_t nmemb, DMResource * f) { f->atime = time(NULL); return f->fops->fread(ptr, size, nmemb, f); } size_t dmfwrite(void *ptr, size_t size, size_t nmemb, DMResource * f) { f->atime = time(NULL); return f->fops->fwrite(ptr, size, nmemb, f); } char *dmfgets(char *s, int size, DMResource * f) { char *p = s, c; int n = 0; while ((c = f->fops->fgetc(f)) != EOF) { n++; if (c == '\n') break; else if (n < size - 1) *p++ = c; } *p = 0; return (n > 0) ? s : NULL; } int dmres_ref(DMResource *node) { if (node->lib != NULL) dmMutexLock(node->lib->mutex); node->atime = time(NULL); node->refcount++; if (node->lib != NULL) dmMutexUnlock(node->lib->mutex); return node->refcount; } int dmres_unref(DMResource *node) { if (node->lib != NULL) dmMutexLock(node->lib->mutex); node->refcount--; if (node->lib != NULL) dmMutexUnlock(node->lib->mutex); return node->refcount; } #define NADDFLAG(flg, ch) \ do { \ if ((flags & (flg)) && offs < size - 1) \ str[offs++] = ch; \ } while (0) void dmres_flags_to_symbolic(char *str, size_t size, int flags) { size_t offs = 0; NADDFLAG(DMF_PRELOAD_RAW, 'r'); NADDFLAG(DMF_PRELOAD_RES, 'e'); NADDFLAG(DMF_PERSIST, 'p'); NADDFLAG(DMF_STREAM, 's'); if (offs < size) str[offs] = 0; } #undef NADDFLAG int dmres_symbolic_to_flags(const char *str) { int offs, flags; for (flags = offs = 0; str[offs]; offs++) switch (tolower(str[offs])) { case 'r': flags |= DMF_PRELOAD_RAW; break; case 'e': flags |= DMF_PRELOAD_RES; break; case 'p': flags |= DMF_PERSIST; break; case 's': flags |= DMF_STREAM; break; } return flags; } int dmres_load_resfile(DMResourceLib *lib, const char *filename) { int ret = DMERR_OK; char line[256]; FILE *f = fopen(filename, "r"); if (f == NULL) return DMERR_FOPEN; dmMutexLock(lib->mutex); while (fgets(line, sizeof(line) - 1, f) != NULL) { int fnstart, fsep; for (fnstart = 0; isspace(line[fnstart]); fnstart++); for (fsep = fnstart; line[fsep] && line[fsep] != '|'; fsep++); if (line[fsep] == '|') { int flags, i; for (i = fsep - 1; i > 0 && isspace(line[i]); i--) line[i] = 0; for (i = fsep; isspace(line[i]); i++); } } dmMutexUnlock(lib->mutex); fclose(f); return ret; } int dmres_write_resfile(DMResourceLib *lib, const char *filename) { int ret; DMResource *node; FILE *f = fopen(filename, "w"); if (f == NULL) return DMERR_FOPEN; dmMutexLock(lib->mutex); for (node = lib->resources; node != NULL; node = node->next) { char tmp[64]; dmres_flags_to_symbolic(tmp, sizeof(tmp), node->flags); if (fprintf(f, "%s|%s\n", node->filename, tmp) < 0) { ret = DMERR_FWRITE; goto error; } } error: dmMutexUnlock(lib->mutex); fclose(f); return ret; } /* Resources subsystem initialization and shutdown routines */ int dmres_init(DMResourceLib **plib, const char *filename, const char *path, const int flags, int (*classifier)(DMResource *)) { DMResourceLib *lib; // Allocate the resource library structure if ((*plib = lib = dmMalloc0(sizeof(DMResourceLib))) == NULL) return DMERR_MALLOC; // Basic data lib->mutex = dmCreateMutex(); lib->flags = flags; lib->resPath = dm_strdup((path != NULL) ? path : DMRES_DATA_PATH); if (flags & DRF_USE_PACK) { #ifdef DM_USE_PACKFS int ret; DMPackEntry *node; lib->packFilename = dm_strdup((filename != NULL) ? filename : DMRES_DATA_PACK); // Initialize PACK, open as read-only ret = dm_pack_open(lib->packFilename, &lib->packFile, TRUE); if (ret != DMERR_OK) { dmError("Error opening PACK file '%s', #%i: %s\n", lib->packFilename, ret, dmErrorStr(ret)); return DMERR_INIT_FAIL; } // Initialize resources from a PACK file for (node = lib->packFile->entries; node != NULL; node = node->next) { DMResource *res = dmres_new(lib, node->filename, node->resFlags & DMF_MASK, node->size); if (res == NULL) { dmError("Could not allocate memory for resource node '%s' [0x%08x], %d.\n", node->filename, node->resFlags, node->size); return DMERR_INIT_FAIL; } dmres_insert(lib, res); } #else // PACK not compiled in, FAIL! return DMERR_INIT_FAIL; #endif } else { // Initialize resources from a resource directory char *resFilename = dm_strdup_printf("%s%s", lib->resPath, DMRES_RES_FILE); int ret = dmres_load_resfile(lib, resFilename); dmFree(resFilename); if (ret != DMERR_OK) return DMERR_INIT_FAIL; } // Okay, classify resources if (lib->resources != NULL && classifier != NULL) { DMResource *node; for (node = lib->resources; node != NULL; node = node->next) { int ret = classifier(node); if (ret != DMERR_OK) return ret; } } // Initialization complete return DMERR_OK; } int dmres_close(DMResourceLib *lib) { DMResource *node; if (lib == NULL) return DMERR_NULLPTR; dmMutexLock(lib->mutex); // Shutdown possible subsystems #ifdef DM_USE_PACKFS if (lib->flags & DRF_USE_PACK) { int res = dm_pack_close(lib->packFile); if (res != DMERR_OK) { dmError("Error closing PACK, #%i: %s\n", res, dmErrorStr(res)); } dmFree(lib->packFilename); } #endif // Free resource entries node = lib->resources; while (node != NULL) { DMResource *next = node->next; dmres_free(node); node = next; } // Etc. dmFree(lib->resPath); dmMutexUnlock(lib->mutex); dmDestroyMutex(lib->mutex); return DMERR_OK; } int dmres_preload(DMResourceLib *lib, BOOL start, int *loaded, int *total) { int ret = DMERR_OK; dmMutexLock(lib->mutex); // Initialize preloading if (lib->preload == NULL || start) { DMResource *node; lib->preload = lib->resources; *loaded = 0; *total = 0; // Calculate total number of resources to be preloaded for (node = lib->resources; node != NULL; node = node->next) { if ((lib->flags & (DRF_PRELOAD_ALL | DRF_PRELOAD_RES)) || (node->flags & (DMF_PRELOAD_RAW | DMF_PRELOAD_RES))) (*total)++; } } else if (lib->preload != NULL) { // Initialize fops and preload dmf_init_fops(lib->preload); if ((ret = dmf_preload(lib->preload)) != DMERR_OK) goto error; (*loaded)++; lib->preload = lib->preload->next; } dmMutexUnlock(lib->mutex); return (lib->preload == NULL) ? DMERR_OK : DMERR_PROGRESS; error: dmMutexUnlock(lib->mutex); return ret; } void dmres_prune(DMResourceLib *lib, int agems, int flags) { DMResource *node; int currtime = time(NULL); dmMutexLock(lib->mutex); for (node = lib->resources; node != NULL; node = node->next) { // Check if node has refcount of 0 and is // not marked as persistent resource if (node->refcount == 0 && (node->flags & DMF_PERSIST) == 0 && (node->flags & (DMF_LOADED_RES | DMF_LOADED_RAW))) { // Check if we match either one of atime or mtime if (((flags & DMPRUNE_ATIME) && currtime - node->atime >= agems) || ((flags & DMPRUNE_MTIME) && currtime - node->mtime >= agems)) { dmres_free_res_data(node); dmres_free_raw_data(node); } } } dmMutexUnlock(lib->mutex); } /* Helper resource access routines */ int dmf_read_str(DMResource *f, Uint8 *s, size_t l) { return dmfread(s, sizeof(Uint8), l, f) == l; } BOOL dmf_read_byte(DMResource *f, Uint8 *val) { int tmp = dmfgetc(f); *val = tmp; return tmp != EOF; } #define DM_DEFINE_FUNC(xname, xtype, xmacro) \ BOOL dmf_read_ ## xname (DMResource *f, xtype *v) { \ xtype result; \ if (dmfread(&result, sizeof( xtype ), 1, f) != 1) \ return FALSE; \ *v = DM_ ## xmacro ## _TO_NATIVE (result); \ return TRUE; \ } DM_DEFINE_FUNC(le16, Uint16, LE16) DM_DEFINE_FUNC(le32, Uint32, LE32) DM_DEFINE_FUNC(be16, Uint16, BE16) DM_DEFINE_FUNC(be32, Uint32, BE32) #ifdef DM_HAVE_64BIT DM_DEFINE_FUNC(le64, Uint64, LE64) DM_DEFINE_FUNC(be64, Uint64, BE64) #endif