Verification¶
After a mod rewrites code, the painful failure is usually not "the effect is wrong" — it is the game throwing or crashing while loading the method. MonoWeaver's verifier checks that the method is still something .NET/Mono will accept, before you save the DLL or keep running.
Recommended usage¶
For rewrites MonoWeaver produced, verify in full at the moment you apply:
damage.Transform(Hooks.ClampDamage)
.Apply(VerifyOptions.Full);
// static int ClampDamage(int original)
This will:
- save the method state before the edit;
- apply the rewrite;
- run the full check;
- keep the edit on success;
- restore the original method and throw on failure.
A mod you intend to release should verify with Mod or Full instead of calling a bare Apply().
Light or Full¶
| Mode | What it checks | Suggested use |
|---|---|---|
VerifyOptions.Light |
Instruction shape, and how many values sit on each execution path | Fast, frequent checks while developing |
VerifyOptions.Full |
Everything in Light plus value types, local initialisation, member access | Before release, in automated tests, on unknown game versions |
VerifyOptions.Mod |
Full without member access checks |
Mods that declare SkipVerification and call non-public game members through publicized assemblies |
- Runtime hook (MonoMod
ILContext) →Mod. Mods usually touch private game members directly, so the extra access check inFullwould only misfire here. - Offline patch →
Full. - Mod methods are small and verify quickly; switch to
Lightonly if it actually feels slow.
Individual flags can be combined:
var options =
VerifyOptions.Instructions |
VerifyOptions.StackBalance |
VerifyOptions.LocalInit;
method.Verify(options).ThrowIfHasErrors();
| Flag | Checks |
|---|---|
Instructions |
Instruction and operand shape |
StackBalance |
How many values are on each path |
StackTypes |
The types of those values; includes StackBalance |
LocalInit |
That locals are assigned before they are read |
AccessTest |
Member access permissions |
Verifying a hand-edited method¶
If the method also went through your own Cecil edits, check it separately:
var report = method.Verify(
VerifyOptions.Full,
maxErrCount: 20);
foreach (var item in report.Diagnostics)
Console.WriteLine(item);
report.ThrowIfHasErrors();
ThrowIfHasErrors() throws on Error or Fatal. A Warning stays in Diagnostics, and it is up to the mod whether that should block loading.
To read the detail behind a throw from Apply(VerifyOptions.Full), catch it:
try
{
plan.Apply(VerifyOptions.Full);
}
catch (ILMethodVerifier.CfgVerifyException error)
{
foreach (var item in error.Diagnostics)
Console.Error.WriteLine(item);
throw;
}
Apply has already restored the method at this point; there is no rewrite for you to undo.
What it actually catches¶
In terms a mod developer meets in practice:
- a callback consumed the original value but returned no replacement;
- a
BeforeorAftercallback left behind a return value nobody uses; - the two paths of an
ifreach the same point carrying a different number or type of values; - after deleting code, a branch still points at the deleted position;
- an illegal jump into or out of a
try/catch/finally; - a local read on a path where it was never assigned;
- an incompatible argument, return value, or field type;
- mod code reaching a private member the current context may not access;
- a type or member the method needs that cannot be resolved from the game's dependencies.
What it does not check¶
Even when Full passes, all of this is still possible:
- you matched the wrong place, and it happens to be well-formed;
- damage, probability, or time units are semantically wrong;
- the callback throws;
- several mods edit the same method and the order conflicts;
- a game update changed when something is called while keeping a similar expression;
- a problem that only shows up on a particular save, map, or in multiplayer.
Use full verification, a unique match, logging, and real playtesting together. None of them alone is the guarantee.
When you get a concrete error, see Verification Failures.