Skip to main content

Python library for parsing Valve game files (VPK, PCF, MDL)

Project description

Valve Parsers

A Python library for parsing Valve game files, extracted from my casual-preloader project. This library provides support for:

  • VPK (Valve Package) files - Valve's archive format used in Source engine games
  • PCF (Particle) files - Valve's particle system files - See constants.py for supported versions
  • MDL (Model) files - read materials[] / materialDirectories[]

Features

  • Support for single-file and multi-file VPK archives (creation and modification)
  • Full VPK directory parsing and file extraction
  • In-place VPK file patching with size checking
  • PCF parsing and encoding
  • Support for all PCF attribute types (see constants.py for these as well)

Installation

pip install valve-parsers

Quick Start (Parsing + Modification)

Creating VPK Archives

from valve_parsers import VPKFile

# Create a single-file VPK
success = VPKFile.create("source_directory", "output/archive.vpk")

# Create a multi-file VPK with size limit (100MB per archive split)
success = VPKFile.create("source_directory", "output/archive", split_size=100*1024*1024)

Working with VPK Archives

from valve_parsers import VPKFile

# Open a VPK file
vpk = VPKFile("path/to/archive.vpk")

# List all files
files = vpk.list_files()
print(f"Found {len(files)} files")

# Find files matching a pattern
texture_files = vpk.list_files(extension="vtf")
material_files = vpk.find_files("materials/*.vmt")

# Extract all files matching a pattern
count = vpk.extract_all("output_dir", pattern="materials/*.vmt")
print(f"Extracted {count} material files")

# Extract a file
vpk.extract_file("materials/models/player/scout.vmt", "output/scout.vmt")

# Or load it directly into memory
file_data = vpk.get_file_data("materials/models/player/scout.vmt")
if file_data:
    content = file_data.decode('utf-8')
    
# Patch a file
# Read a new material file from disk
new_texture_path = "custom_scout_red.vmt"
with open(new_texture_path, 'rb') as f:
    new_texture_data = f.read()

# Target file path inside the VPK
target_file = "materials/models/player/scout_red.vmt"

# Check if a file exists
if vpk.file_exists(target_file):
    print("File found!")
    # Get file info
    info = vpk.get_file_info(target_file)
    if info:
        print(f"Size: {info['size']} bytes, CRC: 0x{info['crc']:08X}")
        # IMPORTANT: Patched files must match original size exactly!
        original_size = info['size']
        if len(new_texture_data) != original_size:
            if len(new_texture_data) < original_size:
                # Pad with spaces to match original size
                padding_needed = original_size - len(new_texture_data)
                print(f"Adding {padding_needed} bytes of padding")
                new_texture_data = new_texture_data + b' ' * padding_needed
            else:
                print(f"ERROR: New file is {len(new_texture_data) - original_size} bytes larger!")
                print("File cannot be patched - size must match exactly")
    
        # Now patch the file
        vpk.patch_file(target_file, new_texture_data, create_backup=False)

MDL Files

from valve_parsers import MDLFile

# Open and parse a MDL file
mdl = MDLFile("path/to/model.mdl")

print(f"version: {mdl.version}")
print(f"checksum: 0x{mdl.checksum:08X}")
print(f"name: {mdl.name}")
print(f"materials: {mdl.materials}")
print(f"material_dirs: {mdl.material_dirs}")

# Rewrite materialDirectories[] in place. New strings are appended at EOF.
new_dirs = [d.replace("models\\", "console\\models\\") for d in mdl.material_dirs]
mdl.rewrite_material_dirs(new_dirs)

PCF Files

from valve_parsers import PCFFile

# Open and decode a PCF file
pcf = PCFFile("path/to/particles.pcf").decode()

print(f"PCF Version: {pcf.version}")
print(f"String dictionary: {len(pcf.string_dictionary)} entries")
print(f"Elements: {len(pcf.elements)} particle systems")

