标 题: 【转载】在游戏中显示自己的文字和图形的方法 {$ `9 d ?: t. N+ k" I
作 者: runjin1 m& T+ w( N6 J, o4 |
时 间: 2009-04-03,22:44:51
7 l) w; b n( B2 A链 接: http://bbs.pediy.com/showthread.php?t=85368& u# d# c; ]4 E+ d. [
- J7 j9 |: U# O- y% N2 B SHook Directx:在游戏中显示自己的文字和图形的方法! W/ m9 c$ `: r' u+ m$ t/ k/ j& q
& n. z9 N; E( v! n$ }这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
$ f* @ A9 ?* O) e其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
: [- g9 q; \) b/ J( B7 _还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.6 ?7 u# N- R, Q; u* L {- ]8 Q S H
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.# x: B! T( ?8 l, K
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址) V d- ]7 A* U' j. a
DWORD oldpro=0;( \) @8 p9 U7 P/ C
memcpy(d3dcen5bytes,pC,5);
* j2 I1 D' |' t; N6 ?% S! F VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);6 u3 A2 W8 \% k; W* U. t
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码7 h( u( M9 T& `3 r4 F+ g1 T2 F# a
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
8 Q: ]! S) A ?5 i. p7 W( T% A2 P* m
& j0 k# R# K0 \8 m$ Q这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:& U! j" G9 r+ ~2 q/ W
HRESULT CreateDevice(8 ]! v1 ?/ |# l3 b
UINT Adapter,
) |* ]& B0 z# g; l' C( T D3DDEVTYPE DeviceType,
6 o$ Q" r3 D( |) J6 L" ^1 C4 u HWND hFocusWindow,! e! n7 f+ O7 l+ _' \& B
DWORD BehaviorFlags,
1 s. w' d, }* l; r0 I D3DPRESENT_PARAMETERS * pPresentationParameters,9 V: o5 Y7 F( R
IDirect3DDevice9 ** ppReturnedDeviceInterface# W7 M* A" f1 S$ B) O: T& a
);
+ n0 g m+ `" n7 e8 L3 P( L! N7 a2 |8 W( w4 p* A* t
/ _# k$ X3 a( @而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
, b. D( m/ P5 `4 v" @6 [HRESULT _stdcall hookedCreateDevice(7 H0 B4 m/ |& z
LPDIRECT3D9 pDx9,& R C; O0 r2 W. I1 M/ C4 V6 G' J
UINT Adapter,: Y5 r8 d9 N$ g4 F5 q- q
D3DDEVTYPE DeviceType,
3 g( J4 x6 D% a' b q- E. ~ HWND hFocusWindow,
0 _: L& i& f% E& a- X) N DWORD BehaviorFlags,
( M5 c0 P2 z- t# P" e+ A D3DPRESENT_PARAMETERS * pPresentationParameters,+ _4 W V* i2 O& y! b( n3 `
IDirect3DDevice9 ** ppReturnedDeviceInterface
- y: T, E. h/ c; N$ h0 W$ N* z2 k6 V/ L# O: R0 M9 d0 A
);" |( T ?& `; m/ U) \' Y) ?( `, A* I
- ~5 J. W/ S0 O1 W! i
其中的LPDIRECT3D9 pDx9就是this指针.' o1 S" x9 x7 C5 y& }: }5 C4 K% _
+ @- f; _2 \9 G/ x5 Z' c
hookedDirect3DCreate9的关键代码如下:3 i3 B s- r8 L. Y7 Q2 `
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
; ~+ x6 v& O! D1 n4 d& Z) G+ V) W0 b( r; N/ r$ X/ m% |% Z
DWORD oldpro=0;
" c- L6 C+ Y/ d* {0 v memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
3 k2 T' s( ~# q" }: L VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);$ O% k" x2 v- J L/ t
*(BYTE*)pCdev=0xe9;# k. x. J- l+ P) L! D+ g: k1 W% O' D, N
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;! L! p" W8 R) T/ J; c# J6 n; m# E
' O3 W! w! ?1 d" j: P
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
; F0 w- l% X! F J: c7 @pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
" O2 ]" E) ^: [5 G memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节 [! H2 ^) `! n. q- v
DWORD oldpro=0;$ x: @) K* M4 W2 L# k
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
( p& f( U+ M7 E+ l/ T *(BYTE*)pPre=0xe9;9 h$ Y o' T0 L. e% {
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
( \! z4 O; S+ I% B4 [" E. p+ ~, a- K! C2 N
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:- c& J4 N3 K1 `: R1 ^: A, V
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";- K0 j6 a' G" K6 {9 }% E1 W, H
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本6 W8 ^0 P+ z1 B) j) O8 L5 {5 q# I
//在这里写入您的其它绘图代码9 {! W0 f+ j7 q; `9 A) h; |
, _9 k# f. A2 [) Q8 d' S0 u0 X x; J, g7 h) S- j
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
, d, k( T3 `6 T" ^/ E, ABOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)3 K) ^$ f( U0 Q
{
% d0 e! j! ], _! w6 `* c, }# [! A! ?
if(m_pD3D && pDxdevice){8 o1 O( g& T: ^' a8 ^% ?# g$ d
- X5 p) W& N) q
RECT myrect;( x" `" r+ s0 h! V/ O$ Q
myrect.top=150; //文本块的y坐标
3 e& F) G( n1 Y" O5 d" E myrect.left=0; //文本块的左坐标
8 Q' \5 F; a: g; ^6 y myrect.right=500+myrect.left;" m8 C, @/ A% E5 z
myrect.bottom=100+myrect.top;( S% x3 T: g7 G
pDxdevice->BeginScene();//开始绘制1 I! ^4 H/ t% e" S/ u
. l3 s* e; L" S) z& g0 `# ]/ ~, a4 N D3DXFONT_DESCA lf;
" O" J& J! u* Z5 [5 e9 y" s ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
7 g) P B; [& ^5 r lf.Height = 24; //字体高度
$ I$ O6 O5 b. G9 ]% k( x lf.Width = 12; // 字体宽度
4 J6 b& q. H0 J* ~ lf.Weight = 100; / @" k* n( Y' q
lf.Italic = false;
$ h* i ?5 Q: y2 a' | N! p7 ` lf.CharSet = DEFAULT_CHARSET;
: |: \- V- r2 F* S' G3 m# } strcpy(lf.FaceName, "Times New Roman"); // 字型7 @; P7 X& x* o2 d& @: F! f
ID3DXFont* font=NULL;
1 B9 d9 r2 U* K if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象" I9 @) @& A2 J
return false;
# G* T3 N) U, _8 ^" H y- |; _) S- o/ w& ^" n5 J* y0 u
font->DrawText(5 X% x2 l* ^: ~; D3 R* e+ j
NULL,# N5 L8 `0 c5 `. g6 s
strText, // 要绘制的文本9 T' W. L4 X8 m7 O
nbuf, + c4 Y( Q0 H- G b, e
&myrect, : j) s& z% |2 M6 e" M; W" w4 ~5 m
DT_TOP | DT_LEFT, // 字符居左显示
2 @ s- _9 \# t# g7 W D3DCOLOR_ARGB(255,255,255,0));
& o- U% D: ~9 j- p, k# I$ x
( u; [9 J7 b0 o0 Q+ S1 e pDxdevice->EndScene();//结束绘制3 ?' r2 W4 V# w; P/ ]2 i0 I
font->Release();//释放对象9 W: @/ ^. q6 m3 C7 R) e
}
( Z l* [( B+ c Z) [. @4 s return true;
; Q) Y2 O- U0 b- G, l$ Y1 @# U} |