最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

模擬.NET應(yīng)用場景,綜合應(yīng)用反編譯、第三方庫調(diào)試、攔截、一庫多版本兼容方案

2023-09-26 23:40 作者:沙漠盡頭的狼  | 我要投稿

免責(zé)聲明

使用者本人對于傳播和利用本公眾號(hào)提供的信息所造成的任何直接或間接的后果和損失負(fù)全部責(zé)任。公眾號(hào)及作者對于這些后果不承擔(dān)任何責(zé)任。如果造成后果,請自行承擔(dān)責(zé)任。謝謝!

大家好,我是沙漠盡頭的狼。

本文首發(fā)于Dotnet9,結(jié)合前面兩篇(如何在沒有第三方.NET庫源碼的情況下調(diào)試第三庫代碼?和攔截、篡改、偽造.NET類庫中不限于public的類和方法),本文將設(shè)計(jì)一個(gè)案例,手把手地帶大家應(yīng)用這兩篇文章中涉及的技能,并介紹一種支持多個(gè)版本的庫的兼容性解決方案(涉及第三方庫的反編譯和強(qiáng)簽名)。

本文的目錄如下:

  1. 前言

  2. 案例設(shè)計(jì)

  3. 使用dnSpy進(jìn)行調(diào)試

  4. 使用Lib.Harmony攔截

  5. 引入高版本Lib.Harmony:支持多個(gè)版本的庫的兼容性使用

  6. 總結(jié)

1. 前言

技術(shù)的存在即合理,關(guān)鍵在于如何使用。在前面的文章中,有讀者留言:

Lib.Harmony似乎不是一個(gè)正經(jīng)的庫,有什么合法的場景需要使用它嗎?

站長回答:非常正經(jīng)。當(dāng)你使用一個(gè)第三方庫,并且確定了版本并已經(jīng)上線,有時(shí)候不能隨意升級(jí)第三方庫,因?yàn)榭赡艽嬖跐撛诘娘L(fēng)險(xiǎn)。這時(shí),你只能修改自己的代碼,而不動(dòng)第三方庫。

還有讀者說得很有道理:

這個(gè)工具非常強(qiáng)大,但有時(shí)也很可怕。

既然讀者有疑問,所以我寫了這篇文章,盡量模擬一個(gè)看起來比較實(shí)際的應(yīng)用場景。你可以跟著做一做,看看這個(gè)工具到底是不是正經(jīng)的。本文提供了詳細(xì)的手把手教程。

2. 案例設(shè)計(jì)

這是一個(gè)小動(dòng)畫游戲,我已經(jīng)將其發(fā)布到NuGet上:Dotnet9Games。在這個(gè)小動(dòng)畫游戲中,我設(shè)置了兩個(gè)陷阱。我們將按照我的步驟一一解決這些問題。首先,我們創(chuàng)建一個(gè).NET Framework 4.6.1的WPF空項(xiàng)目【Dotnet9Playground】。我認(rèn)為大部分人都會(huì)使用這個(gè)版本的桌面應(yīng)用程序,如果不是,請?jiān)谠u(píng)論中告訴我。

2.1. 引入Dotnet9Games包

我已經(jīng)將制作好的(虛構(gòu)的)游戲發(fā)布在[NuGet](NuGet Gallery | Dotnet9Games 1.0.2)上作為第三方包使用。為了模擬一個(gè)比較真實(shí)的場景,直接安裝最新版本即可:

2.2. 添加目標(biāo)游戲

打開MainWindow.xaml,引入Dotnet9Games命名空間:

xml復(fù)制代碼xmlns:dotnet9="https://dotnet9.com"

MainWindow.xaml完整代碼如下:

html復(fù)制代碼<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應(yīng)用場景,綜合應(yīng)用反編譯、第三方庫調(diào)試、攔截、一庫多版本兼容" ?? ?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="個(gè)氣球,點(diǎn)擊" /> ?? ? ? ? ? ? ? ?<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代碼如下:

csharp復(fù)制代碼using System.Windows; ?namespace Dotnet9Playground; ?/// <summary> /// ? ? 綜合小案例:模擬.NET應(yīng)用場景,綜合應(yīng)用反編譯、第三方庫調(diào)試、攔截、一庫多版本兼容 /// </summary> public partial class MainWindow : Window { ?? ?public MainWindow() ?? ?{ ?? ? ? ?InitializeComponent(); ?? ?} ? ? ?private void StartGame_OnClick(object sender, RoutedEventArgs e) ?? ?{ ?? ? ? ?MyBallGame.StartGame(); ?? ?} }

