Loading...   

PFS File Reference

Overview

The PFS file format is the general eqgame compressed archive format, eqg and s3d files for example are both examples of PFS files.

 

Organization

PFS files are organized in the following way:

  • PFS Header
  • Compressed Data Blocks (Variable Count)
  • Directory
  • Footer (Optional)

 

Header

directory_header_offset: uint32

This points to the offset in the file that the directory header is located at.

magic: char[4]

This is a magic string that identifies this file as a PFS file. It contains the string "PFS "

unknown: uint32

The purpose of this field is unknown; in every known PFS file it contains the value 131072.

 

Compressed Data Blocks

These are the file data in compressed form broken up into blocks of 8192 bytes of uncompressed data.  While the file format itself will allow for blocks larger than 8192 bytes the client itself will crash if you make it larger.

deflate_length: uint32

The size of the compressed data in the block.

inflate_length: uint32

The size of the data in the block when uncompressed; this cannot be > 8192 or the client will crash reading it.

data_block: char[deflate_length]

Then there is deflate_length number of bytes of data that is the actual compressed data block.  The data is compressed with fairly standard zlib compression.

 

Directory

The directory contains entries for every file and an extra entry for the file list.

 

entry_count: uint32

The number of directory entries in the directory.

There is then an entry_count number of the following.

file_crc: int32

The filename CRC of a file entry.

file_offset: uint32

The offset in the file that this file entry's data blocks begin at.

file_size: uint32

The total uncompressed size of this file entry.

 

Note: There is always file count + 1 entries in the directory.  The extra entry is the entry for the file name list (documented below), that entry always has a CRC of 0x61580ac9.

 

Footer

Footers are optional, mostly seen in older zones and omitted in newer zones.  They seem to have no effect on the file parsing and one can consider the section essentially obsolete.

 

magic: char[5]

A magic string that contains "STEVE"

date: uint32

Some date value in integer format.

 

Filename List Format

The filename list contains a list of filenames used in the archive.  By taking the CRC of every filename in this list you can match up filenames to directory entries.

 

count: uint32

The number of filenames in the archive.

length: uint32

There is then count number of filename entries starting with the length of the filename in bytes.

filename: char[length]

Then the actual filename in length number of bytes.

 

Filename CRC Algorithim

The filename CRC is based on the standard CRC-32 algorithim as follows with a polynomial of 0x04C11DB7

void GenerateCRCTable() {
    int32 i, j, crc_accum;
    for(i = 0; i < 256; ++i) {
        crc_accum = i << 24;
        for(j = 0; j < 8; ++j) {
            if ((crc_accum & 0x80000000) != 0) {
                crc_accum = (crc_accum << 1) ^ polynomial;
            } else {
                crc_accum = crc_accum << 1;
            }
        }
        crc_table[i] = crc_accum;
    }
}

int32 CRC(int32 crc, char *data, int32 length) {
    int32 i;
    while(length > 0) {
        i = ((crc >> 24) ^ *data) & 0xFF;
        data += 1;
        crc = (crc << 8) ^ crc_table[i];
        length -= 1;
    }
    return crc;
}

To get the CRC of a filename you first convert the filename to lowercase then you CRC the lowercase filename you have plus a null terminator.