view src/dmres.c @ 2576:812b16ee49db

I had been living under apparent false impression that "realfft.c" on which the FFT implementation in DMLIB was basically copied from was released in public domain at some point, but it could very well be that it never was. Correct license is (or seems to be) GNU GPL. Thus I removing the code from DMLIB, and profusely apologize to the author, Philip Van Baren. It was never my intention to distribute code based on his original work under a more liberal license than originally intended.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 11 Mar 2022 16:32:50 +0200
parents 92b93a12c014
children 9807ae37ad69
line wrap: on
line source

/*
 * dmlib
 * -- Resource management
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2003-2015 Tecnic Software productions (TNSP)
 */
#include "dmres.h"
#include <time.h>

#ifdef DM_USE_PACKFS
#    ifdef DM_USE_ZLIB
#        include <zlib.h>
#    else
#        include "dmzlib.h"
#    endif
#endif

#ifdef DM_USE_STDIO
#    include <sys/types.h>
#    include <sys/stat.h>
#    include <unistd.h>
#    include <dirent.h>
#endif


static inline int dmLockLibMutex(DMResourceLib *lib)
{
    if (lib != NULL)
        return dmMutexLock(lib->mutex);
    else
        return DMERR_OK;
}


static inline int dmUnlockLibMutex(DMResourceLib *lib)
{
    if (lib != NULL)
        return dmMutexUnlock(lib->mutex);
    else
        return DMERR_OK;
}


DMResource *dmResourceNew(DMResourceLib *lib, const char *filename, const size_t size, const int flags)
{
    DMResource *node = dmMalloc0(sizeof(DMResource));
    if (node == NULL)
        return NULL;

    node->lib = lib;
    node->filename = dm_strdup(filename);
    node->memAlloc = node->memSize = size;
    node->flags = flags;

    return node;
}


void dmResourceFreeResData(DMResource *node)
{
    if (node->resData != NULL)
    {
        if (node->rops != NULL &&
            node->rops->free != NULL)
            node->rops->free(node);
        else
            dmFree(node->resData);
        node->resData = NULL;
    }
    node->flags &= ~DMF_LOADED_RES;
}


void dmResourceFreeRawData(DMResource *node)
{
    if ((node->flags & DMF_UNALLOCATED) == 0)
    {
        dmFree(node->memData);
        node->memData = NULL;
        node->flags &= ~DMF_LOADED_RAW;
    }
}


void dmResourceFree(DMResource *node)
{
    if (node != NULL)
    {
#ifdef DM_DEBUG
        dmLockLibMutex(node->lib);
        if (node->refcount > 0)
        {
            dmErrorMsg(
                "Attempting to dmResourceFree(%p) node that has resfs %d > 0: '%s'.\n"
                "FOPS=%p, fops->name=%s\n",
                (void *) node, node->refcount, node->filename,
                (void *) node->fops, (node->fops != NULL) ? node->fops->name : "UNDEF");
        }
        dmUnlockLibMutex(node->lib);
#endif
        dmResourceFreeResData(node);
        dmResourceFreeRawData(node);
        dmFree(node->filename);
        dmFree(node);
    }
}


