[C++] GUWOP Thread Safe Rendering with D3D9

Joined
Jul 30, 2023
Messages
5
Reaction score
4
Salutations, niggas.

It is I, GUWOP, once again, speaking to you live from the bando, here to help out some young niggas with their shizz.

In this trapping episode, we will implement basic thread-safe rendering to our p100 hack, and use it to draw a dot at our origin.

This will be later used for our ESP, which will be another episode.

First, here's how to get a player's position:
Code:
void CPlayer::GetOrigin(Vector3D* vTo) const
{
    const DWORD* pLocalPlayer = (const DWORD*)this;
    int v7 = pLocalPlayer[169]; // 0x68AFF
    const float* v8 = *(const float**)(v7 + 20);
    (*vTo)[0] = v8[12];
    (*vTo)[1] = v8[13];
    (*vTo)[2] = v8[14];
}

Aight, aight, but this shizz 3D, how do I put it on my screen?

Needn't worry, GUWOP got you.
Code:
static constexpr DWORD g_worldToScreen = 0x71C20;
static constexpr DWORD g_dx = 0x26EB60;

CData::CData() 
{
    // ...
    m_pDx = *(CDx**)((DWORD)(m_hmSampDll)+g_dx);
    // ...
}

void CData::WorldToScreen(const Vector3D* pSrc, Vector3D* pOut)
{
    typedef void(__thiscall* WorldToScreenFn_t)(CDx*, const Vector3D*, Vector3D*);
    static WorldToScreenFn_t fnWorldtoScreen = (WorldToScreenFn_t)((DWORD)(m_hmSampDll)+g_worldToScreen);
    fnWorldtoScreen(m_pDx, pSrc, pOut);
}

Dayum! Aight, aight, but where do I use this?

We want to be in a game thread, so we can interact with players safely.

but... SHEEIT! how will I draw my D3D9 ESP then? I can't just do that kinda shit wherever I want!

well, GUWOP got you here too... Lets get basic drawing on then first.

We'll begin with the renderer code, then with implementing it.

Code:
#pragma once

class IDrawable
{
public:
    virtual ~IDrawable() {}

    virtual void Draw(IDirect3DDevice9*) = 0;
};

class CRectangle : public IDrawable
{
public:
    CRectangle(const D3DRECT rect, D3DCOLOR color);

    void Draw(IDirect3DDevice9*) override;

private:
    D3DRECT m_rect;
    D3DCOLOR m_color;
};

class CQueue
{
public:
    CQueue();

    std::shared_mutex& GetMutex();

    void Push(IDrawable*);
    void Clear();

    inline decltype(auto) begin()
    {
        return m_drawables.begin();
    }

    inline decltype(auto) end()
    {
        return m_drawables.end();
    }

private:
    std::shared_mutex m_mutex;
    std::deque<std::unique_ptr<IDrawable>> m_drawables;
};

class CDrawing
{
public:
    CDrawing();

    void PushDrawables(std::function<void(CDrawing*)>&&);
    void DrawQueue(IDirect3DDevice9*);

    CQueue& GetQueue();

private:
    CQueue m_queue;
};

Code:
#include "pch.h"
#include "Draw.h"

CRectangle::CRectangle(const D3DRECT rect, D3DCOLOR color)
{
    m_rect = rect;
    m_color = color;
}

void CRectangle::Draw(IDirect3DDevice9* pDevice)
{
    pDevice->Clear(1, &m_rect, D3DCLEAR_TARGET, m_color, 0.f, 0);
}

CQueue::CQueue()
{
}

std::shared_mutex& CQueue::GetMutex()
{
    return m_mutex;
}

void CQueue::Push(IDrawable* pDrawable)
{
    m_drawables.push_front(std::move(std::unique_ptr<IDrawable>(pDrawable)));
}

void CQueue::Clear()
{
    m_drawables.clear();
}

CDrawing::CDrawing()
{
}

void CDrawing::PushDrawables(std::function<void(CDrawing*)>&& fn)
{
    std::unique_lock _(m_queue.GetMutex());

    m_queue.Clear();

    fn(this);
}

