/*
 * WC3 Classic D3D9 command recorder.
 *
 * This proxy keeps the real Wine D3D9 objects. It clones selected COM vtables
 * and replaces only the methods needed by Warcraft III 1.28.5. The render
 * thread writes to a bounded memory queue. A separate thread performs all file
 * or FIFO I/O. If no consumer is connected, recording is nearly dormant.
 */
#define CINTERFACE
#define COBJMACROS
#include <windows.h>
#include <d3d9.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "w3cs_protocol.h"

#define QUEUE_LIMIT (32u * 1024u * 1024u)
#define FRAME_LIMIT W3CS_MAX_FRAME
#define MAX_TEXTURE_STAGES 8u
#define MAX_LIGHTS 8u
#define MAX_RENDER_STATES 256u
#define MAX_TRANSFORMS 512u
#define MAX_TSS_TYPES 33u
#define MAX_SAMPLER_TYPES 16u
#define MAX_TEXTURE_LEVELS 16u
#define BLOB_BUCKETS 4096u
#define BLOB_CACHE_LIMIT (64u * 1024u * 1024u)
/* WC3 rewrites animated ring-buffer vertices every Present, so encoded
 * deltas grow as poses drift from their reliable base. Refresh each slice's
 * base on the reliable plane at most every GEOMETRY_REBASE_INTERVAL sampled
 * frames (15 s at 40 FPS), staggered by draw key so hundreds of slices do
 * not rebase in one burst. */
#define GEOMETRY_REBASE_INTERVAL 600u
/* Demand-driven rebase: once the deltas emitted against one base have cost
 * more than half of that base, resending the exact base pays for itself
 * within a few anchors. Per-slice spacing backs off when a fresh base does
 * not shrink the next anchor delta (motion-dominated geometry), so animated
 * meshes cannot flood the reliable plane. The per-frame byte budget keeps
 * many simultaneous rebases from forming one reliable-plane burst. */
#define GEOMETRY_REBASE_MIN_SPACING 24u
/* Rebase bases ride the reliable plane, which shares one SCTP association
 * with frames. 48 KiB per sampled frame flooded that association during
 * large fights on a 90 ms WAN seat and starved the frame path. */
#define GEOMETRY_REBASE_FRAME_BUDGET (16u * 1024u)
#define GEOMETRY_GOP 4u
/* CPU-skinned WC3 meshes are the dominant WAN payload. Refresh their stable
 * delta base on a short geometry anchor. Every intervening frame must still
 * transmit its exact pose. Reusing an anchor pose for FVF 0x112 made animated
 * trees and building meshes freeze for three frames, then visibly pop. */
#define RESOURCE_BUCKETS 2048u

enum resource_kind {
    RES_VERTEX_BUFFER = 1,
    RES_INDEX_BUFFER = 2,
    RES_TEXTURE = 3,
    RES_SURFACE = 4
};

struct queued_message {
    struct queued_message *next;
    uint32_t size;
    uint8_t bytes[1];
};

struct blob {
    struct blob *next;
    struct blob *id_next;
    uint64_t hash_a;
    uint64_t hash_b;
    uint32_t size;
    uint32_t id;
    uint32_t frame_emitted;
    uint32_t frame_wire_id;
    struct blob *frame_wire_blob;
    BOOL reliable_emitted;
    uint32_t geometry_epoch;
    uint32_t base_refs;
    BOOL pinned;
    uint8_t data[1];
};

struct slice_cache {
    struct slice_cache *next;
    struct slice_cache *compatible_next;
    uint32_t resource_id;
    uint32_t logical_key;
    uint32_t size;
    uint32_t last_offset;
    uint32_t stride;
    uint32_t fvf;
    uint32_t texture_id;
    uint32_t geometry_epoch;
    struct blob *base;
    struct blob *chain_base;
    struct blob *pending_chain_base;
    struct blob *blob;
    uint32_t base_frame;
    uint32_t last_frame;
    uint32_t delta_spend;
    uint32_t rebase_spacing;
    BOOL probe_after_rebase;
};

struct vtable_patch {
    struct vtable_patch *next;
    void *patched_vtable;
    void *original_vtable;
    uint32_t kind;
};

struct resource {
    struct resource *next;
    struct resource *object_next;
    struct resource *id_next;
    void *object;
    void *original_vtable;
    void *hook_vtable;
    uint32_t id;
    uint32_t generation;
    uint32_t kind;
    uint32_t size;
    uint32_t usage;
    uint32_t format;
    uint32_t fvf;
    uint32_t pool;
    uint32_t width;
    uint32_t height;
    uint32_t levels;
    uint32_t parent_id;
    uint32_t level;
    /* new_resource() publishes the object before its D3D descriptor is
     * copied. A resource snapshot can run on another D3D thread. Do not
     * serialize a half-initialized resource into that snapshot. */
    BOOL descriptor_ready;
    /* A create record can be rejected while the bounded queue is full during
     * the bootstrap snapshot burst. A texture update for an id the browser
     * never saw then kills the stream. Track delivery and retry the create
     * before anything references the resource. */
    BOOL create_emitted;
    uint8_t *shadow;
    uint32_t shadow_size;
    uint8_t *mip_shadow[MAX_TEXTURE_LEVELS];
    uint32_t mip_shadow_size[MAX_TEXTURE_LEVELS];
    uint8_t mip_dirty[MAX_TEXTURE_LEVELS];
    uint8_t mip_dynamic[MAX_TEXTURE_LEVELS];
    uint32_t mip_recorded_generation[MAX_TEXTURE_LEVELS];
    uint32_t mip_dirty_left[MAX_TEXTURE_LEVELS];
    uint32_t mip_dirty_top[MAX_TEXTURE_LEVELS];
    uint32_t mip_dirty_right[MAX_TEXTURE_LEVELS];
    uint32_t mip_dirty_bottom[MAX_TEXTURE_LEVELS];
    void *lock_ptr;
    uint32_t lock_offset;
    uint32_t lock_size;
    uint32_t lock_flags;
    int32_t lock_pitch;
    RECT lock_rect;
    BOOL has_lock_rect;
    BOOL lazy_capture_attempted;
    uint32_t lazy_capture_last_frame;
    BOOL lazy_capture_diagnostic;
};

struct stream_binding {
    uint32_t id;
    uint32_t generation;
    uint32_t offset;
    uint32_t stride;
};

struct texture_binding {
    uint32_t id;
    uint32_t generation;
};

struct recorder_state {
    DWORD render_state[MAX_RENDER_STATES];
    uint8_t render_valid[MAX_RENDER_STATES];
    D3DMATRIX transform[MAX_TRANSFORMS];
    uint8_t transform_valid[MAX_TRANSFORMS];
    DWORD tss[MAX_TEXTURE_STAGES][MAX_TSS_TYPES];
    uint8_t tss_valid[MAX_TEXTURE_STAGES][MAX_TSS_TYPES];
    DWORD sampler[MAX_TEXTURE_STAGES][MAX_SAMPLER_TYPES];
    uint8_t sampler_valid[MAX_TEXTURE_STAGES][MAX_SAMPLER_TYPES];
    struct stream_binding streams[MAX_TEXTURE_STAGES];
    uint8_t stream_valid[MAX_TEXTURE_STAGES];
    struct texture_binding textures[MAX_TEXTURE_STAGES];
    uint8_t texture_valid[MAX_TEXTURE_STAGES];
    uint32_t index_id;
    uint32_t index_generation;
    uint8_t index_valid;
    DWORD fvf;
    uint8_t fvf_valid;
    D3DMATERIAL9 material;
    uint8_t material_valid;
    D3DLIGHT9 lights[MAX_LIGHTS];
    uint8_t light_valid[MAX_LIGHTS];
    BOOL light_enabled[MAX_LIGHTS];
    uint8_t light_enable_valid[MAX_LIGHTS];
    D3DVIEWPORT9 viewport;
    uint8_t viewport_valid;
    RECT scissor;
    uint8_t scissor_valid;
};

static INIT_ONCE g_once = INIT_ONCE_STATIC_INIT;
static CRITICAL_SECTION g_queue_lock;
static CRITICAL_SECTION g_resource_lock;
static CRITICAL_SECTION g_frame_lock;
static struct queued_message *g_queue_head;
static struct queued_message *g_queue_tail;
static uint32_t g_queue_bytes;
static HANDLE g_queue_event;
static HANDLE g_writer_thread;
static volatile LONG g_connected;
static volatile LONG g_snapshot_requested;
static volatile LONG g_dropped_frames;
static char g_output_path[MAX_PATH * 2];
static BOOL g_create_output;
static BOOL g_debug;
static BOOL g_forced_no_raster;
static BOOL g_headless_fast;
static BOOL g_exact_geometry;
static volatile LONG g_no_raster;
static volatile LONG g_capture_frame = 1;
static uint32_t g_max_fps;
static uint32_t g_capture_fps;
static LARGE_INTEGER g_clock_frequency;
static LARGE_INTEGER g_last_present_at;
static LARGE_INTEGER g_next_capture_at;
static int64_t g_present_interval;
static char g_control_path[MAX_PATH * 2];
static DWORD g_control_checked_at;
static uint32_t g_recovery_nonce;
static uint32_t g_snapshot_nonce;
static volatile LONG g_stream_enabled = 1;
static volatile LONG g_last_hook;
static uint32_t g_session;
static volatile LONG g_sequence;
static volatile LONG g_frame_number;
static uint32_t g_draw_ordinal;
static uint8_t *g_frame;
static uint32_t g_frame_size;
static BOOL g_frame_overflow;
static BOOL g_frame_dependency_failed;
static BOOL g_frame_keyframe;
static BOOL g_frame_geometry_anchor;
static BOOL g_force_geometry_anchor = TRUE;
static uint32_t g_geometry_gop_position;
static DWORD g_last_keyframe_tick;
static uint32_t g_geometry_epoch;
static uint32_t g_frame_rebase_bytes;
static struct resource *g_resources;
static struct resource *g_resources_by_object[RESOURCE_BUCKETS];
static struct resource *g_resources_by_id[RESOURCE_BUCKETS];
static volatile LONG g_next_resource_id;
static struct blob *g_blobs[BLOB_BUCKETS];
static struct blob *g_blobs_by_id[BLOB_BUCKETS];
/* Ids of reliably emitted blobs that were later freed. The id space is
 * content-hash derived, so a freed id can be re-minted for DIFFERENT
 * content while a browser still holds the old copy (epoch pins survive
 * until the next recovery, and the browser's bundle cache can resurrect an
 * old id into a fresh session). Re-using such an id produced "conflicting
 * pinned blob identity" decode errors that seeded recovery storms. Ids
 * retire forever; a session retires a few thousand, so memory is trivial. */
struct retired_blob_id {
    uint32_t id;
    struct retired_blob_id *next;
};
static struct retired_blob_id *g_retired_blob_ids[BLOB_BUCKETS];
static struct slice_cache *g_slices[BLOB_BUCKETS];
static struct slice_cache *g_compatible_slices[BLOB_BUCKETS];
static uint32_t g_blob_cache_bytes;
static struct vtable_patch *g_vtable_patches;
static struct recorder_state g_state;
static uint32_t g_crc32_table[256];
static uint8_t *g_cursor_pixels;
static uint32_t g_cursor_size;
static uint32_t g_cursor_width;
static uint32_t g_cursor_height;
static uint32_t g_cursor_hot_x;
static uint32_t g_cursor_hot_y;
static BOOL g_cursor_visible = TRUE;
#define MAX_VIOLATION_KINDS 24u
static const char *g_violation_methods[MAX_VIOLATION_KINDS];
static uint32_t g_violation_method_count;
static void *g_default_render_target;
static void *g_default_depth_stencil;

static HMODULE g_real_d3d9;
static IDirect3DDevice9Vtbl *g_device_original;
static HRESULT (STDMETHODCALLTYPE *g_create_device_original)(
    IDirect3D9 *, UINT, D3DDEVTYPE, HWND, DWORD,
    D3DPRESENT_PARAMETERS *, IDirect3DDevice9 **);

static BOOL texture_layout(D3DFORMAT format, uint32_t width, uint32_t height,
                           uint32_t *row_bytes, uint32_t *rows);
static BOOL emit_texture_blob_update_locked(
    const struct w3cs_update_texture *update, const uint8_t *pixels);

static void debug_message(const char *message)
{
    if (g_debug)
        OutputDebugStringA(message);
}

static void debug_resource_event(const char *event, const struct resource *item,
                                 uint32_t value)
{
    char message[192];
    if (!g_debug || !item)
        return;
    wsprintfA(message,
              "w3cs: %s id=%lu kind=%lu size=%lux%lu value=%lu gen=%lu\n",
              event, (unsigned long)item->id, (unsigned long)item->kind,
              (unsigned long)item->width, (unsigned long)item->height,
              (unsigned long)value, (unsigned long)item->generation);
    OutputDebugStringA(message);
}

#define MARK_HOOK(value) InterlockedExchange(&g_last_hook, (value))

static BOOL headless_fast_active(void)
{
    return g_headless_fast
        && InterlockedCompareExchange(&g_no_raster, 0, 0);
}

