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