Unity游戏Mod/插件制作教程06 - Harmony补丁基础
admin
2024-03-19 07:21:33
0

前言
通过之前的教程,我们已经知道如何编写基本的插件,如果你有C#和Unity的基础,这个时候已经可以做出一些功能了,比如通过按键修改游戏数据之类的。但是,这有很大的局限性,因为通常情况下,我们并不想通过按键来调用我们的功能,我们想让大多数的功能都是加载之后就不需要管了,或者想做一些普通情况下比较难以操作的事情。这个时候,通过Harmony进行补丁可以解决我们绝大多数的需求。

Harmony的github链接 https://github.com/pardeike/Harmony 详细信息可以在github查看。

Harmony中使用最频繁的两个地方就是前置补丁和后置补丁,也是最简单的,本篇文章主要就讲这两种。一些特殊的需求需要修改函数本身也是可以的,Harmony支持修改函数的IL码,不过这个就不在基础的范畴了,以后有机会的话会放在进阶篇来讲。

HarmonyPatch特性
要对游戏中的方法进行补丁,首先我们需要确定一个目标,这里我准备了一个类,我们就以这个类为例子,对它进行补丁。

public class People
{
    public string Name{ get; set; };
    public int Age{ get; set; };

    public People(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public void Sleep()
    {
        Console.WriteLine("睡觉");
    }

    public void Say()
    {
        Console.WriteLine("Hello");
    }

    public void Say(string content)
    {
        Console.WriteLine(content);
    }

    public void Say(int content)
    {
        Console.WriteLine(content);
    }
}
这是一个简单的People类,有两个属性,分别是姓名和年龄,一个构造函数,一个Sleep方法,还有3个说话的方法,使用了3种重载。

以Sleep方法为例,我们写一个最简单的补丁。

[HarmonyPatch(typeof(People), "Sleep")]
class PeopleSleepPatch
{
    public static void Postfix(People __instance)
    {
        Console.WriteLine(__instance.Name + "睡觉了");
    }
}
这里的HarmonyPatch特性就用于确定补丁目标,这个特性的参数可以写在一排也可以分成几排写。例子中的两个参数分别是要补丁的类型,还有要补丁的方法的名字。这是最简单的情况,实际上我们还经常会遇到其他几种情况。比如,Say方法有3个重载,如何确定要补丁哪一个?属性要怎么补丁?我们再来看几个例子。

[HarmonyPatch(typeof(People), "Say", new Type[] { })]
[HarmonyPatch(typeof(People), "Say", new Type[] { typeof(string) })]
[HarmonyPatch(typeof(People), "Say", new Type[] { typeof(int) })]
如此,面对有重载的情况,我们只需要在添加一个参数,这个参数是一个Type数组,我们按顺序将参数类型填入即可。

[HarmonyPatch(typeof(People), "Name", MethodType.Getter]
[HarmonyPatch(typeof(People), "Age", MethodType.Setter]
[HarmonyPatch(typeof(People), MethodType.Constructor]
面对属性和构造函数,我们可以使用MethodType枚举来当作参数。需要注意的是,补丁构造函数时,函数名不能写dnSpy中看到的.ctor,而是应该直接省略不写函数名。

补丁方法
既然已经可以确定补丁目标了,接下来让我们了解一下最基础最常用的两种补丁方法,Prefix(前置补丁)、Postfix(后置补丁)。

先说后置补丁,这是最简单的,它在补丁目标运行结束之后运行,上面示例中的就是后置补丁,可以使用__result参数接收目标的返回值。

然后是前置补丁,顾名思义,它是在补丁目标运行之前运行的,这个相对复杂一点。因为我们可以选择是否执行原方法。我们来看两个例子。

[HarmonyPatch(typeof(People), "Say",  new Type[] { typeof(string) })]
class PeopleSayPatch
{
    public static bool Prefix(ref string content)
    {
        content = "要说的内容已被修改";
        return true;
    }
}

[HarmonyPatch(typeof(People), "Name",  MethodType.Getter)]
class PeopleNamePatch
{
    public static bool Prefix(ref string __result)
    {
        __result = "张三";
        return false; //拦截原方法,直接使用我们给出的结果
    }
}
第一个例子,我们将content的值修改为了我们自己想要的值,然后返回true表示让原函数继续执行。第二个例子,我们直接将最终结果修改,然后返回false,表示阻止原函数执行。如果你搞不明白IL代码,不知道如何修改函数本体,也可以通过前置补丁的方式自己计算结果然后修改。

补丁参数
在上面的例子中,我们有时候使用了__instance,有时候使用了__result,想必读者还留有疑问,为什么要这么写。其实,这是Harmony作者为我们定好的获取方法信息的方式。

大概情况如下:

补丁方法必须是静态方法

Prefix需要返回void或者bool类型(void即不拦截)

Postfix需要返回void类型,或者返回的类型要与第一个参数一致(直通模式)

如果原方法不是静态方法,则可以使用名为__instance(两个下划线)的参数来访问对象实例

可以使用名为__result(两个下划线)的参数来访问方法的返回值,如果是Prefix,则得到返回值的默认值

可以使用名为__state(两个下划线)的参数在Prefix补丁中存储任意类型的值,然后在Postfix中使用它,你有责任在Prefix中初始化它的值

可以使用与原方法中同名的参数来访问对应的参数,如果你要写入非引用类型,记得使用ref关键字

补丁使用的参数必须严格对应类型(或者使用object类型)和名字

我们的补丁只需要定义我们需要用到的参数,不用把所有参数都写上

要允许补丁重用,可以使用名为__originalMethod(两个下划线)的参数注入原始方法

Transpilers还有一些可选参数,我们这里不做探讨,想了解可以访问Harmony的wiki。

自动补丁
补丁的情况我们大体介绍完了,但是我们现在只是写了补丁,还没有对游戏进行补丁,其实很简单,我们只要在插件加载的时候,加上一句代码就好。

new Harmony("me.xiaoye97.plugin.Tutorial").PatchAll(); //以作者输入的字符串作为ID,对程序集中所有找到的补丁方法进行补丁。
或者

Harmony.CreateAndPatchAll(typeof(PluginTutorial)); //Harmony以类名为ID进行补丁,并且只补丁此类下的方法。
除了自动补丁之外,还可以进行手动补丁,可以更加细微的控制,就不在基础教程中说了,读者可以通过GitHub继续了解,以后的教程如果遇到需要手动补丁的情况我再继续讲解。

相关内容

热门资讯

2025年全国经营主体发展向新... 来源:中国金融信息网 新华财经北京2月26日电(记者王悦阳)记者2月26日从市场监管总局获悉,202...
光明日报|通关便利化持续赋能外... 通关便利化持续赋能外贸发展 光明日报记者 张 翼 今年是“十五五”开局之年,为确保外贸开好局起好步,...
原创 明... 2026年2月26日,黄金市场出现了一个极其罕见的信号:国际金价和国内金价正在“分道扬镳”。 就在昨...
金银狂飙 直播间“锁价券”锁住... 来源:滚动播报 (来源:北京商报) 春节假期结束,黄金、白银价格盘中再度走强。截至2月26日20时3...
王剑锋周伟率队赴上海开展经贸交... 王剑锋周伟率队赴上海开展经贸交流活动 深化对接合作 共谋发展新篇 2月26日,2026年常州—上海经...
出售水井坊?帝亚吉欧最新回应:... 【导读】帝亚吉欧管理层表示不会贱卖资产,从未讨论过出售水井坊 中国基金报记者 郑俊婷 在日前召开的2...
原创 几... 几块钱包邮背后的商业逻辑:淘宝、拼多多不亏的秘密 打开淘宝、拼多多,五块九的发卡、九块九的数据线、十...
侃股:AH股溢价有望持续收缩 据报道,公募基金不断买入高折价的AH股中的港股,这有助于降低AH股溢价率。从价值投资的角度看,同股、...
连任仅三个多月,八一钢铁董事长... 柯善良、刘文壮先生因工作原因,辞去公司董事长、总经理职务。公司于2026年2月26日召开第九届董事会...
宇树科技、西门子能源…默茨中国... 来源:澎湃新闻 2月26日下午,正在访华的德国总理默茨在浙江杭州率团参访宇树科技。澎湃新闻(www....
原创 中... 细察商务部新闻:中美经贸沟通的“温度”与“深度” 初春二月,乍暖还寒,北京城弥漫着料峭春意,然而,中...
原创 今... 今日金价:大家要有心理准备了,2月26日,金价或将重现15年历史 2026年2月26日,国内金价行情...
海量财经 | 金美信消费金融增... 海报新闻记者 周凌峰 报道 国家金融监管总局厦门监管局2月24日公告称,批复同意金美信消费金融增加...
华尔街“SaaS末日”论沸反盈... 来源:21世纪经济报道 21世纪经济报道记者骆轶琪 报道 软件厂商看起来正持续遭遇来自AI的冲击。 ...
当初摩根放开QD额度坚持了一天... 来源:市场资讯 来源:投基摸狗 之前摩根放开QD基金额度,结果只坚持了一天。 昨天下午三点以后(在...
A股上市银行最年轻董事长来了!... 本文时代周报 作者:黄宇昆 宁波银行迎来重要人事变动。 2月26日晚间,宁波银行发布公告,当日,该行...
港股,突然大变局!什么情况? 2026年开年,港股科技股经历了一轮从狂欢到骤冷的极端行情。1月13日还在年内高点,短短一个多月,恒...
三只羊否认借壳上市传闻 【#三只羊否认借壳上市传闻#】#三只羊声明没有借壳上市#2月26日,三只羊网络发布声明。全文如下: ...
原创 散... 为什么有人在春节前夜割肉离场,而有钱人在同一时刻悄然加仓,这是偶然还是必然,是情绪失控还是制度安排,...
Stellantis去年首次亏... IT之家 2 月 26 日消息,今天晚间,Stellantis 集团公布了 2025 年全年财务业绩...