void dmResourceInsert(DMResourceLib *lib, DMResource * node)
{
    if (lib == NULL || node == 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 dmResourceDelete(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 * dmResourceFind(DMResourceLib *lib, const char *filename)
{
    DMResource *node, *found = NULL;

    if (lib == NULL)
        return NULL;

    dmLockLibMutex(lib);

    for (node = lib->resources; node != NULL; node = node->next)
    {
        if (strcmp(node->filename, filename) == 0)
        {
            found = node;
            break;
        }
    }

    dmUnlockLibMutex(lib);

    return found;
}


#ifdef DM_USE_STDIO
/* Basic stdio file routines
 */
static int dm_stdio_fopen(DMResource *handle)
{
    char *dpath = handle->lib != NULL ? handle->lib->resPath : NULL;
    char *rfilename = dm_strdup_printf("%s%s", dpath != NULL ? dpath : "", handle->filename);
    if (rfilename == NULL)
        return DMERR_MALLOC;

    handle->fh = fopen(rfilename, "rb");
    dmFree(rfilename);

    handle->status = dmGetErrno();
    return (handle->fh != NULL) ? DMERR_OK : DMERR_FOPEN;
}


static void dm_stdio_fclose(DMResource *fh)
{
    if (fh->fh != NULL)
    {
        fclose(fh->fh);
        fh->fh = NULL;
    }
}


static int dm_stdio_ferror(DMResource *fh)
{
    return fh->status;
}


static off_t dm_stdio_ftell(DMResource *fh)
{
    return ftello(fh->fh);
}


static int dm_stdio_fseek(DMResource *fh, const off_t pos, const int whence)
{
    int ret = fseeko(fh->fh, pos, whence);
    fh->status = dmGetErrno();
    return ret;
}


static int dm_stdio_freset(DMResource *fh)
{
    if (fh->fh != NULL)
        return dm_stdio_fseek(fh, 0, SEEK_SET);
    else
        return DMERR_OK;
}


static off_t dm_stdio_fsize(DMResource *fh)
{
    off_t savePos, fileSize;

    // Check if the size is cached
    if (fh->memSize != 0)
        return fh->memSize;

    // Get file size
    if ((savePos = dm_stdio_ftell(fh)) < 0)
        return -1;

    if (dm_stdio_fseek(fh, 0, SEEK_END) != 0)
        return -1;

    if ((fileSize = dm_stdio_ftell(fh)) < 0)
        return -1;

    if (dm_stdio_fseek(fh, savePos, SEEK_SET) != 0)
        return -1;

    fh->memSize = fileSize;
    return fileSize;
}


static BOOL dm_stdio_feof(DMResource *fh)
{
    return feof(fh->fh);
}


static int dm_stdio_fgetc(DMResource *fh)
{
    int ret = fgetc(fh->fh);
    fh->status = dmGetErrno();
    return ret;
}


static int dm_stdio_fputc(int v, DMResource *fh)
{
    int ret = fputc(v, fh->fh);
    fh->status = dmGetErrno();
    return ret;
}


static size_t dm_stdio_fread(void *ptr, size_t size, size_t nmemb, DMResource *fh)
{
    size_t ret = fread(ptr, size, nmemb, fh->fh);
    fh->status = dmGetErrno();
    return ret;
}


static size_t dm_stdio_fwrite(const void *ptr, size_t size, size_t nmemb, DMResource *fh)
{
    size_t ret = fwrite(ptr, size, nmemb, fh->fh);
    fh->status = 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->memData = dmMalloc(handle->memSize);
    if (handle->memData == NULL)
        return DMERR_MALLOC;

    handle->memAlloc = handle->memSize;

    if (dm_stdio_fread(handle->memData, sizeof(Uint8), handle->memSize, handle) != handle->memSize)
        return DMERR_FREAD;

    return DMERR_OK;
}


DMResourceOps dfStdioFileOps =
{
    "Stdio",

    dm_stdio_fopen,
    dm_stdio_fclose,
    dm_stdio_preload,

    dm_stdio_freset,
    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,
};

DMResourceOps dfStdioFHOps =
{
    "StdioFH",

    NULL,
    NULL,
    NULL,

    dm_stdio_freset,
    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,

};
#endif


// Some mingw/windows headers define these as macros, which is bad for us
#ifdef DM_PLAT_WINDOWS
#  undef ferror
#  undef feof
#endif


/*
 * PACK file routines
 */
#ifdef DM_USE_PACKFS

#ifdef DM_USE_ZLIB

#define	DMRES_TMPBUF_SIZE   (128 * 1024)

static int dm_pack_decompress(DMResource *handle, DMPackEntry *node)
{
    int ret = DMERR_OK, zret, cdataLeft;
    Uint8 * cbuffer = NULL;
    z_stream zstr;
    BOOL zinit = FALSE;

    // Allocate a structures and buffers
    if ((cbuffer = dmMalloc(DMRES_TMPBUF_SIZE)) == NULL)
    {
        ret = DMERR_MALLOC;
        goto out;
    }

    // Initialize decompression
    memset(&zstr, 0, sizeof(zstr));
    zstr.next_out = handle->memData;
    zstr.avail_out = handle->memSize;
    cdataLeft = node->csize;

    if ((zret = inflateInit(&zstr)) != Z_OK)
    {
        ret = dmErrorDBG(DMERR_INIT_FAIL,
            "Could not initialize zlib stream decompression.\n");
        goto out;
    }
    zinit = TRUE;

    // Uncompress the data
    while (cdataLeft > 0 && zstr.avail_out > 0 && zret == Z_OK)
    {
        zstr.avail_in = fread(
            cbuffer, sizeof(Uint8),
            (cdataLeft >= DMRES_TMPBUF_SIZE) ? DMRES_TMPBUF_SIZE : cdataLeft,
            handle->lib->packFile->file);

        cdataLeft -= zstr.avail_in;
        zstr.next_in = cbuffer;
        zret = inflate(&zstr, Z_FULL_FLUSH);
    }


out:
    // Cleanup
    if (zinit)
        inflateEnd(&zstr);

    dmFree(cbuffer);
    return ret;
}

#else

static int dm_pack_decompress(DMResource *handle, DMPackEntry *node)
{
    DMZLibContext zctx;
    Uint8 *inBuf = NULL;
    int ret;

    // Allocate buffer for compressed data
    if ((inBuf = dmMalloc(node->csize)) == NULL)
    {
        ret = DMERR_MALLOC;
        goto out;
    }

    // Read compressed data
    if (fread(inBuf, sizeof(Uint8), node->csize, handle->lib->packFile->file) != node->csize)
    {
        ret = DMERR_FREAD;
        goto out;
    }

    // Initialize decompression structures
    if ((ret = dmZLibInitInflate(&zctx)) != DMERR_OK)
        goto out;

    zctx.inBuffer     = zctx.inBufferStart = inBuf;
    zctx.inBufferEnd  = inBuf + node->csize;
    zctx.outBuffer    = zctx.outBufferStart = handle->memData;
    zctx.outBufferEnd = handle->memData + node->size;
    zctx.expandable   = FALSE;

    // Attempt decompression
    if ((ret = dmZLibParseHeader(&zctx, TRUE)) != DMERR_OK)
        goto out;

    if ((ret = dmZLibInflate(&zctx)) != DMERR_OK)
        goto out;

    handle->memData = zctx.outBufferStart;
    handle->memSize = zctx.outBuffer - zctx.outBufferStart;

out:
    dmZLibCloseInflate(&zctx);
    dmFree(inBuf);
    return ret;
}

#endif


static int dm_pack_preload(DMResource *handle)
{
    DMPackEntry *node;
    int ret = DMERR_OK;

    if (handle->lib == NULL || handle->lib->packFile == NULL)
        return DMERR_NULLPTR;

    // Search PACK nodelist for file
    if ((node = dmPackFind(handle->lib->packFile->entries, handle->filename)) == NULL)
    {
        ret = dmErrorDBG(DMERR_NOT_FOUND,
            "Entry '%s' not found in PACK file.\n",
            handle->filename);
        goto out;
    }

    // Seek to entry
    if (fseeko(handle->lib->packFile->file, node->offset, SEEK_SET) != 0)
    {
        ret = dmErrorDBG(DMERR_FSEEK,
            "Could not seek node position in PACK file.\n");
        goto out;
    }

    // Allocate memory for the node
    if ((handle->memData = dmMalloc(node->size)) == NULL)
    {
        ret = dmErrorDBG(DMERR_MALLOC,
            "Failed to allocate node data for '%s' (%d bytes).\n",
            handle->filename, node->size);
        goto out;
    }

    // Check if the entry is compressed
    if (handle->flags & DMF_COMPRESSED)
    {
        if ((ret = dm_pack_decompress(handle, node)) != DMERR_OK)
            goto out;

        if (handle->memSize != node->size)
        {
            ret = dmErrorDBG(DMERR_COMPRESSION,
                "Decompressed data size for '%s' does not match size "
                "stored in PACK entry (%" DM_PRIu_SIZE_T " <> %d).\n",
                handle->filename, handle->memSize, node->size);
        }
    }
    else
    {
        if (node->size != node->csize)
        {
            ret = dmErrorDBG(DMERR_INVALID_DATA,
                "Node '%s' raw size and length fields differ for uncompressed node: %d <> %d.\n",
                handle->filename, node->size, node->csize);
            goto out;
        }
        if (fread(handle->memData, sizeof(Uint8), node->size, handle->lib->packFile->file) != node->size)
        {
            ret = dmErrorDBG(DMERR_FREAD,
                "Error reading raw node data '%s', %d bytes.\n",
                handle->filename, node->size);
            goto out;
        }
    }

out:
    return ret;
}


static int dm_pack_fopen(DMResource *fh)
{
    if ((fh->flags & DMF_LOADED_RAW) == 0)
    {
        int ret = dm_pack_preload(fh);
        if (ret == DMERR_OK)
            fh->flags |= DMF_LOADED_RAW;

        return ret;
    }
    else
        return DMERR_OK;
}


static void dm_pack_fclose(DMResource *fh)
{
    if ((fh->flags & DMF_PERSIST) == 0)
        dmResourceFreeRawData(fh);
}
#endif


static BOOL dm_mem_realloc(DMResource *ctx, const size_t newSize)
{
    size_t grow;

    // Check against max size
    if (ctx->maxSize > 0 && newSize > ctx->maxSize)
    {
        ctx->status = DMERR_BOUNDS;
        return FALSE;
    }

    // New size is smaller than old
    if (newSize < ctx->memAlloc)
        goto out;

    // Compute the allocation grow amount
    grow = (ctx->minAlloc > 0) ? ctx->minAlloc : 8 * 1024;
    if (newSize - ctx->memAlloc > grow)
        grow += newSize - ctx->memAlloc;

    if (ctx->maxSize > 0 && ctx->memAlloc + grow >= ctx->maxSize)
    {
        ctx->status = DMERR_BOUNDS;
        return FALSE;
    }

    // Grow the buffer
    ctx->memAlloc += grow;
    if ((ctx->memData = dmRealloc(ctx->memData, ctx->memAlloc)) == NULL)
    {
        ctx->status = DMERR_MALLOC;
        return FALSE;
    }

out:
    ctx->memSize = newSize;

    return TRUE;
}


static int dm_mem_freset(DMResource *ctx)
{
    ctx->memOffset = 0;
    return DMERR_OK;
}


static int dm_mem_ferror(DMResource *ctx)
{
    return ctx->status;
}


static int dm_mem_fseek(DMResource *ctx, 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 = ctx->memOffset + offset;
            break;

        case SEEK_END:
            newPos = ctx->memSize + offset;
            break;

        default:
            return -1;
    }

    // Set the new position
    ctx->memOffset = newPos;

    // Check the new position
    if (newPos < 0)
        return -1;

    //if (!dm_mem_realloc(ctx, newPos))
    //    return -1;

    return 0;
}


static off_t dm_mem_fsize(DMResource *ctx)
{
    return ctx->memSize;
}


static off_t dm_mem_ftell(DMResource *ctx)
{
    return ctx->memOffset;
}


static BOOL dm_mem_feof(DMResource *ctx)
{
    return ((size_t) ctx->memOffset) >= ctx->memSize;
}


static int dm_mem_fgetc(DMResource *ctx)
{
    // Check for EOF
    if ((size_t) ctx->memOffset < ctx->memSize)
        return ctx->memData[ctx->memOffset++];
    else
        return EOF;
}


static size_t dm_mem_fread(void *buf, size_t size, size_t nmemb, DMResource *ctx)
{
    size_t length = size * nmemb;

    // Check if we can read the whole chunk
    if (((size_t) ctx->memOffset + length) >= ctx->memSize)
    {
        nmemb = (ctx->memSize - ctx->memOffset) / size;
        length = size * nmemb;
    }

    memcpy(buf, ctx->memData + ctx->memOffset, length);
    ctx->memOffset += length;
    return nmemb;
}


static int dm_mem_fputc(int ch, DMResource *ctx)
{
    // Check for EOF
    if (!dm_mem_realloc(ctx, ctx->memOffset + 1))
        return EOF;

    ctx->memData[ctx->memOffset++] = ch;
    return ch;
}


static size_t dm_mem_fwrite(const void *buf, size_t size, size_t nmemb, DMResource *ctx)
{
    size_t length = size * nmemb;

    // Check if we can write the whole chunk
    if (!dm_mem_realloc(ctx, ctx->memOffset + length))
    {
        nmemb = (ctx->memSize - ctx->memOffset) / size;
        length = size * nmemb;
    }

    if (length > 0)
    {
        memcpy(ctx->memData + ctx->memOffset, buf, length);
        ctx->memOffset += length;
    }
    return nmemb;
}


#ifdef DM_USE_PACKFS
DMResourceOps dfPackFileOps =
{
    "PackFS",

    dm_pack_fopen,
    dm_pack_fclose,
    dm_pack_preload,

    dm_mem_freset,
    dm_mem_ferror,
    dm_mem_fseek,
    dm_mem_fsize,
    dm_mem_ftell,
    dm_mem_feof,
    dm_mem_fgetc,
    NULL,
    dm_mem_fread,
    NULL,

};
#endif


DMResourceOps dfMemIOFileOps =
{
    "MemIO",

    NULL,
    NULL,
    NULL,

    dm_mem_freset,
    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,
};


/* FS file handling functions. These functions call the actual
 * functions depending on where the file is located.
 */
static int dmResourcePreload(DMResource *handle)
{
    int ret = DMERR_OK;

    // Check if we want to preload raw data?
    if ((handle->lib->flags & DRF_PRELOAD_RAW) ||
        handle->rops == NULL || handle->rops->load == NULL)
    {
        if (handle->flags & DMF_LOADED_RAW)
            ret = DMERR_OK;
        else
        if (handle->fops->preload != NULL)
        {
            ret = handle->fops->preload(handle);
            if (ret == DMERR_OK)
                handle->flags |= DMF_LOADED_RAW | DMF_PERSIST;
        }
        else
            ret = DMERR_INIT_FAIL;

        dmfreset(handle);
    }

    // Check if resource data is to be preloaded
    if (ret == DMERR_OK && (handle->lib->flags & DRF_PRELOAD_RES))
    {
        if (handle->flags & DMF_LOADED_RES)
            ret = DMERR_OK;
        else
        if (handle->rops != NULL &&
            handle->rops->load != NULL)
        {
            if ((ret = handle->fops->fopen(handle)) == DMERR_OK)
            {
                ret = handle->rops->load(handle);
                handle->fops->fclose(handle);
            }

            if (ret == DMERR_OK)
                handle->flags |= DMF_LOADED_RES;
        }

        dmfreset(handle);
    }

    return ret;
}


int dmf_open(DMResourceLib *lib, const char *filename, DMResource **phandle)
{
    DMResource *handle;
    int ret;

    // Check master directory for resource
    if ((*phandle = handle = dmResourceFind(lib, filename)) == NULL)
    {
#ifdef DM_USE_STDIO
        if (lib->flags & DRF_USE_STDIO)
        {
            // Hmm.. does not exist? Fall back to a stdio file
            *phandle = handle = dmResourceNew(lib, filename, 0, 0);
            if (handle == NULL)
                return DMERR_MALLOC;

            handle->fops   = &dfStdioFileOps;
        }
        else
            return DMERR_INIT_FAIL;
#else
        // Stdio not enabled, fail
        return DMERR_INIT_FAIL;
#endif
    }

    // Check if the data is preloaded
    if ((ret = handle->fops->fopen(handle)) == DMERR_OK)
        dmResourceRef(handle);

    dmfreset(handle);
    return ret;
}


int dmf_open_memio(DMResourceLib *lib, const char *filename,
    Uint8 *buf, const size_t size, DMResource **phandle)
{
    DMResource *handle;

    // Check master directory for resource
    if ((*phandle = handle = dmResourceFind(lib, filename)) == NULL)
    {
        if ((*phandle = handle = dmResourceNew(
            lib, filename, size, DMF_LOADED_RAW | DMF_UNALLOCATED)) == NULL)
            return DMERR_MALLOC;

        handle->fops    = &dfMemIOFileOps;
        handle->memData = buf;
        dmResourceInsert(lib, handle);
    }

    // Increase refcount
    dmResourceRef(handle);
    dmfreset(handle);
    return DMERR_OK;
}


#ifdef DM_USE_STDIO
int dmf_open_stdio(const char *filename, const char *mode, DMResource **phandle)
{
    DMResource *handle;
    if ((*phandle = handle = dmResourceNew(NULL, filename, 0, 0)) == NULL)
        return DMERR_MALLOC;

    handle->fops  = &dfStdioFileOps;
    handle->fh    = fopen(filename, mode);
    handle->status = dmGetErrno();

    if (handle->fh == NULL)
    {
        int error = handle->status;
        dmResourceFree(handle);
        return error;
    }

    dmResourceRef(handle);
    return DMERR_OK;
}


int dmf_open_stdio_stream(FILE *fh, DMResource **phandle)
{
    DMResource *handle;
    if ((*phandle = handle = dmResourceNew(NULL, "", 0, 0)) == NULL)
        return DMERR_MALLOC;

    handle->fops = &dfStdioFHOps;
    handle->fh   = fh;
    dmResourceRef(handle);
    return DMERR_OK;
}
#endif


void dmf_close(DMResource *handle)
{
    if (handle == NULL)
        return;

    dmResourceUnref(handle);

    if (handle->fops->fclose != NULL)
        handle->fops->fclose(handle);

    if (handle->lib == NULL)
        dmResourceFree(handle);
}


int dmfreset(DMResource *fh)
{
    if (fh == NULL)
        return DMERR_NULLPTR;

    if (fh->fops == NULL || fh->fops->freset == NULL)
        return DMERR_OK;

    return fh->fops->freset(fh);
}


int dmferror(DMResource *fh)
{
    return fh->fops->ferror(fh);
}


int dmfseek(DMResource *fh, const off_t offset, const int whence)
{
    return fh->fops->fseek(fh, offset, whence);
}


off_t dmfsize(DMResource *fh)
{
    return fh->fops->fsize(fh);
}


off_t dmftell(DMResource *fh)
{
    return fh->fops->ftell(fh);
}


BOOL dmfeof(DMResource *fh)
{
    return fh->fops->feof(fh);
}


int dmfgetc(DMResource *fh)
{
    return fh->fops->fgetc(fh);
}


int dmfputc(const int val, DMResource *fh)
{
    return fh->fops->fputc(val, fh);
}


size_t dmfread(void *ptr, size_t size, size_t nmemb, DMResource *fh)
{
    return fh->fops->fread(ptr, size, nmemb, fh);
}


size_t dmfwrite(const void *ptr, size_t size, size_t nmemb, DMResource *fh)
{
    return fh->fops->fwrite(ptr, size, nmemb, fh);
}


char *dmfgets(char *str, const int size, DMResource *fh)
{
    char *ptr = str, *end = str + size - 1;
    int c;

    if (size <= 0)
        return NULL;

    while (ptr < end && (c = fh->fops->fgetc(fh)) != EOF)
    {
        *ptr++ = c;
        if (c == '\n')
            break;
    }
    *ptr = 0;

    return (ptr > str) ? str : NULL;
}


int dmfputs(const char *str, DMResource *fh)
{
    return dmfwrite(str, strlen(str), 1, fh) == 1 ? 1 : EOF;
}


int dmResourceRef(DMResource *node)
{
    int res;

    dmLockLibMutex(node->lib);
    res = ++node->refcount;
    dmUnlockLibMutex(node->lib);

    return res;
}


int dmResourceUnref(DMResource *node)
{
    int res;

    dmLockLibMutex(node->lib);
    res = --node->refcount;
    dmUnlockLibMutex(node->lib);

    return res;
}


#ifdef DM_USE_STDIO
static int dmResourcesLoadDirectory(DMResourceLib *lib, const char *path)
{
    int ret = DMERR_OK;
    struct dirent *dh;
    DIR *hdir = opendir(path);
    if (hdir == NULL)
        return dmGetErrno();

    dmLockLibMutex(lib);

    do
    {
        DMResource *node = NULL;
        dh = readdir(hdir);
        if (dh != NULL)
        {
            struct stat sbuf;
            char *fname = dm_strdup_printf("%s/%s", path, dh->d_name);
            if (stat(fname, &sbuf) == -1)
            {
                ret = dmErrorDBG(dmGetErrno(),
                    "Could not stat() %s, #%d: %s\n",
                    fname, ret, dmErrorStr(ret));
                dmFree(fname);
                goto out;
            }

            if (S_ISREG(sbuf.st_mode))
                node = dmResourceNew(lib, dh->d_name, sbuf.st_size, 0);
        }

        if (node != NULL)
        {
            node->fops = &dfStdioFileOps;
            dmResourceInsert(lib, node);
        }
    } while (dh != NULL);

out:
    dmUnlockLibMutex(lib);
    closedir(hdir);
    return ret;
}
#endif


/* Resources subsystem initialization and shutdown routines
 */
int dmResourcesInit(DMResourceLib **plib, const char *filename, const char *path, const int flags, int (*classifier)(DMResource *))
{
    DMResourceLib *lib;
    BOOL initialized = FALSE;

    // 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);

#ifdef DM_USE_PACKFS
    if (flags & DRF_USE_PACK)
    {
        int ret;
        DMPackEntry *node;

        lib->packFilename = dm_strdup(filename);

        // Initialize PACK, open as read-only
        ret = dmPackOpen(lib->packFilename, &lib->packFile, TRUE);
        if (ret != DMERR_OK)
        {
            if ((flags & DRF_USE_STDIO) == 0)
            {
                return dmErrorDBG(DMERR_INIT_FAIL,
                    "Error opening PACK file '%s', #%d: %s\n",
                    lib->packFilename, ret, dmErrorStr(ret));
            }
            else
            {
                // Non-fatal
                dmErrorDBGMsg(
                    "Failed to open PACK '%s' %d: %s\n",
                    lib->packFilename, ret, dmErrorStr(ret));
            }
        }
        else
        {
            // Initialize resources from a PACK file
            for (node = lib->packFile->entries; node != NULL; node = node->next)
            {
                DMResource *res = dmResourceNew(lib, node->filename, node->size, node->flags & DMF_PACK_MASK);
                if (res == NULL)
                {
                    return dmErrorDBG(DMERR_INIT_FAIL,
                        "Could not allocate memory for resource node '%s' [0x%08x], %d bytes.\n",
                        node->filename, node->flags, node->size);
                }

                res->fops = &dfPackFileOps;
                dmResourceInsert(lib, res);
            }

            initialized = TRUE;
        }
    }
#else
    (void) filename;
#endif

#ifdef DM_USE_STDIO
    if (!initialized && (flags & DRF_USE_STDIO))
    {
        // Initialize resources from a resource directory
        int ret = dmResourcesLoadDirectory(lib, lib->resPath);
        if (ret != DMERR_OK)
            return ret;
        initialized = TRUE;
    }
#endif

    if (!initialized)
        return dmErrorDBG(DMERR_INIT_FAIL, "Failed all resource initialization modes.\n");

    // 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 dmResourcesClose(DMResourceLib *lib)
{
    DMResource *node;

    if (lib == NULL)
        return DMERR_NULLPTR;

    dmLockLibMutex(lib);

    // Shutdown possible subsystems
#ifdef DM_USE_PACKFS
    if (lib->flags & DRF_USE_PACK)
    {
        int ret = dmPackClose(lib->packFile);
        if (ret != DMERR_OK)
        {
            dmErrorDBGMsg(
                "Error closing PACK, #%i: %s\n",
                ret, dmErrorStr(ret));
        }

        dmFree(lib->packFilename);
    }
#endif

    // Free resource entries
    node = lib->resources;
    while (node != NULL)
    {
        DMResource *next = node->next;
        dmResourceFree(node);
        node = next;
    }

    // Etc.
    dmFree(lib->resPath);
    dmUnlockLibMutex(lib);
    dmDestroyMutex(lib->mutex);
    return DMERR_OK;
}


int dmResourcesPreload(DMResourceLib *lib, BOOL start, int *loaded, int *total)
{
    int ret = DMERR_OK;

    dmLockLibMutex(lib);

    // 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
        if (lib->flags & (DRF_PRELOAD_RAW | DRF_PRELOAD_RES))
        {
            for (node = lib->resources; node != NULL; node = node->next)
                (*total)++;
        }
    }
    else
    if (lib->preload != NULL)
    {
        // Attempt to preload the resource
        if ((ret = dmResourcePreload(lib->preload)) != DMERR_OK)
        {
            dmErrorDBGMsg("Error preloading '%s', %d: %s\n",
                lib->preload->filename, ret, dmErrorStr(ret));
            goto error;
        }

        (*loaded)++;
        lib->preload = lib->preload->next;
    }

    dmUnlockLibMutex(lib);
    return (lib->preload == NULL) ? DMERR_OK : DMERR_PROGRESS;

error:
    dmUnlockLibMutex(lib);
    return ret;
}


/* Helper resource access routines
 */
int dmvfprintf(DMResource *fh, const char *fmt, va_list ap)
{
    int len, res;
    char *str = dm_strdup_vprintf_len(fmt, ap, &len);
    if (str == NULL)
    {
        fh->status = DMERR_MALLOC;
        return -1;
    }

    res = dmfwrite(str, 1, len, fh);
    dmFree(str);
    return res;
}


int dmfprintf(DMResource *fh, const char *fmt, ...)
{
    int res;
    va_list ap;

    va_start(ap, fmt);
    res = dmvfprintf(fh, fmt, ap);
    va_end(ap);

    return res;
}


BOOL dmf_read_str(DMResource *fh, void *ptr, const size_t len)
{
    return dmfread(ptr, len, 1, fh) == 1;
}


BOOL dmf_read_byte(DMResource *fh, Uint8 *val)
{
    int tmp = dmfgetc(fh);
    *val = tmp;
    return tmp != EOF;
}


#define DM_DEFINE_FFUNC(xname, xtype, xmacro)           \
BOOL dmf_read_ ## xname (DMResource *fh, xtype *val) {  \
    xtype result;                                       \
    if (dmfread(&result, sizeof( xtype ), 1, fh) != 1)  \
        return FALSE;                                   \
    *val = DM_ ## xmacro ## _TO_NATIVE (result);        \
    return TRUE;                                        \
}

#include "dmfiletmpl.h"

#undef DM_DEFINE_FFUNC