// D3D9Hook.cpp -- Direct3D 9 vtable hooks for state caching and shadow upscaling
//
// Intercepts IDirect3D9::CreateDevice to install per-device vtable hooks.
// Features controlled by INI settings:
//   - State cache: skip redundant SetTexture/SetRenderState/etc. calls
//   - Shader constant cache: skip redundant Set*ShaderConstant* calls
//   - 4K shadow upscale: transparently enlarge cascade shadow atlas
#include "D3D9Hook.h"
#include "Hooks.h"
#include "Logger.h"
#include "Config.h"
#include <d3d9.h>

#pragma comment(lib, "d3d9.lib")

#define MAX_TRACKED_TEXTURE_STAGES         288
#define MAX_TRACKED_FIXEDFUNC_STAGES       16
#define MAX_TRACKED_STREAMS                16
#define MAX_TRACKED_RENDER_STATES          256
#define MAX_TRACKED_SAMPLERS               16
#define MAX_TRACKED_SAMPLER_STATES         14
#define MAX_TRACKED_TEXTURE_STAGE_STATES   33
#define MAX_TRACKED_VS_FLOAT_CONSTS        256
#define MAX_TRACKED_PS_FLOAT_CONSTS        224
#define MAX_TRACKED_VS_INT_CONSTS          16
#define MAX_TRACKED_PS_INT_CONSTS          16
#define MAX_TRACKED_VS_BOOL_CONSTS         16
#define MAX_TRACKED_PS_BOOL_CONSTS         16

// -------------------------------------------------------
// D3D9 function typedefs
// -------------------------------------------------------
typedef IDirect3D9* (WINAPI* Direct3DCreate9_t)(UINT SDKVersion);
typedef HRESULT(STDMETHODCALLTYPE* CreateDevice_t)(
    IDirect3D9* pD3D,
    UINT Adapter,
    D3DDEVTYPE DeviceType,
    HWND hFocusWindow,
    DWORD BehaviorFlags,
    D3DPRESENT_PARAMETERS* pPresentationParameters,
    IDirect3DDevice9** ppReturnedDevice);
typedef HRESULT(STDMETHODCALLTYPE* Reset_t)(IDirect3DDevice9* pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters);
typedef HRESULT(STDMETHODCALLTYPE* Present_t)(IDirect3DDevice9* pDevice, const RECT* pSourceRect, const RECT* pDestRect, HWND hDestWindowOverride, const RGNDATA* pDirtyRegion);
typedef HRESULT(STDMETHODCALLTYPE* SetRenderTarget_t)(IDirect3DDevice9* pDevice, DWORD RenderTargetIndex, IDirect3DSurface9* pRenderTarget);
typedef HRESULT(STDMETHODCALLTYPE* SetDepthStencilSurface_t)(IDirect3DDevice9* pDevice, IDirect3DSurface9* pNewZStencil);
typedef HRESULT(STDMETHODCALLTYPE* Clear_t)(IDirect3DDevice9* pDevice, DWORD Count, const D3DRECT* pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil);
typedef HRESULT(STDMETHODCALLTYPE* SetViewport_t)(IDirect3DDevice9* pDevice, const D3DVIEWPORT9* pViewport);
typedef HRESULT(STDMETHODCALLTYPE* SetTexture_t)(IDirect3DDevice9* pDevice, DWORD Stage, IDirect3DBaseTexture9* pTexture);
typedef HRESULT(STDMETHODCALLTYPE* SetTextureStageState_t)(IDirect3DDevice9* pDevice, DWORD Stage, D3DTEXTURESTAGESTATETYPE Type, DWORD Value);
typedef HRESULT(STDMETHODCALLTYPE* SetRenderState_t)(IDirect3DDevice9* pDevice, D3DRENDERSTATETYPE State, DWORD Value);
typedef HRESULT(STDMETHODCALLTYPE* SetSamplerState_t)(IDirect3DDevice9* pDevice, DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value);
typedef HRESULT(STDMETHODCALLTYPE* DrawPrimitive_t)(IDirect3DDevice9* pDevice, D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount);
typedef HRESULT(STDMETHODCALLTYPE* DrawIndexedPrimitive_t)(IDirect3DDevice9* pDevice, D3DPRIMITIVETYPE Type, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount);
typedef HRESULT(STDMETHODCALLTYPE* SetVertexDeclaration_t)(IDirect3DDevice9* pDevice, IDirect3DVertexDeclaration9* pDecl);
typedef HRESULT(STDMETHODCALLTYPE* SetFVF_t)(IDirect3DDevice9* pDevice, DWORD FVF);
typedef HRESULT(STDMETHODCALLTYPE* SetVertexShader_t)(IDirect3DDevice9* pDevice, IDirect3DVertexShader9* pShader);
typedef HRESULT(STDMETHODCALLTYPE* SetVertexShaderConstantF_t)(IDirect3DDevice9* pDevice, UINT StartRegister, const float* pConstantData, UINT Vector4fCount);
typedef HRESULT(STDMETHODCALLTYPE* SetVertexShaderConstantI_t)(IDirect3DDevice9* pDevice, UINT StartRegister, const int* pConstantData, UINT Vector4iCount);
typedef HRESULT(STDMETHODCALLTYPE* SetVertexShaderConstantB_t)(IDirect3DDevice9* pDevice, UINT StartRegister, const BOOL* pConstantData, UINT BoolCount);
typedef HRESULT(STDMETHODCALLTYPE* SetStreamSource_t)(IDirect3DDevice9* pDevice, UINT StreamNumber, IDirect3DVertexBuffer9* pStreamData, UINT OffsetInBytes, UINT Stride);
typedef HRESULT(STDMETHODCALLTYPE* SetIndices_t)(IDirect3DDevice9* pDevice, IDirect3DIndexBuffer9* pIndexData);
typedef HRESULT(STDMETHODCALLTYPE* SetPixelShader_t)(IDirect3DDevice9* pDevice, IDirect3DPixelShader9* pShader);
typedef HRESULT(STDMETHODCALLTYPE* SetPixelShaderConstantF_t)(IDirect3DDevice9* pDevice, UINT StartRegister, const float* pConstantData, UINT Vector4fCount);
typedef HRESULT(STDMETHODCALLTYPE* SetPixelShaderConstantI_t)(IDirect3DDevice9* pDevice, UINT StartRegister, const int* pConstantData, UINT Vector4iCount);
typedef HRESULT(STDMETHODCALLTYPE* SetPixelShaderConstantB_t)(IDirect3DDevice9* pDevice, UINT StartRegister, const BOOL* pConstantData, UINT BoolCount);
typedef HRESULT(STDMETHODCALLTYPE* CreateStateBlock_t)(IDirect3DDevice9* pDevice, D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9** ppSB);
typedef HRESULT(STDMETHODCALLTYPE* EndStateBlock_t)(IDirect3DDevice9* pDevice, IDirect3DStateBlock9** ppSB);
typedef HRESULT(STDMETHODCALLTYPE* StateBlockApply_t)(IDirect3DStateBlock9* pStateBlock);
typedef HRESULT(STDMETHODCALLTYPE* CreateTexture_t)(IDirect3DDevice9* pDevice, UINT Width, UINT Height, UINT Levels, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle);
typedef HRESULT(STDMETHODCALLTYPE* CreateDepthStencilSurface_t)(IDirect3DDevice9* pDevice, UINT Width, UINT Height, D3DFORMAT Format, D3DMULTISAMPLE_TYPE MultiSample, DWORD MultisampleQuality, BOOL Discard, IDirect3DSurface9** ppSurface, HANDLE* pSharedHandle);
typedef HRESULT(STDMETHODCALLTYPE* SetScissorRect_t)(IDirect3DDevice9* pDevice, const RECT* pRect);

