/*
 * Plug and Pwn: Weaponizing Windows PnP Auto-Install
 * DEF CON 34 (2026) -- Alejandro Hernando & Borja Martinez
 *
 * Proof-of-concept released with the talk. Provided for education and
 * authorized security testing only. No warranty. Use only against systems
 * you own or are explicitly permitted to test.
 *
 * pnp_simulate.c
 *
 * Create a ROOT-enumerated device node with a chosen USB hardware ID and let
 * the PnP manager react to it, then report what driver Windows would use:
 *   - local DriverStore (DiInstallDevice)
 *   - Windows Update (IUpdateSearcher COM query)
 * With --install it also fires CM_Setup_DevNode so the Device Install Service
 * actually downloads and installs the WU driver, same as plugging in the real
 * hardware. The fake device is removed again on exit unless you pass --keep.
 *
 * Needs Administrator. Build with MSVC (see build.bat).
 *
 *   pnp_simulate.exe --vid 06CB --pid 0089 --wait 30
 *   pnp_simulate.exe --hwid "USB\VID_1199&PID_A000"
 *   pnp_simulate.exe --vid 06CB --pid 0089 --install --wait 300
 */

#include <windows.h>
#include <setupapi.h>
#include <newdev.h>
#include <cfgmgr32.h>
#include <wuapi.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>

#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "newdev.lib")
#pragma comment(lib, "cfgmgr32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "wuguid.lib")

#ifndef CM_SETUP_DEVNODE_READY
#define CM_SETUP_DEVNODE_READY 0x00000001
#endif

static volatile int g_stop = 0;

static void on_ctrl_c(int sig)
{
    (void)sig;
    g_stop = 1;
    fprintf(stderr, "\n[*] Ctrl+C, cleaning up...\n");
}

/* ----- small helpers ---------------------------------------------------- */

static int is_admin(void)
{
    int elevated = 0;
    HANDLE tok = NULL;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tok)) {
        TOKEN_ELEVATION e;
        DWORD n;
        if (GetTokenInformation(tok, TokenElevation, &e, sizeof(e), &n))
            elevated = e.TokenIsElevated;
        CloseHandle(tok);
    }
    return elevated;
}

static void w2u(const wchar_t *w, char *buf, int size)
{
    if (!w || WideCharToMultiByte(CP_UTF8, 0, w, -1, buf, size, NULL, NULL) <= 0)
        buf[0] = '\0';
}

static void str_upper(char *s)
{
    for (; *s; s++)
        if (*s >= 'a' && *s <= 'z') *s -= 32;
}

/* JSON string, escaped and quoted */
static void json_str(FILE *f, const char *s)
{
    fputc('"', f);
    for (; s && *s; s++) {
        unsigned char c = (unsigned char)*s;
        if (c == '"' || c == '\\') { fputc('\\', f); fputc(c, f); }
        else if (c == '\n') fputs("\\n", f);
        else if (c == '\r') fputs("\\r", f);
        else if (c == '\t') fputs("\\t", f);
        else if (c < 0x20)  fprintf(f, "\\u%04x", c);
        else fputc(c, f);
    }
    fputc('"', f);
}

/* ----- Windows Update search ------------------------------------------- */

typedef struct {
    char title[512];
    char description[1024];
    char driver_class[256];
    char driver_manufacturer[256];
    char driver_version[64];
} wu_driver_t;

typedef struct {
    wu_driver_t *drivers;
    int count;
    int cap;
    HRESULT error;
    char error_msg[256];
    int available;
} wu_result_t;

static void wu_add(wu_result_t *r, const wu_driver_t *d)
{
    if (r->count >= r->cap) {
        int nc = r->cap ? r->cap * 2 : 8;
        wu_driver_t *p = realloc(r->drivers, nc * sizeof(*p));
        if (!p) return;
        r->drivers = p;
        r->cap = nc;
    }
    r->drivers[r->count++] = *d;
}

static void bstr_to_utf8(BSTR b, char *buf, int size)
{
    buf[0] = '\0';
    if (b) WideCharToMultiByte(CP_UTF8, 0, b, -1, buf, size, NULL, NULL);
}

