Instrumenting Direct3D Applications to Capture Video and Calculate FPS

July 23rd, 2013 | Posted by Hernan Di Pietro in Audio | C++ | Deviare | Video

What is your computer’s maximum render quality, resolution, and frames per second for Battlefield 3? Hard core gamers are eager to show off their expensive, tuned setup at its full potential. Overclocked processors and computers cooled with liquid hydrogen are lovely parts of the gaming folklore. The source code below instruments Direct3D 9 applications to calculate frames per second and capture video. It produces an AVI container using the video codec of your choice. It is also possible to capture game screenshots in the 32-bit BMP format by pressing “delete”. The shortcuts for starting and stopping video recording are “F11” and “F12”.

The code we have provided starts the target process in suspended mode, and hooks the Direct3DCreate9 entry point in the d3d9.dll.

Once we catch a Direct3DCreate9 call, we can use the returned IDirect3D9 pointer to index the VTable. Since the call occurred in the target process address space, we cannot use the pointer directly in our host application; however, we can use Deviare’s functions to read the hooked process memory (starting with the IDirect3D9 pointer) to get the method addresses. This is a very interesting and useful technique which avoids sending address data from the intercepted process to the host processes [1].

We use the object’s VTable address to get the address of the CreateDevice method and hook the device creation with INktSpyMgr::CreateHookForAddress and INktSpyMgr::AddCustomHandler. Note that the resulting events INktSpyMgr will trigger both local hook handlers in the SpyMgr host process, and remote hook handlers in the intercepted process. The local handler creates hooks for IDirect3DDevice9::EndScene, IDirect3DDevice9::Reset, IDirect3DDevice9::Present, and IDirect3DDevice9::Release using the remote VTable indexing technique. The remote handler initializes the FPS display, keyboard hook, font, and sprite resources for the console.

All hooking is done from the Deviare host executable. The plugin that resides in the target process address space does not create additional Deviare hooks. Once all needed IDirect3DDevice9 methods are successfully hooked, we can focus on implementing video capture and FPS display.

In order to implement video capture and FPS display we must define the behavior of the pre-call and post-call handlers for the Present, EndScene, and Reset methods.

In the pre-call to Present, we peek at the first item in the keyboard command queue to check for user actions. If it is the “take screenshot” command, we read the current display device backbuffer and save it to disk. If the first item is the “start recording” command, we create and setup the AVI Video file and it’s streams. If it is the “stop recording” command, we simply close the AVI file if it was recording frames. If video recording has been enabled, we add a frame to the current AVI file and update the frame per second statistics. The number of frames recorded depends on the specified frame rate (the default is 15).

In the pre-call to EndScene, if the device is not currently in a reset state, we update the console and frame counter text in the display. If it was not changed, it is simply redrawn with the current string buffer.

Pre-call handling of device Reset occurs when windows are resized, video mode changes, or fullscreen mode is entered or exited. If video is being recorded we stop recording since we do not handle multiple resolution streams in our code. Next, we release our font and sprite resources, and our backing display surface. During post-call Reset function handling, we also check for video recording. Additionally, we recreate the surface, sprite and font resources that we released in the Reset pre-call stage.

Possible Improvements

This code can be improved to include instrumentation of DirectX 11 as well as OpenGL applications and games. The technique would be similar. DirectX 10/11 applications use DXGI low-level interfaces to swap back and front buffers and there are no “lost devices”, so instead of hooking EndScene, Present, and Reset, we would hook DXGI library function calls. This approach would be easier than instrumenting DirectX9, since display device handling is simplified.

Audio Capture: Windows Vista and later systems direct all DirectX audio streams through the native AudioSession APIs. The easiest way to capture audio is through the documented loopback audio device. The limitation is that all system audio is captured, not only audio from our desired application.

If audio isolation is required, hooking the IAudioRenderClient interface should copy audio buffer data into user application memory. Audio data could then be transferred to the audio stream in the AVI file.

The key problem of audio capturing is resampling. If the sample rate and bit depth of the AVI stream and audio buffer do not match, the audio must be resampled. Writing a good resampler is not trivial task. Our Audio Recorder SDK provides a basic and fast resampling code.

Code

The AVRecorderTool code is available on github.

Prerequisites

  1. Deviare Instrumentation Engine
  2. Copy Deviare32.db, Deviare64.db, DeviareCOM.dll, DeviareCom64.dll, DvAgent.dll, and DvAgent64.dll to AVRecorderTool\dll

Acknowledgements

Sebastian Wain contributed with the writing of the introduction.

Notes

  1. The Intercepting Direct3D COM Objects and Making Game Walls Invisible article used IPC to send the remote IDirect3D interface address to the host application’s SpyMgr.

Related Products

  1. Deviare Hooking Engine
  2. Deviare In-Process
  3. SpyStudio
  4. Audio Recorder API

If you liked this article, you might also like:

  1. Injecting a DLL in a Modern UI Metro Application
  2. Intercepting Direct3D COM Objects and Making Game Walls Invisible
  3. How to Identify Virtual Table Functions with the VTBL IDA Pro Plugin
  4. Controlling the Speed of YouTube, Flash, HTML5, and Desktop Videos with Deviare Hooks
  5. Logging Printer Activity

You can follow any responses to this entry through the RSS 2.0 Responses are currently closed, but you can trackback.