You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Harmony Transpiler Patch跳过IL指令时遇异常及方法未拦截问题

问题分析与解决方案

为什么原代码无法跳过目标方法

你的判断完全正确——原代码中无论是否匹配到目标指令,都会执行yield return instr,导致原Callvirt指令被完整保留并执行,所以MethodToSkip仍会被调用,只是在它之前多执行了TestMethod。

为什么放入else分支会触发InvalidProgramException

Callvirt指令的执行依赖严格的IL栈状态:调用实例方法时,栈顶必须是该类的实例引用(如果方法带参数,还要依次压入对应参数)。如果直接移除这个Callvirt指令,原本为它准备的栈元素(实例引用、参数)没有被消费,后续指令会读取错误的栈数据,破坏IL的栈平衡,最终触发无效IL代码的异常。

正确的修改方式

方案1:签名匹配时直接替换

如果TestMethod的签名(返回值类型、参数列表)和MethodToSkip完全一致,直接替换原指令即可,此时栈操作逻辑和原指令完全兼容,不会报错:

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) {
    int found = 0;
    foreach (CodeInstruction instr in instructions) {
        if (instr.opcode == OpCodes.Callvirt && instr.Calls(methodToSkip)) {
            found++;
            // 返回TestMethod的调用,跳过原指令
            yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(TestPatch), nameof(TestMethod)));
            continue;
        }
        yield return instr;
    }

    if (found == 0) {
        Plugin.pluginLog.LogError($"Cannot find symbol {methodToSkip}");
    } else {
        Plugin.pluginLog.LogWarning($"Found {found} occurrences of {methodToSkip}");
    }
}

方案2:签名不匹配时清理栈

如果TestMethod的签名和原方法不同,需要手动清理栈上为原方法准备的参数。比如原MethodToSkip是带1个参数的实例方法,栈上会存在[实例引用, 参数]两个元素,若TestMethod是无参无返回值方法,需要用Pop指令将这些元素从栈中移除:

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) {
    int found = 0;
    foreach (CodeInstruction instr in instructions) {
        if (instr.opcode == OpCodes.Callvirt && instr.Calls(methodToSkip)) {
            found++;
            // 先执行TestMethod
            yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(TestPatch), nameof(TestMethod)));
            // 清理原方法的实例引用和1个参数,共2次Pop
            yield return new CodeInstruction(OpCodes.Pop);
            yield return new CodeInstruction(OpCodes.Pop);
            continue; // 跳过原指令
        }
        yield return instr;
    }

    if (found == 0) {
        Plugin.pluginLog.LogError($"Cannot find symbol {methodToSkip}");
    } else {
        Plugin.pluginLog.LogWarning($"Found {found} occurrences of {methodToSkip}");
    }
}

注意:需根据MethodToSkip的实际签名调整Pop次数:

  • 实例方法:至少Pop1次(移除实例引用),每个额外参数再Pop1次
  • 静态方法:无需移除实例引用,仅按参数数量执行Pop

调试辅助技巧

可以用Harmony的扩展方法查看指令的栈变化,辅助排查栈平衡问题:

// 调试时打印每个指令的栈操作变化
foreach (var instr in instructions) {
    Plugin.pluginLog.LogDebug($"{instr.opcode} - {instr.operand} | 栈变化量: {instr.GetStackChange()}");
}

内容的提问来源于stack exchange,提问作者graem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 00:11:06