static LONG WINAPI exception_probe(EXCEPTION_POINTERS *exception)
{
    char message[160];
    if (g_debug && exception && exception->ExceptionRecord
            && exception->ExceptionRecord->ExceptionCode
               == EXCEPTION_ACCESS_VIOLATION) {
        wsprintfA(message, "w3cs: access violation lastHook=%ld address=%p\n",
                  g_last_hook,
                  exception->ExceptionRecord->ExceptionAddress);
        OutputDebugStringA(message);
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

static void debug_patch_addresses(void *vtable, void *slot,
                                  void *original, void *replacement)
{
    char message[256];
    MEMORY_BASIC_INFORMATION before;
    MEMORY_BASIC_INFORMATION after;
    if (!g_debug)
        return;
    memset(&before, 0, sizeof(before));
    memset(&after, 0, sizeof(after));
    VirtualQuery(replacement, &before, sizeof(before));
    wsprintfA(message,
              "w3cs: vtable=%p slot=%p original=%p hook=%p protect=%lx\n",
              vtable, slot, original, replacement, before.Protect);
    OutputDebugStringA(message);
    VirtualQuery(replacement, &after, sizeof(after));
    wsprintfA(message, "w3cs: hook protect after query=%lx\n", after.Protect);
    OutputDebugStringA(message);
}

static BOOL patch_pointer(void **slot, void *replacement)
{
    DWORD old_protect;
    DWORD ignored;
    if (!VirtualProtect(slot, sizeof(*slot), PAGE_EXECUTE_READWRITE,
                        &old_protect))
        return FALSE;
    *slot = replacement;
    FlushInstructionCache(GetCurrentProcess(), slot, sizeof(*slot));
    VirtualProtect(slot, sizeof(*slot), old_protect, &ignored);
    return TRUE;
}

static void init_crc32_table(void)
{
    uint32_t index;
    for (index = 0; index < 256u; ++index) {
        uint32_t crc = index;
        uint32_t bit;
        for (bit = 0; bit < 8u; ++bit)
            crc = (crc >> 1) ^ (0xedb88320u & (0u - (crc & 1u)));
        g_crc32_table[index] = crc;
    }
}

static uint32_t crc32_bytes(const uint8_t *data, uint32_t size)
{
    uint32_t crc = 0xffffffffu;
    while (size--)
        crc = (crc >> 8) ^ g_crc32_table[(crc ^ *data++) & 0xffu];
    return ~crc;
}

static BOOL write_all(HANDLE file, const void *data, uint32_t size)
{
    const uint8_t *cursor = (const uint8_t *)data;
    while (size) {
        DWORD written = 0;
        if (!WriteFile(file, cursor, size, &written, NULL) || written == 0)
            return FALSE;
        cursor += written;
        size -= written;
    }
    return TRUE;
}

static void clear_queue(void)
{
    struct queued_message *message;
    EnterCriticalSection(&g_queue_lock);
    message = g_queue_head;
    g_queue_head = g_queue_tail = NULL;
    g_queue_bytes = 0;
    LeaveCriticalSection(&g_queue_lock);
    while (message) {
        struct queued_message *next = message->next;
        HeapFree(GetProcessHeap(), 0, message);
        message = next;
    }
}

static void free_message_list(struct queued_message *message)
{
    while (message) {
        struct queued_message *next = message->next;
        HeapFree(GetProcessHeap(), 0, message);
        message = next;
    }
}

/* A fragmented record is one logical dependency. Queue all fragments or none.
 * The old per-fragment enqueue could fill the bounded queue halfway through a
 * large texture or vertex blob. The recorder then marked the blob as emitted,
 * while the relay waited forever for the missing tail fragments. */
static BOOL enqueue_message_list(struct queued_message *head,
                                 struct queued_message *tail,
                                 uint32_t total_size)
{
    if (!head || !tail || !total_size
            || !InterlockedCompareExchange(&g_connected, 0, 0))
        goto reject;
    EnterCriticalSection(&g_queue_lock);
    if (total_size > QUEUE_LIMIT
            || g_queue_bytes > QUEUE_LIMIT - total_size) {
        LeaveCriticalSection(&g_queue_lock);
        goto reject;
    }
    if (g_queue_tail)
        g_queue_tail->next = head;
    else
        g_queue_head = head;
    g_queue_tail = tail;
    g_queue_bytes += total_size;
    LeaveCriticalSection(&g_queue_lock);
    SetEvent(g_queue_event);
    return TRUE;
reject:
    free_message_list(head);
    return FALSE;
}

static BOOL queue_payload(uint8_t kind, uint32_t frame, uint16_t flags,
                          const uint8_t *payload, uint32_t size)
{
    uint32_t count = size ? (size + W3CS_MAX_FRAGMENT - 1) / W3CS_MAX_FRAGMENT : 1;
    uint32_t index;
    uint32_t total_size = 0;
    struct queued_message *head = NULL;
    struct queued_message *tail = NULL;
    if (count > 0xffffu)
        return FALSE;
    for (index = 0; index < count; ++index) {
        uint32_t offset = index * W3CS_MAX_FRAGMENT;
        uint32_t part = size > offset ? size - offset : 0;
        uint32_t packet_size;
        struct queued_message *message;
        uint8_t *packet;
        uint32_t wire_size;
        struct w3cs_envelope *envelope;
        if (part > W3CS_MAX_FRAGMENT)
            part = W3CS_MAX_FRAGMENT;
        wire_size = (uint32_t)sizeof(*envelope) + part;
        packet_size = 4 + wire_size;
        message = (struct queued_message *)HeapAlloc(
            GetProcessHeap(), 0, sizeof(*message) + packet_size);
        if (!message) {
            free_message_list(head);
            return FALSE;
        }
        message->next = NULL;
        message->size = packet_size;
        packet = message->bytes;
        memcpy(packet, &wire_size, 4);
        envelope = (struct w3cs_envelope *)(packet + 4);
        memcpy(envelope->magic, "W3CS", 4);
        envelope->version = W3CS_VERSION;
        envelope->kind = kind;
        envelope->flags = flags | (index + 1 == count ? W3CS_LAST : 0);
        envelope->session = g_session;
        envelope->sequence = (uint32_t)InterlockedIncrement(&g_sequence);
        envelope->frame = frame;
        envelope->fragment_index = (uint16_t)index;
        envelope->fragment_count = (uint16_t)count;
        envelope->payload_size = part;
        envelope->payload_crc32 = crc32_bytes(payload + offset, part);
        if (part)
            memcpy(packet + 4 + sizeof(*envelope), payload + offset, part);
        if (tail)
            tail->next = message;
        else
            head = message;
        tail = message;
        if (total_size > UINT32_MAX - packet_size) {
            free_message_list(head);
            return FALSE;
        }
        total_size += packet_size;
    }
    if (!enqueue_message_list(head, tail, total_size)) {
        if (kind == W3CS_FRAME)
            InterlockedIncrement(&g_dropped_frames);
        return FALSE;
    }
    return TRUE;
}

/* An unhooked or unsupported D3D9 path executed, so the command stream can
 * no longer promise an exact image. Report each distinct method once on the
 * reliable plane; the browser fails closed into the video fallback. Silent
 * divergence is the one failure mode this recorder must never have. */
static void capture_violation(const char *method)
{
    uint32_t index;
    BOOL known = FALSE;
    char message[224];
    EnterCriticalSection(&g_resource_lock);
    for (index = 0; index < g_violation_method_count; ++index) {
        if (g_violation_methods[index] == method) {
            known = TRUE;
            break;
        }
    }
    if (!known && g_violation_method_count < MAX_VIOLATION_KINDS)
        g_violation_methods[g_violation_method_count++] = method;
    LeaveCriticalSection(&g_resource_lock);
    if (known)
        return;
    wsprintfA(message,
              "{\"error\":\"capture-violation\",\"method\":\"%s\"}", method);
    if (g_debug) {
        char line[256];
        wsprintfA(line, "w3cs: capture violation %s\n", method);
        OutputDebugStringA(line);
    }
    queue_payload(W3CS_ERROR, 0, 0, (const uint8_t *)message,
                  (uint32_t)lstrlenA(message));
}

static struct queued_message *dequeue_message(void)
{
    struct queued_message *message;
    EnterCriticalSection(&g_queue_lock);
    message = g_queue_head;
    if (message) {
        g_queue_head = message->next;
        if (!g_queue_head)
            g_queue_tail = NULL;
        g_queue_bytes -= message->size;
    }
    LeaveCriticalSection(&g_queue_lock);
    return message;
}

static HANDLE open_output(void)
{
    DWORD disposition = g_create_output ? OPEN_ALWAYS : OPEN_EXISTING;
    HANDLE file = CreateFileA(g_output_path, GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                              disposition, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file != INVALID_HANDLE_VALUE && g_create_output)
        SetFilePointer(file, 0, NULL, FILE_END);
    return file;
}

static DWORD WINAPI writer_main(void *unused)
{
    HANDLE file = INVALID_HANDLE_VALUE;
    (void)unused;
    for (;;) {
        if (file == INVALID_HANDLE_VALUE) {
            file = open_output();
            if (file == INVALID_HANDLE_VALUE) {
                InterlockedExchange(&g_connected, 0);
                Sleep(250);
                continue;
            }
            InterlockedExchange(&g_connected, 1);
            InterlockedExchange(&g_snapshot_requested, 1);
            {
                static const char hello[] =
                    "{\"producer\":\"wc3-d3d9-proxy\",\"protocol\":1}";
                queue_payload(W3CS_HELLO, 0, 0,
                              (const uint8_t *)hello,
                              (uint32_t)(sizeof(hello) - 1));
            }
        }
        WaitForSingleObject(g_queue_event, 250);
        for (;;) {
            struct queued_message *message = dequeue_message();
            if (!message)
                break;
            if (!write_all(file, message->bytes, message->size)) {
                HeapFree(GetProcessHeap(), 0, message);
                CloseHandle(file);
                file = INVALID_HANDLE_VALUE;
                InterlockedExchange(&g_connected, 0);
                clear_queue();
                break;
            }
            HeapFree(GetProcessHeap(), 0, message);
        }
    }
    return 0;
}

static BOOL CALLBACK recorder_init(PINIT_ONCE once, PVOID parameter, PVOID *context)
{
    char create[8];
    char fps_text[16];
    (void)once;
    (void)parameter;
    (void)context;
    InitializeCriticalSection(&g_queue_lock);
    InitializeCriticalSection(&g_resource_lock);
    InitializeCriticalSection(&g_frame_lock);
    init_crc32_table();
    g_queue_event = CreateEventA(NULL, FALSE, FALSE, NULL);
    g_frame = (uint8_t *)VirtualAlloc(NULL, FRAME_LIMIT,
                                     MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    g_session = GetCurrentProcessId() ^ GetTickCount();
    g_next_resource_id = 100;
    if (!GetEnvironmentVariableA("W3_D3D9_STREAM_FILE", g_output_path,
                                 (DWORD)sizeof(g_output_path)))
        return TRUE;
    g_create_output = GetEnvironmentVariableA(
        "W3_D3D9_STREAM_CREATE", create, (DWORD)sizeof(create)) > 0
        && create[0] == '1';
    g_debug = GetEnvironmentVariableA(
        "W3_D3D9_DEBUG", create, (DWORD)sizeof(create)) > 0
        && create[0] == '1';
    g_forced_no_raster = GetEnvironmentVariableA(
        "W3_D3D9_NO_RASTER", create, (DWORD)sizeof(create)) > 0
        && create[0] == '1';
    g_headless_fast = GetEnvironmentVariableA(
        "W3_D3D9_HEADLESS_FAST", create, (DWORD)sizeof(create)) > 0
        && create[0] == '1';
    /* Parity validation: disable the tolerance-bounded float16 vertex delta
     * so every geometry byte crosses the wire exactly. */
    g_exact_geometry = GetEnvironmentVariableA(
        "W3_D3D9_EXACT_GEOMETRY", create, (DWORD)sizeof(create)) > 0
        && create[0] == '1';
    g_no_raster = g_forced_no_raster ? 1 : 0;
    QueryPerformanceFrequency(&g_clock_frequency);
    if (GetEnvironmentVariableA("W3_D3D9_MAX_FPS", fps_text,
                                (DWORD)sizeof(fps_text))) {
        g_max_fps = (uint32_t)strtoul(fps_text, NULL, 10);
        if (g_max_fps > 240u)
            g_max_fps = 240u;
    }
    if (GetEnvironmentVariableA("W3_D3D9_CAPTURE_FPS", fps_text,
                                (DWORD)sizeof(fps_text))) {
        g_capture_fps = (uint32_t)strtoul(fps_text, NULL, 10);
        if (g_capture_fps > 240u)
            g_capture_fps = 240u;
    }
    GetEnvironmentVariableA("W3_D3D9_CONTROL_FILE", g_control_path,
                            (DWORD)sizeof(g_control_path));
    if (g_debug)
        AddVectoredExceptionHandler(1, exception_probe);
    g_writer_thread = CreateThread(NULL, 0, writer_main, NULL, 0, NULL);
    return TRUE;
}

static void ensure_initialized(void)
{
    InitOnceExecuteOnce(&g_once, recorder_init, NULL, NULL);
}

static void refresh_raster_mode(void)
{
    DWORD now;
    HANDLE file;
    char value[32] = {0};
    DWORD read = 0;
    if (!g_control_path[0])
        return;
    now = GetTickCount();
    if (now - g_control_checked_at < 100u)
        return;
    g_control_checked_at = now;
    file = CreateFileA(g_control_path, GENERIC_READ,
                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file != INVALID_HANDLE_VALUE) {
        ReadFile(file, value, (DWORD)sizeof(value) - 1u, &read, NULL);
        CloseHandle(file);
    }
    if (read > 2u) {
        char *fps_text = value;
        char *recovery_text;
        char *snapshot_text;
        char *stream_text;
        while (*fps_text && *fps_text != ' ' && *fps_text != '\t')
            ++fps_text;
        while (*fps_text == ' ' || *fps_text == '\t')
            ++fps_text;
        if (*fps_text) {
            uint32_t capture_fps = (uint32_t)strtoul(fps_text, NULL, 10);
            if (capture_fps > 240u)
                capture_fps = 240u;
            if (capture_fps && capture_fps != g_capture_fps) {
                g_capture_fps = capture_fps;
                g_next_capture_at.QuadPart = 0;
            }
        }
        recovery_text = fps_text;
        while (*recovery_text && *recovery_text != ' '
                && *recovery_text != '\t')
            ++recovery_text;
        while (*recovery_text == ' ' || *recovery_text == '\t')
            ++recovery_text;
        if (*recovery_text) {
            uint32_t recovery_nonce = (uint32_t)strtoul(
                recovery_text, NULL, 10);
            if (recovery_nonce != g_recovery_nonce) {
                g_recovery_nonce = recovery_nonce;
                g_last_keyframe_tick = 0;
            }
        }
        snapshot_text = recovery_text;
        while (*snapshot_text && *snapshot_text != ' '
                && *snapshot_text != '\t')
            ++snapshot_text;
        while (*snapshot_text == ' ' || *snapshot_text == '\t')
            ++snapshot_text;
        if (*snapshot_text) {
            uint32_t snapshot_nonce = (uint32_t)strtoul(
                snapshot_text, NULL, 10);
            if (snapshot_nonce != g_snapshot_nonce) {
                g_snapshot_nonce = snapshot_nonce;
                InterlockedExchange(&g_snapshot_requested, 1);
                g_last_keyframe_tick = 0;
            }
        }
        stream_text = snapshot_text;
        while (*stream_text && *stream_text != ' '
                && *stream_text != '\t')
            ++stream_text;
        while (*stream_text == ' ' || *stream_text == '\t')
            ++stream_text;
        if (*stream_text)
            InterlockedExchange(&g_stream_enabled,
                                strtoul(stream_text, NULL, 10) ? 1 : 0);
    }
    InterlockedExchange(&g_no_raster,
                        g_forced_no_raster || (read >= 1 && value[0] == '1'));
}

static LARGE_INTEGER pace_present(void)
{
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    if (g_max_fps && InterlockedCompareExchange(&g_no_raster, 0, 0)
            && g_last_present_at.QuadPart) {
        int64_t period = g_clock_frequency.QuadPart / g_max_fps;
        int64_t target = g_last_present_at.QuadPart + period;
        while (now.QuadPart < target) {
            int64_t remaining = target - now.QuadPart;
            DWORD milliseconds = (DWORD)(remaining * 1000
                / g_clock_frequency.QuadPart);
            Sleep(milliseconds > 1u ? milliseconds - 1u : 1u);
            QueryPerformanceCounter(&now);
        }
    }
    if (g_last_present_at.QuadPart)
        g_present_interval = now.QuadPart - g_last_present_at.QuadPart;
    g_last_present_at = now;
    return now;
}

static void choose_next_capture(LARGE_INTEGER now)
{
    int64_t period;
    int64_t expected;
    int64_t predicted;
    if (!g_capture_fps || !g_clock_frequency.QuadPart) {
        InterlockedExchange(&g_capture_frame, 1);
        return;
    }
    period = g_clock_frequency.QuadPart / g_capture_fps;
    expected = g_present_interval;
    if (expected <= 0)
        expected = g_max_fps
            ? g_clock_frequency.QuadPart / g_max_fps : period;
    predicted = now.QuadPart + expected;
    if (!g_next_capture_at.QuadPart)
        g_next_capture_at.QuadPart = now.QuadPart + period;
    if (predicted >= g_next_capture_at.QuadPart) {
        InterlockedExchange(&g_capture_frame, 1);
        do {
            g_next_capture_at.QuadPart += period;
        } while (g_next_capture_at.QuadPart <= now.QuadPart);
    } else {
        InterlockedExchange(&g_capture_frame, 0);
    }
}

static BOOL raw_record_locked(uint8_t opcode, uint8_t flags,
                              const void *payload, uint32_t size)
{
    struct w3cs_record header;
    uint32_t need = (uint32_t)sizeof(header) + size;
    if (!g_frame || g_frame_overflow || need > FRAME_LIMIT - g_frame_size) {
        g_frame_overflow = TRUE;
        return FALSE;
    }
    header.opcode = opcode;
    header.flags = flags;
    header.reserved = 0;
    header.payload_size = size;
    memcpy(g_frame + g_frame_size, &header, sizeof(header));
    g_frame_size += (uint32_t)sizeof(header);
    if (size) {
        memcpy(g_frame + g_frame_size, payload, size);
        g_frame_size += size;
    }
    return TRUE;
}

static BOOL record_transform_locked(uint32_t state,
                                    const D3DMATRIX *matrix);

static void snapshot_state_locked(void)
{
    uint32_t i, stage, type;
    uint32_t state_version = 1;
    raw_record_locked(W3CS_OP_FRAME_STATE, 0, &state_version,
                      sizeof(state_version));
    for (i = 0; i < MAX_RENDER_STATES; ++i) {
        uint32_t pair[2];
        if (!g_state.render_valid[i])
            continue;
        pair[0] = i;
        pair[1] = g_state.render_state[i];
        raw_record_locked(W3CS_OP_SET_RENDER_STATE, 0, pair, sizeof(pair));
    }
    for (i = 0; i < MAX_TRANSFORMS; ++i) {
        if (!g_state.transform_valid[i])
            continue;
        record_transform_locked(i, &g_state.transform[i]);
    }
    for (stage = 0; stage < MAX_TEXTURE_STAGES; ++stage) {
        for (type = 0; type < MAX_TSS_TYPES; ++type) {
            uint32_t triple[3];
            if (!g_state.tss_valid[stage][type])
                continue;
            triple[0] = stage;
            triple[1] = type;
            triple[2] = g_state.tss[stage][type];
            raw_record_locked(W3CS_OP_SET_TEXTURE_STAGE_STATE, 0,
                              triple, sizeof(triple));
        }
        for (type = 0; type < MAX_SAMPLER_TYPES; ++type) {
            uint32_t triple[3];
            if (!g_state.sampler_valid[stage][type])
                continue;
            triple[0] = stage;
            triple[1] = type;
            triple[2] = g_state.sampler[stage][type];
            raw_record_locked(W3CS_OP_SET_SAMPLER_STATE, 0,
                              triple, sizeof(triple));
        }
        if (g_state.stream_valid[stage]) {
            struct {
                uint32_t stream;
                struct stream_binding binding;
            } stream_payload;
            stream_payload.stream = stage;
            stream_payload.binding = g_state.streams[stage];
            raw_record_locked(W3CS_OP_SET_STREAM_SOURCE, 0,
                              &stream_payload, sizeof(stream_payload));
        }
        if (g_state.texture_valid[stage]) {
            uint32_t binding[3] = {stage, g_state.textures[stage].id,
                                   g_state.textures[stage].generation};
            raw_record_locked(W3CS_OP_SET_TEXTURE, 0, binding,
                              sizeof(binding));
        }
    }
    if (g_state.index_valid) {
        uint32_t binding[2] = {g_state.index_id, g_state.index_generation};
        raw_record_locked(W3CS_OP_SET_INDICES, 0, binding, sizeof(binding));
    }
    if (g_state.fvf_valid)
        raw_record_locked(W3CS_OP_SET_FVF, 0, &g_state.fvf,
                          sizeof(g_state.fvf));
    if (g_state.material_valid)
        raw_record_locked(W3CS_OP_SET_MATERIAL, 0, &g_state.material,
                          sizeof(g_state.material));
    for (i = 0; i < MAX_LIGHTS; ++i) {
        if (g_state.light_valid[i]) {
            uint8_t payload[4 + sizeof(D3DLIGHT9)];
            memcpy(payload, &i, 4);
            memcpy(payload + 4, &g_state.lights[i], sizeof(D3DLIGHT9));
            raw_record_locked(W3CS_OP_SET_LIGHT, 0, payload, sizeof(payload));
        }
        if (g_state.light_enable_valid[i]) {
            uint32_t enabled[2] = {i, g_state.light_enabled[i] ? 1u : 0u};
            raw_record_locked(W3CS_OP_LIGHT_ENABLE, 0, enabled,
                              sizeof(enabled));
        }
    }
    if (g_state.viewport_valid)
        raw_record_locked(W3CS_OP_SET_VIEWPORT, 0, &g_state.viewport,
                          sizeof(g_state.viewport));
    if (g_state.scissor_valid)
        raw_record_locked(W3CS_OP_SET_SCISSOR, 0, &g_state.scissor,
                          sizeof(g_state.scissor));
}

static void begin_frame_locked(void)
{
    if (g_frame_size == 0) {
        DWORD now = GetTickCount();
        g_frame_rebase_bytes = 0;
        /* Recovery frames use their own reliable WebSocket. Normal frames
         * use that recovery as their stable geometry base. Start with one
         * recovery, then send another only when the relay changes the nonce. */
        g_frame_keyframe = !g_last_keyframe_tick;
        if (g_frame_keyframe) {
            g_last_keyframe_tick = now;
            if (++g_geometry_epoch == 0)
                g_geometry_epoch = 1;
            g_frame_geometry_anchor = FALSE;
            g_geometry_gop_position = 0;
            g_force_geometry_anchor = TRUE;
        } else {
            g_frame_geometry_anchor = g_force_geometry_anchor
                || g_geometry_gop_position == 0;
            if (g_frame_geometry_anchor) {
                g_force_geometry_anchor = FALSE;
                g_geometry_gop_position = 1;
            } else {
                ++g_geometry_gop_position;
                if (g_geometry_gop_position >= GEOMETRY_GOP)
                    g_geometry_gop_position = 0;
            }
        }
        snapshot_state_locked();
    }
}

static BOOL recording_frame(void)
{
    return InterlockedCompareExchange(&g_connected, 0, 0)
        && InterlockedCompareExchange(&g_stream_enabled, 0, 0)
        && InterlockedCompareExchange(&g_capture_frame, 0, 0);
}

static void append_record(uint8_t opcode, const void *payload, uint32_t size)
{
    if (!recording_frame())
        return;
    EnterCriticalSection(&g_frame_lock);
    begin_frame_locked();
    raw_record_locked(opcode, 0, payload, size);
    LeaveCriticalSection(&g_frame_lock);
}

static struct resource *find_resource_locked(void *object)
{
    struct resource *item;
    uint32_t bucket = ((uint32_t)(uintptr_t)object >> 4u)
        & (RESOURCE_BUCKETS - 1u);
    for (item = g_resources_by_object[bucket]; item;
         item = item->object_next)
        if (item->object == object)
            return item;
    return NULL;
}

static struct resource *find_resource_id_locked(uint32_t id)
{
    struct resource *item;
    uint32_t bucket = id & (RESOURCE_BUCKETS - 1u);
    for (item = g_resources_by_id[bucket]; item; item = item->id_next)
        if (item->id == id)
            return item;
    return NULL;
}

static struct vtable_patch *find_vtable_patch_locked(void *vtable)
{
    struct vtable_patch *patch;
    for (patch = g_vtable_patches; patch; patch = patch->next)
        if (patch->patched_vtable == vtable)
            return patch;
    return NULL;
}

static void resource_binding(void *object, uint32_t *id, uint32_t *generation)
{
    struct resource *item;
    *id = 0;
    *generation = 0;
    if (!object)
        return;
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    if (item) {
        *id = item->id;
        *generation = item->generation;
    }
    LeaveCriticalSection(&g_resource_lock);
}

static BOOL queue_record_message(uint8_t opcode, const void *payload,
                                 uint32_t size)
{
    uint32_t total = (uint32_t)sizeof(struct w3cs_record) + size;
    uint8_t *record = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, total);
    struct w3cs_record *header;
    if (!record)
        return FALSE;
    header = (struct w3cs_record *)record;
    header->opcode = opcode;
    header->flags = 0;
    header->reserved = 0;
    header->payload_size = size;
    if (size)
        memcpy(record + sizeof(*header), payload, size);
    {
        BOOL queued = queue_payload(W3CS_RESOURCE, 0, 0, record, total);
        HeapFree(GetProcessHeap(), 0, record);
        return queued;
    }
}

static BOOL emit_create_resource(struct resource *item)
{
    BOOL queued = FALSE;
    if (item->kind == RES_VERTEX_BUFFER || item->kind == RES_INDEX_BUFFER) {
        struct w3cs_create_buffer create;
        memset(&create, 0, sizeof(create));
        create.id = item->id;
        create.generation = item->generation;
        create.size = item->size;
        create.usage = item->usage;
        create.format_or_fvf = item->kind == RES_VERTEX_BUFFER
            ? item->fvf : item->format;
        create.pool = item->pool;
        create.kind = (uint8_t)item->kind;
        queued = queue_record_message(W3CS_OP_CREATE_BUFFER, &create,
                                      sizeof(create));
    } else if (item->kind == RES_TEXTURE) {
        struct w3cs_create_texture create;
        memset(&create, 0, sizeof(create));
        create.id = item->id;
        create.generation = item->generation;
        create.width = item->width;
        create.height = item->height;
        create.levels = item->levels;
        create.usage = item->usage;
        create.format = item->format;
        create.pool = item->pool;
        queued = queue_record_message(W3CS_OP_CREATE_TEXTURE, &create,
                                      sizeof(create));
    } else {
        return TRUE;
    }
    if (queued)
        item->create_emitted = TRUE;
    return queued;
}

static void emit_cursor_locked(void)
{
    struct w3cs_set_cursor cursor;
    uint8_t *payload;
    uint32_t payload_size;
    if (!g_cursor_pixels || !g_cursor_size)
        return;
    memset(&cursor, 0, sizeof(cursor));
    cursor.hot_x = g_cursor_hot_x;
    cursor.hot_y = g_cursor_hot_y;
    cursor.width = g_cursor_width;
    cursor.height = g_cursor_height;
    cursor.pitch = g_cursor_width * 4u;
    cursor.format = D3DFMT_A8R8G8B8;
    cursor.size = g_cursor_size;
    payload_size = (uint32_t)sizeof(cursor) + g_cursor_size;
    payload = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, payload_size);
    if (!payload)
        return;
    memcpy(payload, &cursor, sizeof(cursor));
    memcpy(payload + sizeof(cursor), g_cursor_pixels, g_cursor_size);
    queue_record_message(W3CS_OP_SET_CURSOR, payload, payload_size);
    HeapFree(GetProcessHeap(), 0, payload);
}

static void emit_cursor_visibility_locked(void)
{
    uint32_t visible = g_cursor_visible ? 1u : 0u;
    queue_record_message(W3CS_OP_SHOW_CURSOR, &visible, sizeof(visible));
}

static void emit_resource_snapshot(void)
{
    struct resource *item;
    BOOL complete;
    EnterCriticalSection(&g_resource_lock);
    /* Keep the boundary and every descriptor/update in one resource-lock
     * epoch. Otherwise a concurrent CreateTexture can put its update inside
     * this snapshot while its create lands outside it. */
    complete = queue_record_message(W3CS_OP_RESOURCE_SNAPSHOT_BEGIN, NULL, 0);
    for (item = g_resources; item; item = item->next) {
        uint32_t level;
        if (!item->descriptor_ready)
            continue;
        if (!emit_create_resource(item)) {
            complete = FALSE;
            /* Never queue a texture update ahead of its create: the browser
             * fails closed on an update for an unknown id, and one such
             * message turned this snapshot's queue-overflow retry into a
             * permanent recovery loop. Skipping the mips also frees queue
             * space so the next retry gets further through the creates. */
            continue;
        }
        if (item->kind == RES_TEXTURE) {
            uint32_t count = item->levels;
            if (!count || count > MAX_TEXTURE_LEVELS)
                count = MAX_TEXTURE_LEVELS;
            for (level = 0; level < count; ++level) {
                struct w3cs_update_texture update;
                uint32_t width = item->width >> level;
                uint32_t height = item->height >> level;
                uint32_t pitch, rows;
                if (!width)
                    width = 1;
                if (!height)
                    height = 1;
                if (!item->mip_shadow[level]
                        || !item->mip_shadow_size[level]
                        || !texture_layout((D3DFORMAT)item->format,
                                           width, height, &pitch, &rows))
                    continue;
                memset(&update, 0, sizeof(update));
                update.id = item->id;
                update.generation = item->generation;
                update.level = level;
                update.width = width;
                update.height = height;
                update.pitch = pitch;
                update.format = item->format;
                update.size = item->mip_shadow_size[level];
                if (emit_texture_blob_update_locked(
                        &update, item->mip_shadow[level])) {
                    item->mip_dirty[level] = FALSE;
                    item->mip_dynamic[level] = FALSE;
                    item->mip_recorded_generation[level] = item->generation;
                } else {
                    complete = FALSE;
                }
            }
        }
    }
    emit_cursor_locked();
    emit_cursor_visibility_locked();
    if (!queue_record_message(W3CS_OP_RESOURCE_SNAPSHOT_END, NULL, 0))
        complete = FALSE;
    /* The snapshot burst can exceed the bounded queue while the writer
     * drains. A partial snapshot is unusable: a create the browser never
     * received turns the first later texture update into a stream failure.
     * Re-arm and repeat the whole snapshot on the next Present. */
    if (!complete) {
        InterlockedExchange(&g_snapshot_requested, 1);
        debug_message("w3cs: resource snapshot incomplete, retrying\n");
    }
    LeaveCriticalSection(&g_resource_lock);
}

static struct resource *new_resource(void *object, uint32_t kind)
{
    struct resource *item = (struct resource *)HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*item));
    if (!item)
        return NULL;
    item->object = object;
    item->kind = kind;
    item->id = (uint32_t)InterlockedIncrement(&g_next_resource_id);
    EnterCriticalSection(&g_resource_lock);
    item->next = g_resources;
    g_resources = item;
    {
        uint32_t object_bucket = ((uint32_t)(uintptr_t)object >> 4u)
            & (RESOURCE_BUCKETS - 1u);
        uint32_t id_bucket = item->id & (RESOURCE_BUCKETS - 1u);
        item->object_next = g_resources_by_object[object_bucket];
        g_resources_by_object[object_bucket] = item;
        item->id_next = g_resources_by_id[id_bucket];
        g_resources_by_id[id_bucket] = item;
    }
    LeaveCriticalSection(&g_resource_lock);
    return item;
}

