Tutorial *42*

Files needed for this tutorial: paktozip.zip

This modification was made to simplify the method of making changes to the games propritary PAK file to the more common uncompressed ZIP file. (ala Quake3)

First, let's start by modifiying the qfiles.h file.


We will need to modify the original quake2 header structure to match the structure used by the file header in a zip.
Change this...

typedef struct


	int		ident;		// == IDPAKHEADER

	int		dirofs;

	int		dirlen;

} dpackheader_t;


#pragma pack(push, 2)

typedef struct


	unsigned long  ident;

	unsigned short version;

	unsigned short flags;

	unsigned short compression;

	unsigned short modtime;

	unsigned short moddate;

	unsigned long  crc32;

	unsigned long  compressedSize;

	unsigned long  uncompressedSize;

	unsigned short filenameLength;

	unsigned short extraFieldLength;

} dpackheader_t;

#pragma pack(pop)

*** The pragma is to keep the compiler from padding the data to fit within the 32bit boundary. Otherwise it mungs the data. ***

For more information about the zip file format visit: http://www.pkware.com/support/appnote.html

Next we will be changing the original Pack header signature to that of the signature used by the Zip file to identify a file header and we will be adding the header signature of the zip's Directory. This...

#define IDPAKHEADER		(('K'<<24)+('C'<<16)+('A'<<8)+'P')

Becomes this...

#define ZPAKHEADER	(0x504B0304)

#define ZPAKDIRHEADER	(0x504B0102)

This completes the modifications to the qfiles.h file! Now we get to the nitty gritty of changing the FS_LoadPackFile function to load the new zip file!


Go to the beginning of the FS_LoadPackFile function so we can make changes to the declared variables used by the function.

Since the dpackheader_t structure will only be a temporary buffer we can rename it: This...

dpackheader_t	header;

Becomes this...

dpackheader_t	temp;

Next we will add a variable to keep track of the number of files we have read from the zip. This...

int	i;

Becomes this...

int	i, numOfItems;

And while we are at it we can remove the an unused variable and comment out another. Remove this...

int	numpackfiles;

And comment out...

unsigned	checksum;

Enough with the variables, now we change some code! Since we won't be using the checksum check in this version we can comment the call to it out.

// checksum = Com_BlockChecksum ((void *)info, header.dirlen);

Next we will be removing the original header checking code as we will be checking the file header for each file ever time we read it in to make sure that it is indeed a file header and that no compress or odd flags have been set.

Remove the following code!

fread (&header, 1, sizeof(header), packhandle);

if (LittleLong(header.ident) != IDPAKHEADER)

	Com_Error (ERR_FATAL, "%s is not a packfile", packfile);

header.dirofs = LittleLong (header.dirofs);

header.dirlen = LittleLong (header.dirlen);

numpackfiles = header.dirlen / sizeof(dpackfile_t);

if (numpackfiles > MAX_FILES_IN_PACK)

	Com_Error (ERR_FATAL, "%s has %i files", packfile, numpackfiles);

newfiles = Z_Malloc (numpackfiles * sizeof(packfile_t));

fseek (packhandle, header.dirofs, SEEK_SET);

fread (info, 1, header.dirlen, packhandle);

Now we can add our own loop to build a table of the files in the zip. We read the file header, check the header signature, and see if the file is compressed or has any bit flags set. After this code:

#ifdef NO_ADDONS

	if (checksum != PAK0_CHECKSUM)

		return NULL;


Add the following For Loop:

	for( i = 0; i < MAX_FILES_IN_PACK; ++i)


		//-- Get the local header of the file.

		fread(&temp, sizeof(dpackheader_t), 1, packhandle);

		//-- Check if finished with pak file item collection.

		if (BigLong(temp.ident) == ZPAKDIRHEADER)


		//-- Check if header signature is correct for the first item.

		if ((BigLong(temp.ident) != ZPAKHEADER) && (i == 0))

		Com_Error (ERR_FATAL, "%s is not a packfile", packfile);

		//-- Check if compression is used or any flags are set.

		if ((temp.compression != 0) || (temp.flags != 0))

			Com_Error (ERR_FATAL, "%s contains errors or is compressed", packfile);

		//-- Get length of data area

		info[i].filelen = temp.uncompressedSize;

		//-- Get the data areas filename and add \0 to the end

		fread( &info[i].name, temp.filenameLength, 1, packhandle);

		info[i].name[temp.filenameLength] = '\0';

		//-- Get the offset of the data area

		info[i].filepos = (ftell(packhandle) + temp.extraFieldLength);

		//-- Goto the next header

		fseek( packhandle, (info[i].filelen + info[i].filepos), SEEK_SET);


Now we need to create an array that will hold all the information about the contents of the zip file and record the number of items that we read from the zip file. After the previous for loop, add this code:

//-- Allocate space for array of packfile structures (filename, offset, length)

newfiles = Z_Malloc (i * sizeof(packfile_t));

//-- The save the number of items collected from the zip file.

numOfItems = i;

Now change the original code to remove the calls to LittleLong (we don't need them) It should look like this:

	for (i=0 ; i < numOfItems; i++)


		strcpy (newfiles[i].name, info[i].name);

		newfiles[i].filepos = info[i].filepos;

		newfiles[i].filelen = info[i].filelen;


Two last changes and we are finished!! Now we need to update the last of the original code to reflect the change in the variables we made at the beginning of this function. This code...

pack->numfiles = numpackfiles;


pack->numfiles = numOfItems;

And this code...

Com_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);

Changes to this...

Com_Printf ("Added packfile %s (%i files)\n", packfile, numOfItems);

That's it! Now all you need to do is convert all your pak files to zip files! ;) Of course there are a few things to keep in mind before you go hog wild with this modification.

1. For compatibility I have maintained the maximum number of files you can put in the zip file at 4096. This was the same for the pack file system.

2. Currently the checksum function is disabled as I don't need it, but you could re-enable it if you can match the method used by the zip program to compute it.

3. The zip format store directories like any other file and my code does not remove them. This isn't a problem as they take very little space and they are ignored by the engine.

4. And of course, your old pack files won't work unless you change them to zip files. Remember to maintain the original file paths when rebuilding them.

5. Quake2 still looks for the file extension of ".pak"

Not logged in
Sign up