準(zhǔn)備操作完成,運(yùn)行程序:

這個(gè)游戲比較簡單,主要包含以下幾個(gè)步驟:

  1. 在主界面提供一個(gè)文本輸入框,用于填寫生成的氣球個(gè)數(shù)??梢酝ㄟ^數(shù)據(jù)綁定將文本框的值綁定到游戲的BallCount屬性。

  2. 提供一個(gè)開始游戲按鈕,點(diǎn)擊按鈕后會(huì)觸發(fā)MyBallGame.StartGame()方法,用于生成氣球并播放動(dòng)畫。

2.3. 引入第一個(gè)陷阱

氣球生成8個(gè)可能太少了,讓我們來生成80個(gè)氣球吧:

怎么彈出一個(gè)紅色的大圓,氣球都消失了?這就是陷阱!

3. 使用dnSpy進(jìn)行調(diào)試

3.1. 分析

輸入80個(gè)氣球后,我們點(diǎn)擊開始游戲是調(diào)用了游戲的方法StartGame(), 我們打開[dnSpy](Releases · dnSpyEx/dnSpy (github.com))(這個(gè)鏈接提供32位和64位下載鏈接),拖入Dotnet9Games.dll,找到該方法代碼:

csharp復(fù)制代碼// Token: 0x06000022 RID: 34 RVA: 0x000022AC File Offset: 0x000004AC public void StartGame() { ?? ?bool flag = this.BallCount > 9; ?? ?if (flag) ?? ?{ ?? ? ? ?this.PlayBrokenHeartAnimation(); ?? ?} ?? ?else ?? ?{ ?? ? ? ?this.GenerateBalloons(); ?? ?} }

原來是當(dāng)氣球個(gè)數(shù)多于9個(gè)時(shí)調(diào)用了PlayBrokenHeartAnimation()方法,這個(gè)方法干啥的呢?看代碼:

大致看出來了嗎?首先是清空氣球控件,然后又添加了一個(gè)紅色的圓動(dòng)畫,我們調(diào)試試試呢?

3.2. 調(diào)試驗(yàn)證

大致說下步驟:

  1. StartGame()方法第一行打上斷點(diǎn);

  2. 點(diǎn)擊dnSpy【啟動(dòng)】按鈕;

  3. 在彈出的【調(diào)試程序】界面里,"調(diào)試引擎"默認(rèn)選擇.NET Framework,"可執(zhí)行程序"選擇我們的WPF主程序Exe【Dotnet9Playground.exe】,再點(diǎn)擊【確定】即將WPF程序運(yùn)行起來了;

  4. 主程序界面氣球個(gè)數(shù)輸入超過9個(gè),比如80?

  5. 點(diǎn)擊“開始游戲”按鈕;

  6. 進(jìn)入斷點(diǎn)了,調(diào)試看看,真的進(jìn)入PlayBrokenHeartAnimation()方法

4. 使用Lib.Harmony攔截

明白了原因,我們使用Lib.Harmony攔截StartGame()方法。

4.1. 安裝Lib.Harmony包

我們安裝最低版本1.2.0.1

為啥是安裝最低版本?

為了后面引入一庫多版本兼容需求,低版本的Lib.Harmony有Bug,我們繼續(xù),哈哈。

4.2. 編寫攔截類

添加攔截類“/Hooks/HookBallGameStartGame.cs”:

csharp復(fù)制代碼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實(shí)例</param> ?? ?/// <returns></returns> ?? ?public static bool HookStartGame(ref object __instance) ?? ?{ ?? ? ? ?#region 原方法原代碼 ? ? ? ? ?//if (BallCount > 9) ?? ? ? ?//{ ?? ? ? ?// ? ?// 播放爆炸動(dòng)畫效果 ?? ? ? ?// ? ?PlayExplosionAnimation(); ?? ? ? ?//} ?? ? ? ?//else ?? ? ? ?//{ ?? ? ? ?// ? ?// 生成彩色氣球 ?? ? ? ?// ? ?GenerateBalloons(); ?? ? ? ?//} ? ? ? ? ?#endregion ? ? ? ? ?#region 攔截替換方法邏輯 ? ? ? ? ?// 1、刪除氣球個(gè)數(shù)限制邏輯 ?? ? ? ?// 2、生成氣球方法為private修飾,我們通過反射調(diào)用 ?? ? ? ?var instanceType = __instance.GetType(); ?? ? ? ?var hookGenerateBalloonsMethod = ?? ? ? ? ? ?instanceType.GetMethod("GenerateBalloons", BindingFlags.Instance | BindingFlags.NonPublic); ? ? ? ? ?// 生成彩色氣球 ?? ? ? ?hookGenerateBalloonsMethod!.Invoke(__instance, null); ? ? ? ? ?#endregion ? ? ? ? ?return false; ?? ?} }