static void remove_resource(struct resource *item)
{
    struct resource **cursor;
    uint32_t level;
    if (!item)
        return;
    EnterCriticalSection(&g_resource_lock);
    for (cursor = &g_resources; *cursor; cursor = &(*cursor)->next) {
        if (*cursor == item) {
            *cursor = item->next;
            break;
        }
    }
    {
        uint32_t object_bucket = ((uint32_t)(uintptr_t)item->object >> 4u)
            & (RESOURCE_BUCKETS - 1u);
        uint32_t id_bucket = item->id & (RESOURCE_BUCKETS - 1u);
        for (cursor = &g_resources_by_object[object_bucket]; *cursor;
             cursor = &(*cursor)->object_next) {
            if (*cursor == item) {
                *cursor = item->object_next;
                break;
            }
        }
        for (cursor = &g_resources_by_id[id_bucket]; *cursor;
             cursor = &(*cursor)->id_next) {
            if (*cursor == item) {
                *cursor = item->id_next;
                break;
            }
        }
    }
    LeaveCriticalSection(&g_resource_lock);
    if (InterlockedCompareExchange(&g_connected, 0, 0))
        queue_record_message(W3CS_OP_DESTROY_RESOURCE, &item->id,
                             sizeof(item->id));
    if (item->shadow)
        HeapFree(GetProcessHeap(), 0, item->shadow);
    for (level = 0; level < MAX_TEXTURE_LEVELS; ++level)
        if (item->mip_shadow[level])
            HeapFree(GetProcessHeap(), 0, item->mip_shadow[level]);
    if (item->hook_vtable)
        HeapFree(GetProcessHeap(), 0, item->hook_vtable);
    HeapFree(GetProcessHeap(), 0, item);
}

static void record_buffer_update(struct resource *item)
{
    uint32_t size;
    uint8_t *source;
    if (!item || !item->lock_ptr || (item->lock_flags & D3DLOCK_READONLY))
        return;
    size = item->lock_size;
    if (!size || item->lock_offset > item->size
            || size > item->size - item->lock_offset)
        return;
    source = (uint8_t *)item->lock_ptr;
    if (!item->shadow) {
        item->shadow = (uint8_t *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, item->size);
        item->shadow_size = item->shadow ? item->size : 0;
    }
    if (!item->shadow)
        return;
    if (memcmp(item->shadow + item->lock_offset, source, size) == 0)
        return;
    memcpy(item->shadow + item->lock_offset, source, size);
    item->generation++;
    EnterCriticalSection(&g_frame_lock);
    {
        uint32_t stream;
        for (stream = 0; stream < MAX_TEXTURE_STAGES; ++stream)
            if (g_state.stream_valid[stream]
                    && g_state.streams[stream].id == item->id)
                g_state.streams[stream].generation = item->generation;
        if (g_state.index_valid && g_state.index_id == item->id)
            g_state.index_generation = item->generation;
    }
    LeaveCriticalSection(&g_frame_lock);
}

static void hash_blob_pair(const uint8_t *data, uint32_t size,
                           uint64_t *hash_a, uint64_t *hash_b)
{
    uint32_t left = 0xffffffffu;
    uint32_t right = 0x6d5a56dau;
    uint32_t remaining = size;
    /* WC3 is a 32-bit process. The former pair of 64-bit FNV multiplies for
     * every byte dominated a core when a dynamic 1 MiB texture changed. Two
     * independent table CRC lanes inspect every byte with native 32-bit work.
     * Exact in-process deduplication still confirms equal bytes with memcmp,
     * and the transport cache independently verifies SHA-256. */
    while (remaining--) {
        uint8_t value = *data++;
        left = (left >> 8) ^ g_crc32_table[(left ^ value) & 0xffu];
        right = (right >> 8) ^ g_crc32_table[
            (right ^ (uint8_t)(value + 0x9du)) & 0xffu];
    }
    left = ~left;
    right = ~right;
    *hash_a = ((uint64_t)right << 32u) | left;
    *hash_b = ((uint64_t)size << 32u)
        | (left ^ ((right << 13u) | (right >> 19u)));
}

static void set_blob_reference_locked(struct blob **slot, struct blob *value)
{
    if (!slot || *slot == value)
        return;
    if (*slot && (*slot)->base_refs) {
        --(*slot)->base_refs;
        (*slot)->pinned = (*slot)->base_refs != 0;
    }
    *slot = value;
    if (value) {
        ++value->base_refs;
        value->pinned = TRUE;
    }
}

static void set_slice_base_locked(struct slice_cache *slice,
                                  struct blob *base, uint32_t frame)
{
    if (!slice)
        return;
    set_blob_reference_locked(&slice->base, base);
    slice->base_frame = frame;
    slice->delta_spend = 0;
    slice->probe_after_rebase = TRUE;
    if (!slice->rebase_spacing)
        slice->rebase_spacing = GEOMETRY_REBASE_MIN_SPACING;
}

static void stage_slice_chain_base_locked(struct slice_cache *slice,
                                          struct blob *base)
{
    if (slice)
        set_blob_reference_locked(&slice->pending_chain_base, base);
}

static void finish_geometry_anchor_locked(BOOL committed)
{
    uint32_t bucket;
    for (bucket = 0; bucket < BLOB_BUCKETS; ++bucket) {
        struct slice_cache *slice;
        for (slice = g_slices[bucket]; slice; slice = slice->next) {
            if (committed)
                set_blob_reference_locked(&slice->chain_base,
                                          slice->pending_chain_base);
            set_blob_reference_locked(&slice->pending_chain_base, NULL);
        }
    }
}

static BOOL blob_id_retired_locked(uint32_t id)
{
    const struct retired_blob_id *item;
    for (item = g_retired_blob_ids[id & (BLOB_BUCKETS - 1u)]; item;
         item = item->next)
        if (item->id == id)
            return TRUE;
    return FALSE;
}

static void retire_blob_id_locked(const struct blob *blob)
{
    struct retired_blob_id *item;
    if (!blob->reliable_emitted || blob_id_retired_locked(blob->id))
        return;
    item = (struct retired_blob_id *)HeapAlloc(GetProcessHeap(), 0,
                                               sizeof(*item));
    if (!item)
        return;
    item->id = blob->id;
    item->next = g_retired_blob_ids[blob->id & (BLOB_BUCKETS - 1u)];
    g_retired_blob_ids[blob->id & (BLOB_BUCKETS - 1u)] = item;
}

static void clear_blob_cache_locked(void)
{
    uint32_t bucket;
    uint32_t retained_bytes = 0;

    /* Keep one stable base for each draw position. Frames encode geometry
     * against these bases, so source-frame drops do not create a delta chain.
     * Each recovery frame advertises the retained bases that it uses. */
    for (bucket = 0; bucket < BLOB_BUCKETS; ++bucket) {
        struct slice_cache *slice;
        for (slice = g_slices[bucket]; slice; slice = slice->next) {
            set_blob_reference_locked(&slice->chain_base, NULL);
            set_blob_reference_locked(&slice->pending_chain_base, NULL);
            slice->blob = slice->base;
            slice->last_frame = 0;
            if (slice->base) {
                slice->base->pinned = TRUE;
                slice->base->frame_emitted = 0;
                slice->base->geometry_epoch = 0;
                /* Reliable delivery belongs to one viewer epoch. The relay
                 * drops its retained resource bundles on reconnect, so a
                 * base sent to the previous viewer is not available to the
                 * new browser unless this epoch emits it again. */
                slice->base->reliable_emitted = FALSE;
            }
        }
    }
    for (bucket = 0; bucket < BLOB_BUCKETS; ++bucket) {
        struct blob **link = &g_blobs[bucket];
        while (*link) {
            struct blob *item = *link;
            if (item->pinned) {
                retained_bytes += item->size;
                link = &item->next;
            } else {
                struct blob **id_link =
                    &g_blobs_by_id[item->id & (BLOB_BUCKETS - 1u)];
                while (*id_link && *id_link != item)
                    id_link = &(*id_link)->id_next;
                if (*id_link == item)
                    *id_link = item->id_next;
                *link = item->next;
                retire_blob_id_locked(item);
                HeapFree(GetProcessHeap(), 0, item);
            }
        }
    }
    g_blob_cache_bytes = retained_bytes;
}

static struct blob *define_blob_locked(const uint8_t *data, uint32_t size)
{
    struct blob *item;
    uint32_t bucket;
    uint64_t hash_a;
    uint64_t hash_b;
    if (!data || !size || size > BLOB_CACHE_LIMIT)
        return NULL;
    hash_blob_pair(data, size, &hash_a, &hash_b);
    bucket = (uint32_t)(hash_a ^ hash_b) & (BLOB_BUCKETS - 1u);
    for (item = g_blobs[bucket]; item; item = item->next) {
        if (item->hash_a == hash_a && item->hash_b == hash_b
                && item->size == size && memcmp(item->data, data, size) == 0)
            return item;
    }
    item = (struct blob *)HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*item) + size);
    if (!item)
        return NULL;
    item->hash_a = hash_a;
    item->hash_b = hash_b;
    item->size = size;
    /* Use a content-derived identity. Resource records then remain identical
     * across launches even when D3D object creation order changes. Resolve the
     * extremely unlikely 32-bit collision against live blobs with a second
     * hash-derived step. The full 128-bit hash remains in every definition and
     * the browser verifies conflicting definitions. */
    {
        uint32_t id = (uint32_t)hash_a ^ (uint32_t)(hash_a >> 32u)
            ^ (uint32_t)hash_b ^ (uint32_t)(hash_b >> 32u);
        uint32_t step = ((uint32_t)(hash_b >> 17u)
                         ^ (uint32_t)hash_a) | 1u;
        BOOL collision;
        if (!id)
            id = 1u;
        do {
            uint32_t id_bucket = id & (BLOB_BUCKETS - 1u);
            struct blob *scan;
            collision = FALSE;
            for (scan = g_blobs_by_id[id_bucket]; scan;
                 scan = scan->id_next) {
                if (scan->id == id) {
                    collision = TRUE;
                    break;
                }
            }
            /* A retired id may still name the OLD content in a browser
             * (epoch pin, or a cached bundle replayed into a new session).
             * Minting different content under it is a protocol violation
             * the browser rejects; salt past it like a live collision. */
            if (!collision && blob_id_retired_locked(id))
                collision = TRUE;
            if (collision) {
                id += step;
                if (!id)
                    id += step;
            }
        } while (collision);
        item->id = id;
        item->id_next = g_blobs_by_id[id & (BLOB_BUCKETS - 1u)];
        g_blobs_by_id[id & (BLOB_BUCKETS - 1u)] = item;
    }
    item->reliable_emitted = FALSE;
    item->geometry_epoch = 0;
    item->pinned = FALSE;
    item->frame_emitted = 0;
    memcpy(item->data, data, size);
    item->next = g_blobs[bucket];
    g_blobs[bucket] = item;
    g_blob_cache_bytes += size;
    return item;
}

static BOOL emit_reliable_blob_locked(struct blob *item)
{
    struct w3cs_define_blob define;
    uint8_t *payload;
    uint32_t payload_size;
    if (!item)
        return FALSE;
    /* A recovery must be independently decodable after the browser discards
     * every base from the prior geometry epoch. Re-emit an exact blob even if
     * a reliable rebase sent the same content earlier in this viewer epoch. */
    if (item->reliable_emitted && !g_frame_keyframe)
        return TRUE;
    if (item->size > UINT32_MAX - (uint32_t)sizeof(define))
        return FALSE;
    payload_size = (uint32_t)sizeof(define) + item->size;
    payload = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, payload_size);
    if (!payload)
        return FALSE;
    define.id = item->id;
    define.size = item->size;
    define.hash_a = item->hash_a;
    define.hash_b = item->hash_b;
    memcpy(payload, &define, sizeof(define));
    memcpy(payload + sizeof(define), item->data, item->size);
    if (queue_record_message(W3CS_OP_DEFINE_BLOB, payload, payload_size)) {
        item->reliable_emitted = TRUE;
        HeapFree(GetProcessHeap(), 0, payload);
        return TRUE;
    }
    HeapFree(GetProcessHeap(), 0, payload);
    return FALSE;
}

static BOOL emit_reliable_geometry_blob_locked(struct blob *item)
{
    BOOL was_emitted;
    BOOL emitted;
    if (!item)
        return FALSE;
    if (item->geometry_epoch == g_geometry_epoch)
        return TRUE;
    /* reliable_emitted is a connection-level texture/resource hint. Geometry
     * has a shorter recovery epoch: an empty recovery frame deliberately
     * discards the browser's old geometry bases. Force the first later use to
     * establish its exact base on the reliable resource plane again. */
    was_emitted = item->reliable_emitted;
    item->reliable_emitted = FALSE;
    emitted = emit_reliable_blob_locked(item);
    if (!emitted)
        item->reliable_emitted = was_emitted;
    else
        item->geometry_epoch = g_geometry_epoch;
    return emitted;
}

static BOOL record_frame_blob_locked(const struct blob *item)
{
    struct w3cs_define_blob define;
    uint8_t *payload;
    uint32_t payload_size;
    BOOL recorded;
    if (!item || item->size > UINT32_MAX - (uint32_t)sizeof(define))
        return FALSE;
    payload_size = (uint32_t)sizeof(define) + item->size;
    payload = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, payload_size);
    if (!payload)
        return FALSE;
    define.id = item->id;
    define.size = item->size;
    define.hash_a = item->hash_a;
    define.hash_b = item->hash_b;
    memcpy(payload, &define, sizeof(define));
    memcpy(payload + sizeof(define), item->data, item->size);
    recorded = raw_record_locked(W3CS_OP_DEFINE_BLOB, 0,
                                 payload, payload_size);
    HeapFree(GetProcessHeap(), 0, payload);
    return recorded;
}

static BOOL record_frame_blob_once_locked(struct blob *item, uint32_t frame)
{
    if (!item)
        return FALSE;
    if (!g_frame_keyframe && item->geometry_epoch == g_geometry_epoch)
        return TRUE;
    if (item->frame_emitted == frame)
        return TRUE;
    if (!record_frame_blob_locked(item))
        return FALSE;
    item->frame_emitted = frame;
    if (g_frame_keyframe)
        item->geometry_epoch = g_geometry_epoch;
    return TRUE;
}

/* Remove a one-shot blob from the cache once its record is queued. Texture
 * mip payloads have no later proxy-side use (cross-session dedup happens at
 * the relay's bundle digests and the browser's cache); leaving tens of MiB
 * of them in the cache tripped the emergency clear_blob_cache_locked() mid
 * session, which freed live geometry bases and produced "missing blob base"
 * decode storms in the browser. */
static void discard_blob_locked(struct blob *item)
{
    struct blob **link;
    struct blob **id_link;
    if (!item || item->pinned || item->base_refs)
        return;
    link = &g_blobs[(uint32_t)(item->hash_a ^ item->hash_b)
                    & (BLOB_BUCKETS - 1u)];
    while (*link && *link != item)
        link = &(*link)->next;
    if (*link != item)
        return;
    *link = item->next;
    id_link = &g_blobs_by_id[item->id & (BLOB_BUCKETS - 1u)];
    while (*id_link && *id_link != item)
        id_link = &(*id_link)->id_next;
    if (*id_link == item)
        *id_link = item->id_next;
    g_blob_cache_bytes -= item->size;
    retire_blob_id_locked(item);
    HeapFree(GetProcessHeap(), 0, item);
}

static BOOL emit_texture_blob_update_locked(
    const struct w3cs_update_texture *update, const uint8_t *pixels)
{
    struct w3cs_update_texture_blob reference;
    struct blob *blob;
    BOOL queued;
    if (!update || !pixels || !update->size)
        return FALSE;
    blob = define_blob_locked(pixels, update->size);
    if (!blob || !emit_reliable_blob_locked(blob))
        return FALSE;
    reference.texture = *update;
    reference.blob_id = blob->id;
    queued = queue_record_message(W3CS_OP_UPDATE_TEXTURE_BLOB,
                                  &reference, sizeof(reference));
    if (queued)
        discard_blob_locked(blob);
    return queued;
}

static BOOL record_frame_blob_delta_locked(struct blob *item,
                                           const struct blob *base,
                                           uint32_t *cost)
{
    struct w3cs_define_blob_delta delta;
    uint8_t *payload;
    uint8_t *mask;
    uint8_t *values;
    uint32_t index;
    uint32_t words;
    uint32_t mask_size;
    uint32_t nonzero = 0;
    uint32_t payload_size;
    BOOL recorded;
    if (!item || !base || item->size != base->size)
        return FALSE;
    if (item == base)
        return TRUE;
    words = (item->size + 3u) / 4u;
    mask_size = (words + 1u) / 2u;
    for (index = 0; index < item->size; ++index)
        if ((item->data[index] ^ base->data[index]) != 0)
            ++nonzero;
    if (mask_size > UINT32_MAX - nonzero
            || sizeof(delta) > UINT32_MAX - mask_size - nonzero)
        return FALSE;
    payload_size = (uint32_t)sizeof(delta) + mask_size + nonzero;
    payload = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, payload_size);
    if (!payload)
        return FALSE;
    delta.blob.id = item->id;
    delta.blob.size = item->size;
    delta.blob.hash_a = item->hash_a;
    delta.blob.hash_b = item->hash_b;
    delta.base_id = base->id;
    memcpy(payload, &delta, sizeof(delta));
    mask = payload + sizeof(delta);
    values = mask + mask_size;
    memset(mask, 0, mask_size);
    for (index = 0; index < item->size; ++index) {
        uint8_t value = item->data[index] ^ base->data[index];
        if (value) {
            uint32_t word = index / 4u;
            uint32_t shift = (word & 1u) ? 4u : 0u;
            mask[word / 2u] |= (uint8_t)(1u
                << (shift + (index & 3u)));
            *values++ = value;
        }
    }
    recorded = raw_record_locked(W3CS_OP_DEFINE_BLOB_XOR_MASK, 0,
                                 payload, payload_size);
    if (recorded && cost)
        *cost = payload_size;
    HeapFree(GetProcessHeap(), 0, payload);
    return recorded;
}

static uint16_t float_to_half(float input)
{
    union { float f; uint32_t u; } value;
    uint32_t sign;
    int32_t exponent;
    uint32_t mantissa;
    value.f = input;
    sign = (value.u >> 16u) & 0x8000u;
    exponent = (int32_t)((value.u >> 23u) & 0xffu) - 127 + 15;
    mantissa = value.u & 0x7fffffu;
    if (exponent <= 0) {
        if (exponent < -10)
            return (uint16_t)sign;
        mantissa = (mantissa | 0x800000u) >> (uint32_t)(1 - exponent);
        if (mantissa & 0x1000u)
            mantissa += 0x2000u;
        return (uint16_t)(sign | (mantissa >> 13u));
    }
    if (exponent >= 31) {
        if (((value.u >> 23u) & 0xffu) == 0xffu && mantissa)
            return (uint16_t)(sign | 0x7c00u | (mantissa >> 13u) | 1u);
        return (uint16_t)(sign | 0x7c00u);
    }
    if (mantissa & 0x1000u) {
        mantissa += 0x2000u;
        if (mantissa & 0x800000u) {
            mantissa = 0;
            ++exponent;
            if (exponent >= 31)
                return (uint16_t)(sign | 0x7c00u);
        }
    }
    return (uint16_t)(sign | ((uint32_t)exponent << 10u)
                      | (mantissa >> 13u));
}

