跳转至

按游戏代码查写法

这页不枚举所有语法,只保留 Mod 开发中最常遇到的几类目标。

每个例子按同一顺序给出:目标游戏函数 → 对应 Pattern → 实际命中的内容。示例里的 PlayerRewardGameAudio 只是占位名,使用时换成目标游戏里的真实类型和函数。

默认已有:

using MonoWeaver.Cecil;
using MonoWeaver.Patterns;

创建 Pattern 后,通常这样查找:

var match = method.Match(pattern).Single();

只有确定目标函数中恰好有一处命中时才用 Single()。找到多处时,应把周围的计算或调用也写进 Pattern。

先按 三种匹配种类 确定用 Cil.ValueCil.Effect 还是 Cil.Condition

1. 匹配一段计算

public static int CalculateDamage(int baseDamage, int bonus)
    => baseDamage + bonus;
var pattern = Cil.Value((int baseDamage, int bonus) =>
    baseDamage + bonus);

命中完整的 baseDamage + bonus。lambda 参数按名称绑定目标方法的同名参数。

2. 在更大的计算中找到准确调用

public static int ReadSelectedScore(Player player)
{
    _ = player.GetScore().ToString();
    return player.GetScore() + 10;
}
var score = Cil.Value((Player player) => player.GetScore());

var pattern = Cil.Value(() => score + 10);

完整 Pattern 只命中参加 + 10 的那次 GetScore(),不会误选上面被丢弃的调用。score 标记则单独指向内部的 GetScore()

var scoreCapture = method.Match(pattern).Single()[score];

3. 匹配只执行行为的调用

public static void PlayHitSound(Player player)
    => GameAudio.Play(player.HitSound);
var pattern = Cil.Effect((Player player) =>
    GameAudio.Play(player.HitSound));

匹配整个 GameAudio.Play(...) 调用,包括传入的 HitSound。这种结果适合在调用前后插入回调,或替换、移除原调用。

如果方法有返回值,但目标代码直接丢弃了结果,也用 Cil.Effect(...)

4. 匹配 if 条件

public static bool TryOpen(Player player)
{
    if (player.HasKey && !player.IsDead)
        return true;

    return false;
}
var pattern = Cil.Condition((Player player) =>
    player.HasKey
    && !player.IsDead);

匹配决定 if 走向的完整条件,包括 && 的短路判断。它不是在搜索函数最后返回的那个普通 bool 值。

5. 匹配构造函数、常量和属性

public static Reward CreateReward(Player player)
    => new Reward("rare", player.Level * 100);
var amount = Cil.Value((Player player) => player.Level * 100);

var pattern = Cil.Value(() => new Reward("rare", amount));

匹配准确的 Reward(string, int) 构造函数、"rare" 常量和 Level * 100amount 标记只指向奖励数量的计算,方便单独修改它。

方法重载也按参数列表区分:Pattern 中写 Select("rare") 不会命中 Select(1)

6. 跨过中间局部变量

public static int DoubleDamage(int damage)
{
    var adjusted = damage + 1;
    return adjusted * 2;
}
var adjusted = Cil.Value((int damage) => damage + 1);

var pattern = Cil.Value(() => adjusted * 2);

即使 damage + 1 先存进了 adjusted,只要这个值的来源明确,默认仍会命中完整的 (damage + 1) * 2adjusted 标记对应当前被乘法使用的那次值。

如果确实只想找某个局部变量,可以写 P.Local<int>(0);但局部变量序号会随游戏版本或编译方式改变,通常不如写出它周围的计算稳定。详见 编译器临时变量

7. 匹配数组读取

public static int ReadNext(int[] values, int index)
    => values[index + 1];
var pattern = Cil.Value((int[] values, int index) =>
    values[index + 1]);

匹配完整的数组读取,包括数组参数和 index + 1。数组长度直接写 .Length;数组写入用 P.StoreElement(...)

8. 不引用游戏 DLL 时

public static int ReadScore(Player player)
    => player.GetScore();
var game = CilSymbols.In("GameAssembly");
var player = game.Type("Game.Player");
var getScore = player.InstanceMethod(
    "GetScore",
    CilType.Int32);

var playerArg = Cil.Arg(player.Assignable(), 0);
var score = Cil.Value(playerArg.Expr.Call(getScore));

var pattern = Cil.Value(score.Expr);
var score = Cil.Value((Player player) => player.GetScore());

var pattern = Cil.Value(() => score.Value);

两者寻找同一处调用,只是符号写法不需要在 Mod 项目中引用游戏 DLL。MatchCaptures 和改写 API 完全相同。

更多 Pattern 写法

字段、属性、方法调用、new、数字转换、加减乘除和比较,可以按反编译器里看到的 C# 直接写。类型、参数名和方法重载需要与目标代码对应。

完整清单见 Pattern 写法。找不到或找到太多时见 匹配不到或匹配过多