Merge pull request #13770 from JoshuaVandaele/usbutils
USBUtils: Refactor USB device handling
This commit is contained in:
20 files changed
+423
-332
No files matched your search
@@ -551,6 +551,8 @@ add_library(core
|
||||
TimePlayed.h
|
||||
TitleDatabase.cpp
|
||||
TitleDatabase.h
|
||||
USBUtils.cpp
|
||||
USBUtils.h
|
||||
WC24PatchEngine.cpp
|
||||
WC24PatchEngine.h
|
||||
WiiRoot.cpp
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/SI/SI_Device.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/USBUtils.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
|
||||
@@ -551,40 +552,35 @@ const Info<bool> MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII{
|
||||
const Info<std::string> MAIN_USB_PASSTHROUGH_DEVICES{{System::Main, "USBPassthrough", "Devices"},
|
||||
""};
|
||||
|
||||
static std::set<std::pair<u16, u16>> LoadUSBWhitelistFromString(const std::string& devices_string)
|
||||
static std::set<USBUtils::DeviceInfo> LoadUSBWhitelistFromString(const std::string& devices_string)
|
||||
{
|
||||
std::set<std::pair<u16, u16>> devices;
|
||||
std::set<USBUtils::DeviceInfo> devices;
|
||||
for (const auto& pair : SplitString(devices_string, ','))
|
||||
{
|
||||
const auto index = pair.find(':');
|
||||
if (index == std::string::npos)
|
||||
continue;
|
||||
|
||||
const u16 vid = static_cast<u16>(strtol(pair.substr(0, index).c_str(), nullptr, 16));
|
||||
const u16 pid = static_cast<u16>(strtol(pair.substr(index + 1).c_str(), nullptr, 16));
|
||||
if (vid && pid)
|
||||
devices.emplace(vid, pid);
|
||||
auto device = USBUtils::DeviceInfo::FromString(pair);
|
||||
if (device)
|
||||
devices.emplace(*device);
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
static std::string SaveUSBWhitelistToString(const std::set<std::pair<u16, u16>>& devices)
|
||||
static std::string SaveUSBWhitelistToString(const std::set<USBUtils::DeviceInfo>& devices)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
for (const auto& device : devices)
|
||||
oss << fmt::format("{:04x}:{:04x}", device.first, device.second) << ',';
|
||||
oss << device.ToString() << ',';
|
||||
std::string devices_string = oss.str();
|
||||
if (!devices_string.empty())
|
||||
devices_string.pop_back();
|
||||
return devices_string;
|
||||
}
|
||||
|
||||
std::set<std::pair<u16, u16>> GetUSBDeviceWhitelist()
|
||||
std::set<USBUtils::DeviceInfo> GetUSBDeviceWhitelist()
|
||||
{
|
||||
return LoadUSBWhitelistFromString(Config::Get(Config::MAIN_USB_PASSTHROUGH_DEVICES));
|
||||
}
|
||||
|
||||
void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
|
||||
void SetUSBDeviceWhitelist(const std::set<USBUtils::DeviceInfo>& devices)
|
||||
{
|
||||
Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Core/USBUtils.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
// DSP Backend Types
|
||||
@@ -358,8 +359,8 @@ extern const Info<std::string> MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS;
|
||||
|
||||
extern const Info<bool> MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII;
|
||||
extern const Info<std::string> MAIN_USB_PASSTHROUGH_DEVICES;
|
||||
std::set<std::pair<u16, u16>> GetUSBDeviceWhitelist();
|
||||
void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
|
||||
std::set<USBUtils::DeviceInfo> GetUSBDeviceWhitelist();
|
||||
void SetUSBDeviceWhitelist(const std::set<USBUtils::DeviceInfo>& devices);
|
||||
|
||||
// Main.EmulatedUSBDevices
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
#include "Core/IOS/USB/Host.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -41,7 +40,9 @@ constexpr libusb_transfer_cb_fn LibUSBMemFunCallback()
|
||||
};
|
||||
}
|
||||
|
||||
bool IsBluetoothDevice(const libusb_device_descriptor& descriptor)
|
||||
} // namespace
|
||||
|
||||
bool LibUSBBluetoothAdapter::IsBluetoothDevice(const libusb_device_descriptor& descriptor)
|
||||
{
|
||||
constexpr u8 SUBCLASS = 0x01;
|
||||
constexpr u8 PROTOCOL_BLUETOOTH = 0x01;
|
||||
@@ -56,8 +57,6 @@ bool IsBluetoothDevice(const libusb_device_descriptor& descriptor)
|
||||
descriptor.idVendor, descriptor.idProduct);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool LibUSBBluetoothAdapter::IsWiiBTModule() const
|
||||
{
|
||||
return m_is_wii_bt_module;
|
||||
@@ -454,41 +453,6 @@ bool LibUSBBluetoothAdapter::OpenDevice(const libusb_device_descriptor& device_d
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo> LibUSBBluetoothAdapter::ListDevices()
|
||||
{
|
||||
std::vector<BluetoothDeviceInfo> device_list;
|
||||
LibusbUtils::Context context;
|
||||
|
||||
if (!context.IsValid())
|
||||
return {};
|
||||
|
||||
int result = context.GetDeviceList([&device_list](libusb_device* device) {
|
||||
auto [config_ret, config] = LibusbUtils::MakeConfigDescriptor(device, 0);
|
||||
if (config_ret != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
libusb_device_descriptor desc;
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (IsBluetoothDevice(desc))
|
||||
{
|
||||
const std::string device_name =
|
||||
IOS::HLE::USBHost::GetDeviceNameFromVIDPID(desc.idVendor, desc.idProduct);
|
||||
device_list.push_back({desc.idVendor, desc.idProduct, device_name});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Failed to get device list: {}", LibusbUtils::ErrorWrap(result));
|
||||
return device_list;
|
||||
}
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::HandleOutputTransfer(libusb_transfer* tr)
|
||||
{
|
||||
HandleTransferError(tr);
|
||||
|
||||
@@ -23,15 +23,8 @@ class LibUSBBluetoothAdapter
|
||||
public:
|
||||
using BufferType = Common::UniqueBuffer<u8>;
|
||||
|
||||
struct BluetoothDeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BluetoothDeviceInfo> ListDevices();
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
static bool IsBluetoothDevice(const libusb_device_descriptor& descriptor);
|
||||
static bool HasConfiguredBluetoothDevice();
|
||||
|
||||
// Public interface is intended to be used by a single thread.
|
||||
|
||||
@@ -11,21 +11,6 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/xchar.h>
|
||||
#ifdef HAVE_LIBUDEV
|
||||
#include <libudev.h>
|
||||
#endif
|
||||
#ifdef __LIBUSB__
|
||||
#include <libusb.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <SetupAPI.h>
|
||||
#include <cfgmgr32.h>
|
||||
#include <devpkey.h>
|
||||
#endif
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
@@ -64,107 +49,6 @@ std::optional<IPCReply> USBHost::Open(const OpenRequest& request)
|
||||
return IPCReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
std::string USBHost::GetDeviceNameFromVIDPID(u16 vid, u16 pid)
|
||||
{
|
||||
std::string device_name;
|
||||
#ifdef _WIN32
|
||||
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 device_name;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
continue;
|
||||
|
||||
const std::wstring_view id_wstr(instance_id);
|
||||
if (id_wstr.find(filter) == std::wstring::npos)
|
||||
continue;
|
||||
|
||||
std::wstring property_value =
|
||||
Common::GetDeviceProperty(dev_info, &dev_info_data, &DEVPKEY_Device_FriendlyName);
|
||||
if (property_value.empty())
|
||||
{
|
||||
property_value =
|
||||
Common::GetDeviceProperty(dev_info, &dev_info_data, &DEVPKEY_Device_DeviceDesc);
|
||||
}
|
||||
|
||||
device_name = WStringToUTF8(property_value);
|
||||
break;
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(dev_info);
|
||||
if (!device_name.empty())
|
||||
return device_name;
|
||||
#endif
|
||||
// libusb can cause BSODs with certain bad OEM drivers on Windows when opening a device.
|
||||
// All offending drivers are known to depend on the BthUsb.sys driver, which is a Miniport Driver
|
||||
// for Bluetooth.
|
||||
// Known offenders:
|
||||
// - btfilter.sys from Qualcomm Atheros Communications
|
||||
// - ibtusb.sys from Intel Corporation
|
||||
#if defined(__LIBUSB__) && !defined(_WIN32)
|
||||
LibusbUtils::Context context;
|
||||
|
||||
if (!context.IsValid())
|
||||
return device_name;
|
||||
|
||||
context.GetDeviceList([&device_name, vid, pid](libusb_device* device) {
|
||||
libusb_device_descriptor desc{};
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (desc.idVendor != vid || desc.idProduct != pid)
|
||||
return true;
|
||||
|
||||
if (desc.iProduct == 0)
|
||||
return false;
|
||||
|
||||
libusb_device_handle* handle{};
|
||||
if (libusb_open(device, &handle) != LIBUSB_SUCCESS)
|
||||
return false;
|
||||
|
||||
device_name = LibusbUtils::GetStringDescriptor(handle, desc.iProduct).value_or("");
|
||||
libusb_close(handle);
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!device_name.empty())
|
||||
return device_name;
|
||||
#endif
|
||||
#ifdef HAVE_LIBUDEV
|
||||
udev* udev = udev_new();
|
||||
if (!udev)
|
||||
return device_name;
|
||||
|
||||
udev_hwdb* hwdb = udev_hwdb_new(udev);
|
||||
if (hwdb)
|
||||
{
|
||||
const std::string modalias = fmt::format("usb:v{:04X}p{:04X}*", vid, pid);
|
||||
udev_list_entry* entries = udev_hwdb_get_properties_list_entry(hwdb, modalias.c_str(), 0);
|
||||
|
||||
if (entries)
|
||||
{
|
||||
udev_list_entry* device_name_entry =
|
||||
udev_list_entry_get_by_name(entries, "ID_MODEL_FROM_DATABASE");
|
||||
if (device_name_entry)
|
||||
{
|
||||
device_name = udev_list_entry_get_value(device_name_entry);
|
||||
}
|
||||
}
|
||||
udev_hwdb_unref(hwdb);
|
||||
}
|
||||
#endif
|
||||
return device_name;
|
||||
}
|
||||
|
||||
void USBHost::DoState(PointerWrap& p)
|
||||
{
|
||||
Device::DoState(p);
|
||||
|
||||
@@ -32,7 +32,6 @@ public:
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
void OnDevicesChanged(const USBScanner::DeviceMap& new_devices);
|
||||
static std::string GetDeviceNameFromVIDPID(u16 vid, u16 pid);
|
||||
|
||||
protected:
|
||||
enum class ChangeEvent
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "Core/IOS/USB/LibusbDevice.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/USBUtils.h"
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
@@ -142,7 +143,7 @@ bool USBScanner::AddNewDevices(DeviceMap* new_devices) const
|
||||
#ifdef __LIBUSB__
|
||||
if (!Core::WantsDeterminism())
|
||||
{
|
||||
auto whitelist = Config::GetUSBDeviceWhitelist();
|
||||
const auto whitelist = Config::GetUSBDeviceWhitelist();
|
||||
if (whitelist.empty())
|
||||
return true;
|
||||
|
||||
@@ -155,7 +156,8 @@ bool USBScanner::AddNewDevices(DeviceMap* new_devices) const
|
||||
{
|
||||
WakeupSantrollerDevice(device);
|
||||
}
|
||||
if (!whitelist.contains({descriptor.idVendor, descriptor.idProduct}))
|
||||
const USBUtils::DeviceInfo device_info{descriptor.idVendor, descriptor.idProduct};
|
||||
if (!whitelist.contains(device_info))
|
||||
return true;
|
||||
|
||||
auto usb_device = std::make_unique<USB::LibusbDevice>(device, descriptor);
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/USBUtils.h"
|
||||
|
||||
#include <charconv>
|
||||
#include <cwchar>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/xchar.h>
|
||||
#ifdef HAVE_LIBUDEV
|
||||
#include <libudev.h>
|
||||
#endif
|
||||
#ifdef __LIBUSB__
|
||||
#include <libusb.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <SetupAPI.h>
|
||||
#include <cfgmgr32.h>
|
||||
#include <devpkey.h>
|
||||
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/WindowsDevice.h"
|
||||
#endif
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
// Device names for known Wii peripherals.
|
||||
static const std::map<USBUtils::DeviceInfo, std::string> s_known_devices{{
|
||||
{{0x046d, 0x0a03}, "Logitech Microphone"},
|
||||
{{0x057e, 0x0308}, "Wii Speak"},
|
||||
{{0x057e, 0x0309}, "Nintendo USB Microphone"},
|
||||
{{0x057e, 0x030a}, "Ubisoft Motion Tracking Camera"},
|
||||
{{0x0e6f, 0x0129}, "Disney Infinity Reader (Portal Device)"},
|
||||
{{0x12ba, 0x0200}, "Harmonix Guitar for PlayStation 3"},
|
||||
{{0x12ba, 0x0210}, "Harmonix Drum Kit for PlayStation 3"},
|
||||
{{0x12ba, 0x0218}, "Harmonix Drum Kit for PlayStation 3"},
|
||||
{{0x12ba, 0x2330}, "Harmonix RB3 Keyboard for PlayStation 3"},
|
||||
{{0x12ba, 0x2338}, "Harmonix RB3 MIDI Keyboard Interface for PlayStation 3"},
|
||||
{{0x12ba, 0x2430}, "Harmonix RB3 Mustang Guitar for PlayStation 3"},
|
||||
{{0x12ba, 0x2438}, "Harmonix RB3 MIDI Guitar Interface for PlayStation 3"},
|
||||
{{0x12ba, 0x2530}, "Harmonix RB3 Squier Guitar for PlayStation 3"},
|
||||
{{0x12ba, 0x2538}, "Harmonix RB3 MIDI Guitar Interface for PlayStation 3"},
|
||||
{{0x1430, 0x0100}, "Tony Hawk Ride Skateboard"},
|
||||
{{0x1430, 0x0150}, "Skylanders Portal"},
|
||||
{{0x1bad, 0x0004}, "Harmonix Guitar Controller for Nintendo Wii"},
|
||||
{{0x1bad, 0x0005}, "Harmonix Drum Controller for Nintendo Wii"},
|
||||
{{0x1bad, 0x3010}, "Harmonix Guitar Controller for Nintendo Wii"},
|
||||
{{0x1bad, 0x3110}, "Harmonix Drum Controller for Nintendo Wii"},
|
||||
{{0x1bad, 0x3138}, "Harmonix Drum Controller for Nintendo Wii"},
|
||||
{{0x1bad, 0x3330}, "Harmonix RB3 Keyboard for Nintendo Wii"},
|
||||
{{0x1bad, 0x3338}, "Harmonix RB3 MIDI Keyboard Interface for Nintendo Wii"},
|
||||
{{0x1bad, 0x3430}, "Harmonix RB3 Mustang Guitar for Nintendo Wii"},
|
||||
{{0x1bad, 0x3438}, "Harmonix RB3 MIDI Guitar Interface for Nintendo Wii"},
|
||||
{{0x1bad, 0x3530}, "Harmonix RB3 Squier Guitar for Nintendo Wii"},
|
||||
{{0x1bad, 0x3538}, "Harmonix RB3 MIDI Guitar Interface for Nintendo Wii"},
|
||||
{{0x21a4, 0xac40}, "EA Active NFL"},
|
||||
}};
|
||||
|
||||
namespace USBUtils
|
||||
{
|
||||
std::optional<DeviceInfo> DeviceInfo::FromString(const std::string& str)
|
||||
{
|
||||
const size_t colon_index = str.find(':');
|
||||
if (colon_index == std::string::npos)
|
||||
return std::nullopt;
|
||||
|
||||
auto parse_hex = [](std::string_view sv, u16& out) -> bool {
|
||||
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), out, 16);
|
||||
return ec == std::errc();
|
||||
};
|
||||
|
||||
DeviceInfo info;
|
||||
std::string_view vid_sv(str.data(), colon_index);
|
||||
std::string_view pid_sv(str.data() + colon_index + 1);
|
||||
|
||||
if (!parse_hex(vid_sv, info.vid))
|
||||
return std::nullopt;
|
||||
if (!parse_hex(pid_sv, info.pid))
|
||||
return std::nullopt;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
std::string DeviceInfo::ToString() const
|
||||
{
|
||||
return fmt::format("{:04x}:{:04x}", vid, pid);
|
||||
}
|
||||
|
||||
std::string DeviceInfo::ToDisplayString() const
|
||||
{
|
||||
const std::string name =
|
||||
// i18n: This replaces the name of a device if it cannot be found.
|
||||
GetDeviceNameFromVIDPID(vid, pid).value_or(Common::GetStringT("Unknown Device"));
|
||||
return ToDisplayString(name);
|
||||
}
|
||||
|
||||
std::string DeviceInfo::ToDisplayString(const std::string& name) const
|
||||
{
|
||||
return fmt::format("{} ({:04x}:{:04x})", name, vid, pid);
|
||||
}
|
||||
|
||||
static std::optional<std::string> GetDeviceNameUsingKnownDevices(u16 vid, u16 pid)
|
||||
{
|
||||
const auto iter = s_known_devices.find(DeviceInfo{vid, pid});
|
||||
if (iter != s_known_devices.end())
|
||||
return iter->second;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::optional<std::string> GetDeviceNameUsingSetupAPI(u16 vid, u16 pid)
|
||||
{
|
||||
std::optional<std::string> device_name;
|
||||
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)
|
||||
{
|
||||
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)
|
||||
continue;
|
||||
|
||||
const std::wstring_view id_wstr(instance_id);
|
||||
if (id_wstr.find(filter) == std::wstring::npos)
|
||||
continue;
|
||||
|
||||
std::wstring property_value =
|
||||
Common::GetDeviceProperty(dev_info, &dev_info_data, &DEVPKEY_Device_FriendlyName);
|
||||
if (property_value.empty())
|
||||
{
|
||||
property_value =
|
||||
Common::GetDeviceProperty(dev_info, &dev_info_data, &DEVPKEY_Device_DeviceDesc);
|
||||
}
|
||||
|
||||
if (!property_value.empty())
|
||||
device_name = WStringToUTF8(property_value);
|
||||
break;
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(dev_info);
|
||||
return device_name;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBUDEV
|
||||
static std::optional<std::string> GetDeviceNameUsingHWDB(u16 vid, u16 pid)
|
||||
{
|
||||
std::optional<std::string> device_name;
|
||||
udev* udev = udev_new();
|
||||
if (!udev)
|
||||
return std::nullopt;
|
||||
|
||||
Common::ScopeGuard udev_guard{[&] { udev_unref(udev); }};
|
||||
|
||||
udev_hwdb* hwdb = udev_hwdb_new(udev);
|
||||
if (!hwdb)
|
||||
return std::nullopt;
|
||||
|
||||
Common::ScopeGuard hwdb_guard{[&] { udev_hwdb_unref(hwdb); }};
|
||||
|
||||
const std::string modalias = fmt::format("usb:v{:04X}p{:04X}*", vid, pid);
|
||||
udev_list_entry* entries = udev_hwdb_get_properties_list_entry(hwdb, modalias.c_str(), 0);
|
||||
if (!entries)
|
||||
return std::nullopt;
|
||||
|
||||
udev_list_entry* device_name_entry =
|
||||
udev_list_entry_get_by_name(entries, "ID_MODEL_FROM_DATABASE");
|
||||
if (!device_name_entry)
|
||||
return std::nullopt;
|
||||
|
||||
device_name = udev_list_entry_get_value(device_name_entry);
|
||||
|
||||
return device_name;
|
||||
}
|
||||
#endif
|
||||
|
||||
// libusb can cause BSODs with certain bad OEM drivers on Windows when opening a device.
|
||||
// All offending drivers are known to depend on the BthUsb.sys driver, which is a Miniport Driver
|
||||
// for Bluetooth.
|
||||
// Known offenders:
|
||||
// - btfilter.sys from Qualcomm Atheros Communications
|
||||
// - ibtusb.sys from Intel Corporation
|
||||
#if defined(__LIBUSB__) && !defined(_WIN32)
|
||||
static std::optional<std::string> GetDeviceNameUsingLibUSB(u16 vid, u16 pid)
|
||||
{
|
||||
std::optional<std::string> device_name;
|
||||
LibusbUtils::Context context;
|
||||
|
||||
if (!context.IsValid())
|
||||
return std::nullopt;
|
||||
|
||||
context.GetDeviceList([&device_name, vid, pid](libusb_device* device) {
|
||||
libusb_device_descriptor desc{};
|
||||
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (desc.idVendor != vid || desc.idProduct != pid)
|
||||
return true;
|
||||
|
||||
if (!desc.iProduct)
|
||||
return false;
|
||||
|
||||
libusb_device_handle* handle;
|
||||
if (libusb_open(device, &handle) != LIBUSB_SUCCESS)
|
||||
return false;
|
||||
|
||||
device_name = LibusbUtils::GetStringDescriptor(handle, desc.iProduct);
|
||||
libusb_close(handle);
|
||||
return false;
|
||||
});
|
||||
return device_name;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<std::string> GetDeviceNameFromVIDPID(u16 vid, u16 pid)
|
||||
{
|
||||
using LookupFn = std::optional<std::string> (*)(u16, u16);
|
||||
|
||||
// Try name lookup backends in priority order:
|
||||
// 1. Known Devices - Known, hard‑coded devices (fast, most reliable)
|
||||
// 2. HWDB - libudev’s hardware database (fast, OS‑specific)
|
||||
// 3. Setup API - Vendor/Product registry on Windows (moderately fast, OS‑specific)
|
||||
// 4. libusb - Fallback to querying the USB device (slowest)
|
||||
static constexpr auto backends = std::to_array<LookupFn>({
|
||||
&GetDeviceNameUsingKnownDevices,
|
||||
#ifdef HAVE_LIBUDEV
|
||||
&GetDeviceNameUsingHWDB,
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
&GetDeviceNameUsingSetupAPI,
|
||||
#endif
|
||||
#if defined(__LIBUSB__) && !defined(_WIN32)
|
||||
&GetDeviceNameUsingLibUSB,
|
||||
#endif
|
||||
});
|
||||
|
||||
for (const auto& backend : backends)
|
||||
{
|
||||
if (auto name = backend(vid, pid))
|
||||
return name;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef __LIBUSB__
|
||||
std::vector<DeviceInfo>
|
||||
ListDevices(const std::function<bool(const libusb_device_descriptor&)>& filter)
|
||||
{
|
||||
std::vector<DeviceInfo> device_list;
|
||||
LibusbUtils::Context context;
|
||||
if (!context.IsValid())
|
||||
return {};
|
||||
|
||||
const int ret = context.GetDeviceList([&device_list, &filter](libusb_device* device) {
|
||||
libusb_device_descriptor desc;
|
||||
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (filter(desc))
|
||||
{
|
||||
device_list.push_back({desc.idVendor, desc.idProduct});
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (ret != LIBUSB_SUCCESS)
|
||||
WARN_LOG_FMT(CORE, "Failed to get device list: {}", LibusbUtils::ErrorWrap(ret));
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> ListDevices(const std::function<bool(const DeviceInfo&)>& filter)
|
||||
{
|
||||
return ListDevices([&filter](const libusb_device_descriptor& desc) {
|
||||
const DeviceInfo info{desc.idVendor, desc.idProduct};
|
||||
return filter(info);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
} // namespace USBUtils
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
struct libusb_device_descriptor;
|
||||
|
||||
namespace USBUtils
|
||||
{
|
||||
|
||||
struct DeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
|
||||
constexpr auto operator<=>(const DeviceInfo& other) const = default;
|
||||
|
||||
// Extracts DeviceInfo from a string in the format "VID:PID"
|
||||
static std::optional<DeviceInfo> FromString(const std::string& str);
|
||||
std::string ToString() const;
|
||||
std::string ToDisplayString() const;
|
||||
std::string ToDisplayString(const std::string& name) const;
|
||||
};
|
||||
|
||||
std::optional<std::string> GetDeviceNameFromVIDPID(u16 vid, u16 pid);
|
||||
|
||||
std::vector<DeviceInfo>
|
||||
ListDevices(const std::function<bool(const struct libusb_device_descriptor&)>& filter =
|
||||
[](const struct libusb_device_descriptor&) { return true; });
|
||||
std::vector<DeviceInfo> ListDevices(const std::function<bool(const DeviceInfo&)>& filter =
|
||||
[](const DeviceInfo&) { return true; });
|
||||
|
||||
} // namespace USBUtils
|
||||
Reference in new issue
Block a user