1 【原創】DirectX
9 遊戲漢化詳解
|
【文章標題】: DirectX 9 遊戲漢化詳解
【文章作者】: noword
【軟件名稱】: 無厘頭太空戰役
【下載地址】: http://www.verycd.com/topics/2819995/
--------------------------------------------------------------------------------
【前言】
先copy一段此遊戲介紹:
這是一個獨特的戰略遊戲,具有即時戰略與塔防的混合風格,玩家將扮演龐大太空艦隊的最高指揮官,你可以自定飛船的構造,擺放飛船的位置,下達命令,然後觀看絢麗的射擊與爆炸。移動和爆炸時會有動態模糊效果。支持自定義地圖。
想玩中文版,兩個遊戲論壇,3DM和YX上,都有人說要漢化,等了幾個月,沒有下文,說是技術原因。於是決定自己來試試看。
【困難何在】
此遊戲的文本都在data目錄下,都是明文的文本文件。修改data\strings.ini,將
改成
進入遊戲後,不出所料,無法顯示此中文。
茫茫多的遊戲愛好者,在嘗試漢化某款自己心儀的遊戲時,都是死在了這一步——找了半天文本資源,然後翻成中文,滿心歡喜和期待的進入遊戲,面對的卻是一堆亂碼或一片空白。滿腔熱情,化爲烏有,無可奈何,黯然神傷。
本文的目的,就是希望能夠幫助這些有志於遊戲漢化的同學,主要介紹瞭如何讓一個英文的遊戲,能夠正確的顯示出中文。
【調試分析】
DirectX 9遊戲的啓動流程是這樣的,先執行Direct3DCreate9,返回值是一個IDirect3D9句柄,然後執行IDirect3D9->CreateDevice,得到IDirect3DDevice9句柄。
有了IDirect3DDevice9就能使用DirectX 9的一切繪圖手段,而我們最關心的就是能夠使用D3DXCreateFont來創建ID3DXFont,繼而能夠非常方便快捷的在遊戲中顯示文字。
用ODBG載入遊戲的exe文件,“查找所有模塊間的調用”,找“d3d9.Direct3DCreate9”:
往下找,就能找到IDirect3D9->CreateDevice:
由於是所謂的COM接口,沒有十分明顯的標誌,不是很好找。
在微軟的DirectX SDK d3d9.h文件中,IDirect3D9的接口是這樣的:
CreateDevice是第17個函數,所以它的地址是(17-1)*4 = 0x40,
00501B74 . 8B52 40 mov edx, dword ptr [edx+40]
這裏的edx+40就是這麼來的。
如果覺得算起來很麻煩的話,還有一個簡單的方法,可以自己編譯一個d3d9的程序,然後反彙編看看。
如果還覺得麻煩,還有個更簡單的方法,直接往下找,通常會有一些調試文本能夠幫助定位,例如:
00501B86 . BB 600B5400 mov ebx, 00540B60 ; ASCII "CreateDevice"
...
00501BBB . BB 700B5400 mov ebx, 00540B70 ; ASCII "CreateDevice failed again"
當然,這個辦法並不通用,有效與否完全要看遊戲作者的臉色。
知道了IDirect3DDevice9的地址,就能植入我們自己的初始化ID3DXFont的代碼。
有了ID3DXFont,下面就是要找到用於顯示文字的函數,並用我們自己的代碼來替換實現。
隨便找一段遊戲中出現的文字,比如開始菜單上出現的“Full 1.37”,ALT-M,進入內存窗口,在所有搜到的該字符串上下“內存訪問”斷點,最終會找到地址在4FFF50的一個函數。
該函數返回時,用的是“retn 14”,在4FFF50處用“retn 14”改寫,切回到遊戲後沒有任何字符出現,說明這個函數正是用來顯示字符的。
原諒我在這裏對於怎麼找到4FFF50的過程含糊其辭,一筆帶過了。確實沒有什麼取巧的方法,完全取決於破解的功力,良好的耐力,以及一點點好運氣。而這也正是遊戲漢化的難點所在。
找到字符串顯示的函數後,還要弄清楚該函數的接口。
在屏幕上顯示一個字符串,通常需要知道這些要素:字符串、顯示的位置(X,Y座標)、顏色、字符的大小,以及一些flag用於表示左對齊,右對齊,居中,加粗,傾斜等。
前面說過,返回用的是“retn 14”,說明棧裏有20(10進制的14h)/4=5個參數,當進入該函數時,棧上的數據是這樣的:
第一個,很明顯就是要顯示的字符串,後面幾個是什麼玩意兒呢?
回到調用004FFF50的地方,在00453C1D下斷點:
可以看到,參數4是一個固定值,第二、三、五參數都是浮點數。
這裏有一個技巧,在函數開始的地方修改參數的值,看看會發生什麼變化,很快就能知道參數的作用。
參數4是顏色值,Alpha和RGB值都是FF,就是白色,與在遊戲中看到的字符顏色相同。
參數2是X座標,參數3是Y座標,參數5用於調整。
需要注意的是,還有兩個參數是通過寄存器ECX和EDX傳遞的,ECX用於表示左對齊(0),右對齊(1)和居中(2),EDX是固定值58D6D0,一個全局的結構或類。
此遊戲有兩種字體,因此用於顯示字符的幾大要素,現在還缺一個,就是不知道如何判斷字符的大小。
在用於顯示文字的004FFF50的上下斷點,多跟幾次,就會發現,每次調用以前,都會調用一個call:
在遊戲目錄data\font下面有兩個文件zekton16.dds.dat和cwfont20.dds.dat,由此判斷004FF170應該是用於選擇字體的函數。
總結一下:
第一步,找到IDirect3DDevice9句柄,用於初始化ID3DXFont。
第二步,找到顯示文字的函數,用自己的代碼實現之。
第三步,逐步找到其他需要修改的地方,比如用於得到字符串寬度、高度,指定寬度的字符串換行顯示等函數。
【具體實現】
我用的是注入dll,然後打內存補丁的方式。這樣的好處是便於更新,可以任意修改實現過程,以後遊戲出了新版本,也只要修改幾個地址變量,重新編譯一下就可以了。
另外一個好處是,不以文件補丁的形式發佈,沒有版權問題。(有人關心這個嗎?)
源碼在這裏:
http://gsbzhcn.googlecode.com/svn/trunk/src
簡單的介紹一下流程:
1.CreateProcess啓動遊戲的exe,並使之處於掛起狀態。
2.VirtualAllocEx在遊戲進程上申請一塊內存,WriteProcessMemory往裏寫入要注入的dll名稱。
3.GetProcAddress得到LoadLibraryA的地址。
4.CreateRemoteThread運行LoadLibraryA,注入dll,dll載入時會爲遊戲進程打上補丁。
6.WaitForSingleObject等候dll載入完成。
7.VirtualFreeEx清理掉之前申請的內存。
8.ResumeThread讓打過補丁的遊戲進程運行起來。
內存補丁主要是讓遊戲在關鍵的地方跳轉到我們的dll,執行一段代碼後再跳回去,或者直接用dll裏的函數代替之。
【結尾】
此遊戲的漢化正在http://code.google.com/p/gsbzhcn/ 進行,文本不多,奈何翻譯人手也不多,希望有興趣的同學能夠參與。
--------------------------------------------------------------------------------
【版權聲明】: 本文原創於看雪技術論壇, 轉載請註明作者並保持文章的完整, 謝謝!
2010年05月25日 14:52:11
【文章作者】: noword
【軟件名稱】: 無厘頭太空戰役
【下載地址】: http://www.verycd.com/topics/2819995/
--------------------------------------------------------------------------------
【前言】
先copy一段此遊戲介紹:
這是一個獨特的戰略遊戲,具有即時戰略與塔防的混合風格,玩家將扮演龐大太空艦隊的最高指揮官,你可以自定飛船的構造,擺放飛船的位置,下達命令,然後觀看絢麗的射擊與爆炸。移動和爆炸時會有動態模糊效果。支持自定義地圖。
想玩中文版,兩個遊戲論壇,3DM和YX上,都有人說要漢化,等了幾個月,沒有下文,說是技術原因。於是決定自己來試試看。
【困難何在】
此遊戲的文本都在data目錄下,都是明文的文本文件。修改data\strings.ini,將
代碼:
MAINMENU_QUIT = "Exit"
代碼:
MAINMENU_QUIT = "退出"
進入遊戲後,不出所料,無法顯示此中文。
茫茫多的遊戲愛好者,在嘗試漢化某款自己心儀的遊戲時,都是死在了這一步——找了半天文本資源,然後翻成中文,滿心歡喜和期待的進入遊戲,面對的卻是一堆亂碼或一片空白。滿腔熱情,化爲烏有,無可奈何,黯然神傷。
本文的目的,就是希望能夠幫助這些有志於遊戲漢化的同學,主要介紹瞭如何讓一個英文的遊戲,能夠正確的顯示出中文。
【調試分析】
DirectX 9遊戲的啓動流程是這樣的,先執行Direct3DCreate9,返回值是一個IDirect3D9句柄,然後執行IDirect3D9->CreateDevice,得到IDirect3DDevice9句柄。
有了IDirect3DDevice9就能使用DirectX 9的一切繪圖手段,而我們最關心的就是能夠使用D3DXCreateFont來創建ID3DXFont,繼而能夠非常方便快捷的在遊戲中顯示文字。
用ODBG載入遊戲的exe文件,“查找所有模塊間的調用”,找“d3d9.Direct3DCreate9”:
代碼:
00501974 . BB 500A5400 mov ebx, 00540A50 ; ASCII "Initialising 3D Engine" 00501979 . E8 22130000 call 00502CA0 0050197E . 6A 20 push 20 00501980 . 8977 18 mov dword ptr [edi+18], esi 00501983 . E8 2E8B0100 call <jmp.&d3d9.Direct3DCreate9> 00501988 . 85C0 test eax, eax 0050198A . 8947 10 mov dword ptr [edi+10], eax ; edi+10 = 58d550
代碼:
00501B4C . 8D77 14 lea esi, dword ptr [edi+14] 00501B4F . 56 push esi ; 58d554 => IDirect3DDevice9 00501B50 . 8D4F 40 lea ecx, dword ptr [edi+40] 00501B53 . 51 push ecx 00501B54 . 6A 40 push 40 00501B56 . EB 14 jmp short 00501B6C ... 00501B6C > 8B4F 18 mov ecx, dword ptr [edi+18] 00501B6F . 8B47 10 mov eax, dword ptr [edi+10] 00501B72 . 8B10 mov edx, dword ptr [eax] 00501B74 . 8B52 40 mov edx, dword ptr [edx+40] 00501B77 . 51 push ecx 00501B78 . 8B4C24 24 mov ecx, dword ptr [esp+24] 00501B7C . 51 push ecx 00501B7D . 55 push ebp 00501B7E . 50 push eax 00501B7F . FFD2 call edx ; IDirect3D9::CreateDevice
由於是所謂的COM接口,沒有十分明顯的標誌,不是很好找。
在微軟的DirectX SDK d3d9.h文件中,IDirect3D9的接口是這樣的:
代碼:
DECLARE_INTERFACE_(IDirect3D9, IUnknown) { /*** IUnknown methods ***/ STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IDirect3D9 methods ***/ STDMETHOD(RegisterSoftwareDevice)(THIS_ void* pInitializeFunction) PURE; STDMETHOD_(UINT, GetAdapterCount)(THIS) PURE; STDMETHOD(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER9* pIdentifier) PURE; STDMETHOD_(UINT, GetAdapterModeCount)(THIS_ UINT Adapter,D3DFORMAT Format) PURE; STDMETHOD(EnumAdapterModes)(THIS_ UINT Adapter,D3DFORMAT Format,UINT Mode,D3DDISPLAYMODE* pMode) PURE; STDMETHOD(GetAdapterDisplayMode)(THIS_ UINT Adapter,D3DDISPLAYMODE* pMode) PURE; STDMETHOD(CheckDeviceType)(THIS_ UINT Adapter,D3DDEVTYPE DevType,D3DFORMAT AdapterFormat,D3DFORMAT BackBufferFormat,BOOL bWindowed) PURE; STDMETHOD(CheckDeviceFormat)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat) PURE; STDMETHOD(CheckDeviceMultiSampleType)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType,DWORD* pQualityLevels) PURE; STDMETHOD(CheckDepthStencilMatch)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat) PURE; STDMETHOD(CheckDeviceFormatConversion)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SourceFormat,D3DFORMAT TargetFormat) PURE; STDMETHOD(GetDeviceCaps)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS9* pCaps) PURE; STDMETHOD_(HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) PURE; STDMETHOD(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE; #ifdef D3D_DEBUG_INFO LPCWSTR Version; #endif };
CreateDevice是第17個函數,所以它的地址是(17-1)*4 = 0x40,
00501B74 . 8B52 40 mov edx, dword ptr [edx+40]
這裏的edx+40就是這麼來的。
如果覺得算起來很麻煩的話,還有一個簡單的方法,可以自己編譯一個d3d9的程序,然後反彙編看看。
如果還覺得麻煩,還有個更簡單的方法,直接往下找,通常會有一些調試文本能夠幫助定位,例如:
00501B86 . BB 600B5400 mov ebx, 00540B60 ; ASCII "CreateDevice"
...
00501BBB . BB 700B5400 mov ebx, 00540B70 ; ASCII "CreateDevice failed again"
當然,這個辦法並不通用,有效與否完全要看遊戲作者的臉色。
知道了IDirect3DDevice9的地址,就能植入我們自己的初始化ID3DXFont的代碼。
有了ID3DXFont,下面就是要找到用於顯示文字的函數,並用我們自己的代碼來替換實現。
隨便找一段遊戲中出現的文字,比如開始菜單上出現的“Full 1.37”,ALT-M,進入內存窗口,在所有搜到的該字符串上下“內存訪問”斷點,最終會找到地址在4FFF50的一個函數。
該函數返回時,用的是“retn 14”,在4FFF50處用“retn 14”改寫,切回到遊戲後沒有任何字符出現,說明這個函數正是用來顯示字符的。
原諒我在這裏對於怎麼找到4FFF50的過程含糊其辭,一筆帶過了。確實沒有什麼取巧的方法,完全取決於破解的功力,良好的耐力,以及一點點好運氣。而這也正是遊戲漢化的難點所在。
找到字符串顯示的函數後,還要弄清楚該函數的接口。
在屏幕上顯示一個字符串,通常需要知道這些要素:字符串、顯示的位置(X,Y座標)、顏色、字符的大小,以及一些flag用於表示左對齊,右對齊,居中,加粗,傾斜等。
前面說過,返回用的是“retn 14”,說明棧裏有20(10進制的14h)/4=5個參數,當進入該函數時,棧上的數據是這樣的:
代碼:
0012FE84 00453C48 返回到 GSB_1_37.00453C48 來自 GSB_1_37.004FFF50 0012FE88 0012FEB8 ASCII "Full 1.37" 0012FE8C 00000000 0012FE90 40400000 0012FE94 FFFFFFFF 0012FE98 447D8000
回到調用004FFF50的地方,在00453C1D下斷點:
代碼:
00453C1D |> \D94424 14 fld dword ptr [esp+14] 00453C21 |. 51 push ecx 00453C22 |. D91C24 fstp dword ptr [esp] ; 參數5 1024.0 00453C25 |. 6A FF push -1 ; 參數4 00453C27 |. D905 F4125400 fld dword ptr [5412F4] 00453C2D |. 83EC 08 sub esp, 8 00453C30 |. D95C24 04 fstp dword ptr [esp+4] ; 參數3 3.0 00453C34 |. 8BD0 mov edx, eax 00453C36 |. D9EE fldz 00453C38 |. D91C24 fstp dword ptr [esp] ; 參數2 0.0 00453C3B |. 56 push esi ; 參數1 00453C3C |. BE 01000000 mov esi, 1 00453C41 |. 8BCE mov ecx, esi 00453C43 |. E8 08C30A00 call 004FFF50
可以看到,參數4是一個固定值,第二、三、五參數都是浮點數。
這裏有一個技巧,在函數開始的地方修改參數的值,看看會發生什麼變化,很快就能知道參數的作用。
參數4是顏色值,Alpha和RGB值都是FF,就是白色,與在遊戲中看到的字符顏色相同。
參數2是X座標,參數3是Y座標,參數5用於調整。
需要注意的是,還有兩個參數是通過寄存器ECX和EDX傳遞的,ECX用於表示左對齊(0),右對齊(1)和居中(2),EDX是固定值58D6D0,一個全局的結構或類。
此遊戲有兩種字體,因此用於顯示字符的幾大要素,現在還缺一個,就是不知道如何判斷字符的大小。
在用於顯示文字的004FFF50的上下斷點,多跟幾次,就會發現,每次調用以前,都會調用一個call:
代碼:
00453BD5 |. BE 44035300 mov esi, 00530344 ; ASCII "zekton16.dds" 00453BDA |. E8 91B50A00 call 004FF170 004479E7 |. BE 54035300 mov esi, 00530354 ; ASCII "cwfont20.dds" 004479EC |. E8 7F770B00 call 004FF170
總結一下:
第一步,找到IDirect3DDevice9句柄,用於初始化ID3DXFont。
第二步,找到顯示文字的函數,用自己的代碼實現之。
第三步,逐步找到其他需要修改的地方,比如用於得到字符串寬度、高度,指定寬度的字符串換行顯示等函數。
【具體實現】
我用的是注入dll,然後打內存補丁的方式。這樣的好處是便於更新,可以任意修改實現過程,以後遊戲出了新版本,也只要修改幾個地址變量,重新編譯一下就可以了。
另外一個好處是,不以文件補丁的形式發佈,沒有版權問題。(有人關心這個嗎?)
源碼在這裏:
http://gsbzhcn.googlecode.com/svn/trunk/src
簡單的介紹一下流程:
1.CreateProcess啓動遊戲的exe,並使之處於掛起狀態。
2.VirtualAllocEx在遊戲進程上申請一塊內存,WriteProcessMemory往裏寫入要注入的dll名稱。
3.GetProcAddress得到LoadLibraryA的地址。
4.CreateRemoteThread運行LoadLibraryA,注入dll,dll載入時會爲遊戲進程打上補丁。
6.WaitForSingleObject等候dll載入完成。
7.VirtualFreeEx清理掉之前申請的內存。
8.ResumeThread讓打過補丁的遊戲進程運行起來。
內存補丁主要是讓遊戲在關鍵的地方跳轉到我們的dll,執行一段代碼後再跳回去,或者直接用dll裏的函數代替之。
【結尾】
此遊戲的漢化正在http://code.google.com/p/gsbzhcn/ 進行,文本不多,奈何翻譯人手也不多,希望有興趣的同學能夠參與。
--------------------------------------------------------------------------------
【版權聲明】: 本文原創於看雪技術論壇, 轉載請註明作者並保持文章的完整, 謝謝!
2010年05月25日 14:52:11