#define LOG_MODULE "hook-d3d9"

#include "hook/d3d9.h"
#include "hook/com-proxy.h"
#include "hook/table.h"

#include "util/log.h"

/* ------------------------------------------------------------------------------------------------------------------
 */

static IDirect3D9 *(STDCALL *real_Direct3DCreate9)(UINT sdk_ver);

static BOOL(STDCALL *real_EnumDisplayDevicesA)(
    const char *dev_name, DWORD dev_num, DISPLAY_DEVICEA *info, DWORD flags);

static HWND(STDCALL *real_CreateWindowExA)(
    DWORD dwExStyle,
    LPCSTR lpClassName,
    LPCSTR lpWindowName,
    DWORD dwStyle,
    int X,
    int Y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam);

static BOOL(STDCALL *real_GetClientRect)(HWND hWnd, LPRECT lpRect);

static HRESULT(STDCALL *real_CreateDevice)(
    IDirect3D9 *self,
    UINT adapter,
    D3DDEVTYPE type,
    HWND hwnd,
    DWORD flags,
    D3DPRESENT_PARAMETERS *pp,
    IDirect3DDevice9 **pdev);

static HRESULT(STDCALL *real_CreateTexture)(
    IDirect3DDevice9 *self,
    UINT width,
    UINT height,
    UINT levels,
    DWORD usage,
    D3DFORMAT format,
    D3DPOOL pool,
    IDirect3DTexture9 **texture,
    HANDLE *shared_handle);

static HRESULT(STDCALL *real_BeginScene)(IDirect3DDevice9 *self);

static HRESULT(STDCALL *real_EndScene)(IDirect3DDevice9 *self);

static HRESULT(STDCALL *real_Present)(
    IDirect3DDevice9 *self,
    CONST RECT *pSourceRect,
    CONST RECT *pDestRect,
    HWND hDestWindowOverride,
    CONST RGNDATA *pDirtyRegion);

static HRESULT(STDCALL *real_SetRenderState)(
    IDirect3DDevice9 *self, D3DRENDERSTATETYPE State, DWORD Value);

static HRESULT(STDCALL *real_DrawPrimitiveUP)(
    IDirect3DDevice9 *self,
    D3DPRIMITIVETYPE primitive_type,
    UINT primitive_count,
    const void *data,
    UINT stride);

static HRESULT(STDCALL *real_Reset)(
    IDirect3DDevice9 *self, D3DPRESENT_PARAMETERS *pp);

static HRESULT(STDCALL *real_SetViewport)(
    IDirect3DDevice9 *self, const D3DVIEWPORT9 *pViewport);

static HRESULT(STDCALL *real_SetVertexShader)(
    IDirect3DDevice9 *self, IDirect3DVertexShader9 *pShader);

static HRESULT(STDCALL *real_SetScissorRect)(
    IDirect3DDevice9 *self, const RECT *pRect);

/* ------------------------------------------------------------------------------------------------------------------
 */

static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver);

static BOOL STDCALL my_EnumDisplayDevicesA(
    const char *dev_name, DWORD dev_num, DISPLAY_DEVICEA *info, DWORD flags);

static HWND STDCALL my_CreateWindowExA(
    DWORD dwExStyle,
    LPCSTR lpClassName,
    LPCSTR lpWindowName,
    DWORD dwStyle,
    int X,
    int Y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam);

static BOOL STDCALL my_GetClientRect(HWND hWnd, LPRECT lpRect);

static HRESULT STDCALL my_CreateDevice(
    IDirect3D9 *self,
    UINT adapter,
    D3DDEVTYPE type,
    HWND hwnd,
    DWORD flags,
    D3DPRESENT_PARAMETERS *pp,
    IDirect3DDevice9 **pdev);

static HRESULT STDCALL my_CreateTexture(
    IDirect3DDevice9 *self,
    UINT width,
    UINT height,
    UINT levels,
    DWORD usage,
    D3DFORMAT format,
    D3DPOOL pool,
    IDirect3DTexture9 **texture,
    HANDLE *shared_handle);

static HRESULT STDCALL my_BeginScene(IDirect3DDevice9 *self);

static HRESULT STDCALL my_EndScene(IDirect3DDevice9 *self);

static HRESULT STDCALL my_Present(
    IDirect3DDevice9 *self,
    CONST RECT *pSourceRect,
    CONST RECT *pDestRect,
    HWND hDestWindowOverride,
    CONST RGNDATA *pDirtyRegion);