static float half_to_float(uint16_t input)
{
    union { uint32_t u; float f; } value;
    uint32_t sign = ((uint32_t)input & 0x8000u) << 16u;
    int32_t exponent = (int32_t)(((uint32_t)input >> 10u) & 0x1fu);
    uint32_t mantissa = (uint32_t)input & 0x3ffu;
    if (!exponent) {
        if (!mantissa) {
            value.u = sign;
            return value.f;
        }
        exponent = 1;
        while (!(mantissa & 0x400u)) {
            mantissa <<= 1u;
            --exponent;
        }
        mantissa &= 0x3ffu;
        exponent += 127u - 15u;
    } else if (exponent == 31) {
        value.u = sign | 0x7f800000u | (mantissa << 13u);
        return value.f;
    } else {
        exponent += 127u - 15u;
    }
    value.u = sign | ((uint32_t)exponent << 23u) | (mantissa << 13u);
    return value.f;
}

static BOOL record_transform_locked(uint32_t state,
                                    const D3DMATRIX *matrix)
{
    const float *values = (const float *)matrix;
    static const uint8_t half_indices[13] = {
        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15
    };
    if (state == (uint32_t)D3DTS_WORLD) {
        uint8_t compact[38];
        uint32_t i;
        BOOL safe = TRUE;
        for (i = 0; i < 13; ++i) {
            const float value = values[half_indices[i]];
            const uint16_t half = float_to_half(value);
            const float reconstructed = half_to_float(half);
            if (!isfinite(value) || !isfinite(reconstructed)
                    || reconstructed != value) {
                safe = FALSE;
                break;
            }
            memcpy(compact + i * sizeof(half), &half, sizeof(half));
        }
        if (safe && isfinite(values[12]) && isfinite(values[13])
                && isfinite(values[14])) {
            memcpy(compact + 26, values + 12, 3 * sizeof(float));
            return raw_record_locked(W3CS_OP_SET_WORLD_TRANSFORM_COMPACT, 0,
                                     compact, sizeof(compact));
        }
    }
    {
        uint8_t payload[4 + sizeof(*matrix)];
        memcpy(payload, &state, 4);
        memcpy(payload + 4, matrix, sizeof(*matrix));
        return raw_record_locked(W3CS_OP_SET_TRANSFORM, 0, payload,
                                 sizeof(payload));
    }
}

static uint32_t fvf_position_words(DWORD fvf)
{
    switch (fvf & D3DFVF_POSITION_MASK) {
    case D3DFVF_XYZ: return 3u;
    case D3DFVF_XYZRHW: return 4u;
#ifdef D3DFVF_XYZW
    case D3DFVF_XYZW: return 4u;
#endif
    case D3DFVF_XYZB1: return 4u;
    case D3DFVF_XYZB2: return 5u;
    case D3DFVF_XYZB3: return 6u;
    case D3DFVF_XYZB4: return 7u;
    case D3DFVF_XYZB5: return 8u;
    default: return 0u;
    }
}

static BOOL fvf_word_is_float(DWORD fvf, uint32_t stride,
                              uint32_t word_index)
{
    uint32_t byte_offset;
    uint32_t word_offset;
    uint32_t cursor = 0;
    uint32_t position_words;
    uint32_t tex_count;
    uint32_t stage;
    if (!stride || (stride & 3u))
        return FALSE;
    byte_offset = (word_index * 4u) % stride;
    word_offset = byte_offset / 4u;
    position_words = fvf_position_words(fvf);
    /* Keep every position bit exact. Half-delta reconstruction stayed inside
     * its numeric tolerance but changed the final projected edge by a fraction
     * of a pixel on alternating frames. At browser upscale that made static
     * buildings appear to vibrate. Normals and texture coordinates retain the
     * compact float16 delta path below. */
    if (word_offset < position_words)
        return FALSE;
    cursor += position_words;
    if (fvf & D3DFVF_NORMAL) {
        if (word_offset >= cursor && word_offset < cursor + 3u)
            return TRUE;
        cursor += 3u;
    }
    if (fvf & D3DFVF_PSIZE) {
        if (word_offset == cursor)
            return TRUE;
        ++cursor;
    }
    if (fvf & D3DFVF_DIFFUSE)
        ++cursor;
    if (fvf & D3DFVF_SPECULAR)
        ++cursor;
    tex_count = (fvf & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;
    for (stage = 0; stage < tex_count && stage < 8u; ++stage) {
        uint32_t code = (fvf >> (16u + stage * 2u)) & 3u;
        uint32_t dimensions = code == 0u ? 2u :
            code == 1u ? 3u : code == 2u ? 4u : 1u;
        if (word_offset >= cursor && word_offset < cursor + dimensions)
            return TRUE;
        cursor += dimensions;
    }
    return FALSE;
}

static BOOL record_frame_vertex_delta_locked(struct blob *item,
                                              const struct blob *base,
                                              uint32_t stride, DWORD fvf,
                                              struct blob **wire_blob,
                                              uint32_t *cost)
{
    struct w3cs_define_blob_float16_delta header;
    uint8_t *float_mask = NULL;
    uint8_t *raw_mask = NULL;
    uint8_t *half_values = NULL;
    uint8_t *raw_values = NULL;
    uint8_t *reconstructed_bytes = NULL;
    uint8_t *payload = NULL;
    uint32_t words;
    uint32_t float_mask_size;
    uint32_t raw_mask_size;
    uint32_t half_size = 0;
    uint32_t raw_size = 0;
    uint32_t nonzero = 0;
    uint32_t word;
    uint32_t payload_size;
    BOOL recorded = FALSE;
    struct blob *wire = NULL;
    if (wire_blob)
        *wire_blob = item;
    if (!item || !base || item->size != base->size)
        return FALSE;
    if (item == base)
        return TRUE;
    words = (item->size + 3u) / 4u;
    float_mask_size = (words + 7u) / 8u;
    raw_mask_size = (words + 1u) / 2u;
    for (word = 0; word < item->size; ++word)
        if ((item->data[word] ^ base->data[word]) != 0)
            ++nonzero;
    float_mask = (uint8_t *)HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, float_mask_size);
    raw_mask = (uint8_t *)HeapAlloc(
        GetProcessHeap(), HEAP_ZERO_MEMORY, raw_mask_size);
    half_values = (uint8_t *)HeapAlloc(
        GetProcessHeap(), 0, words * 2u);
    raw_values = (uint8_t *)HeapAlloc(
        GetProcessHeap(), 0, item->size);
    reconstructed_bytes = (uint8_t *)HeapAlloc(
        GetProcessHeap(), 0, item->size);
    if (!float_mask || !raw_mask || !half_values || !raw_values
            || !reconstructed_bytes)
        goto done;
    memcpy(reconstructed_bytes, base->data, item->size);
    for (word = 0; word < words; ++word) {
        uint32_t byte_offset = word * 4u;
        BOOL encoded_float = FALSE;
        if (byte_offset + 4u <= item->size
                && fvf_word_is_float(fvf, stride, word)) {
            float current;
            float prior;
            float delta;
            float reconstructed;
            float tolerance;
            uint16_t half;
            memcpy(&current, item->data + byte_offset, sizeof(current));
            memcpy(&prior, base->data + byte_offset, sizeof(prior));
            delta = current - prior;
            half = float_to_half(delta);
            reconstructed = prior + half_to_float(half);
            /* Half precision has at most about 0.05% relative error. Base the
             * guard on the transmitted motion, not the absolute world point,
             * so large map coordinates do not hide a bad reconstruction. */
            tolerance = 0.0002f + fabsf(delta) * 0.0006f;
            if (isfinite(current) && isfinite(prior)
                    && isfinite(reconstructed)
                    && fabsf(reconstructed - current) <= tolerance) {
                if (memcmp(&current, &prior, sizeof(current)) != 0) {
                    float_mask[word / 8u] |= (uint8_t)(1u << (word & 7u));
                    memcpy(half_values + half_size, &half, sizeof(half));
                    half_size += 2u;
                    memcpy(reconstructed_bytes + byte_offset,
                           &reconstructed, sizeof(reconstructed));
                }
                encoded_float = TRUE;
            }
        }
        if (!encoded_float) {
            uint32_t byte;
            for (byte = 0; byte < 4u && byte_offset + byte < item->size;
                 ++byte) {
                uint8_t value = item->data[byte_offset + byte]
                    ^ base->data[byte_offset + byte];
                if (!value)
                    continue;
                raw_mask[word / 2u] |= (uint8_t)(1u <<
                    (((word & 1u) ? 4u : 0u) + byte));
                raw_values[raw_size++] = value;
                reconstructed_bytes[byte_offset + byte] =
                    item->data[byte_offset + byte];
            }
        }
    }
    if ((uint64_t)sizeof(header) + float_mask_size + raw_mask_size
            + half_size + raw_size > UINT32_MAX)
        goto done;
    payload_size = (uint32_t)sizeof(header) + float_mask_size
        + raw_mask_size + half_size + raw_size;
    if (payload_size >= (uint32_t)sizeof(struct w3cs_define_blob_delta)
            + raw_mask_size + nonzero) {
        recorded = record_frame_blob_delta_locked(item, base, cost);
        goto done;
    }
    wire = define_blob_locked(reconstructed_bytes, item->size);
    if (!wire)
        goto done;
    if (wire == base) {
        if (wire_blob)
            *wire_blob = wire;
        recorded = TRUE;
        goto done;
    }
    payload = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, payload_size);
    if (!payload)
        goto done;
    header.delta.blob.id = wire->id;
    header.delta.blob.size = wire->size;
    header.delta.blob.hash_a = wire->hash_a;
    header.delta.blob.hash_b = wire->hash_b;
    header.delta.base_id = base->id;
    header.stride = stride;
    header.fvf = fvf;
    memcpy(payload, &header, sizeof(header));
    memcpy(payload + sizeof(header), float_mask, float_mask_size);
    memcpy(payload + sizeof(header) + float_mask_size,
           raw_mask, raw_mask_size);
    memcpy(payload + sizeof(header) + float_mask_size + raw_mask_size,
           half_values, half_size);
    memcpy(payload + sizeof(header) + float_mask_size + raw_mask_size
           + half_size, raw_values, raw_size);
    recorded = raw_record_locked(W3CS_OP_DEFINE_BLOB_FLOAT16_DELTA, 0,
                                 payload, payload_size);
    if (recorded && cost)
        *cost = payload_size;
    if (recorded && wire_blob)
        *wire_blob = wire;
done:
    if (payload) HeapFree(GetProcessHeap(), 0, payload);
    if (raw_values) HeapFree(GetProcessHeap(), 0, raw_values);
    if (half_values) HeapFree(GetProcessHeap(), 0, half_values);
    if (raw_mask) HeapFree(GetProcessHeap(), 0, raw_mask);
    if (float_mask) HeapFree(GetProcessHeap(), 0, float_mask);
    if (reconstructed_bytes)
        HeapFree(GetProcessHeap(), 0, reconstructed_bytes);
    return recorded;
}

static BOOL record_frame_geometry_delta_once_locked(
    struct blob *item, const struct blob *base, uint32_t stride, DWORD fvf,
    BOOL vertex, uint32_t frame, uint32_t *wire_id,
    struct blob **wire_result, uint32_t *cost)
{
    BOOL recorded;
    struct blob *wire = item;
    if (cost)
        *cost = 0;
    /* A content-addressed blob can be referenced by several overlapping
     * draw slices in one Present. Once one exact definition is in this frame,
     * every later draw can use the same id. Re-emitting the same delta with a
     * different slice base wasted tens of KiB without adding state. */
    if (!item)
        return FALSE;
    if (item->frame_emitted == frame) {
        if (wire_id)
            *wire_id = item->frame_wire_id ? item->frame_wire_id : item->id;
        if (wire_result)
            *wire_result = item->frame_wire_blob
                ? item->frame_wire_blob : item;
        return TRUE;
    }
    if (g_exact_geometry)
        vertex = FALSE;
    recorded = vertex
        ? record_frame_vertex_delta_locked(item, base, stride, fvf, &wire,
                                           cost)
        : record_frame_blob_delta_locked(item, base, cost);
    if (recorded) {
        item->frame_emitted = frame;
        item->frame_wire_id = wire ? wire->id : item->id;
        item->frame_wire_blob = wire ? wire : item;
        if (wire_id)
            *wire_id = item->frame_wire_id;
        if (wire_result)
            *wire_result = wire;
    }
    return recorded;
}

static uint32_t primitive_vertex_count(D3DPRIMITIVETYPE type,
                                       uint32_t primitive_count)
{
    uint64_t count;
    switch (type) {
    case D3DPT_POINTLIST:
        count = primitive_count;
        break;
    case D3DPT_LINELIST:
        count = (uint64_t)primitive_count * 2u;
        break;
    case D3DPT_LINESTRIP:
        count = (uint64_t)primitive_count + 1u;
        break;
    case D3DPT_TRIANGLELIST:
        count = (uint64_t)primitive_count * 3u;
        break;
    case D3DPT_TRIANGLESTRIP:
    case D3DPT_TRIANGLEFAN:
        count = (uint64_t)primitive_count + 2u;
        break;
    default:
        return 0;
    }
    return count <= UINT32_MAX ? (uint32_t)count : 0;
}

/* D3D9 games commonly declare MinVertexIndex/NumVertices loosely; the real
 * GPU reads whatever the indices address. Derive the transmitted vertex
 * window from the actual index content so a loose declaration can neither
 * truncate geometry nor transmit unused vertices. */
static BOOL scan_index_maximum_locked(uint32_t resource_id, uint32_t first,
                                      uint32_t count, uint32_t width,
                                      uint32_t *max_index)
{
    struct resource *item = find_resource_id_locked(resource_id);
    uint64_t offset = (uint64_t)first * width;
    uint64_t size = (uint64_t)count * width;
    uint32_t maximum = 0;
    uint32_t position;
    if (!item || !item->shadow || !count
            || offset > item->shadow_size
            || size > item->shadow_size - offset)
        return FALSE;
    if (width == 2u) {
        const uint16_t *values = (const uint16_t *)(item->shadow + offset);
        for (position = 0; position < count; ++position)
            if (values[position] > maximum)
                maximum = values[position];
    } else {
        const uint32_t *values = (const uint32_t *)(item->shadow + offset);
        for (position = 0; position < count; ++position)
            if (values[position] > maximum)
                maximum = values[position];
    }
    *max_index = maximum;
    return TRUE;
}

static uint32_t slice_compatibility_bucket(uint32_t resource_id,
                                           uint32_t size,
                                           uint32_t texture_id,
                                           uint32_t stride,
                                           uint32_t fvf)
{
    uint32_t hash = resource_id * 16777619u;
    hash = (hash ^ size) * 16777619u;
    hash = (hash ^ texture_id) * 16777619u;
    hash = (hash ^ stride) * 16777619u;
    hash ^= fvf;
    return hash & (BLOB_BUCKETS - 1u);
}

static BOOL slice_rebase_due_locked(const struct slice_cache *slice,
                                    uint32_t frame)
{
    uint32_t spacing = slice->rebase_spacing
        ? slice->rebase_spacing : GEOMETRY_REBASE_MIN_SPACING;
    if (!slice->base_frame || frame - slice->base_frame < spacing)
        return FALSE;
    return slice->delta_spend > slice->size / 2u;
}

static BOOL rebase_budget_allows_locked(uint32_t size)
{
    /* Always admit the first rebase of a frame so one oversized slice can
     * never starve behind the byte budget forever. */
    if (g_frame_rebase_bytes == 0)
        return TRUE;
    return (uint64_t)g_frame_rebase_bytes + size
        <= GEOMETRY_REBASE_FRAME_BUDGET;
}

static void note_slice_delta_cost_locked(struct slice_cache *slice,
                                         const struct blob *reference,
                                         uint32_t cost)
{
    /* Only deltas measured against the slice's stable base indicate base
     * staleness. Dependent-frame deltas against the GOP anchor track
     * inter-frame motion and say nothing about the base. */
    if (!slice || reference != slice->base || !cost)
        return;
    slice->delta_spend = slice->delta_spend > UINT32_MAX - cost
        ? UINT32_MAX : slice->delta_spend + cost;
    if (slice->probe_after_rebase) {
        /* The first base delta after a base refresh measures whether fresh
         * bases help this slice at all. Motion-dominated geometry stays
         * expensive right after a rebase; back its spacing off instead of
         * resending bases that cannot pay for themselves. */
        slice->probe_after_rebase = FALSE;
        if (cost > slice->size / 4u)
            slice->rebase_spacing =
                slice->rebase_spacing >= GEOMETRY_REBASE_INTERVAL / 2u
                    ? GEOMETRY_REBASE_INTERVAL
                    : slice->rebase_spacing * 2u;
        else
            slice->rebase_spacing = GEOMETRY_REBASE_MIN_SPACING;
    }
}

