Update your old Gotham Knights .pak mods to the IoStore format the final
(2023) build uses.
Mods built before that update stopped working, and the game gives you nothing to
debug with: no log, no error, just a mod that does nothing or a game that dies on
launch. gkupdate rebuilds an old cooked .pak into the .utoc/.ucas/.pak
container the current build loads, and checks the result is sound.
This is a tool for mod authors to update their own mods. It is not a mod manager. It does not install, enable, or launch anything. If you just want to play with old
.pakmods, use GK-ModLoader instead (coming soon).
What it does and does not do. GK-ModUpdater only changes a mod's packaging
format, from legacy .pak to IoStore. It does not fix a mod that was already
broken, and it cannot make a mod compatible with a game update that moved,
renamed, or removed the assets the mod targets. If the game changed underneath the
mod, converting the format will not bring the old content back. verify (with the
game present) will tell you when a mod points at assets the current build no
longer has, but it can only report that, not repair it.
Ships no third-party binaries. retoc and repak are other people's work
under their own licences, so GK-ModUpdater finds a copy you already have or
downloads one from the official release page when you ask it to.
Download the release zip, unzip it anywhere, and run gk-cli.bat (it opens a
terminal with gkupdate ready). The exe is not code-signed, so on first run
Windows SmartScreen may say "Windows protected your PC". Click More info then
Run anyway. If you would rather not run an unsigned exe, use the from-source
route at the bottom of this file instead.
The final build loads packages through IoStore, by PackageId, not by reading
.uasset files out of a .pak. So:
- A pak holding only loose files (
.ini,.mp4) still works untouched. - A pak holding
.uassetpackages is silently ignored and must be rebuilt.
PackageId = CityHash64(lowercase("/Game/<path>") as UTF-16LE), little-endian.
Mercury/Content/... maps to /Game/....
The first run shows the terms of use and asks you to accept them once.
gkupdate tools --fetch all # get retoc + repak (or --set them)
gkupdate convert "C:\Mods\MyOldMod.pak" --out convertedThat writes converted\MyOldMod_P.{utoc,ucas,pak}. Ship all three together, or
hand them to players who use GK-ModLoader.
convert accepts a .pak, a .zip/.rar/.7z, an already-unpacked cooked
tree, or a folder of any of those. Point it at a whole folder of downloads and it
converts everything convertible inside.
gkupdate convert <thing>... an old .pak / archive / folder -> IoStore
gkupdate verify <file.utoc> check a converted mod is structurally sound
gkupdate tools check or install the retoc / repak helpers
gkupdate terms show the terms of use
Exit codes: 0 fine, 1 a problem with the input, 2 a helper is missing,
3 a helper ran and failed, 4 terms not accepted.
For scripts there is --accept-terms and gkupdate terms --accept; with no
console attached it prints the terms and exits 4 rather than blocking on input.
retoc to-zen --version UE4_27 gets close, but its containers are wrong for
UE4.27 in three ways that crash the game. All three are fixed after conversion:
1. ExportBundlesSize = 0. The loader computes
export_data_size = ExportBundlesSize - (GraphDataOffset + GraphDataSize)
so zero underflows to ~2^64 and it dereferences a wild pointer. Set to the real
ExportBundleData chunk size.
2. Self-referential PackageRedirects, one per package. Real cooked
containers have none. Cleared.
3. Export bundles split one-per-load-phase instead of a single bundle holding
every entry. Merged, but only for packages with no external arcs. GraphData
arcs carry a ToExportBundleIndex pointing into this package's bundle list;
collapsing a package that has arcs leaves them dangling and the loader walks off
the end of the bundle array (a crash that reads address 0x10).
The ContainerId is re-keyed so the TOC, the header chunk's id and the
ContainerId inside the header body all agree. retoc pack-raw does not do
this, and a mismatch makes a container that mounts but provides nothing. The stub
.pak written alongside is the 232-byte empty PakFile v9 index every working
native mod ships.
convert verifies as it goes, and gkupdate verify re-checks an existing
container against what the loader will check:
ExportBundlesSizeequals the real chunk size for every package- bundle region size agrees with the store entry's bundle and export counts
- every GraphData arc targets a bundle that exists
- export data size equals the sum of the export map's serial sizes
- directory-index package ids match
CityHash64of their/Game/path - TOC id, header chunk id and header body id all agree
If GK-ModUpdater can find your game install (or you pass --game), it also checks
every package your mod imports still exists in the shipped assets, which catches
a mod referencing an asset a later patch deleted. It reads the base game's package
list without the AES key: only the directory index and chunk payloads are
encrypted, never the TOC header or the FIoChunkId table.
Every converted mod carries a small marker at the end of its .ucas, so you can
tell a mod was made with GK-ModUpdater:
findstr /C:"gk-modupdater" MyMod_P.ucas
It is trailing data the game ignores, and can be removed without affecting the mod.
If verify reports an unresolved import, the parent asset your mod references is
gone from the current build. Legacy .uasset files keep import paths as plain
strings (the zen format throws them away), so unpack the original pak and read
them:
repak unpack OldMod.pak -o unpacked
# then grep the .uasset files for /Game/ referencesThe fix is to re-parent the mod onto an asset the current build still ships.
python gkupdate.py tools # see what's missing
python -m unittest discover -s tests -t .Python 3.10+, no dependencies beyond the standard library. The tests need neither the game nor the helper tools nor a network connection.
Building the executable:
pip install pyinstaller
python build.pyUse the default onedir build. Windows Defender quarantines --onefile on sight,
because the self-extracting stub trips a generic heuristic.
gkmodkit/
cityhash.py CityHash64 - PackageId and ContainerId derivation
formats.py TOC reader, zen package parser, container header read/rebuild
convert.py conversion pipeline, container repair, verification
archives.py importing and classifying downloads
gamedir.py read-only: locate a game install to resolve imports
tools.py locating or fetching retoc / repak
errors.py exception types - the library never calls sys.exit()
terms.py first-run acceptance of the terms
gkupdate.py the CLI
gkmodkit is usable as a library. Nothing in it prints or exits; it raises from
gkmodkit.errors and lets the caller decide.
Relies on retoc and repak by trumank, which you supply yourself. Neither is redistributed here.
MIT with an Acceptable Use Addendum: use it only with mods you have the right to modify and distribute, and not to produce content unacceptable in a game rated ESRB E10+ (or the equivalent). The addendum carries over into anything built on the code. The field-of-use restriction makes this source-available, not open source. See LICENSE.

