Make your own free website on Tripod.com

還我廬山真面目

作者: Ronnier && 飛鷹

前 言

    談起軟體字型的修改,大家一定會聯想到漢化新世紀梁利鋒,他所寫的文章解決了我們中文化中碰到的一些難題,開拓了中文化界的新天地,在此,我們向他表示感謝。今天,我們寫這篇文章目的只是為了讓大家對軟體字型的修改有更深、更進一步的瞭解,並帶領大家分析、理解反編譯代碼,找字型修改的突破口,詳細講解修改方法。這篇文章從對沈曄駿中文化的 WinZip Self-Extractor 2.2 Build 4003 這個軟體字型變小的問題出發,先提出一種危險的字型修改方法,從而引深出三種不同的安全正確的字型修改方法,並從中引發出很多中文化的技巧。文章的第一部分是由飛鷹編寫,第二部分是由Ronnier編寫,第三部分是由飛鷹和Ronnier共同編寫完成。對於這篇文章我們力求寫的清楚、易讀,所以,文章的篇幅就多了些,還請大家諒解。

    最後,衷心希望這篇文章對您能有所幫助。

第一篇: 誤入歧途

    今天,線線上上下載了沈曄駿中文化的 WinZip Self-Extractor 2.2 Build 4003,下載安裝後發現該中文化版中有幾處的字型非常難看 (如圖一所示) ,沒有改成我們希望看見的「新細明體,9號」,一時心血來潮想幫沈曄駿兄修改一下這個問題。

    先用FI測試可知該軟體是用 MS Visual C++ 編寫的,後用 exescope 開啟看它裡面的資源 (如圖二所示) 。大家可以看到雖然該軟體是用 MS Visual C++ 編寫,但裡面卻有 RCData 資源,奇怪 ! 而且查看Import表中的 GID32.DLL 內確實有 CreateFontIndirectA 函數存在,但在 exescope 中查看該軟體字型設定有問題的視窗,在這裡顯示卻是正常的 (如圖三所示) 。所以初步猜想可能軟體在執行時動態的用CreateFontIndirectA函數改變了我們原先設定的字型及大小,而且該軟體內有RCData資源,我就想修改方法可能與DEPHI程式字型的修改 (請參看梁利鋒兄寫的《Delphi 字型修改一例 》) 類似。

    大概瞭解情況之後,就可以動手修改了 ! 第一步是把它調入uedit中尋找是否有「MS Sans Serif」字型存在(注意: 這裡尋找的是ASCII字串,而不是UNICODE字串,一般修改這種資源尋找的都是ASCII字串) ,每找到一處時就改為「System」儲存返回到軟體中看效果,如果沒有變化就把這一處改回原狀,後再修改下一處,直到軟體的字型起了變化之後,就可以在uedit中記下這個偏移地址為進行下一步打下基礎。在該中文化版中,當我修改了開頭的兩處字型後軟體的界面就起了變化 (如圖四所示) ,這時我們就不必再往後尋找了,因為我們已經找到了問題的根源。可是,像這樣修改後字型變的有一些大、不美觀,如果您怕麻煩的話,就這樣修改好了,不必再把這篇文章往後看下去了;如果您是中文化DIY的話,我希望您能繼續往下看。 (廢話 ! ) 為了把它改成我們希望看到的「新細明體,9號」,先把這兩處的字型改回原狀「MS Sans Serif」,後記下這兩處的偏移地址,第1處是2540cH,第2處是2541cH (如圖五所示) ,這些地址在第二步中將會被用到。

     在第二步中我們將要用到軟體W32DASM,對該中文化版進行反編譯。在繼續修改之前,必須先知道所需字串的RVA值 (即MS Sans Serif的相對虛擬地址) ,這就要用到上面我們記下的偏移地址來計算RVA值了。實際操作中我們不必套用公式來算這個值,可以讓梁利鋒兄編寫的「點睛偏移量轉換器」這個軟體來幫忙,目前該軟體最新的版本是0.94B。關於該軟體的具體使用方法,大家可以參看大宇兄寫的《RVA字串乾坤大挪移》這篇文章,這裡我只簡單介紹: 先點擊「...」瀏覽按鈕,選擇需要載入的檔案-->在「實偏移」純文字框中輸入字串的偏移地址 (即MS Sans Serif的偏移地址) --> 之後,字串的RVA值可以從「虛偏移」純文字框中看到 (如圖六所示) 。在這裡我先計算出了第2處的RAV值是426c1cH,因為1、2處的字型相隔非常近 (如圖五所示) ,所以只需知道第2處的RAV值後,反編譯出來尋找到這個值再往上找幾行就是第1處的RAV值了。啟動W32DASM (我用的是W32DASM 8.93 中文化版) ,在「反編譯」萊單中選擇「開啟檔案反編譯」選項來載入要反編譯的檔案,這裡當然是載入 WinZip Self-Extractor 2.2 Build 4003 中文化版了,不用多說 ! 之後,在「尋找」萊單中選擇「尋找純文字」選項,在「尋找內容」純文字框中輸入426c1c (注意: 不要把它後面的'H'也輸進去,'H'只是表示該值是16進制數) 按「尋找下一個」按鈕進行尋找,在該中文化版中只尋找到一個,那麼我們百分之百的肯定就是它,找對了 ! 先往上移幾行,就可以看到有「MS Sans Serif」的字樣,後往下移幾行,就可以看到有「GDI32.CreateFontIndirectA」的字樣,說明該軟體的字型設定就在這裡,這裡就是我們要修改的地方了。根據上面的'加粗'字型段的說明,再往上移幾行,很容易就找到需要修改的另一處地方。要修改的這兩處的具體代碼摘抄如下:

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                  |
:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0
:0040D83E 59 pop ecx
:0040D83F 59 pop ecx
:0040D840 6A5A push 0000005A

