Merge 95ad2d804d
into 4d0cf1315e
This commit is contained in:
commit
5f6a2c038f
|
@ -7,11 +7,13 @@
|
|||
#include <bit>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
///
|
||||
|
@ -240,4 +242,19 @@ T ExpandValue(T value, size_t left_shift_amount)
|
|||
return (value << left_shift_amount) |
|
||||
(T(-ExtractBit<0>(value)) >> (BitSize<T>() - left_shift_amount));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
constexpr auto AsU8Span(const T& obj)
|
||||
{
|
||||
return std::span{reinterpret_cast<const u8*>(std::addressof(obj)), sizeof(obj)};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
constexpr auto AsWritableU8Span(T& obj)
|
||||
{
|
||||
return std::span{reinterpret_cast<u8*>(std::addressof(obj)), sizeof(obj)};
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
#define RESOURCEPACK_DIR "ResourcePacks"
|
||||
#define DYNAMICINPUT_DIR "DynamicInputTextures"
|
||||
#define GRAPHICSMOD_DIR "GraphicMods"
|
||||
#define FIRMWARE_DIR "Firmware"
|
||||
#define WIISDSYNC_DIR "WiiSDSync"
|
||||
#define ASSEMBLY_DIR "SavedAssembly"
|
||||
#define WIIBANNERS_DIR "WiiBanners"
|
||||
|
|
|
@ -882,6 +882,7 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
|||
s_user_paths[D_DYNAMICINPUT_IDX] = s_user_paths[D_LOAD_IDX] + DYNAMICINPUT_DIR DIR_SEP;
|
||||
s_user_paths[D_GRAPHICSMOD_IDX] = s_user_paths[D_LOAD_IDX] + GRAPHICSMOD_DIR DIR_SEP;
|
||||
s_user_paths[D_BANNERS_WIIROOT_IDX] = s_user_paths[D_LOAD_IDX] + WIIBANNERS_DIR DIR_SEP;
|
||||
s_user_paths[D_FIRMWARE_IDX] = s_user_paths[D_LOAD_IDX] + FIRMWARE_DIR DIR_SEP;
|
||||
s_user_paths[D_WIISDCARDSYNCFOLDER_IDX] = s_user_paths[D_LOAD_IDX] + WIISDSYNC_DIR DIR_SEP;
|
||||
s_user_paths[F_DOLPHINCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DOLPHIN_CONFIG;
|
||||
s_user_paths[F_GCPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCPAD_CONFIG;
|
||||
|
|
|
@ -68,6 +68,7 @@ enum
|
|||
D_RESOURCEPACK_IDX,
|
||||
D_DYNAMICINPUT_IDX,
|
||||
D_GRAPHICSMOD_IDX,
|
||||
D_FIRMWARE_IDX,
|
||||
D_GBAUSER_IDX,
|
||||
D_GBASAVES_IDX,
|
||||
D_WIISDCARDSYNCFOLDER_IDX,
|
||||
|
|
|
@ -699,6 +699,8 @@ if(TARGET LibUSB::LibUSB)
|
|||
IOS/USB/Bluetooth/BTReal.h
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h
|
||||
IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp
|
||||
IOS/USB/Bluetooth/RealtekFirmwareLoader.h
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/Network.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
@ -27,24 +28,6 @@
|
|||
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
template <u16 Opcode, typename CommandType>
|
||||
struct HCICommandPayload
|
||||
{
|
||||
hci_cmd_hdr_t header{Opcode, sizeof(CommandType)};
|
||||
CommandType command{};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
constexpr auto AsU8Span(const T& obj)
|
||||
{
|
||||
return std::span{reinterpret_cast<const u8*>(std::addressof(obj)), sizeof(obj)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
|
||||
|
@ -295,7 +278,7 @@ auto BluetoothRealDevice::ProcessHCIEvent(BufferType buffer) -> BufferType
|
|||
payload.command.latency = 10000;
|
||||
payload.command.delay_variation = 0xffffffff;
|
||||
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(payload));
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(payload));
|
||||
}
|
||||
else if (event == HCI_EVENT_QOS_SETUP_COMPL)
|
||||
{
|
||||
|
@ -402,7 +385,7 @@ void BluetoothRealDevice::TriggerSyncButtonHeldEvent()
|
|||
void BluetoothRealDevice::SendHCIResetCommand()
|
||||
{
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "SendHCIResetCommand");
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(hci_cmd_hdr_t{HCI_CMD_RESET, 0}));
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(hci_cmd_hdr_t{HCI_CMD_RESET, 0}));
|
||||
}
|
||||
|
||||
void BluetoothRealDevice::SendHCIDeleteLinkKeyCommand()
|
||||
|
@ -413,7 +396,7 @@ void BluetoothRealDevice::SendHCIDeleteLinkKeyCommand()
|
|||
payload.command.bdaddr = {};
|
||||
payload.command.delete_all = 0x01;
|
||||
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(payload));
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(payload));
|
||||
}
|
||||
|
||||
bool BluetoothRealDevice::SendHCIStoreLinkKeyCommand()
|
||||
|
@ -450,7 +433,7 @@ bool BluetoothRealDevice::SendHCIStoreLinkKeyCommand()
|
|||
for (auto& [bdaddr, linkkey] : m_link_keys | std::views::take(num_link_keys))
|
||||
payload.link_keys[index++] = {bdaddr, linkkey};
|
||||
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(payload).first(payload_size));
|
||||
m_lib_usb_bt_adapter->SendControlTransfer(Common::AsU8Span(payload).first(payload_size));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,12 @@
|
|||
#include <fmt/format.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.h"
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
|
||||
namespace
|
||||
|
@ -53,13 +55,20 @@ bool LibUSBBluetoothAdapter::IsBluetoothDevice(const libusb_device_descriptor& d
|
|||
|
||||
// Some devices misreport their class, so we avoid relying solely on descriptor checks and allow
|
||||
// users to specify their own VID/PID.
|
||||
return is_bluetooth_protocol || LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(
|
||||
descriptor.idVendor, descriptor.idProduct);
|
||||
return is_bluetooth_protocol ||
|
||||
LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(descriptor.idVendor,
|
||||
descriptor.idProduct) ||
|
||||
IsKnownRealtekBluetoothDevice(descriptor.idVendor, descriptor.idProduct);
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::IsWiiBTModule() const
|
||||
{
|
||||
return m_is_wii_bt_module;
|
||||
return m_device_vid == 0x57e && m_device_pid == 0x305;
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::AreCommandsPendingResponse() const
|
||||
{
|
||||
return !m_pending_hci_transfers.empty() || !m_unacknowledged_commands.empty();
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::HasConfiguredBluetoothDevice()
|
||||
|
@ -115,8 +124,9 @@ LibUSBBluetoothAdapter::LibUSBBluetoothAdapter()
|
|||
NOTICE_LOG_FMT(IOS_WIIMOTE, "Using device {:04x}:{:04x} (rev {:x}) for Bluetooth: {} {} {}",
|
||||
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;
|
||||
|
||||
m_device_vid = device_descriptor.idVendor;
|
||||
m_device_pid = device_descriptor.idProduct;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -151,6 +161,17 @@ LibUSBBluetoothAdapter::LibUSBBluetoothAdapter()
|
|||
|
||||
m_output_worker.Reset("Bluetooth Output",
|
||||
std::bind_front(&LibUSBBluetoothAdapter::SubmitTimedTransfer, this));
|
||||
|
||||
if (IsRealtekVID(m_device_vid) || IsKnownRealtekBluetoothDevice(m_device_vid, m_device_pid))
|
||||
{
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Initializing Realtek Bluetooth device: {:04x}:{:04x}", m_device_vid,
|
||||
m_device_pid);
|
||||
if (!InitializeRealtekBluetoothDevice(*this))
|
||||
{
|
||||
PanicAlertFmtT("Failed to initialize Realtek Bluetooth device.\n\n"
|
||||
"Bluetooth passthrough will probably not work.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LibUSBBluetoothAdapter::~LibUSBBluetoothAdapter()
|
||||
|
@ -159,7 +180,7 @@ LibUSBBluetoothAdapter::~LibUSBBluetoothAdapter()
|
|||
return;
|
||||
|
||||
// Wait for completion (or time out) of all HCI commands.
|
||||
while (!m_pending_hci_transfers.empty() && !m_unacknowledged_commands.empty())
|
||||
while (AreCommandsPendingResponse())
|
||||
{
|
||||
(void)ReceiveHCIEvent();
|
||||
Common::YieldCPU();
|
||||
|
@ -394,6 +415,50 @@ void LibUSBBluetoothAdapter::SendControlTransfer(std::span<const u8> data)
|
|||
ScheduleControlTransfer(REQUEST_TYPE, 0, 0, 0, data, Clock::now());
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::SendBlockingCommand(std::span<const u8> data, std::span<u8> response)
|
||||
{
|
||||
SendControlTransfer(data);
|
||||
|
||||
const hci_cmd_hdr_t cmd = Common::BitCastPtr<hci_cmd_hdr_t>(data.data());
|
||||
|
||||
while (AreCommandsPendingResponse())
|
||||
{
|
||||
const auto event = ReceiveHCIEvent();
|
||||
|
||||
if (event.empty())
|
||||
{
|
||||
Common::YieldCPU();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event[0] != HCI_EVENT_COMMAND_COMPL)
|
||||
continue;
|
||||
|
||||
if (event.size() < sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized hci_command_compl_ep");
|
||||
continue;
|
||||
}
|
||||
|
||||
const hci_command_compl_ep compl_event =
|
||||
Common::BitCastPtr<hci_command_compl_ep>(event.data() + sizeof(hci_event_hdr_t));
|
||||
if (compl_event.opcode != cmd.opcode)
|
||||
continue;
|
||||
|
||||
if (event.size() < sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep) + response.size())
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized command result");
|
||||
break;
|
||||
}
|
||||
|
||||
std::copy_n(event.data() + sizeof(hci_event_hdr_t) + sizeof(hci_command_compl_ep),
|
||||
response.size(), response.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::StartInputTransfers()
|
||||
{
|
||||
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleInputTransfer>();
|
||||
|
|
|
@ -12,12 +12,20 @@
|
|||
#include "Common/Timer.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
template <u16 Opcode, typename CommandType>
|
||||
struct HCICommandPayload
|
||||
{
|
||||
hci_cmd_hdr_t header{Opcode, sizeof(CommandType)};
|
||||
CommandType command{};
|
||||
};
|
||||
|
||||
class LibUSBBluetoothAdapter
|
||||
{
|
||||
public:
|
||||
|
@ -45,6 +53,11 @@ public:
|
|||
// Schedule a transfer to be submitted as soon as possible.
|
||||
void SendControlTransfer(std::span<const u8> data);
|
||||
|
||||
// Blocks and eats events until a command complete event of the correct opcode is received.
|
||||
// Returns true on success or false on error or timeout.
|
||||
// The written response does not include the event header or command complete data.
|
||||
bool SendBlockingCommand(std::span<const u8> data, std::span<u8> response);
|
||||
|
||||
bool IsWiiBTModule() const;
|
||||
|
||||
private:
|
||||
|
@ -91,8 +104,8 @@ private:
|
|||
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;
|
||||
u16 m_device_vid = 0;
|
||||
u16 m_device_pid = 0;
|
||||
|
||||
// Bluetooth spec's Num_HCI_Command_Packets.
|
||||
// This is the number of hci commands that the host is allowed to send.
|
||||
|
@ -114,6 +127,7 @@ private:
|
|||
std::deque<OutstandingCommand> m_unacknowledged_commands;
|
||||
|
||||
bool IsControllerReadyForCommand() const;
|
||||
bool AreCommandsPendingResponse() const;
|
||||
|
||||
// Give the transfer to the worker and track the command appropriately.
|
||||
// This should only be used when IsControllerReadyForCommand is true.
|
||||
|
|
|
@ -0,0 +1,571 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Logic and structures are largely taken from Linux source:
|
||||
// drivers/bluetooth/btusb.c
|
||||
// drivers/bluetooth/btrtl.c
|
||||
// drivers/bluetooth/btrtl.h
|
||||
|
||||
#include "Core/IOS/USB/Bluetooth/RealtekFirmwareLoader.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/HttpRequest.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr u16 RTL_ROM_LMP_8723A = 0x1200;
|
||||
constexpr u16 RTL_ROM_LMP_8723B = 0x8723;
|
||||
constexpr u16 RTL_ROM_LMP_8821A = 0x8821;
|
||||
constexpr u16 RTL_ROM_LMP_8761A = 0x8761;
|
||||
constexpr u16 RTL_ROM_LMP_8822B = 0x8822;
|
||||
constexpr u16 RTL_ROM_LMP_8852A = 0x8852;
|
||||
constexpr u16 RTL_ROM_LMP_8851B = 0x8851;
|
||||
constexpr u16 RTL_ROM_LMP_8922A = 0x8922;
|
||||
|
||||
constexpr std::string_view RTL_EPATCH_SIGNATURE = "Realtech";
|
||||
constexpr std::string_view RTL_EPATCH_SIGNATURE_V2 = "RTBTCore";
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct rtl_vendor_cmd
|
||||
{
|
||||
std::array<u8, 5> data;
|
||||
};
|
||||
static_assert(sizeof(rtl_vendor_cmd) == 5);
|
||||
struct rtl_epatch_header
|
||||
{
|
||||
std::array<u8, 8> signature;
|
||||
u32 fw_version;
|
||||
u16 num_patches;
|
||||
};
|
||||
static_assert(sizeof(rtl_epatch_header) == 8 + 4 + 2);
|
||||
struct rtl_download_cmd
|
||||
{
|
||||
static constexpr int DATA_LEN = 252;
|
||||
|
||||
u8 index;
|
||||
std::array<u8, DATA_LEN> data;
|
||||
};
|
||||
static_assert(sizeof(rtl_download_cmd) == 253);
|
||||
struct rtl_download_response
|
||||
{
|
||||
u8 status;
|
||||
u8 index;
|
||||
};
|
||||
static_assert(sizeof(rtl_download_response) == 2);
|
||||
struct rtl_rom_version_evt
|
||||
{
|
||||
u8 status;
|
||||
u8 version;
|
||||
};
|
||||
static_assert(sizeof(rtl_rom_version_evt) == 2);
|
||||
#pragma pack(pop)
|
||||
} // namespace
|
||||
|
||||
bool IsRealtekVID(u16 vid)
|
||||
{
|
||||
return vid == 0x0bda;
|
||||
}
|
||||
|
||||
bool IsKnownRealtekBluetoothDevice(u16 vid, u16 pid)
|
||||
{
|
||||
// This list comes from Linux source: drivers/bluetooth/btusb.c
|
||||
constexpr std::pair<u16, u16> realtek_ids[] = {
|
||||
// Realtek 8821CE Bluetooth devices
|
||||
{0x13d3, 0x3529},
|
||||
// Realtek 8822CE Bluetooth devices
|
||||
{0x0bda, 0xb00c},
|
||||
{0x0bda, 0xc822},
|
||||
// Realtek 8822CU Bluetooth devices
|
||||
{0x13d3, 0x3549},
|
||||
// Realtek 8852AE Bluetooth devices
|
||||
{0x0bda, 0x2852},
|
||||
{0x0bda, 0xc852},
|
||||
{0x0bda, 0x385a},
|
||||
{0x0bda, 0x4852},
|
||||
{0x04c5, 0x165c},
|
||||
{0x04ca, 0x4006},
|
||||
{0x0cb8, 0xc549},
|
||||
// Realtek 8852CE Bluetooth devices
|
||||
{0x04ca, 0x4007},
|
||||
{0x04c5, 0x1675},
|
||||
{0x0cb8, 0xc558},
|
||||
{0x13d3, 0x3587},
|
||||
{0x13d3, 0x3586},
|
||||
{0x13d3, 0x3592},
|
||||
{0x0489, 0xe122},
|
||||
// Realtek 8852BE Bluetooth devices
|
||||
{0x0cb8, 0xc559},
|
||||
{0x0bda, 0x4853},
|
||||
{0x0bda, 0x887b},
|
||||
{0x0bda, 0xb85b},
|
||||
{0x13d3, 0x3570},
|
||||
{0x13d3, 0x3571},
|
||||
{0x13d3, 0x3572},
|
||||
{0x13d3, 0x3591},
|
||||
{0x0489, 0xe125},
|
||||
// Realtek 8852BT/8852BE-VT Bluetooth devices
|
||||
{0x0bda, 0x8520},
|
||||
// Realtek 8922AE Bluetooth devices
|
||||
{0x0bda, 0x8922},
|
||||
{0x13d3, 0x3617},
|
||||
{0x13d3, 0x3616},
|
||||
{0x0489, 0xe130},
|
||||
// Additional Realtek 8723AE Bluetooth devices
|
||||
{0x0930, 0x021d},
|
||||
{0x13d3, 0x3394},
|
||||
// Additional Realtek 8723BE Bluetooth devices
|
||||
{0x0489, 0xe085},
|
||||
{0x0489, 0xe08b},
|
||||
{0x04f2, 0xb49f},
|
||||
{0x13d3, 0x3410},
|
||||
{0x13d3, 0x3416},
|
||||
{0x13d3, 0x3459},
|
||||
{0x13d3, 0x3494},
|
||||
// Additional Realtek 8723BU Bluetooth devices
|
||||
{0x7392, 0xa611},
|
||||
// Additional Realtek 8723DE Bluetooth devices
|
||||
{0x0bda, 0xb009},
|
||||
{0x2ff8, 0xb011},
|
||||
// Additional Realtek 8761BUV Bluetooth devices
|
||||
{0x2357, 0x0604},
|
||||
{0x0b05, 0x190e},
|
||||
{0x2550, 0x8761},
|
||||
{0x0bda, 0x8771},
|
||||
{0x6655, 0x8771},
|
||||
{0x7392, 0xc611},
|
||||
{0x2b89, 0x8761},
|
||||
// Additional Realtek 8821AE Bluetooth devices
|
||||
{0x0b05, 0x17dc},
|
||||
{0x13d3, 0x3414},
|
||||
{0x13d3, 0x3458},
|
||||
{0x13d3, 0x3461},
|
||||
{0x13d3, 0x3462},
|
||||
// Additional Realtek 8822BE Bluetooth devices
|
||||
{0x13d3, 0x3526},
|
||||
{0x0b05, 0x185c},
|
||||
// Additional Realtek 8822CE Bluetooth devices
|
||||
{0x04ca, 0x4005},
|
||||
{0x04c5, 0x161f},
|
||||
{0x0b05, 0x18ef},
|
||||
{0x13d3, 0x3548},
|
||||
{0x13d3, 0x3549},
|
||||
{0x13d3, 0x3553},
|
||||
{0x13d3, 0x3555},
|
||||
{0x2ff8, 0x3051},
|
||||
{0x1358, 0xc123},
|
||||
{0x0bda, 0xc123},
|
||||
{0x0cb5, 0xc547},
|
||||
};
|
||||
|
||||
return std::ranges::find(realtek_ids, std::pair(vid, pid)) != std::end(realtek_ids);
|
||||
}
|
||||
|
||||
// Returns the data to be loaded, or an empty buffer on error.
|
||||
static Common::UniqueBuffer<char> ParseFirmware(std::span<const char> fw_data,
|
||||
std::span<const char> cfg_data,
|
||||
const hci_read_local_ver_rp& local_ver,
|
||||
u8 rom_version)
|
||||
{
|
||||
if (fw_data.size() < sizeof(rtl_epatch_header))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized firmware file");
|
||||
return {};
|
||||
}
|
||||
|
||||
const rtl_epatch_header epatch_info = Common::BitCastPtr<rtl_epatch_header>(fw_data.data());
|
||||
if (std::memcmp(epatch_info.signature.data(), RTL_EPATCH_SIGNATURE.data(),
|
||||
epatch_info.signature.size()) != 0)
|
||||
{
|
||||
if (std::memcmp(epatch_info.signature.data(), RTL_EPATCH_SIGNATURE_V2.data(),
|
||||
epatch_info.signature.size()) == 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "EPATCH v2 not implemented. Please report device to developers.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad EPATCH signature");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto fw_version = epatch_info.fw_version;
|
||||
const auto num_patches = epatch_info.num_patches;
|
||||
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "fw_version={} num_patches={}", epatch_info.fw_version, num_patches);
|
||||
|
||||
static constexpr u8 EXTENSION_SIGNATURE[] = {0x51, 0x04, 0xfd, 0x77};
|
||||
const auto extension = fw_data.last(std::size(EXTENSION_SIGNATURE));
|
||||
if (std::memcmp(extension.data(), EXTENSION_SIGNATURE, extension.size()) != 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad extension section signature");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Look for the project ID starting from the end of the file.
|
||||
int project_id = -1;
|
||||
for (auto iter = extension.begin(); iter >= fw_data.begin() + (sizeof(rtl_epatch_header) + 3);)
|
||||
{
|
||||
const u8 opcode = *--iter;
|
||||
const u8 length = *--iter;
|
||||
const u8 data = *--iter;
|
||||
|
||||
// EOF
|
||||
if (opcode == 0xff)
|
||||
break;
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Found instruction with length 0");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (opcode == 0 && length == 1)
|
||||
{
|
||||
project_id = data;
|
||||
break;
|
||||
}
|
||||
|
||||
iter -= length;
|
||||
}
|
||||
|
||||
struct LMPSubverProjectID
|
||||
{
|
||||
u16 lmp_subver;
|
||||
u8 project_id;
|
||||
};
|
||||
|
||||
constexpr LMPSubverProjectID subver_project_ids[] = {
|
||||
{RTL_ROM_LMP_8723B, 1}, {RTL_ROM_LMP_8821A, 2}, {RTL_ROM_LMP_8761A, 3},
|
||||
{RTL_ROM_LMP_8822B, 8}, {RTL_ROM_LMP_8723B, 9}, {RTL_ROM_LMP_8821A, 10},
|
||||
{RTL_ROM_LMP_8822B, 13}, {RTL_ROM_LMP_8761A, 14}, {RTL_ROM_LMP_8852A, 18},
|
||||
{RTL_ROM_LMP_8852A, 20}, {RTL_ROM_LMP_8852A, 25}, {RTL_ROM_LMP_8851B, 36},
|
||||
{RTL_ROM_LMP_8922A, 44}, {RTL_ROM_LMP_8852A, 47},
|
||||
};
|
||||
|
||||
// Verify that the project ID is what we expect for our LMP subversion.
|
||||
const auto* const it =
|
||||
std::ranges::find(subver_project_ids, project_id, &LMPSubverProjectID::project_id);
|
||||
if (it == std::end(subver_project_ids) || it->lmp_subver != local_ver.lmp_subversion)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Unexpected project_id: {}", project_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Structure is:
|
||||
// u16 chip_ids[num_patches]; u16 patch_lengths[num_patches]; u32 patch_offsets[num_patches];
|
||||
if (fw_data.size() <
|
||||
sizeof(rtl_epatch_header) + num_patches * (sizeof(u16) + sizeof(u16) + sizeof(u32)))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized firmware file");
|
||||
return {};
|
||||
}
|
||||
const auto* chip_id_base = fw_data.data() + sizeof(rtl_epatch_header);
|
||||
const auto* const patch_length_base = chip_id_base + (sizeof(u16) * num_patches);
|
||||
const auto* const patch_offset_base = patch_length_base + (sizeof(u16) * num_patches);
|
||||
for (int i = 0; i != num_patches; ++i)
|
||||
{
|
||||
const u16 chip_id = Common::BitCastPtr<u16>(chip_id_base + (i * sizeof(u16)));
|
||||
if (chip_id == rom_version + 1)
|
||||
{
|
||||
const u16 patch_length = Common::BitCastPtr<u16>(patch_length_base + (i * sizeof(u16)));
|
||||
const u32 patch_offset = Common::BitCastPtr<u32>(patch_offset_base + (i * sizeof(u32)));
|
||||
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Patch length:{} offset:{}", patch_length, patch_offset);
|
||||
|
||||
if (patch_offset + patch_length > fw_data.size())
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Undersized firmware file");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto result = Common::UniqueBuffer<char>{patch_length + cfg_data.size()};
|
||||
std::copy_n(fw_data.data() + patch_offset, patch_length - sizeof(fw_version), result.data());
|
||||
|
||||
// The last 4 bytes are replaced with fw_version.
|
||||
std::memcpy(result.data() + patch_length - sizeof(fw_version), &fw_version,
|
||||
sizeof(fw_version));
|
||||
|
||||
// Append the config binary.
|
||||
std::ranges::copy(cfg_data, result.data() + patch_length);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Patch not found");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Loads the provided data on the adapter.
|
||||
static bool LoadFirmware(LibUSBBluetoothAdapter& adapter, std::span<const char> data)
|
||||
{
|
||||
if (data.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Firmware data is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Loading firmware, total size: {}", data.size());
|
||||
|
||||
HCICommandPayload<0xfc20, rtl_download_cmd> payload{};
|
||||
|
||||
const auto write_block = [&](std::span<const u8> data) {
|
||||
rtl_download_response result{};
|
||||
if (!adapter.SendBlockingCommand(data, Common::AsWritableU8Span(result)))
|
||||
return false;
|
||||
|
||||
if (result.status != 0x00)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad load firmware status: 0x{:02x}", result.status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Write all blocks except the last one.
|
||||
auto block_iter = data.begin();
|
||||
while (data.end() - block_iter > rtl_download_cmd::DATA_LEN)
|
||||
{
|
||||
const auto block_end = block_iter + rtl_download_cmd::DATA_LEN;
|
||||
std::copy(block_iter, block_end, payload.command.data.data());
|
||||
|
||||
if (!write_block(Common::AsU8Span(payload)))
|
||||
return false;
|
||||
|
||||
if (++payload.command.index == 0x80)
|
||||
payload.command.index = 1;
|
||||
|
||||
block_iter = block_end;
|
||||
}
|
||||
|
||||
// Write the last block.
|
||||
const auto last_block_size = data.end() - block_iter;
|
||||
const auto bytes_short_of_full_block = rtl_download_cmd::DATA_LEN - last_block_size;
|
||||
|
||||
// Mark data end.
|
||||
payload.command.index |= 0x80u;
|
||||
payload.header.length -= bytes_short_of_full_block;
|
||||
std::copy(block_iter, data.end(), payload.command.data.data());
|
||||
|
||||
if (!write_block(Common::AsU8Span(payload).first(sizeof(payload) - bytes_short_of_full_block)))
|
||||
return false;
|
||||
|
||||
NOTICE_LOG_FMT(IOS_WIIMOTE, "Loading firmware complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr u64 LocalVerUID(u16 lmp_subversion, u16 hci_revision, u8 hci_version)
|
||||
{
|
||||
return u64(lmp_subversion) | (u64(hci_revision) << 16u) | (u64(hci_version) << 32u);
|
||||
}
|
||||
|
||||
// Returns the firmware name for a given local version.
|
||||
static std::string GetFirmwareName(const hci_read_local_ver_rp& local_ver)
|
||||
{
|
||||
// This list comes from Linux source: drivers/bluetooth/btrtl.c
|
||||
switch (LocalVerUID(local_ver.lmp_subversion, local_ver.hci_revision, local_ver.hci_version))
|
||||
{
|
||||
case LocalVerUID(RTL_ROM_LMP_8723A, 0xb, 0x6):
|
||||
return "8723a";
|
||||
case LocalVerUID(RTL_ROM_LMP_8723B, 0xb, 0x6):
|
||||
return "8723b";
|
||||
case LocalVerUID(RTL_ROM_LMP_8723B, 0xd, 0x8):
|
||||
return "8723d";
|
||||
case LocalVerUID(RTL_ROM_LMP_8761A, 0xa, 0x6):
|
||||
return "8761a";
|
||||
case LocalVerUID(RTL_ROM_LMP_8761A, 0xb, 0xa):
|
||||
return "8761bu";
|
||||
case LocalVerUID(RTL_ROM_LMP_8821A, 0xa, 0x6):
|
||||
return "8821a";
|
||||
case LocalVerUID(RTL_ROM_LMP_8821A, 0xc, 0x8):
|
||||
return "8821c";
|
||||
case LocalVerUID(RTL_ROM_LMP_8822B, 0xc, 0xa):
|
||||
return "8822cu";
|
||||
case LocalVerUID(RTL_ROM_LMP_8822B, 0xb, 0x7):
|
||||
return "8822b";
|
||||
case LocalVerUID(RTL_ROM_LMP_8851B, 0xb, 0xc):
|
||||
return "8851bu";
|
||||
case LocalVerUID(RTL_ROM_LMP_8852A, 0xa, 0xb):
|
||||
return "8852au";
|
||||
case LocalVerUID(RTL_ROM_LMP_8852A, 0xb, 0xb):
|
||||
return "8852bu";
|
||||
case LocalVerUID(RTL_ROM_LMP_8852A, 0xc, 0xc):
|
||||
return "8852cu";
|
||||
case LocalVerUID(RTL_ROM_LMP_8852A, 0x87, 0xc):
|
||||
return "8852btu";
|
||||
case LocalVerUID(RTL_ROM_LMP_8922A, 0xa, 0xc):
|
||||
return "8922au";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static std::string GetFirmwareLoadPath()
|
||||
{
|
||||
return File::GetUserPath(D_FIRMWARE_IDX) + "rtl_bt/";
|
||||
}
|
||||
|
||||
static void DownloadFirmwareFilesFromInternet(const std::string& fw_filename,
|
||||
const std::string& config_filename)
|
||||
{
|
||||
// Gitlab seems to be an appropriate source.
|
||||
// An alternative could be kernel.org
|
||||
// https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/rtl_bt/
|
||||
constexpr auto base_url = "https://gitlab.com/kernel-firmware/linux-firmware/-/raw/main/rtl_bt/";
|
||||
|
||||
Common::HttpRequest request;
|
||||
request.FollowRedirects(1);
|
||||
|
||||
const auto firmware_load_path = GetFirmwareLoadPath();
|
||||
File::CreateFullPath(firmware_load_path);
|
||||
|
||||
// _fw.bin
|
||||
{
|
||||
const auto fw_url = base_url + fw_filename;
|
||||
const auto response = request.Get(fw_url);
|
||||
if (!response)
|
||||
{
|
||||
PanicAlertFmtT("HTTP GET {0}\n\nError: {1}", fw_url, request.GetLastResponseCode());
|
||||
return;
|
||||
}
|
||||
|
||||
File::WriteStringToFile(
|
||||
firmware_load_path + fw_filename,
|
||||
std::string_view{reinterpret_cast<const char*>(response->data()), response->size()});
|
||||
}
|
||||
|
||||
// _config.bin
|
||||
{
|
||||
const auto config_url = base_url + config_filename;
|
||||
const auto response = request.Get(config_url);
|
||||
if (!response)
|
||||
{
|
||||
if (request.GetLastResponseCode() == 404)
|
||||
{
|
||||
// Some devices don't have this _config.bin.
|
||||
// But the very popular 8761B does, so I suppose it makes sense to show this error.
|
||||
PanicAlertFmtT("File {0} was not found on the server.\n\nThis might be normal.\n\n"
|
||||
"Not all devices expect this file.",
|
||||
config_filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
PanicAlertFmtT("HTTP GET {0}\n\nError: {1}", config_url, request.GetLastResponseCode());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
File::WriteStringToFile(
|
||||
firmware_load_path + config_filename,
|
||||
std::string_view{reinterpret_cast<const char*>(response->data()), response->size()});
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowFirmwareReadError(std::string_view fw_path)
|
||||
{
|
||||
PanicAlertFmtT("Bluetooth passthrough failed to read firmware file from:\n"
|
||||
"{0}\n\n"
|
||||
"Refer to https://wiki.dolphin-emu.org/index.php?title=Bluetooth_Passthrough "
|
||||
"for instructions.",
|
||||
fw_path);
|
||||
}
|
||||
|
||||
bool InitializeRealtekBluetoothDevice(LibUSBBluetoothAdapter& adapter)
|
||||
{
|
||||
// Read local version.
|
||||
hci_read_local_ver_rp local_ver{};
|
||||
if (!adapter.SendBlockingCommand(Common::AsU8Span(hci_cmd_hdr_t{HCI_CMD_READ_LOCAL_VER, 0}),
|
||||
Common::AsWritableU8Span(local_ver)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (local_ver.status != 0x00)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad local version status: 0x{:02x}", local_ver.status);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto firmware_name = GetFirmwareName(local_ver);
|
||||
// If firmware has already been loaded then the subversion changes and we won't find a match.
|
||||
if (firmware_name.empty())
|
||||
{
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Assuming no firmware load is needed");
|
||||
return true;
|
||||
}
|
||||
|
||||
// A name like "8761bu" is turned into "rtl8761bu_fw.bin"
|
||||
const auto base_filename = "rtl" + firmware_name;
|
||||
const auto fw_filename = base_filename + "_fw.bin";
|
||||
const auto config_filename = base_filename + "_config.bin";
|
||||
|
||||
const auto firmware_load_path = GetFirmwareLoadPath();
|
||||
|
||||
const auto fw_path = firmware_load_path + fw_filename;
|
||||
const auto config_path = firmware_load_path + config_filename;
|
||||
|
||||
NOTICE_LOG_FMT(IOS_WIIMOTE, "Loading firmware: {}", base_filename);
|
||||
|
||||
if (!File::Exists(fw_path))
|
||||
{
|
||||
const bool should_download =
|
||||
AskYesNoFmtT("Bluetooth passthrough requires missing firmware: {0}\n\n"
|
||||
"Automatically download from gitlab.com now?",
|
||||
base_filename);
|
||||
|
||||
if (should_download)
|
||||
DownloadFirmwareFilesFromInternet(fw_filename, config_filename);
|
||||
}
|
||||
|
||||
std::string fw_data;
|
||||
if (!File::ReadFileToString(fw_path, fw_data))
|
||||
{
|
||||
ShowFirmwareReadError(fw_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 8723A is old and the binary is loaded as-is without "parsing".
|
||||
if (local_ver.lmp_subversion == RTL_ROM_LMP_8723A)
|
||||
{
|
||||
// File should *not* have the epatch signature.
|
||||
if (fw_data.starts_with(RTL_EPATCH_SIGNATURE))
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Unexpected EPATCH signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
return LoadFirmware(adapter, fw_data);
|
||||
}
|
||||
|
||||
// Read ROM version.
|
||||
rtl_rom_version_evt read_rom_ver{};
|
||||
if (!adapter.SendBlockingCommand(Common::AsU8Span(hci_cmd_hdr_t{0xfc6d, 0}),
|
||||
Common::AsWritableU8Span(read_rom_ver)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (read_rom_ver.status != 0x00)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Bad ROM version status: 0x{:02x}", read_rom_ver.status);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u8 rom_version = read_rom_ver.version;
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "ROM version: 0x{:02x}", rom_version);
|
||||
|
||||
// Apparently only certain devices require the config binary. We load it whenever present.
|
||||
std::string config_data;
|
||||
File::ReadFileToString(config_path, config_data);
|
||||
|
||||
const auto firmware = ParseFirmware(fw_data, config_data, local_ver, rom_version);
|
||||
return LoadFirmware(adapter, firmware);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class LibUSBBluetoothAdapter;
|
||||
|
||||
bool IsRealtekVID(u16 vid);
|
||||
bool IsKnownRealtekBluetoothDevice(u16 vid, u16 pid);
|
||||
|
||||
// Identifies the device and loads firmware as needed.
|
||||
bool InitializeRealtekBluetoothDevice(LibUSBBluetoothAdapter&);
|
|
@ -404,6 +404,7 @@
|
|||
<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\RealtekFirmwareLoader.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" />
|
||||
|
@ -1083,6 +1084,7 @@
|
|||
<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\RealtekFirmwareLoader.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
|
|
Loading…
Reference in New Issue