Browse documentation

Class MethodBodyPatching

Namespace
HarmonyLib
Assembly
0Harmony.dll

Connects Harmony's existing patch construction to an optional method-body source and installer.

public static class MethodBodyPatching
Inheritance
object
MethodBodyPatching

Remarks

A host registers each part once during initialization, before patching starts. An unregistered part keeps Harmony's default. Null registers nothing. Non-null registration after any host-owned target is pinned throws InvalidOperationException. A second non-null registration of the same part also throws InvalidOperationException. Targets already pinned to the default detour do not prevent registration and keep that installer. SetEligibility(Func<MethodBase, bool>) selects the methods the host owns. Without it the other parts have no effect. The host owns discovery, installation and recovery. Harmony never retries a failed installation through another mechanism. Unpatching installs a rebuilt replacement. Existing targets keep their selected installation mechanism. Only Harmony copies implementing this protocol can cooperate on host-owned targets. The contract addresses a runtime that replaces a method's IL body through tokens of the method's own module, with Harmony as one rewriter among others. A host reads the baseline that excludes its own installed body, imports values into that module, and installs under Harmony's patch lock.

Methods

Instantiations(MethodBase)

Lists the closed instantiations that install through a generic definition, including one whose installation is in progress.

public static IEnumerable<MethodBase> Instantiations(MethodBase definition)

Parameters

definition MethodBase

A generic definition the host owns.

Returns

IEnumerable<MethodBase>

The instantiations pinned to the definition's installer. Instantiations that Harmony's ordinary detour holds are not included.

Remarks

An installer can compile each of them after it writes the definition's body.

Rebuild(MethodBase)

Rebuilds the installed body of a target from Harmony's current state and installs it again through the target's pinned installer.

public static void Rebuild(MethodBase method)

Parameters

method MethodBase

A patched method, or the stand-in of a reverse patch.

Remarks

After changing a baseline, a host calls this outside installation. Construction and installation hold Harmony's shared patch lock. A family definition without patch records rebuilds through a member's records. A pin without rebuild information throws InvalidOperationException. A reverse patch uses its active recipe, including on the default detour. A successful ordinary installation supersedes that recipe. A failed installation preserves the recipe. Another participating copy's newer reverse recipe is refused by this copy. Reverse patches into host-owned generic stand-ins are unsupported, including closed instantiations.

SetBodySource(Func<MethodBase, MethodBody>)

Registers the reader of an owned method's baseline body.

public static void SetBodySource(Func<MethodBase, MethodBody> readBody)

Parameters

readBody Func<MethodBase, MethodBody>

Reads the latest baseline body, excluding the installed replacement. Tokens use the target module. Null keeps reflection.

Remarks

The reader can receive a constructed view of an owned definition, with that view's generic context. Read a pipeline input or original image if reflection returns the installed body. Construction calls the reader under Harmony's shared patch lock. Standalone body reads can call it outside that lock, concurrently with installation. The reader must support those concurrent calls. When holding Harmony's lock, it must not wait for another Harmony thread.

SetEligibility(Func<MethodBase, bool>)

Registers the check that selects the methods the host owns.

public static void SetEligibility(Func<MethodBase, bool> accepts)

Parameters

accepts Func<MethodBase, bool>

A side-effect-free eligibility check. False retains Harmony's ordinary mechanisms. Null keeps the default: the host owns no method.

Remarks

With an installer, a fully open generic definition selects definition-wide installation. A closed instantiation of a definition the host accepts installs through that definition. The check is asked about the definition, not the instantiation. Its prefix, postfix and finalizer patches run under a type guard in the definition's body. With a transpiler or an Infix patch, the instantiation gets a closed body of its own, built from the definition's patches and its own, which the definition's body enters under that guard. A definition cannot join instantiations that Harmony's ordinary detour already holds. Without an installer, generic targets keep Harmony's ordinary behavior and the check can receive a closed instantiation directly. With an installer, accepting a partially open target throws NotSupportedException before pinning. Eligibility runs under Harmony's shared patch lock and must not wait for another thread that uses Harmony.

