mirror of
https://github.com/pardeike/Harmony
synced 2026-06-08 16:40:38 +00:00
176 lines
10 KiB
HTML
176 lines
10 KiB
HTML
<!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.54.0.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 depended 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. Beside that, you can only resort to some clever redesign of your patch and find a spot in the calling chain higher up 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) { return 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()
|
|
{
|
|
return "base";
|
|
}
|
|
}
|
|
|
|
public class SubClass : BaseClass
|
|
{
|
|
public override string Method()
|
|
{
|
|
return "subclass";
|
|
}
|
|
}
|
|
</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>Methods that contains generics like <code>Add</code> in <code>List<T>.Add(...)</code> or methods defined in classes that are generic can be patched but you might get unexpected results. Usually you need to patch specific implementations separately, patching each type of T on its own.</p>
|
|
<p>Other times, patching such a method can make the runtime call your patch for types of T that you do not expect. The solution for that is to inject <code>__instance</code> and check its type and run your patch code conditionally.</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 as 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 its 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 inherting 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 patchable.</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 result 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 correctly 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>
|
|
</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/Harmony/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>
|