/* Runs inside an STA thread (see run_wu_search). COM must already be up. */
static void wu_search(const char *hwid, wu_result_t *r)
{
    IUpdateSession *session = NULL;
    IUpdateSearcher *searcher = NULL;
    ISearchResult *result = NULL;
    IUpdateCollection *updates = NULL;
    HRESULT hr;

    hr = CoCreateInstance(&CLSID_UpdateSession, NULL, CLSCTX_INPROC_SERVER,
                          &IID_IUpdateSession, (void **)&session);
    if (FAILED(hr)) {
        r->error = hr;
        _snprintf(r->error_msg, sizeof(r->error_msg),
                  "CoCreateInstance(UpdateSession): 0x%08lX", hr);
        return;
    }

    hr = session->lpVtbl->CreateUpdateSearcher(session, &searcher);
    if (FAILED(hr)) {
        r->error = hr;
        _snprintf(r->error_msg, sizeof(r->error_msg),
                  "CreateUpdateSearcher: 0x%08lX", hr);
        session->lpVtbl->Release(session);
        return;
    }

    /* Force an online scan. A cached search on a fresh box often returns
     * nothing because the first WU sync hasn't run yet. */
    searcher->lpVtbl->put_ServerSelection(searcher, 2 /* ssWindowsUpdate */);
    searcher->lpVtbl->put_Online(searcher, VARIANT_TRUE);

    BSTR criteria = SysAllocString(L"IsInstalled=0 AND Type='Driver'");
    fprintf(stderr, "[*] Searching Windows Update (this takes a moment)...\n");
    fflush(stderr);

    /* Search() can AV if the COM/WU state is wedged; don't take the process
     * down with it. */
    __try {
        hr = searcher->lpVtbl->Search(searcher, criteria, &result);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        DWORD code = GetExceptionCode();
        r->error = (HRESULT)code;
        _snprintf(r->error_msg, sizeof(r->error_msg),
                  "WU Search exception: 0x%08lX", code);
        SysFreeString(criteria);
        searcher->lpVtbl->Release(searcher);
        session->lpVtbl->Release(session);
        return;
    }
    SysFreeString(criteria);

    if (FAILED(hr)) {
        r->error = hr;
        _snprintf(r->error_msg, sizeof(r->error_msg), "WU Search: 0x%08lX", hr);
        searcher->lpVtbl->Release(searcher);
        session->lpVtbl->Release(session);
        return;
    }

    hr = result->lpVtbl->get_Updates(result, &updates);
    if (FAILED(hr)) {
        r->error = hr;
        result->lpVtbl->Release(result);
        searcher->lpVtbl->Release(searcher);
        session->lpVtbl->Release(session);
        return;
    }

    LONG count = 0;
    updates->lpVtbl->get_Count(updates, &count);
    r->available = 1;
    fprintf(stderr, "[*] WU returned %ld driver update(s), filtering by HWID\n", count);

    char target[512];
    strncpy(target, hwid, sizeof(target) - 1);
    target[sizeof(target) - 1] = '\0';
    str_upper(target);

    for (LONG i = 0; i < count; i++) {
        IUpdate *upd = NULL;
        if (FAILED(updates->lpVtbl->get_Item(updates, i, &upd))) continue;

        BSTR title = NULL;
        upd->lpVtbl->get_Title(upd, &title);

        IWindowsDriverUpdate *drv = NULL;
        if (SUCCEEDED(upd->lpVtbl->QueryInterface(upd, &IID_IWindowsDriverUpdate,
                                                  (void **)&drv)) && drv) {
            BSTR drv_hwid = NULL;
            drv->lpVtbl->get_DriverHardwareID(drv, &drv_hwid);

            int match = 0;
            if (drv_hwid) {
                char s[512];
                bstr_to_utf8(drv_hwid, s, sizeof(s));
                str_upper(s);
                if (strstr(s, target)) match = 1;
                SysFreeString(drv_hwid);
            }

            if (match) {
                wu_driver_t d;
                memset(&d, 0, sizeof(d));
                bstr_to_utf8(title, d.title, sizeof(d.title));

                BSTR desc = NULL, cls = NULL, mfg = NULL;
                DATE ver = 0;
                upd->lpVtbl->get_Description(upd, &desc);
                drv->lpVtbl->get_DriverClass(drv, &cls);
                drv->lpVtbl->get_DriverManufacturer(drv, &mfg);
                drv->lpVtbl->get_DriverVerDate(drv, &ver);

                bstr_to_utf8(desc, d.description, sizeof(d.description));
                bstr_to_utf8(cls, d.driver_class, sizeof(d.driver_class));
                bstr_to_utf8(mfg, d.driver_manufacturer, sizeof(d.driver_manufacturer));
                if (ver != 0) {
                    SYSTEMTIME st;
                    if (VariantTimeToSystemTime(ver, &st))
                        _snprintf(d.driver_version, sizeof(d.driver_version),
                                  "%04d-%02d-%02d", st.wYear, st.wMonth, st.wDay);
                }
                if (desc) SysFreeString(desc);
                if (cls)  SysFreeString(cls);
                if (mfg)  SysFreeString(mfg);

                wu_add(r, &d);
            }
            drv->lpVtbl->Release(drv);
        }

        if (title) SysFreeString(title);
        upd->lpVtbl->Release(upd);
    }

    updates->lpVtbl->Release(updates);
    result->lpVtbl->Release(result);
    searcher->lpVtbl->Release(searcher);
    session->lpVtbl->Release(session);
}

