找回密码
 立即注册
首页 资源区 代码 .NET外挂系列:6. harmony中一些实用的反射工具包 ...

.NET外挂系列:6. harmony中一些实用的反射工具包

JulianaEgg 2025-5-28 22:05:49
一:背景

1. 讲故事

本来想研究一下 IL编织和反向补丁的相关harmony知识,看了下其实这些东西对 .NET高级调试 没什么帮助,所以本篇就来说一些比较实用的反射工具包吧。
二:反射工具包

1. AccessTools

AccessTools这个工具包用来简化反射操作,你如果看过 harmony 的底层代码,就会发现无处不在 AccessTools,比如 HarmonyPatch 的第20个重载方法。
  1.     //
  2.     // Summary:
  3.     //     An annotation that specifies a method, property or constructor to patch
  4.     //
  5.     // Parameters:
  6.     //   typeName:
  7.     //     The full name of the declaring class/type
  8.     //
  9.     //   methodName:
  10.     //     The name of the method, property or constructor to patch
  11.     //
  12.     //   methodType:
  13.     //     The HarmonyLib.MethodType
  14.     public HarmonyPatch(string typeName, string methodName, MethodType methodType = MethodType.Normal)
  15.     {
  16.         info.declaringType = AccessTools.TypeByName(typeName);
  17.         info.methodName = methodName;
  18.         info.methodType = methodType;
  19.     }
复制代码
现在的好消息是你也可以直接使用 AccessTools,使用方式和 HarmonyPatch的构造函数注入方式几乎一摸一样, 为了方便演示,我们还是用 Thread 来跟大家聊一聊,我用大模型生成了一批例子。参考如下:
  1.         static void Main(string[] args)
  2.         {
  3.             var thread = new Thread(() => { });
  4.             thread.Start();
  5.             //1. 反射出 Thread.Start 方法。
  6.             var original1 = AccessTools.Method(typeof(Thread), "Start", new Type[] { });
  7.             Console.WriteLine($"1. {original1.Name}");
  8.             //2. 获取 Thread.Priority 属性
  9.             var original2 = AccessTools.PropertyGetter(typeof(Thread), "Priority");
  10.             Console.WriteLine($"2. {original2.Name}");
  11.             //3. 获取 Thread(ThreadStart start) 构造函数信息
  12.             var original3 = AccessTools.Constructor(typeof(Thread), new Type[] { typeof(ThreadStart) });
  13.             Console.WriteLine($"3. {original3.Name}");
  14.             //4. 获取 Thread.Join() 方法
  15.             var original4 = AccessTools.Method(typeof(Thread), "Join", new Type[] { });
  16.             Console.WriteLine($"4. {original4.Name}");
  17.             //5. 获取 Thread.Sleep(int) 方法
  18.             var original5 = AccessTools.Method(typeof(Thread), "Sleep", new Type[] { typeof(int) });
  19.             Console.WriteLine($"5. {original5.Name}");
  20.             //6. 获取 Thread.ManagedThreadId 属性
  21.             var original6 = AccessTools.PropertyGetter(typeof(Thread), "ManagedThreadId");
  22.             Console.WriteLine($"6. {original6.Name}");
  23.             //7. 获取 Thread.CurrentThread 静态属性
  24.             var original7 = AccessTools.PropertyGetter(typeof(Thread), "CurrentThread");
  25.             Console.WriteLine($"7. {original7.Name}");
  26.             //8. 获取 Thread.IsBackground 属性设置器
  27.             var original8 = AccessTools.PropertySetter(typeof(Thread), "IsBackground");
  28.             Console.WriteLine($"8. {original8.Name}");
  29.             //9. 获取 Thread.Abort() 方法 (已过时,但仍可获取)
  30.             var original9 = AccessTools.Method(typeof(Thread), "Abort", new Type[] { });
  31.             Console.WriteLine($"9. {original9?.Name ?? "null"}");
  32.             //10. 获取 Thread.Start(object) 方法 (参数化线程启动)
  33.             var original10 = AccessTools.Method(typeof(Thread), "Start", new Type[] { typeof(object) });
  34.             Console.WriteLine($"10. {original10?.Name ?? "null"}");
  35.             //11. 获取 Thread 类的所有字段
  36.             var allFields = AccessTools.GetDeclaredFields(typeof(Thread));
  37.             Console.WriteLine($"11. Thread类字段数量: {allFields.Count}");
  38.             //12. 获取 Thread 类的所有方法
  39.             var allMethods = AccessTools.GetDeclaredMethods(typeof(Thread));
  40.             Console.WriteLine($"12. Thread类方法数量: {allMethods.Count}");
  41.             //13. 获取 Thread 类的内部类 "StartHelper"
  42.             var threadHelperType = AccessTools.Inner(typeof(Thread), "StartHelper");
  43.             Console.WriteLine($"13. 获取Thread.ThreadHelper内部类: {(threadHelperType != null ? "成功" : "失败")}");
  44.             //14. 获取 ThreadPool.QueueUserWorkItem 方法
  45.             var original15 = AccessTools.Method(typeof(ThreadPool), "QueueUserWorkItem",
  46.                 new Type[] { typeof(WaitCallback) });
  47.             Console.WriteLine($"14. {original15.Name}");
  48.             Console.ReadLine();
  49.         }
