PakRat Modern edits the PAKFILE lump inside Source .bsp files: the embedded
ZIP where a map carries its custom materials, models, sounds and overviews. It
scans the map for everything it references, tells you what is missing, and packs
it — without rebuilding the BSP and without touching any other lump.
It ships as a native Windows GUI and a Python CLI that produce byte-identical output.
- Download
PakRatModern-release.zipfrom the latest release and extract it anywhere. - Run
PakRatModern.exe. Nothing to install: it targets .NET Framework 4.7.2, which every supported Windows already has. - Open your
.bsp(or drag it onto the window). - Set Game Path to the folder that contains
gameinfo.txt—cstrike,hl2,tf… - Click Scan, review the list, then Add checked (or just Auto).
- Save. A
.bakof the original is kept next to it.
Verifying the download
Every release lists the SHA256 of the zip and of the binaries inside it.
Get-FileHash .\PakRatModern-release.zip -Algorithm SHA256GitHub also records a digest for each asset, readable without downloading:
gh api repos/Ayrton09/PakRatModern/releases/latest --jq ".assets[].digest"| Scan | Collects every file the map references from the entity lump, the texdata string table and the static prop lump, then follows dependencies: a .mdl pulls its .vvd / .phy / .vtx and materials, a .vmt pulls its textures and includes. |
| Classify | Each reference is marked Already in PAK, Base game VPK (shipped with the game — not packed), Can add (found on disk) or Missing on disk. |
| Pack | Add checked results, single files, whole folders, or drag and drop. Internal paths are deduced from the disk location. |
| Edit | Rename internal paths, delete entries, extract to disk, preview any entry as text or hex. |
| Verify | Round-trips the PAK through the writer and reader before you save. |
| Save safely | Atomic write, optional .bak, 4-byte lump alignment preserved, game lump guarded. |
Files on disk are looked up in Game Path and in every SearchPaths entry of
its gameinfo.txt — including custom/* — in the same order the engine uses.
Content already shipped in the game's _dir.vpk files is recognised and left
out of the map.
Optional extras the scan also checks
These are not referenced inside the BSP but the game looks for them by name. They are only reported when present on disk.
maps/<map>.nav maps/<map>.txt
maps/<map>_particles.txt maps/<map>_level_sounds.txt
scripts/soundscapes_<map>.txt
resource/overviews/<map>.txt resource/overviews/<map>.dds
resource/overviews/<map>_radar.dds
materials/overviews/<map>.vmt materials/overviews/<map>.vtf
materials/overviews/<map>_radar.vmt materials/overviews/<map>_radar.vtf
Where added files end up
The internal path is the file's location relative to Game Path. Source
mounts every custom/<name>/ folder and download/ as search paths of their
own, so these are stripped:
| On disk | Packed as |
|---|---|
cstrike/materials/custom/wall.vmt |
materials/custom/wall.vmt |
cstrike/custom/mymod/materials/custom/wall.vmt |
materials/custom/wall.vmt |
cstrike/download/models/props/crate.mdl |
models/props/crate.mdl |
D:\work\pack\materials\custom\wall.vmt (outside Game Path) |
materials/custom/wall.vmt |
A file with no recognisable content folder in its path is reported instead of being packed under a guessed name.
Settings and the startup log live in %LOCALAPPDATA%\PakRatModern\.
pakrat_modern.py needs Python 3.8+. The wrapper
pakrat_modern.ps1 finds a local Python or falls back to
WSL, so on Windows you can call it as:
.\pakrat_modern.ps1 <command> [options]| Command | What it does |
|---|---|
list <map.bsp> |
Print every embedded file with its size |
verify <map.bsp> |
Round-trip the PAK; exit code 0 valid, 2 invalid or unreadable |
extract <map.bsp> [--out DIR] [--overwrite] [PATTERN ...] |
Extract all entries, or those matching case-insensitive globs. Existing files are kept unless --overwrite |
add <map.bsp> PATH... --base DIR [--inplace | --out FILE] [--no-backup] |
Add files or folders; internal paths are relative to --base |
remove <map.bsp> NAME... [--inplace | --out FILE] [--no-backup] |
Remove entries by internal path |
add and remove write <map>_packed.bsp / <map>_stripped.bsp by default.
Whenever the output already exists, a .bak copy is made first.
.\pakrat_modern.ps1 add C:\maps\de_mine.bsp C:\content\materials --base C:\content --inplaceThe GUI and the CLI emit the same bytes for the same set of files; a test on each side checks it against a shared reference hash.
- Only the
PAKFILElump and the offsets its resize shifts are rewritten; every other byte of the BSP is preserved. - Following lumps keep their 4-byte alignment. A resize is refused when
LUMP_GAME_LUMPsits after the PAK, because its internal offsets are absolute. - Writes go to a temporary file in the same folder and are swapped in atomically; a failure mid-save leaves the original intact.
- Internal paths are validated on read and write: no
.., no absolute paths, no NTFS-invalid characters, no reserved device names. Extraction is confined to the chosen folder. - Size and entry-count limits count the bytes that actually decompress, not what the ZIP directory claims.
- Paths are case-insensitive, like the engine. Duplicate entries are merged and reported — with a note when their contents differ.
Limitation: LZMA-compressed maps
Some maps store their PAK entries with LZMA. The GUI relies on the ZIP support built into .NET, which handles only Stored and Deflate, so it refuses such a map with an explanation rather than opening it partially (which would silently drop those entries on save).
The CLI reads LZMA and BZip2. One add with it rewrites every entry
uncompressed, after which the GUI opens the map normally. Methods neither tool
reads (Deflate64, PPMd, XZ) are refused by both, by name.
Antivirus false positives
Releases up to 1.2.2 were a PowerShell script packaged with ps2exe: a binary
whose only job is to host PowerShell and run an embedded script, which is the
same shape as real droppers, so heuristic engines flagged it on sight.
Since 1.3.0 the application is a normal compiled .NET assembly with no embedded script, no PowerShell host, and no networking or registry APIs. If a scanner still flags it, please open an issue with the detection name.
Requires the .NET SDK and Python 3.
powershell -ExecutionPolicy Bypass -File .\build_release.ps1That runs both test suites, publishes the application into
release\PakRatModern\, zips it, and prints the SHA256 of the outputs. The
same tests run in GitHub Actions on every push.
dotnet run --project src\PakRatModern.Tests\PakRatModern.Tests.csproj -c Release -f net472
python -m unittest discover -s testssrc/PakRatModern.Core/ BSP, PAK/ZIP, VPK, gameinfo, reference scanner
src/PakRatModern.App/ WinForms interface
src/PakRatModern.Tests/ core tests (no external dependencies)
pakrat_modern.py CLI
tests/ CLI tests
tools/ generates the shared parity reference hash
The PowerShell GUI that preceded 1.3.0 is kept on the
legacy/powershell-gui
branch and is no longer maintained.