# Print particle system data
for element in pcf.elements:
    print(f"Element: {element.element_name}")
    for attr_name, (attr_type, attr_value) in element.attributes.items():
        print(f"  {attr_name.decode()}: {attr_value}")
        
# Rename all operators to ''
for i, element in enumerate(pcf.elements):
    type_name = pcf.string_dictionary[element.type_name_index].decode('ascii')
    if type_name == 'DmeParticleOperator':
        element.element_name = str('').encode('ascii')

# Encode back to file
pcf.encode("output/modified_particles.pcf")

# Find a specific element by name
element = pcf.find_element_by_name("my_explosion_effect")
if element:
    print(f"Found element with {len(element.attributes)} attributes")

# Get all elements of a specific type
operators = pcf.get_elements_by_type('DmeParticleOperator')
print(f"Found {len(operators)} operators")

# Get and set attribute values with helper methods
from valve_parsers import AttributeType

element = pcf.find_element_by_name("my_effect")
if element:
    # Get attribute value
    radius = pcf.get_attribute_value(element, "radius", default=5.0)
    print(f"Current radius: {radius}")

    # Set attribute value
    pcf.set_attribute_value(element, "radius", 10.0, AttributeType.FLOAT)
    pcf.set_attribute_value(element, "color", (255, 0, 0, 255), AttributeType.COLOR)

pcf.encode("output/modified_particles.pcf")

API Reference

VPKFile

The main class for working with VPK archives.

Constructor

  • VPKFile(vpk_path: Union[str, Path], auto_parse: bool = True) - Initialize with path to VPK file
    • vpk_path: Path to the VPK file
    • auto_parse: Automatically parse directory on init (default: True)

Methods

  • parse_directory() -> VPKFile - Parse the VPK directory structure (called automatically unless auto_parse=False)
  • list_files(extension: str = None, path: str = None) -> List[str] - List files with optional filtering
  • find_files(pattern: str) -> List[str] - Find files matching a glob pattern
  • find_file_path(filename: str) -> Optional[str] - Find the full path of a filename
  • extract_file(filepath: str, output_path: str) -> bool - Extract a file from the archive
  • extract_all(output_dir: str, pattern: str = None) -> int - Extract multiple files to a directory
  • patch_file(filepath: str, new_data: bytes, create_backup: bool = False) -> bool - Modify a file in the archive
  • get_file_data(filepath: str) -> Optional[bytes] - Read a file's contents directly into memory
  • file_exists(filepath: str) -> bool - Check if a file exists in the archive
  • get_file_info(filepath: str) -> Optional[Dict] - Get comprehensive information about a file
  • create(source_dir: str, output_base_path: str, split_size: int = None) -> bool - Create new VPK archive (class method)

Properties

  • directory - Parsed directory structure
  • is_dir_vpk - Whether this is a directory VPK file
  • vpk_path - Path to the VPK file

VPKDirectoryEntry

Represents an entry in the VPK directory.

Properties

  • crc: int - CRC32 checksum
  • preload_bytes: int - Number of preload bytes
  • archive_index: int - Archive file index
  • entry_offset: int - Offset within archive
  • entry_length: int - Length of file data
  • preload_data: Optional[bytes] - Preloaded data

PCFFile

The main class for working with PCF particle files.

Constructor

  • PCFFile(input_file: Union[Path, str], version: str = "DMX_BINARY2_PCF1") - Initialize with file path, default version is "DMX_BINARY2_PCF1"

Methods

  • decode() -> PCFFile - Parse the PCF file
  • encode(output_path: Union[Path, str]) -> PCFFile - Write PCF file to disk
  • find_element_by_name(name: str) -> Optional[PCFElement] - Find a particle element by its name
  • get_elements_by_type(type_name: str) -> List[PCFElement] - Get all elements of a specific type
  • get_attribute_value(element: PCFElement, attr_name: str, default=None) - Get an attribute value from an element (static method)
  • set_attribute_value(element: PCFElement, attr_name: str, value, attr_type: AttributeType) - Set an attribute value on an element

