diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 6e896658ef..b6b74e5160 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -55,6 +55,7 @@ void SConfig::SaveSettings() NOTICE_LOG(BOOT, "Saving settings to %s", File::GetUserPath(F_DOLPHINCONFIG_IDX).c_str()); IniFile ini; ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); // load first to not kill unknown stuff + m_SYSCONF->Reload(); SaveGeneralSettings(ini); SaveInterfaceSettings(ini); @@ -330,6 +331,7 @@ void SConfig::SaveBluetoothPassthroughSettings(IniFile& ini) section->Set("Enabled", m_bt_passthrough_enabled); section->Set("VID", m_bt_passthrough_vid); section->Set("PID", m_bt_passthrough_pid); + section->Set("LinkKeys", m_bt_passthrough_link_keys); } void SConfig::LoadSettings() @@ -622,6 +624,7 @@ void SConfig::LoadBluetoothPassthroughSettings(IniFile& ini) section->Get("Enabled", &m_bt_passthrough_enabled, false); section->Get("VID", &m_bt_passthrough_vid, -1); section->Get("PID", &m_bt_passthrough_pid, -1); + section->Get("LinkKeys", &m_bt_passthrough_link_keys, ""); } void SConfig::LoadDefaults() diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 2bb649fcbc..f2275bb4c4 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -147,6 +147,7 @@ struct SConfig : NonCopyable bool m_bt_passthrough_enabled = false; int m_bt_passthrough_pid = -1; int m_bt_passthrough_vid = -1; + std::string m_bt_passthrough_link_keys; // Fifo Player related settings bool bLoopFifoReplay = true; diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.cpp b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.cpp index 7f9a4c7542..b6a5b5b8db 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.cpp +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.cpp @@ -2,7 +2,10 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include +#include #include +#include #include #include @@ -15,6 +18,12 @@ #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h" #include "Core/IPC_HLE/hci.h" +// 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. +static std::map s_link_keys; +static Common::Flag s_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. static Common::Flag s_showed_failed_transfer; @@ -41,6 +50,8 @@ CWII_IPC_HLE_Device_usb_oh1_57e_305_real::CWII_IPC_HLE_Device_usb_oh1_57e_305_re { const int ret = libusb_init(&m_libusb_context); _assert_msg_(WII_IPC_WIIMOTE, ret == 0, "Failed to init libusb."); + + LoadLinkKeys(); } CWII_IPC_HLE_Device_usb_oh1_57e_305_real::~CWII_IPC_HLE_Device_usb_oh1_57e_305_real() @@ -48,6 +59,7 @@ CWII_IPC_HLE_Device_usb_oh1_57e_305_real::~CWII_IPC_HLE_Device_usb_oh1_57e_305_r if (m_handle != nullptr) { SendHCIResetCommand(); + WaitForHCICommandComplete(HCI_CMD_RESET); libusb_release_interface(m_handle, 0); // libusb_handle_events() may block the libusb thread indefinitely, so we need to // call libusb_close() first then immediately stop the thread in StopTransferThread. @@ -56,6 +68,8 @@ CWII_IPC_HLE_Device_usb_oh1_57e_305_real::~CWII_IPC_HLE_Device_usb_oh1_57e_305_r } libusb_exit(m_libusb_context); + + SaveLinkKeys(); } IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::Open(u32 command_address, u32 mode) @@ -94,6 +108,8 @@ IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::Open(u32 command_addr NOTICE_LOG(WII_IPC_WIIMOTE, "Using device %04x:%04x (rev %x) for Bluetooth: %s %s %s", device_descriptor.idVendor, device_descriptor.idProduct, device_descriptor.bcdDevice, manufacturer, product, serial_number); + m_is_wii_bt_module = + device_descriptor.idVendor == 0x57e && device_descriptor.idProduct == 0x305; libusb_free_config_descriptor(config_descriptor); break; } @@ -133,6 +149,15 @@ IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::Close(u32 command_add IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::IOCtlV(u32 command_address) { + if (!m_is_wii_bt_module && s_need_reset_keys.TestAndClear()) + { + // Do this now before transferring any more data, so that this is fully transparent to games + SendHCIDeleteLinkKeyCommand(); + WaitForHCICommandComplete(HCI_CMD_DELETE_STORED_LINK_KEY); + if (SendHCIStoreLinkKeyCommand()) + WaitForHCICommandComplete(HCI_CMD_WRITE_STORED_LINK_KEY); + } + const SIOCtlVBuffer cmd_buffer(command_address); switch (cmd_buffer.Parameter) { @@ -146,6 +171,22 @@ IPCCommandResult CWII_IPC_HLE_Device_usb_oh1_57e_305_real::IOCtlV(u32 command_ad m_fake_read_buffer_size_reply.Set(); return GetNoReply(); } + if (opcode == HCI_CMD_DELETE_STORED_LINK_KEY) + { + // Delete link key(s) from our own link key storage when the game tells the adapter to + const auto* delete_cmd = + reinterpret_cast(Memory::GetPointer(cmd->payload_addr)); + if (delete_cmd->delete_all) + { + s_link_keys.clear(); + } + else + { + btaddr_t addr; + std::copy(std::begin(delete_cmd->bdaddr.b), std::end(delete_cmd->bdaddr.b), addr.begin()); + s_link_keys.erase(addr); + } + } auto buffer = std::vector(cmd->length + LIBUSB_CONTROL_SETUP_SIZE); libusb_fill_control_setup(buffer.data(), cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length); @@ -242,6 +283,21 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::TriggerSyncButtonHeldEvent() m_sync_button_state = SyncButtonState::LongPressed; } +void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::WaitForHCICommandComplete(const u16 opcode) +{ + int actual_length; + std::vector buffer(1024); + // Only try 100 transfers at most, to avoid being stuck in an infinite loop + for (int tries = 0; tries < 100; ++tries) + { + if (libusb_interrupt_transfer(m_handle, HCI_EVENT, buffer.data(), + static_cast(buffer.size()), &actual_length, 20) == 0 && + reinterpret_cast(buffer.data())->event == HCI_EVENT_COMMAND_COMPL && + reinterpret_cast(buffer.data())->Opcode == opcode) + break; + } +} + void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::SendHCIResetCommand() { const u8 type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE; @@ -252,6 +308,62 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::SendHCIResetCommand() INFO_LOG(WII_IPC_WIIMOTE, "Sent a reset command to adapter"); } +void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::SendHCIDeleteLinkKeyCommand() +{ + const u8 type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE; + std::vector packet(sizeof(hci_cmd_hdr_t) + sizeof(hci_delete_stored_link_key_cp)); + + auto* header = reinterpret_cast(packet.data()); + header->opcode = HCI_CMD_DELETE_STORED_LINK_KEY; + header->length = sizeof(hci_delete_stored_link_key_cp); + auto* cmd = + reinterpret_cast(packet.data() + sizeof(hci_cmd_hdr_t)); + cmd->bdaddr = {}; + cmd->delete_all = true; + + libusb_control_transfer(m_handle, type, 0, 0, 0, packet.data(), static_cast(packet.size()), + TIMEOUT); +} + +bool CWII_IPC_HLE_Device_usb_oh1_57e_305_real::SendHCIStoreLinkKeyCommand() +{ + if (s_link_keys.empty()) + return false; + + const u8 type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE; + // The HCI command field is limited to uint8_t, and libusb to uint16_t. + const u8 payload_size = + static_cast(sizeof(hci_write_stored_link_key_cp)) + + (sizeof(btaddr_t) + sizeof(linkkey_t)) * static_cast(s_link_keys.size()); + std::vector packet(sizeof(hci_cmd_hdr_t) + payload_size); + + auto* header = reinterpret_cast(packet.data()); + header->opcode = HCI_CMD_WRITE_STORED_LINK_KEY; + header->length = payload_size; + + auto* cmd = + reinterpret_cast(packet.data() + sizeof(hci_cmd_hdr_t)); + cmd->num_keys_write = static_cast(s_link_keys.size()); + + // This is really ugly, but necessary because of the HCI command structure: + // u8 num_keys; + // u8 bdaddr[6]; + // u8 key[16]; + // where the two last items are repeated num_keys times. + auto iterator = packet.begin() + sizeof(hci_cmd_hdr_t) + sizeof(hci_write_stored_link_key_cp); + for (const auto& entry : s_link_keys) + { + std::copy(entry.first.begin(), entry.first.end(), iterator); + iterator += entry.first.size(); + std::copy(entry.second.begin(), entry.second.end(), iterator); + iterator += entry.second.size(); + } + + libusb_control_transfer(m_handle, type, 0, 0, 0, packet.data(), static_cast(packet.size()), + TIMEOUT); + return true; +} + // Due to how the widcomm stack which Nintendo uses is coded, we must never // let the stack think the controller is buffering more than 10 data packets // - it will cause a u8 underflow and royally screw things up. @@ -311,6 +423,59 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::FakeSyncButtonHeldEvent(const Ctr m_sync_button_state = SyncButtonState::Ignored; } +void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::LoadLinkKeys() +{ + const std::string& entries = SConfig::GetInstance().m_bt_passthrough_link_keys; + if (entries.empty()) + return; + std::vector pairs; + SplitString(entries, ',', pairs); + for (const auto& pair : pairs) + { + const auto index = pair.find('='); + if (index == std::string::npos) + continue; + + btaddr_t address; + StringToMacAddress(pair.substr(0, index), address.data()); + std::reverse(address.begin(), address.end()); + + const std::string& key_string = pair.substr(index + 1); + linkkey_t key; + size_t pos = 0; + for (size_t i = 0; i < key_string.length(); i = i + 2) + { + int value; + std::stringstream(key_string.substr(i, 2)) >> std::hex >> value; + key[pos++] = value; + } + + s_link_keys[address] = key; + } +} + +void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::SaveLinkKeys() +{ + std::ostringstream oss; + for (const auto& entry : s_link_keys) + { + btaddr_t address; + // Reverse the address so that it is stored in the correct order in the config file + std::reverse_copy(entry.first.begin(), entry.first.end(), address.begin()); + oss << MacAddressToString(address.data()); + oss << '='; + oss << std::hex; + for (const u16& data : entry.second) + oss << std::setfill('0') << std::setw(2) << data; + oss << std::dec << ','; + } + std::string config_string = oss.str(); + if (!config_string.empty()) + config_string.pop_back(); + SConfig::GetInstance().m_bt_passthrough_link_keys = config_string; + SConfig::GetInstance().SaveSettings(); +} + bool CWII_IPC_HLE_Device_usb_oh1_57e_305_real::OpenDevice(libusb_device* device) { m_device = libusb_ref_device(device); @@ -402,6 +567,29 @@ void CWII_IPC_HLE_Device_usb_oh1_57e_305_real::TransferCallback(libusb_transfer* { s_showed_failed_transfer.Clear(); } + + if (tr->status == LIBUSB_TRANSFER_COMPLETED && tr->endpoint == HCI_EVENT) + { + const auto* event = reinterpret_cast(tr->buffer); + if (event->event == HCI_EVENT_LINK_KEY_NOTIFICATION) + { + const auto* notification = + reinterpret_cast(tr->buffer + sizeof(hci_event_hdr_t)); + + btaddr_t addr; + std::copy(std::begin(notification->bdaddr.b), std::end(notification->bdaddr.b), addr.begin()); + linkkey_t key; + std::copy(std::begin(notification->key), std::end(notification->key), std::begin(key)); + s_link_keys[addr] = key; + } + else if (event->event == HCI_EVENT_COMMAND_COMPL && + reinterpret_cast(tr->buffer + sizeof(*event))->opcode == + HCI_CMD_RESET) + { + s_need_reset_keys.Set(); + } + } + ctrl->SetRetVal(tr->actual_length); EnqueueReply(ctrl->m_cmd_address); } diff --git a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h index 2981896a39..b55e23ebc8 100644 --- a/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h +++ b/Source/Core/Core/IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h @@ -5,6 +5,7 @@ #pragma once #if defined(__LIBUSB__) +#include #include #include @@ -27,6 +28,9 @@ enum class SyncButtonState Ignored, }; +using btaddr_t = std::array; +using linkkey_t = std::array; + class CWII_IPC_HLE_Device_usb_oh1_57e_305_real final : public CWII_IPC_HLE_Device_usb_oh1_57e_305_base { @@ -67,12 +71,20 @@ private: // Set when we received a command to read the buffer size, and we need to fake a reply Common::Flag m_fake_read_buffer_size_reply; + bool m_is_wii_bt_module = false; + + void WaitForHCICommandComplete(u16 opcode); void SendHCIResetCommand(); + void SendHCIDeleteLinkKeyCommand(); + bool SendHCIStoreLinkKeyCommand(); void FakeReadBufferSizeReply(const CtrlBuffer& ctrl); void FakeSyncButtonEvent(const CtrlBuffer& ctrl, const u8* payload, u8 size); void FakeSyncButtonPressedEvent(const CtrlBuffer& ctrl); void FakeSyncButtonHeldEvent(const CtrlBuffer& ctrl); + void LoadLinkKeys(); + void SaveLinkKeys(); + bool OpenDevice(libusb_device* device); void StartTransferThread(); void StopTransferThread();