// -------------------------------------------------------
// Shadow atlas cascade detection
// -------------------------------------------------------

// -------------------------------------------------------
// Original function pointers (saved during vtable patching)
// -------------------------------------------------------
static Hook hookCreateDevice = { 0 };
static Reset_t s_origReset = NULL;
static Present_t s_origPresent = NULL;
static SetRenderTarget_t s_origSetRenderTarget = NULL;
static SetDepthStencilSurface_t s_origSetDepthStencilSurface = NULL;
static Clear_t s_origClear = NULL;
static SetViewport_t s_origSetViewport = NULL;
static SetTexture_t s_origSetTexture = NULL;
static SetTextureStageState_t s_origSetTextureStageState = NULL;
static SetRenderState_t s_origSetRenderState = NULL;
static SetSamplerState_t s_origSetSamplerState = NULL;
static DrawPrimitive_t s_origDrawPrimitive = NULL;
static DrawIndexedPrimitive_t s_origDrawIndexedPrimitive = NULL;
static SetVertexDeclaration_t s_origSetVertexDeclaration = NULL;
static SetFVF_t s_origSetFVF = NULL;
static SetVertexShader_t s_origSetVertexShader = NULL;
static SetVertexShaderConstantF_t s_origSetVertexShaderConstantF = NULL;
static SetVertexShaderConstantI_t s_origSetVertexShaderConstantI = NULL;
static SetVertexShaderConstantB_t s_origSetVertexShaderConstantB = NULL;
static SetStreamSource_t s_origSetStreamSource = NULL;
static SetIndices_t s_origSetIndices = NULL;
static SetPixelShader_t s_origSetPixelShader = NULL;
static SetPixelShaderConstantF_t s_origSetPixelShaderConstantF = NULL;
static SetPixelShaderConstantI_t s_origSetPixelShaderConstantI = NULL;
static SetPixelShaderConstantB_t s_origSetPixelShaderConstantB = NULL;
static CreateStateBlock_t s_origCreateStateBlock = NULL;
static EndStateBlock_t s_origEndStateBlock = NULL;
static StateBlockApply_t s_origStateBlockApply = NULL;
static CreateTexture_t s_origCreateTexture = NULL;
static CreateDepthStencilSurface_t s_origCreateDepthStencilSurface = NULL;
static SetScissorRect_t s_origSetScissorRect = NULL;

static int s_shadowUpscale = 1;

// -------------------------------------------------------
// Device state cache (tracks last-set values to skip redundant calls)
// -------------------------------------------------------
struct DeviceStateCache {
    IDirect3DDevice9* device;
    BOOL textureValid[MAX_TRACKED_TEXTURE_STAGES];
    IDirect3DBaseTexture9* textures[MAX_TRACKED_TEXTURE_STAGES];
    BOOL textureStageStateValid[MAX_TRACKED_FIXEDFUNC_STAGES][MAX_TRACKED_TEXTURE_STAGE_STATES];
    DWORD textureStageStates[MAX_TRACKED_FIXEDFUNC_STAGES][MAX_TRACKED_TEXTURE_STAGE_STATES];
    BOOL streamValid[MAX_TRACKED_STREAMS];
    IDirect3DVertexBuffer9* streamVB[MAX_TRACKED_STREAMS];
    UINT streamOffset[MAX_TRACKED_STREAMS];
    UINT streamStride[MAX_TRACKED_STREAMS];
    BOOL indexValid;
    IDirect3DIndexBuffer9* indexBuffer;
    BOOL vertexShaderValid;
    IDirect3DVertexShader9* vertexShader;
    BOOL vertexShaderFloatConstValid[MAX_TRACKED_VS_FLOAT_CONSTS];
    float vertexShaderFloatConsts[MAX_TRACKED_VS_FLOAT_CONSTS][4];
    BOOL vertexShaderIntConstValid[MAX_TRACKED_VS_INT_CONSTS];
    int vertexShaderIntConsts[MAX_TRACKED_VS_INT_CONSTS][4];
    BOOL vertexShaderBoolConstValid[MAX_TRACKED_VS_BOOL_CONSTS];
    BOOL vertexShaderBoolConsts[MAX_TRACKED_VS_BOOL_CONSTS];
    BOOL pixelShaderValid;
    IDirect3DPixelShader9* pixelShader;
    BOOL pixelShaderFloatConstValid[MAX_TRACKED_PS_FLOAT_CONSTS];
    float pixelShaderFloatConsts[MAX_TRACKED_PS_FLOAT_CONSTS][4];
    BOOL pixelShaderIntConstValid[MAX_TRACKED_PS_INT_CONSTS];
    int pixelShaderIntConsts[MAX_TRACKED_PS_INT_CONSTS][4];
    BOOL pixelShaderBoolConstValid[MAX_TRACKED_PS_BOOL_CONSTS];
    BOOL pixelShaderBoolConsts[MAX_TRACKED_PS_BOOL_CONSTS];
    BOOL vertexDeclValid;
    IDirect3DVertexDeclaration9* vertexDecl;
    BOOL fvfValid;
    DWORD fvf;
    BOOL renderStateValid[MAX_TRACKED_RENDER_STATES];
    DWORD renderStates[MAX_TRACKED_RENDER_STATES];
    BOOL samplerStateValid[MAX_TRACKED_SAMPLERS][MAX_TRACKED_SAMPLER_STATES];
    DWORD samplerStates[MAX_TRACKED_SAMPLERS][MAX_TRACKED_SAMPLER_STATES];
    IDirect3DSurface9* renderTarget0;
    BOOL renderTarget0DescValid;
    D3DSURFACE_DESC renderTarget0Desc;
    IDirect3DSurface9* depthStencil;
    BOOL depthStencilDescValid;
    D3DSURFACE_DESC depthStencilDesc;
    BOOL viewportValid;
    D3DVIEWPORT9 viewport;
};

static DeviceStateCache g_deviceState = { 0 };

