C# CIL stloc.1 issue

-1

The former question is solved, please proceed to the end.

So I have this code here:

using Harmony;
using RimWorld;
using Verse;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection.Emit;
using System;
using System.Reflection;

namespace RandomPlus
{
    [HarmonyPatch (typeof(Page_ConfigureStartingPawns), "RandomizeCurPawn")]
    class Patch_RandomizeMethod
    {
        static void Prefix ()
        {
            RandomSettings.ResetRerollCounter ();
        }

        static IEnumerable<CodeInstruction> Transpiler (IEnumerable<CodeInstruction> instructions)
        {
            var curPawnFieldInfo = typeof(Page_ConfigureStartingPawns)
                .GetField ("curPawn", BindingFlags.NonPublic | BindingFlags.Instance);
            var randomizeInPlaceMethodInfo = typeof(StartingPawnUtility)
                .GetMethod ("RandomizeInPlace", BindingFlags.Public | BindingFlags.Static);
            var checkPawnIsSatisfiedMethodInfo = typeof(RandomSettings)
                .GetMethod ("CheckPawnIsSatisfied", BindingFlags.Public | BindingFlags.Static);

            var codes = new List<CodeInstruction> (instructions);

            var appropriatePlace = 6;

            /* Removing the following code in its IL form */

            // this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);

            codes.RemoveRange (appropriatePlace, 5);

            /* Adding the following code in its IL form: */

//           do {
//            this.curPawn = StartingPawnUtility.RandomizeInPlace (this.curPawn);
//           } while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);

//
//          // loop start (head: IL_0016)
//          IL_0016: nop
//          IL_0017: ldarg.0
//          IL_0018: ldarg.0
//          IL_0019: ldarg.0
//          IL_001a: ldfld int32 C::curPawn
//          IL_001f: call instance int32 C::RandomizeInPlace(int32)
//          IL_0024: stfld int32 C::curPawn
//          IL_0029: nop
//          IL_002a: ldarg.0
//          IL_002b: call instance bool C::CheckPawnIsSatisfied()
//          IL_0030: ldc.i4.0
//          IL_0031: ceq
//          IL_0033: stloc.1
//          // sequence point: hidden
//          IL_0034: ldloc.1
//          IL_0035: brtrue.s IL_0016
//          // end loop

            List <CodeInstruction> newCodes = new List<CodeInstruction> {
                new CodeInstruction (OpCodes.Nop),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
                new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
                new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
                new CodeInstruction (OpCodes.Nop),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
                new CodeInstruction (OpCodes.Ldc_I4_0),
                new CodeInstruction (OpCodes.Ceq),
                new CodeInstruction (OpCodes.Stloc_1),
                new CodeInstruction (OpCodes.Ldloc_1),
            };

            newCodes [0].labels.Add (new Label ());

            var nopLabel = newCodes [0].labels [0];

            newCodes.Add (new CodeInstruction (OpCodes.Brtrue_S, nopLabel));

            codes.InsertRange (appropriatePlace, newCodes);

            for (var i = 0; i < codes.Count; i++) {
                Log.Message (codes [i].ToString ());
            }

            return codes;
        }
    }
}

What it basically does is it alters a method's code in IL and it is supposed to change this

private void RandomizeCurPawn()
{
    if (!TutorSystem.AllowAction("RandomizePawn"))
    {
        return;
    }
    this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
    TutorSystem.Notify_Event("RandomizePawn");
}

into:

private void RandomizeCurPawn()
{
    if (!TutorSystem.AllowAction("RandomizePawn"))
    {
        return;
    }
    do
        {
            this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);
        }
        while (!RandomSettings.CheckPawnIsSatisfiedMethodInfo);
    TutorSystem.Notify_Event("RandomizePawn");
}

And in order to get the IL code for the part with "while", I came up with this example program so I could get the code and change it for my needs but though I copied the IL code correctly (as far as I can tell), it doesn't work and throws an exception that says:

Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.FormatException: Method RimWorld.Page_ConfigureStartingPawns.RandomizeCurPawn() cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) RimWorld.Page_ConfigureStartingPawns:RandomizeCurPawn_Patch1 (object): IL_003c: stloc.1   


  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__7_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 RandomPlus.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)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()
Verse.LongEventHandler:UpdateCurrentAsynchronousEvent()
Verse.LongEventHandler:LongEventsUpdate(Boolean&)
Verse.Root:Update()
Verse.Root_Entry:Update()

As you can see, it is complaining about "stloc.1" line and I'm not sure why. If anyone knows how to fix this issue, please let me know, I would really appreciate that!

UPD.

I'd like to ask another question here rather than to create a separate one.

Here is what's changed:

