C# CIL stloc.1 问题

C# CIL stloc.1 issue

前一题已解决,请看最后

所以我这里有这段代码:

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;
        }
    }
}

它基本上做的是改变 IL 中的方法代码,它应该改变这个

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

进入:

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

为了获得 "while" 部分的 IL 代码,我想出了 this example program 这样我就可以获得代码并根据需要进行更改,但尽管我复制了 IL代码正确(据我所知),它不起作用并抛出一个异常:

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()

如您所见,它在抱怨 "stloc.1" 行,我不确定为什么。如果有人知道如何解决这个问题,请告诉我,我将不胜感激!

UPD.

我想在这里问另一个问题,而不是创建一个单独的问题。

更改内容如下:

//          // 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),
            };

游戏现在显示此错误:

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()

你认为这是因为我没有为 RandomizeInPlace 传递参数吗?

UPD2:

我更新了 playground,这是我当前的代码和错误:

    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

据我所知:

  • 在您的代码中,您没有局部变量。所以 stloc.1 不会起作用。
  • 在您的示例中,您正在查看调试代码。这是创建和设置局部变量以简化调试※。请改用发布代码。

※:看到代码中使用了stloc.0(设置第一个局部变量)后跟ldloc.0(加载第一个局部变量),其他地方没有使用它们。 stloc.1ldloc.1也是如此。 stlocldloc 之间有 "sequence point: hidden",表示可以添加断点并检查辅助局部变量值的位置。

如果你坚持要加局部变量,看看ILGenerator.DeclareLocal


附录

在原始代码中,你有:

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

这是在做三件事:

  1. 阅读curPawn(这里需要this
  2. 调用StartingPawnUtility.RandomizeInPlace(这里不需要this,这是一个静态调用)
  3. 设置curPawn(这里需要this

总共使用了 2 次this。因此,代码需要加载 this (a.k.a ldarg.0) 两次。

现在,替换代码有:

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

这里,对RandomizeInPlace的调用不是静态调用。因此,代码需要再次加载 this ,总共三次(您甚至在代码中显式编写了 this 的三种用法)。

这就是为什么你的代码中有三个连续的 ldarg.0:

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),
};

我想你想做一个静态调用,就像原来的代码一样,这意味着删除一个 ldarg.0,只留下两个。

注意:我认为你与其他调用指令有类似的问题。


您可以查看 OpCodes 以获取有关 IL 指令的文档。

我已经解决了这个问题!

事实证明,checkPawnIsSatisfied 必须接收 this.curPawn 作为参数,所以我添加了一行将其作为参数传递给方法,它开始工作了!

  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),
  };