改写操作¶
以下写法都是“分别选择一种”,不要把它们连续应用到同一个旧匹配结果。每次 Apply 之后,要继续改就重新 Match。
值¶
Transform:读取旧值并返回新值¶
damage.Transform(Hooks.ClampDamage)
.Apply(VerifyOptions.Full);
// static int ClampDamage(int original)
原值作为回调的第一个参数传入,回调返回值交回游戏原逻辑。返回类型必须能放回原位置。
Observe:只观察,不改值¶
适合日志、统计和触发 Mod 事件。原值仍会交给游戏。回调若有返回值,默认丢弃。
Replace:完全跳过原计算¶
不会先执行原表达式,回调也不会自动收到原值——需要的参数必须全部在 args 中提供。
Before / After:前后追加行为¶
damage.Before(Hooks.OnDamageCalculationStarted)
.Apply(VerifyOptions.Full);
damage.After(Hooks.OnDamageCalculated)
.Apply(VerifyOptions.Full);
After 只表示“计算完成后调用”,不会自动把原值传给回调。需要读取原值时用 Observe。
行为¶
var effect = method.Match(soundPattern).Single();
effect.Before(Hooks.BeforeSound)
.Apply(VerifyOptions.Full);
effect.After(Hooks.AfterSound)
.Apply(VerifyOptions.Full);
effect.Replace(Hooks.PlayCustomSound)
.Apply(VerifyOptions.Full);
effect.Remove()
.Apply(VerifyOptions.Full);
四种独立选择:前面插入、后面插入、整体替换、删除。
条件¶
条件还支持 Before,但没有 After:一个条件可能从不同位置结束,没有唯一的“后面一行”。
改写前先确认可行:
回调可以是什么¶
| 来源 | 适合 |
|---|---|
强类型委托,如 Func<int, int> |
运行时 Mod |
CilMethodSpec |
用签名描述静态回调 |
Cecil MethodReference |
已拿到方法引用的离线修改器 |
静态委托会降级成对该静态方法的直接调用。实例委托、闭包和多播委托只用于运行时 Hook。
传方法组即可:damage.Transform(Hooks.ClampDamage)。只有 C# 9 及以下(net48 项目未设置 LangVersion 时默认是 7.3)对捕获调用时需要写出委托类型,如 match.Arg("x").Transform((Func<int, int>)Hooks.ClampDamage)——那时方法组还没有自然类型;根匹配 ValueMatch<T> 因为 T 已知,任何版本都不需要。
给回调补充参数¶
Transform 和 Observe 自动把原值放在第一个参数。其余参数由 args => ... 按顺序提供:
damage.Transform(
Hooks.AdjustDamage,
args => args
.Arg(0)
.Constant(999))
.Apply(VerifyOptions.Full);
// static int AdjustDamage(int original, int firstMethodArg, int limit)
| 写法 | 传给回调的内容 |
|---|---|
args.This() |
当前对象 |
args.Arg(0) |
目标方法的第 0 个显式参数 |
args.Arg(argumentCapture) |
捕获到的参数 |
args.Local(localCapture) |
捕获到的局部变量当前值 |
args.Capture(valueCapture) |
某个已捕获表达式的值 |
args.Constant(100) |
常量(各基础类型都有重载) |
args.ConstantI4(value, nominalType) |
以指定名义类型传入的整数常量 |
args.Null(type) |
空引用 |
MonoWeaver 会检查参数个数、顺序和类型。如果某个捕获值在回调位置还不可用,也会在修改前拒绝。
回调返回值的去向¶
见 改写计划。要点:
Transform和值Replace的返回值直接交回游戏,不需要配置;Observe的非void结果默认丢弃;Before/After的非void结果默认留在栈上,普通 Hook 应显式Discard()或Store(...)。