标 题: 【转载】在游戏中显示自己的文字和图形的方法' s# z1 x# U4 D7 [1 T/ R
作 者: runjin
* G) w9 o4 A5 M3 M b& I _" M时 间: 2009-04-03,22:44:51% o# k; V! T! D3 D/ d, I! R
链 接: http://bbs.pediy.com/showthread.php?t=85368
- b; w) l( ]- Y& E& p* C# z) z( T7 a5 a) L
Hook Directx:在游戏中显示自己的文字和图形的方法, O0 {9 l- ]4 p) L+ x
! z; {# X* u1 p$ ~
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.3 f) Z% ]- P- I1 D; u- @% f$ z
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
/ J- m& t7 b: j0 N) f' o% E还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
" e$ z2 D7 E7 j, c$ z+ e1 w- m首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
& Z y9 a) j3 W1 b! E5 k; W pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
) U/ R/ ?0 s8 M3 r9 @" y DWORD oldpro=0;
6 l5 O2 E! b" ] memcpy(d3dcen5bytes,pC,5);' S/ h2 }, U6 [5 e- Q
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
- H, w7 L+ |$ `5 @) F7 q# _4 M9 t; | *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码8 W5 z2 m) j6 W' {" ^
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
L, }! q4 q* [3 ~, _, A* `$ ]9 J b2 I9 n& o. {. N
& H# L, [' L1 O' w$ h/ L
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:- a/ c5 G& f5 K. c- Y2 O0 R
HRESULT CreateDevice(
; y% e) ^) S6 Z UINT Adapter,+ C, ]) U( J+ h: c; Q% c
D3DDEVTYPE DeviceType,
. S: H1 [" [( a* c7 p& l, z2 B+ k HWND hFocusWindow,( h0 j+ z# ?$ Z2 E
DWORD BehaviorFlags,6 U& w$ a: A! h) N7 o# X" \
D3DPRESENT_PARAMETERS * pPresentationParameters,+ h) h3 g' I2 [$ I2 C- Q: u
IDirect3DDevice9 ** ppReturnedDeviceInterface3 i# @* P9 t* I i1 m; ]
);$ v8 L2 p4 _: J1 e7 t
+ j, ?2 V. g6 T
3 a6 z9 Q' n3 m9 l3 W4 ~! w. I而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:/ r0 s: ^+ q1 g W, m
HRESULT _stdcall hookedCreateDevice(
V5 e$ X" i6 h9 `& j, m$ s8 f LPDIRECT3D9 pDx9,
, Y' w/ D. \, q! n k; r% w UINT Adapter,
9 J6 F% Q! A" v. d# P, _" K5 ` D3DDEVTYPE DeviceType,
8 u# W$ u _: R1 ?/ M HWND hFocusWindow," Y, j( R; w# `2 W8 Y3 {
DWORD BehaviorFlags,' Z) w) t: C& g& h* E& c. N
D3DPRESENT_PARAMETERS * pPresentationParameters,
- f# C: e$ L# V/ A6 e5 u IDirect3DDevice9 ** ppReturnedDeviceInterface
" O6 l+ K; |: U4 D: \( ~/ u
; i& S4 x6 L7 M- W1 r );
& D+ O" f8 w8 ]1 z# \5 J$ O5 d: A; H _) |# J- }$ z8 J; ~3 n' O0 @
其中的LPDIRECT3D9 pDx9就是this指针.1 a1 B$ {" U I3 m
9 D# v5 \" L/ I& O0 M; {4 y
hookedDirect3DCreate9的关键代码如下:& h, y1 v" I# p7 U2 ~$ ~
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
" O& z3 G5 K. c; e f; g3 p
; M4 } t2 r0 g9 t# A DWORD oldpro=0;
y# O" c5 V" [& p# {8 ] memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
2 [/ i2 f$ d) h, X! ]' @+ f VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);( l$ d' r1 t/ B* Q, M
*(BYTE*)pCdev=0xe9;' K' O6 s; V! o3 o J7 ~
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
: P! Y' R* G1 f+ i/ t, a2 u* O3 j! i2 k& x0 Q% |
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
! Y" w( c/ c2 _/ D7 R) z9 g$ HpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
9 `4 Z7 ?, w3 ^0 c+ F( | memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
* S8 m, S! H' ^; l DWORD oldpro=0;) W" @% @- c1 x: A* v
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
4 c7 F5 L! C! X# S4 e4 v *(BYTE*)pPre=0xe9;( a& j- @" k! m( U) g
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
% }/ x, Z! k- W% X4 ^& }- A
) W" w7 W3 `- R; I C! ]实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:( W! }- l( O! k+ o5 ?3 Q
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";7 \8 d& D2 u/ d5 E2 K4 C* @
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
; M( _* T! F) l; N( Y //在这里写入您的其它绘图代码
# W" Y) k! X; K0 j: R- K. P' O
8 X, r% P/ l* B$ t5 p7 H: L" S9 V9 _2 r+ D3 C
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:& v1 ^0 e* A ], y! [
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
9 I0 F2 x/ ], F9 T0 I! w{
# s& D3 R5 D- g/ y7 W, X
' S$ C' |6 ` r if(m_pD3D && pDxdevice){
2 ]# k0 v w" V6 _, p3 q$ Z" i: L( \$ o/ v+ H+ }- `3 V3 P- e
RECT myrect;8 J+ X" j; h& e
myrect.top=150; //文本块的y坐标8 w4 U# N: V7 J/ u, f$ x
myrect.left=0; //文本块的左坐标+ P7 \' I6 \% L% A2 d% B2 Z) u
myrect.right=500+myrect.left;
; T0 m: I8 t% _1 j myrect.bottom=100+myrect.top;, M$ j2 O: _8 R
pDxdevice->BeginScene();//开始绘制
9 ?4 G! W' Z0 h0 B4 _9 Y
8 F# w- M7 G/ r D3DXFONT_DESCA lf;
/ k. r2 }2 u8 g$ v0 {$ b ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
% M0 g0 K# Z T& @0 C( N2 C lf.Height = 24; //字体高度
& w9 O, Q( k+ }7 | lf.Width = 12; // 字体宽度
$ A- X5 b y% D5 |; V lf.Weight = 100; . ~/ p+ \, W, Z8 M J
lf.Italic = false;
! @( j$ C1 J: ?! P% ~* U lf.CharSet = DEFAULT_CHARSET;
+ c1 z* l) V' K: O strcpy(lf.FaceName, "Times New Roman"); // 字型3 ?2 @& w f! M! ^+ I
ID3DXFont* font=NULL;7 u; k* n2 \# X1 k# d
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
- e/ g2 P$ B" c. C a return false;/ ~; m4 x9 F# ^& H' W7 ~
6 r: a. B1 h# ~+ q font->DrawText(
m9 x* x/ Y0 \8 P L7 h NULL,4 k; R2 z8 A& R, j' x
strText, // 要绘制的文本6 @4 R8 z+ K0 ^. c( z
nbuf,
m' p( U4 c5 S &myrect, n z' Q; Z+ D+ r! ~( [9 R
DT_TOP | DT_LEFT, // 字符居左显示
, ]/ L8 D9 i; r5 I( g D3DCOLOR_ARGB(255,255,255,0)); . L1 M6 J# D4 N$ {
8 e9 o8 M1 G; ^% a" [
pDxdevice->EndScene();//结束绘制+ y$ u6 A, p* T1 \
font->Release();//释放对象
6 l G3 e. m( v/ T$ [- I }" o* ]$ d, ]+ D$ w* J. T, a
return true;
% v& f1 g) ~1 ]+ ^} |