Detour tutorial?

Started by Lockdown, August 21, 2016, 11:11:49 AM

Previous topic - Next topic

Lockdown

Trying to learn how to use detours using CCL. I've looked at a couple mods that rely on them, but they do a lot more than I'm looking for, and it's hard to figure out what exactly do I need to detour just one given class. Are there any tutorials or very simple mods, that I could follow to learn the basics?

RawCode

hooks works on method layer, you can't hook "given class" with current implementation.

post your code and error log

Lockdown

#2
Right, I meant a method. Here's the code I have:


using System;
using System.Collections.Generic;
using CommunityCoreLibrary;
using RimWorld;
using UnityEngine;
using System.Reflection;
using Verse;
using Object = UnityEngine.Object;

namespace RationControl
{
public class ModInitializer : ITab
{
protected GameObject _modInitializerControllerObject;

        public ModInitializer()
        {
            _modInitializerControllerObject = new GameObject("ModInitializer");
            _modInitializerControllerObject.AddComponent<ModInitializerBehaviour>();
            Object.DontDestroyOnLoad(_modInitializerControllerObject);
        }

        protected override void FillTab() { }

}

class ModInitializerBehaviour : MonoBehaviour
    {
        protected bool _reinjectNeeded;
        protected float _reinjectTime;

        public void OnLevelWasLoaded(int level)
        {
            _reinjectNeeded = true;
            if (level >= 0)
                _reinjectTime = 1;
            else
                _reinjectTime = 0;
        }

        public void FixedUpdate()
        {
        }

        public void Start()
        {
            MethodInfo coreMethod = typeof(ITab_Pawn_Health).GetMethod("FillTab", BindingFlags.CreateInstance | BindingFlags.Public);
            MethodInfo modMethod = typeof(ITab_Pawn_Health_Modded).GetMethod("FillTab", BindingFlags.CreateInstance | BindingFlags.Public);
     
            if (coreMethod == null) {
            Log.Error("core method is null");
            }
           
            if (modMethod == null) {
            Log.Error("mod method is null");
            }
     
            try
            {
                Detours.TryDetourFromTo(coreMethod, modMethod);
            }
            catch (Exception)
            {
                Log.Error("Could not Detour Ration Control.");
                throw;
            }
     
            OnLevelWasLoaded(-1);
        }
    }
}


This is based on Killface's AutoEquip mod. I can edit his mod and everything works, but when I try to do the same for my own mod, the coreMethod and modMethod variables are null, so the TryDetourFromTo call fails. I'm trying to edit the methods of the ITab_Pawn_Health class.

RawCode

your binding flags are obviously invalid, please read any tutorial on reflection and or use (BindingFlags)60 or 0x3F3C

Lockdown

Thanks, either of those codes seems to work. I assume these correspond to an enumeration from BindingFlags?

CallMeDio

the only binding flags you need to worry about while reflecting for mod detours with CCL injector are:
bindingflags.Static or bindingflags.Instance  on first spot
and
bindingflags.Public or bindingflags.NonPublic  on second spot then you do:

typeof(ITab_Pawn_Health).GetMethod("FillTab", firstspot | secondspot);

I hope my 'firstspot' and "secondspot" explanations help to understand instead of confuse.
Cheers
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

RawCode

Quote from: Lockdown on August 21, 2016, 12:06:03 PM
Thanks, either of those codes seems to work. I assume these correspond to an enumeration from BindingFlags?

yes you are correct.

QuoteCallMeDio
wrong
you MUST understand how flags works, in other case, you will end in similar thread on forum, especially if try to hook property or constructor, that does not follow "normal" pattern.

JaxelT

#7
Quote from: RawCode on August 21, 2016, 11:40:46 AM
your binding flags are obviously invalid, please read any tutorial on reflection and or use (BindingFlags)60 or 0x3F3C
Quote from: RawCode on August 21, 2016, 07:54:51 PM
Quote from: Lockdown on August 21, 2016, 12:06:03 PM
Thanks, either of those codes seems to work. I assume these correspond to an enumeration from BindingFlags?

yes you are correct.

QuoteCallMeDio
wrong
you MUST understand how flags works, in other case, you will end in similar thread on forum, especially if try to hook property or constructor, that does not follow "normal" pattern.
Don't be a dick.

OP, here's a quick introduction: Detours are a way to hook methods of your own to replace methods that the game engine uses to run code at certain times (when called). CCL offers a very easy way to do this, which lets you broaden the functionality of your mod beyond just what has been outlined for you in XML. You can pretty much make your mods do anything you want so long as it doesn't conflict with other mods that detour the same function and there is functionality in the game engine to support it.