Properties

  • version - PCF version string
  • string_dictionary - List of strings used in the file
  • elements - List of particle system elements

PCFElement

Represents a particle system element.

Properties

  • type_name_index: int - Index into string dictionary for type name
  • element_name: bytes - Name of the element
  • data_signature: bytes - 16-byte signature
  • attributes: Dict[bytes, Tuple[AttributeType, Any]] - Element attributes

MDLFile

The main class for reading and rewriting Source MDL model files.

Constructor

  • MDLFile(mdl_path: Union[str, Path], auto_parse: bool = True) - Initialize with path to MDL file
    • mdl_path: Path to the MDL file
    • auto_parse: Automatically parse on init (default: True)

Methods

  • parse() -> MDLFile - Parse the MDL header (called automatically unless auto_parse=False)
  • rewrite_material_dirs(new_dirs: List[str], *, update_data_length: bool = True) -> None - Replace materialDirectories[] in place

Properties

  • version: int - MDL format version
  • checksum: int - MDL checksum
  • name: str - Internal model name
  • data_length: int - Header dataLength field
  • file_size: int - Current size of the file on disk
  • materials: List[str] - Material names referenced by the model
  • material_dirs: List[str] - Search directories the engine looks in for those materials

Constants

  • PCFVersion - Enum of supported PCF versions
  • AttributeType - Enum of PCF attribute types

Supported Games

This library works with files from Orange Box titles. Mostly intended for TF2, YMMV with other games.

See:

https://developer.valvesoftware.com/wiki/PCF

https://developer.valvesoftware.com/wiki/VPK_(file_format)

https://developer.valvesoftware.com/wiki/MDL_(Source)

Contributing

This library was yoinked from my casual-pre-loader project. Contributions are welcome!

Changelog

1.2.2

  • Added support for path lists to grow in size

1.2.1

  • Added support for empty paths in model strings

1.2.0

  • Start of MDL parser (MDLFile)

1.0.7

  • Performance: Replaced rglob with os.walk for VPK creation
  • Performance: Replaced Path.match with fnmatch in find_files()
  • Bug fix: find_files() now uses correct glob matching behavior

1.0.6

  • Updated README with corrected examples
  • Added documentation for all VPK methods: extract_all(), get_file_data(), file_exists(), get_file_info()
  • Added file patching size-checking examples
  • Made get_file_entry() private
  • Cleaned up internal method documentation

1.0.5

  • Auto-parse support
  • Reading and writing is now 2-3x faster

1.0.2

  • Single file VPK no longer has _dir name

1.0.1

  • Nothing

1.0.0

  • Initial release
  • VPK parsing and creation support
  • PCF parsing and encoding support

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

valve_parsers-1.2.2.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

valve_parsers-1.2.2-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

Details for the file valve_parsers-1.2.2.tar.gz.

File metadata

  • Download URL: valve_parsers-1.2.2.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for valve_parsers-1.2.2.tar.gz
Algorithm Hash digest
SHA256 5ffdae1e7613023ceb4b61ec8f49c1f74d874403bc417b92d9279746a2f26e9c
MD5 c89dc5a0689653c0a835c8ec2b550ccb
BLAKE2b-256 a998cc00e49f86aed5df074caf91be356e84b1b1a3ba5a821ed291f41f8d3525

See more details on using hashes here.

File details

Details for the file valve_parsers-1.2.2-py3-none-any.whl.

File metadata

  • Download URL: valve_parsers-1.2.2-py3-none-any.whl
  • Upload date:
  • Size: 22.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for valve_parsers-1.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a265848ad65a004736e8f4333f9670880f0f073fe99b1b20bef6b8ac5b1dcf70
MD5 0a0bb532fd9a95a5f282ce222e39d7f7
BLAKE2b-256 d27f01cf71049533cd4444f82c542bf6b6a68d4c24dcd992bb1543c720eba9b0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page