From 7f9ca71ed825f4bae1462b6dc6c2d75bb879ac27 Mon Sep 17 00:00:00 2001
From: Marius Greuel <greuelm@mgtek.com>
Date: Sat, 18 Jan 2020 19:07:02 +0200
Subject: [PATCH] Add support for WinUSB devices

---
 external/libusb/libusb.vcxproj         |    3 +-
 external/libusb/libusb.vcxproj.filters |    3 +-
 external/libusb/src/libwinusb.h        | 1299 ++++++++++++++++++++++++
 external/libusb/src/windows.c          | 1281 -----------------------
 external/libusb/src/windows.cpp        |  470 +++++++++
 5 files changed, 1773 insertions(+), 1283 deletions(-)
 create mode 100644 external/libusb/src/libwinusb.h
 delete mode 100644 external/libusb/src/windows.c
 create mode 100644 external/libusb/src/windows.cpp

diff --git a/external/libusb/libusb.vcxproj b/external/libusb/libusb.vcxproj
index ab0f529c..ddd33f5c 100644
--- a/external/libusb/libusb.vcxproj
+++ b/external/libusb/libusb.vcxproj
@@ -23,13 +23,14 @@
     <ClCompile Include="src\error.c" />
     <ClCompile Include="src\registry.c" />
     <ClCompile Include="src\usb.c" />
-    <ClCompile Include="src\windows.c" />
+    <ClCompile Include="src\windows.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="include\lusb0_usb.h" />
     <ClInclude Include="src\driver_api.h" />
     <ClInclude Include="src\error.h" />
     <ClInclude Include="src\libusb-win32_version.h" />
+    <ClInclude Include="src\libwinusb.h" />
     <ClInclude Include="src\registry.h" />
     <ClInclude Include="src\usbi.h" />
   </ItemGroup>
diff --git a/external/libusb/libusb.vcxproj.filters b/external/libusb/libusb.vcxproj.filters
index 0fd41508..8eaf05b1 100644
--- a/external/libusb/libusb.vcxproj.filters
+++ b/external/libusb/libusb.vcxproj.filters
@@ -5,7 +5,7 @@
     <ClCompile Include="src\error.c" />
     <ClCompile Include="src\registry.c" />
     <ClCompile Include="src\usb.c" />
-    <ClCompile Include="src\windows.c" />
+    <ClCompile Include="src\windows.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="include\lusb0_usb.h" />
@@ -14,5 +14,6 @@
     <ClInclude Include="src\libusb-win32_version.h" />
     <ClInclude Include="src\registry.h" />
     <ClInclude Include="src\usbi.h" />
