游戏全名:Air Aces: Pacific
中文译名:空中王牌:太平洋
下载地址:http://www.verycd.com/topics/2832366/
【原创】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下)
这是一款模拟空战游戏,画面和操控性和市面上其他游戏相比较,算是比较粗糙的。不过这不是我关心的,因为游戏发行只有英文版,所以我想通过pediy的方法加入中文支持,也就是汉化。当然最好能借此找出一些通用的游戏汉化方法。
OD载入主程序,输入表如下(部分):
代码:
0B029B20 .idata 输入 OPENGL32.glRotatef 0B029B24 .idata 输入 OPENGL32.glScalef 0B029B28 .idata 输入 OPENGL32.glShadeModel 0B029B2C .idata 输入 OPENGL32.glStencilFunc 0B029B30 .idata 输入 OPENGL32.glStencilOp 0B029B34 .idata 输入 OPENGL32.glTexCoord2f 0B029B38 .idata 输入 OPENGL32.glTexCoordPointer 0B029B3C .idata 输入 OPENGL32.glTexEnvf 0B029B40 .idata 输入 OPENGL32.glTexEnvi
1,常规字体。具体方法是首先用GDI的CreateFont为当前DC创建一个字体,然后调用wglUseFontBitmaps将当前DC中的字符批量转入显示列表,然后调用glCallLists显示文本串。
2,纹理字体。即调用glGenLists创建显示列表,将字符纹理依次放入对应位置,然后调用glCallLists显示文本串。
以上两种方法要实现还是比较麻烦的,有需要的话可以参阅相关文档,这里不再赘言,毕竟我们无意开发游戏。通过对游戏的简单调试分析,可以确定,游戏使用的是第二种方法,也就是使用纹理字体来显示文字。并且很容易就能在gfx目录找到这个用于存放字体的纹理png,如图:
看到这图有人可能会说,只要把字母ps成汉字,就可以显示中文了。的确是这样的,但是英文字母只有26个,而翻译完文本后使用的汉字远不止26,所以我们必须想办法扩容字库。首先应该统计翻译时使用了多少个汉字,用这些汉字重新建立一张码表。然后根据码表扩容上面的字库。
这也正是一些单字节游戏在汉化时的难点所在:必须要找到游戏生成显示列表的代码并修改。所幸的是,这款游戏比我想象的简单。
既然已经确定了是第二种方法,那么在glGenLists下断试试,选中输入函数直接回车,查找输入函数参考发现以下几处调用:
代码:
参考位于 AAP:.text 到 OPENGL32.glGenLists 地址 反汇编 注释 00415872 call <jmp.&OPENGL32.glGenLists> 0041595B call <jmp.&OPENGL32.glGenLists> 00439A56 call <jmp.&OPENGL32.glGenLists> 0048E748 jmp dword ptr [<&OPENGL32.glGenLists OPENGL32.glGenLists
代码:
0041586B . C70424 010000>mov dword ptr [esp], 1 00415872 . E8 D18E0700 call <jmp.&OPENGL32.glGenLists>
代码:
00415954 . C70424 010000>mov dword ptr [esp], 1 0041595B . E8 E88D0700 call <jmp.&OPENGL32.glGenLists>
代码:
00439A4F |> \C70424 000100>mov dword ptr [esp], 100 00439A56 |. E8 ED4C0500 call <jmp.&OPENGL32.glGenLists>
我们详细看看00439A56处的call完整的函数:
代码:
00439A20 /$ 55 push ebp 00439A21 |. 89E5 mov ebp, esp 00439A23 |. 57 push edi 00439A24 |. 56 push esi 00439A25 |. 53 push ebx 00439A26 |. 83EC 3C sub esp, 3C 00439A29 |. 8B7D 0C mov edi, dword ptr [ebp+C] 00439A2C |. C70424 000000>mov dword ptr [esp], 0 00439A33 |. E8 88030000 call 00439DC0 ; 这个call在这里无实际用途,无视 00439A38 |. 8B45 08 mov eax, dword ptr [ebp+8] 00439A3B |. 890424 mov dword ptr [esp], eax 00439A3E |. E8 AD040000 call 00439EF0 ; 加载字符png,并返回纹理 00439A43 |. 8945 E4 mov dword ptr [ebp-1C], eax 00439A46 |. 85C0 test eax, eax 00439A48 |. 74 05 je short 00439A4F 00439A4A |. A3 38FD010B mov dword ptr [B01FD38], eax 00439A4F |> C70424 000100>mov dword ptr [esp], 100 00439A56 |. E8 ED4C0500 call <jmp.&OPENGL32.glGenLists> ; 创建256个显示列表 00439A5B |. A3 34FD010B mov dword ptr [B01FD34], eax 00439A60 |. 8B0D 38FD010B mov ecx, dword ptr [B01FD38] 00439A66 |. 83EC 04 sub esp, 4 00439A69 |. C70424 E10D00>mov dword ptr [esp], 0DE1 00439A70 |. 894C24 04 mov dword ptr [esp+4], ecx 00439A74 |. E8 D74D0500 call <jmp.&OPENGL32.glBindTexture> ; 绑定刚刚返回的纹理至0x0DE1,即GL_TEXTURE_2D 00439A79 |. 31D2 xor edx, edx 00439A7B |. 83EC 08 sub esp, 8 00439A7E |. 8915 30FD010B mov dword ptr [B01FD30], edx 00439A84 |. 8DB6 00000000 lea esi, dword ptr [esi] 00439A8A |. 8DBF 00000000 lea edi, dword ptr [edi] 00439A90 |> 8B0D 30FD010B mov ecx, dword ptr [B01FD30] 00439A96 |. 31D2 xor edx, edx 00439A98 |. 31C0 xor eax, eax 00439A9A |. 52 push edx 00439A9B |. 8B15 34FD010B mov edx, dword ptr [B01FD34] 00439AA1 |. 89CE mov esi, ecx 00439AA3 |. 83E6 0F and esi, 0F 00439AA6 |. 89CB mov ebx, ecx 00439AA8 |. C1EB 04 shr ebx, 4 00439AAB |. 56 push esi 00439AAC |. 01D1 add ecx, edx 00439AAE |. BE 00130000 mov esi, 1300 00439AB3 |. DF2C24 fild qword ptr [esp] 00439AB6 |. 83C4 08 add esp, 8 00439AB9 |. 50 push eax 00439ABA |. 53 push ebx 00439ABB |. D95D E8 fstp dword ptr [ebp-18] 00439ABE |. D905 00664F00 fld dword ptr [4F6600] ; 0.0625 00439AC4 |. D84D E8 fmul dword ptr [ebp-18] 00439AC7 |. D95D E8 fstp dword ptr [ebp-18] 00439ACA |. DF2C24 fild qword ptr [esp] 00439ACD |. 83C4 08 add esp, 8 00439AD0 |. 890C24 mov dword ptr [esp], ecx 00439AD3 |. 897424 04 mov dword ptr [esp+4], esi 00439AD7 |. D95D E0 fstp dword ptr [ebp-20] 00439ADA |. D905 00664F00 fld dword ptr [4F6600] ; 0.0625 00439AE0 |. D84D E0 fmul dword ptr [ebp-20] 00439AE3 |. D95D E0 fstp dword ptr [ebp-20] 00439AE6 |. E8 554C0500 call <jmp.&OPENGL32.glNewList> ; 开始创建显示列表 00439AEB |. 83EC 08 sub esp, 8 00439AEE |. C70424 070000>mov dword ptr [esp], 7 00439AF5 |. E8 464D0500 call <jmp.&OPENGL32.glBegin> ; 7为GL_QUADS,即使用四边形显示每一个字符 00439AFA |. D945 E0 fld dword ptr [ebp-20] 00439AFD |. 83EC 04 sub esp, 4 00439B00 |. D805 04664F00 fadd dword ptr [4F6604] ; 16.0 00439B06 |. D95D E0 fstp dword ptr [ebp-20] 00439B09 |. D905 00664F00 fld dword ptr [4F6600] ; 0.0625 00439B0F |. D845 E0 fadd dword ptr [ebp-20] 00439B12 |. D95D DC fstp dword ptr [ebp-24] 00439B15 |. 8B5D DC mov ebx, dword ptr [ebp-24] 00439B18 |. D905 00664F00 fld dword ptr [4F6600] ; 0.0625 00439B1E |. D845 E8 fadd dword ptr [ebp-18] 00439B21 |. 895C24 04 mov dword ptr [esp+4], ebx 00439B25 |. D95D DC fstp dword ptr [ebp-24] 00439B28 |. 8B75 DC mov esi, dword ptr [ebp-24] 00439B2B |. 893424 mov dword ptr [esp], esi 00439B2E |. E8 5D4C0500 call <jmp.&OPENGL32.glTexCoord2f> 00439B33 |. 83EC 08 sub esp, 8 00439B36 |. 31C0 xor eax, eax 00439B38 |. 894424 04 mov dword ptr [esp+4], eax 00439B3C |. 893C24 mov dword ptr [esp], edi 00439B3F |. E8 544D0500 call <jmp.&OPENGL32.glVertex2i> 00439B44 |. D945 E8 fld dword ptr [ebp-18] 00439B47 |. 83EC 08 sub esp, 8 00439B4A |. 895C24 04 mov dword ptr [esp+4], ebx 00439B4E |. 31DB xor ebx, ebx 00439B50 |. D91C24 fstp dword ptr [esp] 00439B53 |. E8 384C0500 call <jmp.&OPENGL32.glTexCoord2f> 00439B58 |. 83EC 08 sub esp, 8 00439B5B |. 895C24 04 mov dword ptr [esp+4], ebx 00439B5F |. C70424 000000>mov dword ptr [esp], 0 00439B66 |. E8 2D4D0500 call <jmp.&OPENGL32.glVertex2i> 00439B6B |. D945 E0 fld dword ptr [ebp-20] 00439B6E |. 83EC 08 sub esp, 8 00439B71 |. D95C24 04 fstp dword ptr [esp+4] 00439B75 |. D945 E8 fld dword ptr [ebp-18] 00439B78 |. D91C24 fstp dword ptr [esp] 00439B7B |. E8 104C0500 call <jmp.&OPENGL32.glTexCoord2f> 00439B80 |. 83EC 08 sub esp, 8 00439B83 |. 897C24 04 mov dword ptr [esp+4], edi 00439B87 |. C70424 000000>mov dword ptr [esp], 0 00439B8E |. E8 054D0500 call <jmp.&OPENGL32.glVertex2i> 00439B93 |. D945 E0 fld dword ptr [ebp-20] 00439B96 |. 83EC 08 sub esp, 8 00439B99 |. 893424 mov dword ptr [esp], esi 00439B9C |. D95C24 04 fstp dword ptr [esp+4] 00439BA0 |. E8 EB4B0500 call <jmp.&OPENGL32.glTexCoord2f> 00439BA5 |. 83EC 08 sub esp, 8 00439BA8 |. 897C24 04 mov dword ptr [esp+4], edi 00439BAC |. 893C24 mov dword ptr [esp], edi 00439BAF |. E8 E44C0500 call <jmp.&OPENGL32.glVertex2i> 00439BB4 |. 83EC 08 sub esp, 8 00439BB7 |. E8 744C0500 call <jmp.&OPENGL32.glEnd> ; 四边形字符绘制完成 00439BBC |. D97D F2 fstcw word ptr [ebp-E] 00439BBF |. 8B15 14304F00 mov edx, dword ptr [4F3014] 00439BC5 |. D9EE fldz 00439BC7 |. 0FB745 F2 movzx eax, word ptr [ebp-E] 00439BCB |. DD5424 10 fst qword ptr [esp+10] 00439BCF |. 89D1 mov ecx, edx 00439BD1 |. C1E1 05 shl ecx, 5 00439BD4 |. DD5C24 08 fstp qword ptr [esp+8] 00439BD8 |. 29D1 sub ecx, edx 00439BDA |. 66:0D 000C or ax, 0C00 00439BDE |. 51 push ecx 00439BDF |. DB0424 fild dword ptr [esp] 00439BE2 |. D80D 08664F00 fmul dword ptr [4F6608] 00439BE8 |. 66:8945 F0 mov word ptr [ebp-10], ax 00439BEC |. D96D F0 fldcw word ptr [ebp-10] 00439BEF |. DB5D EC fistp dword ptr [ebp-14] 00439BF2 |. D96D F2 fldcw word ptr [ebp-E] 00439BF5 |. 8B75 EC mov esi, dword ptr [ebp-14] 00439BF8 |. 893424 mov dword ptr [esp], esi 00439BFB |. DB0424 fild dword ptr [esp] 00439BFE |. 83C4 04 add esp, 4 00439C01 |. DD1C24 fstp qword ptr [esp] 00439C04 |. E8 DF4B0500 call <jmp.&OPENGL32.glTranslated> 00439C09 |. 83EC 18 sub esp, 18 00439C0C |. E8 274B0500 call <jmp.&OPENGL32.glEndList> ; 字符显示列表结束 00439C11 |. 8B1D 30FD010B mov ebx, dword ptr [B01FD30] 00439C17 |. 43 inc ebx 00439C18 |. 81FB FF000000 cmp ebx, 0FF ; 循环建立256个显示列表 00439C1E |. 891D 30FD010B mov dword ptr [B01FD30], ebx 00439C24 |.^ 0F86 66FEFFFF jbe 00439A90 00439C2A |. 8B45 E4 mov eax, dword ptr [ebp-1C] 00439C2D |. 8D65 F4 lea esp, dword ptr [ebp-C] 00439C30 |. 5B pop ebx 00439C31 |. 5E pop esi 00439C32 |. 5F pop edi 00439C33 |. 5D pop ebp 00439C34 \. C3 retn
代码:
GLuint base; // 绘制字体的显示列表的开始位置 GLuint texture; // 保存字体纹理 float cx; // 字符的X坐标 float cy; // 字符的Y坐标 int size; // 单个字符的尺寸,sub_439a20的第二个参数,此值不固定,游戏将根据分辨率动态计算 base=glGenLists(256); // 创建256个显示列表 glBindTexture(GL_TEXTURE_2D, texture); // 选择字符图象 for (loop=0; loop<256; loop++) // 循环256个显示列表 { cx=float(loop%16)/16.0f; // 当前字符的X坐标 cy=float(loop/16)/16.0f; // 当前字符的Y坐标 glNewList(base+loop,GL_COMPILE); //开始创建显示列表 glBegin(GL_QUADS); // 使用四边形显示每一个字符 glTexCoord2f(cx,1-cy-0.0625f); // 左下角的纹理坐标 glVertex2i(0,0); // 左下角的坐标 glTexCoord2f(cx+0.0625f,1-cy-0.0625f); // 右下角的纹理坐标 glVertex2i(size,0); // 右下角的坐标 glTexCoord2f(cx+0.0625f,1-cy); // 右上角的纹理坐标 glVertex2i(size,size); // 右上角的坐标 glTexCoord2f(cx,1-cy); // 左上角的纹理坐标 glVertex2i(0,size); // 左上角的坐标 glEnd(); glTranslated(unknow,0,0); // 绘制完一个字符,向右平移 glEndList(); // 字符显示列表结束 } // 循环建立256个显示列表 }
细读代码可以发现,程序生成显示列表的时候使用loop%16来换行,使用浮点数0.0625来控制字符纹理的xy坐标。如果要把代码改为读取32*32的纹理,只需要将loop%16改为loop%32,将所有的浮点数0.0625改为0.03125。代码如下:
代码:
GLuint base; // 绘制字体的显示列表的开始位置 GLuint texture; // 保存字体纹理 float cx; // 字符的X坐标 float cy; // 字符的Y坐标 int size; // 单个字符的尺寸,sub_439a20的第二个参数 base=glGenLists(1024); // 创建1024个显示列表 glBindTexture(GL_TEXTURE_2D, texture); // 选择字符图象 for (loop=0; loop<1024; loop++) // 循环1024个显示列表 { cx=float(loop%32)/32.0f; // 当前字符的X坐标 cy=float(loop/32)/32.0f; // 当前字符的Y坐标 glNewList(base+loop,GL_COMPILE); //开始创建显示列表 glBegin(GL_QUADS); // 使用四边形显示每一个字符 glTexCoord2f(cx,1-cy-0.03125f); // 左下角的纹理坐标 glVertex2i(0,0); // 左下角的坐标 glTexCoord2f(cx+0.03125f,1-cy-0.03125f); // 右下角的纹理坐标 glVertex2i(size,0); // 右下角的坐标 glTexCoord2f(cx+0.03125f,1-cy); // 右上角的纹理坐标 glVertex2i(size,size); // 右上角的坐标 glTexCoord2f(cx,1-cy); // 左上角的纹理坐标 glVertex2i(0,size); // 左上角的坐标 glEnd(); glTranslated(unknow,0,0); // 绘制完一个字符,向右平移 glEndList(); // 字符显示列表结束 } // 循环建立1024个显示列表 }
第一处:
代码:
00439A4F |> \C70424 000100>mov dword ptr [esp], 400 00439A56 |. E8 ED4C0500 call <jmp.&OPENGL32.glGenLists> ; 创建1024个显示列表
代码:
00439C17 |. 43 inc ebx 00439C18 |. 81FB FF000000 cmp ebx, 3FF ; 循环建立1024个显示列表
代码:
00439A9B |. 8B15 34FD010B mov edx, dword ptr [B01FD34] ; 将显示列表基址base装入edx 00439AA1 |. 89CE mov esi, ecx 00439AA3 |. 83E6 1F and esi, 0F ; 计数器与0x0f进行与运算,相当于loop%16,计算字符x坐标 00439AA6 |. 89CB mov ebx, ecx 00439AA8 |. C1EB 05 shr ebx, 4 ; 计数器右移4位,即相当于loop/16,计算字符y坐标 00439AAB |. 56 push esi 00439AAC |. 01D1 add ecx, edx ; base+loop 因此这个地方要改成这样: 00439A9B |. 8B15 34FD010B mov edx, dword ptr [B01FD34] ; 将显示列表基址base装入edx 00439AA1 |. 89CE mov esi, ecx 00439AA3 |. 83E6 1F and esi, 1F ; 计数器与0x1f进行与运算,相当于loop%32,计算字符x坐标 00439AA6 |. 89CB mov ebx, ecx 00439AA8 |. C1EB 05 shr ebx, 5 ; 计数器右移5位,即相当于loop/32,计算字符y坐标 00439AAB |. 56 push esi 00439AAC |. 01D1 add ecx, edx ; base+loop
还有要改的为浮点数0.0625,从汇编指令可以看出这个32位浮点数被硬编码到了0x4f6600,因为是硬编码只需要修改保存至可执行文件即可。如图:
32位浮点数0.03125的hex为0x3d000000,这里改为00 00 00 3D,然后可以右键->浮点->32位浮点数,如图:
到这里创建现实列表部分已经修改完成,我们只需要按照先前制作的码表制作一张32*32的png汉字字库,然后放到gfx目录下替换掉原有的字库,游戏就能够创建出我们需要的1024个显示列表。