diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 79aab432cd..64be002800 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -551,6 +551,8 @@ add_library(core TimePlayed.h TitleDatabase.cpp TitleDatabase.h + USBUtils.cpp + USBUtils.h WC24PatchEngine.cpp WC24PatchEngine.h WiiRoot.cpp diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index dc6a48aa7f..31560f97bf 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.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 MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII{ const Info MAIN_USB_PASSTHROUGH_DEVICES{{System::Main, "USBPassthrough", "Devices"}, ""}; -static std::set> LoadUSBWhitelistFromString(const std::string& devices_string) +static std::set LoadUSBWhitelistFromString(const std::string& devices_string) { - std::set> devices; + std::set devices; for (const auto& pair : SplitString(devices_string, ',')) { - const auto index = pair.find(':'); - if (index == std::string::npos) - continue; - - const u16 vid = static_cast(strtol(pair.substr(0, index).c_str(), nullptr, 16)); - const u16 pid = static_cast(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>& devices) +static std::string SaveUSBWhitelistToString(const std::set& 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> GetUSBDeviceWhitelist() +std::set GetUSBDeviceWhitelist() { return LoadUSBWhitelistFromString(Config::Get(Config::MAIN_USB_PASSTHROUGH_DEVICES)); } -void SetUSBDeviceWhitelist(const std::set>& devices) +void SetUSBDeviceWhitelist(const std::set& devices) { Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices)); } diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index f732d68645..580ccfdabf 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -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 MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS; extern const Info MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII; extern const Info MAIN_USB_PASSTHROUGH_DEVICES; -std::set> GetUSBDeviceWhitelist(); -void SetUSBDeviceWhitelist(const std::set>& devices); +std::set GetUSBDeviceWhitelist(); +void SetUSBDeviceWhitelist(const std::set& devices); // Main.EmulatedUSBDevices diff --git a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp index 5fdf8fab06..ab331b7bad 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp @@ -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::ListDevices() -{ - std::vector 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); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h index 6e0f8bc1de..7add7c073e 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h @@ -23,15 +23,8 @@ class LibUSBBluetoothAdapter public: using BufferType = Common::UniqueBuffer; - struct BluetoothDeviceInfo - { - u16 vid; - u16 pid; - std::string name; - }; - - static std::vector 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. diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index f8263d7afd..11adfb9f38 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -11,21 +11,6 @@ #include #include -#include -#include -#ifdef HAVE_LIBUDEV -#include -#endif -#ifdef __LIBUSB__ -#include -#endif - -#ifdef _WIN32 -#include -#include -#include -#endif - #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -64,107 +49,6 @@ std::optional 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); diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h index 9d0f537f33..ba858dc503 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -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 diff --git a/Source/Core/Core/IOS/USB/USBScanner.cpp b/Source/Core/Core/IOS/USB/USBScanner.cpp index b0a170ee35..aa213ce412 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.cpp +++ b/Source/Core/Core/IOS/USB/USBScanner.cpp @@ -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(device, descriptor); diff --git a/Source/Core/Core/USBUtils.cpp b/Source/Core/Core/USBUtils.cpp new file mode 100644 index 0000000000..b2edfd0b22 --- /dev/null +++ b/Source/Core/Core/USBUtils.cpp @@ -0,0 +1,300 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/USBUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_LIBUDEV +#include +#endif +#ifdef __LIBUSB__ +#include +#endif +#ifdef _WIN32 +#include +#include +#include + +#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 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::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 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 GetDeviceNameUsingSetupAPI(u16 vid, u16 pid) +{ + std::optional 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 GetDeviceNameUsingHWDB(u16 vid, u16 pid) +{ + std::optional 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 GetDeviceNameUsingLibUSB(u16 vid, u16 pid) +{ + std::optional 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 GetDeviceNameFromVIDPID(u16 vid, u16 pid) +{ + using LookupFn = std::optional (*)(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({ + &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 +ListDevices(const std::function& filter) +{ + std::vector 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 ListDevices(const std::function& filter) +{ + return ListDevices([&filter](const libusb_device_descriptor& desc) { + const DeviceInfo info{desc.idVendor, desc.idProduct}; + return filter(info); + }); +} +#endif +} // namespace USBUtils diff --git a/Source/Core/Core/USBUtils.h b/Source/Core/Core/USBUtils.h new file mode 100644 index 0000000000..1147cced31 --- /dev/null +++ b/Source/Core/Core/USBUtils.h @@ -0,0 +1,40 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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 FromString(const std::string& str); + std::string ToString() const; + std::string ToDisplayString() const; + std::string ToDisplayString(const std::string& name) const; +}; + +std::optional GetDeviceNameFromVIDPID(u16 vid, u16 pid); + +std::vector +ListDevices(const std::function& filter = + [](const struct libusb_device_descriptor&) { return true; }); +std::vector ListDevices(const std::function& filter = + [](const DeviceInfo&) { return true; }); + +} // namespace USBUtils diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 5803fb0437..bc865343e0 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -473,6 +473,7 @@ + @@ -573,7 +574,6 @@ - @@ -1152,6 +1152,7 @@ + @@ -1244,7 +1245,6 @@ - diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp index 11c98ee883..8517e4328e 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp @@ -28,6 +28,7 @@ #include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h" #include "Core/NetPlayProto.h" #include "Core/System.h" +#include "Core/USBUtils.h" #include "Core/WiiUtils.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" @@ -64,6 +65,7 @@ void WiimoteControllersWidget::UpdateBluetoothAvailableStatus() void WiimoteControllersWidget::StartBluetoothAdapterRefresh() { +#ifdef __LIBUSB__ if (m_bluetooth_adapter_scan_in_progress) return; @@ -75,7 +77,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh() const auto scan_func = [this]() { INFO_LOG_FMT(COMMON, "Refreshing Bluetooth adapter list..."); - auto device_list = LibUSBBluetoothAdapter::ListDevices(); + auto device_list = USBUtils::ListDevices(LibUSBBluetoothAdapter::IsBluetoothDevice); INFO_LOG_FMT(COMMON, "{} Bluetooth adapters available.", device_list.size()); const auto refresh_complete_func = [this, devices = std::move(device_list)]() { OnBluetoothAdapterRefreshComplete(devices); @@ -84,10 +86,11 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh() }; m_bluetooth_adapter_refresh_thread.Push(scan_func); +#endif } void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete( - const std::vector& devices) + const std::vector& devices) { const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID); const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID); @@ -103,10 +106,8 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete( for (auto& device : devices) { - std::string name = device.name.empty() ? tr("Unknown Device").toStdString() : device.name; - QString device_info = - QString::fromStdString(fmt::format("{} ({:04x}:{:04x})", name, device.vid, device.pid)); - m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(device)); + m_bluetooth_adapters->addItem(QString::fromStdString(device.ToDisplayString()), + QVariant::fromValue(device)); if (!found_configured_device && LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(device.vid, device.pid)) @@ -121,13 +122,12 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete( const QString name = QLatin1Char{'['} + tr("disconnected") + QLatin1Char(']'); const std::string name_str = name.toStdString(); - LibUSBBluetoothAdapter::BluetoothDeviceInfo disconnected_device; + USBUtils::DeviceInfo disconnected_device; disconnected_device.vid = configured_vid; disconnected_device.pid = configured_pid; - disconnected_device.name = name_str; - QString device_info = QString::fromStdString( - fmt::format("{} ({:04x}:{:04x})", name_str, configured_vid, configured_pid)); + const QString device_info = + QString::fromStdString(disconnected_device.ToDisplayString(name_str)); m_bluetooth_adapters->insertSeparator(m_bluetooth_adapters->count()); m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(disconnected_device)); @@ -311,13 +311,13 @@ void WiimoteControllersWidget::OnBluetoothPassthroughDeviceChanged(int index) const QVariant item_data = m_bluetooth_adapters->itemData(index); - if (!item_data.isValid() || !item_data.canConvert()) + if (!item_data.isValid() || !item_data.canConvert()) { ERROR_LOG_FMT(COMMON, "Invalid Bluetooth device info selected in WiimoteControllersWidget"); return; } - const auto& device_info = item_data.value(); + const auto& device_info = item_data.value(); Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID, device_info.pid); Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID, device_info.vid); diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h index e8507d9866..ef75fbbcbb 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h @@ -8,7 +8,7 @@ #include #include "Common/WorkQueueThread.h" -#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h" +#include "Core/USBUtils.h" class QCheckBox; class QComboBox; @@ -38,8 +38,7 @@ private: void OnBluetoothPassthroughDeviceChanged(int index); void OnBluetoothPassthroughSyncPressed(); void OnBluetoothPassthroughResetPressed(); - void OnBluetoothAdapterRefreshComplete( - const std::vector& devices); + void OnBluetoothAdapterRefreshComplete(const std::vector& devices); void OnWiimoteRefreshPressed(); void OnWiimoteConfigure(size_t index); void StartBluetoothAdapterRefresh(); diff --git a/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.cpp b/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.cpp index b1133e16d6..05f940505c 100644 --- a/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.cpp +++ b/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.cpp @@ -16,16 +16,17 @@ #include #include +#include + #include "Common/StringUtil.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" +#include "Core/USBUtils.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/Settings/WiiPane.h" -#include "UICommon/USBUtils.h" - static bool IsValidUSBIDString(const std::string& string) { if (string.empty() || string.length() > 4) @@ -105,20 +106,23 @@ void USBDeviceAddToWhitelistDialog::InitControls() device_vid_textbox->setMaxLength(4); device_pid_textbox->setMaxLength(4); } - void USBDeviceAddToWhitelistDialog::RefreshDeviceList() { - const auto& current_devices = USBUtils::GetInsertedDevices(); + const auto whitelist = Config::GetUSBDeviceWhitelist(); + + const auto& current_devices = USBUtils::ListDevices( + [&whitelist](const USBUtils::DeviceInfo& device) { return !whitelist.contains(device); }); + if (current_devices == m_shown_devices) return; const auto selection_string = usb_inserted_devices_list->currentItem(); usb_inserted_devices_list->clear(); - auto whitelist = Config::GetUSBDeviceWhitelist(); for (const auto& device : current_devices) { - if (whitelist.contains({device.first.first, device.first.second})) - continue; - usb_inserted_devices_list->addItem(QString::fromStdString(device.second)); + auto* item = new QListWidgetItem(QString::fromStdString(device.ToDisplayString()), + usb_inserted_devices_list); + QVariant device_data = QVariant::fromValue(device); + item->setData(Qt::UserRole, device_data); } usb_inserted_devices_list->setCurrentItem(selection_string); @@ -147,15 +151,16 @@ void USBDeviceAddToWhitelistDialog::AddUSBDeviceToWhitelist() const u16 vid = static_cast(std::stoul(vid_string, nullptr, 16)); const u16 pid = static_cast(std::stoul(pid_string, nullptr, 16)); + const USBUtils::DeviceInfo new_device{vid, pid}; auto whitelist = Config::GetUSBDeviceWhitelist(); - auto it = whitelist.emplace(vid, pid); - if (!it.second) + if (whitelist.contains(new_device)) { ModalMessageBox::critical(this, tr("USB Whitelist Error"), tr("This USB device is already whitelisted.")); return; } + whitelist.emplace(new_device); Config::SetUSBDeviceWhitelist(whitelist); Config::Save(); accept(); @@ -163,11 +168,13 @@ void USBDeviceAddToWhitelistDialog::AddUSBDeviceToWhitelist() void USBDeviceAddToWhitelistDialog::OnDeviceSelection() { - // Not the nicest way of doing this but... - QString device = usb_inserted_devices_list->currentItem()->text().left(9); - QStringList split = device.split(QString::fromStdString(":")); - QString* vid = new QString(split[0]); - QString* pid = new QString(split[1]); - device_vid_textbox->setText(*vid); - device_pid_textbox->setText(*pid); + auto* current_item = usb_inserted_devices_list->currentItem(); + if (!current_item) + return; + + QVariant item_data = current_item->data(Qt::UserRole); + USBUtils::DeviceInfo device = item_data.value(); + + device_vid_textbox->setText(QString::fromStdString(fmt::format("{:04x}", device.vid))); + device_pid_textbox->setText(QString::fromStdString(fmt::format("{:04x}", device.pid))); } diff --git a/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.h b/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.h index 5ae1199f2e..deb513698a 100644 --- a/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.h +++ b/Source/Core/DolphinQt/Settings/USBDeviceAddToWhitelistDialog.h @@ -3,8 +3,12 @@ #pragma once +#include + #include +#include "Core/USBUtils.h" + class QTimer; class QDialog; class QHeaderView; @@ -44,5 +48,5 @@ private: void OnDeviceSelection(); - std::map, std::string> m_shown_devices; + std::vector m_shown_devices; }; diff --git a/Source/Core/DolphinQt/Settings/WiiPane.cpp b/Source/Core/DolphinQt/Settings/WiiPane.cpp index 0ec003c827..5aa1caea13 100644 --- a/Source/Core/DolphinQt/Settings/WiiPane.cpp +++ b/Source/Core/DolphinQt/Settings/WiiPane.cpp @@ -30,6 +30,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/System.h" +#include "Core/USBUtils.h" #include "DolphinQt/QtUtils/DolphinFileDialog.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" @@ -40,8 +41,6 @@ #include "DolphinQt/Settings.h" #include "DolphinQt/Settings/USBDeviceAddToWhitelistDialog.h" -#include "UICommon/USBUtils.h" - // SYSCONF uses 0 for bottom and 1 for top, but we place them in // the other order in the GUI so that Top will be above Bottom, // matching the respective physical placements of the sensor bar. @@ -469,14 +468,15 @@ void WiiPane::OnUSBWhitelistAddButton() void WiiPane::OnUSBWhitelistRemoveButton() { - QString device = m_whitelist_usb_list->currentItem()->text().left(9); - QStringList split = device.split(QString::fromStdString(":")); - QString vid = QString(split[0]); - QString pid = QString(split[1]); - const u16 vid_u16 = static_cast(std::stoul(vid.toStdString(), nullptr, 16)); - const u16 pid_u16 = static_cast(std::stoul(pid.toStdString(), nullptr, 16)); + auto* current_item = m_whitelist_usb_list->currentItem(); + if (!current_item) + return; + + QVariant item_data = current_item->data(Qt::UserRole); + USBUtils::DeviceInfo device = item_data.value(); + auto whitelist = Config::GetUSBDeviceWhitelist(); - whitelist.erase({vid_u16, pid_u16}); + whitelist.erase(device); Config::SetUSBDeviceWhitelist(whitelist); PopulateUSBPassthroughListWidget(); } @@ -485,11 +485,12 @@ void WiiPane::PopulateUSBPassthroughListWidget() { m_whitelist_usb_list->clear(); auto whitelist = Config::GetUSBDeviceWhitelist(); - for (const auto& device : whitelist) + for (auto& device : whitelist) { - QListWidgetItem* usb_lwi = - new QListWidgetItem(QString::fromStdString(USBUtils::GetDeviceName(device))); - m_whitelist_usb_list->addItem(usb_lwi); + auto* item = + new QListWidgetItem(QString::fromStdString(device.ToDisplayString()), m_whitelist_usb_list); + QVariant device_data = QVariant::fromValue(device); + item->setData(Qt::UserRole, device_data); } ValidateSelectionState(); } diff --git a/Source/Core/UICommon/CMakeLists.txt b/Source/Core/UICommon/CMakeLists.txt index d57fdb811a..2f248e1066 100644 --- a/Source/Core/UICommon/CMakeLists.txt +++ b/Source/Core/UICommon/CMakeLists.txt @@ -19,8 +19,6 @@ add_library(uicommon ResourcePack/ResourcePack.h UICommon.cpp UICommon.h - USBUtils.cpp - USBUtils.h ) target_link_libraries(uicommon diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 89912c802c..4f55660bcc 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -42,13 +42,13 @@ #include "Core/IOS/IOS.h" #include "Core/IOS/STM/STM.h" #include "Core/System.h" +#include "Core/USBUtils.h" #include "Core/WiiRoot.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/GCAdapter.h" #include "UICommon/DiscordPresence.h" -#include "UICommon/USBUtils.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" diff --git a/Source/Core/UICommon/USBUtils.cpp b/Source/Core/UICommon/USBUtils.cpp deleted file mode 100644 index 42c4bd93fd..0000000000 --- a/Source/Core/UICommon/USBUtils.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "UICommon/USBUtils.h" - -#include - -#include -#ifdef __LIBUSB__ -#include -#endif - -#include "Common/CommonTypes.h" -#include "Common/Logging/Log.h" -#include "Core/LibusbUtils.h" - -// Because opening and getting the device name from devices is slow, especially on Windows -// with usbdk, we cannot do that for every single device. We should however still show -// device names for known Wii peripherals. -static const std::map, std::string_view> s_known_peripherals{{ - {{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::map, std::string> GetInsertedDevices() -{ - std::map, std::string> devices; - -#ifdef __LIBUSB__ - LibusbUtils::Context context; - if (!context.IsValid()) - return devices; - - const int ret = context.GetDeviceList([&](libusb_device* device) { - libusb_device_descriptor descr; - libusb_get_device_descriptor(device, &descr); - const std::pair vid_pid{descr.idVendor, descr.idProduct}; - devices[vid_pid] = GetDeviceName(vid_pid); - return true; - }); - if (ret != LIBUSB_SUCCESS) - WARN_LOG_FMT(COMMON, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret)); -#endif - - return devices; -} - -std::string GetDeviceName(const std::pair vid_pid) -{ - const auto iter = s_known_peripherals.find(vid_pid); - const std::string_view device_name = - iter == s_known_peripherals.cend() ? "Unknown" : iter->second; - return fmt::format("{:04x}:{:04x} - {}", vid_pid.first, vid_pid.second, device_name); -} -} // namespace USBUtils diff --git a/Source/Core/UICommon/USBUtils.h b/Source/Core/UICommon/USBUtils.h deleted file mode 100644 index 1eb54d81d9..0000000000 --- a/Source/Core/UICommon/USBUtils.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "Common/CommonTypes.h" - -namespace USBUtils -{ -std::map, std::string> GetInsertedDevices(); -std::string GetDeviceName(std::pair vid_pid); -} // namespace USBUtils