:0040D842 FF75C0 push [ebp-40]

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                  |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax

* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                  |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

----------------------------------------------------------------------------------------------

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                  |
:0040D880 681C6C4200 push 00426C1C
:0040D885 8D45E0 lea eax, dword ptr [ebp-20]
:0040D888 50 push eax
:0040D889 E8328B0000 call 004163C0
:0040D88E 59 pop ecx
:0040D88F 59 pop ecx
:0040D890 6A5A push 0000005A
:0040D892 FF75C0 push [ebp-40]

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                  |
:0040D895 FF159C204200 Call dword ptr [0042209C]
:0040D89B C1E003 shl eax, 03
:0040D89E 99 cdq
:0040D89F 6A48 push 00000048
:0040D8A1 59 pop ecx
:0040D8A2 F7F9 idiv ecx
:0040D8A4 6BC0FF imul eax, FFFFFFFF
:0040D8A7 8945C4 mov dword ptr [ebp-3C], eax

* Possible Reference to Dialog: DialogID_0190
                                  |
:0040D8AA C745D490010000 mov [ebp-2C], 00000190
:0040D8B1 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D8B4 50 push eax

* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                  |
:0040D8B5 FF15A0204200 Call dword ptr [004220A0]
:0040D8BB A38CDE4300 mov dword ptr [0043DE8C], eax
:0040D8C0 FF75C0 push [ebp-40]

    其中,藍色字表示找到的第1處,紅色字表示找到的第2處,綠色字是用來判斷是否找對地方的標誌,經過我的分析、測試,發現只要把紫色字表示的地方8D45C450修改成為68909090即可,68表示 Push 壓入棧,90表示 Nop 空。雖然,68 和 6A 同時都是 Push 指令 (68 壓入的是四字節整數,6A 壓入的是單字節整數) ,但這裡不能改為6A909090,如果這樣改,字型會比「新細明體,9號」大一倍,不知道為什麼。關於修改方法就是把它調入到uedit中尋找C745D4900100008D45C450,找到後修改為C745D49001000068909090 (如圖七所示) ,修改完成後執行該中文化版一看效果,成功 ! 已經有一部分難看的字型被修改成「新細明體,9號」了,這裡我只是修改了在反編譯中找到的符合條件的第2處,至於另一處我想大家也應該知道如何修改了,就是在uedit中尋找C745D4BC0200008D45C450,找到後修改為C745D49001000068909090即可。全改完了,執行軟體製作一個自解壓安裝程式測試一下軟體的執行狀況,一切正常,收工,休息了 ! 修改後該中文化版軟體的界面如圖八所示

    為了進一步研究、測試該中文化版是否修改的成功,我又把它調入到了exescope中修改以前存在字型設定問題的視窗,並把該視窗中字型及大小改變成「楷體,8號」,結果顯示如圖九所示,大家可以看到這裡字型及大小已經按照我們所需要的設定改變了,這說明CreateFontIndirectA函數已經不能動態的改變我們原先設定的字型及大小了。

    我本想這樣修改就完事了,但事實並非如此,經過同行Ronnier兄對我的修改方法的檢查,發現我這種修改方法是極其危險的。Ronnier兄說: 「飛鷹兄之所以這樣修改能成功,也是很僥倖的,這樣修改大有可能會造成軟體無法執行,大多數情況會非法操作,也有可能不非法操作,但會有潛在問題。」哦 ! 看來我飛鷹的編譯功力還差點,誤入了邪道,下面還是請Ronnier兄幫忙拉兄弟一把,讓我能「改邪歸正」。

