Dumping Particles
nosoop edited this page 6 years ago

Particles aren't exactly part of engine / game mod code, but there hasn't been a good way to dump it yet, so I wrote up a script to do just that.

Bash function to dump the particle files from the game package (output can be redirected):

dump_particle_systems() {
    # dump particle system info
    PACKAGE_FILE='/path/to/tf2_misc_dir.vpk'
    script_dir='/path/to/pcfdump.py'
    
    # extract to temporary directory
    temp_dir=$(mktemp -d)
    pushd "${temp_dir}" > /dev/null
        mkdir particles
        vpk x ${PACKAGE_FILE} `vpk l ${PACKAGE_FILE} | grep '\.pcf'` > /dev/null
        
        python3 "${script_dir}"
    popd > /dev/null

    if [[ -d ${temp_dir} ]]; then
        rm -r "${temp_dir}"
    fi
}

Python script to parse all the unique PCF files and output sorted particle system names:

#!/usr/bin/python3

import struct
import pathlib

ignored_suffixes = [ '_high', '_dx80', '_dx90_slow' ]

def unpack(fmt, stream):
    '''
    unpacks values from a stream
    source: https://stackoverflow.com/a/17537253
    '''
    size = struct.calcsize(fmt)
    buf = stream.read(size)
    return struct.unpack(fmt, buf)

def unpack_string(stream, offset = None, encoding = 'utf-8'):
    ''' unpacks a variable-length, zero-terminated string from a stream '''
    if offset is not None:
        stream.seek(offset)
    
    return bytes(iter(lambda: ord(stream.read(1)), 0)).decode(encoding)

particle_map = {}

# dumps particle names from particles/*.pcf
for filepath in pathlib.Path('particles').glob('*.pcf'):
    string_dict = []
    particle_list = []
    
    if any(filepath.stem.endswith(suffix) for suffix in ignored_suffixes):
        continue
    
    with open(str(filepath), 'rb') as particle_file:
        header = unpack_string(particle_file)
        
        # string table
        num_strings, *_ = unpack('<H', particle_file)
        for _ in range(0, num_strings):
            string_entry = unpack_string(particle_file)
            string_dict.append(string_entry)
        
        num_elements, *_ = unpack('<I', particle_file)
        for _ in range(0, num_elements):
            # CDmxElement
            index, *_ = unpack('<H', particle_file)
            element_name = unpack_string(particle_file)
            sig = unpack('<16c', particle_file)
            
            if string_dict[index] == 'DmeParticleSystemDefinition':
                particle_list.append(element_name)
    particle_map[filepath.stem] = particle_list

# dump the stuff
for filename, particle_list in sorted(particle_map.items()):
    print(filename)
    print(*('\t' + p for p in sorted(particle_list)), sep = '\n')