Рисуем в чужом 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

20 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, а какой-то другой, например десятый?

Leave a Reply