// -------------------------------------------------------
// Internal helpers
// -------------------------------------------------------
static void InstallStateBlockApplyHook(IDirect3DStateBlock9* pStateBlock);
static void UpdateTrackedSurfaceDesc(IDirect3DSurface9* pSurface, BOOL* outValid, D3DSURFACE_DESC* outDesc);

static BOOL PatchVTableSlot(void** vtable, UINT index, void* replacement, void** original, const char* label)
{
    DWORD oldProt = 0;

    if (!vtable)
        return FALSE;

    if (original && !*original)
        *original = vtable[index];

    if (vtable[index] == replacement)
        return TRUE;

    if (!VirtualProtect(&vtable[index], sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProt)) {
        LogFmtIf("  WARNING: VirtualProtect failed for %s slot %u (GLE=%lu)", label, index, GetLastError());
        return FALSE;
    }

    vtable[index] = replacement;
    VirtualProtect(&vtable[index], sizeof(void*), oldProt, &oldProt);
    FlushInstructionCache(GetCurrentProcess(), &vtable[index], sizeof(void*));

    LogFmtIf("  %s vtbl[%u] -> %p (original %p)",
        label, index, replacement, original ? *original : NULL);
    return TRUE;
}

static BOOL ShouldInstallDeviceHooks()
{
    return g_d3d9StateCache || g_d3d9ShaderConstCache ||
        g_shadowSizeMul > 1.0f;
}

// -------------------------------------------------------
// Shader constant cache helpers
// -------------------------------------------------------
static BOOL IsRepeatedFloatConstantRange(
    const BOOL* valid,
    const float (*values)[4],
    UINT startRegister,
    const float* pConstantData,
    UINT vector4fCount)
{
    UINT i = 0;

    if (!pConstantData)
        return FALSE;

    for (i = 0; i < vector4fCount; ++i) {
        if (!valid[startRegister + i])
            return FALSE;
        if (memcmp(values[startRegister + i], pConstantData + (i * 4), sizeof(float) * 4) != 0)
            return FALSE;
    }

    return TRUE;
}

static void StoreFloatConstantRange(
    BOOL* valid,
    float (*values)[4],
    UINT startRegister,
    const float* pConstantData,
    UINT vector4fCount)
{
    UINT i = 0;

    if (!pConstantData)
        return;

    for (i = 0; i < vector4fCount; ++i) {
        valid[startRegister + i] = TRUE;
        memcpy(values[startRegister + i], pConstantData + (i * 4), sizeof(float) * 4);
    }
}

static BOOL IsRepeatedIntConstantRange(
    const BOOL* valid,
    const int (*values)[4],
    UINT startRegister,
    const int* pConstantData,
    UINT vector4iCount)
{
    UINT i = 0;

    if (!pConstantData)
        return FALSE;

    for (i = 0; i < vector4iCount; ++i) {
        if (!valid[startRegister + i])
            return FALSE;
        if (memcmp(values[startRegister + i], pConstantData + (i * 4), sizeof(int) * 4) != 0)
            return FALSE;
    }

    return TRUE;
}

static void StoreIntConstantRange(
    BOOL* valid,
    int (*values)[4],
    UINT startRegister,
    const int* pConstantData,
    UINT vector4iCount)
{
    UINT i = 0;

    if (!pConstantData)
        return;

    for (i = 0; i < vector4iCount; ++i) {
        valid[startRegister + i] = TRUE;
        memcpy(values[startRegister + i], pConstantData + (i * 4), sizeof(int) * 4);
    }
}

static BOOL IsRepeatedBoolConstantRange(
    const BOOL* valid,
    const BOOL* values,
    UINT startRegister,
    const BOOL* pConstantData,
    UINT boolCount)
{
    UINT i = 0;

    if (!pConstantData)
        return FALSE;

    for (i = 0; i < boolCount; ++i) {
        if (!valid[startRegister + i])
            return FALSE;
        if (values[startRegister + i] != pConstantData[i])
            return FALSE;
    }

    return TRUE;
}

static void StoreBoolConstantRange(
    BOOL* valid,
    BOOL* values,
    UINT startRegister,
    const BOOL* pConstantData,
    UINT boolCount)
{
    UINT i = 0;

    if (!pConstantData)
        return;

    for (i = 0; i < boolCount; ++i) {
        valid[startRegister + i] = TRUE;
        values[startRegister + i] = pConstantData[i];
    }
}

// -------------------------------------------------------
// Device state tracking and reset
// -------------------------------------------------------
static void ResetTrackedDeviceState(IDirect3DDevice9* pDevice)
{
    ZeroMemory(&g_deviceState, sizeof(g_deviceState));
    g_deviceState.device = pDevice;
}

static void PrimeTrackedDeviceState(IDirect3DDevice9* pDevice)
{
    IDirect3DSurface9* pSurface = NULL;
    D3DVIEWPORT9 viewport;

    if (!pDevice)
        return;

    if (SUCCEEDED(pDevice->GetRenderTarget(0, &pSurface)) && pSurface) {
        g_deviceState.renderTarget0 = pSurface;
        UpdateTrackedSurfaceDesc(pSurface, &g_deviceState.renderTarget0DescValid, &g_deviceState.renderTarget0Desc);
        pSurface->Release();
    }

    pSurface = NULL;
    if (SUCCEEDED(pDevice->GetDepthStencilSurface(&pSurface)) && pSurface) {
        g_deviceState.depthStencil = pSurface;
        UpdateTrackedSurfaceDesc(pSurface, &g_deviceState.depthStencilDescValid, &g_deviceState.depthStencilDesc);
        pSurface->Release();
    }

    ZeroMemory(&viewport, sizeof(viewport));
    if (SUCCEEDED(pDevice->GetViewport(&viewport))) {
        g_deviceState.viewportValid = TRUE;
        g_deviceState.viewport = viewport;
    }
}

static void UpdateTrackedSurfaceDesc(IDirect3DSurface9* pSurface, BOOL* outValid, D3DSURFACE_DESC* outDesc)
{
    if (!outValid || !outDesc)
        return;

    *outValid = FALSE;
    ZeroMemory(outDesc, sizeof(*outDesc));

    if (!pSurface)
        return;

    if (SUCCEEDED(pSurface->GetDesc(outDesc)))
        *outValid = TRUE;
}

static BOOL IsCascadeShadowAtlasActive()
{
    UINT scaledW, scaledH;

    if (s_shadowUpscale <= 1)
        return FALSE;
    if (!g_deviceState.renderTarget0DescValid)
        return FALSE;

    scaledW = 2048 * (UINT)s_shadowUpscale;
    scaledH = 6144 * (UINT)s_shadowUpscale;

    return g_deviceState.renderTarget0Desc.Width == scaledW &&
           g_deviceState.renderTarget0Desc.Height == scaledH &&
           g_deviceState.renderTarget0Desc.Format == (D3DFORMAT)114;
}

