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
That's the whole tool. 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 it does not change how the mod loads. It is a marker, not a lock: it identifies provenance, but anyone can strip it, so it does not prevent misuse.
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/ referencesOne mod referenced a weapon material a patch had deleted; the fix was to re-parent it to the master material its sibling materials already used.
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 terms with an Acceptable Use Addendum. The grant does not extend to using this software with mods you do not have the right to modify and distribute, and/or to produce content that would be unacceptable in a game rated ESRB E10+ (Everyone 10+) and/or the equivalent under another ratings system. The addendum carries over into anything built on the code. That is a field-of-use restriction, so this is source-available rather than open source by the OSI definition. A licence cannot inspect a texture; it states the author's position and travels with the code. See LICENSE.

