【文章标题】: 阿玛迪斯战记窗口化运行
【文章作者】: 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获取的,在此表示感谢。
另外本文是在修改完成之后对比原来的程序贴的代码,所以可能有疏漏并且没有截图,请各位见谅和指正!

附上修改过的可窗口化的游戏主程序:

上传的附件 lob_Win.rar[请到论坛下载]

  • 标 题:答复
  • 作 者:晓欣
  • 时 间:2009-10-20 17:48

引用:
最初由 yeyeshun发布 查看帖子

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=。=
失败是因为创建Surface的时候指定了DDSCAPS_FLIP参数,表示使用页面翻转的形式来向主表面传送数据,这个参数只能在全屏模式下使用。