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