Detouring is done using "reflection," which can be used to access classes, methods, and variables from the base game's code, even if they are not marked public. Here's how to detour a method:

First, you need a DetourInjector.cs in your code. It should look like this:



It needs to import ("using") CCL so that it can extend the SpecialInjector class, which CCL uses to inject your methods into the game. Then, you simply need to return the results of CCL's TryDetourFromTo method so CCL knows whether the detour was successful or not, e.g.


return Detours.TryDetourFromTo(typeof(ThingUtility).GetMethod("DoThing", BindingFlags.Instance | BindingFlags.Public), typeof(_ThingUtility).GetMethod("_DoThing", BindingFlags.Static | BindingFlags.NonPublic));


This assumes that the method you're detouring FROM (the first one) is not marked "static" or anything but "public" in the code. Your own detoured classes and methods should be marked "internal static," hence the flags. There are only four flags you really need to know:
BindingFlags.Static - This class/method/variable is marked static and is the same for every object made with that class.
BindingFlags.Instance - This class/method/variable is a template for objects to be created with and its functionality varies depending on its state. This is object-oriented programming.
Those are the two flags to be put in the first space. The two flags for the second are its visibility, like such:
BindingFlags.Public - Marked public, visible by other classes.
BindingFlags.NonPublic - Marked either private (invisible to other classes), protected (invisible except to classes that extend that class), internal (visible only to that assembly), or something else. Reflection is very useful for these situations because it can give you access to functionality you'd otherwise have to duplicate.

These flags allow the GetMethod method to narrow its search for that method in the class. If you put the wrong flag (e.g. Instance for a static method) it won't show up in the search and you'll get a "Method not found" error. You can Google any others if you want, you most likely won't need them. typeof(X) returns the class itself, NOT an instance of that class, so that the detour applies across all objects of that class. TryDetourFromTo asks first for the detour "from" (the old method) and then "to" a new method (yours). Those are the two parameters. It will return a boolean (true/false) which is then returned by Invoke() for CCL to tell if it was successful. It will take care of the rest.

You'll notice there is an underscore in front of the latter class and method, the one you provide. That is to differentiate between the two classes, anything with an underscore is generally some kind of replacement or important system class/method. You can Google that stuff, it doesn't matter for your purposes. You'll also notice that there is a "using Mod.Detour;" line in there, that imports all the classes from a folder in your namespace called Detour, which is good for organizational purposes, but not necessary.

Now for the finishing touch (assuming your detoured method is done). Create a ModHelperDef in your Defs folder in the mod and put a file called Injector.XML in there. Put this in it:


<?xml version="1.0" encoding="utf-8" ?>
<Defs>

  <CommunityCoreLibrary.ModHelperDef>
    <defName>Mod</defName>
    <ModName>Mod</ModName>
    <minCCLVersion>0.14.0</minCCLVersion>
<SpecialInjectors>
<li>Mod.DetourInjector</li>
</SpecialInjectors>
  </CommunityCoreLibrary.ModHelperDef>

</Defs>


Obviously, replace "Mod" with your mod's namespace. This file is what actually tells CCL to take your DetourInjector and use it to inject detours. CCL won't do anything without it. Once that's done, you should be good to go, but let me tell you about using reflection to access private variables, methods, and classes as well. It will make life easier for you and help your compatibility with other mods.

Let's say you're trying to detour a mod and it makes reference to another method in that class. You try and copy the code over to make your changes, but it's underlined in red because those methods don't exist! That is, they don't exist for your class, because they're hidden from every other class but the one you're detouring from. That could be a pain in the ass, forcing you to remake the methods in your class and reference them instead. Or, you could just reflect the method and use it anyway, private or not, like so:


using System.Reflection;
using System.Runtime.CompilerServices;

[...]


                var method = typeof(ThingUtility).GetMethod("MethodIWant", BindingFlags.Instance | BindingFlags.NonPublic);
                if(method != null)
                    method.Invoke(_this, new object[] { parameters, normally, input });
                else
                    Log.ErrorOnce("Unable to reflect ThingUtility.MethodIWant!", 305432421);


You'll notice the return of GetMethod from your DetourInjector. This allows you to grab the method you want to use, even though it's not public, and store it in a variable (method). Then, you check if you successfully got it, and call Invoke on the method. The variable "_this" is the class you're detouring from which should have been defined in the method you're detouring:


internal static void _DoThing(this ThingUtility _this, Pawn pawn)


Note that this ONLY applies for non-static methods. If the method you're detouring from is static, you won't include that parameter, and you will substitute "null" in place of "_this." And if your method is supposed to return something, just cast it as the thing it returns:


bool invoked = (bool)method.Invoke(_this, new object[] { parameters, normally, input });


