标 题: 【转载】在游戏中显示自己的文字和图形的方法3 h# ^5 k) h7 W* B& m3 ?% p
作 者: runjin
& n) ^2 ^. ]2 h时 间: 2009-04-03,22:44:51% S4 g4 ?9 S" ]" p
链 接: http://bbs.pediy.com/showthread.php?t=85368
7 M- l, o3 i1 C9 ]- p2 j% I6 ~1 ?; V$ ?! Z7 B( X8 ?
Hook Directx:在游戏中显示自己的文字和图形的方法
! f; }, ?5 u, p$ t& A: O G* L
3 h2 I3 n' P. a% |. ]这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.0 w4 O: g4 z0 t- _( P
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.( h4 b8 l* \( d+ C# N/ u
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
5 _* \$ {. @% K9 X& o. C首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.2 D# f' r' G$ d: L% q9 a
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
, M3 x8 S) }5 ?! e. E( U DWORD oldpro=0;& D7 P! D. m: J+ Y, Y: N! I
memcpy(d3dcen5bytes,pC,5);1 x7 C( k, }; f# l0 A
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
: [3 F- M, e. H' J *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
( R* u( f! r: M *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
! _. z* V+ |. u- Q# ?4 ]! |: j: S8 I* m6 i' ]
! ^* [; l8 J. f& d4 Y2 t这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
7 X( H% ]3 x, s+ s/ R" {HRESULT CreateDevice(/ m5 p: w5 ~: D6 w! f0 m
UINT Adapter,
: R* c! X! C; C/ }" f' R% a D3DDEVTYPE DeviceType,
" C1 x2 _1 y) D r, V Z HWND hFocusWindow,
* B2 s B/ r' z DWORD BehaviorFlags,1 b# x }4 w2 [, [# `" d# N
D3DPRESENT_PARAMETERS * pPresentationParameters,! d' w* n8 t. f! c; g
IDirect3DDevice9 ** ppReturnedDeviceInterface K5 R& P) i" Q
);; z) z2 `$ Y+ T+ _. l* G
3 {- v' l' o# |! M" D* Z3 N0 s) \: ^4 o
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
2 F9 @: L2 n: ]) g+ N* nHRESULT _stdcall hookedCreateDevice(
, z' c4 w: {/ u2 a4 F& r- P' T LPDIRECT3D9 pDx9,2 N. ?# j8 Y4 A5 I
UINT Adapter,
' d' k1 e, l2 b1 R' ~$ { D3DDEVTYPE DeviceType,, u3 \; c! U7 D* S
HWND hFocusWindow,! q7 }8 @6 m5 W9 @1 u% o% }- Q
DWORD BehaviorFlags,
) |- D# M' `3 K U9 M D3DPRESENT_PARAMETERS * pPresentationParameters,3 X9 J. R# Y/ I% U
IDirect3DDevice9 ** ppReturnedDeviceInterface; ?; M/ K) L3 W9 ] D
) I5 [, s+ h6 r1 L
);, Q \% D; Q5 Q) x
% t; W% y) n, R3 b
其中的LPDIRECT3D9 pDx9就是this指针.
0 ^; Y: v* h9 ~& O1 m! H7 A$ A& X: ]* \* H& n9 M
hookedDirect3DCreate9的关键代码如下:" {7 x) h0 ?# Q7 S6 z. X9 s
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
0 C5 \* q, `1 [! g' K( r% E9 Y3 }! U! o
DWORD oldpro=0;8 G; }. B! t- G2 J f
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
/ F) Z5 {+ K' D" t3 L9 m VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);9 ~+ q" G2 k- s0 _& v: [
*(BYTE*)pCdev=0xe9;( ?; q1 ]; u7 `, j) _7 t% u/ K
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
( u2 t* B6 O, D' a1 e7 L4 C2 @' q+ m8 t
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
* i( _4 E# P. a- C/ LpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
# J, N0 J; |. I. }4 V8 T r0 \3 M, ~ memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
( s# u8 L% c5 C: c1 t/ S4 k$ W DWORD oldpro=0;
6 b- K! O, x- W3 T VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);+ P {/ j/ v( u$ Q9 s( i
*(BYTE*)pPre=0xe9;6 u' ^& C2 ~* [$ Z( O1 c
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
) X0 n0 R( X: n4 T& N
2 g) C. E- N% Y! R( s实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:% S; I1 M( x" P
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
, j3 Y, |' O/ v- k q8 r DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
+ X6 t0 c* i& D2 A //在这里写入您的其它绘图代码
2 C" l7 I; d4 `3 `. a
$ \ n# K @4 L; l8 [. q, t/ e& S/ O
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:1 D) g# \3 ~, ?& N2 h g
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
# G, g# a3 S8 B, {4 r! @{, U) }+ S, [9 x! E/ r! u2 I4 I
$ j; `* i9 C7 r# s if(m_pD3D && pDxdevice){# l" N3 \( y5 n) o8 M7 j# p
; g. J2 x+ o+ O7 Y3 p; z# j RECT myrect;- a0 E" C* ]( J4 q& \
myrect.top=150; //文本块的y坐标4 s1 V8 N s7 G7 h1 ?4 i
myrect.left=0; //文本块的左坐标
$ D- h0 g0 W2 C s; K& W myrect.right=500+myrect.left;
% f$ |& J' Y! l, O0 P4 }$ } myrect.bottom=100+myrect.top;
) k8 N+ U' D- n* S8 l5 ^ pDxdevice->BeginScene();//开始绘制1 Z2 J+ s5 ?: O0 s6 L
J$ \2 c7 K9 G0 B2 X- H7 g D3DXFONT_DESCA lf;
. l( A \" C4 V% I1 X$ n) A0 d/ u: s ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
$ d# ^+ R- Z% e lf.Height = 24; //字体高度. F7 @( r/ B: J6 x5 f
lf.Width = 12; // 字体宽度
$ i+ \* p; \5 x' A2 Y lf.Weight = 100;
/ M* u2 t4 U2 V! H) C7 s9 U4 S- p lf.Italic = false;
2 ^; M- n# ~" J. Z" Y8 G lf.CharSet = DEFAULT_CHARSET;
- ]5 j/ E( m3 \! c4 g; J strcpy(lf.FaceName, "Times New Roman"); // 字型
5 _5 E/ W+ N0 s9 Q R ID3DXFont* font=NULL;4 Q5 }# A4 F+ ~$ t* t5 w. m3 k0 ?- ]* @
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
9 ^, S. ]9 d4 Q+ @ return false;& D6 `6 s+ n# V7 {" s+ l$ M H
4 ^' i/ N6 P- l* u3 r font->DrawText(3 O2 `' V# G7 m! E$ W
NULL,
, y/ R( \: @1 b0 y5 i7 ]/ b, n strText, // 要绘制的文本% v4 T: z2 d) M6 y( J
nbuf, 1 H6 X: ]6 r/ q; Q1 U- y+ J; {
&myrect,
7 Q( r9 R2 J K( Y- s' m DT_TOP | DT_LEFT, // 字符居左显示. L' r8 K: }5 ~8 p0 V2 \) k
D3DCOLOR_ARGB(255,255,255,0));
. Y4 R7 L: h( @/ o: f: r E# k1 T; c8 o! M7 U+ d
pDxdevice->EndScene();//结束绘制$ J/ w$ z# Q+ }( P N8 ^' S4 X
font->Release();//释放对象
, H& y. K0 K' _7 H$ {1 O }
- Q" g6 r* L! U2 B# H return true;2 }3 }, j0 U5 V, U- d
} |