PCF
The PCF format is an implementation of DMX serialization to store particle data.
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 five known versions: Binary 2 DMX 1 (DoD:S), Binary 2 PCF 1 (Orange Box), Binary 3 PCF 2 (Left 4 Dead), Binary 4 PCF 2 (Left 4 Dead 2), and Binary 5 PCF 2 (Alien Swarm). 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 notable exception is the Source Particle Benchmark tool, where PCF files are plain KeyValues2 files.
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 --> | clouds.pcf found in Portal 2. |
<!-- dmx encoding binary 3 format pcf 2 --> | Left 4 Dead, various unused Portal 2 particles such as zombie.pcf, paint_fizzler.pcf, and chicken.pcf |
<!-- dmx encoding binary 4 format pcf 2 --> | Left 4 Dead 2 |
<!-- dmx encoding binary 5 format pcf 2 --> | Alien Swarm, Portal 2, Counter-Strike: Global Offensive, Titanfall branch |
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:
struct CDmxElement
{
unsigned int typeNameIndex; // String dictionary index
unsigned int elementNameIndex; // String dictionary index
unsigned char dataSignature[16]; // 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.
struct CDmxAttribute
{
unsigned short typeNameIndex; // String dictionary index (unsigned int for Binary 5)
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)