文字說明
名稱:綱: 花和蛇》遊戲漢化
作者:衝出宇宙
受朋友委託,幫他分析這個遊戲如何漢化。後來他們取消了這個意向,所以,我就來和大家探討一下這個遊戲漢化的技術。
1 下載這個遊戲。遊戲特別大,選擇完全安裝。可以看到安裝目錄下有幾個exe檔案和幾個arc檔案。arc檔案特別大,看來可能是數據檔案。運行一下幾個exe檔案,可以發現,hh2d.exe和hanahebi.exe都能進入遊戲。猜測前者是2d版本的,後者是3d加速版本。因為前者的圖像移動有滯後的感覺。我們只是漢化遊戲,所以,研究其2d版本。
2 準備工具。看了一下自己的電腦,由於上次出了問題,系統重新安裝了,已經沒有任何安裝了的分析軟體了。好在IDAG和OD是不需要安裝的,嗬嗬,可以直接用。
3 用idag分析hh2d.exe檔案。
3.1 得到hh2d讀取的檔案名稱
毫無疑問,hh2d運行的時候肯定會先讀取數據包的。於是,對CreateFileA函式下斷點。開始調試hh2d.exe檔案,在運行到斷點CreateFileA之後,按Ctrl+F7返回,發現EIP停在:.text:0044D757 call ds:CreateFileA繼續執行,可以看到,每次讀檔案都是停在這裡。現在,對44D754設斷點,這樣設的目的是為了發現hh2d.exe是打開了具體的哪些檔案。再次調試hh2d.exe,可以發現hh2d.exe打開了以下檔案:mes.arc,data.arc,gcc.arc,bgm.arc,se.arc,voice.arc。在這裡的時候,朋友告訴我說有一個軟體能夠看到許多遊戲裡面的數據信息,叫做mlist3.exe。我找到這個軟體,然後用它依次打開了所有的arc檔案,發現voice.arc裡面都是聲音,gcc.arc裡面都是圖片,只有mes.arc裡面的檔案看起來像文字信息,因為其擴展名是:mes,很像message。
3.2 題外話
拋開遊戲本身不談,先說說mlist3.exe這個軟體。這個軟體是日本人寫的,功能比較強大。想起這點就鬥志昂昂,日本人能搞定的,我還搞不定了么?繼續繼續。
3.3 尋找其他信息
先看看mes.arc檔案的內容,使用ultraedit打開它,可以看到前面一些位元組為:00000000h: F0 01 00 00 5C 4E 42 53 40 4B 46 40 48 2D 4E 46 ; ?..\NBS@KF@H-NF00000010h: 50 03 CE CE CE CE CE CE CE CE CE CE CE CE CE CE ; P.撾撾撾撾撾撾撾00000020h: CE CE CE CE BC 7F 46 65 0C 5E 65 79 5C 50 42 4E ; 撾撾?Fe.^ey\PBN00000030h: 53 4F 46 2D 4E 46 50 03 CE CE CE CE CE CE CE CE ; SOF-NFP.撾撾撾撾00000040h: CE CE CE CE CE CE CE CE CE CE CE CE 09 3B 46 65 ; 撾撾撾撾撾撾.;Fe00000050h: D5 6A 65 79 5C 50 42 4E 53 4F 46 31 47 2D 4E 46 ; 誮ey\PBNSOF1G-NF00000060h: 50 03 CE CE CE CE CE CE CE CE CE CE CE CE CE CE ; P.撾撾撾撾撾撾撾00000070h: CE CE CE CE B1 33 46 65 41 FB 65 79 40 44 4E 4C ; 撾撾?FeA鵮y@DNL00000080h: 47 46 2D 4E 46 50 03 CE CE CE CE CE CE CE CE CE ; GF-NFP.撾撾撾撾?00000090h: CE CE CE CE CE CE CE CE CE CE CE CE 2C 57 46 65 ; 撾撾撾撾撾撾,WFe000000a0h: 15 43 64 79 40 44 4E 4C 47 46 5C 40 4B 42 2D 4E ; .Cdy@DNLGF\@KB-N000000b0h: 46 50 03 CE CE CE CE CE CE CE CE CE CE CE CE CE ; FP.撾撾撾撾撾撾看起來似乎沒有什麼規律,除了開始的F0 01 00 00 看起來像是說這個檔案有0x1F0個什麼結構以外。
3.4 監視hh2d.exe對mes.arc檔案的讀取
我們把目光轉到hh2d.exe檔案上來,這次,我們對ReadFile函式下斷點。運行程式,等程式停到ReadFile斷點處,按Ctrl+F7返回,來到:.text:0044D815 call ds:ReadFile.text:0044D81B retn 8同樣,我們取消ReadFile的斷點,把斷點設在44D814處。這樣,我們設定的斷點有2個,分別是:44D754和44D814。重新運行hh2d.exe,根據44D754處的斷點得到打開mes.arc檔案後得到的句柄hfile(假設為0x350)。取消44D754斷點,把44D814處斷點的條件設定為ecx == 0x350,繼續運行。接下來,程式會中斷在44D814處,ok,我們監視到了hh2d.exe對mes.arc檔案的讀取。看看當前堆疊上面的內容:Stack :0012F8EC dd 416BA44hStack :0012F8F0 dd 4Stack :0012F8F4 dd 12F904hStack :0012F8F8 dd 0比較一下44D807處的代碼:.text:0044D807 push 0 ; lpOverlapped.text:0044D809 lea eax, [esp+4+NumberOfBytesRead].text:0044D80D push eax ; lpNumberOfBytesRead.text:0044D80E mov eax, [esp+8+lpBuffer].text:0044D812 push edX ; nNumberOfBytesToRead.text:0044D813 push eax ; lpBuffer.text:0044D814 push ecx ; hFile.text:0044D815 call ds:ReadFile可以看到,這次是讀取了前面4個位元組,就是F0 01 00 00。繼續運行,再次停在44D807這裡,使用同樣的方法,可以看到這次讀取了0x4D80個位元組。0x4D80/0x1f0 = 0x28,看來每個結構是0x28(40)個位元組。使用F8單步執行,不久就發現程式在40B060和40B1A3之間來回循環。認真的監視一下代碼段:.text:0040B191 cmp eax, edx.text:0040B193 mov [esp+5Ch], edi.text:0040B197 mov [esp+58h], ebx.text:0040B19B mov [esp+48h], bl.text:0040B19F mov [esp+10h], eax.text:0040B1A3 jb loc_40B060發現在40B191處,edx等於0x1f0,而eax不斷的在加1,這裡是一個很明顯的對讀取的數據進行處理的過程。於是開始認真地分析40B060到40B1A3之間的代碼。然而,再經過幾次單步執行後,發現這裡處理的數據不是讀取的那些數據,說明數據已經先進行了處理。因為這裡處理的數據地址在ESI中,於是,從後往前的尋找對esi進行修改的地方,發現只有一處:.text:0040B039 push esi.text:0040B03A mov ecx, edi.text:0040B03C call sub_40A0E0我們知道,ecx一般都是傳遞類指針的,這裡明顯就是調用類裡面的函式。動態分析顯然比靜態分析容易,於是,我們重新啟動程式,並且按照上述的步驟,達到40A0E0處。於是分析得到如下結果:.text:0040A0E0 sub_40A0E0 proc near ; CODE XREF: .text:0040B03Cp.text:0040A0E0.text:0040A0E0 arg_0 = dword PTR 4.text:0040A0E0.text:0040A0E0 mov eax, [ecx+14h] //eax = 0x1F0.text:0040A0E3 push esi.text:0040A0E4 xor esi, esi //esi = 0.text:0040A0E6 test eax, eax.text:0040A0E8 jbe short loc_40A119.text:0040A0EA mov eax, [esp+4+arg_0] //eax = arg0.text:0040A0EE push ebx.text:0040A0EF add eax, 24h //eax +=0x24.text:0040A0F2 mov bl, 3 //bl = 3.text:0040A0F4.text:0040A0F4 loc_40A0F4: ; CODE XREF: sub_40A0E0+36j.text:0040A0F4 xor edx, edx.text:0040A0F6.text:0040A0F6 loc_40A0F6: ; CODE XREF: sub_40A0E0+1Ej.text:0040A0F6 xor [eax+edx-24h], bl //[eax+edx-24h] ^= 3.text:0040A0FA inc edx.text:0040A0FB cmp edx, 20h.text:0040A0FE jl short loc_40A0F6 //這裡前32個位元組都和3異或.text:0040A100 xor dword ptr [eax-4], 65465465h //倒數第2個DW異或.text:0040A107 xor dword ptr [eax], 79651388h //倒數第1個DW異或.text:0040A10D mov edx, [ecx+14h].text:0040A110 inc esi.text:0040A111 add eax, 28h //下一個結構,每個40位元組長.text:0040A114 cmp esi, edx.text:0040A116 jb short loc_40A0F4.text:0040A118 pop ebx.text:0040A119.text:0040A119 loc_40A119: ; CODE XREF: sub_40A0E0+8j.text:0040A119 pop esi.text:0040A11A retn 4.text:0040A11A sub_40A0E0 endp嗬嗬,這下我們知道了他們是如何對結構進行加密的。再次結合40B060到40B1A3之間的代碼,我們最終分析到的mes.arc檔案頭結構為:class CFileNode{public:char FileName; //file nameint FileLen; //file lengthint FilePos; //file start position in arc file};
3.5 得到mes.arc檔案的數據結構
到目前為止,我們已經得到了mes檔案的檔案頭結構。但是,我們還需要得到mes檔案的數據結構。繼續運行,這次程式仍然中斷在44D814處,看看堆疊,讀取了6E2個位元組。記住讀取的數據存放的位置(假設為0x7250000),按F8單步執行,並且注意觀察,看看是否有代碼引用了這個地址的數據。運行到:.text:0046971A mov edi, eax.text:0046971C test edi, edi.text:0046971E jnz short loc_46974F.text:00469720 jmp loc_4697AB發現edi = 0x6E2。繼續單步執行,到如下代碼處:.text:0046977B push edi.text:0046977C push ecx ; 保存解碼後的結果.text:0046977D push ebx ; 原始數據.text:0046977E lea ecx, [esp+1Ch].text:00469782 mov dword ptr [esp+42Ch], 0.text:0046978D call sub_45B980 ; 解碼完畢我們發現,edi = 0x6E2,ebx=0x7250000,而ecx指向一個陌生地址,運行完46978D之後,ecx的數據發生了很大的變化,於是,我們幾乎可以斷定,45B980處就是對數據進行解碼的地方。為了安全起見,我們再次啟動程式,按照上面的步驟單步執行到45B980裡面,發現程式不斷的在45B9D0和45BA75之間循環,這裡顯然是解碼的關鍵部分。代碼如下:.text:0045B980 ; Attributes: bp-based frame.text:0045B980.text:0045B980 sub_45B980 proc near ; CODE XREF: .text:0046978Dp.text:0045B980.text:0045B980 var_1014 = dword ptr -1014h.text:0045B980 var_1010 = dword ptr -1010h.text:0045B980 var_100C = dword ptr -100Ch.text:0045B980 var_1008 = dword ptr -1008h.text:0045B980 var_4 = dword ptr -4.text:0045B980 arg_0 = dword ptr 8.text:0045B980 arg_4 = dword ptr 0Ch.text:0045B980 arg_8 = dword ptr 10h.text:0045B980.text:0045B980 push EBP.text:0045B981 mov ebp, esp.text:0045B983 and esp, 0FFFFFFF8h.text:0045B986 mov eax, 1014h.text:0045B98B call __alloca_probe.text:0045B990 mov eax, dword_5989B0.text:0045B995 push ebx.text:0045B996 push esi.text:0045B997 push edi.text:0045B998 mov [esp+1020h+var_4], eax.text:0045B99F xor eax, eax.text:0045B9A1 mov ecx, 3FBh.text:0045B9A6 lea edi, [esp+1020h+var_1008].text:0045B9AA rep stosd.text:0045B9AC mov ecx, [ebp+arg_8].text:0045B9AF stosw.text:0045B9B1 xor esi, esi.text:0045B9B3 xor edx, edx.text:0045B9B5 xor eax, eax.text:0045B9B7 test ecx, ecx.text:0045B9B9 mov edi, 0FEEh.text:0045B9BE jz loc_45BA7B.text:0045B9C4 mov ebx, [ebp+arg_4].text:0045B9C7 mov ecx, [ebp+arg_0].text:0045B9CA lea ebx, [ebx+0].text:0045B9D0.text:0045B9D0 loc_45B9D0: ; CODE XREF: sub_45B980+F5j.text:0045B9D0 shr eax, 1.text:0045B9D2 test ah, 1.text:0045B9D5 mov [esp+1020h+var_1014], eax.text:0045B9D9 jnz short loc_45B9EB.text:0045B9DB mov al, [esi+ecx].text:0045B9DE MOVZX eax, al.text:0045B9E1 inc esi.text:0045B9E2 or eax, 0FF00h.text:0045B9E7 mov [esp+1020h+var_1014], eax.text:0045B9EB.text:0045B9EB loc_45B9EB: ; CODE XREF: sub_45B980+59j.text:0045B9EB test al, 1.text:0045B9ED jz short loc_45BA07.text:0045B9EF mov al, [esi+ecx].text:0045B9F2 movzx eax, al.text:0045B9F5 inc esi.text:0045B9F6 mov [edx+ebx], al.text:0045B9F9 inc edx.text:0045B9FA mov byte ptr [esp+edi+1020h+var_1008], al.text:0045B9FE inc edi.text:0045B9FF and edi, 0FFFh.text:0045BA05 jmp short loc_45BA6E.text:0045BA07 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?.text:0045BA07.text:0045BA07 loc_45BA07: ; CODE XREF: sub_45B980+6Dj.text:0045BA07 mov bl, [esi+ecx].text:0045BA0A mov al, [esi+ecx+1].text:0045BA0E inc esi.text:0045BA0F movzx ecx, al.text:0045BA12 mov eax, ecx.text:0045BA14 and eax, 0F0h.text:0045BA19 movzx ebx, bl.text:0045BA1C shl eax, 4.text:0045BA1F and ecx, 0Fh.text:0045BA22 or eax, ebx.text:0045BA24 mov ebx, [ebp+arg_4].text:0045BA27 inc esi.text:0045BA28 add ecx, 2.text:0045BA2B mov [esp+1020h+var_100C], ecx.text:0045BA2F mov ecx, 0.text:0045BA34 mov [esp+1020h+var_1010], ecx.text:0045BA38 js short loc_45BA6B.text:0045BA3A lea ebx, [ebx+0].text:0045BA40.text:0045BA40 loc_45BA40: ; CODE XREF: sub_45B980+E9j.text:0045BA40 add ecx, eax.text:0045BA42 and ecx, 0FFFh.text:0045BA48 movzx ecx, byte ptr [esp+ecx+1020h+var_1008].text:0045BA4D mov [edx+ebx], cl.text:0045BA50 mov byte ptr [esp+edi+1020h+var_1008], cl.text:0045BA54 mov ecx, [esp+1020h+var_1010].text:0045BA58 inc edx.text:0045BA59 inc edi.text:0045BA5A and edi, 0FFFh.text:0045BA60 inc ecx.text:0045BA61 cmp ecx, [esp+1020h+var_100C].text:0045BA65 mov [esp+1020h+var_1010], ecx.text:0045BA69 JLE short loc_45BA40.text:0045BA6B.text:0045BA6B loc_45BA6B: ; CODE XREF: sub_45B980+B8j.text:0045BA6B mov ecx, [ebp+arg_0].text:0045BA6E.text:0045BA6E loc_45BA6E: ; CODE XREF: sub_45B980+85j.text:0045BA6E cmp esi, [ebp+arg_8].text:0045BA71 mov eax, [esp+1020h+var_1014].text:0045BA75 jnz loc_45B9D0.text:0045BA7B.text:0045BA7B loc_45BA7B: ; CODE XREF: sub_45B980+3Ej.text:0045BA7B mov ecx, [esp+1020h+var_4].text:0045BA82 mov eax, edx.text:0045BA84 call sub_480E53.text:0045BA89 pop edi.text:0045BA8A pop esi.text:0045BA8B pop ebx.text:0045BA8C mov esp, ebp.text:0045BA8E pop ebp.text:0045BA8F retn 0Ch看到這么複雜的代碼,也沒有必要害怕喔。認真地分析分析,很快就發現,這個解壓縮算法和LZW算法有很多相同之處。其實這個就是一個略有變形的10位LZW算法。到目前為止,我們已經清楚了mes.arc檔案的結構了喔。顯然,其他arc檔案也是這個結構。
3.6 *.mes檔案的結構
根據上面的分析,編寫一個程式,能夠對mes.arc檔案進行解壓縮。解壓縮得到好多檔案,不過,檔案格式都一樣的,都是*.mes檔案。隨便打開一個,比如使用ultraEdit打開FAS000.mes(這個檔案其實就是最先出來的那幕的字幕檔案)。可以看到:00000000h: F4 00 00 00 DA 04 00 00 58 05 00 00 F8 05 00 00 ; ?..?..X...?..00000010h: F5 07 00 00 C9 08 00 00 AD 09 00 00 7F 0A 00 00 ; ?..?..?.....00000020h: 3B 0B 00 00 7C 0D 00 00 74 0E 00 00 16 0F 00 00 ; ;...|...t.......00000030h: 3D 10 00 00 EB 10 00 00 A1 11 00 00 7B 12 00 00 ; =...?..?..{...00000040h: 49 13 00 00 2F 14 00 00 0C 15 00 00 1A 17 00 00 ; I.../...........00000050h: 0E 18 00 00 9C 18 00 00 D0 19 00 00 AA 1A 00 00 ; ....?..?..?..00000060h: 6C 1B 00 00 3D 1C 00 00 03 1E 00 00 D8 1E 00 00 ; l...=.......?..00000070h: 84 1F 00 00 1A 20 00 00 D8 20 00 00 6E 21 00 00 ; ?... ..?..n!..00000080h: 4A 22 00 00 7E 24 00 00 87 25 00 00 11 26 00 00 ; J"..~$..?...&..00000090h: A3 26 00 00 4D 27 00 00 4B 28 00 00 34 29 00 00 ; ?..M'..K(..4)..這個對於我們來說簡直太簡單了,前面的F4 00 00 00 表示有0xf4個結構,後面的剛好有0xf4個整數,可能是表示每個結構的位置吧。具體怎么計算還不知道。再看看下面的某處:00000930h: 00 0D 13 FF 02 01 FF 00 1D 02 1B FF 00 01 81 40 ; ...........丂00000940h: 00 07 0C FF 00 14 FF 00 07 0D FF 00 14 FF 00 0D ; ............00000950h: 00 FF 00 1F 2B 00 00 00 01 8D F7 82 CC 89 D4 82 ; ...+....嶗偺壴?00000960h: D1 82 E7 82 AA 81 41 95 97 82 C9 95 91 82 A2 97 ; 褌鐐獊A晽偵晳偄?00000970h: 78 82 C1 82 C4 82 A2 82 E9 82 E6 82 A4 82 BE 81 ; x偭偰偄傞傛偆偩?00000980h: 63 81 63 00 0D 13 FF 02 02 FF 00 0B F3 C6 0B 00 ; c乧.......篤..雖然是日文,不過,我們還是能夠看出來,這裡是一句日文的話。如果要進行漢化,那么,就該漢化這裡喔。情況其實比這個要複雜很多,不過,那些東西和軟體調試沒有什麼關係。只有實際漢化的時候才需要處理。
3.7 如何顯示漢字
按照上面所說的方式修改文字之後,發現文字不能顯示為中文。但是,英文可以顯示。那么,到底是什麼原因呢?在一次得仔細看了看hh2d.exe檔案的import函式表。發現了TextOutA函式。這個函式是用來輸出文字到螢幕的。如果給這個函式設斷點,會發現,每次出現文字的時候,這個函式都被調用。根據這個原理,取消所有斷點,重新運行程式,然後點擊遊戲界面中的開始,聽到一段音樂之後,會看到文字信息的出現,這個時候,對TextOutA設斷,繼續遊戲。很快就中斷在TextOutA函式處,按Ctrl+F7回到程式領空,能夠看到回到414442處。繼續Ctrl+F7,到了414F5A處,注意,這裡是一大段,看得出來,這裡和輸出字元應該有關係。而且,這一段函數裡面多次調用了TextOutA函式。對這段函式的開頭414F00設斷,取消TextOutA的斷點,繼續程式,很快就停在414F00處。使用F8單步執行,注意看堆疊裡面的第3個輸入參數,發現它為一個比較大的數(比如:8DF7)。同時注意到我們上面看到的*.mes檔案的內容,能清楚地看到日文字元一般都是8***,或者9***什麼的。於是想到8DF7可能是一個日文字元。繼續單步執行,運行到:.text:00414F43 mov ecx, esi.text:00414F45 call sub_4143E0.text:00414F4A lea eax, [esp+14h+var_4].text:00414F4E push eax.text:00414F4F push 0.text:00414F51 push 0.text:00414F53 mov ecx, esi.text:00414F55 call sub_414410 ; out put 8D F7看得到,這裡就是調用了414410(TextOutA)函式進行輸出的。而輸出的字元串保存在eax中,就是var_4這個變數中,看看前面:.text:00414F06 mov cx, [esp+10h+arg_8].text:00414F0B mov ax, cx.text:00414F0E shr ax, 8.text:00414F12 cmp al, 81h。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。.text:00414F3B mov byte ptr [esp+14h+var_4], al.text:00414F3F mov byte ptr [esp+14h+var_4+1], cl可以知道var_4就是第3個參數的值。輸出文字的位置找到了,那么,我們再重新看看前面的代碼:text:00414F06 mov cx, [esp+10h+arg_8].text:00414F0B mov ax, cx.text:00414F0E shr ax, 8.text:00414F12 cmp al, 81h.text:00414F14 push edi.text:00414F15 mov byte ptr [esp+14h+var_4+1], 0.text:00414F1A mov byte ptr [esp+14h+var_4+2], 0.text:00414F1F jb short loc_414F25.text:00414F21 cmp al, 9Fh.text:00414F23 jbe short loc_414F3B.text:00414F25.text:00414F25 loc_414F25: ; CODE XREF: sub_414F00+1Fj.text:00414F25 cmp al, 0E0h.text:00414F27 jb short loc_414F2D.text:00414F29 cmp al, 0EFh.text:00414F2B jbe short loc_414F3B.text:00414F2D.text:00414F2D loc_414F2D: ; CODE XREF: sub_414F00+27j.text:00414F2D cmp al, 0FAh.text:00414F2F jb short loc_414F35.text:00414F31 cmp al, 0FCh.text:00414F33 jbe short loc_414F3B.text:00414F35.text:00414F35 loc_414F35: ; CODE XREF: sub_414F00+2Fj.text:00414F35 mov byte ptr [esp+14h+var_4], cl.text:00414F39 jmp short loc_414F43
這一段的意思很明顯吧?它只比較第一個位元組,如果第一個位元組在0x81-0x9f,0xe0-0xef,0xfa-0xfc之間的話,就跳到414F3B執行,否則就跳到414F35處執行。跟蹤一下,就會發現414F3B那裡只輸出一個字元,而414F35那裡輸出2個字元。也就是說,如果讓漢字能夠顯示,就必須讓它跳轉到414F3B處。考慮到任何大於255的字元都不可能顯示2個字元,於是把上述代碼修改為:.
這樣需要修改的地方還有2處。具體的地方我就不說了。根據Ctrl+F7大法,很容易發現它們的所在。
3.8 編碼的處理
日文軟體顯然使用日文編碼。遊戲要想在安裝了日文字型檔的機器上面正確顯示中文的話,還需要修改其顯示時候的編碼。回憶Api函式,如果還不行的話,回去看看《win2000 api大全》裡面關於文字的api函式。你會發現有一個函式:CreateFontIndirectA具有一些和字元編碼有關的參數。難道我說得還不明顯么?
完成於2006年1月,累死了。休息休息。
品種介紹
在此處添加文本內容
生活習性
在此處添加文本內容
動物圖片
在此處添加文本內容
經濟價值
在此處添加文本內容