-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Anatomy of a Mod File
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
- What Borderlands Sees
- How OpenBLCMM stores hotfix placement
- How FilterTool stored hotfix placement
- Online vs. Offline
- Using multiple mod files
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 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:
- 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, aset
statement will not work, and you'll have to use a hotfix instead. - 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 ofGD_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.
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-* |
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.
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.
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.
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.
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.
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).