#include "core/stdafx.h" #ifndef DEDICATED // This file should not be compiled for DEDICATED! //------------------------------ #define STB_IMAGE_IMPLEMENTATION #include "tier0/threadtools.h" #include "tier1/cvar.h" #include "windows/id3dx.h" #include "windows/input.h" #include "gameui/IConsole.h" #include "gameui/IBrowser.h" #include "inputsystem/inputsystem.h" #include "public/bitmap/stb_image.h" /********************************************************************************** ----------------------------------------------------------------------------------- File : id3dx.cpp Date : 15:06:2021 Author : Kawe Mazidjatari Purpose: Microsoft DirectX 11 'IDXGISwapChain::Present' hook implementation ----------------------------------------------------------------------------------- History: - 15:06:2021 | 14:56 : Created by Kawe Mazidjatari - 17:06:2021 | 13:12 : Destroy / release objects with 'GetResizeBuffers' callback **********************************************************************************/ /////////////////////////////////////////////////////////////////////////////////// typedef BOOL(WINAPI* IPostMessageA)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); typedef BOOL(WINAPI* IPostMessageW)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); /////////////////////////////////////////////////////////////////////////////////// static BOOL g_bInitMenu = false; static BOOL g_bInitialized = false; static BOOL g_bPresentHooked = false; static BOOL g_bImGuiInitialized = false; /////////////////////////////////////////////////////////////////////////////////// static WNDPROC g_oWndProc = NULL; static HWND g_hGameWindow = NULL; extern DWORD g_dThreadId = NULL; /////////////////////////////////////////////////////////////////////////////////// static IPostMessageA g_oPostMessageA = NULL; static IPostMessageW g_oPostMessageW = NULL; /////////////////////////////////////////////////////////////////////////////////// static IDXGIResizeBuffers g_oResizeBuffers = NULL; static IDXGISwapChainPresent g_fnIDXGISwapChainPresent = NULL; static IDXGISwapChain* g_pSwapChain = nullptr; static ID3D11DeviceContext* g_pDeviceContext = nullptr; static ID3D11Device* g_pDevice = nullptr; static ID3D11RenderTargetView* g_pRenderTargetView = nullptr; static ID3D11DepthStencilView* g_pDepthStencilView = nullptr; //################################################################################# // WINDOW PROCEDURE //################################################################################# LRESULT CALLBACK DXGIMsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hWnd, uMsg, wParam, lParam); } LRESULT CALLBACK HwndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam); if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) { if (wParam == g_pImGuiConfig->IConsole_Config.m_nBind0 || wParam == g_pImGuiConfig->IConsole_Config.m_nBind1) { g_pConsole->m_bActivate ^= true; } if (wParam == g_pImGuiConfig->IBrowser_Config.m_nBind0 || wParam == g_pImGuiConfig->IBrowser_Config.m_nBind1) { g_pBrowser->m_bActivate ^= true; } } if (g_pConsole->m_bActivate || g_pBrowser->m_bActivate) {////////////////////////////////////////////////////////////////////////////// g_bBlockInput = true; switch (uMsg) { case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: case WM_KEYDOWN: case WM_KEYUP: case WM_MOUSEACTIVATE: case WM_MOUSEHOVER: case WM_MOUSEHWHEEL: case WM_MOUSELEAVE: case WM_MOUSEMOVE: case WM_MOUSEWHEEL: case WM_SETCURSOR: return 1L; default: break; } }////////////////////////////////////////////////////////////////////////////// else { g_bBlockInput = false; } /////////////////////////////////////////////////////////////////////////////// return CallWindowProc(g_oWndProc, hWnd, uMsg, wParam, lParam); } //################################################################################# // POST MESSAGE //################################################################################# BOOL WINAPI HPostMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (g_bBlockInput && Msg == WM_MOUSEMOVE) { return TRUE; } return g_oPostMessageA(hWnd, Msg, wParam, lParam); } BOOL WINAPI HPostMessageW(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (g_bBlockInput && Msg == WM_MOUSEMOVE) { return TRUE; } return g_oPostMessageW(hWnd, Msg, wParam, lParam); } //################################################################################# // IDXGI PRESENT //################################################################################# void GetPresent() { WNDCLASSEXA wc = { sizeof(WNDCLASSEX), CS_CLASSDC, DXGIMsgProc, 0L, 0L, GetModuleHandleA(NULL), NULL, NULL, NULL, NULL, "DX", NULL }; RegisterClassExA(&wc); HWND hWnd = CreateWindowA("DX", NULL, WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, wc.hInstance, NULL); DXGI_SWAP_CHAIN_DESC sd = { 0 }; D3D_FEATURE_LEVEL nFeatureLevelsSet = D3D_FEATURE_LEVEL_11_0; D3D_FEATURE_LEVEL nFeatureLevelsSupported; ZeroMemory(&sd, sizeof(sd)); /////////////////////////////////////////////////////////////////////////////// sd.BufferCount = 1; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; sd.BufferDesc.Height = 800; sd.BufferDesc.Width = 600; sd.BufferDesc.RefreshRate = { 60, 1 }; sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; sd.Windowed = TRUE; sd.OutputWindow = hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; /////////////////////////////////////////////////////////////////////////////// g_hGameWindow = sd.OutputWindow; UINT nFeatureLevelsRequested = 1; HRESULT hr = 0; IDXGISwapChain* pSwapChain = nullptr; ID3D11Device* pDevice = nullptr; ID3D11DeviceContext* pContext = nullptr; /////////////////////////////////////////////////////////////////////////////// if (FAILED(hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, &nFeatureLevelsSet, nFeatureLevelsRequested, D3D11_SDK_VERSION, &sd, &pSwapChain, &pDevice, &nFeatureLevelsSupported, &pContext))) { Error(eDLL_T::MS, EXIT_FAILURE, "Failed to create device and swap chain: error code = %08x\n", hr); return; } /////////////////////////////////////////////////////////////////////////////// DWORD_PTR* pSwapChainVtable = nullptr; DWORD_PTR* pContextVTable = nullptr; DWORD_PTR* pDeviceVTable = nullptr; pSwapChainVtable = (DWORD_PTR*)pSwapChain; pSwapChainVtable = (DWORD_PTR*)pSwapChainVtable[0]; pContextVTable = (DWORD_PTR*)pContext; pContextVTable = (DWORD_PTR*)pContextVTable[0]; pDeviceVTable = (DWORD_PTR*)pDevice; pDeviceVTable = (DWORD_PTR*)pDeviceVTable[0]; int pIDX = (int)DXGISwapChainVTbl::Present; int rIDX = (int)DXGISwapChainVTbl::ResizeBuffers; g_fnIDXGISwapChainPresent = (IDXGISwapChainPresent)(DWORD_PTR)pSwapChainVtable[pIDX]; g_oResizeBuffers = (IDXGIResizeBuffers)(DWORD_PTR)pSwapChainVtable[rIDX]; pSwapChain->Release(); pContext->Release(); pDevice->Release(); /////////////////////////////////////////////////////////////////////////////// g_bPresentHooked = true; } //################################################################################# // INITIALIZATION //################################################################################# void SetupImGui() { /////////////////////////////////////////////////////////////////////////////// IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGui_ImplWin32_Init(g_hGameWindow); ImGui_ImplDX11_Init(g_pDevice, g_pDeviceContext); ImGui::GetIO().ImeWindowHandle = g_hGameWindow; /////////////////////////////////////////////////////////////////////////////// ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_IsSRGB; g_bImGuiInitialized = true; } void DrawImGui() { ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); g_pBrowser->RunTask(); g_pConsole->RunTask(); if (g_pBrowser->m_bActivate) { g_pInputSystem->EnableInput(false); // Disable input to game when browser is drawn. g_pBrowser->RunFrame(); } if (g_pConsole->m_bActivate) { g_pInputSystem->EnableInput(false); // Disable input to game when console is drawn. g_pConsole->RunFrame(); } if (!g_pConsole->m_bActivate && !g_pBrowser->m_bActivate) { g_pInputSystem->EnableInput(true); // Enable input to game when both are not drawn. } ImGui::EndFrame(); ImGui::Render(); g_pDeviceContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); } void CreateRenderTarget(IDXGISwapChain* pSwapChain) { /////////////////////////////////////////////////////////////////////////////// DXGI_SWAP_CHAIN_DESC sd {}; D3D11_RENDER_TARGET_VIEW_DESC rd {}; ID3D11Texture2D* pBackBuffer = nullptr; /////////////////////////////////////////////////////////////////////////////// pSwapChain->GetDesc(&sd); ZeroMemory(&rd, sizeof(rd)); g_hGameWindow = sd.OutputWindow; rd.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; rd.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; /////////////////////////////////////////////////////////////////////////////// pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); if (pBackBuffer != NULL) { g_pDevice->CreateRenderTargetView(pBackBuffer, &rd, &g_pRenderTargetView); } g_pDeviceContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL); pBackBuffer->Release(); } void CreateViewPort( UINT nWidth, UINT nHeight) { float width = *(float*)(&nWidth); float height = *(float*)(&nHeight); D3D11_VIEWPORT vp{}; /////////////////////////////////////////////////////////////////////////////// vp.Width = width; vp.Height = height; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; /////////////////////////////////////////////////////////////////////////////// g_pDeviceContext->RSSetViewports(1, &vp); } void DestroyRenderTarget() { if (g_pRenderTargetView != nullptr) { g_pRenderTargetView->Release(); g_pRenderTargetView = nullptr; g_pDeviceContext->OMSetRenderTargets(0, 0, 0); if (mat_showdxoutput->GetBool()) { DevMsg(eDLL_T::MS, "+----------------------------------------------------------------+\n"); DevMsg(eDLL_T::MS, "| >>>>>>>>>>>>| !! RENDER TARGET VIEW DESTROYED !! |<<<<<<<<<<<< |\n"); DevMsg(eDLL_T::MS, "+----------------------------------------------------------------+\n"); } } } //################################################################################# // INTERNALS //################################################################################# HRESULT GetDeviceAndCtxFromSwapchain(IDXGISwapChain* pSwapChain, ID3D11Device** ppDevice, ID3D11DeviceContext** ppContext) { HRESULT ret = pSwapChain->GetDevice(__uuidof(ID3D11Device), (PVOID*)ppDevice); if (SUCCEEDED(ret)) { (*ppDevice)->GetImmediateContext(ppContext); } return ret; } HRESULT __stdcall GetResizeBuffers(IDXGISwapChain* pSwapChain, UINT nBufferCount, UINT nWidth, UINT nHeight, DXGI_FORMAT dxFormat, UINT nSwapChainFlags) { g_pConsole->m_bActivate = false; g_pBrowser->m_bActivate = false; g_bInitialized = false; g_bPresentHooked = false; g_nWindowWidth = nWidth; g_nWindowHeight = nHeight; /////////////////////////////////////////////////////////////////////////////// DestroyRenderTarget(); /////////////////////////////////////////////////////////////////////////////// return g_oResizeBuffers(pSwapChain, nBufferCount, nWidth, nHeight, dxFormat, nSwapChainFlags); } HRESULT __stdcall Present(IDXGISwapChain* pSwapChain, UINT nSyncInterval, UINT nFlags) { if (!g_bInitialized) { HRESULT hr = 0; if (FAILED(hr = GetDeviceAndCtxFromSwapchain(pSwapChain, &g_pDevice, &g_pDeviceContext))) { Error(eDLL_T::MS, EXIT_FAILURE, "Failed to get device and context from swap chain: error code = %08x\n", hr); return g_fnIDXGISwapChainPresent(pSwapChain, nSyncInterval, nFlags); } CreateRenderTarget(pSwapChain); if (!g_oWndProc) { // Only initialize HwndProc pointer once to avoid stack overflow during ResizeBuffers(..) SetupImGui(); // Don't re-init imgui every time. g_oWndProc = (WNDPROC)SetWindowLongPtr(g_hGameWindow, GWLP_WNDPROC, (LONG_PTR)HwndProc); } g_pSwapChain = pSwapChain; g_ThreadRenderThreadID = GetCurrentThreadId(); g_bInitialized = true; } DrawImGui(); /////////////////////////////////////////////////////////////////////////////// return g_fnIDXGISwapChainPresent(pSwapChain, nSyncInterval, nFlags); } bool LoadTextureBuffer(unsigned char* buffer, int len, ID3D11ShaderResourceView** out_srv, int* out_width, int* out_height) { // Load PNG buffer to a raw RGBA buffer int image_width = 0; int image_height = 0; unsigned char* image_data = stbi_load_from_memory(buffer, len, &image_width, &image_height, NULL, 4); if (image_data == NULL) { assert(image_data == NULL); return false; } /////////////////////////////////////////////////////////////////////////////// ID3D11Texture2D* pTexture = NULL; D3D11_TEXTURE2D_DESC desc; D3D11_SUBRESOURCE_DATA subResource{}; D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; /////////////////////////////////////////////////////////////////////////////// ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; desc.Height = image_height; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; /////////////////////////////////////////////////////////////////////////////// subResource.pSysMem = image_data; subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; g_pDevice->CreateTexture2D(&desc, &subResource, &pTexture); // Create texture view ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; if (pTexture) { g_pDevice->CreateShaderResourceView(pTexture, &srvDesc, out_srv); pTexture->Release(); } *out_width = image_width; *out_height = image_height; stbi_image_free(image_data); return true; } //################################################################################# // MANAGEMENT //################################################################################# void InstallDXHooks() { /////////////////////////////////////////////////////////////////////////////// g_oPostMessageA = (IPostMessageA)DetourFindFunction("user32.dll", "PostMessageA"); g_oPostMessageW = (IPostMessageW)DetourFindFunction("user32.dll", "PostMessageW"); // Begin the detour transaction DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); // Hook PostMessage DetourAttach(&(LPVOID&)g_oPostMessageA, (PBYTE)HPostMessageA); DetourAttach(&(LPVOID&)g_oPostMessageW, (PBYTE)HPostMessageW); // Hook SwapChain DetourAttach(&(LPVOID&)g_fnIDXGISwapChainPresent, (PBYTE)Present); DetourAttach(&(LPVOID&)g_oResizeBuffers, (PBYTE)GetResizeBuffers); // Commit the transaction HRESULT hr = DetourTransactionCommit(); if (hr != NO_ERROR) { // Failed to hook into the process, terminate Error(eDLL_T::COMMON, 0xBAD0C0DE, "Failed to detour process: error code = %08x\n", hr); } } void DirectX_Shutdown() { // Begin the detour transaction DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); // Unhook PostMessage DetourDetach(&(LPVOID&)g_oPostMessageA, (PBYTE)HPostMessageA); DetourDetach(&(LPVOID&)g_oPostMessageW, (PBYTE)HPostMessageW); // Unhook SwapChain DetourDetach(&(LPVOID&)g_fnIDXGISwapChainPresent, (PBYTE)Present); DetourDetach(&(LPVOID&)g_oResizeBuffers, (PBYTE)GetResizeBuffers); // Commit the transaction DetourTransactionCommit(); /////////////////////////////////////////////////////////////////////////////// // Shutdown ImGui if (g_bImGuiInitialized) { ImGui_ImplWin32_Shutdown(); ImGui_ImplDX11_Shutdown(); g_bImGuiInitialized = false; } g_bInitialized = false; } void VDXGI::GetAdr(void) const { /////////////////////////////////////////////////////////////////////////////// spdlog::debug("| FUN: IDXGISwapChain::Present : {:#18x} |\n", reinterpret_cast(g_fnIDXGISwapChainPresent)); spdlog::debug("| VAR: g_pSwapChain : {:#18x} |\n", reinterpret_cast(g_pSwapChain) ); spdlog::debug("| VAR: g_pRenderTargetView : {:#18x} |\n", reinterpret_cast(g_pRenderTargetView) ); spdlog::debug("| VAR: g_pDeviceContext : {:#18x} |\n", reinterpret_cast(g_pDeviceContext) ); spdlog::debug("| VAR: g_pDevice : {:#18x} |\n", reinterpret_cast(g_pDevice) ); spdlog::debug("| VAR: g_ppGameDevice : {:#18x} |\n", reinterpret_cast(g_ppGameDevice) ); spdlog::debug("+----------------------------------------------------------------+\n"); } //################################################################################# // ENTRYPOINT //################################################################################# DWORD __stdcall DXSwapChainWorker(LPVOID) { g_pImGuiConfig->Load(); // Load ImGui configs. GetPresent(); InstallDXHooks(); return true; } void DirectX_Init() { // Create a worker thread for the in-game console frontend DWORD __stdcall DXSwapChainWorker(LPVOID); HANDLE hThread = CreateThread(NULL, 0, DXSwapChainWorker, NULL, 0, &g_dThreadId); if (hThread) { CloseHandle(hThread); } } #endif // !DEDICATED