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).
This commit is contained in:
Léo Lam 2016-09-16 23:25:57 +02:00
parent e63b07f73b
commit 5b50b1e1aa
4 changed files with 204 additions and 0 deletions

View File

@ -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()

View File

@ -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;

View File

@ -2,7 +2,10 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <iomanip>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <libusb.h>
@ -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<btaddr_t, linkkey_t> 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<hci_delete_stored_link_key_cp*>(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<u8>(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<u8> 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<int>(buffer.size()), &actual_length, 20) == 0 &&
reinterpret_cast<hci_event_hdr_t*>(buffer.data())->event == HCI_EVENT_COMMAND_COMPL &&
reinterpret_cast<SHCIEventCommand*>(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<u8> packet(sizeof(hci_cmd_hdr_t) + sizeof(hci_delete_stored_link_key_cp));
auto* header = reinterpret_cast<hci_cmd_hdr_t*>(packet.data());
header->opcode = HCI_CMD_DELETE_STORED_LINK_KEY;
header->length = sizeof(hci_delete_stored_link_key_cp);
auto* cmd =
reinterpret_cast<hci_delete_stored_link_key_cp*>(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<u16>(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<u8>(sizeof(hci_write_stored_link_key_cp)) +
(sizeof(btaddr_t) + sizeof(linkkey_t)) * static_cast<u8>(s_link_keys.size());
std::vector<u8> packet(sizeof(hci_cmd_hdr_t) + payload_size);
auto* header = reinterpret_cast<hci_cmd_hdr_t*>(packet.data());
header->opcode = HCI_CMD_WRITE_STORED_LINK_KEY;
header->length = payload_size;
auto* cmd =
reinterpret_cast<hci_write_stored_link_key_cp*>(packet.data() + sizeof(hci_cmd_hdr_t));
cmd->num_keys_write = static_cast<u8>(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<u16>(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<std::string> 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<hci_event_hdr_t*>(tr->buffer);
if (event->event == HCI_EVENT_LINK_KEY_NOTIFICATION)
{
const auto* notification =
reinterpret_cast<hci_link_key_notification_ep*>(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<hci_command_compl_ep*>(tr->buffer + sizeof(*event))->opcode ==
HCI_CMD_RESET)
{
s_need_reset_keys.Set();
}
}
ctrl->SetRetVal(tr->actual_length);
EnqueueReply(ctrl->m_cmd_address);
}

View File

@ -5,6 +5,7 @@
#pragma once
#if defined(__LIBUSB__)
#include <array>
#include <atomic>
#include <thread>
@ -27,6 +28,9 @@ enum class SyncButtonState
Ignored,
};
using btaddr_t = std::array<u8, 6>;
using linkkey_t = std::array<u8, 16>;
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();