Рисуем в чужом Direct3D приложении

May 31, 07 by TracKer

Однажды я нашел такую программу как Fraps, она рисовала счетчик FPS в приложениях 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. Ниже на рисунке показан пример с нумерацией.

interface_methods_small.png

Теперь мы ждем вызова 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

google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru
Add your comment

49 responses for this post

  1. MeySight Says:

    Здаствуйте автор хотелось бы узнать в чем может быть пролблема если приложение после использования данного примера кричит о потере девайса (DirectDevice). (Приложение полноэкранное, вылетает после сворачивания его и переключения обратно)

    Заранее спасибо

  2. TracKer Says:

    MeySight, затрудняюсь ответить.
    В примере расмотрен частный случай, далеко не все нужные функции отлавливаются. Возможно проблема в этом.

    Если ответ нужен срочно попробуйте сросить тут: http://forum.sources.ru/index.php?showforum=89

  3. MeySight Says:

    TracKer, спасибо за статью и за ответ будем копать глубже. :)

  4. gekko Says:

    n1, you have help me very much with this!

  5. HeadFore Says:

    Спасибо, очень полезнаю статья. Но madCodeHook сейчас не доступен для свободной загрузки на сайте автора. Можешь выложить необходимый код здесь или выслать на мыло?

  6. TracKer Says:

    HeadFore, Я нашел у себя старенькую версию:
    http://tracker2k.googlepages.com/madCollection_old.rar

  7. XAKE Says:

    Это madCollection для C++ же !! !

  8. TracKer Says:

    XAKE, спамить не хорошо… Я перед тем как выкладывать что-то смотрю что внутри…

  9. XAKE Says:

    извеняюсь !!!! у меня некак injection не происходит , пишет про то что лиценцию и то что надо засунуть в system32 dll’ку

  10. TracKer Says:

    XAKE, скачайте старую версию которую я выложил для HeadFore. В каментах есть ссылка.

  11. XAKE Says:

    Еще раз изменяюсь , у меня теперь другая проблема , хоть в какое приложение пытаюсь зделать injection , меня радует сообщение falied :(
    if not InjectLibraryA(procInfo.hProcess, ‘my_lib.dll’) then
    ShowMessage(‘failed’);

    тоесть inject неполучился !!!

  12. TracKer Says:

    XAKE, давайте код будим разбираться, видимо что-то неправильно делаете.

  13. Mixa Says:

    Тоже маялся с инъекцией, пока не догадался посмотреть, какие модули DX подтягивает игра. Оказалось что d3d8.dll. Пытался подогнать содержимое библиотеки под DX8 -безуспешно. Игра рушится еще на этапе загрузки, не успев отобразить D3D окно.
    Полагаю, собака зарыта в “правильности” переписанной библиотеки под dx8 :( На этапе изучения этой темы, пока что. Надо мне научится сперва вывести текст посредством DX8, судя по всему.
    А так, изменив хук “HookAPI(‘d3d8.dll’……..” инъекция происходит успешно, в чем я убедился просмотрев загружаемые игрой модули.
    Использовал следующих подопытных: RF Online (RF_Online.bin) и PW Online (elementclient.exe).

  14. XAKE Says:

    У меня получилось в игре S.T.A.L.K.E.R Shadow of Chernobil . А почему в такой игре как GTA_SA не получается она использует d3d9 ?

  15. Андрей Says:

    библиотеки для DX8 – http://www.clootie.ru/cbuilder/index.html
    Номера функций:
    15 – CreateDevice8
    35 – EndScene.

    Проверено на PWOnline.

  16. Николай Says:

    Долго пытался найти программу которая моглабы выводить часы в Direct3D приложениях – в конце концов наткнулся на эту статью. К сожалению я сам нечего непонимаю в програмировании, но мне показалось что если в данном примере заменить текст на часы (системное время чч:мм:сс) и подкорректировать размер шрифта – получитсо именно то что я ищщу. если это возможно (и нетребует больших временных затрат) немогбы ктото сделать такую программу? (использовать собираюсь в тойже игре что и в примере – такчто никаких изменений, кроме того какой именно текст выводитсо на экран, нетребуетсо, иначебы непросил :)

  17. firstvirus Says:

    У меня проблема такая. Пишу библиотеку для отрисовки пользовательского интерфейса в 3Д приложении. Суть заключается в том, что программист использует какой-либо 3Д движок для написания своей игры. И мою библиотеку для создания и отрисовки GUI в своей игре. Как мне описать инициализацию моей библиотеки, чтобы моя библиотека могла работать с DirectXDevice созданный 3Д движком?

  18. TracKer Says:

    firstvirus, очень не советую с помощью хуков накладывать интерфейс на игру. Во-первых у вас будет много проблем с портабельностью даже в пределах разных версий Windows. Ну а во-вторых как у вас интерфейс будет получать доступ к данным игры? Помоему это супер-велосипед получается.

  19. Андрей Says:

    обидно, не работает:(

  20. TracKer Says:

    Андрей, а что за приложение? Возможно оно использует не девятый DirectX, а какой-то другой, например десятый?

  21. nommura Says:

    Пожалуйста автор статьи – выложи в архиве или как нить все те файлы которые нужны 100% для работоспособности данного примера, DLL, Hook, ПОЖАЛУЙСТА если тебя не затруднит.
    Ты описал пример и дал пару сайтов, а что куда и как впихивать не объяснил.

    Пожалуйста помоги!!!!!!!!!!!!!!!!!!!!!!!
    Заранее спасибо.

  22. TracKer Says:

    nommura, а ссылка в конце статьи на рабочий исходник вам не подходит?
    Ссылка на заголовки DirectX 9: http://sourceforge.net/projects/delphi-dx9sdk (там большая зеленая кнопка для скачивания, к сожалению прямее дать не могу из-за того что для каждого ссылка генерируется своя)
    Ссылка на старый MadCollection (новый вроде бы платный): http://tracker2k.googlepages.com/madCollection_old.rar
    И, собственно, рабочий исходник: http://tracker2k.kiev.ua/_public/blog/directx_dll_injection/directx_dll_injection_src.rar

  23. nommura Says:

    Да все эти файлы я уже скачал, думал еще надо что то.
    Суть вот в чем: у меня есть программа на делфи, состоит из 1 окна.
    При нажатии на кнопку F1 появляется надпись в окне ПРИВЕТ.
    Как прикрутить эту надпись так чтобы она появлялась поверх DirectX игр?….
    То есть – я запустил Полноэкранное DirectX приложение. Нажал на кнопку F1, у меня на экране появилась надпись “Привет”.
    Ваш пример не компилируется. В предпоследней строке помоему вылетает ошибка.

  24. nommura Says:

    кроме Вас в данный момент мне не к кому больше обратиться…нужно добавить все го лишь 7 текстовых сообщений в программу и все…больше не надо….Пожалуйста помогите – Вы единственный наверно кто может помочь…я даже могу дать исходник програмки….уже надежды почти нету чтобы сделать кроме Вас……

  25. TracKer Says:

    Ответил вам на мыло.

  26. TracKer Says:

    nommura
    Дело в том что я не занимаюсь Delphi с 2007 года поэтому помочь вам не смогу. Программиста найти совсем не проблема воспользуйтесь любым сайтом фрилансеров.
    Например http://www.weblancer.net или http://freelance.ru

  27. nommura Says:

    получилось скомпилировать все но Текста нет…уже 8 приложений опробвано…безрпезультатно

  28. TracKer Says:

    nommura, я писал вам в e-mail как проверить произошла ли инъекция. Вы проверили? Можно ведь миллион приложений попробовать и ничего не получится. И еще, как вы определяете версию DirectX которая используется в ваших приложениях? Может быть там вообще используется DirectDraw, или, например, OpenGL. Этот метод, кстати, годится только для Direct3D.
    Потом, какая у вас винда? Это все тестировалось на XP SP1, сейчас же уже у многих стоит Windows 7, что на 2 поколения дальше XP. Ну и не стоит забывать, что вы используете madCodeHook старой версии (так как новый платный) вполне возможно что методы инъекции применяемые в этой библиотеке перестали работать уже в XP SP2…

  29. TracKer Says:

    Попробуйте воспользоваться сторонним инжектором, например Winject – http://www.cheat-project.com/cheats-hacks/1382/WinJect-1.7/

  30. nommura Says:

    Винда WIn7…
    Winject также не работает – ни при загруженном приложении ни при принудительной загрузке.
    DirectX првоеряю – просто знаю что игра не поддерживает 10 и выше верcию. Как точней проверить я не знаю.
    Как проверить произошла иньекция или нет – я не понял, помоему вы не писали об этом.

  31. TracKer Says:

    Да, вы правы, письмо сохранилось как черновик и не отправилось – моя ошибка.

    Скачайте ProcessExplorer и посмотрите список прикрепленных к вашей игре DLL-ок, уже после инжектирования, естественно. Как это сделать. Запустите, затем проделайте следующие действия:

    View -> Show Lower Pane
    View -> Lower Pane View -> DLLs

    Затем выделите процесс с вашей игрой, вы получите список используемых DLL, включая инжектируемые. Найдите вашу DLL.

  32. nommura Says:

    Да…игра подгружает и madHook.dll и my_lib.dll
    Но там же я в программе посомтрел что madHook.dll имеет Discripion api hooking for 9x/nt
    Судя по всему я предполагаю что этот хук не работает под WIn7.

    Как быть в таком случае? Не подскажете?

  33. carliker Says:

    Уважаемый, Автор. А как динамически менять текст. Например если программа чат (использует TSocketServer и Client), и пришло какое либо слово, вывелось в едит анпример или просто прислоилось переменной, как его вывести на экран в место слова “прдвед” как в примере?

  34. TracKer Says:

    carliker, текст рисуется в EndScene9Callback, соответственно если вам нужен другой текст, чего-то еще дорисовать или дописать – это делается там. Если текст, то при помощи вызова g_Font.DrawTextA, или еще один добавляйте, или изменяйте тот что есть.

    nommura, в таком случае могу только посоветовать найти другую библиотеку для хуков и инъекций. Или же написать свою.

  35. carliker Says:

    Спасибо за ответ, проблема в том что я не знаю как в DLL переслать переменную, чтобы получилось например так:
    var
    TEXT:string;
    ….
    begin
    …..
    TEXT:=Form1.Edit.Text; // Это едит который лежит на основной форме
    g_Font.DrawTextA( nil, PChar(TEXT)’), -1,……

    У меня по каким то причинам не работает.

  36. 4e1ovek Says:

    Здравствуйте.
    После компиляции, и успешного инжекции в l2.exe (в ProcessExplorer библиотека my_lib.dll подключена), запущенное окно игры не содержит необходимого текста.
    Windows 7 x64, Delphi XE, madCollection v2.6, библиотека madCHook.dll из вашего архива madCollection_old.rar.

    Как узнать, к верным ли методам (16 и 42) обращается программа? – если речь идёт о файле Direct3D9.pas, который идёт вместе с Delphi начиная с прошлой (2010) версии, то строки верны.

  37. AlucarD Says:

    У меня вопрос по вот этой функции
    function GetInterfaceMethod(const intf; methodIndex: dword) : pointer;
    begin
    result := pointer(pointer(dword(pointer(intf)^) + methodIndex * 4)^);
    end;
    Насколько я понял в HookCode нужно передать адрес функции. Как получить адрес виртуальной функции из классов DirectX? В каждый объект класса добавляется указатель на таблицу виртуальных функций, которая содержит указатели на эти функции. Как получить хотя бы этот указатель, чтоб дальше от него шагать по адресам функций? Я работаю в C++

  38. AlucarD Says:

    переписал под C++
    void* getCodeAdr(void *adr, int ind)
    {
    return (void*)*(DWORD*)(*((DWORD*)(adr))+ind*4);
    }
    Только этот код зависит от компилятора((
    В данном случае указатель на таблицу виртуальных функций находится в начале класса

  39. AlucarD Says:

    а как вывести таким же способом в directx приложении дочернее WinAPi окно?

  40. execom Says:

    Здравствуйте автор. Спасибо за статью, все работает, но после сворачивания игры (GTA San Andreas) и последующего ее восстановления она отображает пустое окно, хотелось бы узнать в чем проблема, заранее спасибо.

  41. Alik Says:

    У меня приложение запускается с параметром, к имени файла дописываю параметр и не чего не происходит. видимо функция CreateProcessA возвращает false.

    CreateProcessA(‘C:\Program Files\Igrosoft 12 in 1\Igrosoft.exe crzmon_7′, ”, nil, nil, True, CREATE_SUSPENDED, nil,
    ‘C:\Program Files\Igrosoft 12 in 1′, start, procInfo)

    Как быть?

  42. TracKer Says:

    Alik, попробуйте так:
    CreateProcessA(‘C:\Program Files\Igrosoft 12 in 1\Igrosoft.exe’, ‘crzmon_7′, nil, nil, True, CREATE_SUSPENDED, nil,
    ‘C:\Program Files\Igrosoft 12 in 1′, start, procInfo);

  43. Alik Says:

    TRACKER, спасибо за ответ. Но не помогло, запускается так же как и без параметра.

  44. Nexus Says:

    Здравствуйте TRACKER. Спасибо за статью, все работает, но есть одна проблема – после сворачивания и последующего восстановления игра отображает пустое окно или вылетает с ошибкой. Если запустить игру в оконном режиме, то она сворачивается и восстанавливается без проблем, если закомментить строку где выводится сам текст, то тоже все ок. Подскажите пожалуйста в чем проблема? Или хотя бы в какую сторону копать. Ответте пожалуйста.

  45. TracKer Says:

    Nexus, привет! :)
    Я рад что статья вам пригодилась. К сожалению вы не первый кто меня об этом спрашивает, но на этот вопрос я не знаю ответа. Скорее всего когда вы сворачиваете игру какой-то объект уничтожается и создается заново поэтому рисование по старому дескрипрору происходит с ошибкой, что приводит к падению. Однако как это отследить я не подскажу. :(

  46. God Says:

    Когда-то я сам начинал с этой статьи, очень помогла в освоении миханизмов хука и директикс, спасибо автору. Сам сталкивался с проблемой вылетов при сворачивании. Во избежания этой проблемы нужно в отдельном потоке( можно в таймере) проверять – произошла ли потеря девайса или нет d3ddev.TestCooperativeLeveld3d_ok , обычно при сворачивании окна это и происходит. Нужно в этот момент удалять все созданные объекты directx ( текстуры, спрайты, шрифты, поверхности и т.д) . А после восстановления ( разворачивнания окна) , когда d3ddev.TestCooperativeLevel=d3d_ok, нужно создавать их заново. Или при потере девайса снимать хуки, а при восстановлении ставить их обратно.

  47. Андрей Says:

    Пожалуйста, перезалейте madCodeHook.

  48. Nexus Says:

    Долго копался и все таки нашел нормально работающие примеры
    http://forum.madshi.net/viewtopic.php?t=4521
    Может кому еще пригодится :)

  49. % Says:

    Good job!

Leave a Reply