冒险解谜游戏中文网 ChinaAVG
标题:
【转载】在游戏中显示自己的文字和图形的方法
[打印本页]
作者:
jinxin8866
时间:
2010-4-4 21:42
标题:
【转载】在游戏中显示自己的文字和图形的方法
标 题: 【转载】在游戏中显示自己的文字和图形的方法
7 [7 `0 x1 ~! W9 E
作 者: runjin
* K% K# P0 H* b
时 间: 2009-04-03,22:44:51
n8 T, ^6 G2 ?8 W* D7 Z; M
链 接:
http://bbs.pediy.com/showthread.php?t=85368
: Z1 B/ ~- z1 R
% _8 j2 a+ h/ k6 u5 F
Hook Directx:在游戏中显示自己的文字和图形的方法
. j4 S9 l; d; o5 J/ `! X3 }( ^
. N7 X) Z, p2 S
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
/ D; `! o4 v# w/ v& q& ~ G, R
其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.
5 }2 I" E7 n, s' m0 E: L
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
6 p$ b( n+ ?% u. w g8 f! j
首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
' q- C0 f* d" p: e
pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
0 i. z. J+ g, ~4 ?2 ~
DWORD oldpro=0;
9 i6 h# D0 J, n" r- P
memcpy(d3dcen5bytes,pC,5);
) g8 b& s" G0 j1 m4 Q1 y6 N: u
VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
% _3 n* H: ~3 ]3 f( }9 s5 V3 u; p
*(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码
' K3 O. a. ]0 ~6 a$ r
*(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
9 x; C; D: B0 ?7 S% q7 P$ ?( E
2 \, L* B1 z q' @: [# }
0 c& G0 T9 l1 H6 W3 L( ^5 c. }9 s' c
这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
" v. g. \0 g& m. ^
HRESULT CreateDevice(
) |+ I6 {7 G9 O1 A8 o2 J
UINT Adapter,
) W2 c7 l3 T; S1 {# [; W {
D3DDEVTYPE DeviceType,
" I* |) |* M9 A8 a8 |
HWND hFocusWindow,
5 Z8 i2 j. ?# i; r
DWORD BehaviorFlags,
4 U8 A- B- Z- J. M; M2 }# D
D3DPRESENT_PARAMETERS * pPresentationParameters,
! z4 `+ \# V, I6 U& Z! w* F
IDirect3DDevice9 ** ppReturnedDeviceInterface
7 O, @) S2 C ?6 J0 L
);
0 F- e! i( W+ `+ r
0 V+ e3 f; V) U/ ] L3 V! X
6 C' \( K* t0 m5 J, A
而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
! y" s+ d% U8 P' S6 \5 P/ u1 m, r
HRESULT _stdcall hookedCreateDevice(
9 m0 w0 O4 B1 e: {
LPDIRECT3D9 pDx9,
# j+ [7 ?+ M1 q9 q
UINT Adapter,
. G$ T$ H% h( F# B5 a
D3DDEVTYPE DeviceType,
0 l- T" `5 e' x: |( p
HWND hFocusWindow,
+ Y: o6 v6 k$ l0 G4 f8 d) _
DWORD BehaviorFlags,
v0 L, Z* a, M3 M6 x/ C
D3DPRESENT_PARAMETERS * pPresentationParameters,
. ?9 A S# | i1 l
IDirect3DDevice9 ** ppReturnedDeviceInterface
" e7 L: A" |9 q. `2 r
3 D! F" Y$ |: o6 G5 \
);
x" a% M$ S- j8 T4 i) F$ ?* }. I
5 w# g: m8 d a! z+ i! Y
其中的LPDIRECT3D9 pDx9就是this指针.
0 {4 Y( O: z3 g; H9 E
a2 P' s0 e" Y/ _: }
hookedDirect3DCreate9的关键代码如下:
' u# F: u1 r. ]! m+ n
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
$ V' g$ P( g0 o
6 }3 p% i/ G: D& U6 z3 V
DWORD oldpro=0;
% p, g2 o8 H) a9 q7 S3 p
memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节
) ]) t1 ~3 l$ ^
VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
& j4 u0 n1 O1 S! Q6 J I; J6 _8 w
*(BYTE*)pCdev=0xe9;
) C2 e% n" H" f" e) a; _
*(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
8 c# @3 w8 }2 | c
7 a0 z, }4 W: a" ^1 \
在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:
" I2 z" ~5 y# D2 D
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针
8 Q& D, S9 d K1 L: b) Z2 n
memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
# f6 k7 K4 s' l$ b; e/ h
DWORD oldpro=0;
, b4 S! H8 Y. P. N
VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
i- c: e; f+ u9 a
*(BYTE*)pPre=0xe9;
) n, f9 n0 ? I, r9 A' y
*(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
; n# J* q6 B5 o* m# O
+ O& U9 Y0 k: }# l5 F: |
实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
4 |5 _7 P/ k1 ^# p% s& X0 Y
char strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:
[email protected]
";
0 C" h5 s; M- d% P0 G
DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本
7 [; l* A# o$ ?" n
//在这里写入您的其它绘图代码
4 @4 }; u% ]0 T& s6 `
6 n9 y: N/ `' e2 z
6 C# R' f% y' P a+ ]( g
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
( S; @1 P! U) X% K; _5 u% x
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)
/ X2 G) M/ o# a) Y: J, B3 N* k
{
6 x7 h# n0 x3 s! G2 m# }% o9 w
( {& F; k5 H! j3 _
if(m_pD3D && pDxdevice){
; ?; ]/ Y" z# W
* T" t) ~1 O0 k+ E" K" `& T
RECT myrect;
h5 p6 l6 u- W8 ?4 L# b
myrect.top=150; //文本块的y坐标
# X5 U. k+ {: f6 R2 m/ R- J
myrect.left=0; //文本块的左坐标
8 s: ~+ {" f3 `+ {6 r$ ]! Q+ B
myrect.right=500+myrect.left;
1 V9 U# U. @) J2 C
myrect.bottom=100+myrect.top;
8 k; ?6 b& H5 ^' [% |3 G
pDxdevice->BeginScene();//开始绘制
- n9 V! H; E! [$ \' ]2 m8 ~' T& d
% ^% \! K2 `( T9 y. n
D3DXFONT_DESCA lf;
+ M# M3 y1 Z, g7 W* D
ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
5 Q# _. ]. T( J5 Q1 @& m; _" r
lf.Height = 24; //字体高度
* |* j. f! k6 j7 K
lf.Width = 12; // 字体宽度
& d$ P! I: \1 X( F7 ~% U' I8 O P, m
lf.Weight = 100;
/ X" ^9 I1 n, m- N6 }
lf.Italic = false;
% t) E1 g' M j9 u( N u& ^* Y3 p
lf.CharSet = DEFAULT_CHARSET;
6 y1 J, r: z# e$ [# S' D& m' a. Z
strcpy(lf.FaceName, "Times New Roman"); // 字型
1 w; _: m) q* h4 h- _4 J
ID3DXFont* font=NULL;
- D* ~; H# j6 Q) Y, o
if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
8 v; I! p- |* g8 H) p2 L( M$ @$ o
return false;
/ w, i0 m4 x) p" ]0 j0 Z
5 w1 s3 _, |& H* n$ U3 h B
font->DrawText(
! Z" o9 g) m: j* d# r6 ? [
NULL,
z$ C( J* {* ] Y; @
strText, // 要绘制的文本
& `" T& f Y( i
nbuf,
7 D! ~8 p3 |3 @) ~2 ~- w$ I1 U
&myrect,
- u0 e! y- A) U, y! u8 J
DT_TOP | DT_LEFT, // 字符居左显示
4 k7 b$ M9 |6 j
D3DCOLOR_ARGB(255,255,255,0));
% l2 I3 ?: f6 U, X
0 }1 j+ [. n6 L3 G9 u! e$ Z
pDxdevice->EndScene();//结束绘制
3 N4 k8 v$ Q* d; V( {2 \" f3 H- Q
font->Release();//释放对象
9 m6 y% G! ~/ _8 C
}
. Z, n9 j* s1 e) h8 w' w
return true;
* }0 r2 [8 c |, L
}
作者:
shane007
时间:
2010-4-4 21:49
多谢楼主,不过,其实我已经发过了。
, h/ f* e, W! y6 a" T- \
https://www.chinaavg.com/read.php?tid=18802
作者:
solidji
时间:
2010-4-6 16:02
他这个方法不好,局限性比较大
' ~- S- B; S3 U5 x, l
首先不能方便的HOOK所有函数,需要到处打JMP补丁
$ @& U8 t4 b) E
另外每HOOK一个函数都必须自己去查找D3D对象偏移
; A8 ]$ U0 k* ?2 j" g/ t
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
7 w9 T' p3 i% ~/ _! }) a
比如这里CreateDevice地址指针是m_pD3D+0x40,这个资料里一般是没有的要自己反汇编下
3 Z0 N8 n/ ^6 V: M0 U7 D, j) |
/ k& s& Q* \( `7 M1 Q. b' l
还是007那个方法好,也是我一直在使用的方法
/ r8 t5 r) g5 K! x$ b
直接创建一个自己的D3D对象返回,针对Direct3DCreate9与CreateDevice特别处理一下
6 y/ l e$ s7 ~& _2 `" N( P. Y
只需一个钩子,后面所有的D3D事件都得从这过了[s:109]
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2