=====Harmonyがある時=====
SybarisプラグインだけどBepInEx環境で動いていて、HarmonyLib(HarmonyX)が使える場合での Harmony.Patch を呼ぶ方法。\\
Harmony.Patch が呼べるのであれば、任意のタイミングでパッチあてが行えるので強力な外道行為が行える。\\
====Harmonyのインスタンス作成====
あるかないかわからないHarmonyLib(HarmonyX)を参照するわけにはいかないので((いっそ参照してりぞるばで解決しても良いかもだけど))、リフレクションを駆使してインスタンスを作成する。\\
// HarmonyLib.Harmonyを探せ!
foreach (var loadedAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
var typeHarmony = loadedAssembly.GetType("HarmonyLib.Harmony");
var typeHarmonyMethod = assembly.GetType("HarmonyLib.HarmonyMethod");
// あったか!?
if (typeHarmony != null && typeHarmonyMethod != null)
{
// あったぞ!!
var harmony = Activator.CreateInstance(typeHarmony, "GUIDとか");
}
}
====Patchメソッド取得====
これは簡単でGetMethodしてしまえばいいが、オーバーロードがあるので引数を指定してとる。\\
if (harmony != null)
{
// 作れたぞ!!
var harmonyPatch = typeHarmony.GetMethod("Patch", new Type[]
{
typeof(System.Reflection.MethodBase), // original
typeHarmonyMethod, // prefix
typeHarmonyMethod, // postfix
typeHarmonyMethod, // transpiler
typeHarmonyMethod, // finalizer
typeHarmonyMethod, // ilmanipulator
});
}
====Patchに渡す引数====
1つめはMethodBaseなので単純にGetMethod取得すれば良い、残りはHarmonyMethod型だがMethodInfoとDelegateからの変換が用意されているのでGetMethod(Delegateがあるのでラムダ式でも可)で解決出来る。
public static implicit operator HarmonyMethod(MethodInfo method) => new(method);
public static implicit operator HarmonyMethod(Delegate @delegate) => new(@delegate);
それぞれ渡すべきプロトタイプは
prefixはパッチを当てるものと同じもの。\\
postfixはパッチを当てるものの戻り値を受け取る戻りに無しのもの。\\
finalizerはExceptionを貰ってExceptionを返すもの。\\
transpilerとilmanipulatorは以下の様なもの。\\
static IEnumerable Transpiler(IEnumerable instructions)
static void ILManipulator(ILContext il, MethodBase original, ILLabel retLabel)
static void SomeOtherILManipulator(ILContext ctx, MethodBase orig)
渡すときはHarmonyMethod(MethodInfo method)の変換、つまりコンストラクターを呼んで変換させる。
var transpilermethod = typeof(トランスパイラーのあるクラス).GetMethod(トランスパイラーのメソッド名);
var transpiler = Activator.CreateInstance(typeHarmonyMethod, transpiler);
問題が起こりそうなのはTranspilerとILManipulatorのパターンでCodeInstructionがHarmonyの中で、ILLabelがMonoMod.Cliの中で宣言されているので参照出来ない状況だと面倒になる。\\
※ILContextはMono.CecilなのでSybaris2/BepInExでも参照してしまえば良い。
特に面倒が起こりそうな Transpiler の実装方法を示す。
static object Transpiler(IEnumerable instructions)
{
var enumerator = instructions?.GetEnumerator();
if (enumerator != null && enumerator.MoveNext())
{
var type = enumerator.Current.GetType();
var fldOpecode = type.GetField("opcode");
var fldOperand = type.GetField("operand");
var newInstructions = Activator.CreateInstance(typeof(List<>).MakeGenericType(type)) as IList;
foreach (var instruction in instructions)
{
var opecode = fldOpecode.GetValue(instruction);
var operand = fldOperand.GetValue(instruction);
if (opecode.Equals(変えたいところのOpCode) && operand != null && operand.ToString() == 変えたいところのOperand)
newInstructions.Add(Activator.CreateInstance(type, 変えたいOpCode, 変えたいOperand));
else
newInstructions.Add(instruction);
}
}
return newInstructions;
}
引数をIEnumerableで受けて戻り値をobjectとかで宣言してしまいます。\\
引数で貰った IEnumerable を書き換えて返していいのかがわからない((Copilotは書き換えて構わないみたいなことを言いますが…))ので、リストを作り直しそこに詰め込んで返しています。\\
var newInstructions = Activator.CreateInstance(typeof(List<>).MakeGenericType(type)) as IList;
return newInstructions;
こんなのでいいの?って思うかもしれませんが多分これで良いのです。\\
あとは変えたいところだけ、全部変えたいなら全部詰め直して返せば良いです。\\
OpCodeはSystem.Reflection.Emit.OpCodeっぽいのでこれを参照すればHarmonyLibの参照はなしでいけます。\\
ILManipulatorの方もILLabel retLabelをobjectで受けてリフレクションを使えばいけると思います、必要が無い限りILLabelのない方を使えばいいと思います。\\