void CDrawing::DrawQueue(IDirect3DDevice9* pDevice)
{
    std::unique_lock _(m_queue.GetMutex());

    for (auto&& pDrawable : m_queue)
        pDrawable->Draw(pDevice);
}

CQueue& CDrawing::GetQueue()
{
    return m_queue;
}

CDrawing g_drawing{};

If you want more methods for drawing, then I'm sure you can figure it out, or wait until the next GUWOP episode.

Aight, let's get some drawing in this biii then.

We first want to hook Present, and throw our shit right before we present to screen.

Lets get a device pointer first, as always, GUWOP got you.

Code:
IDirect3DDevice9* CContext::GetDevice() const
{
    return *(IDirect3DDevice9**)((DWORD)(this) + 327);
}

static constexpr DWORD g_context = 0x26EB88;

CData::CData() 
{
    m_pContext = *(CContext**)((DWORD)(m_hmSampDll)+g_context);
}

Now, the hooks, and example code for drawing a rectangle at our origin.

Code:
typedef int(__cdecl* GtaSaEveryFrame_t)(int, int, int);
static GtaSaEveryFrame_t g_ogGtaSaEveryFrame = nullptr;
int __cdecl GtaSaEveryFrameHook(int a, int b, int c)
{
    // drawing
    CPlayer* pLocal = g_data.GetLocalPlayer();
    if (pLocal)
    {
        // get origin
        Vector3D vOrigin;
        pLocal->GetOrigin(&vOrigin);

        // get screen position
        Vector3D vScreen;
        g_data.WorldToScreen(&vOrigin, &vScreen);

        // queue origin rectangle to renderer
        g_drawing.PushDrawables([vScreen](CDrawing* pDrawing) {
            CQueue& queue = pDrawing->GetQueue();

            const D3DRECT rect
            {
                .x1 = (LONG)(vScreen[0]) - 2,
                .y1 = (LONG)(vScreen[1]) - 2,
                .x2 = (LONG)(vScreen[0]) + 2,
                .y2 = (LONG)(vScreen[1]) + 2
            };
            queue.Push(new CRectangle(rect, D3DCOLOR_XRGB(255, 255, 0)));
        });
    }
    
    Hook::Remove(GtaSaEveryFrameHook);
    int r = g_ogGtaSaEveryFrame(a, b, c);
    Hook::Reinstall(GtaSaEveryFrameHook);

    return r;
}


typedef void(_stdcall* PresentFn_t)(IDirect3DDevice9* pDevice, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion);
static PresentFn_t g_ogPresent = nullptr;
static void __stdcall PresentHook(IDirect3DDevice9* pDevice, CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion)
{
    // render accumulated queue
    g_drawing.DrawQueue(pDevice);

    Hook::Remove(PresentHook);
    g_ogPresent(pDevice, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
    Hook::Reinstall(PresentHook);
}


static constexpr DWORD g_fnGtaSaEveryFrame = 0x7F99B0; // can't disassemble in IDA, try ghidra decompiler
                                                        // it's also stored inside "dword_C97B24 + 152" but
                                                        // going there after, the fp is null
void Hooks::Install()
{
    Hook::Install((void*)((DWORD)(g_data.GetGtaExe()) + g_fnGtaSaEveryFrame), GtaSaEveryFrameHook, (void**)&g_ogGtaSaEveryFrame);
    void* pPresent = (*(void***)(g_data.GetContext()->GetDevice()))[17];
    Hook::Install(pPresent, PresentHook, (void**)&g_ogPresent);
}

The queue clearing should be moved at the start of the hook, ideally you'd encapsulate that part and give another class as a pointer to a lambda which provides a push drawables function, so it isn't used wrong. Appropriate changes will be provided in the next GUWOP episode.

Until next time,
GUWOP - SCOOCHIE!
 
Joined
Jul 30, 2023
Messages
5
Reaction score
4
I cannot edit the post, however I have to make a clarification. The code is for SA:MP 0.3.7-R5 (MD5 5BA5F0BE7AF99DFD03FB39E88A970A2B) and gta_sa (MD5 170B3A9108687B26DA2D8901C6948A18).

Kind regards
 
Top