raze/source/blood/src/resource.cpp
Christoph Oelckers 2cbe211e7c - transitioned project to CMake and deleted most of the old build system.
The EDuke32 and RedNukem frontends are working, Blood isn't yet.

Notes:

many of the CMake variables and its output still refer to zdoom. Before changing that I wanted to make sure to be able to commit something that works.
support code for Windows XP has been entirely removed. On Windows this will only target Vista and up.
the crc32.h header had to be renamed to deconflict from zlib.
several Windows API calls were changed to call the A-versions directly. Weirdly enough there were places that defined their parameters as T types but in a non-working way.
removed some remaining editor files and support for the native software rendering only Windows backend.
in a few simple cases, replaced 'char' with 'uint8_t'. The code as-is depends on chars being unsigned which is non-portable. This needs to be carefully reviewed.
2019-09-22 23:15:46 +02:00

864 lines
22 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
This file is part of NBlood.
NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "ns.h" // Must come before everything else!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "crc32_.h"
#include "compat.h"
#include "cache1d.h"
#ifdef WITHKPLIB
#include "kplib.h"
#endif
#include "common_game.h"
#include "misc.h"
#include "qheap.h"
#include "resource.h"
#if B_BIG_ENDIAN == 1
#include "qav.h"
#include "seq.h"
#include "sound.h"
#endif
BEGIN_BLD_NS
CACHENODE Resource::purgeHead = { NULL, &purgeHead, &purgeHead, 0 };
#ifdef USE_QHEAP
QHeap *Resource::heap;
#endif
Resource::Resource(void)
{
dict = NULL;
indexName = NULL;
indexId = NULL;
buffSize = 0;
count = 0;
handle = -1;
crypt = true;
}
Resource::~Resource(void)
{
if (dict)
{
for (unsigned int i = 0; i < count; i++)
{
if (dict[i].type)
Free(dict[i].type);
if (dict[i].name)
Free(dict[i].name);
if (dict[i].path)
Free(dict[i].path);
}
Free(dict);
dict = NULL;
buffSize = 0;
count = 0;
}
if (handle != -1)
{
kclose(handle);
}
}
void Resource::Init(const char *filename)
{
RFFHeader header;
#ifdef USE_QHEAP
dassert(heap != NULL);
#endif
if (filename)
{
handle = kopen4loadfrommod(filename, 0);
if (handle != -1)
{
int nFileLength = kfilelength(handle);
dassert(nFileLength != -1);
if (kread(handle, &header, sizeof(RFFHeader)) != sizeof(RFFHeader)
|| memcmp(header.sign, "RFF\x1a", 4))
{
ThrowError("RFF header corrupted");
}
#if B_BIG_ENDIAN == 1
header.version = B_LITTLE16(header.version);
header.offset = B_LITTLE32(header.offset);
header.filenum = B_LITTLE32(header.filenum);
#endif
switch (header.version & 0xff00)
{
case 0x200:
crypt = 0;
break;
case 0x300:
crypt = 1;
break;
default:
ThrowError("Unknown RFF version");
break;
}
count = header.filenum;
if (count)
{
buffSize = 1;
while (count * 2 >= buffSize)
{
buffSize *= 2;
}
dict = (DICTNODE*)Alloc(buffSize * sizeof(DICTNODE));
memset(dict, 0, buffSize * sizeof(DICTNODE));
DICTNODE_FILE *tdict = (DICTNODE_FILE*)Alloc(count*sizeof(DICTNODE_FILE));
int r = klseek(handle, header.offset, SEEK_SET);
dassert(r != -1);
if ((uint32_t)kread(handle, tdict, count * sizeof(DICTNODE_FILE)) != count*sizeof(DICTNODE_FILE))
{
ThrowError("RFF dictionary corrupted");
}
if (crypt)
{
Crypt(tdict, count * sizeof(DICTNODE_FILE),
header.offset + (header.version & 0xff) * header.offset);
}
for (unsigned int i = 0; i < count; i++)
{
dict[i].offset = B_LITTLE32(tdict[i].offset);
dict[i].size = B_LITTLE32(tdict[i].size);
dict[i].flags = tdict[i].flags;
int nTypeLength = strnlen(tdict[i].type, 3);
int nNameLength = strnlen(tdict[i].name, 8);
dict[i].type = (char*)Alloc(nTypeLength+1);
dict[i].name = (char*)Alloc(nNameLength+1);
strncpy(dict[i].type, tdict[i].type, min(3, nTypeLength));
strncpy(dict[i].name, tdict[i].name, min(8, nNameLength));
dict[i].path = NULL;
dict[i].type[nTypeLength] = 0;
dict[i].name[nNameLength] = 0;
dict[i].id = B_LITTLE32(tdict[i].id);
}
Free(tdict);
}
}
}
if (!dict)
{
buffSize = 16;
dict = (DICTNODE*)Alloc(buffSize * sizeof(DICTNODE));
memset(dict, 0, buffSize * sizeof(DICTNODE));
}
Reindex();
#if 0
if (external)
{
char fname[BMAX_PATH];
char type[BMAX_PATH];
BDIR *dirr;
struct Bdirent *dirent;
dirr = Bopendir("./");
if (dirr)
{
while (dirent = Breaddir(dirr))
{
if (!Bwildmatch(dirent->name, external))
continue;
_splitpath(dirent->name, NULL, NULL, fname, type);
if (type[0] == '.')
{
AddExternalResource(fname, &type[1], dirent->size);
}
else
{
AddExternalResource(fname, "", dirent->size);
}
}
Bclosedir(dirr);
}
#if 0
_splitpath2(external, out, &dir, &node, NULL, NULL);
_makepath(ext, dir, node, NULL, NULL);
int status = _dos_findfirst(external, 0, &info);
while (!status)
{
_splitpath2(info.name, out, NULL, NULL, &fname, &type);
if (*type == '.')
{
AddExternalResource(*fname, (char*)(type + 1), info.size);
}
else
{
AddExternalResource(*fname, "", info.size);
}
status = _dos_findnext(&info);
}
_dos_findclose(&info);
#endif
}
#endif
for (unsigned int i = 0; i < count; i++)
{
if (dict[i].flags & DICT_LOCK)
{
Lock(&dict[i]);
}
}
for (unsigned int i = 0; i < count; i++)
{
if (dict[i].flags & DICT_LOAD)
{
Load(&dict[i]);
}
}
}
void Resource::Flush(CACHENODE *h)
{
if (h->ptr)
{
#ifdef USE_QHEAP
heap->Free(h->ptr);
#else
delete[] (char*)h->ptr;
#endif
h->ptr = NULL;
if (h->lockCount == 0)
{
RemoveMRU(h);
return;
}
h->lockCount = 0;
}
}
void Resource::Purge(void)
{
for (unsigned int i = 0; i < count; i++)
{
if (dict[i].ptr)
{
Flush((CACHENODE *)&dict[i]);
}
}
}
DICTNODE **Resource::Probe(const char *fname, const char *type)
{
char name[BMAX_PATH];
dassert(indexName != NULL);
memset(name, 0, sizeof(name));
strcpy(name, type);
strcat(name, fname);
dassert(dict != NULL);
unsigned int hash = Bcrc32(name, strlen(name), 0) & (buffSize - 1);
unsigned int i = hash;
do
{
if (!indexName[i])
{
return &indexName[i];
}
if (!strcmp((*indexName[i]).type, type)
&& !strcmp((*indexName[i]).name, fname))
{
return &indexName[i];
}
if (++i == buffSize)
{
i = 0;
}
} while (i != hash);
ThrowError("Linear probe failed to find match or unused node!");
return NULL;
}
DICTNODE **Resource::Probe(unsigned int id, const char *type)
{
struct {
int id;
char type[BMAX_PATH];
} name;
dassert(indexName != NULL);
memset(&name, 0, sizeof(name));
strcpy(name.type, type);
name.id = id;
dassert(dict != NULL);
unsigned int hash = Bcrc32(&name, strlen(name.type)+sizeof(name.id), 0) & (buffSize - 1);
unsigned int i = hash;
do
{
if (!indexId[i])
{
return &indexId[i];
}
if (!strcmp((*indexId[i]).type, type)
&& (*indexId[i]).id == id)
{
return &indexId[i];
}
if (++i == buffSize)
{
i = 0;
}
} while (i != hash);
ThrowError("Linear probe failed to find match or unused node!");
return NULL;
}
void Resource::Reindex(void)
{
if (indexName)
{
Free(indexName);
}
indexName = (DICTNODE **)Alloc(buffSize * sizeof(DICTNODE*));
memset(indexName, 0, buffSize * sizeof(DICTNODE*));
for (unsigned int i = 0; i < count; i++)
{
DICTNODE **node = Probe(dict[i].name, dict[i].type);
*node = &dict[i];
}
if (indexId)
{
Free(indexId);
}
indexId = (DICTNODE **)Alloc(buffSize * sizeof(DICTNODE*));
memset(indexId, 0, buffSize * sizeof(DICTNODE*));
for (unsigned int i = 0; i < count; i++)
{
if (dict[i].flags & DICT_ID)
{
DICTNODE **node = Probe(dict[i].id, dict[i].type);
*node = &dict[i];
}
}
}
void Resource::Grow(void)
{
buffSize *= 2;
void *p = Alloc(buffSize * sizeof(DICTNODE));
memset(p, 0, buffSize * sizeof(DICTNODE));
memcpy(p, dict, count * sizeof(DICTNODE));
Free(dict);
dict = (DICTNODE*)p;
Reindex();
}
void Resource::AddExternalResource(const char *name, const char *type, int id, int flags, const char *pzDirectory)
{
char name2[BMAX_PATH], type2[BMAX_PATH], filename[BMAX_PATH], path[BMAX_PATH];
if (Bstrlen(type) > 0)
Bsnprintf(filename, BMAX_PATH-1, "%s.%s", name, type);
else
Bsnprintf(filename, BMAX_PATH-1, "%s", name);
if (pzDirectory)
Bsnprintf(path, BMAX_PATH-1, "%s/%s", pzDirectory, filename);
else
Bstrncpy(path, filename, BMAX_PATH-1);
int fhandle = kopen4loadfrommod(filename, 0);
if (fhandle == -1)
return;
int size = kfilelength(fhandle);
kclose(fhandle);
strcpy(name2, name);
strcpy(type2, type);
Bstrupr(name2);
Bstrupr(type2);
dassert(dict != NULL);
DICTNODE **index = Probe(name2, type2);
dassert(index != NULL);
DICTNODE *node = *index;
if (node && (node->flags & DICT_EXTERNAL))
return;
if (!node)
{
if (2 * count >= buffSize)
{
Grow();
}
node = &dict[count++];
index = Probe(name2, type2);
*index = node;
if (node->type)
{
Free(node->type);
node->type = NULL;
}
if (node->name)
{
Free(node->name);
node->name = NULL;
}
if (node->path)
{
Free(node->path);
node->path = NULL;
}
int nTypeLength = strlen(type2);
int nNameLength = strlen(name2);
int nPathLength = strlen(path);
node->type = (char*)Alloc(nTypeLength+1);
node->name = (char*)Alloc(nNameLength+1);
node->path = (char*)Alloc(nPathLength+1);
strcpy(node->type, type2);
strcpy(node->name, name2);
strcpy(node->path, path);
}
node->size = size;
node->flags |= DICT_EXTERNAL | flags;
Flush(node);
if (id >= 0)
{
index = Probe(id, type2);
dassert(index != NULL);
DICTNODE *node = *index;
if (!node)
{
if (2 * count >= buffSize)
{
Grow();
}
node = &dict[count++];
index = Probe(id, type2);
*index = node;
}
if (node->type)
{
Free(node->type);
node->type = NULL;
}
if (node->name)
{
Free(node->name);
node->name = NULL;
}
if (node->path)
{
Free(node->path);
node->path = NULL;
}
int nTypeLength = strlen(type2);
int nNameLength = strlen(name2);
int nPathLength = strlen(path);
node->type = (char*)Alloc(nTypeLength+1);
node->name = (char*)Alloc(nNameLength+1);
node->path = (char*)Alloc(nPathLength+1);
strcpy(node->type, type2);
strcpy(node->name, name2);
strcpy(node->path, path);
node->id = id;
node->size = size;
node->flags |= DICT_EXTERNAL | flags;
Flush(node);
}
}
void *Resource::Alloc(int nSize)
{
#ifdef USE_QHEAP
dassert(heap != NULL);
dassert(nSize != 0);
void *p = heap->Alloc(nSize);
if (p)
{
return p;
}
for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
{
dassert(node->lockCount == 0);
dassert(node->ptr != NULL);
int nFree = heap->Free(node->ptr);
node->ptr = NULL;
RemoveMRU(node);
if (nSize <= nFree)
{
p = Alloc(nSize);
dassert(p != NULL);
return p;
}
}
ThrowError("Out of memory!");
return NULL;
#else
dassert(nSize != 0);
void* p = new char[nSize];
if (p)
{
return p;
}
for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
{
dassert(node->lockCount == 0);
dassert(node->ptr != NULL);
delete[] (char*)node->ptr;
node->ptr = NULL;
RemoveMRU(node);
p = new char[nSize];
if (p)
return p;
}
ThrowError("Out of memory!");
return NULL;
#endif
}
void Resource::Free(void *p)
{
#ifdef USE_QHEAP
dassert(heap != NULL);
dassert(p != NULL);
heap->Free(p);
#else
dassert(p != NULL);
delete[] (char*)p;
#endif
}
DICTNODE *Resource::Lookup(const char *name, const char *type)
{
char name2[BMAX_PATH], type2[BMAX_PATH];
dassert(name != NULL);
dassert(type != NULL);
//if (strlen(name) > 8 || strlen(type) > 3) return NULL;
// Try to load external resource first
AddExternalResource(name, type);
strcpy(name2, name);
strcpy(type2, type);
Bstrupr(type2);
Bstrupr(name2);
return *Probe(name2, type2);
}
DICTNODE *Resource::Lookup(unsigned int id, const char *type)
{
char type2[BMAX_PATH];
dassert(type != NULL);
//if (strlen(type) > 3) return NULL;
strcpy(type2, type);
Bstrupr(type2);
return *Probe(id, type2);
}
void Resource::Read(DICTNODE *n)
{
dassert(n != NULL);
Read(n, n->ptr);
}
void Resource::Read(DICTNODE *n, void *p)
{
char filename[BMAX_PATH];
dassert(n != NULL);
if (n->flags & DICT_EXTERNAL)
{
if (n->path)
Bstrncpy(filename, n->path, BMAX_PATH-1);
else
Bsnprintf(filename, BMAX_PATH-1, "%s.%s", n->name, n->type);
int fhandle = kopen4loadfrommod(filename, 0);
if (fhandle == -1 || (uint32_t)kread(fhandle, p, n->size) != n->size)
{
ThrowError("Error reading external resource (%i)", errno);
}
kclose(fhandle);
}
else
{
int r = klseek(handle, n->offset, SEEK_SET);
if (r == -1)
{
ThrowError("Error seeking to resource!");
}
if ((uint32_t)kread(handle, p, n->size) != n->size)
{
ThrowError("Error loading resource!");
}
if (n->flags & DICT_CRYPT)
{
int size;
if (n->size > 0x100)
{
size = 0x100;
}
else
{
size = n->size;
}
Crypt(n->ptr, size, 0);
}
#if B_BIG_ENDIAN == 1
if (!Bstrcmp(n->type, "QAV"))
{
QAV *qav = (QAV*)p;
qav->nFrames = B_LITTLE32(qav->nFrames);
qav->ticksPerFrame = B_LITTLE32(qav->ticksPerFrame);
qav->at10 = B_LITTLE32(qav->at10);
qav->x = B_LITTLE32(qav->x);
qav->y = B_LITTLE32(qav->y);
qav->nSprite = B_LITTLE32(qav->nSprite);
for (int i = 0; i < qav->nFrames; i++)
{
FRAMEINFO *pFrame = &qav->frames[i];
SOUNDINFO *pSound = &pFrame->sound;
pFrame->nCallbackId = B_LITTLE32(pFrame->nCallbackId);
pSound->sound = B_LITTLE32(pSound->sound);
for (int j = 0; j < 8; j++)
{
TILE_FRAME *pTile = &pFrame->tiles[j];
pTile->picnum = B_LITTLE32(pTile->picnum);
pTile->x = B_LITTLE32(pTile->x);
pTile->y = B_LITTLE32(pTile->y);
pTile->z = B_LITTLE32(pTile->z);
pTile->stat = B_LITTLE32(pTile->stat);
pTile->angle = B_LITTLE16(pTile->angle);
}
}
}
else if (!Bstrcmp(n->type, "SEQ"))
{
Seq *pSeq = (Seq*)p;
pSeq->version = B_LITTLE16(pSeq->version);
pSeq->nFrames = B_LITTLE16(pSeq->nFrames);
pSeq->at8 = B_LITTLE16(pSeq->at8);
pSeq->ata = B_LITTLE16(pSeq->ata);
pSeq->atc = B_LITTLE32(pSeq->atc);
for (int i = 0; i < pSeq->nFrames; i++)
{
SEQFRAME *pFrame = &pSeq->frames[i];
BitReader bitReader((char *)pFrame, sizeof(SEQFRAME));
SEQFRAME swapFrame;
swapFrame.tile = bitReader.readUnsigned(12);
swapFrame.at1_4 = bitReader.readBit();
swapFrame.at1_5 = bitReader.readBit();
swapFrame.at1_6 = bitReader.readBit();
swapFrame.at1_7 = bitReader.readBit();
swapFrame.at2_0 = bitReader.readUnsigned(8);
swapFrame.at3_0 = bitReader.readUnsigned(8);
swapFrame.at4_0 = bitReader.readSigned(8);
swapFrame.at5_0 = bitReader.readUnsigned(5);
swapFrame.at5_5 = bitReader.readBit();
swapFrame.at5_6 = bitReader.readBit();
swapFrame.at5_7 = bitReader.readBit();
swapFrame.at6_0 = bitReader.readBit();
swapFrame.at6_1 = bitReader.readBit();
swapFrame.at6_2 = bitReader.readBit();
swapFrame.at6_3 = bitReader.readBit();
swapFrame.at6_4 = bitReader.readBit();
swapFrame.tile2 = bitReader.readUnsigned(4);
swapFrame.pad = bitReader.readUnsigned(7);
*pFrame = swapFrame;
}
}
else if (!Bstrcmp(n->type, "SFX"))
{
SFX *pSFX = (SFX*)p;
pSFX->relVol = B_LITTLE32(pSFX->relVol);
pSFX->pitch = B_LITTLE32(pSFX->pitch);
pSFX->pitchRange = B_LITTLE32(pSFX->pitchRange);
pSFX->format = B_LITTLE32(pSFX->format);
pSFX->loopStart = B_LITTLE32(pSFX->loopStart);
}
#endif
}
}
void *Resource::Load(DICTNODE *h)
{
dassert(h != NULL);
if (h->ptr)
{
if (!h->lockCount)
{
RemoveMRU(h);
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
}
else
{
h->ptr = Alloc(h->size);
Read(h);
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
return h->ptr;
}
void *Resource::Load(DICTNODE *h, void *p)
{
dassert(h != NULL);
if (p)
{
Read(h, p);
}
return p;
}
void *Resource::Lock(DICTNODE *h)
{
dassert(h != NULL);
if (h->ptr)
{
if (h->lockCount == 0)
{
RemoveMRU(h);
}
}
else
{
h->ptr = Alloc(h->size);
Read(h);
}
h->lockCount++;
return h->ptr;
}
void Resource::Unlock(DICTNODE *h)
{
dassert(h != NULL);
dassert(h->ptr != NULL);
if (h->lockCount > 0)
{
h->lockCount--;
if (h->lockCount == 0)
{
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
}
}
void Resource::Crypt(void *p, int length, unsigned short key)
{
char *cp = (char*)p;
for (int i = 0; i < length; i++, key++)
{
cp[i] ^= (key >> 1);
}
}
void Resource::RemoveMRU(CACHENODE *h)
{
h->prev->next = h->next;
h->next->prev = h->prev;
}
void Resource::FNAddFiles(fnlist_t * fnlist, const char *pattern)
{
char filename[BMAX_PATH];
for (unsigned int i = 0; i < count; i++)
{
DICTNODE *pNode = &dict[i];
if (pNode->flags & DICT_EXTERNAL)
continue;
sprintf(filename, "%s.%s", pNode->name, pNode->type);
if (!Bwildmatch(filename, pattern))
continue;
switch (klistaddentry(&fnlist->findfiles, filename, CACHE1D_FIND_FILE, CACHE1D_SOURCE_GRP))
{
case -1:
return;
case 0:
fnlist->numfiles++;
break;
}
}
}
void Resource::PurgeCache(void)
{
#ifndef USE_QHEAP
for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
{
DICTNODE *pDict = (DICTNODE*)node;
if (!(pDict->flags & DICT_LOAD))
{
dassert(pDict->lockCount == 0);
dassert(pDict->ptr != NULL);
Free(pDict->ptr);
pDict->ptr = NULL;
RemoveMRU(pDict);
}
}
#endif
}
void Resource::PrecacheSounds(void)
{
for (unsigned int i = 0; i < count; i++)
{
DICTNODE *pNode = &dict[i];
if ((!strcmp(pNode->type, "RAW") || !strcmp(pNode->type, "SFX")) && !pNode->ptr)
{
Load(pNode);
G_HandleAsync();
}
}
}
void Resource::RemoveNode(DICTNODE* pNode)
{
Flush(pNode);
if (pNode->name)
{
Free(pNode->name);
pNode->name = NULL;
}
if (pNode->type)
{
Free(pNode->type);
pNode->type = NULL;
}
if (pNode->path)
{
Free(pNode->path);
pNode->path = NULL;
}
*pNode = dict[--count];
Bmemset(&dict[count], 0, sizeof(DICTNODE));
if (pNode->ptr && !pNode->lockCount)
{
pNode->prev->next = pNode;
pNode->next->prev = pNode;
}
Reindex();
}
END_BLD_NS