mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #13751 from jordan-woyak/btreal-improvements
Bluetooth Passthrough Improvements
This commit is contained in:
@@ -695,6 +695,8 @@ if(TARGET LibUSB::LibUSB)
|
||||
IOS/USB/LibusbDevice.h
|
||||
IOS/USB/Bluetooth/BTReal.cpp
|
||||
IOS/USB/Bluetooth/BTReal.h
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
@@ -15,6 +12,18 @@ class SysConf;
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
template <typename T>
|
||||
static void DoStateForMessage(EmulationKernel& ios, PointerWrap& p, std::unique_ptr<T>& message)
|
||||
{
|
||||
u32 request_address = (message != nullptr) ? message->ios_request.address : 0;
|
||||
p.Do(request_address);
|
||||
if (request_address != 0)
|
||||
{
|
||||
IOCtlVRequest request{ios.GetSystem(), request_address};
|
||||
message = std::make_unique<T>(ios, request);
|
||||
}
|
||||
}
|
||||
|
||||
void BackUpBTInfoSection(const SysConf* sysconf);
|
||||
void RestoreBTInfoSection(SysConf* sysconf);
|
||||
|
||||
|
||||
@@ -79,18 +79,6 @@ BluetoothEmuDevice::BluetoothEmuDevice(EmulationKernel& ios, const std::string&
|
||||
|
||||
BluetoothEmuDevice::~BluetoothEmuDevice() = default;
|
||||
|
||||
template <typename T>
|
||||
static void DoStateForMessage(EmulationKernel& ios, PointerWrap& p, std::unique_ptr<T>& message)
|
||||
{
|
||||
u32 request_address = (message != nullptr) ? message->ios_request.address : 0;
|
||||
p.Do(request_address);
|
||||
if (request_address != 0)
|
||||
{
|
||||
IOCtlVRequest request{ios.GetSystem(), request_address};
|
||||
message = std::make_unique<T>(ios, request);
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothEmuDevice::DoState(PointerWrap& p)
|
||||
{
|
||||
bool passthrough_bluetooth = false;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,25 +6,22 @@
|
||||
#if defined(__LIBUSB__)
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/Timer.h"
|
||||
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTBase.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
#include "Core/IOS/USB/USBV0.h"
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
class PointerWrap;
|
||||
struct libusb_device;
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
@@ -49,87 +46,55 @@ public:
|
||||
std::optional<IPCReply> Open(const OpenRequest& request) override;
|
||||
std::optional<IPCReply> Close(u32 fd) override;
|
||||
std::optional<IPCReply> IOCtlV(const IOCtlVRequest& request) override;
|
||||
void Update() override;
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
void UpdateSyncButtonState(bool is_held) override;
|
||||
void TriggerSyncButtonPressedEvent() override;
|
||||
void TriggerSyncButtonHeldEvent() override;
|
||||
|
||||
void HandleCtrlTransfer(libusb_transfer* finished_transfer);
|
||||
void HandleBulkOrIntrTransfer(libusb_transfer* finished_transfer);
|
||||
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
static bool HasConfiguredBluetoothDevice();
|
||||
|
||||
struct BluetoothDeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BluetoothDeviceInfo> ListDevices();
|
||||
|
||||
private:
|
||||
static constexpr u8 INTERFACE = 0x00;
|
||||
// Arbitrarily chosen value that allows emulated software to send commands often enough
|
||||
// so that the sync button event is triggered at least every 200ms.
|
||||
// Ideally this should be equal to 0, so we don't trigger unnecessary libusb transfers.
|
||||
static constexpr u32 TIMEOUT = 200;
|
||||
using BufferType = LibUSBBluetoothAdapter::BufferType;
|
||||
|
||||
std::unique_ptr<LibUSBBluetoothAdapter> m_lib_usb_bt_adapter;
|
||||
|
||||
static constexpr u32 SYNC_BUTTON_HOLD_MS_TO_RESET = 10000;
|
||||
|
||||
std::atomic<SyncButtonState> m_sync_button_state{SyncButtonState::Unpressed};
|
||||
Common::Timer m_sync_button_held_timer;
|
||||
|
||||
std::string m_last_open_error;
|
||||
|
||||
LibusbUtils::Context m_context;
|
||||
libusb_device* m_device = nullptr;
|
||||
libusb_device_handle* m_handle = nullptr;
|
||||
|
||||
std::mutex m_transfers_mutex;
|
||||
struct PendingTransfer
|
||||
{
|
||||
PendingTransfer(std::unique_ptr<USB::TransferCommand> command_, std::unique_ptr<u8[]> buffer_)
|
||||
: command(std::move(command_)), buffer(std::move(buffer_))
|
||||
{
|
||||
}
|
||||
std::unique_ptr<USB::TransferCommand> command;
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
};
|
||||
std::map<libusb_transfer*, PendingTransfer> m_current_transfers;
|
||||
|
||||
// Set when we received a command to which we need to fake a reply
|
||||
Common::Flag m_fake_read_buffer_size_reply;
|
||||
Common::Flag m_fake_vendor_command_reply;
|
||||
u16 m_fake_vendor_command_reply_opcode;
|
||||
// Enqueued when we observe a command to which we need to fake a reply.
|
||||
std::queue<std::function<void(USB::V0IntrMessage&)>> m_fake_replies;
|
||||
|
||||
// This stores the address of paired devices and associated link keys.
|
||||
// It is needed because some adapters forget all stored link keys when they are reset,
|
||||
// which breaks pairings because the Wii relies on the Bluetooth module to remember them.
|
||||
std::map<bdaddr_t, linkkey_t> m_link_keys;
|
||||
Common::Flag m_need_reset_keys;
|
||||
|
||||
// This flag is set when a libusb transfer failed (for reasons other than timing out)
|
||||
// and we showed an OSD message about it.
|
||||
Common::Flag m_showed_failed_transfer;
|
||||
// Endpoints provided by the emulated software.
|
||||
std::unique_ptr<USB::V0IntrMessage> m_hci_endpoint;
|
||||
std::unique_ptr<USB::V0BulkMessage> m_acl_endpoint;
|
||||
|
||||
bool m_is_wii_bt_module = false;
|
||||
// Used for proper Bluetooth packet timing, especially for Wii remote speaker data.
|
||||
TimePoint GetTargetTime() const;
|
||||
|
||||
void TryToFillHCIEndpoint();
|
||||
void TryToFillACLEndpoint();
|
||||
|
||||
[[nodiscard]] BufferType ProcessHCIEvent(BufferType buffer);
|
||||
|
||||
void WaitForHCICommandComplete(u16 opcode);
|
||||
void SendHCIResetCommand();
|
||||
void SendHCIDeleteLinkKeyCommand();
|
||||
bool SendHCIStoreLinkKeyCommand();
|
||||
void FakeVendorCommandReply(USB::V0IntrMessage& ctrl);
|
||||
void FakeReadBufferSizeReply(USB::V0IntrMessage& ctrl);
|
||||
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, const u8* payload, u8 size);
|
||||
|
||||
void FakeVendorCommandReply(u16 opcode, USB::V0IntrMessage& ctrl);
|
||||
|
||||
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, std::span<const u8> payload);
|
||||
void FakeSyncButtonPressedEvent(USB::V0IntrMessage& ctrl);
|
||||
void FakeSyncButtonHeldEvent(USB::V0IntrMessage& ctrl);
|
||||
|
||||
void LoadLinkKeys();
|
||||
void SaveLinkKeys();
|
||||
|
||||
bool OpenDevice(const libusb_device_descriptor& device_descriptor, libusb_device* device);
|
||||
};
|
||||
} // namespace IOS::HLE
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
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 HasConfiguredBluetoothDevice();
|
||||
|
||||
// Public interface is intended to be used by a single thread.
|
||||
|
||||
LibUSBBluetoothAdapter();
|
||||
~LibUSBBluetoothAdapter();
|
||||
|
||||
// Return a packet at the front of the queue, or an empty buffer when nothing is queued.
|
||||
// ReceiveHCIEvent is expected to be called regularly to do some internal processing.
|
||||
[[nodiscard]] BufferType ReceiveHCIEvent();
|
||||
[[nodiscard]] BufferType ReceiveACLData();
|
||||
|
||||
// Schedule a transfer to submit at a specific time.
|
||||
void ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data, TimePoint target_time);
|
||||
void ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span<const u8> data,
|
||||
TimePoint target_time);
|
||||
|
||||
// Schedule a transfer to be submitted as soon as possible.
|
||||
void SendControlTransfer(std::span<const u8> data);
|
||||
|
||||
bool IsWiiBTModule() const;
|
||||
|
||||
private:
|
||||
// Inputs will be dropped when queue is full.
|
||||
static constexpr std::size_t MAX_INPUT_QUEUE_SIZE = 100;
|
||||
|
||||
static constexpr u8 INTERFACE = 0x00;
|
||||
|
||||
std::string m_last_open_error;
|
||||
|
||||
LibusbUtils::Context m_context;
|
||||
libusb_device_handle* m_handle = nullptr;
|
||||
|
||||
// Protects run-flag and Push'ing to the completed transfer queue.
|
||||
std::mutex m_transfers_mutex;
|
||||
|
||||
bool m_run_input_transfers = true;
|
||||
|
||||
// callback/worker threads push transfers here that are ready for clean up.
|
||||
// This avoids locking around m_transfer_buffers.
|
||||
Common::WaitableSPSCQueue<libusb_transfer*> m_transfers_to_free;
|
||||
|
||||
// Incoming packets Push'd from libusb callback thread.
|
||||
Common::SPSCQueue<BufferType> m_hci_event_queue;
|
||||
Common::SPSCQueue<BufferType> m_acl_data_queue;
|
||||
|
||||
// All transfers are alloc'd and kept here until complete.
|
||||
std::map<libusb_transfer*, Common::UniqueBuffer<u8>> m_transfer_buffers;
|
||||
|
||||
struct TimedTransfer
|
||||
{
|
||||
libusb_transfer* transfer{};
|
||||
TimePoint target_time{};
|
||||
};
|
||||
|
||||
// Outgoing data is submitted on a worker thread.
|
||||
// This allows proper packet timing without throttling the core thread.
|
||||
Common::WorkQueueThreadSP<TimedTransfer> m_output_worker;
|
||||
|
||||
// Used by the output worker.
|
||||
Common::PrecisionTimer m_precision_timer;
|
||||
|
||||
// Set to reduce OSD and log spam.
|
||||
bool m_showed_failed_transfer = false;
|
||||
bool m_showed_long_queue_drop = false;
|
||||
|
||||
// Some responses need to be fabricated if we aren't using the Nintendo BT module.
|
||||
bool m_is_wii_bt_module = false;
|
||||
|
||||
// Bluetooth spec's Num_HCI_Command_Packets.
|
||||
// This is the number of hci commands that the host is allowed to send.
|
||||
// We track this to send commands only when the controller is ready.
|
||||
u8 m_num_hci_command_packets = 1;
|
||||
|
||||
// HCI commands are queued when the controller isn't ready for them.
|
||||
// They will be sent after COMMAND_COMPL or COMMAND_STATUS signal readiness.
|
||||
std::queue<TimedTransfer> m_pending_hci_transfers;
|
||||
|
||||
struct OutstandingCommand
|
||||
{
|
||||
u16 opcode{};
|
||||
TimePoint submit_time{};
|
||||
};
|
||||
|
||||
// Sent HCI commands that have yet to receive a COMMAND_COMPL or COMMAND_STATUS response.
|
||||
// Container size will be small, around 2.
|
||||
std::deque<OutstandingCommand> m_unacknowledged_commands;
|
||||
|
||||
bool IsControllerReadyForCommand() const;
|
||||
|
||||
// Give the transfer to the worker and track the command appropriately.
|
||||
// This should only be used when IsControllerReadyForCommand is true.
|
||||
void PushHCICommand(TimedTransfer);
|
||||
|
||||
template <typename EventType>
|
||||
void AcknowledgeCommand(std::span<const u8> buffer);
|
||||
|
||||
libusb_transfer* AllocateTransfer(std::size_t buffer_size);
|
||||
void FreeTransfer(libusb_transfer*);
|
||||
void CleanCompletedTransfers();
|
||||
|
||||
// Do some HCI logic and clean up completed transfers.
|
||||
void Update();
|
||||
|
||||
// Sleep until the target time and then submit the transfer.
|
||||
// Used by worker thread.
|
||||
void SubmitTimedTransfer(TimedTransfer);
|
||||
|
||||
// Immediately submit transfer with libusb.
|
||||
void SubmitTransfer(libusb_transfer*);
|
||||
|
||||
// Start repetitive transfers to fill the hci-event and acl-data queues.
|
||||
void StartInputTransfers();
|
||||
|
||||
// LibUSB callbacks. Invoked from the LibUSB context thread.
|
||||
void HandleOutputTransfer(libusb_transfer*);
|
||||
void HandleInputTransfer(libusb_transfer*);
|
||||
|
||||
// Error logging.
|
||||
void HandleTransferError(libusb_transfer*);
|
||||
|
||||
bool OpenDevice(const libusb_device_descriptor& device_descriptor, libusb_device* device);
|
||||
};
|
||||
@@ -95,7 +95,7 @@ static size_t s_state_writes_in_queue;
|
||||
static std::condition_variable s_state_write_queue_is_empty;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
constexpr u32 STATE_VERSION = 174; // Last changed in PR 13342
|
||||
constexpr u32 STATE_VERSION = 175; // Last changed in PR 13751
|
||||
|
||||
// Increase this if the StateExtendedHeader definition changes
|
||||
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
|
||||
|
||||
@@ -402,6 +402,7 @@
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTEmu.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTReal.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTStub.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\LibUSBBluetoothAdapter.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\hci.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\l2cap.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||
@@ -1079,6 +1080,7 @@
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTEmu.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTReal.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTStub.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\LibUSBBluetoothAdapter.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
@@ -28,7 +25,7 @@
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
@@ -40,8 +37,6 @@
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
WiimoteControllersWidget::WiimoteControllersWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
CreateLayout();
|
||||
@@ -80,7 +75,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh()
|
||||
|
||||
const auto scan_func = [this]() {
|
||||
INFO_LOG_FMT(COMMON, "Refreshing Bluetooth adapter list...");
|
||||
auto device_list = IOS::HLE::BluetoothRealDevice::ListDevices();
|
||||
auto device_list = LibUSBBluetoothAdapter::ListDevices();
|
||||
INFO_LOG_FMT(COMMON, "{} Bluetooth adapters available.", device_list.size());
|
||||
const auto refresh_complete_func = [this, devices = std::move(device_list)]() {
|
||||
OnBluetoothAdapterRefreshComplete(devices);
|
||||
@@ -92,7 +87,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh()
|
||||
}
|
||||
|
||||
void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
const std::vector<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>& devices)
|
||||
const std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo>& devices)
|
||||
{
|
||||
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
@@ -114,7 +109,7 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(device));
|
||||
|
||||
if (!found_configured_device &&
|
||||
IOS::HLE::BluetoothRealDevice::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
{
|
||||
found_configured_device = true;
|
||||
m_bluetooth_adapters->setCurrentIndex(m_bluetooth_adapters->count() - 1);
|
||||
@@ -126,7 +121,7 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
const QString name = QLatin1Char{'['} + tr("disconnected") + QLatin1Char(']');
|
||||
const std::string name_str = name.toStdString();
|
||||
|
||||
IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo disconnected_device;
|
||||
LibUSBBluetoothAdapter::BluetoothDeviceInfo disconnected_device;
|
||||
disconnected_device.vid = configured_vid;
|
||||
disconnected_device.pid = configured_pid;
|
||||
disconnected_device.name = name_str;
|
||||
@@ -314,8 +309,8 @@ void WiimoteControllersWidget::OnBluetoothPassthroughDeviceChanged(int index)
|
||||
return;
|
||||
}
|
||||
|
||||
auto device_info = m_bluetooth_adapters->itemData(index)
|
||||
.value<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>();
|
||||
auto device_info =
|
||||
m_bluetooth_adapters->itemData(index).value<LibUSBBluetoothAdapter::BluetoothDeviceInfo>();
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID, device_info.pid);
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID, device_info.vid);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
@@ -39,7 +39,7 @@ private:
|
||||
void OnBluetoothPassthroughSyncPressed();
|
||||
void OnBluetoothPassthroughResetPressed();
|
||||
void OnBluetoothAdapterRefreshComplete(
|
||||
const std::vector<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>& devices);
|
||||
const std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo>& devices);
|
||||
void OnWiimoteRefreshPressed();
|
||||
void OnWiimoteConfigure(size_t index);
|
||||
void StartBluetoothAdapterRefresh();
|
||||
|
||||
Reference in New Issue
Block a user