static uint32_t define_bound_buffer_blob_locked(uint32_t resource_id,
                                                int64_t offset,
                                                uint64_t size,
                                                uint32_t logical_key)
{
    struct resource *item = find_resource_id_locked(resource_id);
    struct slice_cache *slice;
    struct slice_cache *fallback = NULL;
    struct blob *blob;
    struct blob *reference;
    struct blob *wire_blob = NULL;
    uint32_t wire_blob_id = 0;
    uint32_t slice_bucket;
    uint32_t texture_id = g_state.texture_valid[0]
        ? g_state.textures[0].id : 0u;
    uint32_t stride;
    uint32_t fvf;
    uint32_t compatibility_bucket;
    uint32_t frame = (uint32_t)InterlockedCompareExchange(
        &g_frame_number, 0, 0) + 1u;
    uint32_t delta_cost = 0;
    if (!item || !item->shadow || offset < 0 || size == 0
            || size > UINT32_MAX || (uint64_t)offset > item->shadow_size
            || size > (uint64_t)item->shadow_size - (uint64_t)offset)
        return 0;
    /* The browser needs the buffer descriptor (for example the index width)
     * before the first draw that references it. */
    if (!item->create_emitted && !emit_create_resource(item)) {
        g_frame_dependency_failed = TRUE;
        return 0;
    }
    if (g_blob_cache_bytes > BLOB_CACHE_LIMIT - (uint32_t)size)
        clear_blob_cache_locked();
    stride = item->kind == RES_VERTEX_BUFFER
        ? g_state.streams[0].stride : 0u;
    fvf = item->kind == RES_VERTEX_BUFFER ? g_state.fvf : 0u;
    compatibility_bucket = slice_compatibility_bucket(
        resource_id, (uint32_t)size, texture_id, stride, fvf);
    /*
     * Dynamic WC3 geometry uses ring buffers, so byte offsets rotate even
     * when a draw represents the same object on the next frame.  Keep the
     * delta base by draw position instead. The resource id prevents a stale
     * base from crossing a buffer replacement, while the size check makes a
     * changed draw layout fall back safely.
     */
    slice_bucket = (resource_id * 16777619u ^ logical_key
                    ^ (uint32_t)size) & (BLOB_BUCKETS - 1u);
    for (slice = g_slices[slice_bucket]; slice; slice = slice->next)
        if (slice->resource_id == resource_id
                && slice->logical_key == logical_key
                && slice->size == (uint32_t)size
                && slice->texture_id == texture_id
                && (item->kind != RES_VERTEX_BUFFER
                    || (slice->stride == g_state.streams[0].stride
                        && slice->fvf == g_state.fvf)))
            break;
    if (!g_frame_keyframe && !g_frame_geometry_anchor && slice
            && slice->chain_base
            && memcmp(slice->chain_base->data,
                      item->shadow + (uint32_t)offset,
                      (uint32_t)size) == 0) {
        slice->last_frame = frame;
        slice->last_offset = (uint32_t)offset;
        return slice->chain_base->id;
    }
    if (!slice) {
        /* Draw ordinals shift whenever a unit or UI element appears. Reuse a
         * compatible stable base instead of sending another full geometry
         * blob for the new ordinal. Prefer the same physical ring-buffer
         * range; any same-layout base is still exact after its delta applies. */
        struct slice_cache *candidate;
        for (candidate = g_compatible_slices[compatibility_bucket]; candidate;
             candidate = candidate->compatible_next) {
            if (candidate->resource_id != resource_id
                    || candidate->size != (uint32_t)size
                    || candidate->texture_id != texture_id
                    || candidate->stride != stride
                    || candidate->fvf != fvf
                    || !candidate->base)
                continue;
            if (!fallback)
                fallback = candidate;
            if (candidate->last_offset == (uint32_t)offset) {
                fallback = candidate;
                break;
            }
        }
    }
    if (slice && slice->blob
            && memcmp(slice->blob->data,
                      item->shadow + (uint32_t)offset,
                      (uint32_t)size) == 0) {
        if (g_frame_keyframe) {
            if (!record_frame_blob_once_locked(slice->blob, frame))
                g_frame_dependency_failed = TRUE;
            set_slice_base_locked(slice, slice->blob, frame);
            slice->blob->geometry_epoch = g_geometry_epoch;
            slice->geometry_epoch = g_geometry_epoch;
        } else if (slice->geometry_epoch != g_geometry_epoch) {
            if (!emit_reliable_geometry_blob_locked(slice->blob))
                g_frame_dependency_failed = TRUE;
            set_slice_base_locked(slice, slice->blob, frame);
            slice->geometry_epoch = g_geometry_epoch;
        } else if (!slice->blob->reliable_emitted
                && slice->blob != slice->base) {
            if (slice_rebase_due_locked(slice, frame)
                    && rebase_budget_allows_locked(slice->size)
                    && emit_reliable_geometry_blob_locked(slice->blob)) {
                /* This static pose has spent more delta bytes against its
                 * stale base than the pose itself costs on the reliable
                 * plane. Promote it and stop repeating the delta. */
                g_frame_rebase_bytes += slice->size;
                set_slice_base_locked(slice, slice->blob, frame);
                slice->geometry_epoch = g_geometry_epoch;
                slice->last_frame = frame;
                slice->last_offset = (uint32_t)offset;
                return slice->blob->id;
            }
            /* The identical geometry may have first appeared in a disposable
             * normal frame. Repeat its delta against the reliable recovery
             * base. Reusing only its blob id would make this frame depend on
             * delivery of that prior normal frame. */
            reference = g_frame_geometry_anchor ? slice->base
                : (slice->chain_base ? slice->chain_base : slice->base);
            if (!reference) {
                if (!record_frame_blob_once_locked(slice->blob, frame))
                    g_frame_dependency_failed = TRUE;
                wire_blob = slice->blob;
                wire_blob_id = slice->blob->id;
            } else if (!record_frame_geometry_delta_once_locked(
                           slice->blob, reference,
                           g_state.streams[0].stride, g_state.fvf,
                           item->kind == RES_VERTEX_BUFFER
                               && g_state.fvf_valid
                               && g_state.stream_valid[0]
                               && g_state.streams[0].stride
                               /* The float16 word classifier assumes the
                                * slice starts on a vertex boundary. An
                                * unaligned slice must stay on the exact
                                * byte-XOR path or half deltas could land on
                                * position words. */
                               && (uint32_t)offset
                                   % g_state.streams[0].stride == 0,
                           frame, &wire_blob_id, &wire_blob, &delta_cost)) {
                g_frame_dependency_failed = TRUE;
            } else {
                note_slice_delta_cost_locked(slice, reference, delta_cost);
            }
            if (g_frame_geometry_anchor && wire_blob)
                stage_slice_chain_base_locked(slice, wire_blob);
        }
        slice->last_frame = frame;
        slice->last_offset = (uint32_t)offset;
        return wire_blob_id ? wire_blob_id : slice->blob->id;
    }
    blob = define_blob_locked(item->shadow + (uint32_t)offset,
                              (uint32_t)size);
    if (!blob)
        return 0;
    /* A static model or UI mesh can move to another draw ordinal when a unit
     * appears or disappears. Reference its exact content identity directly,
     * but define it once in this recovery frame. */
    if (blob->reliable_emitted) {
        if (g_frame_keyframe) {
            if (!record_frame_blob_once_locked(blob, frame)) {
                g_frame_dependency_failed = TRUE;
                return 0;
            }
        } else if (!emit_reliable_geometry_blob_locked(blob)) {
            g_frame_dependency_failed = TRUE;
            return 0;
        }
        if (slice) {
            set_slice_base_locked(slice, blob, frame);
            slice->geometry_epoch = g_geometry_epoch;
            blob->geometry_epoch = g_geometry_epoch;
            slice->blob = blob;
            slice->last_frame = frame;
            slice->last_offset = (uint32_t)offset;
        }
        return blob->id;
    }
    if (!slice) {
        slice = (struct slice_cache *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*slice));
        if (!slice) {
            if (!record_frame_blob_once_locked(blob, frame)) {
                g_frame_dependency_failed = TRUE;
                return 0;
            }
            return blob->id;
        }
        slice->resource_id = resource_id;
        slice->logical_key = logical_key;
        slice->size = (uint32_t)size;
        slice->last_offset = (uint32_t)offset;
        slice->stride = stride;
        slice->fvf = fvf;
        slice->texture_id = texture_id;
        set_slice_base_locked(slice, fallback ? fallback->base : blob,
                              fallback ? fallback->base_frame : frame);
        slice->geometry_epoch = fallback ? fallback->geometry_epoch : 0u;
        slice->next = g_slices[slice_bucket];
        g_slices[slice_bucket] = slice;
        slice->compatible_next = g_compatible_slices[compatibility_bucket];
        g_compatible_slices[compatibility_bucket] = slice;
    }
    if (!slice->base) {
        set_slice_base_locked(slice, blob, frame);
    }
    if (g_frame_keyframe) {
        /* A recovery contains the exact current geometry. It resets the
         * stable base used independently by later disposable frames. */
        if (!record_frame_blob_once_locked(blob, frame)) {
            g_frame_dependency_failed = TRUE;
            return 0;
        }
        set_slice_base_locked(slice, blob, frame);
        slice->geometry_epoch = g_geometry_epoch;
        slice->blob = blob;
        slice->last_frame = frame;
        slice->last_offset = (uint32_t)offset;
        return blob->id;
    }
    if (slice->geometry_epoch != g_geometry_epoch) {
        /* This draw was absent from the last recovery. Promote its first exact
         * geometry to the reliable resource plane. The relay records that
         * resource sequence in this frame dependency, so a disposable frame
         * may be dropped without creating a hidden normal-frame chain. */
        if (!emit_reliable_geometry_blob_locked(blob))
            return 0;
        set_slice_base_locked(slice, blob, frame);
        slice->geometry_epoch = g_geometry_epoch;
        blob->geometry_epoch = g_geometry_epoch;
        slice->blob = blob;
        slice->last_frame = frame;
        slice->last_offset = (uint32_t)offset;
        return blob->id;
    }
    /* A recovery base can become expensive as animated vertices move farther
     * from it. Refresh dynamic slices independently on the reliable resource
     * plane. Stagger the refreshes by draw key so hundreds of objects do not
     * create one burst. Each later disposable frame names the new exact base
     * and declares the reliable resource sequence that it needs. This keeps
     * UDP frames small without creating a normal-frame delta chain or pausing
     * the whole scene for a recovery round trip. */
    if (slice->base_frame
            && ((frame - slice->base_frame >= GEOMETRY_REBASE_INTERVAL
                 && frame % GEOMETRY_REBASE_INTERVAL
                     == logical_key % GEOMETRY_REBASE_INTERVAL)
                || slice_rebase_due_locked(slice, frame))
            && rebase_budget_allows_locked((uint32_t)size)) {
        /* The epoch-aware emitter, not emit_reliable_blob_locked: a
         * content-addressed blob can return with reliable_emitted still set
         * from a PREVIOUS geometry epoch (animation cycles repeat poses),
         * but the browser discarded every epoch-pinned base at the last
         * recovery. Skipping the re-send made later deltas reference a base
         * the browser no longer holds ("missing blob base" decode errors). */
        if (!emit_reliable_geometry_blob_locked(blob))
            return 0;
        g_frame_rebase_bytes += (uint32_t)size;
        set_slice_base_locked(slice, blob, frame);
        slice->geometry_epoch = g_geometry_epoch;
        slice->blob = blob;
        slice->last_frame = frame;
        slice->last_offset = (uint32_t)offset;
        return blob->id;
    }
    /* Every disposable frame is independent of other disposable frames.
     * Delta only against the exact base sent in the reliable recovery epoch. */
    reference = g_frame_geometry_anchor ? slice->base
        : (slice->chain_base ? slice->chain_base : slice->base);
    if (!reference) {
        if (!record_frame_blob_once_locked(blob, frame))
            return 0;
        reference = blob;
    }
    /* The reliable epoch base can be older than the adjacent pose, but it
     * lets the relay discard any normal frame without corrupting later
     * frames. */
    if (!record_frame_geometry_delta_once_locked(
            blob, reference, g_state.streams[0].stride, g_state.fvf,
            item->kind == RES_VERTEX_BUFFER && g_state.fvf_valid
                && g_state.stream_valid[0] && g_state.streams[0].stride
                && (uint32_t)offset % g_state.streams[0].stride == 0,
            frame, &wire_blob_id, &wire_blob, &delta_cost)) {
        return 0;
    }
    note_slice_delta_cost_locked(slice, reference, delta_cost);
    if (g_frame_geometry_anchor && wire_blob)
        stage_slice_chain_base_locked(slice, wire_blob);
    slice->blob = blob;
    slice->last_frame = frame;
    slice->last_offset = (uint32_t)offset;
    return wire_blob_id ? wire_blob_id : blob->id;
}

static BOOL texture_layout(D3DFORMAT format, uint32_t width, uint32_t height,
                           uint32_t *row_bytes, uint32_t *rows)
{
    uint32_t bpp = 0;
    switch ((uint32_t)format) {
    case D3DFMT_A8R8G8B8:
    case D3DFMT_X8R8G8B8:
        bpp = 4;
        break;
    case D3DFMT_R5G6B5:
    case D3DFMT_X1R5G5B5:
    case D3DFMT_A1R5G5B5:
    case D3DFMT_A4R4G4B4:
        bpp = 2;
        break;
    case D3DFMT_DXT1:
        *row_bytes = ((width + 3) / 4) * 8;
        *rows = (height + 3) / 4;
        return TRUE;
    case D3DFMT_DXT2:
    case D3DFMT_DXT3:
    case D3DFMT_DXT4:
    case D3DFMT_DXT5:
        *row_bytes = ((width + 3) / 4) * 16;
        *rows = (height + 3) / 4;
        return TRUE;
    default:
        return FALSE;
    }
    *row_bytes = width * bpp;
    *rows = height;
    return TRUE;
}

static void record_texture_update(struct resource *item)
{
    uint32_t level, width, height, full_width, full_height;
    uint32_t row_bytes, rows, size, row;
    uint32_t full_row_bytes, full_rows, dest_x, dest_y;
    uint8_t **shadow;
    uint32_t *shadow_size;
    uint8_t *dense;
    BOOL changed = FALSE;
    if (!item || !item->lock_ptr || (item->lock_flags & D3DLOCK_READONLY))
        return;
    level = item->level;
    if (level >= item->levels || level >= MAX_TEXTURE_LEVELS)
        return;
    full_width = item->width >> level;
    full_height = item->height >> level;
    if (!full_width)
        full_width = 1;
    if (!full_height)
        full_height = 1;
    width = full_width;
    height = full_height;
    if (item->has_lock_rect) {
        width = (uint32_t)(item->lock_rect.right - item->lock_rect.left);
        height = (uint32_t)(item->lock_rect.bottom - item->lock_rect.top);
    }
    if (!texture_layout((D3DFORMAT)item->format, width, height,
                        &row_bytes, &rows) || item->lock_pitch <= 0)
        return;
    if (!texture_layout((D3DFORMAT)item->format, full_width, full_height,
                        &full_row_bytes, &full_rows))
        return;
    size = row_bytes * rows;
    dense = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, size);
    if (!dense)
        return;
    for (row = 0; row < rows; ++row)
        memcpy(dense + row * row_bytes,
               (uint8_t *)item->lock_ptr + row * item->lock_pitch, row_bytes);
    shadow = &item->mip_shadow[level];
    shadow_size = &item->mip_shadow_size[level];
    if (!*shadow || *shadow_size != full_row_bytes * full_rows) {
        if (*shadow)
            HeapFree(GetProcessHeap(), 0, *shadow);
        *shadow = (uint8_t *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, full_row_bytes * full_rows);
        *shadow_size = *shadow ? full_row_bytes * full_rows : 0;
        changed = TRUE;
    }
    if (!*shadow) {
        HeapFree(GetProcessHeap(), 0, dense);
        return;
    }
    if ((uint32_t)item->format == D3DFMT_DXT1
            || (uint32_t)item->format == D3DFMT_DXT2
            || (uint32_t)item->format == D3DFMT_DXT3
            || (uint32_t)item->format == D3DFMT_DXT4
            || (uint32_t)item->format == D3DFMT_DXT5) {
        uint32_t block_size = (uint32_t)item->format == D3DFMT_DXT1 ? 8u : 16u;
        dest_x = item->has_lock_rect
            ? ((uint32_t)item->lock_rect.left / 4u) * block_size : 0;
        dest_y = item->has_lock_rect
            ? (uint32_t)item->lock_rect.top / 4u : 0;
    } else {
        uint32_t bytes_per_pixel = full_row_bytes / full_width;
        dest_x = item->has_lock_rect
            ? (uint32_t)item->lock_rect.left * bytes_per_pixel : 0;
        dest_y = item->has_lock_rect
            ? (uint32_t)item->lock_rect.top : 0;
    }
    if (dest_y > full_rows || rows > full_rows - dest_y
            || dest_x > full_row_bytes || row_bytes > full_row_bytes - dest_x) {
        HeapFree(GetProcessHeap(), 0, dense);
        return;
    }
    for (row = 0; row < rows; ++row) {
        uint8_t *destination = *shadow
            + (dest_y + row) * full_row_bytes + dest_x;
        uint8_t *source = dense + row * row_bytes;
        if (memcmp(destination, source, row_bytes) != 0) {
            memcpy(destination, source, row_bytes);
            changed = TRUE;
        }
    }
    if (!changed) {
        HeapFree(GetProcessHeap(), 0, dense);
        return;
    }
    item->generation++;
    {
        uint32_t left = item->has_lock_rect
            ? (uint32_t)item->lock_rect.left : 0u;
        uint32_t top = item->has_lock_rect
            ? (uint32_t)item->lock_rect.top : 0u;
        uint32_t right = item->has_lock_rect
            ? (uint32_t)item->lock_rect.right : full_width;
        uint32_t bottom = item->has_lock_rect
            ? (uint32_t)item->lock_rect.bottom : full_height;
        if (!item->mip_dirty[level]) {
            item->mip_dirty_left[level] = left;
            item->mip_dirty_top[level] = top;
            item->mip_dirty_right[level] = right;
            item->mip_dirty_bottom[level] = bottom;
        } else {
            if (left < item->mip_dirty_left[level])
                item->mip_dirty_left[level] = left;
            if (top < item->mip_dirty_top[level])
                item->mip_dirty_top[level] = top;
            if (right > item->mip_dirty_right[level])
                item->mip_dirty_right[level] = right;
            if (bottom > item->mip_dirty_bottom[level])
                item->mip_dirty_bottom[level] = bottom;
        }
    }
    item->mip_dirty[level] = TRUE;
    item->mip_dynamic[level] = TRUE;
    if (!InterlockedCompareExchange(&g_connected, 0, 0)) {
        HeapFree(GetProcessHeap(), 0, dense);
        return;
    }
    EnterCriticalSection(&g_frame_lock);
    {
        uint32_t stage;
        for (stage = 0; stage < MAX_TEXTURE_STAGES; ++stage)
            if (g_state.texture_valid[stage]
                    && g_state.textures[stage].id == item->id)
                g_state.textures[stage].generation = item->generation;
    }
    LeaveCriticalSection(&g_frame_lock);
    HeapFree(GetProcessHeap(), 0, dense);
}

/* Put the newest content of each dirty texture mip on the reliable resource
 * channel before the first frame that can reference it. The frame records the
 * resulting generation and the relay declares that reliable sequence as its
 * dependency. SCTP then delivers the texture once, in order, while disposable
 * frame traffic can continue to use latest-wins delivery. Repeating every
 * texture that had ever changed in every recovery frame made a normal WC3
 * frame hundreds of KiB larger and prevented smooth playback over real WANs. */
static void record_dirty_textures_locked(void)
{
    uint32_t stage;
    /* Only sampled textures can affect a draw. WC3 also dirties large render
     * targets and staging textures that are never bound for sampling. Hashing
     * and copying those hidden surfaces dominated a core and could overflow a
     * recovery frame. Check the current bindings for every draw. A global
     * once-per-frame shortcut is incorrect because WC3 binds hundreds of
     * already-dirty textures between the first and last draw of one frame;
     * it left every texture except the first binding permanently white. */
    for (stage = 0; stage < MAX_TEXTURE_STAGES; ++stage) {
        struct resource *item;
        uint32_t prior;
        uint32_t level;
        if (!g_state.texture_valid[stage]
                || !g_state.textures[stage].id)
            continue;
        for (prior = 0; prior < stage; ++prior)
            if (g_state.texture_valid[prior]
                    && g_state.textures[prior].id
                        == g_state.textures[stage].id)
                break;
        if (prior < stage)
            continue;
        item = find_resource_id_locked(g_state.textures[stage].id);
        if (!item || item->kind != RES_TEXTURE)
            continue;
        /* Never send an update for an id whose create the browser may have
         * missed during a full-queue window. */
        if (!item->create_emitted && !emit_create_resource(item)) {
            g_frame_dependency_failed = TRUE;
            continue;
        }
        for (level = 0; level < item->levels
                && level < MAX_TEXTURE_LEVELS; ++level) {
            struct w3cs_update_texture update;
            uint8_t *payload = NULL;
            uint8_t *pixels;
            uint32_t full_width, full_height, full_pitch, full_rows;
            uint32_t left, top, right, bottom;
            uint32_t width, height, pitch, rows, row;
            uint32_t source_x, source_y;
            if (!item->mip_dirty[level]
                    || !item->mip_shadow[level]
                    || !item->mip_shadow_size[level])
                continue;
            full_width = item->width >> level;
            full_height = item->height >> level;
            if (!full_width) full_width = 1;
            if (!full_height) full_height = 1;
            left = item->mip_dirty_left[level];
            top = item->mip_dirty_top[level];
            right = item->mip_dirty_right[level];
            bottom = item->mip_dirty_bottom[level];
            if (right > full_width) right = full_width;
            if (bottom > full_height) bottom = full_height;
            if ((uint32_t)item->format == D3DFMT_DXT1
                    || (uint32_t)item->format == D3DFMT_DXT2
                    || (uint32_t)item->format == D3DFMT_DXT3
                    || (uint32_t)item->format == D3DFMT_DXT4
                    || (uint32_t)item->format == D3DFMT_DXT5) {
                left &= ~3u;
                top &= ~3u;
                right = (right + 3u) & ~3u;
                bottom = (bottom + 3u) & ~3u;
                if (right > full_width) right = full_width;
                if (bottom > full_height) bottom = full_height;
            }
            if (left >= right || top >= bottom) {
                g_frame_dependency_failed = TRUE;
                continue;
            }
            width = right - left;
            height = bottom - top;
            if (!texture_layout((D3DFORMAT)item->format,
                                width, height, &pitch, &rows)) {
                g_frame_dependency_failed = TRUE;
                continue;
            }
            if (!texture_layout((D3DFORMAT)item->format,
                                full_width, full_height,
                                &full_pitch, &full_rows)
                    || (uint64_t)pitch * rows > UINT32_MAX
                    || (uint64_t)sizeof(update) + pitch * rows > UINT32_MAX) {
                g_frame_dependency_failed = TRUE;
                continue;
            }
            /* The first delivery of a complete mip is immutable asset
             * content. Route it through the content-addressed blob path so
             * the relay can bundle it under a stable digest and a returning
             * browser can answer with its cache instead of re-downloading
             * the multi-megabyte texture set. This walker runs before the
             * bootstrap snapshot and used to push the whole set inline,
             * which made every seat pay the full snapshot again. Partial
             * rects and repeat updates (fog mask, minimap) stay inline. */
            if (left == 0 && top == 0 && right == full_width
                    && bottom == full_height
                    && item->mip_recorded_generation[level] == 0
                    && item->mip_shadow_size[level] >= 16u * 1024u) {
                memset(&update, 0, sizeof(update));
                update.id = item->id;
                update.generation = item->generation;
                update.level = level;
                update.width = full_width;
                update.height = full_height;
                update.pitch = full_pitch;
                update.format = item->format;
                update.size = item->mip_shadow_size[level];
                if (emit_texture_blob_update_locked(
                        &update, item->mip_shadow[level])) {
                    item->mip_recorded_generation[level] = item->generation;
                    item->mip_dirty[level] = FALSE;
                    item->mip_dirty_left[level] = 0;
                    item->mip_dirty_top[level] = 0;
                    item->mip_dirty_right[level] = 0;
                    item->mip_dirty_bottom[level] = 0;
                } else {
                    g_frame_dependency_failed = TRUE;
                }
                continue;
            }
            source_y = ((uint32_t)item->format == D3DFMT_DXT1
                    || (uint32_t)item->format == D3DFMT_DXT2
                    || (uint32_t)item->format == D3DFMT_DXT3
                    || (uint32_t)item->format == D3DFMT_DXT4
                    || (uint32_t)item->format == D3DFMT_DXT5)
                ? top / 4u : top;
            if ((uint32_t)item->format == D3DFMT_DXT1)
                source_x = (left / 4u) * 8u;
            else if ((uint32_t)item->format == D3DFMT_DXT2
                    || (uint32_t)item->format == D3DFMT_DXT3
                    || (uint32_t)item->format == D3DFMT_DXT4
                    || (uint32_t)item->format == D3DFMT_DXT5)
                source_x = (left / 4u) * 16u;
            else
                source_x = left * (full_pitch / full_width);
            if (source_y + rows > full_rows
                    || source_x + pitch > full_pitch) {
                g_frame_dependency_failed = TRUE;
                continue;
            }
            memset(&update, 0, sizeof(update));
            update.id = item->id;
            update.generation = item->generation;
            update.level = level;
            update.x = left;
            update.y = top;
            update.width = width;
            update.height = height;
            update.pitch = pitch;
            update.format = item->format;
            update.size = pitch * rows;
            payload = (uint8_t *)HeapAlloc(GetProcessHeap(), 0,
                (uint32_t)sizeof(update) + update.size);
            if (!payload) {
                g_frame_dependency_failed = TRUE;
                continue;
            }
            memcpy(payload, &update, sizeof(update));
            pixels = payload + sizeof(update);
            for (row = 0; row < rows; ++row)
                memcpy(pixels + row * pitch,
                       item->mip_shadow[level]
                           + (source_y + row) * full_pitch + source_x,
                       pitch);
            if (!queue_record_message(W3CS_OP_UPDATE_TEXTURE, payload,
                                      (uint32_t)sizeof(update)
                                          + update.size)) {
                HeapFree(GetProcessHeap(), 0, payload);
                g_frame_dependency_failed = TRUE;
                continue;
            }
            HeapFree(GetProcessHeap(), 0, payload);
            item->mip_recorded_generation[level] = item->generation;
            item->mip_dirty[level] = FALSE;
            item->mip_dirty_left[level] = 0;
            item->mip_dirty_top[level] = 0;
            item->mip_dirty_right[level] = 0;
            item->mip_dirty_bottom[level] = 0;
        }
    }
}

