7 votes

D3D9 Hook - Superposition avec Direct3D9

OK, donc j'essaie de faire une superposition pour quelques boutons supplémentaires dans un jeu Direct X.

J'ai trouvé un échantillon c++ qui se superpose assez bien ici : http://www.gamedev.net/topic/359794-c-direct3d-hooking-sample/

J'ai donc commencé à le convertir en Delphi. Avec un peu de logging, je peux voir qu'il démarre le hook sur le bon processus et qu'il accroche Direct3DCreate9() correctement.

Ensuite, TMyDirect3D9 est créé avec succès. Mais le processus s'arrête à partir de là.

Mon hypothèse (basée sur un débogage dans Ollydbg) est que lorsque je renvoie MyDirect3D9 au processus d'origine via le hooked Direct3DCreate9() et qu'il essaie d'appeler une des fonctions de la classe (interface), il échoue.

Le code est le suivant. Si je peux donner d'autres informations pour vous aider, faites-le moi savoir.

DLL principal :

library LeagueUtilityBox;

{$R *.res}

{$DEFINE DEBUG}

uses
  Windows,
  APIHijack in 'APIHijack.pas',
  Direct3D9 in '..\DirectX 9.0\Direct3D9.pas',
  uSharedMem in '..\Misc\uSharedMem.pas',
  MyDirect3D9 in 'MyDirect3D9.pas',
  MyDirect3DDevice9 in 'MyDirect3DDevice9.pas',
  {$IFDEF DEBUG}
  SysUtils,
  uLog in '..\Misc\uLog.pas',
  {$ENDIF}
  uMisc in 'uMisc.pas';

var
  SharedMem : TSharedMem;
  D3DHook: SDLLHook;
  hHook : DWORD;
  MyDirect3D9 : TMyDirect3D9;

function GetTargetProcess: String;
const
  KeyBase : DWORD = HKEY_CURRENT_USER;
  KeyLocation : String = 'Software\LeagueUtilityBox';
var
  RegKey : HKEY;
  TargetProcess : Array[0..511] Of Char;
  Count : DWORD;
begin
  Result := '';
  If RegOpenKeyEx(KeyBase, PChar(KeyLocation), 0, KEY_QUERY_VALUE, RegKey) = ERROR_SUCCESS Then
    begin
    Count := 512;
    If RegQueryValueEx(RegKey, nil, nil, nil, @TargetProcess[0], @Count) = ERROR_SUCCESS Then
      begin
      Result := String(TargetProcess);
    end;
  end;
end;

type
  TDirect3DCreate9 = function(SDKVersion: LongWord): Pointer; stdcall;

function MyDirect3DCreate9(SDKVersion: LongWord): Pointer; stdcall;
var
  OldFunc : TDirect3DCreate9;
  D3D : PIDirect3D9;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'MyDirect3DCreate9 called');
  {$ENDIF}
  Result := nil;
  OldFunc := TDirect3DCreate9(D3DHook.Functions[0].OrigFn);
  D3D := OldFunc(SDKVersion);
  If D3D <> nil Then
    begin
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'D3D created: 0x' + IntToHex(DWORD(Pointer(D3D)), 8));
    {$ENDIF}
    New(MyDirect3D9);
    MyDirect3D9 := TMyDirect3D9.Create(D3D);
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'MyDirect3D9 Created');
    {$ENDIF}
    Result := @MyDirect3D9;
  end;
end;

procedure InitializeHook;
var
  Process : String;
  I : Integer;
begin
  SetLength(Process, 512);
  GetModuleFileName(GetModuleHandle(nil), PChar(Process), 512);
  For I := Length(Process) DownTo 1 Do
    begin
    If Process[I] = '\' Then Break;
  end;
  Process := Copy(Process, I + 1, Length(Process));
  If CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, PChar(GetTargetProcess), -1, PChar(Process), -1) = 2 Then
    begin
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'Found target: ' + GetTargetProcess);
    {$ENDIF}
    With D3DHook Do
      begin
      Name := 'D3D9.DLL';
      UseDefault := False;
      DefaultFn := nil;
      SetLength(Functions, 1);
      Functions[0].Name := 'Direct3DCreate9';
      Functions[0].HookFn := @MyDirect3DCreate9;
      Functions[0].OrigFn := nil;
    end;
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'About to hook: ' + String(AnsiString(D3DHook.Name)));
    {$ENDIF}
    HookAPICalls(@D3DHook);
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'Hook completed: ' + String(AnsiString(D3DHook.Name)));
    {$ENDIF}
  end;
