MonoWeaver.Cecil10
0.2.0
dotnet add package MonoWeaver.Cecil10 --version 0.2.0
NuGet\Install-Package MonoWeaver.Cecil10 -Version 0.2.0
<PackageReference Include="MonoWeaver.Cecil10" Version="0.2.0" />
<PackageVersion Include="MonoWeaver.Cecil10" Version="0.2.0" />
<PackageReference Include="MonoWeaver.Cecil10" />
paket add MonoWeaver.Cecil10 --version 0.2.0
#r "nuget: MonoWeaver.Cecil10, 0.2.0"
#:package MonoWeaver.Cecil10@0.2.0
#addin nuget:?package=MonoWeaver.Cecil10&version=0.2.0
#tool nuget:?package=MonoWeaver.Cecil10&version=0.2.0
MonoWeaver
MonoWeaver helps C# mod developers find a piece of compiled game logic and safely change it. You describe the expression you are looking for—such as a damage calculation, a method call, or an if condition—and then choose what should happen before, after, or instead of it.
You normally do not need to search for a fixed list of IL instructions. That makes a hook easier to read and less likely to break when the compiler adds a temporary local or uses a different branch layout.
Typical uses include:
- changing a calculated value such as damage, price, or cooldown;
- logging a value without changing the game result;
- replacing or removing a game action;
- changing a short-circuit
ifcondition; - applying the same matching API to a Cecil
MethodDefinitionor a MonoModILContext; - checking the edited method before it is written or executed.
Compatibility
| Package | Mono.Cecil | Target frameworks |
|---|---|---|
MonoWeaver |
0.11.2+ |
netstandard2.0 |
MonoWeaver.Cecil10 |
0.10.0 – 0.10.4 |
netstandard2.0 |
Quick start
Both examples find baseDamage + bonus in Game.Player.ComputeDamage. Lambda parameters bind to target-method parameters with the same names. They use this callback:
using System;
public static class ModHooks
{
public static int ClampDamage(int value)
=> Math.Min(Math.Max(value, 0), 999);
}
Runtime hook with MonoMod
Use this form when the mod loader invokes your hook with an ILContext for ComputeDamage:
using System;
using MonoMod.Cil;
using MonoWeaver.Cecil;
using MonoWeaver.CFG;
using MonoWeaver.Patterns;
public static class DamagePatch
{
public static void Patch(ILContext il)
{
var pattern = Cil.Value((int baseDamage, int bonus) =>
baseDamage + bonus);
il.Method.Match(pattern)
.Single()
.Transform(ModHooks.ClampDamage)
.Apply(VerifyOptions.Mod);
}
}
MonoWeaver handles MonoMod branch labels while applying the rewrite. Every label must already point to a valid target when Apply is called.
Offline DLL patch
Use this form when the output should be a patched assembly on disk:
using System;
using System.Linq;
using Mono.Cecil;
using MonoWeaver.Cecil;
using MonoWeaver.CFG;
using MonoWeaver.Patterns;
public static class DamagePatcher
{
public static void Patch(string inputPath, string outputPath)
{
using var module = ModuleDefinition.ReadModule(inputPath);
var method = module.Types
.Single(type => type.FullName == "Game.Player")
.Methods.Single(candidate => candidate.Name == "ComputeDamage");
var pattern = Cil.Value((int baseDamage, int bonus) =>
baseDamage + bonus);
method.Match(pattern)
.Single()
.Transform(ModHooks.ClampDamage)
.Apply(VerifyOptions.Full);
module.Write(outputPath);
}
}
Single() is deliberate: it fails when there is no match or when more than one place matches. For a mod hook, making the pattern more specific is safer than silently patching the first candidate.
For an offline patch, deploy the assembly that contains ModHooks with the patched game assembly. Instance delegates and closures are runtime-only because they refer to objects in the current process.
Choose the operation by intent
| What the mod should do | API |
|---|---|
| Run a callback before the matched code | Before(...) |
| Run a callback after a value or action | After(...) |
| Read the old value and return a new one | Transform(...) |
| Read or log the old value without changing it | Observe(...) |
| Skip the old code and provide a replacement | Replace(...) |
| Remove a matched no-result action | Remove() |
Every operation creates a RewritePlan. Nothing changes until you call Apply(). Always verify: if the check fails, MonoWeaver restores the method and throws instead of leaving a half-applied edit. Use VerifyOptions.Mod for a runtime hook and VerifyOptions.Full for an offline patch.
Values, actions, and conditions have slightly different valid operations. In particular, a branch-based condition has no single After(...) point; use Transform, Observe, Replace, or Before instead.
Capturing one part of a larger match
The root match can be edited directly. When the hook should target one of the parameters, the lambda parameter is already the capture — read it back by parameter name:
var pattern = Cil.Value((int baseDamage, int bonus) => baseDamage + bonus);
var match = method.Match(pattern).Single();
match.Arg("baseDamage").Transform(ModHooks.ClampDamage)
.Apply(VerifyOptions.Full);
When the target is a compound sub-expression, declare that part as a standalone Cil.Value fragment, use it directly in the expression, and read it back through the same object (match[fragment]).
The matcher follows an unambiguous compiler-generated temporary by default. If several assignments could reach the same local read, it refuses to guess.
When game types are not referenced
If the mod project references the game assembly, the lambda form above is usually the easiest. If you cannot or do not want to load those types, describe them by assembly and type name:
var game = CilSymbols.In("GameAssembly");
var player = game.Type("Game.Player");
var getScore = player.InstanceMethod("GetScore", CilType.Int32);
var scorePattern = Cil.Value(
P.Arg(0, player.Assignable())
.Call(getScore));
Both forms produce the same kind of match result and use the same rewrite operations.
Documentation
Full documentation, in English and Simplified Chinese: https://pkuyo.github.io/MonoWeaver/en/
- Using MonoMod — runtime integration through
ILContext. - Your first hook — the complete offline patch flow, step by step.
- Patterns by example — common game functions shown beside the pattern and the exact part it finds.
- Rewrite operations — what
Before,After,Transform,Observe,Replace, andRemovedo per match kind. - Verification — recommended checks and plain-language troubleshooting.
- Type matching — practical type comparisons for game classes, callbacks, and member access.
Build and test the repository
The whole solution follows the same CecilFlavor switch, so the tests run against either Cecil generation:
dotnet test MonoWeaver.slnx
dotnet test MonoWeaver.slnx -p:CecilFlavor=Latest
Build both packages locally into artifacts/nupkg/:
dotnet pack MonoWeaver/MonoWeaver.csproj -c Release -p:CecilFlavor=Cecil10 -p:Version=0.1.1
dotnet pack MonoWeaver/MonoWeaver.csproj -c Release -p:CecilFlavor=Latest -p:Version=0.1.1
The main projects in this repository are:
| Project | Purpose |
|---|---|
MonoWeaver |
The library used by mods. |
tests/MonoWeaver.PatternTests |
Matching, rewriting, delegate, and MonoMod compatibility tests. |
tests/MonoWeaver.ILTests |
Edited-method checker tests. |
tests/MonoWeaver.DocSamples |
Source of every code block in the docs; compiled, not run. |
MonoWeaver.Fuzz |
Automated stress tests. |
benchmarks/MonoWeaver.Benchmarks |
IL verification throughput, plus a patch-time comparison against MonoMod. |
dotnet run -c Release --project benchmarks/MonoWeaver.Benchmarks -- --verify-only --max-method-us 50000
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Mono.Cecil (>= 0.10.0 && <= 0.10.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.