本帖最后由 shane007 于 2021-2-13 22:23 编辑
" E$ v6 r8 d6 T: ?/ ] |
8 o8 |% S; P3 J2 p/ S+ s" yDirrectxHook 工具
* h3 X/ ]7 W; w. h' o E9 B% J0 d2 V
标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法1 U0 I8 ^# i7 t* U; g% ]5 _% ?7 l
作 者: runjin
: ]* \$ I. f5 K时 间: 2009-04-03,23:44) H, ~" m) g' Z3 O
链 接: http://bbs.pediy.com/showthread.php?t=85368+ W3 t: ?7 a! b' O: f- {
7 m; a7 j+ [5 p& J7 |. v' f
Hook Directx:在游戏中显示自己的文字和图形的方法8 n( ?/ s3 ~2 L& y4 t/ H/ Z
. [) I* x d- Z, Y( x% l4 r e这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.8 d% x# u# Q0 B- v) X
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.; K% k* T4 C8 _6 l' J1 e# p
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
$ w, _) ~' Y4 ^- `首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.$ c' G) b" e$ v; L
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
. o: W2 E; b- U$ I* m3 ] DWORD oldpro=0;
* G, F8 P8 s6 c9 D. }" Q memcpy(d3dcen5bytes,pC,5);
; u, L7 O- X0 H. r VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);0 \! S* a9 H' l( ~5 ~; C
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码9 ~6 t/ r* g- D4 {9 O: O" b2 D2 R
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
( K9 r' M" C& C" q4 o
1 M, F' ]/ z- D! r+ M! g! F9 e0 c: ^ c
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:! q. l3 ]6 p8 Z
HRESULT CreateDevice(4 ?7 E* Z* l' w5 c
UINT Adapter,6 m9 R: r+ M B; I0 k
D3DDEVTYPE DeviceType,
: \' X3 G5 Z3 Y HWND hFocusWindow,
, s( G3 N+ n \* L4 b- U DWORD BehaviorFlags,
+ `: B( H# T: l+ h7 C0 V6 H. n D3DPRESENT_PARAMETERS * pPresentationParameters,
" @3 B# k# g+ X* n2 g4 q IDirect3DDevice9 ** ppReturnedDeviceInterface
/ ^3 S+ B* ?. n9 B4 b. A0 y);
- O# c# X! a1 {, ?3 f6 H& y. K# Y+ G. w1 Y7 F$ y
0 T# y" G: v6 {; m# f( o
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:' ~& ?& s8 @- }' e6 q) r
HRESULT _stdcall hookedCreateDevice(
" k2 ?; b, n$ f' X+ }% _ LPDIRECT3D9 pDx9,
0 u I: s \8 X- c UINT Adapter,
3 d9 [3 o3 h/ [! I e9 ^; |! h D3DDEVTYPE DeviceType,
9 S p4 e- i5 N9 A* m HWND hFocusWindow,
7 k3 \, e8 D7 r, J$ J! h! K9 E! p! F DWORD BehaviorFlags,
) g8 P3 q8 I* E, t D3DPRESENT_PARAMETERS * pPresentationParameters,
9 |( b3 Z( N$ c% J IDirect3DDevice9 ** ppReturnedDeviceInterface& F B7 B; m- i
3 E9 g8 {! h3 y8 e8 w6 ]0 `& a );
$ ?+ l% _4 U5 i8 t' ^) k* ]
) i1 Y) S b. `4 P其中的LPDIRECT3D9 pDx9就是this指针.
# p. |9 ^* [4 Q5 A, w
- J, ?; r a0 p/ u( RhookedDirect3DCreate9的关键代码如下:
0 i% ^3 u% w' d F" X' `3 [$ wpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
# \: X' x- \( T# G2 S& H
: }; B* O: ]7 x DWORD oldpro=0;
# E. K$ I9 H& X5 o) z+ i+ v memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节5 `! t( x: G2 U& W
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);9 }6 r1 B, P+ O+ l0 H9 n2 O' t6 q8 d
*(BYTE*)pCdev=0xe9;
# }* M4 X, c" u; G *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
0 J1 w1 B! O4 [, g! H+ V! m5 g; S0 e8 E
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:( v4 I4 I5 ^9 n" ~) R3 }, h4 B
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
7 ?2 s8 {5 p* k* N8 J6 e memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
: Y0 y" ^8 @' u3 A DWORD oldpro=0;
' G- Q& }5 J/ ^% @0 @. ~: \ VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);$ `( {& K2 v: b( R- Y
*(BYTE*)pPre=0xe9;9 q' Q' m; v; A g$ f q! \! U
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
1 G% D( u4 {3 d0 \, d' K
: V2 ^% l/ l6 m4 c* @3 B. z+ d实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
8 {% Q# J! \* O# Mchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
' }% J% y" T! ]* {- p' A0 D$ v DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
/ [7 x$ k7 O i7 C //在这里写入您的其它绘图代码( B% p( C( y1 t5 ]
4 p$ K9 N& N& Y: i" L7 Y# a' a: i
- R5 W7 ?& U9 A3 L3 s: ~4 ?: T: n再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
( p9 k$ O; q6 ~' JBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
% c" a3 W8 @& y$ e- s& s9 n{3 F: |9 V; n8 w
* f5 [) U+ [' c4 G% H& C if(m_pD3D && pDxdevice){* U# t, Z; U3 |, g1 v
% J) _+ E6 l* s# K1 c8 P! n F7 n b RECT myrect;
j X( X# Y8 Z. x k. B3 T5 L myrect.top=150; //文本块的y坐标' t( i! X* w, C" n! c
myrect.left=0; //文本块的左坐标, |/ g+ M& c; W1 p0 X6 B
myrect.right=500+myrect.left;+ N8 J& z5 ]( q5 X2 _& v1 J
myrect.bottom=100+myrect.top;
# s$ B( j: L4 {9 {6 X- \3 ^ pDxdevice->BeginScene();//开始绘制
" x; y; y1 {1 W! G4 N' Y6 i: q: g. y$ C" X; ~/ A$ h7 |0 I# x
D3DXFONT_DESCA lf;8 M3 e, w. V4 z
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
- B0 A, \9 n) L" o/ @1 \% o9 S lf.Height = 24; //字体高度
0 r4 T+ u7 {% H. S$ |' { lf.Width = 12; // 字体宽度
+ X7 d; h1 R6 U7 S# `$ Z lf.Weight = 100; ! D- r! x! |6 x; w T$ O* F# N) G
lf.Italic = false;
& D ]0 b" [: s! y7 x( ~ lf.CharSet = DEFAULT_CHARSET;! C% t+ s' j/ `1 P
strcpy(lf.FaceName, "Times New Roman"); // 字型
; R3 {9 T- r* ^; F ID3DXFont* font=NULL;! K( F$ \2 X: E6 y' n) c
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象% ]5 N: p" o S# n/ r6 g! y
return false;, C& a$ t, m2 H K' V9 ]
/ P- S; d. Z3 A" { X font->DrawText() d3 e/ Q7 ?& |8 |
NULL,0 G$ _% w9 i- N" B/ X" G' Q5 D: W
strText, // 要绘制的文本9 R. W; ]; S/ G) @& `6 s/ y1 B
nbuf,
; t" @/ ]9 v! S i! l; s% U &myrect,
# T5 a& S [+ B, y3 z( Y# k E7 q DT_TOP | DT_LEFT, // 字符居左显示7 W/ Y* ?, u3 ]4 D
D3DCOLOR_ARGB(255,255,255,0));
$ D5 u# }4 [& g' p
, k3 d0 U, ] R1 [) G M pDxdevice->EndScene();//结束绘制
$ j3 e+ N" z5 @: _* J" B- B6 c font->Release();//释放对象# V( _" b; Q8 u9 G' |1 s P4 y$ q% m
}& ?1 ^- [; L( A g
return true;
+ @8 O2 ~- N" k3 V% G}
* z! R% ]& o( B$ k: c8 b% L( w; b9 Z" ?+ ?* J8 }
效果图:
! l' R9 a* p6 D& F9 j% n# E ' ?( C6 {9 K [2 D# d7 Y- W
- S8 n% [7 `3 P: l$ u/ l1 V+ ~
3 s9 ]& K2 d/ K
* b6 U' k' [; @5 q" [ S" b* r) d) j/ a2 W' L7 b9 ]7 t
6 }! r" S& z" {& J) q后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.# `3 u! G8 ] _! m! ]; _# c% g
* \9 Q6 g$ l% c
8 K; v j2 K" L3 y. S
|