appendix:harmonyがある時

文書の過去の版を表示しています。


SybarisプラグインだけどBepInEx環境で動いていて、HarmonyLib(HarmonyX)が使える場合での Harmony.Patch を呼ぶ方法。
Harmony.Patch が呼べるのであれば、任意のタイミングでパッチあてが行えるので強力な外道行為が行える。

あるかないかわからないHarmonyLib(HarmonyX)を参照するわけにはいかないので1)、リフレクションを駆使してインスタンスを作成する。

// 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とか");
    }
}

これは簡単で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
    });
}

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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
static void ILManipulator(ILContext il, MethodBase original, ILLabel retLabel)
static void SomeOtherILManipulator(ILContext ctx, MethodBase orig)

問題が起こりそうなのはTranspilerとILManipulatorのパターンでCodeInstructionがHarmonyの中で、ILLabelがMonoMod.Cliの中で宣言されているので参照出来ない状況だと面倒になる。
※ILContextはMono.CecilなのでSybaris2/BepInExでも参照してしまえば良い。

特に面倒が起こりそうな Transpiler の実装方法を示す。

static object Transpiler(IEnumerable<object> instructions)
{
    var type = instructions.First().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.Equals(変えたいところのOperand))
            newInstructions.Add(Activator.CreateInstance(type, 変えたいOpCode, 変えたいOperand));
        else
            newInstructions.Add(instruction);
    }
    return newInstructions;
}

引数も戻り値も大嘘で宣言します。:-D
引数で貰った IEnumerable<CodeInstruction> を書き換えて返していいのかがわからないので、リストを作り直しそこに詰め込んで返しています。

var newInstructions = Activator.CreateInstance(typeof(List<>).MakeGenericType(type)) as IList;
return newInstructions;

こんなのでいいの?って思うかもしれませんが多分これで良いのです。
あとは変えたいところだけ、全部変えたいなら全部詰め直して返せば良いです。
OpCodeはSystem.Reflection.Emit.OpCodeっぽいのでこれを参照すればHarmonyLibの参照はなしでいけます。
ILManipulatorの方もILLabel retLabelをobjectで受けてリフレクションを使えばいけると思います、必要が無い限りILLabelのない方を使えばいいと思います。


1)
いっそ参照してりぞるばで解決しても良いかもだけど

コメント

コメントを入力:
 
  • appendix/harmonyがある時.1734502895.txt.gz
  • 最終更新: 2024/12/18 15:21
  • by fumble