[LIB] Harmony v1.2.0.1

Started by Brrainz, January 13, 2017, 04:59:21 PM

Previous topic - Next topic

Roolo

Can anyone help me with this? I want to write a transpiler to update some code in the Pawn_PlayerSettings.GetGizmos() method, but I'm running into the same issue as the one Nightinggale reported a few pages back, namely that this isn't so easy for code inside iterators.

Pardieke's explanation and suggestions below:

Quote from: pardeike on October 21, 2017, 05:05:00 PM

This is because that method internally only returns an iterator object (the class name of that in this case is called '<>c__Iterator22A') that does the job. It's that one you need to patch. The class '<>c__Iterator22A' is an inner class of the class Verse.Corpse and has, as every iterator, two main methods: MoveNext() and Reset(). It's MoveNext() you need to patch.

Alternatively, you could patch the getter and just return your own instance of your own iterator implementation with like a prefix returning false. In that iterator implementation you use reflections to iterate through the original corpses iterator and yield return all the original values. Then you just yield more after the loop and you got yourself new elements added.


I want to try the first suggestion but don't really know how to. In my case I want to patch <GetGizmos>c__Iterator0.MoveNext(). How do I do this, considering it's a private subclass with a deviating naming convention?








Nightinggale

Quote from: Roolo on March 01, 2018, 11:36:05 AM
Can anyone help me with this? I want to write a transpiler to update some code in the Pawn_PlayerSettings.GetGizmos() method, but I'm running into the same issue as the one Nightinggale reported a few pages back, namely that this isn't so easy for code inside iterators.
I'm a bit confused about this. You mention transpiler, but then you quote something related to a Postfix  :o

The postfix issue was fixed in post #174 on page 12. It turns out that you can solve the issue on a higher level than what was proposed. What I did works well for appending data. I never really tried modifying existing data. That step might cause other issues.

If you are indeed talking about actual transpiler, my experience is that all I had to do was to find somewhere inside the loop code where the stack is empty and then add my own code. I managed to get an easy to write and well working solution by just adding variables to the stack and then call a void method. That way I managed to get a C# method with the variables I need and since it's a void method, it will not pollute the stack and it shouldn't affect the vanilla code whatsoever. It should be possible to expand on this approach and write more complex IL code to affect private data, but I have not had the need for that yet.
ModCheck - boost your patch loading times and include patchmods in your main mod.

Brrainz

I'm busy right now and on mobile. Pretty sure he thinks about getting the MethodInfo of that somehow hidden and private method. Can someone explain how to use Harmony's AccessTools to get what he needs?

Roolo

Quote from: Nightinggale on March 01, 2018, 12:24:24 PM
Quote from: Roolo on March 01, 2018, 11:36:05 AM
Can anyone help me with this? I want to write a transpiler to update some code in the Pawn_PlayerSettings.GetGizmos() method, but I'm running into the same issue as the one Nightinggale reported a few pages back, namely that this isn't so easy for code inside iterators.
I'm a bit confused about this. You mention transpiler, but then you quote something related to a Postfix  :o

The postfix issue was fixed in post #174 on page 12. It turns out that you can solve the issue on a higher level than what was proposed. What I did works well for appending data. I never really tried modifying existing data. That step might cause other issues.

If you are indeed talking about actual transpiler, my experience is that all I had to do was to find somewhere inside the loop code where the stack is empty and then add my own code. I managed to get an easy to write and well working solution by just adding variables to the stack and then call a void method. That way I managed to get a C# method with the variables I need and since it's a void method, it will not pollute the stack and it shouldn't affect the vanilla code whatsoever. It should be possible to expand on this approach and write more complex IL code to affect private data, but I have not had the need for that yet.

Quote from: pardeike on March 01, 2018, 01:41:03 PM
I'm busy right now and on mobile. Pretty sure he thinks about getting the MethodInfo of that somehow hidden and private method. Can someone explain how to use Harmony's AccessTools to get what he needs?

