gbe_fork/overlay_experimental/Renderer_Detector.cpp
2022-08-10 03:22:23 -04:00

1508 lines
54 KiB
C++

/*
* Copyright (C) Nemirtingas
* This file is part of the ingame overlay project
*
* The ingame overlay project is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* The ingame overlay project is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the ingame overlay project; if not, see
* <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include "Renderer_Detector.h"
#include "System/Encoding.hpp"
#include "System/String.hpp"
#include "System/System.h"
#include "System/Library.h"
#include "System/ScopedLock.hpp"
#if defined(WIN64) || defined(_WIN64) || defined(__MINGW64__) \
|| defined(WIN32) || defined(_WIN32) || defined(__MINGW32__)
#define RENDERERDETECTOR_OS_WINDOWS
#elif defined(__linux__) || defined(linux)
#define RENDERERDETECTOR_OS_LINUX
#elif defined(__APPLE__)
#define RENDERERDETECTOR_OS_APPLE
#endif
#ifdef RENDERERDETECTOR_OS_WINDOWS
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#include "windows/DX12_Hook.h"
#include "windows/DX11_Hook.h"
#include "windows/DX10_Hook.h"
#include "windows/DX9_Hook.h"
#include "windows/OpenGL_Hook.h"
#include "windows/Vulkan_Hook.h"
#include "windows/DirectX_VTables.h"
#include <random>
#ifdef GetModuleHandle
#undef GetModuleHandle
#endif
class Renderer_Detector
{
static Renderer_Detector* instance;
public:
static Renderer_Detector* Inst()
{
if (instance == nullptr)
{
instance = new Renderer_Detector;
}
return instance;
}
~Renderer_Detector()
{
delete dx9_hook;
delete dx10_hook;
delete dx11_hook;
delete dx12_hook;
delete opengl_hook;
delete vulkan_hook;
}
private:
Renderer_Detector():
dxgi_hooked(false),
dxgi1_2_hooked(false),
dx12_hooked(false),
dx11_hooked(false),
dx10_hooked(false),
dx9_hooked(false),
opengl_hooked(false),
vulkan_hooked(false),
renderer_hook(nullptr),
dx9_hook(nullptr),
dx10_hook(nullptr),
dx11_hook(nullptr),
dx12_hook(nullptr),
opengl_hook(nullptr),
vulkan_hook(nullptr),
detection_done(false)
{
std::wstring tmp(4096, L'\0');
tmp.resize(GetSystemDirectoryW(&tmp[0], tmp.size()));
_SystemDir = System::Encoding::WCharToUtf8(tmp);
System::String::ToLower(_SystemDir);
wchar_t random_str[] = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dis(0, 61);
_WindowClassName.resize(64);
for (int i = 0; i < 64; ++i)
_WindowClassName[i] = random_str[dis(gen)];
}
std::timed_mutex detector_mutex;
std::mutex renderer_mutex;
decltype(&IDXGISwapChain::Present) IDXGISwapChainPresent;
decltype(&IDXGISwapChain1::Present1) IDXGISwapChainPresent1;
decltype(&IDirect3DDevice9::Present) IDirect3DDevice9Present;
decltype(&IDirect3DDevice9Ex::PresentEx) IDirect3DDevice9ExPresentEx;
decltype(&IDirect3DSwapChain9::Present) IDirect3DSwapChain9Present;
decltype(::SwapBuffers)* wglSwapBuffers;
decltype(::vkQueuePresentKHR)* vkQueuePresentKHR;
bool dxgi_hooked;
bool dxgi1_2_hooked;
bool dx12_hooked;
bool dx11_hooked;
bool dx10_hooked;
bool dx9_hooked;
bool opengl_hooked;
bool vulkan_hooked;
Base_Hook detection_hooks;
Renderer_Hook* renderer_hook;
DX12_Hook* dx12_hook;
DX11_Hook* dx11_hook;
DX10_Hook* dx10_hook;
DX9_Hook* dx9_hook;
OpenGL_Hook* opengl_hook;
Vulkan_Hook* vulkan_hook;
bool detection_done;
std::condition_variable stop_detection_cv;
std::mutex stop_detection_mutex;
HWND dummyWindow = nullptr;
std::wstring _WindowClassName;
std::string _SystemDir;
ATOM atom = 0;
std::string FindPreferedModulePath(std::string const& name)
{
std::string res;
std::string tmp;
auto modules = System::GetModules();
for (auto& item : modules)
{
tmp = System::String::CopyLower(item);
if (tmp.length() >= name.length() && strcmp(tmp.c_str() + tmp.length() - name.length(), name.c_str()) == 0)
{
if (strncmp(tmp.c_str(), _SystemDir.c_str(), _SystemDir.length()) == 0)
return item;
// I don't care which one is picked if we can't find a library in the system32 folder...
res = std::move(item);
}
}
return res;
}
HWND CreateHWND()
{
if (dummyWindow == nullptr)
{
HINSTANCE hInst = GetModuleHandleW(nullptr);
if (atom == 0)
{
// Register a window class for creating our render window with.
WNDCLASSEXW windowClass = {};
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = DefWindowProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = hInst;
windowClass.hIcon = NULL;
windowClass.hCursor = ::LoadCursor(NULL, IDC_ARROW);
windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
windowClass.lpszMenuName = NULL;
windowClass.lpszClassName = _WindowClassName.c_str();
windowClass.hIconSm = NULL;
atom = ::RegisterClassExW(&windowClass);
}
if (atom > 0)
{
dummyWindow = ::CreateWindowExW(
NULL,
_WindowClassName.c_str(),
L"",
WS_OVERLAPPEDWINDOW,
0,
0,
1,
1,
NULL,
NULL,
hInst,
nullptr
);
assert(dummyWindow && "Failed to create window");
}
}
return dummyWindow;
}
void DestroyHWND()
{
if (dummyWindow != nullptr)
{
DestroyWindow(dummyWindow);
UnregisterClassW(_WindowClassName.c_str(), GetModuleHandleW(nullptr));
dummyWindow = nullptr;
atom = 0;
}
}
template<typename T>
void HookDetected(T*& detected_renderer)
{
detection_hooks.UnhookAll();
renderer_hook = static_cast<Renderer_Hook*>(detected_renderer);
detected_renderer = nullptr;
detection_done = true;
DestroyHWND();
}
void DeduceDXVersionFromSwapChain(IDXGISwapChain* pSwapChain)
{
IUnknown* pDevice = nullptr;
if (Inst()->dx12_hooked)
{
pSwapChain->GetDevice(IID_PPV_ARGS(reinterpret_cast<ID3D12Device**>(&pDevice)));
}
if (pDevice != nullptr)
{
HookDetected(dx12_hook);
}
else
{
if (dx11_hooked)
{
pSwapChain->GetDevice(IID_PPV_ARGS(reinterpret_cast<ID3D11Device**>(&pDevice)));
}
if (pDevice != nullptr)
{
HookDetected(dx11_hook);
}
else
{
if (dx10_hooked)
{
pSwapChain->GetDevice(IID_PPV_ARGS(reinterpret_cast<ID3D10Device**>(&pDevice)));
}
if (pDevice != nullptr)
{
HookDetected(dx10_hook);
}
}
}
if (pDevice != nullptr)
{
pDevice->Release();
}
}
static HRESULT STDMETHODCALLTYPE MyIDXGISwapChain_Present(IDXGISwapChain* _this, UINT SyncInterval, UINT Flags)
{
auto inst = Inst();
HRESULT res;
bool locked;
std::unique_lock<std::mutex> lk(inst->renderer_mutex, std::defer_lock);
// It appears that (NVidia at least) calls IDXGISwapChain when calling OpenGL or Vulkan SwapBuffers.
// So only lock when OpenGL or Vulkan hasn't already locked the mutex.
locked = lk.try_lock();
res = (_this->*inst->IDXGISwapChainPresent)(SyncInterval, Flags);
if (!locked || inst->detection_done)
return res;
inst->DeduceDXVersionFromSwapChain(_this);
return res;
}
static HRESULT STDMETHODCALLTYPE MyIDXGISwapChain_Present1(IDXGISwapChain1* _this, UINT SyncInterval, UINT Flags, const DXGI_PRESENT_PARAMETERS* pPresentParameters)
{
auto inst = Inst();
HRESULT res;
bool locked;
std::unique_lock<std::mutex> lk(inst->renderer_mutex, std::defer_lock);
// It appears that (NVidia at least) calls IDXGISwapChain when calling OpenGL or Vulkan SwapBuffers.
// So only lock when OpenGL or Vulkan hasn't already locked the mutex.
locked = lk.try_lock();
res = (_this->*inst->IDXGISwapChainPresent1)(SyncInterval, Flags, pPresentParameters);
if (!locked || inst->detection_done)
return res;
inst->DeduceDXVersionFromSwapChain(_this);
return res;
}
static HRESULT STDMETHODCALLTYPE MyDX9Present(IDirect3DDevice9* _this, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
auto res = (_this->*inst->IDirect3DDevice9Present)(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
if (inst->detection_done)
return res;
inst->HookDetected(inst->dx9_hook);
return res;
}
static HRESULT STDMETHODCALLTYPE MyDX9PresentEx(IDirect3DDevice9Ex* _this, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion, DWORD dwFlags)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
auto res = (_this->*inst->IDirect3DDevice9ExPresentEx)(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags);
if (inst->detection_done)
return res;
inst->HookDetected(inst->dx9_hook);
return res;
}
static HRESULT STDMETHODCALLTYPE MyDX9SwapChainPresent(IDirect3DSwapChain9* _this, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion, DWORD dwFlags)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
auto res = (_this->*inst->IDirect3DSwapChain9Present)(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion, dwFlags);
if (inst->detection_done)
return res;
inst->HookDetected(inst->dx9_hook);
return res;
}
static BOOL WINAPI MywglSwapBuffers(HDC hDC)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
auto res = inst->wglSwapBuffers(hDC);
if (inst->detection_done)
return res;
if (gladLoaderLoadGL() >= GLAD_MAKE_VERSION(3, 1))
{
inst->HookDetected(inst->opengl_hook);
}
return res;
}
static VkResult VKAPI_CALL MyvkQueuePresentKHR(VkQueue Queue, const VkPresentInfoKHR* pPresentInfo)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
auto res = inst->vkQueuePresentKHR(Queue, pPresentInfo);
if (inst->detection_done)
return res;
inst->HookDetected(inst->vulkan_hook);
return res;
}
void HookDXGIPresent(IDXGISwapChain* pSwapChain, decltype(&IDXGISwapChain::Present)& pfnPresent, decltype(&IDXGISwapChain::ResizeBuffers)& pfnResizeBuffers, decltype(&IDXGISwapChain::ResizeTarget)& pfnResizeTarget)
{
void** vTable = *reinterpret_cast<void***>(pSwapChain);
(void*&)pfnPresent = vTable[(int)IDXGISwapChainVTable::Present];
(void*&)pfnResizeBuffers = vTable[(int)IDXGISwapChainVTable::ResizeBuffers];
(void*&)pfnResizeTarget = vTable[(int)IDXGISwapChainVTable::ResizeTarget];
if (!dxgi_hooked)
{
dxgi_hooked = true;
(void*&)IDXGISwapChainPresent = vTable[(int)IDXGISwapChainVTable::Present];
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&IDXGISwapChainPresent, (void*)&MyIDXGISwapChain_Present});
detection_hooks.EndHook();
}
}
void HookDXGIPresent1(IDXGISwapChain1* pSwapChain1, decltype(&IDXGISwapChain1::Present1)& pfnPresent1)
{
void** vTable = *reinterpret_cast<void***>(pSwapChain1);
(void*&)pfnPresent1 = vTable[(int)IDXGISwapChainVTable::Present1];
if (!dxgi1_2_hooked)
{
dxgi1_2_hooked = true;
(void*&)IDXGISwapChainPresent1 = vTable[(int)IDXGISwapChainVTable::Present1];
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&IDXGISwapChainPresent1, (void*)&MyIDXGISwapChain_Present1});
detection_hooks.EndHook();
}
}
void HookDX9Present(IDirect3DDevice9* pDevice, bool ex, IDirect3DSwapChain9* pSwapChain,
void*& pfnPresent,
void*& pfnReset,
void*& pfnPresentEx,
void*& pfnSwapChainPresent)
{
void** vTable = *reinterpret_cast<void***>(pDevice);
pfnPresent = vTable[(int)IDirect3DDevice9VTable::Present];
pfnReset = vTable[(int)IDirect3DDevice9VTable::Reset];
(void*&)IDirect3DDevice9Present = vTable[(int)IDirect3DDevice9VTable::Present];
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&IDirect3DDevice9Present, (void*)&MyDX9Present });
detection_hooks.EndHook();
if (ex)
{
pfnPresentEx = vTable[(int)IDirect3DDevice9VTable::PresentEx];
(void*&)IDirect3DDevice9ExPresentEx = vTable[(int)IDirect3DDevice9VTable::PresentEx];
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&IDirect3DDevice9ExPresentEx, (void*)&MyDX9PresentEx });
detection_hooks.EndHook();
}
else
{
pfnPresentEx = nullptr;
IDirect3DDevice9ExPresentEx = nullptr;
}
if (pSwapChain != nullptr)
{
vTable = *reinterpret_cast<void***>(pSwapChain);
pfnSwapChainPresent = vTable[(int)IDirect3DSwapChain9VTable::Present];
(void*&)IDirect3DSwapChain9Present = vTable[(int)IDirect3DSwapChain9VTable::Present];
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&IDirect3DSwapChain9Present, (void*)&MyDX9SwapChainPresent });
detection_hooks.EndHook();
}
else
{
pfnSwapChainPresent = nullptr;
}
}
void HookwglSwapBuffers(decltype(::SwapBuffers)* _wglSwapBuffers)
{
wglSwapBuffers = _wglSwapBuffers;
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&wglSwapBuffers, (void*)&MywglSwapBuffers });
detection_hooks.EndHook();
}
void HookvkQueuePresentKHR(decltype(::vkQueuePresentKHR)* _vkQueuePresentKHR)
{
vkQueuePresentKHR = _vkQueuePresentKHR;
detection_hooks.BeginHook();
detection_hooks.HookFuncs(
std::pair<void**, void*>{ (void**)&vkQueuePresentKHR, (void*)&MyvkQueuePresentKHR }
);
detection_hooks.EndHook();
}
void hook_dx9(std::string const& library_path)
{
if (!dx9_hooked)
{
System::Library::Library libD3d9;
if (!libD3d9.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect DX9", library_path);
return;
}
IDirect3D9Ex* pD3D = nullptr;
IDirect3DDevice9* pDevice = nullptr;
IDirect3DSwapChain9* pSwapChain = nullptr;
auto Direct3DCreate9Ex = libD3d9.GetSymbol<decltype(::Direct3DCreate9Ex)>("Direct3DCreate9Ex");
D3DPRESENT_PARAMETERS params = {};
params.BackBufferWidth = 1;
params.BackBufferHeight = 1;
params.hDeviceWindow = dummyWindow;
params.BackBufferCount = 1;
params.Windowed = TRUE;
params.SwapEffect = D3DSWAPEFFECT_DISCARD;
if (Direct3DCreate9Ex != nullptr)
{
Direct3DCreate9Ex(D3D_SDK_VERSION, &pD3D);
pD3D->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL, D3DCREATE_HARDWARE_VERTEXPROCESSING, &params, NULL, reinterpret_cast<IDirect3DDevice9Ex**>(&pDevice));
}
if (pDevice == nullptr)
{
Direct3DCreate9Ex = nullptr;
auto Direct3DCreate9 = libD3d9.GetSymbol<decltype(::Direct3DCreate9)>("Direct3DCreate9");
if (Direct3DCreate9 != nullptr)
{
// D3DDEVTYPE_HAL
pD3D = reinterpret_cast<IDirect3D9Ex*>(Direct3DCreate9(D3D_SDK_VERSION));
pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL, D3DCREATE_HARDWARE_VERTEXPROCESSING, &params, &pDevice);
}
}
if (pDevice != nullptr)
{
SPDLOG_INFO("Hooked D3D9::Present to detect DX Version");
dx9_hooked = true;
pDevice->GetSwapChain(0, &pSwapChain);
decltype(&IDirect3DDevice9::Present) pfnPresent;
decltype(&IDirect3DDevice9::Reset) pfnReset;
decltype(&IDirect3DDevice9Ex::PresentEx) pfnPresentEx;
decltype(&IDirect3DSwapChain9::Present) pfnSwapChainPresent;
HookDX9Present(pDevice, Direct3DCreate9Ex != nullptr, pSwapChain, (void*&)pfnPresent, (void*&)pfnReset, (void*&)pfnPresentEx, (void*&)pfnSwapChainPresent);
dx9_hook = DX9_Hook::Inst();
dx9_hook->LibraryName = library_path;
dx9_hook->LoadFunctions(pfnPresent, pfnReset, pfnPresentEx, pfnSwapChainPresent);
}
else
{
SPDLOG_WARN("Failed to hook D3D9::Present to detect DX Version");
}
if (pSwapChain) pSwapChain->Release();
if (pDevice) pDevice->Release();
if (pD3D) pD3D->Release();
}
}
void hook_dx10(std::string const& library_path)
{
if (!dx10_hooked)
{
System::Library::Library libD3d10;
if (!libD3d10.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect DX10", library_path);
return;
}
std::string dxgi_path = FindPreferedModulePath("dxgi.dll");
IDXGISwapChain* pSwapChain = nullptr;
ID3D10Device* pDevice = nullptr;
int version = 0;
if (!dxgi_path.empty())
{
HMODULE dxgi = GetModuleHandleA(dxgi_path.c_str());
if (dxgi != nullptr)
{
IDXGIFactory2* pDXGIFactory = nullptr;
auto D3D10CreateDevice = libD3d10.GetSymbol<decltype(::D3D10CreateDevice)>("D3D10CreateDevice");
decltype(CreateDXGIFactory1)* CreateDXGIFactory1 = (decltype(CreateDXGIFactory1))GetProcAddress(dxgi, "CreateDXGIFactory1");
if (D3D10CreateDevice != nullptr && CreateDXGIFactory1 != nullptr)
{
D3D10CreateDevice(NULL, D3D10_DRIVER_TYPE_NULL, NULL, 0, D3D10_SDK_VERSION, &pDevice);
if (pDevice != nullptr)
{
CreateDXGIFactory1(IID_PPV_ARGS(&pDXGIFactory));
if (pDXGIFactory != nullptr)
{
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = {};
SwapChainDesc.BufferCount = 2;
SwapChainDesc.Width = 1;
SwapChainDesc.Height = 1;
SwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.Stereo = FALSE;
SwapChainDesc.SampleDesc = { 1, 0 };
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.Scaling = DXGI_SCALING_NONE;
SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
pDXGIFactory->CreateSwapChainForHwnd(pDevice, dummyWindow, &SwapChainDesc, NULL, NULL, reinterpret_cast<IDXGISwapChain1**>(&pSwapChain));
}
}
}
if (pDXGIFactory) pDXGIFactory->Release();
}
}
if (pDevice != nullptr && pSwapChain != nullptr)
{
version = 1;
}
else
{
SPDLOG_WARN("Failed to instanciate IDXGISwapChain1, fallback to pure DX10 detection");
auto D3D10CreateDeviceAndSwapChain = libD3d10.GetSymbol<decltype(::D3D10CreateDeviceAndSwapChain)>("D3D10CreateDeviceAndSwapChain");
if (D3D10CreateDeviceAndSwapChain != nullptr)
{
DXGI_SWAP_CHAIN_DESC SwapChainDesc = {};
SwapChainDesc.BufferCount = 1;
SwapChainDesc.BufferDesc.Width = 1;
SwapChainDesc.BufferDesc.Height = 1;
SwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
SwapChainDesc.BufferDesc.RefreshRate.Denominator = 0;
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.OutputWindow = dummyWindow;
SwapChainDesc.SampleDesc.Count = 1;
SwapChainDesc.SampleDesc.Quality = 0;
SwapChainDesc.Windowed = TRUE;
D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_NULL, NULL, 0, D3D10_SDK_VERSION, &SwapChainDesc, &pSwapChain, &pDevice);
}
}
if (pSwapChain != nullptr)
{
SPDLOG_INFO("Hooked IDXGISwapChain::Present to detect DX Version");
dx10_hooked = true;
decltype(&IDXGISwapChain::Present) pfnPresent;
decltype(&IDXGISwapChain::ResizeBuffers) pfnResizeBuffers;
decltype(&IDXGISwapChain::ResizeTarget) pfnResizeTarget;
decltype(&IDXGISwapChain1::Present1) pfnPresent1 = nullptr;
HookDXGIPresent(pSwapChain, pfnPresent, pfnResizeBuffers, pfnResizeTarget);
if (version > 0)
{
HookDXGIPresent1(reinterpret_cast<IDXGISwapChain1*>(pSwapChain), pfnPresent1);
}
dx10_hook = DX10_Hook::Inst();
dx10_hook->LibraryName = library_path;
dx10_hook->LoadFunctions(pfnPresent, pfnResizeBuffers, pfnResizeTarget, pfnPresent1);
}
else
{
SPDLOG_WARN("Failed to Hook IDXGISwapChain::Present to detect DX Version");
}
if (pDevice)pDevice->Release();
if (pSwapChain)pSwapChain->Release();
}
}
void hook_dx11(std::string const& library_path)
{
if (!dx11_hooked)
{
System::Library::Library libD3d11;
if (!libD3d11.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect DX11", library_path);
return;
}
std::string dxgi_path = FindPreferedModulePath("dxgi.dll");
IDXGISwapChain* pSwapChain = nullptr;
ID3D11Device* pDevice = nullptr;
int version = 0;
if (!dxgi_path.empty())
{
HMODULE dxgi = GetModuleHandleA(dxgi_path.c_str());
if (dxgi != nullptr)
{
IDXGIFactory2* pDXGIFactory = nullptr;
auto D3D11CreateDevice = libD3d11.GetSymbol<decltype(::D3D11CreateDevice)>("D3D11CreateDevice");
decltype(CreateDXGIFactory1)* CreateDXGIFactory1 = (decltype(CreateDXGIFactory1))GetProcAddress(dxgi, "CreateDXGIFactory1");
if (D3D11CreateDevice != nullptr && CreateDXGIFactory1 != nullptr)
{
D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_NULL, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &pDevice, NULL, NULL);
if (pDevice != nullptr)
{
CreateDXGIFactory1(IID_PPV_ARGS(&pDXGIFactory));
if (pDXGIFactory != nullptr)
{
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = {};
SwapChainDesc.BufferCount = 2;
SwapChainDesc.Width = 1;
SwapChainDesc.Height = 1;
SwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.Stereo = FALSE;
SwapChainDesc.SampleDesc = { 1, 0 };
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.Scaling = DXGI_SCALING_NONE;
SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
pDXGIFactory->CreateSwapChainForHwnd(pDevice, dummyWindow, &SwapChainDesc, NULL, NULL, reinterpret_cast<IDXGISwapChain1**>(&pSwapChain));
}
}
}
if (pDXGIFactory) pDXGIFactory->Release();
}
}
if (pDevice != nullptr && pSwapChain != nullptr)
{
version = 1;
}
else
{
SPDLOG_WARN("Failed to instanciate IDXGISwapChain1, fallback to pure DX11 detection");
auto D3D11CreateDeviceAndSwapChain = libD3d11.GetSymbol<decltype(::D3D11CreateDeviceAndSwapChain)>("D3D11CreateDeviceAndSwapChain");
if (D3D11CreateDeviceAndSwapChain != nullptr)
{
DXGI_SWAP_CHAIN_DESC SwapChainDesc = {};
SwapChainDesc.BufferCount = 1;
SwapChainDesc.BufferDesc.Width = 1;
SwapChainDesc.BufferDesc.Height = 1;
SwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
SwapChainDesc.BufferDesc.RefreshRate.Denominator = 0;
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.OutputWindow = dummyWindow;
SwapChainDesc.SampleDesc.Count = 1;
SwapChainDesc.SampleDesc.Quality = 0;
SwapChainDesc.Windowed = TRUE;
D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_NULL, NULL, 0, NULL, NULL, D3D11_SDK_VERSION, &SwapChainDesc, &pSwapChain, &pDevice, NULL, NULL);
}
}
if (pSwapChain != nullptr)
{
SPDLOG_INFO("Hooked IDXGISwapChain::Present to detect DX Version");
dx11_hooked = true;
decltype(&IDXGISwapChain::Present) pfnPresent = nullptr;
decltype(&IDXGISwapChain::ResizeBuffers) pfnResizeBuffers = nullptr;
decltype(&IDXGISwapChain::ResizeTarget) pfnResizeTarget = nullptr;
decltype(&IDXGISwapChain1::Present1) pfnPresent1 = nullptr;
HookDXGIPresent(pSwapChain, pfnPresent, pfnResizeBuffers, pfnResizeTarget);
if (version > 0)
{
HookDXGIPresent1(reinterpret_cast<IDXGISwapChain1*>(pSwapChain), pfnPresent1);
}
dx11_hook = DX11_Hook::Inst();
dx11_hook->LibraryName = library_path;
dx11_hook->LoadFunctions(pfnPresent, pfnResizeBuffers, pfnResizeTarget, pfnPresent1);
}
else
{
SPDLOG_WARN("Failed to Hook IDXGISwapChain::Present to detect DX Version");
}
if (pDevice) pDevice->Release();
if (pSwapChain) pSwapChain->Release();
}
}
void hook_dx12(std::string const& library_path)
{
if (!dx12_hooked)
{
System::Library::Library libD3d12;
if (!libD3d12.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect DX12", library_path);
return;
}
std::string dxgi_path = FindPreferedModulePath("dxgi.dll");
if (dxgi_path.empty())
{
SPDLOG_WARN("Failed to load dxgi.dll to detect DX12");
return;
}
IDXGIFactory4* pDXGIFactory = nullptr;
IDXGISwapChain1* pSwapChain = nullptr;
ID3D12CommandQueue* pCommandQueue = nullptr;
ID3D12Device* pDevice = nullptr;
auto D3D12CreateDevice = libD3d12.GetSymbol<decltype(::D3D12CreateDevice)>("D3D12CreateDevice");
if (D3D12CreateDevice != nullptr)
{
D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&pDevice));
if (pDevice != nullptr)
{
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = {};
SwapChainDesc.BufferCount = 2;
SwapChainDesc.Width = 1;
SwapChainDesc.Height = 1;
SwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.Stereo = FALSE;
SwapChainDesc.SampleDesc = { 1, 0 };
SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapChainDesc.Scaling = DXGI_SCALING_NONE;
SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
pDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&pCommandQueue));
if (pCommandQueue != nullptr)
{
HMODULE dxgi = GetModuleHandleA(dxgi_path.c_str());
if (dxgi != nullptr)
{
decltype(CreateDXGIFactory1)* CreateDXGIFactory1 = (decltype(CreateDXGIFactory1))GetProcAddress(dxgi, "CreateDXGIFactory1");
if (CreateDXGIFactory1 != nullptr)
{
CreateDXGIFactory1(IID_PPV_ARGS(&pDXGIFactory));
if (pDXGIFactory != nullptr)
{
pDXGIFactory->CreateSwapChainForHwnd(pCommandQueue, dummyWindow, &SwapChainDesc, NULL, NULL, &pSwapChain);
}
}
}
}
}//if (pDevice != nullptr)
}//if (D3D12CreateDevice != nullptr)
if (pCommandQueue != nullptr && pSwapChain != nullptr)
{
SPDLOG_INFO("Hooked IDXGISwapChain::Present to detect DX Version");
dx12_hooked = true;
decltype(&IDXGISwapChain::Present) pfnPresent = nullptr;
decltype(&IDXGISwapChain::ResizeBuffers) pfnResizeBuffers = nullptr;
decltype(&IDXGISwapChain::ResizeTarget) pfnResizeTarget = nullptr;
decltype(&IDXGISwapChain1::Present1) pfnPresent1 = nullptr;
HookDXGIPresent(pSwapChain, pfnPresent, pfnResizeBuffers, pfnResizeTarget);
HookDXGIPresent1(reinterpret_cast<IDXGISwapChain1*>(pSwapChain), pfnPresent1);
void** vTable = *reinterpret_cast<void***>(pCommandQueue);
decltype(&ID3D12CommandQueue::ExecuteCommandLists) pfnExecuteCommandLists;
(void*&)pfnExecuteCommandLists = vTable[(int)ID3D12CommandQueueVTable::ExecuteCommandLists];
dx12_hook = DX12_Hook::Inst();
dx12_hook->LibraryName = library_path;
dx12_hook->LoadFunctions(pfnPresent, pfnResizeBuffers, pfnResizeTarget, pfnExecuteCommandLists, pfnPresent1);
}
else
{
SPDLOG_WARN("Failed to Hook IDXGISwapChain::Present to detect DX Version");
}
if (pSwapChain) pSwapChain->Release();
if (pDXGIFactory) pDXGIFactory->Release();
if (pCommandQueue) pCommandQueue->Release();
if (pDevice) pDevice->Release();
}
}
void hook_opengl(std::string const& library_path)
{
if (!opengl_hooked)
{
System::Library::Library libOpenGL;
if (!libOpenGL.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect OpenGL", library_path);
return;
}
auto wglSwapBuffers = libOpenGL.GetSymbol<decltype(::SwapBuffers)>("wglSwapBuffers");
if (wglSwapBuffers != nullptr)
{
SPDLOG_INFO("Hooked wglSwapBuffers to detect OpenGL");
opengl_hooked = true;
opengl_hook = OpenGL_Hook::Inst();
opengl_hook->LibraryName = library_path;
opengl_hook->LoadFunctions(wglSwapBuffers);
HookwglSwapBuffers(wglSwapBuffers);
}
else
{
SPDLOG_WARN("Failed to Hook wglSwapBuffers to detect OpenGL");
}
}
}
void hook_vulkan(std::string const& library_path)
{
// Vulkan hook disabled until proper implementation.
return;
if (!vulkan_hooked)
{
System::Library::Library libVulkan;
if (!libVulkan.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect Vulkan", library_path);
return;
}
auto vkCreateInstance = libVulkan.GetSymbol<decltype(::vkCreateInstance)>("vkCreateInstance");
auto vkDestroyInstance = libVulkan.GetSymbol<decltype(::vkDestroyInstance)>("vkDestroyInstance");
auto vkGetInstanceProcAddr = libVulkan.GetSymbol<decltype(::vkGetInstanceProcAddr)>("vkGetInstanceProcAddr");
decltype(::vkQueuePresentKHR)* vkQueuePresentKHR = nullptr;
decltype(::vkAcquireNextImageKHR)* vkAcquireNextImageKHR = nullptr;
decltype(::vkAcquireNextImage2KHR)* vkAcquireNextImage2KHR = nullptr;
VkInstanceCreateInfo instance_infos{};
VkInstance instance{};
std::vector<VkPhysicalDevice> phyDevices;
VkDeviceCreateInfo create_info{};
VkDevice pDevice{};
uint32_t count = 0;
instance_infos.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
vkCreateInstance(&instance_infos, nullptr, &instance);
auto vkCreateDevice = (decltype(::vkCreateDevice)*)vkGetInstanceProcAddr(instance, "vkCreateDevice");
auto vkDestroyDevice = (decltype(::vkDestroyDevice)*)vkGetInstanceProcAddr(instance, "vkDestroyDevice");
auto vkGetDeviceProcAddr = (decltype(::vkGetDeviceProcAddr)*)vkGetInstanceProcAddr(instance, "vkGetDeviceProcAddr");
auto vkEnumeratePhysicalDevices = (decltype(::vkEnumeratePhysicalDevices)*)vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices");
auto vkEnumerateDeviceExtensionProperties = (decltype(::vkEnumerateDeviceExtensionProperties)*)vkGetInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties");
auto vkGetPhysicalDeviceProperties = (decltype(::vkGetPhysicalDeviceProperties)*)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties");
vkEnumeratePhysicalDevices(instance, &count, nullptr);
phyDevices.resize(count);
vkEnumeratePhysicalDevices(instance, &count, phyDevices.data());
[&]() {
VkPhysicalDeviceProperties props{};
std::vector<VkExtensionProperties> ext_props;
for (auto& device : phyDevices)
{
vkGetPhysicalDeviceProperties(device, &props);
if (props.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || props.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
{
count = 0;
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, nullptr);
ext_props.resize(count);
vkEnumerateDeviceExtensionProperties(device, nullptr, &count, ext_props.data());
for (auto& ext : ext_props)
{
if (strcmp(ext.extensionName, "VK_KHR_swapchain") == 0)
{
create_info.sType = VkStructureType::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create_info.enabledExtensionCount = 1;
const char* str = "VK_KHR_swapchain";
create_info.ppEnabledExtensionNames = &str;
vkCreateDevice(device, &create_info, nullptr, &pDevice);
if(pDevice != nullptr)
return;
}
}
}
}
}();
if (pDevice != nullptr)
{
vkQueuePresentKHR = (decltype(::vkQueuePresentKHR)*)vkGetDeviceProcAddr(pDevice, "vkQueuePresentKHR");
vkAcquireNextImageKHR = (decltype(::vkAcquireNextImageKHR)*)vkGetDeviceProcAddr(pDevice, "vkAcquireNextImageKHR");
vkAcquireNextImage2KHR = (decltype(::vkAcquireNextImage2KHR)*)vkGetDeviceProcAddr(pDevice, "vkAcquireNextImage2KHR");
vkDestroyDevice(pDevice, nullptr);
}
vkDestroyInstance(instance, nullptr);
if (vkQueuePresentKHR != nullptr /* && (vkAcquireNextImageKHR != nullptr || vkAcquireNextImage2KHR != nullptr)*/)
{
SPDLOG_INFO("Hooked vkQueuePresentKHR to detect Vulkan");
vulkan_hooked = true;
vulkan_hook = Vulkan_Hook::Inst();
vulkan_hook->LibraryName = library_path;
vulkan_hook->LoadFunctions(vkQueuePresentKHR);
HookvkQueuePresentKHR(vkQueuePresentKHR);
}
else
{
SPDLOG_WARN("Failed to Hook vkQueuePresentKHR to detect Vulkan");
}
}
}
public:
Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
{
std::unique_lock<std::timed_mutex> detection_lock(detector_mutex, std::defer_lock);
if (!detection_lock.try_lock_for(timeout))
return nullptr;
{
std::lock_guard<std::mutex> lk(renderer_mutex);
if (detection_done)
{
if (renderer_hook != nullptr)
return renderer_hook;
detection_done = false;
}
if (CreateHWND() == nullptr)
{
return nullptr;
}
}
SPDLOG_TRACE("Started renderer detection.");
std::pair<std::string, void(Renderer_Detector::*)(std::string const&)> libraries[]{
{OpenGL_Hook::DLL_NAME, &Renderer_Detector::hook_opengl},
{Vulkan_Hook::DLL_NAME, &Renderer_Detector::hook_vulkan},
{ DX12_Hook::DLL_NAME, &Renderer_Detector::hook_dx12 },
{ DX11_Hook::DLL_NAME, &Renderer_Detector::hook_dx11 },
{ DX10_Hook::DLL_NAME, &Renderer_Detector::hook_dx10 },
{ DX9_Hook::DLL_NAME, &Renderer_Detector::hook_dx9 },
};
std::string name;
auto start_time = std::chrono::steady_clock::now();
do
{
std::unique_lock<std::mutex> lck(stop_detection_mutex);
if (detection_done)
break;
for (auto const& library : libraries)
{
void* lib_handle = System::Library::GetLibraryHandle(library.first.c_str());
if (lib_handle != nullptr)
{
std::lock_guard<std::mutex> lk(renderer_mutex);
name = FindPreferedModulePath(library.first);
(this->*library.second)(name);
}
}
stop_detection_cv.wait_for(lck, std::chrono::milliseconds{ 100 });
} while (!detection_done && (timeout.count() == -1 || (std::chrono::steady_clock::now() - start_time) <= timeout));
{
std::lock_guard<std::mutex> lk(renderer_mutex);
DestroyHWND();
detection_done = true;
detection_hooks.UnhookAll();
dxgi_hooked = false;
dxgi1_2_hooked = false;
dx12_hooked = false;
dx11_hooked = false;
dx10_hooked = false;
dx9_hooked = false;
opengl_hooked = false;
vulkan_hooked = false;
delete dx9_hook ; dx9_hook = nullptr;
delete dx10_hook ; dx10_hook = nullptr;
delete dx11_hook ; dx11_hook = nullptr;
delete dx12_hook ; dx12_hook = nullptr;
delete opengl_hook; opengl_hook = nullptr;
delete vulkan_hook; vulkan_hook = nullptr;
}
SPDLOG_TRACE("Renderer detection done {}.", (void*)renderer_hook);
return renderer_hook;
}
void stop_detection()
{
{
System::scoped_lock lk(renderer_mutex, stop_detection_mutex);
detection_done = true;
}
stop_detection_cv.notify_all();
}
};
Renderer_Detector* Renderer_Detector::instance = nullptr;
#elif defined(RENDERERDETECTOR_OS_LINUX)
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
#include "linux/OpenGLX_Hook.h"
class Renderer_Detector
{
static Renderer_Detector* instance;
public:
static Renderer_Detector* Inst()
{
if (instance == nullptr)
{
instance = new Renderer_Detector;
}
return instance;
}
~Renderer_Detector()
{
delete openglx_hook;
//delete vulkan_hook;
}
private:
Renderer_Detector() :
openglx_hooked(false),
renderer_hook(nullptr),
openglx_hook(nullptr),
//vulkan_hook(nullptr),
detection_done(false)
{}
std::timed_mutex detector_mutex;
std::mutex renderer_mutex;
Base_Hook detection_hooks;
decltype(::glXSwapBuffers)* glXSwapBuffers;
bool openglx_hooked;
//bool vulkan_hooked;
Renderer_Hook* renderer_hook;
OpenGLX_Hook* openglx_hook;
bool detection_done;
std::condition_variable stop_detection_cv;
std::mutex stop_detection_mutex;
static void MyglXSwapBuffers(Display* dpy, GLXDrawable drawable)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
inst->glXSwapBuffers(dpy, drawable);
if (inst->detection_done)
return;
if (gladLoaderLoadGL() >= GLAD_MAKE_VERSION(3, 1))
{
inst->detection_hooks.UnhookAll();
inst->renderer_hook = static_cast<Renderer_Hook*>(Inst()->openglx_hook);
inst->openglx_hook = nullptr;
inst->detection_done = true;
}
}
void HookglXSwapBuffers(decltype(::glXSwapBuffers)* _glXSwapBuffers)
{
glXSwapBuffers = _glXSwapBuffers;
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&glXSwapBuffers, (void*)&MyglXSwapBuffers });
detection_hooks.EndHook();
}
void hook_openglx(std::string const& library_path)
{
if (!openglx_hooked)
{
System::Library::Library libGLX;
if (!libGLX.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect OpenGLX", library_path);
return;
}
auto glXSwapBuffers = libGLX.GetSymbol<decltype(::glXSwapBuffers)>("glXSwapBuffers");
if (glXSwapBuffers != nullptr)
{
SPDLOG_INFO("Hooked glXSwapBuffers to detect OpenGLX");
openglx_hooked = true;
openglx_hook = OpenGLX_Hook::Inst();
openglx_hook->LibraryName = library_path;
openglx_hook->LoadFunctions(glXSwapBuffers);
HookglXSwapBuffers(glXSwapBuffers);
}
else
{
SPDLOG_WARN("Failed to Hook glXSwapBuffers to detect OpenGLX");
}
}
}
public:
Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)> libraries[]{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)>{OpenGLX_Hook::DLL_NAME, &Renderer_Detector::hook_openglx},
};
std::unique_lock<std::timed_mutex> detection_lock(detector_mutex, std::defer_lock);
if (!detection_lock.try_lock_for(timeout))
return nullptr;
{
std::lock_guard<std::mutex> lk(renderer_mutex);
if (detection_done)
{
if (renderer_hook != nullptr)
return renderer_hook;
detection_done = false;
}
}
SPDLOG_TRACE("Started renderer detection.");
auto start_time = std::chrono::steady_clock::now();
do
{
std::unique_lock<std::mutex> lck(stop_detection_mutex);
if (detection_done)
break;
for (auto const& library : libraries)
{
void* lib_handle = System::Library::GetLibraryHandle(library.first);
if (lib_handle != nullptr)
{
std::lock_guard<std::mutex> lk(renderer_mutex);
std::string lib_path = System::Library::GetLibraryPath(lib_handle);
(this->*library.second)(lib_path);
}
}
stop_detection_cv.wait_for(lck, std::chrono::milliseconds{ 100 });
} while (!detection_done && (timeout.count() == -1 || (std::chrono::steady_clock::now() - start_time) <= timeout));
{
std::lock_guard<std::mutex> lk(renderer_mutex);
detection_done = true;
detection_hooks.UnhookAll();
openglx_hooked = false;
delete openglx_hook; openglx_hook = nullptr;
//delete vulkan_hook; vulkan_hook = nullptr;
}
SPDLOG_TRACE("Renderer detection done {}.", (void*)renderer_hook);
return renderer_hook;
}
void stop_detection()
{
{
System::scoped_lock lk(renderer_mutex, stop_detection_mutex);
detection_done = true;
}
stop_detection_cv.notify_all();
}
};
Renderer_Detector* Renderer_Detector::instance = nullptr;
#elif defined(RENDERERDETECTOR_OS_APPLE)
#include "macosx/OpenGL_Hook.h"
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>
class Renderer_Detector
{
static Renderer_Detector* instance;
public:
static Renderer_Detector* Inst()
{
if (instance == nullptr)
{
instance = new Renderer_Detector;
}
return instance;
}
~Renderer_Detector()
{
delete opengl_hook;
}
private:
Renderer_Detector():
opengl_hooked(false),
renderer_hook(nullptr),
opengl_hook(nullptr),
detection_done(false)
{}
std::timed_mutex detector_mutex;
std::mutex renderer_mutex;
Base_Hook detection_hooks;
decltype(::CGLFlushDrawable)* CGLFlushDrawable;
bool opengl_hooked;
Renderer_Hook* renderer_hook;
OpenGL_Hook* opengl_hook;
bool detection_done;
std::condition_variable stop_detection_cv;
std::mutex stop_detection_mutex;
static int64_t MyCGLFlushDrawable(CGLDrawable_t* glDrawable)
{
auto inst = Inst();
std::lock_guard<std::mutex> lk(inst->renderer_mutex);
int64_t res = inst->CGLFlushDrawable(glDrawable);
if (gladLoaderLoadGL() >= GLAD_MAKE_VERSION(2, 0))
{
inst->detection_hooks.UnhookAll();
inst->renderer_hook = static_cast<Renderer_Hook*>(Inst()->opengl_hook);
inst->opengl_hook = nullptr;
inst->detection_done = true;
}
return res;
}
void HookglFlushDrawable(decltype(::CGLFlushDrawable)* _CGLFlushDrawable)
{
CGLFlushDrawable = _CGLFlushDrawable;
detection_hooks.BeginHook();
detection_hooks.HookFunc(std::pair<void**, void*>{ (void**)&CGLFlushDrawable, (void*)&MyCGLFlushDrawable });
detection_hooks.EndHook();
}
void hook_opengl(std::string const& library_path)
{
if (!opengl_hooked)
{
System::Library::Library libOpenGL;
if (!libOpenGL.OpenLibrary(library_path, false))
{
SPDLOG_WARN("Failed to load {} to detect OpenGL", library_path);
return;
}
auto CGLFlushDrawable = libOpenGL.GetSymbol<decltype(::CGLFlushDrawable)>("CGLFlushDrawable");
if (CGLFlushDrawable != nullptr)
{
SPDLOG_INFO("Hooked CGLFlushDrawable to detect OpenGL");
opengl_hooked = true;
opengl_hook = OpenGL_Hook::Inst();
opengl_hook->LibraryName = library_path;
opengl_hook->LoadFunctions(CGLFlushDrawable);
HookglFlushDrawable(CGLFlushDrawable);
}
else
{
SPDLOG_WARN("Failed to Hook CGLFlushDrawable to detect OpenGL");
}
}
}
public:
Renderer_Hook* detect_renderer(std::chrono::milliseconds timeout)
{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)> libraries[]{
std::pair<const char*, void(Renderer_Detector::*)(std::string const&)>{OpenGL_Hook::DLL_NAME, &Renderer_Detector::hook_opengl}
};
std::unique_lock<std::timed_mutex> detection_lock(detector_mutex, std::defer_lock);
if (!detection_lock.try_lock_for(timeout))
return nullptr;
{
std::lock_guard<std::mutex> lk(renderer_mutex);
if (detection_done)
{
if (renderer_hook != nullptr)
return renderer_hook;
detection_done = false;
}
}
SPDLOG_TRACE("Started renderer detection.");
auto start_time = std::chrono::steady_clock::now();
do
{
std::unique_lock<std::mutex> lck(stop_detection_mutex);
if (detection_done)
break;
for (auto const& library : libraries)
{
void* lib_handle = System::Library::GetLibraryHandle(library.first);
if (lib_handle != nullptr)
{
std::lock_guard<std::mutex> lk(renderer_mutex);
std::string lib_path = System::Library::GetLibraryPath(lib_handle);
(this->*library.second)(lib_path);
}
}
stop_detection_cv.wait_for(lck, std::chrono::milliseconds{ 100 });
} while (!detection_done && (timeout.count() == -1 || (std::chrono::steady_clock::now() - start_time) <= timeout));
{
std::lock_guard<std::mutex> lk(renderer_mutex);
detection_done = true;
detection_hooks.UnhookAll();
opengl_hooked = false;
delete opengl_hook; opengl_hook = nullptr;
//delete vulkan_hook; vulkan_hook = nullptr;
}
SPDLOG_TRACE("Renderer detection done {}.", (void*)renderer_hook);
return renderer_hook;
}
void stop_detection()
{
{
System::scoped_lock lk(renderer_mutex, stop_detection_mutex);
detection_done = true;
}
stop_detection_cv.notify_all();
}
};
Renderer_Detector* Renderer_Detector::instance = nullptr;
#endif
namespace ingame_overlay {
std::future<Renderer_Hook*> DetectRenderer(std::chrono::milliseconds timeout)
{
return std::async(std::launch::async, &Renderer_Detector::detect_renderer, Renderer_Detector::Inst(), timeout);
}
void StopRendererDetection()
{
Renderer_Detector::Inst()->stop_detection();
}
}