srctools.vpk

This module reads and writes VPK archives, used to store content for Source games. These archives are uncompressed, and consist of two sorts of files:

  • The “directory” (usually named archive_dir.vpk). The directory contains the list of all files, and optionally part or whole sections of files.

  • One or more data files (named like archive_032.vpk).This has no structure, and simply consists of file data concatenated together.

Alternatively, a “singular” VPK archive is also possible, where all contents must be stored in the directory file. Note that removing data from a data file is not possible - changing a file’s contents or deleting it will change the directory, but leave the old data. This does have the advantage that updating a shipped game only requires downloading new data files for the additional content, but it means old data stays around.

Opening and closing

VPKs can be opened in three modes, like regular files. Changes to the directory are only applied once write_dirfile() is called, but writes to data files occur immediately. The VPK can be used like a context manager to automatically save, if no exception was raised.

class srctools.vpk.OpenModes

Bases: Enum

Modes for opening VPK files.

READ = 'r'
WRITE = 'w'
APPEND = 'a'
property writable: bool

Check if this mode allows modifying the VPK.

class srctools.vpk.VPK(
dir_file: str | PathLike[str],
*,
mode: OpenModes | str = 'r',
dir_data_limit: int | None = 1024,
version: int = 1,
)

Represents a VPK archive.

VPKs can either be a singular file, or a main archive_dir.vpk with associated numeric archive_XXX.vpk files containing the data.

load_dirfile() None

Read in the directory file to get all filenames. This erases all changes made to the object.

write_dirfile() None

Write the directory file with the changes. This must be performed after writing to the VPK.

Attributes

VPK.folder: str

The directory the VPK is located in, used to find the numeric files.

VPK.filename

The filename of the directory VPK file, or the single file.

This can be assigned to set file_prefix.

VPK.path

The full path of the directory VPK file, or the single file.

This can be assigned to set folder and filename.

VPK.mode: OpenModes

How the file was opened.

  • Read mode, the file will not be modified and it must already exist.

  • Write mode will create the directory if needed.

  • Append mode will also create the directory, but not wipe the file.

VPK.version: int

The VPK version, 1 or 2.

VPK.file_prefix

The VPK filename, without .vpk, or _dir.vpk if a directory.

Deprecated:

Treat as readonly. Assigning to this assumes the VPK is always a directory file. Instead, assign to filename or path.

VPK.dir_limit: int | None

The maximum amount of data for files saved to the dir file.

  • None: No limit.

  • 0: Save all to a data file.

VPK.footer_data: bytes

The block of data after the header, which contains the file data for files stored in the _dir file, not numeric files.

File Objects

class srctools.vpk.FileInfo

Represents a file stored inside a VPK.

Do not call the constructor, it is only meant for VPK’s use. Attributes should not be assigned to - use write() to change the contents.

These have no public constructor, and represent a file in a VPK. All attributes should be considered readonly - call methods instead.

name
filename

The full filename for this file.

dir: str
ext: str
size

The total size of this file.

arch_index: int | None

pack_01_000.vpk file to use, or None for _dir.

arch_len: int

Number of bytes in archive files

vpk: VPK
offset: int
start_data: bytes

The bytes saved into the directory header.

Locating Files

The structure of a VPK organises files by folder and extension, making iteration efficient. To locate a specific file, the simplest way is to index the VPK in one of three ways:

vpk['folders/name.ext']
vpk['folders', 'name.ext']
vpk['folders', 'name', 'ext']

If the extension or folders are known, this avoids needing to split the filename. in, and len() checks also work as expected.

If the filename is not known, iterating the VPK gives each file in turn. Alternatively, the following methods can be used:

for filename in VPK.filenames(ext: str = '', folder: str = '') Iterator[str]

Yield filenames from this VPK.

If an extension or folder is specified, only files with this extension or in this folder are returned.

for folder in VPK.folders(*, ext: str | None = None) Iterator[str]

Yield the names of folders present in this VPK.

If an extension is specified, only folders containing files with that extension are returned.

for file in VPK.fileinfos(
*,
ext: str | None = None,
folder: str = '',
) Iterator[FileInfo]

Yield file info objects from this VPK.

If an extension or folder is specified, only files with this extension or in this folder are returned.

Once found, call read to read the contents:

FileInfo.read() bytes

Return the contents for this file.

Writing

To write to a VPK, create a new file info entry, then call write:

VPK.new_file(
filename: str | tuple[str, str] | tuple[str, str, str],
root: str = '',
) FileInfo

Create the given file, making it empty by default.

If root is set, files are treated as relative to there, otherwise the filename must be relative.

FileExistsError will be raised if the file is already present.

FileInfo.write(data: bytes, arch_index: int | None = None) None

Replace this file with the given byte data.

Parameters:
  • data – The contents of the file to write.

  • arch_index – This is the pak01_000.vpk file to put data into (or None for :file`_dir.vpk`). This is ignored if the VPK is singular.

  • Data written to the directory file is not immediately saved, VPK.write_dirfile() must subsequently be called to do so.

  • If this file already exists in the VPK, the old data is not removed from numeric files. For this reason VPK writes should be done once per file if possible.

Alternatively, call these methods to do both at once.

VPK.add_file(
filename: str | tuple[str, str] | tuple[str, str, str],
data: bytes,
root: str = '',
arch_index: int | None = 0,
) None

Add the given data to the VPK.

If root is set, files are treated as relative to there, otherwise the filename must be relative. arch_index is the pak01_xxx file to copy this to, if the length is larger than self.dir_limit. If None it’s written to the _dir file.

FileExistsError will be raised if the file is already present.

VPK.add_folder(folder: str, prefix: str = '') None

Write all files in a folder to the VPK.

If prefix is set, the folders will be written to that subfolder.

You can also do del vpk['some/filename'] to remove a file.

Miscellaneous

VPK.extract_all(dest_dir: str) None

Extract the contents of this VPK to a directory.

FileInfo.verify() bool

Check this file matches the checksum.

VPK.verify_all() bool

Check all files have a correct checksum.

srctools.vpk.VPK_SIG: Final = 1437209140

The first byte of VPK files.

srctools.vpk.DIR_ARCH_INDEX: Final = 32767

The file index used to indicate data is contained in the directory file.

srctools.vpk.get_arch_filename(prefix: str = 'pak01', index: int | None = None) str

Generate the name for a VPK file.

Prefix is the name of the file, often pak01. index is the index of the data file, or None for the directory.