本帖最后由 shane007 于 2021-2-13 22:23 编辑 - Z, d# M0 Q* |' D: P2 P& u
( C4 B, I* }1 T0 w, ^DirrectxHook 工具
# i) Q/ {; a) a" i, @( b9 |' A/ b9 [# C8 V
标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法
1 B1 p- j0 M1 R) Q0 a% V' a+ s9 n作 者: runjin4 ?( I, a1 y+ Z( r1 M% y
时 间: 2009-04-03,23:44% g" G/ P( }4 W1 t: p6 v8 I
链 接: http://bbs.pediy.com/showthread.php?t=853688 S+ i5 A+ r2 t+ r% r. O7 D8 I
$ Z9 s4 H: _1 X* x6 G: YHook Directx:在游戏中显示自己的文字和图形的方法
4 z7 x. a- r- V: P' h6 V; t7 T! [ S+ d6 P$ b
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.. R; Y+ r, E# ?3 V
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
5 ]% q h0 U$ w$ U2 N还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
- j/ r$ ?: N- e7 s首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.$ \2 V! U' Y( A( I& M9 u N1 n3 J
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址) ?; n7 ~) `1 ^$ [. s9 s& x5 C
DWORD oldpro=0;* X2 A9 P4 h# r6 d, ]+ K2 r1 D' d
memcpy(d3dcen5bytes,pC,5);
, c, ?& n8 K$ L# u) f& I VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);" D- S* i& @7 s r; a3 e
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码: V" ]4 X; G# D' J0 i
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5- m* f) p+ d- N
, x6 X+ {! D: T8 W/ M7 Y8 u, d6 X( W- a2 i
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
+ f5 P$ M# [" g' [# A9 i* o: [HRESULT CreateDevice(/ F7 b- G" g7 j: G
UINT Adapter,0 P( B% i: N0 v; R" L% N% z. @
D3DDEVTYPE DeviceType,
/ z# L6 n' [2 E5 j1 Z; N! d5 N HWND hFocusWindow,1 Z& ~3 H& |! A& Q; F
DWORD BehaviorFlags,
. C/ F9 ^7 s3 Z0 R* I D3DPRESENT_PARAMETERS * pPresentationParameters,
2 y9 ]- }$ M- B; B IDirect3DDevice9 ** ppReturnedDeviceInterface
6 p- } j. x" B1 x2 W/ e( W8 ~);
) c' L6 r( ~4 \( J" N
- o) L! s: O& C+ r( ]* |* o9 U7 \! T0 u9 F1 ^
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
9 v1 u1 }7 ?/ t7 S! WHRESULT _stdcall hookedCreateDevice(+ y' }( H. v% A/ r# B
LPDIRECT3D9 pDx9,
0 e7 A2 v9 F4 B5 P UINT Adapter,+ m4 w) x+ \) T6 j" d3 T @9 T
D3DDEVTYPE DeviceType,
2 L/ w" ]6 i) k0 B HWND hFocusWindow,
5 y! J/ P2 ~3 d: B# }# {2 @ DWORD BehaviorFlags,, V8 W) M; c: o; D3 s/ K
D3DPRESENT_PARAMETERS * pPresentationParameters, K P6 g# L' B# D
IDirect3DDevice9 ** ppReturnedDeviceInterface
$ M5 O' R/ [6 K! t0 Y! @5 w0 b8 {( M& Y& f3 n- C
);' ]- o4 r$ N( H# _2 l6 d
5 e2 \6 K9 \1 V4 ]! w! k
其中的LPDIRECT3D9 pDx9就是this指针.7 S/ G, I8 m8 \
, ]" a5 Y0 n% m8 @7 n: V( z2 g) j( m- khookedDirect3DCreate9的关键代码如下:
' C9 |+ ^* z; u& ^" ^pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针! Q) l# }( ~6 W5 N6 J$ n
( n8 w4 e/ I/ {1 G DWORD oldpro=0;
7 `. F O2 C8 @3 k7 C+ ?8 k memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节5 L4 _- R* O1 E
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);. p; X( _6 n, B! C6 }
*(BYTE*)pCdev=0xe9;
! N0 _' ~1 F/ { v6 M5 ? *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
( f3 X& M! L* S: x z" d% E$ |6 X2 G
" z x0 A( `$ ?$ @3 ^在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:$ I( r: n/ l5 I R5 Q r2 X
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
4 }( P+ J* \' s+ a m. ~ memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节, ^: u- A$ O V4 r* b
DWORD oldpro=0;8 D6 G0 @: H8 A9 J8 W& d
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);! |8 H2 G; j, v" C. X% N! p5 K
*(BYTE*)pPre=0xe9;' U2 U7 d) r6 c$ p% c, K# b! v: p
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
5 k* i8 d1 Q) Y9 z3 u* K. h) x9 l+ z' M C' S# e* D4 { E
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
% Z6 Z# x* n5 Uchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";+ `* P* ^3 W" l) u; z1 o
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本. |' R: A3 S/ j& P1 y
//在这里写入您的其它绘图代码8 F2 P, j3 V' t
8 D) m/ ^; o, y7 U; _: o
$ T8 P8 }# @: |) Z( }4 S+ U1 [再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
" g! |$ m, K' ~9 n5 F2 [BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)% _9 b* m2 X2 r' f$ ?. O) {. r1 m
{
0 U( z+ f# _8 P7 i0 }0 T3 c' j _+ \- G: j2 G0 `7 o9 S4 `, i
if(m_pD3D && pDxdevice){' Z9 n9 F+ g6 d5 p& u9 d
4 Z( [7 T7 b7 Z- x
RECT myrect;1 W7 m* g' `& I/ `# }4 W: _1 v
myrect.top=150; //文本块的y坐标
, x/ B9 Y; h9 [" D myrect.left=0; //文本块的左坐标" v3 M- T& V0 \& J$ p$ ^1 d
myrect.right=500+myrect.left;, X, K( {% g1 e- X/ O3 k% _
myrect.bottom=100+myrect.top;$ q8 \& W' d- `- h/ ?/ l* K" F
pDxdevice->BeginScene();//开始绘制
z0 Q* e$ `/ V! Z
8 l9 I( h8 Q9 h0 w D3DXFONT_DESCA lf;, o1 i; k5 O9 o2 e
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
5 P# p: c1 |* F& R lf.Height = 24; //字体高度
1 i6 o. O, U7 W& s$ k1 X lf.Width = 12; // 字体宽度
( n2 Z5 A8 z5 y/ y, h" k7 P lf.Weight = 100;
- h, E3 |) ~9 S lf.Italic = false;
s0 Z1 g& V8 z2 ?* E lf.CharSet = DEFAULT_CHARSET;! T- H" U, F, T2 ? Y4 ~) _( ?
strcpy(lf.FaceName, "Times New Roman"); // 字型
$ y% E, v7 E X, ` ID3DXFont* font=NULL;
- E" f1 X5 Z* Y H3 A4 f0 v- X3 e [; ? if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象8 f' h% Q' Z; z! w) i
return false;
) ~/ M" b2 ~. @; p& {) g; C5 J# g2 f
font->DrawText(. p* v4 s* q# @/ _% J3 y% a
NULL,' c( Z! F; k4 u. n, a
strText, // 要绘制的文本
- g# F9 F$ j; w) E* U: W: Z$ c nbuf,
5 n7 x2 C% F* P3 \ &myrect, 8 y& }2 E9 D7 d" l, o: T; ~
DT_TOP | DT_LEFT, // 字符居左显示9 t n3 ]( F" L& C5 [! O
D3DCOLOR_ARGB(255,255,255,0));
: O6 M5 x0 k7 ~: ` j% W f, s* k$ j/ v' M, t' V
pDxdevice->EndScene();//结束绘制' F) M: x% g! p1 H7 ]3 |1 z; C
font->Release();//释放对象 U. v5 z' L% }) |- M
}+ l% y9 P4 o( Q( u* X! O
return true;# ~ W+ r1 V* Y# M2 \. X6 M C
}
. k9 Y. A$ _" f
% C+ A& i2 v9 P0 Y0 ~. y( W效果图:; S/ M: v% u& X
/ p% {3 D N$ y' y1 O
: \; @9 t! O; }$ s; A- `% U1 Z& _* p8 M* C' y; c% t+ q
3 `( W! W+ r$ j2 Q; s
3 [. s2 |, p' r( e: ^; i: i! }1 U. f: E) i& I" p' _5 D
后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.8 j/ N& q* R* C. b
; W- a) E* |" Z: K9 \* g0 w$ N* l& P8 ^ C" l
|