BepInPlugins

From Rain World Modding
Jump to navigation Jump to search

BepInPlugins are the predominant form for Rain World code mods.

They are a form of game mod native to BepInEx. They are not compatible with Partiality.

Creating a BepInPlugin

Prerequisites

Before you will be able to make a mod, you need the following:

  • Beginner knowledge of C#, such that you are comfortable with your current level of skill.
    • If you understand the concept of object oriented programming as well as main C# elements such as methods, fields, properties, events, and delegates, you should be set to go.
    • Unity knowledge is optional, but useful for advanced modding. Unlike other BepInEx-modded games, Rain World's ties into Unity are sparse due to the use of the Futile engine wrapper, thus the knowledge is not required.
  • Entry-level skills in reverse engineering, using tools like dnSpy (or any other .NET decompiler of your choosing).
  • A .NET programming environment. You can view feasible options here: https://dotnet.microsoft.com/en-us/platform/tools
    • Windows users will want Visual Studio (not Visual Studio Code, they are similar but not the same!)
  • .NET Framework 4.8 Developer Pack (not runtime!) which you can download here: https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48

Step 1 - The project

The Class Library (.NET framework) template is distinct from the Class Library template in Visual Studio 2022.
The Class Library (.NET framework) template is distinct from the Class Library template in Visual Studio 2022.

Create a new C# project with the Class Library (.NET Framework) template in your IDE. You will then need to add some references.

It's recommended that you copy the files you need to reference to a safe location outside of the Rain World root directory before referencing them. Assuming you already have BepInEx installed and ready to go, the files (relative to the Rain World root directory) you should reference are:

  • BepInEx/core/BepInEx.dll
  • BepInEx/plugins/HOOKS-Assembly-CSharp.dll
  • BepInEx/utils/PUBLIC-Assembly-CSharp.dll
  • RainWorld_Data/Managed/UnityEngine.dll
  • RainWorld_Data/Managed/UnityEngine.CoreModule.dll

Note that certain mods may need more functionality from Unity. In the RainWorld_Data/Managed folder you will find DLL files prefixed with UnityEngine. - these are different parts of the Unity engine that allow doing different things, such as UnityEngine.AssetBundleModule for loading asset bundles built in the Unity editor.

Step 2 - The BepInPlugin class

This section requires care.

Step 2.1 - Setting up the Mod Main class

When you start up a project, it will have a default file in it named Class1.cs and it should be automatically open. You'll see some default code within it.

Start by coming up with a class name. This is basically a way for you (and other code) to find your mod's main class. A generally acceptable naming convention is ModNameMain, for example, SuperJumpBootsMain is a sensible hypothetical name. If you are using Visual Studio, click on the class's name in code (Class1) and hold Left Ctrl, then press the R key twice. This will open the rename prompt, which will automatically edit every single piece of code that refers to that class, and it will also rename the file for you.

Once this is done, your class needs to extend BaseUnityPlugin. You should also make a private method named OnEnable. Once that is done, it should look a bit like this:

namespace YourMod 
{
    public class ModNameMain : BaseUnityPlugin
    {
        private void OnEnable() 
        {
            // Some modders also opt to use Awake() instead of OnEnable() - if you see that, it functions roughly the same.
        }
    }
}

If you are familiar with Unity development, BepInPlugin is a MonoBehaviour and all message methods are available to you. If you don't know what this means, that's okay.

Step 2.2 - Setting up the assembly