static void InvalidateTrackedDeviceState()
{
    IDirect3DDevice9* current = g_deviceState.device;
    ZeroMemory(&g_deviceState, sizeof(g_deviceState));
    g_deviceState.device = current;
}

static void EnsureTrackedDevice(IDirect3DDevice9* pDevice)
{
    if (g_deviceState.device != pDevice)
        ResetTrackedDeviceState(pDevice);
}

// -------------------------------------------------------
// Hooked D3D9 methods -- State block, reset, present
// -------------------------------------------------------
static HRESULT STDMETHODCALLTYPE MyStateBlockApply(IDirect3DStateBlock9* pStateBlock)
{
    InvalidateTrackedDeviceState();
    return s_origStateBlockApply ? s_origStateBlockApply(pStateBlock) : D3DERR_INVALIDCALL;
}

static HRESULT STDMETHODCALLTYPE MyReset(IDirect3DDevice9* pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters)
{
    InvalidateTrackedDeviceState();
    return s_origReset ? s_origReset(pDevice, pPresentationParameters) : D3DERR_INVALIDCALL;
}

static HRESULT STDMETHODCALLTYPE MyPresent(
    IDirect3DDevice9* pDevice,
    const RECT* pSourceRect,
    const RECT* pDestRect,
    HWND hDestWindowOverride,
    const RGNDATA* pDirtyRegion)
{
    HRESULT hr = s_origPresent
        ? s_origPresent(pDevice, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion)
        : D3DERR_INVALIDCALL;

    return hr;
}

// -------------------------------------------------------
// Hooked D3D9 methods -- Render targets, clear, viewport, scissor
// -------------------------------------------------------
static HRESULT STDMETHODCALLTYPE MySetRenderTarget(IDirect3DDevice9* pDevice, DWORD RenderTargetIndex, IDirect3DSurface9* pRenderTarget)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    hr = s_origSetRenderTarget ? s_origSetRenderTarget(pDevice, RenderTargetIndex, pRenderTarget) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && RenderTargetIndex == 0) {
        g_deviceState.renderTarget0 = pRenderTarget;
        UpdateTrackedSurfaceDesc(pRenderTarget, &g_deviceState.renderTarget0DescValid, &g_deviceState.renderTarget0Desc);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetDepthStencilSurface(IDirect3DDevice9* pDevice, IDirect3DSurface9* pNewZStencil)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    hr = s_origSetDepthStencilSurface ? s_origSetDepthStencilSurface(pDevice, pNewZStencil) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr)) {
        g_deviceState.depthStencil = pNewZStencil;
        UpdateTrackedSurfaceDesc(pNewZStencil, &g_deviceState.depthStencilDescValid, &g_deviceState.depthStencilDesc);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MyClear(
    IDirect3DDevice9* pDevice,
    DWORD Count,
    const D3DRECT* pRects,
    DWORD Flags,
    D3DCOLOR Color,
    float Z,
    DWORD Stencil)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);

    // Scale explicit clear rects when rendering to the upscaled cascade atlas
    if (Count > 0 && pRects && IsCascadeShadowAtlasActive()) {
        D3DRECT scaled[8];
        DWORD n = (Count > 8) ? 8 : Count;
        for (DWORD i = 0; i < n; i++) {
            scaled[i].x1 = pRects[i].x1 * (LONG)s_shadowUpscale;
            scaled[i].y1 = pRects[i].y1 * (LONG)s_shadowUpscale;
            scaled[i].x2 = pRects[i].x2 * (LONG)s_shadowUpscale;
            scaled[i].y2 = pRects[i].y2 * (LONG)s_shadowUpscale;
        }
        hr = s_origClear ? s_origClear(pDevice, n, scaled, Flags, Color, Z, Stencil) : D3DERR_INVALIDCALL;
        return hr;
    }

    hr = s_origClear ? s_origClear(pDevice, Count, pRects, Flags, Color, Z, Stencil) : D3DERR_INVALIDCALL;
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetViewport(IDirect3DDevice9* pDevice, const D3DVIEWPORT9* pViewport)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);

    if (pViewport && IsCascadeShadowAtlasActive()) {
        D3DVIEWPORT9 scaled = *pViewport;
        scaled.X      *= (DWORD)s_shadowUpscale;
        scaled.Y      *= (DWORD)s_shadowUpscale;
        scaled.Width   *= (DWORD)s_shadowUpscale;
        scaled.Height  *= (DWORD)s_shadowUpscale;
        hr = s_origSetViewport ? s_origSetViewport(pDevice, &scaled) : D3DERR_INVALIDCALL;
    } else {
        hr = s_origSetViewport ? s_origSetViewport(pDevice, pViewport) : D3DERR_INVALIDCALL;
    }

    // Track the GAME's requested viewport (unscaled) for matching
    if (SUCCEEDED(hr) && pViewport) {
        g_deviceState.viewportValid = TRUE;
        g_deviceState.viewport = *pViewport;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetScissorRect(IDirect3DDevice9* pDevice, const RECT* pRect)
{
    EnsureTrackedDevice(pDevice);

    if (pRect && IsCascadeShadowAtlasActive()) {
        RECT scaled;
        scaled.left   = pRect->left   * (LONG)s_shadowUpscale;
        scaled.top    = pRect->top    * (LONG)s_shadowUpscale;
        scaled.right  = pRect->right  * (LONG)s_shadowUpscale;
        scaled.bottom = pRect->bottom * (LONG)s_shadowUpscale;
        return s_origSetScissorRect
            ? s_origSetScissorRect(pDevice, &scaled)
            : D3DERR_INVALIDCALL;
    }

    return s_origSetScissorRect
        ? s_origSetScissorRect(pDevice, pRect)
        : D3DERR_INVALIDCALL;
}

// -------------------------------------------------------
// Hooked D3D9 methods -- State cache (textures, render states, samplers)
// -------------------------------------------------------
static HRESULT STDMETHODCALLTYPE MySetTexture(IDirect3DDevice9* pDevice, DWORD Stage, IDirect3DBaseTexture9* pTexture)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache &&
        Stage < MAX_TRACKED_TEXTURE_STAGES &&
        g_deviceState.textureValid[Stage] &&
        g_deviceState.textures[Stage] == pTexture) {
        return D3D_OK;
    }

    hr = s_origSetTexture ? s_origSetTexture(pDevice, Stage, pTexture) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && Stage < MAX_TRACKED_TEXTURE_STAGES) {
        g_deviceState.textureValid[Stage] = TRUE;
        g_deviceState.textures[Stage] = pTexture;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetTextureStageState(
    IDirect3DDevice9* pDevice,
    DWORD Stage,
    D3DTEXTURESTAGESTATETYPE Type,
    DWORD Value)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache &&
        Stage < MAX_TRACKED_FIXEDFUNC_STAGES &&
        (DWORD)Type < MAX_TRACKED_TEXTURE_STAGE_STATES &&
        g_deviceState.textureStageStateValid[Stage][Type] &&
        g_deviceState.textureStageStates[Stage][Type] == Value) {
        return D3D_OK;
    }

    hr = s_origSetTextureStageState
        ? s_origSetTextureStageState(pDevice, Stage, Type, Value)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) &&
        Stage < MAX_TRACKED_FIXEDFUNC_STAGES &&
        (DWORD)Type < MAX_TRACKED_TEXTURE_STAGE_STATES) {
        g_deviceState.textureStageStateValid[Stage][Type] = TRUE;
        g_deviceState.textureStageStates[Stage][Type] = Value;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetRenderState(IDirect3DDevice9* pDevice, D3DRENDERSTATETYPE State, DWORD Value)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache &&
        (DWORD)State < MAX_TRACKED_RENDER_STATES &&
        g_deviceState.renderStateValid[State] &&
        g_deviceState.renderStates[State] == Value) {
        return D3D_OK;
    }

    hr = s_origSetRenderState ? s_origSetRenderState(pDevice, State, Value) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && (DWORD)State < MAX_TRACKED_RENDER_STATES) {
        g_deviceState.renderStateValid[State] = TRUE;
        g_deviceState.renderStates[State] = Value;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetSamplerState(IDirect3DDevice9* pDevice, DWORD Sampler, D3DSAMPLERSTATETYPE Type, DWORD Value)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache &&
        Sampler < MAX_TRACKED_SAMPLERS &&
        (DWORD)Type < MAX_TRACKED_SAMPLER_STATES &&
        g_deviceState.samplerStateValid[Sampler][Type] &&
        g_deviceState.samplerStates[Sampler][Type] == Value) {
        return D3D_OK;
    }

    hr = s_origSetSamplerState ? s_origSetSamplerState(pDevice, Sampler, Type, Value) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) &&
        Sampler < MAX_TRACKED_SAMPLERS &&
        (DWORD)Type < MAX_TRACKED_SAMPLER_STATES) {
        g_deviceState.samplerStateValid[Sampler][Type] = TRUE;
        g_deviceState.samplerStates[Sampler][Type] = Value;
    }
    return hr;
}