SetInstaller(Action<MethodBase, MethodInfo, Func<Func<object, int>, byte[]>>)

Registers the installer of an owned method's replacement.

public static void SetInstaller(Action<MethodBase, MethodInfo, Func<Func<object, int>, byte[]>> install)

Parameters

install Action<MethodBase, MethodInfo, Func<Func<object, int>, byte[]>>

Installs the replacement or its complete CLI body. The third argument writes the body using a host token importer. Null keeps Harmony's ordinary detour.

Remarks

Harmony holds its shared patch lock while install runs. The installer must not wait for another thread that uses Harmony. The installer must not call patch, unpatch, reverse-patch or rebuild operations. Re-entry into the same target or generic family throws InvalidOperationException. Instantiations(MethodBase) remains available during installation. The synchronous writer and importer also run under the patch lock. Once the registered installer starts, failure retains the target and family pins without publishing candidate patch records. If a failed installation leaves an unknown state, the installer must refuse further updates to that target. The replacement is a callable copy of the patched body. For a definition-wide target it is null: an open generic definition has no callable copy, so the installer writes the body. The body writer accepts an importer for Type, MethodBase, FieldInfo, string and standalone signature byte[] values. MethodBase values can be DynamicMethod instances. The host owns their token import and lifetime. The importer returns zero for a value it cannot import. For a call to a static method without open generic parameters, including a DynamicMethod, Harmony then imports a generated stand-in with the same signature. The collectible stand-in remains retained while its callback is live. A module that cannot bind collectible assemblies refuses it too. Harmony then offers a permanent stand-in that lives for the process. Unpatching does not unload it. Permanent closed-helper stand-ins reuse a delegate field per original, helper site and signature, bounding growth by distinct combinations. Successful installation updates the field, including calls from older bodies. Failed installation preserves the previous delegate. Ordinary reflection callbacks use module, metadata token, declaring type and method arguments as identity. Distinct dynamic callbacks retain separate permanent entries. A refused generated open generic helper is offered again in a permanent assembly with its generic context. These assemblies accumulate across rebuilds. A permanent generic helper cannot reference collectible dependencies. Generic helper generation requires collectible AssemblyLoadContext support. On .NET Framework and Mono, an Infix patch on a host-owned generic method definition therefore fails before installation. Patching throws a HarmonyException with an inner NotSupportedException and records no patch. Other open generic callbacks must bind directly. Hosts must resolve HarmonyDynamicMethod_* stand-ins, HarmonyGenerated_* helpers and callback dependencies by exact loaded identity. Runtime access checks still apply to private callbacks and Harmony's internal generic guard helpers unless the target module ignores those checks. A refused ldtoken, ldftn or ldvirtftn method operand cannot use a stand-in because that would change its identity. Other unsupported refusals fail body export. The installer must complete export before it mutates the runtime body. A KeyValuePair<Type, bool> with true requests the generic definition instantiated over its own type parameters. A KeyValuePair<MemberInfo, bool> with true requests a self-instantiated generic member, including a generic method's own parameters. If its declaring type is a generic definition, emit a MemberRef with a self-instantiated TypeSpec parent and the member definition's signature. A generic method also requires a MethodSpec. Bare members retain their ordinary metadata meaning. For that member marker, a generic method definition also instantiates its own method parameters. An already constructed method keeps its arguments. The writer returns a complete CLI body with its header, code and exception sections. Call it at most once, synchronously on the installation thread. The net35 and netstandard2.0 Harmony builds cannot export bodies, regardless of the loading runtime. Their writer throws NotSupportedException: Exporting a method body requires a modern Harmony target framework. An installer can use a non-null callable replacement without invoking the writer.

Harmony 3

For Harmony 2, read the 2.x documentation.