+    <ClInclude Include="src\libwinusb.h" />
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/external/libusb/src/libwinusb.h b/external/libusb/src/libwinusb.h
new file mode 100644
index 00000000..c19c2735
--- /dev/null
+++ b/external/libusb/src/libwinusb.h
@@ -0,0 +1,1299 @@
+//
+// libwinusb.h
+// Copyright (C) 2019 Marius Greuel. All rights reserved.
+//
+
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <cassert>
+#include <cerrno>
+#include <memory>
+#include <string>
+#include <vector>
+#include <windows.h>
+#include <winusb.h>
+#include <winioctl.h>
+#include <devpkey.h>
+#include <usbiodef.h>
+#include <setupapi.h>
+#include <strsafe.h>
+#include <lusb0_usb.h>
+
+#pragma comment(lib, "winusb")
+#pragma comment(lib, "setupapi")
+
+namespace LibWinUsb
+{
+    class Handle
+    {
+    public:
+        Handle() = default;
+
+        ~Handle()
+        {
+            Close();
+        }
+
+        Handle(const Handle&) = delete;
+        Handle& operator=(const Handle& other) = delete;
+        Handle(Handle&&) = default;
+        Handle& operator=(Handle&& other) = default;
+
+        operator HANDLE() const noexcept
+        {
+            return m_handle;
+        }
+
+        void Close() noexcept
+        {
+            if (m_handle != nullptr)
+            {
+                CloseHandle(m_handle);
+                m_handle = nullptr;
+            }
+        }
+
+        void Attach(HANDLE handle) noexcept
+        {
+            assert(m_handle == nullptr);
+            m_handle = handle;
+        }
+
+        HANDLE Detach() noexcept
+        {
+            HANDLE handle = m_handle;
+            m_handle = nullptr;
+            return handle;
+        }
+
+    protected:
+        HANDLE m_handle = nullptr;
+    };
+
+    class Event : public Handle
+    {
+    public:
+        Event() = default;
+        Event(const Event&) = delete;
+        Event& operator=(const Event& other) = delete;
+        Event(Event&&) = default;
+        Event& operator=(Event&& other) = default;
+
+        HRESULT Create(BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName = nullptr, LPSECURITY_ATTRIBUTES lpAttributes = nullptr) noexcept
+        {
+            HANDLE hEvent = CreateEventW(lpAttributes, bManualReset, bInitialState, lpName);
+            if (hEvent == nullptr)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            Close();
+            Attach(hEvent);
+            return S_OK;
+        }
+
+        HRESULT Open(LPCWSTR lpName, DWORD dwDesiredAccess = EVENT_ALL_ACCESS, BOOL bInheritHandle = FALSE) noexcept
+        {
+            HANDLE hEvent = OpenEventW(dwDesiredAccess, bInheritHandle, lpName);
+            if (hEvent == nullptr)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            Close();
+            Attach(hEvent);
+            return S_OK;
+        }
+
+        HRESULT Set() noexcept
+        {
+            if (!SetEvent(m_handle))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            return S_OK;
+        }
+
+        HRESULT Reset() noexcept
+        {
+            if (!ResetEvent(m_handle))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            return S_OK;
+        }
+    };
+
+    class AutoResetEvent : public Event
+    {
+    public:
+        HRESULT Create(BOOL bInitialState = FALSE, LPCWSTR lpName = nullptr, LPSECURITY_ATTRIBUTES lpAttributes = nullptr)
+        {
+            return Event::Create(FALSE, bInitialState, lpName, lpAttributes);
+        }
+    };
+
+    class ManualResetEvent : public Event
+    {
+    public:
+        HRESULT Create(BOOL bInitialState = FALSE, LPCWSTR lpName = nullptr, LPSECURITY_ATTRIBUTES lpAttributes = nullptr)
+        {
+            return Event::Create(TRUE, bInitialState, lpName, lpAttributes);
+        }
+    };
+
+    class Encoding
+    {
+    public:
+        static std::string ToAnsi(const std::wstring& text)
+        {
+            std::string str;
+
+            if (!text.empty())
+            {
+                int chars = ::WideCharToMultiByte(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), nullptr, 0, nullptr, nullptr);
+                if (chars == 0)
+                {
+                    return std::string();
+                }
+
+                str.resize(chars);
+                chars = ::WideCharToMultiByte(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), &str[0], static_cast<int>(str.size()), nullptr, nullptr);
+                if (chars == 0)
+                {
+                    return std::string();
+                }
+            }
+
+            return str;
+        }
+
+        static std::wstring ToUnicode(const std::string& text)
+        {
+            std::wstring str;
+
+            if (!text.empty())
+            {
+                int chars = ::MultiByteToWideChar(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), nullptr, 0);
+                if (chars == 0)
+                {
+                    return std::wstring();
+                }
+
+                str.resize(chars);
+                chars = ::MultiByteToWideChar(CP_ACP, 0, text.c_str(), static_cast<int>(text.size()), &str[0], static_cast<int>(str.size()));
+                if (chars == 0)
+                {
+                    return std::wstring();
+                }
+            }
+
+            return str;
+        }
+    };
+
+    class DeviceInfo : public SP_DEVINFO_DATA
+    {
+    public:
+        DeviceInfo()
+        {
+            static_cast<SP_DEVINFO_DATA&>(*this) = { sizeof(SP_DEVINFO_DATA) };
+        }
+
+        DeviceInfo(const DeviceInfo&) = delete;
+        DeviceInfo& operator=(const DeviceInfo& other) = delete;
+        DeviceInfo(DeviceInfo&&) = delete;
+        DeviceInfo& operator=(DeviceInfo&& other) = delete;
+
+    public:
+        HRESULT Open(HDEVINFO hDeviceInfoSet, LPCWSTR pszDeviceInstanceId, HWND hwndParent = nullptr, DWORD dwOpenFlags = 0)
+        {
+            if (!SetupDiOpenDeviceInfoW(hDeviceInfoSet, pszDeviceInstanceId, hwndParent, dwOpenFlags, this))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, bool& value)
+        {
+            DEVPROP_BOOLEAN buffer{};
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), &dwRequiredSize, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (type != DEVPROP_TYPE_BOOLEAN)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            value = buffer != DEVPROP_FALSE;
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, uint32_t& value)
+        {
+            uint32_t buffer{};
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), &dwRequiredSize, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (type != DEVPROP_TYPE_UINT32)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            value = buffer;
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, GUID& value)
+        {
+            GUID buffer{};
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, reinterpret_cast<PBYTE>(&buffer), sizeof(buffer), &dwRequiredSize, 0))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            if (type != DEVPROP_TYPE_GUID)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            value = buffer;
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, std::wstring& value)
+        {
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, nullptr, 0, &dwRequiredSize, 0))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            if (type != DEVPROP_TYPE_STRING)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            std::vector<uint8_t> buffer(dwRequiredSize);
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, buffer.data(), dwRequiredSize, nullptr, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            LPCWSTR pszValue = reinterpret_cast<LPCWSTR>(buffer.data());
+            DWORD dwChars = dwRequiredSize / sizeof(WCHAR);
+            if (dwChars > 0 && pszValue[dwChars - 1] == 0)
+            {
+                dwChars--;
+            }
+
+            value = std::wstring(pszValue, dwChars);
+            return S_OK;
+        }
+
+        HRESULT GetProperty(HDEVINFO hDeviceInfoSet, const DEVPROPKEY* key, std::vector<std::wstring>& values)
+        {
+            DEVPROPTYPE type = DEVPROP_TYPE_EMPTY;
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, nullptr, 0, &dwRequiredSize, 0))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            if (type != DEVPROP_TYPE_STRING_LIST)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_DATATYPE);
+            }
+
+            std::vector<uint8_t> buffer(dwRequiredSize);
+            if (!SetupDiGetDevicePropertyW(hDeviceInfoSet, this, key, &type, buffer.data(), dwRequiredSize, nullptr, 0))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            auto data = reinterpret_cast<const wchar_t*>(buffer.data());
+            while (*data != 0)
+            {
+                values.push_back(data);
+                data += wcslen(data) + 1;
+            }
+
+            return S_OK;
+        }
+    };
+
+    class DeviceInfoSet
+    {
+    public:
+        DeviceInfoSet() = default;
+        DeviceInfoSet(const DeviceInfoSet&) = delete;
+        DeviceInfoSet& operator=(const DeviceInfoSet& other) = delete;
+        DeviceInfoSet(DeviceInfoSet&&) = delete;
+        DeviceInfoSet& operator=(DeviceInfoSet&& other) = delete;
+
+        ~DeviceInfoSet()
+        {
+            Destroy();
+        }
+
+    public:
+        operator HDEVINFO() const noexcept
+        {
+            return m_hDeviceInfoSet;
+        }
+
+    public:
+        HRESULT Create(const GUID* classGuid, HWND hwndParent)
+        {
+            Destroy();
+
+            HDEVINFO hDeviceInfoSet = SetupDiCreateDeviceInfoList(classGuid, hwndParent);
+            if (hDeviceInfoSet == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            m_hDeviceInfoSet = hDeviceInfoSet;
+            return S_OK;
+        }
+
+        HRESULT GetClassDevs(const GUID* classGuid, PCWSTR pszEnumerator, HWND hWndParent, DWORD dwFlags)
+        {
+            Destroy();
+
+            HDEVINFO hDeviceInfoSet = SetupDiGetClassDevsW(classGuid, pszEnumerator, hWndParent, dwFlags);
+            if (hDeviceInfoSet == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            m_hDeviceInfoSet = hDeviceInfoSet;
+            return S_OK;
+        }
+
+        void Destroy()
+        {
+            if (m_hDeviceInfoSet != INVALID_HANDLE_VALUE)
+            {
+                SetupDiDestroyDeviceInfoList(m_hDeviceInfoSet);
+                m_hDeviceInfoSet = INVALID_HANDLE_VALUE;
+            }
+        }
+
+    private:
+        HDEVINFO m_hDeviceInfoSet = INVALID_HANDLE_VALUE;
+    };
+
+    class UsbDriver
+    {
+    public:
+        virtual ~UsbDriver()
+        {
+            Close();
+        }
+
+        UsbDriver() = default;
+        UsbDriver(const UsbDriver&) = delete;
+        UsbDriver& operator=(const UsbDriver& other) = delete;
+        UsbDriver(UsbDriver&&) = delete;
+        UsbDriver& operator=(UsbDriver&& other) = delete;
+
+    public:
+        virtual HRESULT Open(LPCWSTR pszPath)
+        {
+            Close();
+
+            HANDLE hDevice = CreateFileW(
+                pszPath,
+                GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
+                nullptr,
+                OPEN_EXISTING,
+                FILE_FLAG_OVERLAPPED,
+                nullptr);
+            if (hDevice == INVALID_HANDLE_VALUE)
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            m_hDevice.Attach(hDevice);
+            return S_OK;
+        }
+
+        virtual void Close()
+        {
+            m_hDevice.Close();
+        }
+
+        virtual HRESULT GetDescriptor(uint8_t type, uint8_t index, uint16_t languageID, void* buffer, uint16_t bufferSize, uint32_t* bytesReturned, uint32_t timeout) = 0;
+        virtual HRESULT ControlTransfer(uint8_t requesttype, uint8_t request, uint16_t value, uint16_t index, void* buffer, uint16_t bufferSize, uint32_t* bytesReturned, uint32_t timeout) = 0;
+        virtual HRESULT SetConfiguration(uint8_t configuration) = 0;
+        virtual HRESULT ClaimInterface(uint8_t interfaceIndex) = 0;
+        virtual HRESULT ReleaseInterface(uint8_t interfaceIndex) = 0;
+        virtual HRESULT SetAlternateSetting(uint8_t interfaceIndex, uint8_t alternate) = 0;
+        virtual HRESULT ReadPipe(uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesRead, uint32_t timeout) = 0;
+        virtual HRESULT WritePipe(uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesWritten, uint32_t timeout) = 0;
+
+    protected:
+        Handle m_hDevice;
+    };
+
+    class WinUsbDriver : public UsbDriver
+    {
+    public:
+        HRESULT Open(LPCWSTR pszPath) override
+        {
+            HRESULT hr = UsbDriver::Open(pszPath);
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            WINUSB_INTERFACE_HANDLE hWinUsbInterface = nullptr;
+            if (!WinUsb_Initialize(m_hDevice, &hWinUsbInterface))
+            {
+                DWORD dwError = GetLastError();
+                Close();
+                return HRESULT_FROM_WIN32(dwError);
+            }
+
+            m_hWinUsbInterface = hWinUsbInterface;
+            return S_OK;
+        }
+
+        void Close() override
+        {
+            if (m_hWinUsbInterface != nullptr)
+            {
+                WinUsb_Free(m_hWinUsbInterface);
+                m_hWinUsbInterface = nullptr;
+            }
+
+            UsbDriver::Close();
+        }
+
+        HRESULT GetDescriptor(uint8_t type, uint8_t index, uint16_t languageID, void* buffer, uint16_t bufferSize, uint32_t* bytesReturned, uint32_t timeout) override
+        {
+            UNREFERENCED_PARAMETER(timeout);
+
+            if (bytesReturned != nullptr)
+            {
+                *bytesReturned = 0;
+            }
+
+            ULONG bytesTransferred = 0;
+            if (!WinUsb_GetDescriptor(m_hWinUsbInterface, type, index, languageID, static_cast<PUCHAR>(buffer), bufferSize, &bytesTransferred))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (bytesReturned != nullptr)
+            {
+                *bytesReturned = bytesTransferred;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT ControlTransfer(uint8_t requesttype, uint8_t request, uint16_t value, uint16_t index, void* buffer, uint16_t bufferSize, uint32_t* bytesReturned, uint32_t timeout) override
+        {
+            UNREFERENCED_PARAMETER(timeout);
+
+            if (bytesReturned != nullptr)
+            {
+                *bytesReturned = 0;
+            }
+
+            WINUSB_SETUP_PACKET packet{};
+            packet.RequestType = requesttype;
+            packet.Request = request;
+            packet.Value = value;
+            packet.Index = index;
+            packet.Length = bufferSize;
+
+            ULONG bytesTransferred = 0;
+            if (!WinUsb_ControlTransfer(m_hWinUsbInterface, packet, static_cast<PUCHAR>(buffer), bufferSize, &bytesTransferred, nullptr))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (bytesReturned != nullptr)
+            {
+                *bytesReturned = bytesTransferred;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT SetConfiguration(uint8_t configuration) override
+        {
+            // Not supported, fail silently.
+            return S_FALSE;
+        }
+
+        HRESULT ClaimInterface(uint8_t interfaceIndex) override
+        {
+            m_associatedInterface.index = interfaceIndex;
+
+            if (interfaceIndex > 0)
+            {
+                WINUSB_INTERFACE_HANDLE handle = nullptr;
+                if (!WinUsb_GetAssociatedInterface(m_hWinUsbInterface, interfaceIndex, &handle))
+                {
+                    return HRESULT_FROM_WIN32(GetLastError());
+                }
+
+                m_associatedInterface.first = false;
+                m_associatedInterface.handle = handle;
+            }
+            else
+            {
+                m_associatedInterface.first = true;
+                m_associatedInterface.handle = m_hWinUsbInterface;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT ReleaseInterface(uint8_t interfaceIndex) override
+        {
+            if (interfaceIndex != m_associatedInterface.index)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE);
+            }
+
+            if (m_associatedInterface.handle != nullptr && !m_associatedInterface.first)
+            {
+                WinUsb_Free(m_associatedInterface.handle);
+                m_associatedInterface.handle = nullptr;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT SetAlternateSetting(uint8_t interfaceIndex, uint8_t alternate) override
+        {
+            if (m_associatedInterface.handle == nullptr)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE);
+            }
+
+            return E_FAIL;
+        }
+
+        HRESULT ReadPipe(uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesRead, uint32_t timeout) override
+        {
+            UNREFERENCED_PARAMETER(timeout);
+
+            if (bytesRead != nullptr)
+            {
+                *bytesRead = 0;
+            }
+
+            if (m_associatedInterface.handle == nullptr)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE);
+            }
+
+            if (!WinUsb_SetPipePolicy(m_associatedInterface.handle, pipeId, PIPE_TRANSFER_TIMEOUT, sizeof(timeout), &timeout))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            ULONG lengthTransferred = 0;
+            if (!WinUsb_ReadPipe(m_associatedInterface.handle, pipeId, static_cast<PUCHAR>(buffer), bufferSize, &lengthTransferred, nullptr))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (bytesRead != nullptr)
+            {
+                *bytesRead = lengthTransferred;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT WritePipe(uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesWritten, uint32_t timeout) override
+        {
+            UNREFERENCED_PARAMETER(timeout);
+
+            if (bytesWritten != nullptr)
+            {
+                *bytesWritten = 0;
+            }
+
+            if (m_associatedInterface.handle == nullptr)
+            {
+                return HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE);
+            }
+
+            if (!WinUsb_SetPipePolicy(m_associatedInterface.handle, pipeId, PIPE_TRANSFER_TIMEOUT, sizeof(timeout), &timeout))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            ULONG lengthTransferred = 0;
+            if (!WinUsb_WritePipe(m_associatedInterface.handle, pipeId, static_cast<PUCHAR>(buffer), bufferSize, &lengthTransferred, nullptr))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            if (bytesWritten != nullptr)
+            {
+                *bytesWritten = lengthTransferred;
+            }
+
+            return S_OK;
+        }
+
+    private:
+        struct Interface
+        {
+            WINUSB_INTERFACE_HANDLE handle = nullptr;
+            uint8_t index = 0;
+            bool first = false;
+        };
+
+        WINUSB_INTERFACE_HANDLE m_hWinUsbInterface = nullptr;
+        Interface m_associatedInterface;
+    };
+
+    class LibUsbDriver : public UsbDriver
+    {
+    public:
+        HRESULT Open(LPCWSTR pszPath) override
+        {
+            HRESULT hr = UsbDriver::Open(pszPath);
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            hr = m_event.Create();
+            if (FAILED(hr))
+            {
+                Close();
+                return hr;
+            }
+
+            return S_OK;
+        }
+
+        void Close() override
+        {
+            m_event.Close();
+            UsbDriver::Close();
+        }
+
+        HRESULT GetDescriptor(uint8_t type, uint8_t index, uint16_t languageID, void* buffer, uint16_t bufferSize, uint32_t* bytesReturned, uint32_t timeout) override
+        {
+            libusb_request data{};
+            data.timeout = timeout;
+            data.descriptor.type = type;
+            data.descriptor.index = index;
+            data.descriptor.language_id = languageID;
+            data.descriptor.recipient = USB_RECIP_DEVICE;
+            return DeviceIoControl(LIBUSB_IOCTL_GET_DESCRIPTOR, &data, sizeof(data), buffer, bufferSize, bytesReturned);
+        }
+
+        HRESULT ControlTransfer(uint8_t requesttype, uint8_t request, uint16_t value, uint16_t index, void* buffer, uint16_t bufferSize, uint32_t* bytesTransmitted, uint32_t timeout) override
+        {
+            libusb_request data{};
+            data.timeout = timeout;
+
+            uint32_t ioControlCode = 0;
+            switch (requesttype & (0x03 << 5))
+            {
+            case USB_TYPE_STANDARD:
+                switch (request)
+                {
+                case USB_REQ_GET_STATUS:
+                    ioControlCode = LIBUSB_IOCTL_GET_STATUS;
+                    data.status.recipient = requesttype & 0x1F;
+                    data.status.index = index;
+                    break;
+                case USB_REQ_CLEAR_FEATURE:
+                    ioControlCode = LIBUSB_IOCTL_CLEAR_FEATURE;
+                    data.feature.recipient = requesttype & 0x1F;
+                    data.feature.feature = value;
+                    data.feature.index = index;
+                    break;
+                case USB_REQ_SET_FEATURE:
+                    ioControlCode = LIBUSB_IOCTL_SET_FEATURE;
+                    data.feature.recipient = requesttype & 0x1F;
+                    data.feature.feature = value;
+                    data.feature.index = index;
+                    break;
+                case USB_REQ_GET_DESCRIPTOR:
+                    ioControlCode = LIBUSB_IOCTL_GET_DESCRIPTOR;
+                    data.descriptor.recipient = requesttype & 0x1F;
+                    data.descriptor.type = (value >> 8) & 0xFF;
+                    data.descriptor.index = value & 0xFF;
+                    data.descriptor.language_id = index;
+                    break;
+                case USB_REQ_SET_DESCRIPTOR:
+                    ioControlCode = LIBUSB_IOCTL_SET_DESCRIPTOR;
+                    data.descriptor.recipient = requesttype & 0x1F;
+                    data.descriptor.type = (value >> 8) & 0xFF;
+                    data.descriptor.index = value & 0xFF;
+                    data.descriptor.language_id = index;
+                    break;
+                case USB_REQ_GET_CONFIGURATION:
+                    ioControlCode = LIBUSB_IOCTL_GET_CONFIGURATION;
+                    break;
+                case USB_REQ_SET_CONFIGURATION:
+                    data.configuration.configuration = value;
+                    ioControlCode = LIBUSB_IOCTL_SET_CONFIGURATION;
+                    break;
+                case USB_REQ_GET_INTERFACE:
+                    ioControlCode = LIBUSB_IOCTL_GET_INTERFACE;
+                    data.intf.interface_number = index;
+                    break;
+                case USB_REQ_SET_INTERFACE:
+                    ioControlCode = LIBUSB_IOCTL_SET_INTERFACE;
+                    data.intf.interface_number = index;
+                    data.intf.altsetting_number = value;
+                    break;
+                default:
+                    return E_INVALIDARG;
+                }
+                break;
+            case USB_TYPE_CLASS:
+            case USB_TYPE_VENDOR:
+                ioControlCode = (requesttype & 0x80) != 0 ? LIBUSB_IOCTL_VENDOR_READ : LIBUSB_IOCTL_VENDOR_WRITE;
+                data.vendor.type = (requesttype >> 5) & 0x03;
+                data.vendor.recipient = requesttype & 0x1F;
+                data.vendor.request = request;
+                data.vendor.value = value;
+                data.vendor.index = index;
+                break;
+            default:
+                return E_INVALIDARG;
+            }
+
+            if ((requesttype & USB_ENDPOINT_IN) == 0)
+            {
+                if (bytesTransmitted != nullptr)
+                {
+                    *bytesTransmitted = 0;
+                }
+
+                if (bufferSize > 0)
+                {
+                    std::vector<uint8_t> out(sizeof(data) + bufferSize);
+                    std::memcpy(out.data(), &data, sizeof(data));
+                    std::memcpy(out.data() + sizeof(data), buffer, bufferSize);
+                    HRESULT hr = DeviceIoControl(ioControlCode, out.data(), sizeof(data) + bufferSize, nullptr, 0);
+                    if (FAILED(hr))
+                        return hr;
+
+                    if (bytesTransmitted != nullptr)
+                    {
+                        *bytesTransmitted = bufferSize;
+                    }
+
+                    return hr;
+                }
+                else
+                {
+                    return DeviceIoControl(ioControlCode, &data, sizeof(data), nullptr, 0);
+                }
+            }
+            else
+            {
+                return DeviceIoControl(ioControlCode, &data, sizeof(data), buffer, bufferSize, bytesTransmitted);
+            }
+        }
+
+        HRESULT SetConfiguration(uint8_t configuration) override
+        {
+            libusb_request data{};
+            data.configuration.configuration = configuration;
+            data.timeout = LIBUSB_DEFAULT_TIMEOUT;
+            return DeviceIoControl(LIBUSB_IOCTL_SET_CONFIGURATION, &data, sizeof(data), nullptr, 0);
+        }
+
+        HRESULT ClaimInterface(uint8_t interfaceIndex) override
+        {
+            libusb_request data{};
+            data.intf.interface_number = interfaceIndex;
+            data.timeout = LIBUSB_DEFAULT_TIMEOUT;
+            return DeviceIoControl(LIBUSB_IOCTL_CLAIM_INTERFACE, &data, sizeof(data), nullptr, 0);
+        }
+
+        HRESULT ReleaseInterface(uint8_t interfaceIndex) override
+        {
+            libusb_request data{};
+            data.intf.interface_number = interfaceIndex;
+            data.timeout = LIBUSB_DEFAULT_TIMEOUT;
+            return DeviceIoControl(LIBUSB_IOCTL_RELEASE_INTERFACE, &data, sizeof(data), nullptr, 0);
+        }
+
+        HRESULT SetAlternateSetting(uint8_t interfaceIndex, uint8_t alternate) override
+        {
+            libusb_request data{};
+            data.intf.interface_number = interfaceIndex;
+            data.intf.altsetting_number = alternate;
+            data.timeout = LIBUSB_DEFAULT_TIMEOUT;
+            return DeviceIoControl(LIBUSB_IOCTL_SET_INTERFACE, &data, sizeof(data), nullptr, 0);
+        }
+
+        HRESULT ReadPipe(uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesRead, uint32_t timeout) override
+        {
+            return DeviceIoTransfer(LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ, pipeId, buffer, bufferSize, bytesRead, timeout);
+        }
+
+        HRESULT WritePipe(uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesWritten, uint32_t timeout) override
+        {
+            return DeviceIoTransfer(LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE, pipeId, buffer, bufferSize, bytesWritten, timeout);
+        }
+
+    private:
+        static const uint32_t LIBUSB_DEFAULT_TIMEOUT = 5000;
+
+        static const uint32_t LIBUSB_IOCTL_SET_CONFIGURATION = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_GET_CONFIGURATION = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_SET_INTERFACE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_GET_INTERFACE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_SET_FEATURE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_CLEAR_FEATURE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_GET_STATUS = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_SET_DESCRIPTOR = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_GET_DESCRIPTOR = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_IN_DIRECT, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_OUT_DIRECT, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_VENDOR_WRITE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_VENDOR_READ = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80D, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_RESET_ENDPOINT = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80E, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_ABORT_ENDPOINT = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80F, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_RESET_DEVICE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x810, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_SET_DEBUG_LEVEL = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x811, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_GET_VERSION = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_ISOCHRONOUS_WRITE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_IN_DIRECT, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_ISOCHRONOUS_READ = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x814, METHOD_OUT_DIRECT, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_CLAIM_INTERFACE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x815, METHOD_BUFFERED, FILE_ANY_ACCESS);
+        static const uint32_t LIBUSB_IOCTL_RELEASE_INTERFACE = CTL_CODE(FILE_DEVICE_UNKNOWN, 0x816, METHOD_BUFFERED, FILE_ANY_ACCESS);
+
+#include <pshpack1.h>
+
+        struct interface_request
+        {
+            uint32_t interface_number;
+            uint32_t altsetting_number;
+            uint8_t intf_use_index : 1;
+            uint8_t altf_use_index : 1;
+            uint8_t : 6;
+            int16_t interface_index;
+            int16_t altsetting_index;
+        };
+
+        struct libusb_request
+        {
+            uint32_t timeout;
+            union
+            {
+                struct
+                {
+                    uint32_t configuration;
+                } configuration;
+                interface_request intf;
+                struct
+                {
+                    uint32_t endpoint;
+                    uint32_t packet_size;
+                    uint32_t max_transfer_size;
+                    uint32_t transfer_flags;
+                    uint32_t iso_start_frame_latency;
+                } endpoint;
+                struct
+                {
+                    uint32_t type;
+                    uint32_t recipient;
+                    uint32_t request;
+                    uint32_t value;
+                    uint32_t index;
+                } vendor;
+                struct
+                {
+                    uint32_t recipient;
+                    uint32_t feature;
+                    uint32_t index;
+                } feature;
+                struct
+                {
+                    uint32_t recipient;
+                    uint32_t index;
+                    uint32_t status;
+                } status;
+                struct
+                {
+                    uint32_t type;
+                    uint32_t index;
+                    uint32_t language_id;
+                    uint32_t recipient;
+                } descriptor;
+                struct
+                {
+                    uint32_t level;
+                } debug;
+                struct
+                {
+                    uint32_t major;
+                    uint32_t minor;
+                    uint32_t micro;
+                    uint32_t nano;
+                    uint32_t mod_value;
+                } version;
+                struct
+                {
+                    uint32_t property;
+                } device_property;
+                struct
+                {
+                    uint32_t key_type;
+                    uint32_t name_offset;
+                    uint32_t value_offset;
+                    uint32_t value_length;
+                } device_registry_key;
+                struct
+                {
+                    uint32_t objname_index;
+                } objname;
+                struct
+                {
+                    uint32_t information_type;
+                } query_device;
+                struct
+                {
+                    uint32_t interface_index;
+                    uint32_t pipe_id;
+                    uint32_t policy_type;
+                } pipe_policy;
+                struct
+                {
+                    uint32_t policy_type;
+                } power_policy;
+                struct
+                {
+                    uint32_t reset_type;
+                } reset_ex;
+                struct
+                {
+                    uint8_t request_type;
+                    uint8_t request;
+                    uint16_t value;
+                    uint16_t index;
+                    uint16_t length;
+                } control;
+            };
+        };
+
+#include <poppack.h>
+
+    private:
+        static const uint32_t maxReadWriteSize = 0x10000;
+
+        HRESULT DeviceIoTransfer(uint32_t ioControlCode, uint8_t pipeId, void* buffer, uint32_t bufferSize, uint32_t* bytesTransferred, uint32_t timeout)
+        {
+            if (bytesTransferred != nullptr)
+            {
+                *bytesTransferred = 0;
+            }
+
+            if (timeout == 0)
+            {
+                timeout = INFINITE;
+            }
+
+            ManualResetEvent event;
+            HRESULT hr = event.Create();
+            if (FAILED(hr))
+            {
+                return hr;
+            }
+
+            uint32_t transferred = 0;
+            while (bufferSize > 0)
+            {
+                uint32_t chunkSize = bufferSize > maxReadWriteSize ? maxReadWriteSize : bufferSize;
+
+                libusb_request transfer{};
+                transfer.endpoint.endpoint = pipeId;
+                transfer.endpoint.packet_size = 0;
+
+                event.Reset();
+
+                OVERLAPPED overlapped{};
+                overlapped.hEvent = event;
+                hr = DeviceIoControl(ioControlCode, &transfer, sizeof(transfer), buffer, chunkSize, nullptr, &overlapped);
+                if (FAILED(hr))
+                {
+                    return hr;
+                }
+
+                if (WaitForSingleObject(overlapped.hEvent, timeout) != WAIT_OBJECT_0)
+                {
+                    libusb_request cancel{};
+                    cancel.endpoint.endpoint = pipeId;
+                    cancel.timeout = LIBUSB_DEFAULT_TIMEOUT;
+                    DeviceIoControl(LIBUSB_IOCTL_ABORT_ENDPOINT, &cancel, sizeof(cancel), nullptr, 0);
+                    return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
+                }
+
+                DWORD dwNumberOfBytesTransferred = 0;
+                if (!GetOverlappedResult(m_hDevice, &overlapped, &dwNumberOfBytesTransferred, TRUE))
+                {
+                    return HRESULT_FROM_WIN32(GetLastError());
+                }
+
+                transferred += dwNumberOfBytesTransferred;
+                if (dwNumberOfBytesTransferred != chunkSize)
+                {
+                    break;
+                }
+
+                buffer = static_cast<uint8_t*>(buffer) + dwNumberOfBytesTransferred;
+                bufferSize -= dwNumberOfBytesTransferred;
+            }
+
+            if (bytesTransferred != nullptr)
+            {
+                *bytesTransferred = transferred;
+            }
+
+            return S_OK;
+        }
+
+        HRESULT DeviceIoControl(
+            uint32_t ioControlCode,
+            void* lpInBuffer,
+            uint32_t inBufferSize,
+            void* pOutBuffer,
+            uint32_t outBufferSize,
+            uint32_t* bytesReturned = nullptr,
+            LPOVERLAPPED pOverlapped = nullptr)
+        {
+            if (bytesReturned != nullptr)
+            {
+                *bytesReturned = 0;
+            }
+
+            if (pOverlapped == nullptr)
+            {
+                m_event.Reset();
+
+                OVERLAPPED overlapped{};
+                overlapped.hEvent = m_event;
+                if (!::DeviceIoControl(m_hDevice, ioControlCode, lpInBuffer, inBufferSize, pOutBuffer, outBufferSize, nullptr, &overlapped))
+                {
+                    DWORD dwError = GetLastError();
+                    if (dwError != ERROR_IO_PENDING)
+                    {
+                        return HRESULT_FROM_WIN32(dwError);
+                    }
+                }
+
+                DWORD dwNumberOfBytesTransferred = 0;
+                if (!::GetOverlappedResult(m_hDevice, &overlapped, &dwNumberOfBytesTransferred, TRUE))
+                {
+                    return HRESULT_FROM_WIN32(GetLastError());
+                }
+
+                if (bytesReturned != nullptr)
+                {
+                    *bytesReturned = dwNumberOfBytesTransferred;
+                }
+            }
+            else
+            {
+                DWORD dwBytesReturned = 0;
+                if (!::DeviceIoControl(m_hDevice, ioControlCode, lpInBuffer, inBufferSize, pOutBuffer, outBufferSize, &dwBytesReturned, pOverlapped))
+                {
+                    DWORD dwError = GetLastError();
+                    if (dwError != ERROR_IO_PENDING)
+                    {
+                        return HRESULT_FROM_WIN32(dwError);
+                    }
+                }
+
+                if (bytesReturned != nullptr)
+                {
+                    *bytesReturned = dwBytesReturned;
+                }
+            }
+
+            return S_OK;
+        }
+
+    private:
+        ManualResetEvent m_event;
+    };
+
+    class UsbDevice
+    {
+        friend class UsbDeviceEnumerator;
+
+    public:
+        UsbDevice() = default;
+        UsbDevice(const UsbDevice&) = delete;
+        UsbDevice& operator=(const UsbDevice& other) = delete;
+        UsbDevice(UsbDevice&&) = delete;
+        UsbDevice& operator=(UsbDevice&& other) = delete;
+
+    public:
+        std::wstring GetInstanceId() const { return m_instanceId; }
+        std::wstring GetInterfacePath() const { return m_interfacePath; }
+        std::wstring GetService() const { return m_service; }
+
+    public:
+        HRESULT Open(std::unique_ptr<UsbDriver>& driver)
+        {
+            return Open(m_service, m_interfacePath, driver);
+        }
+
+        static HRESULT Open(const std::wstring& service, const std::wstring& interfacePath, std::unique_ptr<UsbDriver>& driver)
+        {
+            if (_wcsicmp(service.c_str(), L"WINUSB") == 0)
+            {
+                driver = std::make_unique<WinUsbDriver>();
+            }
+            else if (_wcsicmp(service.c_str(), L"libusb0") == 0 || _wcsicmp(service.c_str(), L"libusbK") == 0)
+            {
+                driver = std::make_unique<LibUsbDriver>();
+            }
+            else
+            {
+                return E_NOTIMPL;
+            }
+
+            return driver->Open(interfacePath.c_str());
+        }
+
+    private:
+        std::wstring m_instanceId;
+        std::wstring m_interfacePath;
+        std::wstring m_service;
+    };
+
+    class UsbDeviceEnumerator
+    {
+    public:
+        UsbDeviceEnumerator() = default;
+        UsbDeviceEnumerator(const UsbDeviceEnumerator&) = delete;
+        UsbDeviceEnumerator& operator=(const UsbDeviceEnumerator& other) = delete;
+        UsbDeviceEnumerator(UsbDeviceEnumerator&&) = delete;
+        UsbDeviceEnumerator& operator=(UsbDeviceEnumerator&& other) = delete;
+
+    public:
+        std::vector<std::unique_ptr<UsbDevice>>& GetDevices() { return m_devices; }
+
+    public:
+        HRESULT EnumerateDevices()
+        {
+            DeviceInfoSet deviceInfoSet;
+            HRESULT hr = deviceInfoSet.GetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+            if (FAILED(hr))
+                return hr;
+
+            for (DWORD index = 0; ; index++)
+            {
+                DeviceInfo deviceInfo;
+                if (!SetupDiEnumDeviceInfo(deviceInfoSet, index, &deviceInfo))
+                {
+                    DWORD dwError = GetLastError();
+                    if (dwError != ERROR_NO_MORE_ITEMS)
+                    {
+                        return HRESULT_FROM_WIN32(dwError);
+                    }
+
+                    break;
+                }
+
+                SP_DEVICE_INTERFACE_DATA deviceInterface = { sizeof(SP_DEVICE_INTERFACE_DATA) };
+                if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, &deviceInfo, &GUID_DEVINTERFACE_USB_DEVICE, 0, &deviceInterface))
+                {
+                    DWORD dwError = GetLastError();
+                    if (dwError != ERROR_NO_MORE_ITEMS)
+                    {
+                        return HRESULT_FROM_WIN32(dwError);
+                    }
+                }
+                else
+                {
+                    auto device = std::make_unique<UsbDevice>();
+
+                    uint32_t installState = 0;
+                    if (SUCCEEDED(deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_InstanceId, device->m_instanceId)) &&
+                        SUCCEEDED(deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_InstallState, installState)) &&
+                        SUCCEEDED(deviceInfo.GetProperty(deviceInfoSet, &DEVPKEY_Device_Service, device->m_service)) &&
+                        SUCCEEDED(GetDeviceInterfacePath(deviceInfoSet, &deviceInterface, device->m_interfacePath)) &&
+                        installState == 0)
+                    {
+                        m_devices.push_back(std::move(device));
+                    }
+                }
+            }
+
+            return S_OK;
+        }
+
+        HRESULT GetDeviceInterfacePath(HDEVINFO hDeviceInfoSet, PSP_DEVICE_INTERFACE_DATA pDeviceInterfaceData, std::wstring& deviceInterfacePath)
+        {
+            DWORD dwRequiredSize = 0;
+            if (!SetupDiGetDeviceInterfaceDetailW(hDeviceInfoSet, pDeviceInterfaceData, nullptr, 0, &dwRequiredSize, nullptr))
+            {
+                DWORD dwError = GetLastError();
+                if (dwError != ERROR_INSUFFICIENT_BUFFER)
+                {
+                    return HRESULT_FROM_WIN32(dwError);
+                }
+            }
+
+            std::vector<uint8_t> buffer(dwRequiredSize);
+            SP_DEVICE_INTERFACE_DETAIL_DATA_W* pDeviceInterfaceDetailData = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(buffer.data());
+            pDeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
+
+            if (!SetupDiGetDeviceInterfaceDetailW(hDeviceInfoSet, pDeviceInterfaceData, pDeviceInterfaceDetailData, dwRequiredSize, &dwRequiredSize, nullptr))
+            {
+                return HRESULT_FROM_WIN32(GetLastError());
+            }
+
+            deviceInterfacePath = pDeviceInterfaceDetailData->DevicePath;
+            return S_OK;
+        }
+
+    private:
+        std::vector<std::unique_ptr<UsbDevice>> m_devices;
+    };
+}
diff --git a/external/libusb/src/windows.c b/external/libusb/src/windows.c
deleted file mode 100644
index b9a79a07..00000000
--- a/external/libusb/src/windows.c
+++ /dev/null
@@ -1,1281 +0,0 @@
-/* libusb-win32, Generic Windows USB Library
- * Copyright (c) 2002-2005 Stephan Meyer <ste_meyer@web.de>
- * Copyright (c) 2000-2005 Johannes Erdfelt <johannes@erdfelt.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <errno.h>
-#include <ctype.h>
-#include <windows.h>
-#include <winioctl.h>
-#include <setupapi.h>
-
-#include "lusb0_usb.h"
-#include "error.h"
-#include "usbi.h"
-#include "driver_api.h"
-#include "registry.h"
-#include "libusb-win32_version.h"
-
-#define LIBUSB_WIN32_DLL_LARGE_TRANSFER_SUPPORT
-
-#define LIBUSB_DEFAULT_TIMEOUT 5000
-#define LIBUSB_DEVICE_NAME "\\\\.\\libusb0-"
-#define LIBUSB_BUS_NAME "bus-0"
-#define LIBUSB_MAX_DEVICES 256
-
-typedef struct
-{
-    usb_dev_handle *dev;
-    libusb_request req;
-    char *bytes;
-    int size;
-    DWORD control_code;
-    OVERLAPPED ol;
-} usb_context_t;
-
-
-static struct usb_version _usb_version =
-{
-    { VERSION_MAJOR,
-        VERSION_MINOR,
-        VERSION_MICRO,
-        VERSION_NANO },
-    { -1, -1, -1, -1 }
-};
-
-
-static int _usb_setup_async(usb_dev_handle *dev, void **context,
-                            DWORD control_code,
-                            unsigned char ep, int pktsize);
-static int _usb_transfer_sync(usb_dev_handle *dev, int control_code,
-                              int ep, int pktsize, char *bytes, int size,
-                              int timeout);
-
-static int usb_get_configuration(usb_dev_handle *dev, bool_t cached);
-static int _usb_cancel_io(usb_context_t *context);
-static int _usb_abort_ep(usb_dev_handle *dev, unsigned int ep);
-
-static int _usb_io_sync(HANDLE dev, unsigned int code, void *in, int in_size,
-                        void *out, int out_size, int *ret);
-static int _usb_reap_async(void *context, int timeout, int cancel);
-static int _usb_add_virtual_hub(struct usb_bus *bus);
-
-static void _usb_free_bus_list(struct usb_bus *bus);
-static void _usb_free_dev_list(struct usb_device *dev);
-static void _usb_deinit(void);
-
-/* DLL main entry point */
-BOOL WINAPI DllMain(HANDLE module, DWORD reason, LPVOID reserved)
-{
-    switch (reason)
-    {
-    case DLL_PROCESS_ATTACH:
-        break;
-    case DLL_PROCESS_DETACH:
-        _usb_deinit();
-        break;
-    case DLL_THREAD_ATTACH:
-        break;
-    case DLL_THREAD_DETACH:
-        break;
-    default:
-        break;
-    }
-    return TRUE;
-}
-
-
-static int usb_get_configuration(usb_dev_handle *dev, bool_t cached)
-{
-	int ret;
-	char config;
-	libusb_request request;
-
-	if (cached)
-	{
-		memset(&request, 0, sizeof(request));
-		request.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-		if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_GET_CACHED_CONFIGURATION,
-			&request, sizeof(request), &request, sizeof(request), &ret))
-		{
-			USBERR("sending get cached configuration ioctl failed, win error: %s\n", usb_win_error_to_string());
-			ret = -usb_win_error_to_errno();
-		}
-
-		if (ret < 1)
-			ret = -EINVAL;
-		else
-			config = *((char*)&request);
-	}
-	else
-	{
-		ret = usb_control_msg(dev, USB_RECIP_DEVICE | USB_ENDPOINT_IN,
-			USB_REQ_GET_CONFIGURATION, 0, 0, &config, 1,
-			LIBUSB_DEFAULT_TIMEOUT); 
-	}
-
-	if(ret < 0)
-		return ret;
-
-	return config;
-}
-
-int usb_os_open(usb_dev_handle *dev)
-{
-	char dev_name[LIBUSB_PATH_MAX];
-	char *p;
-	int config;
-	if (!dev)
-	{
-		USBERR("invalid device handle %p", dev);
-		return -EINVAL;
-	}
-
-	dev->impl_info = INVALID_HANDLE_VALUE;
-	dev->config = 0;
-	dev->interface = -1;
-	dev->altsetting = -1;
-
-	if (!dev->device->filename)
-	{
-		USBERR0("invalid file name\n");
-		return -ENOENT;
-	}
-
-	/* build the Windows file name from the unique device name */
-	strcpy(dev_name, dev->device->filename);
-
-	p = strstr(dev_name, "--");
-
-	if (!p)
-	{
-		USBERR("invalid file name %s\n", dev->device->filename);
-		return -ENOENT;
-	}
-
-	*p = 0;
-
-	dev->impl_info = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
-		FILE_FLAG_OVERLAPPED, NULL);
-
-	if (dev->impl_info == INVALID_HANDLE_VALUE)
-	{
-		USBERR("failed to open %s: win error: %s",
-			dev->device->filename, usb_win_error_to_string());
-		return -ENOENT;
-	}
-
-	// get the cached configuration (no device i/o)
-	config = usb_get_configuration(dev, TRUE);
-	if (config > 0)
-	{
-		dev->config = config;
-		dev->interface = -1;
-		dev->altsetting = -1;
-	}
-
-	return 0;
-}
-
-int usb_os_close(usb_dev_handle *dev)
-{
-    if (dev->impl_info != INVALID_HANDLE_VALUE)
-    {
-        if (dev->interface >= 0)
-        {
-            usb_release_interface(dev, dev->interface);
-        }
-
-        CloseHandle(dev->impl_info);
-        dev->impl_info = INVALID_HANDLE_VALUE;
-        dev->interface = -1;
-        dev->altsetting = -1;
-    }
-
-    return 0;
-}
-
-int usb_set_configuration(usb_dev_handle *dev, int configuration)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("error: device not open\n");
-        return -EINVAL;
-    }
-
-    if (dev->config == configuration)
-    {
-        return 0;
-    }
-
-    if (dev->interface >= 0)
-    {
-        USBERR0("can't change configuration, an interface is still in use (claimed)\n");
-        return -EINVAL;
-    }
-
-    req.configuration.configuration = configuration;
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_SET_CONFIGURATION,
-                      &req, sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not set config %d: "
-                  "win error: %s", configuration, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    dev->config = configuration;
-    dev->interface = -1;
-    dev->altsetting = -1;
-
-    return 0;
-}
-
-int usb_claim_interface(usb_dev_handle *dev, int interface)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    if (!dev->config)
-    {
-        USBERR("could not claim interface %d, invalid configuration %d\n", interface, dev->config);
-        return -EINVAL;
-    }
-
-    if (dev->interface == interface)
-    {
-        return 0;
-    }
-
-    req.intf.interface_number = interface;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_CLAIM_INTERFACE,
-                      &req, sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not claim interface %d, "
-                  "win error: %s", interface, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-    else
-    {
-        dev->interface = interface;
-        dev->altsetting = 0;
-        return 0;
-    }
-}
-
-int usb_release_interface(usb_dev_handle *dev, int interface)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    if (!dev->config)
-    {
-        USBERR("could not release interface %d, invalid configuration %d\n", interface, dev->config);
-        return -EINVAL;
-    }
-
-    req.intf.interface_number = interface;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RELEASE_INTERFACE,
-                      &req, sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not release interface %d, "
-                  "win error: %s", interface, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-    else
-    {
-        dev->interface = -1;
-        dev->altsetting = -1;
-
-        return 0;
-    }
-}
-
-int usb_set_altinterface(usb_dev_handle *dev, int alternate)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    if (dev->config <= 0)
-    {
-        USBERR("could not set alt interface %d: invalid configuration %d\n", alternate, dev->config);
-        return -EINVAL;
-    }
-
-    if (dev->interface < 0)
-    {
-        USBERR("could not set alt interface %d: no interface claimed\n", alternate);
-        return -EINVAL;
-    }
-
-    req.intf.interface_number = dev->interface;
-    req.intf.altsetting_number = alternate;
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_SET_INTERFACE,
-                      &req, sizeof(libusb_request),
-                      NULL, 0, NULL))
-    {
-        USBERR("could not set alt interface "
-                  "%d/%d: win error: %s",
-                  dev->interface, alternate, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    dev->altsetting = alternate;
-
-    return 0;
-}
-
-static int _usb_setup_async(usb_dev_handle *dev, void **context,
-                            DWORD control_code,
-                            unsigned char ep, int pktsize)
-{
-    usb_context_t **c = (usb_context_t **)context;
-
-    if (((control_code == LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE)
-            || (control_code == LIBUSB_IOCTL_ISOCHRONOUS_WRITE))
-            && (ep & USB_ENDPOINT_IN))
-    {
-        USBERR("invalid endpoint 0x%02x", ep);
-        return -EINVAL;
-    }
-
-    if (((control_code == LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ)
-            || (control_code == LIBUSB_IOCTL_ISOCHRONOUS_READ))
-            && !(ep & USB_ENDPOINT_IN))
-    {
-        USBERR("invalid endpoint 0x%02x\n", ep);
-        return -EINVAL;
-    }
-
-    *c = malloc(sizeof(usb_context_t));
-
-    if (!*c)
-    {
-        USBERR0("memory allocation error\n");
-        return -ENOMEM;
-    }
-
-    memset(*c, 0, sizeof(usb_context_t));
-
-    (*c)->dev = dev;
-    (*c)->req.endpoint.endpoint = ep;
-    (*c)->req.endpoint.packet_size = pktsize;
-    (*c)->control_code = control_code;
-
-    (*c)->ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-
-    if (!(*c)->ol.hEvent)
-    {
-        free(*c);
-        *c = NULL;
-        USBERR("creating event failed: win error: %s",
-                  usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return 0;
-}
-
-int usb_submit_async(void *context, char *bytes, int size)
-{
-    usb_context_t *c = (usb_context_t *)context;
-
-    if (!c)
-    {
-        USBERR0("invalid context");
-        return -EINVAL;
-    }
-
-    if (c->dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    if (c->dev->config <= 0)
-    {
-        USBERR("invalid configuration %d\n", c->dev->config);
-        return -EINVAL;
-    }
-
-    if (c->dev->interface < 0)
-    {
-        USBERR("invalid interface %d\n", c->dev->interface);
-        return -EINVAL;
-    }
-
-
-    c->ol.Offset = 0;
-    c->ol.OffsetHigh = 0;
-    c->bytes = bytes;
-    c->size = size;
-
-    ResetEvent(c->ol.hEvent);
-
-    if (!DeviceIoControl(c->dev->impl_info,
-                         c->control_code,
-                         &c->req, sizeof(libusb_request),
-                         c->bytes,
-                         c->size, NULL, &c->ol))
-    {
-        if (GetLastError() != ERROR_IO_PENDING)
-        {
-            USBERR("submitting request failed, "
-                      "win error: %s", usb_win_error_to_string());
-            return -usb_win_error_to_errno();
-        }
-    }
-
-    return 0;
-}
-
-static int _usb_reap_async(void *context, int timeout, int cancel)
-{
-    usb_context_t *c = (usb_context_t *)context;
-    ULONG ret = 0;
-
-    if (!c)
-    {
-        USBERR0("invalid context\n");
-        return -EINVAL;
-    }
-
-    if (WaitForSingleObject(c->ol.hEvent, timeout) == WAIT_TIMEOUT)
-    {
-        /* request timed out */
-        if (cancel)
-        {
-            _usb_cancel_io(c);
-        }
-
-        USBERR0("timeout error\n");
-        return -ETRANSFER_TIMEDOUT;
-    }
-
-    if (!GetOverlappedResult(c->dev->impl_info, &c->ol, &ret, TRUE))
-    {
-        USBERR("reaping request failed, win error: %s\n",usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return ret;
-}
-
-int usb_reap_async(void *context, int timeout)
-{
-    return _usb_reap_async(context, timeout, TRUE);
-}
-
-int usb_reap_async_nocancel(void *context, int timeout)
-{
-    return _usb_reap_async(context, timeout, FALSE);
-}
-
-
-int usb_cancel_async(void *context)
-{
-    /* NOTE that this function will cancel all pending URBs */
-    /* on the same endpoint as this particular context, or even */
-    /* all pending URBs for this particular device. */
-
-    usb_context_t *c = (usb_context_t *)context;
-
-    if (!c)
-    {
-        USBERR0("invalid context\n");
-        return -EINVAL;
-    }
-
-    if (c->dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    _usb_cancel_io(c);
-
-    return 0;
-}
-
-int usb_free_async(void **context)
-{
-    usb_context_t **c = (usb_context_t **)context;
-
-    if (!*c)
-    {
-        USBERR0("invalid context\n");
-        return -EINVAL;
-    }
-
-    CloseHandle((*c)->ol.hEvent);
-
-    free(*c);
-    *c = NULL;
-
-    return 0;
-}
-
-static int _usb_transfer_sync(usb_dev_handle *dev, int control_code,
-                              int ep, int pktsize, char *bytes, int size,
-                              int timeout)
-{
-    void *context = NULL;
-    int transmitted = 0;
-    int ret;
-    int requested;
-
-	if (!timeout) timeout=INFINITE;
-    ret = _usb_setup_async(dev, &context, control_code, (unsigned char )ep,
-                           pktsize);
-
-    if (ret < 0)
-    {
-        return ret;
-    }
-
-    do
-    {
-#ifdef LIBUSB_WIN32_DLL_LARGE_TRANSFER_SUPPORT
-        requested = size > LIBUSB_MAX_READ_WRITE ? LIBUSB_MAX_READ_WRITE : size;
-#else
-        requested = size;
-#endif
-        ret = usb_submit_async(context, bytes, requested);
-
-        if (ret < 0)
-        {
-            transmitted = ret;
-            break;
-        }
-
-        ret = usb_reap_async(context, timeout);
-
-        if (ret < 0)
-        {
-            transmitted = ret;
-            break;
-        }
-
-        transmitted += ret;
-        bytes += ret;
-        size -= ret;
-    }
-    while (size > 0 && ret == requested);
-
-    usb_free_async(&context);
-
-    return transmitted;
-}
-
-int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size,
-                   int timeout)
-{
-    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
-                              ep, 0, bytes, size, timeout);
-}
-
-int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size,
-                  int timeout)
-{
-    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
-                              ep, 0, bytes, size, timeout);
-}
-
-int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size,
-                        int timeout)
-{
-    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
-                              ep, 0, bytes, size, timeout);
-}
-
-int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
-                       int timeout)
-{
-    return _usb_transfer_sync(dev, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
-                              ep, 0, bytes, size, timeout);
-}
-
-int usb_isochronous_setup_async(usb_dev_handle *dev, void **context,
-                                unsigned char ep, int pktsize)
-{
-    if (ep & 0x80)
-        return _usb_setup_async(dev, context, LIBUSB_IOCTL_ISOCHRONOUS_READ,
-                                ep, pktsize);
-    else
-        return _usb_setup_async(dev, context, LIBUSB_IOCTL_ISOCHRONOUS_WRITE,
-                                ep, pktsize);
-}
-
-int usb_bulk_setup_async(usb_dev_handle *dev, void **context, unsigned char ep)
-{
-    if (ep & 0x80)
-        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
-                                ep, 0);
-    else
-        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
-                                ep, 0);
-}
-
-int usb_interrupt_setup_async(usb_dev_handle *dev, void **context,
-                              unsigned char ep)
-{
-    if (ep & 0x80)
-        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_READ,
-                                ep, 0);
-    else
-        return _usb_setup_async(dev, context, LIBUSB_IOCTL_INTERRUPT_OR_BULK_WRITE,
-                                ep, 0);
-}
-
-int usb_control_msg(usb_dev_handle *dev, int requesttype, int request,
-                    int value, int index, char *bytes, int size, int timeout)
-{
-    int read = 0;
-    libusb_request req;
-    void *out = &req;
-    int out_size = sizeof(libusb_request);
-    void *in = bytes;
-    int in_size = size;
-    int code;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    req.timeout = timeout;
-
-    /* windows doesn't support generic control messages, so it needs to be */
-    /* split up */
-    switch (requesttype & (0x03 << 5))
-    {
-    case USB_TYPE_STANDARD:
-        switch (request)
-        {
-        case USB_REQ_GET_STATUS:
-            req.status.recipient = requesttype & 0x1F;
-            req.status.index = index;
-            code = LIBUSB_IOCTL_GET_STATUS;
-            break;
-
-        case USB_REQ_CLEAR_FEATURE:
-            req.feature.recipient = requesttype & 0x1F;
-            req.feature.feature = value;
-            req.feature.index = index;
-            code = LIBUSB_IOCTL_CLEAR_FEATURE;
-            break;
-
-        case USB_REQ_SET_FEATURE:
-            req.feature.recipient = requesttype & 0x1F;
-            req.feature.feature = value;
-            req.feature.index = index;
-            code = LIBUSB_IOCTL_SET_FEATURE;
-            break;
-
-        case USB_REQ_GET_DESCRIPTOR:
-            req.descriptor.recipient = requesttype & 0x1F;
-            req.descriptor.type = (value >> 8) & 0xFF;
-            req.descriptor.index = value & 0xFF;
-            req.descriptor.language_id = index;
-            code = LIBUSB_IOCTL_GET_DESCRIPTOR;
-            break;
-
-        case USB_REQ_SET_DESCRIPTOR:
-            req.descriptor.recipient = requesttype & 0x1F;
-            req.descriptor.type = (value >> 8) & 0xFF;
-            req.descriptor.index = value & 0xFF;
-            req.descriptor.language_id = index;
-            code = LIBUSB_IOCTL_SET_DESCRIPTOR;
-            break;
-
-        case USB_REQ_GET_CONFIGURATION:
-            code = LIBUSB_IOCTL_GET_CONFIGURATION;
-            break;
-
-        case USB_REQ_SET_CONFIGURATION:
-            req.configuration.configuration = value;
-            code = LIBUSB_IOCTL_SET_CONFIGURATION;
-            break;
-
-        case USB_REQ_GET_INTERFACE:
-            req.intf.interface_number = index;
-            code = LIBUSB_IOCTL_GET_INTERFACE;
-            break;
-
-        case USB_REQ_SET_INTERFACE:
-            req.intf.interface_number = index;
-            req.intf.altsetting_number = value;
-            code = LIBUSB_IOCTL_SET_INTERFACE;
-            break;
-
-        default:
-            USBERR("invalid request 0x%x", request);
-            return -EINVAL;
-        }
-        break;
-
-    case USB_TYPE_VENDOR:
-    case USB_TYPE_CLASS:
-
-        req.vendor.type = (requesttype >> 5) & 0x03;
-        req.vendor.recipient = requesttype & 0x1F;
-        req.vendor.request = request;
-        req.vendor.value = value;
-        req.vendor.index = index;
-
-        if (requesttype & 0x80)
-            code = LIBUSB_IOCTL_VENDOR_READ;
-        else
-            code = LIBUSB_IOCTL_VENDOR_WRITE;
-        break;
-
-    case USB_TYPE_RESERVED:
-    default:
-        USBERR("invalid or unsupported request type: %x",
-                  requesttype);
-        return -EINVAL;
-    }
-
-    /* out request? */
-    if (!(requesttype & USB_ENDPOINT_IN))
-    {
-        if (!(out = malloc(sizeof(libusb_request) + size)))
-        {
-            USBERR0("memory allocation failed\n");
-            return -ENOMEM;
-        }
-
-        memcpy(out, &req, sizeof(libusb_request));
-        memcpy((char *)out + sizeof(libusb_request), bytes, size);
-        out_size = sizeof(libusb_request) + size;
-        in = NULL;
-        in_size = 0;
-    }
-
-    if (!_usb_io_sync(dev->impl_info, code, out, out_size, in, in_size, &read))
-    {
-        USBERR("sending control message failed, win error: %s\n", usb_win_error_to_string());
-        if (!(requesttype & USB_ENDPOINT_IN))
-        {
-            free(out);
-        }
-        return -usb_win_error_to_errno();
-    }
-
-    /* out request? */
-    if (!(requesttype & USB_ENDPOINT_IN))
-    {
-        free(out);
-        return size;
-    }
-    else
-        return read;
-}
-
-
-int usb_os_find_busses(struct usb_bus **busses)
-{
-    struct usb_bus *bus = NULL;
-
-    /* create one 'virtual' bus */
-
-    bus = malloc(sizeof(struct usb_bus));
-
-    if (!bus)
-    {
-        USBERR0("memory allocation failed\n");
-        return -ENOMEM;
-    }
-
-    memset(bus, 0, sizeof(*bus));
-    strcpy(bus->dirname, LIBUSB_BUS_NAME);
-
-    USBMSG("found %s\n", bus->dirname);
-
-    *busses = bus;
-
-    return 0;
-}
-
-int usb_os_find_devices(struct usb_bus *bus, struct usb_device **devices)
-{
-    int i;
-    struct usb_device *dev, *fdev = NULL;
-    char dev_name[LIBUSB_PATH_MAX];
-    int ret;
-    HANDLE handle;
-    libusb_request req;
-
-    for (i = 1; i < LIBUSB_MAX_DEVICES; i++)
-    {
-        ret = 0;
-
-        _snprintf(dev_name, sizeof(dev_name) - 1,"%s%04d",
-                  LIBUSB_DEVICE_NAME, i);
-
-        if (!(dev = malloc(sizeof(*dev))))
-        {
-            USBERR0("memory allocation failed\n");
-            return -ENOMEM;
-        }
-
-        memset(dev, 0, sizeof(*dev));
-        dev->bus = bus;
-        dev->devnum = (unsigned char)i;
-
-        handle = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
-                            FILE_FLAG_OVERLAPPED, NULL);
-
-        if (handle == INVALID_HANDLE_VALUE)
-        {
-            free(dev);
-            continue;
-        }
-
-        /* retrieve device descriptor */
-        req.descriptor.type = USB_DT_DEVICE;
-        req.descriptor.recipient = USB_RECIP_DEVICE;
-        req.descriptor.index = 0;
-        req.descriptor.language_id = 0;
-        req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-        _usb_io_sync(handle, LIBUSB_IOCTL_GET_DESCRIPTOR,
-                     &req, sizeof(libusb_request),
-                     &dev->descriptor, USB_DT_DEVICE_SIZE, &ret);
-
-        if (ret < USB_DT_DEVICE_SIZE)
-        {
-            USBERR0("couldn't read device descriptor\n");
-            free(dev);
-            CloseHandle(handle);
-            continue;
-        }
-
-        _snprintf(dev->filename, LIBUSB_PATH_MAX - 1, "%s--0x%04x-0x%04x",
-                  dev_name, dev->descriptor.idVendor, dev->descriptor.idProduct);
-
-        CloseHandle(handle);
-
-        LIST_ADD(fdev, dev);
-
-        USBMSG("found %s on %s\n", dev->filename, bus->dirname);
-    }
-
-    *devices = fdev;
-
-    return 0;
-}
-
-
-void usb_os_init(void)
-{
-    HANDLE dev;
-    libusb_request req;
-    int i;
-    int ret;
-    char dev_name[LIBUSB_PATH_MAX];
-
-    USBMSG("dll version: %d.%d.%d.%d\n",
-                VERSION_MAJOR, VERSION_MINOR,
-                VERSION_MICRO, VERSION_NANO);
-
-
-    for (i = 1; i < LIBUSB_MAX_DEVICES; i++)
-    {
-        /* build the Windows file name */
-        _snprintf(dev_name, sizeof(dev_name) - 1,"%s%04d",
-                  LIBUSB_DEVICE_NAME, i);
-
-        dev = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
-                         FILE_FLAG_OVERLAPPED, NULL);
-
-        if (dev == INVALID_HANDLE_VALUE)
-        {
-            continue;
-        }
-
-        if (!_usb_io_sync(dev, LIBUSB_IOCTL_GET_VERSION,
-                          &req, sizeof(libusb_request),
-                          &req, sizeof(libusb_request), &ret)
-                || (ret < sizeof(libusb_request)))
-        {
-            USBERR0("getting driver version failed\n");
-            CloseHandle(dev);
-            continue;
-        }
-        else
-        {
-            _usb_version.driver.major = req.version.major;
-            _usb_version.driver.minor = req.version.minor;
-            _usb_version.driver.micro = req.version.micro;
-            _usb_version.driver.nano = req.version.nano;
-
-            USBMSG("driver version: %d.%d.%d.%d\n",
-                        req.version.major, req.version.minor,
-                        req.version.micro, req.version.nano);
-
-            /* set debug level */
-            req.timeout = 0;
-            req.debug.level = usb_log_get_level();
-
-            if (!_usb_io_sync(dev, LIBUSB_IOCTL_SET_DEBUG_LEVEL,
-                              &req, sizeof(libusb_request),
-                              NULL, 0, NULL))
-            {
-                USBERR0("setting debug level failed");
-            }
-
-            CloseHandle(dev);
-            break;
-        }
-    }
-}
-
-
-int usb_resetep(usb_dev_handle *dev, unsigned int ep)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    req.endpoint.endpoint = (int)ep;
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_ABORT_ENDPOINT, &req,
-                      sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not abort ep 0x%02x, win error: %s\n", ep, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_ENDPOINT, &req,
-                      sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not reset ep 0x%02x, win error: %s\n", ep, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return 0;
-}
-
-int usb_clear_halt(usb_dev_handle *dev, unsigned int ep)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    req.endpoint.endpoint = (int)ep;
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_ENDPOINT, &req,
-                      sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not clear halt, ep 0x%02x, "
-                  "win error: %s", ep, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return 0;
-}
-
-int usb_reset(usb_dev_handle *dev)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_DEVICE,
-                      &req, sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not reset device, win error: %s\n", usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return 0;
-}
-
-int usb_reset_ex(usb_dev_handle *dev, unsigned int reset_type)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open\n");
-        return -EINVAL;
-    }
-
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-    req.reset_ex.reset_type = reset_type;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_RESET_DEVICE_EX,
-                      &req, sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not reset device, win error: %s\n", usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return 0;
-}
-
-const struct usb_version *usb_get_version(void)
-{
-    return &_usb_version;
-}
-
-void usb_set_debug(int level)
-{
-    HANDLE dev;
-    libusb_request req;
-    int i;
-    char dev_name[LIBUSB_PATH_MAX];
-
-    if (usb_log_get_level() || level)
-    {
-        USBMSG("setting debugging level to %d (%s)\n",
-                    level, level ? "on" : "off");
-    }
-
-    usb_log_set_level(level);
-
-    /* find a valid device */
-    for (i = 1; i < LIBUSB_MAX_DEVICES; i++)
-    {
-        /* build the Windows file name */
-        _snprintf(dev_name, sizeof(dev_name) - 1,"%s%04d",
-                  LIBUSB_DEVICE_NAME, i);
-
-        dev = CreateFile(dev_name, 0, 0, NULL, OPEN_EXISTING,
-                         FILE_FLAG_OVERLAPPED, NULL);
-
-        if (dev == INVALID_HANDLE_VALUE)
-        {
-            continue;
-        }
-
-        /* set debug level */
-        req.timeout = 0;
-        req.debug.level = usb_log_get_level();
-
-        if (!_usb_io_sync(dev, LIBUSB_IOCTL_SET_DEBUG_LEVEL,
-                          &req, sizeof(libusb_request),
-                          NULL, 0, NULL))
-        {
-            USBERR0("setting debug level failed\n");
-        }
-
-        CloseHandle(dev);
-
-        break;
-    }
-}
-
-int usb_os_determine_children(struct usb_bus *bus)
-{
-    struct usb_device *dev;
-    int i = 0;
-
-    /* add a virtual hub to the bus to emulate this feature */
-    if (_usb_add_virtual_hub(bus))
-    {
-        if (bus->root_dev->children)
-        {
-            free(bus->root_dev->children);
-        }
-
-        bus->root_dev->num_children = 0;
-        for (dev = bus->devices; dev; dev = dev->next)
-            bus->root_dev->num_children++;
-
-        bus->root_dev->children
-        = malloc(sizeof(struct usb_device *) * bus->root_dev->num_children);
-
-        for (dev = bus->devices; dev; dev = dev->next)
-            bus->root_dev->children[i++] = dev;
-    }
-
-    return 0;
-}
-
-static int _usb_cancel_io(usb_context_t *context)
-{
-    int ret;
-    ret = _usb_abort_ep(context->dev, context->req.endpoint.endpoint);
-    WaitForSingleObject(context->ol.hEvent, 0);
-    return ret;
-}
-
-static int _usb_abort_ep(usb_dev_handle *dev, unsigned int ep)
-{
-    libusb_request req;
-
-    if (dev->impl_info == INVALID_HANDLE_VALUE)
-    {
-        USBERR0("device not open");
-        return -EINVAL;
-    }
-
-    req.endpoint.endpoint = (int)ep;
-    req.timeout = LIBUSB_DEFAULT_TIMEOUT;
-
-    if (!_usb_io_sync(dev->impl_info, LIBUSB_IOCTL_ABORT_ENDPOINT, &req,
-                      sizeof(libusb_request), NULL, 0, NULL))
-    {
-        USBERR("could not abort ep 0x%02x, win error: %s\n", ep, usb_win_error_to_string());
-        return -usb_win_error_to_errno();
-    }
-
-    return 0;
-}
-
-static int _usb_io_sync(HANDLE dev, unsigned int code, void *out, int out_size,
-                        void *in, int in_size, int *ret)
-{
-    OVERLAPPED ol;
-    DWORD _ret;
-
-    memset(&ol, 0, sizeof(ol));
-
-    if (ret)
-        *ret = 0;
-
-    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-
-    if (!ol.hEvent)
-        return FALSE;
-
-    if (!DeviceIoControl(dev, code, out, out_size, in, in_size, NULL, &ol))
-    {
-        if (GetLastError() != ERROR_IO_PENDING)
-        {
-            CloseHandle(ol.hEvent);
-            return FALSE;
-        }
-    }
-
-    if (GetOverlappedResult(dev, &ol, &_ret, TRUE))
-    {
-        if (ret)
-            *ret = (int)_ret;
-        CloseHandle(ol.hEvent);
-        return TRUE;
-    }
-
-    CloseHandle(ol.hEvent);
-    return FALSE;
-}
-
-static int _usb_add_virtual_hub(struct usb_bus *bus)
-{
-    struct usb_device *dev;
-
-    if (!bus->root_dev)
-    {
-        if (!(dev = malloc(sizeof(*dev))))
-            return FALSE;
-
-        memset(dev, 0, sizeof(*dev));
-        strcpy(dev->filename, "virtual-hub");
-        dev->bus = bus;
-
-        dev->descriptor.bLength = USB_DT_DEVICE_SIZE;
-        dev->descriptor.bDescriptorType = USB_DT_DEVICE;
-        dev->descriptor.bcdUSB = 0x0200;
-        dev->descriptor.bDeviceClass = USB_CLASS_HUB;
-        dev->descriptor.bDeviceSubClass = 0;
-        dev->descriptor.bDeviceProtocol = 0;
-        dev->descriptor.bMaxPacketSize0 = 64;
-        dev->descriptor.idVendor = 0;
-        dev->descriptor.idProduct = 0;
-        dev->descriptor.bcdDevice = 0x100;
-        dev->descriptor.iManufacturer = 0;
-        dev->descriptor.iProduct = 0;
-        dev->descriptor.iSerialNumber = 0;
-        dev->descriptor.bNumConfigurations = 0;
-
-        bus->root_dev = dev;
-    }
-
-    return TRUE;
-}
-
-static void _usb_free_bus_list(struct usb_bus *bus)
-{
-    if (bus)
-    {
-        _usb_free_bus_list(bus->next);
-        if (bus->root_dev)
-            usb_free_dev(bus->root_dev);
-        _usb_free_dev_list(bus->devices);
-        usb_free_bus(bus);
-    }
-}
-
-static void _usb_free_dev_list(struct usb_device *dev)
-{
-    if (dev)
-    {
-        _usb_free_dev_list(dev->next);
-        usb_free_dev(dev);
-    }
-}
-
-static void _usb_deinit(void)
-{
-    _usb_free_bus_list(usb_get_busses());
-}
diff --git a/external/libusb/src/windows.cpp b/external/libusb/src/windows.cpp
new file mode 100644
index 00000000..2ac8b705
--- /dev/null
+++ b/external/libusb/src/windows.cpp
@@ -0,0 +1,470 @@
+/*
+ * avrdude - A Downloader/Uploader for AVR device programmers
+ * Copyright (C) 2019 Marius Greuel
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "libwinusb.h"
+
+extern "C"
+{
+#include "error.h"
+#include "usbi.h"
+}
+
+static std::string CreateMessageFromError(HRESULT hr)
+{
+    LPSTR pszMessage = nullptr;
+    DWORD dwChars = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, hr, 0, (LPSTR)&pszMessage, 0, nullptr);
+    if (dwChars > 0 && pszMessage != nullptr)
+    {
+        if (dwChars > 0 && pszMessage[dwChars - 1] == '\n')
+            dwChars--;
+
+        if (dwChars > 0 && pszMessage[dwChars - 1] == '\r')
+            dwChars--;
+
+        std::string message = std::string(pszMessage, dwChars);
+        LocalFree(pszMessage);
+        return message;
+    }
+    else
+    {
+        return "Unknown error";
+    }
+}
+
+static int usb_hresult_to_errno(HRESULT hr)
+{
+    if (SUCCEEDED(hr))
+        return 0;
+
+    switch (hr)
+    {
+    case HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER):
+        return EINVAL;
+    case HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY):
+        return ENOMEM;
+    case HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED):
+        return EPERM;
+    case HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE):
+    case HRESULT_FROM_WIN32(ERROR_BAD_DEVICE):
+    case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
+        return ENODEV;
+    case HRESULT_FROM_WIN32(ERROR_SEM_TIMEOUT):
+    case HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED):
+        return ETIMEDOUT;
+    default:
+        return EIO;
+    }
+}
+
+void usb_os_init(void)
+{
+}
+
+void usb_set_debug(int level)
+{
+    if (usb_log_get_level() || level)
+    {
+        USBMSG("setting debugging level to %d (%s)\n", level, level ? "on" : "off");
+    }
+
+    usb_log_set_level(static_cast<USB_LOG_LEVEL>(level));
+}
+
+int usb_os_find_busses(struct usb_bus** busses)
+{
+    auto bus = static_cast<struct usb_bus*>(std::malloc(sizeof(struct usb_bus)));
+    if (bus == nullptr)
+    {
+        USBERR("memory allocation failed\n");
+        return -ENOMEM;
+    }
+
+    std::memset(bus, 0, sizeof(*bus));
+    std::strcpy(bus->dirname, "bus-0");
+
+    USBMSG("found %s\n", bus->dirname);
+
+    *busses = bus;
+    return 0;
+}
+
+int usb_os_find_devices(struct usb_bus* bus, struct usb_device** devices)
+{
+    if (bus == nullptr)
+    {
+        return -EINVAL;
+    }
+
+    if (devices == nullptr)
+    {
+        return -EINVAL;
+    }
+
+    LibWinUsb::UsbDeviceEnumerator enumerator;
+    HRESULT hr = enumerator.EnumerateDevices();
+    if (FAILED(hr))
+    {
+        return -1;
+    }
+
+    struct usb_device* fdev = nullptr;
+
+    const auto& list = enumerator.GetDevices();
+    for (const auto& item : list)
+    {
+        struct usb_device* dev = static_cast<struct usb_device*>(std::malloc(sizeof(struct usb_device)));
+        if (dev == nullptr)
+        {
+            USBERR0("memory allocation failed\n");
+            return -ENOMEM;
+        }
+
+        std::memset(dev, 0, sizeof(*dev));
+
+        std::unique_ptr<LibWinUsb::UsbDriver> driver;
+        if (FAILED(item->Open(driver)) ||
+            FAILED(driver->GetDescriptor(USB_DT_DEVICE, 0, 0, &dev->descriptor, sizeof(dev->descriptor), nullptr, 5000)))
+        {
+            USBERR0("couldn't read device descriptor\n");
+            free(dev);
+            continue;
+        }
+
+        _snprintf(dev->filename, LIBUSB_PATH_MAX - 1, "%s--%s",
+            LibWinUsb::Encoding::ToAnsi(item->GetInterfacePath()).c_str(),
+            LibWinUsb::Encoding::ToAnsi(item->GetService()).c_str());
+
+        LIST_ADD(fdev, dev);
+
+        USBMSG("found %s on %s\n", dev->filename, bus->dirname);
+    }
+
+    *devices = fdev;
+    return 0;
+}
+
+int usb_os_determine_children(struct usb_bus* bus)
+{
+    return 0;
+}
+
+int usb_os_open(usb_dev_handle* dev)
+{
+    if (dev == nullptr)
+    {
+        USBERR("invalid device handle %p", dev);
+        return -EINVAL;
+    }
+
+    dev->impl_info = nullptr;
+    dev->config = 0;
+    dev->interface = -1;
+    dev->altsetting = -1;
+
+    std::string dev_name = dev->device->filename;
+    auto pos = dev_name.find("--");
+    if (pos == std::string::npos)
+    {
+        USBERR("invalid file name %s\n", dev->device->filename);
+        return -ENOENT;
+    }
+
+    std::unique_ptr<LibWinUsb::UsbDriver> driver;
+    HRESULT hr = LibWinUsb::UsbDevice::Open(LibWinUsb::Encoding::ToUnicode(dev_name.substr(pos + 2)), LibWinUsb::Encoding::ToUnicode(dev_name.substr(0, pos)), driver);
+    if (FAILED(hr))
+    {
+        USBERR("failed to open %s: win error: %s\n", dev->device->filename, CreateMessageFromError(hr).c_str());
+        return -ENOENT;
+    }
+
+    dev->impl_info = driver.release();
+
+    return 0;
+}
+
+int usb_os_close(usb_dev_handle* dev)
+{
+    delete static_cast<LibWinUsb::UsbDriver*>(dev->impl_info);
+
+    dev->impl_info = nullptr;
+    dev->interface = -1;
+    dev->altsetting = -1;
+
+    return 0;
+}
+
+int usb_control_msg(usb_dev_handle* dev, int requestType, int request, int value, int index, char* bytes, int size, int timeout)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("error: device not open\n");
+        return -EINVAL;
+    }
+
+    if (requestType == (USB_ENDPOINT_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) && request == USB_REQ_GET_DESCRIPTOR)
+    {
+        uint32_t bytesReturned = 0;
+        HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->GetDescriptor(
+            static_cast<uint8_t>(value >> 8),
+            static_cast<uint8_t>(value),
+            static_cast<uint16_t>(index),
+            bytes,
+            static_cast<uint16_t>(size),
+            &bytesReturned,
+            timeout);
+        if (FAILED(hr))
+        {
+            return -usb_hresult_to_errno(hr);
+        }
+
+        return static_cast<int>(bytesReturned);
+    }
+    else
+    {
+        uint32_t bytesReturned = 0;
+        HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->ControlTransfer(
+            static_cast<uint8_t>(requestType),
+            static_cast<uint8_t>(request),
+            static_cast<uint16_t>(value),
+            static_cast<uint16_t>(index),
+            bytes,
+            static_cast<uint16_t>(size),
+            &bytesReturned,
+            timeout);
+        if (FAILED(hr))
+        {
+            return -usb_hresult_to_errno(hr);
+        }
+
+        return static_cast<int>(bytesReturned);
+    }
+}
+
+int usb_set_configuration(usb_dev_handle* dev, int configuration)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("error: device not open\n");
+        return -EINVAL;
+    }
+
+    if (dev->config == configuration)
+    {
+        return 0;
+    }
+
+    if (dev->interface >= 0)
+    {
+        USBERR0("can't change configuration, an interface is still in use (claimed)\n");
+        return -EINVAL;
+    }
+
+    HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->SetConfiguration(static_cast<uint8_t>(configuration));
+    if (FAILED(hr))
+    {
+        return -usb_hresult_to_errno(hr);
+    }
+
+    dev->config = configuration;
+    dev->interface = -1;
+    dev->altsetting = -1;
+
+    return 0;
+}
+
+int usb_claim_interface(usb_dev_handle* dev, int interface)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("device not open\n");
+        return -EINVAL;
+    }
+
+    if (dev->config == 0)
+    {
+        USBERR("could not claim interface %d, invalid configuration %d\n", interface, dev->config);
+        return -EINVAL;
+    }
+
+    if (dev->interface == interface)
+    {
+        return 0;
+    }
+
+    HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->ClaimInterface(static_cast<uint8_t>(interface));
+    if (FAILED(hr))
+    {
+        return -usb_hresult_to_errno(hr);
+    }
+
+    dev->interface = interface;
+    dev->altsetting = 0;
+
+    return 0;
+}
+
+int usb_release_interface(usb_dev_handle* dev, int interface)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("device not open\n");
+        return -EINVAL;
+    }
+
+    if (dev->config == 0)
+    {
+        USBERR("could not release interface %d, invalid configuration %d\n", interface, dev->config);
+        return -EINVAL;
+    }
+
+    HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->ReleaseInterface(static_cast<uint8_t>(interface));
+    if (FAILED(hr))
+    {
+        return -usb_hresult_to_errno(hr);
+    }
+
+    dev->interface = -1;
+    dev->altsetting = -1;
+
+    return 0;
+}
+
+int usb_set_altinterface(usb_dev_handle* dev, int alternate)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("device not open\n");
+        return -EINVAL;
+    }
+
+    if (dev->config == 0)
+    {
+        USBERR("could not set alt interface %d: invalid configuration %d\n", alternate, dev->config);
+        return -EINVAL;
+    }
+
+    if (dev->interface < 0)
+    {
+        USBERR("could not set alt interface %d: no interface claimed\n", alternate);
+        return -EINVAL;
+    }
+
+    HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->SetAlternateSetting(
+        static_cast<uint8_t>(dev->interface),
+        static_cast<uint8_t>(alternate));
+    if (FAILED(hr))
+    {
+        return -usb_hresult_to_errno(hr);
+    }
+
+    dev->altsetting = alternate;
+
+    return 0;
+}
+
+int usb_bulk_read(usb_dev_handle* dev, int ep, char* bytes, int size, int timeout)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("device not open\n");
+        return -EINVAL;
+    }
+
+    if (dev->config == 0)
+    {
+        USBERR("invalid configuration %d\n", dev->config);
+        return -EINVAL;
+    }
+
+    if (dev->interface < 0)
+    {
+        USBERR("invalid interface %d\n", dev->interface);
+        return -EINVAL;
+    }
+
+    if ((ep & USB_ENDPOINT_IN) == 0)
+    {
+        USBERR("invalid endpoint 0x%02x", ep);
+        return -EINVAL;
+    }
+
+    uint32_t bytesRead = 0;
+    HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->ReadPipe(
+        static_cast<uint8_t>(ep),
+        bytes,
+        size,
+        &bytesRead,
+        timeout);
+    if (FAILED(hr))
+    {
+        return -usb_hresult_to_errno(hr);
+    }
+
+    return static_cast<int>(bytesRead);
+}
+
+int usb_bulk_write(usb_dev_handle* dev, int ep, char* bytes, int size, int timeout)
+{
+    if (dev->impl_info == nullptr)
+    {
+        USBERR0("device not open\n");
+        return -EINVAL;
+    }
+
+    if (dev->config == 0)
+    {
+        USBERR("invalid configuration %d\n", dev->config);
+        return -EINVAL;
+    }
+
+    if (dev->interface < 0)
+    {
+        USBERR("invalid interface %d\n", dev->interface);
+        return -EINVAL;
+    }
+
+    if ((ep & USB_ENDPOINT_IN) != 0)
+    {
+        USBERR("invalid endpoint 0x%02x", ep);
+        return -EINVAL;
+    }
+
+    uint32_t bytesWritten = 0;
+    HRESULT hr = static_cast<LibWinUsb::UsbDriver*>(dev->impl_info)->WritePipe(
+        static_cast<uint8_t>(ep),
+        bytes,
+        size,
+        &bytesWritten,
+        timeout);
+    if (FAILED(hr))
+    {
+        return -usb_hresult_to_errno(hr);
+    }
+
+    return static_cast<int>(bytesWritten);
+}
+
+int usb_interrupt_read(usb_dev_handle* dev, int ep, char* bytes, int size, int timeout)
+{
+    return usb_bulk_read(dev, ep, bytes, size, timeout);
+}
+
+int usb_interrupt_write(usb_dev_handle* dev, int ep, char* bytes, int size, int timeout)
+{
+    return usb_bulk_write(dev, ep, bytes, size, timeout);
+}