view src/dmres.c @ 2260:972d56ad2b78

Do closedir() in dmResourcesLoadDirectory(), even when under Win32.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 17 Jun 2019 00:49:21 +0300
parents 8962901faf5d
children e286454d305f
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);
    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;
}


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