// -------------------------------------------------------
// Hooked D3D9 methods -- Draw calls
// -------------------------------------------------------
static HRESULT STDMETHODCALLTYPE MyDrawPrimitive(IDirect3DDevice9* pDevice, D3DPRIMITIVETYPE PrimitiveType, UINT StartVertex, UINT PrimitiveCount)
{
    EnsureTrackedDevice(pDevice);
    return s_origDrawPrimitive
        ? s_origDrawPrimitive(pDevice, PrimitiveType, StartVertex, PrimitiveCount)
        : D3DERR_INVALIDCALL;
}

static HRESULT STDMETHODCALLTYPE MyDrawIndexedPrimitive(
    IDirect3DDevice9* pDevice,
    D3DPRIMITIVETYPE Type,
    INT BaseVertexIndex,
    UINT MinVertexIndex,
    UINT NumVertices,
    UINT StartIndex,
    UINT PrimitiveCount)
{
    EnsureTrackedDevice(pDevice);
    return s_origDrawIndexedPrimitive
        ? s_origDrawIndexedPrimitive(pDevice, Type, BaseVertexIndex, MinVertexIndex, NumVertices, StartIndex, PrimitiveCount)
        : D3DERR_INVALIDCALL;
}

// -------------------------------------------------------
// Hooked D3D9 methods -- Vertex/pixel shader state cache
// -------------------------------------------------------
static HRESULT STDMETHODCALLTYPE MySetVertexDeclaration(IDirect3DDevice9* pDevice, IDirect3DVertexDeclaration9* pDecl)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache && g_deviceState.vertexDeclValid && g_deviceState.vertexDecl == pDecl) {
        return D3D_OK;
    }

    hr = s_origSetVertexDeclaration ? s_origSetVertexDeclaration(pDevice, pDecl) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr)) {
        g_deviceState.vertexDeclValid = TRUE;
        g_deviceState.vertexDecl = pDecl;
        g_deviceState.fvfValid = FALSE;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetFVF(IDirect3DDevice9* pDevice, DWORD FVF)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache && g_deviceState.fvfValid && g_deviceState.fvf == FVF) {
        return D3D_OK;
    }

    hr = s_origSetFVF ? s_origSetFVF(pDevice, FVF) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr)) {
        g_deviceState.fvfValid = TRUE;
        g_deviceState.fvf = FVF;
        g_deviceState.vertexDeclValid = FALSE;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetVertexShader(IDirect3DDevice9* pDevice, IDirect3DVertexShader9* pShader)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache && g_deviceState.vertexShaderValid && g_deviceState.vertexShader == pShader) {
        return D3D_OK;
    }

    hr = s_origSetVertexShader ? s_origSetVertexShader(pDevice, pShader) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr)) {
        g_deviceState.vertexShaderValid = TRUE;
        g_deviceState.vertexShader = pShader;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetVertexShaderConstantF(
    IDirect3DDevice9* pDevice,
    UINT StartRegister,
    const float* pConstantData,
    UINT Vector4fCount)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9ShaderConstCache &&
        StartRegister + Vector4fCount <= MAX_TRACKED_VS_FLOAT_CONSTS &&
        IsRepeatedFloatConstantRange(
            g_deviceState.vertexShaderFloatConstValid,
            g_deviceState.vertexShaderFloatConsts,
            StartRegister,
            pConstantData,
            Vector4fCount)) {
        return D3D_OK;
    }

    hr = s_origSetVertexShaderConstantF
        ? s_origSetVertexShaderConstantF(pDevice, StartRegister, pConstantData, Vector4fCount)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StartRegister + Vector4fCount <= MAX_TRACKED_VS_FLOAT_CONSTS) {
        StoreFloatConstantRange(
            g_deviceState.vertexShaderFloatConstValid,
            g_deviceState.vertexShaderFloatConsts,
            StartRegister,
            pConstantData,
            Vector4fCount);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetVertexShaderConstantI(
    IDirect3DDevice9* pDevice,
    UINT StartRegister,
    const int* pConstantData,
    UINT Vector4iCount)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9ShaderConstCache &&
        StartRegister + Vector4iCount <= MAX_TRACKED_VS_INT_CONSTS &&
        IsRepeatedIntConstantRange(
            g_deviceState.vertexShaderIntConstValid,
            g_deviceState.vertexShaderIntConsts,
            StartRegister,
            pConstantData,
            Vector4iCount)) {
        return D3D_OK;
    }

    hr = s_origSetVertexShaderConstantI
        ? s_origSetVertexShaderConstantI(pDevice, StartRegister, pConstantData, Vector4iCount)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StartRegister + Vector4iCount <= MAX_TRACKED_VS_INT_CONSTS) {
        StoreIntConstantRange(
            g_deviceState.vertexShaderIntConstValid,
            g_deviceState.vertexShaderIntConsts,
            StartRegister,
            pConstantData,
            Vector4iCount);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetVertexShaderConstantB(
    IDirect3DDevice9* pDevice,
    UINT StartRegister,
    const BOOL* pConstantData,
    UINT BoolCount)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9ShaderConstCache &&
        StartRegister + BoolCount <= MAX_TRACKED_VS_BOOL_CONSTS &&
        IsRepeatedBoolConstantRange(
            g_deviceState.vertexShaderBoolConstValid,
            g_deviceState.vertexShaderBoolConsts,
            StartRegister,
            pConstantData,
            BoolCount)) {
        return D3D_OK;
    }

    hr = s_origSetVertexShaderConstantB
        ? s_origSetVertexShaderConstantB(pDevice, StartRegister, pConstantData, BoolCount)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StartRegister + BoolCount <= MAX_TRACKED_VS_BOOL_CONSTS) {
        StoreBoolConstantRange(
            g_deviceState.vertexShaderBoolConstValid,
            g_deviceState.vertexShaderBoolConsts,
            StartRegister,
            pConstantData,
            BoolCount);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetStreamSource(
    IDirect3DDevice9* pDevice,
    UINT StreamNumber,
    IDirect3DVertexBuffer9* pStreamData,
    UINT OffsetInBytes,
    UINT Stride)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache &&
        StreamNumber < MAX_TRACKED_STREAMS &&
        g_deviceState.streamValid[StreamNumber] &&
        g_deviceState.streamVB[StreamNumber] == pStreamData &&
        g_deviceState.streamOffset[StreamNumber] == OffsetInBytes &&
        g_deviceState.streamStride[StreamNumber] == Stride) {
        return D3D_OK;
    }

    hr = s_origSetStreamSource
        ? s_origSetStreamSource(pDevice, StreamNumber, pStreamData, OffsetInBytes, Stride)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StreamNumber < MAX_TRACKED_STREAMS) {
        g_deviceState.streamValid[StreamNumber] = TRUE;
        g_deviceState.streamVB[StreamNumber] = pStreamData;
        g_deviceState.streamOffset[StreamNumber] = OffsetInBytes;
        g_deviceState.streamStride[StreamNumber] = Stride;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetIndices(IDirect3DDevice9* pDevice, IDirect3DIndexBuffer9* pIndexData)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache && g_deviceState.indexValid && g_deviceState.indexBuffer == pIndexData) {
        return D3D_OK;
    }

    hr = s_origSetIndices ? s_origSetIndices(pDevice, pIndexData) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr)) {
        g_deviceState.indexValid = TRUE;
        g_deviceState.indexBuffer = pIndexData;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetPixelShader(IDirect3DDevice9* pDevice, IDirect3DPixelShader9* pShader)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9StateCache && g_deviceState.pixelShaderValid && g_deviceState.pixelShader == pShader) {
        return D3D_OK;
    }

    hr = s_origSetPixelShader ? s_origSetPixelShader(pDevice, pShader) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr)) {
        g_deviceState.pixelShaderValid = TRUE;
        g_deviceState.pixelShader = pShader;
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetPixelShaderConstantF(
    IDirect3DDevice9* pDevice,
    UINT StartRegister,
    const float* pConstantData,
    UINT Vector4fCount)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9ShaderConstCache &&
        StartRegister + Vector4fCount <= MAX_TRACKED_PS_FLOAT_CONSTS &&
        IsRepeatedFloatConstantRange(
            g_deviceState.pixelShaderFloatConstValid,
            g_deviceState.pixelShaderFloatConsts,
            StartRegister,
            pConstantData,
            Vector4fCount)) {
        return D3D_OK;
    }

    hr = s_origSetPixelShaderConstantF
        ? s_origSetPixelShaderConstantF(pDevice, StartRegister, pConstantData, Vector4fCount)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StartRegister + Vector4fCount <= MAX_TRACKED_PS_FLOAT_CONSTS) {
        StoreFloatConstantRange(
            g_deviceState.pixelShaderFloatConstValid,
            g_deviceState.pixelShaderFloatConsts,
            StartRegister,
            pConstantData,
            Vector4fCount);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetPixelShaderConstantI(
    IDirect3DDevice9* pDevice,
    UINT StartRegister,
    const int* pConstantData,
    UINT Vector4iCount)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9ShaderConstCache &&
        StartRegister + Vector4iCount <= MAX_TRACKED_PS_INT_CONSTS &&
        IsRepeatedIntConstantRange(
            g_deviceState.pixelShaderIntConstValid,
            g_deviceState.pixelShaderIntConsts,
            StartRegister,
            pConstantData,
            Vector4iCount)) {
        return D3D_OK;
    }

    hr = s_origSetPixelShaderConstantI
        ? s_origSetPixelShaderConstantI(pDevice, StartRegister, pConstantData, Vector4iCount)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StartRegister + Vector4iCount <= MAX_TRACKED_PS_INT_CONSTS) {
        StoreIntConstantRange(
            g_deviceState.pixelShaderIntConstValid,
            g_deviceState.pixelShaderIntConsts,
            StartRegister,
            pConstantData,
            Vector4iCount);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MySetPixelShaderConstantB(
    IDirect3DDevice9* pDevice,
    UINT StartRegister,
    const BOOL* pConstantData,
    UINT BoolCount)
{
    HRESULT hr;

    EnsureTrackedDevice(pDevice);
    if (g_d3d9ShaderConstCache &&
        StartRegister + BoolCount <= MAX_TRACKED_PS_BOOL_CONSTS &&
        IsRepeatedBoolConstantRange(
            g_deviceState.pixelShaderBoolConstValid,
            g_deviceState.pixelShaderBoolConsts,
            StartRegister,
            pConstantData,
            BoolCount)) {
        return D3D_OK;
    }

    hr = s_origSetPixelShaderConstantB
        ? s_origSetPixelShaderConstantB(pDevice, StartRegister, pConstantData, BoolCount)
        : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && StartRegister + BoolCount <= MAX_TRACKED_PS_BOOL_CONSTS) {
        StoreBoolConstantRange(
            g_deviceState.pixelShaderBoolConstValid,
            g_deviceState.pixelShaderBoolConsts,
            StartRegister,
            pConstantData,
            BoolCount);
    }
    return hr;
}

