view dmres.c @ 0:32250b436bca

Initial re-import.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 28 Sep 2012 01:54:23 +0300
parents
children e0fc7863d024
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>

#if !defined(DMRES_PACKFS) && !defined(DMRES_STDIO)
#error At least one of DMRES_PACKFS, DMRES_STDIO must be defined.
#endif

#define DMRES_LOCK(x) dmMutexLock(dfResourcesMutex)
#define DMRES_UNLOCK(x) dmMutexUnlock(dfResourcesMutex)


/* Global variables
 */
static BOOL        dfResInitialized = FALSE;
static int         dfResFlags = 0;
static char *      dfResPath = NULL;
DMResource *       dfResources = NULL;
DMMutex *          dfResourcesMutex = NULL;


#ifdef DMRES_PACKFS
static DMPackFile *dfResPackFile = NULL;
static char *      dfResPackFilename = NULL;
#endif


DMResource *dmres_new(const char *filename, int flags, size_t size)
{
    DMResource *res = dmMalloc0(sizeof(DMResource));
    if (res == NULL)
        return NULL;
    
    res->filename = dm_strdup(filename);
    res->flags = flags;
    res->dataSize = size;
    
    return res;
}


void dmres_free(DMResource *res)
{
    if (res != NULL)
    {
        dmFree(res->filename);
        dmFree(res->data);
        dmFree(res);
    }
}


void dmres_insert(DMResource * node)
{
    if (dfResources != NULL)
    {
        node->prev = dfResources->prev;
        dfResources->prev->next = node;
        dfResources->prev = node;
    }
    else
    {
        dfResources = node->prev = node;
    }
    
    node->next = NULL;
}


void dmres_delete(DMResource * node)
{
    if (node->prev)
        node->prev->next = node->next;

    if (node->next)
        node->next->prev = node->prev;
    else
        dfResources->prev = node->prev;

    node->prev = node->next = NULL;
}


DMResource * dmres_find(const char *filename)
{
    DMResource *node, *found = NULL;

    DMRES_LOCK();

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

    DMRES_UNLOCK();

    return found;
}


#ifdef DMRES_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 = ftell(f->fh);
    if (fseek(f->fh, 0L, SEEK_END) != 0)
    {
        f->error = dmGetErrno();
        return -1;
    }

    fileSize = ftell(f->fh);
    if (fseek(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 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 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_fread,

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

    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 DMRES_PACKFS
static int dm_pack_preload(DMResource *handle)
{
    DMPackEntry *node;
    int res = DMERR_OK, cres, cdataLeft;
    z_stream cstream;
    Uint8 *  cbuffer = NULL;

    // Search PACK nodelist for file
    if ((node = dm_pack_find(dfResPackFile->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(dfResPackFile->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,
            dfResPackFile->file);

        cdataLeft -= cstream.avail_in;
        cstream.next_in = cbuffer;
        cres = inflate(&cstream, Z_FULL_FLUSH);
    }

    // Cleanup
    inflateEnd(&(cstream));

error:
    dmFree(cbuffer);
    return res;
}
#endif


static void dm_mem_fclose(DMResource * f)
{
    f->dataSize = 0;
    f->dataOffset = 0;
    dmFree(f->data);
    f->data = NULL;
}


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 (int) 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;
}


DMResourceOps dfPackFileOps =
{
    dm_mem_ferror,
    dm_mem_fseek,
    dm_mem_fsize,
    dm_mem_ftell,
    dm_mem_feof,
    dm_mem_fgetc,
    dm_mem_fread,
    
    NULL,
    dm_mem_fclose,
    dm_pack_preload
};


DMResourceOps dfMemIOFileOps =
{
    dm_mem_ferror,
    dm_mem_fseek,
    dm_mem_fsize,
    dm_mem_ftell,
    dm_mem_feof,
    dm_mem_fgetc,
    dm_mem_fread,
    
    NULL,
    NULL,
    NULL
};


/* FS file handling functions. These functions call the actual
 * functions depending on where the file is located.
 */
DMResource *dmf_open(const char *filename)
{
    int ret;
    DMResource *handle;

    // Check master directory for resource
    if ((handle = dmres_find(filename)) == NULL)
    {
#ifdef DMRES_STDIO
        // Hmm.. does not exist? Fall back to a stdio file
        handle = dmres_new(filename, 0, 0);
        if (handle == NULL)
            return NULL;

        handle->fops = &dfStdioFileOps;
        dmres_insert(handle);
#else
        // Stdio not enabled, fail
        return NULL;
#endif
    }

    // Check fops
    if (handle->fops == NULL)
    {
#ifdef DMRES_PACKFS
        if (dfResFlags & DRF_USE_PACK)
            handle->fops = &dfPackFileOps;
#ifdef DMRES_STDIO
        else
            handle->fops = &dfStdioFileOps;
#else
        handle->fops = &dfPackFileOps;
#endif

#else
        handle->fops = &dfStdioFileOps;
#endif
    }

    // Check if the data is preloaded
    if (handle->flags & DMF_LOADED)
    {
        dmres_ref(handle);
        return handle;
    }

    // Check if we want to preload ..
    ret = DMERR_INIT_FAIL;
    if ((handle->flags & DMF_PRELOAD) &&
        handle->fops->preload != NULL)
        ret = handle->fops->preload(handle);
    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)
        return handle;

    dmres_ref(handle);
    return NULL;
}


DMResource * dmf_open_memio(const char *filename, Uint8 *buf, size_t len)
{
    DMResource *handle;

    // Check master directory for resource
    if ((handle = dmres_find(filename)) == NULL)
    {
        // Hmm.. does not exist? Fall back to a stdio file
        handle = dmres_new(filename, DMF_LOADED, len);
        if (handle == NULL)
            return NULL;

        handle->fops = &dfMemIOFileOps;
        handle->data = buf;
        dmres_insert(handle);
    }

    // Increase refcount
    dmres_ref(handle);

    return handle;
}


#ifdef DMRES_STDIO
DMResource * dmf_create_stdio(const char *filename)
{
    DMResource *handle = dmres_new(filename, 0, 0);
    if (handle == NULL)
        return NULL;

    handle->fops = &dfStdioFileOps;

    handle->fh = fopen(filename, "rb");
    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("", 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);
}

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


int dmres_ref(DMResource *res)
{
    DMRES_LOCK();
    res->atime = time(NULL);
    res->refcount++;
    DMRES_UNLOCK();

    return res->refcount;
}


int dmres_unref(DMResource *res)
{
    DMRES_LOCK();
    res->refcount--;
    DMRES_UNLOCK();

    return res->refcount;
}


int dmres_load_resfile(const char *filename)
{
    int ret;
    char line[256];
    FILE *f = fopen(filename, "r");
    if (f == NULL)
        return DMERR_FOPEN;

    DMRES_LOCK();

    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++);
            
            if (sscanf(&line[i], "%x", &flags) == 1 &&
                strlen(&line[fnstart]) > 0)
            {
                
            }
        }
    }

    DMRES_UNLOCK();
    fclose(f);
    return ret;
}


