Search code examples
c++directxdirectx-11imgui

ImGui Window doesn't show on DLL Injection


  • Current Goal: Trying to inject custom code into DirectX11 Games to display windows.
  • Expected Result: ImGui Demo Window should show up after injection but should not be interactable
  • Result Got: ImGui Demo Window doesn't show up or display anything on injection.

Things I have tried: Making a custom Imgui window and reinjecting, debugging the code to make sure it's being run and is called every frame, making sure nothing is NULL, searching up my problem on google, ImGui GitHub, stack overflow.

D3D11 Init (Called First thing after injection)

    LRESULT CALLBACK DXGIMsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return DefWindowProc(hwnd, uMsg, wParam, lParam); }
    DWORD __stdcall D3D11Init() {
        // Create Dummy Window
        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);

        D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1 };
        D3D_FEATURE_LEVEL obtainedLevel;
        DXGI_SWAP_CHAIN_DESC sd;
        {
            ZeroMemory(&sd, sizeof(sd));
            sd.BufferCount = 1;
            sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
            sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
            sd.OutputWindow = hWnd;
            sd.Windowed = TRUE;
            sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
            sd.SampleDesc.Count = 1;
        }

        HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, levels, sizeof(levels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, &sd, &d3dSwapChain, &d3dDevice, &obtainedLevel, &d3dContext);
        if (FAILED(hr))
        {
            return E_FAIL;
        }

        pSwapChainVTable = (DWORD_PTR*)(d3dSwapChain);
        pSwapChainVTable = (DWORD_PTR*)(pSwapChainVTable[0]);

        pDeviceVTable = (DWORD_PTR*)(d3dDevice);
        pDeviceVTable = (DWORD_PTR*)pDeviceVTable[0];

        pDeviceContextVTable = (DWORD_PTR*)(d3dContext);
        pDeviceContextVTable = (DWORD_PTR*)(pDeviceContextVTable[0]);

        SAFE_RELEASE(d3dSwapChain);
        SAFE_RELEASE(d3dDevice);
        SAFE_RELEASE(d3dContext);
        UnregisterClassA(wc.lpszClassName, wc.hInstance);

        if (MH_Initialize() != MH_OK) { return 1; }
        if (MH_CreateHook((DWORD_PTR*)pSwapChainVTable[8], PresentHook, reinterpret_cast<void**>(&pHookD3D11Present)) != MH_OK) { return 1; }
        if (MH_EnableHook((DWORD_PTR*)pSwapChainVTable[8]) != MH_OK) { return 1; }

        DWORD old_protect;
        VirtualProtect(pHookD3D11Present, 2, PAGE_EXECUTE_READWRITE, &old_protect);
    }

PresentHook (Called everytime the game calls Present Function)

    HRESULT __stdcall PresentHook(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) {
        if(!init) {
            std::cout << "\t[+] Present Hook called First Time!" << std::endl;
            if (FAILED(GetDeviceAndCtxFromSwapChain(pSwapChain, &d3dDevice, &d3dContext)))
                return pHookD3D11Present(pSwapChain, SyncInterval, Flags);

            // Get Game Window Handle
            DXGI_SWAP_CHAIN_DESC sd;
            pSwapChain->GetDesc(&sd);
            window = sd.OutputWindow;

            ImGui::CreateContext();
            ImGuiIO& io = ImGui::GetIO(); (void) io;
            io.IniFilename = NULL;
            io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
            io.DisplaySize = ImVec2(1280, 720);
            
            ImGui_ImplWin32_Init(window);
            ImGui_ImplDX11_Init(d3dDevice, d3dContext);
            init = true;
        };

        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();
        ImGui::ShowDemoWindow();
        ImGui::EndFrame();
        ImGui::Render();
        ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
        return pHookD3D11Present(pSwapChain, SyncInterval, Flags);
    }

Solution

  • The problem was getting the current D3D11RenderTargetView or creating my own D3D11RenderTargetView. Adding parts of the function below or creating a whole new function and adding it to Present Init should fix the problem of ImGui not displaying.

    bool GetDeivceContextRenderTarget(IDXGISwapChain* pSwapChain)
    {
        HRESULT hr = pSwapChain->GetDevice(__uuidof(ID3D11Device), (void**)&pDevice);
        if (FAILED(hr))
            return false;
    
        pDevice->GetImmediateContext(&pContext);
        pContext->OMSetRenderTargets(1, &pRenderTargetView, nullptr);
    
        // If for some reason we fail to get a render target, create one
        if (!pRenderTargetView) {
            ID3D11Texture2D* pBackBuffer = nullptr;
            hr = pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&pBackBuffer));
            if (FAILED(hr))
                return false;
    
            hr = pDevice->CreateRenderTargetView(pBackBuffer, nullptr, &pRenderTargetView);
            pBackBuffer->Release();
            if (FAILED(hr))
                return false;
    
            // Make sure our render target is set, only needed if creating our own, if already exist use original
            pContext->OMSetRenderTargets(1, &pRenderTargetView, nullptr);
        }
        return true;
    }