** 免責事項 **
利用者は、この公開番号で提供された情報の配布および使用によって引き起こされた直接的または間接的な結果および損失について単独で責任を負います。著者および著者は、これらの結果について責任を負いません。結果が出た場合は、自己責任でください。ありがとう!
こんにちは、私は砂漠の果ての狼です。
本文首发于Dotnet9,结合前面两篇(如何在没有第三方.NET库源码的情况下调试第三库代码?和拦截、篡改、伪造.NET类库中不限于public的类和方法),本文将设计一个案例,手把手地带大家应用这两篇文章中涉及的技能,并介绍一种支持多个版本的库的兼容性解决方案(涉及第三方库的反编译和强签名)。
この記事の目次は以下の通り。
- 前のページ
- ケースデザインの事例
- dnSpyを使用してデバッグ
- Lib.Harmony傍受の使用
- 上位バージョンのLib.Harmonyの導入:複数バージョンのライブラリの互換性利用をサポート
- まとめまとめまとめ
1. 前のページ
テクノロジーは存在し、重要なのはその使い方です。前回の記事では、読者からのメッセージがありました。
Lib.Harmonyはまともなライブラリではないようですが、正当なシナリオで使用する必要がありますか?
キャッチコピーは“非常に深刻”。サードパーティ製ライブラリを使用していて、バージョンを確認してすでに稼働している場合、潜在的なリスクがあるため、サードパーティ製ライブラリをアップグレードできない場合があります。この時点で、サードパーティ製ライブラリではなく、独自のコードのみを変更できます。
読者はとても理にかなっている。
このツールは非常に強力ですが、時には恐ろしいです。
読者が疑問を持っているので、この記事を書き、より現実的に見えるユースケースをシミュレートしようとしました。これを行って、このツールが本物かどうかを確認できます。この記事では、詳細なハンズオンチュートリアルを提供します。
2. ケースデザインの事例
这是一个小动画游戏,我已经将其发布到NuGet上:Dotnet9Games。在这个小动画游戏中,我设置了两个陷阱。我们将按照我的步骤一一解决这些问题。首先,我们创建一个.NET Framework 4.6.1的WPF空项目【Dotnet9Playground】。我认为大部分人都会使用这个版本的桌面应用程序,如果不是,请在评论中告诉我。
2.1. Dotnet9Games Packのダウンロード
我已经将制作好的(虚构的)游戏发布在NuGet上作为第三方包使用。为了模拟一个比较真实的场景,直接安装最新版本【本文基于1.0.3版本编写】即可:

2.2.ターゲットゲームを追加
打开MainWindow.xaml,引入Dotnet9Games命名空间:
xmlns:dotnet9="https://dotnet9.com"
MainWindow.xaml完整代码如下:
<Window
x:Class="Dotnet9Playground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dotnet9="https://dotnet9.com"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="综合小案例:模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容"
Width="800"
Height="450"
Background="Bisque"
Icon="Resources/favicon.ico"
mc:Ignorable="d">
<Border Padding="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="20"
Foreground="Blue"
Text="生成" />
<TextBox
x:Name="TextBoxBallCount"
Width="50"
Height="25"
Margin="10,0"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontSize="20"
Foreground="Red"
Text="{Binding ElementName=MyBallGame, Path=BallCount, Mode=TwoWay}" />
<TextBlock
Margin="0,0,10,0"
VerticalAlignment="Center"
FontSize="20"
Foreground="Blue"
Text="个气球,点击" />
<Button
Padding="15,2"
Background="White"
BorderBrush="DarkGreen"
BorderThickness="2"
Click="StartGame_OnClick"
Content="开始游戏"
FontSize="20"
Foreground="DarkOrange" />
</StackPanel>
<dotnet9:BallGame
x:Name="MyBallGame"
Grid.Row="1"
BallCount="8" />
</Grid>
</Border>
</Window>
MainWindow.xaml.cs代码如下:
using System.Windows;
namespace Dotnet9Playground;
/// <summary>
/// 综合小案例:模拟.NET应用场景,综合应用反编译、第三方库调试、拦截、一库多版本兼容
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void StartGame_OnClick(object sender, RoutedEventArgs e)
{
MyBallGame.StartGame();
}
}
準備操作が完了し、プログラムを実行します

