本帖最后由 shane007 于 2021-2-13 22:23 编辑 1 h5 T% |: p4 j2 |8 R2 [+ ~
- }4 Z5 f9 {/ p4 E7 j
DirrectxHook 工具6 s& U/ y" \; y+ f3 f. y
" i& @# r, Q0 B3 g( O
标 题: 【原创】高手莫入 在游戏中显示自己的文字和图形的方法
1 @# K4 T4 ]3 x( H3 c作 者: runjin
O6 c* x+ h. b+ z时 间: 2009-04-03,23:44$ E9 }2 E' r5 k: b& k/ K
链 接: http://bbs.pediy.com/showthread.php?t=85368
& n" p7 K% g9 W3 D* h( i+ C7 n5 s
" Y0 N1 K$ L" U0 V$ u8 Q% j6 yHook Directx:在游戏中显示自己的文字和图形的方法/ C# S M- E6 _% t4 U" G8 M/ h
8 S$ Z: e# O/ o1 c$ O这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.! z9 @8 P, N! |. G: |. J/ K" I
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
+ @% H4 V8 |' {8 w* O' Y: S* i还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
7 j v X0 s d" F首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
! w M7 S" P0 }' Y1 O/ @0 h pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址" k+ k' q/ i3 K/ L# X" d+ m
DWORD oldpro=0;/ U5 K* N v) D6 v" g
memcpy(d3dcen5bytes,pC,5);! @9 @9 D7 u1 i- o& R. d4 F
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
: t% R: W( o8 X/ a- C; ? *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
2 }8 m$ h. d; ~, [ ~3 [$ |. L *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
5 |; Z1 `6 C2 C( ]; W1 m& J9 D C
5 F) B3 E! U4 ~) |3 y) B, b* c$ Z4 M5 i$ q8 _8 J
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
5 A8 Z) X) J: C' Q3 `HRESULT CreateDevice(2 \* \. _1 i, m: e2 }, J
UINT Adapter,
+ _: ^/ z( A/ T/ C D3DDEVTYPE DeviceType,
) H$ o( [: T, n9 N! Y2 }4 p' P/ z+ _ HWND hFocusWindow,
! O* p4 W' I7 B v7 t+ H DWORD BehaviorFlags,6 f* I1 k9 }; S0 B' [6 R6 N( d
D3DPRESENT_PARAMETERS * pPresentationParameters,. c) j- \7 x2 X' R6 K0 w7 q
IDirect3DDevice9 ** ppReturnedDeviceInterface
9 j" S# e) ]( ?% Y: g6 ?) { G% M);
, \% d- ^6 Q8 N. j' Q' k! W& B9 k: Z `, m; S% v* r1 o
. U. t4 w9 p4 `- Z/ Y/ ~而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
4 t$ A- z4 X9 D2 C. Z/ VHRESULT _stdcall hookedCreateDevice(
1 ], Y- |: C& M9 q3 C& B1 w: W% O! ~ LPDIRECT3D9 pDx9,5 ?) w8 v w1 H2 m# x# [( f
UINT Adapter,
' i6 P$ Y k$ r$ e D3DDEVTYPE DeviceType,$ F8 ~& t; x# D
HWND hFocusWindow,: r9 y7 H3 o5 M9 g, h* {
DWORD BehaviorFlags,
% Z2 v8 q+ a; Q% @0 k. g. Q& _ D3DPRESENT_PARAMETERS * pPresentationParameters,7 N; @( x& x+ O9 n9 e) A
IDirect3DDevice9 ** ppReturnedDeviceInterface u2 _4 n" p" a3 z' v2 }& p
: n* [- _# c0 s! R# y2 p- c );' W) ~! L4 q9 Q4 ]3 ~
& {; }+ M# F5 d* G4 T0 o7 Q其中的LPDIRECT3D9 pDx9就是this指针.1 K$ X/ m2 e- j% j
! J! M; @/ _4 G9 [5 ~9 W& ohookedDirect3DCreate9的关键代码如下:! i1 {: C8 t( M- q" }- I
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
) [4 v2 ^3 V8 w2 T, `2 K H7 D8 O$ ^( g: D; Z
DWORD oldpro=0;9 ]! b! N% S/ G W
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
% t J9 ?3 M D VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
! _" g# E! N. u _; R *(BYTE*)pCdev=0xe9;
, N) q9 G2 H: t8 q) A1 Q *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
! Q$ b O( U0 f% K1 L
% D* Q; Z- t/ D3 ~7 r+ P在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
/ R% V8 h. I: {" Y/ n5 g, XpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针- l+ O# c1 r1 G- X
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
! J+ H0 ~ y* w I3 Y* J DWORD oldpro=0;$ ^1 h- m( Z1 W# N% N0 i* i
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro); c7 I4 K+ u' X* I' z! ^8 H
*(BYTE*)pPre=0xe9;
: \* ?1 G8 p( h( [. X *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
2 a4 L) B& @) x, d! u2 q8 Y4 l# K+ o" y9 b
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:/ U7 ^6 [4 ]+ K y2 M* u" {" S
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
' J( g$ }* T' s6 S' k% a$ z6 r DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
5 o& i d+ F/ V$ P% U$ u //在这里写入您的其它绘图代码# n% P$ M; @6 l1 [; n) V
/ N5 ^+ Y0 o. V4 C% u+ P- C- N/ `7 h- k, x, t
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
6 M {# H" U& K9 F$ DBOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)1 K3 C+ v& ^7 @9 h
{
, h: Y' x3 a* K" k; R
' S+ M) J6 ?8 _; c4 U8 ^% k if(m_pD3D && pDxdevice){3 j" f3 R' G+ J0 P1 K& C7 n- V& c" v, m
9 e5 B' Y; C2 |# K
RECT myrect;. G @, ?9 F3 M/ _1 Q
myrect.top=150; //文本块的y坐标+ K+ r% \* S0 I) x, U" ?8 j
myrect.left=0; //文本块的左坐标
8 d" x7 ^% ]0 y! Z myrect.right=500+myrect.left;
$ E I* N$ s' ]7 e' S! V. x. _7 t myrect.bottom=100+myrect.top;( A2 c* D: g9 t _) g; v+ s7 g6 [
pDxdevice->BeginScene();//开始绘制
3 _( O8 s7 l3 P* j6 o
; J1 a7 r. m) e% W) Y) m. ^; F) @ D3DXFONT_DESCA lf;
7 T; u. l8 c) @8 I) ?$ D ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
( n' j2 C5 ?* m1 e2 G. S, u; N2 j" V. T lf.Height = 24; //字体高度$ c' T' T7 z: v( L7 ]( ]4 U
lf.Width = 12; // 字体宽度
3 \3 Y; {5 o# B3 {4 d lf.Weight = 100;
. E6 ?( T& X) s M lf.Italic = false;! N4 j, @5 W0 ]+ N& M* m$ A
lf.CharSet = DEFAULT_CHARSET;
+ j+ L/ h) M1 M) k' y+ T _- l strcpy(lf.FaceName, "Times New Roman"); // 字型
( b8 b$ X" V- T& a8 l9 L ID3DXFont* font=NULL;) s( R; j' o" o; i) k
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象& ~) Y1 O _7 N/ U3 Q4 B. a
return false;
& A y( F* P! c; C0 Y/ P2 Z( ?' R; a" R
font->DrawText(
9 ?4 }! ^7 B& W/ c NULL,
: E8 s, j1 F0 r9 e' @7 h; q strText, // 要绘制的文本
2 S6 V4 H3 h2 U( ~ nbuf,
0 s- m; c, m' k, W. I% B; a &myrect,
, X; b4 j: v2 R1 C6 }" f! O8 Z0 ? DT_TOP | DT_LEFT, // 字符居左显示. r, b# `8 e/ ~
D3DCOLOR_ARGB(255,255,255,0));
3 x, ~, {3 r& z. j7 C9 ^! i7 B; J6 D" Q8 e2 a4 y0 S
pDxdevice->EndScene();//结束绘制0 y' k+ @$ m+ \& {: T2 ^
font->Release();//释放对象8 K! D6 N, z$ P/ i2 B5 C/ p9 J
}! J- f) V- }1 k$ X9 {3 `
return true;' p) \ x, w( g) w
}" y; i- g) t. R5 z. ?7 @$ u
1 {) T3 h9 ^( H$ x; ~& J6 O4 E效果图:) q8 Y$ }# D! W* o( ^
* K' t j) |% p8 B7 Q6 F1 @3 }
& _4 N0 R V3 c4 u1 f4 R( e) a1 \0 G4 Z/ |# |/ x
6 |( ]! d! x8 L
; S1 D2 F! m8 h4 Z5 ^7 h: |
, |& |0 M2 q& m; [; V4 ]后记: 代码是以前写的,因此文章就按照着代码来讲.然而现在看来,把函数入口点的5个字节改成跳转指令这种hook方法并不是那么好,其实可以直接改掉虚函数表中的函数指针,这样更加的安全保险.7 R- M& z; J/ a7 x9 M' }8 ~
' @9 r& d4 N( s0 D* D2 g& l H) O. V; |& G, B: T
|