冒险解谜游戏中文网 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$ ?( E2 \, 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+ npCdev=(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 Ychar 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 z6 C# R' f% y' P  a+ ]( g
再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:
( S; @1 P! U) X% K; _5 u% xBOOL _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/ tpCdev=(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