/* WU COM wants an STA, but DiInstallDevice leaves us in an MTA, so run the
 * search on its own apartment-threaded thread. */
typedef struct { const char *hwid; wu_result_t *result; } wu_ctx_t;

static DWORD WINAPI wu_thread(LPVOID param)
{
    wu_ctx_t *c = param;
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    wu_search(c->hwid, c->result);
    CoUninitialize();
    return 0;
}

static void run_wu_search(const char *hwid, wu_result_t *r)
{
    wu_ctx_t ctx = { hwid, r };
    HANDLE t = CreateThread(NULL, 0, wu_thread, &ctx, 0, NULL);
    if (!t) return;
    WaitForSingleObject(t, 120000);
    CloseHandle(t);
}

static void wu_free(wu_result_t *r)
{
    free(r->drivers);
    r->drivers = NULL;
    r->count = r->cap = 0;
}

static void wu_to_json(FILE *f, const wu_result_t *r)
{
    fputs("{", f);
    if (r->error) {
        fprintf(f, "\"found\":false,\"error\":");
        json_str(f, r->error_msg);
        fprintf(f, ",\"error_code\":%lu", (unsigned long)r->error);
    } else {
        fprintf(f, "\"found\":%s,\"available\":%s",
                r->count > 0 ? "true" : "false",
                r->available ? "true" : "false");
    }
    fprintf(f, ",\"update_count\":%d,\"updates\":[", r->count);
    for (int i = 0; i < r->count; i++) {
        const wu_driver_t *d = &r->drivers[i];
        if (i) fputc(',', f);
        fputs("{\"title\":", f); json_str(f, d->title);
        if (d->description[0])         { fputs(",\"description\":", f); json_str(f, d->description); }
        if (d->driver_class[0])        { fputs(",\"driver_class\":", f); json_str(f, d->driver_class); }
        if (d->driver_manufacturer[0]) { fputs(",\"driver_manufacturer\":", f); json_str(f, d->driver_manufacturer); }
        if (d->driver_version[0])      { fputs(",\"driver_version\":", f); json_str(f, d->driver_version); }
        fputc('}', f);
    }
    fputs("]}", f);
}

/* ----- device create / remove ------------------------------------------ */

typedef struct {
    HDEVINFO devinfo;
    SP_DEVINFO_DATA data;
    wchar_t instance_id[256];
    int created;
    int driver_installed;
} device_t;

/* Concatenate UTF-8 ids into a double-null-terminated wide REG_MULTI_SZ.
 * Returns byte size (including both nulls). */