static void commit_recorded_textures_locked(void)
{
    struct resource *item;
    for (item = g_resources; item; item = item->next) {
        uint32_t level;
        if (item->kind != RES_TEXTURE)
            continue;
        for (level = 0; level < item->levels
                && level < MAX_TEXTURE_LEVELS; ++level) {
            if (item->mip_recorded_generation[level] == item->generation)
                item->mip_dirty[level] = FALSE;
        }
    }
}

/* Resource vtable hooks. */
static ULONG STDMETHODCALLTYPE hook_vb_release(IDirect3DVertexBuffer9 *object)
{
    struct resource *item;
    IDirect3DVertexBuffer9Vtbl *vtable;
    ULONG references;
    MARK_HOOK(201);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DVertexBuffer9Vtbl *)item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return 0;
    references = vtable->Release(object);
    if (!references)
        remove_resource(item);
    return references;
}

static HRESULT STDMETHODCALLTYPE hook_vb_lock(
    IDirect3DVertexBuffer9 *object, UINT offset, UINT size,
    void **data, DWORD flags)
{
    struct resource *item;
    IDirect3DVertexBuffer9Vtbl *vtable;
    HRESULT result;
    MARK_HOOK(202);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DVertexBuffer9Vtbl *)item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    result = vtable->Lock(object, offset, size, data, flags);
    if (SUCCEEDED(result)) {
        EnterCriticalSection(&g_resource_lock);
        item->lock_ptr = *data;
        item->lock_offset = offset;
        item->lock_size = size ? size : item->size - offset;
        item->lock_flags = flags;
        LeaveCriticalSection(&g_resource_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_vb_unlock(IDirect3DVertexBuffer9 *object)
{
    struct resource *item;
    IDirect3DVertexBuffer9Vtbl *vtable;
    MARK_HOOK(203);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DVertexBuffer9Vtbl *)item->original_vtable : NULL;
    if (item)
        record_buffer_update(item);
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    return vtable->Unlock(object);
}

static ULONG STDMETHODCALLTYPE hook_ib_release(IDirect3DIndexBuffer9 *object)
{
    return hook_vb_release((IDirect3DVertexBuffer9 *)object);
}

static HRESULT STDMETHODCALLTYPE hook_ib_lock(
    IDirect3DIndexBuffer9 *object, UINT offset, UINT size,
    void **data, DWORD flags)
{
    return hook_vb_lock((IDirect3DVertexBuffer9 *)object,
                        offset, size, data, flags);
}

static HRESULT STDMETHODCALLTYPE hook_ib_unlock(IDirect3DIndexBuffer9 *object)
{
    return hook_vb_unlock((IDirect3DVertexBuffer9 *)object);
}

static ULONG STDMETHODCALLTYPE hook_texture_release(IDirect3DTexture9 *object)
{
    struct resource *item;
    IDirect3DTexture9Vtbl *vtable;
    ULONG references;
    MARK_HOOK(211);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DTexture9Vtbl *)item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return 0;
    references = vtable->Release(object);
    if (!references)
        remove_resource(item);
    return references;
}

static HRESULT STDMETHODCALLTYPE hook_texture_lock_rect(
    IDirect3DTexture9 *object, UINT level, D3DLOCKED_RECT *locked,
    const RECT *rect, DWORD flags)
{
    struct resource *item;
    IDirect3DTexture9Vtbl *vtable;
    HRESULT result;
    MARK_HOOK(212);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DTexture9Vtbl *)item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    result = vtable->LockRect(object, level, locked, rect, flags);
    if (SUCCEEDED(result)) {
        EnterCriticalSection(&g_resource_lock);
        item->lock_ptr = locked->pBits;
        item->lock_pitch = locked->Pitch;
        item->lock_flags = flags;
        item->level = level;
        item->has_lock_rect = rect != NULL;
        if (rect)
            item->lock_rect = *rect;
        LeaveCriticalSection(&g_resource_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_texture_unlock_rect(
    IDirect3DTexture9 *object, UINT level)
{
    struct resource *item;
    IDirect3DTexture9Vtbl *vtable;
    MARK_HOOK(213);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DTexture9Vtbl *)item->original_vtable : NULL;
    if (item && item->level == level)
        record_texture_update(item);
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    return vtable->UnlockRect(object, level);
}

static ULONG STDMETHODCALLTYPE hook_surface_release(IDirect3DSurface9 *object)
{
    struct resource *item;
    IDirect3DSurface9Vtbl *vtable;
    ULONG references;
    MARK_HOOK(221);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DSurface9Vtbl *)item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return 0;
    references = vtable->Release(object);
    if (!references)
        remove_resource(item);
    return references;
}

static struct resource *surface_parent_locked(struct resource *surface)
{
    if (!surface)
        return NULL;
    return find_resource_id_locked(surface->parent_id);
}

static HRESULT STDMETHODCALLTYPE hook_surface_lock_rect(
    IDirect3DSurface9 *object, D3DLOCKED_RECT *locked,
    const RECT *rect, DWORD flags)
{
    struct resource *surface, *texture;
    IDirect3DSurface9Vtbl *vtable;
    HRESULT result;
    MARK_HOOK(222);
    EnterCriticalSection(&g_resource_lock);
    surface = find_resource_locked(object);
    vtable = surface ? (IDirect3DSurface9Vtbl *)surface->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    result = vtable->LockRect(object, locked, rect, flags);
    if (SUCCEEDED(result)) {
        EnterCriticalSection(&g_resource_lock);
        texture = surface_parent_locked(surface);
        if (texture) {
            debug_resource_event("surface-lock", texture, surface->level);
            texture->lock_ptr = locked->pBits;
            texture->lock_pitch = locked->Pitch;
            texture->lock_flags = flags;
            texture->level = surface->level;
            texture->has_lock_rect = rect != NULL;
            if (rect)
                texture->lock_rect = *rect;
        }
        LeaveCriticalSection(&g_resource_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_surface_unlock_rect(
    IDirect3DSurface9 *object)
{
    struct resource *surface, *texture;
    IDirect3DSurface9Vtbl *vtable;
    MARK_HOOK(223);
    EnterCriticalSection(&g_resource_lock);
    surface = find_resource_locked(object);
    vtable = surface ? (IDirect3DSurface9Vtbl *)surface->original_vtable : NULL;
    texture = surface_parent_locked(surface);
    if (texture) {
        debug_resource_event("surface-unlock-before", texture,
                             surface->level);
        record_texture_update(texture);
        debug_resource_event("surface-unlock-after", texture,
                             surface->level);
    }
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    return vtable->UnlockRect(object);
}

static void hook_surface(IDirect3DSurface9 *object, uint32_t parent_id,
                         uint32_t level)
{
    struct resource *item;
    struct vtable_patch *patch;
    if (!object)
        return;
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    if (item) {
        item->parent_id = parent_id;
        item->level = level;
    }
    LeaveCriticalSection(&g_resource_lock);
    if (item)
        return;
    item = new_resource(object, RES_SURFACE);
    if (!item)
        return;
    EnterCriticalSection(&g_resource_lock);
    patch = find_vtable_patch_locked(object->lpVtbl);
    if (!patch) {
        IDirect3DSurface9Vtbl *copy = (IDirect3DSurface9Vtbl *)HeapAlloc(
            GetProcessHeap(), 0, sizeof(*copy));
        patch = (struct vtable_patch *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*patch));
        if (copy && patch) {
            memcpy(copy, object->lpVtbl, sizeof(*copy));
            patch->patched_vtable = object->lpVtbl;
            patch->original_vtable = copy;
            patch->kind = RES_SURFACE;
            patch->next = g_vtable_patches;
            g_vtable_patches = patch;
        } else {
            if (copy)
                HeapFree(GetProcessHeap(), 0, copy);
            if (patch)
                HeapFree(GetProcessHeap(), 0, patch);
            patch = NULL;
        }
    }
    if (patch && object->lpVtbl->LockRect != hook_surface_lock_rect) {
        patch_pointer((void **)&object->lpVtbl->Release,
                      (void *)hook_surface_release);
        patch_pointer((void **)&object->lpVtbl->LockRect,
                      (void *)hook_surface_lock_rect);
        patch_pointer((void **)&object->lpVtbl->UnlockRect,
                      (void *)hook_surface_unlock_rect);
    }
    if (patch)
        item->original_vtable = patch->original_vtable;
    LeaveCriticalSection(&g_resource_lock);
    if (!patch) {
        remove_resource(item);
        return;
    }
    item->parent_id = parent_id;
    item->level = level;
}

static HRESULT STDMETHODCALLTYPE hook_texture_get_surface_level(
    IDirect3DTexture9 *object, UINT level, IDirect3DSurface9 **surface)
{
    struct resource *item;
    IDirect3DTexture9Vtbl *vtable;
    HRESULT result;
    MARK_HOOK(214);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(object);
    vtable = item ? (IDirect3DTexture9Vtbl *)item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!vtable)
        return D3DERR_INVALIDCALL;
    result = vtable->GetSurfaceLevel(object, level, surface);
    if (SUCCEEDED(result) && surface && *surface) {
        debug_resource_event("get-surface", item, level);
        hook_surface(*surface, item->id, level);
    }
    return result;
}

static void hook_vertex_buffer(IDirect3DVertexBuffer9 *object, UINT size,
                               DWORD usage, DWORD fvf, D3DPOOL pool)
{
    struct resource *item = new_resource(object, RES_VERTEX_BUFFER);
    struct vtable_patch *patch;
    if (!item)
        return;
    EnterCriticalSection(&g_resource_lock);
    patch = find_vtable_patch_locked(object->lpVtbl);
    if (!patch) {
        IDirect3DVertexBuffer9Vtbl *copy = (IDirect3DVertexBuffer9Vtbl *)HeapAlloc(
            GetProcessHeap(), 0, sizeof(*copy));
        patch = (struct vtable_patch *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*patch));
        if (copy && patch) {
            memcpy(copy, object->lpVtbl, sizeof(*copy));
            patch->patched_vtable = object->lpVtbl;
            patch->original_vtable = copy;
            patch->kind = RES_VERTEX_BUFFER;
            patch->next = g_vtable_patches;
            g_vtable_patches = patch;
        } else {
            if (copy)
                HeapFree(GetProcessHeap(), 0, copy);
            if (patch)
                HeapFree(GetProcessHeap(), 0, patch);
            patch = NULL;
        }
    }
    if (patch && object->lpVtbl->Lock != hook_vb_lock) {
        patch_pointer((void **)&object->lpVtbl->Release,
                      (void *)hook_vb_release);
        patch_pointer((void **)&object->lpVtbl->Lock,
                      (void *)hook_vb_lock);
        patch_pointer((void **)&object->lpVtbl->Unlock,
                      (void *)hook_vb_unlock);
    }
    if (patch) {
        item->original_vtable = patch->original_vtable;
        item->size = size;
        item->usage = usage;
        item->fvf = fvf;
        item->pool = pool;
        item->descriptor_ready = TRUE;
        emit_create_resource(item);
    }
    LeaveCriticalSection(&g_resource_lock);
    if (!patch) {
        remove_resource(item);
        return;
    }
}

static void hook_index_buffer(IDirect3DIndexBuffer9 *object, UINT size,
                              DWORD usage, D3DFORMAT format, D3DPOOL pool)
{
    struct resource *item = new_resource(object, RES_INDEX_BUFFER);
    struct vtable_patch *patch;
    if (!item)
        return;
    EnterCriticalSection(&g_resource_lock);
    patch = find_vtable_patch_locked(object->lpVtbl);
    if (!patch) {
        IDirect3DIndexBuffer9Vtbl *copy = (IDirect3DIndexBuffer9Vtbl *)HeapAlloc(
            GetProcessHeap(), 0, sizeof(*copy));
        patch = (struct vtable_patch *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*patch));
        if (copy && patch) {
            memcpy(copy, object->lpVtbl, sizeof(*copy));
            patch->patched_vtable = object->lpVtbl;
            patch->original_vtable = copy;
            patch->kind = RES_INDEX_BUFFER;
            patch->next = g_vtable_patches;
            g_vtable_patches = patch;
        } else {
            if (copy)
                HeapFree(GetProcessHeap(), 0, copy);
            if (patch)
                HeapFree(GetProcessHeap(), 0, patch);
            patch = NULL;
        }
    }
    if (patch && object->lpVtbl->Lock != hook_ib_lock) {
        patch_pointer((void **)&object->lpVtbl->Release,
                      (void *)hook_ib_release);
        patch_pointer((void **)&object->lpVtbl->Lock,
                      (void *)hook_ib_lock);
        patch_pointer((void **)&object->lpVtbl->Unlock,
                      (void *)hook_ib_unlock);
    }
    if (patch) {
        item->original_vtable = patch->original_vtable;
        item->size = size;
        item->usage = usage;
        item->format = format;
        item->pool = pool;
        item->descriptor_ready = TRUE;
        emit_create_resource(item);
    }
    LeaveCriticalSection(&g_resource_lock);
    if (!patch) {
        remove_resource(item);
        return;
    }
}

static void hook_texture(IDirect3DTexture9 *object, UINT width, UINT height,
                         UINT levels, DWORD usage, D3DFORMAT format,
                         D3DPOOL pool)
{
    struct resource *item = new_resource(object, RES_TEXTURE);
    struct vtable_patch *patch;
    if (!item)
        return;
    EnterCriticalSection(&g_resource_lock);
    patch = find_vtable_patch_locked(object->lpVtbl);
    if (!patch) {
        IDirect3DTexture9Vtbl *copy = (IDirect3DTexture9Vtbl *)HeapAlloc(
            GetProcessHeap(), 0, sizeof(*copy));
        patch = (struct vtable_patch *)HeapAlloc(
            GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*patch));
        if (copy && patch) {
            memcpy(copy, object->lpVtbl, sizeof(*copy));
            patch->patched_vtable = object->lpVtbl;
            patch->original_vtable = copy;
            patch->kind = RES_TEXTURE;
            patch->next = g_vtable_patches;
            g_vtable_patches = patch;
        } else {
            if (copy)
                HeapFree(GetProcessHeap(), 0, copy);
            if (patch)
                HeapFree(GetProcessHeap(), 0, patch);
            patch = NULL;
        }
    }
    if (patch && object->lpVtbl->LockRect != hook_texture_lock_rect) {
        patch_pointer((void **)&object->lpVtbl->Release,
                      (void *)hook_texture_release);
        patch_pointer((void **)&object->lpVtbl->LockRect,
                      (void *)hook_texture_lock_rect);
        patch_pointer((void **)&object->lpVtbl->UnlockRect,
                      (void *)hook_texture_unlock_rect);
        patch_pointer((void **)&object->lpVtbl->GetSurfaceLevel,
                      (void *)hook_texture_get_surface_level);
    }
    if (patch) {
        item->original_vtable = patch->original_vtable;
        item->width = width;
        item->height = height;
        item->levels = levels ? levels : IDirect3DTexture9_GetLevelCount(object);
        if (item->levels > MAX_TEXTURE_LEVELS)
            item->levels = MAX_TEXTURE_LEVELS;
        item->usage = usage;
        item->format = format;
        item->pool = pool;
        item->descriptor_ready = TRUE;
        debug_resource_event("create-texture", item, (uint32_t)format);
        emit_create_resource(item);
    }
    LeaveCriticalSection(&g_resource_lock);
    if (!patch) {
        remove_resource(item);
        return;
    }
}

