Skip to content

Creating library mods

Artyom Zuev edited this page Dec 20, 2025 · 15 revisions

Custom libraries are an very powerful tool that can facilitate nearly any modification to Phantom Brigade. The game is powered by Unity/C# and reads IL (interpreted language) at runtime. The IL code is modifiable at runtime, which means that it's possible to patch game logic and execute new code from mods. This tutorial will cover the full process of creating a library mod, from setting up the project to building and exporting your mod.

Dependencies

We strongly recommend reading through the following articles before you begin:

You'll need an IDE software capable of creating and compiling solutions that are .NET 4.7.2 Class Libraries. We strongly recommend using JetBrains Rider (even if you're familiar with other IDEs). It's free for non-commercial use and features a built-in decompiler letting you explore C#/IL code in the Unity and Phantom Brigade assemblies, which is essential when developing mods.

Repository setup

First, we'll need to create a new Git repository on GitHub. It will help you keep track of the changes and give you a backup if things go south. The repository can start private but will have to be made public once you publish your mod to comply with the Modding Guidelines.

Start by following a the Quickstart for repositories tutorial from GitHub. We recommend the following settings:

You should end up with a ready to use project that looks like this, with some files already in place (like README.md and .gitignore):

Local repository

Next, we'll need to clone the project to your machine using Git. Choose an easily accessible folder close to disk root. For this tutorial, I'm going to use D:/Work/UnityProjects/PB_LibraryTutorial.

If you're not familiar with Git and are unsure how to download a project from GitHub, try installing the GitHub Desktop client and following this tutorial. You should end up with folder contents that match what you saw in the browser:

Create a subfolder with the desired mod ID within the root folder. The repository is intended to be used with the External projects feature of the SDK. Mod config and other files will be located under a subfolder to allow using the root as a target for Custom project folders in the mod project manager config. For this tutorial, I'll use PB_Library as the mod ID and solution name. This setup will also allow you to reuse the same repository for several mods:

Creating a solution

Start Rider and select File > New Solution. Select the following settings:

  • Project type: Class Library
  • Solution name & Project name: Same as the mod subfolder
  • Solution directory: Mod subfolder (e.g. D:/Work/UnityProjects/PB_LibraryTutorial/PB_Library in our case)
  • Put solution and project in the same directory: On
  • Create Git repository: Off
  • Target framework: v4.7.2
  • Language: C#
  • Type: Class Library
image

Confirm the project. You should end up with file hierarchy like this (solution folder within the mod folder within the root folder):

image

Solution setup

At the start, your solution will look like this in Rider:

image

Rename (right click in Solution tab, Edit > Rename) the Class1 .cs file you start with into ModLinkCustom and paste the following code into it:

using UnityEngine;
using HarmonyLib;
using PhantomBrigade.Mods;

namespace ModExtensions
{
    public class ModLinkCustom : ModLink
    {
        public static ModLinkCustom ins;
        
        public override void OnLoadStart()
        {
            ins = this;
            Debug.Log ($"OnLoadStart");
        }

        public override void OnLoad (Harmony harmonyInstance)
        {
            base.OnLoad (harmonyInstance);
            Debug.Log ($"OnLoad | Mod: {modID} | Index: {modIndexPreload} | Path: {modPath}");
        }
    }
}

You will see a lot of errors, which is normal at this stage:

This class is important for the modding system, and it can additionally serve as a quick test bed for confirming we have set up the project correctly. For as long as this example displays errors, the project is not fully set up. What's missing is various dependencies: external .dll files needed to make sense of your code. Let's add them.