このゲームは簡単で、以下のステップがあります。
- 在主界面提供一个文本输入框,用于填写生成的气球个数。可以通过数据绑定将文本框的值绑定到游戏的
BallCount属性。 - 提供一个
开始游戏按钮,点击按钮后会触发MyBallGame.StartGame()方法,用于生成气球并播放动画。
2.3.最初の罠の導入
8個の風船を生成するには少なすぎるので、80個の風船を生成しましょう:

どうやって大きな赤い円を飛ばして風船が消えたの?これが罠だ!!
3. dnSpyを使用してデバッグ
3.1.分析する。
输入80个气球后,我们点击开始游戏是调用了游戏的方法StartGame(), 我们打开dnSpy(这个链接提供32位和64位下载链接),拖入Dotnet9Games.dll,找到该方法代码:

// Token: 0x06000022 RID: 34 RVA: 0x000022AC File Offset: 0x000004AC
public void StartGame()
{
bool flag = this.BallCount > 9;
if (flag)
{
this.PlayBrokenHeartAnimation();
}
else
{
this.GenerateBalloons();
}
}
原来是当气球个数多于9个时调用了PlayBrokenHeartAnimation()方法,这个方法干啥的呢?看代码:

大体見えますか?まずバルーンコントロールを空にして赤い円アニメーションを追加しましたが、デバッグはどうでしょうか?
3.2.デバッグの検証
次のステップを取る:
- 在
StartGame()方法第一行打上断点; - 点击
dnSpy【启动】按钮; - 在弹出的【调试程序】界面里,"调试引擎"默认选择
.NET Framework,"可执行程序"选择我们的WPF主程序Exe【Dotnet9Playground.exe】,再点击【确定】即将WPF程序运行起来了; - メインプログラムインターフェイスのバルーン入力数は9個以上、例えば80個です。
- “Start Game”ボタンをクリックします。
- 进入断点了,调试看看,真的进入
PlayBrokenHeartAnimation()方法

4. Lib.Harmony傍受の使用
明白了原因,我们使用Lib.Harmony拦截StartGame()方法。
4.1. Lib.Harmonyパッケージをインストールする
我们安装最低版本1.2.0.1:

なぜ最低バージョンをインストールするのですか?*
为了后面引入一库多版本兼容需求,低版本的Lib.Harmony有Bug,我们继续,哈哈。
4.2.インターセプトクラスの作成
インターセプトクラス“/Hooks/HookBallGameStartGame.cs”を追加します。
using Dotnet9Games.Views;
using Harmony;
using System.Reflection;
namespace Dotnet9Playground.Hooks;
internal class HookBallGameStartGame
{
/// <summary>
/// 拦截游戏的开始方法StartGame
/// </summary>
public static void StartHook()
{
var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallGameStartGame");
var hookClassType = typeof(BallGame);
var hookMethod =
hookClassType!.GetMethod(nameof(BallGame.StartGame), BindingFlags.Public | BindingFlags.Instance);
var replaceMethod = typeof(HookBallGameStartGame).GetMethod(nameof(HookStartGame));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// StartGame替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookStartGame(ref object __instance)
{
#region 原方法原代码
//if (BallCount > 9)
//{
// // 播放爆炸动画效果
// PlayExplosionAnimation();
//}
//else
//{
// // 生成彩色气球
// GenerateBalloons();
//}
#endregion
#region 拦截替换方法逻辑
// 1、删除气球个数限制逻辑
// 2、生成气球方法为private修饰,我们通过反射调用
var instanceType = __instance.GetType();
var hookGenerateBalloonsMethod =
instanceType.GetMethod("GenerateBalloons", BindingFlags.Instance | BindingFlags.NonPublic);
// 生成彩色气球
hookGenerateBalloonsMethod!.Invoke(__instance, null);
#endregion
return false;
}
}
上記のコードは関連するコメントを追加しています。
StartHook()方法用于关联被拦截方法StartGame与拦截替换方法HookStartGame;HookStartGame是拦截替换方法,方法中注释的代码为原方法逻辑代码;- 替换代码你可以将气球个数改大一点,或者像站长一样直接不要
if (BallCount > 9)判断,改为直接调用气球生成方法GenerateBalloons。
在App.xaml.cs注册上面的拦截类:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 拦截气球动画播放方法
HookBallGameStartGame.StartHook();
}
}
WPFプログラムを実行して、バルーンの数を80個に変更し、正常に生成されます:

4.3.それだけか?もう一つのトラップ。
バルーンが動くのを見て、フォームサイズを縮小します(プログラムがクラッシュし、オペレーティングシステムがしばらく立ち往生するため、デバッグをお勧めします)。

異常な手順、もう一度見てください:

例外コードを追加:
/// <summary>
/// 重写MeasureOverride方法,引出Size参数为负数异常
/// </summary>
/// <param name="constraint"></param>
/// <returns></returns>
protected override Size MeasureOverride(Size constraint)
{
// 计算最后一个元素宽度,不需要关注为什么这样写,只是为了引出Size异常使得
var lastChild = _balloons.LastOrDefault();
if (lastChild != null)
{
var remainWidth = ActualWidth;
foreach (var balloon in _balloons)
{
remainWidth -= balloon.Shape.Width;
}
lastChild.Shape.Measure(new Size(remainWidth, lastChild.Shape.Height));
}
return base.MeasureOverride(constraint);
}
*分析
- 在拖动窗体大小时,游戏用户控件
BallGame的MeasureOverride方法会触发,对布局进行重新计算; - メソッド内のロジック:
-
- 如果存在一个运动的气球,那么计算
BallGame的实际宽度减去所有子气球的宽度之间的差,得到remainWidth; - 使用
remainWidth重新计算最后一个气球的大小; remainWidth在做减法操作,那么气球个数足够多,以致于游戏控件宽度小于这些气球宽之和时,就会为负数;- 我们再看看
Size构造函数代码(如果你用的VS,这里推荐大家安装ReSharper,十分方便的查看引用库方法 ),如下截图:
- 如果存在一个运动的气球,那么计算

コードをコピーしてください:
/// <summary>Implements a structure that is used to describe the <see cref="T:System.Windows.Size" /> of an object. </summary>
[TypeConverter(typeof (SizeConverter))]
[ValueSerializer(typeof (SizeValueSerializer))]
[Serializable]
public struct Size : IFormattable
{
// 这里省略N多代码
/// <summary>Initializes a new instance of the <see cref="T:System.Windows.Size" /> structure and assigns it an initial <paramref name="width" /> and <paramref name="height" />.</summary>
/// <param name="width">The initial width of the instance of <see cref="T:System.Windows.Size" />.</param>
/// <param name="height">The initial height of the instance of <see cref="T:System.Windows.Size" />.</param>
public Size(double width, double height)
{
this._width = width >= 0.0 && height >= 0.0 ? width : throw new ArgumentException(MS.Internal.WindowsBase.SR.Get("Size_WidthAndHeightCannotBeNegative"));
this._height = height;
}
// 这里省略N多代码
}
当宽高为负数时会抛出异常,这就能理解了,我们再使用Lib.Harmony拦截BallGame的MeasureOverride方法,如法炮制。
添加/Hooks/HookBallgameMeasureOverride.cs类拦截:
using Dotnet9Games.Views;
using Harmony;
using System.Reflection;
namespace Dotnet9Playground.Hooks;
/// <summary>
/// 拦截BallGame的MeasureOverride方法
/// </summary>
internal class HookBallgameMeasureOverride
{
/// <summary>
/// 拦截游戏的MeasureOverride方法
/// </summary>
public static void StartHook()
{
var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallgameMeasureOverride");
var hookClassType = typeof(BallGame);
var hookMethod = hookClassType!.GetMethod("MeasureOverride", BindingFlags.NonPublic | BindingFlags.Instance);
var replaceMethod = typeof(HookBallgameMeasureOverride).GetMethod(nameof(HookMeasureOverride));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// MeasureOverride替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookMeasureOverride(ref object __instance)
{
// 暂时不做任何处理,返回false表示
return false;
}
}
再在App.xaml.cs添加拦截注册:
using Dotnet9Playground.Hooks;
using System.Windows;
namespace Dotnet9Playground
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 拦截气球动画播放方法
HookBallGameStartGame.StartHook();
// 这是第二个拦截方法:拦截气球MeasureOverride方法
HookBallgameMeasureOverride.StartHook();
}
}
}
手順の再実行:

拦截方法进入了断点,但无法获取BallGame的实例,提示无法读取内存,拦截方法返回False(不执行原方法)有下面的异常:

この時点で、プログラムが例外的に終了し、インターセプトメソッドがTrueを返します(元のメソッドを続行します)。

因为继续执行原方法,取最后一个气球方法又报错var lastChild = _balloons.LastOrDefault();,好无奈呀,心酸。
会社の専門家によるアドバイス:
Sizeは構造体ポインタであるため、0Harmonyバージョン1.2.0.1ではポインタを4ビットとして扱いますが、“Our program”は64ビット、ポインタは8ビットであり、メモリはすべて間違っています。
好,那我们使用高版本Lib.Harmony?
5. 上位バージョンのLib.Harmonyの導入:複数バージョンのライブラリの互換性利用をサポート
5.1.新規作成プロジェクトにハイバージョンのLib.Harmonyを導入
*理由
有可能程序中使用低版本的
Lib.Harmony库做了不少拦截操作,贸然全部升级,测试不到位,容易出现程序大崩溃(当前本程序只加了一个HookBallGameStartGame拦截类),而工程Dotnet9Playground直接引入同一个库多版本无法实现(网友如果有建议欢迎留言)。
添加新类库“Dotnet9HookHigh”,并使用NuGet安装2.2.2稳定最新版Lib.Harmony库:

同时也添加Dotnet9Games的NuGet包,将前面添加的HookBallgameMeasureOverride类剪切到该库,Lib.Harmony高版本用法与低版本有所区别,在代码中有注释,注意对比,升级后的HookBallgameMeasureOverride类定义:
using Dotnet9Games.Views;
using HarmonyLib;
using System.Reflection;
namespace Dotnet9HookHigh;
/// <summary>
/// 拦截BallGame的MeasureOverride方法
/// </summary>
public class HookBallgameMeasureOverride
{
/// <summary>
/// 拦截游戏的MeasureOverride方法
/// </summary>
public static void StartHook()
{
//var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallgameMeasureOverride");
// 上面是低版本Harmony实例获取代码,下面是高版本
var harmony = new Harmony("https://dotnet9.com/HookBallgameMeasureOverride");
var hookClassType = typeof(BallGame);
var hookMethod = hookClassType!.GetMethod("MeasureOverride", BindingFlags.NonPublic | BindingFlags.Instance);
var replaceMethod = typeof(HookBallgameMeasureOverride).GetMethod(nameof(HookMeasureOverride));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// MeasureOverride替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookMeasureOverride(ref object __instance)
{
return false;
}
}
区别如下图,Harmony实例获取代码有变化,其他不变:

主工程Dotnet9Playground添加Dotnet9HookHigh工程的引用,App.xaml.cs中添加引用HookBallgameMeasureOverride命名空间:using Dotnet9HookHigh;,代码如下:
using Dotnet9HookHigh;
using Dotnet9Playground.Hooks;
using System.Windows;
namespace Dotnet9Playground
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// 拦截气球动画播放方法
HookBallGameStartGame.StartHook();
// 这是第二个拦截方法:拦截气球MeasureOverride方法
HookBallgameMeasureOverride.StartHook();
}
}
}
これで終わりか?試してみてください

这提示是指我的新工程Dotnet9HookHigh未成功应用高版本Lib.Harmony(2.2.2),亦指主工程Dotnet9Playground未成功识别加载高版本Lib.Harmony,怎么办?看我接下来的表演!
5.2.低·低バージョンのライブラリカタログ化
5.2.1.プロファイラ出力ディレクトリ

程序输出目录只有一个0Harmony.dll,高低2个版本应该是两个库才对,怎么办?
5.2.2.新規作成ディレクトリ
低版本不变(存储位置依然放输出目录的根目录),为了兼容,我们把高版本改目录存放,比如:Lib/Lib.Harmony/2.2.2/0Harmony.dll,将库按目录结构存放在工程Dotnet9HookHigh中:

- 并将
0Harmony.dll的属性【复制到输出目录】设置为【如果较新则复制】 - 删除
Dotnet9HookHigh对Lib.Harmony库的NuGet引用,改为本地引用(原来的配方,浏览本地路径的方式);

