.NET外挂系列:6. harmony中一些实用的反射工具包
<h2 id="一背景">一:背景</h2><h3 id="1-讲故事">1. 讲故事</h3>
<p>本来想研究一下 <code>IL编织</code>和<code>反向补丁</code>的相关harmony知识,看了下其实这些东西对 <code>.NET高级调试</code> 没什么帮助,所以本篇就来说一些比较实用的反射工具包吧。</p>
<h2 id="二反射工具包">二:反射工具包</h2>
<h3 id="1-accesstools">1. AccessTools</h3>
<p>AccessTools这个工具包用来简化反射操作,你如果看过 harmony 的底层代码,就会发现无处不在 <code>AccessTools</code>,比如 HarmonyPatch 的第20个重载方法。</p>
<pre><code class="language-C#">
//
// Summary:
// An annotation that specifies a method, property or constructor to patch
//
// Parameters:
// typeName:
// The full name of the declaring class/type
//
// methodName:
// The name of the method, property or constructor to patch
//
// methodType:
// The HarmonyLib.MethodType
public HarmonyPatch(string typeName, string methodName, MethodType methodType = MethodType.Normal)
{
info.declaringType = AccessTools.TypeByName(typeName);
info.methodName = methodName;
info.methodType = methodType;
}
</code></pre>
<p>现在的好消息是你也可以直接使用 <code>AccessTools</code>,使用方式和 <code>HarmonyPatch</code>的构造函数注入方式几乎一摸一样, 为了方便演示,我们还是用 Thread 来跟大家聊一聊,我用大模型生成了一批例子。参考如下:</p>
<pre><code class="language-C#">
static void Main(string[] args)
{
var thread = new Thread(() => { });
thread.Start();
//1. 反射出 Thread.Start 方法。
var original1 = AccessTools.Method(typeof(Thread), "Start", new Type[] { });
Console.WriteLine($"1. {original1.Name}");
//2. 获取 Thread.Priority 属性
var original2 = AccessTools.PropertyGetter(typeof(Thread), "Priority");
Console.WriteLine($"2. {original2.Name}");
//3. 获取 Thread(ThreadStart start) 构造函数信息
var original3 = AccessTools.Constructor(typeof(Thread), new Type[] { typeof(ThreadStart) });
Console.WriteLine($"3. {original3.Name}");
//4. 获取 Thread.Join() 方法
var original4 = AccessTools.Method(typeof(Thread), "Join", new Type[] { });
Console.WriteLine($"4. {original4.Name}");
//5. 获取 Thread.Sleep(int) 方法
var original5 = AccessTools.Method(typeof(Thread), "Sleep", new Type[] { typeof(int) });
Console.WriteLine($"5. {original5.Name}");
//6. 获取 Thread.ManagedThreadId 属性
var original6 = AccessTools.PropertyGetter(typeof(Thread), "ManagedThreadId");
Console.WriteLine($"6. {original6.Name}");
//7. 获取 Thread.CurrentThread 静态属性
var original7 = AccessTools.PropertyGetter(typeof(Thread), "CurrentThread");
Console.WriteLine($"7. {original7.Name}");
//8. 获取 Thread.IsBackground 属性设置器
var original8 = AccessTools.PropertySetter(typeof(Thread), "IsBackground");
Console.WriteLine($"8. {original8.Name}");
//9. 获取 Thread.Abort() 方法 (已过时,但仍可获取)
var original9 = AccessTools.Method(typeof(Thread), "Abort", new Type[] { });
Console.WriteLine($"9. {original9?.Name ?? "null"}");
//10. 获取 Thread.Start(object) 方法 (参数化线程启动)
var original10 = AccessTools.Method(typeof(Thread), "Start", new Type[] { typeof(object) });
Console.WriteLine($"10. {original10?.Name ?? "null"}");
//11. 获取 Thread 类的所有字段
var allFields = AccessTools.GetDeclaredFields(typeof(Thread));
Console.WriteLine($"11. Thread类字段数量: {allFields.Count}");
//12. 获取 Thread 类的所有方法
var allMethods = AccessTools.GetDeclaredMethods(typeof(Thread));
Console.WriteLine($"12. Thread类方法数量: {allMethods.Count}");
//13. 获取 Thread 类的内部类 "StartHelper"
var threadHelperType = AccessTools.Inner(typeof(Thread), "StartHelper");
Console.WriteLine($"13. 获取Thread.ThreadHelper内部类: {(threadHelperType != null ? "成功" : "失败")}");
//14. 获取 ThreadPool.QueueUserWorkItem 方法
var original15 = AccessTools.Method(typeof(ThreadPool), "QueueUserWorkItem",
new Type[] { typeof(WaitCallback) });
Console.WriteLine($"14. {original15.Name}");
Console.ReadLine();
}
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/214741/202505/214741-20250523092549184-1819982515.png" alt="" loading="lazy"></p>
<p>是不是非常的方便,不管你爱不爱,反正我是爱了。</p>
<h3 id="2-traverse">2. Traverse</h3>
<p>如果说<code>AccessTools</code> 针对<code>类型反射</code>,那Travers就是针对<strong>实例反射</strong>,并且它还能够挖沟一个对象的全部细节,参考代码如下:</p>
<pre><code class="language-C#">
static void Main(string[] args)
{
var thread = new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine("5. 线程执行完成");
});
// 使用 Traverse 访问线程内部状态
var traverse = Traverse.Create(thread);
// 1. 获取线程的委托 (_start 字段)
var startDelegate = traverse.Field("_startHelper").Field("_start").GetValue<ThreadStart>();
Console.WriteLine($"1. 线程委托方法: {startDelegate?.Method.Name ?? "null"}");
// 2. 获取线程的执行状态 (_threadState 字段)
var threadState = traverse.Field("_threadState").GetValue<int>();
Console.WriteLine($"2. 线程状态: {threadState} (0=未启动, 1=运行中, 2=停止)");
// 3. 设置线程的 IsBackground 属性
traverse.Property("IsBackground").SetValue(true);
Console.WriteLine($"3. 设置后台线程: {thread.IsBackground}");
// 4. 调用 Start 方法
traverse.Method("Start").GetValue();
Console.WriteLine("4. 调用 Start() 方法启动线程");
Console.ReadLine();
}
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/214741/202505/214741-20250523092549171-1142586083.png" alt="" loading="lazy"></p>
<h3 id="3-filelog">3. FileLog</h3>
<p>像 Harmony 这种外挂,一旦有注入失败,很难分析为什么,所以必须要有详细的日志才能帮我们排查问题,在 harmony 中有两种记录日志的方式:<code>全局模式</code>和<code>局部模式</code>,这里我们说下前者,对,只要写上 <code>Harmony.DEBUG = true;</code> 这句话即可,然后harmony就会在桌面创建一个 <code>harmony.log.txt</code> 文件,参考如下:</p>
<pre><code class="language-C#"> internal class Program
{
static void Main(string[] args)
{
Harmony.DEBUG = true;
var harmony = new Harmony("com.example.threadhook");
harmony.PatchAll();
Console.ReadLine();
}
}
{ typeof(object) })]
public class ThreadStartHook
{
public static void Prefix(Thread __instance)
{
}
}
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/214741/202505/214741-20250523092549163-839466084.png" alt="" loading="lazy"></p>
<p>打开之后就可以看到 patch Thread.Start 方法的IL 细节了。</p>
<pre><code class="language-C#">
### 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
### 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
### At 2025-05-21 05.43.09
### Patch: System.Void System.Threading.Thread::Start(System.Object parameter)
### Replacement: static System.Void System.Threading.Thread::System.Threading.Thread.Start_Patch1(System.Threading.Thread this, System.Object parameter)
IL_0000: ldarg.0
IL_0001: call static System.Void Example_20_1_1.ThreadStartHook::Prefix(System.Threading.Thread __instance)
IL_0006: // start original
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: ldc.i4.1
IL_0009: ldc.i4.0
IL_000A: call System.Void System.Threading.Thread::Start(System.Object parameter, System.Boolean captureContext, System.Boolean internalThread)
IL_000F: // end original
IL_000F: ret
DONE
</code></pre>
<p>其实这些日志底层都是通过 <code>FileLog</code> 来写的,万幸的是它也开了口子给开发者,见下面参考代码。</p>
<pre><code class="language-C#">
static void Main(string[] args)
{
Harmony.DEBUG = true;
var harmony = new Harmony("com.example.threadhook");
harmony.PatchAll();
FileLog.Debug("hello world!");
Console.ReadLine();
}
</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/214741/202505/214741-20250523092549156-1421460612.png" alt="" loading="lazy"></p>
<h2 id="三总结">三:总结</h2>
<p>这篇我们讲述的三个小工具包,更多的还是提高我们工作效率而准备的,用完之后也确实让人<code>爱不释手</code>。<br>
<img src="https://images.cnblogs.com/cnblogs_com/huangxincheng/345039/o_210929020104最新消息优惠促销公众号关注二维码.jpg" width="700" height="300" alt="图片名称" align="center"></p><br><br>
来源:https://www.cnblogs.com/huangxincheng/p/18892412
頁:
[1]