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

Difference between revisions of ".pak"

From Quake Wiki

(added link to Quake_tools#PAK_Editors:)
(Format spec: The header is 12 bytes, not 64)
Line 4: Line 4:
 
All numerical values are in little-endian (Intel) format.
 
All numerical values are in little-endian (Intel) format.
  
Header (64 bytes):
+
Header (12 bytes):
  
 
{|class="wikitable"
 
{|class="wikitable"

Revision as of 08:21, 15 August 2020

.pak is Quake's container file format. It is an exceedingly simple uncompressed archive format which preserves file paths and that's about it. See Quake_tools#PAK_Editors for a list of tools to work with .pak files.

Format specification

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

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

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;
}

See also