Detouring this

Started by CallMeDio, August 05, 2016, 10:36:21 AM

Previous topic - Next topic

CallMeDio

Hey, I want to detour this function that is on Bill_Medical.cs :

public override void Notify_IterationCompleted(Pawn billDoer, List<Thing> ingredients)
{
base.Notify_IterationCompleted(billDoer, ingredients);
Pawn giverPawn = this.GiverPawn;
if (this.recipe.Worker.GetPartsToApplyOn(giverPawn, this.recipe).Contains(this.Part))
{
this.recipe.Worker.ApplyOnPawn(giverPawn, this.Part, billDoer, ingredients);
if (giverPawn.RaceProps.IsFlesh)
{
giverPawn.records.Increment(RecordDefOf.OperationsReceived);
billDoer.records.Increment(RecordDefOf.OperationsPerformed);
}
}
this.billStack.Delete(this);
}



I based myself on the CCL detour and detour injector source and saw all that is injected is written as internal static, so Im lost on how to deal with the "this" and how can I use this values on my version as they come from other parts of the file.
Its my first detour, spent more hours than I think its necessary on this, I need some help.
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

skullywag

Skullywag modded to death.
I'd never met an iterator I liked....until Zhentar saved me.
Why Unity5, WHY do you forsake me?

1000101

You simply need to write your method as a method extension for the original class.  This means using "this ClasName foo".  The other method is to use class inheritance which means you don't need to worry about "this" and you can access protected members.

I could easily have written CCLs detours to use class inheritance when possible but for consistency sake for when I could not I wrote them all as static extension methods.  I don't think any of the detours need access to protected members and they need reflection anyway to access private members.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

CallMeDio

The help here made me move some more steps, but now Im too newbie to see what else is wrong, it should be something really simple Im sure of it, the CCL is already after core, and the core library dll at my references, my incredibly simple objective is to add a hediff at the end of a medical operation, just for testing im using foodpoisoning right after the applyonpawn, my files:
this is Bill_Medical.cs (in my project not the default one)


using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;
using Verse.AI;
namespace SC
{
    public class _Bill_Medical : Bill
    {
        private BodyPartRecord part;

        private int partIndex = -1;


        public BodyPartRecord Part
        {
           
            get
            {
                if (this.part == null && this.partIndex >= 0)
                {
                    this.part = this.GiverPawn.RaceProps.body.GetPartAtIndex(this.partIndex);
                }
                return this.part;
            }
            set
            {
                if (this.billStack == null)
                {
                    Log.Error("Can only set Bill_Medical.Part after the bill has been added to a pawn's bill stack.");
                    return;
                }
                if (value != null)
                {
                    this.partIndex = this.GiverPawn.RaceProps.body.GetIndexOfPart(value);
                }
                else
                {
                    this.partIndex = -1;
                }
                this.part = value;
            }
        }

        private Pawn GiverPawn
        {
            get
            {
                Pawn pawn = this.billStack.billGiver as Pawn;
                Corpse corpse = this.billStack.billGiver as Corpse;
                if (corpse != null)
                {
                    pawn = corpse.innerPawn;
                }
                if (pawn == null)
                {
                    throw new InvalidOperationException("Medical bill on non-pawn.");
                }
                return pawn;
            }
        }

       
       
        public override bool ShouldDoNow()
        {
            return !this.suspended;
        }

        public override void Notify_IterationCompleted(Pawn billDoer, List<Thing> ingredients)
        {
            base.Notify_IterationCompleted(billDoer, ingredients);
            Pawn giverPawn = this.GiverPawn;
            if (this.recipe.Worker.GetPartsToApplyOn(giverPawn, this.recipe).Contains(this.Part))
            {
                this.recipe.Worker.ApplyOnPawn(giverPawn, this.Part, billDoer, ingredients);
                giverPawn.health.AddHediff(HediffMaker.MakeHediff(HediffDefOf.FoodPoisoning, giverPawn, null), null, null);
                if (giverPawn.RaceProps.IsFlesh)
                {
                    giverPawn.records.Increment(RecordDefOf.OperationsReceived);
                    billDoer.records.Increment(RecordDefOf.OperationsPerformed);
                 
                }
            }
            this.billStack.Delete(this);
        }

       

    }
}






and the injector


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using RimWorld;
using Verse;
using Verse.AI;
using CommunityCoreLibrary;

namespace SC
{

    public class DetourInjector : SpecialInjector
    {

