fteqw/plugins/mpq/fs_mpq.c

845 lines
20 KiB
C
Raw Normal View History

#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
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
{
char desc[MAX_OSPATH];
vfsfile_t *file;
ofs_t filestart;
ofs_t fileend;
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;
unsigned int foffset;
unsigned int flength;
unsigned int alength;
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];
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
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)
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
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;
}
vfsfile_t *MPQ_OpenVFS(void *handle, flocation_t *loc, const char *mode);
void MPQ_GetDisplayPath(void *handle, char *outpath, unsigned int pathsize)
{
mpqarchive_t *mpq = handle;
strlcpy(outpath, mpq->desc, pathsize);
}
void MPQ_ClosePath(void *handle)
{
mpqarchive_t *mpq = handle;
VFS_CLOSE(mpq->file);
free(mpq->blockdata);
free(mpq->hashdata);
free(mpq->listfile);
free(mpq);
}
qboolean MPQ_FindFile(void *handle, flocation_t *loc, const char *name, void *hashedresult)
{
mpqarchive_t *mpq = handle;
unsigned int hashentry;
unsigned int blockentry;
hashentry = mpq_lookuphash(handle, name, 0);
if (hashentry == HASH_TABLE_EMPTY)
return false;
blockentry = mpq->hashdata[hashentry].block_table_index;
if (blockentry > mpq->blockentries)
return false;
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 false;
}
return true;
}
void MPQ_ReadFile(void *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)
{
while (*string)
{
if (*string == '\r' || *string == '\n' || *string == ';')
break;
if (*wild == '*')
{
if (wild[1] == *string || *string == '/' || *string == '\\')
{
//* terminates if we get a match on the char following it, or if its a \ or / char
wild++;
continue;
}
string++;
}
else if ((*wild == *string) || (*wild == '?'))
{
//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;
}
int MPQ_EnumerateFiles(void *handle, const char *match, int (QDECL *func)(const char *fname, int fsize, void *parm, void *spath), void *parm)
{
int ok = 1;
char *s, *n;
char name[MAX_QPATH];
flocation_t loc;
mpqarchive_t *mpq = 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, parm, handle);
}
while (*n == '\n' || *n == '\r' || *n == ';')
n++;
}
}
return ok;
}
void MPQ_BuildHash(void *handle, int depth, void (QDECL *AddFileHash)(int depth, const char *fname, fsbucket_t *filehandle, void *pathhandle))
{
mpqarchive_t *wp = handle;
char *s, *n;
char name[MAX_QPATH];
mpqarchive_t *mpq = handle;
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++;
memcpy(name, s, n - s);
name[n-s] = 0;
AddFileHash(depth, name, NULL, wp);
}
}
}
void *MPQ_OpenNew(vfsfile_t *file, const char *desc)
{
flocation_t lloc;
mpqarchive_t *mpq;
mpqheader_t header;
ofs_t block_ofs;
ofs_t hash_ofs;
VFS_SEEK(file, 0);
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));
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 ":""
);
}*/
if (MPQ_FindFile(mpq, &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, &lloc, mpq->listfile);
bs = mpq->listfile;
while(1)
{
bs = strchr(bs, '\\');
if (bs)
*bs++ = '/';
else
break;
}
}
return mpq;
}
int MPQ_GeneratePureCRC (void *handle, int seed, int usepure)
{
return 0;
}
struct blastdata_s
{
void *outdata;
unsigned int outlen;
void *indata;
unsigned int inlen;
};
unsigned mpqf_blastin(void *how, unsigned char **buf)
{
struct blastdata_s *args = how;
*buf = args->indata;
return args->inlen;
}
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;
}
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);
}
}
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.
VFS_SEEK(f->archive->file, f->archiveoffset + f->foffset);
bytesread = VFS_READ(f->archive->file, buffer, bytestoread);
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);
VFS_SEEK(f->archive->file, f->archiveoffset);
VFS_READ(f->archive->file, cdata, f->alength);
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");
VFS_SEEK(f->archive->file, f->archiveoffset);
VFS_READ(f->archive->file, f->sectortab, (numsects+1) * sizeof(*f->sectortab));
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");
VFS_SEEK(f->archive->file, f->archiveoffset + f->sectortab[sectidx]);
VFS_READ(f->archive->file, cdata, rawsize);
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;
}
int MPQF_writebytes (struct vfsfile_s *file, const void *buffer, int bytestoread)
{
mpqfile_t *f = (mpqfile_t *)file;
return 0;
}
qboolean MPQF_seek (struct vfsfile_s *file, unsigned long pos)
{
mpqfile_t *f = (mpqfile_t *)file;
if (pos > f->flength)
return false;
f->foffset = pos;
return true;
}
unsigned long MPQF_tell (struct vfsfile_s *file)
{
mpqfile_t *f = (mpqfile_t *)file;
return f->foffset;
}
unsigned long MPQF_getlen (struct vfsfile_s *file)
{
mpqfile_t *f = (mpqfile_t *)file;
return f->flength;
}
void MPQF_close (struct vfsfile_s *file)
{
mpqfile_t *f = (mpqfile_t *)file;
if (f->buffer)
free(f->buffer);
if (f->sectortab)
free(f->sectortab);
free(f);
}
void MPQF_flush (struct vfsfile_s *file)
{
}
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;
}
vfsfile_t *MPQ_OpenVFS(void *handle, flocation_t *loc, const char *mode)
{
mpqarchive_t *mpq = 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->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;
return &f->funcs;
}
qboolean MPQ_PollChanges(void *handle)
{
return false;
}
searchpathfuncs_t funcs =
{
MPQ_GetDisplayPath,
MPQ_ClosePath,
MPQ_BuildHash,
MPQ_FindFile,
MPQ_ReadFile,
MPQ_EnumerateFiles,
MPQ_OpenNew,
MPQ_GeneratePureCRC,
MPQ_OpenVFS,
MPQ_PollChanges,
};
qintptr_t Plug_Init(qintptr_t *args)
{
mpq_init_cryptography();
//we can't cope with being closed randomly. files cannot be orphaned safely.
pPlug_ExportNative("UnsafeClose", NULL);
if (!pPlug_ExportNative("FS_RegisterArchiveType_mpq", &funcs))
{
Con_Printf("avplug: Engine doesn't support media decoder plugins\n");
return false;
}
if (!pPlug_ExportNative("FS_RegisterArchiveType_MPQ", &funcs))
{
Con_Printf("avplug: Engine doesn't support media decoder plugins\n");
return false;
}
return true;
}