How do I make rimworld call my class where it usually would call it's own?

Started by donoya, December 23, 2016, 02:56:35 PM

Previous topic - Next topic

donoya

I'm sure this is a simple problem for anyone who's done any kind of C# modding before. Here's my code:
using System.Collections.Generic;
using System.Linq;
using System.Text;
//Typical starting point for any C# class.

using UnityEngine;
using Verse;
using RimWorld;
//This makes the Rimworld specific code usable in this class.

namespace RW_Revamped_Transcendence
{
    [StaticConstructorOnStartup]
    public class MainTabWindow_Research : RimWorld.MainTabWindow_Research
    {
        public override void PreOpen()
        {
            base.PreOpen();
            this.selectedProject = Find.ResearchManager.currentProj;
            List<ResearchProjectDef> relevantProjects = DefDatabase<ResearchProjectDef>.AllDefsListForReading;
            DefDatabase<ResearchProjectDef>.Clear();
            foreach (ResearchProjectDef researchDef in relevantProjects)
            {
                if (researchDef.defName.StartsWith("RWR_TT_"))
                {
                    DefDatabase<ResearchProjectDef>.Add(researchDef);
                }
            }
            float viewWidth = (DefDatabase<ResearchProjectDef>.AllDefs.Max((ResearchProjectDef d) => d.ResearchViewX) + 2f) * 190f;
        }
    }
}

I'm trying to make rimworld only enable the researches that start with the acronym "RWR_TT_".

Thirite

You have to detour the method. When I get home I'll edit this post with some info/code you'll need.

Edit: I lied. It's in the post below

DaemonDeathAngel


Thirite

Alright, here's a bit of a run-through. I will assume you are using MonoDevelop as that is what the forum recommends and what I use, and I will assume you will have already configured it the way the "Create an assembly mod" tutorial lines out.

First things first, you'll need to add these as .cs files into your solution.
Detours.cs
DetourInjector.cs

These files I graciously stole from somewhere on the forum. Thanks to RawCode and whomever else worked on them. You don't need to touch Detours.cs at all, it does all the heavy lifting here. All we need to do is modify the DetourInjector.cs which will send instructions to it on what methods to detour and what methods they are replaced with.

Now we have to find the method we want to detour. For this example we'll modify the DropBloodFilth method in the Pawn_HealthTracker class. It will look similar to this:
public void DropBloodFilth ()
{
if ((this.pawn.Spawned || (this.pawn.holder != null && this.pawn.holder.owner is Pawn_CarryTracker)) && this.pawn.PositionHeld.InBounds () && this.pawn.RaceProps.BloodDef != null && !this.pawn.InContainer) {
FilthMaker.MakeFilth (this.pawn.PositionHeld, this.pawn.RaceProps.BloodDef, this.pawn.LabelIndefinite (), 1);
}
}


But we have to convert it so that it will act as a detour. So we need to change a few things. In the end our class file will look something like this:

using System;
using Verse;
using RimWorld;

namespace MyRimworldMod
{
public static class MyDetours
{
internal static void _DropBloodFilth (this Pawn_HealthTracker _this)
{
if ((_this.pawn.Spawned || (_this.pawn.holder != null && _this.pawn.holder.owner is Pawn_CarryTracker)) && _this.pawn.PositionHeld.InBounds () && _this.pawn.RaceProps.BloodDef != null && !_this.pawn.InContainer) {
FilthMaker.MakeFilth (_this.pawn.PositionHeld, _this.pawn.RaceProps.BloodDef, _this.pawn.LabelIndefinite (), 1);
}
}

}
}


Notice we added a parameter! If the detoured method had parameters already, the "this Class _this" parameter would come before them. Where Class is the class holding the method we're detouring. eg:    internal static void DrawEquipment (this PawnRenderer _this, Vector3 rootLoc)

But if you put this in MonoDevelop, oh shit! Why can't we access the field "pawn" in the Pawn_HealthTracker class? Well, for whatever annoying reason it's been set to private. If it was public, this code would already work. However, looks like we need to make a private field accessor. Put this before the _DropBloodFilth method:
static FieldInfo pht_pawn = typeof(Pawn_HealthTracker).GetField ("pawn", BindingFlags.NonPublic | BindingFlags.Instance);

Notice you will have to add "System.Reflection" to the "using" section at the top. MonoDevelop should give you a notice to Resolve this when you right click on the error-marked "BindingFlags..." bit.

Basically what this is doing is for the Pawn_HealthTracker class, it looks inside it for a field named "pawn" which is NonPublic and an Instance. From here we can access it inside our detour method. Now inside our method (at the start) we'll add in a variable to get the 'pawn' field of the arbitrary instance of the Pawn_HealthTracker class (each pawn in game has a copy) that is calling our method.

Pawn _pawn = (Pawn)pht_pawn.GetValue (_this);

Alright! From here on we can now modify the detoured method to use this field accessor. It will look like this in the end.

public static class MyDetours
{
static FieldInfo pht_pawn = typeof(Pawn_HealthTracker).GetField ("pawn", BindingFlags.NonPublic | BindingFlags.Instance);
internal static void _DropBloodFilth (this Pawn_HealthTracker _this)
{
Pawn _pawn = (Pawn)pht_pawn.GetValue (_this);
if ((_pawn.Spawned || (_pawn.holder != null && _pawn.holder.owner is Pawn_CarryTracker)) && _pawn.PositionHeld.InBounds () && _pawn.RaceProps.BloodDef != null && !_pawn.InContainer) {
FilthMaker.MakeFilth (_pawn.PositionHeld, _pawn.RaceProps.BloodDef, _pawn.LabelIndefinite (), 1);
}
}

}


Alright, so now we should have successfully copied/converted the method to work as a detour. But obviously we want to change something, so maybe triple blood spray or something.
internal static void _DropBloodFilth (this Pawn_HealthTracker _this)
{
Pawn _pawn = (Pawn)pht_pawn.GetValue (_this);
if ((_pawn.Spawned || (_pawn.holder != null && _pawn.holder.owner is Pawn_CarryTracker)) && _pawn.PositionHeld.InBounds () && _pawn.RaceProps.BloodDef != null && !_pawn.InContainer) {
for (int i = 3; i != 0; i--) {
FilthMaker.MakeFilth (_pawn.PositionHeld, _pawn.RaceProps.BloodDef, _pawn.LabelIndefinite (), 1);
}
Log.Message("Working as intended :^)");
}
}


Alright, now we just have to go into the DetourInjector.cs file and make sure everything is set up properly to 'inject' our code. For the sake of laziness in writing this tutorial it will be if you copied the file from above. Take a good look at what it's doing to make sure you understand it. It's actually pretty simple. Now compile and run the game. Now whenever someone drops blood they will drop three times as much, and you will get an annoying message in the log to boot.


Detouring private methods is even more of a pain in the ass, but entirely possible. If you understand C# and can google, you'll figure it out easily enough. Good luck.

DaemonDeathAngel


donoya

So I've ran into another issue. I can't seem to access the MainTabWindow_Research from the MyDetours class that I had to make. Will I need to detour every method to my class and copy the code or is there a way to get the MainTabWindow_Research object without any direct reference to it in the MyDetours class? If not, how can I detour the constructor?

RawCode

reflection bro.
you can "detour" constructor like any other method, fetch it with reflection, and get function pointer.

please at least try to implement everything yourself, asking for complete code should be last resort.