标 题: 【转载】在游戏中显示自己的文字和图形的方法
5 N2 {- q! t; h作 者: runjin, B1 s4 G7 r6 [, _; e
时 间: 2009-04-03,22:44:51
* @6 N3 n" `# \, x链 接: http://bbs.pediy.com/showthread.php?t=85368" g. z8 U2 z4 J: U) y8 Q+ Y
. U ]# h# b" t( `
Hook Directx:在游戏中显示自己的文字和图形的方法
; Z3 \, d- h$ ]4 M* D c e: \2 [( B: {$ p+ `
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.9 ~" q2 t! D2 C
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
C- `0 d' z% R; y' u. y还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
+ D' n2 ~5 X$ ?) l( _: o首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
: u1 b* p# m/ I" ]2 [ pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址) z2 `9 r/ K; I2 u4 |9 ]2 i* P& X# F$ b
DWORD oldpro=0;
r- T0 g' q1 ]' z memcpy(d3dcen5bytes,pC,5);
% z2 V" x: J" F6 Z VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
# k& i j+ ^0 }: T' e *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
+ M* |2 Y* q! u% Z6 R2 e" Y *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5% R4 i# N8 `& Z$ Q c, }4 q* f
1 a1 ^% I; F' q" J0 Z1 Z/ ?# G0 O* y* _& d8 p
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:5 h) ]. a9 Y2 H$ |: Z
HRESULT CreateDevice(! A) s$ b* A5 ]: _/ s' c
UINT Adapter,2 O V! Z3 H9 S/ N; }
D3DDEVTYPE DeviceType,
2 g9 I+ t# o& o) d/ Q/ t HWND hFocusWindow,( D( Q2 i# U4 @. W# N
DWORD BehaviorFlags,
( L1 {. t) x% S* ^+ @9 v D3DPRESENT_PARAMETERS * pPresentationParameters,/ T8 A6 H* n5 r. L/ r e- {
IDirect3DDevice9 ** ppReturnedDeviceInterface4 e* T3 B3 Z3 N( M r. m q) c
);
) c9 t7 ~$ R* G' n% _- u9 c6 z, f( U1 r
, p; a. E+ V _( X5 M4 u6 \( Q, K
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
+ B; ?. I. D- ZHRESULT _stdcall hookedCreateDevice(
! F0 m$ [- F X. e- N" R; X7 n/ ^ LPDIRECT3D9 pDx9,1 V8 J8 ?, R# G1 e
UINT Adapter,
+ b2 `1 Z+ a3 r; y- X D3DDEVTYPE DeviceType,
' r) u" v2 [" ]! J% E HWND hFocusWindow,8 ]& D6 o) V' u9 v( N
DWORD BehaviorFlags,
0 q$ R3 g, [$ z4 L6 G D3DPRESENT_PARAMETERS * pPresentationParameters,
9 V8 h1 {3 J* }+ p. a) B8 i IDirect3DDevice9 ** ppReturnedDeviceInterface" E, N' L( ^2 E2 m* A
( I, [) _8 z" W7 h );
1 v' J% P$ J M8 b
1 @& j: o; G( L0 ^# N" e2 ]8 L其中的LPDIRECT3D9 pDx9就是this指针.) s1 C, B) k" ?- h6 t$ S z: t
D4 X" m* o }* w3 i; y6 Z! @. VhookedDirect3DCreate9的关键代码如下:
6 R2 l' l& N. {2 }( X) u# JpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
5 ~1 y: F3 T* V f: J3 C4 H' |' S: i( T2 t
DWORD oldpro=0;% [. z3 u* n9 r E) b
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
! z2 J. O2 t0 G VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
8 E0 i1 x0 G% j+ Q( l3 U% E5 q% x/ ?$ G *(BYTE*)pCdev=0xe9;! }4 c7 {& q9 m1 v" n% G
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
6 R3 H& b) b P" n& ^) T) L: O
5 C& U( g# E" r7 K; j( z在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
2 l G5 @! e* ]' GpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
" q+ {5 a* E8 C& M# R* x memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
1 t4 c1 c+ K* U& p' L DWORD oldpro=0;
3 r7 g( \7 y- J2 P VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);8 {. B3 n( T6 x1 w3 j5 Y
*(BYTE*)pPre=0xe9;
: k/ K" i. C7 f! |0 T! _ *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;7 P* m. u+ z( g) U$ }. Z
) {* @& J8 ~5 a$ V% @实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
l& F9 l+ S& F2 f: S4 e% `% Mchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";5 ~3 O8 C7 ^9 }
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
. y2 f( v$ r% m //在这里写入您的其它绘图代码1 s0 }9 g7 t) a9 o
% |$ _; E* R* u! T2 T% A; t& }
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
2 z7 m* @0 j4 |- q! gBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)0 y l; d" o/ _! N7 P/ b
{* h# U8 v& w8 M
5 n% ^% X" A6 P6 E# i if(m_pD3D && pDxdevice){' T1 J8 i/ ?. U. N5 o' @) T
! d' N" O- g& P ]+ [3 p
RECT myrect;
) g/ I$ J0 @6 [" t. @ myrect.top=150; //文本块的y坐标& } j* r }% t
myrect.left=0; //文本块的左坐标
# t3 w8 s* {2 Y5 C myrect.right=500+myrect.left;* C' `% k' ]( w
myrect.bottom=100+myrect.top;
4 z) y+ |, X( M/ h pDxdevice->BeginScene();//开始绘制
$ O" a6 }7 q9 g) W$ N
5 @0 i6 w. \! r+ H M D3DXFONT_DESCA lf;) T8 r1 t, Q. I5 P" b! \
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));2 [# @" d- C/ [, {7 ~( k
lf.Height = 24; //字体高度3 ^$ F; J/ h$ S) v. k
lf.Width = 12; // 字体宽度: q' A' f4 N; I% W5 v) r
lf.Weight = 100;
/ e5 x! q& ]6 v lf.Italic = false;! u5 j" E, \9 Y' m+ e0 q5 v
lf.CharSet = DEFAULT_CHARSET;
3 F2 @9 I2 m% k0 y$ T strcpy(lf.FaceName, "Times New Roman"); // 字型2 Y6 ?, G- Y& B- c; J* A
ID3DXFont* font=NULL;( S- K0 u; x1 ^& |- m
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象6 a+ P4 I, A& ]3 z. y
return false;
: K4 F* u. g* m8 l6 T! F) x. s1 a9 n+ |7 H6 |! p8 W
font->DrawText(2 _$ _" k# k* G. r- Q/ ^0 D
NULL,* R0 U1 d1 g! Z+ j$ [
strText, // 要绘制的文本
3 y* l) D' E# w2 K& f5 \+ ]) Z nbuf,
& s4 U7 m6 @# `- B' } &myrect, ; p, D4 B% l5 f1 ?
DT_TOP | DT_LEFT, // 字符居左显示
, T! N' R8 N8 v D3DCOLOR_ARGB(255,255,255,0));
; E3 @' M) X: f4 a0 K# E+ V8 D ]- p; g) ?
pDxdevice->EndScene();//结束绘制
) L( x+ P; I/ J- R0 m0 P: o font->Release();//释放对象
' P/ c ]+ E. L8 @: q }$ D& A+ G2 f+ k- | C+ |
return true;
5 x. a% ?* P! Y# F E# D5 G U8 Q) }} |