Dynamic IL weaving frame start simple Harmony

Harmony is an open source library aimed at runtime replace, modify or amend any existing C # method. It is mainly used in written in Mono language games and plug-ins, but the technology can be used with any .NET version. It also take care of multiple changes to the same methods (they accumulate and not covered).

It is created for each original method DynamicMethod method, and woven into its code, the code calls a custom method at the beginning and end. It also allows you to write filters to process the original IL code, which can be a more detailed operation of the original method.

Documents can be here found.

  • The latest version 2.0 finally supports .net core.
  • Support Harmony Manual (Patch) and automatic (PatchAll) weaving
  • Position may be woven into the front (the Prefix) executed after the execution (the Postfix) and termination din (the Finalizer), more detail may be manually modified IL (Transpiler)
  • Constructors support, Getter / Setter, virtual / non-virtual methods, static methods

Manual mode

class NoneGenericClass
{
    private readonly bool _isRunning = true;
    private int _counter = 1;

    public int DoSomething()
    {
        Console.WriteLine(nameof(DoSomething));

        if (_isRunning)
        {
            _counter++;
        }
        return _counter * 10;
    }

    public static int DoSomething2()
    {
        Console.WriteLine(nameof(DoSomething2));

        return 3333;
    }

    public IEnumerable<int> GetNumbers()
    {
        Console.WriteLine(nameof(GetNumbers));

        yield return 1;
        yield return 2;
        yield return 3;
    }
}

static class NoneGenericClassPatcher
{
    public static void Patch()
    {
        var harmony = new Harmony(nameof(NoneGenericClassPatcher));

        harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.DoSomething)),
            new HarmonyMethod(GetMethod(nameof(MyPrefix))),
            new HarmonyMethod(GetMethod(nameof(MyPostfix))),
            new HarmonyMethod(GetMethod(nameof(MyTranspiler))),
            new HarmonyMethod(GetMethod(nameof(MyFinalizer))));

        Console.WriteLine(new NoneGenericClass().DoSomething());
        Console.WriteLine();

        harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.GetNumbers)),
            new HarmonyMethod(GetMethod(nameof(MyPrefix))),
            new HarmonyMethod(GetMethod(nameof(PassthroughPostfix))),
            new HarmonyMethod(GetMethod(nameof(MyTranspiler))),
            new HarmonyMethod(GetMethod(nameof(MyFinalizer))));

        Console.WriteLine ( String .join ( " , " , new new NoneGenericClass () GetNumbers ()).); // BUG: when Finalizer method does not take effect PassthroughPostfix
        Console.WriteLine();

        harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.DoSomething2)),
            new HarmonyMethod(GetMethod(nameof(StaticPrefix))),
            new HarmonyMethod(GetMethod(nameof(MyPostfix))),
            new HarmonyMethod(GetMethod(nameof(MyTranspiler))),
            new HarmonyMethod(GetMethod(nameof(MyFinalizer))));

        Console.WriteLine(NoneGenericClass.DoSomething2());
    }

    static MethodInfo GetMethod(string name) => typeof(NoneGenericClassPatcher).GetMethod(name, BindingFlags.Static | BindingFlags.Public);

    public static bool MyPrefix(out Stopwatch __state, ref bool ____isRunning)
    {
        __state = Stopwatch.StartNew();
        Console.WriteLine($"{nameof(MyPrefix)} {____isRunning}");

        return true;
    }

    public static bool StaticPrefix(out Stopwatch __state)
    {
        __state = Stopwatch.StartNew();
        Console.WriteLine($"{nameof(StaticPrefix)}");

        return true;
    }

    public static void MyPostfix(Stopwatch __state, ref int __result, MethodBase __originalMethod)
    {
        Console.WriteLine($"{__state.ElapsedMilliseconds} {__result++}");
        Console.WriteLine(nameof(MyPostfix));
    }

    public static IEnumerable<int> PassthroughPostfix(IEnumerable<int> values)
    {
        yield return 0;
        foreach (var value in values)
            if (value > 1)
                yield return value * 10;
        yield return 99;
        Console.WriteLine(nameof(PassthroughPostfix));
    }

    // looks for STDFLD someField and inserts CALL MyExtraMethod before it
    public static IEnumerable<CodeInstruction> MyTranspiler(IEnumerable<CodeInstruction> instructions)
    {
        Console.WriteLine(nameof(MyTranspiler));
        //var found = false;
        foreach (var instruction in instructions)
        {
            //if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField)
            //{
            //    yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod);
            //    found = true;
            //}
            yield return instruction;
        }
        //if (found == false)
        //    ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod");
    }

    public static void MyFinalizer(Exception __exception)
    {
        Console.WriteLine($"{nameof(MyFinalizer)} {__exception}");
    }
}

 

Automatic mode

public class Annotations
{
    private readonly bool _isRunning;

    public IEnumerable<int> GetNumbers()
    {
        Console.WriteLine(nameof(GetNumbers));

        yield return 1;
        yield return 2;
        yield return 3;
    }
}

[HarmonyPatch(typeof(Annotations))]
[HarmonyPatch(nameof(Annotations.GetNumbers))]
public class AnnotationsPatcher
{
    static AccessTools.FieldRef<Annotations, bool> isRunningRef =
        AccessTools.FieldRefAccess<Annotations, bool>("_isRunning");

    public static void Patch()
    {
        var harmony = new Harmony(nameof(AnnotationsPatcher));

        harmony.PatchAll();

        Console.WriteLine(string.Join(", ", new Annotations().GetNumbers()));
    }

    static bool Prefix(Annotations __instance)
    {
        Console.WriteLine("Prefix");

        return true;
    }

    /// <summary>Not working</summary>
    static IEnumerable<int> Postfix(IEnumerable<int> values)
    {
        yield return 0;
        foreach (var value in values)
            if (value > 1)
                yield return value * 10;
        yield return 99;
        Console.WriteLine(nameof(Postfix));
    }

    // looks for STDFLD someField and inserts CALL MyExtraMethod before it
    public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
    {
        Console.WriteLine(nameof(Transpiler));
        //var found = false;
        foreach (var instruction in instructions)
        {
            //if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField)
            //{
            //    yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod);
            //    found = true;
            //}
            yield return instruction;
        }
        //if (found == false)
        //    ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod");
    }
}

Run the code

static void Main(string[] args)
{
    NoneGenericClassPatcher.Patch();
    Console.WriteLine();
    AnnotationsPatcher.Patch();
}

Output

MyTranspiler
MyPrefix True
DoSomething
1 20
MyPostfix
MyFinalizer
21

MyTranspiler
MyPrefix True
MyFinalizer
GetNumbers
1, 2, 3

MyTranspiler
StaticPrefix
DoSomething2
0 3333
MyPostfix
MyFinalizer
3334

Tran Spinnaker
Prefix
GetNumbers
Postfix
0, 20, 30, 99

Guess you like

Origin www.cnblogs.com/qhca/p/12336332.html
Recommended