Harmony Transpiler Help

Started by ilikegoodfood, October 11, 2018, 02:08:33 PM

Previous topic - Next topic

ilikegoodfood

Continuing on from my other, more general help thread, I need specific help with the creation of two transpiler patches.

I could very easily place a high-priority patch in-front of the other patch and function in such a way that it mimics both functions, plus what I need, and always returns false, however, this would make it incompatible with pretty much everything.

What I need to do is implement a variant of the following  code snippet from GetUpdatedAvailableVerbsList into ARA_VerbCheck_Patch and TryGetAttackVerb. Both versions of the code will need to be different, doing different things.

foreach (Verb verb in this.pawn.health.hediffSet.GetHediffsVerbs())
{
if (verb.IsStillUsableBy(this.pawn))
{
Pawn_MeleeVerbs.meleeVerbs.Add(new VerbEntry(verb, this.pawn));
}
}


So far, I have learned how to read IL Code, about Stacks and how to target a specific line of IL Code for the purposes of patching, all in half a day, but how to get the patch to return altered code to the target location and how best to write that code is still a bit of a mystery to me.

Right, lets start with the first and simplest of the two transpilers: ARA_VerbCheck_Patch.
The patch in question compiles a list of Verbs from the __instance (Pawn) and calls it allVerbs. To make it do what I need it to (enable ranged verbs from a hediffDef), I need to iterate through the hediffs from __instance (Pawn) and add any verbs from those hediffs to allVerbs before the next line of code runs.

Approximation of the additional C# code:
        foreach (Verb verb in __instance.health.hediffSet.GetHediffsVerbs())
{
if (verb.IsStillUsableBy(__instance))
{
allVerbs.Add(new VerbEntry(verb, this.pawn));
}
                }


How would I go about doing this?

Thanks for you help.

Mehni

Quote from: ilikegoodfood on October 11, 2018, 02:08:33 PM
how to get the patch to return altered code to the target location and how best to write that code is still a bit of a mystery to me.

Look at examples of other transpilers; that's how most of us learned. What I refer to as the encyclopedia for harmony patches is erdelf's AlienRaces, but Brrainz' Zombieland is a good example as well. Mods by Uuugggg/AlexTD or Why_is_that and myself also contain transpilers. A common design pattern is to write in high-level C# and insert a call to it at the appropriate spot. That's generally easier than manipulating the stack directly.

Keyword: MethodInfo.

Note that you don't have an __instance in a transpiler: you can however load local variables from their location.

ilikegoodfood

#2
Fortunately for me, the spot I need to change already has __instance loaded to the top of the stack and is a function call, so all I would need to do then is write and call a function at that location? Because my function will take pawn as an argument and __instance is of the pawn, it should pass it through just fine? I'd also need to OpCodes.Nop the next line, since it's also part of the original call.

It should look like the following?:
codes[targetIndex].opcode = OpCodes.callvirt(myFunction)
codes[targetindex + 1] = OpCodes.Nop


EDIT:
I am also looking through the resources you provided. I haven't ignored them. Thanks.

ilikegoodfood

#3
Right, so I think I now understand how to use 'new CodeInstruction' to build a line of opcode along with the operand. What I don't yet understand, and am struggling to, is how to then use 'yield return' to return the code to the system.

The tutorials that I was following edited a List<CodeInstruction>(instructions) and returned the whole thing in one go, but I can't seem to edit the values of that list using 'new CodeInstruction'.

Here is the function that I have so far, which targets the required line and attempts to replace it with the new function call.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Harmony;
using RimWorld;
using Verse;
using AnimalRangeAttack;

namespace MonsterMash
{
    [HarmonyPatch(typeof(ARA__VerbCheck_Patch), "Prefix", null)]
    class harmonyPatches_ARA_VerbCheck_Patch
    {
        static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
        {
            int startIndex = -1, targetIndex = -1;

            var codes = new List<CodeInstruction>(instructions);
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Br_S)
                {
                    startIndex = i; //Provides the index of the callvirt before the call I need.
                    for (int j = startIndex; j < codes.Count; j++)
                    {
                        if (codes[j].opcode == OpCodes.Ldfld)
                        {
                            targetIndex = j; //Finds the first line of code that is run after the for loop.
                            Log.Message("Found line IL_0015");
                            break;
                        }
                    }
                    break;
                }
            }
            codes[targetIndex].opcode = new CodeInstruction(opcode: OpCodes.Callvirt, operand: AccessTools.Method(type: typeof(Functions_GetHediffVerbs), name: nameof(Functions_GetHediffVerbs.GetHediffVerbs_forAnimal)));
            codes[targetIndex + 1].opcode = new CodeInstruction(opcode: OpCodes.Nop);
            return codes.AsEnumerable();
        }
    }
}


