[C#][HarmonyPatch]Manual patch internal class/anonymous method/d
前言:
在使用HarmonyPatch制作各种各样mod的时候,总会遇到一些奇奇怪怪的情况导致无法patch。本篇文章将会介绍其中的一些情况以及应对方法。
主要内容:
介绍manual patch
如何manual patch internal class下的method
如何manual patch 匿名方法(委托)
另:本人并未系统的学习过C#,所有对于C#的了解都是基于读别人的代码后连猜带蒙给蒙出来的,知识的局限性必然会导致我敲代码的局限性。希望大佬们能够多多指正我的错误,以及提出更加优化解法。

什么是manual patch:
通常情况下,我们会使用如下的模版进行patch,也是最常见的方式
这种方法,相当于你告诉HarmonyPatch:去,把某个class下的某个method给我找到,然后我要对它进行修改。
大多数情况下,HarmonyPatch都能帮你找到,所以这是最常见的方式。但是某些特殊的情况(后文我会介绍),HarmonyPatch无法做到,这个时候我们就不能依赖HarmonyPatch帮我们找到method,我们需要通过各种各样的方式,自己去找到想要patch的method,然后把它的信息告诉HarmonyPatch,最后对该method进行修改,那么这种方式我们就称它为manual patch。因为你需要手动去找到method的信息。
那么manual patch的模版如下
如果你看到这里还是云里雾里,没关系,因为接下来我们会通过两个实例,结合该模版进行操作。代码这种东西,看多了自然就懂了。

Manual patch internal class下的method:
如果想要patch的class是internal class,那么HarmonyPatch是没有办法直接工作的。
实例:
以Farm Together这款游戏进行举例,该游戏下有这样一个class以及method
我的目的是对LocalFarmhandScript.StartGoingToTile进行transpiler
将bool flag = false; 替换成 bool flag = true;
将this.teleportTime = magnitude / 3f; 替换成this.teleportTime = magnitude / 300f;
那么按照通常方法,应该这么写
但是很遗憾,由于LocalFarmhandScript是internal class,HarmonyPatch没有办法直接寻找到该class,直接导致语句报错。
那么这个时候,就是使用manual patch的时候了。我们先通过AccessTools.Method找到MethodInfo/MethodBase,通过该信息手动定位到想要patch的method,然后进行修改,具体例子如下

Manual patch anonymous method/delegate:
实例:
以Monster Train(怪物火车)这款游戏进行举例,该游戏下有这样一个class以及method
我的目的是对StoryEventPoolData.TriggerNode进行transpiler
将 saveManager.Save(true); 替换成saveManager.Save(false);
首先我尝试的是导出该函数下的所有IL instructions
导出数据如下
非常遗憾,无论我怎么看,我也找不到SaveManager.Save这个函数的调用,那更别谈把参数从true改成false了。
但是如果我用dnspy打开该函数,把光标移动到saveManager.Save(true);这条语句上,然后右键点击Edit IL Instructions,会出现如下截图

很明显,在这张截图里,我们就能看见SaveManager::Save。
这时候,我们看下截图的左上角的标题,可以发现Method Body不是TriggerNode,而是<TriggerNode>b__0。换言之,我们需要patch的其实并不是TriggerNode,而是这个<TriggerNode>b__0。
但是这个<TriggerNode>b__0究竟是什么,老实说我真的一头雾水。于是我去网上搜了下,发现这玩意叫匿名函数或者委托。根据我浅薄的理解,大概意思呢就是一个大函数里面包含了一个小函数,由于这个小函数不常用,于是就不另外给它进行正式的书写以及命名了,所以称它为匿名函数。
事儿就是这么个事儿,但问题是我要如何定位到这个<TriggerNode>b__0,然后进行修改呢?
我进行了多种尝试,比如
通通以失败告终后,我突然想起上面那个farm together的例子,先尝试下通过AccessTools.Method能不能获取MethodInfo,然后再谈后话。
回到导出TriggerNode下所有IL instructions的数据,可以发现如下这么一条代码
那么这段代码里,其实就包含了所有关于我想要定位的那个<TriggerNode>b__0的信息。
首先它的全名应该是"StoryEventPoolData/<>c__DisplayClass7_0:<TriggerNode>b__0",注意需要把双冒号替换成单冒号,因为AccessTools.Method就是这么规定格式的。
再来它的参数类型为IScreen。
有了这些信息后,我们就可以尝试使用AccessTools.Method来获取Method信息,即
幸运的是,我测试了它的返回值不为空,也就是说成功找到了。那么接下来就简单了,无非就是用manual patch来解决定位的问题,具体代码如下

补充阅读:
https://harmony.pardeike.net/articles/patching.html