之前的方法及其局限
問題背景和最初的嘗試見這里海雪。最開始的想法比較簡單声怔,只想著利用 PostprocessBuild
這個事件,來對已經(jīng)準(zhǔn)備好的本地工程文件(iOS 或 Android)中的 .NET 程序集進(jìn)行注入。但是栓霜,這樣做限制很多蜕青。
首先苟蹈,無法對 IL2CPP 作為 Scripting Backend 的情況進(jìn)行注入。因為觸發(fā)這個事件時右核,本地工程文件中沒有 .NET 程序集慧脱,只有 C++ 代碼,無法用 Cecil 進(jìn)行注入贺喝。
第二菱鸥,Android 平臺,用 Mono2x 作為 Scripting Backend 的情況下躏鱼,也需要打包為 Android Studio Project 才能使用氮采。對于直接打包成 apk 的情況,無法簡單的進(jìn)行注入(除非使用解包染苛、注入鹊漠、重新簽名打包的方法,比較麻煩)茶行。
第三躯概,iOS 平臺,即使用 Mono2x 作為 Scripting Backend畔师,也無法成功娶靡。這是因為在 iOS 平臺打包需要先進(jìn)行一個叫 AOT Cross Compiling 的步驟,對所有的程序集生成對應(yīng)的 .dll.s 文件看锉。這些文件包含的信息會在運行時被校驗姿锭,如果我篡改了程序集,而沒有理會 .dll.s 文件伯铣,在運行時會報錯艾凯。錯誤信息類似
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 腳本名稱,兩組星號表示兩個不同值懂傀。這錯誤最終導(dǎo)致腳本加載失敗趾诗,無法運行游戲。與錯誤信息描述不同,我并沒有在出問題的腳本上寫任何條件編譯的代碼恃泪。要想解決這個問題郑兴,估計需要篡改 .dll.s 文件才可以,仍然是很不經(jīng)濟(jì)的贝乎。
篡改編譯器的方法
接下來一個辦法情连,就是對 Unity 的 C# 編譯器 mcs.exe 進(jìn)行篡改。我沒有深入實驗览效,因為幾個簡單的實驗就耗費了一天多的時間却舀。我主要嘗試了兩種方法,當(dāng)然锤灿,都沒成功挽拔。
方法一,將原 mcs.exe 重命名(如 mcs1.exe)但校,而后自己寫一個 .NET 控制臺應(yīng)用程序螃诅,占據(jù)原來 mcs.exe 的位置,在其中用 System.Diagnostic.Process 類來啟動 mcs1.exe状囱。這個過程中术裸,我對 Process 對象的一些配置,如環(huán)境變量(EnvironmentVariables 屬性)亭枷、輸入輸出重定向(RedirectStandardXXX 屬性)進(jìn)行了多種排列組合袭艺,仍無法正確調(diào)用 mcs1.exe,就更不要說調(diào)用之后的事情了叨粘。
方法二猾编,直接在 mcs.exe 中注入代碼。因為 mcs.exe 也是一個 .NET 應(yīng)用程序宣鄙,并且看上去未經(jīng)混淆袍镀,所以直接注入是可行的默蚌。即冻晤,「把向游戲程序集中注入代碼的代碼,注入到編譯器中绸吸”腔。」這樣做主要的問題,是 mcs.exe 的輸出目錄是臨時文件夾锦茁,無法保證其中有我們依賴的(如注入后寫入程序集時攘轩,需要用 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 提醒了我笨篷。在這個事件回調(diào)中處理 DLL瞳秽,理論上在任何平臺、任何 Scripting Backend 上都可以有效注入率翅。實現(xiàn)過程中有幾個要點需要注意:
事件
OnPostprocessScene
對應(yīng) Build Settings 中指定打包的場景個數(shù)练俐,所以它可能執(zhí)行多次,故而需要防止重復(fù)冕臭。除了上述 UnityDllInjector 中提供的方法腺晾,還可以直接把注入標(biāo)記寫入你的目標(biāo)程序集。但值得注意的是辜贵,新增一個用于標(biāo)記的空類在 iOS + Mono2x 下又是不好用的悯蝉,猜測還和 AOT 交叉編譯有關(guān)。保險的做法之一念颈,是在游戲代碼中保留幾個bool
常量泉粉,值為false
,注入前檢查相應(yīng)的值榴芳,如果為true
則跳過嗡靡,否則注入。注入完成后窟感,將相應(yīng)的bool
常量篡改為true
即可讨彼。游戲腳本對應(yīng)的程序集,在注入時一定處于和 Assets 同級的 Library 下的 ScriptAssemblies 文件夾下柿祈,但要注意你依賴的 Unity 程序集哈误。我使用 UnityDllInjector 提供的方法,依然不能保證獲取到需要的程序集躏嚎。最終我采用的方法是蜜自,使用
EditorApplication.applicationContentsPath
獲取 Unity 安裝目錄,在其中 Data/Managed 目錄里尋找必要的程序集卢佣。
目前我測試了 Android + Mono/IL2CPP 和 iOS + IL2CPP重荠,都沒有問題。iOS + Mono2x 可能由于我們項目本身的一些問題虚茶,在 Xcode 鏈接階段有一些問題戈鲁。
舊文搬運,2017-06-15 首發(fā)于博客園嘹叫。