Ludeon Forums

RimWorld => Mods => Help => Topic started by: Lockdown on August 21, 2016, 11:11:49 AM

Title: Detour tutorial?
Post by: Lockdown on August 21, 2016, 11:11:49 AM
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?
Title: Re: Detour tutorial?
Post by: RawCode on August 21, 2016, 11:24:44 AM
hooks works on method layer, you can't hook "given class" with current implementation.

post your code and error log
Title: Re: Detour tutorial?
Post by: Lockdown on August 21, 2016, 11:31:22 AM
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.
Title: Re: Detour tutorial?
Post by: 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
Title: Re: Detour tutorial?
Post by: 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 (https://msdn.microsoft.com/en-us/library/system.reflection.bindingflags(v=vs.110).aspx)?
Title: Re: Detour tutorial?
Post by: CallMeDio on August 21, 2016, 04:06:24 PM
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
Title: Re: Detour tutorial?
Post by: 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 (https://msdn.microsoft.com/en-us/library/system.reflection.bindingflags(v=vs.110).aspx)?

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.
Title: Re: Detour tutorial?
Post by: JaxelT on August 21, 2016, 09:14:55 PM
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 (https://msdn.microsoft.com/en-us/library/system.reflection.bindingflags(v=vs.110).aspx)?

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:

(http://puu.sh/qJJ1m/3fbb686015.png)

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!
Title: Re: Detour tutorial?
Post by: CallMeDio on August 21, 2016, 10:25:04 PM
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.
Title: Re: Detour tutorial?
Post by: 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.
Title: Re: Detour tutorial?
Post by: RemingtonRyder on August 22, 2016, 09:08:15 AM
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.
Title: Re: Detour tutorial?
Post by: milon on August 22, 2016, 10:30:32 AM
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!  :)
Title: Re: Detour tutorial?
Post by: JaxelT on August 22, 2016, 11:04:09 AM
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.
Title: Re: Detour tutorial?
Post by: Lockdown on August 22, 2016, 11:16:25 AM
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.
Title: Re: Detour tutorial?
Post by: RemingtonRyder on August 22, 2016, 12:38:53 PM
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.
Title: Re: Detour tutorial?
Post by: milon on August 22, 2016, 04:56:41 PM
Quote from: JaxelT on August 22, 2016, 11:04:09 AM
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.

You should consider putting that up on the RimWorld wiki somewhere.  I'm pretty sure the modding section there is *sorely* outdated...
Title: Re: Detour tutorial?
Post by: RawCode on August 23, 2016, 08:59:02 AM
Quote from: milon on August 22, 2016, 10:30:32 AM
Or better yet, post a link to an already existing online reference / tutorial / etc.
Well, is this more or less "offensive" and useless?
http://lmgtfy.com/?q=bindingflags

ps. there is documentation about everything on this planet, especially on framework existing over 20 years.

QuoteThis isn't your forum. You should expect questions from non-experts.
Current question is unrelated to CCL or RimWorld, it's generic C# question covered by MSDN documentation.
This forum is not place to learn C# and posting any tutorial about C# (especially with major parts ripped or "explained differently compared to official version" is harmful.
People must learn C# from official sources and books designed by actually skilled people, nobody here can explain anything better then official documentation.
No matter how many people will "like" tutorial and praise it helpfulness, it is inferior compared to official documentation.

This is not my forum, still, i want only good for users and forum, sadly, not everyone is able to understand it, probably i came wrong forum, or wrong time.
Title: Re: Detour tutorial?
Post by: RemingtonRyder on August 23, 2016, 09:59:41 AM
The official documentation probably is better. I don't doubt that. However using examples which are in the context of RimWorld and illustrating how to overcome potential pitfalls makes it more interesting for people in this forum.

If you want to get people interested in reading the official documentation, you've got to get their interest first. :/
Title: Re: Detour tutorial?
Post by: RawCode on August 23, 2016, 10:04:05 AM
Quote from: MarvinKosh on August 23, 2016, 09:59:41 AM
If you want to get people interested in reading the official documentation, you've got to get their interest first. :/

I got their interest by giving no direct answer and leaving only option - reading documentation...
Title: Re: Detour tutorial?
Post by: CallMeDio on August 23, 2016, 10:19:48 AM
QuoteWell, is this more or less "offensive" and useless?
It is not offensive, but its still useless(not understandable or applicable) to who this thread might interest (beginners), it is like saying for someone detouring for the first time:
Quoteuse (BindingFlags)60 or 0x3F3C
they are probably going to spend 2 hours trying to figure why typing 0x3F3C is giving a error, how big of a help is this? this is a help forum.

QuoteCurrent question is unrelated to CCL or RimWorld, it's generic C# question covered by MSDN documentation.
Original post:
QuoteTrying to learn how to use detours using CCL.
It is related to CCL or Rimworld, even if the original question was about bindingflags, the game modding is done in C# of course is there going to be C# on the forums, what you said is like wanting to do a forum about plumbing without mentioning water.
I have seen several university teachers like you, they know so much and it is so easy that they completely erase of memory how it was when they knew nothing and think everybody understand what they say as easily as they see it.
Title: Re: Detour tutorial?
Post by: RawCode on August 23, 2016, 11:42:08 AM
Quotethey are probably going to spend 2 hours trying to figure why typing 0x3F3C is giving a error, how big of a help is this? this is a help forum.

probably we are on completely different level of conscience, in my state people with problems related to abstract thinking are "non existent"...

Quoteuse (BindingFlags)60 or 0x3F3C

means that you should use (BindingFlags) statement with numeric (magic number) value of 60 or 0x3F3C ((BindingFlags)60 or (BindingFlags)0x3F3C)

well, this is sad, very very sad, there is no reason to continue this discussion at all
Title: Re: Detour tutorial?
Post by: 1000101 on August 23, 2016, 12:02:24 PM
Actually, you should never use "magic numbers" and they are an example of bad programming techniques and being a piss-poor programmer.

The correct way is to always use named constants.

Instead of saying "use (BindingFlags) 60" (which is bad) one should direct them to make a proper constant and use that.

To wit:public constant BindingFlags UniversalBindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

The above is the proper way to do it.  That "UniversalBindingFlags" has the value of 60 is both irrelevant and assigning it directly to the value of 60 is dangerous as it introduces the potential for it to break should the API and libraries change which can and sometimes does happen.  That it hasn't for .Net is completely irrelevant.  If you want people to learn how to program and to direct them to "official sources" then it also helps to not teach them bad programming techniques when you do provide information yourself.
Title: Re: Detour tutorial?
Post by: JaxelT on August 23, 2016, 06:07:44 PM
Mods should probably lock this thread since Lockdown's question was answered, seeing as RawCode is just using it to have an egotistical meltdown.