冒险解谜游戏中文网 ChinaAVG
标题:
【转】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下)
[打印本页]
作者:
shane007
时间:
2011-1-30 14:06
标题:
【转】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下)
本帖最后由 shane007 于 2011-1-30 14:08 编辑
; a0 q [ D6 |" E z9 k, A
4 [' `1 a5 d' G% F+ \
在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
8 g$ W4 @! _+ S
% X, u- o: g# D
代码:
- W- e* `& _9 P' f
void WINAPI glCallLists(
# O; O5 O2 S3 Q2 P h
GLsizei n,
. V) H E+ y) E2 N# ^4 K$ D' i
GLenum type,
( Y0 t; s4 D: @ ^8 O
const GLvoid *lists
7 I4 Q, P" y0 T% l$ \8 E% m
);
! Y6 L6 ~" ]# b& Z( F2 @
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:
; o& O: m! l6 b
, i1 u' N4 x8 V
代码:
L% @: l% d" t$ W/ ?0 O+ q* ?
00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
; F- a) n+ ]" k& k
00439D42 |. 890424 mov dword ptr [esp], eax
( z- g' I9 ]) A6 K) s- M6 n
00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>
' f/ D& a5 a$ y9 y7 t+ i" F8 V
00439D4A |. 83EC 04 sub esp, 4
6 A6 ]5 }1 y; Y% j4 @
00439D4D |. 893424 mov dword ptr [esp], esi ; |
( B+ A- a8 {8 K7 t
00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen
+ S+ J2 Q8 M4 _& p% L3 X( p
00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数
0 r4 n k4 Q7 t- m( }! J
00439D58 |. 897424 08 mov dword ptr [esp+8], esi
& y' y9 x6 _% O$ r$ n8 b( `! k
00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx
7 d6 G V- X+ q3 A7 T1 s
00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>
" O% @* C. X) [/ F1 T, S
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:
$ C, {* }8 N- ?3 O. r2 ~* Z8 O$ o
( D5 k$ s, j7 D9 x9 t" N8 }
代码:
. J+ w1 R: r- S, A6 ?
#define GL_BYTE 0x1400
: L% o% r; x5 L- h$ G" a
#define GL_UNSIGNED_BYTE 0x1401
5 C B; R7 T# O. S
#define GL_SHORT 0x1402
! k8 F' P3 _9 A# ?3 }( X, S, v) ^% s. O
#define GL_UNSIGNED_SHORT 0x1403
2 I. @& n/ k/ M. }3 `3 S: W q
#define GL_INT 0x1404
9 S/ Y; T2 T7 K; v. X$ {9 V4 f5 N2 T
#define GL_UNSIGNED_INT 0x1405
6 H2 I4 _3 S3 q6 M7 Q
#define GL_FLOAT 0x1406
2 X- Y8 }. ?$ L' s& K/ R
#define GL_2_BYTES 0x1407
2 R/ A- f( ^* _+ E" F8 w v
#define GL_3_BYTES 0x1408
! @8 _* i L) n7 D& Z4 e1 |
#define GL_4_BYTES 0x1409
8 t: J7 _9 ]% H# S ]
#define GL_DOUBLE 0x140A
4 w) |$ w- i3 H* ?0 b! K$ \
无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
4 j, j7 u) t, l; x1 T( h
! N) d, g5 v# J4 [
图中可以看出,导入函数wcslen的RVA为0xB05214B,理论上只要我将call <jmp.&msvcrt.strlen>改为call [0xB05214B],便可以达到目的。但是事情总是比之前想象的复杂一点。这里如果直接改为call dword ptr [0xB05214B],目的地址与指令地址偏移量大,call为远call,指令长度为6字节,而原来的call是近call,指令长度为5字节,这将覆盖掉下面的mov dword ptr [esp], eax指令。后果将是glCallLists永远得不到正确的字符串长度。
7 }& e5 l6 D/ `# f3 ~3 K# t
不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:
8 Z6 @9 h8 t! l5 U6 i/ r0 x
[attach]18537[/attach]
* I! y5 d+ q* T2 P4 D8 @2 L
至此,对游戏引擎的修改已经结束。
2 e7 _% T% T; w; U/ S
剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。
8 [: w# d; _& f4 t+ l+ [/ N
事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。
' p: x$ t# I0 h2 f$ a
最后附上一张中文版截图:
1 ~- q4 O' ~9 t5 F% R
[attach]18536[/attach]
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2