pardeike/Harmony · error · InvalidOperationException

Unbalanced exception markers – cannot rewrite.

Error message

Unbalanced exception markers – cannot rewrite.

What it means

FaultBlockRewriter converts C# 'fault' style exception blocks (identified by special fault markers) into rewritten IL. During Rewrite it pairs each fault marker with a matching begin/end exception marker; if either matching marker cannot be found the instruction stream is unbalanced and rewriting cannot proceed safely, so it throws InvalidOperationException rather than producing corrupt IL.

Solutions

  1. Review your transpilers for instructions that remove, reorder, or clone fault/exception block boundaries (Branch, Leave, marker instructions)
  2. Exclude the method from fault rewriting or patch a wrapper method instead
  3. Upgrade Harmony in case the marker-matching logic handles your IL pattern in a newer release
  4. Inspect the method's exception regions with the CLI or MethodBody.ExceptionHandlingClauses to understand the block layout

Example fix

// before (transpiler deletes fault-block boundary)
codes.RemoveAll(c => c.opcode == OpCodes.Leave);
// after (only remove leaves outside exception regions)
codes.RemoveAll(c => c.opcode == OpCodes.Leave && !IsInsideExceptionRegion(codes, c));
Defensive patterns

Strategy: try-catch

Try / catch

try { /* transpile via harmony.Patch with your transpiler */ }
catch (InvalidOperationException ex) when (ex.Message.Contains("Unbalanced exception markers")) { /* fall back: patch without that transpiler or skip method */ }

Prevention

When it happens

Trigger: Rewrite() processing an instruction list whose fault markers do not line up with FindMatchingBeginException/FindMatchingEndException results — e.g. transpilers inserted or removed exception-block boundary instructions, or nested/reordered try-fault blocks breaking marker pairing.

Common situations: A custom transpiler that deletes or duplicates instructions inside a method containing fault blocks; combining Harmony patches on methods compiled with unusual exception-region layouts; older/newer compiler output that confuses the marker matcher.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15). Data as JSON: /api/errors/5dce957370f9622b. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Internal/FaultBlockRewriter.cs:68

			var i = 0;
			var rewritten = new List<CodeInstruction>(instructions.Count * 2);
			while (i < instructions.Count)
			{
				var cur = instructions[i];

				if (cur.HasBlock(ExceptionBlockType.BeginFaultBlock) == false)
				{
					rewritten.Add(new CodeInstruction(cur));
					++i;
					continue;
				}

				var beginExceptionIdx = FindMatchingBeginException(rewritten);
				var endExceptionIdx = FindMatchingEndException(instructions, i + 1);

				if (beginExceptionIdx < 0 || endExceptionIdx < 0)
					throw new InvalidOperationException("Unbalanced exception markers – cannot rewrite.");

				var faultBody = new List<CodeInstruction>();
				for (var k = i; k < endExceptionIdx; ++k)
					faultBody.Add(CloneWithoutFaultMarker(instructions[k]));

				i = endExceptionIdx + 1;

				var failedLocal = generator.DeclareLocal(typeof(bool));
				var skipFault = generator.DefineLabel();

				rewritten.AddRange([
					Nop.WithBlocks(new ExceptionBlock(ExceptionBlockType.BeginCatchBlock, typeof(object))),
					Pop,
					Ldc_I4_1,
					Stloc[failedLocal.LocalIndex],
					Rethrow,
					Nop.WithBlocks(new ExceptionBlock(ExceptionBlockType.BeginFinallyBlock)),
					Ldloc[failedLocal.LocalIndex],

View on GitHub (pinned to e7872dc170)