标 题: 【转载】在游戏中显示自己的文字和图形的方法2 R F% M, L3 [3 Q
作 者: runjin" m6 ?% I9 J7 M2 m
时 间: 2009-04-03,22:44:514 _3 {. `! L- {+ P5 J! s4 y
链 接: http://bbs.pediy.com/showthread.php?t=85368
! R, M' E$ |6 Q# Y
8 K$ Z! p+ k) D6 v* d! {Hook Directx:在游戏中显示自己的文字和图形的方法
# i8 y4 p( [/ e. s8 k6 y6 Y/ A. P1 A; Z& p: x5 K
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.. ~5 f/ y2 K6 h, j3 W" s0 ?1 M
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.: Q% U# X8 h# y1 U- R- U3 }, R
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
, e5 V# C! A) S, k/ _& O! l首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
6 D' b2 T; f$ A; X5 a1 q6 G- K pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
+ d9 P+ C. U) r DWORD oldpro=0;% M( }3 O3 b' T# V+ M% l% S
memcpy(d3dcen5bytes,pC,5);
6 o' u" a/ \4 l& N+ r+ D VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
7 K4 ?3 w/ A* f5 g$ t, ?. ^3 a *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码0 Z b$ Q. b1 i9 h
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5* W. }5 B5 W% W2 v6 \7 h
& z. |+ m3 z+ H
) T: g E3 o: ~9 W5 X这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
/ I4 q1 N7 q7 t n* I( gHRESULT CreateDevice(
3 H3 _& V3 P" }4 z* I UINT Adapter,
: V# w2 S& \; X6 A D3DDEVTYPE DeviceType,
/ R) D8 T0 F7 g# `5 A+ V4 k" l HWND hFocusWindow,
$ \, A# m" g' W! f. B DWORD BehaviorFlags,7 y. n! p1 ?* E5 w" d- j( Y
D3DPRESENT_PARAMETERS * pPresentationParameters,
6 E5 Z0 j$ I _ IDirect3DDevice9 ** ppReturnedDeviceInterface# E# Z+ L9 z0 P( D; x
);
, ~7 ^9 d( c, }$ l# ^' w) b+ R
+ J9 N, N; t- j
- {1 S. P0 t$ `9 P而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
1 @4 {# B \+ a1 ?HRESULT _stdcall hookedCreateDevice(1 b1 }! C* B# V0 |$ E6 z$ K1 `
LPDIRECT3D9 pDx9,
$ l F2 ~ `( [8 |* a5 U UINT Adapter," ?+ ?, h7 z" m' z3 J
D3DDEVTYPE DeviceType,0 R; c/ K* z1 [3 y8 Y
HWND hFocusWindow,9 J2 R' S9 o$ G- G. [0 {
DWORD BehaviorFlags,
0 w. j+ e8 \* Y. Y1 f i' Q' C) M- W1 i D3DPRESENT_PARAMETERS * pPresentationParameters,, b7 i: ~4 H4 |; n
IDirect3DDevice9 ** ppReturnedDeviceInterface
$ d7 a9 G2 o5 {6 b0 x* o! j5 _: e1 _* V! u, c* q- \- s
);, o( |7 \- m5 e/ u& T
1 l& E2 w; f2 s2 E其中的LPDIRECT3D9 pDx9就是this指针.' I. ^0 r$ J. j+ e! }
* u+ ^: w; q* d8 n! l- d4 s: dhookedDirect3DCreate9的关键代码如下:% `0 w F# a! @; m9 Y2 c+ F6 s' l. w
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
; c( M$ ?& x7 s L0 q) ~2 d/ q+ A; P' Z* v6 P- P
DWORD oldpro=0;
* P1 M2 A# s( d" Y# A# U memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
' L( m. ?; d; g8 S6 j' @ VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);8 {8 I) p. \3 @* M
*(BYTE*)pCdev=0xe9;' f* [9 r$ |9 L( @3 g- D$ M
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;- Y& P* v5 W: F; \( h& i
+ I' G5 h, S" V3 S, l: f" U( O$ z6 R# W0 I
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
2 R; _; V9 a, f) u8 zpPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针- q7 w7 L! T* p; X: X& [
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
, l' s0 U' ~ g! ?+ i! i1 m7 J/ N6 S DWORD oldpro=0;9 v [0 S0 L2 @! p/ R
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
% i) u2 p, Z0 n& N h! G0 q *(BYTE*)pPre=0xe9;
6 B2 ~7 c& Y+ }, r; |: f *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;8 i& u: ^7 V% u/ o! k
2 w9 I6 y. k/ V; D7 k& K8 d实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:% Y' H/ |- m/ ?! b) P# b" H
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
9 H* r% F, y8 p DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
" V( S: `4 x: S: \2 t //在这里写入您的其它绘图代码( N) L) V g2 F5 J7 q8 A+ M0 _
4 U7 J/ L9 E7 R2 u
7 o! r6 I& N6 ^3 \再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
; L7 v) C9 K/ N% F1 Z4 v, T+ _BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)$ c$ `) j- _* D$ A' e/ B
{
9 n" Y, _3 u- @5 j6 v* Z' n
9 }: \6 ]5 Q* u0 M5 r& v/ K: ? if(m_pD3D && pDxdevice){
: Y& W3 l+ ~5 `& y# I `
9 A5 }- I: z, W+ g RECT myrect;/ j+ x# M% X0 I, U
myrect.top=150; //文本块的y坐标
+ H6 n2 s% ^3 Y1 } myrect.left=0; //文本块的左坐标' t W7 t6 j6 j, v8 x& p5 |! `
myrect.right=500+myrect.left;# n4 N" F8 m6 `" B0 q Y# S5 `
myrect.bottom=100+myrect.top;0 W }# W/ Q( p+ U% Z! q' D1 H
pDxdevice->BeginScene();//开始绘制
6 C' L% ?: G9 Y' G4 U2 Z& k$ p* [
' n$ }' C9 Q1 w# i g D3DXFONT_DESCA lf;$ x7 H" j& }6 v" {& o& D
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));% a, Q3 F+ P& d4 E
lf.Height = 24; //字体高度- Q- B) E0 |* J$ g# A) [" t- a
lf.Width = 12; // 字体宽度
: X2 u( S/ g1 R+ \ lf.Weight = 100; 0 D. X* e; |6 H
lf.Italic = false;4 Q# K: U+ \1 G3 E% l! {
lf.CharSet = DEFAULT_CHARSET;
; M' K$ {, ]6 z strcpy(lf.FaceName, "Times New Roman"); // 字型* S2 b1 d- p& v* ?, C$ T, g
ID3DXFont* font=NULL;
A" J) w: X9 X6 n; t if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象* j& j. V# w0 U/ |+ o
return false;
$ X7 F' [; b# O( w2 H; E
4 F$ i1 f- F* G# T font->DrawText(" y( c- z& A, l8 U
NULL,
5 t! R0 q$ X. w2 J) |: M strText, // 要绘制的文本
; \) K" h5 \1 O% L" P nbuf,
, s" m! q( B ~& g- Z &myrect, " W g$ y, p4 z: X
DT_TOP | DT_LEFT, // 字符居左显示
# t' s5 Y- l+ B* H) b D3DCOLOR_ARGB(255,255,255,0));
& R1 j) R/ U6 B1 G# Y5 ~% x" K% U% ]2 l1 k1 W$ c
pDxdevice->EndScene();//结束绘制9 W! {& i5 P& c0 \: a* g
font->Release();//释放对象
+ K, A' z; i( a! @% z }
5 [1 R+ i) s5 [: F, C2 c N# b return true;% A3 `( P9 _4 E; M$ O& P* H! o! E
} |