Packed Animation File

From MultimediaWiki
Jump to: navigation, search

A Packed Animation File (PAF) is a movie used in the Windows version of the game Heart Of Darkness. The source code for a reverse engineered decoder is available at:

There is also a PlayStation version of this game; which employs the PlayStation Motion Decoder format for its FMV cutscenes, rather than this proprietary PAF format.

Archive Format

The Windows version of Heart Of Darkness contains a large data file called hod.paf. This file contains 25 PAF files concatenated together and prepended with a header. The header is 100 bytes long and consists of 25 little endian, 32-bit numbers which are absolute offsets into the archive file. For example, bytes 0-3 contain the number 0x000000C8 = 100 which indicates that the first file occurs immediately following the 100-byte header.

File Format

A PAF consists of a header and a series of interleaved audio and video data. The video data is stored with a custom paletted video encoding method. The audio is stored as uncompressed, stereo, 16-bit, little endian PCM data.

All multi-byte numbers are stored in little endian format.

bytes 0-55     signature
bytes 56-127   unknown; might be unused bytes for the signature block
bytes 132-135  frame count
bytes 140-143  video width
bytes 144-147  video height
bytes 152-155  read buffer size
bytes 156-159  number of frame blocks to preload
bytes 160-163  frame blocks count
bytes 164-167  starting offset of multimedia data
bytes 168-171  max video frames block count
bytes 172-175  max audio frames block count

The 55-byte signature field should contain the string "Packed Animation File V1.0\n(c) 1992-96 Amazing Studio\x0A\x1A".

Several tables follow


Video Codec

The custom video codec is a paletted video codec that maintains an array of 256 3-byte values. Each value has a range of 6 bits (due to VGA-era graphics) which will probably need to be scaled to support the more common 8-bit RGB component model.

The video resolution is usually 256x192. In fact, the reverse engineered decoder hardcodes these values as constants although they appear to be encoded in the file header.

The video codec maintains a ring buffer of 4 frames that are all allocated and initialized to 0 at the beginning of the decode process. The codec may refer back to any of the other 3 frames while decoding the current video frame.

 allocate array of 4 video_frames and initialize to 0
 current_frame = 0
   decode data to video_frames[current_frame]
   current_frame = current_frame + 1

Decoding a video frame operates by processing the video frame as a sequence of opcodes and data:

 consume next byte as opcode
 if bit 5 of opcode is set (opcode & 0x20):
   frame is a keyframe
   clear all 4 video_frames to 0
   reset current_frame to 0
 if bit 6 of opcode is set (opcode * 0x40):
   frame begins with palette update
   consume next byte as index
   consume next byte as count
   starting at the palette entry denoted by index,
    copy (count * 3) bytes from bytestream to palette 
 process the lower 4 bits of the opcode

For the last step of the opcode decoding process, there are 4 valid encoding modes: 0, 1, 2, and 4.

Mode 0

Block-based motion compensation using 4x4 blocks with either horizontal or vertical vectors; might incorporate VQ as well.


Mode 1

Uncompressed data. This mode specifies that (width * height) bytes should be copied directly from the encoded buffer into the output. Note that there are 2 unknown bytes before the raw data (possibly chunk length data; this needs to be verified). Thus, a mode 1 chunk would be laid out as:

 0x01 [2 unknown bytes] [width * height bytes of video data]

Mode 2

Copy reference frame: Consume the next byte in the stream as the reference frame (which should be 0, 1, 2, or 3, and should not be the same as the current frame number). Copy the specified reference frame to the current frame.

Mode 4

Run length encoding:

 skip the next 2 bytes in the bytestream (chunk length?)
 while there is still data in the bytestream, consume next byte as code
 if code < 0:
   consume next byte as run code
   count = -code + 1
   output count copies of run code
   count = code + 1
   copy count bytes from bytestream to output