end;

procedure InitializeDLL;
begin
  SharedMem := TSharedMem.Create('LeagueUtilityBox', 1024);
  Try
    hHook := PDWORD(SharedMem.Buffer)^;
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'Initializing DLL: ' + IntToStr(hHook));
    {$ENDIF}
  Finally
    SharedMem.Free;
  end;
end;

procedure UninitializeDLL;
begin
  UnhookWindowsHookEx(hHook);
end;

function WindowsHookCallback(nCode: Integer; WPARAM: Integer; LPARAM: Integer): LRESULT; stdcall;
begin
  Result := CallNextHookEx(hHook, nCode, WPARAM, LPARAM);
end;

procedure EntryPoint(Reason: DWORD);
begin
  Case Reason Of
    DLL_PROCESS_ATTACH:
      begin
      InitializeDLL;
      InitializeHook;
    end;
    DLL_PROCESS_DETACH:
      begin
      UninitializeDLL;
    end;
  end;
end;

exports
  WindowsHookCallback;

begin
  DLLProc := @EntryPoint;
  EntryPoint(DLL_PROCESS_ATTACH);
end.

L'IDirect3D9 personnalisé :

unit MyDirect3D9;

interface

uses Direct3D9, Windows, uMisc, uLog;

type
  PMyDirect3D9 = ^TMyDirect3D9;
  TMyDirect3D9 = class(TInterfacedObject, IDirect3D9)
  private
    fD3D: PIDirect3D9;
  public
    constructor Create(D3D: PIDirect3D9);

    function QueryInterface(riid: REFIID; ppvObj: PPointer): HRESULT; stdcall;
    function _AddRef: DWORD; stdcall;
    function _Release: DWORD; stdcall;

    function RegisterSoftwareDevice(pInitializeFunction: Pointer): HResult; stdcall;
    function GetAdapterCount: LongWord; stdcall;
    function GetAdapterIdentifier(Adapter: LongWord; Flags: DWord; out pIdentifier: TD3DAdapterIdentifier9): HResult; stdcall;
    function GetAdapterModeCount(Adapter: LongWord; Format: TD3DFormat): LongWord; stdcall;
    function EnumAdapterModes(Adapter: LongWord; Format: TD3DFormat; Mode: LongWord; out pMode: TD3DDisplayMode): HResult; stdcall;
    function GetAdapterDisplayMode(Adapter: LongWord; out pMode: TD3DDisplayMode): HResult; stdcall;
    function CheckDeviceType(Adapter: LongWord; CheckType: TD3DDevType; AdapterFormat, BackBufferFormat: TD3DFormat; Windowed: BOOL): HResult; stdcall;
    function CheckDeviceFormat(Adapter: LongWord; DeviceType: TD3DDevType; AdapterFormat: TD3DFormat; Usage: DWord; RType: TD3DResourceType; CheckFormat: TD3DFormat): HResult; stdcall;
    function CheckDeviceMultiSampleType(Adapter: LongWord; DeviceType: TD3DDevType; SurfaceFormat: TD3DFormat; Windowed: BOOL; MultiSampleType: TD3DMultiSampleType; pQualityLevels: PDWORD): HResult; stdcall;
    function CheckDepthStencilMatch(Adapter: LongWord; DeviceType: TD3DDevType; AdapterFormat, RenderTargetFormat, DepthStencilFormat: TD3DFormat): HResult; stdcall;
    function CheckDeviceFormatConversion(Adapter: LongWord; DeviceType: TD3DDevType; SourceFormat, TargetFormat: TD3DFormat): HResult; stdcall;
    function GetDeviceCaps(Adapter: LongWord; DeviceType: TD3DDevType; out pCaps: TD3DCaps9): HResult; stdcall;
    function GetAdapterMonitor(Adapter: LongWord): HMONITOR; stdcall;
    function CreateDevice(Adapter: LongWord; DeviceType: TD3DDevType; hFocusWindow: HWND; BehaviorFlags: DWord; pPresentationParameters: PD3DPresentParameters; out ppReturnedDeviceInterface: IDirect3DDevice9): HResult; stdcall;
  end;