static HRESULT STDMETHODCALLTYPE MyCreateStateBlock(IDirect3DDevice9* pDevice, D3DSTATEBLOCKTYPE Type, IDirect3DStateBlock9** ppSB)
{
    HRESULT hr = s_origCreateStateBlock ? s_origCreateStateBlock(pDevice, Type, ppSB) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && ppSB && *ppSB)
        InstallStateBlockApplyHook(*ppSB);
    return hr;
}

static HRESULT STDMETHODCALLTYPE MyEndStateBlock(IDirect3DDevice9* pDevice, IDirect3DStateBlock9** ppSB)
{
    HRESULT hr = s_origEndStateBlock ? s_origEndStateBlock(pDevice, ppSB) : D3DERR_INVALIDCALL;
    if (SUCCEEDED(hr) && ppSB && *ppSB)
        InstallStateBlockApplyHook(*ppSB);
    return hr;
}

// -------------------------------------------------------
// Hooked D3D9 methods -- 4K shadow atlas upscaling
// -------------------------------------------------------
static HRESULT STDMETHODCALLTYPE MyCreateTexture(
    IDirect3DDevice9* pDevice,
    UINT Width, UINT Height, UINT Levels,
    DWORD Usage, D3DFORMAT Format, D3DPOOL Pool,
    IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle)
{
    // Detect cascade shadow atlas: 2048x6144, R32F, render target
    if (s_shadowUpscale > 1 &&
        Width == 2048 && Height == 6144 &&
        Format == (D3DFORMAT)114 &&
        (Usage & D3DUSAGE_RENDERTARGET))
    {
        UINT newW = Width * (UINT)s_shadowUpscale;
        UINT newH = Height * (UINT)s_shadowUpscale;
        HRESULT hr = s_origCreateTexture
            ? s_origCreateTexture(pDevice, newW, newH, Levels, Usage, Format, Pool, ppTexture, pSharedHandle)
            : D3DERR_INVALIDCALL;
        if (SUCCEEDED(hr)) {
            LogFmtIf("  [4K Shadows] Cascade atlas texture upscaled: %ux%u -> %ux%u (R32F RT)",
                     Width, Height, newW, newH);
            return hr;
        }
        LogFmtIf("  [4K Shadows] WARNING: Cascade atlas upscale failed (hr=0x%08X), using original %ux%u", hr, Width, Height);
    }

    return s_origCreateTexture
        ? s_origCreateTexture(pDevice, Width, Height, Levels, Usage, Format, Pool, ppTexture, pSharedHandle)
        : D3DERR_INVALIDCALL;
}

