+PRIVATE FUNCTIONS - PK3 HANDLING
+
+=============================================================================
+*/
+
+/*
+====================
+PK3_GetEndOfCentralDir
+
+Extract the end of the central directory from a PK3 package
+====================
+*/
+qboolean PK3_GetEndOfCentralDir (const char *packfile, FILE *packhandle, pk3_endOfCentralDir_t *eocd)
+{
+ long filesize, maxsize;
+ qbyte *buffer, *ptr;
+ int ind;
+
+ // Get the package size
+ fseek (packhandle, 0, SEEK_END);
+ filesize = ftell (packhandle);
+ 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 = Mem_Alloc (tempmempool, maxsize);
+ fseek (packhandle, filesize - maxsize, SEEK_SET);
+ if (fread (buffer, 1, maxsize, packhandle) != 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)
+{
+ qbyte *central_dir, *ptr;
+ unsigned int ind;
+ int remaining;
+
+ // Load the central directory in memory
+ central_dir = Mem_Alloc (tempmempool, eocd->cdir_size);
+ fseek (pack->handle, eocd->cdir_offset, SEEK_SET);
+ fread (central_dir, 1, eocd->cdir_size, pack->handle);
+
+ // 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++)
+ {
+ size_t namesize, count;
+ packfile_t *file;
+
+ // 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 >= 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] != '/')
+ {
+ // Extract the name
+ file = &pack->files[pack->numfiles];
+ memcpy (file->name, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
+ file->name[namesize] = '\0';
+
+ // Compression, sizes and offset
+ if (BuffLittleShort (&ptr[10]))
+ file->flags = FILE_FLAG_DEFLATED;
+ file->packsize = BuffLittleLong (&ptr[20]);
+ file->realsize = BuffLittleLong (&ptr[24]);
+ file->offset = BuffLittleLong (&ptr[42]);
+
+ pack->numfiles++;
+ }
+ }
+
+ // 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;
+ }
+
+ 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)
+{
+ FILE *packhandle;
+ pk3_endOfCentralDir_t eocd;
+ pack_t *pack;
+ int real_nb_files;
+
+ packhandle = fopen (packfile, "rb");
+ if (!packhandle)
+ return NULL;
+
+ if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
+ Sys_Error ("%s is not a PK3 file", packfile);
+
+ // Multi-volume ZIP archives are NOT allowed
+ if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
+ Sys_Error ("%s is a multi-volume ZIP archive", packfile);
+
+ if (eocd.nbentries > MAX_FILES_IN_PACK)
+ Sys_Error ("%s contains too many files (%hu)", packfile, eocd.nbentries);
+
+ // Create a package structure in memory
+ pack = Mem_Alloc (pak_mempool, sizeof (pack_t));
+ strcpy (pack->filename, packfile);
+ pack->handle = packhandle;
+ pack->numfiles = eocd.nbentries;
+ pack->mempool = Mem_AllocPool (packfile);
+ pack->files = Mem_Alloc (pack->mempool, eocd.nbentries * sizeof(packfile_t));
+ pack->next = packlist;
+ packlist = pack;
+
+ real_nb_files = PK3_BuildFileList (pack, &eocd);
+ if (real_nb_files <= 0)
+ Sys_Error ("%s is not a valid PK3 file", packfile);
+
+ Con_Printf ("Added packfile %s (%i files)\n", packfile, real_nb_files);
+ return pack;
+}
+
+
+/*
+====================
+PK3_GetTrueFileOffset
+
+Find where the true file data offset is
+====================
+*/
+void PK3_GetTrueFileOffset (packfile_t *file, pack_t *pack)
+{
+ qbyte buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
+ size_t count;
+
+ // Already found?
+ if (file->flags & FILE_FLAG_TRUEOFFS)
+ return;
+
+ // Load the local file description
+ fseek (pack->handle, file->offset, SEEK_SET);
+ count = fread (buffer, 1, ZIP_LOCAL_CHUNK_BASE_SIZE, pack->handle);
+ if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
+ Sys_Error ("Can't retrieve file %s in package %s", file->name, pack->filename);
+
+ // Skip name and extra field
+ file->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
+
+ file->flags |= FILE_FLAG_TRUEOFFS;
+}
+
+
+/*
+=============================================================================
+
+OTHER PRIVATE FUNCTIONS