第二篇: 改邪歸正

    首先,飛鷹兄找到的字型調用位置,字型名稱的位置等等完全正確,但是他對機器碼的意義理解完全錯了。機器碼在執行的時候是非常嚴格的,比如飛鷹兄說的 68 和 6A 代表 push 指令沒錯,但單單 68 和 6A 什麼也不是。只有 68XXXXXXXX 和 6AXX 才是完整的 push 指令,也就是說,68 必須和之後的四個字節 (32 位模式下,16 位則是兩個字節) 一起,6A 必須和之後的一個字節一起構成 push 指令。當指令譯碼器讀到 68 時,它就自動把其後的四個字節數 (32 位模式下,16 位則是兩個字節數) 做為源操作數壓入堆疊了,而不會再去讀那四個字節是否是指令。比如: 6890505859 執行起來是:
        push 59585090
        而不是這樣的:
        push
        nop
        push eax
        pop eax
        pop ecx

    所以,飛鷹兄所改的68909090這樣的語句,從本質上說就是錯的。但是,飛鷹兄說這樣改成功了,為什麼會如此呢 ? 要回到這個軟體本身來看了。正像他所說的那樣,這個對話框本身就有字型定義「新細明體,9pt」了,而這裡的 CreateFontIndirectA 函數確實是想忽略字型定義而動態的再給它改成別的字型。剛才說過了,68 這個 push 語句是由五個字節組成的。也就是說,68909090 還不是完整的語句,必須跟上後面的 FF 才是完整的 push FF909090 這樣一條指令。但是這個 FF 是原來 FF15A0204200 也就是 call GDI32.CreateFontIndirectA 指令的一部分,那麼,整個函數調用已經被完全破壞了,而其後的指令也會隨之改變。由於不再調用 GDI32.CreateFontIndirectA 函數了,所以此處的字型就保持了原來對話框中定義的「新細明體,9pt」。

    為了驗證我說的,我們比較一下用 W32Dsm 反編譯這樣修改前後的檔案,注意用「——————」包括起來的部分:

這是原來的檔案:

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
——————————————————————————————————
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
——————————————————————————————————
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax :0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

這是改過的檔案:

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
——————————————————————————————————
:0040D861 68909090FF push FF909090
:0040D866 15A0204200 adc eax, 004220A0
——————————————————————————————————
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

    看到了您,原來的裝入地址、壓棧和函數調用指令變成了一個壓棧和加法指令。可以說,在這個軟體裡這麼做沒有問題那是很僥倖的。這麼改是及其危險的,因為我們沒法預料隨後的多少條指令會受牽連。一般來說這麼改後程式是無法執行的,大多數情況會非法操作,也有可能不非法操作,但會有潛在問題。

     那麼,這個軟體的字型要怎麼改才是正確的呢 ? 當然,用梁利鋒兄的《野蠻人戰記》中我提出來的把整個函數調用脫出來的方法是絕對可以的,因為那是通法。不過對於這個軟體不必要這麼麻煩,因為它有它自己的特點。有兩種簡單的方法可以做到:

    第一種,很簡單,剛才說過了,鑒於這個軟體的情況比較特殊,它原來有字型定義,只是調用了 CreateFontIndirectA 函數想給它改掉,所以,索性把整個 50FF15A0204200 改成 90909090909090,把整個函數調用廢了就成。這樣修改後該中文化版軟體的界面如圖八所示。對於別的軟體這種方法好不好用就不敢說了,想過去對於原先對話框就有了字型定義的軟體也許都可以這麼試試。

    第二種應該說是比較「正解」的方法您,有興趣的朋友可以看看。因為這個軟體裝入 LogFont 結構的過程比較清晰,我們完全可以讓 CreateFontIndirectA 去建立一個「新細明體,9pt」出來。