static HRESULT STDCALL my_SetRenderState(
    IDirect3DDevice9 *self, D3DRENDERSTATETYPE State, DWORD Value);

static HRESULT STDCALL my_DrawPrimitiveUP(
    IDirect3DDevice9 *self,
    D3DPRIMITIVETYPE primitive_type,
    UINT primitive_count,
    const void *data,
    UINT stride);

static HRESULT STDCALL
my_Reset(IDirect3DDevice9 *self, D3DPRESENT_PARAMETERS *pp);

static HRESULT STDCALL
my_SetViewport(IDirect3DDevice9 *self, const D3DVIEWPORT9 *pViewport);

static HRESULT STDCALL
my_SetVertexShader(IDirect3DDevice9 *self, IDirect3DVertexShader9 *pShader);

static HRESULT STDCALL
my_SetScissorRect(IDirect3DDevice9 *self, const RECT *pRect);

/* ------------------------------------------------------------------------------------------------------------------
 */

static const struct hook_symbol hook_d3d9_hook_syms[] = {
    {.name = "Direct3DCreate9",
     .patch = my_Direct3DCreate9,
     .link = (void **) &real_Direct3DCreate9},
};

static const struct hook_symbol hook_d3d9_hook_user32_syms[] = {
    {.name = "EnumDisplayDevicesA",
     .patch = my_EnumDisplayDevicesA,
     .link = (void **) &real_EnumDisplayDevicesA},
    {.name = "CreateWindowExA",
     .patch = my_CreateWindowExA,
     .link = (void **) &real_CreateWindowExA},
    {.name = "GetClientRect",
     .patch = my_GetClientRect,
     .link = (void **) &real_GetClientRect},
};

/* ------------------------------------------------------------------------------------------------------------------
 */

static HRESULT hook_d3d9_irp_handler_real_invalid(struct hook_d3d9_irp *irp);

