冒险解谜游戏中文网 ChinaAVG

标题: 【转载】在游戏中显示自己的文字和图形的方法 [打印本页]

作者: jinxin8866    时间: 2010-4-4 21:42
标题: 【转载】在游戏中显示自己的文字和图形的方法
标 题: 【转载】在游戏中显示自己的文字和图形的方法
4 g/ l  a8 d4 V1 S/ i作 者: runjin8 o9 L( V! e- q& ]
时 间: 2009-04-03,22:44:516 B) l% {' a; b
链 接: http://bbs.pediy.com/showthread.php?t=85368
! G" h% h8 `/ a7 y8 i. Y! c' i, P: X
Hook Directx:在游戏中显示自己的文字和图形的方法
  S0 L/ R7 T  f; ^  g; P5 E; W, B% a0 S! @
这个方法出自我大概两年前的一个项目,现在经整理后贴出来和大家分享一下,利用该方法可以在一般的directx游戏里面绘制文本甚至图形对象.
1 y' F8 I6 j( |) Q: j' q其实思路上非常简单,大致是这样的:要在directx中绘制文字和各种图形对象,只要获得一个类型为LPDIRECT3DDEVICE9的设备对象指针.怎样获得这个指针呢?我的方法是首先hook掉Direct3DCreate9以获得类型为LPDIRECT3D9的Direct3D对象的接口指针,这个Direct3D对象有一个成员函数为 IDirect3D9::CreateDevice,设备对象指针就是在这个函数里面创建的.所以,只要根据Direct3D对象接口指针找到Direct3D对象的虚函数表,再根据虚函数表确定IDirect3D9::CreateDevice的内存地址,就可以hook这个函数,从而获得类型为LPDIRECT3DDEVICE9的设备对象指针,然后就可以随意绘制文字或者图形了.2 V+ J- l) b; y1 I
还有一个要hook的地方,即IDirect3DDevice9::Present,这个函数用于交换当前后备缓存区,刷新窗口.要使得我们自己绘制的东西一直显示在屏幕上,比较好的处理方法是hook掉IDirect3DDevice9::Present,在程序真正调用这个函数前插入我们自己的绘制代码.只要根据设备对象指针找到设备对象的虚函数表,根据虚函数表找到IDirect3DDevice9::Present在内存中的地址就可以hook了.下面是对hook directx的详细说明.
8 I3 e( N; p/ X首先是找到Direct3DCreate9的内存地址,然后把入口的5个字节修改为跳转指令.
( K5 t* e8 Y% r, J- ~- b) Y% ~    pC=GetProcAddress(GetModuleHandle("d3d9.dll"),"Direct3DCreate9");//获得内存地址
6 X: N, w1 v/ e$ f9 K; Y  A; {' ?7 _    DWORD oldpro=0;& _1 [. Z0 c2 }% ]# I
    memcpy(d3dcen5bytes,pC,5);
% L* W6 [  H3 o8 d3 R0 ~) C    VirtualProtect(pC,5,PAGE_EXECUTE_READWRITE,&oldpro);
4 I# G- S7 n8 L- t/ k( S$ V: s    *(BYTE*)pC=0xe9;//0xe9在汇编中是跳转指令操作码  x( X" h2 w- q" ~' O* @1 r
    *(DWORD*)((BYTE*)pC+1)=(DWORD)hookedDirect3DCreate9-(DWORD)pC-5;//目标地址-原地址-5
% H% {$ E6 K! o, n( v$ Y
! x: H1 Q# g' }! b
' s: m( x7 X+ p( l, k这样,在程序运行到Direct3DCreate9时就会跳转到hookedDirect3DCreate9,在这个函数中,首先是还原Direct3DCreate9入口的5个字节,然后调用真正的Direct3DCreate9,如果函数调用成功,就会返回类型为LPDIRECT3D9的Direct3D对象指针,这正是我们所需要的,得到这个指针后,就可以根据Direct3D对象的虚函数表确定IDirect3D9::CreateDevice的内存地址,然后就可以把这个函数入口的5个字节修改成为跳转指令,跳到我们自己的函数中去. 有个地方值得注意的是,在directx的虚函数中把this指针作为第一个形参入栈了.例如说sdk中IDirect3D9::CreateDevice的函数说明是这样:
, i/ f/ w/ M7 i; j# m: r' d7 H/ q* MHRESULT CreateDevice(
& j7 M4 b$ C! }/ e( X  UINT Adapter,4 w) j. I& Z  L  y% Z
  D3DDEVTYPE DeviceType,! e$ a* u8 U. ?) ^" E$ ]& `$ `" p
  HWND hFocusWindow,
* g  S% `1 S* V* b  DWORD BehaviorFlags,
+ P1 z" N, k5 \* o# a, i4 O# Y  D3DPRESENT_PARAMETERS * pPresentationParameters,
! }* \+ @& b! A% E& `  IDirect3DDevice9 ** ppReturnedDeviceInterface4 ?! S- U% J9 I/ [4 }! l( k
);
; b) i4 f6 Q( ?7 T. S( a2 u+ g6 D. b: Q9 Z' d! T9 ^

% J& |2 g. b6 z9 e* F# p* p, K而为了程序跳转到我们的代码执行完后保持栈的平衡,hookedDirect3DCreat9函数声明应该是这样:
9 B6 v# ^' ]- n8 ^# lHRESULT _stdcall hookedCreateDevice(
7 y* {4 _& `, U- n7 y9 f$ k                                  LPDIRECT3D9 pDx9,
$ J* |! \7 @1 s                                  UINT Adapter,
  \' {6 Q, s) c7 m1 w/ B                                  D3DDEVTYPE DeviceType,
/ N. Q+ x! P1 ^/ R: h                                  HWND hFocusWindow,6 \, E4 u  }& O- D
                                  DWORD BehaviorFlags,
9 t. I& t: H! @+ C- n/ |                                  D3DPRESENT_PARAMETERS * pPresentationParameters,
7 e. Q0 E+ J' a. v! J                                  IDirect3DDevice9 ** ppReturnedDeviceInterface
0 }5 ~& L2 m: t+ F" R% f; }  @8 w2 q) X: n3 E, ]- I
                                  );
. Y1 n2 I5 }0 ?- F
6 ^% u4 S. o. @4 M4 l( M其中的LPDIRECT3D9 pDx9就是this指针.* C) r* J0 Q6 t% Q5 C
  E) A5 r# R+ o
hookedDirect3DCreate9的关键代码如下:
* R' h1 x, i& Z' u- K* N8 Q  wpCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
( c' ?% N# y$ S
7 g4 T1 B/ n+ \        DWORD oldpro=0;
7 i' ]4 r8 F9 k        memcpy(devcen5bytes,pCdev,5);//保存IDirect3D9::CreateDevice入口5个字节, e# C4 s# W, X, v' q
        VirtualProtect(pCdev,5,PAGE_EXECUTE_READWRITE,&oldpro);
3 q/ Y+ ~( C  A        *(BYTE*)pCdev=0xe9;' L. H3 |( |3 r
        *(DWORD*)((BYTE*)pCdev+1)=(DWORD)hookedCreateDevice-(DWORD)pCdev-5;
) O! t4 J5 I, }7 v6 E9 d: _
& a7 k7 m! f) |4 _+ ]- L& Z8 W% {$ i3 }在hookedCreateDevice中,首先还原原来的CreateDevice函数入口的5个字节,然后调用原来的函数, IDirect3D9::CreateDevice的最后一个参数是一个二重指针,如果函数调用成功,这个二重指针所指向的指针就是我们所需要的设备对象指针,由此找到设备对象的虚函数表,然后确定IDirect3DDevice9::Present的内存地址,然后又可以改掉入口的5个字节为跳转指令. hookedCreateDevice的关键代码如下:% X! ~' x1 h1 l5 U
pPre=(void*)*(DWORD*)(*(DWORD*)m_pDevice+0x44);//获得IDirect3DDevice9::Present的地址指针0 V; G% A$ R7 T# K7 N$ ^" c
        memcpy(pren5bytes,pPre,5);//保存IDirect3DDevice9::Present入口的5个字节
$ v$ E4 ~$ [1 c1 o& U7 @5 J        DWORD oldpro=0;
: L: ~# O8 }# Y9 y, C5 `4 F6 [! W- N        VirtualProtect(pPre,5,PAGE_EXECUTE_READWRITE,&oldpro);
# o8 T6 A# i2 ]7 |; n        *(BYTE*)pPre=0xe9;( ~4 E% T& ~' R& a+ i0 G4 `, y
        *(DWORD*)((BYTE*)pPre+1)=(DWORD)hookedPresent-(DWORD)pPre-5;
+ z2 p3 Z. [, X' }
8 f2 U- P! M+ r$ {! q7 K实际上这几个函数的hook都是同样的道理,现在,当程序运行到IDirect3DDevice9::Present后又会跳转到hookedPresent,而我们自己的绘制代码就是放在hookedPresent里面.在执行完自己的绘制代码后再调用原来的IDirect3DDevice9::Present.我的hookedPresent的关键代码如下:
& ^/ h$ Q- C' y0 {$ ^1 Wchar strdraw[]="The drawing in directx game\nAuthor:RunJin\nEmail:[email protected]";
& y! d' T+ ]0 w- h) b            DrawMyText(pDxdevice,strdraw,sizeof strdraw-1);//绘制文本, ]7 m. I: m5 {. Z6 |3 S1 [
            //在这里写入您的其它绘图代码7 ~6 l" O5 g" X
4 p7 |& g# U3 L! g1 X

. |- {* O4 j4 a9 H1 ]再来看看其中的DrawMyText,这个函数是我的绘制文本的函数:- ~! [5 d& j9 f5 a% t
BOOL _stdcall DrawMyText(LPDIRECT3DDEVICE9 pDxdevice,TCHAR* strText ,int nbuf)5 M' j2 ~& W$ a) F* |
{; J0 C8 `; p: R! W% w

4 r2 a, G- o$ }& O    if(m_pD3D && pDxdevice){
( z$ O- n: u/ g4 n% U; ~8 E
6 F" I' J4 b1 t4 B7 O  m# k        RECT myrect;# P. w& w' S/ q3 h
        myrect.top=150;  //文本块的y坐标( q% @. J$ n2 k
        myrect.left=0; //文本块的左坐标
% v/ w4 P* k8 R9 H; \% a- q5 q        myrect.right=500+myrect.left;9 T# J4 M" x6 w3 p3 @: X
        myrect.bottom=100+myrect.top;2 k1 f: d) O1 U# w1 P1 {* y5 V: A1 N
        pDxdevice->BeginScene();//开始绘制
, u3 L4 O* ?# `: L9 r5 A7 E+ S9 P7 I( ]' O/ {7 s! ^
        D3DXFONT_DESCA lf;
1 X+ @" E, ?8 T, h0 }' n! A9 i) ^        ZeroMemory(&lf, sizeof(D3DXFONT_DESCA));
6 _* N  e  K2 v' Y. x        lf.Height = 24; //字体高度& Y+ R# _" Q& J- s! }: s( p
        lf.Width = 12; // 字体宽度
* G; ]0 A, V8 q        lf.Weight = 100; - G" e  ]6 M/ @, Y; o3 x
        lf.Italic = false;4 V, T, p5 u! X& L; y
        lf.CharSet = DEFAULT_CHARSET;4 F# h* j: Y$ ^8 Q5 x
        strcpy(lf.FaceName, "Times New Roman"); // 字型
$ N" `- f/ w* N3 q% j+ ]        ID3DXFont* font=NULL;
4 ]+ o- Y) [, V! _8 X9 k+ o        if(D3D_OK!=D3DXCreateFontIndirect(pDxdevice, &lf, &font)) //创建字体对象
# {+ L& s. U( i+ t6 n8 S            return false;
8 a( f1 K( n+ R7 }: ]2 l& V' X, ]
" `! B: c4 B4 n  A3 M) [        font->DrawText() i, `) f5 p$ S1 c
            NULL,* y8 F2 y# {! V5 Q! M* f/ p
            strText, // 要绘制的文本
% K! Y+ b9 M" [8 b5 j            nbuf, . M& V5 q8 J9 e. d: }) y
            &myrect, ) O2 S1 G9 X. y! r
            DT_TOP | DT_LEFT, // 字符居左显示
; r* k" C1 M3 n+ j) n% a9 @            D3DCOLOR_ARGB(255,255,255,0));
6 g1 G; u* H- ]( L  C2 w
" b; p* ^& G# y0 c7 B% b8 j        pDxdevice->EndScene();//结束绘制
1 p/ u# b; A8 E5 x- M* j4 D  {4 ^        font->Release();//释放对象5 R1 s8 X; ~& ~- F* Z
    }
* ^$ m8 k5 B) F7 X/ U    return true;7 l9 g, y) f5 u- {9 E$ c; g
}
作者: shane007    时间: 2010-4-4 21:49
多谢楼主,不过,其实我已经发过了。
2 R8 Q/ @) C' thttps://www.chinaavg.com/read.php?tid=18802
作者: solidji    时间: 2010-4-6 16:02
他这个方法不好,局限性比较大
) F+ d# `* q8 N首先不能方便的HOOK所有函数,需要到处打JMP补丁
4 l; S9 l1 {8 v另外每HOOK一个函数都必须自己去查找D3D对象偏移, w( Y* ^9 s0 t/ c
pCdev=(void*)*(DWORD*)(*(DWORD*)m_pD3D+0x40);//获得IDirect3D9::CreateDevice的地址指针
! \' u: c  g7 x5 e& t比如这里CreateDevice地址指针是m_pD3D+0x40,这个资料里一般是没有的要自己反汇编下
7 o! j0 y6 r# D& |# F/ R* J$ J0 K* t3 ?$ d. |  @: F( x
还是007那个方法好,也是我一直在使用的方法6 S# _4 `4 n( }9 ?1 d& q
直接创建一个自己的D3D对象返回,针对Direct3DCreate9与CreateDevice特别处理一下3 }! G# `0 `7 k
只需一个钩子,后面所有的D3D事件都得从这过了[s:109]




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2