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(&parameters);
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