Yes I'm sorry. It was indeed a bit confusing. I only referred to Nightinggale's case since that also involved code inside an iterator being patched. In my case the goal is not to write a postfix or a prefix, but a transpiler that targets code inside an iterator method (in my case the method Pawn_PlayerSettings.GetGizmos() ). I'm well aware how to write transpilers, but this case is a bit different, since I cannot patch Pawn_PlayerSettings.GetGizmos(), since that only returns an iterator object. Instead I want to modify code inside the MoveNext() method inside with my transpiler. The problem is that I don't really know how to patch this method since its a method of a private subclass.

Normally I'd write something like:

[HarmonyPatch(typeof(Pawn_PlayerSettings), "GetGizmos")]
//patch class including transpiler here

But since it's an iterator method this wont work, and I want to find a way to patch the method MoveNext of the inner iterator class of GetGizmos, and I don't really know how to do this.

Of course I can just use a Prefix patch returning false to replace the entire implementation of Pawn_PlayerSettings.MoveNext, but I figured it wouldn't harm to learn a neater, and more compatibility-friendly solution. Postfixing is not a solution in my case by the way, since I want to prevent a method call, if a certain condition holds.

I hope this is a bit more clear.

Brrainz

Hi again,
check out the code of one of my mods as an example. It's not exactly what you need but if you explore the classes and utility functions used you will find a solution:

https://github.com/pardeike/SameSpot/blob/ca294dbe78b6959f730661e451e36b7e48e7af3d/Source/Main.cs#L65

Roolo

Quote from: pardeike on March 02, 2018, 11:34:11 AM
Hi again,
check out the code of one of my mods as an example. It's not exactly what you need but if you explore the classes and utility functions used you will find a solution:

https://github.com/pardeike/SameSpot/blob/ca294dbe78b6959f730661e451e36b7e48e7af3d/Source/Main.cs#L65

Thanks. In a quick glance that indeed looks very useful. I'll look further into it when I get home, which is in a few days.

Brrainz


New Version!

Now that Rimworld 1.0 is announced and most of you are going to update you mods, it is time to move on to the next version of Harmony.

Harmony v1.1 has many improvements that you are going to love and use. It can inject private fields. It can patch methods with try/catch logic. It has less bugs.

When it comes to compatibility, there is a slight catch. Transpilers have a slightly different api (CodeInstruction now has a new field "blocks" to hold try/catch logic) so Harmony contains some self-patching logic to make it coexist with older versions. This can lead to minor incompatibility issues if other mods use the old version and transpile methods that contain try/catch logic and do it by duplicating or removing code instructions. Harmony will try to fix those transpilers automatically but may fail so. It's an edge case and one that I only saw twice (one auto fixed) in my test period with two of my more popular mods. The solution is always to ask the other author to upgrade to Harmony 1.1 or to fix their transpiler logic.

Now is the time to upgrade. HugsLib and other mods do it too and it requires only trivial changes in some of the renamed api.

Grab the latest release from GitHub:
https://github.com/pardeike/Harmony/releases

Nightinggale

What happens if one mod use 1.0 and another 1.1? Would it make sense to make all RW 0.18 mods use harmony 1.0 while all RW 1.0 mods use Harmony 1.1?
ModCheck - boost your patch loading times and include patchmods in your main mod.

Brrainz

Harmony 1.0 and 1.1 can coexist. Tested with Achtung and Zombieland which are used with many other mods that of course use Harmony 1.0

Harmony 1.1 does upgrade any older version internally by patching the essential internal core methods.

Roolo

I managed to get patching iterator methods with transpilers working a while a go with your help. Below is an example (for readers also interested in patching methods returning IEnumerable). With your help it wasn't too hard to figure this all out, but I can imagine many modders aren't aware of the hidden objects in for example iterator methods, so they don't know how to patch the concerning methods. Maybe it's a good idea to add some documentation about this on GitHub?


  //example transpiler patch of Pawn.GetGizmos, which is an iterator method which isn't accessible the regular way
  [HarmonyPatch]
  public static class Pawn_GetGizmos_Transpiler {
        static MethodBase TargetMethod()//The target method is found using the custom logic defined here
        {
            var predicateClass = typeof(Pawn).GetNestedTypes(AccessTools.all)
                .FirstOrDefault(t => t.FullName.Contains("c__Iterator2"));//c__Iterator2 is the hidden object's name, the number at the end of the name may vary. View the IL code to find out the name
            return predicateClass.GetMethods(AccessTools.all).FirstOrDefault(m => m.Name.Contains("MoveNext")); //Look for the method MoveNext inside the hidden iterator object
        }
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
         //transpiler code here
        }
  }