- これで終わりですか?なぜ間違っているのか?*
5.3.同じライブラリの複数バージョンの設定
5.3.1. App.config配置多版本
修改Dotnet9Palyground的App.config文件,添加0Harmony.dll两个版本及读取位置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="0Harmony"
publicKeyToken="null"/>
<codeBase version="1.2.0.1" href="0Harmony.dll" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="0Harmony"
publicKeyToken="null"/>
<codeBase version="2.2.2.0" href="Lib\Lib.Harmony\2.2.2\0Harmony.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
再実行するか、エラーを報告するか。ああ、気絶してしまう...。
5.3.2.フォーカス:ライブラリの強い署名
上面分目录、配置文件版本配置目录也还不够,主工程还是无法区分两个版本的Lib.Harmony库,这里涉及.NET 库强签名,就是上面App.config配置中的publicKeyToken特性,加上这个主程序就认识了,关于强签名网上找到个说明《.Net程序集强签名详解》:
強力な署名dllはGACに登録でき、異なるアプリケーションが同じdllを共有できます。
強い署名付きライブラリ、またはアプリケーションは強い署名付きdllのみを参照でき、強い署名なしdllは参照できないが、強い署名なしdllは強い署名付きdllを参照できる。
強力な署名はソースコードを保護できず、強力な署名dllは逆コンパイル可能です。
強力な署名dllは第三者の悪意のある改ざんから保護します。
这里,对于1.2.0.1版本的0Harmony.dll库我们依然不动,只对2.2.2高版本做强签名处理,签名步骤参考[VS2008版本引入第三方dll无强签名],我们来一起做一遍,这里会借助Everything软件搜索使用到的命令程序,建议提前下载。
注意:暂时不要用最新预览版2.3.0-prerelease.2,站长做这个示例签名用这个版本花了2个晚上没成功,换成2.2.2就可以,下面的图也重新录了,可能该版本有其他依赖的缘故,只是猜测:

- 创建一个新的随机密钥对
0Harmony.snk
使用Everything查找一个sn.exe程序,随便使用一个,比如:"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe",在高版本目录下生成一个密钥对文件0Harmony.snk,命令如下:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe" -k "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.snk"

- 反编译
0Harmony.dll
查找ildasm.exe,比如C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe,执行以下命令生成0Harmony.dll的il中间文件:
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe" "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.dll" /out="F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.il"

- 再コンパイル、強力な名前付きパラメータ
查找ilasm.exe,比如C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ilasm.exe,执行以下命令做签名:
"C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ilasm.exe" "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.il" /dll /resource="F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.res" /key="F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.snk" /optimize

- 署名情報の確認
"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe" -v "F:\github_gitee\TerminalMACS.ManagerForWPF\src\Demo\MultiVersionLibrary\Dotnet9HookHigh\Lib\Lib.Harmony\2.2.2\0Harmony.dll"

也可将生成的dll拖入dnSpy查看:

作为对比,查看NuGet下载的Lib.Harmony是没做签名的:

我们将签名补充进App.Config文件。

** 注 **:ランダムな鍵ペアを使用しているため、あなたが生成する署名は私の署名とは異なります:
再调试,能正常拦截MeasureOverride方法了,传入的实例也能正常显示BallGame(就这?对,我搞了2个晚上。。。。):