static DWORD build_multisz(char ids[][512], int n, wchar_t *out, int out_chars)
{
    wchar_t *p = out;
    for (int i = 0; i < n; i++) {
        int len = MultiByteToWideChar(CP_UTF8, 0, ids[i], -1, p,
                                      (int)((out + out_chars) - p));
        if (len > 0) p += len;
    }
    *p = L'\0';
    return (DWORD)((p - out + 1) * sizeof(wchar_t));
}

static int device_create(device_t *dev, char hwids[][512], int n_hw,
                         char compat[][512], int n_compat,
                         const wchar_t *desc, int do_install)
{
    memset(dev, 0, sizeof(*dev));
    dev->devinfo = INVALID_HANDLE_VALUE;

    wchar_t hw_multi[2048];
    DWORD hw_size = build_multisz(hwids, n_hw, hw_multi, 2048);

    HDEVINFO di = SetupDiCreateDeviceInfoList(NULL, NULL);
    if (di == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "[-] SetupDiCreateDeviceInfoList: 0x%08lX\n", GetLastError());
        return -1;
    }

    SP_DEVINFO_DATA data;
    data.cbSize = sizeof(data);
    static const GUID zero_guid = {0};
    if (!SetupDiCreateDeviceInfoW(di, L"PnPSimDevice", &zero_guid, desc, NULL,
                                  DICD_GENERATE_ID, &data)) {
        fprintf(stderr, "[-] SetupDiCreateDeviceInfo: 0x%08lX\n", GetLastError());
        SetupDiDestroyDeviceInfoList(di);
        return -1;
    }

    if (!SetupDiSetDeviceRegistryPropertyW(di, &data, SPDRP_HARDWAREID,
                                           (const BYTE *)hw_multi, hw_size)) {
        fprintf(stderr, "[-] set HARDWAREID: 0x%08lX\n", GetLastError());
        SetupDiDestroyDeviceInfoList(di);
        return -1;
    }

    if (n_compat > 0) {
        wchar_t c_multi[2048];
        DWORD c_size = build_multisz(compat, n_compat, c_multi, 2048);
        SetupDiSetDeviceRegistryPropertyW(di, &data, SPDRP_COMPATIBLEIDS,
                                          (const BYTE *)c_multi, c_size);
    }

    fprintf(stderr, "[*] Registering device (DIF_REGISTERDEVICE)...\n");
    if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, di, &data)) {
        fprintf(stderr, "[-] DIF_REGISTERDEVICE: 0x%08lX\n", GetLastError());
        SetupDiDestroyDeviceInfoList(di);
        return -1;
    }

    if (SetupDiGetDeviceInstanceIdW(di, &data, dev->instance_id,
                                    sizeof(dev->instance_id) / sizeof(wchar_t), NULL)) {
        char id[256];
        w2u(dev->instance_id, id, sizeof(id));
        fprintf(stderr, "[+] Registered: %s\n", id);
    }

    /* CM_Setup_DevNode(READY) is what the kernel calls after a bus driver
     * enumerates a real PDO. It kicks off DsmSvc, which searches DriverStore
     * and Windows Update and installs the match. Only do it on --install. */
    if (do_install) {
        DEVINST inst = 0;
        CONFIGRET cr = CM_Locate_DevNodeW(&inst, dev->instance_id, CM_LOCATE_DEVNODE_NORMAL);
        if (cr == CR_SUCCESS) {
            fprintf(stderr, "[*] CM_Setup_DevNode(READY)...\n");
            cr = CM_Setup_DevNode(inst, CM_SETUP_DEVNODE_READY);
            if (cr == CR_SUCCESS)
                fprintf(stderr, "[+] Setup queued, DsmSvc will fetch the driver\n");
            else
                fprintf(stderr, "[-] CM_Setup_DevNode: 0x%lX\n", cr);
        } else {
            fprintf(stderr, "[-] CM_Locate_DevNode: 0x%lX\n", cr);
        }
    } else {
        fprintf(stderr, "[*] Query only (pass --install to download+install)\n");
    }

    /* Explicit local search too, so setupapi.dev.log shows the DriverStore
     * lookup even when nothing matches. */
    fprintf(stderr, "[*] DiInstallDevice (local DriverStore)...\n");
    BOOL reboot = FALSE;
    if (DiInstallDevice(NULL, di, &data, NULL, 0, &reboot)) {
        fprintf(stderr, "[+] Local driver installed\n");
        dev->driver_installed = 1;
        if (reboot) fprintf(stderr, "[!] Reboot required\n");
    } else {
        fprintf(stderr, "[-] DiInstallDevice: 0x%08lX (no local driver)\n", GetLastError());
    }

    dev->devinfo = di;
    dev->data = data;
    dev->created = 1;
    return 0;
}