Much easier; now you only have to detour the method you wanted to detour. What about a variable that's private? You can reflect that as well:


        internal static FieldInfo _pawn;

        internal static Pawn GetPawn(this Pawn_RelationsTracker _this)
        {
            if (_Pawn_RelationsTracker._pawn == null)
            {
                _Pawn_RelationsTracker._pawn = typeof(Pawn_RelationsTracker).GetField("pawn", BindingFlags.Instance | BindingFlags.NonPublic);
                if (_Pawn_RelationsTracker._pawn == null)
                {
                    Log.ErrorOnce("Unable to reflect Pawn_RelationsTracker.pawn!", 305432421);
                }
            }
            return (Pawn)_Pawn_RelationsTracker._pawn.GetValue(_this);
        }


And then replace the reference to the variable with a reference to your new method:


Pawn pawn = _this.GetPawn();


Finally, how do you reflect an entire class? Some entire classes are marked internal or private, which means you can't use typeof() to grab methods from them, because you can't even see the class itself. Reflection still has your back:


var MedicalRecipesUtility = Type.GetType("RimWorld.MedicalRecipesUtility, Assembly-CSharp, Version=0.14.6054.28275, Culture=neutral, PublicKeyToken=null");
var restore = MedicalRecipesUtility.GetMethod("RestorePartAndSpawnAllPreviousParts", BindingFlags.Static | BindingFlags.Public);


Ta-da! Now nothing is off limits to you. Go and twist RimWorld into an abomination of your own creation!

CallMeDio

Awesome information JaxelT, this forum really needed something like that  :)

QuoteRawCode
Ok, ok, RawCode, but lets be practical, that's what you need to know, CCL do dozens of detours and it doesn't use another flag once.
You must know what you need to know, when someone need a uncommon flag we will end up with another thread anyway because there is no info about it, because probably no mod ever needed them.
You need to filter what you say, you are on a thread of a guy detouring for the first time, and everybody who ends up here because of a search is probably on the first times too, the info you added is useless for everybody who this thread might interest and out of scope or purpose.
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

RawCode

If you find my posts "useless", i have very very bad news for you.

JaxelT
good job, there is short version of your article:
"g reflection c#"

spoonfeeding is road nowhere, "you" will end up with another thread because there is no info, and nobody will provide you with such info, and due to lack of some "skills" you won't try to search for information self.
Result - useless thread with no answer.

When community resist spoonfeeders, all and every individual is forced to read documentation one way or another and only interesting and complex questions stay on forum.

Is usage of TryDetourFromTo(method a, method b) interesting or complex?
Ah yes, there is no 5 pages article about it, lets discuss.

RemingtonRyder

RawCode,

This isn't your forum. You should expect questions from non-experts.

What you call "spoonfeeding" is simply giving people what they can realistically comprehend and use right now.

And yeah, of course they're going to come back and ask more questions. But with more information about detouring they can ask better questions.

To put it bluntly, complaining about plebians brings you down to the level of Twitch subscriber elitists. You're better than that, or at least I hope so.

milon

#11
I'm going to chime in here too.

If someone doesn't have anything constructive to share, don't post.  I can appreciate not wanting to reinvent the wheel or get stuck with hand-holding, but those can both be avoided with the use of silence.  Or better yet, post a link to an already existing online reference / tutorial / etc.  Very little work for the poster, very helpful and informative for the one with the question.

Lockdown (& others), keep the questions coming!  That's what this Help forum is for!  :)

JaxelT

Quote from: RawCode on August 22, 2016, 03:08:27 AM
If you find my posts "useless", i have very very bad news for you.

JaxelT
good job, there is short version of your article:
"g reflection c#"

spoonfeeding is road nowhere, "you" will end up with another thread because there is no info, and nobody will provide you with such info, and due to lack of some "skills" you won't try to search for information self.
Result - useless thread with no answer.

When community resist spoonfeeders, all and every individual is forced to read documentation one way or another and only interesting and complex questions stay on forum.

Is usage of TryDetourFromTo(method a, method b) interesting or complex?
Ah yes, there is no 5 pages article about it, lets discuss.

Happily enough, now that I've taken the time to write out that little tutorial, I can just link it to anyone else who asks how detouring works, saving both my time in explaining it and their time in figuring out how it works from scratch and vague Internet pages. Go whinge about "help vampires" somewhere else, and stay out of our Help forum.

Lockdown

JaxelT, thank you so much for the detailed guide. It made me notice I was doing a bunch of things completely wrong without realizing it, and covered all the topics I was still confused about.

RemingtonRyder

Yeah, I forgot to mention, the guide was quite helpful. I looked at LT's No Cleaning Please mod but didn't really understand what the rebinding flags were for.