Files
pardeike-Harmony/docs/articles/patching-edgecases.html
Andreas Pardeike 0072943ba5 draft for docs
2025-08-23 00:31:53 +02:00

226 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<!--[if IE]><![endif]-->
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Patching </title>
<meta name="viewport" content="width=device-width">
<meta name="title" content="Patching ">
<meta name="generator" content="docfx 2.48.1.0">
<link rel="shortcut icon" href="../favicon.ico">
<link rel="stylesheet" href="../styles/docfx.vendor.css">
<link rel="stylesheet" href="../styles/docfx.css">
<link rel="stylesheet" href="../styles/main.css">
<meta property="docfx:navrel" content="../toc.html">
<meta property="docfx:tocrel" content="toc.html">
</head>
<body data-spy="scroll" data-target="#affix" data-offset="120">
<div id="wrapper">
<header>
<nav id="autocollapse" class="navbar navbar-inverse ng-scope" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../index.html">
<img id="logo" class="svg" src="../logo.svg" alt="">
</a>
</div>
<div class="collapse navbar-collapse" id="navbar">
<form class="navbar-form navbar-right" role="search" id="search">
<div class="form-group">
<input type="text" class="form-control" id="search-query" placeholder="Search" autocomplete="off">
</div>
</form>
</div>
</div>
</nav>
<div class="subnav navbar navbar-default">
<div class="container hide-when-search" id="breadcrumb">
<ul class="breadcrumb">
<li></li>
</ul>
</div>
</div>
</header>
<div role="main" class="container body-content hide-when-search">
<div class="sidenav hide-when-search">
<a class="btn toc-toggle collapse" data-toggle="collapse" href="#sidetoggle" aria-expanded="false" aria-controls="sidetoggle">Show / Hide Table of Contents</a>
<div class="sidetoggle collapse" id="sidetoggle">
<div id="sidetoc"></div>
</div>
</div>
<div class="article row grid-right">
<div class="col-md-10">
<article class="content wrap" id="_content" data-uid="">
<h1 id="patching">Patching</h1>
<h2 id="edge-cases">Edge Cases</h2>
<p>Patching at runtime is very flexible. But it comes with its downsides. This section describes edge cases that need workarounds, are hard to solve or sometimes impossible.</p>
<h3 id="inlining">Inlining</h3>
<p>This <a href="https://mattwarren.org/2016/03/09/adventures-in-benchmarking-method-inlining">Article</a> describes the details pretty good. An inlined method is no longer a method and is not called in the normal way. As a result, Harmony cannot patch these methods and your patches will simply be non-functional.</p>
<p>The solution is highly dependent on your situation. If you have control over the host application, you could run it in debug mode, but that would come with a large speed penalty. Besides that, you can only resort to some clever redesign of your patch and find a spot higher up in the call chain that is not inlined. This sometimes requires mass-patching all occurances of all methods that call the inlined method and patching there (<code>TargetMethods()</code> is your friend).</p>
<h3 id="calling-base-methods">Calling Base Methods</h3>
<p>When the class you want to patch overrides a method in its base class, calling the base implementation with <code>base.SomeMethod()</code> does not work as you expected, when you call it from your patch code.</p>
<pre><code class="lang-csharp" name="example">using HarmonyLib;
using System;
using System.Runtime.CompilerServices;
[HarmonyPatch]
class Patch
{
[HarmonyReversePatch]
[HarmonyPatch(typeof(BaseClass), nameof(BaseClass.Method))]
[MethodImpl(MethodImplOptions.NoInlining)]
static string BaseMethodDummy(SubClass instance) =&gt; null;
[HarmonyPatch(typeof(SubClass), nameof(SubClass.Method))]
static void Prefix(SubClass __instance)
{
var str = BaseMethodDummy(__instance);
Console.WriteLine(str);
}
}
public class BaseClass
{
public virtual string Method() =&gt; &quot;base&quot;;
}
public class SubClass : BaseClass
{
public override string Method() =&gt; &quot;subclass&quot;;
}
</code></pre>
<p>The reason for this is that the resolution of <code>base.SomeMethod()</code> happens in your compiler. It will create IL code that targets that specific method. At runtime however, you can't simply use reflections or delegates to call it. They all will be resolved to the overwriting method. The only solution that is known to solve this is to use a <code>Reverse Patch</code>, that copies the original to a stub of your own that you then can call. See this <a href="https://gist.github.com/pardeike/45196a8b8ef331f38b14e1a7e5ee1782">gist</a> for an example and a comparison.</p>
<h3 id="generics">Generics</h3>
<p>Generics can be difficult to patch. In general, expect generic methods and methods of generic classes to be shared between different types of <code>T</code> during runtime. This means that by patching one method, the method will be patched for all types of <code>T</code>. Depending on the type of generic and your .NET runtime, this can be worked around in a few ways:</p>
<ul>
<li>If the generic includes a value type, such as <code>int</code>, in most (but not all) cases, the method will not be shared. Patching a method which uses a value type parameter will patch only that specific method. Conversely, patching a generic with an object type will <em>not</em> patch the value type method, so both may have to be patched.</li>
<li>If the method is a non-generic non-static method of a generic class, you can check the generic type using <code>__instance</code> (such as <code>__instance.GetType().DeclaringType.GenericTypeArguments</code>), and adjust your code's behavior depending on the type.</li>
<li>If the method is a generic method of a non-generic class, you may be able to examine the method's arguments, if any argument contains <code>T</code>. Also, generic type data will be lost (if <code>Method&lt;T&gt;</code> is patched using <code>Method&lt;string&gt;</code>, <code>Method&lt;object&gt;</code> will become <code>Method&lt;string&gt;</code>)</li>
<li>If the method is a static non-generic method of a generic class, generic type data will be lost (see above).</li>
</ul>
<h3 id="changing-the-type-returned-by-a-constructor">Changing the type returned by a constructor</h3>
<p>It seems to be easy to make a constructor return a different type. Unfortunately, C# and the intermediate bytecode (CIL) doesnt work like that. A constructor in C# is compiled into the following IL code:</p>
<pre><code>newobj instance void Test::.ctor();
</code></pre>
<p>And the newobj IL code is described by Microsoft as</p>
<blockquote>
<p>The newobj instruction allocates a new instance of the class associated with ctor and initializes all the fields in the new instance to 0 (of the proper type) or null references as appropriate. It then calls the constructor ctor with the given arguments along with the newly created instance. After the constructor has been called, the now initialized object reference (type O) is pushed on the stack.</p>
</blockquote>
<p>So a constructor is just an initialiser method that gets the newly empty obj as an argument to set the values of fields. All the &quot;create object of type T&quot; logic is in the IL code and the internal logic of the C# runtime. Which means you cannot change the type from within the constructor method. All you can do is to manipulate the place where the constructor is called (the operand of newobj or some extra IL after it that changes the value on the stack).</p>
<h3 id="static-constructors">Static Constructors</h3>
<p>Static constructors of a class will run as soon as you touch or instantiate that class. That unfortunately means that when Harmony asks for some basic required information for that class, it will trigger the static constructor before the patch happens.</p>
<p>As a result, you cannot patch static constructors unless you plan to run them again (which often defeats the purpose). It also has the side effect that your patches to other methods in such a class will run the constructor at the wrong moment - causing errors. In that case, you need to time the patching so it happens when it's ok to run the static constructor or when it already has been triggered.</p>
<h3 id="native-external-methods">Native (External) Methods</h3>
<p>A method that has only an external implementation (like a native Unity method) can normally not be patched. Harmony requires access to the original IL code to build the replacement. Thus adding Prefix or Postfix to it does not work. This leaves only one possibility: using a transpiler to create your own implementation.</p>
<p>As a result, you can patch native methods with a transpiler-only patch that ignores the (empty) input and returns a new implementation that will replace the original. <strong>Beware:</strong> after patching, the original implemenation is lost and you cannot call it anymore, making this approach less useful.</p>
<h3 id="marshalbyrefobject">MarshalByRefObject</h3>
<p>Methods inheriting from <code>MarshalByRefObject</code> are kind of special and patching them and information about how the .NET runtime implements the glue code between managed methods and their jitted assembler code does not exist. Thus special methods like certain types of generics and methods inheriting from MarshalByRefObject are difficult or impossible to patch.</p>
<h3 id="special-classes">Special Classes</h3>
<p>Related to the problem with marshalled classes, .NET contains classes like <a href="https://docs.microsoft.com/en-us/dotnet/api/system.web.httprequest">HttpRequest</a> that exhibit strange side effects when patching methods in them. Sometimes, its necessary to patch some methods with identity patches (no prefix, postfix or transpiler, but still patched) to make patches on other methods in the same class work. Details are sparse and it really depends on your architecture, your .NET version, the runtime environment and the class you are patching. There is no simple solution but sometimes, experimenting gives results.</p>
<h3 id="methods-with-dead-code">Methods With Dead Code</h3>
<p>In some environments (like Mono) the runtime poses strict rules about creating methods that should not contain dead code. This becomes problematic, when patching methods like the following results in a <code>InvalidProgramException</code>:</p>
<pre><code class="lang-csharp">public SomeType MyMethod()
{
throw new NotImplementedException()
}
</code></pre>
<p>That method has no <code>RET</code> IL code in its body and if you try to patch it, Harmony will generate illegal IL. The only solution to this is to create a <code>Transpiler</code> that transpiles the method to a correct version by creating valid IL. This is also true for adding a Prefix or Postfix to that method. The way Harmony works, the replacement method needs to be valid to add calls to your patches to it.</p>
<h3 id="patching-too-early-missingmethodexception-in-unity">Patching too early: MissingMethodException in Unity</h3>
<p>When patching too early, for example on the injected assemblys entry point, Unity will throw a <code>MissingMethodException: Attempted to access a missing method</code>.</p>
<p>This situation occurs when the original method directly or indirectly calls an <code>external</code> UnityEngine method.</p>
<p>In the following example code, patching either <code>SomeMethod()</code> or <code>SomeOtherMethod()</code> will cause the exception:</p>
<pre><code class="lang-csharp" name="example">class SomeGameObject
{
GameObject gameObject;
void SomeMethod() =&gt; UnityEngine.Object.DontDestroyOnLoad(gameObject);
void SomeOtherMethod() =&gt; SomeMethod();
}
</code></pre>
<p><code>UnityEngine.Object.DontDestroyOnLoad()</code> is an external UnityEngine method:</p>
<pre><code class="lang-csharp">[MethodImpl(MethodImplOptions.InternalCall)]
[GeneratedByOldBindingsGenerator]
public static extern void DontDestroyOnLoad(Object target);
</code></pre>
<p>To prevent this issue, make sure UnityEngine has finished its startup phase (dynamically linking external methods to actual binary) before patching such methods.</p>
<p>One way to do so is to execute patching only after Unity has loaded the first scene, for example by using the <code>SceneManager.sceneLoaded</code> event:</p>
<pre><code class="lang-csharp" name="example">public static class Patcher
{
private static bool patched = false;
public static void Main() =&gt;
//DoPatch(); &lt;-- Do not execute patching on assembly entry point
SceneManager.sceneLoaded += SceneLoaded;
private static void DoPatch()
{
var harmony = new Harmony(&quot;test&quot;);
harmony.PatchAll();
patched = true;
}
private static void SceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode loadSceneMode)
{
// Execute patching after unity has finished it's startup and loaded at least the first game scene
if (!patched)
DoPatch();
}
}
</code></pre></article>
</div>
<div class="hidden-sm col-md-2" role="complementary">
<div class="sideaffix">
<div class="contribution">
<ul class="nav">
<li>
<a href="https://github.com/pardeike/Harmony/blob/master/Documentation/articles/patching-edgecases.md/#L1" class="contribution-link">Improve this Doc</a>
</li>
</ul>
</div>
<nav class="bs-docs-sidebar hidden-print hidden-xs hidden-sm affix" id="affix">
<!-- <p><a class="back-to-top" href="#top">Back to top</a><p> -->
</nav>
</div>
</div>
</div>
</div>
<footer>
<div class="grad-bottom"></div>
<div class="footer">
<div class="container">
<span class="pull-right">
<a href="#top">Back to top</a>
</span>
<span>Generated by <strong>DocFX</strong></span>
</div>
</div>
</footer>
</div>
<script type="text/javascript" src="../styles/docfx.vendor.js"></script>
<script type="text/javascript" src="../styles/docfx.js"></script>
<script type="text/javascript" src="../styles/main.js"></script>
</body>
</html>