d3d8-capture.cpp 8.6 KB

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