static void device_remove(device_t *dev)
{
    if (!dev->created || dev->devinfo == INVALID_HANDLE_VALUE) return;

    fprintf(stderr, "[*] Removing device (DIF_REMOVE)...\n");
    SP_REMOVEDEVICE_PARAMS rp;
    memset(&rp, 0, sizeof(rp));
    rp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    rp.ClassInstallHeader.InstallFunction = DIF_REMOVE;
    rp.Scope = DI_REMOVEDEVICE_GLOBAL;

    if (SetupDiSetClassInstallParamsW(dev->devinfo, &dev->data,
                                      &rp.ClassInstallHeader, sizeof(rp)) &&
        SetupDiCallClassInstaller(DIF_REMOVE, dev->devinfo, &dev->data)) {
        fprintf(stderr, "[+] Device removed\n");
    } else {
        fprintf(stderr, "[-] DIF_REMOVE: 0x%08lX\n", GetLastError());
    }

    SetupDiDestroyDeviceInfoList(dev->devinfo);
    dev->devinfo = INVALID_HANDLE_VALUE;
    dev->created = 0;
}

/* ----- PnP notification observer --------------------------------------- */

typedef struct {
    char type[64];
    char instance_id[256];
    char detail[512];
    DWORD t_ms;
} pnp_event_t;

typedef struct {
    pnp_event_t *events;
    int count;
    int cap;
    HCMNOTIFICATION handle;
    DWORD start;
    int active;
} observer_t;

static const char *action_name(CM_NOTIFY_ACTION a)
{
    switch (a) {
    case CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL:   return "DEVICE_INTERFACE_ARRIVAL";
    case CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL:   return "DEVICE_INTERFACE_REMOVAL";
    case CM_NOTIFY_ACTION_DEVICEQUERYREMOVE:        return "DEVICE_QUERY_REMOVE";
    case CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED:  return "DEVICE_QUERY_REMOVE_FAILED";
    case CM_NOTIFY_ACTION_DEVICEREMOVEPENDING:      return "DEVICE_REMOVE_PENDING";
    case CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE:     return "DEVICE_REMOVE_COMPLETE";
    case CM_NOTIFY_ACTION_DEVICECUSTOMEVENT:        return "DEVICE_CUSTOM_EVENT";
    case CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED: return "DEVICE_INSTANCE_ENUMERATED";
    case CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED:    return "DEVICE_INSTANCE_STARTED";
    case CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED:    return "DEVICE_INSTANCE_REMOVED";
    default:                                        return "UNKNOWN";
    }
}

static DWORD CALLBACK pnp_callback(HCMNOTIFICATION h, PVOID ctx,
                                   CM_NOTIFY_ACTION action,
                                   PCM_NOTIFY_EVENT_DATA data, DWORD size)
{
    (void)h; (void)size;
    observer_t *obs = ctx;
    if (!obs->active) return ERROR_SUCCESS;

    if (obs->count >= obs->cap) {
        int nc = obs->cap ? obs->cap * 2 : 64;
        pnp_event_t *p = realloc(obs->events, nc * sizeof(*p));
        if (!p) return ERROR_SUCCESS;
        obs->events = p;
        obs->cap = nc;
    }

    pnp_event_t *ev = &obs->events[obs->count++];
    memset(ev, 0, sizeof(*ev));
    strncpy(ev->type, action_name(action), sizeof(ev->type) - 1);
    ev->t_ms = GetTickCount() - obs->start;

    if (data) {
        if (data->FilterType == CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE)
            w2u(data->u.DeviceInterface.SymbolicLink, ev->detail, sizeof(ev->detail));
        else if (data->FilterType == CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE)
            w2u(data->u.DeviceInstance.InstanceId, ev->instance_id, sizeof(ev->instance_id));
    }

    fprintf(stderr, "  [PnP] %s", ev->type);
    if (ev->instance_id[0]) fprintf(stderr, " - %s", ev->instance_id);
    if (ev->detail[0])      fprintf(stderr, " (%s)", ev->detail);
    fputc('\n', stderr);
    return ERROR_SUCCESS;
}

