+// Functions exported from zlib
+#if defined(WIN32) && defined(ZLIB_USES_WINAPI)
+# define ZEXPORT WINAPI
+#else
+# define ZEXPORT
+#endif
+
+static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
+static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
+static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
+static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
+
+#define qz_inflateInit2(strm, windowBits) \
+ qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
+
+static dllfunction_t zlibfuncs[] =
+{
+ {"inflate", (void **) &qz_inflate},
+ {"inflateEnd", (void **) &qz_inflateEnd},
+ {"inflateInit2_", (void **) &qz_inflateInit2_},
+ {"inflateReset", (void **) &qz_inflateReset},
+ {NULL, NULL}
+};
+
+// Handle for Zlib DLL
+static dllhandle_t zlib_dll = NULL;
+
+
+/*
+====================
+PK3_CloseLibrary
+
+Unload the Zlib DLL
+====================
+*/
+void PK3_CloseLibrary (void)
+{
+ Sys_UnloadLibrary (&zlib_dll);
+}
+
+
+/*
+====================
+PK3_OpenLibrary
+
+Try to load the Zlib DLL
+====================
+*/
+qboolean PK3_OpenLibrary (void)
+{
+ const char* dllnames [] =
+ {
+#if defined(WIN64)
+ "zlib64.dll",
+#elif defined(WIN32)
+# ifdef ZLIB_USES_WINAPI
+ "zlibwapi.dll",
+ "zlib.dll",
+# else
+ "zlib1.dll",
+# endif
+#elif defined(MACOSX)
+ "libz.dylib",
+#else
+ "libz.so.1",
+ "libz.so",
+#endif
+ NULL
+ };
+
+ // Already loaded?
+ if (zlib_dll)
+ return true;
+
+ // Load the DLL
+ if (! Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs))
+ {
+ Con_Printf ("Compressed files support disabled\n");
+ return false;
+ }
+
+ Con_Printf ("Compressed files support enabled\n");
+ return true;
+}
+
+
+/*
+====================
+PK3_GetEndOfCentralDir
+
+Extract the end of the central directory from a PK3 package
+====================
+*/
+qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
+{
+ fs_offset_t filesize, maxsize;
+ unsigned char *buffer, *ptr;
+ int ind;
+
+ // Get the package size
+ filesize = lseek (packhandle, 0, SEEK_END);
+ if (filesize < ZIP_END_CDIR_SIZE)
+ return false;
+
+ // Load the end of the file in memory
+ if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
+ maxsize = filesize;
+ else
+ maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
+ buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
+ lseek (packhandle, filesize - maxsize, SEEK_SET);
+ if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
+ {
+ Mem_Free (buffer);
+ return false;
+ }
+
+ // Look for the end of central dir signature around the end of the file
+ maxsize -= ZIP_END_CDIR_SIZE;
+ ptr = &buffer[maxsize];
+ ind = 0;
+ while (BuffBigLong (ptr) != ZIP_END_HEADER)
+ {
+ if (ind == maxsize)
+ {
+ Mem_Free (buffer);
+ return false;
+ }
+
+ ind++;
+ ptr--;
+ }
+
+ memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
+ eocd->signature = LittleLong (eocd->signature);
+ eocd->disknum = LittleShort (eocd->disknum);
+ eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
+ eocd->localentries = LittleShort (eocd->localentries);
+ eocd->nbentries = LittleShort (eocd->nbentries);
+ eocd->cdir_size = LittleLong (eocd->cdir_size);
+ eocd->cdir_offset = LittleLong (eocd->cdir_offset);
+ eocd->comment_size = LittleShort (eocd->comment_size);
+
+ Mem_Free (buffer);
+
+ return true;
+}
+
+
+/*
+====================
+PK3_BuildFileList
+
+Extract the file list from a PK3 file
+====================
+*/
+int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
+{
+ unsigned char *central_dir, *ptr;
+ unsigned int ind;
+ fs_offset_t remaining;
+
+ // Load the central directory in memory
+ central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
+ lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
+ read (pack->handle, central_dir, eocd->cdir_size);
+
+ // Extract the files properties
+ // The parsing is done "by hand" because some fields have variable sizes and
+ // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
+ remaining = eocd->cdir_size;
+ pack->numfiles = 0;
+ ptr = central_dir;
+ for (ind = 0; ind < eocd->nbentries; ind++)
+ {
+ fs_offset_t namesize, count;
+
+ // Checking the remaining size
+ if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
+ {
+ Mem_Free (central_dir);
+ return -1;
+ }
+ remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
+
+ // Check header
+ if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
+ {
+ Mem_Free (central_dir);
+ return -1;
+ }
+
+ namesize = BuffLittleShort (&ptr[28]); // filename length
+
+ // Check encryption, compression, and attributes
+ // 1st uint8 : general purpose bit flag
+ // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
+ // 2nd uint8 : external file attributes
+ // Check bits 3 (file is a directory) and 5 (file is a volume (?))
+ if ((ptr[8] & 0x29) == 0 && (ptr[38] & 0x18) == 0)
+ {
+ // Still enough bytes for the name?
+ if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
+ {
+ Mem_Free (central_dir);
+ return -1;
+ }
+
+ // WinZip doesn't use the "directory" attribute, so we need to check the name directly
+ if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
+ {
+ char filename [sizeof (pack->files[0].name)];
+ fs_offset_t offset, packsize, realsize;
+ int flags;
+
+ // Extract the name (strip it if necessary)
+ namesize = min(namesize, (int)sizeof (filename) - 1);
+ memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
+ filename[namesize] = '\0';
+
+ if (BuffLittleShort (&ptr[10]))
+ flags = PACKFILE_FLAG_DEFLATED;
+ else
+ flags = 0;
+ offset = BuffLittleLong (&ptr[42]);
+ packsize = BuffLittleLong (&ptr[20]);
+ realsize = BuffLittleLong (&ptr[24]);
+ FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
+ }
+ }
+
+ // Skip the name, additionnal field, and comment
+ // 1er uint16 : extra field length
+ // 2eme uint16 : file comment length
+ count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
+ ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
+ remaining -= count;
+ }
+
+ // If the package is empty, central_dir is NULL here
+ if (central_dir != NULL)
+ Mem_Free (central_dir);
+ return pack->numfiles;
+}
+
+
+/*
+====================
+FS_LoadPackPK3
+
+Create a package entry associated with a PK3 file
+====================
+*/
+pack_t *FS_LoadPackPK3 (const char *packfile)
+{
+ int packhandle;
+ pk3_endOfCentralDir_t eocd;
+ pack_t *pack;
+ int real_nb_files;
+
+ packhandle = open (packfile, O_RDONLY | O_BINARY);
+ if (packhandle < 0)
+ return NULL;
+
+ if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
+ {
+ Con_Printf ("%s is not a PK3 file\n", packfile);
+ close(packhandle);
+ return NULL;
+ }
+
+ // Multi-volume ZIP archives are NOT allowed
+ if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
+ {
+ Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
+ close(packhandle);
+ return NULL;
+ }
+
+ // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
+ // since eocd.nbentries is an unsigned 16 bits integer
+#if MAX_FILES_IN_PACK < 65535
+ if (eocd.nbentries > MAX_FILES_IN_PACK)
+ {
+ Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
+ close(packhandle);
+ return NULL;
+ }
+#endif
+
+ // Create a package structure in memory
+ pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
+ pack->ignorecase = true; // PK3 ignores case
+ strlcpy (pack->filename, packfile, sizeof (pack->filename));
+ pack->handle = packhandle;
+ pack->numfiles = eocd.nbentries;
+ pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
+ pack->next = packlist;
+ packlist = pack;
+
+ real_nb_files = PK3_BuildFileList (pack, &eocd);
+ if (real_nb_files < 0)
+ {
+ Con_Printf ("%s is not a valid PK3 file\n", packfile);
+ close(pack->handle);
+ Mem_Free(pack);
+ return NULL;
+ }
+
+ Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
+ return pack;
+}
+
+
+/*
+====================
+PK3_GetTrueFileOffset
+
+Find where the true file data offset is
+====================
+*/
+qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
+{
+ unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
+ fs_offset_t count;
+
+ // Already found?
+ if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
+ return true;
+
+ // Load the local file description
+ lseek (pack->handle, pfile->offset, SEEK_SET);
+ count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
+ if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
+ {
+ Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
+ return false;
+ }
+
+ // Skip name and extra field
+ pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
+
+ pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
+ return true;
+}
+
+
+/*
+=============================================================================
+
+OTHER PRIVATE FUNCTIONS
+
+=============================================================================
+*/
+
+
+/*
+====================
+FS_AddFileToPack
+
+Add a file to the list of files contained into a package
+====================
+*/
+static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
+ fs_offset_t offset, fs_offset_t packsize,
+ fs_offset_t realsize, int flags)
+{
+ int (*strcmp_funct) (const char* str1, const char* str2);
+ int left, right, middle;
+ packfile_t *pfile;
+
+ strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
+
+ // Look for the slot we should put that file into (binary search)
+ left = 0;
+ right = pack->numfiles - 1;
+ while (left <= right)
+ {
+ int diff;
+
+ middle = (left + right) / 2;
+ diff = strcmp_funct (pack->files[middle].name, name);
+
+ // If we found the file, there's a problem
+ if (!diff)
+ Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
+
+ // If we're too far in the list
+ if (diff > 0)
+ right = middle - 1;
+ else
+ left = middle + 1;
+ }
+
+ // We have to move the right of the list by one slot to free the one we need
+ pfile = &pack->files[left];
+ memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
+ pack->numfiles++;
+
+ strlcpy (pfile->name, name, sizeof (pfile->name));
+ pfile->offset = offset;
+ pfile->packsize = packsize;
+ pfile->realsize = realsize;
+ pfile->flags = flags;
+
+ return pfile;
+}
+