view src/dmres.c @ 2208:90ec1ec89c56

Revamp the palette handling in lib64gfx somewhat, add helper functions to lib64util for handling external palette file options and add support for specifying one of the "internal" palettes or external (.act) palette file to gfxconv and 64vw.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 14 Jun 2019 05:01:12 +0300
parents 8962901faf5d
children 972d56ad2b78
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->rawSize = 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->rawData);
        node->rawData = 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",
                node, node->refcount, node->filename,
                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->error = 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->error;
}


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->error = 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->rawSize != 0)
        return fh->rawSize;

    // 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->rawSize = 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->error = dmGetErrno();
    return ret;
}


static int dm_stdio_fputc(int v, DMResource *fh)
{
    int ret = fputc(v, fh->fh);
    fh->error = 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->error = 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->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->rawData = dmMalloc(handle->rawSize);
    if (handle->rawData == NULL)
        return DMERR_MALLOC;

    if (dm_stdio_fread(handle->rawData, sizeof(Uint8), handle->rawSize, handle) != handle->rawSize)
        return DMERR_FREAD;

    return DMERR_OK;
}


DMResourceOps dfStdioFileOps =
{
    "Stdio",

    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,

    dm_stdio_fopen,
    dm_stdio_fclose,
    dm_stdio_preload
};

DMResourceOps dfStdioFHOps =
{
    "StdioFH",

    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,

    NULL,
    NULL,
    NULL
};
#endif


// Some mingw/windows headers define these as macros, which is bad for us
#ifdef DM_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
    dmMemset(&zstr, 0, sizeof(zstr));
    zstr.next_out = handle->rawData;
    zstr.avail_out = handle->rawSize;
    cdataLeft = node->length;

    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 ctx;
    Uint8 *inBuf = NULL;
    int ret;

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

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

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

    ctx.inBuffer     = ctx.inBufferStart = inBuf;
    ctx.inBufferEnd  = inBuf + node->length;
    ctx.outBuffer    = ctx.outBufferStart = handle->rawData;
    ctx.outBufferEnd = handle->rawData + node->size;
    ctx.expandable   = FALSE;

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

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

    handle->rawData = ctx.outBufferStart;
    handle->rawSize = ctx.outBuffer - ctx.outBufferStart;

out:
    dmZLibCloseInflate(&ctx);
    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->rawData = 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->rawSize != node->size)
        {
            ret = dmErrorDBG(DMERR_COMPRESSION,
                "Decompressed data size for '%s' does not match size stored in PACK entry (%d <> %d).\n",
                handle->filename, handle->rawSize, node->size);
        }
    }
    else
    {
        if (node->size != node->length)
        {
            ret = dmErrorDBG(DMERR_INVALID_DATA,
                "Node '%s' raw size and length fields differ for uncompressed node: %d <> %d.\n",
                handle->filename, node->size, node->length);
            goto out;
        }
        if (fread(handle->rawData, 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 int dm_mem_freset(DMResource *fh)
{
    fh->rawOffset = 0;
    return DMERR_OK;
}


static int dm_mem_ferror(DMResource *fh)
{
    return fh->error;
}


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

        case SEEK_END:
            newPos = fh->rawSize + offset;
            break;

        default:
            return -1;
    }

    // Set the new position
    fh->rawOffset = newPos;

    // Check the new position
    if (newPos < 0 && (size_t) newPos >= fh->rawSize)
        return -1;

    return 0;
}


static off_t dm_mem_fsize(DMResource *fh)
{
    return fh->rawSize;
}


static off_t dm_mem_ftell(DMResource *fh)
{
    return fh->rawOffset;
}


static BOOL dm_mem_feof(DMResource *fh)
{
    // Check for EOF
    if ((size_t) fh->rawOffset <= fh->rawSize)
        return FALSE;
    else
        return TRUE;
}


static int dm_mem_fgetc(DMResource *fh)
{
    // Check for EOF
    if ((size_t) fh->rawOffset < fh->rawSize)
        return fh->rawData[fh->rawOffset++];
    else
        return EOF;
}


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

    // Check if we can read the whole chunk
    if (((size_t) fh->rawOffset + length) >= fh->rawSize)
    {
        nmemb = (fh->rawSize - fh->rawOffset) / size;
        length = size * nmemb;
    }

    memcpy(buf, fh->rawData + fh->rawOffset, length);
    fh->rawOffset += length;
    return nmemb;
}


static int dm_mem_fputc(int ch, DMResource *fh)
{
    // Check for EOF
    if ((size_t) fh->rawOffset < fh->rawSize)
    {
        fh->rawData[fh->rawOffset++] = ch;
        return ch;
    }
    else
        return EOF;
}


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

    // Check if we can write the whole chunk
    if (((size_t) fh->rawOffset + length) >= fh->rawSize)
    {
        nmemb = (fh->rawSize - fh->rawOffset) / size;
        length = size * nmemb;
    }

    if (length > 0)
    {
        memcpy(fh->rawData + fh->rawOffset, buf, length);
        fh->rawOffset += length;
    }
    return nmemb;
}


#ifdef DM_USE_PACKFS
DMResourceOps dfPackFileOps =
{
    "PackFS",

    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,

    dm_pack_fopen,
    dm_pack_fclose,
    dm_pack_preload,
};
#endif


DMResourceOps dfMemIOFileOps =
{
    "MemIO",

    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,

    NULL,
    NULL,
    NULL
};


/* 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);
        if (handle->flags & DMF_TEMPORARY)
        {
            handle->flags &= ~DMF_TEMPORARY;
            dmResourceInsert(lib, handle);
        }
    }
    else
    if (handle->flags & DMF_TEMPORARY)
    {
        dmResourceFree(handle);
        *phandle = handle = NULL;
    }

    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->rawData = 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->error = dmGetErrno();

    if (handle->fh == NULL)
    {
        int error = handle->error;
        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)
{
    fh->atime = time(NULL);
    return fh->fops->ferror(fh);
}

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

off_t dmfsize(DMResource *fh)
{
    fh->atime = time(NULL);
    return fh->fops->fsize(fh);
}

off_t dmftell(DMResource *fh)
{
    fh->atime = time(NULL);
    return fh->fops->ftell(fh);
}

BOOL dmfeof(DMResource *fh)
{
    fh->atime = time(NULL);
    return fh->fops->feof(fh);
}

int dmfgetc(DMResource *fh)
{
    fh->atime = time(NULL);
    return fh->fops->fgetc(fh);
}

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

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

size_t dmfwrite(const void *ptr, size_t size, size_t nmemb, DMResource *fh)
{
    fh->atime = time(NULL);
    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)
{
    dmLockLibMutex(node->lib);
    node->atime = time(NULL);
    node->refcount++;
    dmUnlockLibMutex(node->lib);

    return node->refcount;
}


int dmResourceUnref(DMResource *node)
{
    dmLockLibMutex(node->lib);
    node->refcount--;
    dmUnlockLibMutex(node->lib);

    return node->refcount;
}


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

#ifdef __WIN32
#else
    closedir(hdir);
#endif

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


void dmResourcePrune(DMResourceLib *lib, const int agems, int const flags)
{
    DMResource *node;
    const int stamp = time(NULL);
    dmLockLibMutex(lib);

    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)))
        {
            if (((flags & DMPRUNE_ATIME) && stamp - node->atime >= agems) ||
                ((flags & DMPRUNE_MTIME) && stamp - node->mtime >= agems))
            {
                dmResourceFreeResData(node);
                dmResourceFreeRawData(node);
            }
        }
    }

    dmUnlockLibMutex(lib);
}


/* 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->error = 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