Hi,
I spent the last few days fiddling with Cities: Skylines, Unity and Mono, trying to figure out a way to detour methods, that is, redirect calls from one method to another. This is useful when you need to edit some static method or one of the many non-virtual methods. If this does not ring any bell for you, you probably do not need this.
There are a few points to note here:
1) You could be using tools like MonoCecil to constantly change the DLL and mark all methods virtual by definition (except static ones). This would actually solve most of the problems, as the C# compiler by default emits CallVirts anyway. I myself consider directly editing the binaries cheating
.
2) The code I provide here has only be tested on Windows. There are three different approaches to detours in the code, the simplest one may also work on other systems.
3) The code is probably not ready for production.
4) The mods using this code will most likely be incompatible with each other (that is the nature of detours).
5) Use at your own risk.
Here is the link.
Now, looking at the code it seems very simple. Why did it take me some days to some simple patching? Well, my first attempt was the obvious patching that I am still using (patching in a jump to the correct method). In my testing, it was crashing all the time. This is when I looked into various other ways to attack the problem. Basically, there are two different approaches: Either pre-JIT or post-JIT detouring. I first attempted post-JIT (the aforementioned patching), which seemed to fail. I then explored some pre-JIT approaches, which were all about replacing the MSIL instead of the machine language. This worked out really fine in my own Unity test project (as did the simple patching), but failed for Cities: Skylines. This is due to the way that MSIL encodes jumps, calls and all other operations: They take a token which represents a method (or a field, ...) instead of an address (makes sense, right?). Tokens are specific to the assembly the MSIL is living in, so copying MSIL from one assembly to another will most likely fail, because the tokens do not match. Adding new tokens requires one to edit the metadata of the assembly, which is simple enough using special tools, but hard if you are bound to runtime patching using only managed code (which I gladly accepted as a challenge; maintaining an unmanaged DLL only adds more work as soon as you are targeting multiple platforms). This means that pretty much all pre-JIT approaches are bound to fail, which is why I switched back to post-JIT. This time around I went for an approach similar to this one for .NET. The basic idea is this: After JIT compilation, a reference to the code has to be stored somewhere, so let's replace that. Due to the way the compilation works, methods can only be detoured in this way before their callers have been compiled. When the crashes this was producing when using it with Cities: Skylines, I noticed that the crashes are always UI related. Turns out all this time I have made the game crash by writing too much stuff to the DebugOutputPanel. Oh well. At least I learnt a lot about the Mono JIT compiler in the last five days.
Long story short: Post-JIT compilation works great, either by primitive patching or by elaborate pointer-juggling.
Regards,
-cope.
I spent the last few days fiddling with Cities: Skylines, Unity and Mono, trying to figure out a way to detour methods, that is, redirect calls from one method to another. This is useful when you need to edit some static method or one of the many non-virtual methods. If this does not ring any bell for you, you probably do not need this.
There are a few points to note here:
1) You could be using tools like MonoCecil to constantly change the DLL and mark all methods virtual by definition (except static ones). This would actually solve most of the problems, as the C# compiler by default emits CallVirts anyway. I myself consider directly editing the binaries cheating
2) The code I provide here has only be tested on Windows. There are three different approaches to detours in the code, the simplest one may also work on other systems.
3) The code is probably not ready for production.
4) The mods using this code will most likely be incompatible with each other (that is the nature of detours).
5) Use at your own risk.
Here is the link.
Now, looking at the code it seems very simple. Why did it take me some days to some simple patching? Well, my first attempt was the obvious patching that I am still using (patching in a jump to the correct method). In my testing, it was crashing all the time. This is when I looked into various other ways to attack the problem. Basically, there are two different approaches: Either pre-JIT or post-JIT detouring. I first attempted post-JIT (the aforementioned patching), which seemed to fail. I then explored some pre-JIT approaches, which were all about replacing the MSIL instead of the machine language. This worked out really fine in my own Unity test project (as did the simple patching), but failed for Cities: Skylines. This is due to the way that MSIL encodes jumps, calls and all other operations: They take a token which represents a method (or a field, ...) instead of an address (makes sense, right?). Tokens are specific to the assembly the MSIL is living in, so copying MSIL from one assembly to another will most likely fail, because the tokens do not match. Adding new tokens requires one to edit the metadata of the assembly, which is simple enough using special tools, but hard if you are bound to runtime patching using only managed code (which I gladly accepted as a challenge; maintaining an unmanaged DLL only adds more work as soon as you are targeting multiple platforms). This means that pretty much all pre-JIT approaches are bound to fail, which is why I switched back to post-JIT. This time around I went for an approach similar to this one for .NET. The basic idea is this: After JIT compilation, a reference to the code has to be stored somewhere, so let's replace that. Due to the way the compilation works, methods can only be detoured in this way before their callers have been compiled. When the crashes this was producing when using it with Cities: Skylines, I noticed that the crashes are always UI related. Turns out all this time I have made the game crash by writing too much stuff to the DebugOutputPanel. Oh well. At least I learnt a lot about the Mono JIT compiler in the last five days.
Long story short: Post-JIT compilation works great, either by primitive patching or by elaborate pointer-juggling.
Regards,
-cope.