/* Device vtable hooks. */
static HRESULT STDMETHODCALLTYPE hook_create_vertex_buffer(
    IDirect3DDevice9 *device, UINT length, DWORD usage, DWORD fvf,
    D3DPOOL pool, IDirect3DVertexBuffer9 **buffer, HANDLE *shared)
{
    HRESULT result;
    MARK_HOOK(101);
    result = g_device_original->CreateVertexBuffer(
        device, length, usage, fvf, pool, buffer, shared);
    if (SUCCEEDED(result) && buffer && *buffer)
        hook_vertex_buffer(*buffer, length, usage, fvf, pool);
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_create_index_buffer(
    IDirect3DDevice9 *device, UINT length, DWORD usage, D3DFORMAT format,
    D3DPOOL pool, IDirect3DIndexBuffer9 **buffer, HANDLE *shared)
{
    HRESULT result;
    MARK_HOOK(102);
    result = g_device_original->CreateIndexBuffer(
        device, length, usage, format, pool, buffer, shared);
    if (SUCCEEDED(result) && buffer && *buffer)
        hook_index_buffer(*buffer, length, usage, format, pool);
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_create_texture(
    IDirect3DDevice9 *device, UINT width, UINT height, UINT levels,
    DWORD usage, D3DFORMAT format, D3DPOOL pool,
    IDirect3DTexture9 **texture, HANDLE *shared)
{
    HRESULT result;
    MARK_HOOK(103);
    result = g_device_original->CreateTexture(
        device, width, height, levels, usage, format, pool, texture, shared);
    if (SUCCEEDED(result) && texture && *texture)
        hook_texture(*texture, width, height, levels, usage, format, pool);
    return result;
}

/* Warcraft III 1.14 uses the device copy paths for a large part of its
 * managed texture population. LockRect-only tracing therefore observes the
 * texture objects, but not their pixels. Capture the successful copy from the
 * readable source after Wine has completed it, and attach those pixels to the
 * destination generation that later draw calls reference. */
static void record_copied_texture_level_locked(
    struct resource *destination, UINT level,
    const D3DLOCKED_RECT *locked, const RECT *destination_rect)
{
    if (!destination || destination->kind != RES_TEXTURE || !locked)
        return;
    destination->lock_ptr = locked->pBits;
    destination->lock_pitch = locked->Pitch;
    destination->lock_flags = 0;
    destination->level = level;
    destination->has_lock_rect = destination_rect != NULL;
    if (destination_rect)
        destination->lock_rect = *destination_rect;
    record_texture_update(destination);
    destination->lock_ptr = NULL;
    destination->lock_pitch = 0;
    destination->lock_flags = 0;
    destination->has_lock_rect = FALSE;
}

static HRESULT STDMETHODCALLTYPE hook_update_texture(
    IDirect3DDevice9 *device, IDirect3DBaseTexture9 *source,
    IDirect3DBaseTexture9 *destination)
{
    IDirect3DTexture9 *source_texture;
    IDirect3DTexture9 *destination_texture;
    IDirect3DTexture9Vtbl *source_vtable;
    struct resource *source_item;
    struct resource *destination_item;
    UINT source_levels, destination_levels, levels, level;
    HRESULT result;
    MARK_HOOK(106);
    result = g_device_original->UpdateTexture(device, source, destination);
    if (FAILED(result) || !source || !destination
            || IDirect3DBaseTexture9_GetType(source) != D3DRTYPE_TEXTURE
            || IDirect3DBaseTexture9_GetType(destination) != D3DRTYPE_TEXTURE)
        return result;
    source_texture = (IDirect3DTexture9 *)source;
    destination_texture = (IDirect3DTexture9 *)destination;
    EnterCriticalSection(&g_resource_lock);
    source_item = find_resource_locked(source_texture);
    destination_item = find_resource_locked(destination_texture);
    source_vtable = source_item
        ? (IDirect3DTexture9Vtbl *)source_item->original_vtable : NULL;
    LeaveCriticalSection(&g_resource_lock);
    if (!source_vtable || !destination_item)
        return result;
    source_levels = source_vtable->GetLevelCount(source_texture);
    destination_levels = IDirect3DTexture9_GetLevelCount(destination_texture);
    levels = source_levels < destination_levels
        ? source_levels : destination_levels;
    if (levels > MAX_TEXTURE_LEVELS)
        levels = MAX_TEXTURE_LEVELS;
    for (level = 0; level < levels; ++level) {
        D3DLOCKED_RECT locked;
        if (FAILED(source_vtable->LockRect(
                source_texture, level, &locked, NULL, D3DLOCK_READONLY)))
            continue;
        EnterCriticalSection(&g_resource_lock);
        destination_item = find_resource_locked(destination_texture);
        record_copied_texture_level_locked(
            destination_item, level, &locked, NULL);
        LeaveCriticalSection(&g_resource_lock);
        source_vtable->UnlockRect(source_texture, level);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_update_surface(
    IDirect3DDevice9 *device, IDirect3DSurface9 *source,
    const RECT *source_rect, IDirect3DSurface9 *destination,
    const POINT *destination_point)
{
    struct resource *destination_surface;
    struct resource *destination_texture;
    D3DSURFACE_DESC description;
    D3DLOCKED_RECT locked;
    RECT copied_rect;
    HRESULT result;
    MARK_HOOK(107);
    result = g_device_original->UpdateSurface(
        device, source, source_rect, destination, destination_point);
    if (FAILED(result) || !source || !destination
            || FAILED(IDirect3DSurface9_GetDesc(source, &description))
            || FAILED(IDirect3DSurface9_LockRect(
                source, &locked, source_rect, D3DLOCK_READONLY)))
        return result;
    copied_rect.left = destination_point ? destination_point->x : 0;
    copied_rect.top = destination_point ? destination_point->y : 0;
    copied_rect.right = copied_rect.left + (source_rect
        ? source_rect->right - source_rect->left : (LONG)description.Width);
    copied_rect.bottom = copied_rect.top + (source_rect
        ? source_rect->bottom - source_rect->top : (LONG)description.Height);
    EnterCriticalSection(&g_resource_lock);
    destination_surface = find_resource_locked(destination);
    destination_texture = surface_parent_locked(destination_surface);
    record_copied_texture_level_locked(
        destination_texture,
        destination_surface ? destination_surface->level : 0,
        &locked, &copied_rect);
    LeaveCriticalSection(&g_resource_lock);
    IDirect3DSurface9_UnlockRect(source);
    return result;
}

/* Wine's D3D8 compatibility path can populate a D3D9 managed texture below
 * the public D3D9 LockRect vtable that this proxy wraps. The draw calls still
 * arrive through D3D9, so take one read-only snapshot the first time such a
 * texture is sampled. This is lazy: unused map and UI textures never cross
 * the command stream. */
static void capture_managed_texture_if_missing(
    IDirect3DBaseTexture9 *base_texture)
{
    IDirect3DTexture9 *texture;
    IDirect3DTexture9Vtbl *vtable;
    struct resource *item;
    UINT levels, level;
    uint32_t frame;
    BOOL missing = FALSE;
    BOOL attempted = FALSE;
    BOOL complete = FALSE;
    if (!base_texture)
        return;
    texture = (IDirect3DTexture9 *)base_texture;
    frame = (uint32_t)InterlockedCompareExchange(&g_frame_number, 0, 0);
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(texture);
    vtable = item ? (IDirect3DTexture9Vtbl *)item->original_vtable : NULL;
    /* Do not use IDirect3DBaseTexture9::GetType here. Warcraft III 1.14
     * reaches this hook through Wine's D3D8 compatibility object. The object
     * is the D3D9 texture that CreateTexture registered, but the D3D8-facing
     * GetType call can use a different interface view and reject the capture
     * before we inspect the registered resource. The recorder's identity and
     * kind are authoritative. Also inspect non-managed textures once: some
     * Wine paths report the backend pool rather than the D3D8 source pool. */
    if (item && item->kind == RES_TEXTURE
            && !item->lazy_capture_attempted
            && (!item->lazy_capture_last_frame
                || frame - item->lazy_capture_last_frame >= 10u)) {
        for (level = 0; level < item->levels; ++level)
            if (!item->mip_shadow[level]) {
                missing = TRUE;
                break;
            }
        if (g_debug && !item->lazy_capture_diagnostic) {
            debug_resource_event(missing ? "lazy-texture-start"
                                         : "lazy-texture-already-present",
                                 item, (item->pool << 16u) | item->levels);
            item->lazy_capture_diagnostic = TRUE;
        }
        if (missing)
            item->lazy_capture_last_frame = frame ? frame : 1u;
    }
    levels = item ? item->levels : 0;
    LeaveCriticalSection(&g_resource_lock);
    if (!missing || !vtable)
        return;
    if (levels > MAX_TEXTURE_LEVELS)
        levels = MAX_TEXTURE_LEVELS;
    for (level = 0; level < levels; ++level) {
        D3DLOCKED_RECT locked;
        HRESULT lock_result;
        IDirect3DSurface9 *surface = NULL;
        IDirect3DSurface9Vtbl *surface_vtable = NULL;
        BOOL surface_locked = FALSE;
        EnterCriticalSection(&g_resource_lock);
        item = find_resource_locked(texture);
        missing = item && !item->mip_shadow[level];
        LeaveCriticalSection(&g_resource_lock);
        if (!missing)
            continue;
        attempted = TRUE;
        lock_result = vtable->LockRect(
            texture, level, &locked, NULL, D3DLOCK_READONLY);
        /* Wine's D3D8 bridge rejects READONLY on some managed aliases even
         * though a normal lock succeeds. We do not modify the returned
         * memory; this fallback only lets the recorder inspect it. */
        if (FAILED(lock_result))
            lock_result = vtable->LockRect(
                texture, level, &locked, NULL, 0);
        if (FAILED(lock_result)
                && SUCCEEDED(vtable->GetSurfaceLevel(
                    texture, level, &surface)) && surface) {
            struct vtable_patch *patch;
            EnterCriticalSection(&g_resource_lock);
            patch = find_vtable_patch_locked(surface->lpVtbl);
            surface_vtable = patch
                ? (IDirect3DSurface9Vtbl *)patch->original_vtable
                : surface->lpVtbl;
            LeaveCriticalSection(&g_resource_lock);
            lock_result = surface_vtable->LockRect(
                surface, &locked, NULL, D3DLOCK_READONLY);
            if (FAILED(lock_result))
                lock_result = surface_vtable->LockRect(
                    surface, &locked, NULL, 0);
            surface_locked = SUCCEEDED(lock_result);
        }
        if (FAILED(lock_result)) {
            EnterCriticalSection(&g_resource_lock);
            item = find_resource_locked(texture);
            debug_resource_event("lazy-texture-lock-failed", item,
                                 (uint32_t)lock_result);
            LeaveCriticalSection(&g_resource_lock);
            if (surface && surface_vtable)
                surface_vtable->Release(surface);
            continue;
        }
        EnterCriticalSection(&g_resource_lock);
        item = find_resource_locked(texture);
        record_copied_texture_level_locked(item, level, &locked, NULL);
        debug_resource_event("lazy-texture-captured", item, level);
        LeaveCriticalSection(&g_resource_lock);
        if (surface_locked) {
            surface_vtable->UnlockRect(surface);
            surface_vtable->Release(surface);
        } else {
            vtable->UnlockRect(texture, level);
        }
    }
    EnterCriticalSection(&g_resource_lock);
    item = find_resource_locked(texture);
    if (item) {
        complete = TRUE;
        for (level = 0; level < item->levels
                && level < MAX_TEXTURE_LEVELS; ++level) {
            if (!item->mip_shadow[level]) {
                complete = FALSE;
                break;
            }
        }
        /* A texture can be bound before Warcraft has finished populating it.
         * A failed first read must not permanently mark it as captured; that
         * produced white units and buildings later in a replay. Retry at a
         * bounded cadence until every mip has a readable shadow. */
        if (complete)
            item->lazy_capture_attempted = TRUE;
        else if (attempted)
            item->lazy_capture_attempted = FALSE;
    }
    LeaveCriticalSection(&g_resource_lock);
}

static HRESULT STDMETHODCALLTYPE hook_set_cursor_properties(
    IDirect3DDevice9 *device, UINT hot_x, UINT hot_y,
    IDirect3DSurface9 *surface)
{
    HRESULT result;
    D3DSURFACE_DESC description;
    D3DLOCKED_RECT locked;
    uint8_t *pixels = NULL;
    uint32_t size = 0;
    uint32_t row;
    MARK_HOOK(104);
    result = g_device_original->SetCursorProperties(
        device, hot_x, hot_y, surface);
    if (FAILED(result) || !surface
            || FAILED(IDirect3DSurface9_GetDesc(surface, &description))
            || description.Format != D3DFMT_A8R8G8B8
            || !description.Width || !description.Height
            || description.Width > 256u || description.Height > 256u
            || description.Width > UINT32_MAX / 4u
            || description.Height > UINT32_MAX / (description.Width * 4u))
        return result;
    size = description.Width * description.Height * 4u;
    pixels = (uint8_t *)HeapAlloc(GetProcessHeap(), 0, size);
    if (!pixels)
        return result;
    if (FAILED(IDirect3DSurface9_LockRect(
            surface, &locked, NULL, D3DLOCK_READONLY)) || locked.Pitch <= 0) {
        HeapFree(GetProcessHeap(), 0, pixels);
        return result;
    }
    for (row = 0; row < description.Height; ++row)
        memcpy(pixels + row * description.Width * 4u,
               (uint8_t *)locked.pBits + row * locked.Pitch,
               description.Width * 4u);
    IDirect3DSurface9_UnlockRect(surface);
    EnterCriticalSection(&g_resource_lock);
    if (g_cursor_pixels)
        HeapFree(GetProcessHeap(), 0, g_cursor_pixels);
    g_cursor_pixels = pixels;
    g_cursor_size = size;
    g_cursor_width = description.Width;
    g_cursor_height = description.Height;
    g_cursor_hot_x = hot_x;
    g_cursor_hot_y = hot_y;
    if (InterlockedCompareExchange(&g_connected, 0, 0))
        emit_cursor_locked();
    LeaveCriticalSection(&g_resource_lock);
    return result;
}

static BOOL STDMETHODCALLTYPE hook_show_cursor(IDirect3DDevice9 *device,
                                                BOOL show)
{
    BOOL previous;
    MARK_HOOK(105);
    previous = g_device_original->ShowCursor(device, show);
    EnterCriticalSection(&g_resource_lock);
    if (g_cursor_visible != show) {
        g_cursor_visible = show;
        if (InterlockedCompareExchange(&g_connected, 0, 0))
            emit_cursor_visibility_locked();
    }
    LeaveCriticalSection(&g_resource_lock);
    return previous;
}

static HRESULT STDMETHODCALLTYPE hook_set_render_state(
    IDirect3DDevice9 *device, D3DRENDERSTATETYPE state, DWORD value)
{
    HRESULT result;
    MARK_HOOK(110);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetRenderState(device, state, value);
    if (SUCCEEDED(result) && (uint32_t)state < MAX_RENDER_STATES) {
        uint32_t pair[2] = {(uint32_t)state, value};
        EnterCriticalSection(&g_frame_lock);
        if (g_state.render_valid[state]
                && g_state.render_state[state] == value) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.render_state[state] = value;
        g_state.render_valid[state] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_RENDER_STATE, 0, pair, sizeof(pair));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_transform(
    IDirect3DDevice9 *device, D3DTRANSFORMSTATETYPE state,
    const D3DMATRIX *matrix)
{
    HRESULT result;
    MARK_HOOK(111);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetTransform(device, state, matrix);
    if (SUCCEEDED(result) && matrix && (uint32_t)state < MAX_TRANSFORMS) {
        uint32_t index = (uint32_t)state;
        EnterCriticalSection(&g_frame_lock);
        if (g_state.transform_valid[index]
                && memcmp(&g_state.transform[index], matrix,
                          sizeof(*matrix)) == 0) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.transform[index] = *matrix;
        g_state.transform_valid[index] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            record_transform_locked(index, matrix);
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_texture(
    IDirect3DDevice9 *device, DWORD stage, IDirect3DBaseTexture9 *texture)
{
    HRESULT result;
    MARK_HOOK(112);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetTexture(device, stage, texture);
    if (SUCCEEDED(result) && stage >= MAX_TEXTURE_STAGES && texture)
        capture_violation("vertex-texture-sampler");
    if (SUCCEEDED(result) && stage < MAX_TEXTURE_STAGES) {
        uint32_t payload[3];
        capture_managed_texture_if_missing(texture);
        resource_binding(texture, &payload[1], &payload[2]);
        payload[0] = stage;
        EnterCriticalSection(&g_frame_lock);
        if (g_state.texture_valid[stage]
                && g_state.textures[stage].id == payload[1]
                && g_state.textures[stage].generation == payload[2]) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.textures[stage].id = payload[1];
        g_state.textures[stage].generation = payload[2];
        g_state.texture_valid[stage] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_TEXTURE, 0, payload,
                              sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_fvf(IDirect3DDevice9 *device,
                                               DWORD fvf)
{
    HRESULT result;
    MARK_HOOK(113);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetFVF(device, fvf);
    if (SUCCEEDED(result)) {
        EnterCriticalSection(&g_frame_lock);
        if (g_state.fvf_valid && g_state.fvf == fvf) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.fvf = fvf;
        g_state.fvf_valid = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_FVF, 0, &fvf, sizeof(fvf));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_stream_source(
    IDirect3DDevice9 *device, UINT stream, IDirect3DVertexBuffer9 *buffer,
    UINT offset, UINT stride)
{
    HRESULT result;
    MARK_HOOK(114);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetStreamSource(
            device, stream, buffer, offset, stride);
    if (SUCCEEDED(result) && stream < MAX_TEXTURE_STAGES) {
        struct {
            uint32_t stream;
            struct stream_binding binding;
        } payload;
        payload.stream = stream;
        resource_binding(buffer, &payload.binding.id,
                         &payload.binding.generation);
        payload.binding.offset = offset;
        payload.binding.stride = stride;
        EnterCriticalSection(&g_frame_lock);
        if (g_state.stream_valid[stream]
                && memcmp(&g_state.streams[stream], &payload.binding,
                          sizeof(payload.binding)) == 0) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.streams[stream] = payload.binding;
        g_state.stream_valid[stream] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_STREAM_SOURCE, 0, &payload,
                              sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_indices(
    IDirect3DDevice9 *device, IDirect3DIndexBuffer9 *buffer)
{
    HRESULT result;
    MARK_HOOK(115);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetIndices(device, buffer);
    if (SUCCEEDED(result)) {
        uint32_t payload[2];
        resource_binding(buffer, &payload[0], &payload[1]);
        EnterCriticalSection(&g_frame_lock);
        if (g_state.index_valid && g_state.index_id == payload[0]
                && g_state.index_generation == payload[1]) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.index_id = payload[0];
        g_state.index_generation = payload[1];
        g_state.index_valid = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_INDICES, 0, payload,
                              sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_material(
    IDirect3DDevice9 *device, const D3DMATERIAL9 *material)
{
    HRESULT result;
    MARK_HOOK(116);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetMaterial(device, material);
    if (SUCCEEDED(result) && material) {
        EnterCriticalSection(&g_frame_lock);
        if (g_state.material_valid
                && memcmp(&g_state.material, material,
                          sizeof(*material)) == 0) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.material = *material;
        g_state.material_valid = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_MATERIAL, 0, material,
                              sizeof(*material));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_light(
    IDirect3DDevice9 *device, DWORD index, const D3DLIGHT9 *light)
{
    HRESULT result;
    MARK_HOOK(117);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetLight(device, index, light);
    if (SUCCEEDED(result) && light && index >= MAX_LIGHTS)
        capture_violation("light-index-range");
    if (SUCCEEDED(result) && light && index < MAX_LIGHTS) {
        uint8_t payload[4 + sizeof(*light)];
        memcpy(payload, &index, 4);
        memcpy(payload + 4, light, sizeof(*light));
        EnterCriticalSection(&g_frame_lock);
        if (g_state.light_valid[index]
                && memcmp(&g_state.lights[index], light,
                          sizeof(*light)) == 0) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.lights[index] = *light;
        g_state.light_valid[index] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_LIGHT, 0, payload, sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_light_enable(
    IDirect3DDevice9 *device, DWORD index, BOOL enable)
{
    HRESULT result;
    MARK_HOOK(118);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->LightEnable(device, index, enable);
    if (SUCCEEDED(result) && index < MAX_LIGHTS) {
        uint32_t payload[2] = {index, enable ? 1u : 0u};
        EnterCriticalSection(&g_frame_lock);
        if (g_state.light_enable_valid[index]
                && g_state.light_enabled[index] == enable) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.light_enabled[index] = enable;
        g_state.light_enable_valid[index] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_LIGHT_ENABLE, 0, payload,
                              sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_tss(
    IDirect3DDevice9 *device, DWORD stage, D3DTEXTURESTAGESTATETYPE type,
    DWORD value)
{
    HRESULT result;
    MARK_HOOK(119);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetTextureStageState(
            device, stage, type, value);
    if (SUCCEEDED(result) && stage < MAX_TEXTURE_STAGES
            && (uint32_t)type < MAX_TSS_TYPES) {
        uint32_t payload[3] = {stage, (uint32_t)type, value};
        EnterCriticalSection(&g_frame_lock);
        if (g_state.tss_valid[stage][type]
                && g_state.tss[stage][type] == value) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.tss[stage][type] = value;
        g_state.tss_valid[stage][type] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_TEXTURE_STAGE_STATE, 0,
                              payload, sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_sampler(
    IDirect3DDevice9 *device, DWORD sampler, D3DSAMPLERSTATETYPE type,
    DWORD value)
{
    HRESULT result;
    MARK_HOOK(120);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetSamplerState(
            device, sampler, type, value);
    if (SUCCEEDED(result) && sampler >= MAX_TEXTURE_STAGES)
        capture_violation("vertex-sampler-state");
    if (SUCCEEDED(result) && sampler < MAX_TEXTURE_STAGES
            && (uint32_t)type < MAX_SAMPLER_TYPES) {
        uint32_t payload[3] = {sampler, (uint32_t)type, value};
        EnterCriticalSection(&g_frame_lock);
        if (g_state.sampler_valid[sampler][type]
                && g_state.sampler[sampler][type] == value) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.sampler[sampler][type] = value;
        g_state.sampler_valid[sampler][type] = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_SAMPLER_STATE, 0,
                              payload, sizeof(payload));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_viewport(
    IDirect3DDevice9 *device, const D3DVIEWPORT9 *viewport)
{
    HRESULT result;
    MARK_HOOK(121);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetViewport(device, viewport);
    if (SUCCEEDED(result) && viewport) {
        EnterCriticalSection(&g_frame_lock);
        if (g_state.viewport_valid
                && memcmp(&g_state.viewport, viewport,
                          sizeof(*viewport)) == 0) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.viewport = *viewport;
        g_state.viewport_valid = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_VIEWPORT, 0, viewport,
                              sizeof(*viewport));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_set_scissor(
    IDirect3DDevice9 *device, const RECT *rect)
{
    HRESULT result;
    MARK_HOOK(122);
    result = headless_fast_active() ? D3D_OK
        : g_device_original->SetScissorRect(device, rect);
    if (SUCCEEDED(result) && rect) {
        EnterCriticalSection(&g_frame_lock);
        if (g_state.scissor_valid
                && memcmp(&g_state.scissor, rect, sizeof(*rect)) == 0) {
            LeaveCriticalSection(&g_frame_lock);
            return result;
        }
        g_state.scissor = *rect;
        g_state.scissor_valid = 1;
        if (recording_frame()) {
            begin_frame_locked();
            raw_record_locked(W3CS_OP_SET_SCISSOR, 0, rect, sizeof(*rect));
        }
        LeaveCriticalSection(&g_frame_lock);
    }
    return result;
}

/* Per-draw capture-coverage checks. These conditions render on the real GPU
 * but are not represented in the command stream. Any active use is reported
 * so the browser fails closed instead of showing an approximate image. */
static void check_draw_capture_locked(void)
{
    uint32_t stream;
    uint32_t stage;
    for (stream = 1; stream < MAX_TEXTURE_STAGES; ++stream) {
        if (g_state.stream_valid[stream] && g_state.streams[stream].id) {
            capture_violation("multi-stream-draw");
            break;
        }
    }
    for (stage = 0; stage < MAX_TEXTURE_STAGES; ++stage) {
        struct resource *item;
        uint32_t row_bytes;
        uint32_t rows;
        if (!g_state.texture_valid[stage] || !g_state.textures[stage].id)
            continue;
        item = find_resource_id_locked(g_state.textures[stage].id);
        if (item && item->kind == RES_TEXTURE
                && !texture_layout((D3DFORMAT)item->format, 4u, 4u,
                                   &row_bytes, &rows))
            capture_violation("texture-format");
    }
}

static HRESULT STDMETHODCALLTYPE hook_clear(
    IDirect3DDevice9 *device, DWORD count, const D3DRECT *rects,
    DWORD flags, D3DCOLOR color, float z, DWORD stencil)
{
    MARK_HOOK(123);
    struct {
        uint32_t count;
        uint32_t flags;
        uint32_t color;
        float z;
        uint32_t stencil;
    } payload = {count, flags, color, z, stencil};
    (void)rects;
    /* The browser reproduces one full-target clear at the start of the frame
     * with depth 1.0. Every other clear shape would diverge silently. */
    if (recording_frame()) {
        if (rects && count)
            capture_violation("clear-rects");
        if (g_draw_ordinal > 0)
            capture_violation("mid-frame-clear");
        if ((flags & D3DCLEAR_ZBUFFER) && z != 1.0f)
            capture_violation("clear-depth-value");
    }
    append_record(W3CS_OP_CLEAR, &payload, sizeof(payload));
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->Clear(
        device, count, rects, flags, color, z, stencil);
}

static HRESULT STDMETHODCALLTYPE hook_draw_primitive(
    IDirect3DDevice9 *device, D3DPRIMITIVETYPE type,
    UINT start_vertex, UINT primitive_count)
{
    uint32_t draw_ordinal = g_draw_ordinal++;
    uint32_t payload[4] = {
        (uint32_t)type, start_vertex, primitive_count, 0
    };
    MARK_HOOK(124);
    if (recording_frame()) {
        EnterCriticalSection(&g_resource_lock);
        EnterCriticalSection(&g_frame_lock);
        record_dirty_textures_locked();
        begin_frame_locked();
        check_draw_capture_locked();
        if (g_state.stream_valid[0] && g_state.streams[0].stride) {
            uint32_t vertices = primitive_vertex_count(type, primitive_count);
            int64_t offset = (int64_t)g_state.streams[0].offset
                + (int64_t)start_vertex * g_state.streams[0].stride;
            uint64_t bytes = (uint64_t)vertices
                * g_state.streams[0].stride;
            payload[3] = define_bound_buffer_blob_locked(
                g_state.streams[0].id, offset, bytes,
                (draw_ordinal << 1));
        }
        raw_record_locked(W3CS_OP_DRAW_PRIMITIVE, 0,
                          payload, sizeof(payload));
        LeaveCriticalSection(&g_frame_lock);
        LeaveCriticalSection(&g_resource_lock);
    }
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->DrawPrimitive(
        device, type, start_vertex, primitive_count);
}

static HRESULT STDMETHODCALLTYPE hook_draw_indexed_primitive(
    IDirect3DDevice9 *device, D3DPRIMITIVETYPE type, INT base_vertex,
    UINT min_vertex, UINT num_vertices, UINT start_index,
    UINT primitive_count)
{
    uint32_t draw_ordinal = g_draw_ordinal++;
    struct {
        uint32_t type;
        int32_t base_vertex;
        uint32_t min_vertex;
        uint32_t num_vertices;
        uint32_t start_index;
        uint32_t primitive_count;
        uint32_t vertex_blob;
        uint32_t index_blob;
    } payload = {(uint32_t)type, base_vertex, min_vertex, num_vertices,
                 start_index, primitive_count, 0, 0};
    MARK_HOOK(125);
    if (recording_frame()) {
        uint32_t index_width = 2u;
        uint32_t index_count;
        EnterCriticalSection(&g_resource_lock);
        EnterCriticalSection(&g_frame_lock);
        record_dirty_textures_locked();
        begin_frame_locked();
        check_draw_capture_locked();
        index_count = primitive_vertex_count(type, primitive_count);
        if (g_state.index_valid) {
            struct resource *indices = find_resource_id_locked(
                g_state.index_id);
            if (indices && indices->format == D3DFMT_INDEX32)
                index_width = 4u;
        }
        if (g_state.stream_valid[0] && g_state.streams[0].stride) {
            uint64_t vertex_count = (uint64_t)min_vertex + num_vertices;
            int64_t vertex_offset = (int64_t)g_state.streams[0].offset
                + (int64_t)base_vertex * g_state.streams[0].stride;
            uint32_t max_index;
            if (g_state.index_valid && scan_index_maximum_locked(
                    g_state.index_id, start_index, index_count,
                    index_width, &max_index))
                vertex_count = (uint64_t)max_index + 1u;
            payload.vertex_blob = define_bound_buffer_blob_locked(
                g_state.streams[0].id, vertex_offset,
                vertex_count * g_state.streams[0].stride,
                (draw_ordinal << 1));
        }
        if (g_state.index_valid) {
            payload.index_blob = define_bound_buffer_blob_locked(
                g_state.index_id, (int64_t)start_index * index_width,
                (uint64_t)index_count * index_width,
                (draw_ordinal << 1) | 1u);
        }
        raw_record_locked(W3CS_OP_DRAW_INDEXED_PRIMITIVE, 0,
                          &payload, sizeof(payload));
        LeaveCriticalSection(&g_frame_lock);
        LeaveCriticalSection(&g_resource_lock);
    }
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->DrawIndexedPrimitive(
        device, type, base_vertex, min_vertex, num_vertices,
        start_index, primitive_count);
}

static HRESULT STDMETHODCALLTYPE hook_present(
    IDirect3DDevice9 *device, const RECT *source, const RECT *destination,
    HWND window, const RGNDATA *dirty)
{
    uint32_t frame;
    HRESULT result;
    LARGE_INTEGER now;
    MARK_HOOK(126);
    refresh_raster_mode();
    frame = (uint32_t)InterlockedIncrement(&g_frame_number);
    if (InterlockedCompareExchange(&g_stream_enabled, 0, 0)
            && InterlockedExchange(&g_snapshot_requested, 0)) {
        /* Blobs surviving from a previous viewer keep reliable_emitted set,
         * which would make this snapshot silently skip their content for a
         * browser that never received it. Texture mips now enter the blob
         * cache mid-session (the dirty walker's content-addressed path), so
         * the cache must be reset BEFORE the snapshot, not only after. */
        EnterCriticalSection(&g_resource_lock);
        EnterCriticalSection(&g_frame_lock);
        clear_blob_cache_locked();
        LeaveCriticalSection(&g_frame_lock);
        LeaveCriticalSection(&g_resource_lock);
        emit_resource_snapshot();
        EnterCriticalSection(&g_resource_lock);
        EnterCriticalSection(&g_frame_lock);
        g_frame_size = 0;
        g_frame_overflow = FALSE;
        g_frame_keyframe = FALSE;
        g_frame_geometry_anchor = FALSE;
        g_last_keyframe_tick = 0;
        clear_blob_cache_locked();
        LeaveCriticalSection(&g_frame_lock);
        LeaveCriticalSection(&g_resource_lock);
    }
    if (recording_frame()) {
        BOOL queued = FALSE;
        EnterCriticalSection(&g_resource_lock);
        EnterCriticalSection(&g_frame_lock);
        begin_frame_locked();
        if (!g_frame_overflow && !g_frame_dependency_failed)
            queued = queue_payload(W3CS_FRAME, frame,
                                   (g_frame_keyframe ? W3CS_KEYFRAME : 0)
                                   | (g_frame_geometry_anchor
                                      ? W3CS_GEOMETRY_ANCHOR : 0),
                                   g_frame, g_frame_size);
        /* A dropped recovery frame deadlocks the stream: the relay discards
         * every later normal frame while it waits, and it ignores repair
         * requests while one recovery is outstanding. Regenerate the
         * recovery on the next captured frame, like anchors already do. */
        if (g_frame_keyframe && !queued)
            g_last_keyframe_tick = 0;
        if (g_frame_geometry_anchor) {
            finish_geometry_anchor_locked(queued);
            if (!queued) {
                g_force_geometry_anchor = TRUE;
                g_geometry_gop_position = 0;
            }
        }
        /* queue_payload counts its own rejections. Count only frames that
         * were never offered to the queue. */
        if (!queued && (g_frame_overflow || g_frame_dependency_failed))
            InterlockedIncrement(&g_dropped_frames);
        if (queued)
            commit_recorded_textures_locked();
        g_frame_size = 0;
        g_frame_overflow = FALSE;
        g_frame_dependency_failed = FALSE;
        g_frame_keyframe = FALSE;
        g_frame_geometry_anchor = FALSE;
        LeaveCriticalSection(&g_frame_lock);
        LeaveCriticalSection(&g_resource_lock);
    }
    g_draw_ordinal = 0;
    result = headless_fast_active() ? D3D_OK
        : g_device_original->Present(
            device, source, destination, window, dirty);
    now = pace_present();
    choose_next_capture(now);
    return result;
}

/* Coverage hooks. None of these D3D9 paths are represented in the command
 * stream. Each forwards to the real device so the game keeps working, then
 * reports a capture violation so command mode fails closed instead of
 * diverging silently. The 20-map fidelity gate depends on this detection. */
static HRESULT STDMETHODCALLTYPE hook_draw_primitive_up(
    IDirect3DDevice9 *device, D3DPRIMITIVETYPE type, UINT primitive_count,
    const void *vertex_data, UINT vertex_stride)
{
    MARK_HOOK(130);
    if (recording_frame())
        capture_violation("DrawPrimitiveUP");
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->DrawPrimitiveUP(
        device, type, primitive_count, vertex_data, vertex_stride);
}

static HRESULT STDMETHODCALLTYPE hook_draw_indexed_primitive_up(
    IDirect3DDevice9 *device, D3DPRIMITIVETYPE type, UINT min_vertex,
    UINT num_vertices, UINT primitive_count, const void *index_data,
    D3DFORMAT index_format, const void *vertex_data, UINT vertex_stride)
{
    MARK_HOOK(131);
    if (recording_frame())
        capture_violation("DrawIndexedPrimitiveUP");
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->DrawIndexedPrimitiveUP(
        device, type, min_vertex, num_vertices, primitive_count,
        index_data, index_format, vertex_data, vertex_stride);
}

static HRESULT STDMETHODCALLTYPE hook_draw_rect_patch(
    IDirect3DDevice9 *device, UINT handle, const float *segments,
    const D3DRECTPATCH_INFO *info)
{
    MARK_HOOK(132);
    if (recording_frame())
        capture_violation("DrawRectPatch");
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->DrawRectPatch(device, handle, segments, info);
}

static HRESULT STDMETHODCALLTYPE hook_draw_tri_patch(
    IDirect3DDevice9 *device, UINT handle, const float *segments,
    const D3DTRIPATCH_INFO *info)
{
    MARK_HOOK(133);
    if (recording_frame())
        capture_violation("DrawTriPatch");
    if (InterlockedCompareExchange(&g_no_raster, 0, 0))
        return D3D_OK;
    return g_device_original->DrawTriPatch(device, handle, segments, info);
}

static HRESULT STDMETHODCALLTYPE hook_multiply_transform(
    IDirect3DDevice9 *device, D3DTRANSFORMSTATETYPE state,
    const D3DMATRIX *matrix)
{
    MARK_HOOK(134);
    /* Mutates transform state without passing SetTransform, so the shadow
     * state diverges for every later frame. */
    capture_violation("MultiplyTransform");
    return headless_fast_active() ? D3D_OK
        : g_device_original->MultiplyTransform(device, state, matrix);
}

static HRESULT STDMETHODCALLTYPE hook_reset(
    IDirect3DDevice9 *device, D3DPRESENT_PARAMETERS *parameters)
{
    MARK_HOOK(135);
    /* Reset destroys default-pool resources and restores device defaults.
     * The recorder does not model that epoch change. */
    capture_violation("Reset");
    return g_device_original->Reset(device, parameters);
}

static HRESULT STDMETHODCALLTYPE hook_set_clip_plane(
    IDirect3DDevice9 *device, DWORD index, const float *plane)
{
    MARK_HOOK(136);
    capture_violation("SetClipPlane");
    return headless_fast_active() ? D3D_OK
        : g_device_original->SetClipPlane(device, index, plane);
}

static HRESULT STDMETHODCALLTYPE hook_set_vertex_shader(
    IDirect3DDevice9 *device, IDirect3DVertexShader9 *shader)
{
    MARK_HOOK(137);
    /* NULL selects the fixed-function pipeline and is the expected call. */
    if (shader)
        capture_violation("SetVertexShader");
    return g_device_original->SetVertexShader(device, shader);
}

static HRESULT STDMETHODCALLTYPE hook_set_pixel_shader(
    IDirect3DDevice9 *device, IDirect3DPixelShader9 *shader)
{
    MARK_HOOK(138);
    if (shader)
        capture_violation("SetPixelShader");
    return g_device_original->SetPixelShader(device, shader);
}

static HRESULT STDMETHODCALLTYPE hook_set_vertex_declaration(
    IDirect3DDevice9 *device, IDirect3DVertexDeclaration9 *declaration)
{
    MARK_HOOK(139);
    if (declaration)
        capture_violation("SetVertexDeclaration");
    return g_device_original->SetVertexDeclaration(device, declaration);
}

static HRESULT STDMETHODCALLTYPE hook_set_render_target(
    IDirect3DDevice9 *device, DWORD index, IDirect3DSurface9 *surface)
{
    MARK_HOOK(140);
    /* Restoring the original backbuffer is benign; anything else means
     * render-to-texture content the stream never carries. */
    if (index != 0 || (surface && surface != g_default_render_target))
        capture_violation("SetRenderTarget");
    return g_device_original->SetRenderTarget(device, index, surface);
}

static HRESULT STDMETHODCALLTYPE hook_set_depth_stencil_surface(
    IDirect3DDevice9 *device, IDirect3DSurface9 *surface)
{
    MARK_HOOK(141);
    if (surface && surface != g_default_depth_stencil)
        capture_violation("SetDepthStencilSurface");
    return g_device_original->SetDepthStencilSurface(device, surface);
}

static HRESULT STDMETHODCALLTYPE hook_stretch_rect(
    IDirect3DDevice9 *device, IDirect3DSurface9 *source,
    const RECT *source_rect, IDirect3DSurface9 *destination,
    const RECT *destination_rect, D3DTEXTUREFILTERTYPE filter)
{
    MARK_HOOK(142);
    capture_violation("StretchRect");
    return g_device_original->StretchRect(
        device, source, source_rect, destination, destination_rect, filter);
}

static HRESULT STDMETHODCALLTYPE hook_color_fill(
    IDirect3DDevice9 *device, IDirect3DSurface9 *surface, const RECT *rect,
    D3DCOLOR color)
{
    MARK_HOOK(143);
    capture_violation("ColorFill");
    return g_device_original->ColorFill(device, surface, rect, color);
}

static HRESULT STDMETHODCALLTYPE hook_create_cube_texture(
    IDirect3DDevice9 *device, UINT edge, UINT levels, DWORD usage,
    D3DFORMAT format, D3DPOOL pool, IDirect3DCubeTexture9 **texture,
    HANDLE *shared)
{
    HRESULT result;
    MARK_HOOK(144);
    result = g_device_original->CreateCubeTexture(
        device, edge, levels, usage, format, pool, texture, shared);
    if (SUCCEEDED(result))
        capture_violation("CreateCubeTexture");
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_create_volume_texture(
    IDirect3DDevice9 *device, UINT width, UINT height, UINT depth,
    UINT levels, DWORD usage, D3DFORMAT format, D3DPOOL pool,
    IDirect3DVolumeTexture9 **texture, HANDLE *shared)
{
    HRESULT result;
    MARK_HOOK(145);
    result = g_device_original->CreateVolumeTexture(
        device, width, height, depth, levels, usage, format, pool,
        texture, shared);
    if (SUCCEEDED(result))
        capture_violation("CreateVolumeTexture");
    return result;
}

static void STDMETHODCALLTYPE hook_set_gamma_ramp(
    IDirect3DDevice9 *device, UINT swap_chain, DWORD flags,
    const D3DGAMMARAMP *ramp)
{
    MARK_HOOK(146);
    capture_violation("SetGammaRamp");
    g_device_original->SetGammaRamp(device, swap_chain, flags, ramp);
}

static HRESULT STDMETHODCALLTYPE hook_create_state_block(
    IDirect3DDevice9 *device, D3DSTATEBLOCKTYPE type,
    IDirect3DStateBlock9 **state_block)
{
    HRESULT result;
    MARK_HOOK(147);
    result = g_device_original->CreateStateBlock(device, type, state_block);
    /* IDirect3DStateBlock9::Apply changes device state below every hooked
     * setter, so a used state block silently corrupts the shadow state. */
    if (SUCCEEDED(result))
        capture_violation("CreateStateBlock");
    return result;
}

static HRESULT STDMETHODCALLTYPE hook_begin_state_block(
    IDirect3DDevice9 *device)
{
    MARK_HOOK(148);
    capture_violation("BeginStateBlock");
    return g_device_original->BeginStateBlock(device);
}

static HRESULT STDMETHODCALLTYPE hook_set_stream_source_freq(
    IDirect3DDevice9 *device, UINT stream, UINT setting)
{
    MARK_HOOK(149);
    if (setting != 1u)
        capture_violation("SetStreamSourceFreq");
    return g_device_original->SetStreamSourceFreq(device, stream, setting);
}

static void hook_device(IDirect3DDevice9 *device)
{
    IDirect3DDevice9Vtbl *copy;
    if (!device)
        return;
    debug_message("w3cs: hook_device begin\n");
    copy = (IDirect3DDevice9Vtbl *)HeapAlloc(
        GetProcessHeap(), 0, sizeof(*copy));
    if (!copy)
        return;
    memcpy(copy, device->lpVtbl, sizeof(*copy));
    g_device_original = device->lpVtbl;
    /* Remember the original render target and depth surface. Restoring them
     * is a benign SetRenderTarget; any other target is a capture violation. */
    {
        IDirect3DSurface9 *surface = NULL;
        if (SUCCEEDED(g_device_original->GetRenderTarget(device, 0, &surface))
                && surface) {
            g_default_render_target = surface;
            IDirect3DSurface9_Release(surface);
        }
        surface = NULL;
        if (SUCCEEDED(g_device_original->GetDepthStencilSurface(
                device, &surface)) && surface) {
            g_default_depth_stencil = surface;
            IDirect3DSurface9_Release(surface);
        }
    }
    copy->SetCursorProperties = hook_set_cursor_properties;
    copy->ShowCursor = hook_show_cursor;
    copy->CreateTexture = hook_create_texture;
    copy->UpdateTexture = hook_update_texture;
    copy->UpdateSurface = hook_update_surface;
    copy->CreateVertexBuffer = hook_create_vertex_buffer;
    copy->CreateIndexBuffer = hook_create_index_buffer;
    copy->SetRenderState = hook_set_render_state;
    copy->SetTransform = hook_set_transform;
    copy->SetTexture = hook_set_texture;
    copy->SetFVF = hook_set_fvf;
    copy->SetStreamSource = hook_set_stream_source;
    copy->SetIndices = hook_set_indices;
    copy->SetMaterial = hook_set_material;
    copy->SetLight = hook_set_light;
    copy->LightEnable = hook_light_enable;
    copy->SetTextureStageState = hook_set_tss;
    copy->SetSamplerState = hook_set_sampler;
    copy->SetViewport = hook_set_viewport;
    copy->SetScissorRect = hook_set_scissor;
    copy->Clear = hook_clear;
    copy->DrawPrimitive = hook_draw_primitive;
    copy->DrawIndexedPrimitive = hook_draw_indexed_primitive;
    copy->Present = hook_present;
    copy->DrawPrimitiveUP = hook_draw_primitive_up;
    copy->DrawIndexedPrimitiveUP = hook_draw_indexed_primitive_up;
    copy->DrawRectPatch = hook_draw_rect_patch;
    copy->DrawTriPatch = hook_draw_tri_patch;
    copy->MultiplyTransform = hook_multiply_transform;
    copy->Reset = hook_reset;
    copy->SetClipPlane = hook_set_clip_plane;
    copy->SetVertexShader = hook_set_vertex_shader;
    copy->SetPixelShader = hook_set_pixel_shader;
    copy->SetVertexDeclaration = hook_set_vertex_declaration;
    copy->SetRenderTarget = hook_set_render_target;
    copy->SetDepthStencilSurface = hook_set_depth_stencil_surface;
    copy->StretchRect = hook_stretch_rect;
    copy->ColorFill = hook_color_fill;
    copy->CreateCubeTexture = hook_create_cube_texture;
    copy->CreateVolumeTexture = hook_create_volume_texture;
    copy->SetGammaRamp = hook_set_gamma_ramp;
    copy->CreateStateBlock = hook_create_state_block;
    copy->BeginStateBlock = hook_begin_state_block;
    copy->SetStreamSourceFreq = hook_set_stream_source_freq;
    device->lpVtbl = copy;
    debug_message("w3cs: hook_device complete\n");
}

static HRESULT STDMETHODCALLTYPE hook_create_device(
    IDirect3D9 *d3d, UINT adapter, D3DDEVTYPE type, HWND focus,
    DWORD behavior, D3DPRESENT_PARAMETERS *parameters,
    IDirect3DDevice9 **device)
{
    HRESULT result;
    debug_message("w3cs: CreateDevice begin\n");
    result = g_create_device_original(
        d3d, adapter, type, focus, behavior, parameters, device);
    debug_message(SUCCEEDED(result) ? "w3cs: CreateDevice succeeded\n"
                                   : "w3cs: CreateDevice failed\n");
    if (SUCCEEDED(result) && device && *device)
        hook_device(*device);
    return result;
}

static IDirect3D9 *load_real_d3d9(UINT sdk_version)
{
    char path[MAX_PATH];
    typedef IDirect3D9 *(WINAPI *create_fn)(UINT);
    union {
        FARPROC procedure;
        create_fn create;
    } symbol;
    UINT length = GetSystemDirectoryA(path, MAX_PATH);
    if (!length || length + 10 > MAX_PATH)
        return NULL;
    memcpy(path + length, "\\d3d9.dll", 10);
    g_real_d3d9 = LoadLibraryA(path);
    if (!g_real_d3d9)
        return NULL;
    symbol.procedure = GetProcAddress(g_real_d3d9, "Direct3DCreate9");
    return symbol.procedure ? symbol.create(sdk_version) : NULL;
}

__declspec(dllexport) IDirect3D9 *WINAPI Direct3DCreate9(UINT sdk_version)
{
    IDirect3D9 *d3d;
    HMODULE self;
    ensure_initialized();
    /* Warcraft loads d3d9 dynamically and releases that loader reference.
     * Pin the proxy because the real COM vtables continue to call our hooks. */
    GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
                       | GET_MODULE_HANDLE_EX_FLAG_PIN,
                       (LPCSTR)&g_once, &self);
    debug_message("w3cs: Direct3DCreate9 begin\n");
    d3d = load_real_d3d9(sdk_version);
    if (!d3d)
        return NULL;
    debug_message("w3cs: Direct3DCreate9 loaded real d3d9\n");
    if (d3d->lpVtbl->CreateDevice != hook_create_device) {
        g_create_device_original = d3d->lpVtbl->CreateDevice;
        debug_patch_addresses(d3d->lpVtbl, &d3d->lpVtbl->CreateDevice,
                              (void *)g_create_device_original,
                              (void *)hook_create_device);
        if (!patch_pointer((void **)&d3d->lpVtbl->CreateDevice,
                           (void *)hook_create_device))
            debug_message("w3cs: failed to patch CreateDevice\n");
    }
    debug_patch_addresses(d3d->lpVtbl, &d3d->lpVtbl->CreateDevice,
                          (void *)g_create_device_original,
                          (void *)hook_create_device);
    debug_message("w3cs: Direct3DCreate9 complete\n");
    return d3d;
}

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
    (void)reserved;
    if (reason == DLL_PROCESS_ATTACH)
        DisableThreadLibraryCalls(instance);
    return TRUE;
}
