mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-26 05:41:52 +00:00
27a59a0cbc
vulkan, wasapi, quake injector features added. irc, avplug, cef plugins/drivers reworked/updated/added openal reverb, doppler effects added. 'dir' console command now attempts to view clicked files. lots of warning fixes, should now only be deprecation warnings for most targets (depending on compiler version anyway...). SendEntity finally reworked to use flags properly. effectinfo improved, other smc-targetted fixes. mapcluster stuff now has support for linux. .basebone+.baseframe now exist in ssqc. qcc: -Fqccx supports qccx syntax, including qccx hacks. don't expect these to work in fteqw nor dp though. qcc: rewrote function call handling to use refs rather than defs. this makes struct passing more efficient and makes the __out keyword usable with fields etc. qccgui: can cope a little better with non-unicode files. can now represent most quake chars. qcc: suppressed warnings from *extensions.qc git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5000 fc73d0e0-1445-4013-8a0c-d673dee63da5
920 lines
22 KiB
C
920 lines
22 KiB
C
#include "quakedef.h"
|
|
#include "../plugin.h"
|
|
//#include "../engine.h"
|
|
#include "fs.h"
|
|
#include <assert.h>
|
|
|
|
#include "../../engine/libs/zlib.h"
|
|
#include "blast.h"
|
|
|
|
//http://bazaar.launchpad.net/~jeanfrancois.roy/mpqkit/trunk/files
|
|
//http://www.zezula.net/en/mpq/main.html
|
|
//http://www.wc3c.net/tools/specs/QuantamMPQFormat.txt
|
|
|
|
#ifdef MULTITHREAD
|
|
threading_t *threading;
|
|
#define Sys_CreateMutex threading->CreateMutex
|
|
#define Sys_LockMutex threading->LockMutex
|
|
#define Sys_UnlockMutex threading->UnlockMutex
|
|
#define Sys_DestroyMutex threading->DestroyMutex
|
|
#endif
|
|
|
|
typedef unsigned long long ofs_t;
|
|
|
|
typedef struct
|
|
{
|
|
char mpq_magic[4];
|
|
unsigned int header_size;
|
|
unsigned int archive_size;
|
|
unsigned short version;
|
|
unsigned short sector_size_shift;
|
|
unsigned int hash_table_offset;
|
|
unsigned int block_table_offset;
|
|
unsigned int hash_table_length;
|
|
unsigned int block_table_length;
|
|
} mpqheader_t;
|
|
|
|
enum
|
|
{
|
|
MPQFileValid = 0x80000000,
|
|
MPQFileHasSectorAdlers = 0x04000000,
|
|
MPQFileStopSearchMarker = 0x02000000,
|
|
MPQFileOneSector = 0x01000000,
|
|
MPQFilePatch = 0x00100000,
|
|
MPQFileOffsetAdjustedKey = 0x00020000,
|
|
MPQFileEncrypted = 0x00010000,
|
|
MPQFileCompressed = 0x00000200,
|
|
MPQFileDiabloCompressed = 0x00000100,
|
|
MPQFileFlagsMask = 0x87030300
|
|
};
|
|
typedef struct
|
|
{
|
|
unsigned int offset;
|
|
unsigned int archived_size;
|
|
unsigned int size;
|
|
unsigned int flags;
|
|
} mpqblock_t;
|
|
|
|
typedef struct
|
|
{
|
|
unsigned int hash_a;
|
|
unsigned int hash_b;
|
|
unsigned short locale;
|
|
unsigned short platform;
|
|
unsigned int block_table_index;
|
|
} mpqhash_t;
|
|
|
|
typedef struct
|
|
{
|
|
searchpathfuncs_t pub;
|
|
|
|
char desc[MAX_OSPATH];
|
|
void *mutex;
|
|
vfsfile_t *file;
|
|
ofs_t filestart;
|
|
ofs_t fileend;
|
|
|
|
unsigned int references;
|
|
|
|
unsigned int sectorsize;
|
|
|
|
mpqhash_t *hashdata;
|
|
unsigned int hashentries;
|
|
|
|
mpqblock_t *blockdata;
|
|
unsigned int blockentries;
|
|
|
|
char *listfile;
|
|
|
|
mpqheader_t header_0;
|
|
struct
|
|
{
|
|
unsigned long long extended_block_offset_table_offset;
|
|
unsigned short hash_table_offset_high;
|
|
unsigned short block_table_offset_high;
|
|
} header_1;
|
|
} mpqarchive_t;
|
|
|
|
typedef struct
|
|
{
|
|
vfsfile_t funcs;
|
|
unsigned int flags;
|
|
unsigned int encryptkey;
|
|
mpqarchive_t *archive;
|
|
qofs_t foffset;
|
|
qofs_t flength; //decompressed size
|
|
qofs_t alength; //size on disk
|
|
|
|
ofs_t archiveoffset;
|
|
|
|
unsigned int buffersect;
|
|
unsigned int bufferlength;
|
|
char *buffer;
|
|
unsigned int *sectortab;
|
|
} mpqfile_t;
|
|
|
|
|
|
static qboolean crypt_table_initialized = false;
|
|
static unsigned int crypt_table[0x500];
|
|
|
|
static void mpq_init_cryptography(void)
|
|
{
|
|
// prepare crypt_table
|
|
unsigned int seed = 0x00100001;
|
|
unsigned int index1 = 0;
|
|
unsigned int index2 = 0;
|
|
unsigned int i;
|
|
|
|
if (!crypt_table_initialized)
|
|
{
|
|
crypt_table_initialized = true;
|
|
|
|
for (index1 = 0; index1 < 0x100; index1++)
|
|
{
|
|
for (index2 = index1, i = 0; i < 5; i++, index2 += 0x100)
|
|
{
|
|
unsigned int temp1, temp2;
|
|
|
|
seed = (seed * 125 + 3) % 0x2AAAAB;
|
|
temp1 = (seed & 0xFFFF) << 0x10;
|
|
|
|
seed = (seed * 125 + 3) % 0x2AAAAB;
|
|
temp2 = (seed & 0xFFFF);
|
|
|
|
crypt_table[index2] = (temp1 | temp2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define HASH_POSITION 0
|
|
#define HASH_NAME_A 1
|
|
#define HASH_NAME_B 2
|
|
#define HASH_KEY 3
|
|
static unsigned int mpq_hash_cstring(const char *string, unsigned int type)
|
|
{
|
|
unsigned int seed1 = 0x7FED7FED;
|
|
unsigned int seed2 = 0xEEEEEEEE;
|
|
unsigned int shifted_type = (type << 8);
|
|
unsigned int ch;
|
|
|
|
assert(crypt_table_initialized);
|
|
assert(string);
|
|
|
|
while (*string != 0)
|
|
{
|
|
ch = *string++;
|
|
if (ch == '/')
|
|
ch = '\\';
|
|
if (ch > 0x60 && ch < 0x7b) ch -= 0x20;
|
|
|
|
seed1 = crypt_table[shifted_type + ch] ^ (seed1 + seed2);
|
|
seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
|
|
}
|
|
|
|
return seed1;
|
|
}
|
|
|
|
#define MPQSwapInt32LittleToHost(a) (a)
|
|
#define MPQSwapInt32HostToLittle(a) (a)
|
|
|
|
static void mpq_decrypt(void* data, size_t length, unsigned int key, qboolean disable_output_swapping)
|
|
{
|
|
unsigned int* buffer32 = (unsigned int*)data;
|
|
unsigned int seed = 0xEEEEEEEE;
|
|
unsigned int ch;
|
|
|
|
assert(crypt_table_initialized);
|
|
assert(data);
|
|
|
|
// round to 4 bytes
|
|
length = length / 4;
|
|
|
|
if (disable_output_swapping)
|
|
{
|
|
while (length-- > 0)
|
|
{
|
|
ch = MPQSwapInt32LittleToHost(*buffer32);
|
|
|
|
seed += crypt_table[0x400 + (key & 0xFF)];
|
|
ch = ch ^ (key + seed);
|
|
|
|
key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B);
|
|
seed = ch + seed + (seed << 5) + 3;
|
|
|
|
*buffer32++ = ch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (length-- > 0)
|
|
{
|
|
ch = MPQSwapInt32LittleToHost(*buffer32);
|
|
|
|
seed += crypt_table[0x400 + (key & 0xFF)];
|
|
ch = ch ^ (key + seed);
|
|
|
|
key = ((~key << 0x15) + 0x11111111) | (key >> 0x0B);
|
|
seed = ch + seed + (seed << 5) + 3;
|
|
|
|
*buffer32++ = MPQSwapInt32HostToLittle(ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define HASH_TABLE_EMPTY 0xffffffff
|
|
#define HASH_TABLE_DELETED 0xfffffffe
|
|
static unsigned int mpq_lookuphash(mpqarchive_t *mpq, const char *filename, int locale)
|
|
{
|
|
unsigned int initial_position = mpq_hash_cstring(filename, HASH_POSITION) % mpq->hashentries;
|
|
unsigned int current_position = initial_position;
|
|
unsigned int hash_a = mpq_hash_cstring(filename, HASH_NAME_A);
|
|
unsigned int hash_b = mpq_hash_cstring(filename, HASH_NAME_B);
|
|
|
|
// Search through the hash table until we either find the file we're looking for, or we find an unused hash table entry,
|
|
// indicating the end of the cluster of used hash table entries
|
|
while (mpq->hashdata[current_position].block_table_index != HASH_TABLE_EMPTY)
|
|
{
|
|
if (mpq->hashdata[current_position].block_table_index != HASH_TABLE_DELETED)
|
|
{
|
|
if (mpq->hashdata[current_position].hash_a == hash_a &&
|
|
mpq->hashdata[current_position].hash_b == hash_b &&
|
|
mpq->hashdata[current_position].locale == locale)
|
|
{
|
|
return current_position;
|
|
}
|
|
}
|
|
|
|
current_position++;
|
|
current_position %= mpq->hashentries;
|
|
|
|
//avoid infinity
|
|
if (current_position == initial_position)
|
|
break;
|
|
}
|
|
|
|
return HASH_TABLE_EMPTY;
|
|
}
|
|
|
|
static vfsfile_t *MPQ_OpenVFS(searchpathfuncs_t *handle, flocation_t *loc, const char *mode);
|
|
static void MPQ_ClosePath(searchpathfuncs_t *handle)
|
|
{
|
|
mpqarchive_t *mpq = (void*)handle;
|
|
Sys_LockMutex(mpq->mutex);
|
|
if (--mpq->references)
|
|
{
|
|
Sys_UnlockMutex(mpq->mutex);
|
|
return;
|
|
}
|
|
Sys_UnlockMutex(mpq->mutex);
|
|
Sys_DestroyMutex(mpq->mutex);
|
|
VFS_CLOSE(mpq->file);
|
|
free(mpq->blockdata);
|
|
free(mpq->hashdata);
|
|
free(mpq->listfile);
|
|
free(mpq);
|
|
}
|
|
static unsigned int MPQ_FindFile(searchpathfuncs_t *handle, flocation_t *loc, const char *name, void *hashedresult)
|
|
{
|
|
mpqarchive_t *mpq = (void*)handle;
|
|
unsigned int blockentry;
|
|
|
|
if (hashedresult)
|
|
{
|
|
mpqblock_t *block = hashedresult;
|
|
if (block >= mpq->blockdata && block <= mpq->blockdata + mpq->blockentries)
|
|
blockentry = (mpqblock_t*)block - mpq->blockdata;
|
|
else
|
|
return FF_NOTFOUND;
|
|
}
|
|
else
|
|
{
|
|
unsigned int hashentry = mpq_lookuphash(mpq, name, 0);
|
|
if (hashentry == HASH_TABLE_EMPTY)
|
|
return FF_NOTFOUND;
|
|
blockentry = mpq->hashdata[hashentry].block_table_index;
|
|
if (blockentry > mpq->blockentries)
|
|
return FF_NOTFOUND;
|
|
}
|
|
if (loc)
|
|
{
|
|
loc->index = blockentry;
|
|
loc->offset = 0;
|
|
*loc->rawname = 0;
|
|
loc->len = mpq->blockdata[blockentry].size;
|
|
// loc->foo = foo;
|
|
}
|
|
|
|
if (mpq->blockdata[blockentry].flags & MPQFilePatch)
|
|
{
|
|
Con_DPrintf("Cannot cope with patch files\n");
|
|
return FF_NOTFOUND;
|
|
}
|
|
return FF_FOUND;
|
|
}
|
|
static void MPQ_ReadFile(searchpathfuncs_t *handle, flocation_t *loc, char *buffer)
|
|
{
|
|
vfsfile_t *f;
|
|
f = MPQ_OpenVFS(handle, loc, "rb");
|
|
if (!f) //err...
|
|
return;
|
|
VFS_READ(f, buffer, loc->len);
|
|
VFS_CLOSE(f);
|
|
}
|
|
|
|
static int mpqwildcmp(const char *wild, const char *string, char **end)
|
|
{
|
|
int s, w;
|
|
while (*string)
|
|
{
|
|
s = *string;
|
|
if (s == '\r' || s == '\n' || s == ';')
|
|
break;
|
|
w = *wild;
|
|
|
|
if (s >= 'A' && s <= 'Z')
|
|
s = s-'A'+'a';
|
|
if (w >= 'A' && w <= 'Z')
|
|
w = w-'A'+'a';
|
|
|
|
if (w == '*')
|
|
{
|
|
w = wild[1];
|
|
if (w >= 'A' && w <= 'Z')
|
|
w = w-'A'+'a';
|
|
if (w == s || s == '/' || s == '\\')
|
|
{
|
|
//* terminates if we get a match on the char following it, or if its a \ or / char
|
|
wild++;
|
|
continue;
|
|
}
|
|
string++;
|
|
}
|
|
else if ((w == s) || (w == '?'))
|
|
{
|
|
//this char matches
|
|
wild++;
|
|
string++;
|
|
}
|
|
else
|
|
{
|
|
//failure
|
|
while (*string && *string != '\r' && *string != '\n' && *string != ';')
|
|
string++;
|
|
*end = (char*)string;
|
|
return false;
|
|
}
|
|
}
|
|
*end = (char*)string;
|
|
|
|
while (*wild == '*')
|
|
{
|
|
wild++;
|
|
}
|
|
return !*wild;
|
|
}
|
|
static int MPQ_EnumerateFiles(searchpathfuncs_t *handle, const char *match, int (QDECL *func)(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath), void *parm)
|
|
{
|
|
int ok = 1;
|
|
char *s, *n;
|
|
char name[MAX_QPATH];
|
|
flocation_t loc;
|
|
mpqarchive_t *mpq = (mpqarchive_t*)handle;
|
|
if (mpq->listfile)
|
|
{
|
|
s = mpq->listfile;
|
|
for (s = mpq->listfile; *s && ok; s = n)
|
|
{
|
|
if (mpqwildcmp(match, s, &n) && n - s < MAX_QPATH-1)
|
|
{
|
|
memcpy(name, s, n - s);
|
|
name[n-s] = 0;
|
|
|
|
if (!MPQ_FindFile(handle, &loc, name, NULL))
|
|
loc.len = 0;
|
|
ok = func(name, loc.len, 0, parm, handle);
|
|
}
|
|
|
|
while (*n == '\n' || *n == '\r' || *n == ';')
|
|
n++;
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
static void MPQ_BuildHash(searchpathfuncs_t *handle, int depth, void (QDECL *AddFileHash)(int depth, const char *fname, fsbucket_t *filehandle, void *pathhandle))
|
|
{
|
|
char *s, *n;
|
|
char name[MAX_QPATH];
|
|
mpqarchive_t *mpq = (void*)handle;
|
|
flocation_t loc;
|
|
if (mpq->listfile)
|
|
{
|
|
s = mpq->listfile;
|
|
for (s = mpq->listfile; ; s = n)
|
|
{
|
|
while (*s == '\n' || *s == '\r' || *s == ';')
|
|
s++;
|
|
if (!*s)
|
|
break;
|
|
n = s;
|
|
while (*n && *n != '\r' && *n != '\n' && *n != ';')
|
|
n++;
|
|
|
|
if (n-s >= sizeof(name))
|
|
continue;
|
|
|
|
memcpy(name, s, n - s);
|
|
name[n-s] = 0;
|
|
//precompute the name->block lookup. fte normally does the hashing outside the archive code.
|
|
//however, its possible multiple hash tables point to a single block, so we need to pass null for the third arg (or allocate fsbucket_ts one per hash instead of buckets).
|
|
if (MPQ_FindFile(&mpq->pub, &loc, name, NULL))
|
|
AddFileHash(depth, name, NULL, &mpq->blockdata[loc.index]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int MPQ_GeneratePureCRC (searchpathfuncs_t *handle, int seed, int usepure)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static qboolean MPQ_PollChanges(searchpathfuncs_t *handle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static searchpathfuncs_t *MPQ_OpenArchive(vfsfile_t *file, const char *desc, const char *prefix)
|
|
{
|
|
flocation_t lloc;
|
|
mpqarchive_t *mpq;
|
|
mpqheader_t header;
|
|
ofs_t block_ofs;
|
|
ofs_t hash_ofs;
|
|
|
|
VFS_SEEK(file, 0);
|
|
|
|
if (prefix && *prefix)
|
|
return NULL; //not supported at this time
|
|
|
|
if (VFS_READ(file, &header, sizeof(header)) != sizeof(header))
|
|
return NULL;
|
|
|
|
if (memcmp(header.mpq_magic, "MPQ\x1a", 4))
|
|
return NULL;
|
|
|
|
mpq = malloc(sizeof(*mpq));
|
|
memset(mpq, 0, sizeof(*mpq));
|
|
Q_strlcpy(mpq->desc, desc, sizeof(mpq->desc));
|
|
mpq->header_0 = header;
|
|
mpq->file = file;
|
|
mpq->filestart = 0;
|
|
mpq->sectorsize = 512 << mpq->header_0.sector_size_shift;
|
|
|
|
block_ofs = header.block_table_offset;
|
|
hash_ofs = header.hash_table_offset;
|
|
if (header.version >= 1)
|
|
{
|
|
VFS_READ(file, &mpq->header_1, sizeof(mpq->header_1));
|
|
}
|
|
block_ofs |= ((ofs_t)mpq->header_1.block_table_offset_high)<<32u;
|
|
hash_ofs |= ((ofs_t)mpq->header_1.hash_table_offset_high)<<32u;
|
|
|
|
mpq->fileend = VFS_GETLEN(file);
|
|
|
|
if (block_ofs + sizeof(*mpq->blockdata) * mpq->blockentries > mpq->fileend ||
|
|
hash_ofs + sizeof(*mpq->hashdata) * mpq->hashentries > mpq->fileend)
|
|
{
|
|
Con_Printf("\"%s\" appears truncated\n", desc);
|
|
free(mpq);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
mpq->hashentries = mpq->header_0.hash_table_length;
|
|
mpq->hashdata = malloc(sizeof(*mpq->hashdata) * mpq->hashentries);
|
|
VFS_SEEK(file, hash_ofs);
|
|
VFS_READ(file, mpq->hashdata, sizeof(*mpq->hashdata) * mpq->hashentries);
|
|
mpq_decrypt(mpq->hashdata, sizeof(*mpq->hashdata) * mpq->hashentries, mpq_hash_cstring("(hash table)", HASH_KEY), false);
|
|
|
|
mpq->blockentries = mpq->header_0.block_table_length;
|
|
mpq->blockdata = malloc(sizeof(*mpq->blockdata) * mpq->blockentries);
|
|
VFS_SEEK(file, block_ofs);
|
|
VFS_READ(file, mpq->blockdata, sizeof(*mpq->blockdata) * mpq->blockentries);
|
|
mpq_decrypt(mpq->blockdata, sizeof(*mpq->blockdata) * mpq->blockentries, mpq_hash_cstring("(block table)", HASH_KEY), true);
|
|
|
|
/*for (i = 0; i < mpq->header_0.block_table_length; i++)
|
|
{
|
|
Con_Printf("offset = %08x, csize = %i, usize=%i, flags=%s%s%s%s%s%s%s%s%s\n", mpq->blockdata[i].offset, mpq->blockdata[i].archived_size, mpq->blockdata[i].size,
|
|
(mpq->blockdata[i].flags & MPQFileValid)?"valid ":"",
|
|
(mpq->blockdata[i].flags & MPQFileHasSectorAdlers)?"sectoradlers ":"",
|
|
(mpq->blockdata[i].flags & MPQFileStopSearchMarker)?"stopsearch ":"",
|
|
(mpq->blockdata[i].flags & MPQFileOneSector)?"singlesector ":"",
|
|
(mpq->blockdata[i].flags & MPQFileOffsetAdjustedKey)?"offsetadjust ":"",
|
|
(mpq->blockdata[i].flags & MPQFileEncrypted)?"encrypted ":"",
|
|
(mpq->blockdata[i].flags & MPQFileCompressed)?"compressed ":"",
|
|
(mpq->blockdata[i].flags & MPQFileDiabloCompressed)?"dcompressed ":"",
|
|
(mpq->blockdata[i].flags & ~MPQFileFlagsMask)?"OTHERS ":""
|
|
);
|
|
}*/
|
|
|
|
|
|
mpq->references = 1;
|
|
mpq->mutex = Sys_CreateMutex();
|
|
|
|
if (MPQ_FindFile(&mpq->pub, &lloc, "(listfile)", NULL))
|
|
{
|
|
char *bs;
|
|
mpq->listfile = malloc(lloc.len+2);
|
|
mpq->listfile[0] = 0;
|
|
mpq->listfile[lloc.len] = 0;
|
|
mpq->listfile[lloc.len+1] = 0;
|
|
MPQ_ReadFile(&mpq->pub, &lloc, mpq->listfile);
|
|
bs = mpq->listfile;
|
|
while(1)
|
|
{
|
|
bs = strchr(bs, '\\');
|
|
if (bs)
|
|
*bs++ = '/';
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
mpq->pub.fsver = FSVER;
|
|
mpq->pub.ClosePath = MPQ_ClosePath;
|
|
mpq->pub.BuildHash = MPQ_BuildHash;
|
|
mpq->pub.FindFile = MPQ_FindFile;
|
|
mpq->pub.ReadFile = MPQ_ReadFile;
|
|
mpq->pub.EnumerateFiles = MPQ_EnumerateFiles;
|
|
mpq->pub.GeneratePureCRC = MPQ_GeneratePureCRC;
|
|
mpq->pub.OpenVFS = MPQ_OpenVFS;
|
|
mpq->pub.PollChanges = MPQ_PollChanges;
|
|
|
|
return &mpq->pub;
|
|
}
|
|
|
|
|
|
struct blastdata_s
|
|
{
|
|
void *outdata;
|
|
unsigned int outlen;
|
|
void *indata;
|
|
unsigned int inlen;
|
|
};
|
|
static unsigned mpqf_blastin(void *how, unsigned char **buf)
|
|
{
|
|
struct blastdata_s *args = how;
|
|
*buf = args->indata;
|
|
return args->inlen;
|
|
}
|
|
static int mpqf_blastout(void *how, unsigned char *buf, unsigned len)
|
|
{
|
|
struct blastdata_s *args = how;
|
|
if (len > args->outlen)
|
|
return 1;
|
|
memcpy(args->outdata, buf, len);
|
|
args->outdata = (char*)args->outdata + len;
|
|
args->outlen -= len;
|
|
return 0;
|
|
}
|
|
static void MPQF_decompress(qboolean legacymethod, void *outdata, unsigned int outlen, void *indata, unsigned int inlen)
|
|
{
|
|
int ret;
|
|
|
|
int methods;
|
|
if (legacymethod)
|
|
methods = 8;
|
|
else
|
|
{
|
|
methods = *(unsigned char*)indata;
|
|
indata = (char*)indata + 1;
|
|
inlen--;
|
|
}
|
|
|
|
if (methods == 8)
|
|
{
|
|
struct blastdata_s args = {outdata, outlen, indata, inlen};
|
|
blast(mpqf_blastin, &args, mpqf_blastout, &args);
|
|
}
|
|
else if (methods == 2)
|
|
{
|
|
z_stream strm =
|
|
{
|
|
indata,
|
|
inlen,
|
|
0,
|
|
|
|
outdata,
|
|
outlen,
|
|
0,
|
|
|
|
NULL,
|
|
NULL,
|
|
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
|
|
Z_UNKNOWN,
|
|
0,
|
|
0
|
|
};
|
|
|
|
inflateInit2(&strm, MAX_WBITS);
|
|
|
|
while ((ret=inflate(&strm, Z_SYNC_FLUSH)) != Z_STREAM_END)
|
|
{
|
|
if (strm.avail_in == 0 || strm.avail_out == 0)
|
|
{
|
|
if (strm.avail_in == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (strm.avail_out == 0)
|
|
{
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//doh, it terminated for no reason
|
|
if (ret != Z_STREAM_END)
|
|
{
|
|
inflateEnd(&strm);
|
|
Con_Printf("Couldn't decompress gz file\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
inflateEnd(&strm);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("mpq: unsupported decompression method - %x\n", methods);
|
|
memset(outdata, 0, outlen);
|
|
}
|
|
}
|
|
|
|
static int MPQF_readbytes (struct vfsfile_s *file, void *buffer, int bytestoread)
|
|
{
|
|
int bytesread = 0;
|
|
mpqfile_t *f = (mpqfile_t *)file;
|
|
|
|
if (bytestoread + f->foffset > f->flength)
|
|
bytestoread = f->flength - f->foffset;
|
|
if (bytestoread < 0)
|
|
return 0;
|
|
|
|
if (!(f->flags & (MPQFileCompressed|MPQFileDiabloCompressed)))
|
|
{
|
|
//no compression, just a raw file.
|
|
Sys_LockMutex(f->archive->mutex);
|
|
VFS_SEEK(f->archive->file, f->archiveoffset + f->foffset);
|
|
bytesread = VFS_READ(f->archive->file, buffer, bytestoread);
|
|
Sys_UnlockMutex(f->archive->mutex);
|
|
f->foffset += bytesread;
|
|
}
|
|
else if (f->flags & MPQFileOneSector)
|
|
{
|
|
//fairly simple packed data, no sector nonsense. decode in one go
|
|
if (!f->buffer)
|
|
{
|
|
char *cdata = malloc(f->alength);
|
|
f->buffer = malloc(f->flength);
|
|
Sys_LockMutex(f->archive->mutex);
|
|
VFS_SEEK(f->archive->file, f->archiveoffset);
|
|
VFS_READ(f->archive->file, cdata, f->alength);
|
|
Sys_UnlockMutex(f->archive->mutex);
|
|
|
|
if (f->flags & MPQFileEncrypted)
|
|
{
|
|
mpq_decrypt(cdata, f->alength, f->encryptkey, false);
|
|
}
|
|
if (f->flags & (MPQFileCompressed|MPQFileDiabloCompressed))
|
|
{
|
|
//decompress
|
|
MPQF_decompress(!!(f->flags&MPQFileDiabloCompressed), f->buffer, f->flength, cdata, f->alength);
|
|
}
|
|
else
|
|
{
|
|
//lazy...
|
|
memcpy(f->buffer, cdata, f->flength);
|
|
}
|
|
free(cdata);
|
|
}
|
|
memcpy((char*)buffer+bytesread, f->buffer + f->foffset, bytestoread);
|
|
f->foffset += bytestoread;
|
|
bytesread += bytestoread;
|
|
}
|
|
else
|
|
{
|
|
//sectors are weird.
|
|
//sectors are allocated for decompressed size, not compressed. I have no idea how this works.
|
|
//time to find out.
|
|
for (;;)
|
|
{
|
|
int numsects = (f->flength + (f->archive->sectorsize) - 1) / f->archive->sectorsize;
|
|
int sectidx = f->foffset / f->archive->sectorsize;
|
|
qboolean lastsect = false;
|
|
int chunkofs, chunklen;
|
|
if (sectidx >= numsects-1)
|
|
{
|
|
lastsect = true;
|
|
sectidx = numsects-1;
|
|
}
|
|
|
|
if (sectidx != f->buffersect || !f->buffer)
|
|
{
|
|
int rawsize;
|
|
char *cdata;
|
|
f->buffersect = sectidx;
|
|
if (!f->sectortab)
|
|
{
|
|
f->sectortab = malloc((numsects+1) * sizeof(*f->sectortab));
|
|
if (!f->sectortab)
|
|
pSys_Error("out of memory");
|
|
Sys_LockMutex(f->archive->mutex);
|
|
VFS_SEEK(f->archive->file, f->archiveoffset);
|
|
VFS_READ(f->archive->file, f->sectortab, (numsects+1) * sizeof(*f->sectortab));
|
|
Sys_UnlockMutex(f->archive->mutex);
|
|
|
|
if (f->flags & MPQFileEncrypted)
|
|
mpq_decrypt(f->sectortab, (numsects+1) * sizeof(*f->sectortab), f->encryptkey-1, true);
|
|
}
|
|
//data is packed, sector table gives offsets. there's an extra index on the end which is the size of the last sector.
|
|
rawsize = f->sectortab[sectidx+1]-f->sectortab[sectidx];
|
|
|
|
cdata = malloc(rawsize);
|
|
if (!cdata)
|
|
pSys_Error("out of memory");
|
|
if (!f->buffer)
|
|
f->buffer = malloc(f->archive->sectorsize);
|
|
if (!f->buffer)
|
|
pSys_Error("out of memory");
|
|
Sys_LockMutex(f->archive->mutex);
|
|
VFS_SEEK(f->archive->file, f->archiveoffset + f->sectortab[sectidx]);
|
|
VFS_READ(f->archive->file, cdata, rawsize);
|
|
Sys_UnlockMutex(f->archive->mutex);
|
|
|
|
if (lastsect)
|
|
f->bufferlength = f->flength - ((numsects-1)*f->archive->sectorsize);
|
|
else
|
|
f->bufferlength = f->archive->sectorsize;
|
|
|
|
if (f->flags & MPQFileEncrypted)
|
|
mpq_decrypt(cdata, rawsize, f->encryptkey+sectidx, false);
|
|
if (f->flags & (MPQFileCompressed|MPQFileDiabloCompressed))
|
|
{
|
|
//decompress
|
|
MPQF_decompress(!!(f->flags&MPQFileDiabloCompressed), f->buffer, f->bufferlength, cdata, rawsize);
|
|
}
|
|
else
|
|
{
|
|
//lazy...
|
|
memcpy(f->buffer, cdata, f->bufferlength);
|
|
}
|
|
free(cdata);
|
|
}
|
|
|
|
chunkofs = (f->foffset%f->archive->sectorsize);
|
|
chunklen = f->archive->sectorsize - chunkofs;
|
|
if (chunklen > bytestoread)
|
|
chunklen = bytestoread;
|
|
bytestoread -= chunklen;
|
|
memcpy((char*)buffer+bytesread, f->buffer + chunkofs, chunklen);
|
|
f->foffset += chunklen;
|
|
bytesread += chunklen;
|
|
if (!chunklen || !bytestoread)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bytesread;
|
|
}
|
|
static int MPQF_writebytes (struct vfsfile_s *file, const void *buffer, int bytestoread)
|
|
{
|
|
// mpqfile_t *f = (mpqfile_t *)file;
|
|
return 0;
|
|
}
|
|
static qboolean MPQF_seek (struct vfsfile_s *file, qofs_t pos)
|
|
{
|
|
mpqfile_t *f = (mpqfile_t *)file;
|
|
if (pos > f->flength)
|
|
return false;
|
|
f->foffset = pos;
|
|
return true;
|
|
}
|
|
static qofs_t MPQF_tell (struct vfsfile_s *file)
|
|
{
|
|
mpqfile_t *f = (mpqfile_t *)file;
|
|
return f->foffset;
|
|
}
|
|
static qofs_t MPQF_getlen (struct vfsfile_s *file)
|
|
{
|
|
mpqfile_t *f = (mpqfile_t *)file;
|
|
return f->flength;
|
|
}
|
|
static qboolean MPQF_close (struct vfsfile_s *file)
|
|
{
|
|
mpqfile_t *f = (mpqfile_t *)file;
|
|
if (f->buffer)
|
|
free(f->buffer);
|
|
if (f->sectortab)
|
|
free(f->sectortab);
|
|
|
|
MPQ_ClosePath(&f->archive->pub);
|
|
|
|
free(f);
|
|
|
|
return true;
|
|
}
|
|
static void MPQF_flush (struct vfsfile_s *file)
|
|
{
|
|
}
|
|
|
|
static qboolean MPQF_GetKey(unsigned int flags, unsigned int blockoffset, unsigned int blocksize, unsigned int *key)
|
|
{
|
|
if (flags & MPQFileEncrypted)
|
|
{
|
|
*key = mpq_hash_cstring("(listfile)", HASH_KEY);
|
|
|
|
if (flags & MPQFileOffsetAdjustedKey)
|
|
*key = (*key + (unsigned int)(blockoffset)) ^ blocksize;
|
|
}
|
|
else
|
|
*key = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static vfsfile_t *MPQ_OpenVFS(searchpathfuncs_t *handle, flocation_t *loc, const char *mode)
|
|
{
|
|
mpqarchive_t *mpq = (void*)handle;
|
|
mpqblock_t *block = &mpq->blockdata[loc->index];
|
|
mpqfile_t *f;
|
|
|
|
if (block->flags & MPQFilePatch)
|
|
{
|
|
Con_Printf("Cannot cope with patch files\n");
|
|
return NULL;
|
|
}
|
|
|
|
f = malloc(sizeof(*f));
|
|
f->buffer = NULL;
|
|
f->buffersect = -1;
|
|
f->sectortab = NULL;
|
|
f->foffset = 0;
|
|
f->archiveoffset = block->offset;
|
|
MPQF_GetKey(block->flags, f->archiveoffset, block->size, &f->encryptkey);
|
|
f->flags = block->flags;
|
|
f->archive = mpq;
|
|
f->flength = block->size;
|
|
f->alength = block->archived_size;
|
|
f->funcs.ReadBytes = MPQF_readbytes;
|
|
f->funcs.WriteBytes = MPQF_writebytes;
|
|
f->funcs.Seek = MPQF_seek;
|
|
f->funcs.Tell = MPQF_tell;
|
|
f->funcs.GetLen = MPQF_getlen;
|
|
f->funcs.Close = MPQF_close;
|
|
f->funcs.Flush = MPQF_flush;
|
|
|
|
Sys_LockMutex(mpq->mutex);
|
|
mpq->references++;
|
|
Sys_UnlockMutex(mpq->mutex);
|
|
|
|
return &f->funcs;
|
|
}
|
|
|
|
qintptr_t Plug_Init(qintptr_t *args)
|
|
{
|
|
mpq_init_cryptography();
|
|
|
|
#ifdef MULTITHREAD
|
|
if (CHECKBUILTIN(Sys_GetThreadingFuncs))
|
|
threading = pSys_GetThreadingFuncs(sizeof(*threading));
|
|
if (!threading)
|
|
{
|
|
Con_Printf("mpq: Engine doesn't support threading\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
//we can't cope with being closed randomly. files cannot be orphaned safely.
|
|
//so ask the engine to ensure we don't get closed before everything else is.
|
|
pPlug_ExportNative("UnsafeClose", NULL);
|
|
|
|
if (!pPlug_ExportNative("FS_RegisterArchiveType_mpq", MPQ_OpenArchive))
|
|
{
|
|
Con_Printf("mpq: Engine doesn't support filesystem plugins\n");
|
|
return false;
|
|
}
|
|
if (!pPlug_ExportNative("FS_RegisterArchiveType_MPQ", MPQ_OpenArchive))
|
|
{
|
|
Con_Printf("mpq: Engine doesn't support filesystem plugins\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|