This library is a completely refactored and rewritten version of the original graphics hook. The code is more clean, readable, and has a variety of new features, such as scaling and forcing memory capture. Currently, only D3D9, 10, and 11 are implemented. (This commit may be updated on this branch)
905 lines
20 KiB
C
905 lines
20 KiB
C
#define _CRT_SECURE_NO_WARNINGS
|
|
#include <windows.h>
|
|
#include <shlobj.h>
|
|
#include <psapi.h>
|
|
#include <ipc-util/pipe.h>
|
|
#include "graphics-hook.h"
|
|
#include "../obfuscate.h"
|
|
#include "../funchook.h"
|
|
|
|
#define DEBUG_OUTPUT
|
|
|
|
#ifdef DEBUG_OUTPUT
|
|
#define DbgOut(x) OutputDebugStringA(x)
|
|
#else
|
|
#define DbgOut(x)
|
|
#endif
|
|
|
|
struct thread_data {
|
|
CRITICAL_SECTION mutexes[NUM_BUFFERS];
|
|
CRITICAL_SECTION data_mutex;
|
|
void *volatile cur_data;
|
|
uint8_t *shmem_textures[2];
|
|
HANDLE copy_thread;
|
|
HANDLE copy_event;
|
|
HANDLE stop_event;
|
|
volatile int cur_tex;
|
|
unsigned int pitch;
|
|
unsigned int cy;
|
|
volatile bool locked_textures[NUM_BUFFERS];
|
|
};
|
|
|
|
static ipc_pipe_client_t pipe = {0};
|
|
static HANDLE signal_restart = NULL;
|
|
static HANDLE signal_stop = NULL;
|
|
static HANDLE signal_ready = NULL;
|
|
static HANDLE signal_exit = NULL;
|
|
static HANDLE tex_mutexes[2] = {NULL, NULL};
|
|
static HANDLE filemap_hook_info = NULL;
|
|
|
|
static volatile bool stop_loop = false;
|
|
static HANDLE capture_thread = NULL;
|
|
static char system_path[MAX_PATH] = {0};
|
|
static char process_name[MAX_PATH] = {0};
|
|
static char keepalive_name[64] = {0};
|
|
|
|
static unsigned int shmem_id_counter = 0;
|
|
static void *shmem_info = NULL;
|
|
static HANDLE shmem_file_handle = 0;
|
|
|
|
static struct thread_data thread_data = {0};
|
|
|
|
volatile bool active = false;
|
|
struct hook_info *global_hook_info = NULL;
|
|
|
|
static inline void wait_for_dll_main_finish(HANDLE thread_handle)
|
|
{
|
|
if (thread_handle) {
|
|
WaitForSingleObject(thread_handle, 100);
|
|
CloseHandle(thread_handle);
|
|
}
|
|
}
|
|
|
|
static inline bool init_pipe(void)
|
|
{
|
|
char new_name[64];
|
|
sprintf(new_name, "%s%d", PIPE_NAME, GetCurrentProcessId());
|
|
|
|
if (!ipc_pipe_client_open(&pipe, new_name)) {
|
|
DbgOut("Failed to open pipe\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static HANDLE init_event(const char *name, DWORD pid)
|
|
{
|
|
HANDLE handle = get_event_plus_id(name, pid);
|
|
if (!handle)
|
|
hlog("Failed to get event '%s': %lu", name, GetLastError());
|
|
return handle;
|
|
}
|
|
|
|
static HANDLE init_mutex(const char *name, DWORD pid)
|
|
{
|
|
char new_name[64];
|
|
HANDLE handle;
|
|
|
|
sprintf(new_name, "%s%d", name, pid);
|
|
|
|
handle = OpenMutexA(MUTEX_ALL_ACCESS, false, new_name);
|
|
if (!handle)
|
|
hlog("Failed to open mutex '%s': %lu", name, GetLastError());
|
|
return handle;
|
|
}
|
|
|
|
static inline bool init_signals(void)
|
|
{
|
|
DWORD pid = GetCurrentProcessId();
|
|
|
|
signal_restart = init_event(EVENT_CAPTURE_RESTART, pid);
|
|
if (!signal_restart) {
|
|
return false;
|
|
}
|
|
|
|
signal_stop = init_event(EVENT_CAPTURE_STOP, pid);
|
|
if (!signal_stop) {
|
|
return false;
|
|
}
|
|
|
|
signal_ready = init_event(EVENT_HOOK_READY, pid);
|
|
if (!signal_ready) {
|
|
return false;
|
|
}
|
|
|
|
signal_exit = init_event(EVENT_HOOK_EXIT, pid);
|
|
if (!signal_exit) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool init_mutexes(void)
|
|
{
|
|
DWORD pid = GetCurrentProcessId();
|
|
|
|
tex_mutexes[0] = init_mutex(MUTEX_TEXTURE1, pid);
|
|
if (!tex_mutexes[0]) {
|
|
return false;
|
|
}
|
|
|
|
tex_mutexes[1] = init_mutex(MUTEX_TEXTURE2, pid);
|
|
if (!tex_mutexes[1]) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool init_system_path(void)
|
|
{
|
|
HRESULT hr = SHGetFolderPathA(NULL, CSIDL_SYSTEM, NULL,
|
|
SHGFP_TYPE_CURRENT, system_path);
|
|
if (hr != S_OK) {
|
|
hlog("Failed to get windows system path: %08lX", hr);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void log_current_process(void)
|
|
{
|
|
DWORD len = GetModuleBaseNameA(GetCurrentProcess(), NULL, process_name,
|
|
MAX_PATH);
|
|
if (len > 0) {
|
|
process_name[len] = 0;
|
|
hlog("Hooked to process: %s", process_name);
|
|
}
|
|
|
|
hlog("(half life scientist) everything.. seems to be in order");
|
|
}
|
|
|
|
static inline bool init_hook_info(void)
|
|
{
|
|
filemap_hook_info = get_hook_info(GetCurrentProcessId());
|
|
if (!filemap_hook_info) {
|
|
hlog("Failed to create hook info file mapping: %lu",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
global_hook_info = MapViewOfFile(filemap_hook_info, FILE_MAP_ALL_ACCESS,
|
|
0, 0, sizeof(struct hook_info));
|
|
if (!global_hook_info) {
|
|
hlog("Failed to map the hook info file mapping: %lu",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool init_hook(HANDLE thread_handle)
|
|
{
|
|
wait_for_dll_main_finish(thread_handle);
|
|
|
|
sprintf(keepalive_name, "%s%d", EVENT_HOOK_KEEPALIVE,
|
|
GetCurrentProcessId());
|
|
|
|
if (!init_pipe()) {
|
|
return false;
|
|
}
|
|
if (!init_signals()) {
|
|
return false;
|
|
}
|
|
if (!init_mutexes()) {
|
|
return false;
|
|
}
|
|
if (!init_system_path()) {
|
|
return false;
|
|
}
|
|
if (!init_hook_info()) {
|
|
return false;
|
|
}
|
|
|
|
log_current_process();
|
|
|
|
SetEvent(signal_restart);
|
|
return true;
|
|
}
|
|
|
|
static inline void close_handle(HANDLE *handle)
|
|
{
|
|
if (*handle) {
|
|
CloseHandle(*handle);
|
|
*handle = NULL;
|
|
}
|
|
}
|
|
|
|
static void free_hook(void)
|
|
{
|
|
if (filemap_hook_info) {
|
|
CloseHandle(filemap_hook_info);
|
|
filemap_hook_info = NULL;
|
|
}
|
|
if (global_hook_info) {
|
|
UnmapViewOfFile(global_hook_info);
|
|
global_hook_info = NULL;
|
|
}
|
|
|
|
close_handle(&tex_mutexes[1]);
|
|
close_handle(&tex_mutexes[0]);
|
|
close_handle(&signal_exit);
|
|
close_handle(&signal_ready);
|
|
close_handle(&signal_stop);
|
|
close_handle(&signal_restart);
|
|
ipc_pipe_client_free(&pipe);
|
|
}
|
|
|
|
static inline bool d3d8_hookable(void)
|
|
{
|
|
return !!global_hook_info->offsets.d3d8.present &&
|
|
!!global_hook_info->offsets.d3d8.reset;
|
|
}
|
|
|
|
static inline bool ddraw_hookable(void)
|
|
{
|
|
return !!global_hook_info->offsets.ddraw.surface_create &&
|
|
!!global_hook_info->offsets.ddraw.surface_restore &&
|
|
!!global_hook_info->offsets.ddraw.surface_release &&
|
|
!!global_hook_info->offsets.ddraw.surface_unlock &&
|
|
!!global_hook_info->offsets.ddraw.surface_blt &&
|
|
!!global_hook_info->offsets.ddraw.surface_flip &&
|
|
!!global_hook_info->offsets.ddraw.surface_set_palette &&
|
|
!!global_hook_info->offsets.ddraw.palette_set_entries;
|
|
}
|
|
|
|
static inline bool d3d9_hookable(void)
|
|
{
|
|
return !!global_hook_info->offsets.d3d9.present &&
|
|
!!global_hook_info->offsets.d3d9.present_ex &&
|
|
!!global_hook_info->offsets.d3d9.present_swap &&
|
|
!!global_hook_info->offsets.d3d9.reset &&
|
|
!!global_hook_info->offsets.d3d9.reset_ex;
|
|
}
|
|
|
|
static inline bool dxgi_hookable(void)
|
|
{
|
|
return !!global_hook_info->offsets.dxgi.present &&
|
|
!!global_hook_info->offsets.dxgi.resize;
|
|
}
|
|
|
|
static inline bool attempt_hook(void)
|
|
{
|
|
static bool ddraw_hooked = false;
|
|
static bool d3d8_hooked = false;
|
|
static bool d3d9_hooked = false;
|
|
static bool dxgi_hooked = false;
|
|
static bool gl_hooked = false;
|
|
|
|
if (!d3d9_hooked) {
|
|
if (!d3d9_hookable()) {
|
|
d3d9_hooked = true;
|
|
} else {
|
|
d3d9_hooked = hook_d3d9();
|
|
if (d3d9_hooked) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!dxgi_hooked) {
|
|
if (!dxgi_hookable()) {
|
|
dxgi_hooked = true;
|
|
} else {
|
|
dxgi_hooked = hook_dxgi();
|
|
if (dxgi_hooked) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!gl_hooked) {
|
|
gl_hooked = hook_gl();
|
|
if (gl_hooked) {
|
|
return true;
|
|
}
|
|
/*} else {
|
|
rehook_gl();*/
|
|
}
|
|
|
|
/*if (!d3d8_hooked) {
|
|
if (!d3d8_hookable()) {
|
|
d3d8_hooked = true;
|
|
} else {
|
|
d3d8_hooked = hook_d3d8();
|
|
if (d3d8_hooked) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ddraw_hooked) {
|
|
if (!ddraw_hookable()) {
|
|
ddraw_hooked = true;
|
|
} else {
|
|
ddraw_hooked = hook_ddraw();
|
|
if (ddraw_hooked) {
|
|
return true;
|
|
}
|
|
}
|
|
}*/
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline void capture_loop(void)
|
|
{
|
|
while (!attempt_hook())
|
|
Sleep(40);
|
|
|
|
for (size_t n = 0; !stop_loop; n++) {
|
|
/* this causes it to check every 4 seconds, but still with
|
|
* a small sleep interval in case the thread needs to stop */
|
|
if (n % 100 == 0) attempt_hook();
|
|
Sleep(40);
|
|
}
|
|
}
|
|
|
|
static DWORD WINAPI main_capture_thread(HANDLE thread_handle)
|
|
{
|
|
if (!init_hook(thread_handle)) {
|
|
DbgOut("Failed to init hook\n");
|
|
free_hook();
|
|
return 0;
|
|
}
|
|
|
|
capture_loop();
|
|
return 0;
|
|
}
|
|
|
|
static inline void hlogv(const char *format, va_list args)
|
|
{
|
|
char message[1024] = "";
|
|
int num = _vsprintf_p(message, 1024, format, args);
|
|
if (num) {
|
|
if (!ipc_pipe_client_write(&pipe, message, num + 1)) {
|
|
ipc_pipe_client_free(&pipe);
|
|
}
|
|
DbgOut(message);
|
|
DbgOut("\n");
|
|
}
|
|
}
|
|
|
|
void hlog(const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
hlogv(format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void hlog_hr(const char *text, HRESULT hr)
|
|
{
|
|
LPSTR buffer = NULL;
|
|
|
|
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, hr, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
|
(LPSTR)&buffer, 0, NULL);
|
|
|
|
if (buffer) {
|
|
hlog("%s (0x%08lX): %s", text, hr, buffer);
|
|
LocalFree(buffer);
|
|
} else {
|
|
hlog("%s (0x%08lX)", text, hr);
|
|
}
|
|
}
|
|
|
|
inline const char *get_process_name(void)
|
|
{
|
|
return process_name;
|
|
}
|
|
|
|
inline HMODULE get_system_module(const char *module)
|
|
{
|
|
char base_path[MAX_PATH];
|
|
|
|
strcpy(base_path, system_path);
|
|
strcat(base_path, "\\");
|
|
strcat(base_path, module);
|
|
return GetModuleHandleA(module);
|
|
}
|
|
|
|
inline HMODULE load_system_library(const char *name)
|
|
{
|
|
char base_path[MAX_PATH];
|
|
HMODULE module;
|
|
|
|
strcpy(base_path, system_path);
|
|
strcat(base_path, "\\");
|
|
strcat(base_path, name);
|
|
|
|
module = GetModuleHandleA(base_path);
|
|
if (module)
|
|
return module;
|
|
|
|
return LoadLibraryA(base_path);
|
|
}
|
|
|
|
static inline bool capture_alive(void)
|
|
{
|
|
HANDLE event = OpenEventA(EVENT_ALL_ACCESS, false, keepalive_name);
|
|
if (event) {
|
|
CloseHandle(event);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool capture_active(void)
|
|
{
|
|
return active;
|
|
}
|
|
|
|
static inline bool capture_stopped(void)
|
|
{
|
|
return WaitForSingleObject(signal_stop, 0) == WAIT_OBJECT_0;
|
|
}
|
|
|
|
static inline bool capture_restarted(void)
|
|
{
|
|
return WaitForSingleObject(signal_restart, 0) == WAIT_OBJECT_0;
|
|
}
|
|
|
|
inline bool capture_should_stop(void)
|
|
{
|
|
bool stop_requested = false;
|
|
|
|
if (capture_active())
|
|
stop_requested = capture_stopped() || !capture_alive();
|
|
|
|
return stop_requested;
|
|
}
|
|
|
|
inline bool capture_should_init(void)
|
|
{
|
|
if (!capture_active() && capture_restarted()) {
|
|
if (capture_alive()) {
|
|
if (!ipc_pipe_client_valid(&pipe)) {
|
|
init_pipe();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline uint64_t get_clockfreq(void)
|
|
{
|
|
static bool have_clockfreq = false;
|
|
static LARGE_INTEGER clock_freq;
|
|
|
|
if (!have_clockfreq) {
|
|
QueryPerformanceFrequency(&clock_freq);
|
|
have_clockfreq = true;
|
|
}
|
|
|
|
return clock_freq.QuadPart;
|
|
}
|
|
|
|
uint64_t os_gettime_ns(void)
|
|
{
|
|
LARGE_INTEGER current_time;
|
|
double time_val;
|
|
|
|
QueryPerformanceCounter(¤t_time);
|
|
time_val = (double)current_time.QuadPart;
|
|
time_val *= 1000000000.0;
|
|
time_val /= (double)get_clockfreq();
|
|
|
|
return (uint64_t)time_val;
|
|
}
|
|
|
|
inline int try_lock_shmem_tex(int id)
|
|
{
|
|
int next = id == 0 ? 1 : 0;
|
|
|
|
if (WaitForSingleObject(tex_mutexes[id], 0) == WAIT_OBJECT_0) {
|
|
return id;
|
|
} else if (WaitForSingleObject(tex_mutexes[next], 0) == WAIT_OBJECT_0) {
|
|
return next;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
inline void unlock_shmem_tex(int id)
|
|
{
|
|
if (id != -1) {
|
|
ReleaseMutex(tex_mutexes[id]);
|
|
}
|
|
}
|
|
|
|
static inline bool frame_ready(uint64_t interval)
|
|
{
|
|
static uint64_t last_time = 0;
|
|
uint64_t elapsed;
|
|
uint64_t t;
|
|
|
|
if (!interval) {
|
|
return true;
|
|
}
|
|
|
|
t = os_gettime_ns();
|
|
elapsed = t - last_time;
|
|
|
|
if (elapsed < interval) {
|
|
return false;
|
|
}
|
|
|
|
last_time = (elapsed > interval * 2) ? t : last_time + interval;
|
|
return true;
|
|
}
|
|
|
|
inline bool capture_ready(void)
|
|
{
|
|
return capture_active() &&
|
|
frame_ready(global_hook_info->frame_interval);
|
|
}
|
|
|
|
static inline bool init_shared_info(size_t size)
|
|
{
|
|
char name[64];
|
|
sprintf_s(name, 64, "%s%u", SHMEM_TEXTURE, ++shmem_id_counter);
|
|
|
|
shmem_file_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL,
|
|
PAGE_READWRITE, 0, (DWORD)size, name);
|
|
if (!shmem_file_handle) {
|
|
hlog("init_shared_info: Failed to create shared memory: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
shmem_info = MapViewOfFile(shmem_file_handle, FILE_MAP_ALL_ACCESS,
|
|
0, 0, size);
|
|
if (!shmem_info) {
|
|
hlog("init_shared_info: Failed to map shared memory: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool capture_init_shtex(struct shtex_data **data, HWND window,
|
|
uint32_t base_cx, uint32_t base_cy, uint32_t cx, uint32_t cy,
|
|
uint32_t format, bool flip, uint32_t handle)
|
|
{
|
|
if (!init_shared_info(sizeof(struct shtex_data))) {
|
|
hlog("capture_init_shtex: Failed to initialize memory");
|
|
return false;
|
|
}
|
|
|
|
*data = shmem_info;
|
|
(*data)->tex_handle = handle;
|
|
|
|
global_hook_info->window = (uint32_t)window;
|
|
global_hook_info->type = CAPTURE_TYPE_TEXTURE;
|
|
global_hook_info->format = format;
|
|
global_hook_info->flip = flip;
|
|
global_hook_info->map_id = shmem_id_counter;
|
|
global_hook_info->map_size = sizeof(struct shtex_data);
|
|
global_hook_info->cx = cx;
|
|
global_hook_info->cy = cy;
|
|
global_hook_info->base_cx = base_cx;
|
|
global_hook_info->base_cy = base_cy;
|
|
|
|
if (!SetEvent(signal_ready)) {
|
|
hlog("capture_init_shtex: Failed to signal ready: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
active = true;
|
|
return true;
|
|
}
|
|
|
|
static DWORD CALLBACK copy_thread(LPVOID unused)
|
|
{
|
|
uint32_t pitch = thread_data.pitch;
|
|
uint32_t cy = thread_data.cy;
|
|
HANDLE events[2] = {NULL, NULL};
|
|
int shmem_id = 0;
|
|
|
|
if (!duplicate_handle(&events[0], thread_data.copy_event)) {
|
|
hlog_hr("copy_thread: Failed to duplicate copy event: %d",
|
|
GetLastError());
|
|
return 0;
|
|
}
|
|
|
|
if (!duplicate_handle(&events[1], thread_data.stop_event)) {
|
|
hlog_hr("copy_thread: Failed to duplicate stop event: %d",
|
|
GetLastError());
|
|
goto finish;
|
|
}
|
|
|
|
for (;;) {
|
|
int copy_tex;
|
|
void *cur_data;
|
|
|
|
DWORD ret = WaitForMultipleObjects(2, events, false, INFINITE);
|
|
if (ret != WAIT_OBJECT_0) {
|
|
break;
|
|
}
|
|
|
|
EnterCriticalSection(&thread_data.data_mutex);
|
|
copy_tex = thread_data.cur_tex;
|
|
cur_data = thread_data.cur_data;
|
|
LeaveCriticalSection(&thread_data.data_mutex);
|
|
|
|
if (copy_tex < NUM_BUFFERS && !!cur_data) {
|
|
EnterCriticalSection(&thread_data.mutexes[copy_tex]);
|
|
|
|
int lock_id = try_lock_shmem_tex(shmem_id);
|
|
if (lock_id != -1) {
|
|
memcpy(thread_data.shmem_textures[lock_id],
|
|
cur_data, pitch * cy);
|
|
|
|
unlock_shmem_tex(lock_id);
|
|
((struct shmem_data*)shmem_info)->last_tex =
|
|
lock_id;
|
|
|
|
shmem_id = lock_id == 0 ? 1 : 0;
|
|
}
|
|
|
|
LeaveCriticalSection(&thread_data.mutexes[copy_tex]);
|
|
}
|
|
}
|
|
|
|
finish:
|
|
for (size_t i = 0; i < 2; i++) {
|
|
if (events[i]) {
|
|
CloseHandle(events[i]);
|
|
}
|
|
}
|
|
|
|
(void)unused;
|
|
return 0;
|
|
}
|
|
|
|
inline void shmem_copy_data(size_t idx, void *volatile data)
|
|
{
|
|
EnterCriticalSection(&thread_data.data_mutex);
|
|
thread_data.cur_tex = (int)idx;
|
|
thread_data.cur_data = data;
|
|
thread_data.locked_textures[idx] = true;
|
|
LeaveCriticalSection(&thread_data.data_mutex);
|
|
|
|
SetEvent(thread_data.copy_event);
|
|
}
|
|
|
|
inline bool shmem_texture_data_lock(int idx)
|
|
{
|
|
bool locked;
|
|
|
|
EnterCriticalSection(&thread_data.data_mutex);
|
|
locked = thread_data.locked_textures[idx];
|
|
LeaveCriticalSection(&thread_data.data_mutex);
|
|
|
|
if (locked) {
|
|
EnterCriticalSection(&thread_data.mutexes[idx]);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline void shmem_texture_data_unlock(int idx)
|
|
{
|
|
EnterCriticalSection(&thread_data.data_mutex);
|
|
thread_data.locked_textures[idx] = false;
|
|
LeaveCriticalSection(&thread_data.data_mutex);
|
|
|
|
LeaveCriticalSection(&thread_data.mutexes[idx]);
|
|
}
|
|
|
|
static inline bool init_shmem_thread(uint32_t pitch, uint32_t cy)
|
|
{
|
|
struct shmem_data *data = shmem_info;
|
|
|
|
thread_data.pitch = pitch;
|
|
thread_data.cy = cy;
|
|
thread_data.shmem_textures[0] = (uint8_t*)data + data->tex1_offset;
|
|
thread_data.shmem_textures[1] = (uint8_t*)data + data->tex2_offset;
|
|
|
|
thread_data.copy_event = CreateEvent(NULL, false, false, NULL);
|
|
if (!thread_data.copy_event) {
|
|
hlog("init_shmem_thread: Failed to create copy event: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
thread_data.stop_event = CreateEvent(NULL, true, false, NULL);
|
|
if (!thread_data.stop_event) {
|
|
hlog("init_shmem_thread: Failed to create stop event: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < NUM_BUFFERS; i++) {
|
|
InitializeCriticalSection(&thread_data.mutexes[i]);
|
|
}
|
|
|
|
InitializeCriticalSection(&thread_data.data_mutex);
|
|
|
|
thread_data.copy_thread = CreateThread(NULL, 0, copy_thread, NULL, 0,
|
|
NULL);
|
|
if (!thread_data.copy_thread) {
|
|
hlog("init_shmem_thread: Failed to create thread: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifndef ALIGN
|
|
#define ALIGN(bytes, align) (((bytes) + ((align) - 1)) & ~((align) - 1))
|
|
#endif
|
|
|
|
bool capture_init_shmem(struct shmem_data **data, HWND window,
|
|
uint32_t base_cx, uint32_t base_cy, uint32_t cx, uint32_t cy,
|
|
uint32_t pitch, uint32_t format, bool flip)
|
|
{
|
|
uint32_t tex_size = cy * pitch;
|
|
uint32_t aligned_header = ALIGN(sizeof(struct shmem_data), 32);
|
|
uint32_t aligned_tex = ALIGN(tex_size, 32);
|
|
uint32_t total_size = aligned_header + aligned_tex * 2;
|
|
uintptr_t align_pos;
|
|
|
|
if (!init_shared_info(total_size)) {
|
|
hlog("capture_init_shmem: Failed to initialize memory");
|
|
return false;
|
|
}
|
|
|
|
*data = shmem_info;
|
|
|
|
/* to ensure fast copy rate, align texture data to 256bit addresses */
|
|
align_pos = (uintptr_t)shmem_info;
|
|
align_pos += aligned_header;
|
|
align_pos &= ~(32 - 1);
|
|
align_pos -= (uintptr_t)shmem_info;
|
|
|
|
(*data)->last_tex = -1;
|
|
(*data)->tex1_offset = (uint32_t)align_pos;
|
|
(*data)->tex2_offset = (*data)->tex1_offset + aligned_tex;
|
|
|
|
global_hook_info->window = (uint32_t)window;
|
|
global_hook_info->type = CAPTURE_TYPE_MEMORY;
|
|
global_hook_info->format = format;
|
|
global_hook_info->flip = flip;
|
|
global_hook_info->map_id = shmem_id_counter;
|
|
global_hook_info->map_size = total_size;
|
|
global_hook_info->pitch = pitch;
|
|
global_hook_info->cx = cx;
|
|
global_hook_info->cy = cy;
|
|
global_hook_info->base_cx = base_cx;
|
|
global_hook_info->base_cy = base_cy;
|
|
|
|
if (!init_shmem_thread(pitch, cy)) {
|
|
return false;
|
|
}
|
|
|
|
if (!SetEvent(signal_ready)) {
|
|
hlog("capture_init_shmem: Failed to signal ready: %d",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
active = true;
|
|
return true;
|
|
}
|
|
|
|
static inline void thread_data_free(void)
|
|
{
|
|
if (thread_data.copy_thread) {
|
|
DWORD ret;
|
|
|
|
SetEvent(thread_data.stop_event);
|
|
ret = WaitForSingleObject(thread_data.copy_thread, 500);
|
|
if (ret != WAIT_OBJECT_0)
|
|
TerminateThread(thread_data.copy_thread, (DWORD)-1);
|
|
|
|
CloseHandle(thread_data.copy_thread);
|
|
}
|
|
if (thread_data.stop_event)
|
|
CloseHandle(thread_data.stop_event);
|
|
if (thread_data.copy_event)
|
|
CloseHandle(thread_data.copy_event);
|
|
for (size_t i = 0; i < NUM_BUFFERS; i++)
|
|
DeleteCriticalSection(&thread_data.mutexes[i]);
|
|
|
|
DeleteCriticalSection(&thread_data.data_mutex);
|
|
|
|
memset(&thread_data, 0, sizeof(thread_data));
|
|
}
|
|
|
|
void capture_free(void)
|
|
{
|
|
thread_data_free();
|
|
|
|
if (shmem_info) {
|
|
UnmapViewOfFile(shmem_info);
|
|
shmem_info = NULL;
|
|
}
|
|
|
|
close_handle(&shmem_file_handle);
|
|
|
|
SetEvent(signal_restart);
|
|
active = false;
|
|
}
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID unused1)
|
|
{
|
|
if (reason == DLL_PROCESS_ATTACH) {
|
|
wchar_t name[MAX_PATH];
|
|
|
|
HANDLE cur_thread = OpenThread(THREAD_ALL_ACCESS, false,
|
|
GetCurrentThreadId());
|
|
|
|
/* this prevents the library from being automatically unloaded
|
|
* by the next FreeLibrary call */
|
|
GetModuleFileNameW(hinst, name, MAX_PATH);
|
|
LoadLibraryW(name);
|
|
|
|
capture_thread = CreateThread(NULL, 0,
|
|
(LPTHREAD_START_ROUTINE)main_capture_thread,
|
|
(LPVOID)cur_thread, 0, 0);
|
|
if (!capture_thread) {
|
|
CloseHandle(cur_thread);
|
|
return false;
|
|
}
|
|
|
|
} else if (reason == DLL_PROCESS_DETACH) {
|
|
if (capture_thread) {
|
|
stop_loop = true;
|
|
WaitForSingleObject(capture_thread, 300);
|
|
CloseHandle(capture_thread);
|
|
}
|
|
|
|
free_hook();
|
|
}
|
|
|
|
(void)unused1;
|
|
return true;
|
|
}
|
|
|
|
__declspec(dllexport) LRESULT CALLBACK dummy_debug_proc(int code,
|
|
WPARAM wparam, LPARAM lparam)
|
|
{
|
|
static bool hooking = true;
|
|
MSG *msg = (MSG*)lparam;
|
|
|
|
if (hooking && msg->message == (WM_USER + 432)) {
|
|
HMODULE user32 = GetModuleHandleW(L"USER32");
|
|
BOOL (WINAPI *unhook_windows_hook_ex)(HHOOK) = NULL;
|
|
|
|
unhook_windows_hook_ex = get_obfuscated_func(user32,
|
|
"VojeleY`bdgxvM`hhDz",
|
|
0x7F55F80C9EE3A213ULL);
|
|
|
|
if (unhook_windows_hook_ex)
|
|
unhook_windows_hook_ex((HHOOK)msg->lParam);
|
|
hooking = false;
|
|
}
|
|
|
|
return CallNextHookEx(0, code, wparam, lparam);
|
|
}
|