废话

如果你看了前几篇文章,应该对XML PatchOperations有印象,它使用xpath的语法对xml文件进行patch操作。而Harmony的作用与之相似,是对C#代码进行patch操作。
Harmony是一个运行库,专门为解决mod冲突之类的问题,不仅用于rimworld,还有很多著名的社区都以Harmony为基础创造了很多mod。
Harmony的原理是利用反射获取对应类中的方法,然后加上特性标签进行逻辑控制,达到不破坏原有代码进行更新的效果。

核心内容

1.使用Harmony对C#代码Patch

1.1 环境建立

很多人还在使用旧方法,每个人都复制了一份Harmony库到自己的代码里,这样无形增加了储存空间和加载速度。我只讲目前最新的Harmony使用姿势。
按照教程1的步骤建一个C#运行库。然后下载最新的Harmony库文件。可以在创意工坊搜索到,然后引用0Harmony.dll。顺带一提,最新的0Harmony.dll是以framework4.7.2为基础的工程,如果framework版本不够还需要更新一下版本。

1.2 创建管理器实例

using Verse;
using HarmonyLib;
using System.Reflection;
namespace SR.NDQ.Patch
{
    [StaticConstructorOnStartup]
    public class PatchMain
    {
        public static Harmony instance;
        static PatchMain()
        {
            instance = new Harmony("SR.NoDecreeQuest");
            instance.PatchAll(Assembly.GetExecutingAssembly());//对所有特性标签的方法进行patch
        }
    }
}

1.3 编写对应Patch方法

[HarmonyPatch]
标签需要填写改写的类和方法名字以及参数类型,至于type参数类型可以不填,只有一种情况不填会报错,就是这个方法有其他重载方法,不填type无法确定是哪个同名方法。

[HarmonyPrefix]
标签表示下面的方法在原函数之前执行,这里的bool返回不是原函数的返回值。
true表示原函数继续执行,false表示原函数被替代,不会继续执行

[HarmonyPatch(typeof(QuestUtility), "SendLetterQuestAvailable", new Type[] { typeof(Quest) })]
class QuestUtility_Patch2
{
    [HarmonyPrefix]
    static bool Prefix(Quest quest)
    {
        NDQMain.instance.SRLog("SendLetterQuestAvailable");
        if (!NDQMain.instance.modSetting.isOpenMod)
        {
            return true;
        }
        return quest != null;
    }
}

[HarmonyPatch]标签可以拆分写,像下面注释的那种写法。
如果原函数不是void返回值,那么ref XXX __result表示的就是原来的返回值,而bool就是是否取代原方法。

//[HarmonyPatch(typeof(QuestUtility))]
//[HarmonyPatch("GenerateQuestAndMakeAvailable")]
//[HarmonyPatch(new Type[] {typeof(QuestScriptDef),typeof(Slate)})]
[HarmonyPatch(typeof(QuestUtility), "GenerateQuestAndMakeAvailable", new Type[] { typeof(QuestScriptDef), typeof(Slate) })]
class QuestUtility_Patch1
{
    [HarmonyPrefix]
    static bool Prefix(ref Quest __result, QuestScriptDef root, Slate vars)
    {
        NDQMain.instance.SRLog("GenerateQuestAndMakeAvailable");
        if (!NDQMain.instance.modSetting.isOpenMod)
        {
            return true;
        }
        bool isDecreeQuest = false;
        if (root.decreeTags != null)
        {
            foreach (var s in root.decreeTags)
            {
                if (s.Equals("All"))
                {
                    isDecreeQuest = true;
                }
            }
        }
        if (!isDecreeQuest)
        {
            Quest quest = QuestGen.Generate(root, vars);
            Find.QuestManager.Add(quest);
            __result = quest;
        }
        else
        {
            __result = null;
            NDQMain.instance.SRLog("法令任务已拦截");
        }
        return false;//false将不执行原方法
    }
}

1.4 示例

这份代码的内容是拦截原版法令任务生成。可以参考一下工程结构。源码

https://github.com/pardeike/Harmony/wiki上有更详细的使用方法,本篇只介绍了简单通用的部分。

如果这篇文章对你有帮助,点赞收藏支持一下呗!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