c++capturedirect3d9

Direct3D9 BackBuffer to PixelArray


I'm trying to hook a DirectX game. I successfully loaded my hook and I'm able to save images/backbuffer to the disk using:

HRESULT Capture(IDirect3DDevice9* Device, const char* FilePath)
{
    IDirect3DSurface9* RenderTarget = nullptr;
    HRESULT result = Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &RenderTarget);
    //for some reason result is never S_OK but this still works.
    result = D3DXSaveSurfaceToFile(FilePath, D3DXIFF_PNG, RenderTarget, nullptr, nullptr);
    SafeRelease(RenderTarget);
    return result;
}

It saves successfully and I'm glad. However, I want to save to a pixel array instead of to the disk. I tried using:

#include <memory>

std::unique_ptr<std::uint8_t[]> mem(new std::uint8_t[100 * 100 * 4]);

HRESULT Direct3DDevice9Proxy::EndScene()
{
    IDirect3DSurface9* RenderTarget = nullptr;
    HRESULT result = ptr_Direct3DDevice9->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &RenderTarget);

    D3DLOCKED_RECT LR;
    RenderTarget->LockRect(&LR, nullptr, D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY);
    memcpy(mem.get(), LR.pBits, LR.Pitch - 1);
    RenderTarget->UnlockRect();

    SafeRelease(RenderTarget);
    return ptr_Direct3DDevice9->EndScene();
}

However, it never locks, and it throws an access violation if I try to access LR.pBits. I'm not sure why it won't lock. Is there another way that I can grab the pixels from the backbuffer into a byte array? The game's max viewport is 1366x768.

EDIT: I've tried:

void dump_buffer(LPDIRECT3DDEVICE9 Device, std::unique_ptr<std::uint8_t[]> &bits)
{
    IDirect3DSurface9* RenderTarget = nullptr;
    IDirect3DSurface9* DestTarget = nullptr;
    D3DSURFACE_DESC rtDesc = {};

    Device->GetRenderTarget(0, &RenderTarget);
    RenderTarget->GetDesc(&rtDesc);

    Device->CreateOffscreenPlainSurface(rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DPOOL_DEFAULT, &DestTarget, nullptr);
    Device->GetRenderTargetData(RenderTarget, DestTarget);

    if(DestTarget != nullptr)
    {
        D3DLOCKED_RECT rect;
        DestTarget->LockRect(&rect, 0, D3DLOCK_READONLY);    
        memcpy(bits.get(), rect.pBits, rtDesc.Width * rtDesc.Height * 4);

        std::uint8_t* ptr = &bits[0];
        CG::Image(ptr, rtDesc.Width, rtDesc.Height).Save("Foo.bmp");

        DestTarget->UnlockRect();
        DestTarget->Release();
    }

    RenderTarget->Release();
}

HRESULT Direct3DDevice9Proxy::EndScene()
{
    dump_buffer(ptr_Direct3DDevice9, mem);
    return ptr_Direct3DDevice9->EndScene();
}

but my image is black. Any ideas?


Solution

  • Note that EndScene queues the scene to be rendered - you're not guaranteed that anything has been rendered before that (or even, immediately after).

    IDirect3DDevice9::GetFrontBufferData might be more of use to you. It "Generates a copy of the device's front buffer and places that copy in a system memory buffer provided by the application."

    What you want to do is call CreateOffscreenPlainSurface to create pSurface in system memory and then call GetFrontBufferData(0, pSurface).