From 5b50b1e1aa3923fa46c3407885d44a7cbc570d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 16 Sep 2016 23:25:57 +0200 Subject: [PATCH] WII_IPC_HLE/57e_305: Store link keys 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. It doesn't fix adapters that can't remember any link key at all and always return 0 for the number of stored/written link keys. For those adapters, there is no fix. This also improves the usability of passthrough mode for adapters that already work, since pairings will now keep working even if the link keys get cleared by something else (for example by the host Bluetooth stack). --- Source/Core/Core/ConfigManager.cpp | 3 + Source/Core/Core/ConfigManager.h | 1 + .../WII_IPC_HLE_Device_usb_bt_real.cpp | 188 ++++++++++++++++++ .../IPC_HLE/WII_IPC_HLE_Device_usb_bt_real.h | 12 ++ 4 files changed, 204 insertions(+) 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();