static HRESULT STDMETHODCALLTYPE MyCreateDepthStencilSurface(
    IDirect3DDevice9* pDevice,
    UINT Width, UINT Height,
    D3DFORMAT Format,
    D3DMULTISAMPLE_TYPE MultiSample,
    DWORD MultisampleQuality,
    BOOL Discard,
    IDirect3DSurface9** ppSurface,
    HANDLE* pSharedHandle)
{
    // Detect cascade shadow atlas depth: 2048x6144, D24S8
    if (s_shadowUpscale > 1 &&
        Width == 2048 && Height == 6144 &&
        Format == (D3DFORMAT)75)
    {
        UINT newW = Width * (UINT)s_shadowUpscale;
        UINT newH = Height * (UINT)s_shadowUpscale;
        HRESULT hr = s_origCreateDepthStencilSurface
            ? s_origCreateDepthStencilSurface(pDevice, newW, newH, Format, MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle)
            : D3DERR_INVALIDCALL;
        if (SUCCEEDED(hr)) {
            LogFmtIf("  [4K Shadows] Cascade atlas depth upscaled: %ux%u -> %ux%u (D24S8)",
                     Width, Height, newW, newH);
            return hr;
        }
        LogFmtIf("  [4K Shadows] WARNING: Cascade depth upscale failed (hr=0x%08X), using original %ux%u", hr, Width, Height);
    }

    return s_origCreateDepthStencilSurface
        ? s_origCreateDepthStencilSurface(pDevice, Width, Height, Format, MultiSample, MultisampleQuality, Discard, ppSurface, pSharedHandle)
        : D3DERR_INVALIDCALL;
}

// -------------------------------------------------------
// Device hook installation
// -------------------------------------------------------
static void InstallStateBlockApplyHook(IDirect3DStateBlock9* pStateBlock)
{
    void** vtable;

    if (!ShouldInstallDeviceHooks() || !pStateBlock)
        return;

    vtable = *(void***)pStateBlock;
    PatchVTableSlot(vtable, 5, (void*)MyStateBlockApply, (void**)&s_origStateBlockApply, "IDirect3DStateBlock9::Apply");
}