implementation

uses MyDirect3DDevice9;

constructor TMyDirect3D9.Create(D3D: PIDirect3D9);
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.Create');
  {$ENDIF}
  fD3D := D3D;
end;

function TMyDirect3D9.QueryInterface(riid: REFIID; ppvObj: PPointer): HRESULT; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.QueryInterface');
  {$ENDIF}
  Result := fD3D^.QueryInterface(riid, ppvObj);
end;

function TMyDirect3D9._AddRef: DWORD; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9._AddRef');
  {$ENDIF}
  Result := fD3D^._AddRef;
end;

function TMyDirect3D9._Release: DWORD; stdcall;
var
  count : DWORD;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9._Release');
  {$ENDIF}
  count := fD3D^._Release;
  If count = 0 Then
    begin
    Self.Free;
  end;
  Result := count;
end;

function TMyDirect3D9.RegisterSoftwareDevice(pInitializeFunction: Pointer): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.RegisterSoftwareDevice');
  {$ENDIF}
  Result := fD3D^.RegisterSoftwareDevice(pInitializeFunction);
end;

function TMyDirect3D9.GetAdapterCount: LongWord; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.GetAdapterCount');
  {$ENDIF}
  Result := fD3D^.GetAdapterCount;
end;

function TMyDirect3D9.GetAdapterIdentifier(Adapter: LongWord; Flags: DWord; out pIdentifier: TD3DAdapterIdentifier9): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.GetAdapterIdentifier');
  {$ENDIF}
  Result := fD3D^.GetAdapterIdentifier(Adapter, Flags, pIdentifier);
end;

function TMyDirect3D9.GetAdapterModeCount(Adapter: LongWord; Format: TD3DFormat): LongWord; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.GetAdapterModeCount');
  {$ENDIF}
  Result := fD3D^.GetAdapterModeCount(Adapter, Format);
end;

function TMyDirect3D9.EnumAdapterModes(Adapter: LongWord; Format: TD3DFormat; Mode: LongWord; out pMode: TD3DDisplayMode): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.EnumAdapterModes');
  {$ENDIF}
  Result := fD3D^.EnumAdapterModes(Adapter, Format, Mode, pMode);
end;

function TMyDirect3D9.GetAdapterDisplayMode(Adapter: LongWord; out pMode: TD3DDisplayMode): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.GetAdapterDisplayMode');
  {$ENDIF}
  Result := fD3D^.GetAdapterDisplayMode(Adapter, pMode);
end;

function TMyDirect3D9.CheckDeviceType(Adapter: LongWord; CheckType: TD3DDevType; AdapterFormat, BackBufferFormat: TD3DFormat; Windowed: BOOL): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.CheckDeviceType');
  {$ENDIF}
  Result := fD3D^.CheckDeviceType(Adapter, CheckType, AdapterFormat, BackBufferFormat, Windowed);
end;

function TMyDirect3D9.CheckDeviceFormat(Adapter: LongWord; DeviceType: TD3DDevType; AdapterFormat: TD3DFormat; Usage: DWord; RType: TD3DResourceType; CheckFormat: TD3DFormat): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.CheckDeviceFormat');
  {$ENDIF}
  Result := fD3D^.CheckDeviceFormat(Adapter, DeviceType, AdapterFormat, Usage, RType, CheckFormat);
end;

function TMyDirect3D9.CheckDeviceMultiSampleType(Adapter: LongWord; DeviceType: TD3DDevType; SurfaceFormat: TD3DFormat; Windowed: BOOL; MultiSampleType: TD3DMultiSampleType; pQualityLevels: PDWORD): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.CheckDeviceMultiSampleType');
  {$ENDIF}
  Result := fD3D^.CheckDeviceMultiSampleType(Adapter, DeviceType, SurfaceFormat, Windowed, MultiSampleType, pQualityLevels);