I'm currently getting an error that reads 'Cannot implicitly convert type 'Harmony.CodeInstruction' to 'System.Reflection.Emit.OpCode' on the two lines before the return.
My current (limited) understanding, is that by using yield return instead of editing and then returning my local version of the code (the List), will make the changes directly, thus the error doesn't occur. I have not, however, worked out with sufficient confidence how I should alter my code to alter my implementation in the way described.

EDIT:

Is this what the final code should look like, using yield return? If I understand it correctly, this will overwrite the line identified as codes[j] and codes[j+1] with the new commands. Is that correct?
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Harmony;
using RimWorld;
using Verse;
using AnimalRangeAttack;

namespace MonsterMash
{
    [HarmonyPatch(typeof(ARA__VerbCheck_Patch), "Prefix", null)]
    class harmonyPatches_ARA_VerbCheck_Patch
    {
        [HarmonyBefore("com.github.rimworld.mod.AnimalRangeAttack")]
        static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
        {
            int startIndex = -1;

            var codes = new List<CodeInstruction>(instructions);
            for (int i = 0; i < codes.Count; i++)
            {
                if (codes[i].opcode == OpCodes.Br_S)
                {
                    startIndex = i; //Provides the index of the callvirt before the call I need.
                    for (int j = startIndex; j < codes.Count; j++)
                    {
                        if (codes[j].opcode == OpCodes.Ldfld && codes[j + 1].opcode == OpCodes.Callvirt)
                        {
                            yield return new CodeInstruction(opcode: OpCodes.Callvirt, operand: AccessTools.Method(type: typeof(Functions_GetHediffVerbs), name: nameof(Functions_GetHediffVerbs.GetHediffVerbs_forAnimal)));
                            yield return new CodeInstruction(opcode: OpCodes.Nop);
                        }
                    }
                    break;
                }
            }
        }
    }
}


