Harmony

repository·master·Indexed 27 days ago

https://github.com/pardeike/harmony

A library for patching, replacing, and decorating .NET and Mono methods during runtime, allowing developers to alter the functionality of existing C# applications without modifying original files. It supports prefix, postfix, and infix patches, with specific implementation rules for IL emission and stack restoration. Available via NuGet as Lib.Harmony (dependency-merged) or Lib.Harmony.Thin.

Tokens
17.3K
Snippets
12
Records
128
Agent score
90%

What's inside Harmony

  1. Overview of Harmony 2 functionality

    master

    Harmony is a high-level library for altering functionality in C# applications at runtime via monkey patching. Unlike other solutions, it does not modify the contents of DLL files on disk.

    Supported Environments:

    • Mono and .NET on Windows, Unix, and macOS.
    • Unity: Works with most profiles, including .NET 4.x. Note that it may not work if Unity uses a heavily stripped-down NetStandard profile.
  2. Introduction to Harmony

    master
    Harmony is a library designed for patching, replacing, and decorating .NET methods during runtime. It allows you to alter the functionality of existing C# applications (like games) without modifying the original DLL files on disk. This makes it ideal for modding environments where multiple concurrent changes are needed and where file-system modifications might be blocked by anti-cheat or legal constraints.
  3. Understand Harmony Transpilers

    master

    A Transpiler in Harmony is a specialized patch used to rewrite the Common Intermediate Language (CIL) of an existing method. Unlike prefix or postfix patches that run before or after a method, a Transpiler allows you to manipulate the actual instruction stream (the code inside the method) by receiving the original instructions and returning a modified sequence.

    Key Concepts:

    • Input/Output: It takes an IEnumerable<CodeInstruction> and must return an IEnumerable<CodeInstruction>.
    • Complexity: Writing transpilers requires a deep understanding of CIL (Common Intermediate Language) and how the stack-based execution model works.
    • Stack Management: You must ensure that your modifications do not leave unnecessary elements on the stack or cause stack underflows, as this will lead to compilation errors.
  4. Understand Infix Patching goals and constraints

    master

    Infix patching allows you to target specific call sites inside an outer method. This approach relies on the call-site stack contract rather than IL data-flow analysis.

    Key Technical Constraints

    When implementing or using infix patches, be aware of the following:

    • Call Replacement: You can only replace the call (or callvirt/calli/newobj if supported in later versions).
    • Prefix Handling: If the original call is preceded by call-only prefixes (such as constrained. or tail.), you must absorb and re-emit them inside the infix block immediately before the re-issued call.
    • Stack Effect: Your patch must end with the same stack effect as the original call. If the original call pushes a result (non-void), your patch must push a result; if the original was void, your patch must not push anything.
    • Parameter Injection: Infix patches reuse Harmony's existing parameter injection machinery.
  5. Core Patching Capabilities

    master

    Unlike simple hooking libraries that only allow replacing a method, Harmony provides several advanced capabilities:

    • Preserve Original Logic: Keep the original method intact while executing your own code.
    • Prefix/Postfix Execution: Execute your code specifically before (Prefix) or after (Postfix) the original method.
    • IL Code Processing: Modify the original method using IL code processors.
    • Coexistence: Multiple Harmony patches can coexist on the same method without conflicting.
  6. Understand Harmony patch types

    master

    Harmony provides four primary types of patches to inject code into original methods:

    • Prefix: Runs before the original method. Used to modify arguments, set the return value, skip the original method, or set state for a postfix.
    • Postfix: Runs after the original method. Used to read/change the result, access arguments, or read state set by a prefix.
    • Transpiler: An advanced patch that modifies the original method's IL (Intermediate Language) instructions directly during the patching process.
    • Finalizer: Runs after all other patches (including postfixes). It is the only patch type immune to exceptions thrown by the original method or other patches. It is used for guaranteed cleanup or exception handling.

    Additionally, Reverse Patching allows you to patch your own methods by defining a stub that mimics the original and applying the original's logic onto it.

  7. Understand Harmony Transpilers

    master

    A Transpiler is a post-compiler stage that modifies the IL (Intermediate Language) code of an original method. Unlike Prefix or Postfix patches, which execute at runtime, a Transpiler runs once during the patching process to alter the method's source code.

    Key characteristics:

    • It modifies IL code, not C# source code.
    • It is executed only once when the method is patched (and again if subsequent transpilers are added).
    • It cannot access runtime state because it runs during the patching phase.
    • Multiple transpilers can be chained together to produce the final IL output.
    • Use this for advanced cases where you need to modify the internal logic of a method, such as inserting static method calls, removing parts of the original code, or changing values/method calls.
  8. Understand the IL emission algorithm for infix calls

    master

    When implementing infix call site transformations, the IL emission follows a specific lifecycle to ensure the stack effect remains identical to the original call. The process involves capturing arguments into locals, executing prefixes, conditionally executing the original call, executing postfixes, and performing write-backs for byref arguments.

    Execution Lifecycle

    1. Capture: Pop the instance and arguments from the stack into local variables (using stloc).
    2. Prefixes: Execute prefixes in order. If a prefix returns a bool, it can be used to set a __runOriginal flag to skip the original call.
    3. Original Call: If __runOriginal is true, reload the instance and arguments from locals and execute the inner method. If the method is non-void, store the result in a __result local.
    4. Postfixes: Execute postfixes in order. If a postfix returns a value and the original call was non-void, the postfix result can replace __result.
    5. Write-backs: If any byref arguments were captured as values, they must be stored back to their original addresses. (Note: It is preferred to capture the managed pointer directly to avoid this step).
    6. Stack Restoration: Restore the original stack effect by loading __result onto the stack (for non-void methods) or pushing nothing (for void methods).
  9. Create a reverse patch using [HarmonyReversePatch]

    master

    A reverse patch is a stub method in your own code that copies the implementation of an original method (or part of it) into your own class. This allows you to call private methods with native performance, without reflection or delegates.

    To create one, apply the [HarmonyReversePatch] attribute to your stub method. The method signature must match the original method's signature (including static/non-static status). If patching an instance method, you can include the instance as the first argument (position 0) to access the original object.

    Warning: If you define an instance method as a stub, the copied IL expects this to point to the original class type. This will fail if your stub is in a different class type.

  10. Implement manual patching with HarmonyMethod

    master

    If you do not want to use annotations, you can perform manual patching. You are responsible for retrieving the MethodInfo for your patch methods and wrapping them in HarmonyMethod objects to pass them to the Patch() method.

    Patch methods must be static. While they cannot be dynamic methods directly, you can use static factory methods that return MethodInfo or DynamicMethod objects.

    [HarmonyPatch(...)]
    class Patch
    {
    	// the return type of factory methods can be either MethodInfo or DynamicMethod
    	[HarmonyPrefix]
    	static MethodInfo PrefixFactory(MethodBase originalMethod)
    	{
    		// return an instance of MethodInfo or an instance of DynamicMethod
    	}
    
    	[HarmonyPostfix]
    	static MethodInfo PostfixFactory(MethodBase originalMethod)
    	{
    		// return an instance of MethodInfo or an instance of DynamicMethod
    	}
    }