Freeman: Guerrilla Warfare

Freeman: Guerrilla Warfare

(Servono più voti)
The Ultimate Guide to Modding Game Code
Da 8Z
A dive into the process of using ReiPatcher to maniuplate the game's code, allowing you to do things from modifying multipliers to adding completely new behaviours. VERY DIFFICULT, NOT FOR THE FAINT OF HEART!
   
Premio
Aggiungi ai preferiti
Preferito
Rimuovi dai preferiti
Introduction
So hello there, aspiring modder for F:GW or just a player curious about modding the game! This guide is the first and by far the only tutorial on how to modify the game internally. We will be going over how to install required tools, how to use them to view the game code, and how to write a plugin that modifies the code.

First of all, it's best to explain what the game uses to process code, and how we can change it. F:GW is powered by Unity, which uses C# as its coding language (UnityScript too, but that is practically the same thing). After the developers write the code in C#, it is compiled into an Assembly file.

The Assembly file is basically a lower-level, compressed version of the written code. It uses Common Intermediate Language[en.wikipedia.org] (CIL, or IL), a form of lower-level code. There will be a more detailed introduction of IL below, but for now know that it is what the game reads internally.

We cannot change the source code of the game directly. However, we can modify the Assembly file by inserting, modifying or removing IL code, so that it does what we want.

The easiest way is directly changing the Assembly. However, this is a bad solution because a) you have to change it each time the developer updates anything, and b) it doesn't support multiple mods. Hence, this guide will introduce you to ReiPatcher, a tool which allows modders to inject whatever they want, however they like, and supports multiple mods from different users.

This guide expects you to be very invested, and will have very little hand-holding going on. If you have a question, comment or Google to find some solutions. Also, this is still a work in progress, so some stuff might be missing as I also try to learn how to do some of the things.
Prerequsites
You need the following things to start modding the game.
  • An IL code viewer tool. I recommend ILSpy[github.com], but .NET Reflector or JustDecompile also works.
  • Reflexil[reflexil.net] for ILSpy. There are respective versions for other tools too.
  • ReiPatcher[www.hongfire.com]. (This tool is originally used for modding hentai games - imagine that!)
  • Mono.Cecil.Inject[github.com], used to make editing IL with code easier.
  • Visual Studio 2017[www.visualstudio.com]. Anything that can write and compile C# code is also fine.
  • (Optional) UnityInjector[www.hongfire.com], used to create Plugin assemblies for advanced behaviour. If you're looking to do serious stuff, consider this.
  • The game itself, duh.

This guide assumes (for the sake of my sanity) you know how to write C# code, and is familiar with Unity's API and architecture. I will only go over the basics of IL editing and using ReiPatcher.
Part 1: Setting Up
First of all, download all the above tools, and install them.

ILSpy / JustDecompile and Reflexil
Download, then either install (JustDecompile) or unpack (ILSpy).
When installing Reflexil for ILSpy, unpack all files into same folder as the executable.
When installing Reflexil for JustDecompile, you can use the in-built plugin manager.

Visual Studio 2017
Download, run install file.
Make sure you include the .NET desktop development Workload!

ReiPatcher and Mono.Cecil.Inject
Follow this guide for installing ReiPatcher.[umaiumeunion.github.io]
Alternatively, refer to my guide here in the (post 2.0.0) How to Install section.

As for Mono.Cecil.Inject, just put it alongside Mono.Cecil.dll.

UnityInjector
Follow the readme: Unpack assemblies into the _Data/Managed folder and plugin assembly into ReiPatcher/Patches folder.
Part 2: Unpacking the Code
Now, with the tools available, we can open up an Assembly file, and read the scripts the developer has written.

Open up ILSpy. Your interface should look like this:
Some explanation for each window:
1 - The assembly window. This is where each assembly will show up, along with each class, function, variable and method in the assembly.
2 - The code window. This is where the code shows up.
3 - The Reflexil window. This is where you can change and inject IL code, but we will mainly use it to read IL code and get its index.
4 - The language selector. You have 3 options: C#, IL, and IL with C#. More on this later.
5 - Reflexil button. Click to open the window.

Now, go to Freeman Guerrilla Warfare\Freeman Guerrilla Warfare_Data\Managed, and drag Assembly-UnityScript.dll into window 1. This is where almost all the code for the game resides. Open the tree node, and your window will look like this:
You won't see the patched tags because your assembly is untouched, but everything else should be the same.