themadgunman

Hi, im looking for a little help here, i wanted to edit the code at Rimworld.Bill_ProductionWithUft.SetBoundUft so i wrote the following which i thought should work

class HarmonyPatches
    {
        [HarmonyPatch(typeof(Bill_ProductionWithUft), "SetBoundUft")]
        public class SetBoundUft
        {
            [HarmonyPrefix]
            internal static bool Prefix(Bill_ProductionWithUft  _instance,Thing value, bool setOtherLink)
            {
                if (value != _instance.boundUftInt)
                {
                    UnfinishedThing unfinishedThing = _instance.boundUftInt;
                    _instance.boundUftInt = value;
                    if (setOtherLink)
                    {
                        if (unfinishedThing != null && unfinishedThing.BoundBill == _instance)
                        {
                            unfinishedThing.BoundBill = null;
                        }
                        if (value != null && value.BoundBill != _instance)
                        {
                            this.boundUftInt.BoundBill = this;
                        }
                    }
                }
                return true;
            }
        }


However boundUftInt is a private variable in the original so it wont reference from outside the Namespace, do i need to use Traverse somehow, or is my Patch setup wrong, or both ?

Brrainz

You could try the latest master from GitHub as it contains a preview version 1.1.1 that contains a feature called private field injection. With SomeType ___fieldname or ref SomeType ___fieldname you can access them directly.

If you rather want to stay on your current version, your best bet is to use Traverse.Create(__instance).Field("foo").GetValue<SomeType>() (your example has only one underscore, hope this isn't a typo).

themadgunman

Quote from: Brrainz on July 24, 2018, 10:31:42 AM

If you rather want to stay on your current version, your best bet is to use Traverse.Create(__instance).Field("foo").GetValue<SomeType>() (your example has only one underscore, hope this isn't a typo).

Actually its more a case of the fact im a noob at this, most of my knowledge is just from the wiki or diving into other ppls code. I've done some patching before using straight method replacement but dealing with this.* and the private variable are pushing me into new territory :)

I'll try the new version method first, Traverse still scares me, thanks for pointing me in the right direction

themadgunman

ok, so, i couldnt get the whole private field injection to work and i came up with this using Traverse

static class HarmonyPatches
    {
        static HarmonyPatches()
        {
            HarmonyInstance harmony = HarmonyInstance.Create("rimworld.themadgunman.RPP");
            MethodInfo targetmethod = AccessTools.Method(typeof(RimWorld.Bill_ProductionWithUft), "SetBoundUft");
            HarmonyMethod prefixmethod = new HarmonyMethod(typeof(RPP.HarmonyPatches).GetMethod("SetBoundUft_Prefix"));
            harmony.Patch(targetmethod, prefixmethod, null);
            Log.Message("Rpp.... Test Patching", false);
        }
        static void SetBoundUft_Prefix(ref Bill_ProductionWithUft __instance, UnfinishedThing value, bool setOtherLink=true)
        {
            UnfinishedThing t = Traverse.Create(__instance).Field("boundUftInt").GetValue<UnfinishedThing>();
            Log.ErrorOnce("SetBoundUft Patch Called", __instance.GetHashCode());
            if (value != t)
            {
                UnfinishedThing unfinishedThing = t;
                t = value;
                if (setOtherLink)
                {
                    if (unfinishedThing != null && unfinishedThing.BoundBill == __instance)
                    {
                        unfinishedThing.BoundBill = null;
                    }
                    if (value != null && value.BoundBill != __instance)
                    {
                        t.BoundBill = __instance;
                    }
                }
            }
        }
    }


Which compiles and loads it just doesnt seem to ever call my changed code when i create a new item :( I think i might have reached the limits of my ability :D

Brrainz

I see that typeof(RPP.HarmonyPatches).GetMethod("SetBoundUft_Prefix") clearly returns NULL. Either use AccessTools.Method or add the flags for non public methods.