本帖最后由 shane007 于 2011-1-30 14:13 编辑 0 o3 @8 F" |6 O$ q
' V1 T3 H# R# J' }4 d2 Q作 者: noword_forever$ X; v) C2 W6 `& |9 \
时 间: 2010-05-25,14:52:51
) U1 o# t( B; `* N2 Z链 接: http://bbs.pediy.com/showthread.php?t=113739
5 p7 C; W, ~. p/ w; L6 Q- p# M4 V! @4 g4 l3 J
【文章标题】: DirectX 9 游戏汉化详解
9 |7 ]6 x* H H) s4 l! ?5 o+ |【文章作者】: noword
8 V. k- e$ G% I+ j【软件名称】: 无厘头太空战役/ }3 E$ R; d3 C
【下载地址】: http://www.verycd.com/topics/2819995/
1 C. T! }, R8 @4 b9 ^. }--------------------------------------------------------------------------------
$ r% r8 N# h7 e* m( W: G 【前言】: R0 N: P. s1 W8 r$ Z8 s
先copy一段此游戏介绍:# G3 G+ u- a1 t6 Q& { U9 r
( O7 U2 ?, u2 x2 C4 q. {) d! N 这是一个独特的战略游戏,具有即时战略与塔防的混合风格,玩家将扮演庞大太空舰队的最高指挥官,你可以自定飞船的构造,摆放飞船的位置,下达命令,然后观看绚丽的射击与爆炸。移动和爆炸时会有动态模糊效果。支持自定义地图。/ H3 @% S4 f. q, v# x/ X# B& h
8 D6 o: t# q; y
想玩中文版,两个游戏论坛,3DM和YX上,都有人说要汉化,等了几个月,没有下文,说是技术原因。于是决定自己来试试看。8 c8 T! `! j; z( L. @
) K, R+ G2 m; ]. C. i* t" ]
: Z J# p' p5 S 【困难何在】$ |; G t1 j% [% r! D) ]% C
此游戏的文本都在data目录下,都是明文的文本文件。修改data\strings.ini,将
* p0 D$ i! S1 D, E5 e; \
# ~7 u/ N, R; G1 X0 V代码:# ~: k' T) ]' \
MAINMENU_QUIT = "Exit"7 e( u- C" U$ f, O* f
& X! H6 b0 H2 w. A2 x4 `
改成
7 O0 N8 m. w3 c2 B1 i- T+ ] & }7 G6 r: C2 U! D
代码:
5 X9 h' l% _* Z# Y( j$ J( h$ Q, ? MAINMENU_QUIT = "退出"4 S! X" a7 R" r8 A% C0 @: `; s
0 s; w1 y/ Z8 m' N# S
( y" a0 p5 B5 d! K$ F8 v& S 进入游戏后,不出所料,无法显示此中文。
: o5 u. b$ h9 |) x$ c6 y
* v8 C& E6 E9 H# r, L( H5 N5 V 茫茫多的游戏爱好者,在尝试汉化某款自己心仪的游戏时,都是死在了这一步——找了半天文本资源,然后翻成中文,满心欢喜和期待的进入游戏,面对的却是一堆乱码或一片空白。满腔热情,化为乌有,无可奈何,黯然神伤。
' I$ q# A, l& g6 R
7 [- Y% l6 a6 W- b 本文的目的,就是希望能够帮助这些有志于游戏汉化的同学,主要介绍了如何让一个英文的游戏,能够正确的显示出中文。
7 x# y3 K1 O i 1 {& k7 s2 L+ U W7 N
/ |; _1 \2 c+ g* O! Z) K4 G8 Y
【调试分析】1 b1 z9 W5 ]# Q' q# o0 }! \; L
DirectX 9游戏的启动流程是这样的,先执行Direct3DCreate9,返回值是一个IDirect3D9句柄,然后执行IDirect3D9->CreateDevice,得到IDirect3DDevice9句柄。
; u; w: F$ O' y 有了IDirect3DDevice9就能使用DirectX 9的一切绘图手段,而我们最关心的就是能够使用D3DXCreateFont来创建ID3DXFont,继而能够非常方便快捷的在游戏中显示文字。
8 a- l8 W7 }, N2 l9 o* l3 ?7 \! A) V
9 j5 m3 U- ]% `4 c, d 用ODBG载入游戏的exe文件,“查找所有模块间的调用”,找“d3d9.Direct3DCreate9”:/ }5 X* c0 ?9 ?8 J+ y$ d3 D/ ^
. D9 i/ }( G+ \; x/ J: c代码:
( n" W- k4 \3 y- h9 i 00501974 . BB 500A5400 mov ebx, 00540A50 ; ASCII "Initialising 3D Engine"
5 g D2 u- u# D/ G, b7 }% M 00501979 . E8 22130000 call 00502CA0) F; @, v) f `( u/ c( D5 z
0050197E . 6A 20 push 20) m' W5 Z) ^9 Q; k1 T1 x7 A- }
00501980 . 8977 18 mov dword ptr [edi+18], esi
% Z2 u- Z* ~4 \9 Z 00501983 . E8 2E8B0100 call <jmp.&d3d9.Direct3DCreate9>; D9 F9 R1 z* t
00501988 . 85C0 test eax, eax% j7 j/ h* M3 q, {7 X4 P0 M4 F& e
0050198A . 8947 10 mov dword ptr [edi+10], eax ; edi+10 = 58d550
% r, h" Z7 {* D7 M: m ! z1 Q# C- O7 f" b" r" L
往下找,就能找到IDirect3D9->CreateDevice:
1 p' v1 i# M6 {$ j; A
& [. F& h6 j, f6 O( v代码:( m" P! y3 @ ?; r" f
00501B4C . 8D77 14 lea esi, dword ptr [edi+14]2 E) s+ z2 v% u* E/ W& s" Z# m3 E4 t' @
00501B4F . 56 push esi ; 58d554 => IDirect3DDevice9
* m5 g5 b& d1 }+ d7 i6 X! s 00501B50 . 8D4F 40 lea ecx, dword ptr [edi+40]
" Q: S; _: s+ n3 Y q 00501B53 . 51 push ecx9 \. x) u o' E! q( o% `% d
00501B54 . 6A 40 push 407 [8 z& y+ r9 t) w" |1 ^
00501B56 . EB 14 jmp short 00501B6C( Z2 {% ?, ^, n( l% T7 e# W6 t
...
! k6 \ [2 Q* E1 h$ ?) [' m 00501B6C > 8B4F 18 mov ecx, dword ptr [edi+18]0 A6 u X9 |$ o# j
00501B6F . 8B47 10 mov eax, dword ptr [edi+10]8 [' K) r# N7 ?) p4 b
00501B72 . 8B10 mov edx, dword ptr [eax]# L6 O) {$ R6 R; e0 q8 @4 S
00501B74 . 8B52 40 mov edx, dword ptr [edx+40]2 U9 v9 e# P! M4 H/ L' O
00501B77 . 51 push ecx
- N# P6 t0 l4 K8 f7 r 00501B78 . 8B4C24 24 mov ecx, dword ptr [esp+24]; l, ?+ s- R1 ]3 c* X
00501B7C . 51 push ecx
1 [% ^; y5 M; {' n% n6 X 00501B7D . 55 push ebp9 @$ r) p0 i5 z# C+ v
00501B7E . 50 push eax
' G4 q: O. p/ c' L 00501B7F . FFD2 call edx ; IDirect3D9::CreateDevice
6 q$ s' s. K" m3 e: G' X" | + l4 w8 E! F( [, J" J; o1 n+ r7 e
" M1 |, v7 ]- P- l
由于是所谓的COM接口,没有十分明显的标志,不是很好找。
0 f+ n* \# d: q& X ) J* y7 F6 g& v6 P" `
在微软的DirectX SDK d3d9.h文件中,IDirect3D9的接口是这样的:" A' a1 i& L# M: ? {7 c
& D: h# V, w. |. ]+ j
( u. C, U2 }( I0 \) h6 `
代码:# z- ?' C) v% @/ l' B* `
DECLARE_INTERFACE_(IDirect3D9, IUnknown)" n1 S$ E0 l, [0 A$ H4 x. U5 h$ }
{; M* \5 ^: R& t! \* f, }; l7 V
/*** IUnknown methods ***/
- P& Y7 }! y- d STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE;, x/ x5 @: P& j' E6 Z& H
STDMETHOD_(ULONG,AddRef)(THIS) PURE; O1 `% W1 Q+ J) M
STDMETHOD_(ULONG,Release)(THIS) PURE;- r4 Q1 K2 k" L
( z" j7 e4 \/ X- Z
/*** IDirect3D9 methods ***/; G, O. a9 L+ @8 N, R
STDMETHOD(RegisterSoftwareDevice)(THIS_ void* pInitializeFunction) PURE;5 j" {( c9 b( ] y3 _6 h Q; S. T
STDMETHOD_(UINT, GetAdapterCount)(THIS) PURE;
0 s0 O0 n& A) m7 s& {8 T STDMETHOD(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags,D3DADAPTER_IDENTIFIER9* pIdentifier) PURE;
3 ?# L$ P3 m6 N/ q$ Y STDMETHOD_(UINT, GetAdapterModeCount)(THIS_ UINT Adapter,D3DFORMAT Format) PURE;6 x/ u( J# h6 j8 ]
STDMETHOD(EnumAdapterModes)(THIS_ UINT Adapter,D3DFORMAT Format,UINT Mode,D3DDISPLAYMODE* pMode) PURE;$ P% q- X8 F3 z& O
STDMETHOD(GetAdapterDisplayMode)(THIS_ UINT Adapter,D3DDISPLAYMODE* pMode) PURE;
/ l3 F( \! Y1 u) l- I STDMETHOD(CheckDeviceType)(THIS_ UINT Adapter,D3DDEVTYPE DevType,D3DFORMAT AdapterFormat,D3DFORMAT BackBufferFormat,BOOL bWindowed) PURE;
( X; d3 E/ b7 X# r2 S STDMETHOD(CheckDeviceFormat)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,DWORD Usage,D3DRESOURCETYPE RType,D3DFORMAT CheckFormat) PURE;* ~! ?4 x4 G: l! Y$ t
STDMETHOD(CheckDeviceMultiSampleType)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SurfaceFormat,BOOL Windowed,D3DMULTISAMPLE_TYPE MultiSampleType,DWORD* pQualityLevels) PURE;9 f5 ^' k1 @$ i1 t
STDMETHOD(CheckDepthStencilMatch)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT AdapterFormat,D3DFORMAT RenderTargetFormat,D3DFORMAT DepthStencilFormat) PURE;' Z3 @7 s6 j) Y+ \# L
STDMETHOD(CheckDeviceFormatConversion)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DFORMAT SourceFormat,D3DFORMAT TargetFormat) PURE;
7 S$ Q' p9 m1 L STDMETHOD(GetDeviceCaps)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,D3DCAPS9* pCaps) PURE;
- |" S q6 `( E4 L% J STDMETHOD_(HMONITOR, GetAdapterMonitor)(THIS_ UINT Adapter) PURE;
* l0 c& f' [! h c5 m8 C! I STDMETHOD(CreateDevice)(THIS_ UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) PURE;
% w' h3 n0 I( f# c) H9 o
0 u3 K3 M( g! J5 n5 H #ifdef D3D_DEBUG_INFO t+ P0 p$ f; p( x! T
LPCWSTR Version;# K3 J5 z9 G, N" D( q
#endif
1 E$ | `6 ], `* t };1 w( A' t2 M6 y- M+ X
- l0 L: C$ T4 w, T$ U! J
' T3 t$ k4 O# b; \/ q, h% m/ w
CreateDevice是第17个函数,所以它的地址是(17-1)*4 = 0x40,/ X: q# Z& @" f' q$ M: ^( ^
00501B74 . 8B52 40 mov edx, dword ptr [edx+40]
* H. X1 G4 H% K- f0 b 这里的edx+40就是这么来的。
# ^) }7 _5 S4 H: J* h3 v / n3 W( [6 V1 Z
如果觉得算起来很麻烦的话,还有一个简单的方法,可以自己编译一个d3d9的程序,然后反汇编看看。
1 n4 k4 F# M/ i2 \
% s& X* X& G" n* ], P3 Y6 {! v 如果还觉得麻烦,还有个更简单的方法,直接往下找,通常会有一些调试文本能够帮助定位,例如:; |9 R! x" M! C/ @1 L% v. e0 I
00501B86 . BB 600B5400 mov ebx, 00540B60 ; ASCII "CreateDevice"
r8 U9 y i5 r) o- B ...
5 o/ {5 M0 k5 p! t' m0 U 00501BBB . BB 700B5400 mov ebx, 00540B70 ; ASCII "CreateDevice failed again"
, x, { p- ? F: O 当然,这个办法并不通用,有效与否完全要看游戏作者的脸色。/ T6 y3 I# u, b- s, ~. P* Y/ e# b
; E1 n# N- ~" l6 j+ P$ G
知道了IDirect3DDevice9的地址,就能植入我们自己的初始化ID3DXFont的代码。" d& b9 a# ]. I
: K* e3 s' P$ y5 E 有了ID3DXFont,下面就是要找到用于显示文字的函数,并用我们自己的代码来替换实现。& _) T9 {9 P/ {" ~7 [6 [, I
" F) j S( `1 _) { @ 随便找一段游戏中出现的文字,比如开始菜单上出现的“Full 1.37”,ALT-M,进入内存窗口,在所有搜到的该字符串上下“内存访问”断点,最终会找到地址在4FFF50的一个函数。" h6 `. A2 h' ~
+ z" M, [6 m/ [7 p
该函数返回时,用的是“retn 14”,在4FFF50处用“retn 14”改写,切回到游戏后没有任何字符出现,说明这个函数正是用来显示字符的。2 c6 A' i! S* G& V5 E% h! L3 _
" h# N" ?8 W/ p2 c$ ` 原谅我在这里对于怎么找到4FFF50的过程含糊其辞,一笔带过了。确实没有什么取巧的方法,完全取决于破解的功力,良好的耐力,以及一点点好运气。而这也正是游戏汉化的难点所在。
' }. B4 b$ B7 p2 P$ a& d' r ! a7 B2 I9 p' S( U* v7 Y0 p0 x _
找到字符串显示的函数后,还要弄清楚该函数的接口。7 u: ^ P7 d# o# b$ y' H( z
9 O: z1 C4 c( S% }5 `$ g 在屏幕上显示一个字符串,通常需要知道这些要素:字符串、显示的位置(X,Y坐标)、颜色、字符的大小,以及一些flag用于表示左对齐,右对齐,居中,加粗,倾斜等。: g, j! g' N3 ^
8 i" e1 h* x8 v% q& @+ L1 W
前面说过,返回用的是“retn 14”,说明栈里有20(10进制的14h)/4=5个参数,当进入该函数时,栈上的数据是这样的:, j# x9 g$ ^4 d# L4 d1 C
3 }+ }; G1 x; a$ r) D
代码:2 m1 d' D: Q. A& _; b/ I8 D
0012FE84 00453C48 返回到 GSB_1_37.00453C48 来自 GSB_1_37.004FFF50
8 Z# |9 l: F3 Y& \9 H 0012FE88 0012FEB8 ASCII "Full 1.37"
r, M$ B% ? [ k7 M6 Y" P. w( } 0012FE8C 00000000
2 @4 I0 e/ |$ a, I L 0012FE90 40400000
* _9 {" T+ j2 ?2 Z5 y4 p. e 0012FE94 FFFFFFFF
$ o" M6 @# B- Z 0012FE98 447D8000( w4 c* n- h# e' K8 ?8 m
$ l4 r! J& L+ e3 t 第一个,很明显就是要显示的字符串,后面几个是什么玩意儿呢?
( t M: f$ v3 l& @( `
& k- F6 t5 g5 C6 H9 u 回到调用004FFF50的地方,在00453C1D下断点:
' ?! G9 t) n6 _# h: X) o. W + O0 {& F4 l4 J. d% R5 I; c9 z
代码:6 F( y; W! @) X
00453C1D |> \D94424 14 fld dword ptr [esp+14]
4 u# o8 L( v/ e8 }8 ~1 t 00453C21 |. 51 push ecx
- s. K: s0 b4 m3 t: s" B; n 00453C22 |. D91C24 fstp dword ptr [esp] ; 参数5 1024.0, _6 y- H8 d4 } A1 g+ C
00453C25 |. 6A FF push -1 ; 参数4
$ o" ~! u/ c' R; k2 y0 Z3 v 00453C27 |. D905 F4125400 fld dword ptr [5412F4]
x# c& b% e* C' m 00453C2D |. 83EC 08 sub esp, 8
" E# @3 E- E5 U* ]0 r: G z 00453C30 |. D95C24 04 fstp dword ptr [esp+4] ; 参数3 3.0) a6 M2 T( G U- ~. g J' j) c
00453C34 |. 8BD0 mov edx, eax D% Y* D2 G0 p7 I
00453C36 |. D9EE fldz, q% l7 E g. A
00453C38 |. D91C24 fstp dword ptr [esp] ; 参数2 0.0
* @% [* O4 C) _' `* M7 I* x 00453C3B |. 56 push esi ; 参数1
) v7 Y0 M0 ?# D3 V7 t# S. t 00453C3C |. BE 01000000 mov esi, 1
. T5 E8 {% ^! Z; a6 k( E 00453C41 |. 8BCE mov ecx, esi+ w- p7 I0 [& @
00453C43 |. E8 08C30A00 call 004FFF50
5 M7 g/ I$ p4 T1 ~) q a6 v/ a8 d / x$ A# \ d: `5 H6 c8 i
- a2 K4 b! d: o" u& S- n 可以看到,参数4是一个固定值,第二、三、五参数都是浮点数。
. s6 F4 A; I, C5 e 0 K# m5 G! b8 V2 H/ D @
这里有一个技巧,在函数开始的地方修改参数的值,看看会发生什么变化,很快就能知道参数的作用。; z2 _+ b& P2 Z$ y0 X
9 J8 _. z( ]2 W5 g6 J
参数4是颜色值,Alpha和RGB值都是FF,就是白色,与在游戏中看到的字符颜色相同。
1 M/ r' x+ l3 O g* a 参数2是X坐标,参数3是Y坐标,参数5用于调整。
% O7 |/ U+ h9 l, c; d: _5 X5 l 需要注意的是,还有两个参数是通过寄存器ECX和EDX传递的,ECX用于表示左对齐(0),右对齐(1)和居中(2),EDX是固定值58D6D0,一个全局的结构或类。. r: X" I% \! y6 n3 r& |
! a, }7 Q0 V( L) N2 [& L! w
此游戏有两种字体,因此用于显示字符的几大要素,现在还缺一个,就是不知道如何判断字符的大小。
8 _. {) O9 e# k7 r' R
8 |3 s+ \" t" s# M7 }& ^+ } 在用于显示文字的004FFF50的上下断点,多跟几次,就会发现,每次调用以前,都会调用一个call:, c; L: n! u- c8 ]1 z2 D g* ~! r
# T6 \# J. q3 b
代码:
' T) ?6 j9 a7 V7 L0 ~, I; d 00453BD5 |. BE 44035300 mov esi, 00530344 ; ASCII "zekton16.dds"
, w/ x" L! _8 D! y$ F 00453BDA |. E8 91B50A00 call 004FF170
+ f* c( f) [5 E4 _
- d, Q8 E& J: S& V* z: h( i 004479E7 |. BE 54035300 mov esi, 00530354 ; ASCII "cwfont20.dds"
5 u S. t1 d3 y1 V6 k6 W$ i! i9 \! G 004479EC |. E8 7F770B00 call 004FF170
" r" |1 @ T8 k5 b4 k/ X
a: H, h2 H: P" Z 在游戏目录data\font下面有两个文件zekton16.dds.dat和cwfont20.dds.dat,由此判断004FF170应该是用于选择字体的函数。5 Q4 c" e5 N8 R, k1 \4 C
' Q }" K; \, A- I( R* V 总结一下:
+ R: T) h" E( w6 T" b. A 第一步,找到IDirect3DDevice9句柄,用于初始化ID3DXFont。. F/ S; F- ^7 y, _2 V$ v
第二步,找到显示文字的函数,用自己的代码实现之。" J7 J q/ U9 k- S6 E+ x
第三步,逐步找到其他需要修改的地方,比如用于得到字符串宽度、高度,指定宽度的字符串换行显示等函数。
4 g" S& _+ P, Q
. q( p+ W. a# N/ A& x3 l: _
5 o- M& S; A& ^( j8 P, j4 h- x 【具体实现】- ~; `1 l6 R1 o! h/ C) h0 h
我用的是注入dll,然后打内存补丁的方式。这样的好处是便于更新,可以任意修改实现过程,以后游戏出了新版本,也只要修改几个地址变量,重新编译一下就可以了。
+ ]1 z1 v- y0 e& P: A 另外一个好处是,不以文件补丁的形式发布,没有版权问题。(有人关心这个吗?); o9 K1 H! L6 d
$ y7 T3 U( m$ C( ` 源码在这里:0 f1 E# [+ J0 k0 s: Y
http://gsbzhcn.googlecode.com/svn/trunk/src3 g6 T; [" j; `3 K# x0 V. {4 E& n
7 f# `, f( a1 E W4 i6 m& N 简单的介绍一下流程:
1 Q- G% }" Q& b' v 1.CreateProcess启动游戏的exe,并使之处于挂起状态。, h5 [! [8 I3 W }; ]% C6 o, z
2.VirtualAllocEx在游戏进程上申请一块内存,WriteProcessMemory往里写入要注入的dll名称。
5 F; k u# q# }! ^+ `& S 3.GetProcAddress得到LoadLibraryA的地址。
5 d4 [8 n' M' k& e. k' Q7 u. ` x 4.CreateRemoteThread运行LoadLibraryA,注入dll,dll载入时会为游戏进程打上补丁。 o- y- s3 m+ z! P1 A, w
6.WaitForSingleObject等候dll载入完成。
. @4 M1 M. C" A8 O8 E 7.VirtualFreeEx清理掉之前申请的内存。$ O$ F4 M3 S2 i9 }# w6 ~
8.ResumeThread让打过补丁的游戏进程运行起来。3 Y$ m* t: R! A! Q7 d) v
! j- s; A" ~7 P4 S8 z' c$ l# g 内存补丁主要是让游戏在关键的地方跳转到我们的dll,执行一段代码后再跳回去,或者直接用dll里的函数代替之。
& X( w1 D8 b8 {2 @ 4 W" {3 `( W* I7 C+ d
- t- t4 ]# D1 m 【结尾】7 H9 }% E( h1 n2 S
此游戏的汉化正在http://code.google.com/p/gsbzhcn/ 进行,文本不多,奈何翻译人手也不多,希望有兴趣的同学能够参与。6 m1 E0 Z+ a& k
$ n, M9 H r# V" P--------------------------------------------------------------------------------2 C* f" H6 y6 e) N1 \
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
% `1 p$ L6 V5 P& |# j5 X- v
( Q: x8 M& S, p+ d B o( q 2010年05月25日 14:52:11 |