-
Notifications
You must be signed in to change notification settings - Fork 5
Mod system overview
This section details the design of the modding system in Phantom Brigade and is intended for mod developers interested in the internal workings of the game. We recommend using the tools provided with the SDK for most mods: manual file editing should ideally be the last resort option.
Due to data-driven structure with exposed config files, most of the Phantom Brigade can be easily modified. It's possible to alter scenarios, rebalance equipment, add new units or localization, etc. by changing the config files loaded by the game. However, we do not recommend directly modifying the files in the install directory for a number of reasons:
- Modifying content of the application directory is invisible to our bug reporter system and risks mods being reverted when the game is updated or validated. It also locks modding on user accounts with limited writing permissions.
- It is not possible to install multiple mods altering the same config when directly editing the game
- It is not possible to modify game logic
- And a whole host of other issues (inability to control mod order, the need to duplicate a bunch of data unrelated to mod intent etc.)
Our modding system is an attempt to solve all these issues. It closely follows the design of the mod systems in other popular moddable Unity games. The core idea is to have a separate folder holding configs and additional content and sideloading that separate data on top of built-in game data. This allows for easy installation, sorting, combining multiple mods etc. This folder is in the same location as save files and game settings: C:/Users/Windows Username/AppData/Local/PhantomBrigade/Mods .
If you can't find the folder, Windows Explorer settings might be set to hide it on your PC. Please refer to the following web page for details. View hidden files and folders in Windows - Microsoft Support. Alternatively, you can press the "Local mods" button in the Mods tab of the main menu to open the folder.

Each mod gets a separate subfolder, e.g. Mods/MyMod1, Mods/MyMod2 and so on:

