1 #include <inttypes.h>
2 #include <obs-module.h>
3 #include <obs-hotkey.h>
4 #include <util/platform.h>
5 #include <util/threading.h>
6 #include <windows.h>
7 #include <dxgi.h>
8 #include <util/sse-intrin.h>
9 #include <util/util_uint64.h>
10 #include <ipc-util/pipe.h>
11 #include "obfuscate.h"
12 #include "inject-library.h"
13 #include "graphics-hook-info.h"
14 #include "graphics-hook-ver.h"
15 #include "window-helpers.h"
16 #include "cursor-capture.h"
17 #include "app-helpers.h"
18 #include "nt-stuff.h"
19
20 #define do_log(level, format, ...) \
21 blog(level, "[game-capture: '%s'] " format, \
22 obs_source_get_name(gc->source), ##__VA_ARGS__)
23
24 #define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__)
25 #define info(format, ...) do_log(LOG_INFO, format, ##__VA_ARGS__)
26 #define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
27
28 /* clang-format off */
29
30 #define SETTING_MODE "capture_mode"
31 #define SETTING_CAPTURE_WINDOW "window"
32 #define SETTING_ACTIVE_WINDOW "active_window"
33 #define SETTING_WINDOW_PRIORITY "priority"
34 #define SETTING_COMPATIBILITY "sli_compatibility"
35 #define SETTING_CURSOR "capture_cursor"
36 #define SETTING_TRANSPARENCY "allow_transparency"
37 #define SETTING_LIMIT_FRAMERATE "limit_framerate"
38 #define SETTING_CAPTURE_OVERLAYS "capture_overlays"
39 #define SETTING_ANTI_CHEAT_HOOK "anti_cheat_hook"
40 #define SETTING_HOOK_RATE "hook_rate"
41
42 /* deprecated */
43 #define SETTING_ANY_FULLSCREEN "capture_any_fullscreen"
44
45 #define SETTING_MODE_ANY "any_fullscreen"
46 #define SETTING_MODE_WINDOW "window"
47 #define SETTING_MODE_HOTKEY "hotkey"
48
49 #define HOTKEY_START "hotkey_start"
50 #define HOTKEY_STOP "hotkey_stop"
51
52 #define TEXT_MODE obs_module_text("Mode")
53 #define TEXT_GAME_CAPTURE obs_module_text("GameCapture")
54 #define TEXT_ANY_FULLSCREEN obs_module_text("GameCapture.AnyFullscreen")
55 #define TEXT_SLI_COMPATIBILITY obs_module_text("SLIFix")
56 #define TEXT_ALLOW_TRANSPARENCY obs_module_text("AllowTransparency")
57 #define TEXT_WINDOW obs_module_text("WindowCapture.Window")
58 #define TEXT_MATCH_PRIORITY obs_module_text("WindowCapture.Priority")
59 #define TEXT_MATCH_TITLE obs_module_text("WindowCapture.Priority.Title")
60 #define TEXT_MATCH_CLASS obs_module_text("WindowCapture.Priority.Class")
61 #define TEXT_MATCH_EXE obs_module_text("WindowCapture.Priority.Exe")
62 #define TEXT_CAPTURE_CURSOR obs_module_text("CaptureCursor")
63 #define TEXT_LIMIT_FRAMERATE obs_module_text("GameCapture.LimitFramerate")
64 #define TEXT_CAPTURE_OVERLAYS obs_module_text("GameCapture.CaptureOverlays")
65 #define TEXT_ANTI_CHEAT_HOOK obs_module_text("GameCapture.AntiCheatHook")
66 #define TEXT_HOOK_RATE obs_module_text("GameCapture.HookRate")
67 #define TEXT_HOOK_RATE_SLOW obs_module_text("GameCapture.HookRate.Slow")
68 #define TEXT_HOOK_RATE_NORMAL obs_module_text("GameCapture.HookRate.Normal")
69 #define TEXT_HOOK_RATE_FAST obs_module_text("GameCapture.HookRate.Fast")
70 #define TEXT_HOOK_RATE_FASTEST obs_module_text("GameCapture.HookRate.Fastest")
71
72 #define TEXT_MODE_ANY TEXT_ANY_FULLSCREEN
73 #define TEXT_MODE_WINDOW obs_module_text("GameCapture.CaptureWindow")
74 #define TEXT_MODE_HOTKEY obs_module_text("GameCapture.UseHotkey")
75
76 #define TEXT_HOTKEY_START obs_module_text("GameCapture.HotkeyStart")
77 #define TEXT_HOTKEY_STOP obs_module_text("GameCapture.HotkeyStop")
78
79 /* clang-format on */
80
81 #define DEFAULT_RETRY_INTERVAL 2.0f
82 #define ERROR_RETRY_INTERVAL 4.0f
83
84 enum capture_mode {
85 CAPTURE_MODE_ANY,
86 CAPTURE_MODE_WINDOW,
87 CAPTURE_MODE_HOTKEY
88 };
89
90 enum hook_rate {
91 HOOK_RATE_SLOW,
92 HOOK_RATE_NORMAL,
93 HOOK_RATE_FAST,
94 HOOK_RATE_FASTEST
95 };
96
97 struct game_capture_config {
98 char *title;
99 char *class;
100 char *executable;
101 enum window_priority priority;
102 enum capture_mode mode;
103 bool cursor;
104 bool force_shmem;
105 bool allow_transparency;
106 bool limit_framerate;
107 bool capture_overlays;
108 bool anticheat_hook;
109 enum hook_rate hook_rate;
110 };
111
112 struct game_capture {
113 obs_source_t *source;
114
115 struct cursor_data cursor_data;
116 HANDLE injector_process;
117 uint32_t cx;
118 uint32_t cy;
119 uint32_t pitch;
120 DWORD process_id;
121 DWORD thread_id;
122 HWND next_window;
123 HWND window;
124 float retry_time;
125 float fps_reset_time;
126 float retry_interval;
127 struct dstr title;
128 struct dstr class;
129 struct dstr executable;
130 enum window_priority priority;
131 obs_hotkey_pair_id hotkey_pair;
132 volatile long hotkey_window;
133 volatile bool deactivate_hook;
134 volatile bool activate_hook_now;
135 bool wait_for_target_startup;
136 bool showing;
137 bool active;
138 bool capturing;
139 bool activate_hook;
140 bool process_is_64bit;
141 bool error_acquiring;
142 bool dwm_capture;
143 bool initial_config;
144 bool convert_16bit;
145 bool is_app;
146 bool cursor_hidden;
147
148 struct game_capture_config config;
149
150 ipc_pipe_server_t pipe;
151 gs_texture_t *texture;
152 gs_texture_t *extra_texture;
153 gs_texrender_t *extra_texrender;
154 bool linear_sample;
155 struct hook_info *global_hook_info;
156 HANDLE keepalive_mutex;
157 HANDLE hook_init;
158 HANDLE hook_restart;
159 HANDLE hook_stop;
160 HANDLE hook_ready;
161 HANDLE hook_exit;
162 HANDLE hook_data_map;
163 HANDLE global_hook_info_map;
164 HANDLE target_process;
165 HANDLE texture_mutexes[2];
166 wchar_t *app_sid;
167 int retrying;
168 float cursor_check_time;
169
170 union {
171 struct {
172 struct shmem_data *shmem_data;
173 uint8_t *texture_buffers[2];
174 };
175
176 struct shtex_data *shtex_data;
177 void *data;
178 };
179
180 void (*copy_texture)(struct game_capture *);
181 };
182
183 struct graphics_offsets offsets32 = {0};
184 struct graphics_offsets offsets64 = {0};
185
use_anticheat(struct game_capture * gc)186 static inline bool use_anticheat(struct game_capture *gc)
187 {
188 return gc->config.anticheat_hook && !gc->is_app;
189 }
190
open_mutex_plus_id(struct game_capture * gc,const wchar_t * name,DWORD id)191 static inline HANDLE open_mutex_plus_id(struct game_capture *gc,
192 const wchar_t *name, DWORD id)
193 {
194 wchar_t new_name[64];
195 _snwprintf(new_name, 64, L"%s%lu", name, id);
196 return gc->is_app ? open_app_mutex(gc->app_sid, new_name)
197 : open_mutex(new_name);
198 }
199
open_mutex_gc(struct game_capture * gc,const wchar_t * name)200 static inline HANDLE open_mutex_gc(struct game_capture *gc, const wchar_t *name)
201 {
202 return open_mutex_plus_id(gc, name, gc->process_id);
203 }
204
open_event_plus_id(struct game_capture * gc,const wchar_t * name,DWORD id)205 static inline HANDLE open_event_plus_id(struct game_capture *gc,
206 const wchar_t *name, DWORD id)
207 {
208 wchar_t new_name[64];
209 _snwprintf(new_name, 64, L"%s%lu", name, id);
210 return gc->is_app ? open_app_event(gc->app_sid, new_name)
211 : open_event(new_name);
212 }
213
open_event_gc(struct game_capture * gc,const wchar_t * name)214 static inline HANDLE open_event_gc(struct game_capture *gc, const wchar_t *name)
215 {
216 return open_event_plus_id(gc, name, gc->process_id);
217 }
218
open_map_plus_id(struct game_capture * gc,const wchar_t * name,DWORD id)219 static inline HANDLE open_map_plus_id(struct game_capture *gc,
220 const wchar_t *name, DWORD id)
221 {
222 wchar_t new_name[64];
223 swprintf(new_name, 64, L"%s%lu", name, id);
224
225 debug("map id: %S", new_name);
226
227 return gc->is_app ? open_app_map(gc->app_sid, new_name)
228 : OpenFileMappingW(GC_MAPPING_FLAGS, false, new_name);
229 }
230
open_hook_info(struct game_capture * gc)231 static inline HANDLE open_hook_info(struct game_capture *gc)
232 {
233 return open_map_plus_id(gc, SHMEM_HOOK_INFO, gc->process_id);
234 }
235
convert_format(uint32_t format)236 static inline enum gs_color_format convert_format(uint32_t format)
237 {
238 switch (format) {
239 case DXGI_FORMAT_R8G8B8A8_UNORM:
240 return GS_RGBA;
241 case DXGI_FORMAT_B8G8R8X8_UNORM:
242 return GS_BGRX;
243 case DXGI_FORMAT_B8G8R8A8_UNORM:
244 return GS_BGRA;
245 case DXGI_FORMAT_R10G10B10A2_UNORM:
246 return GS_R10G10B10A2;
247 case DXGI_FORMAT_R16G16B16A16_UNORM:
248 return GS_RGBA16;
249 case DXGI_FORMAT_R16G16B16A16_FLOAT:
250 return GS_RGBA16F;
251 case DXGI_FORMAT_R32G32B32A32_FLOAT:
252 return GS_RGBA32F;
253 }
254
255 return GS_UNKNOWN;
256 }
257
close_handle(HANDLE * p_handle)258 static void close_handle(HANDLE *p_handle)
259 {
260 HANDLE handle = *p_handle;
261 if (handle) {
262 if (handle != INVALID_HANDLE_VALUE)
263 CloseHandle(handle);
264 *p_handle = NULL;
265 }
266 }
267
kernel32(void)268 static inline HMODULE kernel32(void)
269 {
270 static HMODULE kernel32_handle = NULL;
271 if (!kernel32_handle)
272 kernel32_handle = GetModuleHandleW(L"kernel32");
273 return kernel32_handle;
274 }
275
open_process(DWORD desired_access,bool inherit_handle,DWORD process_id)276 static inline HANDLE open_process(DWORD desired_access, bool inherit_handle,
277 DWORD process_id)
278 {
279 typedef HANDLE(WINAPI * PFN_OpenProcess)(DWORD, BOOL, DWORD);
280 PFN_OpenProcess open_process_proc = NULL;
281 if (!open_process_proc)
282 open_process_proc = (PFN_OpenProcess)get_obfuscated_func(
283 kernel32(), "NuagUykjcxr", 0x1B694B59451ULL);
284
285 return open_process_proc(desired_access, inherit_handle, process_id);
286 }
287
hook_rate_to_float(enum hook_rate rate)288 static inline float hook_rate_to_float(enum hook_rate rate)
289 {
290 switch (rate) {
291 case HOOK_RATE_SLOW:
292 return 2.0f;
293 case HOOK_RATE_FAST:
294 return 0.5f;
295 case HOOK_RATE_FASTEST:
296 return 0.1f;
297 case HOOK_RATE_NORMAL:
298 /* FALLTHROUGH */
299 default:
300 return 1.0f;
301 }
302 }
303
stop_capture(struct game_capture * gc)304 static void stop_capture(struct game_capture *gc)
305 {
306 ipc_pipe_server_free(&gc->pipe);
307
308 if (gc->hook_stop) {
309 SetEvent(gc->hook_stop);
310 }
311 if (gc->global_hook_info) {
312 UnmapViewOfFile(gc->global_hook_info);
313 gc->global_hook_info = NULL;
314 }
315 if (gc->data) {
316 UnmapViewOfFile(gc->data);
317 gc->data = NULL;
318 }
319
320 if (gc->app_sid) {
321 LocalFree(gc->app_sid);
322 gc->app_sid = NULL;
323 }
324
325 close_handle(&gc->hook_restart);
326 close_handle(&gc->hook_stop);
327 close_handle(&gc->hook_ready);
328 close_handle(&gc->hook_exit);
329 close_handle(&gc->hook_init);
330 close_handle(&gc->hook_data_map);
331 close_handle(&gc->keepalive_mutex);
332 close_handle(&gc->global_hook_info_map);
333 close_handle(&gc->target_process);
334 close_handle(&gc->texture_mutexes[0]);
335 close_handle(&gc->texture_mutexes[1]);
336
337 obs_enter_graphics();
338 gs_texrender_destroy(gc->extra_texrender);
339 gc->extra_texrender = NULL;
340 gs_texture_destroy(gc->extra_texture);
341 gc->extra_texture = NULL;
342 gs_texture_destroy(gc->texture);
343 gc->texture = NULL;
344 obs_leave_graphics();
345
346 if (gc->active)
347 info("capture stopped");
348
349 gc->copy_texture = NULL;
350 gc->wait_for_target_startup = false;
351 gc->active = false;
352 gc->capturing = false;
353
354 if (gc->retrying)
355 gc->retrying--;
356 }
357
free_config(struct game_capture_config * config)358 static inline void free_config(struct game_capture_config *config)
359 {
360 bfree(config->title);
361 bfree(config->class);
362 bfree(config->executable);
363 memset(config, 0, sizeof(*config));
364 }
365
game_capture_destroy(void * data)366 static void game_capture_destroy(void *data)
367 {
368 struct game_capture *gc = data;
369 stop_capture(gc);
370
371 if (gc->hotkey_pair)
372 obs_hotkey_pair_unregister(gc->hotkey_pair);
373
374 obs_enter_graphics();
375 cursor_data_free(&gc->cursor_data);
376 obs_leave_graphics();
377
378 dstr_free(&gc->title);
379 dstr_free(&gc->class);
380 dstr_free(&gc->executable);
381 free_config(&gc->config);
382 bfree(gc);
383 }
384
using_older_non_mode_format(obs_data_t * settings)385 static inline bool using_older_non_mode_format(obs_data_t *settings)
386 {
387 return obs_data_has_user_value(settings, SETTING_ANY_FULLSCREEN) &&
388 !obs_data_has_user_value(settings, SETTING_MODE);
389 }
390
get_config(struct game_capture_config * cfg,obs_data_t * settings,const char * window)391 static inline void get_config(struct game_capture_config *cfg,
392 obs_data_t *settings, const char *window)
393 {
394 const char *mode_str = NULL;
395
396 build_window_strings(window, &cfg->class, &cfg->title,
397 &cfg->executable);
398
399 if (using_older_non_mode_format(settings)) {
400 bool any = obs_data_get_bool(settings, SETTING_ANY_FULLSCREEN);
401 mode_str = any ? SETTING_MODE_ANY : SETTING_MODE_WINDOW;
402 } else {
403 mode_str = obs_data_get_string(settings, SETTING_MODE);
404 }
405
406 if (mode_str && strcmp(mode_str, SETTING_MODE_WINDOW) == 0)
407 cfg->mode = CAPTURE_MODE_WINDOW;
408 else if (mode_str && strcmp(mode_str, SETTING_MODE_HOTKEY) == 0)
409 cfg->mode = CAPTURE_MODE_HOTKEY;
410 else
411 cfg->mode = CAPTURE_MODE_ANY;
412
413 cfg->priority = (enum window_priority)obs_data_get_int(
414 settings, SETTING_WINDOW_PRIORITY);
415 cfg->force_shmem = obs_data_get_bool(settings, SETTING_COMPATIBILITY);
416 cfg->cursor = obs_data_get_bool(settings, SETTING_CURSOR);
417 cfg->allow_transparency =
418 obs_data_get_bool(settings, SETTING_TRANSPARENCY);
419 cfg->limit_framerate =
420 obs_data_get_bool(settings, SETTING_LIMIT_FRAMERATE);
421 cfg->capture_overlays =
422 obs_data_get_bool(settings, SETTING_CAPTURE_OVERLAYS);
423 cfg->anticheat_hook =
424 obs_data_get_bool(settings, SETTING_ANTI_CHEAT_HOOK);
425 cfg->hook_rate =
426 (enum hook_rate)obs_data_get_int(settings, SETTING_HOOK_RATE);
427 }
428
s_cmp(const char * str1,const char * str2)429 static inline int s_cmp(const char *str1, const char *str2)
430 {
431 if (!str1 || !str2)
432 return -1;
433
434 return strcmp(str1, str2);
435 }
436
capture_needs_reset(struct game_capture_config * cfg1,struct game_capture_config * cfg2)437 static inline bool capture_needs_reset(struct game_capture_config *cfg1,
438 struct game_capture_config *cfg2)
439 {
440 if (cfg1->mode != cfg2->mode) {
441 return true;
442
443 } else if (cfg1->mode == CAPTURE_MODE_WINDOW &&
444 (s_cmp(cfg1->class, cfg2->class) != 0 ||
445 s_cmp(cfg1->title, cfg2->title) != 0 ||
446 s_cmp(cfg1->executable, cfg2->executable) != 0 ||
447 cfg1->priority != cfg2->priority)) {
448 return true;
449
450 } else if (cfg1->force_shmem != cfg2->force_shmem) {
451 return true;
452
453 } else if (cfg1->limit_framerate != cfg2->limit_framerate) {
454 return true;
455
456 } else if (cfg1->capture_overlays != cfg2->capture_overlays) {
457 return true;
458 }
459
460 return false;
461 }
462
hotkey_start(void * data,obs_hotkey_pair_id id,obs_hotkey_t * hotkey,bool pressed)463 static bool hotkey_start(void *data, obs_hotkey_pair_id id,
464 obs_hotkey_t *hotkey, bool pressed)
465 {
466 UNUSED_PARAMETER(id);
467 UNUSED_PARAMETER(hotkey);
468
469 struct game_capture *gc = data;
470
471 if (pressed && gc->config.mode == CAPTURE_MODE_HOTKEY) {
472 info("Activate hotkey pressed");
473 os_atomic_set_long(&gc->hotkey_window,
474 (long)(uintptr_t)GetForegroundWindow());
475 os_atomic_set_bool(&gc->deactivate_hook, true);
476 os_atomic_set_bool(&gc->activate_hook_now, true);
477 }
478
479 return true;
480 }
481
hotkey_stop(void * data,obs_hotkey_pair_id id,obs_hotkey_t * hotkey,bool pressed)482 static bool hotkey_stop(void *data, obs_hotkey_pair_id id, obs_hotkey_t *hotkey,
483 bool pressed)
484 {
485 UNUSED_PARAMETER(id);
486 UNUSED_PARAMETER(hotkey);
487
488 struct game_capture *gc = data;
489
490 if (pressed && gc->config.mode == CAPTURE_MODE_HOTKEY) {
491 info("Deactivate hotkey pressed");
492 os_atomic_set_bool(&gc->deactivate_hook, true);
493 }
494
495 return true;
496 }
497
game_capture_update(void * data,obs_data_t * settings)498 static void game_capture_update(void *data, obs_data_t *settings)
499 {
500 struct game_capture *gc = data;
501 struct game_capture_config cfg;
502 bool reset_capture = false;
503 const char *window =
504 obs_data_get_string(settings, SETTING_CAPTURE_WINDOW);
505
506 get_config(&cfg, settings, window);
507 reset_capture = capture_needs_reset(&cfg, &gc->config);
508
509 gc->error_acquiring = false;
510
511 if (cfg.mode == CAPTURE_MODE_HOTKEY &&
512 gc->config.mode != CAPTURE_MODE_HOTKEY) {
513 gc->activate_hook = false;
514 } else {
515 gc->activate_hook = !!window && !!*window;
516 }
517
518 free_config(&gc->config);
519 gc->config = cfg;
520 gc->retry_interval = DEFAULT_RETRY_INTERVAL *
521 hook_rate_to_float(gc->config.hook_rate);
522 gc->wait_for_target_startup = false;
523
524 dstr_free(&gc->title);
525 dstr_free(&gc->class);
526 dstr_free(&gc->executable);
527
528 if (cfg.mode == CAPTURE_MODE_WINDOW) {
529 dstr_copy(&gc->title, gc->config.title);
530 dstr_copy(&gc->class, gc->config.class);
531 dstr_copy(&gc->executable, gc->config.executable);
532 gc->priority = gc->config.priority;
533 }
534
535 if (!gc->initial_config) {
536 if (reset_capture) {
537 stop_capture(gc);
538 }
539 } else {
540 gc->initial_config = false;
541 }
542 }
543
544 extern void wait_for_hook_initialization(void);
545
game_capture_create(obs_data_t * settings,obs_source_t * source)546 static void *game_capture_create(obs_data_t *settings, obs_source_t *source)
547 {
548 struct game_capture *gc = bzalloc(sizeof(*gc));
549
550 wait_for_hook_initialization();
551
552 gc->source = source;
553 gc->initial_config = true;
554 gc->retry_interval = DEFAULT_RETRY_INTERVAL *
555 hook_rate_to_float(gc->config.hook_rate);
556 gc->hotkey_pair = obs_hotkey_pair_register_source(
557 gc->source, HOTKEY_START, TEXT_HOTKEY_START, HOTKEY_STOP,
558 TEXT_HOTKEY_STOP, hotkey_start, hotkey_stop, gc, gc);
559
560 game_capture_update(gc, settings);
561 return gc;
562 }
563
564 #define STOP_BEING_BAD \
565 " This is most likely due to security software. Please make sure " \
566 "that the OBS installation folder is excluded/ignored in the " \
567 "settings of the security software you are using."
568
check_file_integrity(struct game_capture * gc,const char * file,const char * name)569 static bool check_file_integrity(struct game_capture *gc, const char *file,
570 const char *name)
571 {
572 DWORD error;
573 HANDLE handle;
574 wchar_t *w_file = NULL;
575
576 if (!file || !*file) {
577 warn("Game capture %s not found." STOP_BEING_BAD, name);
578 return false;
579 }
580
581 if (!os_utf8_to_wcs_ptr(file, 0, &w_file)) {
582 warn("Could not convert file name to wide string");
583 return false;
584 }
585
586 handle = CreateFileW(w_file, GENERIC_READ | GENERIC_EXECUTE,
587 FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
588
589 bfree(w_file);
590
591 if (handle != INVALID_HANDLE_VALUE) {
592 CloseHandle(handle);
593 return true;
594 }
595
596 error = GetLastError();
597 if (error == ERROR_FILE_NOT_FOUND) {
598 warn("Game capture file '%s' not found." STOP_BEING_BAD, file);
599 } else if (error == ERROR_ACCESS_DENIED) {
600 warn("Game capture file '%s' could not be loaded." STOP_BEING_BAD,
601 file);
602 } else {
603 warn("Game capture file '%s' could not be loaded: %lu." STOP_BEING_BAD,
604 file, error);
605 }
606
607 return false;
608 }
609
is_64bit_windows(void)610 static inline bool is_64bit_windows(void)
611 {
612 #ifdef _WIN64
613 return true;
614 #else
615 BOOL x86 = false;
616 bool success = !!IsWow64Process(GetCurrentProcess(), &x86);
617 return success && !!x86;
618 #endif
619 }
620
is_64bit_process(HANDLE process)621 static inline bool is_64bit_process(HANDLE process)
622 {
623 BOOL x86 = true;
624 if (is_64bit_windows()) {
625 bool success = !!IsWow64Process(process, &x86);
626 if (!success) {
627 return false;
628 }
629 }
630
631 return !x86;
632 }
633
open_target_process(struct game_capture * gc)634 static inline bool open_target_process(struct game_capture *gc)
635 {
636 gc->target_process = open_process(
637 PROCESS_QUERY_INFORMATION | SYNCHRONIZE, false, gc->process_id);
638 if (!gc->target_process) {
639 warn("could not open process: %s", gc->config.executable);
640 return false;
641 }
642
643 gc->process_is_64bit = is_64bit_process(gc->target_process);
644 gc->is_app = is_app(gc->target_process);
645 if (gc->is_app) {
646 gc->app_sid = get_app_sid(gc->target_process);
647 }
648 return true;
649 }
650
init_keepalive(struct game_capture * gc)651 static inline bool init_keepalive(struct game_capture *gc)
652 {
653 wchar_t new_name[64];
654 swprintf(new_name, 64, WINDOW_HOOK_KEEPALIVE L"%lu", gc->process_id);
655
656 gc->keepalive_mutex = gc->is_app
657 ? create_app_mutex(gc->app_sid, new_name)
658 : CreateMutexW(NULL, false, new_name);
659 if (!gc->keepalive_mutex) {
660 warn("Failed to create keepalive mutex: %lu", GetLastError());
661 return false;
662 }
663
664 return true;
665 }
666
init_texture_mutexes(struct game_capture * gc)667 static inline bool init_texture_mutexes(struct game_capture *gc)
668 {
669 gc->texture_mutexes[0] = open_mutex_gc(gc, MUTEX_TEXTURE1);
670 gc->texture_mutexes[1] = open_mutex_gc(gc, MUTEX_TEXTURE2);
671
672 if (!gc->texture_mutexes[0] || !gc->texture_mutexes[1]) {
673 DWORD error = GetLastError();
674 if (error == 2) {
675 if (!gc->retrying) {
676 gc->retrying = 2;
677 info("hook not loaded yet, retrying..");
678 }
679 } else {
680 warn("failed to open texture mutexes: %lu",
681 GetLastError());
682 }
683 return false;
684 }
685
686 return true;
687 }
688
689 /* if there's already a hook in the process, then signal and start */
attempt_existing_hook(struct game_capture * gc)690 static inline bool attempt_existing_hook(struct game_capture *gc)
691 {
692 gc->hook_restart = open_event_gc(gc, EVENT_CAPTURE_RESTART);
693 if (gc->hook_restart) {
694 debug("existing hook found, signaling process: %s",
695 gc->config.executable);
696 SetEvent(gc->hook_restart);
697 return true;
698 }
699
700 return false;
701 }
702
reset_frame_interval(struct game_capture * gc)703 static inline void reset_frame_interval(struct game_capture *gc)
704 {
705 struct obs_video_info ovi;
706 uint64_t interval = 0;
707
708 if (obs_get_video_info(&ovi)) {
709 interval =
710 util_mul_div64(ovi.fps_den, 1000000000ULL, ovi.fps_num);
711
712 /* Always limit capture framerate to some extent. If a game
713 * running at 900 FPS is being captured without some sort of
714 * limited capture interval, it will dramatically reduce
715 * performance. */
716 if (!gc->config.limit_framerate)
717 interval /= 2;
718 }
719
720 gc->global_hook_info->frame_interval = interval;
721 }
722
init_hook_info(struct game_capture * gc)723 static inline bool init_hook_info(struct game_capture *gc)
724 {
725 gc->global_hook_info_map = open_hook_info(gc);
726 if (!gc->global_hook_info_map) {
727 warn("init_hook_info: get_hook_info failed: %lu",
728 GetLastError());
729 return false;
730 }
731
732 gc->global_hook_info = MapViewOfFile(gc->global_hook_info_map,
733 FILE_MAP_ALL_ACCESS, 0, 0,
734 sizeof(*gc->global_hook_info));
735 if (!gc->global_hook_info) {
736 warn("init_hook_info: failed to map data view: %lu",
737 GetLastError());
738 return false;
739 }
740
741 if (gc->config.force_shmem) {
742 warn("init_hook_info: user is forcing shared memory "
743 "(multi-adapter compatibility mode)");
744 }
745
746 gc->global_hook_info->offsets = gc->process_is_64bit ? offsets64
747 : offsets32;
748 gc->global_hook_info->capture_overlay = gc->config.capture_overlays;
749 gc->global_hook_info->force_shmem = gc->config.force_shmem;
750 gc->global_hook_info->UNUSED_use_scale = false;
751 gc->global_hook_info->allow_srgb_alias = true;
752 reset_frame_interval(gc);
753
754 obs_enter_graphics();
755 if (!gs_shared_texture_available()) {
756 warn("init_hook_info: shared texture capture unavailable");
757 gc->global_hook_info->force_shmem = true;
758 }
759 obs_leave_graphics();
760
761 return true;
762 }
763
pipe_log(void * param,uint8_t * data,size_t size)764 static void pipe_log(void *param, uint8_t *data, size_t size)
765 {
766 struct game_capture *gc = param;
767 if (data && size)
768 info("%s", data);
769 }
770
init_pipe(struct game_capture * gc)771 static inline bool init_pipe(struct game_capture *gc)
772 {
773 char name[64];
774 sprintf(name, "%s%lu", PIPE_NAME, gc->process_id);
775
776 if (!ipc_pipe_server_start(&gc->pipe, name, pipe_log, gc)) {
777 warn("init_pipe: failed to start pipe");
778 return false;
779 }
780
781 return true;
782 }
783
inject_library(HANDLE process,const wchar_t * dll)784 static inline int inject_library(HANDLE process, const wchar_t *dll)
785 {
786 return inject_library_obf(process, dll, "D|hkqkW`kl{k\\osofj",
787 0xa178ef3655e5ade7, "[uawaRzbhh{tIdkj~~",
788 0x561478dbd824387c, "[fr}pboIe`dlN}",
789 0x395bfbc9833590fd, "\\`zs}gmOzhhBq",
790 0x12897dd89168789a, "GbfkDaezbp~X",
791 0x76aff7238788f7db);
792 }
793
hook_direct(struct game_capture * gc,const char * hook_path_rel)794 static inline bool hook_direct(struct game_capture *gc,
795 const char *hook_path_rel)
796 {
797 wchar_t hook_path_abs_w[MAX_PATH];
798 wchar_t *hook_path_rel_w;
799 wchar_t *path_ret;
800 HANDLE process;
801 int ret;
802
803 os_utf8_to_wcs_ptr(hook_path_rel, 0, &hook_path_rel_w);
804 if (!hook_path_rel_w) {
805 warn("hook_direct: could not convert string");
806 return false;
807 }
808
809 path_ret = _wfullpath(hook_path_abs_w, hook_path_rel_w, MAX_PATH);
810 bfree(hook_path_rel_w);
811
812 if (path_ret == NULL) {
813 warn("hook_direct: could not make absolute path");
814 return false;
815 }
816
817 process = open_process(PROCESS_ALL_ACCESS, false, gc->process_id);
818 if (!process) {
819 warn("hook_direct: could not open process: %s (%lu)",
820 gc->config.executable, GetLastError());
821 return false;
822 }
823
824 ret = inject_library(process, hook_path_abs_w);
825 CloseHandle(process);
826
827 if (ret != 0) {
828 warn("hook_direct: inject failed: %d", ret);
829 return false;
830 }
831
832 return true;
833 }
834
create_inject_process(struct game_capture * gc,const char * inject_path,const char * hook_dll)835 static inline bool create_inject_process(struct game_capture *gc,
836 const char *inject_path,
837 const char *hook_dll)
838 {
839 wchar_t *command_line_w = malloc(4096 * sizeof(wchar_t));
840 wchar_t *inject_path_w;
841 wchar_t *hook_dll_w;
842 bool anti_cheat = use_anticheat(gc);
843 PROCESS_INFORMATION pi = {0};
844 STARTUPINFO si = {0};
845 bool success = false;
846
847 os_utf8_to_wcs_ptr(inject_path, 0, &inject_path_w);
848 os_utf8_to_wcs_ptr(hook_dll, 0, &hook_dll_w);
849
850 si.cb = sizeof(si);
851
852 swprintf(command_line_w, 4096, L"\"%s\" \"%s\" %lu %lu", inject_path_w,
853 hook_dll_w, (unsigned long)anti_cheat,
854 anti_cheat ? gc->thread_id : gc->process_id);
855
856 success = !!CreateProcessW(inject_path_w, command_line_w, NULL, NULL,
857 false, CREATE_NO_WINDOW, NULL, NULL, &si,
858 &pi);
859 if (success) {
860 CloseHandle(pi.hThread);
861 gc->injector_process = pi.hProcess;
862 } else {
863 warn("Failed to create inject helper process: %lu",
864 GetLastError());
865 }
866
867 free(command_line_w);
868 bfree(inject_path_w);
869 bfree(hook_dll_w);
870 return success;
871 }
872
873 extern char *get_hook_path(bool b64);
874
inject_hook(struct game_capture * gc)875 static inline bool inject_hook(struct game_capture *gc)
876 {
877 bool matching_architecture;
878 bool success = false;
879 char *inject_path;
880 char *hook_path;
881
882 if (gc->process_is_64bit) {
883 inject_path = obs_module_file("inject-helper64.exe");
884 } else {
885 inject_path = obs_module_file("inject-helper32.exe");
886 }
887
888 hook_path = get_hook_path(gc->process_is_64bit);
889
890 if (!check_file_integrity(gc, inject_path, "inject helper")) {
891 goto cleanup;
892 }
893 if (!check_file_integrity(gc, hook_path, "graphics hook")) {
894 goto cleanup;
895 }
896
897 #ifdef _WIN64
898 matching_architecture = gc->process_is_64bit;
899 #else
900 matching_architecture = !gc->process_is_64bit;
901 #endif
902
903 if (matching_architecture && !use_anticheat(gc)) {
904 info("using direct hook");
905 success = hook_direct(gc, hook_path);
906 } else {
907 info("using helper (%s hook)",
908 use_anticheat(gc) ? "compatibility" : "direct");
909 success = create_inject_process(gc, inject_path, hook_path);
910 }
911
912 cleanup:
913 bfree(inject_path);
914 bfree(hook_path);
915 return success;
916 }
917
918 static const char *blacklisted_exes[] = {
919 "explorer",
920 "steam",
921 "battle.net",
922 "galaxyclient",
923 "skype",
924 "uplay",
925 "origin",
926 "devenv",
927 "taskmgr",
928 "chrome",
929 "discord",
930 "firefox",
931 "systemsettings",
932 "applicationframehost",
933 "cmd",
934 "shellexperiencehost",
935 "winstore.app",
936 "searchui",
937 "lockapp",
938 "windowsinternal.composableshell.experiences.textinput.inputapp",
939 NULL,
940 };
941
is_blacklisted_exe(const char * exe)942 static bool is_blacklisted_exe(const char *exe)
943 {
944 char cur_exe[MAX_PATH];
945
946 if (!exe)
947 return false;
948
949 for (const char **vals = blacklisted_exes; *vals; vals++) {
950 strcpy(cur_exe, *vals);
951 strcat(cur_exe, ".exe");
952
953 if (strcmpi(cur_exe, exe) == 0)
954 return true;
955 }
956
957 return false;
958 }
959
target_suspended(struct game_capture * gc)960 static bool target_suspended(struct game_capture *gc)
961 {
962 return thread_is_suspended(gc->process_id, gc->thread_id);
963 }
964
965 static bool init_events(struct game_capture *gc);
966
init_hook(struct game_capture * gc)967 static bool init_hook(struct game_capture *gc)
968 {
969 struct dstr exe = {0};
970 bool blacklisted_process = false;
971
972 if (gc->config.mode == CAPTURE_MODE_ANY) {
973 if (get_window_exe(&exe, gc->next_window)) {
974 info("attempting to hook fullscreen process: %s",
975 exe.array);
976 }
977 } else {
978 if (get_window_exe(&exe, gc->next_window)) {
979 info("attempting to hook process: %s", exe.array);
980 }
981 }
982
983 blacklisted_process = is_blacklisted_exe(exe.array);
984 if (blacklisted_process)
985 info("cannot capture %s due to being blacklisted", exe.array);
986 dstr_free(&exe);
987
988 if (blacklisted_process) {
989 return false;
990 }
991 if (target_suspended(gc)) {
992 return false;
993 }
994 if (!open_target_process(gc)) {
995 return false;
996 }
997 if (!init_keepalive(gc)) {
998 return false;
999 }
1000 if (!init_pipe(gc)) {
1001 return false;
1002 }
1003 if (!attempt_existing_hook(gc)) {
1004 if (!inject_hook(gc)) {
1005 return false;
1006 }
1007 }
1008 if (!init_texture_mutexes(gc)) {
1009 return false;
1010 }
1011 if (!init_hook_info(gc)) {
1012 return false;
1013 }
1014 if (!init_events(gc)) {
1015 return false;
1016 }
1017
1018 SetEvent(gc->hook_init);
1019
1020 gc->window = gc->next_window;
1021 gc->next_window = NULL;
1022 gc->active = true;
1023 gc->retrying = 0;
1024 return true;
1025 }
1026
setup_window(struct game_capture * gc,HWND window)1027 static void setup_window(struct game_capture *gc, HWND window)
1028 {
1029 HANDLE hook_restart;
1030 HANDLE process;
1031
1032 GetWindowThreadProcessId(window, &gc->process_id);
1033 if (gc->process_id) {
1034 process = open_process(PROCESS_QUERY_INFORMATION, false,
1035 gc->process_id);
1036 if (process) {
1037 gc->is_app = is_app(process);
1038 if (gc->is_app) {
1039 gc->app_sid = get_app_sid(process);
1040 }
1041 CloseHandle(process);
1042 }
1043 }
1044
1045 /* do not wait if we're re-hooking a process */
1046 hook_restart = open_event_gc(gc, EVENT_CAPTURE_RESTART);
1047 if (hook_restart) {
1048 gc->wait_for_target_startup = false;
1049 CloseHandle(hook_restart);
1050 }
1051
1052 /* otherwise if it's an unhooked process, always wait a bit for the
1053 * target process to start up before starting the hook process;
1054 * sometimes they have important modules to load first or other hooks
1055 * (such as steam) need a little bit of time to load. ultimately this
1056 * helps prevent crashes */
1057 if (gc->wait_for_target_startup) {
1058 gc->retry_interval =
1059 3.0f * hook_rate_to_float(gc->config.hook_rate);
1060 gc->wait_for_target_startup = false;
1061 } else {
1062 gc->next_window = window;
1063 }
1064 }
1065
get_fullscreen_window(struct game_capture * gc)1066 static void get_fullscreen_window(struct game_capture *gc)
1067 {
1068 HWND window = GetForegroundWindow();
1069 MONITORINFO mi = {0};
1070 HMONITOR monitor;
1071 DWORD styles;
1072 RECT rect;
1073
1074 gc->next_window = NULL;
1075
1076 if (!window) {
1077 return;
1078 }
1079 if (!GetWindowRect(window, &rect)) {
1080 return;
1081 }
1082
1083 /* ignore regular maximized windows */
1084 styles = (DWORD)GetWindowLongPtr(window, GWL_STYLE);
1085 if ((styles & WS_MAXIMIZE) != 0 && (styles & WS_BORDER) != 0) {
1086 return;
1087 }
1088
1089 monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
1090 if (!monitor) {
1091 return;
1092 }
1093
1094 mi.cbSize = sizeof(mi);
1095 if (!GetMonitorInfo(monitor, &mi)) {
1096 return;
1097 }
1098
1099 if (rect.left == mi.rcMonitor.left &&
1100 rect.right == mi.rcMonitor.right &&
1101 rect.bottom == mi.rcMonitor.bottom &&
1102 rect.top == mi.rcMonitor.top) {
1103 setup_window(gc, window);
1104 } else {
1105 gc->wait_for_target_startup = true;
1106 }
1107 }
1108
get_selected_window(struct game_capture * gc)1109 static void get_selected_window(struct game_capture *gc)
1110 {
1111 HWND window;
1112
1113 if (dstr_cmpi(&gc->class, "dwm") == 0) {
1114 wchar_t class_w[512];
1115 os_utf8_to_wcs(gc->class.array, 0, class_w, 512);
1116 window = FindWindowW(class_w, NULL);
1117 } else {
1118 window = find_window(INCLUDE_MINIMIZED, gc->priority,
1119 gc->class.array, gc->title.array,
1120 gc->executable.array);
1121 }
1122
1123 if (window) {
1124 setup_window(gc, window);
1125 } else {
1126 gc->wait_for_target_startup = true;
1127 }
1128 }
1129
try_hook(struct game_capture * gc)1130 static void try_hook(struct game_capture *gc)
1131 {
1132 if (gc->config.mode == CAPTURE_MODE_ANY) {
1133 get_fullscreen_window(gc);
1134 } else {
1135 get_selected_window(gc);
1136 }
1137
1138 if (gc->next_window) {
1139 gc->thread_id = GetWindowThreadProcessId(gc->next_window,
1140 &gc->process_id);
1141
1142 // Make sure we never try to hook ourselves (projector)
1143 if (gc->process_id == GetCurrentProcessId())
1144 return;
1145
1146 if (!gc->thread_id && gc->process_id)
1147 return;
1148 if (!gc->process_id) {
1149 warn("error acquiring, failed to get window "
1150 "thread/process ids: %lu",
1151 GetLastError());
1152 gc->error_acquiring = true;
1153 return;
1154 }
1155
1156 if (!init_hook(gc)) {
1157 stop_capture(gc);
1158 }
1159 } else {
1160 gc->active = false;
1161 }
1162 }
1163
init_events(struct game_capture * gc)1164 static inline bool init_events(struct game_capture *gc)
1165 {
1166 if (!gc->hook_restart) {
1167 gc->hook_restart = open_event_gc(gc, EVENT_CAPTURE_RESTART);
1168 if (!gc->hook_restart) {
1169 warn("init_events: failed to get hook_restart "
1170 "event: %lu",
1171 GetLastError());
1172 return false;
1173 }
1174 }
1175
1176 if (!gc->hook_stop) {
1177 gc->hook_stop = open_event_gc(gc, EVENT_CAPTURE_STOP);
1178 if (!gc->hook_stop) {
1179 warn("init_events: failed to get hook_stop event: %lu",
1180 GetLastError());
1181 return false;
1182 }
1183 }
1184
1185 if (!gc->hook_init) {
1186 gc->hook_init = open_event_gc(gc, EVENT_HOOK_INIT);
1187 if (!gc->hook_init) {
1188 warn("init_events: failed to get hook_init event: %lu",
1189 GetLastError());
1190 return false;
1191 }
1192 }
1193
1194 if (!gc->hook_ready) {
1195 gc->hook_ready = open_event_gc(gc, EVENT_HOOK_READY);
1196 if (!gc->hook_ready) {
1197 warn("init_events: failed to get hook_ready event: %lu",
1198 GetLastError());
1199 return false;
1200 }
1201 }
1202
1203 if (!gc->hook_exit) {
1204 gc->hook_exit = open_event_gc(gc, EVENT_HOOK_EXIT);
1205 if (!gc->hook_exit) {
1206 warn("init_events: failed to get hook_exit event: %lu",
1207 GetLastError());
1208 return false;
1209 }
1210 }
1211
1212 return true;
1213 }
1214
1215 enum capture_result { CAPTURE_FAIL, CAPTURE_RETRY, CAPTURE_SUCCESS };
1216
init_data_map(struct game_capture * gc,HWND window)1217 static inline bool init_data_map(struct game_capture *gc, HWND window)
1218 {
1219 wchar_t name[64];
1220 swprintf(name, 64, SHMEM_TEXTURE "_%" PRIu64 "_",
1221 (uint64_t)(uintptr_t)window);
1222
1223 gc->hook_data_map =
1224 open_map_plus_id(gc, name, gc->global_hook_info->map_id);
1225 return !!gc->hook_data_map;
1226 }
1227
init_capture_data(struct game_capture * gc)1228 static inline enum capture_result init_capture_data(struct game_capture *gc)
1229 {
1230 gc->cx = gc->global_hook_info->cx;
1231 gc->cy = gc->global_hook_info->cy;
1232 gc->pitch = gc->global_hook_info->pitch;
1233
1234 if (gc->data) {
1235 UnmapViewOfFile(gc->data);
1236 gc->data = NULL;
1237 }
1238
1239 CloseHandle(gc->hook_data_map);
1240
1241 DWORD error = 0;
1242 if (!init_data_map(gc, gc->window)) {
1243 HWND retry_hwnd = (HWND)(uintptr_t)gc->global_hook_info->window;
1244 error = GetLastError();
1245
1246 /* if there's an error, just override. some windows don't play
1247 * nice. */
1248 if (init_data_map(gc, retry_hwnd)) {
1249 error = 0;
1250 }
1251 }
1252
1253 if (!gc->hook_data_map) {
1254 if (error == 2) {
1255 return CAPTURE_RETRY;
1256 } else {
1257 warn("init_capture_data: failed to open file "
1258 "mapping: %lu",
1259 error);
1260 }
1261 return CAPTURE_FAIL;
1262 }
1263
1264 gc->data = MapViewOfFile(gc->hook_data_map, FILE_MAP_ALL_ACCESS, 0, 0,
1265 gc->global_hook_info->map_size);
1266 if (!gc->data) {
1267 warn("init_capture_data: failed to map data view: %lu",
1268 GetLastError());
1269 return CAPTURE_FAIL;
1270 }
1271
1272 return CAPTURE_SUCCESS;
1273 }
1274
1275 #define PIXEL_16BIT_SIZE 2
1276 #define PIXEL_32BIT_SIZE 4
1277
convert_5_to_8bit(uint16_t val)1278 static inline uint32_t convert_5_to_8bit(uint16_t val)
1279 {
1280 return (uint32_t)((double)(val & 0x1F) * (255.0 / 31.0));
1281 }
1282
convert_6_to_8bit(uint16_t val)1283 static inline uint32_t convert_6_to_8bit(uint16_t val)
1284 {
1285 return (uint32_t)((double)(val & 0x3F) * (255.0 / 63.0));
1286 }
1287
copy_b5g6r5_tex(struct game_capture * gc,int cur_texture,uint8_t * data,uint32_t pitch)1288 static void copy_b5g6r5_tex(struct game_capture *gc, int cur_texture,
1289 uint8_t *data, uint32_t pitch)
1290 {
1291 uint8_t *input = gc->texture_buffers[cur_texture];
1292 uint32_t gc_cx = gc->cx;
1293 uint32_t gc_cy = gc->cy;
1294 uint32_t gc_pitch = gc->pitch;
1295
1296 for (size_t y = 0; y < gc_cy; y++) {
1297 uint8_t *row = input + (gc_pitch * y);
1298 uint8_t *out = data + (pitch * y);
1299
1300 for (size_t x = 0; x < gc_cx; x += 8) {
1301 __m128i pixels_blue, pixels_green, pixels_red;
1302 __m128i pixels_result;
1303 __m128i *pixels_dest;
1304
1305 __m128i *pixels_src =
1306 (__m128i *)(row + x * sizeof(uint16_t));
1307 __m128i pixels = _mm_load_si128(pixels_src);
1308
1309 __m128i zero = _mm_setzero_si128();
1310 __m128i pixels_low = _mm_unpacklo_epi16(pixels, zero);
1311 __m128i pixels_high = _mm_unpackhi_epi16(pixels, zero);
1312
1313 __m128i blue_channel_mask = _mm_set1_epi32(0x0000001F);
1314 __m128i blue_offset = _mm_set1_epi32(0x00000003);
1315 __m128i green_channel_mask = _mm_set1_epi32(0x000007E0);
1316 __m128i green_offset = _mm_set1_epi32(0x00000008);
1317 __m128i red_channel_mask = _mm_set1_epi32(0x0000F800);
1318 __m128i red_offset = _mm_set1_epi32(0x00000300);
1319
1320 pixels_blue =
1321 _mm_and_si128(pixels_low, blue_channel_mask);
1322 pixels_blue = _mm_slli_epi32(pixels_blue, 3);
1323 pixels_blue = _mm_add_epi32(pixels_blue, blue_offset);
1324
1325 pixels_green =
1326 _mm_and_si128(pixels_low, green_channel_mask);
1327 pixels_green =
1328 _mm_add_epi32(pixels_green, green_offset);
1329 pixels_green = _mm_slli_epi32(pixels_green, 5);
1330
1331 pixels_red =
1332 _mm_and_si128(pixels_low, red_channel_mask);
1333 pixels_red = _mm_add_epi32(pixels_red, red_offset);
1334 pixels_red = _mm_slli_epi32(pixels_red, 8);
1335
1336 pixels_result = _mm_set1_epi32(0xFF000000);
1337 pixels_result =
1338 _mm_or_si128(pixels_result, pixels_blue);
1339 pixels_result =
1340 _mm_or_si128(pixels_result, pixels_green);
1341 pixels_result = _mm_or_si128(pixels_result, pixels_red);
1342
1343 pixels_dest = (__m128i *)(out + x * sizeof(uint32_t));
1344 _mm_store_si128(pixels_dest, pixels_result);
1345
1346 pixels_blue =
1347 _mm_and_si128(pixels_high, blue_channel_mask);
1348 pixels_blue = _mm_slli_epi32(pixels_blue, 3);
1349 pixels_blue = _mm_add_epi32(pixels_blue, blue_offset);
1350
1351 pixels_green =
1352 _mm_and_si128(pixels_high, green_channel_mask);
1353 pixels_green =
1354 _mm_add_epi32(pixels_green, green_offset);
1355 pixels_green = _mm_slli_epi32(pixels_green, 5);
1356
1357 pixels_red =
1358 _mm_and_si128(pixels_high, red_channel_mask);
1359 pixels_red = _mm_add_epi32(pixels_red, red_offset);
1360 pixels_red = _mm_slli_epi32(pixels_red, 8);
1361
1362 pixels_result = _mm_set1_epi32(0xFF000000);
1363 pixels_result =
1364 _mm_or_si128(pixels_result, pixels_blue);
1365 pixels_result =
1366 _mm_or_si128(pixels_result, pixels_green);
1367 pixels_result = _mm_or_si128(pixels_result, pixels_red);
1368
1369 pixels_dest =
1370 (__m128i *)(out + (x + 4) * sizeof(uint32_t));
1371 _mm_store_si128(pixels_dest, pixels_result);
1372 }
1373 }
1374 }
1375
copy_b5g5r5a1_tex(struct game_capture * gc,int cur_texture,uint8_t * data,uint32_t pitch)1376 static void copy_b5g5r5a1_tex(struct game_capture *gc, int cur_texture,
1377 uint8_t *data, uint32_t pitch)
1378 {
1379 uint8_t *input = gc->texture_buffers[cur_texture];
1380 uint32_t gc_cx = gc->cx;
1381 uint32_t gc_cy = gc->cy;
1382 uint32_t gc_pitch = gc->pitch;
1383
1384 for (size_t y = 0; y < gc_cy; y++) {
1385 uint8_t *row = input + (gc_pitch * y);
1386 uint8_t *out = data + (pitch * y);
1387
1388 for (size_t x = 0; x < gc_cx; x += 8) {
1389 __m128i pixels_blue, pixels_green, pixels_red,
1390 pixels_alpha;
1391 __m128i pixels_result;
1392 __m128i *pixels_dest;
1393
1394 __m128i *pixels_src =
1395 (__m128i *)(row + x * sizeof(uint16_t));
1396 __m128i pixels = _mm_load_si128(pixels_src);
1397
1398 __m128i zero = _mm_setzero_si128();
1399 __m128i pixels_low = _mm_unpacklo_epi16(pixels, zero);
1400 __m128i pixels_high = _mm_unpackhi_epi16(pixels, zero);
1401
1402 __m128i blue_channel_mask = _mm_set1_epi32(0x0000001F);
1403 __m128i blue_offset = _mm_set1_epi32(0x00000003);
1404 __m128i green_channel_mask = _mm_set1_epi32(0x000003E0);
1405 __m128i green_offset = _mm_set1_epi32(0x000000C);
1406 __m128i red_channel_mask = _mm_set1_epi32(0x00007C00);
1407 __m128i red_offset = _mm_set1_epi32(0x00000180);
1408 __m128i alpha_channel_mask = _mm_set1_epi32(0x00008000);
1409 __m128i alpha_offset = _mm_set1_epi32(0x00000001);
1410 __m128i alpha_mask32 = _mm_set1_epi32(0xFF000000);
1411
1412 pixels_blue =
1413 _mm_and_si128(pixels_low, blue_channel_mask);
1414 pixels_blue = _mm_slli_epi32(pixels_blue, 3);
1415 pixels_blue = _mm_add_epi32(pixels_blue, blue_offset);
1416
1417 pixels_green =
1418 _mm_and_si128(pixels_low, green_channel_mask);
1419 pixels_green =
1420 _mm_add_epi32(pixels_green, green_offset);
1421 pixels_green = _mm_slli_epi32(pixels_green, 6);
1422
1423 pixels_red =
1424 _mm_and_si128(pixels_low, red_channel_mask);
1425 pixels_red = _mm_add_epi32(pixels_red, red_offset);
1426 pixels_red = _mm_slli_epi32(pixels_red, 9);
1427
1428 pixels_alpha =
1429 _mm_and_si128(pixels_low, alpha_channel_mask);
1430 pixels_alpha = _mm_srli_epi32(pixels_alpha, 15);
1431 pixels_alpha =
1432 _mm_sub_epi32(pixels_alpha, alpha_offset);
1433 pixels_alpha =
1434 _mm_andnot_si128(pixels_alpha, alpha_mask32);
1435
1436 pixels_result = pixels_red;
1437 pixels_result =
1438 _mm_or_si128(pixels_result, pixels_alpha);
1439 pixels_result =
1440 _mm_or_si128(pixels_result, pixels_blue);
1441 pixels_result =
1442 _mm_or_si128(pixels_result, pixels_green);
1443
1444 pixels_dest = (__m128i *)(out + x * sizeof(uint32_t));
1445 _mm_store_si128(pixels_dest, pixels_result);
1446
1447 pixels_blue =
1448 _mm_and_si128(pixels_high, blue_channel_mask);
1449 pixels_blue = _mm_slli_epi32(pixels_blue, 3);
1450 pixels_blue = _mm_add_epi32(pixels_blue, blue_offset);
1451
1452 pixels_green =
1453 _mm_and_si128(pixels_high, green_channel_mask);
1454 pixels_green =
1455 _mm_add_epi32(pixels_green, green_offset);
1456 pixels_green = _mm_slli_epi32(pixels_green, 6);
1457
1458 pixels_red =
1459 _mm_and_si128(pixels_high, red_channel_mask);
1460 pixels_red = _mm_add_epi32(pixels_red, red_offset);
1461 pixels_red = _mm_slli_epi32(pixels_red, 9);
1462
1463 pixels_alpha =
1464 _mm_and_si128(pixels_high, alpha_channel_mask);
1465 pixels_alpha = _mm_srli_epi32(pixels_alpha, 15);
1466 pixels_alpha =
1467 _mm_sub_epi32(pixels_alpha, alpha_offset);
1468 pixels_alpha =
1469 _mm_andnot_si128(pixels_alpha, alpha_mask32);
1470
1471 pixels_result = pixels_red;
1472 pixels_result =
1473 _mm_or_si128(pixels_result, pixels_alpha);
1474 pixels_result =
1475 _mm_or_si128(pixels_result, pixels_blue);
1476 pixels_result =
1477 _mm_or_si128(pixels_result, pixels_green);
1478
1479 pixels_dest =
1480 (__m128i *)(out + (x + 4) * sizeof(uint32_t));
1481 _mm_store_si128(pixels_dest, pixels_result);
1482 }
1483 }
1484 }
1485
copy_16bit_tex(struct game_capture * gc,int cur_texture,uint8_t * data,uint32_t pitch)1486 static inline void copy_16bit_tex(struct game_capture *gc, int cur_texture,
1487 uint8_t *data, uint32_t pitch)
1488 {
1489 if (gc->global_hook_info->format == DXGI_FORMAT_B5G5R5A1_UNORM) {
1490 copy_b5g5r5a1_tex(gc, cur_texture, data, pitch);
1491
1492 } else if (gc->global_hook_info->format == DXGI_FORMAT_B5G6R5_UNORM) {
1493 copy_b5g6r5_tex(gc, cur_texture, data, pitch);
1494 }
1495 }
1496
copy_shmem_tex(struct game_capture * gc)1497 static void copy_shmem_tex(struct game_capture *gc)
1498 {
1499 int cur_texture;
1500 HANDLE mutex = NULL;
1501 uint32_t pitch;
1502 int next_texture;
1503 uint8_t *data;
1504
1505 if (!gc->shmem_data)
1506 return;
1507
1508 cur_texture = gc->shmem_data->last_tex;
1509
1510 if (cur_texture < 0 || cur_texture > 1)
1511 return;
1512
1513 next_texture = cur_texture == 1 ? 0 : 1;
1514
1515 if (object_signalled(gc->texture_mutexes[cur_texture])) {
1516 mutex = gc->texture_mutexes[cur_texture];
1517
1518 } else if (object_signalled(gc->texture_mutexes[next_texture])) {
1519 mutex = gc->texture_mutexes[next_texture];
1520 cur_texture = next_texture;
1521
1522 } else {
1523 return;
1524 }
1525
1526 if (gs_texture_map(gc->texture, &data, &pitch)) {
1527 if (gc->convert_16bit) {
1528 copy_16bit_tex(gc, cur_texture, data, pitch);
1529
1530 } else if (pitch == gc->pitch) {
1531 memcpy(data, gc->texture_buffers[cur_texture],
1532 (size_t)pitch * (size_t)gc->cy);
1533 } else {
1534 uint8_t *input = gc->texture_buffers[cur_texture];
1535 uint32_t best_pitch = pitch < gc->pitch ? pitch
1536 : gc->pitch;
1537
1538 for (size_t y = 0; y < gc->cy; y++) {
1539 uint8_t *line_in = input + gc->pitch * y;
1540 uint8_t *line_out = data + pitch * y;
1541 memcpy(line_out, line_in, best_pitch);
1542 }
1543 }
1544
1545 gs_texture_unmap(gc->texture);
1546 }
1547
1548 ReleaseMutex(mutex);
1549 }
1550
is_16bit_format(uint32_t format)1551 static inline bool is_16bit_format(uint32_t format)
1552 {
1553 return format == DXGI_FORMAT_B5G5R5A1_UNORM ||
1554 format == DXGI_FORMAT_B5G6R5_UNORM;
1555 }
1556
init_shmem_capture(struct game_capture * gc)1557 static inline bool init_shmem_capture(struct game_capture *gc)
1558 {
1559 const uint32_t dxgi_format = gc->global_hook_info->format;
1560 const bool convert_16bit = is_16bit_format(dxgi_format);
1561 const enum gs_color_format format =
1562 convert_16bit ? GS_BGRA : convert_format(dxgi_format);
1563
1564 obs_enter_graphics();
1565 gs_texrender_destroy(gc->extra_texrender);
1566 gc->extra_texrender = NULL;
1567 gs_texture_destroy(gc->extra_texture);
1568 gc->extra_texture = NULL;
1569 gs_texture_destroy(gc->texture);
1570 gc->texture = NULL;
1571 gs_texture_t *const texture =
1572 gs_texture_create(gc->cx, gc->cy, format, 1, NULL, GS_DYNAMIC);
1573 obs_leave_graphics();
1574
1575 bool success = texture != NULL;
1576 if (success) {
1577 const bool linear_sample = format != GS_R10G10B10A2;
1578
1579 gs_texrender_t *extra_texrender = NULL;
1580 if (!linear_sample) {
1581 extra_texrender =
1582 gs_texrender_create(GS_BGRA, GS_ZS_NONE);
1583 success = extra_texrender != NULL;
1584 if (!success)
1585 warn("init_shmem_capture: failed to create extra texrender");
1586 }
1587
1588 if (success) {
1589 gc->texture_buffers[0] = (uint8_t *)gc->data +
1590 gc->shmem_data->tex1_offset;
1591 gc->texture_buffers[1] = (uint8_t *)gc->data +
1592 gc->shmem_data->tex2_offset;
1593 gc->convert_16bit = convert_16bit;
1594
1595 gc->texture = texture;
1596 gc->extra_texture = NULL;
1597 gc->extra_texrender = extra_texrender;
1598 gc->linear_sample = linear_sample;
1599 gc->copy_texture = copy_shmem_tex;
1600 } else {
1601 gs_texture_destroy(texture);
1602 }
1603 } else {
1604 warn("init_shmem_capture: failed to create texture");
1605 }
1606
1607 return success;
1608 }
1609
init_shtex_capture(struct game_capture * gc)1610 static inline bool init_shtex_capture(struct game_capture *gc)
1611 {
1612 obs_enter_graphics();
1613 gs_texrender_destroy(gc->extra_texrender);
1614 gc->extra_texrender = NULL;
1615 gs_texture_destroy(gc->extra_texture);
1616 gc->extra_texture = NULL;
1617 gs_texture_destroy(gc->texture);
1618 gc->texture = NULL;
1619 gs_texture_t *const texture =
1620 gs_texture_open_shared(gc->shtex_data->tex_handle);
1621 bool success = texture != NULL;
1622 if (success) {
1623 enum gs_color_format format =
1624 gs_texture_get_color_format(texture);
1625 const bool ten_bit_srgb = (format == GS_R10G10B10A2);
1626 enum gs_color_format linear_format =
1627 ten_bit_srgb ? GS_BGRA : gs_generalize_format(format);
1628 const bool linear_sample = (linear_format == format);
1629 gs_texture_t *extra_texture = NULL;
1630 gs_texrender_t *extra_texrender = NULL;
1631 if (!linear_sample) {
1632 if (ten_bit_srgb) {
1633 extra_texrender = gs_texrender_create(
1634 linear_format, GS_ZS_NONE);
1635 success = extra_texrender != NULL;
1636 if (!success)
1637 warn("init_shtex_capture: failed to create extra texrender");
1638 } else {
1639 extra_texture = gs_texture_create(
1640 gs_texture_get_width(texture),
1641 gs_texture_get_height(texture),
1642 linear_format, 1, NULL, 0);
1643 success = extra_texture != NULL;
1644 if (!success)
1645 warn("init_shtex_capture: failed to create extra texture");
1646 }
1647 }
1648
1649 if (success) {
1650 gc->texture = texture;
1651 gc->linear_sample = linear_sample;
1652 gc->extra_texture = extra_texture;
1653 gc->extra_texrender = extra_texrender;
1654 } else {
1655 gs_texture_destroy(texture);
1656 }
1657 } else {
1658 warn("init_shtex_capture: failed to open shared handle");
1659 }
1660 obs_leave_graphics();
1661
1662 return success;
1663 }
1664
start_capture(struct game_capture * gc)1665 static bool start_capture(struct game_capture *gc)
1666 {
1667 debug("Starting capture");
1668
1669 /* prevent from using a DLL version that's higher than current */
1670 if (gc->global_hook_info->hook_ver_major > HOOK_VER_MAJOR) {
1671 warn("cannot initialize hook, DLL hook version is "
1672 "%" PRIu32 ".%" PRIu32
1673 ", current plugin hook major version is %d.%d",
1674 gc->global_hook_info->hook_ver_major,
1675 gc->global_hook_info->hook_ver_minor, HOOK_VER_MAJOR,
1676 HOOK_VER_MINOR);
1677 return false;
1678 }
1679
1680 if (gc->global_hook_info->type == CAPTURE_TYPE_MEMORY) {
1681 if (!init_shmem_capture(gc)) {
1682 return false;
1683 }
1684
1685 info("memory capture successful");
1686 } else {
1687 if (!init_shtex_capture(gc)) {
1688 return false;
1689 }
1690
1691 info("shared texture capture successful");
1692 }
1693
1694 return true;
1695 }
1696
capture_valid(struct game_capture * gc)1697 static inline bool capture_valid(struct game_capture *gc)
1698 {
1699 if (!gc->dwm_capture && !IsWindow(gc->window))
1700 return false;
1701
1702 return !object_signalled(gc->target_process);
1703 }
1704
check_foreground_window(struct game_capture * gc,float seconds)1705 static void check_foreground_window(struct game_capture *gc, float seconds)
1706 {
1707 // Hides the cursor if the user isn't actively in the game
1708 gc->cursor_check_time += seconds;
1709 if (gc->cursor_check_time >= 0.1f) {
1710 DWORD foreground_process_id;
1711 GetWindowThreadProcessId(GetForegroundWindow(),
1712 &foreground_process_id);
1713 if (gc->process_id != foreground_process_id)
1714 gc->cursor_hidden = true;
1715 else
1716 gc->cursor_hidden = false;
1717 gc->cursor_check_time = 0.0f;
1718 }
1719 }
1720
game_capture_tick(void * data,float seconds)1721 static void game_capture_tick(void *data, float seconds)
1722 {
1723 struct game_capture *gc = data;
1724 bool deactivate = os_atomic_set_bool(&gc->deactivate_hook, false);
1725 bool activate_now = os_atomic_set_bool(&gc->activate_hook_now, false);
1726
1727 if (activate_now) {
1728 HWND hwnd = (HWND)(uintptr_t)os_atomic_load_long(
1729 &gc->hotkey_window);
1730
1731 if (is_uwp_window(hwnd))
1732 hwnd = get_uwp_actual_window(hwnd);
1733
1734 if (get_window_exe(&gc->executable, hwnd)) {
1735 get_window_title(&gc->title, hwnd);
1736 get_window_class(&gc->class, hwnd);
1737
1738 gc->priority = WINDOW_PRIORITY_CLASS;
1739 gc->retry_time = 10.0f * hook_rate_to_float(
1740 gc->config.hook_rate);
1741 gc->activate_hook = true;
1742 } else {
1743 deactivate = false;
1744 activate_now = false;
1745 }
1746 } else if (deactivate) {
1747 gc->activate_hook = false;
1748 }
1749
1750 if (!obs_source_showing(gc->source)) {
1751 if (gc->showing) {
1752 if (gc->active)
1753 stop_capture(gc);
1754 gc->showing = false;
1755 }
1756 return;
1757
1758 } else if (!gc->showing) {
1759 gc->retry_time =
1760 10.0f * hook_rate_to_float(gc->config.hook_rate);
1761 }
1762
1763 if (gc->hook_stop && object_signalled(gc->hook_stop)) {
1764 debug("hook stop signal received");
1765 stop_capture(gc);
1766 }
1767 if (gc->active && deactivate) {
1768 stop_capture(gc);
1769 }
1770
1771 if (gc->active && !gc->hook_ready && gc->process_id) {
1772 gc->hook_ready = open_event_gc(gc, EVENT_HOOK_READY);
1773 }
1774
1775 if (gc->injector_process && object_signalled(gc->injector_process)) {
1776 DWORD exit_code = 0;
1777
1778 GetExitCodeProcess(gc->injector_process, &exit_code);
1779 close_handle(&gc->injector_process);
1780
1781 if (exit_code != 0) {
1782 warn("inject process failed: %ld", (long)exit_code);
1783 gc->error_acquiring = true;
1784
1785 } else if (!gc->capturing) {
1786 gc->retry_interval =
1787 ERROR_RETRY_INTERVAL *
1788 hook_rate_to_float(gc->config.hook_rate);
1789 stop_capture(gc);
1790 }
1791 }
1792
1793 if (gc->hook_ready && object_signalled(gc->hook_ready)) {
1794 debug("capture initializing!");
1795 enum capture_result result = init_capture_data(gc);
1796
1797 if (result == CAPTURE_SUCCESS)
1798 gc->capturing = start_capture(gc);
1799 else
1800 debug("init_capture_data failed");
1801
1802 if (result != CAPTURE_RETRY && !gc->capturing) {
1803 gc->retry_interval =
1804 ERROR_RETRY_INTERVAL *
1805 hook_rate_to_float(gc->config.hook_rate);
1806 stop_capture(gc);
1807 }
1808 }
1809
1810 gc->retry_time += seconds;
1811
1812 if (!gc->active) {
1813 if (!gc->error_acquiring &&
1814 gc->retry_time > gc->retry_interval) {
1815 if (gc->config.mode == CAPTURE_MODE_ANY ||
1816 gc->activate_hook) {
1817 try_hook(gc);
1818 gc->retry_time = 0.0f;
1819 }
1820 }
1821 } else {
1822 if (!capture_valid(gc)) {
1823 info("capture window no longer exists, "
1824 "terminating capture");
1825 stop_capture(gc);
1826 } else {
1827 if (gc->copy_texture) {
1828 obs_enter_graphics();
1829 gc->copy_texture(gc);
1830 obs_leave_graphics();
1831 }
1832
1833 if (gc->config.cursor) {
1834 check_foreground_window(gc, seconds);
1835 obs_enter_graphics();
1836 cursor_capture(&gc->cursor_data);
1837 obs_leave_graphics();
1838 }
1839
1840 gc->fps_reset_time += seconds;
1841 if (gc->fps_reset_time >= gc->retry_interval) {
1842 reset_frame_interval(gc);
1843 gc->fps_reset_time = 0.0f;
1844 }
1845 }
1846 }
1847
1848 if (!gc->showing)
1849 gc->showing = true;
1850 }
1851
game_capture_render_cursor(struct game_capture * gc)1852 static inline void game_capture_render_cursor(struct game_capture *gc)
1853 {
1854 POINT p = {0};
1855 HWND window;
1856
1857 if (!gc->global_hook_info->cx || !gc->global_hook_info->cy)
1858 return;
1859
1860 window = !!gc->global_hook_info->window
1861 ? (HWND)(uintptr_t)gc->global_hook_info->window
1862 : gc->window;
1863
1864 ClientToScreen(window, &p);
1865
1866 cursor_draw(&gc->cursor_data, -p.x, -p.y, gc->global_hook_info->cx,
1867 gc->global_hook_info->cy);
1868 }
1869
game_capture_render(void * data,gs_effect_t * unused)1870 static void game_capture_render(void *data, gs_effect_t *unused)
1871 {
1872 UNUSED_PARAMETER(unused);
1873
1874 struct game_capture *gc = data;
1875 if (!gc->texture || !gc->active)
1876 return;
1877
1878 const bool allow_transparency = gc->config.allow_transparency;
1879 gs_effect_t *const effect = obs_get_base_effect(
1880 allow_transparency ? OBS_EFFECT_DEFAULT : OBS_EFFECT_OPAQUE);
1881
1882 bool linear_sample = gc->linear_sample;
1883 gs_texture_t *texture = gc->texture;
1884 if (!linear_sample && !obs_source_get_texcoords_centered(gc->source)) {
1885 gs_texture_t *const extra_texture = gc->extra_texture;
1886 if (extra_texture) {
1887 gs_copy_texture(extra_texture, texture);
1888 texture = extra_texture;
1889 } else {
1890 gs_texrender_t *const texrender = gc->extra_texrender;
1891 gs_texrender_reset(texrender);
1892 const uint32_t cx = gs_texture_get_width(texture);
1893 const uint32_t cy = gs_texture_get_height(texture);
1894 if (gs_texrender_begin(texrender, cx, cy)) {
1895 gs_effect_t *const default_effect =
1896 obs_get_base_effect(OBS_EFFECT_DEFAULT);
1897 const bool previous =
1898 gs_framebuffer_srgb_enabled();
1899 gs_enable_framebuffer_srgb(false);
1900 gs_enable_blending(false);
1901 gs_ortho(0.0f, (float)cx, 0.0f, (float)cy,
1902 -100.0f, 100.0f);
1903 gs_eparam_t *const image =
1904 gs_effect_get_param_by_name(
1905 default_effect, "image");
1906 gs_effect_set_texture(image, texture);
1907 while (gs_effect_loop(default_effect, "Draw")) {
1908 gs_draw_sprite(texture, 0, 0, 0);
1909 }
1910 gs_enable_blending(true);
1911 gs_enable_framebuffer_srgb(previous);
1912
1913 gs_texrender_end(texrender);
1914
1915 texture = gs_texrender_get_texture(texrender);
1916 }
1917 }
1918
1919 linear_sample = true;
1920 }
1921
1922 gs_eparam_t *const image = gs_effect_get_param_by_name(effect, "image");
1923 const uint32_t flip = gc->global_hook_info->flip ? GS_FLIP_V : 0;
1924 const char *tech_name = allow_transparency && !linear_sample
1925 ? "DrawSrgbDecompress"
1926 : "Draw";
1927 while (gs_effect_loop(effect, tech_name)) {
1928 const bool previous = gs_framebuffer_srgb_enabled();
1929 gs_enable_framebuffer_srgb(allow_transparency || linear_sample);
1930 gs_enable_blending(allow_transparency);
1931 if (linear_sample)
1932 gs_effect_set_texture_srgb(image, texture);
1933 else
1934 gs_effect_set_texture(image, texture);
1935 gs_draw_sprite(texture, flip, 0, 0);
1936 gs_enable_blending(true);
1937 gs_enable_framebuffer_srgb(previous);
1938
1939 if (allow_transparency && gc->config.cursor &&
1940 !gc->cursor_hidden) {
1941 game_capture_render_cursor(gc);
1942 }
1943 }
1944
1945 if (!allow_transparency && gc->config.cursor && !gc->cursor_hidden) {
1946 gs_effect_t *const default_effect =
1947 obs_get_base_effect(OBS_EFFECT_DEFAULT);
1948
1949 while (gs_effect_loop(default_effect, "Draw")) {
1950 game_capture_render_cursor(gc);
1951 }
1952 }
1953 }
1954
game_capture_width(void * data)1955 static uint32_t game_capture_width(void *data)
1956 {
1957 struct game_capture *gc = data;
1958 return gc->active ? gc->cx : 0;
1959 }
1960
game_capture_height(void * data)1961 static uint32_t game_capture_height(void *data)
1962 {
1963 struct game_capture *gc = data;
1964 return gc->active ? gc->cy : 0;
1965 }
1966
game_capture_name(void * unused)1967 static const char *game_capture_name(void *unused)
1968 {
1969 UNUSED_PARAMETER(unused);
1970 return TEXT_GAME_CAPTURE;
1971 }
1972
game_capture_defaults(obs_data_t * settings)1973 static void game_capture_defaults(obs_data_t *settings)
1974 {
1975 obs_data_set_default_string(settings, SETTING_MODE, SETTING_MODE_ANY);
1976 obs_data_set_default_int(settings, SETTING_WINDOW_PRIORITY,
1977 (int)WINDOW_PRIORITY_EXE);
1978 obs_data_set_default_bool(settings, SETTING_COMPATIBILITY, false);
1979 obs_data_set_default_bool(settings, SETTING_CURSOR, true);
1980 obs_data_set_default_bool(settings, SETTING_TRANSPARENCY, false);
1981 obs_data_set_default_bool(settings, SETTING_LIMIT_FRAMERATE, false);
1982 obs_data_set_default_bool(settings, SETTING_CAPTURE_OVERLAYS, false);
1983 obs_data_set_default_bool(settings, SETTING_ANTI_CHEAT_HOOK, true);
1984 obs_data_set_default_int(settings, SETTING_HOOK_RATE,
1985 (int)HOOK_RATE_NORMAL);
1986 }
1987
mode_callback(obs_properties_t * ppts,obs_property_t * p,obs_data_t * settings)1988 static bool mode_callback(obs_properties_t *ppts, obs_property_t *p,
1989 obs_data_t *settings)
1990 {
1991 bool capture_window;
1992
1993 if (using_older_non_mode_format(settings)) {
1994 capture_window =
1995 !obs_data_get_bool(settings, SETTING_ANY_FULLSCREEN);
1996 } else {
1997 const char *mode = obs_data_get_string(settings, SETTING_MODE);
1998 capture_window = strcmp(mode, SETTING_MODE_WINDOW) == 0;
1999 }
2000
2001 p = obs_properties_get(ppts, SETTING_CAPTURE_WINDOW);
2002 obs_property_set_visible(p, capture_window);
2003
2004 p = obs_properties_get(ppts, SETTING_WINDOW_PRIORITY);
2005 obs_property_set_visible(p, capture_window);
2006
2007 return true;
2008 }
2009
insert_preserved_val(obs_property_t * p,const char * val,size_t idx)2010 static void insert_preserved_val(obs_property_t *p, const char *val, size_t idx)
2011 {
2012 char *class = NULL;
2013 char *title = NULL;
2014 char *executable = NULL;
2015 struct dstr desc = {0};
2016
2017 build_window_strings(val, &class, &title, &executable);
2018
2019 dstr_printf(&desc, "[%s]: %s", executable, title);
2020 obs_property_list_insert_string(p, idx, desc.array, val);
2021 obs_property_list_item_disable(p, idx, true);
2022
2023 dstr_free(&desc);
2024 bfree(class);
2025 bfree(title);
2026 bfree(executable);
2027 }
2028
check_window_property_setting(obs_properties_t * ppts,obs_property_t * p,obs_data_t * settings,const char * val,size_t idx)2029 bool check_window_property_setting(obs_properties_t *ppts, obs_property_t *p,
2030 obs_data_t *settings, const char *val,
2031 size_t idx)
2032 {
2033 const char *cur_val;
2034 bool match = false;
2035 size_t i = 0;
2036
2037 cur_val = obs_data_get_string(settings, val);
2038 if (!cur_val) {
2039 return false;
2040 }
2041
2042 for (;;) {
2043 const char *val = obs_property_list_item_string(p, i++);
2044 if (!val)
2045 break;
2046
2047 if (strcmp(val, cur_val) == 0) {
2048 match = true;
2049 break;
2050 }
2051 }
2052
2053 if (cur_val && *cur_val && !match) {
2054 insert_preserved_val(p, cur_val, idx);
2055 return true;
2056 }
2057
2058 UNUSED_PARAMETER(ppts);
2059 return false;
2060 }
2061
window_changed_callback(obs_properties_t * ppts,obs_property_t * p,obs_data_t * settings)2062 static bool window_changed_callback(obs_properties_t *ppts, obs_property_t *p,
2063 obs_data_t *settings)
2064 {
2065 return check_window_property_setting(ppts, p, settings,
2066 SETTING_CAPTURE_WINDOW, 1);
2067 }
2068
EnumFirstMonitor(HMONITOR monitor,HDC hdc,LPRECT rc,LPARAM data)2069 static BOOL CALLBACK EnumFirstMonitor(HMONITOR monitor, HDC hdc, LPRECT rc,
2070 LPARAM data)
2071 {
2072 *(HMONITOR *)data = monitor;
2073
2074 UNUSED_PARAMETER(hdc);
2075 UNUSED_PARAMETER(rc);
2076 return false;
2077 }
2078
window_not_blacklisted(const char * title,const char * class,const char * exe)2079 static bool window_not_blacklisted(const char *title, const char *class,
2080 const char *exe)
2081 {
2082 UNUSED_PARAMETER(title);
2083 UNUSED_PARAMETER(class);
2084
2085 return !is_blacklisted_exe(exe);
2086 }
2087
game_capture_properties(void * data)2088 static obs_properties_t *game_capture_properties(void *data)
2089 {
2090 HMONITOR monitor;
2091 uint32_t cx = 1920;
2092 uint32_t cy = 1080;
2093
2094 /* scaling is free form, this is mostly just to provide some common
2095 * values */
2096 bool success = !!EnumDisplayMonitors(NULL, NULL, EnumFirstMonitor,
2097 (LPARAM)&monitor);
2098 if (success) {
2099 MONITORINFO mi = {0};
2100 mi.cbSize = sizeof(mi);
2101
2102 if (!!GetMonitorInfo(monitor, &mi)) {
2103 cx = (uint32_t)(mi.rcMonitor.right - mi.rcMonitor.left);
2104 cy = (uint32_t)(mi.rcMonitor.bottom - mi.rcMonitor.top);
2105 }
2106 }
2107
2108 /* update from deprecated settings */
2109 if (data) {
2110 struct game_capture *gc = data;
2111 obs_data_t *settings = obs_source_get_settings(gc->source);
2112 if (using_older_non_mode_format(settings)) {
2113 bool any = obs_data_get_bool(settings,
2114 SETTING_ANY_FULLSCREEN);
2115 const char *mode = any ? SETTING_MODE_ANY
2116 : SETTING_MODE_WINDOW;
2117
2118 obs_data_set_string(settings, SETTING_MODE, mode);
2119 }
2120 obs_data_release(settings);
2121 }
2122
2123 obs_properties_t *ppts = obs_properties_create();
2124 obs_property_t *p;
2125
2126 p = obs_properties_add_list(ppts, SETTING_MODE, TEXT_MODE,
2127 OBS_COMBO_TYPE_LIST,
2128 OBS_COMBO_FORMAT_STRING);
2129
2130 obs_property_list_add_string(p, TEXT_MODE_ANY, SETTING_MODE_ANY);
2131 obs_property_list_add_string(p, TEXT_MODE_WINDOW, SETTING_MODE_WINDOW);
2132 obs_property_list_add_string(p, TEXT_MODE_HOTKEY, SETTING_MODE_HOTKEY);
2133
2134 obs_property_set_modified_callback(p, mode_callback);
2135
2136 p = obs_properties_add_list(ppts, SETTING_CAPTURE_WINDOW, TEXT_WINDOW,
2137 OBS_COMBO_TYPE_LIST,
2138 OBS_COMBO_FORMAT_STRING);
2139 obs_property_list_add_string(p, "", "");
2140 fill_window_list(p, INCLUDE_MINIMIZED, window_not_blacklisted);
2141
2142 obs_property_set_modified_callback(p, window_changed_callback);
2143
2144 p = obs_properties_add_list(ppts, SETTING_WINDOW_PRIORITY,
2145 TEXT_MATCH_PRIORITY, OBS_COMBO_TYPE_LIST,
2146 OBS_COMBO_FORMAT_INT);
2147 obs_property_list_add_int(p, TEXT_MATCH_TITLE, WINDOW_PRIORITY_TITLE);
2148 obs_property_list_add_int(p, TEXT_MATCH_CLASS, WINDOW_PRIORITY_CLASS);
2149 obs_property_list_add_int(p, TEXT_MATCH_EXE, WINDOW_PRIORITY_EXE);
2150
2151 obs_properties_add_bool(ppts, SETTING_COMPATIBILITY,
2152 TEXT_SLI_COMPATIBILITY);
2153
2154 obs_properties_add_bool(ppts, SETTING_TRANSPARENCY,
2155 TEXT_ALLOW_TRANSPARENCY);
2156
2157 obs_properties_add_bool(ppts, SETTING_LIMIT_FRAMERATE,
2158 TEXT_LIMIT_FRAMERATE);
2159
2160 obs_properties_add_bool(ppts, SETTING_CURSOR, TEXT_CAPTURE_CURSOR);
2161
2162 obs_properties_add_bool(ppts, SETTING_ANTI_CHEAT_HOOK,
2163 TEXT_ANTI_CHEAT_HOOK);
2164
2165 obs_properties_add_bool(ppts, SETTING_CAPTURE_OVERLAYS,
2166 TEXT_CAPTURE_OVERLAYS);
2167
2168 p = obs_properties_add_list(ppts, SETTING_HOOK_RATE, TEXT_HOOK_RATE,
2169 OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
2170 obs_property_list_add_int(p, TEXT_HOOK_RATE_SLOW, HOOK_RATE_SLOW);
2171 obs_property_list_add_int(p, TEXT_HOOK_RATE_NORMAL, HOOK_RATE_NORMAL);
2172 obs_property_list_add_int(p, TEXT_HOOK_RATE_FAST, HOOK_RATE_FAST);
2173 obs_property_list_add_int(p, TEXT_HOOK_RATE_FASTEST, HOOK_RATE_FASTEST);
2174
2175 UNUSED_PARAMETER(data);
2176 return ppts;
2177 }
2178
2179 struct obs_source_info game_capture_info = {
2180 .id = "game_capture",
2181 .type = OBS_SOURCE_TYPE_INPUT,
2182 .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
2183 OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_SRGB,
2184 .get_name = game_capture_name,
2185 .create = game_capture_create,
2186 .destroy = game_capture_destroy,
2187 .get_width = game_capture_width,
2188 .get_height = game_capture_height,
2189 .get_defaults = game_capture_defaults,
2190 .get_properties = game_capture_properties,
2191 .update = game_capture_update,
2192 .video_tick = game_capture_tick,
2193 .video_render = game_capture_render,
2194 .icon_type = OBS_ICON_TYPE_GAME_CAPTURE,
2195 };
2196