PCF File Format

From Valve Developer Community
Revision as of 04:25, 10 October 2018 by DesertFoxZalem5 (talk | contribs) (Added VIDE particle converter)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The PCF format is an implementation of DMX serialization to store particle data.

Note:The current content of this page is based on information provided by data mining. Explanation may be incomplete or incorrect.
Note:Source code given represents the structure of the currently described subject, not how it may best be utilized.

Origin

Prior to the Orange Box, particles were handled exclusively in code. To increase flexibility, particles were moved to independent files interpreted dynamically by the engine.

File Format

Currently, there are four known versions: Binary 2 DMX 1 (DoD:S), Binary 2 PCF 1 (Orange Box), Binary 3 PCF 2 (Left 4 Dead), and Binary 4 PCF 2 (Left 4 Dead 2). The serialization of each is basically the same. All strings are null-terminated and other data is little-endian. Version differences only seem to describe what set of particle operators the PCF uses. More specific version differences are described later.

A PCF file has three main sections: the string dictionary, the element dictionary, and the data itself.

Fun Fact: Valve's PCFs are five bytes larger than necessary.

Header

PCF files use a "magic string" for a header:

String Version
<!-- dmx encoding binary 2 format dmx 1 --> Only found with DoD:S. Predecessor to and/or interchangeable with Binary 2 PCF 1?
<!-- dmx encoding binary 2 format pcf 1 --> Orange Box
<!-- dmx encoding binary 3 format pcf 1 --> Portal 2 clouds.pcf
<!-- dmx encoding binary 3 format pcf 2 --> Left 4 Dead
<!-- dmx encoding binary 4 format pcf 2 --> Left 4 Dead 2
<!-- dmx encoding binary 5 format pcf 2 --> Alien Swarm, Portal 2, Titanfall 2

String Dictionary

The string dictionary starts with the number of strings to follow. For Binary 4 PCF 2, the number is an (unsigned?) int, otherwise, an (unsigned?) short.

Unlike previous versions, Binary 4 PCF 2 has every string in the dictionary. Strings that would normally appear as-is in the two remaining sections are serialized as offsets.

Element Dictionary

The element dictionary starts with an (unsigned?) int with the number of element structures to follow.

(In general practice, any int's that count structures and/or elements are unsigned in nature, as it is highly improbable to have a negative count of them, and also prevents any integer-overflow bugs.)

For Binary 2 DMX 1, Binary 2 PCF 1, and Binary 3 PCF 2:

struct CDmxElement
{
	unsigned short typeNameIndex; // String dictionary index
	std::string elementName; // Element name
	unsigned char dataSignature[16]; // Globally unique identifier
};


For Binary 4 PCF 2:

struct CDmxElement
{
	unsigned short typeNameIndex; // String dictionary index
	unsigned short elementNameIndex; // String dictionary index
	unsigned char dataSignature[16]; // Globally unique identifier
};


For Binary 5 PCF 2:

Confirm:Data signature is 20 bytes?
struct CDmxElement
{
	unsigned short typeNameIndex; // String dictionary index
	unsigned short elementNameIndex; // String dictionary index
	unsigned char dataSignature[20]; // Globally unique identifier
};

Data

Every element owns a list of attributes. An attribute may contain generic data, or reference another element.

Attributes appear in the order of each element, preceded by an integer count. This listing continues through the end of the file.

Note:Left 4 Dead 2's particle editor does not save attributes unrecognized by the owner, or with the editor given default value.
struct CDmxAttribute
{
	unsigned short typeNameIndex; // String dictionary index
	unsigned char attributeType; // See below tables
	void *attributeData; // Pointer to data, use attributeType to safely deference
};
Attribute Type Size Default Value Notes
ATTRIBUTE_ELEMENT 0x01 4 Bytes  ? Integer index into element array
ATTRIBUTE_INTEGER 0x02 4 Bytes 0 int
ATTRIBUTE_FLOAT 0x03 4 Bytes 0.0 float
ATTRIBUTE_BOOLEAN 0x04 1 Byte false bool
ATTRIBUTE_STRING 0x05 1+ Byte(s) Empty C-style string. For Binary 4 PCF 2, unsigned short
ATTRIBUTE_BINARY 0x06 4+ Bytes Empty Array of char preceded by integer count
ATTRIBUTE_TIME 0x07 4 Bytes 0.0 Technically float, written as (int)( float * 10000.0 ), read as ( int / 10000.0 )
ATTRIBUTE_COLOR 0x08 4 Bytes 0 0 0 0 unsigned char red , green , blue , alpha;
ATTRIBUTE_VECTOR2 0x09 8 Bytes 0.0 0.0 float x , y;
ATTRIBUTE_VECTOR3 0x0A 12 Bytes 0.0 0.0 0.0 float x , y , z;
ATTRIBUTE_VECTOR4 0x0B 16 Bytes 0.0 0.0 0.0 0.0 float x , y , z , w;
ATTRIBUTE_QANGLE 0x0C 12 Bytes 0.0 0.0 0.0 Same as ATTRIBUTE_VECTOR3
ATTRIBUTE_QUATERNION 0x0D 16 Bytes 0.0 0.0 0.0 1.0 Same as ATTRIBUTE_VECTOR4, but w defaults to 1
ATTRIBUTE_MATRIX 0x0E 64 Bytes Identity matrix float[4][4];

Each type has an array counterpart. The data is preceded by an integer count. An array may be empty.

Attribute Type
ATTRIBUTE_ELEMENT_ARRAY 0x0F
ATTRIBUTE_INTEGER_ARRAY 0x10
ATTRIBUTE_FLOAT_ARRAY 0x11
ATTRIBUTE_BOOLEAN_ARRAY 0x12
ATTRIBUTE_STRING_ARRAY 0x13
ATTRIBUTE_BINARY_ARRAY 0x14
ATTRIBUTE_TIME_ARRAY 0x15
ATTRIBUTE_COLOR_ARRAY 0x16
ATTRIBUTE_VECTOR2_ARRAY 0x17
ATTRIBUTE_VECTOR3_ARRAY 0x18
ATTRIBUTE_VECTOR4_ARRAY 0x19
ATTRIBUTE_QANGLE_ARRAY 0x1A
ATTRIBUTE_QUATERNION_ARRAY 0x1B
ATTRIBUTE_MATRIX_ARRAY 0x1C

Hex hacking

It is possible to load L4D1 Particles into a Source 2009 mod using a simple hex hack.

Open up the particle file in Notepad and look for this header:

"<!– dmx encoding binary 3 format pcf 2 –>"

Change it to

"<!– dmx encoding binary 2 format pcf 1 –>"

then save.

VIDE

You can use VIDE to convert newer pcf files to the orangebox pcf version.


See also

  • VIDE - Valve Integrated Development Environment (A third-party Source toolkit)