diff --git a/plugins/win-capture/CMakeLists.txt b/plugins/win-capture/CMakeLists.txt index ac250fb01..8c0e4bfa3 100644 --- a/plugins/win-capture/CMakeLists.txt +++ b/plugins/win-capture/CMakeLists.txt @@ -3,6 +3,7 @@ project(win-capture) set(win-capture_HEADERS obfuscate.h hook-helpers.h + inject-library.h cursor-capture.h graphics-hook-info.h window-helpers.h @@ -11,6 +12,7 @@ set(win-capture_HEADERS set(win-capture_SOURCES dc-capture.c obfuscate.c + inject-library.c cursor-capture.c game-capture.c window-helpers.c diff --git a/plugins/win-capture/data/locale/en-US.ini b/plugins/win-capture/data/locale/en-US.ini index b5a83f20e..28383e5a2 100644 --- a/plugins/win-capture/data/locale/en-US.ini +++ b/plugins/win-capture/data/locale/en-US.ini @@ -16,3 +16,4 @@ GameCapture.ForceScaling="Force Scaling" GameCapture.ScaleRes="Scale Resolution" GameCapture.LimitFramerate="Limit capture framerate" GameCapture.CaptureOverlays="Capture third-party overlays (such as steam)" +GameCapture.AntiCheatHook="Use anti-cheat compatibility hook" diff --git a/plugins/win-capture/game-capture.c b/plugins/win-capture/game-capture.c index 47e8e66f4..6e9b5cab4 100644 --- a/plugins/win-capture/game-capture.c +++ b/plugins/win-capture/game-capture.c @@ -6,6 +6,7 @@ #include #include #include "obfuscate.h" +#include "inject-library.h" #include "graphics-hook-info.h" #include "window-helpers.h" #include "cursor-capture.h" @@ -29,6 +30,7 @@ #define SETTING_TRANSPARENCY "allow_transparency" #define SETTING_LIMIT_FRAMERATE "limit_framerate" #define SETTING_CAPTURE_OVERLAYS "capture_overlays" +#define SETTING_ANTI_CHEAT_HOOK "anti_cheat_hook" #define TEXT_GAME_CAPTURE obs_module_text("GameCapture") #define TEXT_ANY_FULLSCREEN obs_module_text("GameCapture.AnyFullscreen") @@ -44,6 +46,7 @@ #define TEXT_CAPTURE_CURSOR obs_module_text("CaptureCursor") #define TEXT_LIMIT_FRAMERATE obs_module_text("GameCapture.LimitFramerate") #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 ERROR_RETRY_INTERVAL 4.0f @@ -62,6 +65,7 @@ struct game_capture_config { bool allow_transparency : 1; bool limit_framerate : 1; bool capture_overlays : 1; + bool anticheat_hook : 1; }; struct game_capture { @@ -249,6 +253,8 @@ static inline void get_config(struct game_capture_config *cfg, SETTING_LIMIT_FRAMERATE); cfg->capture_overlays = obs_data_get_bool(settings, 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); ret = sscanf(scale_str, "%"PRIu32"x%"PRIu32, @@ -554,23 +560,77 @@ static inline bool init_pipe(struct game_capture *gc) 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, - 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 *inject_path_w; - wchar_t *hook_path_w; + wchar_t *hook_dll_w; + bool anti_cheat = gc->config.anticheat_hook; PROCESS_INFORMATION pi = {0}; STARTUPINFO si = {0}; bool success = false; 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); - 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, 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); bfree(inject_path_w); - bfree(hook_path_w); + bfree(hook_dll_w); return success; } static inline bool inject_hook(struct game_capture *gc) { + bool matching_architecture; bool success = false; + const char *hook_dll; char *inject_path; char *hook_path; if (gc->process_is_64bit) { + hook_dll = "graphics-hook64.dll"; inject_path = obs_module_file("inject-helper64.exe"); - hook_path = obs_module_file("graphics-hook64.dll"); } else { + hook_dll = "graphics-hook32.dll"; 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")) { goto cleanup; } @@ -609,7 +673,20 @@ static inline bool inject_hook(struct game_capture *gc) 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: bfree(inject_path); @@ -1173,7 +1250,7 @@ static void game_capture_tick(void *data, float seconds) close_handle(&gc->injector_process); if (exit_code != 0) { - warn("inject process failed: %lu", exit_code); + warn("inject process failed: %ld", (long)exit_code); gc->error_acquiring = true; } 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_bool(settings, SETTING_LIMIT_FRAMERATE, 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, @@ -1481,11 +1559,14 @@ static obs_properties_t *game_capture_properties(void *data) obs_properties_add_bool(ppts, SETTING_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, TEXT_CAPTURE_OVERLAYS); - obs_properties_add_bool(ppts, SETTING_CURSOR, TEXT_CAPTURE_CURSOR); - UNUSED_PARAMETER(data); return ppts; } diff --git a/plugins/win-capture/inject-helper/CMakeLists.txt b/plugins/win-capture/inject-helper/CMakeLists.txt index 57c12429e..8c08c5e0a 100644 --- a/plugins/win-capture/inject-helper/CMakeLists.txt +++ b/plugins/win-capture/inject-helper/CMakeLists.txt @@ -1,9 +1,11 @@ project(inject-helper) set(inject-helper_HEADERS + ../inject-library.h ../obfuscate.h) set(inject-helper_SOURCES + ../inject-library.c ../obfuscate.c inject-helper.c) diff --git a/plugins/win-capture/inject-helper/inject-helper.c b/plugins/win-capture/inject-helper/inject-helper.c index 5fa5d2cba..a34778467 100644 --- a/plugins/win-capture/inject-helper/inject-helper.c +++ b/plugins/win-capture/inject-helper/inject-helper.c @@ -1,8 +1,17 @@ +#define _CRT_SECURE_NO_WARNINGS + #include +#include +#include #include #include #include #include "../obfuscate.h" +#include "../inject-library.h" + +#if defined(_MSC_VER) && !defined(inline) +#define inline __inline +#endif static void load_debug_privilege(void) { @@ -27,71 +36,88 @@ static void load_debug_privilege(void) CloseHandle(token); } -typedef HHOOK (WINAPI *set_windows_hook_ex_t)(int, HOOKPROC, HINSTANCE, DWORD); - -#define RETRY_INTERVAL_MS 500 -#define TOTAL_RETRY_TIME_MS 4000 -#define RETRY_COUNT (TOTAL_RETRY_TIME_MS / RETRY_INTERVAL_MS) - -static int inject_library_safe(DWORD thread_id, const char *dll) +static inline HANDLE open_process(DWORD desired_access, bool inherit_handle, + DWORD process_id) { - 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; + HANDLE (WINAPI *open_process_proc)(DWORD, BOOL, DWORD); + open_process_proc = get_obfuscated_func(GetModuleHandleW(L"KERNEL32"), + "HxjcQrmkb|~", 0xc82efdf78201df87); - if (!lib || !user32) { - return -3; - } - -#ifdef _WIN64 - proc = GetProcAddress(lib, "dummy_debug_proc"); -#else - proc = GetProcAddress(lib, "_dummy_debug_proc@12"); -#endif - - if (!proc) { - return -4; - } - - set_windows_hook_ex = get_obfuscated_func(user32, "[bs^fbkmwuKfmfOvI", - 0xEAD293602FCF9778ULL); - - hook = set_windows_hook_ex(WH_GETMESSAGE, proc, lib, thread_id); - if (!hook) { - return -5; - } - - /* 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. */ - - for (i = 0; i < RETRY_COUNT; i++) { - Sleep(RETRY_INTERVAL_MS); - PostThreadMessage(thread_id, WM_USER + 432, 0, (LPARAM)hook); - } - return 0; + return open_process_proc(desired_access, inherit_handle, process_id); } -int main(int argc, char *argv[]) +static inline int inject_library(HANDLE process, const wchar_t *dll) { - DWORD thread_id; + 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); +} + +static inline int inject_library_safe(DWORD thread_id, const wchar_t *dll) +{ + return inject_library_safe_obf(thread_id, dll, + "[bs^fbkmwuKfmfOvI", 0xEAD293602FCF9778ULL); +} + +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; + } + + return ret; +} + +static int inject_helper(wchar_t *argv[], const wchar_t *dll) +{ + DWORD id; + DWORD use_safe_inject; + + use_safe_inject = wcstol(argv[2], NULL, 10); + + id = wcstol(argv[3], NULL, 10); + if (id == 0) { + return INJECT_ERROR_INVALID_PARAMS; + } + + return use_safe_inject + ? inject_library_safe(id, dll) + : inject_library_full(id, dll); +} + +int main(int argc, char *argv_ansi[]) +{ + wchar_t dll_path[MAX_PATH]; + LPWSTR pCommandLineW; + LPWSTR *argv; + int ret = INJECT_ERROR_INVALID_PARAMS; load_debug_privilege(); - if (argc < 3) { - return -1; + 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); - thread_id = strtol(argv[2], NULL, 10); - if (thread_id == 0) { - return -2; - } - - return inject_library_safe(thread_id, argv[1]); + return ret; }