# 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**
## 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
```bash
pip install valve-parsers
```
## Quick Start (Parsing + Modification)
### Creating VPK Archives
```python
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
```python
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)
```
### PCF Files
```python
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
### Constants
- `PCFVersion` - Enum of supported PCF versions
- `AttributeType` - Enum of PCF attribute types
## Supported Games
This library works with VPK and PCF 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)
## Contributing
This library was yoinked from my casual-pre-loader project. Contributions are welcome!
## Changelog
### 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
Raw data
{
"_id": null,
"home_page": null,
"name": "valve-parsers",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "valve, vpk, pcf, particle, steam, source engine, team fortress, tf2, half-life, parser, archive, modding",
"author": "cueki",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/52/a2/80bae478ebbc7697fde7b99aeb3d366c8266a841648ae7df1c82151a23c0/valve_parsers-1.0.7.tar.gz",
"platform": null,
"description": "# Valve Parsers\n\nA Python library for parsing Valve game files, extracted from my casual-preloader project. This library provides support for:\n\n- **VPK (Valve Package)** files - Valve's archive format used in Source engine games\n- **PCF (Particle)** files - Valve's particle system files - **See constants.py for supported versions**\n\n## Features\n\n- Support for single-file and multi-file VPK archives (creation and modification)\n- Full VPK directory parsing and file extraction\n- In-place VPK file patching with size checking\n- PCF parsing and encoding\n- Support for all PCF attribute types (see constants.py for these as well)\n\n## Installation\n\n```bash\npip install valve-parsers\n```\n\n## Quick Start (Parsing + Modification)\n\n### Creating VPK Archives\n\n```python\nfrom valve_parsers import VPKFile\n\n# Create a single-file VPK\nsuccess = VPKFile.create(\"source_directory\", \"output/archive.vpk\")\n\n# Create a multi-file VPK with size limit (100MB per archive split)\nsuccess = VPKFile.create(\"source_directory\", \"output/archive\", split_size=100*1024*1024)\n```\n\n### Working with VPK Archives\n\n```python\nfrom valve_parsers import VPKFile\n\n# Open a VPK file\nvpk = VPKFile(\"path/to/archive.vpk\")\n\n# List all files\nfiles = vpk.list_files()\nprint(f\"Found {len(files)} files\")\n\n# Find files matching a pattern\ntexture_files = vpk.list_files(extension=\"vtf\")\nmaterial_files = vpk.find_files(\"materials/*.vmt\")\n\n# Extract all files matching a pattern\ncount = vpk.extract_all(\"output_dir\", pattern=\"materials/*.vmt\")\nprint(f\"Extracted {count} material files\")\n\n# Extract a file\nvpk.extract_file(\"materials/models/player/scout.vmt\", \"output/scout.vmt\")\n\n# Or load it directly into memory\nfile_data = vpk.get_file_data(\"materials/models/player/scout.vmt\")\nif file_data:\n content = file_data.decode('utf-8')\n \n# Patch a file\n# Read a new material file from disk\nnew_texture_path = \"custom_scout_red.vmt\"\nwith open(new_texture_path, 'rb') as f:\n new_texture_data = f.read()\n\n# Target file path inside the VPK\ntarget_file = \"materials/models/player/scout_red.vmt\"\n\n# Check if a file exists\nif vpk.file_exists(target_file):\n print(\"File found!\")\n # Get file info\n info = vpk.get_file_info(target_file)\n if info:\n print(f\"Size: {info['size']} bytes, CRC: 0x{info['crc']:08X}\")\n # IMPORTANT: Patched files must match original size exactly!\n original_size = info['size']\n if len(new_texture_data) != original_size:\n if len(new_texture_data) < original_size:\n # Pad with spaces to match original size\n padding_needed = original_size - len(new_texture_data)\n print(f\"Adding {padding_needed} bytes of padding\")\n new_texture_data = new_texture_data + b' ' * padding_needed\n else:\n print(f\"ERROR: New file is {len(new_texture_data) - original_size} bytes larger!\")\n print(\"File cannot be patched - size must match exactly\")\n \n # Now patch the file\n vpk.patch_file(target_file, new_texture_data, create_backup=False)\n```\n\n### PCF Files\n\n```python\nfrom valve_parsers import PCFFile\n\n# Open and decode a PCF file\npcf = PCFFile(\"path/to/particles.pcf\").decode()\n\nprint(f\"PCF Version: {pcf.version}\")\nprint(f\"String dictionary: {len(pcf.string_dictionary)} entries\")\nprint(f\"Elements: {len(pcf.elements)} particle systems\")\n\n# Print particle system data\nfor element in pcf.elements:\n print(f\"Element: {element.element_name}\")\n for attr_name, (attr_type, attr_value) in element.attributes.items():\n print(f\" {attr_name.decode()}: {attr_value}\")\n \n# Rename all operators to ''\nfor i, element in enumerate(pcf.elements):\n type_name = pcf.string_dictionary[element.type_name_index].decode('ascii')\n if type_name == 'DmeParticleOperator':\n element.element_name = str('').encode('ascii')\n\n# Encode back to file\npcf.encode(\"output/modified_particles.pcf\")\n\n# Find a specific element by name\nelement = pcf.find_element_by_name(\"my_explosion_effect\")\nif element:\n print(f\"Found element with {len(element.attributes)} attributes\")\n\n# Get all elements of a specific type\noperators = pcf.get_elements_by_type('DmeParticleOperator')\nprint(f\"Found {len(operators)} operators\")\n\n# Get and set attribute values with helper methods\nfrom valve_parsers import AttributeType\n\nelement = pcf.find_element_by_name(\"my_effect\")\nif element:\n # Get attribute value\n radius = pcf.get_attribute_value(element, \"radius\", default=5.0)\n print(f\"Current radius: {radius}\")\n\n # Set attribute value\n pcf.set_attribute_value(element, \"radius\", 10.0, AttributeType.FLOAT)\n pcf.set_attribute_value(element, \"color\", (255, 0, 0, 255), AttributeType.COLOR)\n\npcf.encode(\"output/modified_particles.pcf\")\n```\n\n## API Reference\n\n### VPKFile\n\nThe main class for working with VPK archives.\n\n#### Constructor\n- `VPKFile(vpk_path: Union[str, Path], auto_parse: bool = True)` - Initialize with path to VPK file\n - `vpk_path`: Path to the VPK file\n - `auto_parse`: Automatically parse directory on init (default: True)\n\n#### Methods\n- `parse_directory() -> VPKFile` - Parse the VPK directory structure (called automatically unless `auto_parse=False`)\n- `list_files(extension: str = None, path: str = None) -> List[str]` - List files with optional filtering\n- `find_files(pattern: str) -> List[str]` - Find files matching a glob pattern\n- `find_file_path(filename: str) -> Optional[str]` - Find the full path of a filename\n- `extract_file(filepath: str, output_path: str) -> bool` - Extract a file from the archive\n- `extract_all(output_dir: str, pattern: str = None) -> int` - Extract multiple files to a directory\n- `patch_file(filepath: str, new_data: bytes, create_backup: bool = False) -> bool` - Modify a file in the archive\n- `get_file_data(filepath: str) -> Optional[bytes]` - Read a file's contents directly into memory\n- `file_exists(filepath: str) -> bool` - Check if a file exists in the archive\n- `get_file_info(filepath: str) -> Optional[Dict]` - Get comprehensive information about a file\n- `create(source_dir: str, output_base_path: str, split_size: int = None) -> bool` - Create new VPK archive (class method)\n\n#### Properties\n- `directory` - Parsed directory structure\n- `is_dir_vpk` - Whether this is a directory VPK file\n- `vpk_path` - Path to the VPK file\n\n### VPKDirectoryEntry\n\nRepresents an entry in the VPK directory.\n\n#### Properties\n- `crc: int` - CRC32 checksum\n- `preload_bytes: int` - Number of preload bytes\n- `archive_index: int` - Archive file index\n- `entry_offset: int` - Offset within archive\n- `entry_length: int` - Length of file data\n- `preload_data: Optional[bytes]` - Preloaded data\n\n### PCFFile\n\nThe main class for working with PCF particle files.\n\n#### Constructor\n- `PCFFile(input_file: Union[Path, str], version: str = \"DMX_BINARY2_PCF1\")` - Initialize with file path, default version is \"DMX_BINARY2_PCF1\"\n\n#### Methods\n- `decode() -> PCFFile` - Parse the PCF file\n- `encode(output_path: Union[Path, str]) -> PCFFile` - Write PCF file to disk\n- `find_element_by_name(name: str) -> Optional[PCFElement]` - Find a particle element by its name\n- `get_elements_by_type(type_name: str) -> List[PCFElement]` - Get all elements of a specific type\n- `get_attribute_value(element: PCFElement, attr_name: str, default=None)` - Get an attribute value from an element (static method)\n- `set_attribute_value(element: PCFElement, attr_name: str, value, attr_type: AttributeType)` - Set an attribute value on an element\n\n#### Properties\n- `version` - PCF version string\n- `string_dictionary` - List of strings used in the file\n- `elements` - List of particle system elements\n\n### PCFElement\n\nRepresents a particle system element.\n\n#### Properties\n- `type_name_index: int` - Index into string dictionary for type name\n- `element_name: bytes` - Name of the element\n- `data_signature: bytes` - 16-byte signature\n- `attributes: Dict[bytes, Tuple[AttributeType, Any]]` - Element attributes\n\n### Constants\n\n- `PCFVersion` - Enum of supported PCF versions\n- `AttributeType` - Enum of PCF attribute types\n\n## Supported Games\n\nThis library works with VPK and PCF files from Orange Box titles. \nMostly intended for TF2, YMMV with other games.\n\nSee: \n\nhttps://developer.valvesoftware.com/wiki/PCF \n\nhttps://developer.valvesoftware.com/wiki/VPK_(file_format)\n\n## Contributing\n\nThis library was yoinked from my casual-pre-loader project. Contributions are welcome!\n\n## Changelog\n### 1.0.7\n- Performance: Replaced rglob with os.walk for VPK creation\n- Performance: Replaced Path.match with fnmatch in find_files()\n- Bug fix: find_files() now uses correct glob matching behavior\n### 1.0.6\n- Updated README with corrected examples\n- Added documentation for all VPK methods: extract_all(), get_file_data(), file_exists(), get_file_info()\n- Added file patching size-checking examples\n- Made get_file_entry() private\n- Cleaned up internal method documentation\n### 1.0.5\n- Auto-parse support\n- Reading and writing is now 2-3x faster\n### 1.0.2\n- Single file VPK no longer has _dir name\n### 1.0.1\n- Nothing\n### 1.0.0\n- Initial release\n- VPK parsing and creation support\n- PCF parsing and encoding support\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python library for parsing Valve game files (VPK and PCF)",
"version": "1.0.7",
"project_urls": {
"Homepage": "https://github.com/cueki/valve-parsers",
"Issues": "https://github.com/cueki/valve-parsers/issues",
"Repository": "https://github.com/cueki/valve-parsers"
},
"split_keywords": [
"valve",
" vpk",
" pcf",
" particle",
" steam",
" source engine",
" team fortress",
" tf2",
" half-life",
" parser",
" archive",
" modding"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "a2240f82f70bd3aa5756ed87dced3266b18e98be269375255a3d1d9d7d56eba7",
"md5": "5f2750d3dae088a88ddea195b9e325b9",
"sha256": "5795999a28c899f8ed48e7c19e2b56977dc1e32e331b327fdf537ff010436e6b"
},
"downloads": -1,
"filename": "valve_parsers-1.0.7-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5f2750d3dae088a88ddea195b9e325b9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 17978,
"upload_time": "2025-10-19T10:59:09",
"upload_time_iso_8601": "2025-10-19T10:59:09.899515Z",
"url": "https://files.pythonhosted.org/packages/a2/24/0f82f70bd3aa5756ed87dced3266b18e98be269375255a3d1d9d7d56eba7/valve_parsers-1.0.7-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "52a280bae478ebbc7697fde7b99aeb3d366c8266a841648ae7df1c82151a23c0",
"md5": "b72f2096b34fe94e161f153cd71f5c9b",
"sha256": "ff7855d7e9e216ced8ae923ec6d4db37965cffcee382bc8c59beabb86f879e6a"
},
"downloads": -1,
"filename": "valve_parsers-1.0.7.tar.gz",
"has_sig": false,
"md5_digest": "b72f2096b34fe94e161f153cd71f5c9b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 19429,
"upload_time": "2025-10-19T10:59:11",
"upload_time_iso_8601": "2025-10-19T10:59:11.063869Z",
"url": "https://files.pythonhosted.org/packages/52/a2/80bae478ebbc7697fde7b99aeb3d366c8266a841648ae7df1c82151a23c0/valve_parsers-1.0.7.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-19 10:59:11",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "cueki",
"github_project": "valve-parsers",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "valve-parsers"
}