一. 访问
虽然前面的教程已经覆盖了很多可能遇到的情况,但还是有一种对新手来说相对棘手的情况没有讲到,那就是被internal修饰的类,internal修饰符让类只能被自己程序集内的其他成员访问,我们的Mod显然不在此列,那怎么办呢?很显然,反射。
我们使用Type t = Assembly.Load(程序集名称).GetType(类名);的方式即可获取这个类,然后使用Activator来创建实例。
假如现在在游戏的Assembly-CSharp.dll里有这样一个类
namespace Logic
{
internal class Food
{
public int addHP; //HP回复量
public int addEnergy; //能量回复量
public Food(int hp, int energy)
{
addHP = hp;
addEnergy = energy;
}
public void SomeMethod()
{
//...
}
}
}
现在,我们来创建一个Food实例
Type t = Assembly.Load("Assembly-CSharp").GetType("Logic.Food");
object food = Activator.CreateInstance(t, 10, 20);
这样,我们就获得了一个回复10点HP,20点能量的Food。
另外,如果你需要多次访问这个类,应该先保存好类型再多次使用,而不是每次创建都加载一次程序集来获取,这样会影响性能。
二. Patch
在上面我们通过程序集加载的方式获得了Food类型,那是不是可以直接使用这个类型来Patch呢?答案是不行的,因为我们获取到的是一个不确定的类型,但是Harmony进行Patch需要一个知道一个具体的类型,那怎么办呢?我们可以从和它相关的类入手。
使用Dnspy分析这个类,查看都有谁使用了它,然后挑一个合适的类进行我们的工作。这个类需要不是intelnal的,我们要可以直接Patch到这个类。我们假设有一个People类,它有一个Cook方法可以得到一个Food。
namespace Logic
{
public class People
{
public string Name;
public int Age;
//...
public Food Cook()
{
Food food = new Food(this.Age, this.Age*2);
return food;
}
}
}
既然这个People的Cook方法可以得到Food类,那我们就可以从它下手,进行如下Patch。
public static UnityModManager.ModEntry mod;
public static HarmonyInstance harmony;
public static bool patchFinish = false;
public static bool Load(UnityModManager.ModEntry modEntry)
{
mod = modEntry;
harmony.PatchAll(Assembly.GetExecutingAssembly());
return true;
}
public static void MyPostfix()
{
mod.Logger.Log("间接补丁的Postfix方法被调用");
}
[HarmonyPatch(typeof(People), "Cook")]
class PeoplePatch
{
public static void Postfix(object __result)
{
if (patchFinish) return;
var original = __result.GetType().GetMethod("SomeMethod", AccessTools.all);
var postfix = typeof(Main).GetMethod("MyPostfix");
harmony.Patch(original, null, new HarmonyMethod(postfix));
patchFinish = true;
}
}
这样,我们就通过手动补丁的方式对intelnal的类进行了修补,间接补丁的入手点很多,比如如果它有父类,可以从父类入手,如果是某些方法的返回值或者参数,也可以从这些方法入手。还要稍微注意一下我们GetMethod时候使用了AccessTools.all,这是我们之前讲过的工具,all代表了全类型标志,这样可以保证我们获取到非公开的方法。
讲到这里,我们几乎可以解决能遇到的各种Patch了,剩下的基本就是要靠发挥想象力或者再进行一些其他技术的发挥。