        public override bool Inject()
        {
           

           
            MethodInfo RimWorld_Bill_Medic_IterationCompleted = typeof(Bill_Medical).GetMethod("Notify_IterationCompleted", BindingFlags.Instance | BindingFlags.Public);
            MethodInfo MyCustomIterationCompleted = typeof(_Bill_Medical).GetMethod("Notify_IterationCompleted", BindingFlags.Instance | BindingFlags.Public);
            if (!Detours.TryDetourFromTo(RimWorld_Bill_Medic_IterationCompleted, MyCustomIterationCompleted))
                return false;

            MethodInfo RimWorld_Bill_Medic_bodypart = typeof(Bill_Medical).GetMethod("BodyPartRecord", BindingFlags.Instance | BindingFlags.Public);
            MethodInfo MyCustombodypart = typeof(_Bill_Medical).GetMethod("BodyPartRecord", BindingFlags.Instance | BindingFlags.Public);
            if (!Detours.TryDetourFromTo(RimWorld_Bill_Medic_bodypart, MyCustombodypart))
                return false;

            MethodInfo RimWorld_Bill_Medic_giverpawn = typeof(Bill_Medical).GetMethod("GiverPawn", BindingFlags.Instance | BindingFlags.NonPublic);
            MethodInfo MyCustomgiverpawn = typeof(_Bill_Medical).GetMethod("GiverPawn", BindingFlags.Instance | BindingFlags.NonPublic);
            if (!Detours.TryDetourFromTo(RimWorld_Bill_Medic_giverpawn, MyCustomgiverpawn))
                return false;



            return true;
        }

     

    }

}


It should be that kind of thing that someone experienced will see in seconds and I would take days, its almost there  :)
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

RemingtonRyder

It would help if you say what happens when you try to load and use this in RimWorld. :)

CallMeDio

Hey Marvin  :), nothing happens, no errors compiling or when opening rimworld, no errors when it is running, And when I do the medic operation it fixes the part like it should but no hediff like I want to, what makes me guess that the injection of my custom part is not actually replacing.
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

1000101

You cannot add fields or properties which would implicitly add new fields.  Not without using a proper override and actually instantiating the new class.  You can only add new methods.

Trying to access new fields in a detour when the object is not actually of that type will cause memory corruption as no memory as been allocated for those fields.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

CallMeDio

#7
Ok, so where my limited knowlodge of a few days learning C# allow me to understand is that the bodypartrecord and giverpawn can not be detoured the way Im doing it without instantiating a new class. But in this case what is the most practical way of getting this mess and putting it to work properly? remove this properties? or doing a proper override?
- If it is doing a proper override and instantiating a new class how I do that?
- And if I need to remove the properties how can I make the Notify_IterationCompleted work without it?


QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

CallMeDio

Is this any closer?

internal static class _Bill_Medical
    {
        internal static BodyPartRecord part;
        internal static int partIndex = -1;
        internal static BillStack billStack;
        internal static RecipeDef recipe;
        internal static Bill b;

        internal static BodyPartRecord Part
        {
           
            get
            {
                if (part == null && partIndex >= 0)
                {
                    part = GiverPawn.RaceProps.body.GetPartAtIndex(partIndex);
                }
                return part;
            }
            set
            {
                if (billStack == null)
                {
                    Log.Error("Can only set Bill_Medical.Part after the bill has been added to a pawn's bill stack.");
                    return;
                }
                if (value != null)
                {
                    partIndex = GiverPawn.RaceProps.body.GetIndexOfPart(value);
                }
                else
                {
                    partIndex = -1;
                }
                part = value;
            }
        }

        internal static Pawn GiverPawn
        {
            get
            {
                Pawn pawn = billStack.billGiver as Pawn;
                Corpse corpse = billStack.billGiver as Corpse;
                if (corpse != null)
                {
                    pawn = corpse.innerPawn;
                }
                if (pawn == null)
                {
                    throw new InvalidOperationException("Medical bill on non-pawn.");
                }
                return pawn;
            }
        }

       
       
       

        internal static void Notify_IterationCompleted(Pawn billDoer, List<Thing> ingredients)
        {
            Notify_IterationCompleted(billDoer, ingredients);
            Pawn giverPawn = GiverPawn;
            if (recipe.Worker.GetPartsToApplyOn(giverPawn, recipe).Contains(Part))
            {
                recipe.Worker.ApplyOnPawn(giverPawn, Part, billDoer, ingredients);
                giverPawn.health.AddHediff(HediffMaker.MakeHediff(HediffDefOf.FoodPoisoning, giverPawn, null), null, null);
                if (giverPawn.RaceProps.IsFlesh)
                {
                    giverPawn.records.Increment(RecordDefOf.OperationsReceived);
                    billDoer.records.Increment(RecordDefOf.OperationsPerformed);
                 
                }
            }
            billStack.Delete(b);
        }

       

    }