上面的代碼加了相關(guān)的注釋,這里再提一提:

  • StartHook()方法用于關(guān)聯(lián)被攔截方法StartGame與攔截替換方法HookStartGame;

  • HookStartGame是攔截替換方法,方法中注釋的代碼為原方法邏輯代碼;

  • 替換代碼你可以將氣球個(gè)數(shù)改大一點(diǎn),或者像站長一樣直接不要if (BallCount > 9)判斷,改為直接調(diào)用氣球生成方法GenerateBalloons

App.xaml.cs注冊上面的攔截類:

csharp復(fù)制代碼public partial class App : Application { ?? ?protected override void OnStartup(StartupEventArgs e) ?? ?{ ?? ? ? ?base.OnStartup(e); ? ? ? ? ?// 攔截氣球動(dòng)畫播放方法 ?? ? ? ?HookBallGameStartGame.StartHook(); ?? ?} }

現(xiàn)在再運(yùn)行WPF程序,我們把氣球個(gè)數(shù)改為80個(gè),正常生成了:

4.3. 就這樣?No,再來一陷阱

看著氣球在動(dòng),我們縮放下窗體大小(這里建議Debug下嘗試,因?yàn)槌绦驎?huì)崩潰,導(dǎo)致操作系統(tǒng)會(huì)卡那么一小會(huì)兒):

程序異常了,再截圖看看:

貼上異常代碼:

csharp復(fù)制代碼/// <summary> /// 重寫MeasureOverride方法,引出Size參數(shù)為負(fù)數(shù)異常 /// </summary> /// <param name="constraint"></param> /// <returns></returns> protected override Size MeasureOverride(Size constraint) { ?? ?// 計(jì)算最后一個(gè)元素寬度,不需要關(guān)注為什么這樣寫,只是為了引出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); }

分析

  • 在拖動(dòng)窗體大小時(shí),游戲用戶控件BallGameMeasureOverride方法會(huì)觸發(fā),對布局進(jìn)行重新計(jì)算;

  • 方法內(nèi)邏輯:


    1. 如果存在一個(gè)運(yùn)動(dòng)的氣球,那么計(jì)算BallGame的實(shí)際寬度減去所有子氣球的寬度之間的差,得到remainWidth;

    2. 使用remainWidth重新計(jì)算最后一個(gè)氣球的大??;

    3. remainWidth在做減法操作,那么氣球個(gè)數(shù)足夠多,以致于游戲控件寬度小于這些氣球?qū)捴蜁r(shí),就會(huì)為負(fù)數(shù);

    4. 我們再看看Size構(gòu)造函數(shù)代碼(如果你用的VS,這里推薦大家安裝ReSharper,十分方便的查看引用庫方法 ),如下截圖:

代碼復(fù)制過來看:

csharp復(fù)制代碼 ?/// <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多代碼 ??}

當(dāng)寬高為負(fù)數(shù)時(shí)會(huì)拋出異常,這就能理解了,我們再使用Lib.Harmony攔截BallGameMeasureOverride方法,如法炮制。

添加/Hooks/HookBallgameMeasureOverride.cs類攔截:

csharp復(fù)制代碼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實(shí)例</param> ?? ?/// <returns></returns> ?? ?public static bool HookMeasureOverride(ref object __instance) ?? ?{ ?? ? ? ?// 暫時(shí)不做任何處理,返回false表示 ?? ? ? ?return false; ?? ?} }

再在App.xaml.cs添加攔截注冊:

