【文章标题】: 阿玛迪斯战记窗口化运行
【文章作者】: yes2
【软件名称】: 阿玛迪斯战记
【下载地址】: 自己搜索下载
【加壳方式】: 简体版,盗版脱壳免CD的
【使用工具】: OD
【软件介绍】: 阿玛迪斯战记,汉堂老游戏
【作者声明】: 看了天之痕的窗口化(http://bbs.pediy.com/showthread.php?t=98941),很有启发,决定动手把这个窗口化一下,因为这个游戏是全屏运行的并且不支持切换窗口,一旦切换出游戏再切换回来的时候,整个画面都灰了无法继续游戏了,当年很是因为这个问题苦恼。
--------------------------------------------------------------------------------
【详细过程】
阿玛迪斯和天之痕差不多,也是DirectDraw绘制2D,所以别的也不多说了,直接开工。
全屏DirectX游戏调试很痛苦,经常中断之后啥都出不来,只有重启。。。所以开工之后不急着下断运行,先看怎么创建窗口的:
看了一下,调用的是CreateWindowEx,代码:
00428C0A |. 53 push ebx ; /lParam => NULL
00428C0B |. 56 push esi ; |hInst
00428C0C |. 8B35 14F14400 mov esi, dword ptr [<&USER32.GetSyst>; |USER32.GetSystemMetrics
00428C12 |. 53 push ebx ; |hMenu => NULL
00428C13 |. 53 push ebx ; |hParent => NULL
00428C14 |. 6A 01 push 1 ; |/Index = SM_CYSCREEN
00428C16 |. FFD6 call esi ; |\GetSystemMetrics
00428C18 |. 50 push eax ; |Height
00428C19 |. 53 push ebx ; |/Index => SM_CXSCREEN
00428C1A |. FFD6 call esi ; |\GetSystemMetrics
00428C1C |. 50 push eax ; |Width
00428C1D |. 53 push ebx ; |Y => 0
00428C1E |. 53 push ebx ; |X => 0
00428C1F |. 68 00000080 push 80000000 ; |Style = WS_POPUP
00428C24 |. 68 A83F4500 push 00453FA8 ; |WindowName = "The Lord of Beast"
00428C29 |. 68 0C404500 push 0045400C ; |Class = "The Lord of Beast"
00428C2E |. 6A 08 push 8 ; |ExtStyle = WS_EX_TOPMOST
00428C30 |. FF15 18F14400 call dword ptr [<&USER32.CreateWindow>; \CreateWindowExA
可以看到,获取了当前分辨率创建全屏窗口,并且是WS_EX_TOPMOST的ExStyle;
改小点,就640*480好了,再把ExStyle改成WS_EX_APPWINDOW:
00428C0A |. 53 push ebx ; /lParam => NULL
00428C0B |. 56 push esi ; |hInst
00428C0C |. 53 push ebx ; |hMenu => NULL
00428C0D |. 53 push ebx ; |hParent => NULL
00428C0E |. 68 E0010000 push 1E0 ; |Height = 1E0 (480.)
00428C13 |. 68 80020000 push 280 ; |Width = 280 (640.)
00428C18 |. 53 push ebx ; |Y => 0
00428C19 |. 53 push ebx ; |X => 0
00428C1A |. 68 00000080 push 80000000 ; |Style = WS_POPUP
00428C1F |. 68 A83F4500 push 00453FA8 ; |WindowName = "The Lord of Beast"
00428C24 |. 68 0C404500 push 0045400C ; |Class = "The Lord of Beast"
00428C29 |. 68 00000400 push 40000 ; |ExtStyle = WS_EX_APPWINDOW
00428C2E |. 90 nop ; |
00428C2F |. 90 nop ; |
00428C30 |. FF15 18F14400 call dword ptr [<&USER32.CreateWindow>; \CreateWindowExA
然后就可以看一下DirectDrawCreate了,程序在初始化DirectX的时候,出现错误会有相对详细的错误对话框,根据错误信息可以很快看到在哪里调用SetCooperativeLevel,看代码:
00429E8C |. E8 6F890100 call <jmp.&DDRAW.DirectDrawCreate> //这里调用DirectDrawCreate
00429E91 |. 85C0 test eax, eax
00429E93 |. 74 21 je short 00429EB6
00429E95 |. 8B86 10110000 mov eax, dword ptr [esi+1110]
00429E9B |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00429E9D |. 68 08444500 push 00454408 ; |Title = "Initialize Error:Cannot initial Directx"
00429EA2 |. 68 78424500 push 00454278 ; |Text = "Please check the version of Directx"
00429EA7 |. 50 push eax ; |hOwner
00429EA8 |. FF15 1CF14400 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
00429EAE |. 5F pop edi
00429EAF |. 32C0 xor al, al
00429EB1 |. 5E pop esi
00429EB2 |. 83C4 64 add esp, 64
00429EB5 |. C3 retn
00429EB6 |> 8B06 mov eax, dword ptr [esi]
00429EB8 |. 8B96 10110000 mov edx, dword ptr [esi+1110]
00429EBE |. 6A 11 push 11
00429EC0 |. 52 push edx
00429EC1 |. 8B08 mov ecx, dword ptr [eax]
00429EC3 |. 50 push eax
00429EC4 |. FF51 50 call dword ptr [ecx+50] //这里就是SetCooperativeLevel啦
00429EC7 |. 85C0 test eax, eax
00429EC9 |. 74 21 je short 00429EEC
00429ECB |. 8B86 10110000 mov eax, dword ptr [esi+1110]
00429ED1 |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00429ED3 |. 68 D8434500 push 004543D8 ; |Title = "Initialize Error:Cannot set CooperativeLevel"
00429ED8 |. 68 78424500 push 00454278 ; |Text = "Please check the version of Directx"
00429EDD |. 50 push eax ; |hOwner
00429EDE |. FF15 1CF14400 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
不要全屏独占模式,把【00429EBE |. 6A 11 push 11】改成push 8,普通窗口模式;
再往下看可以看到错误信息有个"Initialize Error:Cannot switch to %dx%d Mode",可以猜出是要切换到ModeX,因为咱们是要窗口模式的,所以这一段代码就不要执行了,要跳掉,代码就不贴了。
继续往下,可以看到错误信息有个"Initialize Error:Cannot Build Surface",那么上面就是要CreateSurface了:
00429F48 |> \8B0E mov ecx, dword ptr [esi]
00429F4A |. 8D46 04 lea eax, dword ptr [esi+4]
00429F4D |. 8D7E 70 lea edi, dword ptr [esi+70]
00429F50 |. 6A 00 push 0
00429F52 |. C700 6C000000 mov dword ptr [eax], 6C //这里填充一个DDSURFACEDESC结构,6c是size
00429F58 |. C746 08 21000>mov dword ptr [esi+8], 21 //ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT
00429F5F |. C746 6C 18020>mov dword ptr [esi+6C], 218 //ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
//DDSCAPS_FLIP | DDSCAPS_COMPLEX
00429F66 |. C746 18 01000>mov dword ptr [esi+18], 1 //ddsd.dwBackBufferCount = 1;
00429F6D |. 8B11 mov edx, dword ptr [ecx]
00429F6F |. 57 push edi
00429F70 |. 50 push eax
00429F71 |. 51 push ecx
00429F72 |. FF52 18 call dword ptr [edx+18]
这里需要改一下,因为窗口模式使用这些参数CreateSurface会失败,具体原因我就不知道了,我不会DirectX=。=
改后代码如下:
00429EEC |> \8B0E mov ecx, dword ptr [esi]
00429EEE |. 8D46 04 lea eax, dword ptr [esi+4]
00429EF1 |. 8D7E 70 lea edi, dword ptr [esi+70]
00429EF4 |. 6A 00 push 0
00429EF6 |. C700 6C000000 mov dword ptr [eax], 6C
00429EFC |. C746 08 01000>mov dword ptr [esi+8], 1 //ddsd.dwFlags = DDSD_CAPS
00429F03 |. C746 6C 00020>mov dword ptr [esi+6C], 200 //ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE
00429F0A |. C746 18 01000>mov dword ptr [esi+18], 1 //这一行其实没用了,不过懒得去掉了
00429F11 |. 8B11 mov edx, dword ptr [ecx]
00429F13 |. 57 push edi
00429F14 |. 50 push eax
00429F15 |. 51 push ecx
00429F16 |. FF52 18 call dword ptr [edx+18]
修改前和修改后的代码Offset不一样,这个一会再说;
接下来的错误信息是"Initialize Error:Cannot Build Background Plane",该创建离屏表面了,这里还是要修改,这里贴一下翻译后的代码:
原来的是这样的:
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
hRet = g_pDDSFront->GetAttachedSurface(&ddscaps, &g_pDDSBack);
要改成这样的:
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 640;
ddsd.dwHeight = 480;
hRet = g_pDD->CreateSurface(&ddsd, &g_pDDSBack, NULL);
基本上来说,跟创建主表面的CreateSurface差不多,只要修改一下结构的参数,并且调用参数传入g_pDDSBack的指针就好了。
这里解释一下创建主表面那个代码修改后为什么offset不一样,因为创建离屏表面这里修改后字节比较多,于是把原本用于设置ModeX的代码覆盖掉了,整体代码上移。创建离屏表面的代码如下,基本和创建主表面的代码是一致的,参数有变化:
00429F3E |> \8B0E mov ecx, dword ptr [esi]
00429F40 |. 8D46 04 lea eax, dword ptr [esi+4]
00429F43 |. 8D7E 74 lea edi, dword ptr [esi+74]
00429F46 |. 6A 00 push 0
00429F48 |. C700 6C000000 mov dword ptr [eax], 6C
00429F4E |. C746 08 07000>mov dword ptr [esi+8], 7
00429F55 |. C746 6C 40000>mov dword ptr [esi+6C], 40
00429F5C |. C746 10 80020>mov dword ptr [esi+10], 280
00429F63 |. C746 0C E0010>mov dword ptr [esi+C], 1E0
00429F6A |. 8B11 mov edx, dword ptr [ecx]
00429F6C |. 57 push edi
00429F6D |. 50 push eax
00429F6E |. 51 push ecx
00429F6F |. FF52 18 call dword ptr [edx+18]
好啦,现在可以启动看效果啦,发现两个问题:
1.画面是花的;
2.画面依然刷的整个屏幕都乱七八糟的;
问题1是因为游戏是16色的,在设置ModeX那里可以看到(可惜现在已经被覆盖了),而桌面是32色的,天之痕作者是深入程序彻底修改为32色,我比较懒,直接把桌面颜色模式设置为16色。。。
问题2,是因为窗口模式的DirectX需要设置Clipper,于是在创建完离屏表面之后,设置一下Clipper,c代码如下:
LPDIRECTDRAWCLIPPER pMyClipper;
hRet = g_pDD->CreateClipper(0, &pMyClipper, NULL);
hRet = pMyClipper->SetHWnd(0, g_hMainWnd);
hRet = g_pDDSFront->SetClipper(pMyClipper);
g_hMainWnd的值可以参考SetCooperativeLevel的参数。
好在刚才覆盖了设置ModeX的代码,空间还是比较大的,创建完离屏表面的代码后面添加以下代码:
00429F97 |> \6A 00 push 0
00429F99 |. 8D5424 04 lea edx, dword ptr [esp+4]
00429F9D |. 52 push edx
00429F9E |. 8B0E mov ecx, dword ptr [esi]
00429FA0 |. 8B11 mov edx, dword ptr [ecx]
00429FA2 |. 6A 00 push 0
00429FA4 |. 51 push ecx
00429FA5 |. FF52 10 call dword ptr [edx+10]
00429FA8 |. 8B96 10110000 mov edx, dword ptr [esi+1110]
00429FAE |. 8B0424 mov eax, dword ptr [esp]
00429FB1 |. 8B08 mov ecx, dword ptr [eax]
00429FB3 |. 52 push edx
00429FB4 |. 6A 00 push 0
00429FB6 |. 50 push eax
00429FB7 |. FF51 20 call dword ptr [ecx+20]
00429FBA |. 8D7E 70 lea edi, dword ptr [esi+70]
00429FBD |. 8B3F mov edi, dword ptr [edi]
00429FBF |. 8B1424 mov edx, dword ptr [esp]
00429FC2 |. 8B0F mov ecx, dword ptr [edi]
00429FC4 |. 52 push edx
00429FC5 |. 57 push edi
00429FC6 |. FF51 70 call dword ptr [ecx+70]
好了,现在两个问题都解决了,运行一下看看,动画正常,点击一下,游戏死掉了。。。
OD调试一下发现错误是在这里:
0042DC27 |. F3:AB |rep stos dword ptr es:[edi]
分析一下,大概是申请了一块内存,然后操作越界了。。。仔细观察前后代码,发现这块空间是g_pDDSBack->Lock之后得到的,并且出错代码的操作是要对这块空间进行1E0*400*4=1E0000字节的操作,但是这个空间只有1E0*280*2=96000大小,那么如何满足他呢?
猜测,他的需求只和1E0(离屏表面的高)有关,那么就把离屏表面的宽设置为1E0000/2/1E0=800,于是在创建离屏表面那里,把
00429F5C |. C746 10 80020>mov dword ptr [esi+10], 280
改成:
00429F5C |. C746 10 00080>mov dword ptr [esi+10], 800
再次运行,ok了。
==========================
更新:
这个数值不是固定800,而是需要执行如下代码来获取:
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_PITCH;
g_pDDSFront->GetSurfaceDesc(&ddsd);
执行的时机是创建完成主表面之后,然后ddsd.lPitch/2就是离屏表面需要设置的宽度了。
具体代码就不贴了。
==========================
画面清晰了就可以看到,画面被拉长了。。。似乎是分辨率的问题,这里就创建窗口和离屏表面涉及到分辨率,换一下分辨率试试看,发现和创建窗口那里没关系,于是在创建离屏表面那里试试看,有门,答案就是高度要和屏幕分辨率高度一样,于是改一下创建离屏表面的代码,如下:
00429F3E |> \A1 14F14400 mov eax, dword ptr [<&USER32.GetSyst>
00429F43 |. 6A 01 push 1 ; /Index = SM_CYSCREEN
00429F45 |. FFD0 call eax ; \GetSystemMetrics
00429F47 |. 8946 0C mov dword ptr [esi+C], eax
00429F4A |. 8B0E mov ecx, dword ptr [esi]
00429F4C |. 8D46 04 lea eax, dword ptr [esi+4]
00429F4F |. 8D7E 74 lea edi, dword ptr [esi+74]
00429F52 |. 6A 00 push 0
00429F54 |. C700 6C000000 mov dword ptr [eax], 6C
00429F5A |. C746 08 07000>mov dword ptr [esi+8], 7
00429F61 |. C746 6C 40000>mov dword ptr [esi+6C], 40
00429F68 |. C746 10 00080>mov dword ptr [esi+10], 800
00429F6F |. 8B11 mov edx, dword ptr [ecx]
00429F71 |. 57 push edi
00429F72 |. 50 push eax
00429F73 |. 51 push ecx
00429F74 |. FF52 18 call dword ptr [edx+18]
好啦,现在一切正常啦,玩游戏啦~
本人不会DirectX,思路及相关知识是从《天之痕窗口化》及google获取的,在此表示感谢。
另外本文是在修改完成之后对比原来的程序贴的代码,所以可能有疏漏并且没有截图,请各位见谅和指正!
附上修改过的可窗口化的游戏主程序:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!