First, we need to declare a preliminary attribute on the assembly itself to prevent errors. To do this, view your Solution Explorer (it's on the right, where your files are listed in Visual Studio) and open the "Properties" object. Open AssemblyInfo.cs
The AssemblyInfo.cs file is located within the Properties object.
An example from the Superstructure Fuse Fix mod.

At the top of AssemblyInfo.cs, paste this line. You should see other lines starting with using to put it below.

using System.Security.Permissions;

And then at the bottom of the file, paste this code:

#pragma warning disable CS0618 // SecurityAction.RequestMinimum is obsolete. However, this does not apply to the mod, which still needs it. Suppress the warning indicating that it is obsolete.
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
#pragma warning restore CS0618

Save AssemblyInfo.cs and close it, that is all that needs to be done with that file. Return to ModNameMain.cs (or Class1.cs, if you didn't rename it, which you should do now).

Step 2.3 - Setting up the mod's information

Now, you need to apply the appropriate attributes to your mod class. The most important of these is the BepInPlugin attribute, as it is responsible for storing your mod's unique ID, display name, and version. Please note that there are certain guidelines to follow when coming up with data for your mod:

  • Your plugin GUID must be unique, so that it does not clash with anyone else's mod. A great way to do this is following a format like your_name.mod_id. If it is not unique, it will break both your mod and the other person's mod, if they are both installed.
  • Match your plugin GUID with the mod ID you made for modinfo.json - they should be identical. This ensures that other modders have an easily accessible way to find your mod's ID.
  • Once you decide on a GUID, do not change it after you release your mod. This can wreak havoc on any mods that depend on yours! You can replace the display name at any time, just not the GUID.

To declare these, start by making three public const string fields in your class. Use them in the BepInPlugin constructor.

Some mods use dependencies, for example, SlugBase and RegionKit are two very popular choices. Not only do you need to add these dependencies to your modinfo.json, but you also need to add them to your mod's code! If you forget to do this, there is a chance your mod could load before a mod that you require, which means its code hasn't loaded yet. This will cause the game to crash on startup, or cause the mod to error out and do nothing.

To register a dependency in your code, use the BepInDependency attribute. When you put this all together, it should look like this:

using BepInEx;
namespace SomeMod
{
    // There are two types of dependencies:
    // 1. BepInDependency.DependencyFlags.HardDependency - The other mod *MUST* be installed, and your mod cannot run without it. This ensures their mod loads before yours, preventing errors.
    // 2. BepInDependency.DependencyFlags.SoftDependency - The other mod doesn't need to be installed, but if it is, it should load before yours.
    [BepInDependency("author.some_other_mods_guid", BepInDependency.DependencyFlags.HardDependency)]
    // Of course, you should erase the line above. It's just an example.

    // As mentioned before, if the other modder uses constant strings, you can do something like this instead:
    // [BepInDependency(SomeOtherModMain.PLUGIN_GUID, BepInDependency.DependencyFlags.HardDependency)]

    // In general, it is considered bad form to directly put strings into the constructor of the BepInPlugin attribute.
    // By creating const string fields, other mods that depend on yours can import your mod's DLL file, and then their code can actually directly reference the data you put here!
    // This saves your fellow modders a lot of time and headache, and could potentially save you in the future too.
    [BepInPlugin(PLUGIN_GUID, PLUGIN_NAME, PLUGIN_VERSION)]
    public class MyModName : BaseUnityPlugin
    {
        public const string PLUGIN_GUID = "your_name.your_mod_id"; // This should be the same as the id in modinfo.json!
        public const string PLUGIN_NAME = "Your Mod's Name"; // This should be a human-readable version of your mod's name. This is used for log files and also displaying which mods get loaded. In general, it's a good idea to match this with your modinfo.json as well.
        public const string PLUGIN_VERSION = "1.0.0"; // This follows semantic versioning. For more information, see https://semver.org/ - again, match what you have in modinfo.json
        // It should go without saying, but for this to benefit other modders, the class *and* these const strings must be public.

        private void OnEnable()
        {
        }
    }
}

Once this is done, your code is fully prepared to facilitate everything you need for your mod to work!

Step 3 - Hooking

Hooks allow you to execute your own code when the method you are hooking from the game code is called.

Hooking is the recommended way of modifying the functionality of the game, as your hooks will allow the hooks of other mods and the game code itself to run as expected (presuming you don't do something that they don't expect). From your class constructor or OnEnable, you can subscribe to the event that triggers when a method from the Rain World code is called. If you don't know how events work, here's a quick example:

namespace SomeMod {

	[BepInPlugin(PLUGIN_GUID, PLUGIN_NAME, PLUGIN_VERSION)]
	public class MyMod : BaseUnityPlugin
	{
	    public const string PLUGIN_GUID = "your_name.your_mod_id";
	    public const string PLUGIN_NAME = "Your Mod's Name";
	    public const string PLUGIN_VERSION = "1.0.0";


	    public void OnEnable()
	    {
	        /* This is called when the mod is loaded. */
	        // subscribe your PlayerUpdateHook to the Player.Update method from the game
	        On.Player.Update += PlayerUpdateHook;
	    }

	    void PlayerUpdateHook(On.Player.orig_Update orig, Player self, bool eu)
	    {
	        // Whenever Player.Update gets called by the game, it takes a detour into your code here instead.
			// Do anything that you need to happen when the player updates in here.
			
	        orig(self, eu); // Then, use this to to tell the game that it needs to run the normal code (and other mods' hooks) now.
	
			// And optionally, you can have more code here too, after orig
			// In general, you will want to always try to call orig
			// If you skip calling orig, this prevents all other mods from running their own hooks
			// and it also stops the game from doing the vanilla behavior.

	    }

	}
}


Note that our hook there - PlayerUpdateHook - takes the orig method, the Player object whose Update method was called (since Player.Update is not static), and the parameters taken by the original method.

If you have many hooks consider organising them, perhaps into separate classes.

"Where can I find these magical and elusive Rain World methods?"

Since the source code for Rain World is not public, one must use a decompiler such as DnSpy or ILSpy to look through the Assembly-CSharp.dll file.

Reminder: you should never distribute significant portions of the game's code or the binaries, or that of any closed source mods unless you have explicit permission to do so from the mod author. Pay attention to licenses on public repositories too - see GitHub's guide to code licensing and if in doubt ask the author.

Testing your code

Build your code and find the Dynamic Link Library (DLL) file that it has compiled to. You then need to:

  1. Create a folder and JSON for your mod, as described by Downpour Reference/Mod Directories, and
  2. Copy your mod DLL to a plugins folder within your mod's folder, as described by Downpour Reference/BepInEx Files.
  3. Run the game and enable your mod by clicking on its name in the mod list in the Remix menu. Click the Apply Mods button.
    • At time of writing, Rain World Remix does not support hot reloading of code mods, so the game needs to be restarted to apply your mod.

Enums

Due to their abundance in Rain World's code and their general usefulness in state machines, it's very possible that at some point you'll want to add your own value to an enum. To do that, you can use Downpour Reference/ExtEnum.

Advanced Techniques

IL Hooking

IL hooking can be used to modify the Intermediate Language (IL) instructions of the game at runtime. This allows advanced editing of individual instructions within the game code.

Risk of Thunder's guide to IL hooking can be found here.

RuntimeDetour

RuntimeDetour can be used to hook methods not covered by HOOKS-Assembly-CSharp.dll, as well as property getters and setters.

A brief guide can be found on the MonoMod RuntimeDetour page. MonoMod's RuntimeDetour guide can be found here.

Harmony Patching

Harmony patching is a form of runtime patching.

The HarmonyX wiki can be found here.