如何在泛型类的静态非泛型方法上进行.NET运行时方法修补?(Harmony 或 MonoMod)

How to do .NET runtime method patch on generic class's static non-generic method? (Harmony or MonoMod)

提问人:user2771324 提问时间:9/24/2022 最后编辑:user2771324 更新时间:9/24/2022 访问量:1031

问:

在此示例中,我想修补 .
如何使用 Harmony 或 MonoMod 完成它?
PatchTarget.QSingleton\<T\>.get_Instance()

和谐:

“未处理的异常。System.NotSupportedException:指定的方法 不受支持。

单模组:

“未处理的异常。System.ArgumentException:给定的泛型 实例化无效。

代码片段:(可用dotnetfiddle.net)

using System; 
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;

namespace PatchTarget {
  public abstract class QSingleton<T> where T : QSingleton<T>, new() {
    protected static T instance = null; protected QSingleton() { } 

    public static T Instance { get {
      if (instance == null) {
        instance = new T();
        Console.Write($"{typeof(T).Name}.Instance: impl=QSingleton");
      }
      return instance; 
    } }
  } 
}

namespace Patch {
  public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
    public static T InstanceHack() {
      Console.Write($"{typeof(T).Name}.Instance: impl=InstanceHack");
      return null;
    }
  }
    
  public static class HarmonyPatch {
    public static Harmony harmony = new Harmony("Try");

    public static void init() {
      var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
      var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
      harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
    }
  }

  public static class MonoModPatch {
    public static MonoMod.RuntimeDetour.Detour sHook;

    public static void init() {
      var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
      var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
      sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
    }
  }
}

class Program {
  public static void Main() {
    Patch.HarmonyPatch.init();
    // Patch.MonoModPatch.init();
    Console.WriteLine($"done");
  }
}
C# .NET 运行时 钩子 和谐

评论


答:

0赞 user2771324 9/24/2022 #1

经过一番反复试验,我得到了一些工作,但不是背后的原因。

  • Harmony 和 MonoMod.RuntimeDetour 都可以挂接 typeof(QSingleton<SampleA>)。GetMethod(),但不是 typeof(QSingleton<>)。GetMethod() 中。
  • 和谐输出出乎意料。
  • Harmony 属性注释似乎不起作用。
  • 生成 IL 似乎毫无用处,因为泛型可能缺少 TypeSpec。

问题:

  • QSingleton<>有什么区别。实例和 QSingleton<SampleA>。示例中的实例?
    我猜<>。Instance 是 MethodDef,而 <SampleA>。实例的数据类型为 TypeSpec.MemberRef。
  • 为什么 Harmony/MonoMod.RuntimeDetour 需要 TypeSpec.MemberRef?用于生成重定向存根?
  • 是否可以在 Harmony 下修复钩子?
  • 如果 TypeSpec 已经存在,Harmony/MonoMod 可以生成“ldtoken <TypeSpec>”吗?
  • Harmony/MonoMod 能否为泛型动态生成必要的 TypeSpec?

代码片段:(可使用 dotnetfiddle.net 运行)

using System; 
using HarmonyLib;

namespace PatchTarget {
  public abstract class QSingleton<T> where T : QSingleton<T>, new() {
    protected static T instance = null; protected QSingleton() { } 

    public static T Instance { get {
      if (instance == null) {
        instance = new T();
        Console.WriteLine($"{typeof(T).Name}.Instance: impl=QSingleton");
      }
      return instance; 
    } }
  }

  public class SampleA : QSingleton<SampleA> {
    public SampleA() { Console.WriteLine("SampleA ctor"); }
  }

  public class SampleB : QSingleton<SampleB> {
    public SampleB() { Console.WriteLine("SampleB ctor"); }
  }
}

namespace Patch {
  public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
    public static T InstanceHack() {
      Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
      return null;
    }

    // For Harmony as Prefix, but attribute does not work.
    public static bool InstanceHackPrefix(T __result) {
      Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
      __result = null;
      return false;
    }
  }

  public static class HarmonyPatch {
    public static Harmony harmony = new Harmony("Try");

    public static void init() {
      // Attribute does not work.
      // Transpiler does not work because the lack of TypeSpec to setup generic parameters.
      var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
      var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHackPrefix");
      harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
    }
  }

  public static class MonoModPatch {
    public static MonoMod.RuntimeDetour.Detour sHook;

    public static void init() {
      var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
      var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHack");
      sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
    }
  }
}

class Program {
  public static void Main() {
    _ = PatchTarget.SampleA.Instance;
    // MonoMod works (replaces globally).
    // Harmony hooks, but in an expected way (T becomes SampleB, not 1st generic type parameter).
    // try { Patch.HarmonyPatch.init(); } catch (Exception e) { Console.WriteLine($"Harmony error: {e.ToString()}"); }
    try { Patch.MonoModPatch.init(); } catch (Exception e) { Console.WriteLine($"MonoMod error: {e.ToString()}"); }
    _ = PatchTarget.SampleB.Instance;
    _ = PatchTarget.SampleA.Instance;
    Console.WriteLine($"done");
  }
}

MonoMod.RuntimeDetour 输出:(按预期工作)

SampleA.Instance:impl=QSingleton
SampleB.Instance:impl=InstanceHack SampleA.Instance:impl=InstanceHack

和声输出:(破碎<T>)

SampleA.Instance:impl=QSingleton
SampleB.Instance:impl=InstanceHack SampleB.Instance:impl=InstanceHack