首页
论坛
课程
招聘
[原创]高手莫入 在游戏中显示自己的文字和图形的方法
2009-4-3 22:44 70172

[原创]高手莫入 在游戏中显示自己的文字和图形的方法

2009-4-3 22:44
70172
Hook Directx:在游戏中显示自己的文字和图形的方法

这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
    pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
    DWORD oldpro=0;
    memcpy(d3dcen5bytes,pC,5);
    VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
    *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
    *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5

这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
HRESULT CreateDevice(
  UINT Adapter,
  D3DDEVTYPE DeviceType,
  HWND hFocusWindow,
  DWORD BehaviorFlags,
  D3DPRESENT_PARAMETERS * pPresentationParameters,
  IDirect3DDevice9 ** ppReturnedDeviceInterface
);

而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
HRESULT _stdcall hookedCreateDevice(
                                  LPDIRECT3D9 pDx9,
                                  UINT Adapter,
                                  D3DDEVTYPE DeviceType,
                                  HWND hFocusWindow,
                                  DWORD BehaviorFlags,
                                  D3DPRESENT_PARAMETERS * pPresentationParameters,
                                  IDirect3DDevice9 ** ppReturnedDeviceInterface

                                  );

其中的LPDIRECT3D9 pDx9就是this指针.

hookedDirect3DCreate9的关键代码如下:
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针

        DWORD oldpro=0;
        memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
        VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
        *(BYTE*)pCdev=0xe9;
        *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;

在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
        memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
        DWORD oldpro=0;
        VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
        *(BYTE*)pPre=0xe9;
        *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;

实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:pediy3000@hotmail.com";
            DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
            //在这里写入您的其它绘图代码

再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
{

    if(m_pD3D && pDxdevice){

        RECT myrect;
        myrect.top=150;  //文本块的y坐标
        myrect.left=0; //文本块的左坐标
        myrect.right=500+myrect.left;
        myrect.bottom=100+myrect.top;
        pDxdevice->BeginScene();//开始绘制

        D3DXFONT_DESCA lf;
        ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
        lf.Height = 24; //字体高度
        lf.Width = 12; // 字体宽度
        lf.Weight = 100;
        lf.Italic = false;
        lf.CharSet = DEFAULT_CHARSET;
        strcpy(lf.FaceName, "Times New Roman"); // 字型
        ID3DXFont* font=NULL;
        if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
            return false;

        font->DrawText(
            NULL,
            strText, // 要绘制的文本
            nbuf,
            &myrect,
            DT_TOP | DT_LEFT, // 字符居左显示
            D3DCOLOR_ARGB(255,255,255,0));

        pDxdevice->EndScene();//结束绘制
        font->Release();//释放对象
    }
    return true;
}

效果图:



  

后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.

源代码:

第五届安全开发者峰会(SDC 2021)议题征集正式开启!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (64)
雪    币: 203
活跃值: 活跃值 (12)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wyfe 活跃值 2009-4-3 22:52
2
0
又是一篇好文章
雪    币: 265
活跃值: 活跃值 (35)
能力值: ( LV13,RANK:350 )
在线值:
发帖
回帖
粉丝
moonife 活跃值 8 2009-4-3 23:28
3
0
不错,不过这个方面的code不是很懂啊
雪    币: 218
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htsf110 活跃值 2009-4-4 13:04
4
0
学习一下!!!!
雪    币: 221
活跃值: 活跃值 (11)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
himcrack 活跃值 6 2009-4-4 13:44
5
0
不错 基于此也许可以开发另一种外挂汉化..
雪    币: 100
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
夜凉如水 活跃值 3 2009-4-5 07:42
6
0
统一楼上的意见
雪    币: 140
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
runjin 活跃值 4 2009-4-5 09:26
7
0
这要看看游戏本身是用什么办法来绘制文字了
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kingbe 活跃值 2009-4-6 00:39
8
0
可是……这个方法不能把文字显示到别人客户端上去啊!
雪    币: 273
活跃值: 活跃值 (16)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
popeylj 活跃值 6 2009-4-6 11:06
9
0
感谢lz提供这方面的分享!
雪    币: 221
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ccgto 活跃值 2009-4-6 20:47
10
0
如何把画面保存为bmp?
雪    币: 217
活跃值: 活跃值 (17)
能力值: ( LV13,RANK:530 )
在线值:
发帖
回帖
粉丝
foxabu 活跃值 13 2009-4-7 00:01
11
0
g.cn

->

诛仙D3D游戏环境下如何实现真正D3D的窗口,给出关键代码及其方法(希望大家在D3D游戏中做出D3D窗口)!

-<

更有意思。
雪    币: 211
活跃值: 活跃值 (15)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
脱壳请教 活跃值 1 2009-4-7 04:03
12
0
感觉用DrawText速度比较慢,不大适合游戏里显示汉字
还是用绘图的方式快点,就是需要创建相应的字库图比较大
雪    币: 1099
活跃值: 活跃值 (310)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
loqich 活跃值 2009-4-7 14:29
13
0
dx8 很慢 是用GDI实现的
dx9 很快的 已经不是了
雪    币: 204
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
udknight 活跃值 2009-4-7 15:31
14
0
楼主以前是盛越或者是壁虎的吧
雪    币: 250
活跃值: 活跃值 (58)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
weolar 活跃值 10 2009-4-8 10:59
15
0
感谢分享!虽然对dx编程不懂……
雪    币: 297
活跃值: 活跃值 (72)
能力值: ( LV9,RANK:220 )
在线值:
发帖
回帖
粉丝
noword_forever 活跃值 5 2009-4-8 17:18
16
0
每个游戏输出文字的接口都不一样,基本上要找到以下要素:
字符串
位置
颜色
字号
然后才能用自己的代码接管过来。
雪    币: 140
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
runjin 活跃值 4 2009-4-9 02:05
17
0
我和那个没关系,你不说我还不知道.

游戏内广告..........
似乎是一个不错的应用.
雪    币: 215
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
veninson 活跃值 2009-4-9 04:56
18
0
看了不少hook dx的文章,都迷迷糊糊的,今天看了楼主的,终于懂了
雪    币: 153
活跃值: 活跃值 (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
stoy 活跃值 2009-4-9 12:46
19
0
学习了
雪    币: 109
活跃值: 活跃值 (134)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2009-4-10 17:19
20
0
这个办法不错。
雪    币: 2147
活跃值: 活跃值 (462)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
kagayaki 活跃值 2009-4-11 16:21
21
0
收藏!!!!
雪    币: 97
活跃值: 活跃值 (16)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
nence 活跃值 2009-4-11 17:22
22
0
我在星际里边咋用不起来?
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
akiel 活跃值 2009-4-11 20:20
23
0
不错, 收藏下,慢慢理解
雪    币: 140
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
runjin 活跃值 4 2009-4-13 14:57
24
0
星际不是dx9
雪    币: 204
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
udknight 活跃值 2009-4-13 18:27
25
0
N年以前在CodeProject上面有人就发了份完整的接管D3D8和D3D9的代码,其实真正实现了完全接管的话,3D方面的东西都是可以添加进游戏的。关键的问题是客户端接管的同时还要绕过NP等保护系统,还需要考虑到Vista系统的hook,同时服务端的带宽也是个不小的问题,带宽太小游戏在线人数过多,资源同步就成了个大问题。而实际各个游戏实现又不一样,简单套用相同的代码并不能通吃所有的游戏。不好弄啊这个。
游客
登录 | 注册 方可回帖
返回