and injector:

public class DetourInjector : SpecialInjector
    {

        public override bool Inject()
        {
           

           
            MethodInfo RimWorld_Bill_Medic_IterationCompleted = typeof(Bill_Medical).GetMethod("Notify_IterationCompleted", BindingFlags.Instance | BindingFlags.Public);
            MethodInfo MyCustomIterationCompleted = typeof(_Bill_Medical).GetMethod("Notify_IterationCompleted", BindingFlags.Static | BindingFlags.NonPublic);
            if (!Detours.TryDetourFromTo(RimWorld_Bill_Medic_IterationCompleted, MyCustomIterationCompleted))
                return false;

            MethodInfo RimWorld_Bill_Medic_bodypart = typeof(Bill_Medical).GetMethod("BodyPartRecord", BindingFlags.Instance | BindingFlags.Public);
            MethodInfo MyCustombodypart = typeof(_Bill_Medical).GetMethod("BodyPartRecord", BindingFlags.Static | BindingFlags.NonPublic);
            if (!Detours.TryDetourFromTo(RimWorld_Bill_Medic_bodypart, MyCustombodypart))
                return false;

            MethodInfo RimWorld_Bill_Medic_giverpawn = typeof(Bill_Medical).GetMethod("GiverPawn", BindingFlags.Instance | BindingFlags.NonPublic);
            MethodInfo MyCustomgiverpawn = typeof(_Bill_Medical).GetMethod("GiverPawn", BindingFlags.Static | BindingFlags.NonPublic);
            if (!Detours.TryDetourFromTo(RimWorld_Bill_Medic_giverpawn, MyCustomgiverpawn))
                return false;



            return true;
        }

     

    }
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

RawCode

InternalGetHashCode() //must use internal one to get gmalloc handle.
+
hashmap()
+

[StructLayout(LayoutKind.Explicit,Size=64)]
public unsafe struct FieldExtension
{
[FieldOffset(dont) ] public int  FieldA;
[FieldOffset(overlap) ] public int  FieldB;
[FieldOffset(offsets)] public int  FieldC;
}


using extension method its possible to write

YourOriginalObject.ExField(offset)

to get your AUX fields without memory corruption

CallMeDio

#10
I just gave some shots trying to cut down this fields and replace with Bill_Medical.GiverPawn inside the methods for example and all I get is that Bill_Medical does not have a definition, and the original GiverPawn is private so I don't have the smallest idea of how to do that (learning C# for 5 days) I need some level 1 easy clues (lol). edit: just noticed you said original object and not class, but I don't have idea how to do it either.
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

1000101

In some of CCLs detours you may notice regions which are called "Reflected Methods" (or something along those lines) which are used to get private fields and methods.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

CallMeDio

Ok, thanks to this tips, I made some progress and now I reached this:

internal static class _Bill_Medical
    {
        internal static FieldInfo _Part;
        internal static FieldInfo _GiverPawn;
        internal static MethodInfo _Delete;
        internal static MethodInfo _GetPartsToApplyOn;
        internal static MethodInfo _recipe;


        internal static BodyPartRecord Part()
        {
           
             if( _Part == null )
            {
                _Part = typeof(BodyPartRecord).GetField("Part", BindingFlags.Instance | BindingFlags.Public);
            }
            return (BodyPartRecord)_Part.GetValue( null );
        }

        internal static Pawn GiverPawn()
        {

            if (_Part == null)
            {
                _Part = typeof(Pawn).GetField("GiverPawn", BindingFlags.Instance | BindingFlags.NonPublic);
            }
            return (Pawn)_GiverPawn.GetValue(null);
        }

        internal static BillStack Delete(Bill bill)
        {

            if (_Delete == null)
            {
                _Delete = typeof(BillStack).GetMethod("Delete", BindingFlags.Instance | BindingFlags.Public);
            }
            return (BillStack)_Delete.Invoke(null, new object[] { bill});
        }

        internal static RecipeWorker GetPartsToApplyOn(Pawn pawn, RecipeDef recipe)
        {

            if (_GetPartsToApplyOn == null)
            {
                _GetPartsToApplyOn = typeof(RecipeWorker).GetMethod("GetPartsToApplyOn", BindingFlags.Instance | BindingFlags.Public);
            }
            return (RecipeWorker)_GetPartsToApplyOn.Invoke(null, new object[] { pawn, recipe });
        }

        internal static Bill recipe()
        {

            if (_recipe == null)
            {
                _recipe = typeof(Bill).GetMethod("recipe", BindingFlags.Instance | BindingFlags.Public);
            }
            return (Bill)_recipe.Invoke(null, new object[] {});
        }





        internal static void Notify_IterationCompleted(Pawn billDoer, List<Thing> ingredients)
        {
            Notify_IterationCompleted(billDoer, ingredients);
            Pawn giverPawn = GiverPawn();
            if (_GetPartsToApplyOn(giverPawn, recipe).Contains(Part()))
            {
                recipe.Worker.ApplyOnPawn(giverPawn, Part(), billDoer, ingredients);
                giverPawn.health.AddHediff(HediffMaker.MakeHediff(HediffDefOf.FoodPoisoning, giverPawn, null), null, null);
                if (giverPawn.RaceProps.IsFlesh)
                {
                    giverPawn.records.Increment(RecordDefOf.OperationsReceived);
                    billDoer.records.Increment(RecordDefOf.OperationsPerformed);
                 
                }
            }
            //Delete(); <- cannot figure how to do with this, used to be billstack.Delete(this) i made a method definition for it, still cannot
            //figure how to get this Bill im in without calling "this"
        }

       

    }



2 problems on this:
1- I don't know how to deal with the if containing the _GetPartsToApplyOn as it always says "Non-invocable member _Bill_Medical._GetPartsToApplyOn cannot be used as a method. (But I made all the reflection with it as method info, why?)

2- the delete part i market with a comment. I did it with a methodinfo and it worked but I don't know how to bring back the original functionality of it that was "this.billStack.Delete(this);" if i call _Delete() it don't give errors just fine but how Im going to reference itself inside the _Delete if i cannot use "this"? will i have to create something for Bill as a reflection?
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died

1000101

Because (a) you need a "this" and (b) you didn't specify "this".

internal static foo MyDetour( this Bar _this, the method paramters )

Only if the original member is static do you use not "this" and use "null" in it's place.  If it's an instance member you need "this" as above.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

CallMeDio

I added what you said on my detour and a couple of .obj . where needed

internal static void Notify_IterationCompleted(this Bill_Medical obj,Pawn billDoer, List<Thing> ingredients)
        {
            obj.Notify_IterationCompleted(billDoer, ingredients);
            Pawn giverPawn = GiverPawn();
            if (GetPartsToApplyOn(giverPawn, obj.recipe).Contains(Part()))
            {
                obj.recipe.Worker.ApplyOnPawn(giverPawn, Part(), billDoer, ingredients);
                giverPawn.health.AddHediff(HediffMaker.MakeHediff(HediffDefOf.FoodPoisoning, giverPawn, null), null, null);
                if (giverPawn.RaceProps.IsFlesh)
                {
                    giverPawn.records.Increment(RecordDefOf.OperationsReceived);
                    billDoer.records.Increment(RecordDefOf.OperationsPerformed);
                 
                }
            }
            Delete(obj);
        }


I have no errors on my file anymore but it still does nothing ingame, Im almost sure it is because of the nulls on the invokes as none of the reflected methods and fields are static, still Im struggling to replace this nulls with anything, that is, if i need to replace them with anything:

example
internal static Pawn GiverPawn()
        {

            if (_GiverPawn  == null)
            {
                _GiverPawn = typeof(Pawn).GetField("GiverPawn", BindingFlags.Instance | BindingFlags.NonPublic);
            }
            return (Pawn)_GiverPawn.GetValue(null);//<------------- this
        }


tried to do something similiar:

internal static Pawn GiverPawn(this Pawn obj)
        {

            if (_GiverPawn  == null)
            {
                _GiverPawn = typeof(Pawn).GetField("GiverPawn", BindingFlags.Instance | BindingFlags.NonPublic);
            }
            return (Pawn)_GiverPawn.GetValue(obj);
        }


When doing this I get an error on the other calls that "there is no argument given that corresponds to the required formal parameter obj"
Almost there guys, sorry for disturbing so much and thanks for the help so far   ;)
QuoteYou may need a rubber duck.  Also, try some caveman debugging.

Released mods: No Mood Loss on Prisoner Sold or Died