先看看用 FontKey 監視出的 GDI32.CreateFontIndirectA 調用情況:

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF6_00000000_00000000_00000000_000002BC_00_00_00_00_00_00_00_00:"MS Sans Serif")
CreateFontIndirectA returns: A20

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF6_00000000_00000000_00000000_00000190_00_00_00_00_00_00_00_00:"MS Sans Serif")
CreateFontIndirectA returns: A40

再來看看檔案裡這兩個 GDI32.CreateFontIndirectA 的調用過程:

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                 |
:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0
:0040D83E 59 pop ecx
:0040D83F 59 pop ecx
:0040D840 6A5A push 0000005A
:0040D842 FF75C0 push [ebp-40]
* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C
* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                 |
:0040D880 681C6C4200 push 00426C1C
:0040D885 8D45E0 lea eax, dword ptr [ebp-20]
:0040D888 50 push eax
:0040D889 E8328B0000 call 004163C0
:0040D88E 59 pop ecx
:0040D88F 59 pop ecx
:0040D890 6A5A push 0000005A
:0040D892 FF75C0 push [ebp-40]
* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D895 FF159C204200 Call dword ptr [0042209C]
:0040D89B C1E003 shl eax, 03
:0040D89E 99 cdq
:0040D89F 6A48 push 00000048
:0040D8A1 59 pop ecx
:0040D8A2 F7F9 idiv ecx
:0040D8A4 6BC0FF imul eax, FFFFFFFF
:0040D8A7 8945C4 mov dword ptr [ebp-3C], eax
* Possible Reference to Dialog: DialogID_0190
                                 |
:0040D8AA C745D490010000 mov [ebp-2C], 00000190
:0040D8B1 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D8B4 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D8B5 FF15A0204200 Call dword ptr [004220A0]
:0040D8BB A38CDE4300 mov dword ptr [0043DE8C], eax
:0040D8C0 FF75C0 push [ebp-40]

    很明顯,CreateFontIndirectA 的 LogFont 結構兩次調用都是存放在 EBP-3C 中的值所指向的記憶體單元中,也就是 FontKey 監視出來的 006BFD0C 這裡。我們仔細的讀一下這段程式,可以發現字型名稱 MS Sans Serif 是由這幾條指令寫入 LogFont 結構中的 (一個 LogFont 結構最低位 4 個字節是字型大小,往高位間隔 28 個字節存放的是字型名稱,此例中 3C-20=1C,就是十進制的 28。)

:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0

    這裡,把字型名稱所在地址,LogFont 結構中的對應地址做為參數傳給了一個幾程式,讓這個幾程式去完成裝入字型名稱的工作。所以,把飛鷹兄找到的兩個 MS Sans Serif 改成新細明體,字型名稱就改過來了。然後,接著往下看,字型大小是由這幾句定義的:

:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax

    這裡,鬼知道它想算什麼,先把 EAX 中的值左移三位,再怎麼除以一個 00000048,然後還乘以 FFFFFFFF,不過看起來結果應該就是 FontKey 記錄的 FFFFFFF6 了,然後送入到 LogFont 結構的首部。非常高興哦,這裡我們有這麼多字節可以動手,於是把 C1E003996A4859F7F96BC0FF8945C4 改為 C645DB86C745C4F4FFFFFF90909090,結果就是這幾句變成了:

:0040D84B C645DB86 mov [ebp-25], 86
:0040D84F C745C4F4FFFFFF mov [ebp-3C], FFFFFFF4
:0040D856 90 nop :0040D857 90 nop
:0040D858 90 nop :0040D859 90 nop

    把兩個調用 CreateFontIndirectA 的地方都這樣改過。好了,儲存一看,沒問題了,再用 FontKey 監視一下,結果如下:

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF4_00000000_00000000_00000000_000002BC_00_00_00
_86_00_00_00_00:"新細明體")
CreateFontIndirectA returns: A5C

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF4_00000000_00000000_00000000_00000190_00_00_00
_86_00_00_00_00:"新細明體")
CreateFontIndirectA returns: 568

    標準的「新細明體,12px」的 LogFont 結構就這樣建立成功了,一切 OK。這樣修改後該中文化版軟體的界面如圖十所示

    想必大家從圖中可以看出「下一步」按鈕的字型被加粗了,而其它按鈕的字型卻是規則的,那麼,上面的反編譯代碼中那一段是改變字重的呢 ? 大家只要根據上面監視出來 LogFont 結構就可以知道:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC 與 :0040D8AA C745D490010000 mov [ebp-2C], 00000190 這兩段代碼就是改變按鈕中字型字重的語句。我看了看微軟關於這兩個字型函數(一個是 CreateFont,一個是 CreateFontIndirect)的說明,其中它這樣寫到: 「weight: This specifies the font's weight as a number between 0 and 900. The value 0 selects the default, 400 is normal, and 700 is bold.」其實這個參數就是字重 0 - 預設,400 - 普通,700 - 粗體。因為,400 的16進制數為 190,700 的16進制數為 2BC,所以,那個 :0040D85A C745D4BC020000 mov [ebp-2C], 000002BC 就顯示了粗體字,而:0040D8AA C745D490010000 mov [ebp-2C], 00000190 就顯示了規則字。

第三篇: 疑問解答

    看完上面的內容之後,可能大家會提出很多問題,下面我們就來解答一些在字型修改中可能會碰到的問題。

    一開始的問題我們相信大家都會說,上面的文章中Ronnier兄提到的透過把整個函數調用脫出來的修改字型設定的方法是怎麼一回事 ? 其實,這種修改方法在梁利鋒兄寫的《野蠻人戰記》這篇文章中講的也比較簡單,不容易理解,這裡我們就請Ronnier兄把詳細的修改方法傳授給我們您 ! 在看下面內容之前,我們建議大家先看一看梁利鋒兄寫的《野蠻人戰記》這篇文章,後再繼續往下看。

    Ronnier兄說: 其實這種修改方法說到底也很簡單,就是自己構建兩個東西:

    第一,「新細明體,12px」的 LogFont 結構,一個標準的「新細明體,12px」LogFont 結構,它的十六進制值是 f4ffffff000000000000000000000000900100000000008600000000cbcecce5 而如果要粗體字就把 9001 改為 BC02,如果是大字型環境把 F4FFFFFF 改為 F1FFFFFF,因為大字型裡 9pt=15px。

    第二,一個小函數,這個小函數其實就是把我們構建的那個 LogFont 結構做為參數然後調用 CreateFontIndirectA。其內容就是這樣三行:

    Push 「LogFont 結構的起始地址」
        Call GDI32.CreateFontIndirectA
    Ret

按照梁利鋒的文章裡說的,可以把它們寫在一起,舉個例子說明您,還是拿上面的那個軟體:

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                 |
:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0
:0040D83E 59 pop ecx
:0040D83F 59 pop ecx
:0040D840 6A5A push 0000005A
:0040D842 FF75C0 push [ebp-40]
* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

其中調用 CreateFontIndirectA 函數的就是這兩句:

:0040D864 50 push eax
:0040D865 FF15A0204200 Call dword ptr [004220A0]

就改這兩句就行了,改為 Call 我們自定義的那個函數的地址,就行了。

    比如已經把 LogFont 結構記錄在檔案偏移 300 處開始的 32 個字節中,在此之後就應該是我們自己定義的那個函數了,函數這麼寫:

