使用BepInEx修改unity3d游戲(1)——以SaintGearForce(圣齒輪部隊(duì))為例初識(shí)BepInEx

前言說明
本篇除判斷游戲類型一節(jié)中其余內(nèi)容演示均圍繞
[RJ01002988]セイントギアフォース(中文名:圣齒輪部隊(duì))(英文名:SaintGearForce)
本文建立在您已經(jīng)分析清楚該游戲的修改位置的情況下,側(cè)重點(diǎn)是BepInEx的使用
如果著重于分析游戲那這一系列文章可能就變成《如何修改SaintGearForce》了
當(dāng)前互聯(lián)網(wǎng)中修改unity游戲很多都是直接修改Assembly-CSharp.dll或者是GameAssembly.dll
在游戲更新后重復(fù)工作量較大,如果使用BepInEx可以顯著降低工作量

接下來將會(huì)以幾個(gè)常見的修改方式,作為第一篇的演示例子,完整代碼位于文章末尾
由于bilibili編輯限制,相關(guān)勘誤會(huì)在頂置評(píng)論中發(fā)布

判斷游戲類型
判斷游戲架構(gòu)
這個(gè)沒什么好說的,拿工具掃一下就知道了

如圖所示,很清楚的寫明了是amd64
判斷runtime
首先你要知道,你要?jiǎng)邮值挠螒蚓烤故鞘裁磖untime
目前unity主要有il2cpp及mono兩種,其他wasm之類的用的比較少的不在BepInEx中介紹與演示


如圖所示,以上圖片有帶有MonoBleedingEdge可以判斷為mono運(yùn)行時(shí),而帶有GameAssembly.dll是il2cpp運(yùn)行時(shí)
這個(gè)方法能大致判斷目標(biāo)應(yīng)用的runtime,部分應(yīng)用可能會(huì)隱藏相關(guān)特征
本篇我們以游戲SaintGearForce為例
除了MonoBleedingEdge
?我們發(fā)現(xiàn)SaintGearForce_Data\Managed\Assembly-CSharp.dll
因此這個(gè)想必就是mono
判斷unity版本
其實(shí)這個(gè)很好判斷的,用文本編輯器打開游戲Data目錄下的globalgamemanagers

可以很清晰的看見版本2021.3.11

安裝BepInEx
首先我們?nèi)ithub下載壓縮包

如圖所示,有幾個(gè)不同的zip,我們本次練習(xí)的目標(biāo)是amd64的,因此選擇BepInEx_x64_5.4.21.0.zip下載
下載完成后解壓里面的文件到游戲目錄

然后,打開游戲后直接關(guān)閉游戲,讓BepInEx自行生成相關(guān)文件
隨后編輯BepInEx的配置,開啟調(diào)試日志窗口

保存配置后,再次打開游戲就可以見到日志窗口了


編寫Plugin
創(chuàng)建plugin
首先我們要加載模板
然后使用模板創(chuàng)建插件,注意這里使用的是bepinex5
,因?yàn)橛螒虿皇莍l2cpp沒必要沖pre-release
這里的-T是插件將要使用的目標(biāo)運(yùn)行時(shí),-U是游戲的unity版本

創(chuàng)建完后我們cd至創(chuàng)建的插件目錄下,把使用如下命令把Harmony安裝上
添加游戲本體至依賴
如圖所示,復(fù)制游戲中的Assembly-CSharp.dll至插件目錄下,并添加至依賴

這個(gè)步驟我覺得應(yīng)該不用細(xì)說吧,就復(fù)制個(gè)文件然后添加依賴
編譯測(cè)試

正常編譯插件即可,圖中的代碼是模板生成的,自帶一個(gè)日志,可以很方便的讓我們看出插件加載沒加載。

把構(gòu)建的插件,放到BepInEx\plugins目錄下啟動(dòng)游戲

