This page describes the AVF file format used in various Nancy Drew games by Her Interactive. In the first few titles, they were used for still and animated scenes (UI elements and overlays being the main exception, done with TGA files), while in later games were relegated to just particular still backdrops. This video format does not support audio; any relationships between video and audio are handled by the game itself.
All multi-byte numbers are in little-endian format, unless otherwise specified.
The following games have had their AVF files examined and figured out to the extent described below. Other games in the series are potentially different.
- ✔ Secrets Can Kill
- ✔ Stay Tuned for Danger
- ✔ Message in a Haunted Mansion
- ✔ Secret of Shadow Ranch
- ✔ Curse of Blackmoor Manor
This appears at the top of any AVF file. It identifies the file as an AVF file and defines some information about the video as a whole.
|0x00||15||Constant null-terminated string
|0x15||2||Number of entries in the frame index (see next section)|
|0x17||2||Width of each frame, in pixels.|
|0x19||2||Height of each frame in pixels.|
|0x1B||6||Unknown. Seems to always be
The frame index details offsets into this file for each frame of animation. This index starts immediately after the header. Each entry is 19 bytes long.
|0x00||2||Frame number, zero-indexed.|
|0x02||4||Absolute offset to first byte of the frame.|
|0x06||4||Size of the frame data in bytes.|
|0x0A||3||Unknown, seems to be file-local padding. "File-local" means that this padding is (as far as has been investigated) the same for every entry in the index, but different between different AVF files.|
|0x0D||6||Unknown, seems to be game-local padding. "Game-local" means that this padding seems to be the same for every index entry of every AVF file in a particular game, but differs between different Nancy Drew titles.|
An AVF's frames are "raw", in the sense that they come with no kind of header. These frames are "encrypted" (in a strenuous use of the term) and compressed in LZSS format. The following subsections go through these pieces in the order necessary to read the image data.
To summarize the below, in order to read the frame data you need to do the following:
- For each byte, subtract its offset modulo 256 from itself, and modulo that result by 256 (specifically the "floored division" kind of modulo1).
- Decompress the LZSS-compressed data.
- Convert the RGB555 color data to your format of choice.
The "encryption", for lack of a better term, involves adding offsets to each byte. Specifically, for each byte in the frame, you take its (zero-indexed!) offset from the start of the frame data, and add the least-significant byte of that offset to the byte. The stored byte is the least-significant byte of the sum. As an example, given non-encrypted frame data
00 00 00 00 00 00 00 00
The result after encryption would be
00 01 02 03 04 05 06 07
, and for original data
FF FF FF FF FF FF FF FF
the result would be
FF 00 01 02 03 04 05 06
. Notice how the very first byte is unmodified by this operation (and in fact, every 256th byte, or every byte whose offset satisfies
offset % 256 == 0).
To read the frame data, you simply invert this operation by subtracting the low-order byte of each byte's offset from that byte.
While this section won't describe LZSS compression in general, we will list the points that differ between implementations:
- Flag bits for data chucks are stored in groups of eight bits before the next eight chunks.
- The first chunk's flag bit is the least-significant in the group, with the next 7 chunks going in increasing significance.
- The last group of flag bits will have "fake" flag bits if there aren't 8 data chunks left. These are safe to ignore.
- Circular buffer is 4096 bytes, and starts filling at offset 0xFEE (not 0x000).
- The circular buffer is initially filled with
0x20values (ASCII spaces).
The frame's image data after decompression is a series of 16-bit color values in RGB555 format. The most significant bit of the 16-bit value is the unused bit. as a binary representation:
0rrr rrgg gggb bbbb
Important! These 16-bit values are not little-endian, but rather big-endian. This means that the first byte of each pixel contains the unused most-significant bit.