:00400320 6800034000 push 00400300
:00400325 FF15A0204200 Call dword ptr [004220A0]
:0040032B C3 ret

    注意,因為這個檔案裡的 GDI32.CreateFontIndirectA 函數入口是在 004220A0 的值所指的地址,所以可以把原來檔案裡的 Call GDI32.CreateFontIndirectA 語句直接抄過來用,因為它是個直接地址調用,而如果原來的檔案裡的 Call GDI32.CreateFontIndirectA 語句是形如 Call XXXXXXXX (機器碼是 E8XXXXXXXX) 這樣的間接地址調用,則不能直接抄過來用,而要透過計算相對偏移。計算方法參照下面的內容。

    好了,在 300 開始寫了 f4ffffff000000000000000000000000900100000000008600000000cbcecce56800034000FF15A0204200C3 後,就該修改原檔案的調用部分了,把 50FF15A0204200 改為 E8B72AFFFF9090,就是改為:

:0040D864 E8B72AFFFF Call 00400320
:0040D869 90 nop
:0040D86A 90 nop

    這裡從 0040D864 到 00400320 的相對偏移為 FFFF2AB7 是這樣計算的: 00400320-0040D864-5=FFFF2AB7,為什麼要減 5 是因為 Call 00400320 這個指令占 5 個字節。

    好了,就是這樣了,記著從 CreateFontIndirectA 的 push 語句開始改起,才能避免潛在的錯誤。現在已經修改了存在字型設定問題的一處地方,相信修改另一處對大家來說也已經不是什麼難事了您 ! 修改完後該中文化版軟體的界面如圖十所示。還有要注意的就是梁利鋒兄的《野蠻人戰記》中「例外」那一節了。下面這段內容就是摘自《野蠻人戰記》中「例外」那一節。

************************************************************************************************

例 外

    不過這樣的修改有時候會遇上類似下面的代碼:

:00401FC2 56 push esi
* Reference To: GDI32.CreateFontIndirectA, Ord:0053h
                                  |
:00401FC3 8B3D08704000 mov edi, dword ptr [00407008]
:00401FC9 FFD7 call edi

    這樣,因為 call 語句呼叫的是上一句 mov 指令放入 edi 的指令,所以這一句 mov 指令也同樣是必不可少的,是不能盲目消除的,當然,這樣的程式可能有各種變化,我不可能在這裡盡列,所以需要修改者自行進行這一方面的的判斷。其實這樣的問題在 CreateFontA 函數中出現的可能性更大,所以在修改 CreateFontA 函數更要小心。

    另外,需要注意,如下的代碼:

* Reference To: GDI32.CreateFontIndirectA, Ord:0053h
                                 |
:00401FC2 8B3D08704000 mov edi, dword ptr [00407008]
:00401FC8 56 push esi :00401FC9 FFD7 call edi

    雖然符合我的這種修改的方法所說的順序,不過 push 和 call 語句加起來只有 2 個字節,所以還是需要認識到可以把這一句 mov 指令一起移入我們自己的函數才能修改的。

