一个开放源代码,实现动态IL注入(Hook或补丁工具)框架:Lib.Harmony

-- 一个开放源代码,实现动态IL注入(Hook或补丁工具)框架:Lib.Harmony
【官网】:https://github.com/pardeike/Harmony

应用场景

例如游戏补丁等场景:用于在运行时修补替换和装饰 .net/.net core 方法的库。以少数基于反射的声名,注解方式实现修改原有功能。

基础资源

Lib.Harmony 2.1.1

使用须知

Lib.Harmony -用于在运行时修补替换和装饰 .net/.net core 方法的库。 但是该技术可以与任何.NET版本一起使用。它对同一方法的多次更改是累积而不是覆盖。 1)最新2.0版支持.net core 2)Harmony支持手动(Patch)和自动(PatchAll)。 3)位置可以是执行前(Prefix)、执行后(Postfix)和终结嚣(Finalizer),也可更详细的手动修改IL(Transpiler)、 4)Getter/Setter、虚/ 非虚 方法、 静态 方法

配置步骤

【Nuget包地址】

https://www.nuget.org/packages/Lib.Harmony/

[注]  2.0版开始支持.net core.


A)原理。

1)自动补丁(patch)被识别,Lib.Harmony使用的是c#的特性机制。

2)它为每个原始方法创建DynamicMethod方法,并向其织入代码,该代码在开始和结束时调用自定义方法。它还允许您编写过滤器来处理原始的IL代码,从而可以对原始方法进行更详细的操作。

B)补丁参数及注意事项。

1)补丁方法必须是静态方法
2)Prefix需要返回void或者bool类型(void即不拦截)
3)Postfix需要返回void类型,或者返回的类型要与第一个参数一致(直通模式)
4)如果原方法不是静态方法,则可以使用名为__instance(两个下划线)的参数来访问对象实例
5)可以使用名为__result(两个下划线)的参数来访问方法的返回值,如果是Prefix,则得到返回值的默认值
6)可以使用名为__state(两个下划线)的参数在Prefix补丁中存储任意类型的值,然后在Postfix中使用它,你有责任在Prefix中初始化它的值
7)可以使用与原方法中同名的参数来访问对应的参数,如果你要写入非引用类型,记得使用ref关键字
8)补丁使用的参数必须严格对应类型(或者使用object类型)和名字
9)我们的补丁只需要定义我们需要用到的参数,不用把所有参数都写上
10)要允许补丁重用,可以使用名为__originalMethod(两个下划线)的参数注入原始方法


常见问题

快速入门

1)一个不修改原业务代码实现补丁的方案示例.

 假设有一个待修改的处理类:

 Lib.Harmony


1.1)在指定方法前后Inject(注入)方法.

Lib.Harmony

1.2)修改指定方法中的传入参数。

Lib.Harmony

1.3)修改指定方法的返回值。

Lib.Harmony


[附:源码地址]

https://github.com/configlab/ResearchNotes-Demo

[源码解析-1: Hook调用]

using CoreLab.LibHarmony.HookDemo;
using HarmonyLib;
using System;
using System.Reflection;

namespace CoreLab.LibHarmony
{
    /// <summary>
	/// author: http://config.net.cn
	/// project site:https://github.com/configlab/ResearchNotes-Demo
	/// description:inject method before or after target method
	/// create date: 2021-10-6
	/// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            InitHook();
            TestBusiness tb = new TestBusiness();
            tb.DoWork("dowork.entry");
        }
        private static void InitHook()
        {
            Harmony.DEBUG = true;//开启Harmony调试日志
            InjectWithoutParams.AddInjectMethod();//调用了注入方法
            var harmony = new Harmony("cn.net.config.configlab");//唯一的实例标识
            var assembly = Assembly.GetExecutingAssembly();//注入到当前执行的程序集中
            harmony.PatchAll(assembly);//自动补丁
        }
    }
}
[源码解析-2: Inject一个Method]

using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Text;

namespace CoreLab.LibHarmony.HookDemo
{
	/// <summary>
	/// author: http://config.net.cn
	/// project site:https://github.com/configlab/ResearchNotes-Demo
	/// description:inject method before or after target method
	/// create date: 2021-10-6
	/// </summary>
	public class InjectWithoutParams
    {
		public static void AddInjectMethod()
		{
			var harmony = new Harmony("CoreLab.LibHarmony.TestHook.DoPatching");

			//
			var mOriginal = AccessTools.Method(typeof(TestBusiness), "DoWork");
			var mPrefix = SymbolExtensions.GetMethodInfo(() => InjectPrefix());
			var mPostfix = SymbolExtensions.GetMethodInfo(() => InjectPostfix());
			// in general, add null checks here (new HarmonyMethod() does it for you too)

			harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix));//这里不一定要前后同时注入,不想注入则传入null
			//harmony.Patch(mOriginal,null, new HarmonyMethod(mPostfix));
		}

		private static void InjectPrefix()
		{
			Console.WriteLine("TestHook.InjectPrefix(IL中插入在目标方法前的方法)");
		}

		private static void InjectPostfix()
		{
			Console.WriteLine("TestHook.InjectPostfix(IL中插入在目标方法后的方法)");
		}
	}
}
[源码解析-3: 修改返回值]

using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace CoreLab.LibHarmony.HookDemo
{
	/// <summary>
	/// author: http://config.net.cn
	/// project site:https://github.com/configlab/ResearchNotes-Demo
	/// description:update the return result of method before exit function
	/// create date: 2021-10-6
	/// </summary>
	[HarmonyPatch(typeof(TestBusiness), nameof(TestBusiness.GetName))]
	public class UpdateResultOfMethod
	{
        public static void Postfix(ref string __result)
        {
            __result = "配置啦(config.net.cn)--IL中方法的返回值被修改";
        }
	}
}

参考资料