Explaining a couple of things in the tree node:
  • The top tree (blue square) is an assembly (a DLL or EXE file, basically). It's where the code is.
  • References refers to what other assemblies this assembly requires to work. Doesn't really matter for us.
  • the thing that looks like {} is a namespace. Everything in the game currently uses the default namespace, so just open it up.
  • Every child of the namespace is a Class. In other words, it's a piece of code that is (usually, but not always) attached to a GameObject.
  • The blue boxes in a class are Variables (or Fields). They represent a property of the class.
  • The purple boxes are Methods. They're the functions of the class, and each function can be called by this or other classes to do something.
  • the green box is called a Constructor. It's also a method, but it's run when the class is created by something else to feed it basic information.

At this point, you can look around each class and see what they do. A few important classes you might be interested in are: Army, Character, Faction, Item, MapAgent, NPCGen, Player, Squad, Location, and Merchant.

All the code you are viewing is in C# right now. In the next part, I will introduce you to IL code and how to understand it.
Part 3: A Crash Course on IL
Unfortunately, you may have noticed that you cannot edit C# code directly. This is because the assembly doesn't actually contain C# code, but a lower form language called Common Intermediate Language, or IL for short. ILSpy is reverting IL code into C# for you to read, but it can also display IL code.

On the top bar where it says C#, switch to IL, and select a random method. You will notice that instead of readable code, each line is a string of characters, sometimes followed by a field.

This is IL, and it operates very differently from other higher-level languages. This very helpful multi part guide (1[resources.infosecinstitute.com], 2[resources.infosecinstitute.com], 3[resources.infosecinstitute.com]) from Infosec Institude provides a fantastic overview and introduction to IL, and it is highly recommended you read them all.

In a nutshell, IL is all about maniuplating the evaluation stack. Think of it as a tube with one open end - you can only put in and take away things from the top. Each line of IL code contains an OpCode, which says what should be done, and sometimes an argument, which complements the OpCode. You can change the language to IL with C# to see what each line in C# is represented in IL.

It's impossible to keep up with all the OpCodes, but you can mouse over an OpCode in the Reflexil window to see what it does. If you still don't understand an OpCode, Google it. A couple of most notable OpCodes are:
  • ldarg.0 - Puts the thing stored in Argument 0 onto the top of the evaluation stack. For all intents and purposes, this means "this", or the class itself. Sometimes it will look like ldarg.1 or ldarg followed by something, which means it's loading something else.
  • ldc.i4 - Loads an integer onto the top of the evaluation stack. Sometimes it will look like ldc.i4.0, which means it loads the integer 0.
  • ldc.r4 - Same as before, but loads a float instead.
  • add - Removes the top 2 values from the evaluation stack, adds them, and puts the result back on top of the evaluation stack.
  • call - Calls a function. If the function needs arguments, it will take them from the evaluation stack.

Understand this well, because we will almost exclusively deal with IL code when writing our mod.
Part 4: ReiPatcher's Architecture
We'll put ILSpy aside for now, and explain how to write a Patch for ReiPatcher.

First, it is worth noting that mods using ReiPatcher follow a Patcher-Hook/Core-Plugin architecture. You can read about it here[umaiumeunion.github.io], but basically:
  • Patcher - Modify the game assembly with IL maniuplation. This is also where you link the Hook/Core into the game.
  • Hook/Core - Contains custom behaviour for the mod. If you need to add complex behaviour, you need this part.
  • Plugin - A custom MonoBehaviour that can use the Unity API. Requires UnityInjector and is out of the scope of this guide for now, but just know this exists.
Each of these is a seperate assembly file. For now, just know that if we need to edit the assembly of the game, we need at least the Patcher part.

Regarding the actual format of the Patcher, refer to this part of the guide[umaiumeunion.github.io].
Part 5: Hello World, Finally
After a lot of explanation, now is finally the time to move onto writing something.

We'll start with something simple: A Hello World program that modifies the version text to include our own message.

Open up Visual Studio, and create a new project. The framework you should be using is Class Library (.NET Framework), and the Framework setting below should be .NET Framework 3.5.
For naming conventions, it's best to follow <GAME>.<MOD_NAME>.Patcher. So for our example, we'll call it FGW.HelloWorld.Patcher.

Create the project. In your first class, copy over the PatchBase class's code from this part of the guide[umaiumeunion.github.io]. It should now look like this:
That's a lot of errors. This is because we haven't set our reference yet.