************************************************************************************************

    也許有些朋友會問,如果一開始就找不到軟體中「MS Sans Serif」字型存在,哪該怎麼辦 ? 其實很簡單,只要先反編譯需要的軟體,後尋找是否有「CreateFont*」 (*表示A或者IndirectA,也可表示空或其它字串) 的字樣,如有就往上找幾行就能看見這個字型的名稱,接下來就可以按照上面講的方法進行修改了。大家是否又會問,如果在反編譯代碼中找到不止一個RAV值或者根本就找不到符合的RAV值,又該怎麼辦 ? 對於第一種情況 (找到不止不一個RAV值) ,大家只有一處一處的分別修改後看軟體的執行結果來判斷是否改對地方;對於第二種情況 (根本就找不到符合的RAV值) ,可以減少一個字串後再尋找,例如: 尋找426c1c改成尋找426c1,這樣改了之後再尋找又可能找到很多,如何修改呢 ? 方法同上 !

    最後我還想說一點,現在我們見到的 WinZip 系列的軟體大多數都採用了一些非unicode編碼,所以中文化後有部分中文字會變成亂碼。在這次修改過程中,如果大家在exescope中開啟沈曄駿兄中文化的 WinZip Self-Extractor 2.2 Build 4003查看時,有些對話框顯示出來就全是亂碼,不過該中文化版沈曄駿兄已經修改了這個問題,只是大家在exescope看見是亂碼,實際執行中是正常的。至於如何修改這種非unicode編碼,大家可以參看呂達嶸兄寫的《關於 Winzip 8.0 beta 版的中文化》這篇文章,也可以參看香港的黃權燊先生寫的《使用 CXA 遇到的?題及特別功能》這篇文章,並用他自己開發的軟體CXA來進行中文化工作。

    這幾天,沈曄駿兄又來信詢問是否可以把該中文化版製作成的自解壓程式的安裝界面給一起中文化掉。我們研究了一下,用 exescope 開啟該中文化版仔細查看裡面的資源後,發現內在SFXHEADERW32M、SFXHEADERW32MG、SFXPROHEADERW32M、SFXPROHEADERW32MG等資源存在 (如圖二所示) ,我們估計可能需要中文化的內容就放在這些裡面,但面對這種我們從來沒有中文化過的特殊資源應該如何應付呢 ? 於是,Ronnier兄就提出用 Restorator 這個軟體來幫助中文化,Ronnier兄說到: 用 Restorator 開啟檔案,看到的那幾個叫做 SFXHEADERW32M、SFXHEADERW32MG、SFXHEADERPROW32M、SFXHEADERPROW32MG 的資源分別對應了普通自解壓檔案英、德文,安裝自解壓檔案英、德文的檔案頭,修改方法只要用該軟體選單裡的「資源->擷取為->擷取為...」存成 .EXE 檔案就可以改了,改完用「資源->回寫到」匯入即可;漢化新世紀的偉乾兄也提出: 可以用 exescope 把他們匯出來儲存,不過一般匯出的這些資源都加了殼,中文化前需要先脫殼後中文化,中文化完成後再加殼匯入去,但是聽說這種方式的資源,匯出來和導進去的檔案大小要一樣,如果不一致,透過把檔案後面的00增加或刪除達到一致後再匯入。但透過測試,我們發現用上面寫到的這兩種方法分別匯出該中文化版軟體中的這些資源後,從它們裡面都無法尋找到需要中文化的內容,而且,還發現雖然用 exescope 這個軟體可以匯出這種特殊資源,但無法把它們再匯入去。所以,沒辦法 ! 飛鷹又再次與偉乾兄聯繫,尋求其它中文化方法。果真不出多久,偉乾兄又告訴了我們另一種修改方法,他說: 可以檢查一下註冊表或WIN/目錄或SYSTEM目錄,看看有沒有需要中文化的資源存在,或者可以到它的網站看看,是否自定義有SFX的檔案下載。經過前面提出的多種修改方法的嘗試,我們始終沒能找到需要中文化的內容,因此,在這篇文章結稿之時,還是沒能把中文化版軟體製作成的自解壓程式的安裝界面給中文化掉,還望廣大網友能賜教。雖然,上面寫到的這種特殊資源的中文化方法對於該軟體無效,不過,這並不代表對其它軟體也無效,如果大家在中文化過程中遇到此類問題,大可以用上面說到的這些修改方法一試,難說就成功了也說不定 ! 關於 Restorator 這個軟體的使用方法,大家可以參看偉乾兄寫的《關於Restorator的快速上手》這篇文章;exescope 這個軟體我們相信大家已經用的很熟了,但也可以看看brucez兄寫的《ExeScope 4.2Xup 使用中的小秘訣》這篇文章,增點一點功力。

    現在才發現寫篇文章真是累,花了我們很多很多的時間才完成。但想想漢化新世紀的偉乾兄、梁利鋒兄及其它中文化DIY是那樣的無私、偉大,為我們寫出了許多技術含量高的中文化教學,培養出了一批又一批的中文化人,我們這點累又算得上什麼呢,只要大家快樂就行。

    這篇文章中使用到的全部工具都可以在漢化新世紀網站上找到。由於諸多原因,文章中難免存在一些不足,殷切希望廣大中文化愛好者批評指正。

Ronnier && 飛鷹  寫於2001年春節 修正於2001年2月8日



回教學