Skip to content

Anatomy of a Mod File

CJ Kucera edited this page May 12, 2023 · 7 revisions

Most of what's in this page isn't something that you actually need to know about when writing mods (and it's entirely extraneous if you're only using mods), but it may be of interest to folks who want to know how mod files work, and how hotfixes interact with "regular" commands, etc.

Set versus Hotfix

This is well-covered elsewhere, but it may be worth reiterating here. There are two kinds of statements which make up Borderlands mods: set statements, and hotfixes.

"set" statements

set statements are the most basic and easy-to-understand of the modding statements. They take an object name, a property name, and a new value to set that property to. For instance, the following statement in a mod file would set the game to the four-player difficulty scaling:

set WillowCoopGameInfo NumPlayers 4

The object name is WillowCoopGameInfo, the property name is NumPlayers, and the new value is 4.

These statements, while simple, have a couple of drawbacks:

  1. They can only modify objects which are available from the main menu of the game. If you can't do an obj dump <objectname> from the main menu and see the data you want to change, a set statement will not work, and you'll have to use a hotfix instead.
  2. They can also only modify an entire property. For the above example, the property was only a single number so it hardly matters, but some properties are gigantic. If you want to change the main enemy loot drop pool, for instance (the BalancedItems property of GD_Itempools.GeneralItemPools.Pool_GunsAndGear), you'll be looking at a statement whose value approaches 4000 characters long. If you're only looking to modify a small part of that property, such as one of the probability weights, using a hotfix may be simpler.

Hotfixes

As covered elsewhere, hotfixes come in three flavors: Patch, OnDemand, and Level. "OnDemand" hotfixes get applied when a specific class gets loaded, are usually used to edit character information. "Level" hotfixes get applied when a level is loaded. "Patch" hotfixes are pretty much never used by mods, and are only really seen in official Gearbox patches.

Taken from the Hotfix tutorial, the data portion of a hotfix looks like so:

Patch:
"<object>,<path>,<old_value>,<value>"

On Demand:
"<package>,<object>,<path>,<old_value>,<value>"

Level:
"<level>,<object>,<path>,<old_value>,<value>"

For OnDemand and Level hotfixes, the "package" and "level" values are optional, and on all of them the "old_value" values are optional.

So our above set example could be written with a Patch hotfix like so:

WillowCoopGameInfo,NumPlayers,,4

Or a Level hotfix like so:

,WillowCoopGameInfo,NumPlayers,,4

