匹配不到或匹配过多¶
先看诊断¶
匹配失败时,结果集里带着解释:
var candidates = method.Match(damagePattern);
if (candidates.Count != 1)
Console.WriteLine(candidates.ExplainFailure());
诊断会说明方法中哪些 IL 无法表达、哪次临时变量穿透被拒绝、局部变量定义约束(Cil.Local(definedBy))为什么没满足。先读它,再改 Pattern。
匹配不到¶
按这个顺序查:
- 匹配种类选对了吗:值、行为、条件是三种不同的东西,见 三种匹配种类。
if (a && b)里的判断是条件,return a && b;里的是值。 - 方法选对了吗:
FullName拼写、重载、嵌套类型的+分隔符。 - 游戏更新了吗:表达式还在不在,参数顺序变没变。
- 参数名对吗:pattern lambda 参数名必须与目标方法元数据中的参数名完全一致。
- 常量和重载对吗:
Select("rare")不会命中Select(1);1和1L是不同的常量。 - 左右顺序对吗:
a - b和b - a不同;比较运算同理。 - 有没有把运行时对象捕获进 lambda:外部变量会变成闭包字段读取,匹配不到游戏里的值。请改用 pattern lambda 参数、DSL 占位符、字面常量或静态字段。
- 是不是被临时变量挡住了:默认会跟随来源唯一的临时变量,但来源有歧义时不会猜。用
Cil.Local(definedBy)明确来源,见 编译器临时变量。
匹配过多¶
多匹配比匹配不到更危险——它意味着 Pattern 描述的不是唯一一处逻辑。
不要取第一个,而是给 Pattern 增加上下文:
- 把外层调用写进去:不是找
GetScore(),而是找GetScore() + 10; - 把常量写进去:
Level * 100比Level * x(x是Cil.Any<int>())具体; - 把字段或属性写进去;
- 把真正要改的那一小段声明成片段并内嵌,外层负责定位。
var score = Cil.Value((Player player) => player.GetScore());
var pattern = Cil.Value(() => score + 10);
这个 Pattern 只命中参加 + 10 的那次调用,score 标记再单独指向内部的 GetScore()。
如果确实需要改多处,显式遍历并逐个判断,不要依赖顺序:
var candidates = method.Match(damagePattern);
foreach (var candidate in candidates)
Console.WriteLine($"IL_{candidate.FirstInstruction.Offset:X4}");
类型范围过宽¶
符号写法默认精确匹配,Assignable() 会放宽到子类。多匹配时先检查是不是 Assignable() 加多了:
var game = CilSymbols.In("GameAssembly");
var enemy = game.Type("Game.Enemy");
var exact = P.Arg(0, enemy);
var allowDerived = P.Arg(0, enemy.Assignable());
匹配过期¶
方法一旦被改写,之前拿到的匹配位置可能已经失效。
- 一个
RewritePlan只能成功提交一次; - 要继续改同一个方法,重新
Match; - 手动用 Cecil 改动方法体之后同样要重新匹配。
常见失败速查¶
| 现象 | 优先检查 |
|---|---|
No matching expression was found |
方法是否选对、游戏是否更新、常量和重载是否写对 |
| 找到多个结果 | 给 Pattern 增加外层调用、字段、常量或内嵌片段上下文 |
| 回调参数数量错误 | Transform/Observe 已自动提供第一个原值参数 |
| 回调返回类型错误 | Transform 必须返回能替代原值的类型,条件必须返回 bool |
match is stale |
方法已被其他修改改变,重新 Match |
| 提交后检查失败 | 见 检查失败 |