可以看見,插件被加載成功,說明我們的環(huán)境并沒有什么問題。
日志
這里不得不說一句,日志是一個(gè)很重要的東西,它關(guān)系著你能不能舒適的編寫插件。
此處我們演示新增日志tag及開啟HarmonyFileLog

如圖所示,我們新增并添加了一個(gè)叫g(shù)log的ManualLogSource,以及設(shè)置了HarmonyFileLog.Enabled

在使用自行添加的gLog后,日志窗口成功的輸出了glog標(biāo)簽的日志

Harmony
為什么這一節(jié)叫Harmony,因?yàn)檫@一節(jié)已經(jīng)是Harmony的部分了。
CreateAndPatchAll
Harmony支持多種創(chuàng)建patch的方法,這里圖省事,先演示CreateAndPatchAll

創(chuàng)建一個(gè)class,然后使用Harmony.CreateAndPatchAll,該class就會(huì)被應(yīng)用了
HarmonyPrefix
這個(gè)跟Xposed的beforeHookedMethod很相似,也基本上就是這樣用的。
它的作用就是在方法執(zhí)行前進(jìn)行一些操作,包括但不限于修改入?yún)ⅰ?/p>
本次以修改該游戲的SP及EP為例進(jìn)行演示


從上圖我們可以看出,消費(fèi)SP及EP的method有一個(gè)叫consume_amount類型為int的入?yún)?/p>
那么我們使用如上代碼,將入?yún)⒋蛴〔⒃O(shè)置為1
編譯插件后,進(jìn)入游戲進(jìn)行測(cè)試


如圖所示,使用SP及EP的技能均只花費(fèi)了1點(diǎn)
HarmonyPostfix
這個(gè)跟Xposed的afterHookedMethod很相似,都是在method執(zhí)行完后進(jìn)行某些操作,包括但不限于修改返回值。

本次以修改游戲加點(diǎn)需要的點(diǎn)數(shù)為例。


如圖所示,這里我們希望修改statusGrowUpData.consume_sp及growUpSkillData.amount

此處的__result是該框架的一個(gè)關(guān)鍵字,在執(zhí)行完方法后,演示代碼會(huì)把consume_sp以及amount修改為1

如圖所示,技能消耗的SP現(xiàn)在是1了
HarmonyTranspiler
這玩意不推薦使用。通用性極差,但某些場(chǎng)景下不得不使用它。
這里我們虛構(gòu)一個(gè)場(chǎng)景,就是戰(zhàn)斗時(shí)HP血量為0時(shí)不觸發(fā)敗北邏輯。
先上一段戰(zhàn)斗傷害的邏輯。

讓圖所示,當(dāng)HP小于等于0時(shí),敗北邏輯觸發(fā)
雖然我們可以直接把damage_amount直接歸0免傷立于不敗之地
但為了演示HarmonyTranspiler我們通過消除敗北邏輯實(shí)現(xiàn)不敗

打開該方法的IL,如圖邏輯所示,我們只需在63處替換為ret即可

如上,演示代碼會(huì)打印codes[63]的opcode,并替換為ret

如圖所示,HP歸零時(shí)不會(huì)觸發(fā)敗北邏輯,實(shí)現(xiàn)立于不敗之地的需求。

結(jié)束語(yǔ)
本文使用BepInEx實(shí)現(xiàn)了幾個(gè)修改Assembly-CSharp.dll實(shí)現(xiàn)的需求
但沒有實(shí)現(xiàn)通過配置或者是插入U(xiǎn)I等方式控制功能的開關(guān),存在某些必?cái)£P(guān)卡會(huì)卡關(guān)的問題
想必看完之后你還會(huì)對(duì)?[HarmonyPatch(typeof(PlayerStatusManeger), nameof(PlayerStatusManeger.Damage_HP))]
?之類的地方感到困惑
在后續(xù)文章中,將會(huì)演示通過配置及顯式UI開關(guān)的方式控制patch的行為,以及進(jìn)一步介紹BepInEx與Harmony

代碼