static int observer_start(observer_t *obs)
{
    memset(obs, 0, sizeof(*obs));
    obs->start = GetTickCount();
    obs->active = 1;

    CM_NOTIFY_FILTER filter;
    memset(&filter, 0, sizeof(filter));
    filter.cbSize = sizeof(filter);
    filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE;
    filter.Flags = CM_NOTIFY_FILTER_FLAG_ALL_DEVICE_INSTANCES;

    CONFIGRET cr = CM_Register_Notification(&filter, obs, pnp_callback, &obs->handle);
    if (cr != CR_SUCCESS) {
        fprintf(stderr, "[-] CM_Register_Notification: 0x%lX\n", cr);
        return -1;
    }
    fprintf(stderr, "[+] PnP observer started\n");
    return 0;
}

static void observer_stop(observer_t *obs)
{
    obs->active = 0;
    if (obs->handle) {
        CM_Unregister_Notification(obs->handle);
        obs->handle = NULL;
    }
    fprintf(stderr, "[*] PnP observer stopped (%d events)\n", obs->count);
}

static void observer_to_json(FILE *f, const observer_t *obs)
{
    fputs("[", f);
    for (int i = 0; i < obs->count; i++) {
        const pnp_event_t *ev = &obs->events[i];
        if (i) fputc(',', f);
        fputs("{\"event_type\":", f); json_str(f, ev->type);
        fprintf(f, ",\"timestamp_ms\":%lu", ev->t_ms);
        if (ev->instance_id[0]) { fputs(",\"device_instance_id\":", f); json_str(f, ev->instance_id); }
        if (ev->detail[0])      { fputs(",\"detail\":", f); json_str(f, ev->detail); }
        fputc('}', f);
    }
    fputs("]", f);
}

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

static void usage(const char *prog)
{
    fprintf(stderr, "Usage: %s --vid XXXX --pid XXXX [options]\n", prog);
    fprintf(stderr, "       %s --hwid \"USB\\VID_XXXX&PID_XXXX\" [options]\n\n", prog);
    fprintf(stderr, "  --vid XXXX     USB vendor ID (hex)\n");
    fprintf(stderr, "  --pid XXXX     USB product ID (hex)\n");
    fprintf(stderr, "  --hwid STR     full hardware ID (may repeat, up to 4)\n");
    fprintf(stderr, "  --compat STR   compatible ID (may repeat, up to 4)\n");
    fprintf(stderr, "  --wait N       seconds to observe PnP events (default 15)\n");
    fprintf(stderr, "  --keep         leave the device registered on exit\n");
    fprintf(stderr, "  --no-wu        skip the Windows Update query\n");
    fprintf(stderr, "  --install      actually download+install via WU (CM_Setup_DevNode)\n");
    fprintf(stderr, "  --help         this help\n\n");
    fprintf(stderr, "Requires Administrator.\n");
}

