-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Pakfile Modding
The majority of BL2/TPS/BL3/WL modding in this scene has tended to be focused
around the UE set
command, and taking advantage of Gearbox's micropatch/hotfix
system to make changes to existing game objects. BL2/TPS additionally has the
option to use PythonSDK to do much fancier things inside the UE engine itself.
BL3/WL's move to Unreal Engine 4 has opened up a newer method of modding, though,
which some in the community have started playing with: pakfile modding.
The foundation of pakfile modding is that UE4 will happily load in any .pak
files that it finds in the pakfile directory, so long as they're unencrypted,
or use the same encryption key as the main game's pakfiles. It'll also look
through subdirectories in the pakfile dir, which allows for some nice mod
organization. Custom pakfiles can be used to both modify existing objects,
and also create brand new ones.
The most fundamental tool for working with pakfiles is, of course, UE4's own
UnrealPak.exe
. You can get this yourself by downloading UE4 itself -- it'll
be included somewhere in the distribution. BL3 and Wonderlands use UE version
4.26, so be sure to grab that version.
Details about extracting pakfiles for both BL3 and WL can be found at the
Accessing Borderlands 3 Data page. The most important detail in there
is that you'll need to have a crypto.json
file set up to extract the paks.
When running UnrealPak.exe
, you'll pass it the argument -cryptokeys=crypto.json
to specify the encryption parameters. This is necessary when decrypting the
base-game paks, but not actually needed when re-packing for mod purposes. The
game will happily load unencrypted pakfiles if found. You can use that same
crypto.json
file to encrypt your paks with the same key as the rest of the
game, too, if you want, though.
You can use UnrealPak.exe
's -list
argument to get some information about
the pakfile which will come in handy:
$ UnrealPak.exe pakchunk2-WindowsNoEditor_2_P.pak -list -cryptokeys=crypto.json
LogPakFile: Display: Parsing crypto keys from a crypto key cache file
LogPakFile: Display: Mount point ../../../OakGame/Content/WwiseAudio/
LogPakFile: Display: "2865348835.bnk" offset: 0, size: 30240 bytes, sha1: CDCA3883E50E4485E69180E9271B218709F6196E.
LogPakFile: Display: "4258405419.bnk" offset: 30720, size: 173759 bytes, sha1: CDF8B2FF0B6E1A6C19DA19417FD9E5A67F3BAC72.
...
LogPakFile: Display: 147 files (47754906 bytes), (47754906 filtered bytes).
LogPakFile: Display: Unreal pak executed in 0.074438 seconds
The most important thing to note there is the "Mount point," which is in this case
../../../OakGame/Content/WwiseAudio/
. If you're constructing a pakfile to
override any of the files inside this pakfile, you'll need to make sure that your
mount point and file contents are set up properly so that the new file is loaded
into the game.
One method of doing this (and possibly not the best, but it's the one way I've
found so far) is to create a text file like contents.txt
(the name isn't actually
important), with contents that look something like this:
"export/*" "../../../OakGame/Content/WwiseAudio/"
Then create an export
directory and slap your updated files in there. For
instance, you might have export/2865348835.bnk
, if you're looking to update that
first file listed above. Then, with that contents.txt
file in place, you can do:
$ UnrealPak.exe My_Mod_999999_P.pak -Create=contents.txt -encrypt -cryptokeys=crypto.json -encryptindex
The -encrypt
, -cryptokeys
, and -encryptindex
parameters aren't actually
required. If you specify them, the pakfile will be encrypted using the same
methods used by the base-game pakfiles. The engine will happily load unencrypted
pakfiles, though, so it's definitely not necessary.
It's recommended that you run another -list
on your freshly-created pakfile to
make sure that the mount point and file list look identical to the ones you're
overriding.
For editing (or creating) game objects, UAssetAPI/UAssetGUI is currently the tool of choice. UAssetGUI in particular lets you load up objects, export them as JSON files, and then re-import the JSON files later. The actual editing process is far too involved to get into here, but there should be places to ask around online.
Note that apocalyptech's UAssetAPI fork is intended to aid in hotfix modding, rather than pakfile modding. I'm honestly not sure if the additions in that fork get in the way of doing the object export/import, but you may want to stick with the original version when doing pakfile modding, just in case.
Looking through the game's pakfile directory (OakGame/Content/Paks
), you can
see that they use a patching system where there's a collection of "base" pakfiles
which then get overridden by a series of new pakfiles as updates to the game are
released. The pakfiles are effectively added-to based on the "priority" of the
new pakfiles, indicated with a suffix like _2_P
.
For instance, in Borderlands 3, all the pakfiles starting with pakchunk2
store the main game audio, like music, sound effects, and the like, and the
release order of some of the pakfiles in that series are:
-
pakchunk2-WindowsNoEditor.pak
- The original pakfile from the game launch -
pakchunk2-WindowsNoEditor_0_P.pak
- The first patch to base-game audio data, released along with the Bloody Harvest patch. -
pakchunk2-WindowsNoEditor_1_P.pak
- The second patch to base-game audio data, released along with the Maliwan Takedown patch. - ... and so on.
You can take a look at apocalyptech's bl3data repo
and wldata repo for a full index of which
pakfiles were released with which patches. There's also a
pakfile lookup webpage for BL3 and a
pakfile lookup webpage for Wonderlands
which lets you search in pakfile contents. For instance,
a query for this particular soundbank file
shows that it first showed up in the DLC1 support patch (pakchunk2-WindowsNoEditor_2_P.pak
,
and was later altered in the Guardian Takedown patch (pakchunk2-WindowsNoEditor_6_P.pak
).
When the game engine wants to load up a file from the collected group of pakfiles, it
will take the version found in the pakfile with the highest priority. So if a file
is found in both pakchunk2-WindowsNoEditor.pak
and pakchunk2-WindowsNoEditor_1_P.pak
,
it'll take the version from that latter pakfile. It effectively feels like "patching"
the pakfiles, though technically it's just a pakfile with higher priority, which happens
to contain some of the same files.
It's worth at least mentioning that many of the pakfiles are language or level
depdendent, so may not always be loaded. For instance, in BL3, pakchunk3-*
is English audio (dialogue, etc), whereas pakchunk85-*
through pakchunk91-*
are localized audio for other languages. Level data is stored in pairs of
pakfiles from pakchunk13-*
through pakchunk84-*
. DLC data, however, is
stored in one big pakfile which includes all level and localized-audio. The
specific numbers for Wonderlands is different, of course, but it's the same
system.
It's not quite correct to think of the pakfile processing as being a "load order," really, but it might be helpful to think of it that way. If you do think of it as a load order, here's basically how it would look: First, the game loads all the relevant "base" pakfiles:
pakchunk0-WindowsNoEditor.pak
pakchunk1-WindowsNoEditor.pak
pakchunk2-WindowsNoEditor.pak
- ...
Any custom (mod) pakfiles put in the same directory alongside the base-game pakfiles would get sorted and loaded in that order. For instance, if we pretend there's a couple of mods in that dir, the order would look something like this:
AAA_Mod_To_Load_First.pak
pakchunk0-WindowsNoEditor.pak
pakchunk1-WindowsNoEditor.pak
pakchunk2-WindowsNoEditor.pak
ZZZ_Mod_To_Load_Last.pak
Then, effectively, the engine would load all pakfiles with a priority of 0
:
pakchunk0-WindowsNoEditor_0_P.pak
pakchunk1-WindowsNoEditor_0_P.pak
pakchunk2-WindowsNoEditor_0_P.pak
- ...
...and then it'd load all pakfiles with a priority of 1
:
pakchunk0-WindowsNoEditor_1_P.pak
pakchunk1-WindowsNoEditor_1_P.pak
pakchunk2-WindowsNoEditor_1_P.pak
- ...
... and then onto 2
, and so on. Really what's happening is that the files
in those other pakfiles just take priority over what was stored in the previous
versions, but you can think of it as if they're loading and "overwriting" the
old contents.
If we pretend that the game only has pakchunk0-*
and pakchunk1-*
pakfiles,
with our two pretend mod pakfiles above, our hypothetical full load order would
end up looking like:
AAA_Mod_To_Load_First.pak
pakchunk0-WindowsNoEditor.pak
pakchunk1-WindowsNoEditor.pak
ZZZ_Mod_To_Load_Last.pak
pakchunk0-WindowsNoEditor_0_P.pak
pakchunk1-WindowsNoEditor_0_P.pak
pakchunk0-WindowsNoEditor_1_P.pak
pakchunk1-WindowsNoEditor_1_P.pak
Custom mod pakfiles are also grouped by patch level. So if our hypothetical
mod patchfile was named AAA_Mod_To_Load_First_1_P.pak
, even though there isn't
an AAA_Mod_To_Load_First.pak
or AAA_Mod_To_Load_First_0_P.pak
file, the
load order would look like this instead:
pakchunk0-WindowsNoEditor.pak
pakchunk1-WindowsNoEditor.pak
ZZZ_Mod_To_Load_Last.pak
pakchunk0-WindowsNoEditor_0_P.pak
pakchunk1-WindowsNoEditor_0_P.pak
AAA_Mod_To_Load_First_1_P.pak
pakchunk0-WindowsNoEditor_1_P.pak
pakchunk1-WindowsNoEditor_1_P.pak
This is potentially important for pakfile mods which want to override existing
files/objects inside the base-game pakfiles. For instance, the soundbank
3471258537.bnk
, mentioned above, was introduced in pakchunk2-WindowsNoEditor_2_P.pak
and then patched in pakchunk2-WindowsNoEditor_6_P.pak
. If a mod wanted to
alter that soundbank, it would need to specify a patch level of 6 or higher
(ideally 7 or higher, unless you want to make sure its name sorts after
pakchunk2
). Otherwise the version from pakchunk2-WindowsNoEditor_6_P.pak
would overwrite the mod. So, for instance, a modfile named
Audio_Fix_999_P.pak
would work just fine, because the relevant load
order would look like:
pakchunk2-WindowsNoEditor_2_P.pak
pakchunk2-WindowsNoEditor_6_P.pak
Audio_Fix_999_P.pak
A modfile named Audio_Fix_6_P.pak
wouldn't work right, because the relevant
load order would be:
pakchunk2-WindowsNoEditor_2_P.pak
Audio_Fix_6_P.pak
pakchunk2-WindowsNoEditor_6_P.pak
Or if it were just Audio_Fix.pak
, the load order would be:
Audio_Fix.pak
pakchunk2-WindowsNoEditor_2_P.pak
pakchunk2-WindowsNoEditor_6_P.pak
Best to stick with a high patch number so that it always ends up at the end!
Of course, keeping a bunch of mod pakfiles right in the same dir as the base
game pakfiles seems a little messy. Fortunately, the engine also supports
reading mods from subdirectories. This has some interesting implications
for pakfile-loading order as well. When the engine alphabetizes the list of
mods to be loaded, it includes the directory name as part of the alphabetization.
If you had a pakfile named Mod_To_Load.pak
inside two different subdirs named
aaa_mods
and zzz_mods
, the load order would end up looking like:
aaa_mods\Mod_To_Load.pak
pakchunk0-WindowsNoEditor.pak
pakchunk1-WindowsNoEditor.pak
zzz_mods\Mod_To_Load.pak
pakchunk0-WindowsNoEditor_0_P.pak
pakchunk1-WindowsNoEditor_0_P.pak
- ...
So even though the filename of the two mods is identical, the one in aaa_mods
would get loaded before zzz_mods
, because aaa_mods
comes before pakchunk*
,
and zzz_mods
comes after pakchunk*
.
By convention, UE4 pakfile modders seem to recommend using a mods directory
named ~mods
, because the tilde sign (~
) sorts after practically everything
else (at least for regular ASCII text). That way mods will get loaded at
the end.
You'd still need to be aware of the patching behavior, though, if your pakfile is attempting to overwrite any base-game file/object. If your pakfile only consists of entirely-new files/objects, though, you should be able to use basically any filename you want.
As a short and sweet summary, here's what we recommdend for pakfile mod naming/placement:
- Create a directory alongside the base-game pakfiles named
~mods
. Store all your pakfile mods in there. - Name your pakfile mods like:
Mod_Description_999999_P.pak
. The exact number is up to you -- really any number above 30 or so is likely to be safe essentially forever. The_P
at the end is definitely important, though, so that the pakfile gets loaded after all the base-game stuff. - If there are any load-order dependencies between mods, make sure to coordinate the mods so that their load order works properly given the rules above.
That's basically it!