1 #include <dxgi.h>
2
3 #include "../d3d8-api/d3d8.h"
4 #include "graphics-hook.h"
5
6 #include <detours.h>
7
8 typedef HRESULT(STDMETHODCALLTYPE *reset_t)(IDirect3DDevice8 *,
9 D3DPRESENT_PARAMETERS *);
10 typedef HRESULT(STDMETHODCALLTYPE *present_t)(IDirect3DDevice8 *, CONST RECT *,
11 CONST RECT *, HWND,
12 CONST RGNDATA *);
13
14 reset_t RealReset = NULL;
15 present_t RealPresent = NULL;
16
17 struct d3d8_data {
18 HMODULE d3d8;
19 uint32_t cx;
20 uint32_t cy;
21 D3DFORMAT d3d8_format;
22 DXGI_FORMAT dxgi_format;
23
24 struct shmem_data *shmem_info;
25 HWND window;
26 uint32_t pitch;
27 IDirect3DSurface8 *copy_surfaces[NUM_BUFFERS];
28 bool texture_ready[NUM_BUFFERS];
29 bool surface_locked[NUM_BUFFERS];
30 int cur_surface;
31 int copy_wait;
32 };
33
34 static d3d8_data data = {};
35
d3d8_to_dxgi_format(D3DFORMAT format)36 static DXGI_FORMAT d3d8_to_dxgi_format(D3DFORMAT format)
37 {
38 switch ((unsigned long)format) {
39 case D3DFMT_X1R5G5B5:
40 case D3DFMT_A1R5G5B5:
41 return DXGI_FORMAT_B5G5R5A1_UNORM;
42 case D3DFMT_R5G6B5:
43 return DXGI_FORMAT_B5G6R5_UNORM;
44 case D3DFMT_A8R8G8B8:
45 return DXGI_FORMAT_B8G8R8A8_UNORM;
46 case D3DFMT_X8R8G8B8:
47 return DXGI_FORMAT_B8G8R8X8_UNORM;
48 }
49
50 return DXGI_FORMAT_UNKNOWN;
51 }
52
d3d8_get_backbuffer(IDirect3DDevice8 * device)53 static IDirect3DSurface8 *d3d8_get_backbuffer(IDirect3DDevice8 *device)
54 {
55 IDirect3DSurface8 *backbuffer;
56 HRESULT hr;
57
58 hr = device->GetRenderTarget(&backbuffer);
59 if (FAILED(hr)) {
60 hlog_hr("d3d8_get_backbuffer: Failed to get backbuffer", hr);
61 backbuffer = nullptr;
62 }
63
64 return backbuffer;
65 }
66
d3d8_get_window_handle(IDirect3DDevice8 * device)67 static bool d3d8_get_window_handle(IDirect3DDevice8 *device)
68 {
69 D3DDEVICE_CREATION_PARAMETERS parameters;
70 HRESULT hr;
71 hr = device->GetCreationParameters(¶meters);
72 if (FAILED(hr)) {
73 hlog_hr("d3d8_get_window_handle: Failed to get "
74 "device creation parameters",
75 hr);
76 return false;
77 }
78
79 data.window = parameters.hFocusWindow;
80
81 return true;
82 }
83
d3d8_init_format_backbuffer(IDirect3DDevice8 * device)84 static bool d3d8_init_format_backbuffer(IDirect3DDevice8 *device)
85 {
86 IDirect3DSurface8 *backbuffer;
87 D3DSURFACE_DESC desc;
88 HRESULT hr;
89
90 if (!d3d8_get_window_handle(device))
91 return false;
92
93 backbuffer = d3d8_get_backbuffer(device);
94 if (!backbuffer)
95 return false;
96
97 hr = backbuffer->GetDesc(&desc);
98 backbuffer->Release();
99 if (FAILED(hr)) {
100 hlog_hr("d3d8_init_format_backbuffer: Failed to get "
101 "backbuffer descriptor",
102 hr);
103 return false;
104 }
105
106 data.d3d8_format = desc.Format;
107 data.dxgi_format = d3d8_to_dxgi_format(desc.Format);
108 data.cx = desc.Width;
109 data.cy = desc.Height;
110
111 return true;
112 }
113
d3d8_shmem_init_buffer(IDirect3DDevice8 * device,int idx)114 static bool d3d8_shmem_init_buffer(IDirect3DDevice8 *device, int idx)
115 {
116 HRESULT hr;
117
118 hr = device->CreateImageSurface(data.cx, data.cy, data.d3d8_format,
119 &data.copy_surfaces[idx]);
120 if (FAILED(hr)) {
121 hlog_hr("d3d8_shmem_init_buffer: Failed to create surface", hr);
122 return false;
123 }
124
125 if (idx == 0) {
126 D3DLOCKED_RECT rect;
127 hr = data.copy_surfaces[0]->LockRect(&rect, nullptr,
128 D3DLOCK_READONLY);
129 if (FAILED(hr)) {
130 hlog_hr("d3d8_shmem_init_buffer: Failed to lock buffer",
131 hr);
132 return false;
133 }
134
135 data.pitch = rect.Pitch;
136 data.copy_surfaces[0]->UnlockRect();
137 }
138
139 return true;
140 }
141
d3d8_shmem_init(IDirect3DDevice8 * device)142 static bool d3d8_shmem_init(IDirect3DDevice8 *device)
143 {
144 for (int i = 0; i < NUM_BUFFERS; i++) {
145 if (!d3d8_shmem_init_buffer(device, i)) {
146 return false;
147 }
148 }
149 if (!capture_init_shmem(&data.shmem_info, data.window, data.cx, data.cy,
150 data.pitch, data.dxgi_format, false)) {
151 return false;
152 }
153
154 hlog("d3d8 memory capture successful");
155 return true;
156 }
157
d3d8_free()158 static void d3d8_free()
159 {
160 capture_free();
161
162 for (size_t i = 0; i < NUM_BUFFERS; i++) {
163 if (data.copy_surfaces[i]) {
164 if (data.surface_locked[i])
165 data.copy_surfaces[i]->UnlockRect();
166 data.copy_surfaces[i]->Release();
167 }
168 }
169
170 memset(&data, 0, sizeof(data));
171
172 hlog("----------------- d3d8 capture freed -----------------");
173 }
174
d3d8_init(IDirect3DDevice8 * device)175 static void d3d8_init(IDirect3DDevice8 *device)
176 {
177 data.d3d8 = get_system_module("d3d8.dll");
178
179 if (!d3d8_init_format_backbuffer(device))
180 return;
181
182 if (!d3d8_shmem_init(device))
183 d3d8_free();
184 }
185
d3d8_shmem_capture_copy(int idx)186 static void d3d8_shmem_capture_copy(int idx)
187 {
188 D3DLOCKED_RECT rect;
189 HRESULT hr;
190
191 if (data.texture_ready[idx]) {
192 data.texture_ready[idx] = false;
193
194 IDirect3DSurface8 *target = data.copy_surfaces[idx];
195 hr = target->LockRect(&rect, nullptr, D3DLOCK_READONLY);
196 if (SUCCEEDED(hr)) {
197 data.surface_locked[idx] = true;
198 shmem_copy_data(idx, rect.pBits);
199 }
200 }
201 }
202
d3d8_shmem_capture(IDirect3DDevice8 * device,IDirect3DSurface8 * backbuffer)203 static void d3d8_shmem_capture(IDirect3DDevice8 *device,
204 IDirect3DSurface8 *backbuffer)
205 {
206 int next_surface;
207 HRESULT hr;
208
209 next_surface = (data.cur_surface + 1) % NUM_BUFFERS;
210 d3d8_shmem_capture_copy(next_surface);
211
212 if (data.copy_wait < NUM_BUFFERS - 1) {
213 data.copy_wait++;
214 } else {
215 IDirect3DSurface8 *src = backbuffer;
216 IDirect3DSurface8 *dst = data.copy_surfaces[data.cur_surface];
217
218 if (shmem_texture_data_lock(data.cur_surface)) {
219 dst->UnlockRect();
220 data.surface_locked[data.cur_surface] = false;
221 shmem_texture_data_unlock(data.cur_surface);
222 }
223
224 hr = device->CopyRects(src, nullptr, 0, dst, nullptr);
225 if (FAILED(hr)) {
226 hlog_hr("d3d8_shmem_capture: CopyRects "
227 "failed",
228 hr);
229 }
230
231 data.texture_ready[data.cur_surface] = true;
232 }
233
234 data.cur_surface = next_surface;
235 }
236
d3d8_capture(IDirect3DDevice8 * device,IDirect3DSurface8 * backbuffer)237 static void d3d8_capture(IDirect3DDevice8 *device,
238 IDirect3DSurface8 *backbuffer)
239 {
240 if (capture_should_stop()) {
241 d3d8_free();
242 }
243 if (capture_should_init()) {
244 d3d8_init(device);
245 }
246 if (capture_ready()) {
247 d3d8_shmem_capture(device, backbuffer);
248 }
249 }
250
hook_reset(IDirect3DDevice8 * device,D3DPRESENT_PARAMETERS * parameters)251 static HRESULT STDMETHODCALLTYPE hook_reset(IDirect3DDevice8 *device,
252 D3DPRESENT_PARAMETERS *parameters)
253 {
254 if (capture_active())
255 d3d8_free();
256
257 return RealReset(device, parameters);
258 }
259
260 static bool hooked_reset = false;
261
setup_reset_hooks(IDirect3DDevice8 * device)262 static void setup_reset_hooks(IDirect3DDevice8 *device)
263 {
264 uintptr_t *vtable = *(uintptr_t **)device;
265
266 DetourTransactionBegin();
267
268 RealReset = (reset_t)vtable[14];
269 DetourAttach((PVOID *)&RealReset, hook_reset);
270
271 const LONG error = DetourTransactionCommit();
272 const bool success = error == NO_ERROR;
273 if (success) {
274 hlog("Hooked IDirect3DDevice8::Reset");
275 hooked_reset = true;
276 } else {
277 RealReset = nullptr;
278 }
279 }
280
hook_present(IDirect3DDevice8 * device,CONST RECT * src_rect,CONST RECT * dst_rect,HWND override_window,CONST RGNDATA * dirty_region)281 static HRESULT STDMETHODCALLTYPE hook_present(IDirect3DDevice8 *device,
282 CONST RECT *src_rect,
283 CONST RECT *dst_rect,
284 HWND override_window,
285 CONST RGNDATA *dirty_region)
286 {
287 IDirect3DSurface8 *backbuffer;
288
289 if (!hooked_reset)
290 setup_reset_hooks(device);
291
292 backbuffer = d3d8_get_backbuffer(device);
293 if (backbuffer) {
294 d3d8_capture(device, backbuffer);
295 backbuffer->Release();
296 }
297
298 return RealPresent(device, src_rect, dst_rect, override_window,
299 dirty_region);
300 }
301
302 typedef IDirect3D8 *(WINAPI *d3d8create_t)(UINT);
303
manually_get_d3d8_present_addr(HMODULE d3d8_module,void ** present_addr)304 static bool manually_get_d3d8_present_addr(HMODULE d3d8_module,
305 void **present_addr)
306 {
307 d3d8create_t create;
308 D3DPRESENT_PARAMETERS pp;
309 HRESULT hr;
310
311 IDirect3DDevice8 *device;
312 IDirect3D8 *d3d8;
313
314 hlog("D3D8 value invalid, manually obtaining");
315
316 create = (d3d8create_t)GetProcAddress(d3d8_module, "Direct3DCreate8");
317 if (!create) {
318 hlog("Failed to load Direct3DCreate8");
319 return false;
320 }
321
322 d3d8 = create(D3D_SDK_VERSION);
323 if (!d3d8) {
324 hlog("Failed to create D3D8 context");
325 return false;
326 }
327
328 memset(&pp, 0, sizeof(pp));
329 pp.Windowed = true;
330 pp.SwapEffect = D3DSWAPEFFECT_FLIP;
331 pp.BackBufferFormat = D3DFMT_A8R8G8B8;
332 pp.BackBufferWidth = 2;
333 pp.BackBufferHeight = 2;
334 pp.BackBufferCount = 1;
335 pp.hDeviceWindow = dummy_window;
336
337 hr = d3d8->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
338 dummy_window,
339 D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp,
340 &device);
341 d3d8->Release();
342
343 if (SUCCEEDED(hr)) {
344 uintptr_t *vtable = *(uintptr_t **)device;
345 *present_addr = (void *)vtable[15];
346
347 device->Release();
348 } else {
349 hlog("Failed to create D3D8 device");
350 return false;
351 }
352
353 return true;
354 }
355
hook_d3d8(void)356 bool hook_d3d8(void)
357 {
358 HMODULE d3d8_module = get_system_module("d3d8.dll");
359 uint32_t d3d8_size;
360 void *present_addr = nullptr;
361
362 if (!d3d8_module) {
363 return false;
364 }
365
366 d3d8_size = module_size(d3d8_module);
367
368 if (global_hook_info->offsets.d3d8.present < d3d8_size) {
369 present_addr = get_offset_addr(
370 d3d8_module, global_hook_info->offsets.d3d8.present);
371 } else {
372 if (!dummy_window) {
373 return false;
374 }
375
376 if (!manually_get_d3d8_present_addr(d3d8_module,
377 &present_addr)) {
378 hlog("Failed to get D3D8 value");
379 return true;
380 }
381 }
382
383 if (!present_addr) {
384 hlog("Invalid D3D8 value");
385 return true;
386 }
387
388 DetourTransactionBegin();
389
390 RealPresent = (present_t)present_addr;
391 DetourAttach((PVOID *)&RealPresent, hook_present);
392
393 const LONG error = DetourTransactionCommit();
394 const bool success = error == NO_ERROR;
395 if (success) {
396 hlog("Hooked IDirect3DDevice8::Present");
397 hlog("Hooked D3D8");
398 } else {
399 RealPresent = nullptr;
400 hlog("Failed to attach Detours hook: %ld", error);
401 }
402
403 return success;
404 }
405