RCE Endeavors 😅

August 1, 2022

Creating an ESP: Drawing (3/4)

Filed under: Game Hacking,Programming,Reverse Engineering — admin @ 8:42 PM

Table of Contents:

Now that we are at a point where we can get screen coordinates for an entity, the drawing part should be simple. We will start off with a very basic approach: drawing externally. This will be done by calling the DrawText function in GDI. We can create a function to achieve this as follows:

void DrawTextGDI(const Vector2& screenPosition, const std::string text) {

    auto windowHandle = FindWindow(L"Valve001", L"HALF-LIFE 2 - Direct3D 9");
    auto windowDC = GetDC(windowHandle);

    SetBkColor(windowDC, RGB(0, 0, 0));
    SetBkMode(windowDC, TRANSPARENT);
    SetTextColor(windowDC, RGB(0xFF, 0xA5, 0x00));

    RECT rect{};
    DrawTextA(windowDC, text.c_str(), text.length(), &rect, DT_CALCRECT);
    auto size{ rect.right -= rect.left };
    rect.left = static_cast<LONG>(screenPosition.x - size / 2.0f);
    rect.right = static_cast<LONG>(screenPosition.x + size / 2.0f);
    rect.top = static_cast<LONG>(screenPosition.y - 20);
    rect.bottom = rect.top + size;

    DrawTextA(windowDC, text.c_str(), -1, &rect, DT_NOCLIP);
}

Here we find the game window, get the device context, and draw the text. We can write a function to loop through the entity list and draw the text above enemy entities.

void DrawEnemyEntityText() {

    auto* serverEntity{ reinterpret_cast<IServerEntity*>(
    GetServerTools()->FirstEntity()) };

    if (serverEntity != nullptr) {
        do {
            auto* modelName{ serverEntity->GetModelName().ToCStr() };
            if (modelName != nullptr) {
                auto entityName{ std::string{GetEntityName(serverEntity)} };

                if (IsEntityEnemy(entityName)) {

                    auto enemyEyePosition{ GetEyePosition(serverEntity) };
                    Vector2 screenPosition{};
                    auto shouldDraw{ WorldToScreen(enemyEyePosition, screenPosition) };

                    if (shouldDraw) {
                        DrawTextGDI(screenPosition, entityName);
                    }
                }
            }

            serverEntity = reinterpret_cast<IServerEntity*>(
                GetServerTools()->NextEntity(serverEntity));

        } while (serverEntity != nullptr);
    }
}

Calling this function in a loop and looking at the results, we see the following

The results look pretty good. However, if your refresh rate is high enough, there will be a very noticeable flicker in the text. What is happening is that the game’s rendering is conflicting with our drawing; we are trying to constantly draw something on the screen (our text) and the game engine is also trying to constantly draw something on the screen (the player’s view). There are a couple of ways to get around this: you can use SetWindowLongPtr to subclass the window and install a new window procedure. This will allow you to handle WM_PAINT messages and draw your text. Or you can create an entirely new window to draw on and keep it activated at the foreground, though this approach has problems with games running in full screen mode.

Ideally we would want to render our text the same way that the game renders its graphics. This is possible, but it will require additional work. To have the game render our text, we will need to hook in to the function that gets executed after rendering. In Direct3D9 games this is the EndScene function, and will be our target. Fortunately, finding this function is pretty easy. Since Microsoft ships symbols for d3d9.dll, we can attach a debugger, load the symbols, and get the address.

From here we can create a function pointer to it as we did earlier for the interfaces

DWORD_PTR GetEndSceneAddress() {

    constexpr auto globalEndSceneOffset = 0x5C0B0;
    auto endSceneAddress{ reinterpret_cast<DWORD_PTR>(
    GetModuleHandle(L"d3d9.dll")) + globalEndSceneOffset };

    return endSceneAddress;
}

Now that the function address is known, we can install a hook on it. To do this, I will be reusing the HookEngine class from an earlier article series. The hook logic will be pretty simple: we will call the DrawEntityEnemyText function as before and then call the original EndScene function.

HRESULT WINAPI EndSceneHook(IDirect3DDevice9* device) {

    DrawEnemyEntityText(device);

    using EndSceneFnc = HRESULT(WINAPI*)(IDirect3DDevice9* device);
    auto original{ (EndSceneFnc)HookEngine::GetOriginalAddressFromHook(EndSceneHook) };
    HRESULT result{};
    if (original != nullptr) {
        result = original(device);
    }

    return result;
}

In DrawEnemyEntityText, instead of calling DrawTextGDI, we will write a new function, DrawTextD3D9, which will draw text using Direct3D APIs.

ID3DXFont* GetFont(IDirect3DDevice9* device) {

    static ID3DXFont* font{};

    if (font != nullptr) {
        return font;
    }

    if (device == nullptr) {
        std::cerr << "No device to create font for."
            << std::endl;
        return nullptr;
    }

    D3DXFONT_DESC fontDesc {
        .Height = 30,
        .Width = 0,
        .Weight = FW_REGULAR,
        .MipLevels = 0,
        .Italic = false,
        .CharSet = DEFAULT_CHARSET,
        .OutputPrecision = OUT_DEFAULT_PRECIS,
        .Quality = DEFAULT_QUALITY,
        .PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE,
        .FaceName = L"Consolas"
    };

    auto result{ D3DXCreateFontIndirect(device, &fontDesc, &font) };
    if (FAILED(result))
    {
        std::cerr << "Could not create font. Error = "
            << std::hex << result
            << std::endl;
    }

    return font;
}

void DrawTextD3D9(const Vector2& screenPosition, const std::string text, IDirect3DDevice9* device) {

    RECT rect{};
    GetFont(device)->DrawTextA(nullptr, text.c_str(), text.length(), &rect, DT_CALCRECT, D3DCOLOR_XRGB(0, 0, 0));
    int size{ rect.right -= rect.left };
    rect.left = static_cast<LONG>(screenPosition.x - size / 2.0f);
    rect.right = static_cast<LONG>(screenPosition.x + size / 2.0f);
    rect.top = static_cast<LONG>(screenPosition.y - 20);
    rect.bottom = rect.top + size;
    GetFont(device)->DrawTextA(nullptr, text.c_str(), -1, &rect, DT_NOCLIP, D3DCOLOR_XRGB(0xFF, 0xA5, 0x00));
}

The DrawTextD3D9 function looks very close to DrawTextGDI, but it performs its drawing on the game’s IDirect3DDevice9 device instead of directly on top of the window. As a result, we are rendering our text in the same rendering pipeline, and the text flicker will not be present. You can see the before and after below.

We now have a functional proof of concept that performs world-to-screen transformation of entities and displays text above their heads. The steps shown throughout this series are common to all ESP hacks and can be used as a reference in building more complex ones.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

 

Powered by WordPress