csharp復(fù)制代碼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); ? ? ? ? ? ? ?// 攔截氣球動(dòng)畫播放方法 ?? ? ? ? ? ?HookBallGameStartGame.StartHook(); ? ? ? ? ? ? ?// 這是第二個(gè)攔截方法:攔截氣球MeasureOverride方法 ?? ? ? ? ? ?HookBallgameMeasureOverride.StartHook(); ?? ? ? ?} ?? ?} }

再運(yùn)行程序:

攔截方法進(jìn)入了斷點(diǎn),但無法獲取BallGame的實(shí)例,提示無法讀取內(nèi)存,攔截方法返回False(不執(zhí)行原方法)有下面的異常:

這時(shí)程序異常退出,我們將攔截方法返回True(繼續(xù)執(zhí)行原方法),又有提示:

因?yàn)槔^續(xù)執(zhí)行原方法,取最后一個(gè)氣球方法又報(bào)錯(cuò)var lastChild = _balloons.LastOrDefault();,好無奈呀,心酸。

經(jīng)過公司專家指點(diǎn):

因?yàn)镾ize是個(gè)結(jié)構(gòu)體指針,0Harmony 1.2.0.1版本把指針當(dāng)成4位,但“我們的程序”是64位,指針是8位,所有內(nèi)存錯(cuò)了。

好,那我們使用高版本Lib.Harmony

5. 引入高版本Lib.Harmony:支持多個(gè)版本的庫的兼容性使用

5.1. 新創(chuàng)建工程引入高版本Lib.Harmony

理由

有可能程序中使用低版本的Lib.Harmony庫做了不少攔截操作,貿(mào)然全部升級(jí),測試不到位,容易出現(xiàn)程序大崩潰(當(dāng)前本程序只加了一個(gè)HookBallGameStartGame攔截類),而工程Dotnet9Playground直接引入同一個(gè)庫多版本無法實(shí)現(xiàn)(網(wǎng)友如果有建議歡迎留言)。

添加新類庫“Dotnet9HookHigh”,并使用NuGet安裝2.2.2穩(wěn)定最新版Lib.Harmony庫:

同時(shí)也添加Dotnet9GamesNuGet包,將前面添加的HookBallgameMeasureOverride類剪切到該庫,Lib.Harmony高版本用法與低版本有所區(qū)別,在代碼中有注釋,注意對比,升級(jí)后的HookBallgameMeasureOverride類定義:

csharp復(fù)制代碼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實(shí)例獲取代碼,下面是高版本 ?? ? ? ?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實(shí)例</param> ?? ?/// <returns></returns> ?? ?public static bool HookMeasureOverride(ref object __instance) ?? ?{ ?? ? ? ?return false; ?? ?} }

區(qū)別如下圖,Harmony實(shí)例獲取代碼有變化,其它不變:

主工程Dotnet9Playground添加Dotnet9HookHigh工程的引用,App.xaml.cs中添加引用HookBallgameMeasureOverride命名空間:using Dotnet9HookHigh;,代碼如下:

csharp復(fù)制代碼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); ? ? ? ? ? ? ?// 攔截氣球動(dòng)畫播放方法 ?? ? ? ? ? ?HookBallGameStartGame.StartHook(); ? ? ? ? ? ? ?// 這是第二個(gè)攔截方法:攔截氣球MeasureOverride方法 ?? ? ? ? ? ?HookBallgameMeasureOverride.StartHook(); ?? ? ? ?} ?? ?} }

這就完了?運(yùn)行試試:

這提示是指我的新工程Dotnet9HookHigh未成功應(yīng)用高版本Lib.Harmony(2.2.2),亦指主工程Dotnet9Playground未成功識(shí)別加載高版本Lib.Harmony,怎么辦?看我接下來的表演!

5.2. 高低版本的庫分目錄存放

5.2.1. 分析程序輸出目錄

程序輸出目錄只有一個(gè)0Harmony.dll,高低2個(gè)版本應(yīng)該是兩個(gè)庫才對,怎么辦?

5.2.2. 新創(chuàng)建目錄

低版本不變(存在位置依然放輸出目錄的根目錄),為了兼容,我們把高版本改目錄存放,比如:Lib/Lib.Harmony/2.2.2/0Harmony.dll,將庫按目錄結(jié)構(gòu)存放在工程Dotnet9HookHigh中:

  • 并將0Harmony.dll的屬性【復(fù)制到輸出目錄】設(shè)置為【如果較新則復(fù)制】

  • 刪除Dotnet9HookHighLib.Harmony庫的NuGet引用,改為本地引用(原來的配方,瀏覽本地路徑的方式);