static HRESULT hook_d3d9_irp_handler_real_ctx_create(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_enum_display_devices(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_create_window_ex(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_get_client_rect(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_create_device(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_create_texture(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_begin_scene(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_end_scene(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_present(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_set_render_state(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_draw_primitive_up(struct hook_d3d9_irp *irp);

static HRESULT hook_d3d9_irp_handler_real_dev_reset(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_set_viewport(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_set_vertex_shader(struct hook_d3d9_irp *irp);

static HRESULT
hook_d3d9_irp_handler_real_dev_set_scissor_rect(struct hook_d3d9_irp *irp);

/* ------------------------------------------------------------------------------------------------------------------
 */

static const hook_d3d9_irp_handler_t hook_d3d9_irp_real_handlers[] = {
    [HOOK_D3D9_IRP_OP_INVALID] = hook_d3d9_irp_handler_real_invalid,
    [HOOK_D3D9_IRP_OP_CTX_CREATE] = hook_d3d9_irp_handler_real_ctx_create,
    [HOOK_D3D9_IRP_OP_ENUM_DISPLAY_DEVICES] =
        hook_d3d9_irp_handler_real_enum_display_devices,
    [HOOK_D3D9_IRP_OP_CREATE_WINDOW_EX] =
        hook_d3d9_irp_handler_real_create_window_ex,
    [HOOK_D3D9_IRP_OP_GET_CLIENT_RECT] =
        hook_d3d9_irp_handler_real_get_client_rect,
    [HOOK_D3D9_IRP_OP_CTX_CREATE_DEVICE] =
        hook_d3d9_irp_handler_real_dev_create_device,
    [HOOK_D3D9_IRP_OP_DEV_CREATE_TEXTURE] =
        hook_d3d9_irp_handler_real_dev_create_texture,
    [HOOK_D3D9_IRP_OP_DEV_BEGIN_SCENE] =
        hook_d3d9_irp_handler_real_dev_begin_scene,
    [HOOK_D3D9_IRP_OP_DEV_END_SCENE] = hook_d3d9_irp_handler_real_dev_end_scene,
    [HOOK_D3D9_IRP_OP_DEV_PRESENT] = hook_d3d9_irp_handler_real_dev_present,
    [HOOK_D3D9_IRP_OP_DEV_SET_RENDER_STATE] =
        hook_d3d9_irp_handler_real_dev_set_render_state,
    [HOOK_D3D9_IRP_OP_DEV_DRAW_PRIMITIVE_UP] =
        hook_d3d9_irp_handler_real_dev_draw_primitive_up,
    [HOOK_D3D9_IRP_OP_DEV_RESET] = hook_d3d9_irp_handler_real_dev_reset,
    [HOOK_D3D9_IRP_OP_DEV_SET_VIEWPORT] =
        hook_d3d9_irp_handler_real_dev_set_viewport,
    [HOOK_D3D9_IRP_OP_DEV_SET_VERTEX_SHADER] =
        hook_d3d9_irp_handler_real_dev_set_vertex_shader,
    [HOOK_D3D9_IRP_OP_DEV_SET_SCISSOR_RECT] =
        hook_d3d9_irp_handler_real_dev_set_scissor_rect,
};

static const hook_d3d9_irp_handler_t *hook_d3d9_handlers;
static size_t hook_d3d9_nhandlers;

/* ------------------------------------------------------------------------------------------------------------------
 */

static IDirect3D9 *STDCALL my_Direct3DCreate9(UINT sdk_ver)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_CTX_CREATE;
    irp.args.ctx_create.sdk_ver = sdk_ver;
    irp.args.ctx_create.ctx = NULL;

    hr = hook_d3d9_irp_invoke_next(&irp);

    if (hr == S_OK && irp.args.ctx_create.ctx) {
        return irp.args.ctx_create.ctx;
    } else {
        return NULL;
    }
}

static BOOL STDCALL my_EnumDisplayDevicesA(
    const char *dev_name, DWORD dev_num, DISPLAY_DEVICEA *info, DWORD flags)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_ENUM_DISPLAY_DEVICES;
    irp.args.enum_display_devices.dev_name = dev_name;
    irp.args.enum_display_devices.dev_num = dev_num;
    irp.args.enum_display_devices.info = info;
    irp.args.enum_display_devices.flags = flags;

    hr = hook_d3d9_irp_invoke_next(&irp);

    if (hr == S_OK) {
        return TRUE;
    } else {
        return FALSE;
    }
}

static HWND STDCALL my_CreateWindowExA(
    DWORD dwExStyle,
    LPCSTR lpClassName,
    LPCSTR lpWindowName,
    DWORD dwStyle,
    int X,
    int Y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_CREATE_WINDOW_EX;
    irp.args.create_window_ex.ex_style = dwExStyle;
    irp.args.create_window_ex.class_name = lpClassName;
    irp.args.create_window_ex.window_name = lpWindowName;
    irp.args.create_window_ex.style = dwStyle;
    irp.args.create_window_ex.x = X;
    irp.args.create_window_ex.y = Y;
    irp.args.create_window_ex.width = nWidth;
    irp.args.create_window_ex.height = nHeight;
    irp.args.create_window_ex.wnd_parent = hWndParent;
    irp.args.create_window_ex.menu = hMenu;
    irp.args.create_window_ex.instance = hInstance;
    irp.args.create_window_ex.param = lpParam;
    irp.args.create_window_ex.result = NULL;

    hr = hook_d3d9_irp_invoke_next(&irp);

    if (hr == S_OK && irp.args.create_window_ex.result != NULL) {
        return irp.args.create_window_ex.result;
    } else {
        return NULL;
    }
}

static BOOL STDCALL my_GetClientRect(HWND hWnd, LPRECT lpRect)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_GET_CLIENT_RECT;
    irp.args.get_client_rect.wnd = hWnd;
    irp.args.get_client_rect.rect = lpRect;

    hr = hook_d3d9_irp_invoke_next(&irp);

    if (hr == S_OK) {
        return TRUE;
    } else {
        return FALSE;
    }
}

static HRESULT STDCALL my_CreateDevice(
    IDirect3D9 *self,
    UINT adapter,
    D3DDEVTYPE type,
    HWND hwnd,
    DWORD flags,
    D3DPRESENT_PARAMETERS *pp,
    IDirect3DDevice9 **pdev)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_CTX_CREATE_DEVICE;
    irp.args.ctx_create_device.self = self;
    irp.args.ctx_create_device.adapter = adapter;
    irp.args.ctx_create_device.type = type;
    irp.args.ctx_create_device.hwnd = hwnd;
    irp.args.ctx_create_device.flags = flags;
    irp.args.ctx_create_device.pp = pp;
    irp.args.ctx_create_device.pdev = pdev;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL my_CreateTexture(
    IDirect3DDevice9 *self,
    UINT width,
    UINT height,
    UINT levels,
    DWORD usage,
    D3DFORMAT format,
    D3DPOOL pool,
    IDirect3DTexture9 **texture,
    HANDLE *shared_handle)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_CREATE_TEXTURE;
    irp.args.dev_create_texture.self = self;
    irp.args.dev_create_texture.width = width;
    irp.args.dev_create_texture.height = height;
    irp.args.dev_create_texture.levels = levels;
    irp.args.dev_create_texture.usage = usage;
    irp.args.dev_create_texture.format = format;
    irp.args.dev_create_texture.pool = pool;
    irp.args.dev_create_texture.texture = texture;
    irp.args.dev_create_texture.shared_handle = shared_handle;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL my_BeginScene(IDirect3DDevice9 *self)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_BEGIN_SCENE;
    irp.args.dev_begin_scene.self = self;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL my_EndScene(IDirect3DDevice9 *self)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_END_SCENE;
    irp.args.dev_end_scene.self = self;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL my_Present(
    IDirect3DDevice9 *self,
    CONST RECT *pSourceRect,
    CONST RECT *pDestRect,
    HWND hDestWindowOverride,
    CONST RGNDATA *pDirtyRegion)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_PRESENT;
    irp.args.dev_present.self = self;
    irp.args.dev_present.source_rect = pSourceRect;
    irp.args.dev_present.dest_rect = pDestRect;
    irp.args.dev_present.dest_window_override = hDestWindowOverride;
    irp.args.dev_present.dirty_region = pDirtyRegion;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL
my_SetRenderState(IDirect3DDevice9 *self, D3DRENDERSTATETYPE State, DWORD Value)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_SET_RENDER_STATE;
    irp.args.dev_set_render_state.self = self;
    irp.args.dev_set_render_state.state = State;
    irp.args.dev_set_render_state.value = Value;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL my_DrawPrimitiveUP(
    IDirect3DDevice9 *self,
    D3DPRIMITIVETYPE primitive_type,
    UINT primitive_count,
    const void *data,
    UINT stride)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_DRAW_PRIMITIVE_UP;
    irp.args.dev_draw_primitive_up.self = self;
    irp.args.dev_draw_primitive_up.primitive_type = primitive_type;
    irp.args.dev_draw_primitive_up.primitive_count = primitive_count;
    irp.args.dev_draw_primitive_up.data = data;
    irp.args.dev_draw_primitive_up.stride = stride;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL
my_SetViewport(IDirect3DDevice9 *self, const D3DVIEWPORT9 *pViewport)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_SET_VIEWPORT;
    irp.args.dev_set_viewport.self = self;
    irp.args.dev_set_viewport.pViewport = pViewport;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL
my_SetVertexShader(IDirect3DDevice9 *self, IDirect3DVertexShader9 *pShader)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_SET_VERTEX_SHADER;
    irp.args.dev_set_vertex_shader.self = self;
    irp.args.dev_set_vertex_shader.pShader = pShader;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL
my_Reset(IDirect3DDevice9 *self, D3DPRESENT_PARAMETERS *pp)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_RESET;
    irp.args.dev_reset.self = self;
    irp.args.dev_reset.pp = pp;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

static HRESULT STDCALL
my_SetScissorRect(IDirect3DDevice9 *self, const RECT *pRect)
{
    struct hook_d3d9_irp irp;
    HRESULT hr;

    memset(&irp, 0, sizeof(irp));

    irp.op = HOOK_D3D9_IRP_OP_DEV_SET_SCISSOR_RECT;
    irp.args.dev_set_scissor_rect.self = self;
    irp.args.dev_set_scissor_rect.pRect = pRect;

    hr = hook_d3d9_irp_invoke_next(&irp);

    return hr;
}

/* ------------------------------------------------------------------------------------------------------------------
 */

static HRESULT hook_d3d9_irp_handler_invoke_real(struct hook_d3d9_irp *irp)
{
    hook_d3d9_irp_handler_t handler;

    log_assert(irp != NULL);
    log_assert(irp->op < lengthof(hook_d3d9_irp_real_handlers));

    handler = hook_d3d9_irp_real_handlers[irp->op];

    log_assert(handler != NULL);

    return handler(irp);
}

static HRESULT hook_d3d9_irp_handler_real_invalid(struct hook_d3d9_irp *irp)
{
    log_fatal("Called invalid handler");

    return E_FAIL;
}

static HRESULT hook_d3d9_irp_handler_real_ctx_create(struct hook_d3d9_irp *irp)
{
    IDirect3D9 *api;
    IDirect3D9Vtbl *api_vtbl;
    struct com_proxy *api_proxy;
    HRESULT res;

    log_assert(irp);

    api = real_Direct3DCreate9(irp->args.ctx_create.sdk_ver);

    if (api == NULL) {
        return E_FAIL;
    }

    res = com_proxy_wrap(&api_proxy, api, sizeof(*api->lpVtbl));

    if (res != S_OK) {
        log_fatal("Wrapping com proxy failed: %08lx", res);
    }

    api_vtbl = api_proxy->vptr;

    real_CreateDevice = api_vtbl->CreateDevice;
    api_vtbl->CreateDevice = my_CreateDevice;

    irp->args.ctx_create.ctx = (IDirect3D9 *) api_proxy;

    return S_OK;
}

static HRESULT
hook_d3d9_irp_handler_real_enum_display_devices(struct hook_d3d9_irp *irp)
{
    BOOL res;

    log_assert(irp);

    res = real_EnumDisplayDevicesA(
        irp->args.enum_display_devices.dev_name,
        irp->args.enum_display_devices.dev_num,
        irp->args.enum_display_devices.info,
        irp->args.enum_display_devices.flags);

    if (res == TRUE) {
        return S_OK;
    } else {
        return E_FAIL;
    }
}

static HRESULT
hook_d3d9_irp_handler_real_create_window_ex(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    irp->args.create_window_ex.result = real_CreateWindowExA(
        irp->args.create_window_ex.ex_style,
        irp->args.create_window_ex.class_name,
        irp->args.create_window_ex.window_name,
        irp->args.create_window_ex.style,
        irp->args.create_window_ex.x,
        irp->args.create_window_ex.y,
        irp->args.create_window_ex.width,
        irp->args.create_window_ex.height,
        irp->args.create_window_ex.wnd_parent,
        irp->args.create_window_ex.menu,
        irp->args.create_window_ex.instance,
        irp->args.create_window_ex.param);

    if (irp->args.create_window_ex.result) {
        return S_OK;
    } else {
        return E_FAIL;
    }
}

static HRESULT
hook_d3d9_irp_handler_real_get_client_rect(struct hook_d3d9_irp *irp)
{
    BOOL res;

    log_assert(irp);

    res = real_GetClientRect(
        irp->args.get_client_rect.wnd, irp->args.get_client_rect.rect);

    if (res == TRUE) {
        return S_OK;
    } else {
        return E_FAIL;
    }
}

static HRESULT
hook_d3d9_irp_handler_real_dev_create_device(struct hook_d3d9_irp *irp)
{
    HRESULT hr;
    IDirect3DDevice9 *api;
    IDirect3DDevice9Vtbl *api_vtbl;
    struct com_proxy *api_proxy;

    log_assert(irp);

    hr = real_CreateDevice(
        irp->args.ctx_create_device.self,
        irp->args.ctx_create_device.adapter,
        irp->args.ctx_create_device.type,
        irp->args.ctx_create_device.hwnd,
        irp->args.ctx_create_device.flags,
        irp->args.ctx_create_device.pp,
        irp->args.ctx_create_device.pdev);

    if (hr != S_OK) {
        return hr;
    }

    api = *irp->args.ctx_create_device.pdev;
    hr = com_proxy_wrap(&api_proxy, api, sizeof(*api->lpVtbl));

    if (hr != S_OK) {
        log_warning("Wrapping com proxy failed: %08lx", hr);
        return hr;
    }

    api_vtbl = api_proxy->vptr;

    real_CreateTexture = api_vtbl->CreateTexture;
    api_vtbl->CreateTexture = my_CreateTexture;

    real_BeginScene = api_vtbl->BeginScene;
    api_vtbl->BeginScene = my_BeginScene;

    real_EndScene = api_vtbl->EndScene;
    api_vtbl->EndScene = my_EndScene;

    real_Present = api_vtbl->Present;
    api_vtbl->Present = my_Present;

    real_SetRenderState = api_vtbl->SetRenderState;
    api_vtbl->SetRenderState = my_SetRenderState;

    real_DrawPrimitiveUP = api_vtbl->DrawPrimitiveUP;
    api_vtbl->DrawPrimitiveUP = my_DrawPrimitiveUP;

    real_Reset = api_vtbl->Reset;
    api_vtbl->Reset = my_Reset;

    real_SetViewport = api_vtbl->SetViewport;
    api_vtbl->SetViewport = my_SetViewport;

    real_SetVertexShader = api_vtbl->SetVertexShader;
    api_vtbl->SetVertexShader = my_SetVertexShader;

    real_SetScissorRect = api_vtbl->SetScissorRect;
    api_vtbl->SetScissorRect = my_SetScissorRect;

    *irp->args.ctx_create_device.pdev = (IDirect3DDevice9 *) api_proxy;

    return hr;
}

static HRESULT
hook_d3d9_irp_handler_real_dev_create_texture(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_CreateTexture(
        irp->args.dev_create_texture.self,
        irp->args.dev_create_texture.width,
        irp->args.dev_create_texture.height,
        irp->args.dev_create_texture.levels,
        irp->args.dev_create_texture.usage,
        irp->args.dev_create_texture.format,
        irp->args.dev_create_texture.pool,
        irp->args.dev_create_texture.texture,
        irp->args.dev_create_texture.shared_handle);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_begin_scene(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_BeginScene(irp->args.dev_begin_scene.self);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_end_scene(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_EndScene(irp->args.dev_end_scene.self);
}

static HRESULT hook_d3d9_irp_handler_real_dev_present(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_Present(
        irp->args.dev_present.self,
        irp->args.dev_present.source_rect,
        irp->args.dev_present.dest_rect,
        irp->args.dev_present.dest_window_override,
        irp->args.dev_present.dirty_region);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_set_render_state(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_SetRenderState(
        irp->args.dev_set_render_state.self,
        irp->args.dev_set_render_state.state,
        irp->args.dev_set_render_state.value);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_draw_primitive_up(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_DrawPrimitiveUP(
        irp->args.dev_draw_primitive_up.self,
        irp->args.dev_draw_primitive_up.primitive_type,
        irp->args.dev_draw_primitive_up.primitive_count,
        irp->args.dev_draw_primitive_up.data,
        irp->args.dev_draw_primitive_up.stride);
}

static HRESULT hook_d3d9_irp_handler_real_dev_reset(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_Reset(irp->args.dev_reset.self, irp->args.dev_reset.pp);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_set_viewport(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_SetViewport(
        irp->args.dev_set_viewport.self, irp->args.dev_set_viewport.pViewport);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_set_vertex_shader(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_SetVertexShader(
        irp->args.dev_set_vertex_shader.self,
        irp->args.dev_set_vertex_shader.pShader);
}

static HRESULT
hook_d3d9_irp_handler_real_dev_set_scissor_rect(struct hook_d3d9_irp *irp)
{
    log_assert(irp);

    return real_SetScissorRect(
        irp->args.dev_set_scissor_rect.self,
        irp->args.dev_set_scissor_rect.pRect);
}

/* ------------------------------------------------------------------------------------------------------------------
 */

void hook_d3d9_init(const hook_d3d9_irp_handler_t *handlers, size_t nhandlers)
{
    log_assert(handlers);
    log_assert(nhandlers > 0);

    hook_d3d9_handlers = handlers;
    hook_d3d9_nhandlers = nhandlers;

    hook_table_apply(
        NULL, "d3d9.dll", hook_d3d9_hook_syms, lengthof(hook_d3d9_hook_syms));

    hook_table_apply(
        NULL,
        "user32.dll",
        hook_d3d9_hook_user32_syms,
        lengthof(hook_d3d9_hook_user32_syms));
}

HRESULT
hook_d3d9_irp_invoke_next(struct hook_d3d9_irp *irp)
{
    hook_d3d9_irp_handler_t handler;
    HRESULT hr;

    log_assert(irp != NULL);
    log_assert(irp->next_handler <= hook_d3d9_nhandlers);

    if (irp->next_handler < hook_d3d9_nhandlers) {
        handler = hook_d3d9_handlers[irp->next_handler++];
        hr = handler(irp);

        if (FAILED(hr)) {
            irp->next_handler = (size_t) -1;
        }
    } else {
        irp->next_handler = (size_t) -1;
        hr = hook_d3d9_irp_handler_invoke_real(irp);
    }

    return hr;
}