On the right, right click References, and choose Add Reference. Click the Browse... button on the lower right, and choose Mono.Cecil.dll, Mono.Cecil.Inject.dll and ReiPatcher.exe in your ReiPatcher folder.
After that, before the namespace line, include these:
using System.Linq; using ReiPatcher; using ReiPatcher.Patch;
This allows use to use the PatchBase class and some various useful stuff.

Now, we can begin actually writing. The bulk of your code should be in the Patch function, and you also need to fill the CanPatch and PrePatch functions.

Before anything, go change the Name of the patcher. Also, create a tag like this:
public override string Name => "HelloWorld Patcher"; public override string Version => "1.0"; public static string Version_Tag = "HELLOWORLD_PATCH_TAG";
The tag will later be used to identify whether an assembly has been patched or not.

The PrePatch function is where you load the game's assembly. If you have any Hook assemblies to load, it also happens here. For now, just enter:
public override void PrePatch() { RPConfig.RequestAssembly("Assembly-UnityScript.dll"); }
This tells ReiPatcher that we request to change this assembly.

Next, the CanPatch function is where we tell ReiPatcher whether it should patch the assembly or not. This is to prevent us patching an assembly more than one time (which causes issues). We can use the tag earlier to identify a patched assembly like such:
public override bool CanPatch(PatcherArguments args) { return (args.Assembly.Name.Name == "Assembly-UnityScript" && GetPatchedAttributes(args.Assembly).All(att => att.Info != Version_Tag)); }