Open Dependencies > Assemblies in your Solution tab, right click the header, select Reference, then Add From... and navigate to the installation folder for Phantom Brigade. Assuming it is installed through Steam, the path would be something like D:/Steam/steamapps/common/Phantom Brigade. In there, navigate to phantombrigade_Data/Managed/ subfolder. Pick the following .dll files from that folder:

  • 0Harmony.dll (code injection library)
  • Assembly-CSharp.dll (main game assembly)
  • Assembly-Csharp-firstpass.dll (plugins)
  • Entitas.dll (entity component system)
  • UnityEngine.dll (main engine assembly)
  • UnityEngine.CoreModule.dll (supplemental engine assembly with debug classes)
  • UnityUtilities.dll (secondary assets & utilities)

If you added everything correctly, you should see a list like this under your Solution tab, and the ModLinkCustom class should now compile without any errors:

image

Now that the project has no compilation errors and all dependencies are in place, make sure to commit your changes to the repository. If you are a beginner with Git and aren't sure how to upload your changes, follow this tutorial from GitHub (assuming you're using GitHub Desktop as your Git client).

Side note: Having solution files not pushed to the Git repository can be the reason why Rider highlights your Solution tab in red, even if you do not have any remaining compilation errors. The red highlights should disappear as soon as you upload your solution files alongside ModLinkCustom.cs.

Simple patch

Next, let's create a simple patch class to try out code injection with Harmony. Start by right clicking the solution and select Add > Class/Interface:

image

Let's choose a name pattern that'll let us keep creating classes of the same type, for example PatchesTests. Future classes can follow the same pattern and get names like PatchesOverworldUI, PatchesDamageLogic, PatchesPilotEconomy etc.

image

If Rider offers to add a file to Git for you, we recommend to decline and tick the "Do not ask again" checkbox. You are likely to be using a standalone Git client so Rider probably shouldn't make Git changes on your behalf. Once inside the new class, try filling in the following snippet.

Deciding what to patch, whether to use a Prefix patch or Postfix patch and deciding on a specific implementation of each patch is very context specific. There is no tutorial that can cover all possible cases, but what we can do is provide a few working examples and set you up with tools making it easier to find answers for a particular case. Let's start with a simple idea: tapping into a method refreshing the "active mods" label in the top right corner of the main menu, and appending some text at the end:

We'll cover where to look in a bit more detail further down, but for now, let's assume we already know that the class responsible for this label is called CIViewPauseFooter. This is where decompilation capabilities built into Rider (or standalone decompilation tools like ILSpy, if you're using another IDE) come in handy. We can browse through the code, find the method that interests us and learn how to approach patching it. Let's start with a simple patch class pointing to CIViewPauseFooter and fill in the rest a bit later:

using HarmonyLib;

namespace PB_Library
{
    [HarmonyPatch] // Tells Harmony this class contains patches
    public class PatchesTest
    {
        // HarmonyPatch (Type, string) shows what class and what method within that class to modify
        [HarmonyPatch (typeof (CIViewPauseFooter), "PatchedMethod")]
        public static void PatchContent ()
        {
        }
    }
}

With that in place, let's select CIViewPauseFooter you typed into the HarmonyPatch attribute and press F12 (navigate to definition). You should see the decompiler progress bar pop up and should see the class code open shortly after:

image

The code responsible for displaying the list of mods probably has the word "mods" somewhere in it. Searching for it reveals the method responsible (RefreshModDisplay) and the UI object we need to access to modify the text (text labelMods).

image

This is perfect material for your first patch, with the method and the field that we need to access both public. Private methods and fields are possible to access, but this involves additional steps best left for later patches, once you're confident with the fundamentals. Knowing the targets, we can finish our patch class:

    [HarmonyPatch]
    public class PatchesTest
    {
        // HarmonyPatch (Type, string) shows what class and what method within that class to modify
        // HarmonyPostfix indicates that code must be added after existing code
        [HarmonyPatch (typeof (CIViewPauseFooter), "RefreshModDisplay")]
        [HarmonyPostfix]
        public static void RefreshModDisplay_Postfix (CIViewPauseFooter __instance)
        {
            // __instance is a magic argument initialized by Harmony
            // If this is a local method, it'll reference the instance running the patched method
            if (__instance == null || __instance.labelMods == null)
                return;
            
            // Grab label from CIViewPauseFooter instance and modify it
            var label = __instance.labelMods;
            label.text += "\n\nMy patch is working!";
        }
    }

If you are set on modifying a private method and/or private property, this will involve Reflection. If you're new to C# or haven't interacted with the Reflection feature before, check out Introduction to Reflection.

Building

With all of the above done, we can move to building the library and testing the project. But first, do not forget to commit all your changes to Git! If everything was set up correctly, this step should be very simple. Click the v next to the Build button (hammer icon) and select Release configuration. Then click the Build button.

image

If the build succeeds, you should see something like this in the Build Output window, including the path to the generated .dll:

image

Setting up mod project

This tutorial assumes you are using the Mod SDK and takes an opportunity to show how to use the external projects and file links in the Mod SDK to help manage code mods. If you are putting together your mod without the SDK and already have a mod folder with metadata.yaml, you can skip this chapter, create Libraries folder and paste the .dll you built into that folder.

It's time to make the mod using the SDK. First, create a new mod with the ID we selected earlier (subfolder name). In our case, that's PB_Library. Fill in some metadata if needed, but we'll circle back to the config later. Save it and open the default mod project folder by clicking the folder icon above the metadata editor:

image

Once in the folder, select metadata.yaml and project.yaml and copy them. Alternatively, you can skip the whole step with creating a new mod in the SDK and copy project.yaml and metadata.yaml from another mod: just make sure to edit project.yaml to set all flags to false and make sure to edit metadata.yaml to set id to your mod ID (PB_Library).

image

Then, paste them into the subfolder above the solution in our local Git repository (and delete the original temporary mod under the default ModsSource folder in AppData):

image

With the mod project files now residing in a new location, tell the SDK to scan for mod projects in an additional folder. Open the Settings foldout on top of the Mod Manager, expand Custom Project Folders, add a new folder with + then select your root folder (above PB_Library) as a target:

image

Click "Save Settings", then "Load All" to refresh the list of loaded mods. If the old PB_Library from ModsSource persists in the list, recompile or restart the Editor. Your externally stored PB_Library mod should be in the list and should show the external path above the metadata:

image

Next, it's time to show the Mod SDK where the built library .dll file is, so that it gets copied into an appropriate folder when the mod is exported. Start by adding the DLL data block at the bottom of the mod project editor:

image

Add a file entry, set it to Relative mode, click the folder icon to open the file selector window and navigate to PB_Library/obj/Release to find the PB_Library.dll:

image

You should end up with something that looks like this (don't forget to save the mod project and commit the changes to Git!) It's a fairly robust With the saved path being relative to the project root and with the mod project files being stored under your repository root, your mod project can be cloned to any path and will continue working no matter where you download it.

image

Time to click Export to user. If the library path is correct, you will see this in the export folder:

image

Ingame test

After booting up the game, it looks like the patch is working!

image

If you hit an error, there are a few things you can use to troubleshoot the issue:

  • Watch out for warnings displayed by the mod system on game startup
  • Check the Mods screen in the game to see if your mod has an exclamation mark above. If it does, it has errors and has been skipped. Hover over the warning icon to see the error details in the tooltip and use RMB/LMB to scroll through error messages if there are multiple errors.
  • Check the in-game console for warnings and errors. It can be opened in developer mode using Shift+Ctrl+F11.
  • Finally, check the game log files in case the issue can not be seen anywhere else. If you still have nothing to go on, try adding logging to your ModLink class and your patches to try and narrow down the part that doesn't work

Additional notes

You can find the source files for this tutorial in this repository: PB_LibraryTutorial. Feel free to modify that example as you see fit (just don't forget to alter the mod ID and folder name to ensure your mod can be loaded!).

If you're interested in a more advanced example with more types of patches (e.g. overriding entire methods or accessing private methods and properties), check out the PB_ModExtensions repository.

Clone this wiki locally