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 filevpk_path: Path to the VPK fileauto_parse: Automatically parse directory on init (default: True)
Methods
parse_directory() -> VPKFile- Parse the VPK directory structure (called automatically unlessauto_parse=False)list_files(extension: str = None, path: str = None) -> List[str]- List files with optional filteringfind_files(pattern: str) -> List[str]- Find files matching a glob patternfind_file_path(filename: str) -> Optional[str]- Find the full path of a filenameextract_file(filepath: str, output_path: str) -> bool- Extract a file from the archiveextract_all(output_dir: str, pattern: str = None) -> int- Extract multiple files to a directorypatch_file(filepath: str, new_data: bytes, create_backup: bool = False) -> bool- Modify a file in the archiveget_file_data(filepath: str) -> Optional[bytes]- Read a file's contents directly into memoryfile_exists(filepath: str) -> bool- Check if a file exists in the archiveget_file_info(filepath: str) -> Optional[Dict]- Get comprehensive information about a filecreate(source_dir: str, output_base_path: str, split_size: int = None) -> bool- Create new VPK archive (class method)
Properties
directory- Parsed directory structureis_dir_vpk- Whether this is a directory VPK filevpk_path- Path to the VPK file
VPKDirectoryEntry
Represents an entry in the VPK directory.
Properties
crc: int- CRC32 checksumpreload_bytes: int- Number of preload bytesarchive_index: int- Archive file indexentry_offset: int- Offset within archiveentry_length: int- Length of file datapreload_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 fileencode(output_path: Union[Path, str]) -> PCFFile- Write PCF file to diskfind_element_by_name(name: str) -> Optional[PCFElement]- Find a particle element by its nameget_elements_by_type(type_name: str) -> List[PCFElement]- Get all elements of a specific typeget_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 stringstring_dictionary- List of strings used in the fileelements- List of particle system elements
PCFElement
Represents a particle system element.
Properties
type_name_index: int- Index into string dictionary for type nameelement_name: bytes- Name of the elementdata_signature: bytes- 16-byte signatureattributes: 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 filemdl_path: Path to the MDL fileauto_parse: Automatically parse on init (default: True)
Methods
parse() -> MDLFile- Parse the MDL header (called automatically unlessauto_parse=False)rewrite_material_dirs(new_dirs: List[str], *, update_data_length: bool = True) -> None- ReplacematerialDirectories[]in place
Properties
version: int- MDL format versionchecksum: int- MDL checksumname: str- Internal model namedata_length: int- HeaderdataLengthfieldfile_size: int- Current size of the file on diskmaterials: List[str]- Material names referenced by the modelmaterial_dirs: List[str]- Search directories the engine looks in for those materials
Constants
PCFVersion- Enum of supported PCF versionsAttributeType- 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.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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file valve_parsers-1.2.1.tar.gz.
File metadata
- Download URL: valve_parsers-1.2.1.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e7ea3e33be6152a0b029e9fb78c8ac9c30fd745cb43b9abf95228a1adeafc52
|
|
| MD5 |
447f58323e8bc15a93541bb496d85f5e
|
|
| BLAKE2b-256 |
e1c9ffb89e7ba9dc9239be76146af7c6486a2bd0e6fd1d6cce2133aebee2b2c1
|
File details
Details for the file valve_parsers-1.2.1-py3-none-any.whl.
File metadata
- Download URL: valve_parsers-1.2.1-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d57d9746da8eba4c46492d645792fa096f74b7c21f813359fc68115b87a020d
|
|
| MD5 |
9c8d72ccf40b95f97d61f176bbb0c676
|
|
| BLAKE2b-256 |
8b981ae5833d0b9ea1daa81f07425466f6a2fe40cc0d2ac68d747c4f34431b55
|