Hosting and domain costs until October 2024 have been generously sponsored by dumptruck_ds. Thank you!

.pak

From Quake Wiki

.pak is Quake's container file format. It is an exceedingly simple uncompressed archive format (collection of folders & files) which preserves file paths and that's about it. There are many tools for working with .pak files.

Overview[edit]

PAK files are commonly used to store data to be loaded by a Quake game engine. The data may include graphics, objects, textures, sounds, and other game data.

Filenames inside PAK files are restricted to a maximum length of 56 characters[1]. Sub-directories inside the PAK file are part of this. For example a file foo.bsp in a directory maps would technically have the name maps/foo.bsp which has a length of 12 characters.

Quake engines usually load the pak0.pak file in the ID1 subfolder, plus whatever other PAK archives they find in the game directory (subfolder) they're told to use. The PAKs are treated exactly the same as if their contents were extracted into separate files, rather than being packed together in one PAK archive.

PAK files are typically named pak0.pak, pak1.pak, pak2.pak, and so on. The pak0.pak which comes with Quake and lives in the ID1 folder is required by all Quake game engines.

File precedence[edit]

caption

Since a file might exist in the same path in multiple pak files and the game directories (ID1 and mod), precedence is important to know about.

Consider a map maps/start.bsp existing in the following files and paths:

  • id1/pak0.pak:maps/start.bsp
  • id1/pak1.pak:maps/start.bsp
  • mod/pak0.pak:maps/start.bsp
  • mod/maps/start.bsp

Generally the engine will prefer the one from the last PAK file in the path hierarchy, so in this case the file from the mod's pak0.pak.

But some modern engines will actually prefer a file outside PAK files over PAK files in the same mod path. So for example FTE, Darkplaces or QuakeSpasm-Spiked will use the unpacked mod/maps/start.bsp which (editor's subjective opinion) is infinitely more reasonable.

Format specification[edit]

All numerical values are in little-endian (Intel) format.

Header (12 bytes):

Name Type Info
id 4 byte string Should be "PACK" (not null-terminated).
offset Integer (4 bytes) Index to the beginning of the file table.
size Integer (4 bytes) Size of the file table.

You can determine the number of files stored in the .pak file by dividing the "size" value in the header by 64 (the size of the file entry struct).

File entry (64 bytes):

Name Type Info
name 56 byte null-terminated string Includes path. Example: "maps/e1m1.bsp".
offset Integer (4 bytes) The offset (from the beginning of the pak file) to the beginning of this file's contents.
size Integer (4 byte) The size of this file.

The pak files packaged with Quake store the file table after all the actual contents, but it shouldn't matter which comes first.

Games which use the .pak format[edit]

Most games based on the Quake engine retain the use of the .pak file format.

Some other completely unrelated games also use the .pak format, due to its simplicity.

Sample code[edit]

Here is some public-domain C code to open a .pak file, and return the contents of a certain file inside it. (The LittleLong function, not defined here, should byteswap the variable contents only if the code is compiled on a big-endian machine.)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct pak_header_s
{
  char id[4];
  int offset;
  int size;
} pak_header_t;

typedef struct pak_file_s
{
  char name[56];
  int offset;
  int size;
} pak_file_t;

/* pak_filename : the os filename of the .pak file */
/* filename     : the name of the file you're trying to load from the .pak file (remember to use forward slashes for the path) */
/* out_filesize : if not null, the loaded file's size will be returned here */
/* returns a malloced buffer containing the file contents (remember to free it later), or NULL if any error occurred */
void *pak_load_file(const char *pak_filename, const char *filename, int *out_filesize)
{
  FILE *fp;
  pak_header_t pak_header;
  int num_files;
  int i;
  pak_file_t pak_file;
  void *buffer;

  fp = fopen(pak_filename, "rb");
  if (!fp)
    return NULL;

  if (!fread(&pak_header, sizeof(pak_header), 1, fp))
    goto pak_error;
  if (memcmp(pak_header.id, "PACK", 4) != 0)
    goto pak_error;

  pak_header.offset = LittleLong(pak_header.offset);
  pak_header.size = LittleLong(pak_header.size);

  num_files = pak_header.size / sizeof(pak_file_t);

  if (fseek(fp, pak_header.offset, SEEK_SET) != 0)
    goto pak_error;

  for (i = 0; i < num_files; i++)
  {
    if (!fread(&pak_file, sizeof(pak_file_t), 1, fp))
      goto pak_error;

    if (!strcmp(pak_file.name, filename))
    {
      pak_file.offset = LittleLong(pak_file.offset);
      pak_file.size = LittleLong(pak_file.size);

      if (fseek(fp, pak_file.offset, SEEK_SET) != 0)
        goto pak_error;

      buffer = malloc(pak_file.size);
      if (!buffer)
        goto pak_error;

      if (!fread(buffer, pak_file.size, 1, fp))
      {
        free(buffer);
        goto pak_error;
      }

      if (out_filesize)
        *out_filesize = pak_file.size;
      return buffer;
    }
  }

pak_error:
  fclose(fp);
  return NULL;
}

Notes[edit]

  1. Unofficial Quake Specs, http://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_3.htm#CPAKF (Note that 0x38 is actually 56, not 50)

Template:Quake file formats