Deadlock Research
verifiednote 0001build 66792026-08-15vpklayout

VPK archive layout and inventory

Question

How is Deadlock's content packaged on disk, and where in that packaging does gameplay data live relative to everything else?

Summary

Findings

Top-level layout

game/
  bin/win64/deadlock.exe, vconsole2.exe
  citadel/                 the game mount
    pak01_000.vpk .. pak01_273.vpk    content archives
    pak01_dir.vpk                     the index (6.6 MB)
    shaders_pc_*.vpk, shaders_vulkan_*.vpk
    resource/localization/            loose .txt, NOT packed
    gameinfo.gi, steam.inf
  core/                    shared engine content (6 archives)
  citadel_<lang>/          per-language overlay, one small VPK each (~19 MB)

citadel/gameinfo.gi confirms the mount name and lists 17 supported languages. English is not among the citadel_<lang>/ overlay directories — it is the base content.

Content inventory (citadel/pak01_dir.vpk)

countsizeextensionwhat
79,3052,903.1 MB.vsnd_csounds
15,56952.1 MB.vpcf_cparticles
12,86423,987.2 MB.vtex_ctextures
9,372122.1 MB.vnmclip_canimation clips
5,013825.7 MB.vmdl_cmodels
3,94916.9 MB.vmat_cmaterials
4274.6 MB.vcss_cPanorama UI stylesheets
4191.1 MB.vxml_cPanorama UI layouts
1102.1 MB.vrr_cresponse rules (VO logic)
850.8 MB.vdata_cgameplay data
290.4 MB.txtloose config

Top-level directories: sounds (79,304), models (22,931), particles (15,686), materials (6,414), panorama (3,449), scripts (203), stats (6).

Inferred: anyone after gameplay data should filter to scripts/ + .vdata_c and ignore 99.9% of the archive by volume. Extracting the whole pak is never necessary for this goal.

VPK v2 directory format

Header is signature:u32 = 0x55AA1234, version:u32, treeSize:u32, followed in v2 by four section-size u32s (file data, archive MD5, other MD5, signature) — so the tree starts at byte 28.

The tree is three nested runs of null-terminated strings, each run ended by an empty string: extension → directory → filename. Each filename is followed by a 18-byte footer:

crc32:u32  preloadBytes:u16  archiveIndex:u16  entryOffset:u32  entryLength:u32  0xFFFF:u16

then preloadBytes of inline data. A directory of " " (single space) means the archive root. archiveIndex selects which pak01_NNN.vpk holds the body.

The per-entry crc32 is what makes cross-patch diffing cheap — see the --manifest flag.

Reproduce

python tools/find_game.py
python tools/vpk_list.py
python tools/vpk_list.py --prefix scripts/ --ext vdata_c --list

Expected head of find_game.py at this build:

ClientVersion   6679   <-- verified_against
SourceRevision  10912166
VersionDate     Aug 14 2026
archives        274 pak01_*.vpk  (27.4 GiB)

To diff content against a later patch:

python tools/vpk_list.py --manifest > manifests/6679.txt
# after patching:
python tools/vpk_list.py --manifest > manifests/NEXT.txt
diff manifests/6679.txt manifests/NEXT.txt

Gotchas

Open questions

Sources