使用 Mono.Cecil 輔助 Unity3D 手游進(jìn)行性能測試(續(xù))

之前的方法及其局限

問題背景和最初的嘗試見這里。最開始的想法比較簡單,只想著利用 PostprocessBuild 這個(gè)事件,來對已經(jīng)準(zhǔn)備好的本地工程文件(iOS 或 Android)中的 .NET 程序集進(jìn)行注入。但是,這樣做限制很多。

首先,無法對 IL2CPP 作為 Scripting Backend 的情況進(jìn)行注入。因?yàn)橛|發(fā)這個(gè)事件時(shí),本地工程文件中沒有 .NET 程序集,只有 C++ 代碼,無法用 Cecil 進(jìn)行注入。

第二,Android 平臺,用 Mono2x 作為 Scripting Backend 的情況下,也需要打包為 Android Studio Project 才能使用。對于直接打包成 apk 的情況,無法簡單的進(jìn)行注入(除非使用解包、注入、重新簽名打包的方法,比較麻煩)。

第三,iOS 平臺,即使用 Mono2x 作為 Scripting Backend,也無法成功。這是因?yàn)樵?iOS 平臺打包需要先進(jìn)行一個(gè)叫 AOT Cross Compiling 的步驟,對所有的程序集生成對應(yīng)的 .dll.s 文件。這些文件包含的信息會在運(yùn)行時(shí)被校驗(yàn),如果我篡改了程序集,而沒有理會 .dll.s 文件,在運(yùn)行時(shí)會報(bào)錯。錯誤信息類似

A script behaviour (probably XXX?) has a different seralization layout when loading. (Read ** bytes but expected ** bytes)

Did you #ifdef UNITY_EDITOR a section of your serialized properties in any of your scripts?

其中 XXX 是 .NET 腳本名稱,兩組星號表示兩個(gè)不同值。這錯誤最終導(dǎo)致腳本加載失敗,無法運(yùn)行游戲。與錯誤信息描述不同,我并沒有在出問題的腳本上寫任何條件編譯的代碼。要想解決這個(gè)問題,估計(jì)需要篡改 .dll.s 文件才可以,仍然是很不經(jīng)濟(jì)的。

篡改編譯器的方法

接下來一個(gè)辦法,就是對 Unity 的 C# 編譯器 mcs.exe 進(jìn)行篡改。我沒有深入實(shí)驗(yàn),因?yàn)閹讉€(gè)簡單的實(shí)驗(yàn)就耗費(fèi)了一天多的時(shí)間。我主要嘗試了兩種方法,當(dāng)然,都沒成功。

方法一,將原 mcs.exe 重命名(如 mcs1.exe),而后自己寫一個(gè) .NET 控制臺應(yīng)用程序,占據(jù)原來 mcs.exe 的位置,在其中用 System.Diagnostic.Process 類來啟動 mcs1.exe。這個(gè)過程中,我對 Process 對象的一些配置,如環(huán)境變量(EnvironmentVariables 屬性)、輸入輸出重定向(RedirectStandardXXX 屬性)進(jìn)行了多種排列組合,仍無法正確調(diào)用 mcs1.exe,就更不要說調(diào)用之后的事情了。

方法二,直接在 mcs.exe 中注入代碼。因?yàn)?mcs.exe 也是一個(gè) .NET 應(yīng)用程序,并且看上去未經(jīng)混淆,所以直接注入是可行的。即,「把向游戲程序集中注入代碼的代碼,注入到編譯器中?!惯@樣做主要的問題,是 mcs.exe 的輸出目錄是臨時(shí)文件夾,無法保證其中有我們依賴的(如注入后寫入程序集時(shí),需要用 Mono.Cecil 的 DefaultAssemblyResolver 進(jìn)行解析的)程序集。

通過 OnPostprocessScene 回調(diào)事件來進(jìn)行注入

Unity 雖然沒有在執(zhí)行 mcs.exe 和后續(xù)步驟(IL2CPP、Android 打包 apk、iOS 上的 AOT 交叉編譯等)之間提供回調(diào),但是回調(diào)事件 OnPostprocessScene 目前是確保在它們之間至少觸發(fā)一次的。多虧 https://github.com/rayosu/UnityDllInjector 提醒了我。在這個(gè)事件回調(diào)中處理 DLL,理論上在任何平臺、任何 Scripting Backend 上都可以有效注入。實(shí)現(xiàn)過程中有幾個(gè)要點(diǎn)需要注意:

  • 事件 OnPostprocessScene 對應(yīng) Build Settings 中指定打包的場景個(gè)數(shù),所以它可能執(zhí)行多次,故而需要防止重復(fù)。除了上述 UnityDllInjector 中提供的方法,還可以直接把注入標(biāo)記寫入你的目標(biāo)程序集。但值得注意的是,新增一個(gè)用于標(biāo)記的空類在 iOS + Mono2x 下又是不好用的,猜測還和 AOT 交叉編譯有關(guān)。保險(xiǎn)的做法之一,是在游戲代碼中保留幾個(gè) bool 常量,值為 false,注入前檢查相應(yīng)的值,如果為 true 則跳過,否則注入。注入完成后,將相應(yīng)的 bool 常量篡改為 true 即可。

  • 游戲腳本對應(yīng)的程序集,在注入時(shí)一定處于和 Assets 同級的 Library 下的 ScriptAssemblies 文件夾下,但要注意你依賴的 Unity 程序集。我使用 UnityDllInjector 提供的方法,依然不能保證獲取到需要的程序集。最終我采用的方法是,使用 EditorApplication.applicationContentsPath 獲取 Unity 安裝目錄,在其中 Data/Managed 目錄里尋找必要的程序集。

目前我測試了 Android + Mono/IL2CPP 和 iOS + IL2CPP,都沒有問題。iOS + Mono2x 可能由于我們項(xiàng)目本身的一些問題,在 Xcode 鏈接階段有一些問題。


舊文搬運(yùn),2017-06-15 首發(fā)于博客園。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容