這就完了嗎?咋還是報(bào)那個(gè)錯(cuò)?

5.3. 同庫多版本配置

5.3.1. App.config配置多版本

修改Dotnet9PalygroundApp.config文件,添加0Harmony.dll兩個(gè)版本及讀取位置:

xml復(fù)制代碼<?xml version="1.0" encoding="utf-8" ?> <configuration> ?? ?<startup> ? ? ? ? ?<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> ?? ?</startup> ?<runtime> ?<assemblyBinding> ?<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>

再運(yùn)行,還是報(bào)上面的錯(cuò)?啊,我要暈了。。。。

5.3.2. 重點(diǎn):庫的強(qiáng)簽名

上面分目錄、配置文件版本配置目錄也還不夠,主工程還是無法區(qū)分兩個(gè)版本的Lib.Harmony庫,這里涉及.NET 庫強(qiáng)簽名,就是上面App.config配置中的publicKeyToken特性,加上這個(gè)主程序就認(rèn)識(shí)了,關(guān)于強(qiáng)簽名網(wǎng)上找到個(gè)說明[《.Net程序集強(qiáng)簽名詳解》](.Net程序集強(qiáng)簽名詳解_51CTO博客_.net 簽名):

  1. 可以將強(qiáng)簽名的dll注冊到GAC,不同的應(yīng)用程序可以共享同一dll。

  2. 強(qiáng)簽名的庫,或者應(yīng)用程序只能引用強(qiáng)簽名的dll,不能引用未強(qiáng)簽名的dll,但是未強(qiáng)簽名的dll可以引用強(qiáng)簽名的dll。

  3. 強(qiáng)簽名無法保護(hù)源代碼,強(qiáng)簽名的dll是可以被反編譯的。

  4. 強(qiáng)簽名的dll可以防止第三方惡意篡改。

這里,對于1.2.0.1版本的0Harmony.dll庫我們依然不動(dòng),只對2.2.2高版本做強(qiáng)簽名處理,簽名步驟參考[VS2008版本引入第三方dll無強(qiáng)簽名],我們來一起做一遍,這里會(huì)借助Everything軟件搜索使用到的命令程序,建議提前下載。

注意:暫時(shí)不要用最新預(yù)覽版2.3.0-prerelease.2,站長做這個(gè)示例簽名用這個(gè)版本花了2個(gè)晚上沒成功,換成2.2.2就可以,下面的圖也重新錄了,可能該版本有其他依賴的緣故,只是猜測:

  1. 創(chuàng)建一個(gè)新的隨機(jī)密鑰對0Harmony.snk

使用Everything查找一個(gè)sn.exe程序,隨便使用一個(gè),比如:"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\sn.exe",在高版本目錄下生成一個(gè)密鑰對文件0Harmony.snk,命令如下:

shell復(fù)制代碼"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"

  1. 反編譯0Harmony.dll

查找ildasm.exe,比如C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe,執(zhí)行以下命令生成0Harmony.dll的il中間文件:

shell復(fù)制代碼"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"

  1. 重新編譯,附帶強(qiáng)命名參數(shù)

查找ilasm.exe,比如C:\Windows\Microsoft.NET\Framework64\v2.0.50727\ilasm.exe,執(zhí)行以下命令做簽名:

shell復(fù)制代碼"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

  1. 驗(yàn)證簽名信息

shell復(fù)制代碼"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是沒做簽名的:

我們將簽名補(bǔ)充進(jìn)App.Config文件。

注意:因?yàn)槲覀兪褂玫碾S機(jī)密鑰對,所以您生成的簽名和我的肯定不一樣:

再調(diào)試,能正常攔截MeasureOverride方法了,傳入的實(shí)例也能正常顯示BallGame(就這?對,我搞了2個(gè)晚上。。。。):

5.4. 一切就緒,完善最后一個(gè)攔截

代碼如下:

csharp復(fù)制代碼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實(shí)例獲取代碼,下面是高版本 ?? ? ? ?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實(shí)例</param> ?? ?/// <returns></returns> ?? ?public static bool HookMeasureOverride(ref object __instance) ?? ?{ ?? ? ? ?#region 原方法代碼邏輯 ? ? ? ? ?//// 計(jì)算最后一個(gè)元素寬度,不需要關(guān)注為什么這樣寫,只是為了引出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; ?? ? ? ?} ? ? ? ? ?// 注意:關(guān)鍵代碼在這,如果剩余寬度大于0才重新計(jì)算最后一個(gè)子項(xiàng)大小 ?// 這段代碼可能沒什么意義,可按實(shí)際開發(fā)修改 ?? ? ? ?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); ?? ?} }

其中關(guān)鍵代碼是:

csharp復(fù)制代碼// 注意:關(guān)鍵代碼在這,如果剩余寬度大于0才重新計(jì)算最后一個(gè)子項(xiàng)大小 // 這段代碼可能沒什么意義,可按實(shí)際開發(fā)修改 if (remainWidth > 0) { ?? ?var lashShape = GetBalloonShape(lastChild); ?? ?lashShape.Measure(new Size(remainWidth, lashShape.Height)); }

其他代碼就是反射的使用,不再細(xì)說,我們運(yùn)行程序,現(xiàn)在隨便縮放窗體了:

當(dāng)剩余寬度小于0時(shí)跳過計(jì)算最后一個(gè)子項(xiàng)大小

5.4. 小優(yōu)化

上面部分截圖中可能您也看到了0Harmony.ref文件,我們簡單說說。

Git一般是配置成不能上傳可執(zhí)行程序或dll文件的,但多版本dll特殊,部分庫不能直接從NuGet引用,所以本文中的高版本Lib.Harmony庫只能使用自己強(qiáng)簽名版本,我們將dll文件擴(kuò)展名改為“.ref"以允許上傳,他人能正常使用,程序如果需要正常編譯、生成,則給Dotnet9HookHigh工程添加生成前命令行,即生成時(shí)將.ref復(fù)制一份為.dll

shell復(fù)制代碼copy "$(ProjectDir)Lib\Lib.Harmony\2.2.2\0Harmony.ref" "$(ProjectDir)Lib\Lib.Harmony\2.2.2\0Harmony.dll"

6. 總結(jié)

  • 技術(shù)交流加群請?zhí)砑诱鹃L微信號(hào):dotnet9com

  • 文中示例代碼:MultiVersionLibrary

文中案例寫的一般,特別是第二個(gè)陷阱,有興趣可以閱讀游戲相關(guān)代碼,提PR大家一起切磋,把這個(gè)案例寫的更合理、更有趣、更好玩一點(diǎn),能讓第二個(gè)陷阱寫一些好玩的特效,攔截后實(shí)現(xiàn)不同的效果,這才是攔截的樂趣。

本文通過一個(gè)模擬實(shí)際案例,幫助大家應(yīng)用前兩篇文章中涉及的技能(dnSpy調(diào)試第三方庫和Lib.Harmony攔截第三方庫),并且介紹一種支持多個(gè)版本的庫的兼容性解決方案。

通過本文介紹支持多個(gè)版本的庫的兼容性解決方案,讀者可以簡單了解如何反編譯第三方庫,以及如何使用強(qiáng)簽名技術(shù)來保證庫的兼容性(和安全性,本文未展開說,可以閱讀此文[淺談.NET程序集安全簽名](淺談.NET程序集安全簽名 - 知乎 (zhihu.com)))。希望本文提供的案例能幫助讀者更好地理解和應(yīng)用這些技能。

謝謝您閱讀到這,可以關(guān)注【Dotnet9】微信公眾號(hào),大家技術(shù)交流、保持進(jìn)步


模擬.NET應(yīng)用場景,綜合應(yīng)用反編譯、第三方庫調(diào)試、攔截、一庫多版本兼容方案的評(píng)論 (共 條)

分享到微博請遵守國家法律
樟树市| 海丰县| 鸡东县| 虎林市| 石狮市| 黎城县| 武宁县| 渭南市| 长海县| 澄城县| 文昌市| 长岛县| 浦城县| 顺昌县| 承德县| 新丰县| 额敏县| 井陉县| 阿克苏市| 丰镇市| 安庆市| 长武县| 石渠县| 井陉县| 道孚县| 高阳县| 祁东县| 毕节市| 枝江市| 永寿县| 铁岭市| 蕲春县| 扎囊县| 凤山市| 丰县| 祁连县| 德化县| 澎湖县| 奉节县| 新津县| 大姚县|