Nicking assets from Tachyon: The Fringe, Part 2: The PFF Archive

The first file we’ll look at on our foray into pixel piracy is the PFF Archive. This is an archive file containing all of the game assets for Tachyon. There’s just the one,¬†Tachyon.pff, and it’s nearly 400MB in size (no compression, but when extracted the contents¬†usually take ~2GB on disk, due to a lot of small files). (This was back in the day when 2GB was a lot). This file format has been used by several NovaLogic games, most prominently the Delta Force series. Later versions of the format seem to support compression, but the format we’re working with is basically straight storage.

Technically the PFF format isn’t vital for our model extraction project- extractors have been around for the past 20 years, and we could just as easily work off an already extracted archive. However, that makes moving between computers a bit annoying, as you then need to find the extractor, extract the directory, point the app at the directory. Too much work, easier to point the app at a single file (especially as we aren’t looking to modification of underlying files). This also serves as a starting point for the blog series, to play with formatting and such before the content is important.

Thankfully at least one of the PFF extractors (from Devil’s Claw) also came with source code; so no reverse engineering required, just a minor port. Thank you Devil’s Claw.

On with the show.

 

 

General format of the file is a Header, a set of File Entries, a Footer, and then a massive blob of data.

 

Header

Size Type Name Value Comments
4 int Header Size 20
4 char[4] Version String PFF3 PFF3, PFF4, etc
4 int File Count 8134 Number of File Entries
4 int File Segment Size 36 Size of Individual record, 32 to 40 bytes
4 int File List Offset 0x16C6A7F1 Start of File Entry Array, near end of file in this case

One interesting bit about the header is that the version string is lies (according to the old code). File Segment Size varies by PFF version, from 32 bytes for “V2”, 36 for V3, 40 for V4. But, “V2” and V3 have the same version string: PFF3.

File Segment Entry

Size Type Name Comments
4 int Deleted 0 for “not deleted”
4 int File Position Index into PFF where file content starts
4 int File Length
4 int Creation Date Guessing unix like timestamp; all around 2000 if true
16 char* File Name Null terminated string
4 int Modified Date Optional based on Segment Size, present in our data set, and has some weird values for timestamps (2020s+)
4 int Compression Level Optional based on Segment Size

Footer

Size Type Name Comments
4 int System IP Who packed the file, a 192.168.*.* address.
4 int Reserved/Unknown
4 char[] KING tag. Value=KING… Magic value, at a guess?

Appears after all of the File Entries. At a guess the KING tag is intended as a magic value to check that the file is otherwise valid.

Thoughts

It’s a straightforward archive format, with some support for extensions built in. It’s not bad, and kind of elegant for the goal of ‘single file archive’. It’s a bit wasteful though- what I usually see, when size matters, is the entries will have a bit field, with some fields defined, and others reserved for future use. IE deleted and compression level don’t need 8 bytes all to themselves. Though I’ll grant, for this use you’re talking about 8 bytes across 8000 entries- 64KB of semi-wasted space in a 400MB file.

Source code for the C# reader can be found here, and the Source for the Devils Claw extractor is here.

Other Notes: I wrote this a long time ago and never got around to publishing it because I wanted to get formatting for the tables right. Giving up on that for now, and just publishing to get the process started again. This’ll be a bigger problem in some of the next file formats, unfortunately, but we’ll see what happens. I also may use this as an opportunity to learn Rust, as Visual Studio is getting on my nerves.

Leave a Reply


Allowed HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>