Vpk.exe: Difference between revisions
Tpd1864blake (talk | contribs) m (→Control File creation: removed unused variable) |
|||
(94 intermediate revisions by 39 users not shown) | |||
Line 1: | Line 1: | ||
'''VPK''' ( | {{LanguageBar}} | ||
{{Todo|Create {{code|vpk.exe}} page for {{src2|1}} games.}} | |||
'''VPK''' (Valve Pak) files are uncompressed archives used to package game content, first introduced in {{l4d|3.1}} (with all subsequent games (post-L4D) also using it). VPKs are later backported to previous version of the engine ({{tobbranch|1}}), creating {{src13|2}} (also known as '''SteamPipe branch'''), replacing [[GCF]] archives. Valve's post GCF games store [[VMT|material]]s, [[MDL|model]]s, [[particles]], [[VCD|choreography scenes]] and many other file types in VPK files. Some third-party games (even using an engine as old as {{src04|3.1}}) can load depot VPK files as long as Steam is running. Today, all modern {{src|4}} games and all {{src2|4}} games used VPKs. | |||
VPKs can be created with the [[command line]] tool {{code|vpk.exe}}. | |||
VPKs | Executable and some archive files are discarded by {{code|vpk.exe}}: [[.zip]], [[.reg]], [[.rar]], [[.msi]], [[.exe]], [[.dll]], [[.com]], [[.cmd]] and [[.bat]]. | ||
VPKs are also used to distribute mods via the the [[addoninstaller]] tool that ships with some games, such as {{l4d2|4}}. | |||
{{confusion|{{vtmb|2}} uses a different package format with the same file extension, called [[VPK (file format)/Vampire: The Masquerade - Bloodlines|Vampire PacK]] files, and relies on different tool ({{vtfedit|1}} or VPK Creator on [[VTMB_Unofficial_Patch_Setup|Bloodlines SDK]]) to create Vampire's VPK files.}} | |||
{|class="standard-table" | == Creation == | ||
! Game || Path to VPK.exe | The tool can be located in the bin folders for most Source games, such as the ones below. The tool is not game dependent, however it is suggested that you use the tool that corresponds to the game you are creating the vpk for. The version in one game may not be as up to date as the version in another due to game updates. | ||
{|class="standard-table" style="background-color:black;" | |||
! Game || Path to VPK.exe || Version | |||
|- | |- | ||
| {{as}} | | {{as|4}} || ..\steamapps\common\Alien Swarm\bin || v1 | ||
|- | |||
| {{css|4}} || ..\steamapps\common\Counter-Strike Source\bin || v2 | |||
|- | |||
| {{csgo|4}} || ..\steamapps\common\Counter-Strike Global Offensive\bin || v2 | |||
|- | |- | ||
| {{ | | {{hl2|4}} (and Episodes)</br>{{hls|4}} || ..\steamapps\common\Half-Life 2\bin || v2 | ||
|- | |- | ||
| {{l4d}} | | {{l4d|4}} || ..\steamapps\common\Left 4 Dead\bin || v1 | ||
|- | |- | ||
| {{ | | {{l4d2|4}} || ..\steamapps\common\Left 4 Dead 2\bin || v1 | ||
|- | |||
| {{portal|4}} || ..\steamapps\common\Portal\bin || v2 | |||
|- | |||
| {{portal2|4}} || ..\steamapps\common\Portal 2\bin || v1 | |||
|- | |||
| {{src13|4}} || ..\steamapps\common\Source SDK Base 2013\<Singleplayer or Multiplayer>\bin || v2 | |||
|- | |||
| {{tf2|4}} || ..\steamapps\common\Team Fortress 2\bin || v2 | |||
|} | |} | ||
{{bug|hidetested=1|For some reason sometimes when vpk.exe is used to extract VPKs it gives empty folders, {{portal|4}}'s vpk.exe | |||
(..\steamapps\common\Portal\bin) seems to extract the files fine | |||
(there might be a corruption when used since it is a diffrent version). | |||
{{workaround|Third party extractors like {{vpkedit|1}} or [[GCFScape]] can extract every .VPK file with no errors}}}} | |||
For servers installed using [[SteamCMD]], it is located in the server's bin\ directory. On Linux, it is named vpk_linux32 instead of vpk.exe. | |||
== Linux | === Linux / Unix === | ||
Note: The vpk binary makes VPK files of version 2.0, which are '''not''' compatible with games like {{l4d2|3}} that use an older version of the VPK format (in fact {{l4d2|3.1}} will not even start if it detects an unsupported vpk). There is a Python package that can be installed with <syntaxhighlight lang="bash">pip install vpk</syntaxhighlight> which supports the older format. It is also far more convenient than using the built-in tool. Instructions can be found on the [https://github.com/ValvePython/vpk Github page]. | |||
On Linux / Unix clients, the vpk file can be found replacing "<code>..\steamapps\common</code>" for "<code>~/.steam/steam/steamapps/common/</code>". However, it is named <code>vpk_linux32</code> instead of <code>vpk.exe</code>. '''Note:''' If you installed Steam via Flatpak, the path is slightly different. Look in <code>~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common</code> | |||
If you are running on a 64-bit system, this binary will not work by default. You must tell it to use the 32-bit libraries located in the same directory. This can be done by setting the <code>LD_LIBRARY_PATH</code> variable.<br> | |||
The following script, if created in the above listed bin directories, will create a wrapper to properly launch the 32-bit executable; then a link (or desktop shortcut) to <code>vpk.sh</code> (if saved as said name) can be created, from there: | |||
<syntaxhighlight lang="bash"> | |||
#!/bin/bash | |||
DIR=$(dirname "${BASH_SOURCE[0]}") | |||
export LD_LIBRARY_PATH=$(cd "$DIR" && pwd) | |||
exec "$DIR/vpk_linux32" "${@}" | |||
</syntaxhighlight> | |||
Alternatively, if you're running 64-bit Linux, you can use this script to execute the <code>vpk_linux32</code> binary successfully. Save this script to "<code>/usr/local/bin/vpk</code>" and set it as an executable in the file's properties, for ease of use: | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
#!/bin/bash | #!/bin/bash | ||
VPK_LINUX=$(find "${HOME}/.local/share/Steam" -type f -iname "vpk_linux32" -print | head -n 1) | |||
VALVE_LIB_DIR=$(dirname "${VPK_LINUX}") | |||
LD_LIBRARY_PATH="${VALVE_LIB_DIR}:${LD_LIBRARY_PATH}" "${VPK_LINUX}" "${@}" | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | '''HELPFUL NOTE:''' The "<code>${@}</code>" is for drag-and-drop with *.desktop files and arguments when using the <code>vpk</code> command via console. | ||
=== Windows === | |||
On Windows, you can easily drag-and-drop folders onto <code>vpk.exe</code> file in the <code>bin</code> folder and get a *.vpk file in return, and vice-versa by drag-and-dropping a *.vpk file instead. | |||
{{tip|Some users may find it easier to create a shortcut (*.lnk) linked to <code>vpk.exe</code> as it can be placed anywhere, like a folder that you often are packaging files from, making packaging folders into VPKs and vice-versa quicker and easier than having to return to the <code>bin</code> folder to do so. A good example of this is the <code>custom</code> folder, where placing a shortcut there could make debugging of custom vpk files much quicker.}} | |||
== Commands == | |||
==== Usage ==== | |||
::<code>vpk [options] <directory></code> | ::<code>vpk [options] <directory></code> | ||
::<code>vpk [options] <vpkfile></code> | ::<code>vpk [options] <vpkfile></code> | ||
==Create VPK/Add Files== | ::<code>vpk [options] <command> <command arguments...></code> | ||
; <code>vpk < | |||
: | ==== Example ==== | ||
::<code>vpk -?</code> | |||
:; Lists all of the help info, list of available arguments, and info about each argument. | |||
=== Create VPK/Add Files === | |||
; <code>vpk <directory></code> | |||
: Create VPK from directory structure. Must be an existing location. The VPK will appear next to the directory. {{note|This is invoked when a directory is dragged onto the VPK tool.}} | |||
; <code>vpk a <vpkfile> <filename1> <filename2> ...</code> | ; <code>vpk a <vpkfile> <filename1> <filename2> ...</code> | ||
: Add file(s). | : Add file(s). | ||
; <code>a <vpkfile> @<filename></code> | ; <code>vpk a <vpkfile> @<filename></code> | ||
: Adds the files referenced in a "response file" (''not'' response rules). Note the <code>@</code> symbol. | : Adds the files referenced in a "response file" (''not'' response rules). Note the <code>@</code> symbol. | ||
; <code>k vpkfile <keyvalues_filename></code> | : The added file will not be added with its file path if the default file path matches that of CMD | ||
: Add files references in a [[keyvalues]] control file. {{bug|They will appear inside the VPK with their full path (<code>C:\etc\</code>) intact - is there a way to avoid this?}} | ; <code>vpk k <vpkfile> <keyvalues_filename></code> | ||
: Add files references in a [[keyvalues]] control file. {{bug|hidetested=1|They will appear inside the VPK with their full path (<code>C:\etc\</code>) intact - is there a way to avoid this?}} | |||
==Extract Files== | === Extract Files === | ||
; <code>vpk <vpkfile></code> | ; <code>vpk <vpkfile></code> | ||
: Extract all files from VPK. {{note|This is invoked when a .VPK file is dragged onto the VPK tool.}} | : Extract all files from VPK. {{note|This is invoked when a .VPK file is dragged onto the VPK tool.}} | ||
==Display VPK Info== | ; <code>vpk x <vpkfile> <filename1> <filename2> ...</code> | ||
: Extract file(s). {{bug|hidetested=1|Selective file extraction doesn't seem to work}} | |||
=== Display VPK Info === | |||
; <code>vpk l <vpkfile></code> | ; <code>vpk l <vpkfile></code> | ||
: List contents of VPK. | : List contents of VPK. | ||
; <code>vpk L <vpkfile></code> | ; <code>vpk L <vpkfile></code> | ||
: List ''Detailed'' contents of VPK. | : List ''Detailed'' contents of VPK. | ||
==VPK Integrity/Security== | === VPK Integrity/Security === | ||
; <code>vpk checksig <vpkfile></code> | ; <code>vpk checksig <vpkfile></code> | ||
: Verify signature of specified VPK file. Requires -k to specify key file to use. | : Verify signature of specified VPK file. Requires -k to specify key file to use. | ||
==Misc== | == Misc. == | ||
; <code>vpk generate_keypair <keybasename></code> | ; <code>vpk generate_keypair <keybasename></code> | ||
: Generate public/private key file. Output files will be named ''< | : Generate public/private key file. Output files will be named ''<keybasename>.publickey.vdf'' and ''<keybasename>.privatekey.vdf'' {{note|Remember: your private key should be kept private.}} | ||
==Options== | |||
=== Options === | |||
; {{tip|Please note the case of these options. A capital letter is different than a lowercase letter.}} | ; {{tip|Please note the case of these options. A capital letter is different than a lowercase letter.}} | ||
; <code>-v</code> | ; <code>-v</code> | ||
Line 75: | Line 125: | ||
: Produce a multi-chunk VPK. {{note|Required if creating a VPK with key values.}} | : Produce a multi-chunk VPK. {{note|Required if creating a VPK with key values.}} | ||
:* Each chunk is a file limited to around 200MB. | :* Each chunk is a file limited to around 200MB. | ||
:* To reduce patch sizes, chunks are never overwritten. New/modified files are instead written to a brand new chunk every time you run the tool. {{note|Multi-chunk generations only works when creating a VPK from a response file.}}{{tip|To inspect a multi-chunk VPK open the '_dir' file.}} | :* To reduce patch sizes, chunks are never overwritten. New/modified files are instead written to a brand new chunk every time you run the tool. {{note|{{l4d}}{{l4d2}} Multi-chunk generations only works when creating a VPK from a response file. This is not required on {{src13|2}}.}}{{tip|To inspect a multi-chunk VPK open the '_dir' file.}} | ||
; <code>-P</code> | ; <code>-P</code> | ||
: Use SteamPipe-friendly incremental build algorithm. Use with 'k' command. For optimal incremental build performance, the control file used for the previous build should exist and be named the same as theinput control file, with '.bak' appended, and each file entryshould have an 'md5' value. The 'md5' field need not be theactual MD5 of the file contents, it is just a unique identifierthat will be compared to determine if the file contents has changedbetween builds. | : Use SteamPipe-friendly incremental build algorithm. Use with 'k' command. For optimal incremental build performance, the control file used for the previous build should exist and be named the same as theinput control file, with '.bak' appended, and each file entryshould have an 'md5' value. The 'md5' field need not be theactual MD5 of the file contents, it is just a unique identifierthat will be compared to determine if the file contents has changedbetween builds. | ||
Line 88: | Line 138: | ||
: With command 'checksig': Check signature using specified key file. | : With command 'checksig': Check signature using specified key file. | ||
= Examples = | == Examples == | ||
Listed below are some examples of using the tool and what they will do. | Listed below are some examples of using the tool and what they will do. | ||
==Creating A Key Value File and VPK== | ===Creating your own VPKs for a mod=== | ||
:1. Create a folder with the correct directory structure and files that you wish to use such as <code> | |||
Your mod's assets must be packaged within the appropriate content folders ("sound," "materials," "models," etc.) in the root directory of your VPK. Otherwise you won't be able to see or use your assets in Hammer! | |||
For a specific example, imagine you're packing the content for your mod called "MyMod," and want to include a sound .wav named "ocean_ambient.wav". In your MyMod folder to be packed, you would probably want to place ocean_ambient.wav under "MyMod/sound/", or better, in a specific subdirectory of that one, like "MyMod/sound/ambient/". The same principle is easily applied to other assets like compiled model files, or materials and textures. These would be best placed in subdirectories of "MyMod/models" and "MyMod/materials", respectively. | |||
VPKs must be mounted in gameinfo.txt. | |||
====Creating A Key Value File and VPK==== | |||
:1. Create a folder with the correct directory structure and files that you wish to use such as <code>MyMod/resource/ui/<file.res></code> | |||
:2. Use the command line in a prompt or a bat: | :2. Use the command line in a prompt or a bat: | ||
::<code>vpk generate_keypair <name></code> | ::<code>vpk generate_keypair <name></code> | ||
::<code>vpk -M -k <name>.publickey.vdf -K <name>.privatekey.vdf " | ::<code>vpk -M -k <name>.publickey.vdf -K <name>.privatekey.vdf "..\SteamApps\common\Team Fortress 2\bin\MyMod"</code> | ||
:3. In the folder where the | :3. In the folder where the VPK tool is located there will now be a public key VDF, a private key VDF, a VPK named mymod_000 and a VPK named mymod_dir. | ||
::{{Warning|Never disclose or share your private key | ::{{Warning|Never disclose or share your private key VDF or the key. Only share the public key.}} | ||
::* You must distribute your mod with both the mymod_dir and mymod_000 | ::* You must distribute your mod with both the mymod_dir and mymod_000 VPKs for the keyvalue to work. | ||
= Response File = | ==== Response File ==== | ||
A "response file" contains a list of files to be added to a VPK. Paths are relative to the current directory of the <code>vpk</code> tool. | A "response file" contains a list of files to be added to a VPK. Paths are relative to the current directory of the <code>vpk</code> tool. | ||
Line 108: | Line 165: | ||
<source lang="python"> | <source lang="python"> | ||
# | #What folders to look for, and pack into the pak01 vpk set. | ||
target_folders = [ "materials", "models", "particles", "scenes" ] | target_folders = [ "materials", "models", "resource", "media", "particles", "scripts", "maps", "expressions", "scenes" ] | ||
file_types = [ "vmt", "vtf", "mdl", "phy", "vtx", "vvd", "pcf", "vcd" ] | #What files to look for, in the aforementioned folders. | ||
vpk_path = " | file_types = [ "vmt", "vtf", "mdl", "phy", "vtx", "vvd", "ani", "pcf", "vcd", "txt", "res", "vfont", "cur", "dat" , "bik", "mov", "bsp", "nav", "lst", "lmp", "vfe" ] | ||
#which vpk.exe to use. do not use \! | |||
vpk_path = "../steamapps/common/SourceFilmmaker/game/bin/vpk.exe" | |||
# Script begins | # Script begins | ||
Line 129: | Line 188: | ||
out.close() | out.close() | ||
#the "pak01" here specifies the multi-chunk vpk names. Could be changed to "pak02" or "hl2_textures", or whatever your mod needs. | |||
subprocess.call([vpk_path, "-M", "a", "pak01", "@" + response_path]) | subprocess.call([vpk_path, "-M", "a", "pak01", "@" + response_path]) | ||
</source> | |||
To handle this process for {{portal2|4}} you can also use the [[P2 Multichunk Tool]], to automate the response file creation and the creation of the vpk files. | |||
::{{Warning|[[P2 Multichunk Tool]] will ''probably'' work for other games aside {{portal2|3}} if the .vpk path is changed accordingly, it's functions have been expanded to make it easier to experiment, but since it haven't tested outside {{portal2|3}} there is no guarantee it will work with other games.}} | |||
==== Control File creation ==== | |||
This process is more complicated and advanced than other uses. Creating a Control file allows vpk.exe to differentiate new or edited files from unaffected files, and only writes the changes to a mod's VPK archive. When a mod's VPK files generated with this script are uploaded to the Steam Workshop, users will only need to download the affected files instead of downloading the entire mod again. | |||
Below is a Python script which generates a control file for each category of assets a mod contains (materials, models, etc.). Then, it creates multi-chunk VPKs for each category which is signed using a public/private keypair and utilizes the SteamPipe incremental build algorithm. The public key should be distributed separately from the uploaded mod files. | |||
{{note|This script relies on the vdf module. Download it from [https://github.com/ValvePython/vdf here].}} | |||
<source lang="python"> | |||
import os,sys,subprocess,fnmatch,shutil,vdf,hashlib | |||
from os.path import join,exists | |||
# Point this to your vpk.exe location for the game your mod uses | |||
vpk_path = "C:/Program Files (x86)/Steam/steamapps/common/Black Mesa/bin/vpk.exe" | |||
# Point this to another folder to copy the VPK files here. This makes it convenient to upload changes to the Steam Workshop | |||
# Use Steam Workshop Uploader or Crowbar to upload mods to the Workshop | |||
# Keep empty to disable VPK file copying | |||
upload_path = "" | |||
# Your mod's name. This is used for VPK filenames and for public/private keypair | |||
# If you already have public/private keypair generated, place it in your mod folder. Otherwise this will generate its own | |||
# Can contain spaces. Do not include any special symbols: \/:*?[]<>{} | |||
mod_name = "MyModNameHere" | |||
# By default, the mod location is the current working directory. Change this if needed. | |||
input_path = os.getcwd() | |||
os.chdir(input_path) | |||
len_cd = len(input_path) + 1 | |||
# Define the VPK folder structure. Change this to fit your needs. | |||
folder_info = { | |||
"maps": { | |||
"folders": [ "maps" ], | |||
"files": [ "bsp", "nav", "ain", "txt" ] | |||
}, | |||
"materials": { | |||
"folders": [ "materials", "ui" ], | |||
"files": [ "vtf", "vmt", "png" ] | |||
}, | |||
"models": { | |||
"folders": [ "models" ], | |||
"files": [ "mdl", "phy", "vtx", "vvd", "ani" ] | |||
}, | |||
"sounds": { | |||
"folders": [ "sound" ], | |||
"files": [ "wav", "mp3", "ogg" ] | |||
}, | |||
"misc": { | |||
"folders": [ "campaigns", "cfg", "particles", "resource", "scenes", "scripts", "ui" ], | |||
"files": [ "json", "cfg", "pcf", "txt", "dat", "vcd", "dmx", "qml" ] | |||
} | |||
} | |||
if not mod_name: | |||
print("WARNING: mod_name variable is empty. Your mod cannot be unnamed. Aborting.") | |||
sys.exit(1) | |||
vpk_data = vdf.VDFDict() | |||
for key, value in folder_info.items(): | |||
vpk_data[key] = {} | |||
def md5hash(file_path): | |||
"""Generate an MD5 hash for the specified file""" | |||
hash_md5 = hashlib.md5() | |||
with open(file_path, "rb") as f: | |||
for chunk in iter(lambda: f.read(4096), b""): | |||
hash_md5.update(chunk) | |||
return hash_md5.hexdigest() | |||
# Generate control files | |||
for folder_type in folder_info: | |||
print(f'Indexing {folder_type} folder') | |||
for user_folder in folder_info[folder_type]["folders"]: | |||
for root, dirs, files in os.walk(join(input_path, user_folder)): | |||
for file in files: | |||
if file.rsplit(".")[-1] in folder_info[folder_type]["files"]: | |||
vpk_data[folder_type][join(root, file)[len_cd:].replace("/", "\\")] = { | |||
"destpath": join(root, file)[len_cd:].replace("/", "\\"), | |||
"md5": md5hash(join(root, file)) | |||
} | |||
new_controlfile = join(input_path, folder_type + "_control.vdf") | |||
if exists(new_controlfile): | |||
if exists(new_controlfile + ".bak"): | |||
try: | |||
os.remove(new_controlfile + ".bak") | |||
except Exception as e: | |||
print(f"Could not delete file: {e}") | |||
os.rename(new_controlfile, new_controlfile + ".bak") | |||
with open(new_controlfile, 'w') as f: | |||
f.write(vdf.dumps(vpk_data[folder_type], pretty=True)) | |||
with open(new_controlfile, 'r') as f: | |||
new_content = f.read().replace('\\\\', '\\') | |||
with open(new_controlfile, 'w') as f: | |||
f.write(new_content) | |||
# Pack the VPK files | |||
for folder_type in folder_info: | |||
print(f'Packing {folder_type} folder') | |||
if not exists(join(input_path, mod_name + ".privatekey.vdf")) and not exists(join(input_path, mod_name + ".publickey.vdf")): | |||
print("Public/Private keypair not detected. Generating a new one.") | |||
subprocess.call([ | |||
vpk_path, | |||
"generate_keypair", | |||
mod_name | |||
]) | |||
if not exists(join(input_path, mod_name + ".privatekey.vdf")): | |||
print("WARNING: Public key exists but private key not found. Aborting.") | |||
sys.exit(1) | |||
if not exists(join(input_path, mod_name + ".publickey.vdf")): | |||
print("WARNING: Private key exists but public key not found. Aborting.") | |||
sys.exit(1) | |||
subprocess.call([ | |||
vpk_path, | |||
"-M", | |||
"-K", mod_name + ".privatekey.vdf", | |||
"-k", mod_name + ".publickey.vdf", | |||
"-P", | |||
"k", | |||
mod_name + "_" + folder_type, | |||
join(input_path, folder_type + "_control.vdf") | |||
]) | |||
# Rename the VPK files | |||
for filename in os.listdir(input_path): | |||
if fnmatch.fnmatch(filename, mod_name.lower() + "*.vpk"): | |||
new_filename = mod_name + filename[len(mod_name):] | |||
os.rename(join(input_path, filename), join(input_path, new_filename)) | |||
if upload_path: | |||
# Delete destination VPK files if they exist. | |||
for filename in os.listdir(upload_path): | |||
if fnmatch.fnmatch(filename.lower(), mod_name.lower() + "*.vpk"): | |||
try: | |||
os.remove(join(upload_path, filename)) | |||
except Exception as e: | |||
print(f"Could not delete file: {e}") | |||
# Copy the VPK files | |||
for filename in os.listdir(input_path): | |||
if fnmatch.fnmatch(filename, mod_name + "*.vpk"): | |||
try: | |||
shutil.copy(join(input_path, filename), upload_path) | |||
except Exception as e: | |||
print(f"Could not copy file: {e}") | |||
# Copyright © 2025 tpd1864blake | |||
# This work has been marked as dedicated to the public domain. | |||
# See https://creativecommons.org/public-domain/cc0/ | |||
</source> | </source> | ||
=== Excluded files === | === Excluded files === | ||
Executable and archive files are discarded by the VPK tool: | Executable and some archive files are discarded by the VPK tool: | ||
<pre> | |||
.zip .reg .rar .msi .exe .dll .com .cmd .bat | |||
</pre> | |||
{{warning|Other VPK files are not excluded from compilation, and will be compiled into the new VPK if present in the search paths!}} | |||
= See also = | |||
= See | |||
* [[VPK File Format]] (technical details) | * [[VPK File Format]] (technical details) | ||
Line 144: | Line 358: | ||
* [[L4D2 Campaign Add-on Tutorial]] | * [[L4D2 Campaign Add-on Tutorial]] | ||
* [[Releasing A Portal 2 Map]] | * [[Releasing A Portal 2 Map]] | ||
* [[P2 Multichunk Tool]] (Easy handling of mass vpk packing) | |||
* [[Swarm Campaign Add-on Tutorial]] | * [[Swarm Campaign Add-on Tutorial]] | ||
* [https://github.com/geotavros/VpkCompare/releases/ VPK Compare GUI Tool] for finding conflicts between VPK files | |||
* [https://web.archive.org/web/20160628021955/https://facepunch.com/showthread.php?t=1327883 Source Multi-Tool] (VPK Creator - GUI) | |||
* [[Fixing VPK mounting for older Source SDK Bases]] (for {{src06|1.bold}} & {{src07|1.bold}}) | |||
=== Software === | |||
* {{vPKEdit|2}} - Program which can create and modify VPK files (supports both VPK v1 and v2, aswell as {{vtmb|1}} VPK format) | |||
* {{gcfscape|2}} - Program which modify VPK files, since GCFScape 1.7.1 (supports both VPK v1 and v2, no longer updated) | |||
[[Category:Modding]] | [[Category:Modding]] | ||
[[Category:Steam]] <!-- Like the page about GCF itself, Steam not only use it, but also requiring it if you want to play Source 2004, 2006, 2007 and 2009 games without the loose branch, or without unpacking depot VPK files. --> | |||
[[category:File formats]] |
Latest revision as of 13:52, 30 July 2025


VPK (Valve Pak) files are uncompressed archives used to package game content, first introduced in Left 4 Dead (with all subsequent games (post-L4D) also using it). VPKs are later backported to previous version of the engine (Orange Box branch), creating Source 2013 (also known as SteamPipe branch), replacing GCF archives. Valve's post GCF games store materials, models, particles, choreography scenes and many other file types in VPK files. Some third-party games (even using an engine as old as Source 2004) can load depot VPK files as long as Steam is running. Today, all modern
Source games and all
Source 2 games used VPKs.
VPKs can be created with the command line tool vpk.exe.
Executable and some archive files are discarded by vpk.exe: .zip, .reg, .rar, .msi, .exe, .dll, .com, .cmd and .bat.
VPKs are also used to distribute mods via the the addoninstaller tool that ships with some games, such as Left 4 Dead 2.


Creation
The tool can be located in the bin folders for most Source games, such as the ones below. The tool is not game dependent, however it is suggested that you use the tool that corresponds to the game you are creating the vpk for. The version in one game may not be as up to date as the version in another due to game updates.
Game | Path to VPK.exe | Version |
---|---|---|
![]() |
..\steamapps\common\Alien Swarm\bin | v1 |
![]() |
..\steamapps\common\Counter-Strike Source\bin | v2 |
![]() |
..\steamapps\common\Counter-Strike Global Offensive\bin | v2 |
![]() ![]() |
..\steamapps\common\Half-Life 2\bin | v2 |
![]() |
..\steamapps\common\Left 4 Dead\bin | v1 |
![]() |
..\steamapps\common\Left 4 Dead 2\bin | v1 |
![]() |
..\steamapps\common\Portal\bin | v2 |
![]() |
..\steamapps\common\Portal 2\bin | v1 |
![]() |
..\steamapps\common\Source SDK Base 2013\<Singleplayer or Multiplayer>\bin | v2 |
![]() |
..\steamapps\common\Team Fortress 2\bin | v2 |


(..\steamapps\common\Portal\bin) seems to extract the files fine (there might be a corruption when used since it is a diffrent version).
For servers installed using SteamCMD, it is located in the server's bin\ directory. On Linux, it is named vpk_linux32 instead of vpk.exe.
Linux / Unix
Note: The vpk binary makes VPK files of version 2.0, which are not compatible with games like Left 4 Dead 2 that use an older version of the VPK format (in fact Left 4 Dead 2 will not even start if it detects an unsupported vpk). There is a Python package that can be installed with
pip install vpk
which supports the older format. It is also far more convenient than using the built-in tool. Instructions can be found on the Github page.
On Linux / Unix clients, the vpk file can be found replacing "..\steamapps\common
" for "~/.steam/steam/steamapps/common/
". However, it is named vpk_linux32
instead of vpk.exe
. Note: If you installed Steam via Flatpak, the path is slightly different. Look in ~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common
If you are running on a 64-bit system, this binary will not work by default. You must tell it to use the 32-bit libraries located in the same directory. This can be done by setting the LD_LIBRARY_PATH
variable.
The following script, if created in the above listed bin directories, will create a wrapper to properly launch the 32-bit executable; then a link (or desktop shortcut) to vpk.sh
(if saved as said name) can be created, from there:
#!/bin/bash
DIR=$(dirname "${BASH_SOURCE[0]}")
export LD_LIBRARY_PATH=$(cd "$DIR" && pwd)
exec "$DIR/vpk_linux32" "${@}"
Alternatively, if you're running 64-bit Linux, you can use this script to execute the vpk_linux32
binary successfully. Save this script to "/usr/local/bin/vpk
" and set it as an executable in the file's properties, for ease of use:
#!/bin/bash
VPK_LINUX=$(find "${HOME}/.local/share/Steam" -type f -iname "vpk_linux32" -print | head -n 1)
VALVE_LIB_DIR=$(dirname "${VPK_LINUX}")
LD_LIBRARY_PATH="${VALVE_LIB_DIR}:${LD_LIBRARY_PATH}" "${VPK_LINUX}" "${@}"
HELPFUL NOTE: The "${@}
" is for drag-and-drop with *.desktop files and arguments when using the vpk
command via console.
Windows
On Windows, you can easily drag-and-drop folders onto vpk.exe
file in the bin
folder and get a *.vpk file in return, and vice-versa by drag-and-dropping a *.vpk file instead.

vpk.exe
as it can be placed anywhere, like a folder that you often are packaging files from, making packaging folders into VPKs and vice-versa quicker and easier than having to return to the bin
folder to do so. A good example of this is the custom
folder, where placing a shortcut there could make debugging of custom vpk files much quicker.Commands
Usage
vpk [options] <directory>
vpk [options] <vpkfile>
vpk [options] <command> <command arguments...>
Example
vpk -?
- Lists all of the help info, list of available arguments, and info about each argument.
Create VPK/Add Files
vpk <directory>
- Create VPK from directory structure. Must be an existing location. The VPK will appear next to the directory.
Note:This is invoked when a directory is dragged onto the VPK tool.
vpk a <vpkfile> <filename1> <filename2> ...
- Add file(s).
vpk a <vpkfile> @<filename>
- Adds the files referenced in a "response file" (not response rules). Note the
@
symbol. - The added file will not be added with its file path if the default file path matches that of CMD
vpk k <vpkfile> <keyvalues_filename>
- Add files references in a keyvalues control file.
Bug:They will appear inside the VPK with their full path (
C:\etc\
) intact - is there a way to avoid this?
Extract Files
vpk <vpkfile>
- Extract all files from VPK.
Note:This is invoked when a .VPK file is dragged onto the VPK tool.
vpk x <vpkfile> <filename1> <filename2> ...
- Extract file(s).
Bug:Selective file extraction doesn't seem to work
Display VPK Info
vpk l <vpkfile>
- List contents of VPK.
vpk L <vpkfile>
- List Detailed contents of VPK.
VPK Integrity/Security
vpk checksig <vpkfile>
- Verify signature of specified VPK file. Requires -k to specify key file to use.
Misc.
vpk generate_keypair <keybasename>
- Generate public/private key file. Output files will be named <keybasename>.publickey.vdf and <keybasename>.privatekey.vdf
Note:Remember: your private key should be kept private.
Options
Tip:Please note the case of these options. A capital letter is different than a lowercase letter.
-v
- Verbose output.
-M
- Produce a multi-chunk VPK.
Note:Required if creating a VPK with key values.
- Each chunk is a file limited to around 200MB.
- To reduce patch sizes, chunks are never overwritten. New/modified files are instead written to a brand new chunk every time you run the tool.
Note:
Multi-chunk generations only works when creating a VPK from a response file. This is not required on
Source 2013.
Tip:To inspect a multi-chunk VPK open the '_dir' file.
-P
- Use SteamPipe-friendly incremental build algorithm. Use with 'k' command. For optimal incremental build performance, the control file used for the previous build should exist and be named the same as theinput control file, with '.bak' appended, and each file entryshould have an 'md5' value. The 'md5' field need not be theactual MD5 of the file contents, it is just a unique identifierthat will be compared to determine if the file contents has changedbetween builds.
-c <size>
- Use specified chunk size (in MB). Default is 200.
-a
- Align files within chunk on n-byte boundary. Default is 1.
-K
- With commands 'a' or 'k': Sign VPK with specified private key.
-k
- With commands 'a' or 'k': Public key that will be distributed and used by third parties to verify signatures.
- With command 'checksig': Check signature using specified key file.
Examples
Listed below are some examples of using the tool and what they will do.
Creating your own VPKs for a mod
Your mod's assets must be packaged within the appropriate content folders ("sound," "materials," "models," etc.) in the root directory of your VPK. Otherwise you won't be able to see or use your assets in Hammer! For a specific example, imagine you're packing the content for your mod called "MyMod," and want to include a sound .wav named "ocean_ambient.wav". In your MyMod folder to be packed, you would probably want to place ocean_ambient.wav under "MyMod/sound/", or better, in a specific subdirectory of that one, like "MyMod/sound/ambient/". The same principle is easily applied to other assets like compiled model files, or materials and textures. These would be best placed in subdirectories of "MyMod/models" and "MyMod/materials", respectively.
VPKs must be mounted in gameinfo.txt.
Creating A Key Value File and VPK
- 1. Create a folder with the correct directory structure and files that you wish to use such as
MyMod/resource/ui/<file.res>
- 2. Use the command line in a prompt or a bat:
vpk generate_keypair <name>
vpk -M -k <name>.publickey.vdf -K <name>.privatekey.vdf "..\SteamApps\common\Team Fortress 2\bin\MyMod"
- 3. In the folder where the VPK tool is located there will now be a public key VDF, a private key VDF, a VPK named mymod_000 and a VPK named mymod_dir.
Warning:Never disclose or share your private key VDF or the key. Only share the public key.
- You must distribute your mod with both the mymod_dir and mymod_000 VPKs for the keyvalue to work.
Response File
A "response file" contains a list of files to be added to a VPK. Paths are relative to the current directory of the vpk
tool.
Below is a Python script which generates a response file and then builds a multi-chunk VPK. Put it in your mod folder. You will need to edit the three variables at the top.
#What folders to look for, and pack into the pak01 vpk set.
target_folders = [ "materials", "models", "resource", "media", "particles", "scripts", "maps", "expressions", "scenes" ]
#What files to look for, in the aforementioned folders.
file_types = [ "vmt", "vtf", "mdl", "phy", "vtx", "vvd", "ani", "pcf", "vcd", "txt", "res", "vfont", "cur", "dat" , "bik", "mov", "bsp", "nav", "lst", "lmp", "vfe" ]
#which vpk.exe to use. do not use \!
vpk_path = "../steamapps/common/SourceFilmmaker/game/bin/vpk.exe"
# Script begins
import os,subprocess
from os.path import join
response_path = join(os.getcwd(),"vpk_list.txt")
out = open(response_path,'w')
len_cd = len(os.getcwd()) + 1
for user_folder in target_folders:
for root, dirs, files in os.walk(join(os.getcwd(),user_folder)):
for file in files:
if len(file_types) and file.rsplit(".")[-1] in file_types:
out.write(os.path.join(root[len_cd:].replace("/","\\"),file) + "\n")
out.close()
#the "pak01" here specifies the multi-chunk vpk names. Could be changed to "pak02" or "hl2_textures", or whatever your mod needs.
subprocess.call([vpk_path, "-M", "a", "pak01", "@" + response_path])
To handle this process for Portal 2 you can also use the P2 Multichunk Tool, to automate the response file creation and the creation of the vpk files.
Warning:P2 Multichunk Tool will probably work for other games aside Portal 2 if the .vpk path is changed accordingly, it's functions have been expanded to make it easier to experiment, but since it haven't tested outside Portal 2 there is no guarantee it will work with other games.
Control File creation
This process is more complicated and advanced than other uses. Creating a Control file allows vpk.exe to differentiate new or edited files from unaffected files, and only writes the changes to a mod's VPK archive. When a mod's VPK files generated with this script are uploaded to the Steam Workshop, users will only need to download the affected files instead of downloading the entire mod again.
Below is a Python script which generates a control file for each category of assets a mod contains (materials, models, etc.). Then, it creates multi-chunk VPKs for each category which is signed using a public/private keypair and utilizes the SteamPipe incremental build algorithm. The public key should be distributed separately from the uploaded mod files.

import os,sys,subprocess,fnmatch,shutil,vdf,hashlib
from os.path import join,exists
# Point this to your vpk.exe location for the game your mod uses
vpk_path = "C:/Program Files (x86)/Steam/steamapps/common/Black Mesa/bin/vpk.exe"
# Point this to another folder to copy the VPK files here. This makes it convenient to upload changes to the Steam Workshop
# Use Steam Workshop Uploader or Crowbar to upload mods to the Workshop
# Keep empty to disable VPK file copying
upload_path = ""
# Your mod's name. This is used for VPK filenames and for public/private keypair
# If you already have public/private keypair generated, place it in your mod folder. Otherwise this will generate its own
# Can contain spaces. Do not include any special symbols: \/:*?[]<>{}
mod_name = "MyModNameHere"
# By default, the mod location is the current working directory. Change this if needed.
input_path = os.getcwd()
os.chdir(input_path)
len_cd = len(input_path) + 1
# Define the VPK folder structure. Change this to fit your needs.
folder_info = {
"maps": {
"folders": [ "maps" ],
"files": [ "bsp", "nav", "ain", "txt" ]
},
"materials": {
"folders": [ "materials", "ui" ],
"files": [ "vtf", "vmt", "png" ]
},
"models": {
"folders": [ "models" ],
"files": [ "mdl", "phy", "vtx", "vvd", "ani" ]
},
"sounds": {
"folders": [ "sound" ],
"files": [ "wav", "mp3", "ogg" ]
},
"misc": {
"folders": [ "campaigns", "cfg", "particles", "resource", "scenes", "scripts", "ui" ],
"files": [ "json", "cfg", "pcf", "txt", "dat", "vcd", "dmx", "qml" ]
}
}
if not mod_name:
print("WARNING: mod_name variable is empty. Your mod cannot be unnamed. Aborting.")
sys.exit(1)
vpk_data = vdf.VDFDict()
for key, value in folder_info.items():
vpk_data[key] = {}
def md5hash(file_path):
"""Generate an MD5 hash for the specified file"""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
# Generate control files
for folder_type in folder_info:
print(f'Indexing {folder_type} folder')
for user_folder in folder_info[folder_type]["folders"]:
for root, dirs, files in os.walk(join(input_path, user_folder)):
for file in files:
if file.rsplit(".")[-1] in folder_info[folder_type]["files"]:
vpk_data[folder_type][join(root, file)[len_cd:].replace("/", "\\")] = {
"destpath": join(root, file)[len_cd:].replace("/", "\\"),
"md5": md5hash(join(root, file))
}
new_controlfile = join(input_path, folder_type + "_control.vdf")
if exists(new_controlfile):
if exists(new_controlfile + ".bak"):
try:
os.remove(new_controlfile + ".bak")
except Exception as e:
print(f"Could not delete file: {e}")
os.rename(new_controlfile, new_controlfile + ".bak")
with open(new_controlfile, 'w') as f:
f.write(vdf.dumps(vpk_data[folder_type], pretty=True))
with open(new_controlfile, 'r') as f:
new_content = f.read().replace('\\\\', '\\')
with open(new_controlfile, 'w') as f:
f.write(new_content)
# Pack the VPK files
for folder_type in folder_info:
print(f'Packing {folder_type} folder')
if not exists(join(input_path, mod_name + ".privatekey.vdf")) and not exists(join(input_path, mod_name + ".publickey.vdf")):
print("Public/Private keypair not detected. Generating a new one.")
subprocess.call([
vpk_path,
"generate_keypair",
mod_name
])
if not exists(join(input_path, mod_name + ".privatekey.vdf")):
print("WARNING: Public key exists but private key not found. Aborting.")
sys.exit(1)
if not exists(join(input_path, mod_name + ".publickey.vdf")):
print("WARNING: Private key exists but public key not found. Aborting.")
sys.exit(1)
subprocess.call([
vpk_path,
"-M",
"-K", mod_name + ".privatekey.vdf",
"-k", mod_name + ".publickey.vdf",
"-P",
"k",
mod_name + "_" + folder_type,
join(input_path, folder_type + "_control.vdf")
])
# Rename the VPK files
for filename in os.listdir(input_path):
if fnmatch.fnmatch(filename, mod_name.lower() + "*.vpk"):
new_filename = mod_name + filename[len(mod_name):]
os.rename(join(input_path, filename), join(input_path, new_filename))
if upload_path:
# Delete destination VPK files if they exist.
for filename in os.listdir(upload_path):
if fnmatch.fnmatch(filename.lower(), mod_name.lower() + "*.vpk"):
try:
os.remove(join(upload_path, filename))
except Exception as e:
print(f"Could not delete file: {e}")
# Copy the VPK files
for filename in os.listdir(input_path):
if fnmatch.fnmatch(filename, mod_name + "*.vpk"):
try:
shutil.copy(join(input_path, filename), upload_path)
except Exception as e:
print(f"Could not copy file: {e}")
# Copyright © 2025 tpd1864blake
# This work has been marked as dedicated to the public domain.
# See https://creativecommons.org/public-domain/cc0/
Excluded files
Executable and some archive files are discarded by the VPK tool:
.zip .reg .rar .msi .exe .dll .com .cmd .bat

See also
- VPK File Format (technical details)
- L4D Campaign Add-on Tutorial
- L4D2 Campaign Add-on Tutorial
- Releasing A Portal 2 Map
- P2 Multichunk Tool (Easy handling of mass vpk packing)
- Swarm Campaign Add-on Tutorial
- VPK Compare GUI Tool for finding conflicts between VPK files
- Source Multi-Tool (VPK Creator - GUI)
- Fixing VPK mounting for older Source SDK Bases (for Source 2006 & Source 2007)
Software
VPKEdit - Program which can create and modify VPK files (supports both VPK v1 and v2, aswell as Vampire: The Masquerade – Bloodlines VPK format)
GCFScape - Program which modify VPK files, since GCFScape 1.7.1 (supports both VPK v1 and v2, no longer updated)