end;

function TMyDirect3D9.CheckDepthStencilMatch(Adapter: LongWord; DeviceType: TD3DDevType; AdapterFormat, RenderTargetFormat, DepthStencilFormat: TD3DFormat): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.CheckDepthStencilMatch');
  {$ENDIF}
  Result := fD3D^.CheckDepthStencilMatch(Adapter, DeviceType, AdapterFormat, RenderTargetFormat, DepthStencilFormat);
end;

function TMyDirect3D9.CheckDeviceFormatConversion(Adapter: LongWord; DeviceType: TD3DDevType; SourceFormat, TargetFormat: TD3DFormat): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.CheckDeviceFormatConversion');
  {$ENDIF}
  Result := fD3D^.CheckDeviceFormatConversion(Adapter, DeviceType, SourceFormat, TargetFormat);
end;

function TMyDirect3D9.GetDeviceCaps(Adapter: LongWord; DeviceType: TD3DDevType; out pCaps: TD3DCaps9): HResult; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.GetDeviceCaps');
  {$ENDIF}
  Result := fD3D^.GetDeviceCaps(Adapter, DeviceType, pCaps);
end;

function TMyDirect3D9.GetAdapterMonitor(Adapter: LongWord): HMONITOR; stdcall;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.GetAdapterMonitor');
  {$ENDIF}
  Result := fD3D^.GetAdapterMonitor(Adapter);
end;

function TMyDirect3D9.CreateDevice(Adapter: LongWord; DeviceType: TD3DDevType; hFocusWindow: HWND; BehaviorFlags: DWord; pPresentationParameters: PD3DPresentParameters; out ppReturnedDeviceInterface: IDirect3DDevice9): HResult; stdcall;
var
  hr : HRESULT;
begin
  {$IFDEF DEBUG}
  WriteToLog('C:\LeagueUtilityBox.log', 'TMyDirect3D9.CreateDevice');
  {$ENDIF}
  hr := fD3D^.CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);
  If Succeeded(hr) Then
    begin
    {$IFDEF DEBUG}
    WriteToLog('C:\LeagueUtilityBox.log', 'fD3D^.CreateDevice Succeeded');
    {$ENDIF}
    ppReturnedDeviceInterface := TMyDirect3DDevice9.Create(PIDirect3D9(@Self), @ppReturnedDeviceInterface);
  end;
  Result := hr;
end;

end.

UPDATE : Ainsi, puisque les interfaces Delphi semblent se comporter différemment des interfaces réelles (Delphi a une interface intermédiaire qui lui permet de communiquer avec d'autres interfaces). J'ai donc converti l'interface en un tableau de pointeurs.

Maintenant le programme appelle avec succès CreateDevice(). Je peux le voir à la fois dans les journaux et dans les pas à pas dans Ollydbg.

Maintenant, ce qui se passe, c'est que lorsque CreateDevice appelle l'original IDirect3D9.CreateDevice(), il se plante à nouveau. Lorsque je débogue dans Ollydbg, je remarque que le pointeur est déréférencé une fois de trop.

UPDATE 2 : Ok, j'ai corrigé quelques problèmes de pointeurs avec PIDirect3D9 vs IDirect3D9 à différents endroits. Donc l'original IDirect3D9.CreateDevice() est appelé. Mais il y a une erreur avec D3DERR_INVALIDCALL ! !!

C'est si déroutant.

UPDATE 3 : Ok, avec un peu plus de débogage, il semble que lorsque j'appelle la fonction, un paramètre supplémentaire est poussé sur la pile. Ce qui rend le premier paramètre invalide. Ceci est prouvé par DirectX Debugging qui dit que le paramètre iAdapter est invalide (premier paramètre).

UPDATE 4 : En utilisant IntRefToMethPtr() pour obtenir le pointeur direct de l'appel original CreateDevice, j'ai pu le faire appeler avec les mêmes piles. Même résultat. Il semble que j'ai pris la mauvaise route en essayant de l'accrocher dans Delphi.

UPDATE 5 : Réécriture de la méthode d'accrochage. Maintenant, j'accroche essentiellement EndScene(). Le hook fonctionne maintenant très bien dans un programme de test (Vertices.exe qui est fourni avec la démo du hook trouvée dans la première URL de ce post). Mais dans le jeu principal, il fait planter le jeu. Dans tous les cas, j'ai beaucoup appris.

3voto

ssube Points 8838

J'ai fait cela plusieurs fois maintenant, et les détails sont un peu longs à mettre dans une réponse, mais il y a quelques problèmes communs et quelques problèmes spécifiques sur lesquels vous devrez travailler.

Tout d'abord, vous devez implémenter les interfaces IDirect3D9 et IDirect3DDevice9 (au moins). exactement comme ils sont faits dans les bibliothèques, ou binairement compatibles. Les interfaces sont basées sur des appels de fonctions virtuelles (je ne suis pas sûr de l'équivalent en Pascal), donc toutes les méthodes doivent être virtuelles, les méthodes doivent être dans le même ordre et prendre les mêmes arguments (qui doivent aussi être dans le même ordre), etc.

