d3d8-capture.cpp 8.4 KB

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