int dmres_write_resfile(const char *filename)
{
    int ret;
    DMResource *node;
    FILE *f = fopen(filename, "w");
    if (f == NULL)
        return DMERR_FOPEN;

    DMRES_LOCK();
    
    for (node = dfResources; node != NULL; node = node->next)
    {
        if (fprintf(f, "%s|%08x\n", node->filename, node->flags) < 0)
        {
            ret = DMERR_FWRITE;
            goto error;
        }
    }

error:
    DMRES_UNLOCK();
    fclose(f);
    return ret;
}


/* Resources subsystem initialization and shutdown routines
 */
int dmres_init(const char *filename, const char *path, int flags)
{
    // Check if we are already initialized
    if (dfResInitialized)
        return DMERR_ALREADY_INIT;

    dfResFlags        = flags; 
    dfResPath         = dm_strdup((path != NULL) ? path : DMRES_DATA_PATH);
    dfResourcesMutex  = dmCreateMutex();

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

        dfResPackFilename = dm_strdup((filename != NULL) ? filename : DMRES_DATA_PACK);

        // Initialize PACK, open as read-only
        ret = dm_pack_open(dfResPackFilename, &dfResPackFile, TRUE);
        if (ret != DMERR_OK)
        {
            dmError("Error opening PACK file '%s', #%i: %s\n",
                dfResPackFilename, ret, dmErrorStr(ret));

            return DMERR_INIT_FAIL;
        }
        
        // Initialize resources from a PACK file
        for (node = dfResPackFile->entries; node != NULL; node = node->next)
        {
            DMResource *res = dmres_new(node->filename, node->resFlags, 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(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", dfResPath, DMRES_RES_FILE);
        int ret = dmres_load_resfile(resFilename);
        dmFree(resFilename);
        
        if (ret != DMERR_OK)
            return DMERR_INIT_FAIL;
    }


    // Initialization complete
    dfResInitialized = TRUE;
    return DMERR_OK;
}


void dmres_close(void)
{
    DMResource *node;
    DMRES_LOCK();

    if (!dfResInitialized)
        return;
    
    // Shutdown possible subsystems
#ifdef DMRES_PACKFS
    if (dfResFlags & DRF_USE_PACK)
    {
        int res = dm_pack_close(dfResPackFile);
        if (res != DMERR_OK)
        {
            dmError("Error closing PACK, #%i: %s\n",
                        res, dmErrorStr(res));
        }

        dmFree(dfResPackFilename);
    }
#endif

    // Free resource entries
    node = dfResources;
    while (node != NULL)
    {
        DMResource *next = node->next;
        dmres_free(node);
        node = next;
    }

    // Etc.
    dmFree(dfResPath);
    DMRES_UNLOCK();
    dmDestroyMutex(dfResourcesMutex);
    dfResInitialized = FALSE;
}


BOOL dmres_preload(int *loaded, int *total)
{
    static DMResource *dfPreload = NULL;

    DMRES_LOCK();
    
    if (dfPreload == NULL)
    {
        DMResource *node;
        dfPreload = dfResources;
        *loaded = 0;
        *total = 0;
        for (node = dfResources; node != NULL; node = node->next)
        {
            if (node->flags & DMF_PRELOAD)
                (*total)++;
        }
    }

    if (dfPreload != NULL)
    {
        if (dfPreload->flags & DMF_PRELOAD)
        {
            (*loaded)++;
        }

        dfPreload = dfPreload->next;
    }

    DMRES_UNLOCK();
    
    return (*total) == (*loaded);
}


void dmres_prune(int agems, int flags)
{
    DMResource *node, *next;
    int currtime = time(NULL);
    DMRES_LOCK();

    node = dfResources;
    while (node != NULL)
    {
        next = 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)
        {
            // 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_delete(node);
            }
        }
        node = next;
    }

    DMRES_UNLOCK();
}


/* Helper resource access routines
 */
int dmf_read_str(DMResource *f, Uint8 *s, size_t l)
{
    return dmfread(s, sizeof(Uint8), l, f) == l;
}


#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