5.4.最後のインターセプトを完了するために準備した。
コードは以下のとおり。
using Dotnet9Games.Views;
using HarmonyLib;
using System.Collections;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
namespace Dotnet9HookHigh;
/// <summary>
/// 拦截BallGame的MeasureOverride方法
/// </summary>
public class HookBallgameMeasureOverride
{
/// <summary>
/// 拦截游戏的MeasureOverride方法
/// </summary>
public static void StartHook()
{
//var harmony = HarmonyInstance.Create("https://dotnet9.com/HookBallgameMeasureOverride");
// 上面是低版本Harmony实例获取代码,下面是高版本
var harmony = new Harmony("https://dotnet9.com/HookBallgameMeasureOverride");
var hookClassType = typeof(BallGame);
var hookMethod = hookClassType!.GetMethod("MeasureOverride", BindingFlags.NonPublic | BindingFlags.Instance);
var replaceMethod = typeof(HookBallgameMeasureOverride).GetMethod(nameof(HookMeasureOverride));
var replaceHarmonyMethod = new HarmonyMethod(replaceMethod);
harmony.Patch(hookMethod, replaceHarmonyMethod);
}
/// <summary>
/// MeasureOverride替换方法
/// </summary>
/// <param name="__instance">BallGame实例</param>
/// <returns></returns>
public static bool HookMeasureOverride(ref object __instance)
{
#region 原方法代码逻辑
//// 计算最后一个元素宽度,不需要关注为什么这样写,只是为了引出Size异常使得
//var lastChild = _balloons.LastOrDefault();
//if (lastChild != null)
//{
// var remainWidth = ActualWidth;
// foreach (var balloon in _balloons)
// {
// remainWidth -= balloon.Shape.Width;
// }
// lastChild.Shape.Measure(new Size(remainWidth, lastChild.Shape.Height));
//}
//return base.MeasureOverride(constraint);
#endregion
#region 拦截替换代码
var instanceType = __instance.GetType();
var balloonsField = instanceType.GetField("_balloons", BindingFlags.NonPublic | BindingFlags.Instance);
var balloons = (IEnumerable)balloonsField!.GetValue(__instance);
var lastChild = balloons.Cast<object>().LastOrDefault();
if (lastChild == null)
{
return false;
}
var remainWidth = ((UserControl)__instance).ActualWidth;
foreach (object balloon in balloons)
{
remainWidth -= GetBalloonSize(balloon).Width;
}
// 注意:关键代码在这,如果剩余宽度大于0才重新计算最后一个子项大小
// 这段代码可能没什么意义,可按实际开发修改
if (remainWidth > 0)
{
var lashShape = GetBalloonShape(lastChild);
lashShape.Measure(new Size(remainWidth, lashShape.Height));
}
#endregion
return false;
}
private static Ellipse GetBalloonShape(object balloon)
{
var shapeProperty = balloon.GetType().GetProperty("Shape");
var shape = (Ellipse)shapeProperty!.GetValue(balloon);
return shape;
}
private static Size GetBalloonSize(object balloon)
{
var shape = GetBalloonShape(balloon);
return new Size(shape.Width, shape.Height);
}
}
キーコードは以下の通り。
// 注意:关键代码在这,如果剩余宽度大于0才重新计算最后一个子项大小
// 这段代码可能没什么意义,可按实际开发修改
if (remainWidth > 0)
{
var lashShape = GetBalloonShape(lastChild);
lashShape.Measure(new Size(remainWidth, lashShape.Height));
}
他のコードはリフレクションの使用であり、もはや詳細ではなく、プログラムを実行して、今すぐフォームを拡大します:

残りの幅が0より小さい場合、最後の子サイズの計算をスキップ

5.4.小さな最適化。
上面部分截图中可能您也看到了0Harmony.ref文件,我们简单说说。
Git一般是配置成不能上传可执行程序或dll文件的,但多版本dll特殊,部分库不能直接从NuGet引用,所以本文中的高版本Lib.Harmony库只能使用自己强签名版本,我们将dll文件扩展名改为“.ref"以允许上传,他人能正常使用,程序如果需要正常编译、生成,则给Dotnet9HookHigh工程添加生成前命令行,即生成时将.ref复制一份为.dll:
copy "$(ProjectDir)Lib\Lib.Harmony\2.2.2\0Harmony.ref" "$(ProjectDir)Lib\Lib.Harmony\2.2.2\0Harmony.dll"

6. まとめまとめまとめ
文中示例代码:MultiVersionLibrary
一般的なケース、特に2番目のトラップを書くことに興味があり、ゲーム関連のコードを読むことに興味があり、PRを提案し、このケースをより合理的で、より興味深く、より楽しい、2番目のトラップがいくつかの楽しい特殊効果を書くことができ、異なる効果を達成するために傍受後、これは傍受の喜びです。
本文通过一个模拟实际案例,帮助大家应用前两篇文章中涉及的技能(dnSpy调试第三方库和Lib.Harmony拦截第三方库),并且介绍一种支持多个版本的库的兼容性解决方案。
通过本文介绍支持多个版本的库的兼容性解决方案,读者可以简单了解如何反编译第三方库,以及如何使用强签名技术来保证库的兼容性(和安全性,本文未展开说,可以阅读此文浅谈.NET程序集安全签名)。希望本文提供的案例能帮助读者更好地理解和应用这些技能。