//          // loop start (head: IL_000e)
//          IL_000e: ldarg.0
//          IL_000f: ldarg.0
//          IL_0010: ldarg.0
//          IL_0011: ldfld int32 C::curPawn
//          IL_0016: call instance int32 C::RandomizeInPlace(int32)
//          IL_001b: stfld int32 C::curPawn
//          IL_0020: ldarg.0
//          IL_0021: call instance bool C::CheckPawnIsSatisfied()
//          IL_0026: brfalse.s IL_000e
//          // end loop

            List <CodeInstruction> newCodes = new List<CodeInstruction> {
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
                new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
                new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
                new CodeInstruction (OpCodes.Ldarg_0),
                new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
            };

The game now shows me this error:

Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.FormatException: Method RimWorld.Page_ConfigureStartingPawns.RandomizeCurPawn() cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) RimWorld.Page_ConfigureStartingPawns:RandomizeCurPawn_Patch1 (object): IL_003c: call      0x00000011


  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__7_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 RandomPlus.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)
Verse.LongEventHandler:ExecuteToExecuteWhenFinished()

Do you think it's because I don't pass an argument for RandomizeInPlace?

UPD2:

I've updated the playground and here is my current code and error:

    List <CodeInstruction> newCodes = new List<CodeInstruction> {
        new CodeInstruction (OpCodes.Ldarg_0),
        new CodeInstruction (OpCodes.Ldarg_0),
        new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
        new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
        new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
        new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
    };

Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for RandomPlus.HarmonyPatches ---> System.FormatException: Method RimWorld.Page_ConfigureStartingPawns.RandomizeCurPawn() cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method) RimWorld.Page_ConfigureStartingPawns:RandomizeCurPawn_Patch1 (object): IL_003a: call      0x00000011
c#
cil
asked on Stack Overflow Jun 8, 2019 by joethehat • edited Jun 8, 2019 by joethehat

2 Answers

1

As far as I can tell:

  • In your code, you do not have local variables. So stloc.1 won't work.
  • In your example, you are looking at debug code. Which is creating and setting local variables to ease debugging※. Use release code instead.

※: See that the code uses stloc.0 (set the first local variable) followed by ldloc.0 (load the first local variable), and they are not used anywhere else. The same is true for stloc.1 and ldloc.1. There are "sequence point: hidden" in between of stloc and ldloc which indicates position where you could add a breakpoint and inspect the value of the helper local variable.

If you insist in adding local variables, have a look at ILGenerator.DeclareLocal.


Addendum

In the original code, you have:

this.curPawn = StartingPawnUtility.RandomizeInPlace(this.curPawn);

This is doing three things:

  1. Read curPawn (we need this here)
  2. Call StartingPawnUtility.RandomizeInPlace (we do NOT need this here, this is a static call)
  3. Set curPawn (we need this here)

For a great total of 2 uses of this. Thus, the code needs to load this (a.k.a ldarg.0) twice.

Now, the replacement code has:

this.curPawn = this.RandomizeInPlace (this.curPawn);

Here, the call to RandomizeInPlace is not a static call. Hence, the code needs need to load this once more, for a total of three times (you even have the three uses of this written explicitly in the code).

This is why you have three consecutive ldarg.0 in your code:

List <CodeInstruction> newCodes = new List<CodeInstruction> {
    new CodeInstruction (OpCodes.Ldarg_0), // <--
    new CodeInstruction (OpCodes.Ldarg_0), // <--
    new CodeInstruction (OpCodes.Ldarg_0), // <--
    new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
    new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
    new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
    new CodeInstruction (OpCodes.Ldarg_0),
    new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
};

I suppose you want to do a static call, just like the original code, which imply to remove one ldarg.0, leaving only two.

Note: I think you a similar problem with the other call instruction.


You can check OpCodes for documentation on the IL instructions.

answered on Stack Overflow Jun 8, 2019 by Theraot • edited Jun 8, 2019 by Theraot
0

I've solved the issue!

Turns out, checkPawnIsSatisfied has to receive this.curPawn as an argument so I added a line that passes it as an argument to the method and it started working!

  List <CodeInstruction> newCodes = new List<CodeInstruction> {
    new CodeInstruction (OpCodes.Ldarg_0),
    new CodeInstruction (OpCodes.Ldarg_0),
    new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
    new CodeInstruction (OpCodes.Call, randomizeInPlaceMethodInfo),
    new CodeInstruction (OpCodes.Stfld, curPawnFieldInfo),
    new CodeInstruction (OpCodes.Ldarg_0),
    new CodeInstruction (OpCodes.Ldfld, curPawnFieldInfo),
    new CodeInstruction (OpCodes.Call, checkPawnIsSatisfiedMethodInfo),
  };
answered on Stack Overflow Jun 10, 2019 by joethehat

User contributions licensed under CC BY-SA 3.0