Finally, we head to the meat of the section, the Patch function. Like in ILSpy where you clicked on a method to view and change its IL code, you have to do the same thing in code from here.
public override void Patch(PatcherArguments args) { // I hope you have read the IL guide earlier, otherwise just copy over // Gets the Type UIDesktop, where our method is located TypeDefinition uiDesktop = args.Assembly.MainModule.GetType("UIDesktop"); // args contains args.Assembly, where the MainModule is resided // Get the OnEnable method, where the code we want to change is located. MethodDefinition onEnable = uiDesktop.GetMethod("OnEnable"); // We need this to get the list of IL instructions (onEnable.Body.Instructions) // ... as well as the IL Processor below // The ILProcessor is used to create and change IL code. ILProcessor onEnableIl = onEnable.Body.GetILProcessor(); // Create our new string string newVersionStr = "(Hello world!) Version "; // Replace the 10th IL command with our new command, which loads our string instead of the default one // You can know the index of the command through Reflexil, it is the leftmost number (NOT THE OFFSET NUMBER) onEnableIl.Replace(onEnable.Body.Instructions[10], onEnableIl.Create(OpCodes.Ldstr, newVersionStr)); // Finally, apply our tag so we don't repeat patch the assembly later SetPatchedAttribute(args.Assembly, Version_Tag); }

At this time, your entire file should look like this:
using System.Linq; using ReiPatcher; using ReiPatcher.Patch; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Inject; namespace FGW.HelloWorld.Patcher { public class MyPluginPatcher : PatchBase { public override string Name => "HelloWorld Patcher"; public override string Version => "1.0"; public static string Version_Tag = "HELLOWORLD_PATCH_TAG"; public override bool CanPatch(PatcherArguments args) { return (args.Assembly.Name.Name == "Assembly-UnityScript" && GetPatchedAttributes(args.Assembly).All(att => att.Info != Version_Tag)); } public override void Patch(PatcherArguments args) { TypeDefinition uiDesktop = args.Assembly.MainModule.GetType("UIDesktop"); MethodDefinition onEnable = uiDesktop.GetMethod("OnEnable"); ILProcessor onEnableIl = onEnable.Body.GetILProcessor(); string newVersionStr = "(Hello world!) Version "; onEnableIl.Replace(onEnable.Body.Instructions[10], onEnableIl.Create(OpCodes.Ldstr, newVersionStr)); SetPatchedAttribute(args.Assembly, Version_Tag); } public override void PrePatch() { RPConfig.RequestAssembly("Assembly-UnityScript.dll"); } } }

Build the code. The assembly file will appear in the bin folder of your project. Copy it over to ReiPatcher/Patches, then run ReiPatcher with the bat file or the -c command. If everything goes right, your Command Prompt window should look like this:
`3_]6O.png]Run the game, and look at the lower right corner. If you see Hello World before the version number, congratulations!

In the next part, we will go over some basic commands you have at your disposal, as well as how to combine them to make some nifty changes.
Part 6: Basic Mono.Cecil Manuvers
With the basic structure of a plugin in place, now it is time to delve into the commands you have at your disposal, as well as how you can combine them to add more complicated logic.

Inserting new lines
The first thing we may want to do is to add more lines instead of replacing existing ones. For the Hello World plugin we just wrote, we used this method:
void ILProcessor.Replace(Instruction target, Instruction instruction);
Which does exactly what it sounds like - replacing the target Instruction with our new Instruction created with
Instruction ILProcessor.Create(OpCode opcode, ...);
But we can do more than that. In addition to the Replace method, we can use
// Inserts instruction before target instruction. void ILProcessor.InsertBefore(Instruction target, Instruction instruction);
// Inserts instruction after target instruction. void ILProcessor.InsertAfter(Instruction target, Instruction instruction);
// Adds to the end of the method, hence no instruction needed void ILProcessor.Append(Instruction instruction);
to add new lines of code. One thing to note here is that because inserting Instructions will change the index of the instructions you want to change, it is important to save the instruction position as a variable before inserting Instructions!

Referencing Fields and Static Fields
Often times, we want to not insert numbers but also references to other fields. For example, if I wanted a specific skill to affect a variable, I need to get its value first.

Referencing static fields and fields are similar to referencing numbers. You can refer to a float like such:
ILProcessor.Create(OpCodes.Ldc_R4, 1f);
For a static field or field, it's a similar case, except you pass a field you must define earlier. You can stack static field references with field references to do pretty nifty things.

In this example, we want to get the Commanding skill of the player.
// Get the Type MapAgent, which holds... TypeDefinition mapAgent = args.Assembly.MainModule.GetType("MapAgent"); // A field called "code", somehow also a MapAgent class... var codeField = mapAgent.Fields.First(fld => fld.Name.Equals("code")); // Which holds NPCGen, the class used to contain skill levels... var genField = mapAgent.Fields.First(fld => fld.Name.Equals("gen")); // Which has the skill we want to find - Commanding, for example. var skillField = npcGen.Fields.First(fld => fld.Name.Equals("commanding")); // We can insert all of these into a method, but we need to know where. // Remember to first store the instruction before changing it! var inst= methodDefinition.Body.Instructions[10]; var il = methodDefinition.GetILProcessor(); // Finally, insert all of these before the target instruction. // It's recommended to use InsertBefore to make ordering them easier. il.InsertBefore(inst, il.Create(OpCodes.Ldsfld, codeField)); il.InsertBefore(inst, il.Create(OpCodes.Ldfld, genField)); il.InsertBefore(inst, il.Create(OpCodes.Ldfld, skillField)); // And convert it into a float if you want to multiply it by another float or something. il.InsertBefore(inst, il.Create(OpCodes.Conv_R4));
This sequence of IL commands is equalvant to (float)MapAgent.code.gen.commanding. If you need to access something from within the class, remember to use the OpCode ldarg.0.
Inserting a new Method
Sometimes we want an extra method in a type. While it is usually easier to use a seperate Hook DLL to inject with (more on this later), you can also do it through IL.

The process is as simple as defining a new MethodDefinition, and adding it to the Type like such:
TypeDefinition type = args.Assembly.MainModule.GetType("TypeName"); MethodDefinition saveMerchants = new MethodDefinition("NewMethod", MethodAttributes.Public, args.Assembly.MainModule.TypeSystem.Void); global.Methods.Add(saveMerchants);
After that, you can insert lines into the new method normally. Remember to use the OpCode ret to return the function.
Part 7: Hooks and Plugins
By far, you may have realized how difficult it is using IL code to maniuplate complex behaviour. The things the Patcher can do alone is limited; but it can be combined with the Hook and Plugin assmeblies to gain access to many more methods and functionalities of the Unity environment.

This, of course, requires UnityInjector. Be sure you have installed and configured it. You can refer to this guide here[umaiumeunion.github.io].

First off, we will create a simple Hook, use the Patcher to inject it where we want, and create a Plugin to utilize the internal functions of the game.
2 commenti
8Z  [autore] 9 mar, ore 21:00 
Community edition is free and sufficient for your needs.
Dna 18 mar 2018, ore 13:01 
Very nice guide!
I guess I need to spend more time on hongfire :P Apparently it's full of all sorts of nifty tools.

Personally, I prefer dnSpy as an alternative to ilSpy+Reflexil duo. Has a nice IL and C# editors + allows to debug unity release builds.