标 题: 【转载】在游戏中显示自己的文字和图形的方法
0 j/ T2 E R9 u作 者: runjin: c; _. B. `5 a3 Q
时 间: 2009-04-03,22:44:51& Y$ p( ~7 R: w) f! |
链 接: http://bbs.pediy.com/showthread.php?t=85368; ~3 [$ K3 r( c) S' H& P( q2 ^
' U3 c" Y+ B4 s8 k
Hook Directx:在游戏中显示自己的文字和图形的方法
" q+ _9 t$ r4 L* X% h+ k$ r* W2 C. P; b* O
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
( M1 z, ]2 T j4 {其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
' P7 k( ~9 Y+ r/ K4 W还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.; |% a/ b: W9 d2 ]+ |+ T" B
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.3 ?) a+ W* O: V5 ~
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址, B1 Q2 F9 F$ W
DWORD oldpro=0;) Z: ], t P3 q/ I: u- j: b
memcpy(d3dcen5bytes,pC,5);) p" Z: m+ o% Q1 f
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);5 a; l# R! U" ^2 d2 S6 C
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
' w7 Z3 B7 [: W# X/ X2 ^6 |- B *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-50 e! Z k, p# x5 b; {# l
7 ?( e7 ~' L/ Q R, V8 F# y7 b- J4 n7 S( Z& q- ^- G8 I
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
5 u. i9 u1 E, d& [7 y! {3 oHRESULT CreateDevice(
4 f; O' M1 \* [9 c7 ?5 p UINT Adapter,5 ^5 T, A" k' B% X, S
D3DDEVTYPE DeviceType,/ e! v; G( `1 D. N6 m! T* c" K( F3 y
HWND hFocusWindow,
7 @' {/ `8 T' z% w: ^ DWORD BehaviorFlags,7 w" ? a6 k' @; B
D3DPRESENT_PARAMETERS * pPresentationParameters,
`: x" G0 E9 G1 U1 e IDirect3DDevice9 ** ppReturnedDeviceInterface
; {- s5 x' q b( D k);" H' [# Y l1 c# N6 f
7 L5 g, u/ h& g* K9 A9 M" }
: s# z' `- v3 t: a; Z/ a6 ]
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
) Q) t: O$ P0 ~5 x5 ^6 ZHRESULT _stdcall hookedCreateDevice(
3 ]: M6 e* D$ B- s3 V LPDIRECT3D9 pDx9,, |8 Q5 G' s& Z7 W' e- u" J
UINT Adapter,/ y# M$ u5 M) i- H7 c `
D3DDEVTYPE DeviceType,# z* i6 G$ m: z- q
HWND hFocusWindow,( e3 H' P, C1 P1 u8 R
DWORD BehaviorFlags,& v* n2 P, n0 U" x8 F$ u
D3DPRESENT_PARAMETERS * pPresentationParameters,& S8 U; o! u' F& i0 i7 {+ b4 c
IDirect3DDevice9 ** ppReturnedDeviceInterface
: _/ u: ^2 c% a4 W7 S% `
/ l4 K5 \6 _; A! p3 d );! _' h$ @3 }; G _- a7 D7 Y- \
, S0 ^' |2 J+ X# ?' I7 @其中的LPDIRECT3D9 pDx9就是this指针.. o& s+ S$ g, K8 F+ Q" }
+ [0 P* ~# f$ t* [( w N% W% w
hookedDirect3DCreate9的关键代码如下:5 L8 l/ z0 [; W4 S0 ?
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
3 n+ V, V: t$ U. O9 Z4 K" t Z
DWORD oldpro=0;+ h7 }$ V8 O8 c1 x2 [0 u5 _) a# ]
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
9 D( H8 N0 G P8 G1 Z0 M Y VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
7 S2 j# G b* C' B *(BYTE*)pCdev=0xe9;* y ~- a& L; R# N% T) j9 A
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;. k$ }0 N1 }6 y! O
# Q1 E; l0 D7 G在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
- c1 ]% d& `' d8 d, m/ EpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
5 K+ S( o! ^4 X0 v. S, t% b3 T7 ` memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节+ e/ y3 R3 `: e: b$ w$ h, _* c
DWORD oldpro=0;
" L' ~# r% G! I VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);5 T* G0 S7 R( H% {. P3 v
*(BYTE*)pPre=0xe9;# h. Y& h" p$ Q4 p+ D: ]
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
" v( s. f+ t; J i
) l/ K: g% q* J; }+ T* q实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:; q/ `, C* a, {7 x
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";' J/ Y5 ]! Y& p7 T0 W( B! B
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
: m( Y3 ]) ~$ P2 J# P. z/ O9 K( Z //在这里写入您的其它绘图代码
0 g8 j2 ]% ^/ D" ]$ k S. {, ]' I, C' ]: d* y* N
0 {6 s# {& ?: Y( _再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:5 V: q- ^/ g2 }+ _! i
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
4 N* M- u+ j- t1 D: i& x" s{- H& K; r" p! c( R9 w" |. z
3 x: [4 A$ |( m' q. r- x+ [. l
if(m_pD3D && pDxdevice){' Y$ ~3 j q# ?# H
/ \0 f& q F' u
RECT myrect;; f! A/ h( r6 L X" |3 v# ~
myrect.top=150; //文本块的y坐标
$ _$ T' O. q* z* y7 y8 O7 s a! J) X myrect.left=0; //文本块的左坐标
4 }" u$ @9 G) ^. U; v [- P myrect.right=500+myrect.left;# Q8 L: f9 ]6 a9 k! M3 g5 u
myrect.bottom=100+myrect.top;
" {$ B, z; `& t! F% s/ C( y pDxdevice->BeginScene();//开始绘制, O' o, j( L. l6 g! ~" W# t
1 O( e, v C2 h D3DXFONT_DESCA lf;
' W+ w' o; j! z* \+ R( k ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
8 H4 y. g* g0 I& r- W+ K lf.Height = 24; //字体高度
# B6 R4 t, V6 L7 w" D lf.Width = 12; // 字体宽度! i( W6 P/ b- S7 ~8 h8 m* I
lf.Weight = 100;
' f7 f; X5 x5 \% T lf.Italic = false;
4 `* N$ {' p$ c. ^/ `% g lf.CharSet = DEFAULT_CHARSET;2 s8 T0 Y/ q3 X
strcpy(lf.FaceName, "Times New Roman"); // 字型
0 j$ }- ]* I/ ]) Q7 x ID3DXFont* font=NULL;+ I' E& k( A0 P" {/ d5 S
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
% ]! E9 \/ \* A9 p return false;
/ Y/ Z, ~( I; h: A7 U, o! s T
3 N( @2 K9 l* h4 f font->DrawText() N- A: y ^& B# p& Y. ? `
NULL,
% @9 Y( J! v( @" x% I* H# x3 I strText, // 要绘制的文本 h: M% P% w6 |
nbuf,
" j1 r* G K5 _ &myrect, 7 i$ k' N! A4 P6 f: e0 g3 @0 X1 \
DT_TOP | DT_LEFT, // 字符居左显示
8 _1 Z: {( ^2 J/ W3 e+ U D3DCOLOR_ARGB(255,255,255,0));
) P( h6 w" s, R' [3 J0 h
& G3 R3 M0 B$ G) ~2 ?. p$ E2 [( B7 P pDxdevice->EndScene();//结束绘制9 Z8 T2 g5 h1 m& \ A% h
font->Release();//释放对象% J/ p5 H! e* B2 B$ Z+ X; R4 g
}; d( g3 m6 u8 x+ e/ U; Q& R) `
return true;
( G9 e! ?7 o7 B: P} |