#define _CRT_SECURE_NO_WARNINGS #ifdef _MSC_VER #pragma warning(disable : 4214) /* nonstandard extension, non-int bitfield */ #pragma warning(disable : 4054) /* function pointer to data pointer */ #endif #include #define COBJMACROS #include #include #include "gl-decs.h" #include "graphics-hook.h" #include "../funchook.h" #define DUMMY_WINDOW_CLASS_NAME L"graphics_hook_gl_dummy_window" static const GUID GUID_IDXGIFactory1 = {0x770aae78, 0xf26f, 0x4dba, {0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87}}; static const GUID GUID_IDXGIResource = {0x035f3ab4, 0x482e, 0x4e50, {0xb4, 0x1f, 0x8a, 0x7f, 0x8b, 0xd8, 0x96, 0x0b}}; static struct func_hook swap_buffers; static struct func_hook wgl_swap_layer_buffers; static struct func_hook wgl_swap_buffers; static struct func_hook wgl_delete_context; static bool darkest_dungeon_fix = false; struct gl_data { HDC hdc; uint32_t base_cx; uint32_t base_cy; uint32_t cx; uint32_t cy; DXGI_FORMAT format; GLuint fbo; bool using_shtex : 1; bool using_scale : 1; bool shmem_fallback : 1; union { /* shared texture */ struct { struct shtex_data *shtex_info; ID3D11Device *d3d11_device; ID3D11DeviceContext *d3d11_context; ID3D11Texture2D *d3d11_tex; IDXGISwapChain *dxgi_swap; HANDLE gl_device; HANDLE gl_dxobj; HANDLE handle; HWND hwnd; GLuint texture; }; /* shared memory */ struct { struct shmem_data *shmem_info; int cur_tex; int copy_wait; GLuint pbos[NUM_BUFFERS]; GLuint textures[NUM_BUFFERS]; bool texture_ready[NUM_BUFFERS]; bool texture_mapped[NUM_BUFFERS]; }; }; }; static HMODULE gl = NULL; static bool nv_capture_available = false; static struct gl_data data = {0}; static inline bool gl_error(const char *func, const char *str) { GLenum error = glGetError(); if (error != 0) { hlog("%s: %s: %lu", func, str, error); return true; } return false; } static void gl_free(void) { capture_free(); if (data.using_shtex) { if (data.gl_dxobj) jimglDXUnregisterObjectNV(data.gl_device, data.gl_dxobj); if (data.gl_device) jimglDXCloseDeviceNV(data.gl_device); if (data.texture) glDeleteTextures(1, &data.texture); if (data.d3d11_tex) ID3D11Resource_Release(data.d3d11_tex); if (data.d3d11_context) ID3D11DeviceContext_Release(data.d3d11_context); if (data.d3d11_device) ID3D11Device_Release(data.d3d11_device); if (data.dxgi_swap) IDXGISwapChain_Release(data.dxgi_swap); if (data.hwnd) DestroyWindow(data.hwnd); } else { for (size_t i = 0; i < NUM_BUFFERS; i++) { if (data.pbos[i]) { if (data.texture_mapped[i]) { glBindBuffer(GL_PIXEL_PACK_BUFFER, data.pbos[i]); glUnmapBuffer(GL_PIXEL_PACK_BUFFER); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); } glDeleteBuffers(1, &data.pbos[i]); } if (data.textures[i]) glDeleteTextures(1, &data.textures[i]); } } if (data.fbo) glDeleteFramebuffers(1, &data.fbo); gl_error("gl_free", "GL error occurred on free"); memset(&data, 0, sizeof(data)); hlog("------------------ gl capture freed ------------------"); } static inline void *base_get_proc(const char *name) { return (void*)GetProcAddress(gl, name); } static inline void *wgl_get_proc(const char *name) { return (void*)jimglGetProcAddress(name); } static inline void *get_proc(const char *name) { void *func = wgl_get_proc(name); if (!func) func = base_get_proc(name); return func; } static void init_nv_functions(void) { jimglDXSetResourceShareHandleNV = get_proc("wglDXSetResourceShareHandleNV"); jimglDXOpenDeviceNV = get_proc("wglDXOpenDeviceNV"); jimglDXCloseDeviceNV = get_proc("wglDXCloseDeviceNV"); jimglDXRegisterObjectNV = get_proc("wglDXRegisterObjectNV"); jimglDXUnregisterObjectNV = get_proc("wglDXUnregisterObjectNV"); jimglDXObjectAccessNV = get_proc("wglDXObjectAccessNV"); jimglDXLockObjectsNV = get_proc("wglDXLockObjectsNV"); jimglDXUnlockObjectsNV = get_proc("wglDXUnlockObjectsNV"); nv_capture_available = !!jimglDXSetResourceShareHandleNV && !!jimglDXOpenDeviceNV && !!jimglDXCloseDeviceNV && !!jimglDXRegisterObjectNV && !!jimglDXUnregisterObjectNV && !!jimglDXObjectAccessNV && !!jimglDXLockObjectsNV && !!jimglDXUnlockObjectsNV; if (nv_capture_available) hlog("Shared-texture OpenGL capture available"); } #define GET_PROC(cur_func, ptr, func) \ do { \ ptr = get_proc(#func); \ if (!ptr) { \ hlog("%s: failed to get function '%s'", #cur_func, \ #func); \ success = false; \ } \ } while (false) static bool init_gl_functions(void) { bool success = true; jimglGetProcAddress = base_get_proc("wglGetProcAddress"); if (!jimglGetProcAddress) { hlog("init_gl_functions: failed to get wglGetProcAddress"); return false; } GET_PROC(init_gl_functions, jimglMakeCurrent, wglMakeCurrent); GET_PROC(init_gl_functions, jimglGetCurrentDC, wglGetCurrentDC); GET_PROC(init_gl_functions, jimglGetCurrentContext, wglGetCurrentContext); GET_PROC(init_gl_functions, glTexImage2D, glTexImage2D); GET_PROC(init_gl_functions, glReadBuffer, glReadBuffer); GET_PROC(init_gl_functions, glGetTexImage, glGetTexImage); GET_PROC(init_gl_functions, glDrawBuffer, glDrawBuffer); GET_PROC(init_gl_functions, glGetError, glGetError); GET_PROC(init_gl_functions, glBufferData, glBufferData); GET_PROC(init_gl_functions, glDeleteBuffers, glDeleteBuffers); GET_PROC(init_gl_functions, glDeleteTextures, glDeleteTextures); GET_PROC(init_gl_functions, glGenBuffers, glGenBuffers); GET_PROC(init_gl_functions, glGenTextures, glGenTextures); GET_PROC(init_gl_functions, glMapBuffer, glMapBuffer); GET_PROC(init_gl_functions, glUnmapBuffer, glUnmapBuffer); GET_PROC(init_gl_functions, glBindBuffer, glBindBuffer); GET_PROC(init_gl_functions, glGetIntegerv, glGetIntegerv); GET_PROC(init_gl_functions, glBindTexture, glBindTexture); GET_PROC(init_gl_functions, glGenFramebuffers, glGenFramebuffers); GET_PROC(init_gl_functions, glDeleteFramebuffers, glDeleteFramebuffers); GET_PROC(init_gl_functions, glBindFramebuffer, glBindFramebuffer); GET_PROC(init_gl_functions, glBlitFramebuffer, glBlitFramebuffer); GET_PROC(init_gl_functions, glFramebufferTexture2D, glFramebufferTexture2D); init_nv_functions(); return success; } static void get_window_size(HDC hdc, uint32_t *cx, uint32_t *cy) { HWND hwnd = WindowFromDC(hdc); RECT rc = {0}; if (darkest_dungeon_fix) { *cx = 1920; *cy = 1080; } else { GetClientRect(hwnd, &rc); *cx = rc.right; *cy = rc.bottom; } } static inline bool gl_shtex_init_window(void) { data.hwnd = CreateWindowExW(0, DUMMY_WINDOW_CLASS_NAME, L"Dummy GL window, ignore", WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 2, 2, NULL, NULL, GetModuleHandle(NULL), NULL); if (!data.hwnd) { hlog("gl_shtex_init_window: failed to create window: %d", GetLastError()); return false; } return true; } typedef HRESULT (WINAPI *create_dxgi_factory1_t)(REFIID, void **); static const D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, }; static inline bool gl_shtex_init_d3d11(void) { D3D_FEATURE_LEVEL level_used; IDXGIFactory1 *factory; IDXGIAdapter *adapter; HRESULT hr; HMODULE d3d11 = load_system_library("d3d11.dll"); if (!d3d11) { hlog("gl_shtex_init_d3d11: failed to load D3D11.dll: %d", GetLastError()); return false; } HMODULE dxgi = load_system_library("dxgi.dll"); if (!dxgi) { hlog("gl_shtex_init_d3d11: failed to load DXGI.dll: %d", GetLastError()); return false; } DXGI_SWAP_CHAIN_DESC desc = {0}; desc.BufferCount = 2; desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.BufferDesc.Width = 2; desc.BufferDesc.Height = 2; desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.SampleDesc.Count = 1; desc.Windowed = true; desc.OutputWindow = data.hwnd; create_dxgi_factory1_t create_factory = (void*)GetProcAddress(dxgi, "CreateDXGIFactory1"); if (!create_factory) { hlog("gl_shtex_init_d3d11: failed to load CreateDXGIFactory1 " "procedure: %d", GetLastError()); return false; } PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN create = (void*)GetProcAddress( d3d11, "D3D11CreateDeviceAndSwapChain"); if (!create) { hlog("gl_shtex_init_d3d11: failed to load " "D3D11CreateDeviceAndSwapChain procedure: %d", GetLastError()); return false; } hr = create_factory(&GUID_IDXGIFactory1, (void**)&factory); if (FAILED(hr)) { hlog_hr("gl_shtex_init_d3d11: failed to create factory", hr); return false; } hr = IDXGIFactory1_EnumAdapters1(factory, 0, (IDXGIAdapter1**)&adapter); IDXGIFactory1_Release(factory); if (FAILED(hr)) { hlog_hr("gl_shtex_init_d3d11: failed to create adapter", hr); return false; } hr = create(adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, feature_levels, sizeof(feature_levels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, &desc, &data.dxgi_swap, &data.d3d11_device, &level_used, &data.d3d11_context); IDXGIAdapter_Release(adapter); if (FAILED(hr)) { hlog_hr("gl_shtex_init_d3d11: failed to create device", hr); return false; } return true; } static inline bool gl_shtex_init_d3d11_tex(void) { IDXGIResource *dxgi_res; HRESULT hr; D3D11_TEXTURE2D_DESC desc = {0}; desc.Width = data.cx; desc.Height = data.cy; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; hr = ID3D11Device_CreateTexture2D(data.d3d11_device, &desc, NULL, &data.d3d11_tex); if (FAILED(hr)) { hlog_hr("gl_shtex_init_d3d11_tex: failed to create texture", hr); return false; } hr = ID3D11Device_QueryInterface(data.d3d11_tex, &GUID_IDXGIResource, (void**)&dxgi_res); if (FAILED(hr)) { hlog_hr("gl_shtex_init_d3d11_tex: failed to get IDXGIResource", hr); return false; } hr = IDXGIResource_GetSharedHandle(dxgi_res, &data.handle); IDXGIResource_Release(dxgi_res); if (FAILED(hr)) { hlog_hr("gl_shtex_init_d3d11_tex: failed to get shared handle", hr); return false; } return true; } static inline bool gl_shtex_init_gl_tex(void) { data.gl_device = jimglDXOpenDeviceNV(data.d3d11_device); if (!data.gl_device) { hlog("gl_shtex_init_gl_tex: failed to open device"); return false; } glGenTextures(1, &data.texture); if (gl_error("gl_shtex_init_gl_tex", "failed to generate texture")) { return false; } data.gl_dxobj = jimglDXRegisterObjectNV(data.gl_device, data.d3d11_tex, data.texture, GL_TEXTURE_2D, WGL_ACCESS_WRITE_DISCARD_NV); if (!data.gl_dxobj) { hlog("gl_shtex_init_gl_tex: failed to register object"); return false; } return true; } static inline bool gl_init_fbo(void) { glGenFramebuffers(1, &data.fbo); return !gl_error("gl_init_fbo", "failed to initialize FBO"); } static bool gl_shtex_init(HWND window) { if (!gl_shtex_init_window()) { return false; } if (!gl_shtex_init_d3d11()) { return false; } if (!gl_shtex_init_d3d11_tex()) { return false; } if (!gl_shtex_init_gl_tex()) { return false; } if (!gl_init_fbo()) { return false; } if (!capture_init_shtex(&data.shtex_info, window, data.base_cx, data.base_cy, data.cx, data.cy, data.format, true, (uintptr_t)data.handle)) { return false; } hlog("gl shared texture capture successful"); return true; } static inline bool gl_shmem_init_data(size_t idx, size_t size) { glBindBuffer(GL_PIXEL_PACK_BUFFER, data.pbos[idx]); if (gl_error("gl_shmem_init_data", "failed to bind pbo")) { return false; } glBufferData(GL_PIXEL_PACK_BUFFER, size, 0, GL_STREAM_READ); if (gl_error("gl_shmem_init_data", "failed to set pbo data")) { return false; } glBindTexture(GL_TEXTURE_2D, data.textures[idx]); if (gl_error("gl_shmem_init_data", "failed to set bind texture")) { return false; } glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, data.cx, data.cy, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); if (gl_error("gl_shmem_init_data", "failed to set texture data")) { return false; } return true; } static inline bool gl_shmem_init_buffers(void) { uint32_t size = data.cx * data.cy * 4; GLint last_pbo; GLint last_tex; glGenBuffers(NUM_BUFFERS, data.pbos); if (gl_error("gl_shmem_init_buffers", "failed to generate buffers")) { return false; } glGenTextures(NUM_BUFFERS, data.textures); if (gl_error("gl_shmem_init_buffers", "failed to generate textures")) { return false; } glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &last_pbo); if (gl_error("gl_shmem_init_buffers", "failed to save pixel pack buffer")) { return false; } glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); if (gl_error("gl_shmem_init_buffers", "failed to save texture")) { return false; } for (size_t i = 0; i < NUM_BUFFERS; i++) { if (!gl_shmem_init_data(i, size)) { return false; } } glBindBuffer(GL_PIXEL_PACK_BUFFER, last_pbo); glBindTexture(GL_TEXTURE_2D, last_tex); return true; } static bool gl_shmem_init(HWND window) { if (!gl_shmem_init_buffers()) { return false; } if (!gl_init_fbo()) { return false; } if (!capture_init_shmem(&data.shmem_info, window, data.base_cx, data.base_cy, data.cx, data.cy, data.cx * 4, data.format, true)) { return false; } hlog("gl memory capture successful"); return true; } #define INIT_SUCCESS 0 #define INIT_FAILED -1 #define INIT_SHTEX_FAILED -2 static int gl_init(HDC hdc) { HWND window = WindowFromDC(hdc); int ret = INIT_FAILED; bool success = false; RECT rc = {0}; if (darkest_dungeon_fix) { data.base_cx = 1920; data.base_cy = 1080; } else { GetClientRect(window, &rc); data.base_cx = rc.right; data.base_cy = rc.bottom; } data.hdc = hdc; data.format = DXGI_FORMAT_B8G8R8A8_UNORM; data.using_scale = global_hook_info->use_scale; data.using_shtex = nv_capture_available && !global_hook_info->force_shmem && !data.shmem_fallback; if (data.using_scale) { data.cx = global_hook_info->cx; data.cy = global_hook_info->cy; } else { data.cx = data.base_cx; data.cy = data.base_cy; } if (data.using_shtex) { success = gl_shtex_init(window); if (!success) ret = INIT_SHTEX_FAILED; } else { success = gl_shmem_init(window); } if (!success) gl_free(); else ret = INIT_SUCCESS; return ret; } static void gl_copy_backbuffer(GLuint dst) { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, data.fbo); if (gl_error("gl_copy_backbuffer", "failed to bind FBO")) { return; } glBindTexture(GL_TEXTURE_2D, dst); if (gl_error("gl_copy_backbuffer", "failed to bind texture")) { return; } glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst, 0); if (gl_error("gl_copy_backbuffer", "failed to set frame buffer")) { return; } glReadBuffer(GL_BACK); /* darkest dungeon fix */ darkest_dungeon_fix = glGetError() == GL_INVALID_OPERATION && _strcmpi(process_name, "Darkest.exe") == 0; glDrawBuffer(GL_COLOR_ATTACHMENT0); if (gl_error("gl_copy_backbuffer", "failed to set draw buffer")) { return; } glBlitFramebuffer(0, 0, data.base_cx, data.base_cy, 0, 0, data.cx, data.cy, GL_COLOR_BUFFER_BIT, GL_LINEAR); gl_error("gl_copy_backbuffer", "failed to blit"); } static void gl_shtex_capture(void) { GLint last_fbo; GLint last_tex; jimglDXLockObjectsNV(data.gl_device, 1, &data.gl_dxobj); glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &last_fbo); if (gl_error("gl_shtex_capture", "failed to get last fbo")) { return; } glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); if (gl_error("gl_shtex_capture", "failed to get last texture")) { return; } gl_copy_backbuffer(data.texture); glBindTexture(GL_TEXTURE_2D, last_tex); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, last_fbo); jimglDXUnlockObjectsNV(data.gl_device, 1, &data.gl_dxobj); IDXGISwapChain_Present(data.dxgi_swap, 0, 0); } static inline void gl_shmem_capture_queue_copy(void) { for (int i = 0; i < NUM_BUFFERS; i++) { if (data.texture_ready[i]) { GLvoid *buffer; data.texture_ready[i] = false; glBindBuffer(GL_PIXEL_PACK_BUFFER, data.pbos[i]); if (gl_error("gl_shmem_capture_queue_copy", "failed to bind pbo")) { return; } buffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); if (buffer) { data.texture_mapped[i] = true; shmem_copy_data(i, buffer); } break; } } } static inline void gl_shmem_capture_stage(GLuint dst_pbo, GLuint src_tex) { glBindTexture(GL_TEXTURE_2D, src_tex); if (gl_error("gl_shmem_capture_stage", "failed to bind src_tex")) { return; } glBindBuffer(GL_PIXEL_PACK_BUFFER, dst_pbo); if (gl_error("gl_shmem_capture_stage", "failed to bind dst_pbo")) { return; } glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); if (gl_error("gl_shmem_capture_stage", "failed to read src_tex")) { return; } } static void gl_shmem_capture(void) { int next_tex; GLint last_fbo; GLint last_tex; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &last_fbo); if (gl_error("gl_shmem_capture", "failed to get last fbo")) { return; } glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); if (gl_error("gl_shmem_capture", "failed to get last texture")) { return; } gl_shmem_capture_queue_copy(); next_tex = (data.cur_tex == NUM_BUFFERS - 1) ? 0 : data.cur_tex + 1; gl_copy_backbuffer(data.textures[next_tex]); if (data.copy_wait < NUM_BUFFERS - 1) { data.copy_wait++; } else { GLuint src = data.textures[next_tex]; GLuint dst = data.pbos[next_tex]; if (shmem_texture_data_lock(next_tex)) { glUnmapBuffer(GL_PIXEL_PACK_BUFFER); data.texture_mapped[next_tex] = false; shmem_texture_data_unlock(next_tex); } gl_shmem_capture_stage(dst, src); data.texture_ready[next_tex] = true; } glBindTexture(GL_TEXTURE_2D, last_tex); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, last_fbo); } static void gl_capture(HDC hdc) { static bool functions_initialized = false; static bool critical_failure = false; if (critical_failure) { return; } if (!functions_initialized) { functions_initialized = init_gl_functions(); if (!functions_initialized) { critical_failure = true; return; } } /* reset error flag */ glGetError(); if (capture_should_stop()) { gl_free(); } if (capture_should_init()) { if (gl_init(hdc) == INIT_SHTEX_FAILED) { data.shmem_fallback = true; gl_init(hdc); } } if (capture_ready() && hdc == data.hdc) { uint32_t new_cx; uint32_t new_cy; /* reset capture if resized */ get_window_size(hdc, &new_cx, &new_cy); if (new_cx != data.base_cx || new_cy != data.base_cy) { if (new_cx != 0 && new_cy != 0) gl_free(); return; } if (data.using_shtex) gl_shtex_capture(); else gl_shmem_capture(); } } static BOOL WINAPI hook_swap_buffers(HDC hdc) { BOOL ret; if (!global_hook_info->capture_overlay) gl_capture(hdc); unhook(&swap_buffers); BOOL (WINAPI *call)(HDC) = swap_buffers.call_addr; ret = call(hdc); rehook(&swap_buffers); if (global_hook_info->capture_overlay) gl_capture(hdc); return ret; } static BOOL WINAPI hook_wgl_swap_buffers(HDC hdc) { BOOL ret; if (!global_hook_info->capture_overlay) gl_capture(hdc); unhook(&wgl_swap_buffers); BOOL (WINAPI *call)(HDC) = wgl_swap_buffers.call_addr; ret = call(hdc); rehook(&wgl_swap_buffers); if (global_hook_info->capture_overlay) gl_capture(hdc); return ret; } static BOOL WINAPI hook_wgl_swap_layer_buffers(HDC hdc, UINT planes) { BOOL ret; if (!global_hook_info->capture_overlay) gl_capture(hdc); unhook(&wgl_swap_layer_buffers); BOOL (WINAPI *call)(HDC, UINT) = wgl_swap_layer_buffers.call_addr; ret = call(hdc, planes); rehook(&wgl_swap_layer_buffers); if (global_hook_info->capture_overlay) gl_capture(hdc); return ret; } static BOOL WINAPI hook_wgl_delete_context(HGLRC hrc) { BOOL ret; if (capture_active()) { HDC last_hdc = jimglGetCurrentDC(); HGLRC last_hrc = jimglGetCurrentContext(); jimglMakeCurrent(data.hdc, hrc); gl_free(); jimglMakeCurrent(last_hdc, last_hrc); } unhook(&wgl_delete_context); BOOL (WINAPI *call)(HGLRC) = wgl_delete_context.call_addr; ret = call(hrc); rehook(&wgl_delete_context); return ret; } static bool gl_register_window(void) { WNDCLASSW wc = {0}; wc.style = CS_OWNDC; wc.hInstance = GetModuleHandle(NULL); wc.lpfnWndProc = DefWindowProc; wc.lpszClassName = DUMMY_WINDOW_CLASS_NAME; if (!RegisterClassW(&wc)) { hlog("gl_register_window: failed to register window class: %d", GetLastError()); return false; } return true; } bool hook_gl(void) { void *wgl_dc_proc; void *wgl_slb_proc; void *wgl_sb_proc; gl = get_system_module("opengl32.dll"); if (!gl) { return false; } if (!gl_register_window()) { return true; } wgl_dc_proc = base_get_proc("wglDeleteContext"); wgl_slb_proc = base_get_proc("wglSwapLayerBuffers"); wgl_sb_proc = base_get_proc("wglSwapBuffers"); hook_init(&swap_buffers, SwapBuffers, hook_swap_buffers, "SwapBuffers"); if (wgl_dc_proc) { hook_init(&wgl_delete_context, wgl_dc_proc, hook_wgl_delete_context, "wglDeleteContext"); rehook(&wgl_delete_context); } if (wgl_slb_proc) { hook_init(&wgl_swap_layer_buffers, wgl_slb_proc, hook_wgl_swap_layer_buffers, "wglSwapLayerBuffers"); rehook(&wgl_swap_layer_buffers); } if (wgl_sb_proc) { hook_init(&wgl_swap_buffers, wgl_sb_proc, hook_wgl_swap_buffers, "wglSwapBuffers"); rehook(&wgl_swap_buffers); } rehook(&swap_buffers); return true; }