| 关键词: nbsp 0057 dword ptr mov push eax esp ecx esi |
【介绍】 前几天,tbsgame.net 的 Lejardo老大,通过 MSN Messager 与我联系,说是想汉化一游戏—— War In The Pacific : The struggle against Japan 1941-1945,是一个古老的2D回合制游戏,我想也只有骨灰级的玩家会关心这个。 以前,曾在该网站,参与了《铁血联盟2黄金版》的汉化工作。但是,那是有完整的C源码的汉化,与这次的难度不可同日而语。 从TLF的FTP里下载了一个硬盘版,谁知不能升级最新的1.8补丁,于是从已经升级的人那里得到1.8的exe,虽然在正式游戏时会出错,但好歹能看到选单画面和任务简报了。 在DX9之前,几乎每个英文游戏都有一套自己的字符显示引擎,使用DirectDraw的2D游戏把字符存放在Surface中,使用Direct3D的3D游戏则把字符存放在Texture中,显示时根据字符找到相应的Surface或Texture,然后把它们贴上屏幕。 汉化的方法,一种是使用WINDOWS的GDI函数,直接操作HDC。这种方法的缺点是效率低下,这也是为什么在DX9之前,英文游戏要使用自己的字符输出引擎的原因;另一种方法是修改原有的引擎,使其支持中文输出,但如果游戏没有使用unicode,就会牵涉到一个单字节、双字节的问题,在没有源码的情况下非常麻烦;最后一种是把游戏分析透彻后,外接一个dll,使用自己的字符输出引擎。 考虑到该游戏是一个2D游戏,而且现在的电脑配置已远远高于该游戏的推荐配置,所以准备使用第一种方法进行汉化。 【分析】 首先要搞清楚游戏原本的字符显示机制。 用ODBG载入,加-w参数,让游戏在窗口模式下运行。 随便找一显示出来的字符串,然后在内存中搜索,下内存断点,步步跟踪,层层深入,最后找到了这里: 代码:-------------------------------------------------------------------------------- 0057E5E0 /$ A1 105D6004 mov eax,dword ptr ds:[4605D10] 0057E5E5 |. 83EC 18 sub esp,18 0057E5E8 |. 85C0 test eax,eax 0057E5EA |. 0F84 79010000 je war_in_t.0057E769 0057E5F0 |. 8B0D 080B0E04 mov ecx,dword ptr ds:[40E0B08] ; [ecx]+10 = 颜色 0057E5F6 |. 85C9 test ecx,ecx 0057E5F8 |. 0F84 6B010000 je war_in_t.0057E769 0057E5FE |. 55 push ebp 0057E5FF |. 8B6C24 2C mov ebp,dword ptr ss:[esp+2C] 0057E603 |. 85ED test ebp,ebp 0057E605 |. 56 push esi 0057E606 |. 57 push edi 0057E607 |. 75 06 jnz short war_in_t.0057E60F 0057E609 |. 8B2D 54840F04 mov ebp,dword ptr ds:[40F8454] ; 目标surface 0057E60F |> 8B4424 30 mov eax,dword ptr ss:[esp+30] 0057E613 |. 8D70 01 lea esi,dword ptr ds:[eax+1] 0057E616 |> 8A10 /mov dl,byte ptr ds:[eax] 0057E618 |. 40 |inc eax 0057E619 |. 84D2 |test dl,dl 0057E61B |.^ 75 F9 \jnz short war_in_t.0057E616 0057E61D |. 2BC6 sub eax,esi 0057E61F |. 8BF0 mov esi,eax 0057E621 |. 81FE 58020000 cmp esi,258 0057E627 |. 897424 0C mov dword ptr ss:[esp+C],esi 0057E62B |. 7C 0C jl short war_in_t.0057E639 0057E62D |. C74424 0C 57020000 mov dword ptr ss:[esp+C],257 0057E635 |. 8B7424 0C mov esi,dword ptr ss:[esp+C] 0057E639 |> 8B4424 28 mov eax,dword ptr ss:[esp+28] 0057E63D |. 8B5424 2C mov edx,dword ptr ss:[esp+2C] 0057E641 |. 33FF xor edi,edi 0057E643 |. 85F6 test esi,esi 0057E645 |. A3 48740604 mov dword ptr ds:[4067448],eax 0057E64A |. 8915 E4760604 mov dword ptr ds:[40676E4],edx 0057E650 |. 897C24 10 mov dword ptr ss:[esp+10],edi 0057E654 |. 0F8E 0C010000 jle war_in_t.0057E766 0057E65A |. 53 push ebx 0057E65B |. 8B5C24 3C mov ebx,dword ptr ss:[esp+3C] 0057E65F |. 90 nop 0057E660 |> 8B7424 34 /mov esi,dword ptr ss:[esp+34] 0057E664 |. 0FB63437 |movzx esi,byte ptr ds:[edi+esi] 0057E668 |. 83FE 20 |cmp esi,20 0057E66B |. 0F8C CB000000 |jl war_in_t.0057E73C 0057E671 |. 3B31 |cmp esi,dword ptr ds:[ecx] ; [ecx] == 80 0057E673 |. 0F8D DB000000 |jge war_in_t.0057E754 ; 非法字符? 0057E679 |. 803D 88860F04 00 |cmp byte ptr ds:[40F8688],0 0057E680 |. 74 05 |je short war_in_t.0057E687 0057E682 |. 8B79 04 |mov edi,dword ptr ds:[ecx+4] 0057E685 |. EB 0C |jmp short war_in_t.0057E693 0057E687 |> 8BFE |mov edi,esi 0057E689 |. 6BFF 38 |imul edi,edi,38 0057E68C |. 8BBC0F 24080000 |mov edi,dword ptr ds:[edi+ecx+824] 0057E693 |> 833D 585E6004 00 |cmp dword ptr ds:[4605E58],0 0057E69A |. 7E 4A |jle short war_in_t.0057E6E6 0057E69C |. 894424 18 |mov dword ptr ss:[esp+18],eax 0057E6A0 |. 03C7 |add eax,edi 0057E6A2 |. 894424 20 |mov dword ptr ss:[esp+20],eax 0057E6A6 |. 895424 1C |mov dword ptr ss:[esp+1C],edx 0057E6AA |. 8B41 08 |mov eax,dword ptr ds:[ecx+8] 0057E6AD |. 03C2 |add eax,edx 0057E6AF |. 85DB |test ebx,ebx 0057E6B1 |. 894424 24 |mov dword ptr ss:[esp+24],eax 0057E6B5 |. 74 10 |je short war_in_t.0057E6C7 0057E6B7 |. 8D4C24 18 |lea ecx,dword ptr ss:[esp+18] 0057E6BB |. 51 |push ecx 0057E6BC |. 53 |push ebx 0057E6BD |. 6A 00 |push 0 0057E6BF |. E8 FC75FFFF |call war_in_t.00575CC0 0057E6C4 |. 83C4 0C |add esp,0C 0057E6C7 |> 8B15 585E6004 |mov edx,dword ptr ds:[4605E58] 0057E6CD |. 6A 00 |push 0 ; /Arg5 = 00000000 0057E6CF |. 55 |push ebp ; |Arg4 0057E6D0 |. 6A 00 |push 0 ; |Arg3 = 00000000 0057E6D2 |. 52 |push edx ; |Arg2 => 00000000 0057E6D3 |. 8D4424 28 |lea eax,dword ptr ss:[esp+28] ; | 0057E6D7 |. 50 |push eax ; |Arg1 0057E6D8 |. E8 33CAFFFF |call war_in_t.0057B110 ; \war_in_t.0057B110 0057E6DD |. 8B0D 080B0E04 |mov ecx,dword ptr ds:[40E0B08] 0057E6E3 |. 83C4 14 |add esp,14 0057E6E6 |> 56 |push esi 0057E6E7 |. 51 |push ecx 0057E6E8 |. E8 F3B3FFFF |call war_in_t.00579AE0 ; 颜色相关 0057E6ED |. 6BF6 38 |imul esi,esi,38 0057E6F0 |. 8B0D E4760604 |mov ecx,dword ptr ds:[40676E4] 0057E6F6 |. 8B15 48740604 |mov edx,dword ptr ds:[4067448] 0057E6FC |. A1 080B0E04 |mov eax,dword ptr ds:[40E0B08] 0057E701 |. 55 |push ebp 0057E702 |. 6A 00 |push 0 0057E704 |. 68 A00F0000 |push 0FA0 0057E709 |. 6A 00 |push 0 0057E70B |. 53 |push ebx 0057E70C |. 51 |push ecx 0057E70D |. 52 |push edx 0057E70E |. 8D8C06 20080000 |lea ecx,dword ptr ds:[esi+eax+820] 0057E715 |. 51 |push ecx 0057E716 |. E8 C5F7FFFF |call war_in_t.0057DEE0 0057E71B |. A1 48740604 |mov eax,dword ptr ds:[4067448] 0057E720 |. 8B0D 080B0E04 |mov ecx,dword ptr ds:[40E0B08] 0057E726 |. 8B15 E4760604 |mov edx,dword ptr ds:[40676E4] 0057E72C |. 83C4 28 |add esp,28 0057E72F |. 03C7 |add eax,edi 0057E731 |. 8B7C24 14 |mov edi,dword ptr ss:[esp+14] 0057E735 |. A3 48740604 |mov dword ptr ds:[4067448],eax 0057E73A |. EB 18 |jmp short war_in_t.0057E754 0057E73C |> 83FE 0A |cmp esi,0A 0057E73F |. 75 13 |jnz short war_in_t.0057E754 0057E741 |. A1 74720604 |mov eax,dword ptr ds:[4067274] 0057E746 |. A3 48740604 |mov dword ptr ds:[4067448],eax 0057E74B |. 0351 08 |add edx,dword ptr ds:[ecx+8] 0057E74E |. 8915 E4760604 |mov dword ptr ds:[40676E4],edx 0057E754 |> 8B7424 10 |mov esi,dword ptr ss:[esp+10] 0057E758 |. 47 |inc edi 0057E759 |. 3BFE |cmp edi,esi 0057E75B |. 897C24 14 |mov dword ptr ss:[esp+14],edi 0057E75F |.^ 0F8C FBFEFFFF \jl war_in_t.0057E660 0057E765 |. 5B pop ebx 0057E766 |> 5F pop edi 0057E767 |. 5E pop esi 0057E768 |. 5D pop ebp 0057E769 |> 83C4 18 add esp,18 0057E76C \. C3 retn-------------------------------------------------------------------------------- 从0057E660到0057E75F的循环是把字符串中的字符逐个显示出来。 其中 代码:-------------------------------------------------------------------------------- 0057E671 |. 3B31 |cmp esi,dword ptr ds:[ecx] ; [ecx] == 80 0057E673 |. 0F8D DB000000 |jge war_in_t.0057E754 ; 非法字符?-------------------------------------------------------------------------------- 过滤了大于80(十进制128)的字符,这就是为什么中文无法显示的原因。当然,简单的把这个判断去掉是不可行的。 这个函数有3个参数:输出的字符串和坐标(x,y),而如果我们要用自己的代码代替,还需要知道两样东西:颜色,以及输出的目的地(目标Surface)。 把 0057E6E8 的 “call 00579AE0”nop掉,发现所有字符不再有颜色变化,可见这个函数同颜色有关。继续跟踪,发现颜色存放在[40E0B08],一个全局的struct中。 而目标Surface是通过跟踪 0057E716 的“call 0057DEE0”得到的。 接下来要做的,就是把这个函数替换成我们自己的。 【动手】 这个游戏使用了DirectDraw7,先写一个DirectDraw7的程序: 代码:-------------------------------------------------------------------------------- #include <windows.h> #include <ddraw.h> #pragma comment(lib,"ddraw.lib") #pragma comment(lib,"dxguid.lib") #define GSM_CAPTION GetSystemMetrics(SM_CYCAPTION) //标题栏 #define GSM_CXBORDER GetSystemMetrics(SM_CXFIXEDFRAME) //不可调边框 #define GSM_CYBORDER GetSystemMetrics(SM_CYFIXEDFRAME) #define GSM_CYMENU GetSystemMetrics(SM_CYMENU) //如果有菜单 #define MAXWIDTH 640 //游戏显示区大小 #define MAXHEIGHT 480 //全局变量 LPDIRECTDRAW7 lpDD; //DirectDraw对象 LPDIRECTDRAWSURFACE7 lpDDSPrimary; //DirectDraw主表面 LPDIRECTDRAWSURFACE7 lpDDSBack; //后台缓冲表面 char szMsg1[] = "001 Tutorial"; char szMsg2[] = "按ESC退出"; BOOL bActive = TRUE; HWND hwnd; HFONT hfont; //函数声明 LRESULT CALLBACK WinProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); BOOL InitWindow (HINSTANCE hInstance, int nCmdShow); BOOL InitDDraw (void); //初始化DirectX void FreeDDraw (void); //释放DirectX对象 void MainLoop (void); //游戏主循环 //------------------------------------------------------- //函数:WinMain() //功能:Win32应用程序入口函数.进行初始化工作,处理消息循环 //------------------------------------------------------- int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; //初始化主窗口 if (!InitWindow (hInstance, nCmdShow)) return FALSE; //初始化DirectDraw环境,并实现DirectDraw功能 if (!InitDDraw ()) { MessageBox (GetActiveWindow (), "初始化DirectDraw过程中出错!", "Error", MB_OK); FreeDDraw (); DestroyWindow (GetActiveWindow ()); return FALSE; } hfont = CreateFont (15, 0, 0, 0, FW_EXTRALIGHT, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, 3, VARIABLE_PITCH | FF_DONTCARE, "Lucida Sans Unicode"); while (1) { if (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) { //如果有消息就处理消息 if (!GetMessage (&msg, NULL, 0, 0)) return msg.wParam; TranslateMessage (&msg); DispatchMessage (&msg); } else if (bActive) { //如果程序处于激活状态,进入游戏主循环 MainLoop (); } //等待消息 else WaitMessage (); } return msg.wParam; } //-------------------------------------- //函数:InitWindow() //功能:创建主窗口 //-------------------------------------- static BOOL InitWindow (HINSTANCE hInstance, int nCmdShow) { WNDCLASS wc; //窗口类结构 //填充窗口类结构 wc.style = 0; wc.lpfnWndProc = WinProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon (hInstance, IDI_APPLICATION); wc.hCursor = LoadCursor (NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "dxHello"; //注册窗口类 RegisterClass (&wc); //创建主窗口 hwnd = CreateWindowEx (0, "dxHello", "", WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_OVERLAPPED, 0, 0, MAXWIDTH + (GSM_CXBORDER << 1), //注意这里 MAXHEIGHT + GSM_CAPTION + (GSM_CYBORDER << 1), //注意这里 NULL, NULL, hInstance, NULL); if (!hwnd) return FALSE; ShowWindow (hwnd, nCmdShow); UpdateWindow (hwnd); return TRUE; } //-------------------------------------------------- //函数:WinProc() //功能:处理主窗口消息 //-------------------------------------------------- LRESULT CALLBACK WinProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_ACTIVATEAPP: bActive = wParam; break; case WM_KEYDOWN: //击键消息 switch (wParam) { case VK_ESCAPE: PostMessage (hWnd, WM_CLOSE, 0, 0); break; } break; case WM_SETCURSOR: SetCursor (NULL); return TRUE; case WM_DESTROY: //退出消息 FreeDDraw (); DeleteObject (hfont); PostQuitMessage (0); break; } //调用缺省消息处理过程 return DefWindowProc (hWnd, message, wParam, lParam); } //---------------------------------------------------------------- //函数: InitDDraw() //功能: 初始化DirectDraw环境并实现其功能.包括:创建DirectDraw对象, //设置显示模式,创建主表面 //---------------------------------------------------------------- bool InitDDraw (void) { DDSURFACEDESC2 ddsd; //表面描述 //创建DirectDraw对象 if (DirectDrawCreateEx (NULL, (void **) &lpDD, IID_IDirectDraw7, NULL) != DD_OK) return FALSE; //设置窗口模式 if (lpDD->SetCooperativeLevel (hwnd, DDSCL_NORMAL) != DD_OK) return FALSE; //填充主表面信息 ZeroMemory (&ddsd, sizeof (ddsd)); ddsd.dwSize = sizeof (ddsd); ddsd.dwFlags = DDSD_CAPS; // | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; // | DDSCAPS_FLIP | DDSCAPS_COMPLEX; //创建主表面对象 if (lpDD->CreateSurface (&ddsd, &lpDDSPrimary, NULL) != DD_OK) return FALSE; LPDIRECTDRAWCLIPPER lpclip; lpDD->CreateClipper (NULL, &lpclip, NULL); lpDDSPrimary->SetClipper (lpclip); lpclip->SetHWnd (NULL, hwnd); lpclip->Release (); ZeroMemory (&ddsd, sizeof (ddsd)); ddsd.dwSize = sizeof (ddsd); ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY | DDSCAPS_LOCALVIDMEM; ddsd.dwWidth = MAXWIDTH; ddsd.dwHeight = MAXHEIGHT; //创建后表面对象 if (lpDD->CreateSurface (&ddsd, &lpDDSBack, NULL) != DD_OK) return FALSE; return TRUE; } //------------------------------------------------------- //函数:FreeDDraw() //功能:释放所有的DirectDraw对象 //------------------------------------------------------- void FreeDDraw (void) { if (lpDD != NULL) { if (lpDDSPrimary != NULL) { lpDDSPrimary->Release (); lpDDSPrimary = NULL; } lpDD->Release (); lpDD = NULL; } } void _TextOut (int x, int y, char *string, COLORREF color) { HDC hdc; if (lpDDSBack->GetDC (&hdc) == DD_OK) { SelectObject (hdc, hfont); SetBkMode (hdc, TRANSPARENT); SetTextColor (hdc, color); TextOut (hdc, x, y, string, lstrlen (string)); lpDDSBack->ReleaseDC (hdc); } } //------------------------------------------------------- //函数:MainLoop() //功能:游戏主循环 //------------------------------------------------------- void MainLoop (void) { //后台缓冲表面上的操作 HDC hdc; RECT rect; //这个是主表面的区域 RECT rectback = { 0, 0, MAXWIDTH, MAXHEIGHT};; //这个是次表面的区域 if (lpDDSBack->GetDC (&hdc) == DD_OK) { //清屏 GetWindowRect (hwnd, &rect); //取得整个窗口区域 rect.left += GSM_CXBORDER; //修正到主表面区域 rect.top += GSM_CAPTION + GSM_CYBORDER; rect.right -= GSM_CXBORDER; rect.bottom -= GSM_CYBORDER; FillRect (hdc, &rectback, (HBRUSH) GetStockObject (BLACK_BRUSH)); lpDDSBack->ReleaseDC (hdc); } _TextOut (220, 200, szMsg1, RGB (0xf0, 0xf0, 0xf0)); _TextOut (220, 220, szMsg2, RGB (0, 255, 0)); lpDDSPrimary->Blt (&rect, lpDDSBack, &rectback, DDBLT_WAIT, NULL); }-------------------------------------------------------------------------------- 编译后,把_TextOut的汇编代码植入游戏(可用ODBG的二进制复制粘贴功能),并修改相关的api调用。 其中int x, int y, char *string, COLORREF color,lpDDSBack都已经分析出,而hfont需要在游戏开始时用CreateFontA创建,偷偷保存在数据段的末尾,结束前用DeleteObject析构。 代码:-------------------------------------------------------------------------------- 04634300 51 push ecx ; _TextOut 04634301 A1 54840F04 mov eax,dword ptr ds:[40F8454] 04634306 8B08 mov ecx,dword ptr ds:[eax] 04634308 8D1424 lea edx,dword ptr ss:[esp] 0463430B 52 push edx 0463430C 50 push eax 0463430D FF51 44 call dword ptr ds:[ecx+44] ; BackSurface->GetDC 04634310 85C0 test eax,eax 04634312 75 61 jnz short war_in_t.04634375 04634314 A1 FC1F6204 mov eax,dword ptr ds:[4621FFC] ; hfont 04634319 8B0C24 mov ecx,dword ptr ss:[esp] 0463431C 56 push esi 0463431D 50 push eax 0463431E 51 push ecx 0463431F FF15 34105B00 call dword ptr ds:[<&GDI32.SelectObject>] ; GDI32.SelectObject 04634325 8B5424 04 mov edx,dword ptr ss:[esp+4] 04634329 6A 01 push 1 0463432B 52 push edx 0463432C FF15 E4806304 call dword ptr ds:[<&GDI32.SetBkMode>] ; GDI32.SetBkMode 04634332 8B4424 18 mov eax,dword ptr ss:[esp+18] 04634336 8B4C24 04 mov ecx,dword ptr ss:[esp+4] 0463433A 50 push eax 0463433B 51 push ecx 0463433C FF15 4C105B00 call dword ptr ds:[<&GDI32.SetTextColor>] ; GDI32.SetTextColor 04634342 8B7424 14 mov esi,dword ptr ss:[esp+14] 04634346 56 push esi 04634347 FF15 2C115B00 call dword ptr ds:[<&KERNEL32.lstrlenA>] ; KERNEL32.lstrlenA 0463434D 8B5424 10 mov edx,dword ptr ss:[esp+10] 04634351 8B4C24 04 mov ecx,dword ptr ss:[esp+4] 04634355 50 push eax 04634356 8B4424 10 mov eax,dword ptr ss:[esp+10] 0463435A 56 push esi 0463435B 52 push edx 0463435C 50 push eax 0463435D 51 push ecx 0463435E FF15 50105B00 call dword ptr ds:[<&GDI32.TextOutA>] ; GDI32.TextOutA 04634364 8B4C24 04 mov ecx,dword ptr ss:[esp+4] 04634368 A1 54840F04 mov eax,dword ptr ds:[40F8454] 0463436D 8B10 mov edx,dword ptr ds:[eax] 0463436F 51 push ecx 04634370 50 push eax 04634371 FF52 68 call dword ptr ds:[edx+68] ; BackSurface->ReleaseDC 04634374 5E pop esi 04634375 59 pop ecx 04634376 C3 retn-------------------------------------------------------------------------------- 然后修改原来的字符串输出函数: 代码:-------------------------------------------------------------------------------- 0057E5E0 - E9 9B5A0B04 jmp war_in_t.04634080 ; 原: mov eax, dword ptr ds:[4605D10]-------------------------------------------------------------------------------- 跳到这里,调用我们自己的_TextOut: 代码:-------------------------------------------------------------------------------- 04634080 8B0D 080B0E04 mov ecx,dword ptr ds:[40E0B08] 04634086 FF71 10 push dword ptr ds:[ecx+10] ; 颜色 04634089 FF7424 10 push dword ptr ss:[esp+10] ; 字符串 0463408D FF7424 10 push dword ptr ss:[esp+10] ; y 04634091 FF7424 10 push dword ptr ss:[esp+10] ; x 04634095 E8 66020000 call war_in_t.04634300 ; _TextOut 0463409A 83C4 10 add esp,10 0463409D C3 retn-------------------------------------------------------------------------------- 至此,游戏已能正常显示中文。 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|