One of the advantages of a hotfix is that we can modify a single specific part of a property rather than redefining the whole thing. Using GD_Itempools.GeneralItemPools.Pool_GunsAndGear as an example, if we wanted to double the weight of weapon drops in that pool, we could use this Level hotfix (the default value we're changing is 1):

,GD_Itempools.GeneralItemPools.Pool_GunsAndGear,BalancedItems[0].Probability.BaseValueScaleConstant,,2

Hotfixes also have an associated name, which must be unique for each hotfix, and there is a necessary prefix depending on the hotfix type:

Hotfix Type Name Format
Patch SparkPatchEntry-*
OnDemand SparkOnDemandPatchEntry-*
Level SparkLevelPatchEntry-*

What Borderlands Sees

When you use exec <filename> from the Borderlands console, the engine will open the specified filename and attempt to execute each line in the patch file. One important thing to know here is that pretty much the only useful command that Borderlands can process at this point are set commands. If you take a look at an OpenBLCMM patch file, you'll see a bunch of stuff that looks like XML. For example, you may see a patch file which contains something like:

<category name="Game Tweaks">
    <category name="Emulate Four-Player Difficulty">
        <comment># Make the game think we're playing co-op</comment>
        <code profiles="default">set WillowCoopGameInfo NumPlayers 4</code>
    </category>
</category>

#Commands:
set WillowCoopGameInfo NumPlayers 4

If you're looking at an old-style FilterTool-saved file, it would look like:

#<Game Tweaks>

    #<Emulate Four-Player Difficulty>

        # Make the game think we're playing co-op

        set WillowCoopGameInfo NumPlayers 4

    #</Emulate Four-Player Difficulty>

#</Game Tweaks>

All the extra XML-like stuff around the set command are basically ignored by Borderlands (most likely it's technically attempting to run the XMLish lines as command but failing, since they're not valid commands). Likewise, comments like the one shown above get ignored/failed as well. (Using a # mark in front of comments is entirely optional, and many mod authors don't bother with it.) For OpenBLCMM files, all of the commands that actually get run are appended at the very bottom of the file, whereas FT-style files have them inline with all the category information.

Wait, what about hotfixes?

If Borderlands only really understands set commands, how do hotfixes get into the system? This is where a tool like OpenBLCMM is extraordinarily helpful. To actually register a hotfix with Borderlands, there are two special set commands which happen, and you'll see these at the very end of the UCP patch file, for instance. If we use our hotfix example from above and say that its name is SparkLevelPatchEntry-FourPlayerScaling1, the statements you see will look like:

set Transient.SparkServiceConfiguration_6 Keys ("SparkLevelPatchEntry-FourPlayerScaling1")

set Transient.SparkServiceConfiguration_6 Values (",WillowCoopGameInfo,NumPlayers,,4")

So the name of the hotfix ends up in Transient.SparkServiceConfiguration_6, in the Keys property, and the hotfix data itself ends up in the Values property.

So what happens when we have more than one hotfix? Essentially you'd end up concatenating all the keys and values within those two set statements, like so:

set Transient.SparkServiceConfiguration_6 Keys ("SparkLevelPatchEntry-FourPlayerScaling1", "SparkLevelPatchEntry-BetterWeaponChance1")

set Transient.SparkServiceConfiguration_6 Values (",WillowCoopGameInfo,NumPlayers,,4", ",GD_Itempools.GeneralItemPools.Pool_GunsAndGear,BalancedItems[0].Probability.BaseValueScaleConstant,,2")

As you can probably imagine, these arrays can become quite unwieldy. OpenBLCMM takes care of dealing with all those details for you, so you never have to set anything in Transient.SparkServiceConfiguration_6 yourself. If you take a look at the last few statements of UCP's Patch.txt, you'll see how useful it is.

How OpenBLCMM stores hotfix placement

OpenBLCMM is actually extremely straightforward about things, since hotfixes are handled in much the same way that regular set commands are. A hotfix in OpenBLCMM would look something like:

<hotfix name="FourPlayerScaling" level="None">
    <code profiles="default">set WillowCoopGameInfo NumPlayers 4</code>
</hotfix>

... and then at the very bottom of the file:

set Transient.SparkServiceConfiguration_6 Keys ("SparkLevelPatchEntry-FourPlayerScaling1")
set Transient.SparkServiceConfiguration_6 Values (",WillowCoopGameInfo,NumPlayers,,4")

As mentioned above, only the bottom set statements actually do anything.

How FilterTool stored hotfix placement

FilterTool kept track of where the hotfixes are defined up in the main XMLish area of the patch file. A full patch file with a hotfix might look like this:

#<Game Tweaks>

    #<Emulate Four-Player Difficulty>

        #<hotfix><key>"SparkLevelPatchEntry-FourPlayerScaling1"</key><value>",WillowCoopGameInfo,NumPlayers,,4"</value><on>

    #</Emulate Four-Player Difficulty>

#</Game Tweaks>

set Transient.SparkServiceConfiguration_6 Keys ("SparkLevelPatchEntry-FourPlayerScaling1")

set Transient.SparkServiceConfiguration_6 Values (",WillowCoopGameInfo,NumPlayers,,4")

As with any other non-set line, that XMLish hotfix definition at the top is completely ignored by Borderlands, and it's just there to help FT keep track of where the hotfix "lives". If you were to toggle a hotfix off in the FT GUI, the <on> at the end of that line would switch to <off>, and the key/value entries in the set statements at the bottom would be altered to remove the disabled hotfix, which is what would acutally disable the hotfix.

Online vs. Offline

The above examples of hotfix configuration in patch files shows the format of "online" Patch files, intended to be used with Borderlands sessions which talk over the internet to Gearbox. There's an alternate version of the end-file set commands which are used for "Offline" sessions, because they don't support the same thing. An end-of-file stanza on an offline patch file will look like this, and includes more than just the two set commands:

set Transient.SparkServiceConfiguration_0 ServiceName Micropatch

set Transient.SparkServiceConfiguration_0 ConfigurationGroup Default

set Transient.SparkServiceConfiguration_0 Keys ("KeyName1",...)

set Transient.SparkServiceConfiguration_0 Values ("ValueData1",...)

set Transient.GearboxAccountData_1 Services (Transient.SparkServiceConfiguration_0)

So, very nearly the same thing, but with a slightly different object name, and a few extra statements surrounding the hotfix data.

Using multiple mod files

Hotfixes are one of the main reasons why using multiple mods in different files isn't recommended. If you have two mod files with hotfixes, you're going to have two mod files which have the same set Transient.SparkServiceConfiguration_6 statements at the end. Even though the hotfixes appear in the main body of the mods, the only actual statement that Borderlands cares about is those end set statements. So in this case, the second mod file's hotfixes would completely overwrite the first mod file's hotfixes.

Again, if you use OpenBLCMM to manage multiple mods, all of this is taken care of for you. If for whatever reason you don't want to use that tool, you'd have to manually merge in those set statements to include hotfixes from all your mods (while making sure that the hotfix keys remain unique).

Clone this wiki locally