Рисуем в чужом Direct3D приложении
May 31, 07 by TracKerОднажды я нашел такую программу как Fraps, она рисовала счетчик FPS в приложениях DirectX. Мне захотелось написать что-то подобное. И вот что у меня получилось…
Что нужно иметь для работы:
- http://madshi.net/ – madCodeHook из madCollection, для перехвата вызовов DirectX и не только
- http://sourceforge.net/projects/delphi-dx9sdk – Новые заголовки DirectX 9 для Delphi
- http://www.mirgames.ru/articles/directx/dxtutorial.html – И обязательно прочитать эту статью чтобы именть какое-то представление о работе DirectX
И так, приступим.
Попробуем написать какой-то текст в чужем приложении. Для этого на нужно кстановить перехватчик (hook или хук) на вызовы трех функций, нужных нам для отрисовки нашего текста. Но сделать это не так просто. Для того чтобы выполнить наш програмный код в чужем приложении необходимо создать DLL с этим кодом и провести так называемую “DLL инъекцию” (DLL Injection). Для этого необходимо создать приостановленый процесс (запустьть наше DirectX приложение вызовом CreateProcessA с флагом CREATE_SUSPENDED), далее Инджектировать нашу DLL в процесс (вызов InjectLibraryA), и продолжить выполнение нашего процесса (вызов ResumeThread).
Код:
uses Direct3D9, madCodeHook;procedure TForm1.Button1Click(Sender: TObject); var start: TStartupInfo; procInfo: TProcessInformation; begin ZeroMemory(@start, SizeOf(start)); start.cb:=SizeOf(Start); if CreateProcessA( 'E:\Games\Lineage II\system\l2.exe', '', nil, nil, True, CREATE_SUSPENDED, nil, 'E:\Games\Lineage II\system', start, procInfo) then begin if not InjectLibraryA( procInfo.hProcess, 'my_lib.dll') then ShowMessage('failed'); ResumeThread(procInfo.hThread); end; end;
Теперь, что качается самой DLL. Первым делом нужно установить хук на вызов Direct3DCreate9 из d3d9.dll. Далее нужно установить хук на создание D3DDevice, потом на метод интерфейса IDirect3D9.CreateDevice (тут также необходимо произвести инициализацию шрифта которым мы будим писать текст), и наконец на метод интерфейса IDirect3DDevice9.EndScene (тут мы будим писать текст).
Код:
library my_lib;uses SysUtils, Classes, madCodeHook, Windows, Direct3D9, D3DX9; var D3DObj: IDirect3D9; D3DDev: IDirect3DDevice9; g_Font: ID3DXFont; function GetInterfaceMethod(const intf; methodIndex: dword) : pointer; begin result := pointer(pointer(dword(pointer(intf)^) + methodIndex * 4)^); end; var EndScene9Next : function (self: pointer): HResult stdcall = nil; var CreateDevice9Next : function (self: Pointer; Adapter: LongWord; DeviceType: TD3DDevType; hFocusWindow: HWND; BehaviorFlags: DWord; pPresentationParameters: PD3DPresentParameters; out ppReturnedDeviceInterface: IDirect3DDevice9) : HRESULT stdcall = nil; var Direct3DCreate9Next: function (SDKVersion: LongWord): DWORD stdcall = nil; function EndScene9Callback(self: pointer): HResult; stdcall; var TextRect: TRect; begin TextRect := Rect(100,100,100,100); g_Font.DrawTextA( nil, PChar('Превед!!! :)'), -1, @TextRect, DT_LEFT or DT_NOCLIP, D3DCOLOR_RGBA($00, $ff, $ff, $ff) ); Result:=EndScene9Next(self); end; function CreateDevice9Callback(self: pointer; Adapter: LongWord; DeviceType: TD3DDevType; hFocusWindow: HWND; BehaviorFlags: DWord; pPresentationParameters: PD3DPresentParameters; out ppReturnedDeviceInterface: IDirect3DDevice9) : HRESULT; stdcall; var A: Integer; begin result := CreateDevice9Next(self, adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); D3DDev := ppReturnedDeviceInterface; if (result = 0) then begin A := D3DXCreateFont( D3DDev, 100, 0, FW_BOLD, 1, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH or FF_DONTCARE, PChar('Arial'), g_Font ); HookCode(GetInterfaceMethod(ppReturnedDeviceInterface{^}, 42), @EndScene9Callback, @EndScene9Next); end; end; function Direct3DCreate9Callback(SDKVersion: LongWord): DWORD; stdcall; begin Result:=Direct3DCreate9Next(SDKVersion); D3DObj := IDirect3D9(Result); if (Result <> 0) then begin if (@CreateDevice9Next = nil) then UnhookCode(@CreateDevice9Next); HookCode(GetInterfaceMethod(result, 16), @CreateDevice9Callback, @CreateDevice9Next); end; end; begin HookAPI('d3d9.dll', 'Direct3DCreate9', @Direct3DCreate9Callback, @Direct3DCreate9Next); end.
Первое что выполняется в DLL, это:
HookAPI('d3d9.dll', 'Direct3DCreate9', @Direct3DCreate9Callback, @Direct3DCreate9Next);
Эта функция осущесвляет перехват функции Direct3DCreate9, находящейся в библиотеке d3d9.dll и заменяет ее на вызов функции Direct3DCreate9Callback (которая описана выше в коде). Подробнее об этой функции можно узнать тут: http://help.madshi.net/ApiCodeHooking.htm
После этого мы ждем когда главное приложение вызовет Direct3DCreate9 и сработает Direct3DCreate9Callback. В Direct3DCreate9Callback мы вызываем настоящую функцию, указатель на которую хранится в переменной Direct3DCreate9Next:
Result := Direct3DCreate9Next(SDKVersion);
В результате этого мы получаем указатель на интерфейс IDirect3D9, а именно объект D3DObj. Теперь нас интересует процесс создания IDirect3DDevice9, для этого мы ставим хук на метод IDirect3D9.CreateDevice:
HookCode(GetInterfaceMethod(result, 16), @CreateDevice9Callback, @CreateDevice9Next);
В качестве медода мы пишем не его название, а его порядковый номер в объявлении, в данном случае 16. Ниже на рисунке показан пример с нумерацией.
![]()
Теперь мы ждем вызова CreateDevice9Callback. Как только это происходит мы делаем вызов самой функции Direct3DCreate9Next, и получаем объект D3DDev, после этого создаем объект шрифта g_Font вызовом D3DXCreateFont. Паремтры этой функции более подробно можно узнать тут: http://msdn2.microsoft.com/en-us/library/bb172773.aspx
И тут же ставим хук на вызов метода IDirect3DDevice9.EndScene, он имеет номер 42.
Все приготовления завершены. Теперь мы ждем вызова EndScene9Callback. В этой функции мы осуществляем сам процесс рисования следующим вызовом:
TextRect := Rect(100,100,100,100); g_Font.DrawTextA( nil, PChar('Превед!!! :)'), -1, @TextRect, DT_LEFT or DT_NOCLIP, D3DCOLOR_RGBA($00, $ff, $ff, $ff) );
Подробную информацию об этой функции можно прочесть тут:
MSDN Help
Пример работы программы:
shot00004.jpg
Ну и собственно работающий Исходник:
directx_dll_injection_src.rar
MeySight Says: 27.07.07 at 21:11
Здаствуйте автор хотелось бы узнать в чем может быть пролблема если приложение после использования данного примера кричит о потере девайса (DirectDevice). (Приложение полноэкранное, вылетает после сворачивания его и переключения обратно)
Заранее спасибо
TracKer Says: 28.07.07 at 17:45
MeySight, затрудняюсь ответить.
В примере расмотрен частный случай, далеко не все нужные функции отлавливаются. Возможно проблема в этом.
Если ответ нужен срочно попробуйте сросить тут: http://forum.sources.ru/index.php?showforum=89
MeySight Says: 28.07.07 at 19:55
TracKer, спасибо за статью и за ответ будем копать глубже.
gekko Says: 26.12.07 at 03:32
n1, you have help me very much with this!
HeadFore Says: 22.07.08 at 22:33
Спасибо, очень полезнаю статья. Но madCodeHook сейчас не доступен для свободной загрузки на сайте автора. Можешь выложить необходимый код здесь или выслать на мыло?
TracKer Says: 23.07.08 at 15:50
HeadFore, Я нашел у себя старенькую версию:
http://tracker2k.googlepages.com/madCollection_old.rar
XAKE Says: 25.08.08 at 16:18
Это madCollection для C++ же !! !
TracKer Says: 27.08.08 at 17:38
XAKE, спамить не хорошо… Я перед тем как выкладывать что-то смотрю что внутри…
XAKE Says: 29.08.08 at 20:18
извеняюсь !!!! у меня некак injection не происходит , пишет про то что лиценцию и то что надо засунуть в system32 dll’ку
TracKer Says: 29.08.08 at 21:33
XAKE, скачайте старую версию которую я выложил для HeadFore. В каментах есть ссылка.
XAKE Says: 31.08.08 at 09:48
Еще раз изменяюсь , у меня теперь другая проблема , хоть в какое приложение пытаюсь зделать injection , меня радует сообщение falied
if not InjectLibraryA(procInfo.hProcess, ‘my_lib.dll’) then
ShowMessage(‘failed’);
тоесть inject неполучился !!!
TracKer Says: 31.08.08 at 12:50
XAKE, давайте код будим разбираться, видимо что-то неправильно делаете.
Mixa Says: 01.09.08 at 14:40
Тоже маялся с инъекцией, пока не догадался посмотреть, какие модули DX подтягивает игра. Оказалось что d3d8.dll. Пытался подогнать содержимое библиотеки под DX8 -безуспешно. Игра рушится еще на этапе загрузки, не успев отобразить D3D окно.
На этапе изучения этой темы, пока что. Надо мне научится сперва вывести текст посредством DX8, судя по всему.
Полагаю, собака зарыта в “правильности” переписанной библиотеки под dx8
А так, изменив хук “HookAPI(‘d3d8.dll’……..” инъекция происходит успешно, в чем я убедился просмотрев загружаемые игрой модули.
Использовал следующих подопытных: RF Online (RF_Online.bin) и PW Online (elementclient.exe).
XAKE Says: 03.09.08 at 04:16
У меня получилось в игре S.T.A.L.K.E.R Shadow of Chernobil . А почему в такой игре как GTA_SA не получается она использует d3d9 ?
Андрей Says: 26.03.09 at 10:09
библиотеки для DX8 – http://www.clootie.ru/cbuilder/index.html
Номера функций:
15 – CreateDevice8
35 – EndScene.
Проверено на PWOnline.
Николай Says: 01.02.10 at 06:10
Долго пытался найти программу которая моглабы выводить часы в Direct3D приложениях – в конце концов наткнулся на эту статью. К сожалению я сам нечего непонимаю в програмировании, но мне показалось что если в данном примере заменить текст на часы (системное время чч:мм:сс) и подкорректировать размер шрифта – получитсо именно то что я ищщу. если это возможно (и нетребует больших временных затрат) немогбы ктото сделать такую программу? (использовать собираюсь в тойже игре что и в примере – такчто никаких изменений, кроме того какой именно текст выводитсо на экран, нетребуетсо, иначебы непросил
firstvirus Says: 22.04.10 at 14:30
У меня проблема такая. Пишу библиотеку для отрисовки пользовательского интерфейса в 3Д приложении. Суть заключается в том, что программист использует какой-либо 3Д движок для написания своей игры. И мою библиотеку для создания и отрисовки GUI в своей игре. Как мне описать инициализацию моей библиотеки, чтобы моя библиотека могла работать с DirectXDevice созданный 3Д движком?
TracKer Says: 04.06.10 at 11:05
firstvirus, очень не советую с помощью хуков накладывать интерфейс на игру. Во-первых у вас будет много проблем с портабельностью даже в пределах разных версий Windows. Ну а во-вторых как у вас интерфейс будет получать доступ к данным игры? Помоему это супер-велосипед получается.
Андрей Says: 30.07.10 at 13:02
обидно, не работает:(
TracKer Says: 30.07.10 at 13:10
Андрей, а что за приложение? Возможно оно использует не девятый DirectX, а какой-то другой, например десятый?