標籤:des winform io 使用 for sp 資料 on 問題
前段時間在研究某遊戲輔助,老外出品,支援七種語言,可這輔助相關的外掛程式卻少有中文,因為作者都是老外,並且他們不願意添加中文。有一些沒有加密的外掛程式就被善良的國內使用者使用工具軟體手工漢化了,但是經過混淆加密的外掛程式就比較困難了,一是需要解密,二是外掛程式數量多更新快,最後弄得只好放棄。有一天,一位使用者問我,能不能做一個補丁程式,不去解密也不去修改來源程式,只是在視窗顯示的時候把文字漢化,他的意思就是hook表單顯示的過程,顯然,這是個很不錯的想法。
很多年前,曾經流行過一個名叫“南極星”的軟體,後來又出了個“金山快譯”,這兩個都是可以把程式介面進行本地化的外掛,而我要做的卻是補丁,也就是內掛,雖然形式不同,但原理上總是相通的吧。抱著這種想法,開始了新的代碼之旅,但沒過多久我就徹底放棄了,主要原因是只支援window視窗,對WPF無效,因為WPF表單中的內容不是有效控制項,而且在“自動”方面上更是難以為繼。
我的關子已經賣得夠多了吧,你知道的,最終我還是達到了目的。
.NET的程式只有在在.NET的環境中才會如漁得水般的靈活。對於普通winform和WPF表單,在視窗建構函式運行之後,我們只需要遍曆視窗類別的所有私人欄位就可以得到全部的控制項了,直接修改其對應的文字,這就是漢化的全過程!那麼問題來了,一如何拿到視窗的執行個體引用,二如何在視窗顯示以前修改文字,三對於使用者自訂的表單控制項如何處理。下面一一解答。
先來說第二個問題,是通過MethodReplace的不正當手段實現的,原理就是在CLR中修改方法MethodDesc中代碼地址來實現的,在調用源方法的方法還沒有被Jit前用新方法的地址來代替源方法,使源方法在被調用時整個方法體轉向到新方法中(具體過程請Google)。因為顯示視窗的方法都是由.NET提供現成編譯好的本地代碼,是無法替換地址的,所以最好的切入點就是視窗的建構函式。通過當前Domain的AssemblyLoad事件,可以輕易的在程式集被裝載時拿到控制權,然後遍曆程式集中所有的視窗類別,然後對它們的建構函式進行地址轉向,這樣就搶在了建立視窗的方法被Jit之前。想一想,所有視窗中的控制項都需要在建構函式中建立,這通常是通過調用一個名為InitializeComponent的方法,但是這個方法名是不靠譜的,因為它會被混淆,所以仍然要在新方法中使用Opcodes.Calli欄位以正確的簽名來調用構造方法的源地址(具體請參看MSDN)來完成表單內控制項的初始化過程,這樣一來問題就解決了,直接遍曆修改即可。等等,怎麼修改的內容不全面,還有遺漏。好吧,我忘了有些列表的內容是在視窗第一次被顯示時被初始化的,這個問題是VS的IDE造成的,弄得大家都喜歡把使用者初始化的資料放在視窗的Load事件中。如何應對?很簡單,我把漢化的過程也添加到Load事件中,這樣就讓漢化的過程發生在視窗原始Load事件之後了。
在第二個問題完美解決時,第一個問題也被解決了,視窗的建構函式是執行個體方法,執行個體方法的第一個參數永遠是this,因此,添加事件、反射修改私人欄位都得以實現,還可以實現更多本地化以外的事情……
第三個問題相對就簡單多了,唯獨的只是繁雜。我們知道絕大多數近件的文字都放在名為Text的屬性之中,直接修改或是反射修改就可以,但有少數控仍需要特殊處理,例如:ToolTip控制項的Caption屬性,菜單的層級結構,列表近件中的內容集合,PropertyGrid中的特性名稱。再有,就是一些自訂風格的視窗類別,它們控制項的文字可能會放在名為Text、Title、SubTitle、Caption、Value1、Value2等等屬性中,利用反射遍曆查詢一下就可以了,當然還要為這些類型和欄位做緩衝的,這會提升效率。
後來,功能雖然實現了,但它並不好用,使用者體驗很差勁,主要是自動翻譯的效果非常不理想,但我實在是無法解決這種複雜的專業問題的,所以,果斷放棄自動翻譯,轉為輸出資料行表,然後再有目的的翻譯。
在CLR中本地化正在啟動並執行.NET視窗