本帖最后由 shane007 于 2021-2-13 22:23 编辑 # ~4 \. K2 T! `
/ Y5 r3 A9 h$ R# N1 C. i( Q; ]' {DirrectxHook 工具
+ `, B1 Z2 k$ M" t
- d. B+ O) H" R8 D标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法; ]& I+ f, L! \) ]: N
作 者: runjin7 T Y7 W) Z1 {& D- z3 m$ ]
时 间: 2009-04-03,23:44& \8 x8 T% y( f# S0 @( O' b
链 接: http://bbs.pediy.com/showthread.php?t=85368
# u" Q2 B; N0 s
6 X" J: k" T- rHook Directx:在游戏中显示自己的文字和图形的方法/ W7 s1 W( v, F5 g! u, m
! O( L2 j8 ^: a8 y# ?! D' z9 A这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.7 Z3 @; S2 a' `
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.; P( s9 X. R; N: j) e$ Y( w
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.4 ~$ X! B4 z$ @ R' A7 q* P
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
& W/ k6 Q+ U5 X+ l0 { pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址9 L1 r& }8 W/ u' ~
DWORD oldpro=0;
; s- R! l9 j/ `! O memcpy(d3dcen5bytes,pC,5);
- x& I U( _4 A* o- x/ H& k VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
P/ `0 W s8 M& Q *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码" T0 u/ P, c+ j9 I/ ?
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-56 n) a/ I- x1 l. E, q
2 [( t: \3 ~/ e$ j6 @* n
. @$ o, ]- H3 i9 L, c. f这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
& u) i+ q `6 \# w1 {HRESULT CreateDevice(- G3 A( m. j* q! y# S7 e7 }* m
UINT Adapter,
9 F1 c7 _; i; v7 b" l1 t0 Z) X D3DDEVTYPE DeviceType,4 a( j; [% @) O4 \2 I" l! Z
HWND hFocusWindow,
7 ^, l0 P! C. |" U DWORD BehaviorFlags,
3 U; ^6 t# l& z, F D3DPRESENT_PARAMETERS * pPresentationParameters,
/ u, B1 e" {9 z; w IDirect3DDevice9 ** ppReturnedDeviceInterface( |5 \- X* e) f* p
);" R! `: [2 H# ~: Z
" z" K+ ^2 B; ?7 N6 g
0 G( V& s7 R" e* ^5 r而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
% h/ ^6 X% Z" y$ H1 W5 N* XHRESULT _stdcall hookedCreateDevice(8 B, C7 ?' ~9 {! ~5 N
LPDIRECT3D9 pDx9,
: h4 x5 X, t; I/ U/ @ UINT Adapter,
: b$ w$ y1 T' i D3DDEVTYPE DeviceType,& a* K* R6 M6 `8 c) U; w
HWND hFocusWindow,
; A7 A' S9 c9 E. L6 @# D DWORD BehaviorFlags,
6 K) k# L4 k& p8 t" ~) v' F7 ]1 P0 Z D3DPRESENT_PARAMETERS * pPresentationParameters,
% Q& N) g- U: g: q IDirect3DDevice9 ** ppReturnedDeviceInterface
" W* U. z7 i! j7 w! ^, U; B0 e% M" n5 T
);
3 m9 S7 O5 g6 v( ]; i5 Y/ E0 f3 W! E0 ~+ d
其中的LPDIRECT3D9 pDx9就是this指针.
' i) O( x) G/ z. Y) s
1 S: P( Z9 L( hhookedDirect3DCreate9的关键代码如下:# e/ Z. }. a6 d5 ]" U9 S
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
7 H/ h5 d5 S9 i$ N( i
$ c. Q! ?1 S3 H8 Y DWORD oldpro=0;7 `. H, G: @& g- O+ ]- b: m0 i
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节$ J$ P% X6 P5 ~4 c/ J4 E5 K
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro); `% s$ e0 S8 r! t# F
*(BYTE*)pCdev=0xe9;% k) ~, k" q8 w% U# _
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;4 |" _6 B! F( p+ v' J6 j9 @
& w& }5 f7 V! ^1 |在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:/ ^+ K/ j/ ]! N( {( G
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针' `# F ~! b1 z
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
" r- A% E4 j# O9 k DWORD oldpro=0;
. T9 ~; C! u9 P5 ^ VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);% }; a% G. S1 w; [% S2 A8 a3 x
*(BYTE*)pPre=0xe9;0 i* C4 @, G. e. J
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;7 ]0 V( s% ~$ Z2 o4 c9 w' d
8 Y& X! U# T0 R* A' d+ J) o6 A
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
- t% E7 Z: `) }" X7 Q- e+ p2 Y0 [char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";* l5 e; Z1 d' {. p, w' Y
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本3 N5 S2 `1 c* b" w' b! @0 e: k8 x
//在这里写入您的其它绘图代码. j7 k8 ?# g4 i% A
' o0 w: \ }1 |5 H; ]2 \. t4 k3 L1 d# \( Y! w- B. V. o; h! h: E
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
, {! S! w/ Y2 R! M! j# ] H+ T0 z% {BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)0 i! m- h M2 }: N. |: f5 g; |
{
0 {7 B' I! R. X4 O# y
* x2 z3 b: x+ v5 T7 _/ R" L1 Q `" w if(m_pD3D && pDxdevice){7 Z. O- F2 t/ f( o4 \
, E; j6 K+ k# d' E4 m
RECT myrect;
) k9 `" y9 f' p4 s* K" G1 L myrect.top=150; //文本块的y坐标
) A& c5 a; W5 ?5 U. X4 F, A myrect.left=0; //文本块的左坐标0 @& ]% ^5 l) V# b9 Y5 P9 Z! U
myrect.right=500+myrect.left;
5 a S- | V3 x: L8 `! e myrect.bottom=100+myrect.top;( ~! [' M3 p+ y* N2 V6 y5 B1 y
pDxdevice->BeginScene();//开始绘制8 G/ P+ D/ S+ o8 \/ g
+ L) }' ^; R" G. G" h# `8 [5 K D3DXFONT_DESCA lf;1 z) \; y' y U4 Q
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));0 ^* X# ~% m6 F9 r
lf.Height = 24; //字体高度
% t8 }8 [6 h) y! w' m% e$ d lf.Width = 12; // 字体宽度
6 r: I: M5 e+ ]1 ? lf.Weight = 100; # |5 i5 a6 i" u* g
lf.Italic = false;. h7 w" I! q# P* `: U1 ~' m
lf.CharSet = DEFAULT_CHARSET;! h& O6 k$ C6 {5 g! Z5 }1 e7 L
strcpy(lf.FaceName, "Times New Roman"); // 字型
5 p+ t5 v5 X# R2 Q+ g: f @ ID3DXFont* font=NULL;
& C! x+ ]5 \/ ~0 k) L3 ? if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
* q+ A/ Y" k7 D f return false;3 @3 G4 d. {! L* k# [
* E3 e0 l/ a* f4 c$ S! w font->DrawText(2 z8 ?6 o8 V6 o4 R, }
NULL,
9 R$ g* R1 h" Q9 D1 R5 ?& k# U strText, // 要绘制的文本
3 k4 g+ B7 U5 U nbuf, 6 K1 @- \6 @( W2 C& c
&myrect, 2 L/ R1 G* S5 ~) l) N' d
DT_TOP | DT_LEFT, // 字符居左显示
1 Q- d2 j3 k$ x: f9 b ? D3DCOLOR_ARGB(255,255,255,0));
7 ^& \5 K9 o7 `/ s/ z: R' y [
2 k2 f. \* q. O( |' J' e% I k pDxdevice->EndScene();//结束绘制
. Z9 h! D. K2 V" l6 E font->Release();//释放对象8 u2 B; H" Y( d* }( N# b
}4 j8 U* c# T* b1 J
return true;
^9 H. y8 i) q: x& x}' W5 q: I( z; O& y$ h
) P% i1 @. W% ]( r0 X
效果图:
$ M6 u/ `7 g S O7 R
& z* q# W5 P8 }% w5 ?: H0 n* W; m* V
0 e( P1 `" t- } G- V9 P. i5 d0 C, x
4 b: o1 B0 u, X
3 v) Q. {" O- V4 ]; u% c, A% w! v( g% [; Q. s
后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.; P/ s0 m9 x' \
" a9 S! {& a, F5 r8 o
( m; X. ?0 `; ^, @/ z" H$ J g! p |