Pakfile Modding - BLCM/BLCMods GitHub Wiki

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.

Pakfile Modding Tools

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.

UnrealPak.exe

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.

UAssetAPI/UAssetGUI

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.

Standard Pakfile Patching

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.

Pakfile Load Order

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.

Recommended Pakfile Mod Layout

As a short and sweet summary, here's what we recommdend for pakfile mod naming/placement:

  1. Create a directory alongside the base-game pakfiles named ~mods. Store all your pakfile mods in there.
  2. 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.
  3. 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!