标 题: 【转载】在游戏中显示自己的文字和图形的方法4 Q. _: u1 F5 y1 r% F
作 者: runjin
. }) N1 y [, t3 N! Q时 间: 2009-04-03,22:44:51
' b' M7 c7 D% w链 接: http://bbs.pediy.com/showthread.php?t=85368
- X$ g+ |+ q/ j4 e& T# V( ^5 p9 t5 p$ b
Hook Directx:在游戏中显示自己的文字和图形的方法
# D6 `3 a7 u' w L! V, L
! N/ O% {: {5 A. d7 i) M这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.6 \% x9 \, w% d
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.! V G' b& k' P: q7 _; z
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
& a* g1 h- c. G9 ^5 o: U1 V首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
" m; |* q1 Z1 t1 c pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址: O9 ~1 }6 R% x/ J# }1 m
DWORD oldpro=0;
/ V5 d+ j6 ^5 \: _$ y memcpy(d3dcen5bytes,pC,5);2 y* V# H2 m" M5 C# W
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
( A8 }8 w" B' p8 z4 w *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码# s. o1 o8 @2 o/ ?
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
0 s* K0 f; K) e' S9 ?* T5 c7 x$ j3 `0 p* x
9 @6 r7 L* S. H* D/ w& \$ z! K
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:9 h& P9 x; Z- h; _( }% I0 ?( r
HRESULT CreateDevice(
7 x) U. C0 ~8 L7 W$ U+ i2 d UINT Adapter,1 l% g# v. \' e& i& T! O3 l
D3DDEVTYPE DeviceType,( S; n* V1 P& l; v6 S+ G
HWND hFocusWindow,
* x$ a6 L# w, `: b. g) t DWORD BehaviorFlags,
, V/ S# ?) O3 q D3DPRESENT_PARAMETERS * pPresentationParameters,
) N* z& n; |: X+ x& v IDirect3DDevice9 ** ppReturnedDeviceInterface
$ ^1 @# K. e0 g5 |& I2 f);# Y- F! G. B" f
O- Q# t0 K) m! U x: g+ s" O
4 ^, g9 c/ V ]" f. E0 K' D1 \
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:& n% F$ f( R3 a; r
HRESULT _stdcall hookedCreateDevice(7 @4 v2 x7 p" U# G! B' T# [
LPDIRECT3D9 pDx9,
% n t* K8 A/ @# W( E( p0 w UINT Adapter,
# w! ~+ M$ g+ R& k D3DDEVTYPE DeviceType,
# g' g4 ]& s3 X7 q8 n7 e HWND hFocusWindow,
p# ]) j- v/ ?, Z, s! z DWORD BehaviorFlags,
2 K# P- s. p8 H! d$ X D3DPRESENT_PARAMETERS * pPresentationParameters,
2 V# |9 ?; H: t( W& P) H IDirect3DDevice9 ** ppReturnedDeviceInterface
- { d7 O U/ l- x! e
/ o; T* }' V" z" m: o! M: d );
0 w; D+ n5 x4 N; N- }" \: ?" |' s+ Y4 i# e4 w7 [# ~- b: s
其中的LPDIRECT3D9 pDx9就是this指针.
2 P( h3 y$ f4 |) J! I& t, N
8 O+ B' f$ c; H7 p" LhookedDirect3DCreate9的关键代码如下:
; O$ C; }9 o7 Y3 j. u: H& ~: `pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针" l- w- c6 `7 y
$ m, F$ i* H7 b( p; {* N2 Z( _
DWORD oldpro=0;
& f: F+ K, q; B# b4 H6 t memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
$ z: S8 `0 Y0 @+ b VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);1 k6 E1 g* b- @: p
*(BYTE*)pCdev=0xe9;
8 j2 }! u7 l1 b" u *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
5 W: q; e, n! n3 X' d- v" @ i' y% i: ]
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:" J- x8 `+ j- v$ L: E) P
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
- \! u w% X \# W! H memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节7 q j3 O0 D& _& }+ K2 K
DWORD oldpro=0;' t. }3 `9 P7 @
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);* W/ O: G0 v3 a& t8 o6 s
*(BYTE*)pPre=0xe9;! N2 B! d7 ?* B
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;/ @0 f& V. u. t6 `
! f b& Z$ C/ u# H4 n7 K
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:5 O2 [- X! x0 K( u6 m5 {+ r5 k, w
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";" b9 r- _# @$ L$ C0 F& G, k2 \
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本1 o0 d- N! `! K. d* _
//在这里写入您的其它绘图代码
! y. V5 E' o' B' @$ K* K& q u7 e0 D& q( @* S
& l R E3 R6 N; y* @再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
1 T4 b6 d6 d7 |: S3 i" S( f$ E5 d, mBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)/ D7 r- w7 P; R! ~
{ O; v2 A o8 Y# g# Q
2 j! g/ Y B* j: Z if(m_pD3D && pDxdevice){
, v* ?, N! F9 ?
+ ?- N9 r' u+ B9 ?& d) v1 \ RECT myrect;
/ v( @9 {" P! a1 c) x myrect.top=150; //文本块的y坐标5 i' i& t$ O' }) v# K1 p. M
myrect.left=0; //文本块的左坐标9 Y! a* I/ g4 H ~
myrect.right=500+myrect.left;+ z. b0 ~% h+ A6 T' d5 {6 q
myrect.bottom=100+myrect.top;
8 P' u$ [7 u6 o9 w0 I; A pDxdevice->BeginScene();//开始绘制- v, D5 R2 M3 x, r" f% U$ i9 Z
8 ~8 g6 a7 B, L
D3DXFONT_DESCA lf;
; m! s- T- F& O$ @- p ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
3 q4 N3 X; v+ `% l. M1 T lf.Height = 24; //字体高度
3 k. h, N ?! O6 L9 W lf.Width = 12; // 字体宽度& g6 t4 p5 V. ?% }% o
lf.Weight = 100;
9 t; @7 }' L' L; a+ \; } lf.Italic = false;' q. d5 R- }7 P }$ |! m
lf.CharSet = DEFAULT_CHARSET;
" H$ `) z. @2 A' j" u5 }; j strcpy(lf.FaceName, "Times New Roman"); // 字型
; G+ h3 G3 ?. S P, b- g ID3DXFont* font=NULL;: o5 N j: x) E2 Q1 g
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
* Q% X! Q3 Y; T: }" z8 e6 A return false;$ j$ Y* _' z5 f1 P W$ j8 Q/ W
! L8 V# e) h. a font->DrawText(8 B0 h3 R" r, D' ?% d* F
NULL,
9 J5 L" {: J8 p strText, // 要绘制的文本
$ V% e$ ~+ ?0 q nbuf,
[( d* Y; d3 w/ l* \ g0 h &myrect, * y$ k% y- D, e. H
DT_TOP | DT_LEFT, // 字符居左显示; l1 R8 A' D6 q9 p( g
D3DCOLOR_ARGB(255,255,255,0)); * `8 S5 u: ?/ J- G3 D! x& R4 C9 f
/ e, x- q: A6 e, o- D! V5 E T pDxdevice->EndScene();//结束绘制
9 o5 o9 ], G: k1 G$ d1 { font->Release();//释放对象$ U) @6 f; @) o
}' y; U8 [, C) Y7 j5 w
return true;* Y6 d* T7 o* l }: o7 E7 K
} |