EDIT 2:
I have now tried running the game with that second batch of code, but am getting an error message back from the game:
Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for MonsterMash.Main ---> System.FormatException: Method AnimalRangeAttack.ARA__VerbCheck_Patch.Prefix(Verse.Pawn&, Verse.Verb&) cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) AnimalRangeAttack.ARA__VerbCheck_Patch:Prefix_Patch0 (Verse.Pawn&,Verse.Verb&): IL_0000: ret       


  at Harmony.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, Harmony.PatchInfo patchInfo, System.String instanceID) [0x00000] in <filename unknown>:0
  at Harmony.PatchProcessor.Patch () [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.<PatchAll>b__9_0 (System.Type type) [0x00000] in <filename unknown>:0
  at Harmony.CollectionExtensions.Do[Type] (IEnumerable`1 sequence, System.Action`1 action) [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.PatchAll (System.Reflection.Assembly assembly) [0x00000] in <filename unknown>:0
  at MonsterMash.Main..cctor () [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Runtime.CompilerServices.RuntimeHelpers:RunClassConstructor (intptr)
  at System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (RuntimeTypeHandle type) [0x00000] in <filename unknown>:0
  at Verse.StaticConstructorOnStartupUtility.CallAll () [0x00000] in <filename unknown>:0
  at Verse.PlayDataLoader.<DoPlayLoad>m__2 () [0x00000] in <filename unknown>:0
  at Verse.LongEventHandler.ExecuteToExecuteWhenFinished () [0x00000] in <filename unknown>:0
Verse.Log:Error(String, Boolean)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:UpdateCurrentAsynchronousEvent()
Verse.LongEventHandler:LongEventsUpdate(Boolean&)
Verse.Root:Update_Patch1(Object)
Verse.Root_Entry:Update()


Being a complete beginner at this transpiler stuff, I have no idea why it is producing that error. Is it because ARA_VerbCheck_Patch.Prefix starts with a OpCodes.Nop at IL_0000 or is it an error with my code?

Mehni

QuoteIf I understand it correctly, this will overwrite the line identified as codes[j] and codes[j+1] with the new commands. Is that correct?

No. yield return new CodeInstruction adds a new code instruction to the stack, it doesn't change any existing codeinstructions. Use Harmony.DEBUG = true to see the resulting stack.

codes[targetIndex + 1].opcode = new CodeInstruction(opcode: OpCodes.Nop);
this should be something like
codes[targetIndex + 1].opcode = Opcodes.Nop You don't want a new instruction, you wish to alter the current instruction.

ilikegoodfood

#5
I have successfully managed to get my patch to target the required location and run. I've got it finding the required lines to edit and making the changes, however, I am getting a fatal error on my return function.

What exactly should I be returning from my transpiler in my return function?

The tutorials I have been following used 'return codes.AsEnumerable()', as have I, which kills it...

It's this snippet right here... I get the Log.Message but then a crash.
                        if (codes[j].opcode == OpCodes.Ldfld && codes[j + 1].opcode == OpCodes.Callvirt)
                        {
                            codes[j].opcode = OpCodes.Callvirt;
                            codes[j].operand = "instance class ['mscorlib'] System.Collections.Generic.List`1 <class ['Assembly-CSharp'] Verse.Verb>]GetHediffVerbs_forAnimal";
                            codes[j + 1].opcode = OpCodes.Nop;
                            Log.Message("Transpiler for ARA__VerbCheck_Patch complete");
                            return codes.AsEnumerable();
                        }

Mehni

Is that operand even valid? Use methodinfo. Examples available in what I linked previously.

QuoteWhat exactly should I be returning from my transpiler in my return function?

Also from the earlier linked examples:


        public static IEnumerable<CodeInstruction> ForceExitTimeExtender_Transpiler(IEnumerable<CodeInstruction> instructions)
        {
                int timeInTicksToLeave = GenDate.TicksPerDay + (MeMiMoSettings.extraDaysUntilKickedOut * GenDate.TicksPerDay);

                List<CodeInstruction> instructionList = instructions.ToList();
                for (int i = 0; i < instructionList.Count; i++)
                {
                    CodeInstruction instruction = instructionList[i];
                    if (instruction.opcode == OpCodes.Ldc_I4)
                    {
                        //change message to leave
                        instruction.operand = timeInTicksToLeave;
                    }
                    yield return instructionList[i];
                }
        }


There are other ways; returning them AsEnumerable is valid. Do note that enumerables are lazy: "it works until I return" doesn't always mean the error is in how you return.

ilikegoodfood

#7
Thanks again. I had great trouble finding information on how to write to the operand. I also clearly misunderstood what methodinfo was being used to specify. In the example, sine it was wrapped as part of a new Codeinstruction call.

I'll take another crack at it in the morning.

Mehni

np. Transpilers aren't easy to work with and the learning curve is steep -- there's maybe a handful of people in the modding community who are really comfortable writing them. Sadly for you none of them responded ;-)

Don't give up. You're on the right track.

ilikegoodfood

#9
So I ended up staring at the vast blocks of code for the alien races mod, yet again, and the use of MethodInfo and the benefits of initializing patches by name suddenly all made sense.
I'm now in the process of rewriting all of my patches to not only be initialized directly, but also to use the yield return system and I am suddenly having an issue with the function I'm targeting being declared private. The 'nameof()' part of the 'AccessTools.Method' is telling me 'does not contain a definition for 'Prefix''. Is there some other method or identifier that I should use to target this private function?
I am looking it up myself as well, but finding information of this seems to be a little tricky... Either that or I'm just bad at it, but the effect is the same.

Thanks again; again.

EDIT 1:
Am I right in thinking that I should use 'name: "Prefix"' instead of 'nameof(Prefix)' in cases where I'm handling private targets?
This thread seems related, but it's talking specifically about Generics, not them being private. I'm not certain if that's what I need or not.

EDIT 2:
Using 'name: "Prefix"' instead of 'nameof(Prefix)' works just fine, however, I am now getting a different error.
The original code that I'm substituting is this:
List<Verb> allVerbs = __instance.verbTracker.AllVerbs;

And I'm essentially substituting it with this:
List<Verb> allVerbs = getVerbsAndHediffVerbs_forAnimal(__instance);

Now, the original IL code read like this:
/* 0x00000297 02           */ IL_0013: ldarg.0
/* 0x00000298 50           */ IL_0014: ldind.ref
/* 0x00000299 7B1B00000A   */ IL_0015: ldfld     class ['Assembly-CSharp']Verse.VerbTracker ['Assembly-CSharp']Verse.Pawn::verbTracker
/* 0x0000029E 6F1C00000A   */ IL_001A: callvirt  instance class [mscorlib]System.Collections.Generic.List`1<class ['Assembly-CSharp']Verse.Verb> ['Assembly-CSharp']Verse.VerbTracker::get_AllVerbs()


Am I right in thinking that I can replace */IL_0015 and then set */IL_0016 to 'OpCodes.Nop', or do I need to call my class first, like it did for the verb tracker, and then call the function?

EDIT 2:
I don't have time to trim this down right now, so here's the whole error, as it shows up in the log:
Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for MonsterMash.harmonyPatches ---> System.FormatException: Method AnimalRangeAttack.ARA__VerbCheck_Patch.Prefix(Verse.Pawn&, Verse.Verb&) cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) AnimalRangeAttack.ARA__VerbCheck_Patch:Prefix_Patch0 (Verse.Pawn&,Verse.Verb&): IL_0000: callvirt  0x00000001


  at Harmony.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, Harmony.PatchInfo patchInfo, System.String instanceID) [0x00000] in <filename unknown>:0
  at Harmony.PatchProcessor.Patch () [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.Patch (System.Reflection.MethodBase original, Harmony.HarmonyMethod prefix, Harmony.HarmonyMethod postfix, Harmony.HarmonyMethod transpiler) [0x00000] in <filename unknown>:0
  at MonsterMash.harmonyPatches..cctor () [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Runtime.CompilerServices.RuntimeHelpers:RunClassConstructor (intptr)
  at System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (RuntimeTypeHandle type) [0x00000] in <filename unknown>:0
  at Verse.StaticConstructorOnStartupUtility.CallAll () [0x00000] in <filename unknown>:0
  at Verse.PlayDataLoader.<DoPlayLoad>m__2 () [0x00000] in <filename unknown>:0
  at Verse.LongEventHandler.ExecuteToExecuteWhenFinished () [0x00000] in <filename unknown>:0
Verse.Log:Error(String, Boolean)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:UpdateCurrentAsynchronousEvent()
Verse.LongEventHandler:LongEventsUpdate(Boolean&)
Verse.Root:Update_Patch1(Object)
Verse.Root_Entry:Update()

Mehni


QuoteEDIT 1:
Am I right in thinking that I should use 'name: "Prefix"' instead of 'nameof(Prefix)' in cases where I'm handling private targets?

Be careful not to confuse two things here. The name: part is a bit optional syntax to specify parameter names. It's particularly useful for overloaded methods with lots of optional parameters. name: nameof(Prefix) is just as valid.

But yes, if it's a private method (or type) you'd find them by string rather than by nameof().
"Prefix" is what you'd use to target a private method, and nameof(Prefix) is what you'd use for a public method. You can use "Prefix" for public methods too, but you run the risk of a typo ruining your evening...

AccessTools is a Harmony-supplied wrapper for some of the functions in System.Reflection. It's a bit lacking in documentation: if you can't find what you need with IntelliSense/on GitHub/in the Harmony docs/in the Harmony source, you can read up on System.Reflection.

You can also ask on the harmony discord (https://discord.gg/xXgghXR) or the other modders in the rimworld discord (https://discord.gg/rimworld)

QuoteAm I right in thinking that I can replace */IL_0015 and then set */IL_0016 to 'OpCodes.Nop', or do I need to call my class first, like it did for the verb tracker, and then call the function?

Possibly. I'd need a bit more context to give a definitive answer, but the meta answer here is: Use MethodInfo. Check out Mehni's Misc Modifications I linked earlier for an example, this is a design pattern I used frequently.

ilikegoodfood

#11
EDIT: I found my error. It's such a simple one too. My index is out by 1. I'll fix it soon and then I should be able to get it working.
I'm leaving my earlier comment attached for reference. You can ignore it.

END EDIT - RETAINED FOR REFERENCE

I've enlisted the help a friend who writes code for a living, and while he hasn't specifically used the reflection library before, we seem to have made some progress... before getting stuck again.

My new, much improved function, looks like this:
Initialization line:
harmony.Patch(original: AccessTools.Method(type: typeof(ARA__VerbCheck_Patch), name: "Prefix"), prefix: null, postfix: null, transpiler: new HarmonyMethod(type: patchType, name: nameof(ARA__VerbCheck_Patch_Transpiler)));

Transpiler:
        static IEnumerable<CodeInstruction> ARA__VerbCheck_Patch_Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions, ILGenerator ilg)
        {
            Log.Message("Transpiling ARA__VerbCheck_Patch");
            bool findBR = false;
            int targetIndex = -1;

            List<CodeInstruction> instructionList = instructions.ToList();
            for (int i = 0; i < instructionList.Count; i++)
            {
                CodeInstruction instruction = instructionList[i];

                if (findBR == false && instruction.opcode == OpCodes.Br)
                {
                    findBR = true;
                    targetIndex = i + 1; //Provides the index of the callvirt before the call I need.
                    Log.Message("Found targetIndex: " + targetIndex.ToString());

                    // yield return new CodeInstruction(opcode: OpCodes.Ldarg_0);
                    // yield return new CodeInstruction(opcode: OpCodes.Ldind_Ref);
                    // yield return new CodeInstruction(opcode: OpCodes.Ldfld, operand: AccessTools.Field(type: typeof(Pawn), name: "__instance"));
                    yield return new CodeInstruction(opcode: OpCodes.Callvirt, operand: AccessTools.Method(type: functionType, name: nameof(functionsForPatches.getVerbsAndHediffVerbs_forAnimal)));
                    // yield return new CodeInstruction(opcode: OpCodes.Nop);

                    Log.Message("New lines injected!");

                    i += 4; //This skips the instructions that used to be found at the location I am changing, effectively removing them from the stack.
                }
                Log.Message(targetIndex.ToString());
                yield return instruction;
            }
        }


So, my initial theories about why it wasn't don't seem to matter anymore, as the variation now produces no  difference to which of errors we get.
The commented-out lines are due to uncertainty in exactly what OpCodes.Callvirt does and doesn't replace.

I know remove everything from the first Br.s, IL_0011, and replace it with some combination of the lines of code. For a time I wondered if inserting OpCode.Callvirt also automatically called the field to memory, due to the description of the stack transitional behavior in Microsoft's .net documentation.
The commented-out OpCodes.Ldfld produces and error, but I don't believe I need it anyway.

Now, regardless of which combination of lines I use (just the Callvirt, the top four, the top two and bottom two, the bottom two) I am getting an error for a 'System.ArgumentException: Label not marked' (Link to pastebin of error log - expires sometime tomorrow).

So, I did some research into the issue and found both a forum thread and some .net documentation that seemed closely related. From those resources, I have determined that the label system is used to define the start and end-points of the jumps in the code. I then went and checked to see if there were any jumps to and/or from the section of code I am tampering with, but there aren't any. I also tried using the commented out OpCodes.Nop to match the line-numbers, and thus prevent any changes, without success.

As such, I am once again thoroughly stuck, and in need of help.

Also, while I tried enabling harmony.DEBUG, the only function I could get Visual Studios to accept was 'HarmonyInstance.DEBUG = true'. I placed the line in the initializer, before creating my instance of HarmonyInstance, but I can't seem to work out where it's sending the resultant stack. I'm not getting any information from it that I can find.

The full code for the function I am applying the transpiler to is:
In C#:
using System;
using System.Collections.Generic;
using Harmony;
using Verse;

namespace AnimalRangeAttack
{
// Token: 0x02000003 RID: 3
[HarmonyPatch(typeof(Pawn), "TryGetAttackVerb", null)]
public static class ARA__VerbCheck_Patch
{
// Token: 0x06000002 RID: 2 RVA: 0x00002078 File Offset: 0x00000278
private static bool Prefix(ref Pawn __instance, ref Verb __result)
{
bool flag = !__instance.AnimalOrWildMan();
bool result;
if (flag)
{
result = true;
}
else
{
List<Verb> allVerbs = __instance.verbTracker.AllVerbs;
for (int i = 0; i < allVerbs.Count; i++)
{
bool flag2 = allVerbs[i].verbProps.range > 1.1f;
if (flag2)
{
__result = allVerbs[i];
return false;
}
}
result = true;
}
return result;
}
}
}


In IL:
// Token: 0x02000003 RID: 3
.class public auto ansi abstract sealed beforefieldinit AnimalRangeAttack.ARA__VerbCheck_Patch
extends [mscorlib]System.Object
{
.custom instance void ['0Harmony']Harmony.HarmonyPatch::.ctor(class [mscorlib]System.Type, string, class [mscorlib]System.Type[]) = (
01 00 5a 56 65 72 73 65 2e 50 61 77 6e 2c 20 41
73 73 65 6d 62 6c 79 2d 43 53 68 61 72 70 2c 20
56 65 72 73 69 6f 6e 3d 30 2e 31 39 2e 36 38 31
34 2e 31 34 36 30 39 2c 20 43 75 6c 74 75 72 65
3d 6e 65 75 74 72 61 6c 2c 20 50 75 62 6c 69 63
4b 65 79 54 6f 6b 65 6e 3d 6e 75 6c 6c 10 54 72
79 47 65 74 41 74 74 61 63 6b 56 65 72 62 ff ff
ff ff 00 00
)
// Methods
// Token: 0x06000002 RID: 2 RVA: 0x00002078 File Offset: 0x00000278
.method private hidebysig static
bool Prefix (
class ['Assembly-CSharp']Verse.Pawn& __instance,
class ['Assembly-CSharp']Verse.Verb& __result
) cil managed
{
// Header Size: 12 bytes
// Code Size: 107 (0x6B) bytes
// LocalVarSig Token: 0x11000002 RID: 2
.maxstack 3
.locals init (
[0] class [mscorlib]System.Collections.Generic.List`1<class ['Assembly-CSharp']Verse.Verb>,
[1] bool,
[2] bool,
[3] int32,
[4] bool,
[5] bool
)

/* 0x00000284 00           */ IL_0000: nop
/* 0x00000285 02           */ IL_0001: ldarg.0
/* 0x00000286 50           */ IL_0002: ldind.ref
/* 0x00000287 281A00000A   */ IL_0003: call      bool ['Assembly-CSharp']Verse.WildManUtility::AnimalOrWildMan(class ['Assembly-CSharp']Verse.Pawn)
/* 0x0000028C 16           */ IL_0008: ldc.i4.0
/* 0x0000028D FE01         */ IL_0009: ceq
/* 0x0000028F 0B           */ IL_000B: stloc.1
/* 0x00000290 07           */ IL_000C: ldloc.1
/* 0x00000291 2C04         */ IL_000D: brfalse.s IL_0013

/* 0x00000293 17           */ IL_000F: ldc.i4.1
/* 0x00000294 0C           */ IL_0010: stloc.2
/* 0x00000295 2B56         */ IL_0011: br.s      IL_0069

/* 0x00000297 02           */ IL_0013: ldarg.0
/* 0x00000298 50           */ IL_0014: ldind.ref
/* 0x00000299 7B1B00000A   */ IL_0015: ldfld     class ['Assembly-CSharp']Verse.VerbTracker ['Assembly-CSharp']Verse.Pawn::verbTracker
/* 0x0000029E 6F1C00000A   */ IL_001A: callvirt  instance class [mscorlib]System.Collections.Generic.List`1<class ['Assembly-CSharp']Verse.Verb> ['Assembly-CSharp']Verse.VerbTracker::get_AllVerbs()
/* 0x000002A3 0A           */ IL_001F: stloc.0
/* 0x000002A4 16           */ IL_0020: ldc.i4.0
/* 0x000002A5 0D           */ IL_0021: stloc.3
/* 0x000002A6 2B32         */ IL_0022: br.s      IL_0056
// loop start (head: IL_0056)
/* 0x000002A8 00           */ IL_0024: nop
/* 0x000002A9 06           */ IL_0025: ldloc.0
/* 0x000002AA 09           */ IL_0026: ldloc.3
/* 0x000002AB 6F1D00000A   */ IL_0027: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<class ['Assembly-CSharp']Verse.Verb>::get_Item(int32)
/* 0x000002B0 7B1E00000A   */ IL_002C: ldfld     class ['Assembly-CSharp']Verse.VerbProperties ['Assembly-CSharp']Verse.Verb::verbProps
/* 0x000002B5 7B1F00000A   */ IL_0031: ldfld     float32 ['Assembly-CSharp']Verse.VerbProperties::range
/* 0x000002BA 22CDCC8C3F   */ IL_0036: ldc.r4    1.1
/* 0x000002BF FE02         */ IL_003B: cgt
/* 0x000002C1 1304         */ IL_003D: stloc.s   V_4
/* 0x000002C3 1104         */ IL_003F: ldloc.s   V_4
/* 0x000002C5 2C0E         */ IL_0041: brfalse.s IL_0051

/* 0x000002C7 00           */ IL_0043: nop
/* 0x000002C8 03           */ IL_0044: ldarg.1
/* 0x000002C9 06           */ IL_0045: ldloc.0
/* 0x000002CA 09           */ IL_0046: ldloc.3
/* 0x000002CB 6F1D00000A   */ IL_0047: callvirt  instance !0 class [mscorlib]System.Collections.Generic.List`1<class ['Assembly-CSharp']Verse.Verb>::get_Item(int32)
/* 0x000002D0 51           */ IL_004C: stind.ref
/* 0x000002D1 16           */ IL_004D: ldc.i4.0
/* 0x000002D2 0C           */ IL_004E: stloc.2
/* 0x000002D3 2B18         */ IL_004F: br.s      IL_0069

/* 0x000002D5 00           */ IL_0051: nop
/* 0x000002D6 09           */ IL_0052: ldloc.3
/* 0x000002D7 17           */ IL_0053: ldc.i4.1
/* 0x000002D8 58           */ IL_0054: add
/* 0x000002D9 0D           */ IL_0055: stloc.3

/* 0x000002DA 09           */ IL_0056: ldloc.3
/* 0x000002DB 06           */ IL_0057: ldloc.0
/* 0x000002DC 6F2000000A   */ IL_0058: callvirt  instance int32 class [mscorlib]System.Collections.Generic.List`1<class ['Assembly-CSharp']Verse.Verb>::get_Count()
/* 0x000002E1 FE04         */ IL_005D: clt
/* 0x000002E3 1305         */ IL_005F: stloc.s   V_5
/* 0x000002E5 1105         */ IL_0061: ldloc.s   V_5
/* 0x000002E7 2DBF         */ IL_0063: brtrue.s  IL_0024
// end loop

/* 0x000002E9 17           */ IL_0065: ldc.i4.1
/* 0x000002EA 0C           */ IL_0066: stloc.2
/* 0x000002EB 2B00         */ IL_0067: br.s      IL_0069

/* 0x000002ED 08           */ IL_0069: ldloc.2
/* 0x000002EE 2A           */ IL_006A: ret
} // end of method ARA__VerbCheck_Patch::Prefix

} // end of class AnimalRangeAttack.ARA__VerbCheck_Patch

ilikegoodfood

#12
So, I have everything working up to the call itself.

At first I had my function accepting a 'ref Pawn pawn' but this caused an error where 'method has 0 generic parameters but is being passed 1 generic parameter.
This lead me to understand that I was passing the ref pawn as an 'object'. As such I tried changing my function to accept an object and then, since it will never receive anything but a pawn, cast it directly as a pawn with 'Pawn thisPawn = (Pawn)Pawn;'.
While that solved the argument issue, it lead to a different error; 'invalid IL in wrapper'.

I have no idea what is causing this second error or how to bypass/fix it. Please help, again.

Error Log:
Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for MonsterMash.harmonyPatches ---> System.FormatException: Method AnimalRangeAttack.ARA__VerbCheck_Patch.Prefix(Verse.Pawn&, Verse.Verb&) cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) AnimalRangeAttack.ARA__VerbCheck_Patch:Prefix_Patch0 (Verse.Pawn&,Verse.Verb&): IL_001b: call      0x00000003


  at Harmony.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, Harmony.PatchInfo patchInfo, System.String instanceID) [0x00000] in <filename unknown>:0
  at Harmony.PatchProcessor.Patch () [0x00000] in <filename unknown>:0
  at Harmony.HarmonyInstance.Patch (System.Reflection.MethodBase original, Harmony.HarmonyMethod prefix, Harmony.HarmonyMethod postfix, Harmony.HarmonyMethod transpiler) [0x00000] in <filename unknown>:0
  at MonsterMash.harmonyPatches..cctor () [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Runtime.CompilerServices.RuntimeHelpers:RunClassConstructor (intptr)
  at System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor (RuntimeTypeHandle type) [0x00000] in <filename unknown>:0
  at Verse.StaticConstructorOnStartupUtility.CallAll () [0x00000] in <filename unknown>:0
  at Verse.PlayDataLoader.<DoPlayLoad>m__2 () [0x00000] in <filename unknown>:0
  at Verse.LongEventHandler.ExecuteToExecuteWhenFinished () [0x00000] in <filename unknown>:0
Verse.Log:Error(String, Boolean)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:UpdateCurrentAsynchronousEvent()
Verse.LongEventHandler:LongEventsUpdate(Boolean&)
Verse.Root:Update_Patch1(Object)
Verse.Root_Entry:Update()


Transpiler code:
        [HarmonyBefore(new string[] { "com.github.rimworld.mod.AnimalRangeAttack" })]
        static IEnumerable<CodeInstruction> ARA__VerbCheck_Patch_Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
        {
            Log.Message("Transpiling ARA__VerbCheck_Patch");
            bool foundTarget = false;
            int targetIndex = -1;

            List<CodeInstruction> instructionList = instructions.ToList();

            for (int i = 1; i < instructionList.Count; i++)
            {
                if (foundTarget == false && instructionList[i - 1].opcode == OpCodes.Br && instructionList[i].opcode == OpCodes.Ldarg_0)
                {
                    targetIndex = i + 2;
                    Log.Message("targetIndex = true at index: " + targetIndex.ToString());
                    break;
                }
            }

            for (int i = 0; i<instructionList.Count; i++)
            {
                CodeInstruction instruction = instructionList[i];

                if (i == targetIndex)
                {
                    yield return new CodeInstruction(opcode: OpCodes.Call, operand: AccessTools.Method(type: functionType, name: nameof(functionsForPatches.getVerbsAndHediffVerbs_forAnimal), parameters: new[] { typeof(object) }));
                    yield return new CodeInstruction(opcode: OpCodes.Stloc_0);
                    yield return new CodeInstruction(opcode: OpCodes.Ldc_I4_0);
                    instruction = new CodeInstruction(opcode: OpCodes.Nop);

                    Log.Message("New lines injected!");

                    i += 3; //This skips the instructions that used to be found at the location I am changing, effectively removing them from the stack.
                }
                Log.Message(i.ToString() + " : " + instruction.opcode.ToString());
                yield return instruction;
            }
        }


Target Function:
    class functionsForPatches
    {
        public List<Verb> getVerbsAndHediffVerbs_forAnimal(object __instance)
        {
            Pawn newInstance = (Pawn)__instance;
            List<Verb> allVerbs = new List<Verb>();

            foreach(Verb verb in newInstance.verbTracker.AllVerbs)
            {
                allVerbs.Add(verb);
            }
            foreach(Verb verb in newInstance.health.hediffSet.GetHediffsVerbs())
            {
                if (verb.IsStillUsableBy(newInstance))
                {
                    allVerbs.Add(verb);
                }
            }
            return allVerbs;
        }
    }


EDIT:
While this is a slight side-note, I have come to the realization that, even if I manage to get all these transpiler patches working, I may not want to.

The issue is that what I am working on is an implementation of pawn race and hediff verbs, so that people and animals may use ranged attacks from health effects. This will allow the implementation of body-mounted weaponry, such as an arm-mounted charge rifle, say.

While it is something that I personally would like to see the base game support, implementing it will cause a number of knock-on effects that will cause this small project to spiral out into a major framework.
A few examples of this are as follows:
The pawn UI will need to be adjusted so that you can tell a pawn with a ranged verb to hold fire.
The pawn UI will need to be adjusted so that, if the pawn has multiple ranged verbs, you can toggle between them.
Not sure if the old 'Brawler' trait is still the same, but if it is, then hediffs with ranged verbs should trigger their 'has ranged weapon equipped' bad thoughts.

If I do decide to get into this fully, and create what could be a fairly major framework, I would probably want to completely re-implement the RangeAnimalFramework into it, so that they are a singular functioning part, instead of having one framework dependent on another.
While that should make my work easier in some cases, I have no idea how code-sharing rules and re-implementations of such things work. I have no idea if BrokenValkirie would be okay with that, or, on the other hand, if they may like to help out.

I am also aware that some of the code already exists. CombatExtended uses a weapon-swapping system that I could, with their permission, either copy or re-implement (they recently gave permission for the re-use of some of their other code in a specific mod, so I don't see that being a problem).
Obviously, compatibility is of utmost importance to me. technically speaking, using transpilers, CombatExtended could be remade in a such a way that it had complete compatibility. What I definitely don't want is to exclude many mods from being used with it.
This makes fully grasping the use of transpilers of paramount importance for such a project.

If I do decide to go ahead with it, and learn everything I need to, and get all the permissions, then I would probably also ask erdelf for help directly.

So, for right now, I need to learn how to get this stuff working and knock out a prototype, After that, I need to seriously consider if I am willing and able to commit to such a project. I would kind of like to, but I wouldn't like to be the sole author...

ilikegoodfood

I joined the Harmony discord and my problem was solved over there. Thanks for your help.