复制代码
1.png

是不是非常的方便,不管你爱不爱,反正我是爱了。
2. Traverse

如果说AccessTools 针对类型反射,那Travers就是针对实例反射,并且它还能够挖沟一个对象的全部细节,参考代码如下:
  1.         static void Main(string[] args)
  2.         {
  3.             var thread = new Thread(() =>
  4.             {
  5.                 Thread.Sleep(1000);
  6.                 Console.WriteLine("5. 线程执行完成");
  7.             });
  8.             // 使用 Traverse 访问线程内部状态
  9.             var traverse = Traverse.Create(thread);
  10.             // 1. 获取线程的委托 (_start 字段)
  11.             var startDelegate = traverse.Field("_startHelper").Field("_start").GetValue<ThreadStart>();
  12.             Console.WriteLine($"1. 线程委托方法: {startDelegate?.Method.Name ?? "null"}");
  13.             // 2. 获取线程的执行状态 (_threadState 字段)
  14.             var threadState = traverse.Field("_threadState").GetValue<int>();
  15.             Console.WriteLine($"2. 线程状态: {threadState} (0=未启动, 1=运行中, 2=停止)");
  16.             // 3. 设置线程的 IsBackground 属性
  17.             traverse.Property("IsBackground").SetValue(true);
  18.             Console.WriteLine($"3. 设置后台线程: {thread.IsBackground}");
  19.             // 4. 调用 Start 方法
  20.             traverse.Method("Start").GetValue();
  21.             Console.WriteLine("4. 调用 Start() 方法启动线程");
  22.             Console.ReadLine();
  23.         }
复制代码
2.png

3. FileLog

像 Harmony 这种外挂,一旦有注入失败,很难分析为什么,所以必须要有详细的日志才能帮我们排查问题,在 harmony 中有两种记录日志的方式:全局模式和局部模式,这里我们说下前者,对,只要写上 Harmony.DEBUG = true; 这句话即可,然后harmony就会在桌面创建一个 harmony.log.txt 文件,参考如下:
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             Harmony.DEBUG = true;
  6.             var harmony = new Harmony("com.example.threadhook");
  7.             harmony.PatchAll();
  8.             Console.ReadLine();
  9.         }
  10.     }
  11.     [HarmonyPatch(typeof(Thread), "Start", new Type[] { typeof(object) })]
  12.     public class ThreadStartHook
  13.     {
  14.         public static void Prefix(Thread __instance)
  15.         {
  16.         }
  17.     }
复制代码
3.png

打开之后就可以看到 patch Thread.Start 方法的IL 细节了。
  1. ### Harmony id=com.example.threadhook, version=2.3.6.0, location=D:\skyfly\20.20250116\src\Example\Example_20_1_1\bin\Debug\net8.0\0Harmony.dll, env/clr=8.0.13, platform=Win32NT
  2. ### Started from static System.Void Example_20_1_1.Program::Main(System.String[] args), location D:\skyfly\20.20250116\src\Example\Example_20_1_1\bin\Debug\net8.0\Example_20_1_1.dll
  3. ### At 2025-05-21 05.43.09
  4. ### Patch: System.Void System.Threading.Thread::Start(System.Object parameter)
  5. ### Replacement: static System.Void System.Threading.Thread::System.Threading.Thread.Start_Patch1(System.Threading.Thread this, System.Object parameter)
  6. IL_0000: ldarg.0
  7. IL_0001: call       static System.Void Example_20_1_1.ThreadStartHook::Prefix(System.Threading.Thread __instance)
  8. IL_0006: // start original
  9. IL_0006: ldarg.0
  10. IL_0007: ldarg.1
  11. IL_0008: ldc.i4.1
  12. IL_0009: ldc.i4.0
  13. IL_000A: call       System.Void System.Threading.Thread::Start(System.Object parameter, System.Boolean captureContext, System.Boolean internalThread)
  14. IL_000F: // end original
  15. IL_000F: ret
  16. DONE
复制代码
其实这些日志底层都是通过 FileLog 来写的,万幸的是它也开了口子给开发者,见下面参考代码。
  1.         static void Main(string[] args)
  2.         {
  3.             Harmony.DEBUG = true;
  4.             var harmony = new Harmony("com.example.threadhook");
  5.             harmony.PatchAll();
  6.             FileLog.Debug("hello world!");
  7.             Console.ReadLine();
  8.         }
复制代码
4.png

三:总结

这篇我们讲述的三个小工具包,更多的还是提高我们工作效率而准备的,用完之后也确实让人爱不释手。
5.jpg


来源:新程序网络收集,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册