In each mod folder, there should be a metadata.yaml file that provides some essential information about content of the mod to the game, allowing for loading it and for drawing of mod management UI
- Identifier (unique internal name for the mod used to confirm the game isn't loading multiple copies of the same mod, used to store settings etc.)
- Version
- Loading priority (which mods load first)
- Included types of content (more on that later)
- Compatible versions of the game (e.g.
gameVersionMin: 2.0is required for PB 2.0 to recognize and load any mods) - Name & description
Here's an example of a metadata.yaml file featuring all the fields mentioned above:

The game scans the Mods folder on startup. It starts by attempting to read metadata.yaml. From there, it attempts to load content declared by the metadata. For example, if the metadata says the mod includes config overrides, the game will attempt to find the ConfigOverrides folder and load its contents. If a mod encounters an error (e.g. version incompatibility, mismatch between metadata and content or bad data), it will be recorded to make debugging easier.
You can see the list of loaded mods in the top right of the main menu and in the Mods window available through the main menu. The Mods window lets you inspect errors that might prevent a mod from loading, inspect the metadata and optionally disable mods. By default, the game considers all Workshop subscriptions and all folders under the Mods folder to be active, but disabling them through the Mods menu is a convenient way to manage them without needing to unsubscribe or delete anything.

If you install, change, disable or re-enable a mod while the game is running, you will need to restart the game to see the effects.
Mods can include different content types. These are represented by subfolders in the mod folder and their presence is declared in metadata.yaml. The set of supported content types has expanded over time to include configs, localizations, images and more.

- Metadata flag:
includesConfigOverrides - Folder:
ConfigOverrides/
This is the simplest possible type of mod content, quite similar to editing Configs folder directly.
- Duplicates parts of Configs folder hierarchy under Mods/[ModName]/ConfigOverrides and you can replace anything or add new files.
- The files you place there have to be modified copies of .yaml configs from the game, with exact same internal layout
- The changes are injected whenever database is reloaded, not just on game initialization, so they are resistant to databases loading late, databases loading multiple times etc.
- For instance, you can put new weapon at
Mods/[ModName]/ConfigOverrides/DataDecomposed/Equipment/Part_Presets/my_new_weapon.yamland the game will load it - Another example: you can put your own version of global settings config at
Mods/[ModName]/ConfigOverrides/Data/Settings/simulation.yamland it will be the version of global settings game will start using - Limitation: Only contents of Data and DataDecomposed folders are supported. We have some other files under Configs that can't be modified with this mod content type for the time being. Applying a mod with config overrides is not a disk operation modifying files in PB/Configs directly; it's an injection operation during loading steps of
DataLinkerandDataMultiLinkerdata managers. The files that can be overwritten, however, are what you'd usually want to modify anyway - other files are a small minority and aren't a good fit for modding (some old binary files, unused configs predating current database system etc).
Refer to the Config modifications to learn about making this type of modification with the SDK.

- Metadata flag:
includesConfigOverrides - Folder:
ConfigOverrides/
This content type allows for much more fine grained modifications resistant to multiple mods modifying the same file, file changing shape when game is updated etc.
- On the surface it looks similar to config overrides: duplicate parts of Configs folder hierarchy under
Mods/[ModName]/ConfigEditsand put your .yaml matching path/name of existing - However, the content of these files is different. Internally, these files have a different structure. Instead of duplicating real config of the same name at the same path, they store a list of changes. The edit config allows you request removal or, what's more exciting, allow you to edit individual fields and collection entries on a config of the same name.
- This is a big game changer for config mods. One of the big pain points in modding is conflicts between different mods modifying same file and the need to constantly update the modified copy of extracted file every time developers update a game. For instance, our
simulation.yamlcontains hundreds of parameters, and many different mods will definitely want to modify it, whether it's to change allowed squad size of physics settings or loot generation multipliers. - Ordinarily, the changes have to be merged manually and turned into a separate compilation mod - there is no other way to let multiple mods changing the same file coexist. With this feature, one mod can have a single line
simulation.yamlfile modifying a line calledsquadSizeand another can havesimulation.yamldescribing changes to couple other fields - and every change will be seamlessly merged. It's as if every single collection entry or field in our configs is a separate .yaml or a separate folder, allowing for very targeted changes. - Another good example is a request to change squad size frequently popping up in our modding channel. Without this feature, it requires maintaining full copies of every single scenario in the game and a copy of global simulation settings file, which is not nice and blocks an uncountable number of other possible mods related to these files. With the config edit feature, squad size mod will not conflict with much else and requires no updates when we edit simulation config or scenarios in a new release.
The format of these edit configs is relatively simple. The main limitation is that a path needs to be entered for every individual edit. There is no support for multiline edits such as declaring a whole object at once. Key elements of each ConfigEdit config:
- Set
removedtotrueto remove targeted config from the game (for instance, if your mod replaces parts of the arsenal and needs to remove built-in weapons from the game) - Fill
editsfield to perform fine grained changes in targeted config- Each line must begin with
-, just like any serializedList<T>in other configs. - Each line must be wrapped in
'quotation marks, like so:- 'hidden: true'. This enables you to use reserved characters anywhere within the line. - Each edit consists of two sides separated by a colon
:- left side is a path, and right side is a value- For top level fields, the path is just the field name. For instance, to modify a top level field named
squadSizeto a value of3you can just type:- 'squadSize: 3'. - Some fields are nested within other fields - to reach them, include all steps into the path, separated by full stops
.- for instance, here is a modification to a field nested 1 level deep:- 'customPlayerSlot.spawnTagsUsed: true'. - You can even access collections as if keys in dictionaries or indexes in lists were field names, like so:
- 'states.no_hostiles.visible: false'(no_hostilesis a dictionary key here);unitChecks.0.value: 1(0is a list index here!)
- For top level fields, the path is just the field name. For instance, to modify a top level field named
- You can repeat edits with same path multiple times, since the
editsfield is not a dictionary. For instance, it's totally fine to modify atagscollection on some config (e.g. removing something) and then modify it again further down (e.g. to add something)
- Each line must begin with
- The set of value types that can be parsed is for now limited to the following set:
-
string,bool,int,float,Vector2,Vector3,Vector4 - For string fields, right-side value will be used as is
- For bools, it will be parsed using equality to
true - For floats and integers, refer to expected string format in C# documentation for
int.TryParseandfloat.TryParse. An example would be6,8.62etc. - For vectors, the expected format is a correct number of floats in expected float format, separated by commas and wrapped by parentheses. An example would be
(3.1, 4, 7.2)etc. - However, not every case requires parsing. For instance, what if all you want to do is fill a field with a default value, e.g. fill a reference field with a fresh instance of its type? There is a special reserved keyword for doing so,
!d(for “default”). Use it like so:- 'customExitBehaviour: !d'
-
For normal fields, the following operations are supported:
- Overwriting a value with specified new value
-
- 'field: 7'- overwrite field named fieldwith value of 7
-
- For
List<T>, the following operations are supported:- Overwriting a value with specified new value
-
- 'list.4: 7'- overwrite index 4 in list named list with a value of 7
-
- Adding a new value at specified index:
-
- 'list.3: !+'- create a new object (whatever the type may be) and add it to the list at index 3 -
- 'list.0: 7 !+'- insert value of 7 to the very beginning of the list -
- 'list.8: 7 !+'- insert value of 7 to index 8 (if list has more entries) or just to the very end of the list (if list has fewer entries than 8)
-
- Removing a specified index:
-
- 'list.3: !-'- remove entry at index 3
-
- Overwriting a value with specified new value
- For
HashSet<string>, the following operations are supported:- Adding a value:
-
- 'tags: context_facility !+'- attempt to insert the stringcontext_facilityto the hash set tags
-
- Removing a value:
-
- 'tags: context_fort !-'- attempt to remove the stringcontext_fortfrom the hash set
-
- Adding a value:
- For
Dictionary<T>, the following operations are supported:- Overwriting a value with specified new value
-
- 'dictionary.existing_key: true'- overwrite value at keyexisting_keyin dictionary named dictionary with value oftrue
-
- Adding a new entry:
-
- 'dictionary.new_key: !+'- create a new object (whatever the type may be) and try to insert it to the dictionary at keynew_key -
- 'dictionary.new_key: true !+'- attempt to insert value of true with new keynew_keyinto the dictionary
-
- Removing an entry:
-
- 'dictionary.existing_key: !-'- attempt to remove the entry with key withexisting_key
-
- Overwriting a value with specified new value
Full set of supported operation tags:
-
!+- add new entry -
!-- remove entry -
!d- set to default value (for reference types - a new instance, not null) -
!n- set to null
The set of supported operations is subject to change. However, if you encounter a case warranting large changes to the config edit system (e.g. if you need ability to parse complex data types), consider trying to implement what you're after using libraries, not config edits.
Refer to the Config editing system to learn about making this type of modification with the SDK.

- Metadata flag:
includesLibraries - Folder:
Libraries/
This is a very powerful type of mod content enabled by C#/Unity's reliance on editable interpreted language (IL): ability to load libraries that can patch game logic and execute new code.
- This feature is partially enabled by a popular library called Harmony - we've already been using it, but for internal needs, like patching Unity Editor UI
- It is recommended that you read Harmony documentation if you're a programmer interested in creating a library mod.
- The mod system tries to discover all assemblies in Libraries folder, like
Mods/[ModName]/Libraries/MyLibrary.dll - It then fetches types from that assembly and tries to find a class inheriting from special type
ModLink- if found, it's instantiated, filled with data, and has a special entry method invoked -
ModLinkprovides an entry point to modders: you can declare an override toOnLoadmethod in it, executing arbitrary code; you get access to full mod metadata (so you know where you are, what mod you're in, what other mods are loaded) and access to the Harmony patcher object, allowing you to modify code in PB.

General setup of a new library goes as follows:
- Create a .NET Class Library (.dll) project in Visual Studio Community, Rider or other .NET IDE, targeting .NET Framework 4.7.2
- Add the following references to it from your install folder (e.g.
SteamGames/Phantom Brigade/PhantomBrigade_Data/Managed/):0Harmony.dllAssembly-CSharp.dllAssembly-CSharp-firstpass.dllEntitas.dllSystem.dllUnityEngine.dll
- Create a
ModLink.csfile - In it, wrap everything in your own custom namespace, ideally matching the
idfield in yourmetadata.yaml, e.g.ModTest - Declare a class inheriting from type
ModLink, e.g. public classModTestLink : ModLink- In that class, declare an override method OnLoad with this signature:
public override void OnLoad (Harmony harmonyInstance) - If needed, implement any custom logic originating from this starting point. For instance, you have access to
metadatafield that can tell you what folder you're working with, what mod is this library loaded in, etc; accessModManager.loadedModsto interact with other mods, access any game classes, perform custom patching procedures usingHarmonyobject passed into the method etc.
- In that class, declare an override method OnLoad with this signature:
- Declare a plain class called
Patches- Declare any number of subclasses annotated with Harmony patch attributes within that class, for example:
[HarmonyPatch (typeof (PhantomBrigade.GameController), MethodType.Normal), HarmonyPatch ("Initialize")] - All of these patches will be applied automatically unless you declare
OnLoadoverride and comment outbase.OnLoad (harmonyInstance)call in it.
- Declare any number of subclasses annotated with Harmony patch attributes within that class, for example:
- Build the .dll, place it in Libraries folder of your mod
- You are free to get into the logic setting up scenario units and changing how they are generated. You are also free to override how post-deserialization code in some data container works, changing how weapon efficiency is explained to AI, you are free to prefix, postfix or entirely replace any method you want modified - sky's the limit. Most mods changing logic or implementing new systems in Unity games such as Rimworld, Oxygen not Included, KSP and others operate on a similar principle.
Refer to the Including libraries to learn about making this type of modification with the SDK.
- Metadata flag:
includesTextures - Folder:
Textures/
This content type allows you to overwrite existing textures or extend the set of available textures:
- Place your textures under under Mods/[ModName]/Textures.
- If the folder is not recognized by the game, it will be skipped (see PhantomBrigade_Data/StreamingAssets/UI to see what folders the game knows about and samples of content in them)
- If a texture doesn't match the requirements game has defined for a given folder, it will be skipped (inspect existing textures in StreamingAssets to determine the required format)
- If a texture with the same name was already loaded (for a given folder), it will be replaced. If no such name existed before, new texture will be registered under it.
Refer to the Including textures to learn about making this type of modification with the SDK.
Work in progress
Refer to the Including textures to learn about making this type of modification with the SDK.
- Metadata flag:
includesLocalizations - Folder:
Localizations/
This content type allows you to add entire new localizations to the game. General principle is as follows:
- Copy official English folder from game installation folder
Configs/TextintoMyMod/Localizations, rename it to the language you are localizing the game into, e.g.MyMod/Localizaions/French - Translate all text in the included files
- If a mod with localization is loaded, game settings window will have a new Language setting that will let you switch to localization language
Some additional notes:
- To test coverage of the localization system, there is a console command
data.toggle-localization-debug, which will render all localized text in a different color and randomize its content - To determine which part of a game uses which portion of localization database, it's recommended to use IL inspection tools like ILSpy or dotPeek, open
Assembly-CSharpfrom game install folder and look for methods namedResolveTextin files inheriting fromDataContainerclass. Majority of databases in the game resolve localized text in such methods, and reading them will allow you to learn how localization keys are generated and used from data keys. This can also be useful for localization editing mods discussed above.
Keep an eye out on game log - it will contain plenty of information from mod manager in case anything goes wrong, or confirmation of successful loading if everything goes well. Look for messages towards very beginning of the log, prefixed with “Mod Manager”. To look at the log, press Ctrl+Shift+F11 or open the log file in application data folder.
It might also be useful to enable developerMode: true property in debug.yaml in AppData/Local/PhantomBrigade/Settings to get access to additional options, debug information and console commands while developing a mod.
Note that no bug reports or crashes from a game in developer mode or with active mods will be processed by BYG.
This wiki is a work in progress. Please make sure to check the built-in tutorials within the SDK or the modding articles on the game wiki to supplement this page. If you get stuck or experience a bug, please don't hesitate to ask questions in the #phantom-modding channel of the official Discord server. We can't wait to see what you create!