Browse Source

win-capture: Add direct hooking and use by default

I've come to realize that it's probably not wise to deviate from the
original version's functionality due to the fact that the original
version works without issues.  I'm wondering if some of the capture
problems have been due to the fact that the direct hook method (via
CreateRemoteThread) was removed, so I put it back in, made it default,
and added an option to use anti-cheat compatibility just like in the
original version.
jp9000 10 years ago
parent
commit
f4d0da4e04

+ 2 - 0
plugins/win-capture/CMakeLists.txt

@@ -3,6 +3,7 @@ project(win-capture)
 set(win-capture_HEADERS
 set(win-capture_HEADERS
 	obfuscate.h
 	obfuscate.h
 	hook-helpers.h
 	hook-helpers.h
+	inject-library.h
 	cursor-capture.h
 	cursor-capture.h
 	graphics-hook-info.h
 	graphics-hook-info.h
 	window-helpers.h
 	window-helpers.h
@@ -11,6 +12,7 @@ set(win-capture_HEADERS
 set(win-capture_SOURCES
 set(win-capture_SOURCES
 	dc-capture.c
 	dc-capture.c
 	obfuscate.c
 	obfuscate.c
+	inject-library.c
 	cursor-capture.c
 	cursor-capture.c
 	game-capture.c
 	game-capture.c
 	window-helpers.c
 	window-helpers.c

+ 1 - 0
plugins/win-capture/data/locale/en-US.ini

@@ -16,3 +16,4 @@ GameCapture.ForceScaling="Force Scaling"
 GameCapture.ScaleRes="Scale Resolution"
 GameCapture.ScaleRes="Scale Resolution"
 GameCapture.LimitFramerate="Limit capture framerate"
 GameCapture.LimitFramerate="Limit capture framerate"
 GameCapture.CaptureOverlays="Capture third-party overlays (such as steam)"
 GameCapture.CaptureOverlays="Capture third-party overlays (such as steam)"
+GameCapture.AntiCheatHook="Use anti-cheat compatibility hook"

+ 93 - 12
plugins/win-capture/game-capture.c

@@ -6,6 +6,7 @@
 #include <emmintrin.h>
 #include <emmintrin.h>
 #include <ipc-util/pipe.h>
 #include <ipc-util/pipe.h>
 #include "obfuscate.h"
 #include "obfuscate.h"
+#include "inject-library.h"
 #include "graphics-hook-info.h"
 #include "graphics-hook-info.h"
 #include "window-helpers.h"
 #include "window-helpers.h"
 #include "cursor-capture.h"
 #include "cursor-capture.h"
@@ -29,6 +30,7 @@
 #define SETTING_TRANSPARENCY     "allow_transparency"
 #define SETTING_TRANSPARENCY     "allow_transparency"
 #define SETTING_LIMIT_FRAMERATE  "limit_framerate"
 #define SETTING_LIMIT_FRAMERATE  "limit_framerate"
 #define SETTING_CAPTURE_OVERLAYS "capture_overlays"
 #define SETTING_CAPTURE_OVERLAYS "capture_overlays"
+#define SETTING_ANTI_CHEAT_HOOK  "anti_cheat_hook"
 
 
 #define TEXT_GAME_CAPTURE        obs_module_text("GameCapture")
 #define TEXT_GAME_CAPTURE        obs_module_text("GameCapture")
 #define TEXT_ANY_FULLSCREEN      obs_module_text("GameCapture.AnyFullscreen")
 #define TEXT_ANY_FULLSCREEN      obs_module_text("GameCapture.AnyFullscreen")
@@ -44,6 +46,7 @@
 #define TEXT_CAPTURE_CURSOR      obs_module_text("CaptureCursor")
 #define TEXT_CAPTURE_CURSOR      obs_module_text("CaptureCursor")
 #define TEXT_LIMIT_FRAMERATE     obs_module_text("GameCapture.LimitFramerate")
 #define TEXT_LIMIT_FRAMERATE     obs_module_text("GameCapture.LimitFramerate")
 #define TEXT_CAPTURE_OVERLAYS    obs_module_text("GameCapture.CaptureOverlays")
 #define TEXT_CAPTURE_OVERLAYS    obs_module_text("GameCapture.CaptureOverlays")
+#define TEXT_ANTI_CHEAT_HOOK     obs_module_text("GameCapture.AntiCheatHook")
 
 
 #define DEFAULT_RETRY_INTERVAL 2.0f
 #define DEFAULT_RETRY_INTERVAL 2.0f
 #define ERROR_RETRY_INTERVAL 4.0f
 #define ERROR_RETRY_INTERVAL 4.0f
@@ -62,6 +65,7 @@ struct game_capture_config {
 	bool                          allow_transparency : 1;
 	bool                          allow_transparency : 1;
 	bool                          limit_framerate : 1;
 	bool                          limit_framerate : 1;
 	bool                          capture_overlays : 1;
 	bool                          capture_overlays : 1;
+	bool                          anticheat_hook : 1;
 };
 };
 
 
 struct game_capture {
 struct game_capture {
@@ -249,6 +253,8 @@ static inline void get_config(struct game_capture_config *cfg,
 			SETTING_LIMIT_FRAMERATE);
 			SETTING_LIMIT_FRAMERATE);
 	cfg->capture_overlays = obs_data_get_bool(settings,
 	cfg->capture_overlays = obs_data_get_bool(settings,
 			SETTING_CAPTURE_OVERLAYS);
 			SETTING_CAPTURE_OVERLAYS);
+	cfg->anticheat_hook = obs_data_get_bool(settings,
+			SETTING_ANTI_CHEAT_HOOK);
 
 
 	scale_str = obs_data_get_string(settings, SETTING_SCALE_RES);
 	scale_str = obs_data_get_string(settings, SETTING_SCALE_RES);
 	ret = sscanf(scale_str, "%"PRIu32"x%"PRIu32,
 	ret = sscanf(scale_str, "%"PRIu32"x%"PRIu32,
@@ -554,23 +560,77 @@ static inline bool init_pipe(struct game_capture *gc)
 	return true;
 	return true;
 }
 }
 
 
+static inline bool inject_library(HANDLE process, const wchar_t *dll)
+{
+	return inject_library_obf(process, dll,
+			"D|hkqkW`kl{k\\osofj", 0xa178ef3655e5ade7,
+			"[uawaRzbhh{tIdkj~~", 0x561478dbd824387c,
+			"[fr}pboIe`dlN}", 0x395bfbc9833590fd,
+			"\\`zs}gmOzhhBq", 0x12897dd89168789a,
+			"GbfkDaezbp~X", 0x76aff7238788f7db);
+}
+
+static inline bool hook_direct(struct game_capture *gc,
+		const char *hook_path_rel)
+{
+	wchar_t hook_path_abs_w[MAX_PATH];
+	wchar_t *hook_path_rel_w;
+	wchar_t *path_ret;
+	HANDLE process;
+	int ret;
+
+	os_utf8_to_wcs_ptr(hook_path_rel, 0, &hook_path_rel_w);
+	if (!hook_path_rel_w) {
+		warn("hook_direct: could not convert string");
+		return false;
+	}
+
+	path_ret = _wfullpath(hook_path_abs_w, hook_path_rel_w, MAX_PATH);
+	bfree(hook_path_rel_w);
+
+	if (path_ret == NULL) {
+		warn("hook_direct: could not make absolute path");
+		return false;
+	}
+
+	process = open_process(PROCESS_ALL_ACCESS, false, gc->process_id);
+	if (!process) {
+		warn("hook_direct: could not open process: %s (%lu)",
+				gc->config.executable, GetLastError());
+		return false;
+	}
+
+	ret = inject_library(process, hook_path_abs_w);
+	CloseHandle(process);
+
+	if (ret != 0) {
+		warn("hook_direct: inject failed: %ld", ret);
+		return false;
+	}
+
+	return true;
+}
+
 static inline bool create_inject_process(struct game_capture *gc,
 static inline bool create_inject_process(struct game_capture *gc,
-		const char *inject_path, const char *hook_path)
+		const char *inject_path, const char *hook_dll)
 {
 {
 	wchar_t *command_line_w = malloc(4096 * sizeof(wchar_t));
 	wchar_t *command_line_w = malloc(4096 * sizeof(wchar_t));
 	wchar_t *inject_path_w;
 	wchar_t *inject_path_w;
-	wchar_t *hook_path_w;
+	wchar_t *hook_dll_w;
+	bool anti_cheat = gc->config.anticheat_hook;
 	PROCESS_INFORMATION pi = {0};
 	PROCESS_INFORMATION pi = {0};
 	STARTUPINFO si = {0};
 	STARTUPINFO si = {0};
 	bool success = false;
 	bool success = false;
 
 
 	os_utf8_to_wcs_ptr(inject_path, 0, &inject_path_w);
 	os_utf8_to_wcs_ptr(inject_path, 0, &inject_path_w);
-	os_utf8_to_wcs_ptr(hook_path, 0, &hook_path_w);
+	os_utf8_to_wcs_ptr(hook_dll, 0, &hook_dll_w);
 
 
 	si.cb = sizeof(si);
 	si.cb = sizeof(si);
 
 
-	swprintf(command_line_w, 4096, L"\"%s\" \"%s\" %lu",
-			inject_path_w, hook_path_w, gc->thread_id);
+	swprintf(command_line_w, 4096, L"\"%s\" \"%s\" %lu %lu",
+			inject_path_w, hook_dll_w,
+			(unsigned long)anti_cheat,
+			anti_cheat ? gc->thread_id : gc->process_id);
 
 
 	success = !!CreateProcessW(inject_path_w, command_line_w, NULL, NULL,
 	success = !!CreateProcessW(inject_path_w, command_line_w, NULL, NULL,
 			false, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
 			false, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
@@ -584,24 +644,28 @@ static inline bool create_inject_process(struct game_capture *gc,
 
 
 	free(command_line_w);
 	free(command_line_w);
 	bfree(inject_path_w);
 	bfree(inject_path_w);
-	bfree(hook_path_w);
+	bfree(hook_dll_w);
 	return success;
 	return success;
 }
 }
 
 
 static inline bool inject_hook(struct game_capture *gc)
 static inline bool inject_hook(struct game_capture *gc)
 {
 {
+	bool matching_architecture;
 	bool success = false;
 	bool success = false;
+	const char *hook_dll;
 	char *inject_path;
 	char *inject_path;
 	char *hook_path;
 	char *hook_path;
 
 
 	if (gc->process_is_64bit) {
 	if (gc->process_is_64bit) {
+		hook_dll = "graphics-hook64.dll";
 		inject_path = obs_module_file("inject-helper64.exe");
 		inject_path = obs_module_file("inject-helper64.exe");
-		hook_path = obs_module_file("graphics-hook64.dll");
 	} else {
 	} else {
+		hook_dll = "graphics-hook32.dll";
 		inject_path = obs_module_file("inject-helper32.exe");
 		inject_path = obs_module_file("inject-helper32.exe");
-		hook_path = obs_module_file("graphics-hook32.dll");
 	}
 	}
 
 
+	hook_path = obs_module_file(hook_dll);
+
 	if (!check_file_integrity(gc, inject_path, "inject helper")) {
 	if (!check_file_integrity(gc, inject_path, "inject helper")) {
 		goto cleanup;
 		goto cleanup;
 	}
 	}
@@ -609,7 +673,20 @@ static inline bool inject_hook(struct game_capture *gc)
 		goto cleanup;
 		goto cleanup;
 	}
 	}
 
 
-	success = create_inject_process(gc, inject_path, hook_path);
+#ifdef _WIN64
+	matching_architecture = gc->process_is_64bit;
+#else
+	matching_architecture = !gc->process_is_64bit;
+#endif
+
+	if (matching_architecture && !gc->config.anticheat_hook) {
+		info("using direct hook");
+		success = hook_direct(gc, hook_path);
+	} else {
+		info("using helper (%s hook)", gc->config.anticheat_hook ?
+				"compatibility" : "direct");
+		success = create_inject_process(gc, inject_path, hook_dll);
+	}
 
 
 cleanup:
 cleanup:
 	bfree(inject_path);
 	bfree(inject_path);
@@ -1173,7 +1250,7 @@ static void game_capture_tick(void *data, float seconds)
 		close_handle(&gc->injector_process);
 		close_handle(&gc->injector_process);
 
 
 		if (exit_code != 0) {
 		if (exit_code != 0) {
-			warn("inject process failed: %lu", exit_code);
+			warn("inject process failed: %ld", (long)exit_code);
 			gc->error_acquiring = true;
 			gc->error_acquiring = true;
 
 
 		} else if (!gc->capturing) {
 		} else if (!gc->capturing) {
@@ -1309,6 +1386,7 @@ static void game_capture_defaults(obs_data_t *settings)
 	obs_data_set_default_string(settings, SETTING_SCALE_RES, "0x0");
 	obs_data_set_default_string(settings, SETTING_SCALE_RES, "0x0");
 	obs_data_set_default_bool(settings, SETTING_LIMIT_FRAMERATE, false);
 	obs_data_set_default_bool(settings, SETTING_LIMIT_FRAMERATE, false);
 	obs_data_set_default_bool(settings, SETTING_CAPTURE_OVERLAYS, false);
 	obs_data_set_default_bool(settings, SETTING_CAPTURE_OVERLAYS, false);
+	obs_data_set_default_bool(settings, SETTING_ANTI_CHEAT_HOOK, false);
 }
 }
 
 
 static bool any_fullscreen_callback(obs_properties_t *ppts,
 static bool any_fullscreen_callback(obs_properties_t *ppts,
@@ -1481,11 +1559,14 @@ static obs_properties_t *game_capture_properties(void *data)
 	obs_properties_add_bool(ppts, SETTING_LIMIT_FRAMERATE,
 	obs_properties_add_bool(ppts, SETTING_LIMIT_FRAMERATE,
 			TEXT_LIMIT_FRAMERATE);
 			TEXT_LIMIT_FRAMERATE);
 
 
+	obs_properties_add_bool(ppts, SETTING_CURSOR, TEXT_CAPTURE_CURSOR);
+
+	obs_properties_add_bool(ppts, SETTING_ANTI_CHEAT_HOOK,
+			TEXT_ANTI_CHEAT_HOOK);
+
 	obs_properties_add_bool(ppts, SETTING_CAPTURE_OVERLAYS,
 	obs_properties_add_bool(ppts, SETTING_CAPTURE_OVERLAYS,
 			TEXT_CAPTURE_OVERLAYS);
 			TEXT_CAPTURE_OVERLAYS);
 
 
-	obs_properties_add_bool(ppts, SETTING_CURSOR, TEXT_CAPTURE_CURSOR);
-
 	UNUSED_PARAMETER(data);
 	UNUSED_PARAMETER(data);
 	return ppts;
 	return ppts;
 }
 }

+ 2 - 0
plugins/win-capture/inject-helper/CMakeLists.txt

@@ -1,9 +1,11 @@
 project(inject-helper)
 project(inject-helper)
 
 
 set(inject-helper_HEADERS
 set(inject-helper_HEADERS
+	../inject-library.h
 	../obfuscate.h)
 	../obfuscate.h)
 
 
 set(inject-helper_SOURCES
 set(inject-helper_SOURCES
+	../inject-library.c
 	../obfuscate.c
 	../obfuscate.c
 	inject-helper.c)
 	inject-helper.c)
 
 

+ 74 - 48
plugins/win-capture/inject-helper/inject-helper.c

@@ -1,8 +1,17 @@
+#define _CRT_SECURE_NO_WARNINGS
+
 #include <stdio.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
 #include <windows.h>
 #include <windows.h>
 #include <shellapi.h>
 #include <shellapi.h>
 #include <stdbool.h>
 #include <stdbool.h>
 #include "../obfuscate.h"
 #include "../obfuscate.h"
+#include "../inject-library.h"
+
+#if defined(_MSC_VER) && !defined(inline)
+#define inline __inline
+#endif
 
 
 static void load_debug_privilege(void)
 static void load_debug_privilege(void)
 {
 {
@@ -27,71 +36,88 @@ static void load_debug_privilege(void)
 	CloseHandle(token);
 	CloseHandle(token);
 }
 }
 
 
-typedef HHOOK (WINAPI *set_windows_hook_ex_t)(int, HOOKPROC, HINSTANCE, DWORD);
+static inline HANDLE open_process(DWORD desired_access, bool inherit_handle,
+		DWORD process_id)
+{
+	HANDLE (WINAPI *open_process_proc)(DWORD, BOOL, DWORD);
+	open_process_proc = get_obfuscated_func(GetModuleHandleW(L"KERNEL32"),
+			"HxjcQrmkb|~", 0xc82efdf78201df87);
 
 
-#define RETRY_INTERVAL_MS      500
-#define TOTAL_RETRY_TIME_MS    4000
-#define RETRY_COUNT            (TOTAL_RETRY_TIME_MS / RETRY_INTERVAL_MS)
+	return open_process_proc(desired_access, inherit_handle, process_id);
+}
 
 
-static int inject_library_safe(DWORD thread_id, const char *dll)
+static inline int inject_library(HANDLE process, const wchar_t *dll)
 {
 {
-	HMODULE user32 = GetModuleHandleW(L"USER32");
-	set_windows_hook_ex_t set_windows_hook_ex;
-	HMODULE lib = LoadLibraryA(dll);
-	LPVOID proc;
-	HHOOK hook;
-	size_t i;
-
-	if (!lib || !user32) {
-		return -3;
-	}
+	return inject_library_obf(process, dll,
+			"E}mo|d[cefubWk~bgk", 0x7c3371986918e8f6,
+			"Rqbr`T{cnor{Bnlgwz", 0x81bf81adc9456b35,
+			"]`~wrl`KeghiCt", 0xadc6a7b9acd73c9b,
+			"Zh}{}agHzfd@{", 0x57135138eb08ff1c,
+			"DnafGhj}l~sX", 0x350bfacdf81b2018);
+}
 
 
-#ifdef _WIN64
-	proc = GetProcAddress(lib, "dummy_debug_proc");
-#else
-	proc = GetProcAddress(lib, "_dummy_debug_proc@12");
-#endif
+static inline int inject_library_safe(DWORD thread_id, const wchar_t *dll)
+{
+	return inject_library_safe_obf(thread_id, dll,
+			"[bs^fbkmwuKfmfOvI", 0xEAD293602FCF9778ULL);
+}
 
 
-	if (!proc) {
-		return -4;
+static inline int inject_library_full(DWORD process_id, const wchar_t *dll)
+{
+	HANDLE process = open_process(PROCESS_ALL_ACCESS, false, process_id);
+	int ret;
+
+	if (process) {
+		ret = inject_library(process, dll);
+		CloseHandle(process);
+	} else {
+		ret = INJECT_ERROR_OPEN_PROCESS_FAIL;
 	}
 	}
 
 
-	set_windows_hook_ex = get_obfuscated_func(user32, "[bs^fbkmwuKfmfOvI",
-			0xEAD293602FCF9778ULL);
+	return ret;
+}
 
 
-	hook = set_windows_hook_ex(WH_GETMESSAGE, proc, lib, thread_id);
-	if (!hook) {
-		return -5;
-	}
+static int inject_helper(wchar_t *argv[], const wchar_t *dll)
+{
+	DWORD id;
+	DWORD use_safe_inject;
 
 
-	/* SetWindowsHookEx does not inject the library in to the target
-	 * process unless the event associated with it has occurred, so
-	 * repeatedly send the hook message to start the hook at small
-	 * intervals to signal to SetWindowsHookEx to process the message and
-	 * therefore inject the library in to the target process.  Repeating
-	 * this is mostly just a precaution. */
+	use_safe_inject = wcstol(argv[2], NULL, 10);
 
 
-	for (i = 0; i < RETRY_COUNT; i++) {
-		Sleep(RETRY_INTERVAL_MS);
-		PostThreadMessage(thread_id, WM_USER + 432, 0, (LPARAM)hook);
+	id = wcstol(argv[3], NULL, 10);
+	if (id == 0) {
+		return INJECT_ERROR_INVALID_PARAMS;
 	}
 	}
-	return 0;
+
+	return use_safe_inject
+		? inject_library_safe(id, dll)
+		: inject_library_full(id, dll);
 }
 }
 
 
-int main(int argc, char *argv[])
+int main(int argc, char *argv_ansi[])
 {
 {
-	DWORD thread_id;
+	wchar_t dll_path[MAX_PATH];
+	LPWSTR pCommandLineW;
+	LPWSTR *argv;
+	int ret = INJECT_ERROR_INVALID_PARAMS;
 
 
 	load_debug_privilege();
 	load_debug_privilege();
 
 
-	if (argc < 3) {
-		return -1;
-	}
-
-	thread_id = strtol(argv[2], NULL, 10);
-	if (thread_id == 0) {
-		return -2;
+	pCommandLineW = GetCommandLineW();
+	argv = CommandLineToArgvW(pCommandLineW, &argc);
+	if (argv && argc == 4) {
+		DWORD size = GetModuleFileNameW(NULL,
+				dll_path, MAX_PATH);
+		if (size) {
+			wchar_t *name_start = wcsrchr(dll_path, '\\');
+			if (name_start) {
+				*(++name_start) = 0;
+				wcscpy(name_start, argv[1]);
+				ret = inject_helper(argv, dll_path);
+			}
+		}
 	}
 	}
+	LocalFree(argv);
 
 
-	return inject_library_safe(thread_id, argv[1]);
+	return ret;
 }
 }