mirror of
https://github.com/Shpoike/Quakespasm.git
synced 2024-11-10 15:31:39 +00:00
823 lines
26 KiB
C
823 lines
26 KiB
C
|
#include "quakedef.h"
|
||
|
|
||
|
#ifdef USE_ZLIB
|
||
|
#include <zlib.h>
|
||
|
#else
|
||
|
#pragma message("zips supported but with no zlib")
|
||
|
#endif
|
||
|
|
||
|
//ZIP features:
|
||
|
//zip64 for huge zips, except that quakespasm's filesystem calls are all otherwise 31bit.
|
||
|
//utf-8 encoding support (non-utf-8 is always ibm437)
|
||
|
//compression mode: store
|
||
|
//compression mode: deflate (via zlib)
|
||
|
//bigendian cpus. everything misaligned.
|
||
|
|
||
|
//NOT supported:
|
||
|
//symlink flag (files are ignored completely)
|
||
|
//compression mode: deflate64
|
||
|
//other compression modes
|
||
|
//split archives
|
||
|
//if central dir is crypted/compressed, the archive will fail to open
|
||
|
//if a file is crypted/compressed, the file will (internally) be marked as corrupt
|
||
|
//crc verification
|
||
|
//infozip utf-8 name override.
|
||
|
//other 'extra' fields.
|
||
|
|
||
|
#define qofs_t long long
|
||
|
#define qofs_Make(low,high) ((unsigned int)(low) | ((qofs_t)(high)<<32))
|
||
|
|
||
|
struct zipinfo
|
||
|
{
|
||
|
unsigned int thisdisk; //this disk number
|
||
|
unsigned int centraldir_startdisk; //central directory starts on this disk
|
||
|
qofs_t centraldir_numfiles_disk; //number of files in the centraldir on this disk
|
||
|
qofs_t centraldir_numfiles_all; //number of files in the centraldir on all disks
|
||
|
qofs_t centraldir_size; //total size of the centraldir
|
||
|
qofs_t centraldir_offset; //start offset of the centraldir with respect to the first disk that contains it
|
||
|
unsigned short commentlength; //length of the comment at the end of the archive.
|
||
|
|
||
|
unsigned int zip64_centraldirend_disk; //zip64 weirdness
|
||
|
qofs_t zip64_centraldirend_offset;
|
||
|
unsigned int zip64_diskcount;
|
||
|
qofs_t zip64_eocdsize;
|
||
|
unsigned short zip64_version_madeby;
|
||
|
unsigned short zip64_version_needed;
|
||
|
|
||
|
unsigned short centraldir_compressionmethod;
|
||
|
unsigned short centraldir_algid;
|
||
|
|
||
|
qofs_t centraldir_end;
|
||
|
qofs_t zipoffset;
|
||
|
};
|
||
|
struct zipcentralentry
|
||
|
{
|
||
|
unsigned char *fname;
|
||
|
qofs_t cesize;
|
||
|
unsigned int flags;
|
||
|
time_t mtime;
|
||
|
|
||
|
//PK12
|
||
|
unsigned short version_madeby;
|
||
|
unsigned short version_needed;
|
||
|
unsigned short gflags;
|
||
|
unsigned short cmethod;
|
||
|
unsigned short lastmodfiletime;
|
||
|
unsigned short lastmodfiledate;
|
||
|
unsigned int crc32;
|
||
|
qofs_t csize;
|
||
|
qofs_t usize;
|
||
|
unsigned short fnane_len;
|
||
|
unsigned short extra_len;
|
||
|
unsigned short comment_len;
|
||
|
unsigned int disknum;
|
||
|
unsigned short iattributes;
|
||
|
unsigned int eattributes;
|
||
|
qofs_t localheaderoffset; //from start of disk
|
||
|
};
|
||
|
|
||
|
struct ziplocalentry
|
||
|
{
|
||
|
//PK34
|
||
|
unsigned short version_needed;
|
||
|
unsigned short gpflags;
|
||
|
unsigned short cmethod;
|
||
|
unsigned short lastmodfiletime;
|
||
|
unsigned short lastmodfiledate;
|
||
|
unsigned int crc32;
|
||
|
qofs_t csize;
|
||
|
qofs_t usize;
|
||
|
unsigned short fname_len;
|
||
|
unsigned short extra_len;
|
||
|
};
|
||
|
|
||
|
#define LittleU2FromPtr(p) (unsigned int)((p)[0] | ((p)[1]<<8u))
|
||
|
#define LittleU4FromPtr(p) (unsigned int)((p)[0] | ((p)[1]<<8u) | ((p)[2]<<16u) | ((p)[3]<<24u))
|
||
|
#define LittleU8FromPtr(p) qofs_Make(LittleU4FromPtr(p), LittleU4FromPtr((p)+4))
|
||
|
|
||
|
#define SIZE_LOCALENTRY 30
|
||
|
#define SIZE_ENDOFCENTRALDIRECTORY 22
|
||
|
#define SIZE_ZIP64ENDOFCENTRALDIRECTORY_V1 56
|
||
|
#define SIZE_ZIP64ENDOFCENTRALDIRECTORY_V2 84
|
||
|
#define SIZE_ZIP64ENDOFCENTRALDIRECTORY SIZE_ZIP64ENDOFCENTRALDIRECTORY_V2
|
||
|
#define SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR 20
|
||
|
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
char name[MAX_QPATH];
|
||
|
qofs_t localpos;
|
||
|
qofs_t filelen;
|
||
|
unsigned int crc;
|
||
|
unsigned int flags;
|
||
|
// time_t time;
|
||
|
} zpackfile_t;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
int raw;
|
||
|
qofs_t rawsize;
|
||
|
|
||
|
size_t numfiles;
|
||
|
zpackfile_t *files;
|
||
|
} zipfile_t;
|
||
|
#define ZFL_DEFLATED 1 //need to use zlib
|
||
|
#define ZFL_STORED 2 //direct access is okay
|
||
|
#define ZFL_SYMLINK 4 //file is a symlink
|
||
|
#define ZFL_CORRUPT 8 //file is corrupt or otherwise unreadable.
|
||
|
#define ZFL_WEAKENCRYPT 16 //traditional zip encryption
|
||
|
|
||
|
|
||
|
//for zip->quake charsets
|
||
|
unsigned short ibmtounicode[256] =
|
||
|
{
|
||
|
0x0000,0x263A,0x263B,0x2665,0x2666,0x2663,0x2660,0x2022,0x25D8,0x25CB,0x25D9,0x2642,0x2640,0x266A,0x266B,0x263C, //0x00(non-ascii, display-only)
|
||
|
0x25BA,0x25C4,0x2195,0x203C,0x00B6,0x00A7,0x25AC,0x21A8,0x2191,0x2193,0x2192,0x2190,0x221F,0x2194,0x25B2,0x25BC, //0x10(non-ascii, display-only)
|
||
|
0x0020,0x0021,0x0022,0x0023,0x0024,0x0025,0x0026,0x0027,0x0028,0x0029,0x002A,0x002B,0x002C,0x002D,0x002E,0x002F, //0x20(ascii)
|
||
|
0x0030,0x0031,0x0032,0x0033,0x0034,0x0035,0x0036,0x0037,0x0038,0x0039,0x003A,0x003B,0x003C,0x003D,0x003E,0x003F, //0x30(ascii)
|
||
|
0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047,0x0048,0x0049,0x004A,0x004B,0x004C,0x004D,0x004E,0x004F, //0x40(ascii)
|
||
|
0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057,0x0058,0x0059,0x005A,0x005B,0x005C,0x005D,0x005E,0x005F, //0x50(ascii)
|
||
|
0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067,0x0068,0x0069,0x006A,0x006B,0x006C,0x006D,0x006E,0x006F, //0x60(ascii)
|
||
|
0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077,0x0078,0x0079,0x007A,0x007B,0x007C,0x007D,0x007E,0x2302, //0x70(mostly ascii, one display-only)
|
||
|
0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,0x00EA,0x00EB,0x00E8,0x00EF,0x00EE,0x00EC,0x00C4,0x00C5, //0x80(non-ascii, printable)
|
||
|
0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,0x00FF,0x00D6,0x00DC,0x00A2,0x00A3,0x00A5,0x20A7,0x0192, //0x90(non-ascii, printable)
|
||
|
0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,0x00BF,0x2310,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB, //0xa0(non-ascii, printable)
|
||
|
0x2591,0x2592,0x2593,0x2502,0x2524,0x2561,0x2562,0x2556,0x2555,0x2563,0x2551,0x2557,0x255D,0x255C,0x255B,0x2510, //0xb0(box-drawing, printable)
|
||
|
0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x255E,0x255F,0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x2567, //0xc0(box-drawing, printable)
|
||
|
0x2568,0x2564,0x2565,0x2559,0x2558,0x2552,0x2553,0x256B,0x256A,0x2518,0x250C,0x2588,0x2584,0x258C,0x2590,0x2580, //0xd0(box-drawing, printable)
|
||
|
0x03B1,0x00DF,0x0393,0x03C0,0x03A3,0x03C3,0x00B5,0x03C4,0x03A6,0x0398,0x03A9,0x03B4,0x221E,0x03C6,0x03B5,0x2229, //0xe0(maths(greek), printable)
|
||
|
0x2261,0x00B1,0x2265,0x2264,0x2320,0x2321,0x00F7,0x2248,0x00B0,0x2219,0x00B7,0x221A,0x207F,0x00B2,0x25A0,0x00A0, //0xf0(maths, printable)
|
||
|
};
|
||
|
|
||
|
|
||
|
static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip, struct zipinfo *info)
|
||
|
{
|
||
|
qboolean result = false;
|
||
|
//zip comment is capped to 65k or so, so we can use a single buffer for this
|
||
|
byte traildata[0x10000 + SIZE_ENDOFCENTRALDIRECTORY+SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR];
|
||
|
byte *magic;
|
||
|
unsigned int trailsize = 0x10000 + SIZE_ENDOFCENTRALDIRECTORY+SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR;
|
||
|
if ((qofs_t)trailsize > zip->rawsize)
|
||
|
trailsize = zip->rawsize;
|
||
|
//FIXME: do in a loop to avoid a huge block of stack use
|
||
|
Sys_FileSeek(zip->raw, zip->rawsize - trailsize);
|
||
|
Sys_FileRead(zip->raw, traildata, trailsize);
|
||
|
|
||
|
memset(info, 0, sizeof(*info));
|
||
|
|
||
|
for (magic = traildata+trailsize-SIZE_ENDOFCENTRALDIRECTORY; magic >= traildata; magic--)
|
||
|
{
|
||
|
if (magic[0] == 'P' &&
|
||
|
magic[1] == 'K' &&
|
||
|
magic[2] == 5 &&
|
||
|
magic[3] == 6)
|
||
|
{
|
||
|
info->centraldir_end = (zip->rawsize-trailsize)+(magic-traildata);
|
||
|
|
||
|
info->thisdisk = LittleU2FromPtr(magic+4);
|
||
|
info->centraldir_startdisk = LittleU2FromPtr(magic+6);
|
||
|
info->centraldir_numfiles_disk = LittleU2FromPtr(magic+8);
|
||
|
info->centraldir_numfiles_all = LittleU2FromPtr(magic+10);
|
||
|
info->centraldir_size = LittleU4FromPtr(magic+12);
|
||
|
info->centraldir_offset = LittleU4FromPtr(magic+16);
|
||
|
info->commentlength = LittleU2FromPtr(magic+20);
|
||
|
|
||
|
result = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!result)
|
||
|
Con_Printf("zip: unable to find end-of-central-directory\n");
|
||
|
else
|
||
|
|
||
|
//now look for a zip64 header.
|
||
|
//this gives more larger archives, more files, larger files, more spanned disks.
|
||
|
//note that the central directory itself is the same, it just has a couple of extra attributes on files that need them.
|
||
|
for (magic -= SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR; magic >= traildata; magic--)
|
||
|
{
|
||
|
if (magic[0] == 'P' &&
|
||
|
magic[1] == 'K' &&
|
||
|
magic[2] == 6 &&
|
||
|
magic[3] == 7)
|
||
|
{
|
||
|
byte z64eocd[SIZE_ZIP64ENDOFCENTRALDIRECTORY];
|
||
|
|
||
|
info->zip64_centraldirend_disk = LittleU4FromPtr(magic+4);
|
||
|
info->zip64_centraldirend_offset = LittleU8FromPtr(magic+8);
|
||
|
info->zip64_diskcount = LittleU4FromPtr(magic+16);
|
||
|
|
||
|
if (info->zip64_diskcount != 1 || info->zip64_centraldirend_disk != 0)
|
||
|
{
|
||
|
Con_Printf("zip: archive is spanned\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Sys_FileSeek(zip->raw, info->zip64_centraldirend_offset);
|
||
|
Sys_FileRead(zip->raw, z64eocd, sizeof(z64eocd));
|
||
|
|
||
|
if (z64eocd[0] == 'P' &&
|
||
|
z64eocd[1] == 'K' &&
|
||
|
z64eocd[2] == 6 &&
|
||
|
z64eocd[3] == 6)
|
||
|
{
|
||
|
info->zip64_eocdsize = LittleU8FromPtr(z64eocd+4) + 12;
|
||
|
info->zip64_version_madeby = LittleU2FromPtr(z64eocd+12);
|
||
|
info->zip64_version_needed = LittleU2FromPtr(z64eocd+14);
|
||
|
info->thisdisk = LittleU4FromPtr(z64eocd+16);
|
||
|
info->centraldir_startdisk = LittleU4FromPtr(z64eocd+20);
|
||
|
info->centraldir_numfiles_disk = LittleU8FromPtr(z64eocd+24);
|
||
|
info->centraldir_numfiles_all = LittleU8FromPtr(z64eocd+32);
|
||
|
info->centraldir_size = LittleU8FromPtr(z64eocd+40);
|
||
|
info->centraldir_offset = LittleU8FromPtr(z64eocd+48);
|
||
|
|
||
|
if (info->zip64_eocdsize >= 84)
|
||
|
{
|
||
|
info->centraldir_compressionmethod = LittleU2FromPtr(z64eocd+56);
|
||
|
// info->zip64_2_centraldir_csize = LittleU8FromPtr(z64eocd+58);
|
||
|
// info->zip64_2_centraldir_usize = LittleU8FromPtr(z64eocd+66);
|
||
|
info->centraldir_algid = LittleU2FromPtr(z64eocd+74);
|
||
|
// info.zip64_2_bitlen = LittleU2FromPtr(z64eocd+76);
|
||
|
// info->zip64_2_flags = LittleU2FromPtr(z64eocd+78);
|
||
|
// info->zip64_2_hashid = LittleU2FromPtr(z64eocd+80);
|
||
|
// info->zip64_2_hashlength = LittleU2FromPtr(z64eocd+82);
|
||
|
//info->zip64_2_hashdata = LittleUXFromPtr(z64eocd+84, info->zip64_2_hashlength);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Con_Printf("zip: zip64 end-of-central directory at unknown offset.\n");
|
||
|
result = false;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (info->thisdisk || info->centraldir_startdisk || info->centraldir_numfiles_disk != info->centraldir_numfiles_all)
|
||
|
{
|
||
|
Con_Printf("zip: archive is spanned\n");
|
||
|
result = false;
|
||
|
}
|
||
|
if (info->centraldir_compressionmethod || info->centraldir_algid)
|
||
|
{
|
||
|
Con_Printf("zip: encrypted centraldir\n");
|
||
|
result = false;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static qboolean FSZIP_ReadCentralEntry(zipfile_t *zip, byte *data, struct zipcentralentry *entry)
|
||
|
{
|
||
|
entry->flags = 0;
|
||
|
entry->fname = (unsigned char*)"";
|
||
|
entry->fnane_len = 0;
|
||
|
|
||
|
if (data[0] != 'P' ||
|
||
|
data[1] != 'K' ||
|
||
|
data[2] != 1 ||
|
||
|
data[3] != 2)
|
||
|
return false; //verify the signature
|
||
|
|
||
|
//if we read too much, we'll catch it after.
|
||
|
entry->version_madeby = LittleU2FromPtr(data+4);
|
||
|
entry->version_needed = LittleU2FromPtr(data+6);
|
||
|
entry->gflags = LittleU2FromPtr(data+8);
|
||
|
entry->cmethod = LittleU2FromPtr(data+10);
|
||
|
entry->lastmodfiletime = LittleU2FromPtr(data+12);
|
||
|
entry->lastmodfiledate = LittleU2FromPtr(data+12);
|
||
|
entry->crc32 = LittleU4FromPtr(data+16);
|
||
|
entry->csize = LittleU4FromPtr(data+20);
|
||
|
entry->usize = LittleU4FromPtr(data+24);
|
||
|
entry->fnane_len = LittleU2FromPtr(data+28);
|
||
|
entry->extra_len = LittleU2FromPtr(data+30);
|
||
|
entry->comment_len = LittleU2FromPtr(data+32);
|
||
|
entry->disknum = LittleU2FromPtr(data+34);
|
||
|
entry->iattributes = LittleU2FromPtr(data+36);
|
||
|
entry->eattributes = LittleU4FromPtr(data+38);
|
||
|
entry->localheaderoffset = LittleU4FromPtr(data+42);
|
||
|
entry->cesize = 46;
|
||
|
|
||
|
//mark the filename position
|
||
|
entry->fname = data+entry->cesize;
|
||
|
entry->cesize += entry->fnane_len;
|
||
|
|
||
|
entry->mtime = 0;
|
||
|
|
||
|
//parse extra
|
||
|
if (entry->extra_len)
|
||
|
{
|
||
|
byte *extra = data + entry->cesize;
|
||
|
byte *extraend = extra + entry->extra_len;
|
||
|
unsigned short extrachunk_tag;
|
||
|
unsigned short extrachunk_len;
|
||
|
while(extra+4 < extraend)
|
||
|
{
|
||
|
extrachunk_tag = LittleU2FromPtr(extra+0);
|
||
|
extrachunk_len = LittleU2FromPtr(extra+2);
|
||
|
if (extra + extrachunk_len > extraend)
|
||
|
break; //error
|
||
|
extra += 4;
|
||
|
|
||
|
switch(extrachunk_tag)
|
||
|
{
|
||
|
case 1: //zip64 extended information extra field. the attributes are only present if the reegular file info is nulled out with a -1
|
||
|
if (entry->usize == 0xffffffffu)
|
||
|
{
|
||
|
entry->usize = LittleU8FromPtr(extra);
|
||
|
extra += 8;
|
||
|
}
|
||
|
if (entry->csize == 0xffffffffu)
|
||
|
{
|
||
|
entry->csize = LittleU8FromPtr(extra);
|
||
|
extra += 8;
|
||
|
}
|
||
|
if (entry->localheaderoffset == 0xffffffffu)
|
||
|
{
|
||
|
entry->localheaderoffset = LittleU8FromPtr(extra);
|
||
|
extra += 8;
|
||
|
}
|
||
|
if (entry->disknum == 0xffffu)
|
||
|
{
|
||
|
entry->disknum = LittleU4FromPtr(extra);
|
||
|
extra += 4;
|
||
|
}
|
||
|
break;
|
||
|
#if !defined(_MSC_VER) || _MSC_VER > 1200
|
||
|
case 0x000a: //NTFS extra field
|
||
|
//0+4: reserved
|
||
|
//4+2: subtag(must be 1, for times)
|
||
|
//6+2: subtagsize(times: must be == 8*3+4)
|
||
|
//8+8: mtime
|
||
|
//16+8: atime
|
||
|
//24+8: ctime
|
||
|
if (extrachunk_len >= 32 && LittleU2FromPtr(extra+4) == 1 && LittleU2FromPtr(extra+6) == 8*3)
|
||
|
entry->mtime = LittleU8FromPtr(extra+8) / 10000000ULL - 11644473600ULL;
|
||
|
else
|
||
|
Con_Printf("zip: unsupported ntfs subchunk %x\n", extrachunk_tag);
|
||
|
extra += extrachunk_len;
|
||
|
break;
|
||
|
#endif
|
||
|
case 0x5455:
|
||
|
if (extra[0] & 1)
|
||
|
entry->mtime = LittleU4FromPtr(extra+1);
|
||
|
//access and creation do NOT exist in the central header.
|
||
|
extra += extrachunk_len;
|
||
|
break;
|
||
|
default:
|
||
|
/* Con_Printf("Unknown chunk %x\n", extrachunk_tag);
|
||
|
case 0x5455: //extended timestamp
|
||
|
case 0x7875: //unix uid/gid
|
||
|
case 0x9901: //aes crypto
|
||
|
*/ extra += extrachunk_len;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
entry->cesize += entry->extra_len;
|
||
|
}
|
||
|
|
||
|
//parse comment
|
||
|
entry->cesize += entry->comment_len;
|
||
|
|
||
|
//check symlink flags
|
||
|
{
|
||
|
byte madeby = entry->version_madeby>>8; //system
|
||
|
//vms, unix, or beos file attributes includes a symlink attribute.
|
||
|
//symlinks mean the file contents is just the name of another file.
|
||
|
if (madeby == 2 || madeby == 3 || madeby == 16)
|
||
|
{
|
||
|
unsigned short unixattr = entry->eattributes>>16;
|
||
|
if ((unixattr & 0xF000) == 0xA000)//fa&S_IFMT==S_IFLNK
|
||
|
entry->flags |= ZFL_SYMLINK;
|
||
|
else if ((unixattr & 0xA000) == 0xA000)//fa&S_IFMT==S_IFLNK
|
||
|
entry->flags |= ZFL_SYMLINK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (entry->gflags & (1u<<0)) //encrypted
|
||
|
{
|
||
|
#ifdef ZIPCRYPT
|
||
|
entry->flags |= ZFL_WEAKENCRYPT;
|
||
|
#else
|
||
|
entry->flags |= ZFL_CORRUPT;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
if (entry->gflags & (1u<<5)) //is patch data
|
||
|
entry->flags |= ZFL_CORRUPT;
|
||
|
else if (entry->gflags & (1u<<6)) //strong encryption
|
||
|
entry->flags |= ZFL_CORRUPT;
|
||
|
else if (entry->gflags & (1u<<13)) //strong encryption
|
||
|
entry->flags |= ZFL_CORRUPT;
|
||
|
else if (entry->cmethod == 0)
|
||
|
entry->flags |= ZFL_STORED;
|
||
|
//1: shrink
|
||
|
//2-5: reduce
|
||
|
//6: implode
|
||
|
//7: tokenize
|
||
|
else if (entry->cmethod == 8)
|
||
|
entry->flags |= ZFL_DEFLATED;
|
||
|
//8: deflate64 - patented. sometimes written by microsoft's crap, so this might be problematic. only minor improvements.
|
||
|
//10: implode
|
||
|
//12: bzip2
|
||
|
// else if (entry->cmethod == 12)
|
||
|
// entry->flags |= ZFL_BZIP2;
|
||
|
// else if (entry->cmethod == 14)
|
||
|
// entry->flags |= ZFL_LZMA;
|
||
|
//19: lz77
|
||
|
//97: wavpack
|
||
|
//98: ppmd
|
||
|
else
|
||
|
entry->flags |= ZFL_CORRUPT; //unsupported compression method.
|
||
|
|
||
|
if ((entry->flags & ZFL_WEAKENCRYPT) && !(entry->flags & ZFL_DEFLATED))
|
||
|
entry->flags |= ZFL_CORRUPT; //only support decryption with deflate.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo *info, const char *prefix)
|
||
|
{
|
||
|
qboolean success = false;
|
||
|
zpackfile_t *f;
|
||
|
struct zipcentralentry entry;
|
||
|
qofs_t ofs = 0;
|
||
|
unsigned int i;
|
||
|
//lazily read the entire thing.
|
||
|
byte *centraldir = malloc(info->centraldir_size);
|
||
|
if (centraldir)
|
||
|
{
|
||
|
Sys_FileSeek(zip->raw, info->centraldir_offset+info->zipoffset);
|
||
|
if ((qofs_t)Sys_FileRead(zip->raw, centraldir, info->centraldir_size) == info->centraldir_size)
|
||
|
{
|
||
|
zip->numfiles = info->centraldir_numfiles_disk;
|
||
|
zip->files = f = Z_Malloc (zip->numfiles * sizeof(*f));
|
||
|
|
||
|
for (i = 0; i < zip->numfiles; i++)
|
||
|
{
|
||
|
if (!FSZIP_ReadCentralEntry(zip, centraldir+ofs, &entry) || ofs + entry.cesize > info->centraldir_size)
|
||
|
break;
|
||
|
|
||
|
f->crc = entry.crc32;
|
||
|
|
||
|
//copy out the filename and lowercase it
|
||
|
if (entry.gflags & (1u<<11))
|
||
|
{ //already utf-8 encoding
|
||
|
if (entry.fnane_len > sizeof(f->name)-1)
|
||
|
entry.fnane_len = sizeof(f->name)-1;
|
||
|
memcpy(f->name, entry.fname, entry.fnane_len);
|
||
|
f->name[entry.fnane_len] = 0;
|
||
|
}
|
||
|
else
|
||
|
{ //legacy charset
|
||
|
int i;
|
||
|
int nlen = 0;
|
||
|
int cl;
|
||
|
for (i = 0; i < entry.fnane_len; i++)
|
||
|
{
|
||
|
#if 1
|
||
|
cl = ibmtounicode[entry.fname[i]];
|
||
|
if (cl > 127)
|
||
|
f->name[nlen] = '?'; //we can't encode non-ascii chars
|
||
|
else
|
||
|
f->name[nlen] = cl;
|
||
|
cl = 1;
|
||
|
#else
|
||
|
cl = utf8_encode(f->name+nlen, ibmtounicode[entry.fname[i]], sizeof(f->name)-1 - nlen);
|
||
|
#endif
|
||
|
if (!cl) //overflowed, truncate cleanly.
|
||
|
break;
|
||
|
nlen += cl;
|
||
|
}
|
||
|
f->name[nlen] = 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (prefix && *prefix)
|
||
|
{
|
||
|
if (!strcmp(prefix, ".."))
|
||
|
{
|
||
|
char *c;
|
||
|
for (c = f->name; *c; )
|
||
|
{
|
||
|
if (*c++ == '/')
|
||
|
break;
|
||
|
}
|
||
|
memmove(f->name, c, strlen(c)+1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
size_t prelen = strlen(prefix);
|
||
|
size_t oldlen = strlen(f->name);
|
||
|
if (prelen+1+oldlen+1 > sizeof(f->name))
|
||
|
*f->name = 0;
|
||
|
else
|
||
|
{
|
||
|
memmove(f->name+prelen+1, f->name, oldlen);
|
||
|
f->name[prelen] = '/';
|
||
|
memmove(f->name, prefix, prelen);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
q_strlwr(f->name);
|
||
|
|
||
|
f->filelen = *f->name?entry.usize:0;
|
||
|
f->localpos = entry.localheaderoffset+info->zipoffset;
|
||
|
f->flags = entry.flags;
|
||
|
// f->mtime = entry.mtime;
|
||
|
|
||
|
ofs += entry.cesize;
|
||
|
f++;
|
||
|
}
|
||
|
|
||
|
success = i == zip->numfiles;
|
||
|
if (!success)
|
||
|
{
|
||
|
free(zip->files);
|
||
|
zip->files = NULL;
|
||
|
zip->numfiles = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(centraldir);
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
static qboolean FSZIP_ValidateLocalHeader(zipfile_t *zip, zpackfile_t *zfile, qofs_t *datastart, qofs_t *datasize)
|
||
|
{
|
||
|
struct ziplocalentry local;
|
||
|
byte localdata[SIZE_LOCALENTRY];
|
||
|
qofs_t localstart = zfile->localpos;
|
||
|
|
||
|
Sys_FileSeek(zip->raw, localstart);
|
||
|
Sys_FileRead(zip->raw, localdata, sizeof(localdata));
|
||
|
|
||
|
//make sure we found the right sort of table.
|
||
|
if (localdata[0] != 'P' ||
|
||
|
localdata[1] != 'K' ||
|
||
|
localdata[2] != 3 ||
|
||
|
localdata[3] != 4)
|
||
|
return false;
|
||
|
|
||
|
local.version_needed = LittleU2FromPtr(localdata+4);
|
||
|
local.gpflags = LittleU2FromPtr(localdata+6);
|
||
|
local.cmethod = LittleU2FromPtr(localdata+8);
|
||
|
local.lastmodfiletime = LittleU2FromPtr(localdata+10);
|
||
|
local.lastmodfiledate = LittleU2FromPtr(localdata+12);
|
||
|
local.crc32 = LittleU4FromPtr(localdata+14);
|
||
|
local.csize = LittleU4FromPtr(localdata+18);
|
||
|
local.usize = LittleU4FromPtr(localdata+22);
|
||
|
local.fname_len = LittleU2FromPtr(localdata+26);
|
||
|
local.extra_len = LittleU2FromPtr(localdata+28);
|
||
|
|
||
|
localstart += SIZE_LOCALENTRY;
|
||
|
localstart += local.fname_len;
|
||
|
|
||
|
//parse extra
|
||
|
if (local.usize == 0xffffffffu || local.csize == 0xffffffffu) //don't bother otherwise.
|
||
|
if (local.extra_len)
|
||
|
{
|
||
|
byte extradata[65536];
|
||
|
byte *extra = extradata;
|
||
|
byte *extraend = extradata + local.extra_len;
|
||
|
unsigned short extrachunk_tag;
|
||
|
unsigned short extrachunk_len;
|
||
|
|
||
|
Sys_FileSeek(zip->raw, localstart);
|
||
|
Sys_FileRead(zip->raw, extradata, sizeof(extradata));
|
||
|
|
||
|
while(extra+4 < extraend)
|
||
|
{
|
||
|
extrachunk_tag = LittleU2FromPtr(extra+0);
|
||
|
extrachunk_len = LittleU2FromPtr(extra+2);
|
||
|
if (extra + extrachunk_len > extraend)
|
||
|
break; //error
|
||
|
extra += 4;
|
||
|
|
||
|
switch(extrachunk_tag)
|
||
|
{
|
||
|
case 1: //zip64 extended information extra field. the attributes are only present if the reegular file info is nulled out with a -1
|
||
|
if (local.usize == 0xffffffffu)
|
||
|
{
|
||
|
local.usize = LittleU8FromPtr(extra);
|
||
|
extra += 8;
|
||
|
}
|
||
|
if (local.csize == 0xffffffffu)
|
||
|
{
|
||
|
local.csize = LittleU8FromPtr(extra);
|
||
|
extra += 8;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
/* Con_Printf("Unknown chunk %x\n", extrachunk_tag);
|
||
|
case 0x000a: //NTFS (timestamps)
|
||
|
case 0x5455: //extended timestamp
|
||
|
case 0x7875: //unix uid/gid
|
||
|
*/ extra += extrachunk_len;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
localstart += local.extra_len;
|
||
|
*datastart = localstart; //this is the end of the local block, and the start of the data block (well, actually, should be encryption, but we don't support that).
|
||
|
*datasize = local.csize;
|
||
|
|
||
|
if (local.gpflags & (1u<<3))
|
||
|
{
|
||
|
//crc, usize, and csize were not known at the time the file was compressed.
|
||
|
//there is a 'data descriptor' after the file data, but to parse that we would need to decompress the file.
|
||
|
//instead, just depend upon upon the central directory and don't bother checking.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (local.crc32 != zfile->crc)
|
||
|
return false;
|
||
|
if (local.usize != zfile->filelen)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//FIXME: with pure paths, we still don't bother checking the crc (again, would require decompressing the entire file in advance).
|
||
|
|
||
|
if (local.cmethod == 0)
|
||
|
return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED)) == ZFL_STORED;
|
||
|
if (local.cmethod == 8)
|
||
|
return (zfile->flags & (ZFL_STORED|ZFL_CORRUPT|ZFL_DEFLATED)) == ZFL_DEFLATED;
|
||
|
return false; //some other method that we don't know.
|
||
|
}
|
||
|
|
||
|
pack_t *FSZIP_LoadArchive (const char *packfile)
|
||
|
{
|
||
|
size_t i;
|
||
|
packfile_t *newfiles;
|
||
|
int numpackfiles;
|
||
|
pack_t *pack;
|
||
|
|
||
|
zipfile_t zip;
|
||
|
struct zipinfo info;
|
||
|
|
||
|
#ifndef USE_ZLIB
|
||
|
qboolean zlibneeded = false;
|
||
|
#endif
|
||
|
|
||
|
zip.rawsize = Sys_FileOpenRead(packfile, &zip.raw);
|
||
|
if (zip.raw < 0)
|
||
|
return NULL;
|
||
|
|
||
|
//try to find the header
|
||
|
if (!FSZIP_FindEndCentralDirectory(&zip, &info))
|
||
|
{
|
||
|
Sys_FileClose(zip.raw);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//now try to read it.
|
||
|
if (!FSZIP_EnumerateCentralDirectory(&zip, &info, "") && !info.zip64_diskcount)
|
||
|
{
|
||
|
//uh oh... the central directory wasn't where it was meant to be!
|
||
|
//assuming that the endofcentraldir is packed at the true end of the centraldir (and that we're not zip64 and thus don't have an extra block), then we can guess based upon the offset difference
|
||
|
info.zipoffset = info.centraldir_end - (info.centraldir_offset+info.centraldir_size);
|
||
|
if (!FSZIP_EnumerateCentralDirectory(&zip, &info, ""))
|
||
|
{
|
||
|
Sys_FileClose(zip.raw);
|
||
|
Con_Printf ("zipfile \"%s\" appears to be missing its central directory\n", packfile);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//lame zone.
|
||
|
//copy the files into something compatible with quake's pak support.
|
||
|
//ignore compressed / corrupt / unusable files
|
||
|
pack = (pack_t *) Z_Malloc (sizeof (pack_t));
|
||
|
q_strlcpy (pack->filename, packfile, sizeof(pack->filename));
|
||
|
pack->handle = zip.raw;
|
||
|
|
||
|
numpackfiles = 0;
|
||
|
newfiles = NULL;
|
||
|
|
||
|
newfiles = Z_Malloc(sizeof(*newfiles) * zip.numfiles);
|
||
|
for (numpackfiles = 0, i = 0; i < zip.numfiles; i++)
|
||
|
{
|
||
|
qofs_t startpos, datasize;
|
||
|
zpackfile_t *zp = &zip.files[i];
|
||
|
if (zp->flags & ZFL_CORRUPT)
|
||
|
continue; //we can't cope with this.
|
||
|
if (zp->flags & ZFL_SYMLINK)
|
||
|
continue; //file data is just a filename
|
||
|
|
||
|
if (!FSZIP_ValidateLocalHeader(&zip, zp, &startpos, &datasize))
|
||
|
continue; //local header is corrupt
|
||
|
|
||
|
if (zp->flags & ZFL_DEFLATED)
|
||
|
{
|
||
|
#ifndef USE_ZLIB
|
||
|
zlibneeded = true;
|
||
|
continue;
|
||
|
#endif
|
||
|
}
|
||
|
else if (zp->flags & ZFL_STORED)
|
||
|
{
|
||
|
if (datasize != zp->filelen)
|
||
|
continue;
|
||
|
datasize = 0;
|
||
|
}
|
||
|
else
|
||
|
continue;
|
||
|
|
||
|
//usable file.
|
||
|
memcpy(newfiles[numpackfiles].name, zp->name, MAX_QPATH-1);
|
||
|
newfiles[numpackfiles].name[MAX_QPATH-1] = 0;
|
||
|
newfiles[numpackfiles].filelen = zp->filelen;
|
||
|
newfiles[numpackfiles].filepos = startpos;
|
||
|
newfiles[numpackfiles].deflatedsize = datasize;
|
||
|
numpackfiles++;
|
||
|
}
|
||
|
pack->numfiles = numpackfiles;
|
||
|
pack->files = newfiles;
|
||
|
|
||
|
//we don't need this stuff now.
|
||
|
Z_Free(zip.files);
|
||
|
|
||
|
#ifndef USE_ZLIB
|
||
|
if (zlibneeded)
|
||
|
Con_Printf ("zipfile \"%s\" contains compressed files, but zlib was disabled at compile time.\n", packfile);
|
||
|
#endif
|
||
|
//Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
|
||
|
return pack;
|
||
|
}
|
||
|
|
||
|
FILE *FSZIP_Deflate(FILE *src, int srcsize, int outsize)
|
||
|
{
|
||
|
#ifdef USE_ZLIB
|
||
|
byte inbuffer[65536];
|
||
|
byte outbuffer[65536];
|
||
|
z_stream strm;
|
||
|
int ret;
|
||
|
|
||
|
FILE *of;
|
||
|
#ifdef _WIN32
|
||
|
/*warning: annother app might manage to open the file before we can. if the file is not opened exclusively then we can end up with issues
|
||
|
on windows, fopen is typically exclusive anyway, but not on unix. but on unix, tmpfile is actually usable, so special-case the windows code and hope that its never an issue
|
||
|
tmpfile isn't usable in windows. it creates the file in the root dir and requires admin rights, which is stupid.
|
||
|
*/
|
||
|
char *fname = _tempnam(NULL, "ftemp");
|
||
|
of = fopen(fname, "w+bD");
|
||
|
#else
|
||
|
of = tmpfile();
|
||
|
#endif
|
||
|
|
||
|
if (!of)
|
||
|
{
|
||
|
fclose(src);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
memset(&strm, 0, sizeof(strm));
|
||
|
strm.data_type = Z_UNKNOWN;
|
||
|
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)
|
||
|
{
|
||
|
strm.avail_in = fread(inbuffer, 1, sizeof(inbuffer), src);
|
||
|
strm.next_in = inbuffer;
|
||
|
if (!strm.avail_in)
|
||
|
break;
|
||
|
}
|
||
|
if (strm.avail_out == 0)
|
||
|
{
|
||
|
strm.next_out = outbuffer;
|
||
|
fwrite(outbuffer, 1, strm.total_out, of);
|
||
|
strm.total_out = 0;
|
||
|
strm.avail_out = sizeof(outbuffer);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//doh, it terminated for no reason
|
||
|
if (ret != Z_STREAM_END)
|
||
|
{
|
||
|
inflateEnd(&strm);
|
||
|
fclose(src);
|
||
|
fclose(of);
|
||
|
Con_Printf("Couldn't decompress file\n");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
fwrite(outbuffer, 1, strm.total_out, of);
|
||
|
inflateEnd(&strm);
|
||
|
|
||
|
fclose(src);
|
||
|
|
||
|
fseek(of, SEEK_SET, 0);
|
||
|
return of;
|
||
|
#else
|
||
|
fclose(src);
|
||
|
return NULL;
|
||
|
#endif
|
||
|
}
|