La partie que je regarderais de près est la façon dont pascal gère les fonctions, elles doivent être binairement compatibles avec le code construit par Visual C++ ( __thiscall virtuel, etc.)

De plus, avec quelques regex simples, vous pouvez généralement générer votre en-tête personnalisé et le squelette de votre fichier de code à partir de l'en-tête D3D9 existant. Notez que, même si cela peut sembler idiot, un wrapper D3D9 complet peut (pour IDirect3DDevice9 seul) atteindre 2300 lignes ; générer les bases à partir de l'en-tête permet d'éviter de nombreuses saisies susceptibles de provoquer des erreurs.

Pour gérer les boutons, vous devrez également a) dessiner par-dessus le rendu existant et b) récupérer les entrées.

a) est trivial : vous attendez simplement que device->Present() et faites votre dessin avant d'appeler le vrai présent. La seule vraie difficulté concerne les états du dispositif. Vous devrez sauvegarder les états existants, définir vos états pour le dessin en superposition, puis réinitialiser les états du périphérique. Si vous ne les réinitialisez pas correctement, vous risquez de rencontrer toutes sortes de problèmes. Les états qui doivent être définis dépendent de votre application, mais en général, le culling, le tri/test de profondeur et autres sont ceux que vous voulez désactiver.

b) pour faire des boutons, vous devrez également vous connecter à l'entrée de la fenêtre d'une manière ou d'une autre. Implémenter le même type de wrapper que vous avez ici, mais pour DInput (s'il est utilisé) est probablement une bonne idée. Vous pouvez alors effectuer la vérification de l'entrée, le rendu et la logique dans la méthode Present du wrapper I...Device9.

Il existe également un décent J'ai un peu de code dans les environs pour des wrappers comme ceux-ci ; j'ai des d3d 8 à 9 (traduction runtime) et 9, et je connais 2 autres wrappers d3d9 qui peuvent être réutilisés. Cela pourrait valoir la peine d'y réfléchir, soit pour vérifier votre code, soit pour utiliser du code existant.

Si vous souhaitez obtenir plus d'informations à ce sujet ou si vous trouvez quelque chose d'intéressant, je serai heureux de vous aider ou de vous informer.

1voto

Marco van de Voort Points 15378

Vous pouvez consulter les paquets DirextX existants pour Delphi, ne serait-ce que pour confirmer (avec leurs exemples) que les constructions utilisées sont les mêmes que celles que vous utilisez.

Le site que je connais le mieux est celui de Clootie : http://clootie.ru/delphi/index.html Mais je crois qu'il y a plusieurs tentatives

Il existe des SDK DX9 et DX10 avec des exemples.

1voto

Michael Grenzer Points 483

Vous pouvez obtenir une DLL Proxy D3D9 entièrement fonctionnelle si vous utilisez le SDK D3D9 de Clootie.

http://sourceforge.net/projects/delphi-dx9sdk/

et la base d3d9 "avancée" de GD

http://www.gamedeception.net/attachment.php?attachmentid=3035&d=1260299029

Je l'utilise sur Delphi Architect XE3 et il compile et fonctionne bien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X