int main(int argc, char *argv[])
{
    char *vid = NULL, *pid = NULL;
    char hwids[4][512] = {{0}};
    char compat[4][512] = {{0}};
    int n_hw = 0, n_compat = 0;
    int wait_s = 15, keep = 0, skip_wu = 0, install = 0;

    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--vid") && i + 1 < argc) vid = argv[++i];
        else if (!strcmp(argv[i], "--pid") && i + 1 < argc) pid = argv[++i];
        else if (!strcmp(argv[i], "--hwid") && i + 1 < argc) {
            if (n_hw < 4) strncpy(hwids[n_hw++], argv[++i], 511);
        } else if (!strcmp(argv[i], "--compat") && i + 1 < argc) {
            if (n_compat < 4) strncpy(compat[n_compat++], argv[++i], 511);
        } else if (!strcmp(argv[i], "--wait") && i + 1 < argc) wait_s = atoi(argv[++i]);
        else if (!strcmp(argv[i], "--keep")) keep = 1;
        else if (!strcmp(argv[i], "--no-wu")) skip_wu = 1;
        else if (!strcmp(argv[i], "--install")) install = 1;
        else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
            usage(argv[0]);
            return 0;
        }
    }

    /* Build the hardware ID from VID/PID unless one was given directly. */
    if (vid && pid && n_hw == 0) {
        unsigned v = (unsigned)strtoul(vid, NULL, 16);
        unsigned p = (unsigned)strtoul(pid, NULL, 16);
        if (!v || v > 0xFFFF || !p || p > 0xFFFF) {
            fprintf(stderr, "[-] Invalid VID/PID\n");
            return 1;
        }
        _snprintf(hwids[0], 512, "USB\\VID_%04X&PID_%04X", v, p);
        n_hw = 1;
    }
    if (n_hw == 0) {
        usage(argv[0]);
        return 1;
    }

    SetConsoleOutputCP(CP_UTF8);
    if (!is_admin())
        fprintf(stderr, "[!] Not elevated. Device simulation needs Administrator.\n");

    fprintf(stderr, "[*] Hardware ID: %s\n", hwids[0]);
    fprintf(stderr, "[*] Observing for %d s\n", wait_s);
    signal(SIGINT, on_ctrl_c);

    observer_t obs;
    observer_start(&obs);

    wchar_t desc[300];
    _snwprintf(desc, 300, L"PnP Simulation (%hs)", hwids[0]);

    device_t dev;
    int created = (device_create(&dev, hwids, n_hw, compat, n_compat, desc, install) == 0);

    wu_result_t wu;
    memset(&wu, 0, sizeof(wu));
    int wu_done = 0;
    if (created && !skip_wu && !g_stop) {
        run_wu_search(hwids[0], &wu);
        wu_done = 1;
        if (wu.error)
            fprintf(stderr, "[-] WU error: %s\n", wu.error_msg);
        else if (wu.count > 0)
            fprintf(stderr, "[+] Windows Update has %d driver(s)!\n", wu.count);
        else
            fprintf(stderr, "[*] Windows Update: no match\n");
    }

    if (created) {
        fprintf(stderr, "[+] Watching PnP for %d s...\n", wait_s);
        for (int i = 0; i < wait_s * 10 && !g_stop; i++)
            Sleep(100);
    } else {
        fprintf(stderr, "[-] Device creation failed\n");
    }

    observer_stop(&obs);

    /* JSON result to stdout */
    FILE *f = stdout;
    fputs("{", f);
    fputs("\"hardware_id\":", f); json_str(f, hwids[0]);
    if (vid) { fputs(",\"vid\":", f); json_str(f, vid); }
    if (pid) { fputs(",\"pid\":", f); json_str(f, pid); }
    fprintf(f, ",\"device_created\":%s", created ? "true" : "false");
    if (created) {
        char id[256];
        w2u(dev.instance_id, id, sizeof(id));
        fputs(",\"instance_id\":", f); json_str(f, id);
    }
    fprintf(f, ",\"driverstore_match\":%s", (created && dev.driver_installed) ? "true" : "false");
    if (wu_done) {
        fputs(",\"windows_update\":", f);
        wu_to_json(f, &wu);
    }
    fputs(",\"pnp_events\":", f);
    observer_to_json(f, &obs);
    fputs("}\n", f);

    if (wu_done) wu_free(&wu);
    free(obs.events);

    if (created && !keep)
        device_remove(&dev);
    else if (created && keep)
        fprintf(stderr, "[*] Device kept (--keep). Remove with pnputil /remove-device.\n");

    return created ? 0 : 1;
}