static void InstallDeviceHooks(IDirect3DDevice9* pDevice)
{
    void** vtable;

    if (!ShouldInstallDeviceHooks() || !pDevice)
        return;

    vtable = *(void***)pDevice;

    PatchVTableSlot(vtable, 16, (void*)MyReset, (void**)&s_origReset, "IDirect3DDevice9::Reset");
    PatchVTableSlot(vtable, 17, (void*)MyPresent, (void**)&s_origPresent, "IDirect3DDevice9::Present");
    if (s_shadowUpscale > 1) {
        PatchVTableSlot(vtable, 23, (void*)MyCreateTexture, (void**)&s_origCreateTexture, "IDirect3DDevice9::CreateTexture");
        PatchVTableSlot(vtable, 29, (void*)MyCreateDepthStencilSurface, (void**)&s_origCreateDepthStencilSurface, "IDirect3DDevice9::CreateDepthStencilSurface");
    }
    PatchVTableSlot(vtable, 37, (void*)MySetRenderTarget, (void**)&s_origSetRenderTarget, "IDirect3DDevice9::SetRenderTarget");
    PatchVTableSlot(vtable, 39, (void*)MySetDepthStencilSurface, (void**)&s_origSetDepthStencilSurface, "IDirect3DDevice9::SetDepthStencilSurface");
    PatchVTableSlot(vtable, 43, (void*)MyClear, (void**)&s_origClear, "IDirect3DDevice9::Clear");
    PatchVTableSlot(vtable, 47, (void*)MySetViewport, (void**)&s_origSetViewport, "IDirect3DDevice9::SetViewport");
    if (s_shadowUpscale > 1) {
        PatchVTableSlot(vtable, 75, (void*)MySetScissorRect, (void**)&s_origSetScissorRect, "IDirect3DDevice9::SetScissorRect");
    }
    PatchVTableSlot(vtable, 57, (void*)MySetRenderState, (void**)&s_origSetRenderState, "IDirect3DDevice9::SetRenderState");
    PatchVTableSlot(vtable, 59, (void*)MyCreateStateBlock, (void**)&s_origCreateStateBlock, "IDirect3DDevice9::CreateStateBlock");
    PatchVTableSlot(vtable, 61, (void*)MyEndStateBlock, (void**)&s_origEndStateBlock, "IDirect3DDevice9::EndStateBlock");
    PatchVTableSlot(vtable, 65, (void*)MySetTexture, (void**)&s_origSetTexture, "IDirect3DDevice9::SetTexture");
    PatchVTableSlot(vtable, 67, (void*)MySetTextureStageState, (void**)&s_origSetTextureStageState, "IDirect3DDevice9::SetTextureStageState");
    PatchVTableSlot(vtable, 69, (void*)MySetSamplerState, (void**)&s_origSetSamplerState, "IDirect3DDevice9::SetSamplerState");
    PatchVTableSlot(vtable, 81, (void*)MyDrawPrimitive, (void**)&s_origDrawPrimitive, "IDirect3DDevice9::DrawPrimitive");
    PatchVTableSlot(vtable, 82, (void*)MyDrawIndexedPrimitive, (void**)&s_origDrawIndexedPrimitive, "IDirect3DDevice9::DrawIndexedPrimitive");
    PatchVTableSlot(vtable, 87, (void*)MySetVertexDeclaration, (void**)&s_origSetVertexDeclaration, "IDirect3DDevice9::SetVertexDeclaration");
    PatchVTableSlot(vtable, 89, (void*)MySetFVF, (void**)&s_origSetFVF, "IDirect3DDevice9::SetFVF");
    PatchVTableSlot(vtable, 92, (void*)MySetVertexShader, (void**)&s_origSetVertexShader, "IDirect3DDevice9::SetVertexShader");
    if (g_d3d9ShaderConstCache) {
        PatchVTableSlot(vtable, 94, (void*)MySetVertexShaderConstantF, (void**)&s_origSetVertexShaderConstantF, "IDirect3DDevice9::SetVertexShaderConstantF");
        PatchVTableSlot(vtable, 96, (void*)MySetVertexShaderConstantI, (void**)&s_origSetVertexShaderConstantI, "IDirect3DDevice9::SetVertexShaderConstantI");
        PatchVTableSlot(vtable, 98, (void*)MySetVertexShaderConstantB, (void**)&s_origSetVertexShaderConstantB, "IDirect3DDevice9::SetVertexShaderConstantB");
    }
    PatchVTableSlot(vtable, 100, (void*)MySetStreamSource, (void**)&s_origSetStreamSource, "IDirect3DDevice9::SetStreamSource");
    PatchVTableSlot(vtable, 104, (void*)MySetIndices, (void**)&s_origSetIndices, "IDirect3DDevice9::SetIndices");
    PatchVTableSlot(vtable, 107, (void*)MySetPixelShader, (void**)&s_origSetPixelShader, "IDirect3DDevice9::SetPixelShader");
    if (g_d3d9ShaderConstCache) {
        PatchVTableSlot(vtable, 109, (void*)MySetPixelShaderConstantF, (void**)&s_origSetPixelShaderConstantF, "IDirect3DDevice9::SetPixelShaderConstantF");
        PatchVTableSlot(vtable, 111, (void*)MySetPixelShaderConstantI, (void**)&s_origSetPixelShaderConstantI, "IDirect3DDevice9::SetPixelShaderConstantI");
        PatchVTableSlot(vtable, 113, (void*)MySetPixelShaderConstantB, (void**)&s_origSetPixelShaderConstantB, "IDirect3DDevice9::SetPixelShaderConstantB");
    }

    LogIf("  D3D9 device hooks active.");
}

// -------------------------------------------------------
// IDirect3D9::CreateDevice hook (entry point for all device hooks)
// -------------------------------------------------------
HRESULT STDMETHODCALLTYPE MyCreateDevice(
    IDirect3D9* pD3D,
    UINT Adapter,
    D3DDEVTYPE DeviceType,
    HWND hFocusWindow,
    DWORD BehaviorFlags,
    D3DPRESENT_PARAMETERS* pPresentationParameters,
    IDirect3DDevice9** ppReturnedDevice)
{
    CreateDevice_t pfnCreateDevice;
    HRESULT hr;

    if (g_logging) {
        Log("--- Intercepted IDirect3D9::CreateDevice ---");
        LogFmt("  Original BehaviorFlags: 0x%08X", BehaviorFlags);

        if (BehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING)
            Log("  > Requested Software Vertex Processing");
        else if (BehaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING)
            Log("  > Requested Mixed Vertex Processing");
        else if (BehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING)
            Log("  > Requested Hardware Vertex Processing");
    }

    pfnCreateDevice = (CreateDevice_t)hookCreateDevice.trampoline;
    hr = pfnCreateDevice(
        pD3D, Adapter, DeviceType, hFocusWindow,
        BehaviorFlags, pPresentationParameters, ppReturnedDevice);

    if (SUCCEEDED(hr) && ppReturnedDevice && *ppReturnedDevice) {
        ResetTrackedDeviceState(*ppReturnedDevice);
        PrimeTrackedDeviceState(*ppReturnedDevice);
        InstallDeviceHooks(*ppReturnedDevice);
    }

    return hr;
}

// -------------------------------------------------------
// Public entry point
// -------------------------------------------------------
void InstallD3D9Hook()
{
    Direct3DCreate9_t pfnDirect3DCreate9;
    IDirect3D9* pDummyD3D9;
    void** vtable;
    void* pCreateDevice;
    HMODULE hD3D9;

    if (!ShouldInstallDeviceHooks()) {
        LogIf("Skipping D3D9 Hook (all D3D9 settings disabled).");
        return;
    }

    // Compute shadow atlas upscale factor from ShadowSizeMul
    s_shadowUpscale = (g_shadowSizeMul > 1.0f) ? 2 : 1;

    LogIf("Initializing D3D9 Hook...");

    hD3D9 = GetModuleHandleA("d3d9.dll");
    if (!hD3D9) {
        LogIf("  WARNING: d3d9.dll not loaded yet. Hooking deferred.");
        return;
    }

    pfnDirect3DCreate9 = (Direct3DCreate9_t)GetProcAddress(hD3D9, "Direct3DCreate9");
    if (!pfnDirect3DCreate9) {
        LogIf("  ERROR: Could not find Direct3DCreate9 in d3d9.dll");
        return;
    }

    pDummyD3D9 = pfnDirect3DCreate9(D3D_SDK_VERSION);
    if (!pDummyD3D9) {
        LogIf("  ERROR: Failed to create dummy IDirect3D9 instance");
        return;
    }

    vtable = *(void***)pDummyD3D9;
    pCreateDevice = vtable[16];

    if (!hookCreateDevice.original) {
        InstallHook(&hookCreateDevice, pCreateDevice, (void*)MyCreateDevice);
        LogFmtIf("  IDirect3D9::CreateDevice hooked @ %p (Trampoline: %p)",
            pCreateDevice, hookCreateDevice.trampoline);
    }

    if (g_d3d9StateCache)
        LogIf("  D3D9 state cache strategy is active.");
    if (g_d3d9ShaderConstCache)
        LogIf("  D3D9 shader constant cache strategy is active.");
    if (s_shadowUpscale > 1)
        LogFmtIf("  4K Shadow upscale active (%dx): cascade atlas 2048x6144 -> %ux%u.",
                 s_shadowUpscale, 2048 * s_shadowUpscale, 6144 * s_shadowUpscale);

    pDummyD3D9->Release();
}
