diff --git a/Source/Core/Common/WindowsDevice.cpp b/Source/Core/Common/WindowsDevice.cpp index 405ea5e25b..4bbd213774 100644 --- a/Source/Core/Common/WindowsDevice.cpp +++ b/Source/Core/Common/WindowsDevice.cpp @@ -14,29 +14,6 @@ namespace Common { -std::wstring GetDeviceProperty(const HDEVINFO& device_info, const PSP_DEVINFO_DATA device_data, - const DEVPROPKEY* requested_property) -{ - DWORD required_size = 0; - DEVPROPTYPE device_property_type; - BOOL result; - - result = SetupDiGetDeviceProperty(device_info, device_data, requested_property, - &device_property_type, nullptr, 0, &required_size, 0); - if (!result && GetLastError() != ERROR_INSUFFICIENT_BUFFER) - return std::wstring(); - - std::vector unicode_buffer(required_size / sizeof(TCHAR)); - - result = SetupDiGetDeviceProperty( - device_info, device_data, requested_property, &device_property_type, - reinterpret_cast(unicode_buffer.data()), required_size, nullptr, 0); - if (!result) - return std::wstring(); - - return std::wstring(unicode_buffer.data()); -} - std::optional GetPropertyHelper(auto function, auto dev, const DEVPROPKEY* requested_property, DEVPROPTYPE expected_type) @@ -83,6 +60,36 @@ std::optional GetDeviceInterfaceStringProperty(LPCWSTR iface, DEVPROP_TYPE_STRING); } +NullTerminatedStringList GetDeviceInterfaceList(LPGUID iface_class_guid, DEVINSTID device_id, + ULONG flags) +{ + while (true) + { + ULONG list_size = 0; + const auto size_result = + CM_Get_Device_Interface_List_Size(&list_size, iface_class_guid, device_id, flags); + if (size_result != CR_SUCCESS || list_size == 0) + list_size = 1; + + auto buffer = std::make_unique_for_overwrite(list_size); + const auto list_result = + CM_Get_Device_Interface_List(iface_class_guid, device_id, buffer.get(), list_size, flags); + + // "A new device can be added to the system causing the size returned to no longer be valid." + // Microsoft recommends trying again in a loop. + if (list_result == CR_BUFFER_SMALL) + continue; + + if (list_result != CR_SUCCESS) + { + ERROR_LOG_FMT(COMMON, "CM_Get_Device_Interface_List: {}", list_result); + buffer[0] = 0; + } + + return {std::move(buffer)}; + } +} + } // namespace Common #endif diff --git a/Source/Core/Common/WindowsDevice.h b/Source/Core/Common/WindowsDevice.h index 463c6299b4..8c682ac850 100644 --- a/Source/Core/Common/WindowsDevice.h +++ b/Source/Core/Common/WindowsDevice.h @@ -5,6 +5,8 @@ #ifdef _WIN32 +#include +#include #include #include @@ -22,16 +24,53 @@ namespace Common { -// Obtains a device property and returns it as a wide string. -std::wstring GetDeviceProperty(const HANDLE& device_info, const PSP_DEVINFO_DATA device_data, - const DEVPROPKEY* requested_property); - std::optional GetDevNodeStringProperty(DEVINST device, const DEVPROPKEY* requested_property); std::optional GetDeviceInterfaceStringProperty(LPCWSTR iface, const DEVPROPKEY* requested_property); +// Allows iterating null-separated/terminated string lists returned by the Windows API. +template +struct NullTerminatedStringList +{ + class Iterator + { + friend NullTerminatedStringList; + + public: + constexpr T* operator*() { return m_ptr; } + + constexpr Iterator& operator++() + { + m_ptr += std::basic_string_view(m_ptr).size() + 1; + return *this; + } + + constexpr Iterator operator++(int) + { + const auto result{*this}; + ++*this; + return result; + } + + constexpr bool operator==(std::default_sentinel_t) const { return *m_ptr == T{}; } + + private: + constexpr Iterator(T* ptr) : m_ptr{ptr} {} + + T* m_ptr; + }; + + constexpr auto begin() const { return Iterator{list.get()}; } + constexpr auto end() const { return std::default_sentinel; } + + std::unique_ptr list; +}; + +NullTerminatedStringList GetDeviceInterfaceList(LPGUID iface_class_guid, DEVINSTID device_id, + ULONG flags); + } // namespace Common #endif diff --git a/Source/Core/Core/HW/WiimoteReal/IOWin.cpp b/Source/Core/Core/HW/WiimoteReal/IOWin.cpp index 63b3123d6e..e1cf4971e9 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOWin.cpp +++ b/Source/Core/Core/HW/WiimoteReal/IOWin.cpp @@ -583,20 +583,8 @@ auto WiimoteScannerWindows::FindWiimoteHIDDevices() -> FindResults // Enumerate connected HID interfaces IDs. auto class_guid = GUID_DEVINTERFACE_HID; constexpr ULONG flags = CM_GET_DEVICE_INTERFACE_LIST_PRESENT; - ULONG list_size = 0; - CM_Get_Device_Interface_List_Size(&list_size, &class_guid, nullptr, flags); - const auto buffer = std::make_unique_for_overwrite(list_size); - const auto list_result = - CM_Get_Device_Interface_List(&class_guid, nullptr, buffer.get(), list_size, flags); - if (list_result != CR_SUCCESS) - { - ERROR_LOG_FMT(WIIMOTE, "CM_Get_Device_Interface_List: {}", list_result); - return results; - } - - for (const WCHAR* hid_iface = buffer.get(); *hid_iface != L'\0'; - hid_iface += wcslen(hid_iface) + 1) + for (auto* hid_iface : Common::GetDeviceInterfaceList(&class_guid, nullptr, flags)) { // TODO: WiimoteWindows::GetId() does a redundant conversion. const auto hid_iface_utf8 = WStringToUTF8(hid_iface); diff --git a/Source/Core/Core/USBUtils.cpp b/Source/Core/Core/USBUtils.cpp index b2edfd0b22..79b3a1958c 100644 --- a/Source/Core/Core/USBUtils.cpp +++ b/Source/Core/Core/USBUtils.cpp @@ -3,6 +3,7 @@ #include "Core/USBUtils.h" +#include #include #include #include @@ -21,9 +22,11 @@ #include #endif #ifdef _WIN32 -#include #include #include +#include +// initguid.h must be included before usbiodef.h +#include #include "Common/StringUtil.h" #include "Common/WindowsDevice.h" @@ -119,43 +122,36 @@ static std::optional GetDeviceNameUsingKnownDevices(u16 vid, u16 pi } #ifdef _WIN32 -static std::optional GetDeviceNameUsingSetupAPI(u16 vid, u16 pid) +static std::optional GetDeviceNameUsingCfgMgr32(u16 vid, u16 pid) { - std::optional device_name; + auto class_guid = GUID_DEVINTERFACE_USB_DEVICE; + const ULONG flags = CM_GET_DEVICE_INTERFACE_LIST_PRESENT; const std::wstring filter = fmt::format(L"VID_{:04X}&PID_{:04X}", vid, pid); - HDEVINFO dev_info = - SetupDiGetClassDevs(nullptr, nullptr, nullptr, DIGCF_PRESENT | DIGCF_ALLCLASSES); - if (dev_info == INVALID_HANDLE_VALUE) - return std::nullopt; - SP_DEVINFO_DATA dev_info_data; - dev_info_data.cbSize = sizeof(SP_DEVINFO_DATA); - - for (DWORD i = 0; SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data); ++i) + for (auto* iface : Common::GetDeviceInterfaceList(&class_guid, nullptr, flags)) { - TCHAR instance_id[MAX_DEVICE_ID_LEN]; - if (CM_Get_Device_ID(dev_info_data.DevInst, instance_id, MAX_DEVICE_ID_LEN, 0) != CR_SUCCESS) + if (!std::basic_string_view{iface}.contains(filter)) continue; - const std::wstring_view id_wstr(instance_id); - if (id_wstr.find(filter) == std::wstring::npos) - continue; + auto dev_inst_id = Common::GetDeviceInterfaceStringProperty(iface, &DEVPKEY_Device_InstanceId); + if (!dev_inst_id.has_value()) + return std::nullopt; - std::wstring property_value = - Common::GetDeviceProperty(dev_info, &dev_info_data, &DEVPKEY_Device_FriendlyName); - if (property_value.empty()) + DEVINST dev_inst{}; + if (CM_Locate_DevNode(&dev_inst, dev_inst_id->data(), CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { - property_value = - Common::GetDeviceProperty(dev_info, &dev_info_data, &DEVPKEY_Device_DeviceDesc); + ERROR_LOG_FMT(COMMON, "CM_Locate_DevNode"); + return std::nullopt; } - if (!property_value.empty()) - device_name = WStringToUTF8(property_value); - break; + // FYI: BusReportedDeviceDesc seems to be more of a friendly name + // while DeviceDesc tends to be something more like "USB Input Device". + return Common::GetDevNodeStringProperty(dev_inst, &DEVPKEY_Device_BusReportedDeviceDesc) + .or_else(std::bind(Common::GetDevNodeStringProperty, dev_inst, &DEVPKEY_Device_DeviceDesc)) + .transform(WStringToUTF8); } - SetupDiDestroyDeviceInfoList(dev_info); - return device_name; + return std::nullopt; } #endif @@ -245,7 +241,7 @@ std::optional GetDeviceNameFromVIDPID(u16 vid, u16 pid) &GetDeviceNameUsingHWDB, #endif #ifdef _WIN32 - &GetDeviceNameUsingSetupAPI, + &GetDeviceNameUsingCfgMgr32, #endif #if defined(__LIBUSB__) && !defined(_WIN32) &GetDeviceNameUsingLibUSB,