diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 9960658fde..54dfb24644 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -115,8 +115,6 @@ void WaitUntilDoneBooting(); void SaveScreenShot(); void SaveScreenShot(std::string_view name); -void Callback_WiimoteInterruptChannel(int number, u16 channel_id, const u8* data, u32 size); - // This displays messages in a user-visible way. void DisplayMessage(std::string message, int time_in_ms); diff --git a/Source/Core/Core/HW/Wiimote.cpp b/Source/Core/Core/HW/Wiimote.cpp index 2c911a4577..4fc857b82a 100644 --- a/Source/Core/Core/HW/Wiimote.cpp +++ b/Source/Core/Core/HW/Wiimote.cpp @@ -4,8 +4,6 @@ #include "Core/HW/Wiimote.h" -#include - #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" @@ -46,15 +44,44 @@ void SetSource(unsigned int index, WiimoteSource source) WiimoteReal::HandleWiimoteSourceChange(index); - // Reconnect to the emulator. - Core::RunAsCPUThread([index, previous_source, source] { - if (previous_source != WiimoteSource::None) - ::Wiimote::Connect(index, false); - - if (source == WiimoteSource::Emulated) - ::Wiimote::Connect(index, true); - }); + Core::RunAsCPUThread([index] { UpdateSource(index); }); } + +void UpdateSource(unsigned int index) +{ + const auto ios = IOS::HLE::GetIOS(); + if (!ios) + return; + + const auto bluetooth = std::static_pointer_cast( + ios->GetDeviceByName("/dev/usb/oh1/57e/305")); + if (!bluetooth) + return; + + bluetooth->AccessWiimoteByIndex(index)->SetSource(GetHIDWiimoteSource(index)); +} + +HIDWiimote* GetHIDWiimoteSource(unsigned int index) +{ + HIDWiimote* hid_source = nullptr; + + switch (GetSource(index)) + { + case WiimoteSource::Emulated: + hid_source = static_cast(::Wiimote::GetConfig()->GetController(index)); + break; + + case WiimoteSource::Real: + hid_source = WiimoteReal::g_wiimotes[index].get(); + break; + + default: + break; + } + + return hid_source; +} + } // namespace WiimoteCommon namespace Wiimote @@ -143,25 +170,6 @@ void Initialize(InitializeMode init_mode) Movie::ChangeWiiPads(); } -void Connect(unsigned int index, bool connect) -{ - if (SConfig::GetInstance().m_bt_passthrough_enabled || index >= MAX_BBMOTES) - return; - - const auto ios = IOS::HLE::GetIOS(); - if (!ios) - return; - - const auto bluetooth = std::static_pointer_cast( - ios->GetDeviceByName("/dev/usb/oh1/57e/305")); - - if (bluetooth) - bluetooth->AccessWiimoteByIndex(index)->Activate(connect); - - const char* const message = connect ? "Wii Remote {} connected" : "Wii Remote {} disconnected"; - Core::DisplayMessage(fmt::format(message, index + 1), 3000); -} - void ResetAllWiimotes() { for (int i = WIIMOTE_CHAN_0; i < MAX_BBMOTES; ++i) @@ -184,84 +192,6 @@ void Pause() WiimoteReal::Pause(); } -// An L2CAP packet is passed from the Core to the Wiimote on the HID CONTROL channel. -void ControlChannel(int number, u16 channel_id, const void* data, u32 size) -{ - if (WiimoteCommon::GetSource(number) == WiimoteSource::Emulated) - { - static_cast(s_config.GetController(number)) - ->ControlChannel(channel_id, data, size); - } - else - { - WiimoteReal::ControlChannel(number, channel_id, data, size); - } -} - -// An L2CAP packet is passed from the Core to the Wiimote on the HID INTERRUPT channel. -void InterruptChannel(int number, u16 channel_id, const void* data, u32 size) -{ - if (WiimoteCommon::GetSource(number) == WiimoteSource::Emulated) - { - static_cast(s_config.GetController(number)) - ->InterruptChannel(channel_id, data, size); - } - else - { - WiimoteReal::InterruptChannel(number, channel_id, data, size); - } -} - -bool ButtonPressed(int number) -{ - const WiimoteSource source = WiimoteCommon::GetSource(number); - - if (s_last_connect_request_counter[number] > 0) - { - --s_last_connect_request_counter[number]; - if (source != WiimoteSource::None && NetPlay::IsNetPlayRunning()) - Wiimote::NetPlay_GetButtonPress(number, false); - return false; - } - - bool button_pressed = false; - - if (source == WiimoteSource::Emulated) - button_pressed = - static_cast(s_config.GetController(number))->CheckForButtonPress(); - - if (source == WiimoteSource::Real) - button_pressed = WiimoteReal::CheckForButtonPress(number); - - if (source != WiimoteSource::None && NetPlay::IsNetPlayRunning()) - button_pressed = Wiimote::NetPlay_GetButtonPress(number, button_pressed); - - return button_pressed; -} - -// This function is called periodically by the Core to update Wiimote state. -void Update(int number, bool connected) -{ - if (connected) - { - if (WiimoteCommon::GetSource(number) == WiimoteSource::Emulated) - static_cast(s_config.GetController(number))->Update(); - else - WiimoteReal::Update(number); - } - else - { - if (ButtonPressed(number)) - { - Connect(number, true); - // arbitrary value so it doesn't try to send multiple requests before Dolphin can react - // if Wii Remotes are polled at 200Hz then this results in one request being sent per 500ms - s_last_connect_request_counter[number] = 100; - } - } -} - -// Save/Load state void DoState(PointerWrap& p) { for (int i = 0; i < MAX_BBMOTES; ++i) @@ -281,10 +211,7 @@ void DoState(PointerWrap& p) // If using a real wiimote or the save-state source does not match the current source, // then force a reconnection on load. if (source == WiimoteSource::Real || source != WiimoteSource(state_wiimote_source)) - { - Connect(i, false); - Connect(i, true); - } + WiimoteCommon::UpdateSource(i); } } } diff --git a/Source/Core/Core/HW/Wiimote.h b/Source/Core/Core/HW/Wiimote.h index 8709c064b6..88450fecde 100644 --- a/Source/Core/Core/HW/Wiimote.h +++ b/Source/Core/Core/HW/Wiimote.h @@ -53,8 +53,17 @@ enum class WiimoteSource namespace WiimoteCommon { +class HIDWiimote; + WiimoteSource GetSource(unsigned int index); void SetSource(unsigned int index, WiimoteSource source); + +// Used to reconnect WiimoteDevice instance to HID source. +// Must be run from CPU thread. +void UpdateSource(unsigned int index); + +HIDWiimote* GetHIDWiimoteSource(unsigned int index); + } // namespace WiimoteCommon namespace Wiimote @@ -67,12 +76,9 @@ enum class InitializeMode // The Real Wii Remote sends report every ~5ms (200 Hz). constexpr int UPDATE_FREQ = 200; -// Custom channel ID used in ControlChannel to indicate disconnects -constexpr int DOLPHIN_DISCONNET_CONTROL_CHANNEL = 99; void Shutdown(); void Initialize(InitializeMode init_mode); -void Connect(unsigned int index, bool connect); void ResetAllWiimotes(); void LoadConfig(); void Resume(); @@ -91,10 +97,6 @@ ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(int number, WiimoteEmu::DrawsomeTabletGroup group); ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGroup group); -void ControlChannel(int number, u16 channel_id, const void* data, u32 size); -void InterruptChannel(int number, u16 channel_id, const void* data, u32 size); -bool ButtonPressed(int number); -void Update(int number, bool connected); bool NetPlay_GetButtonPress(int wiimote, bool pressed); } // namespace Wiimote diff --git a/Source/Core/Core/HW/WiimoteCommon/DataReport.cpp b/Source/Core/Core/HW/WiimoteCommon/DataReport.cpp index 0197553517..ef8cc799f3 100644 --- a/Source/Core/Core/HW/WiimoteCommon/DataReport.cpp +++ b/Source/Core/Core/HW/WiimoteCommon/DataReport.cpp @@ -324,7 +324,7 @@ DataReportBuilder::DataReportBuilder(InputReportID rpt_id) : m_data(rpt_id) void DataReportBuilder::SetMode(InputReportID rpt_id) { m_data.report_id = rpt_id; - m_manip = MakeDataReportManipulator(rpt_id, GetDataPtr() + HEADER_SIZE); + m_manip = MakeDataReportManipulator(rpt_id, GetDataPtr() + sizeof(m_data.report_id)); } InputReportID DataReportBuilder::GetMode() const @@ -405,7 +405,7 @@ u8* DataReportBuilder::GetDataPtr() u32 DataReportBuilder::GetDataSize() const { - return m_manip->GetDataSize() + HEADER_SIZE; + return m_manip->GetDataSize() + sizeof(m_data.report_id); } u8* DataReportBuilder::GetIRDataPtr() diff --git a/Source/Core/Core/HW/WiimoteCommon/DataReport.h b/Source/Core/Core/HW/WiimoteCommon/DataReport.h index 75b649d854..bddfbd456c 100644 --- a/Source/Core/Core/HW/WiimoteCommon/DataReport.h +++ b/Source/Core/Core/HW/WiimoteCommon/DataReport.h @@ -94,11 +94,11 @@ public: u32 GetDataSize() const; - static constexpr int HEADER_SIZE = 2; - static constexpr int MAX_DATA_SIZE = MAX_PAYLOAD - 2; + // The largest report is 0x3d (21 extension bytes). + static constexpr int MAX_DATA_SIZE = 21; private: - TypedHIDInputData> m_data; + TypedInputData> m_data; std::unique_ptr m_manip; }; diff --git a/Source/Core/Core/HW/WiimoteCommon/WiimoteConstants.h b/Source/Core/Core/HW/WiimoteCommon/WiimoteConstants.h index 99cf0e04e7..71f73bf85b 100644 --- a/Source/Core/Core/HW/WiimoteCommon/WiimoteConstants.h +++ b/Source/Core/Core/HW/WiimoteCommon/WiimoteConstants.h @@ -8,6 +8,9 @@ namespace WiimoteCommon { +// Note this size includes the HID header. +// e.g. 0xa1 0x3d 0x... +// TODO: Kill/rename this constant so it's more clear. constexpr u8 MAX_PAYLOAD = 23; enum class InputReportID : u8 diff --git a/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h b/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h index 6afa1fc38c..05ca77f0c6 100644 --- a/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h +++ b/Source/Core/Core/HW/WiimoteCommon/WiimoteHid.h @@ -21,6 +21,44 @@ constexpr u8 HID_HANDSHAKE_SUCCESS = 0; constexpr u8 HID_PARAM_INPUT = 1; constexpr u8 HID_PARAM_OUTPUT = 2; +class HIDWiimote +{ +public: + using InterruptCallbackType = std::function; + + virtual ~HIDWiimote() = default; + + virtual void EventLinked() = 0; + virtual void EventUnlinked() = 0; + + // Called every ~200hz after HID channels are established. + virtual void Update() = 0; + + void SetInterruptCallback(InterruptCallbackType callback) { m_callback = std::move(callback); } + + // HID report type:0xa2 (data output) payloads sent to the wiimote interrupt channel. + // Does not include HID-type header. + virtual void InterruptDataOutput(const u8* data, u32 size) = 0; + + // Used to connect a disconnected wii remote on button press. + virtual bool IsButtonPressed() = 0; + +protected: + void InterruptDataInputCallback(const u8* data, u32 size) + { + InterruptCallback((WiimoteCommon::HID_TYPE_DATA << 4) | WiimoteCommon::HID_PARAM_INPUT, data, + size); + } + + void InterruptCallback(u8 hid_type, const u8* data, u32 size) + { + m_callback(hid_type, data, size); + } + +private: + InterruptCallbackType m_callback; +}; + #ifdef _MSC_VER #pragma warning(push) // Disable warning for zero-sized array: @@ -29,41 +67,19 @@ constexpr u8 HID_PARAM_OUTPUT = 2; #pragma pack(push, 1) -struct HIDPacket -{ - static constexpr int HEADER_SIZE = 1; - - u8 param : 4; - u8 type : 4; - - u8 data[0]; -}; - template -struct TypedHIDInputData +struct TypedInputData { - TypedHIDInputData(InputReportID _rpt_id) - : param(HID_PARAM_INPUT), type(HID_TYPE_DATA), report_id(_rpt_id) - { - } - - u8 param : 4; - u8 type : 4; + TypedInputData(InputReportID _rpt_id) : report_id(_rpt_id) {} InputReportID report_id; - - T data; + T payload = {}; static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v); u8* GetData() { return reinterpret_cast(this); } const u8* GetData() const { return reinterpret_cast(this); } - - constexpr u32 GetSize() const - { - static_assert(sizeof(*this) == sizeof(T) + 2); - return sizeof(*this); - } + constexpr u32 GetSize() const { return sizeof(*this); } }; #pragma pack(pop) diff --git a/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h b/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h index e641b85d8b..158552b143 100644 --- a/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h +++ b/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h @@ -167,7 +167,7 @@ static_assert(sizeof(OutputReportSpeakerData) == 21, "Wrong size"); // FYI: Also contains LSB of accel data: union ButtonData { - static constexpr u16 BUTTON_MASK = ~0x6060; + static constexpr u16 BUTTON_MASK = ~0x60e0; u16 hex; diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index bd25279867..862a4a49ad 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -63,29 +63,32 @@ void Wiimote::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneri (this->*handler)(Common::BitCastPtr(rpt.data)); } -// Here we process the Output Reports that the Wii sends. Our response will be -// an Input Report back to the Wii. Input and Output is from the Wii's -// perspective, Output means data to the Wiimote (from the Wii), Input means -// data from the Wiimote. -// -// The call browser: -// -// 1. Wiimote_InterruptChannel > InterruptChannel > HIDOutputReport -// 2. Wiimote_ControlChannel > ControlChannel > HIDOutputReport +void Wiimote::EventLinked() +{ + Reset(); +} -void Wiimote::HIDOutputReport(const void* data, u32 size) +void Wiimote::EventUnlinked() +{ + Reset(); +} + +void Wiimote::InterruptDataOutput(const u8* data, u32 size) { if (!size) { - ERROR_LOG(WIIMOTE, "HIDOutputReport: zero sized data"); + ERROR_LOG(WIIMOTE, "OutputData: zero sized data"); return; } - auto& rpt = *static_cast(data); + auto& rpt = *reinterpret_cast(data); const int rpt_size = size - OutputReportGeneric::HEADER_SIZE; - DEBUG_LOG(WIIMOTE, "HIDOutputReport (page: %i, cid: 0x%02x, wm: 0x%02x)", m_index, - m_reporting_channel, int(rpt.rpt_id)); + if (!rpt_size) + { + ERROR_LOG(WIIMOTE, "OutputData: zero sized report"); + return; + } // WiiBrew: // In every single Output Report, bit 0 (0x01) of the first byte controls the Rumble feature. @@ -132,21 +135,16 @@ void Wiimote::HIDOutputReport(const void* data, u32 size) } } -void Wiimote::CallbackInterruptChannel(const u8* data, u32 size) -{ - Core::Callback_WiimoteInterruptChannel(m_index, m_reporting_channel, data, size); -} - void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code) { - TypedHIDInputData rpt(InputReportID::Ack); - auto& ack = rpt.data; + TypedInputData rpt(InputReportID::Ack); + auto& ack = rpt.payload; ack.buttons = m_status.buttons; ack.rpt_id = rpt_id; ack.error_code = error_code; - CallbackInterruptChannel(rpt.GetData(), rpt.GetSize()); + InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); } void Wiimote::HandleExtensionSwap() @@ -246,9 +244,9 @@ void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&) // Less than 0x20 triggers the low-battery flag: m_status.battery_low = m_status.battery < 0x20; - TypedHIDInputData rpt(InputReportID::Status); - rpt.data = m_status; - CallbackInterruptChannel(rpt.GetData(), rpt.GetSize()); + TypedInputData rpt(InputReportID::Status); + rpt.payload = m_status; + InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); } void Wiimote::HandleWriteData(const OutputReportWriteData& wd) @@ -442,8 +440,8 @@ bool Wiimote::ProcessReadDataRequest() return false; } - TypedHIDInputData rpt(InputReportID::ReadDataReply); - auto& reply = rpt.data; + TypedInputData rpt(InputReportID::ReadDataReply); + auto& reply = rpt.payload; reply.buttons = m_status.buttons; reply.address = Common::swap16(m_read_request.address); @@ -539,7 +537,7 @@ bool Wiimote::ProcessReadDataRequest() reply.error = static_cast(error_code); - CallbackInterruptChannel(rpt.GetData(), rpt.GetSize()); + InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); return true; } @@ -552,7 +550,6 @@ void Wiimote::DoState(PointerWrap& p) // No need to sync. This is not wiimote state. // p.Do(m_sensor_bar_on_top); - p.Do(m_reporting_channel); p.Do(m_reporting_mode); p.Do(m_reporting_continuous); diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index 4805d7b95c..2702ae5b6b 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -72,7 +72,6 @@ void Wiimote::Reset() SetRumble(false); // Wiimote starts in non-continuous CORE mode: - m_reporting_channel = 0; m_reporting_mode = InputReportID::ReportCore; m_reporting_continuous = false; @@ -404,10 +403,6 @@ void Wiimote::UpdateButtonsStatus() // This is called every ::Wiimote::UPDATE_FREQ (200hz) void Wiimote::Update() { - // Check if connected. - if (0 == m_reporting_channel) - return; - const auto lock = GetStateLock(); // Hotkey / settings modifier @@ -567,7 +562,7 @@ void Wiimote::SendDataReport() Movie::CheckWiimoteStatus(m_index, rpt_builder, m_active_extension, GetExtensionEncryptionKey()); // Send the report: - CallbackInterruptChannel(rpt_builder.GetDataPtr(), rpt_builder.GetDataSize()); + InterruptDataInputCallback(rpt_builder.GetDataPtr(), rpt_builder.GetDataSize()); // The interleaved reporting modes toggle back and forth: if (InputReportID::ReportInterleave1 == m_reporting_mode) @@ -576,97 +571,7 @@ void Wiimote::SendDataReport() m_reporting_mode = InputReportID::ReportInterleave1; } -void Wiimote::ControlChannel(const u16 channel_id, const void* data, u32 size) -{ - // Check for custom communication - if (channel_id == ::Wiimote::DOLPHIN_DISCONNET_CONTROL_CHANNEL) - { - // Wii Remote disconnected. - Reset(); - - return; - } - - if (!size) - { - ERROR_LOG(WIIMOTE, "ControlChannel: zero sized data"); - return; - } - - m_reporting_channel = channel_id; - - const auto& hidp = *reinterpret_cast(data); - - DEBUG_LOG(WIIMOTE, "Emu ControlChannel (page: %i, type: 0x%02x, param: 0x%02x)", m_index, - hidp.type, hidp.param); - - switch (hidp.type) - { - case HID_TYPE_HANDSHAKE: - PanicAlert("HID_TYPE_HANDSHAKE - %s", (hidp.param == HID_PARAM_INPUT) ? "INPUT" : "OUPUT"); - break; - - case HID_TYPE_SET_REPORT: - if (HID_PARAM_INPUT == hidp.param) - { - PanicAlert("HID_TYPE_SET_REPORT - INPUT"); - } - else - { - // AyuanX: My experiment shows Control Channel is never used - // shuffle2: but lwbt uses this, so we'll do what we must :) - HIDOutputReport(hidp.data, size - HIDPacket::HEADER_SIZE); - - // TODO: Should this be above the previous? - u8 handshake = HID_HANDSHAKE_SUCCESS; - CallbackInterruptChannel(&handshake, sizeof(handshake)); - } - break; - - case HID_TYPE_DATA: - PanicAlert("HID_TYPE_DATA - %s", (hidp.param == HID_PARAM_INPUT) ? "INPUT" : "OUTPUT"); - break; - - default: - PanicAlert("HidControlChannel: Unknown type %x and param %x", hidp.type, hidp.param); - break; - } -} - -void Wiimote::InterruptChannel(const u16 channel_id, const void* data, u32 size) -{ - if (!size) - { - ERROR_LOG(WIIMOTE, "InterruptChannel: zero sized data"); - return; - } - - m_reporting_channel = channel_id; - - const auto& hidp = *reinterpret_cast(data); - - switch (hidp.type) - { - case HID_TYPE_DATA: - switch (hidp.param) - { - case HID_PARAM_OUTPUT: - HIDOutputReport(hidp.data, size - HIDPacket::HEADER_SIZE); - break; - - default: - PanicAlert("HidInput: HID_TYPE_DATA - param 0x%02x", hidp.param); - break; - } - break; - - default: - PanicAlert("HidInput: Unknown type 0x%02x and param 0x%02x", hidp.type, hidp.param); - break; - } -} - -bool Wiimote::CheckForButtonPress() +bool Wiimote::IsButtonPressed() { u16 buttons = 0; const auto lock = GetStateLock(); diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index 8ded6f0dc4..a01d4c545f 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -85,7 +85,7 @@ void UpdateCalibrationDataChecksum(T& data, int cksum_bytes) } } -class Wiimote : public ControllerEmu::EmulatedController +class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::HIDWiimote { public: static constexpr u16 IR_LOW_X = 0x7F; @@ -124,12 +124,12 @@ public: ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(DrawsomeTabletGroup group) const; ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const; - void Update(); - void StepDynamics(); + void Update() override; + void EventLinked() override; + void EventUnlinked() override; + void InterruptDataOutput(const u8* data, u32 size) override; + bool IsButtonPressed() override; - void InterruptChannel(u16 channel_id, const void* data, u32 size); - void ControlChannel(u16 channel_id, const void* data, u32 size); - bool CheckForButtonPress(); void Reset(); void DoState(PointerWrap& p); @@ -145,6 +145,7 @@ private: // This is the region exposed over bluetooth: static constexpr int EEPROM_FREE_SIZE = 0x1700; + void StepDynamics(); void UpdateButtonsStatus(); // Returns simulated accelerometer data in m/s^2. @@ -167,8 +168,6 @@ private: Common::Vec3 GetTotalAngularVelocity() const; Common::Matrix44 GetTotalTransformation() const; - void HIDOutputReport(const void* data, u32 size); - void HandleReportRumble(const WiimoteCommon::OutputReportRumble&); void HandleReportLeds(const WiimoteCommon::OutputReportLeds&); void HandleReportMode(const WiimoteCommon::OutputReportMode&); @@ -191,7 +190,6 @@ private: void SetRumble(bool on); - void CallbackInterruptChannel(const u8* data, u32 size); void SendAck(WiimoteCommon::OutputReportID rpt_id, WiimoteCommon::ErrorCode err); bool IsSideways() const; @@ -276,7 +274,6 @@ private: // Wiimote index, 0-3 const u8 m_index; - u16 m_reporting_channel; WiimoteCommon::InputReportID m_reporting_mode; bool m_reporting_continuous; diff --git a/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp b/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp index aa094aa92b..7fec2bd9a2 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp +++ b/Source/Core/Core/HW/WiimoteReal/IOLinux.cpp @@ -15,6 +15,9 @@ namespace WiimoteReal { +constexpr u16 L2CAP_PSM_HID_CNTL = 0x0011; +constexpr u16 L2CAP_PSM_HID_INTR = 0x0013; + WiimoteScannerLinux::WiimoteScannerLinux() : m_device_id(-1), m_device_sock(-1) { // Get the id of the first Bluetooth device. @@ -139,8 +142,8 @@ bool WiimoteLinux::ConnectInternal() addr.l2_bdaddr = m_bdaddr; addr.l2_cid = 0; - // Output channel - addr.l2_psm = htobs(WC_OUTPUT); + // Control channel + addr.l2_psm = htobs(L2CAP_PSM_HID_CNTL); if ((m_cmd_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP))) { int retry = 0; @@ -149,7 +152,7 @@ bool WiimoteLinux::ConnectInternal() // If opening channel fails sleep and try again if (retry == 3) { - WARN_LOG(WIIMOTE, "Unable to connect output channel to Wiimote: %s", strerror(errno)); + WARN_LOG(WIIMOTE, "Unable to connect control channel of Wiimote: %s", strerror(errno)); close(m_cmd_sock); m_cmd_sock = -1; return false; @@ -160,12 +163,12 @@ bool WiimoteLinux::ConnectInternal() } else { - WARN_LOG(WIIMOTE, "Unable to open output socket to Wiimote: %s", strerror(errno)); + WARN_LOG(WIIMOTE, "Unable to open control socket to Wiimote: %s", strerror(errno)); return false; } - // Input channel - addr.l2_psm = htobs(WC_INPUT); + // Interrupt channel + addr.l2_psm = htobs(L2CAP_PSM_HID_INTR); if ((m_int_sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP))) { int retry = 0; @@ -174,7 +177,7 @@ bool WiimoteLinux::ConnectInternal() // If opening channel fails sleep and try again if (retry == 3) { - WARN_LOG(WIIMOTE, "Unable to connect input channel to Wiimote: %s", strerror(errno)); + WARN_LOG(WIIMOTE, "Unable to connect interrupt channel of Wiimote: %s", strerror(errno)); close(m_int_sock); close(m_cmd_sock); m_int_sock = m_cmd_sock = -1; @@ -186,7 +189,7 @@ bool WiimoteLinux::ConnectInternal() } else { - WARN_LOG(WIIMOTE, "Unable to open input socket from Wiimote: %s", strerror(errno)); + WARN_LOG(WIIMOTE, "Unable to open interrupt socket to Wiimote: %s", strerror(errno)); close(m_cmd_sock); m_int_sock = m_cmd_sock = -1; return false; diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp index 031ad1690a..39aece1e2e 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp @@ -49,6 +49,7 @@ static std::mutex s_known_ids_mutex; std::recursive_mutex g_wiimotes_mutex; // Real wii remotes assigned to a particular slot. +// Assignments must be done from the CPU thread with the above mutex held. std::unique_ptr g_wiimotes[MAX_BBMOTES]; struct WiimotePoolEntry @@ -129,6 +130,8 @@ void AddWiimoteToPool(std::unique_ptr wiimote) return; } + wiimote->EmuStop(); + std::lock_guard lk(g_wiimotes_mutex); s_wiimote_pool.emplace_back(WiimotePoolEntry{std::move(wiimote)}); } @@ -221,54 +224,32 @@ void Wiimote::ClearReadQueue() } } -void Wiimote::ControlChannel(const u16 channel, const void* const data, const u32 size) +void Wiimote::EventLinked() { - // Check for custom communication - if (channel == ::Wiimote::DOLPHIN_DISCONNET_CONTROL_CHANNEL) - { - if (m_really_disconnect) - { - DisconnectInternal(); - } - else - { - EmuStop(); - } - } - else - { - InterruptChannel(channel, data, size); - const auto& hidp = *static_cast(data); - if (hidp.type == HID_TYPE_SET_REPORT) - { - u8 handshake = HID_HANDSHAKE_SUCCESS; - Core::Callback_WiimoteInterruptChannel(m_index, channel, &handshake, sizeof(handshake)); - } - } + m_is_linked = true; + + ClearReadQueue(); + ResetDataReporting(); + EnablePowerAssertionInternal(); } -void Wiimote::InterruptChannel(const u16 channel, const void* const data, const u32 size) +void Wiimote::EventUnlinked() { - // first interrupt/control channel sent - if (channel != m_channel) - { - m_channel = channel; + if (m_really_disconnect) + DisconnectInternal(); + else + EmuStop(); +} - ClearReadQueue(); - - EmuStart(); - } - - auto const report_data = static_cast(data); - Report rpt(report_data, report_data + size); +void Wiimote::InterruptDataOutput(const u8* data, const u32 size) +{ + Report rpt(size + REPORT_HID_HEADER_SIZE); + std::copy_n(data, size, rpt.data() + REPORT_HID_HEADER_SIZE); // Convert output DATA packets to SET_REPORT packets. // Nintendo Wiimotes work without this translation, but 3rd // party ones don't. - if (rpt[0] == 0xa2) - { - rpt[0] = WR_SET_REPORT | BT_OUTPUT; - } + rpt[0] = WR_SET_REPORT | BT_OUTPUT; // Disallow games from turning off all of the LEDs. // It makes Wiimote connection status confusing. @@ -299,7 +280,11 @@ void Wiimote::Read() Report rpt(MAX_PAYLOAD); auto const result = IORead(rpt.data()); - if (result > 0 && m_channel > 0) + // Drop the report if not connected. + if (!m_is_linked) + return; + + if (result > 0) { if (SConfig::GetInstance().iBBDumpPort > 0 && m_index == WIIMOTE_BALANCE_BOARD) { @@ -456,25 +441,18 @@ Report& Wiimote::ProcessReadQueue() void Wiimote::Update() { - if (!IsConnected()) - { - HandleWiimoteDisconnect(m_index); - return; - } - // Pop through the queued reports const Report& rpt = ProcessReadQueue(); // Send the report - if (!rpt.empty() && m_channel > 0) - { - Core::Callback_WiimoteInterruptChannel(m_index, m_channel, rpt.data(), (u32)rpt.size()); - } + if (!rpt.empty()) + InterruptCallback(rpt.front(), rpt.data() + REPORT_HID_HEADER_SIZE, + u32(rpt.size() - REPORT_HID_HEADER_SIZE)); } -bool Wiimote::CheckForButtonPress() +bool Wiimote::IsButtonPressed() { - Report& rpt = ProcessReadQueue(); + Report& rpt = m_last_input_report; if (rpt.size() >= 4) { const auto mode = InputReportID(rpt[1]); @@ -512,23 +490,16 @@ bool Wiimote::PrepareOnThread() (Common::SleepCurrentThread(200), IOWrite(req_status_report, sizeof(req_status_report))); } -void Wiimote::EmuStart() -{ - ResetDataReporting(); - EnablePowerAssertionInternal(); -} - void Wiimote::EmuStop() { - m_channel = 0; + m_is_linked = false; + ResetDataReporting(); DisablePowerAssertionInternal(); } void Wiimote::EmuResume() { - m_last_input_report.clear(); - EnablePowerAssertionInternal(); } @@ -811,11 +782,6 @@ int Wiimote::GetIndex() const return m_index; } -void Wiimote::SetChannel(u16 channel) -{ - m_channel = channel; -} - void LoadSettings() { std::string ini_filename = File::GetUserPath(D_CONFIG_IDX) + WIIMOTE_INI_NAME ".ini"; @@ -933,8 +899,10 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr& wm, unsigned int led_report.leds = u8(1 << (i % WIIMOTE_BALANCE_BOARD)); wm->QueueReport(led_report); - g_wiimotes[i] = std::move(wm); - Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); }); + Core::RunAsCPUThread([i, &wm] { + g_wiimotes[i] = std::move(wm); + WiimoteCommon::UpdateSource(i); + }); NOTICE_LOG(WIIMOTE, "Connected real wiimote to slot %i.", i + 1); @@ -951,7 +919,10 @@ static void TryToConnectBalanceBoard(std::unique_ptr wm) static void HandleWiimoteDisconnect(int index) { - g_wiimotes[index] = nullptr; + Core::RunAsCPUThread([index] { + g_wiimotes[index] = nullptr; + WiimoteCommon::UpdateSource(index); + }); } // This is called from the GUI thread @@ -961,52 +932,6 @@ void Refresh() s_wiimote_scanner.SetScanMode(WiimoteScanMode::SCAN_ONCE); } -void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size) -{ - std::lock_guard lk(g_wiimotes_mutex); - if (g_wiimotes[wiimote_number]) - g_wiimotes[wiimote_number]->InterruptChannel(channel_id, data, size); -} - -void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size) -{ - std::lock_guard lk(g_wiimotes_mutex); - if (g_wiimotes[wiimote_number]) - g_wiimotes[wiimote_number]->ControlChannel(channel_id, data, size); -} - -// Read the Wiimote once -void Update(int wiimote_number) -{ - // Try to get a lock and return without doing anything if we fail - // This avoids blocking the CPU thread - if (!g_wiimotes_mutex.try_lock()) - return; - - if (g_wiimotes[wiimote_number]) - g_wiimotes[wiimote_number]->Update(); - - g_wiimotes_mutex.unlock(); - - // Wiimote::Update() may remove the Wiimote if it was disconnected. - if (!g_wiimotes[wiimote_number]) - ::Wiimote::Connect(wiimote_number, false); -} - -bool CheckForButtonPress(int wiimote_number) -{ - if (!g_wiimotes_mutex.try_lock()) - return false; - - bool button_pressed = false; - - if (g_wiimotes[wiimote_number]) - button_pressed = g_wiimotes[wiimote_number]->CheckForButtonPress(); - - g_wiimotes_mutex.unlock(); - return button_pressed; -} - bool IsValidDeviceName(const std::string& name) { return "Nintendo RVL-CNT-01" == name || "Nintendo RVL-CNT-01-TR" == name || @@ -1029,9 +954,10 @@ void HandleWiimoteSourceChange(unsigned int index) { std::lock_guard wm_lk(g_wiimotes_mutex); - if (auto removed_wiimote = std::move(g_wiimotes[index])) - AddWiimoteToPool(std::move(removed_wiimote)); - + Core::RunAsCPUThread([index] { + if (auto removed_wiimote = std::move(g_wiimotes[index])) + AddWiimoteToPool(std::move(removed_wiimote)); + }); ProcessWiimotePool(); } diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index 14aa2b9c4d..a0695ffae7 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -26,14 +26,12 @@ namespace WiimoteReal { using WiimoteCommon::MAX_PAYLOAD; +// Includes HID "type" header byte. using Report = std::vector; +constexpr int REPORT_HID_HEADER_SIZE = 1; constexpr u32 WIIMOTE_DEFAULT_TIMEOUT = 1000; -// Communication channels -constexpr u8 WC_OUTPUT = 0x11; -constexpr u8 WC_INPUT = 0x13; - // The 4 most significant bits of the first byte of an outgoing command must be // 0x50 if sending on the command channel and 0xA0 if sending on the interrupt // channel. On Mac and Linux we use interrupt channel; on Windows, command. @@ -46,7 +44,7 @@ constexpr u8 WR_SET_REPORT = 0xA0; constexpr u8 BT_INPUT = 0x01; constexpr u8 BT_OUTPUT = 0x02; -class Wiimote +class Wiimote : public WiimoteCommon::HIDWiimote { public: Wiimote(const Wiimote&) = delete; @@ -60,45 +58,25 @@ public: virtual std::string GetId() const = 0; - void ControlChannel(const u16 channel, const void* const data, const u32 size); - void InterruptChannel(const u16 channel, const void* const data, const u32 size); - void Update(); - bool CheckForButtonPress(); - bool GetNextReport(Report* report); - Report& ProcessReadQueue(); - - void Read(); - bool Write(); bool IsBalanceBoard(); - void StartThread(); - void StopThread(); + void InterruptDataOutput(const u8* data, const u32 size) override; + void Update() override; + void EventLinked() override; + void EventUnlinked() override; + bool IsButtonPressed() override; - // "handshake" / stop packets - void EmuStart(); void EmuStop(); + void EmuResume(); void EmuPause(); - virtual void EnablePowerAssertionInternal() {} - virtual void DisablePowerAssertionInternal() {} - // connecting and disconnecting from physical devices - // (using address inserted by FindWiimotes) - // these are called from the Wiimote's thread. - virtual bool ConnectInternal() = 0; - virtual void DisconnectInternal() = 0; - bool Connect(int index); - - // TODO: change to something like IsRelevant - virtual bool IsConnected() const = 0; - void Prepare(); - bool PrepareOnThread(); - void ResetDataReporting(); + virtual bool IsConnected() const = 0; void QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size); @@ -110,14 +88,11 @@ public: int GetIndex() const; - void SetChannel(u16 channel); - protected: Wiimote(); int m_index = 0; Report m_last_input_report; - u16 m_channel = 0; // If true, the Wiimote will be really disconnected when it is disconnected by Dolphin. // In any other case, data reporting is not paused to allow reconnecting on any button press. @@ -125,6 +100,23 @@ protected: bool m_really_disconnect = false; private: + void Read(); + bool Write(); + + void StartThread(); + void StopThread(); + + bool PrepareOnThread(); + + void ResetDataReporting(); + + virtual void EnablePowerAssertionInternal() {} + virtual void DisablePowerAssertionInternal() {} + + virtual bool ConnectInternal() = 0; + virtual void DisconnectInternal() = 0; + + Report& ProcessReadQueue(); void ClearReadQueue(); void WriteReport(Report rpt); @@ -134,6 +126,8 @@ private: void ThreadFunc(); + bool m_is_linked = false; + // We track the speaker state to convert unnecessary speaker data into rumble reports. bool m_speaker_enable = false; bool m_speaker_mute = false; @@ -199,11 +193,6 @@ extern std::unique_ptr g_wiimotes[MAX_BBMOTES]; void AddWiimoteToPool(std::unique_ptr); -void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size); -void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size); -void Update(int wiimote_number); -bool CheckForButtonPress(int wiimote_number); - bool IsValidDeviceName(const std::string& name); bool IsBalanceBoardName(const std::string& name); bool IsNewWiimote(const std::string& identifier); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp index f86f5e5d18..e3b7cc4340 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" @@ -45,30 +43,25 @@ BluetoothEmu::BluetoothEmu(Kernel& ios, const std::string& device_name) BackUpBTInfoSection(&sysconf); ConfPads bt_dinf{}; - bdaddr_t tmp_bd; - u8 i = 0; - while (i < MAX_BBMOTES) - { - // Previous records can be safely overwritten, since they are backed up - tmp_bd[5] = bt_dinf.active[i].bdaddr[0] = bt_dinf.registered[i].bdaddr[0] = i; - tmp_bd[4] = bt_dinf.active[i].bdaddr[1] = bt_dinf.registered[i].bdaddr[1] = 0; - tmp_bd[3] = bt_dinf.active[i].bdaddr[2] = bt_dinf.registered[i].bdaddr[2] = 0x79; - tmp_bd[2] = bt_dinf.active[i].bdaddr[3] = bt_dinf.registered[i].bdaddr[3] = 0x19; - tmp_bd[1] = bt_dinf.active[i].bdaddr[4] = bt_dinf.registered[i].bdaddr[4] = 2; - tmp_bd[0] = bt_dinf.active[i].bdaddr[5] = bt_dinf.registered[i].bdaddr[5] = 0x11; - const char* wm_name; - if (i == WIIMOTE_BALANCE_BOARD) - wm_name = "Nintendo RVL-WBC-01"; - else - wm_name = "Nintendo RVL-CNT-01"; + for (u8 i = 0; i != MAX_BBMOTES; ++i) + { + // Note: BluetoothEmu::GetConnectionHandle and WiimoteDevice::GetNumber rely on final byte. + const bdaddr_t tmp_bd = {0x11, 0x02, 0x19, 0x79, 0, i}; + + // Previous records can be safely overwritten, since they are backed up + std::copy(tmp_bd.begin(), tmp_bd.end(), std::rbegin(bt_dinf.active[i].bdaddr)); + std::copy(tmp_bd.begin(), tmp_bd.end(), std::rbegin(bt_dinf.registered[i].bdaddr)); + + const auto& wm_name = + (i == WIIMOTE_BALANCE_BOARD) ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01"; memcpy(bt_dinf.registered[i].name, wm_name, 20); memcpy(bt_dinf.active[i].name, wm_name, 20); DEBUG_LOG(IOS_WIIMOTE, "Wii Remote %d BT ID %x,%x,%x,%x,%x,%x", i, tmp_bd[0], tmp_bd[1], tmp_bd[2], tmp_bd[3], tmp_bd[4], tmp_bd[5]); - m_wiimotes.emplace_back(this, i, tmp_bd, WiimoteCommon::GetSource(i) != WiimoteSource::None); - i++; + + m_wiimotes.emplace_back(std::make_unique(this, i, tmp_bd)); } bt_dinf.num_registered = MAX_BBMOTES; @@ -82,10 +75,7 @@ BluetoothEmu::BluetoothEmu(Kernel& ios, const std::string& device_name) PanicAlertT("Failed to write BT.DINF to SYSCONF"); } -BluetoothEmu::~BluetoothEmu() -{ - m_wiimotes.clear(); -} +BluetoothEmu::~BluetoothEmu() = default; template static void DoStateForMessage(Kernel& ios, PointerWrap& p, std::unique_ptr& message) @@ -122,12 +112,22 @@ void BluetoothEmu::DoState(PointerWrap& p) m_acl_pool.DoState(p); for (unsigned int i = 0; i < MAX_BBMOTES; i++) - m_wiimotes[i].DoState(p); + m_wiimotes[i]->DoState(p); } -bool BluetoothEmu::RemoteDisconnect(u16 connection_handle) +bool BluetoothEmu::RemoteConnect(WiimoteDevice& wiimote) { - return SendEventDisconnect(connection_handle, 0x13); + // If page scan is disabled the controller will not see this connection request. + if (!(m_scan_enable & HCI_PAGE_SCAN_ENABLE)) + return false; + + SendEventRequestConnection(wiimote); + return true; +} + +bool BluetoothEmu::RemoteDisconnect(const bdaddr_t& address) +{ + return SendEventDisconnect(GetConnectionHandle(address), 0x13); } IPCCommandResult BluetoothEmu::Close(u32 fd) @@ -226,13 +226,15 @@ void BluetoothEmu::SendToDevice(u16 connection_handle, u8* data, u32 size) void BluetoothEmu::IncDataPacket(u16 connection_handle) { - m_packet_count[connection_handle & 0xff]++; + m_packet_count[GetWiimoteNumberFromConnectionHandle(connection_handle)]++; } // Here we send ACL packets to CPU. They will consist of header + data. // The header is for example 07 00 41 00 which means size 0x0007 and channel 0x0041. -void BluetoothEmu::SendACLPacket(u16 connection_handle, const u8* data, u32 size) +void BluetoothEmu::SendACLPacket(const bdaddr_t& source, const u8* data, u32 size) { + const u16 connection_handle = GetConnectionHandle(source); + DEBUG_LOG(IOS_WIIMOTE, "ACL packet from %x ready to send to stack...", connection_handle); if (m_acl_endpoint && !m_hci_endpoint && m_event_queue.empty()) @@ -331,30 +333,8 @@ void BluetoothEmu::Update() m_acl_endpoint.reset(); } - // We wait for ScanEnable to be sent from the Bluetooth stack through HCI_CMD_WRITE_SCAN_ENABLE - // before we initiate the connection. - // - // FiRES: TODO find a better way to do this - - // Create ACL connection - if (m_hci_endpoint && (m_scan_enable & HCI_PAGE_SCAN_ENABLE)) - { - for (const auto& wiimote : m_wiimotes) - { - if (wiimote.EventPagingChanged(m_scan_enable)) - SendEventRequestConnection(wiimote); - } - } - - // Link channels when connected - if (m_acl_endpoint) - { - for (auto& wiimote : m_wiimotes) - { - if (wiimote.LinkChannel()) - break; - } - } + for (auto& wiimote : m_wiimotes) + wiimote->Update(); const u64 interval = SystemTimers::GetTicksPerSecond() / Wiimote::UPDATE_FREQ; const u64 now = CoreTiming::GetTicks(); @@ -362,8 +342,8 @@ void BluetoothEmu::Update() if (now - m_last_ticks > interval) { g_controller_interface.UpdateInput(); - for (unsigned int i = 0; i < m_wiimotes.size(); i++) - Wiimote::Update(i, m_wiimotes[i].IsConnected()); + for (auto& wiimote : m_wiimotes) + wiimote->UpdateInput(); m_last_ticks = now; } @@ -414,7 +394,7 @@ void BluetoothEmu::ACLPool::WriteToEndpoint(const USB::V0BulkMessage& endpoint) m_ios.EnqueueIPCReply(endpoint.ios_request, sizeof(hci_acldata_hdr_t) + size); } -bool BluetoothEmu::SendEventInquiryComplete() +bool BluetoothEmu::SendEventInquiryComplete(u8 num_responses) { SQueuedEvent event(sizeof(SHCIEventInquiryComplete), 0); @@ -422,6 +402,7 @@ bool BluetoothEmu::SendEventInquiryComplete() inquiry_complete->EventType = HCI_EVENT_INQUIRY_COMPL; inquiry_complete->PayloadLength = sizeof(SHCIEventInquiryComplete) - 2; inquiry_complete->EventStatus = 0x00; + inquiry_complete->num_responses = num_responses; AddEventToQueue(event); @@ -432,77 +413,73 @@ bool BluetoothEmu::SendEventInquiryComplete() bool BluetoothEmu::SendEventInquiryResponse() { - if (m_wiimotes.empty()) - return false; + // We only respond with the first discoverable remote. + // The Wii instructs users to press 1+2 in the desired play order. + // Responding with all remotes at once can place them in undesirable slots. + // Additional scans will connect each remote in the proper order. + constexpr u8 num_responses = 1; - DEBUG_ASSERT(sizeof(SHCIEventInquiryResult) - 2 + - (m_wiimotes.size() * sizeof(hci_inquiry_response)) < - 256); + static_assert( + sizeof(SHCIEventInquiryResult) - 2 + (num_responses * sizeof(hci_inquiry_response)) < 256); - SQueuedEvent event(static_cast(sizeof(SHCIEventInquiryResult) + - m_wiimotes.size() * sizeof(hci_inquiry_response)), - 0); - - SHCIEventInquiryResult* inquiry_result = (SHCIEventInquiryResult*)event.buffer; - - inquiry_result->EventType = HCI_EVENT_INQUIRY_RESULT; - inquiry_result->PayloadLength = - (u8)(sizeof(SHCIEventInquiryResult) - 2 + (m_wiimotes.size() * sizeof(hci_inquiry_response))); - inquiry_result->num_responses = (u8)m_wiimotes.size(); - - for (size_t i = 0; i < m_wiimotes.size(); i++) + const auto iter = std::find_if(m_wiimotes.begin(), m_wiimotes.end(), + std::mem_fn(&WiimoteDevice::IsInquiryScanEnabled)); + if (iter == m_wiimotes.end()) { - if (m_wiimotes[i].IsConnected()) - continue; - - u8* buffer = event.buffer + sizeof(SHCIEventInquiryResult) + i * sizeof(hci_inquiry_response); - hci_inquiry_response* response = (hci_inquiry_response*)buffer; - - response->bdaddr = m_wiimotes[i].GetBD(); - response->uclass[0] = m_wiimotes[i].GetClass()[0]; - response->uclass[1] = m_wiimotes[i].GetClass()[1]; - response->uclass[2] = m_wiimotes[i].GetClass()[2]; - - response->page_scan_rep_mode = 1; - response->page_scan_period_mode = 0; - response->page_scan_mode = 0; - response->clock_offset = 0x3818; - - DEBUG_LOG(IOS_WIIMOTE, "Event: Send Fake Inquiry of one controller"); - DEBUG_LOG(IOS_WIIMOTE, " bd: %02x:%02x:%02x:%02x:%02x:%02x", response->bdaddr[0], - response->bdaddr[1], response->bdaddr[2], response->bdaddr[3], response->bdaddr[4], - response->bdaddr[5]); + // No remotes are discoverable. + SendEventInquiryComplete(0); + return false; } - AddEventToQueue(event); + const auto& wiimote = *iter; + SQueuedEvent event( + u32(sizeof(SHCIEventInquiryResult) + num_responses * sizeof(hci_inquiry_response)), 0); + + const auto inquiry_result = reinterpret_cast(event.buffer); + inquiry_result->EventType = HCI_EVENT_INQUIRY_RESULT; + inquiry_result->num_responses = num_responses; + + u8* const buffer = event.buffer + sizeof(SHCIEventInquiryResult); + const auto response = reinterpret_cast(buffer); + + response->bdaddr = wiimote->GetBD(); + response->page_scan_rep_mode = 1; + response->page_scan_period_mode = 0; + response->page_scan_mode = 0; + std::copy_n(wiimote->GetClass().begin(), HCI_CLASS_SIZE, response->uclass); + response->clock_offset = 0x3818; + + DEBUG_LOG(IOS_WIIMOTE, "Event: Send Fake Inquiry of one controller"); + DEBUG_LOG(IOS_WIIMOTE, " bd: %02x:%02x:%02x:%02x:%02x:%02x", response->bdaddr[0], + response->bdaddr[1], response->bdaddr[2], response->bdaddr[3], response->bdaddr[4], + response->bdaddr[5]); + + inquiry_result->PayloadLength = + u8(sizeof(SHCIEventInquiryResult) - 2 + + (inquiry_result->num_responses * sizeof(hci_inquiry_response))); + + AddEventToQueue(event); + SendEventInquiryComplete(num_responses); return true; } -bool BluetoothEmu::SendEventConnectionComplete(const bdaddr_t& bd) +bool BluetoothEmu::SendEventConnectionComplete(const bdaddr_t& bd, u8 status) { - WiimoteDevice* wiimote = AccessWiimote(bd); - if (wiimote == nullptr) - return false; - SQueuedEvent event(sizeof(SHCIEventConnectionComplete), 0); SHCIEventConnectionComplete* connection_complete = (SHCIEventConnectionComplete*)event.buffer; connection_complete->EventType = HCI_EVENT_CON_COMPL; connection_complete->PayloadLength = sizeof(SHCIEventConnectionComplete) - 2; - connection_complete->EventStatus = 0x00; - connection_complete->Connection_Handle = wiimote->GetConnectionHandle(); + connection_complete->EventStatus = status; + connection_complete->Connection_Handle = GetConnectionHandle(bd); connection_complete->bdaddr = bd; connection_complete->LinkType = HCI_LINK_ACL; connection_complete->EncryptionEnabled = HCI_ENCRYPTION_MODE_NONE; AddEventToQueue(event); - WiimoteDevice* connection_wiimote = AccessWiimote(connection_complete->Connection_Handle); - if (connection_wiimote) - connection_wiimote->EventConnectionAccepted(); - static constexpr const char* link_type[] = { "HCI_LINK_SCO 0x00 - Voice", "HCI_LINK_ACL 0x01 - Data", @@ -521,7 +498,6 @@ bool BluetoothEmu::SendEventConnectionComplete(const bdaddr_t& bd) return true; } -// This is called from Update() after ScanEnable has been enabled. bool BluetoothEmu::SendEventRequestConnection(const WiimoteDevice& wiimote) { SQueuedEvent event(sizeof(SHCIEventRequestConnection), 0); @@ -646,14 +622,7 @@ bool BluetoothEmu::SendEventReadRemoteFeatures(u16 connection_handle) read_remote_features->PayloadLength = sizeof(SHCIEventReadRemoteFeatures) - 2; read_remote_features->EventStatus = 0x00; read_remote_features->ConnectionHandle = connection_handle; - read_remote_features->features[0] = wiimote->GetFeatures()[0]; - read_remote_features->features[1] = wiimote->GetFeatures()[1]; - read_remote_features->features[2] = wiimote->GetFeatures()[2]; - read_remote_features->features[3] = wiimote->GetFeatures()[3]; - read_remote_features->features[4] = wiimote->GetFeatures()[4]; - read_remote_features->features[5] = wiimote->GetFeatures()[5]; - read_remote_features->features[6] = wiimote->GetFeatures()[6]; - read_remote_features->features[7] = wiimote->GetFeatures()[7]; + std::copy_n(wiimote->GetFeatures().begin(), HCI_FEATURES_SIZE, read_remote_features->features); DEBUG_LOG(IOS_WIIMOTE, "Event: SendEventReadRemoteFeatures"); DEBUG_LOG(IOS_WIIMOTE, " Connection_Handle: 0x%04x", read_remote_features->ConnectionHandle); @@ -788,7 +757,7 @@ bool BluetoothEmu::SendEventNumberOfCompletedPackets() event_hdr->length += sizeof(hci_num_compl_pkts_info); hci_event->num_con_handles++; info->compl_pkts = m_packet_count[i]; - info->con_handle = m_wiimotes[i].GetConnectionHandle(); + info->con_handle = GetConnectionHandle(m_wiimotes[i]->GetBD()); DEBUG_LOG(IOS_WIIMOTE, " Connection_Handle: 0x%04x", info->con_handle); DEBUG_LOG(IOS_WIIMOTE, " Number_Of_Completed_Packets: %i", info->compl_pkts); @@ -855,8 +824,8 @@ bool BluetoothEmu::SendEventLinkKeyNotification(const u8 num_to_send) { hci_link_key_rep_cp* link_key_info = (hci_link_key_rep_cp*)((u8*)&event_link_key->bdaddr + sizeof(hci_link_key_rep_cp) * i); - link_key_info->bdaddr = m_wiimotes[i].GetBD(); - memcpy(link_key_info->key, m_wiimotes[i].GetLinkKey(), HCI_KEY_SIZE); + link_key_info->bdaddr = m_wiimotes[i]->GetBD(); + std::copy_n(m_wiimotes[i]->GetLinkKey().begin(), HCI_KEY_SIZE, link_key_info->key); DEBUG_LOG(IOS_WIIMOTE, " bd: %02x:%02x:%02x:%02x:%02x:%02x", link_key_info->bdaddr[0], link_key_info->bdaddr[1], link_key_info->bdaddr[2], link_key_info->bdaddr[3], @@ -1147,7 +1116,6 @@ void BluetoothEmu::CommandInquiry(const u8* input) SendEventCommandStatus(HCI_CMD_INQUIRY); SendEventInquiryResponse(); - SendEventInquiryComplete(); } void BluetoothEmu::CommandInquiryCancel(const u8* input) @@ -1170,8 +1138,6 @@ void BluetoothEmu::CommandCreateCon(const u8* input) DEBUG_LOG(IOS_WIIMOTE, " bd: %02x:%02x:%02x:%02x:%02x:%02x", create_connection.bdaddr[0], create_connection.bdaddr[1], create_connection.bdaddr[2], create_connection.bdaddr[3], create_connection.bdaddr[4], create_connection.bdaddr[5]); - INFO_LOG(IOS_WIIMOTE, "Command: HCI_CMD_ACCEPT_CON"); - DEBUG_LOG(IOS_WIIMOTE, " pkt_type: %i", create_connection.pkt_type); DEBUG_LOG(IOS_WIIMOTE, " page_scan_rep_mode: %i", create_connection.page_scan_rep_mode); DEBUG_LOG(IOS_WIIMOTE, " page_scan_mode: %i", create_connection.page_scan_mode); @@ -1179,7 +1145,12 @@ void BluetoothEmu::CommandCreateCon(const u8* input) DEBUG_LOG(IOS_WIIMOTE, " accept_role_switch: %i", create_connection.accept_role_switch); SendEventCommandStatus(HCI_CMD_CREATE_CON); - SendEventConnectionComplete(create_connection.bdaddr); + + WiimoteDevice* wiimote = AccessWiimote(create_connection.bdaddr); + const bool successful = wiimote && wiimote->EventConnectionRequest(); + + // Status 0x08 (Connection Timeout) if WiimoteDevice does not accept the connection. + SendEventConnectionComplete(create_connection.bdaddr, successful ? 0x00 : 0x08); } void BluetoothEmu::CommandDisconnect(const u8* input) @@ -1191,14 +1162,12 @@ void BluetoothEmu::CommandDisconnect(const u8* input) DEBUG_LOG(IOS_WIIMOTE, " ConnectionHandle: 0x%04x", disconnect.con_handle); DEBUG_LOG(IOS_WIIMOTE, " Reason: 0x%02x", disconnect.reason); - DisplayDisconnectMessage((disconnect.con_handle & 0xFF) + 1, disconnect.reason); - SendEventCommandStatus(HCI_CMD_DISCONNECT); SendEventDisconnect(disconnect.con_handle, disconnect.reason); WiimoteDevice* wiimote = AccessWiimote(disconnect.con_handle); if (wiimote) - wiimote->EventDisconnect(); + wiimote->EventDisconnect(disconnect.reason); } void BluetoothEmu::CommandAcceptCon(const u8* input) @@ -1219,13 +1188,23 @@ void BluetoothEmu::CommandAcceptCon(const u8* input) SendEventCommandStatus(HCI_CMD_ACCEPT_CON); - // this connection wants to be the master - if (accept_connection.role == 0) - { - SendEventRoleChange(accept_connection.bdaddr, true); - } + WiimoteDevice* wiimote = AccessWiimote(accept_connection.bdaddr); + const bool successful = wiimote && wiimote->EventConnectionAccept(); - SendEventConnectionComplete(accept_connection.bdaddr); + if (successful) + { + // This connection wants to be the master. + // The controller performs a master-slave switch and notifies the host. + if (accept_connection.role == 0) + SendEventRoleChange(accept_connection.bdaddr, true); + + SendEventConnectionComplete(accept_connection.bdaddr, 0x00); + } + else + { + // Status 0x08 (Connection Timeout) if WiimoteDevice no longer wants this connection. + SendEventConnectionComplete(accept_connection.bdaddr, 0x08); + } } void BluetoothEmu::CommandLinkKeyRep(const u8* input) @@ -1377,8 +1356,9 @@ void BluetoothEmu::CommandReset(const u8* input) reply.status = 0x00; INFO_LOG(IOS_WIIMOTE, "Command: HCI_CMD_RESET"); - SendEventCommandComplete(HCI_CMD_RESET, &reply, sizeof(hci_status_rp)); + + // TODO: We should actually reset connections and channels and everything here. } void BluetoothEmu::CommandSetEventFilter(const u8* input) @@ -1386,6 +1366,12 @@ void BluetoothEmu::CommandSetEventFilter(const u8* input) hci_set_event_filter_cp set_event_filter; std::memcpy(&set_event_filter, input, sizeof(set_event_filter)); + // It looks like software only ever sets a "new device inquiry response" filter. + // This is one we can safely ignore because of our fake inquiry implementation + // and documentation says controllers can opt to not implement this filter anyways. + + // TODO: There should be a warn log if an actual filter is being set. + hci_set_event_filter_rp reply; reply.status = 0x00; @@ -1421,13 +1407,9 @@ void BluetoothEmu::CommandReadStoredLinkKey(const u8* input) reply.max_num_keys = 255; if (read_stored_link_key.read_all == 1) - { reply.num_keys_read = static_cast(m_wiimotes.size()); - } else - { ERROR_LOG(IOS_WIIMOTE, "CommandReadStoredLinkKey isn't looking for all devices"); - } INFO_LOG(IOS_WIIMOTE, "Command: HCI_CMD_READ_STORED_LINK_KEY:"); DEBUG_LOG(IOS_WIIMOTE, "input:"); @@ -1486,8 +1468,6 @@ void BluetoothEmu::CommandWriteLocalName(const u8* input) SendEventCommandComplete(HCI_CMD_WRITE_LOCAL_NAME, &reply, sizeof(hci_write_local_name_rp)); } -// Here we normally receive the timeout interval. -// But not from homebrew games that use lwbt. Why not? void BluetoothEmu::CommandWritePageTimeOut(const u8* input) { hci_write_page_timeout_cp write_page_timeout; @@ -1502,7 +1482,6 @@ void BluetoothEmu::CommandWritePageTimeOut(const u8* input) SendEventCommandComplete(HCI_CMD_WRITE_PAGE_TIMEOUT, &reply, sizeof(hci_host_buffer_size_rp)); } -// This will enable ScanEnable so that Update() can start the Wii Remote. void BluetoothEmu::CommandWriteScanEnable(const u8* input) { hci_write_scan_enable_cp write_scan_enable; @@ -1601,6 +1580,8 @@ void BluetoothEmu::CommandWriteInquiryMode(const u8* input) hci_write_inquiry_mode_rp reply; reply.status = 0x00; + // TODO: Software seems to set an RSSI mode but our fake inquiries generate standard events. + static constexpr const char* inquiry_mode_tag[] = { "Standard Inquiry Result event format (default)", "Inquiry Result format with RSSI", @@ -1743,45 +1724,50 @@ void BluetoothEmu::CommandVendorSpecific_FC4C(const u8* input, u32 size) SendEventCommandComplete(0xFC4C, &reply, sizeof(hci_status_rp)); } -// -// -// --- helper -// -// WiimoteDevice* BluetoothEmu::AccessWiimoteByIndex(std::size_t index) { - const u16 connection_handle = static_cast(0x100 + index); - return AccessWiimote(connection_handle); + if (index < MAX_BBMOTES) + return m_wiimotes[index].get(); + + return nullptr; +} + +u16 BluetoothEmu::GetConnectionHandle(const bdaddr_t& address) +{ + // Handles are normally generated per connection but HLE allows fixed values for each remote. + return 0x100 + address.back(); +} + +u32 BluetoothEmu::GetWiimoteNumberFromConnectionHandle(u16 connection_handle) +{ + // Fixed handle values are generated in GetConnectionHandle. + return connection_handle & 0xff; } WiimoteDevice* BluetoothEmu::AccessWiimote(const bdaddr_t& address) { - const auto iterator = - std::find_if(m_wiimotes.begin(), m_wiimotes.end(), - [&address](const WiimoteDevice& remote) { return remote.GetBD() == address; }); - return iterator != m_wiimotes.cend() ? &*iterator : nullptr; + // Fixed bluetooth addresses are generated in WiimoteDevice::WiimoteDevice. + const auto wiimote = AccessWiimoteByIndex(address.back()); + + if (wiimote && wiimote->GetBD() == address) + return wiimote; + + return nullptr; } WiimoteDevice* BluetoothEmu::AccessWiimote(u16 connection_handle) { - for (auto& wiimote : m_wiimotes) - { - if (wiimote.GetConnectionHandle() == connection_handle) - return &wiimote; - } + const auto wiimote = + AccessWiimoteByIndex(GetWiimoteNumberFromConnectionHandle(connection_handle)); + + if (wiimote) + return wiimote; ERROR_LOG(IOS_WIIMOTE, "Can't find Wiimote by connection handle %02x", connection_handle); PanicAlertT("Can't find Wii Remote by connection handle %02x", connection_handle); + return nullptr; } -void BluetoothEmu::DisplayDisconnectMessage(const int wiimote_number, const int reason) -{ - // TODO: If someone wants to be fancy we could also figure out what the values for pDiscon->reason - // mean - // and display things like "Wii Remote %i disconnected due to inactivity!" etc. - Core::DisplayMessage( - fmt::format("Wii Remote {} disconnected by emulated software", wiimote_number), 3000); -} } // namespace Device } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h index 0f7a5686b0..6a518a2180 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.h @@ -52,16 +52,18 @@ public: void Update() override; // Send ACL data back to Bluetooth stack - void SendACLPacket(u16 connection_handle, const u8* data, u32 size); + void SendACLPacket(const bdaddr_t& source, const u8* data, u32 size); - bool RemoteDisconnect(u16 connection_handle); + // Returns true if controller is configured to see the connection request. + bool RemoteConnect(WiimoteDevice&); + bool RemoteDisconnect(const bdaddr_t& address); WiimoteDevice* AccessWiimoteByIndex(std::size_t index); void DoState(PointerWrap& p) override; private: - std::vector m_wiimotes; + std::vector> m_wiimotes; bdaddr_t m_controller_bd{{0x11, 0x02, 0x19, 0x79, 0x00, 0xff}}; @@ -100,9 +102,13 @@ private: u32 m_packet_count[MAX_BBMOTES] = {}; u64 m_last_ticks = 0; + static u16 GetConnectionHandle(const bdaddr_t&); + WiimoteDevice* AccessWiimote(const bdaddr_t& address); WiimoteDevice* AccessWiimote(u16 connection_handle); + static u32 GetWiimoteNumberFromConnectionHandle(u16 connection_handle); + // Send ACL data to a device (wiimote) void IncDataPacket(u16 connection_handle); void SendToDevice(u16 connection_handle, u8* data, u32 size); @@ -112,10 +118,10 @@ private: bool SendEventCommandStatus(u16 opcode); void SendEventCommandComplete(u16 opcode, const void* data, u32 data_size); bool SendEventInquiryResponse(); - bool SendEventInquiryComplete(); + bool SendEventInquiryComplete(u8 num_responses); bool SendEventRemoteNameReq(const bdaddr_t& bd); bool SendEventRequestConnection(const WiimoteDevice& wiimote); - bool SendEventConnectionComplete(const bdaddr_t& bd); + bool SendEventConnectionComplete(const bdaddr_t& bd, u8 status); bool SendEventReadClockOffsetComplete(u16 connection_handle); bool SendEventConPacketTypeChange(u16 connection_handle, u16 packet_type); bool SendEventReadRemoteVerInfo(u16 connection_handle); @@ -176,8 +182,6 @@ private: void CommandVendorSpecific_FC4C(const u8* input, u32 size); void CommandVendorSpecific_FC4F(const u8* input, u32 size); - static void DisplayDisconnectMessage(int wiimote_number, int reason); - #pragma pack(push, 1) #define CONF_PAD_MAX_REGISTERED 10 diff --git a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp index 4cdc16a3b2..43169b5872 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" @@ -18,6 +20,8 @@ #include "Common/Swap.h" #include "Core/Core.h" #include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteCommon/WiimoteConstants.h" +#include "Core/HW/WiimoteCommon/WiimoteHid.h" #include "Core/Host.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/WiimoteHIDAttr.h" @@ -49,214 +53,318 @@ private: u8* m_buffer; }; -WiimoteDevice::WiimoteDevice(Device::BluetoothEmu* host, int number, bdaddr_t bd, bool ready) - : m_bd(bd), - m_name(number == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01"), - m_host(host) +constexpr int CONNECTION_MESSAGE_TIME = 3000; + +WiimoteDevice::WiimoteDevice(Device::BluetoothEmu* host, int number, bdaddr_t bd) + : m_host(host), m_bd(bd), + m_name(number == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01") + { INFO_LOG(IOS_WIIMOTE, "Wiimote: #%i Constructed", number); - m_connection_state = ready ? ConnectionState::Ready : ConnectionState::Inactive; - m_connection_handle = 0x100 + number; - memset(m_link_key, 0xA0 + number, HCI_KEY_SIZE); - - if (m_bd == BDADDR_ANY) - m_bd = {{0x11, 0x02, 0x19, 0x79, static_cast(number)}}; - - m_uclass[0] = 0x00; - m_uclass[1] = 0x04; - m_uclass[2] = 0x48; - - m_features[0] = 0xBC; - m_features[1] = 0x02; - m_features[2] = 0x04; - m_features[3] = 0x38; - m_features[4] = 0x08; - m_features[5] = 0x00; - m_features[6] = 0x00; - m_features[7] = 0x00; - + m_link_key.fill(0xa0 + number); + m_class = {0x00, 0x04, 0x48}; + m_features = {0xBC, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00}; m_lmp_version = 0x2; m_lmp_subversion = 0x229; + + const auto hid_source = WiimoteCommon::GetHIDWiimoteSource(GetNumber()); + + // UGLY: This prevents an OSD message in SetSource -> Activate. + if (hid_source) + SetBasebandState(BasebandState::RequestConnection); + + SetSource(hid_source); +} + +WiimoteDevice::~WiimoteDevice() = default; + +WiimoteDevice::SChannel::SChannel() : psm(L2CAP_PSM_ANY), remote_cid(L2CAP_NULL_CID) +{ +} + +bool WiimoteDevice::SChannel::IsAccepted() const +{ + return remote_cid != L2CAP_NULL_CID; +} + +bool WiimoteDevice::SChannel::IsRemoteConfigured() const +{ + return remote_mtu != 0; +} + +bool WiimoteDevice::SChannel::IsComplete() const +{ + return IsAccepted() && IsRemoteConfigured() && state == State::Complete; } void WiimoteDevice::DoState(PointerWrap& p) { - bool passthrough_bluetooth = false; - p.Do(passthrough_bluetooth); - if (passthrough_bluetooth && p.GetMode() == PointerWrap::MODE_READ) - { - Core::DisplayMessage("State needs Bluetooth passthrough to be enabled. Aborting load state.", - 3000); - p.SetMode(PointerWrap::MODE_VERIFY); - return; - } - - // this function is usually not called... see Device::BluetoothEmu::DoState - - p.Do(m_connection_state); - - p.Do(m_hid_control_channel.connected); - p.Do(m_hid_control_channel.connected_wait); - p.Do(m_hid_control_channel.config); - p.Do(m_hid_control_channel.config_wait); - p.Do(m_hid_interrupt_channel.connected); - p.Do(m_hid_interrupt_channel.connected_wait); - p.Do(m_hid_interrupt_channel.config); - p.Do(m_hid_interrupt_channel.config_wait); - + p.Do(m_baseband_state); + p.Do(m_hid_state); p.Do(m_bd); - p.Do(m_connection_handle); - p.Do(m_uclass); + p.Do(m_class); p.Do(m_features); p.Do(m_lmp_version); p.Do(m_lmp_subversion); p.Do(m_link_key); p.Do(m_name); - - p.Do(m_channel); + p.Do(m_channels); + p.Do(m_connection_request_counter); } -// -// -// -// -// --- Simple and ugly state machine -// -// -// -// -// - -bool WiimoteDevice::LinkChannel() +u32 WiimoteDevice::GetNumber() const { - if (m_connection_state != ConnectionState::Linking) + return GetBD().back(); +} + +bool WiimoteDevice::IsInquiryScanEnabled() const +{ + // Our Wii Remote is conveniently discoverable as long as it's enabled and doesn't have a + // baseband connection. + return !IsConnected() && IsSourceValid(); +} + +bool WiimoteDevice::IsPageScanEnabled() const +{ + // Our Wii Remote will accept a connection as long as it isn't currently connected. + return !IsConnected() && IsSourceValid(); +} + +void WiimoteDevice::SetBasebandState(BasebandState new_state) +{ + // Prevent button press from immediately causing connection attempts. + m_connection_request_counter = ::Wiimote::UPDATE_FREQ; + + const bool was_connected = IsConnected(); + + m_baseband_state = new_state; + + // Update wiimote connection checkboxes in UI. + Host_UpdateDisasmDialog(); + + if (!IsSourceValid()) + return; + + if (IsConnected() && !was_connected) + m_hid_source->EventLinked(); + else if (!IsConnected() && was_connected) + m_hid_source->EventUnlinked(); +} + +const WiimoteDevice::SChannel* WiimoteDevice::FindChannelWithPSM(u16 psm) const +{ + for (auto& [cid, channel] : m_channels) + { + if (channel.psm == psm) + return &channel; + } + + return nullptr; +} + +WiimoteDevice::SChannel* WiimoteDevice::FindChannelWithPSM(u16 psm) +{ + for (auto& [cid, channel] : m_channels) + { + if (channel.psm == psm) + return &channel; + } + + return nullptr; +} + +u16 WiimoteDevice::GenerateChannelID() const +{ + // "Identifiers from 0x0001 to 0x003F are reserved" + constexpr u16 starting_id = 0x40; + + u16 cid = starting_id; + + while (m_channels.count(cid) != 0) + ++cid; + + return cid; +} + +bool WiimoteDevice::LinkChannel(u16 psm) +{ + const auto* const channel = FindChannelWithPSM(psm); + + // Attempt to connect the channel. + if (!channel) + { + SendConnectionRequest(psm); + return false; + } + + return channel->IsComplete(); +} + +bool WiimoteDevice::IsSourceValid() const +{ + return m_hid_source != nullptr; +} + +bool WiimoteDevice::IsConnected() const +{ + return m_baseband_state == BasebandState::Complete; +} + +void WiimoteDevice::Activate(bool connect) +{ + const char* message = nullptr; + + if (connect && m_baseband_state == BasebandState::Inactive) + { + SetBasebandState(BasebandState::RequestConnection); + + message = "Wii Remote {} connected"; + } + else if (!connect && IsConnected()) + { + Reset(); + + // Does a real remote gracefully disconnect l2cap channels first? + // Not doing that doesn't seem to break anything. + m_host->RemoteDisconnect(GetBD()); + + message = "Wii Remote {} disconnected"; + } + + if (message) + Core::DisplayMessage(fmt::format(message, GetNumber() + 1), CONNECTION_MESSAGE_TIME); +} + +bool WiimoteDevice::EventConnectionRequest() +{ + if (!IsPageScanEnabled()) return false; - // try to connect L2CAP_PSM_HID_CNTL - if (!m_hid_control_channel.connected) - { - if (m_hid_control_channel.connected_wait) - return false; + Core::DisplayMessage( + fmt::format("Wii Remote {} connected from emulated software", GetNumber() + 1), + CONNECTION_MESSAGE_TIME); - m_hid_control_channel.connected_wait = true; - SendConnectionRequest(0x0040, L2CAP_PSM_HID_CNTL); - return true; - } + SetBasebandState(BasebandState::Complete); - // try to config L2CAP_PSM_HID_CNTL - if (!m_hid_control_channel.config) - { - if (m_hid_control_channel.config_wait) - return false; - - m_hid_control_channel.config_wait = true; - SendConfigurationRequest(0x0040); - return true; - } - - // try to connect L2CAP_PSM_HID_INTR - if (!m_hid_interrupt_channel.connected) - { - if (m_hid_interrupt_channel.connected_wait) - return false; - - m_hid_interrupt_channel.connected_wait = true; - SendConnectionRequest(0x0041, L2CAP_PSM_HID_INTR); - return true; - } - - // try to config L2CAP_PSM_HID_INTR - if (!m_hid_interrupt_channel.config) - { - if (m_hid_interrupt_channel.config_wait) - return false; - - m_hid_interrupt_channel.config_wait = true; - SendConfigurationRequest(0x0041); - return true; - } - - DEBUG_LOG(IOS_WIIMOTE, "ConnectionState CONN_LINKING -> CONN_COMPLETE"); - m_connection_state = ConnectionState::Complete; - - // Update wiimote connection status in the UI - Host_UpdateDisasmDialog(); - - return false; + return true; } -// -// -// -// -// --- Events -// -// -// -// -// -void WiimoteDevice::Activate(bool ready) +bool WiimoteDevice::EventConnectionAccept() { - if (ready && (m_connection_state == ConnectionState::Inactive)) + if (!IsPageScanEnabled()) + return false; + + SetBasebandState(BasebandState::Complete); + + // A connection acceptance means the remote seeked out the connection. + // In this situation the remote actively creates HID channels. + m_hid_state = HIDState::Linking; + + return true; +} + +void WiimoteDevice::EventDisconnect(u8 reason) +{ + // If someone wants to be fancy we could also figure out the values for reason + // and display things like "Wii Remote %i disconnected due to inactivity!" etc. + // FYI: It looks like reason is always 0x13 (User Ended Connection). + + Core::DisplayMessage( + fmt::format("Wii Remote {} disconnected by emulated software", GetNumber() + 1), + CONNECTION_MESSAGE_TIME); + + Reset(); +} + +void WiimoteDevice::SetSource(WiimoteCommon::HIDWiimote* hid_source) +{ + if (m_hid_source && IsConnected()) { - m_connection_state = ConnectionState::Ready; + Activate(false); } - else if (!ready) + + m_hid_source = hid_source; + + if (m_hid_source) { - m_host->RemoteDisconnect(m_connection_handle); - EventDisconnect(); + m_hid_source->SetInterruptCallback(std::bind(&WiimoteDevice::InterruptDataInputCallback, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); + Activate(true); } } -void WiimoteDevice::EventConnectionAccepted() +void WiimoteDevice::Reset() { - DEBUG_LOG(IOS_WIIMOTE, "ConnectionState %x -> CONN_LINKING", int(m_connection_state)); - m_connection_state = ConnectionState::Linking; + SetBasebandState(BasebandState::Inactive); + m_hid_state = HIDState::Inactive; + m_channels = {}; } -void WiimoteDevice::EventDisconnect() +void WiimoteDevice::Update() { - // Send disconnect message to plugin - Wiimote::ControlChannel(m_connection_handle & 0xFF, Wiimote::DOLPHIN_DISCONNET_CONTROL_CHANNEL, - nullptr, 0); + if (m_baseband_state == BasebandState::RequestConnection) + { + if (m_host->RemoteConnect(*this)) + { + // After a connection request is visible to the controller switch to inactive. + SetBasebandState(BasebandState::Inactive); + } + } - m_connection_state = ConnectionState::Inactive; + if (!IsConnected()) + return; - // Clear channel flags - ResetChannels(); + // Send configuration for any newly connected channels. + for (auto& [cid, channel] : m_channels) + { + if (channel.IsAccepted() && channel.state == SChannel::State::Inactive) + { + // A real wii remote has been observed requesting this MTU. + constexpr u16 REQUEST_MTU = 185; - // Update wiimote connection status in the UI - Host_UpdateDisasmDialog(); + channel.state = SChannel::State::ConfigurationPending; + SendConfigurationRequest(channel.remote_cid, REQUEST_MTU, L2CAP_FLUSH_TIMO_DEFAULT); + } + } + + // If the connection originated from the wii remote it will create + // HID control and interrupt channels (in that order). + if (m_hid_state == HIDState::Linking) + { + if (LinkChannel(L2CAP_PSM_HID_CNTL) && LinkChannel(L2CAP_PSM_HID_INTR)) + { + DEBUG_LOG(IOS_WIIMOTE, "HID linking is complete."); + m_hid_state = HIDState::Inactive; + } + } } -bool WiimoteDevice::EventPagingChanged(u8 page_mode) const +void WiimoteDevice::UpdateInput() { - return (m_connection_state == ConnectionState::Ready) && (page_mode & HCI_PAGE_SCAN_ENABLE); -} + if (m_connection_request_counter) + --m_connection_request_counter; -void WiimoteDevice::ResetChannels() -{ - // reset connection process - m_hid_control_channel = {}; - m_hid_interrupt_channel = {}; -} + if (!IsSourceValid()) + return; -// -// -// -// -// --- Input parsing -// -// -// -// -// + // Allow button press to trigger activation after a second of no connection activity. + if (!m_connection_request_counter && m_baseband_state == BasebandState::Inactive) + { + if (Wiimote::NetPlay_GetButtonPress(GetNumber(), m_hid_source->IsButtonPressed())) + Activate(true); + } + + // Verify interrupt channel is connected and configured. + const auto* channel = FindChannelWithPSM(L2CAP_PSM_HID_INTR); + if (channel && channel->IsComplete()) + m_hid_source->Update(); +} // This function receives L2CAP commands from the CPU void WiimoteDevice::ExecuteL2capCmd(u8* ptr, u32 size) { - // parse the command l2cap_hdr_t* header = (l2cap_hdr_t*)ptr; u8* data = ptr + sizeof(l2cap_hdr_t); const u32 data_size = size - sizeof(l2cap_hdr_t); @@ -269,55 +377,70 @@ void WiimoteDevice::ExecuteL2capCmd(u8* ptr, u32 size) return; } - switch (header->dcid) + if (header->dcid == L2CAP_SIGNAL_CID) { - case L2CAP_SIGNAL_CID: SignalChannel(data, data_size); + return; + } + + const auto itr = m_channels.find(header->dcid); + if (itr == m_channels.end()) + { + ERROR_LOG(IOS_WIIMOTE, "L2CAP: SendACLPacket to unknown channel %i", header->dcid); + return; + } + + const SChannel& channel = itr->second; + switch (channel.psm) + { + case L2CAP_PSM_SDP: + HandleSDP(header->dcid, data, data_size); break; - default: + // Original (non-TR) remotes process "set reports" on control channel. + // Commercial games don't use this. Some homebrew does. (e.g. Gecko OS) + case L2CAP_PSM_HID_CNTL: { - DEBUG_ASSERT_MSG(IOS_WIIMOTE, DoesChannelExist(header->dcid), - "L2CAP: SendACLPacket to unknown channel %i", header->dcid); - - const auto itr = m_channel.find(header->dcid); - const int number = m_connection_handle & 0xFF; - - if (itr != m_channel.end()) + const u8 hid_type = data[0]; + if (hid_type == ((WiimoteCommon::HID_TYPE_SET_REPORT << 4) | WiimoteCommon::HID_PARAM_OUTPUT)) { - const SChannel& channel = itr->second; - switch (channel.psm) + struct DataFrame { - case L2CAP_PSM_SDP: - HandleSDP(header->dcid, data, data_size); - break; + l2cap_hdr_t header; + u8 hid_type; + } data_frame; - case L2CAP_PSM_HID_CNTL: - if (number < MAX_BBMOTES) - Wiimote::ControlChannel(number, header->dcid, data, data_size); - break; + static_assert(sizeof(data_frame) == sizeof(data_frame.hid_type) + sizeof(l2cap_hdr_t)); - case L2CAP_PSM_HID_INTR: - { - if (number < MAX_BBMOTES) - { - DEBUG_LOG(WIIMOTE, "Wiimote_InterruptChannel"); - DEBUG_LOG(WIIMOTE, " Channel ID: %04x", header->dcid); - const std::string temp = ArrayToString(data, data_size); - DEBUG_LOG(WIIMOTE, " Data: %s", temp.c_str()); + data_frame.header.dcid = channel.remote_cid; + data_frame.header.length = sizeof(data_frame.hid_type); + data_frame.hid_type = WiimoteCommon::HID_HANDSHAKE_SUCCESS; - Wiimote::InterruptChannel(number, header->dcid, data, data_size); - } - } - break; + m_host->SendACLPacket(GetBD(), reinterpret_cast(&data_frame), sizeof(data_frame)); - default: - ERROR_LOG(IOS_WIIMOTE, "Channel 0x04%x has unknown PSM %x", header->dcid, channel.psm); - break; - } + // Does the wii remote reply on the control or interrupt channel in this situation? + m_hid_source->InterruptDataOutput(data + sizeof(hid_type), data_size - sizeof(hid_type)); + } + else + { + ERROR_LOG(IOS_WIIMOTE, "Unknown HID-type (0x%x) on L2CAP_PSM_HID_CNTL", hid_type); } } break; + + case L2CAP_PSM_HID_INTR: + { + const u8 hid_type = data[0]; + if (hid_type == ((WiimoteCommon::HID_TYPE_DATA << 4) | WiimoteCommon::HID_PARAM_OUTPUT)) + m_hid_source->InterruptDataOutput(data + sizeof(hid_type), data_size - sizeof(hid_type)); + else + ERROR_LOG(IOS_WIIMOTE, "Unknown HID-type (0x%x) on L2CAP_PSM_HID_INTR", hid_type); + } + break; + + default: + ERROR_LOG(IOS_WIIMOTE, "Channel 0x%x has unknown PSM %x", header->dcid, channel.psm); + break; } } @@ -365,40 +488,47 @@ void WiimoteDevice::SignalChannel(u8* data, u32 size) } } -// -// -// -// -// --- Receive Commands from CPU -// -// -// -// -// - void WiimoteDevice::ReceiveConnectionReq(u8 ident, u8* data, u32 size) { l2cap_con_req_cp* command_connection_req = (l2cap_con_req_cp*)data; - // create the channel - SChannel& channel = m_channel[command_connection_req->scid]; - channel.psm = command_connection_req->psm; - channel.scid = command_connection_req->scid; - channel.dcid = command_connection_req->scid; - DEBUG_LOG(IOS_WIIMOTE, "[L2CAP] ReceiveConnectionRequest"); DEBUG_LOG(IOS_WIIMOTE, " Ident: 0x%02x", ident); - DEBUG_LOG(IOS_WIIMOTE, " PSM: 0x%04x", channel.psm); - DEBUG_LOG(IOS_WIIMOTE, " SCID: 0x%04x", channel.scid); - DEBUG_LOG(IOS_WIIMOTE, " DCID: 0x%04x", channel.dcid); + DEBUG_LOG(IOS_WIIMOTE, " PSM: 0x%04x", command_connection_req->psm); + DEBUG_LOG(IOS_WIIMOTE, " SCID: 0x%04x", command_connection_req->scid); - // response - l2cap_con_rsp_cp rsp; - rsp.scid = channel.scid; - rsp.dcid = channel.dcid; - rsp.result = L2CAP_SUCCESS; + l2cap_con_rsp_cp rsp = {}; + rsp.scid = command_connection_req->scid; rsp.status = L2CAP_NO_INFO; + if (FindChannelWithPSM(command_connection_req->psm) != nullptr) + { + ERROR_LOG(IOS_WIIMOTE, "Multiple channels with same PSM (%d) are not allowed.", + command_connection_req->psm); + + // A real wii remote refuses multiple connections with the same PSM. + rsp.result = L2CAP_NO_RESOURCES; + rsp.dcid = L2CAP_NULL_CID; + } + else + { + // Create the channel. + const u16 local_cid = GenerateChannelID(); + + SChannel& channel = m_channels[local_cid]; + channel.psm = command_connection_req->psm; + channel.remote_cid = command_connection_req->scid; + + if (channel.psm != L2CAP_PSM_SDP && channel.psm != L2CAP_PSM_HID_CNTL && + channel.psm != L2CAP_PSM_HID_INTR) + { + WARN_LOG(IOS_WIIMOTE, "L2CAP connection with unknown psm (0x%x)", channel.psm); + } + + rsp.result = L2CAP_SUCCESS; + rsp.dcid = local_cid; + } + DEBUG_LOG(IOS_WIIMOTE, "[L2CAP] SendConnectionResponse"); SendCommandToACL(ident, L2CAP_CONNECT_RSP, sizeof(l2cap_con_rsp_cp), (u8*)&rsp); } @@ -419,14 +549,8 @@ void WiimoteDevice::ReceiveConnectionResponse(u8 ident, u8* data, u32 size) DEBUG_ASSERT(rsp->status == L2CAP_NO_INFO); DEBUG_ASSERT(DoesChannelExist(rsp->scid)); - SChannel& channel = m_channel[rsp->scid]; - channel.dcid = rsp->dcid; - - // update state machine - if (channel.psm == L2CAP_PSM_HID_CNTL) - m_hid_control_channel.connected = true; - else if (channel.psm == L2CAP_PSM_HID_INTR) - m_hid_interrupt_channel.connected = true; + SChannel& channel = m_channels[rsp->scid]; + channel.remote_cid = rsp->dcid; } void WiimoteDevice::ReceiveConfigurationReq(u8 ident, u8* data, u32 size) @@ -438,7 +562,7 @@ void WiimoteDevice::ReceiveConfigurationReq(u8 ident, u8* data, u32 size) DEBUG_ASSERT(command_config_req->flags == 0x00); DEBUG_ASSERT(DoesChannelExist(command_config_req->dcid)); - SChannel& channel = m_channel[command_config_req->dcid]; + SChannel& channel = m_channels[command_config_req->dcid]; DEBUG_LOG(IOS_WIIMOTE, "[L2CAP] ReceiveConfigurationRequest"); DEBUG_LOG(IOS_WIIMOTE, " Ident: 0x%02x", ident); @@ -451,13 +575,16 @@ void WiimoteDevice::ReceiveConfigurationReq(u8 ident, u8* data, u32 size) u32 resp_len = 0; l2cap_cfg_rsp_cp* rsp = (l2cap_cfg_rsp_cp*)temp_buffer; - rsp->scid = channel.dcid; + rsp->scid = channel.remote_cid; rsp->flags = 0x00; rsp->result = L2CAP_SUCCESS; resp_len += sizeof(l2cap_cfg_rsp_cp); - // read configuration options + // If the option is not provided, configure the default. + u16 remote_mtu = L2CAP_MTU_DEFAULT; + + // Read configuration options. while (offset < size) { l2cap_cfg_opt_t* options = (l2cap_cfg_opt_t*)&data[offset]; @@ -469,16 +596,16 @@ void WiimoteDevice::ReceiveConfigurationReq(u8 ident, u8* data, u32 size) { DEBUG_ASSERT(options->length == L2CAP_OPT_MTU_SIZE); l2cap_cfg_opt_val_t* mtu = (l2cap_cfg_opt_val_t*)&data[offset]; - channel.mtu = mtu->mtu; + remote_mtu = mtu->mtu; DEBUG_LOG(IOS_WIIMOTE, " MTU: 0x%04x", mtu->mtu); } break; + // We don't care what the flush timeout is. Our packets are not dropped. case L2CAP_OPT_FLUSH_TIMO: { DEBUG_ASSERT(options->length == L2CAP_OPT_FLUSH_TIMO_SIZE); l2cap_cfg_opt_val_t* flush_time_out = (l2cap_cfg_opt_val_t*)&data[offset]; - channel.flush_time_out = flush_time_out->flush_timo; DEBUG_LOG(IOS_WIIMOTE, " FlushTimeOut: 0x%04x", flush_time_out->flush_timo); } break; @@ -498,11 +625,7 @@ void WiimoteDevice::ReceiveConfigurationReq(u8 ident, u8* data, u32 size) DEBUG_LOG(IOS_WIIMOTE, "[L2CAP] SendConfigurationResponse"); SendCommandToACL(ident, L2CAP_CONFIG_RSP, resp_len, temp_buffer); - // update state machine - if (channel.psm == L2CAP_PSM_HID_CNTL) - m_hid_control_channel.connected = true; - else if (channel.psm == L2CAP_PSM_HID_INTR) - m_hid_interrupt_channel.connected = true; + channel.remote_mtu = remote_mtu; } void WiimoteDevice::ReceiveConfigurationResponse(u8 ident, u8* data, u32 size) @@ -516,13 +639,7 @@ void WiimoteDevice::ReceiveConfigurationResponse(u8 ident, u8* data, u32 size) DEBUG_ASSERT(rsp->result == L2CAP_SUCCESS); - // update state machine - const SChannel& channel = m_channel[rsp->scid]; - - if (channel.psm == L2CAP_PSM_HID_CNTL) - m_hid_control_channel.config = true; - else if (channel.psm == L2CAP_PSM_HID_INTR) - m_hid_interrupt_channel.config = true; + m_channels[rsp->scid].state = SChannel::State::Complete; } void WiimoteDevice::ReceiveDisconnectionReq(u8 ident, u8* data, u32 size) @@ -534,7 +651,10 @@ void WiimoteDevice::ReceiveDisconnectionReq(u8 ident, u8* data, u32 size) DEBUG_LOG(IOS_WIIMOTE, " DCID: 0x%04x", command_disconnection_req->dcid); DEBUG_LOG(IOS_WIIMOTE, " SCID: 0x%04x", command_disconnection_req->scid); - // response + DEBUG_ASSERT(DoesChannelExist(command_disconnection_req->dcid)); + + m_channels.erase(command_disconnection_req->dcid); + l2cap_discon_req_cp rsp; rsp.dcid = command_disconnection_req->dcid; rsp.scid = command_disconnection_req->scid; @@ -543,28 +663,19 @@ void WiimoteDevice::ReceiveDisconnectionReq(u8 ident, u8* data, u32 size) SendCommandToACL(ident, L2CAP_DISCONNECT_RSP, sizeof(l2cap_discon_req_cp), (u8*)&rsp); } -// -// -// -// -// --- Send Commands To CPU -// -// -// -// -// - -// We assume Wiimote is always connected -void WiimoteDevice::SendConnectionRequest(u16 scid, u16 psm) +void WiimoteDevice::SendConnectionRequest(u16 psm) { - // create the channel - SChannel& channel = m_channel[scid]; + DEBUG_ASSERT(FindChannelWithPSM(psm) == nullptr); + + const u16 local_cid = GenerateChannelID(); + + // Create the channel. + SChannel& channel = m_channels[local_cid]; channel.psm = psm; - channel.scid = scid; l2cap_con_req_cp cr; cr.psm = psm; - cr.scid = scid; + cr.scid = local_cid; DEBUG_LOG(IOS_WIIMOTE, "[L2CAP] SendConnectionRequest"); DEBUG_LOG(IOS_WIIMOTE, " Psm: 0x%04x", cr.psm); @@ -573,34 +684,13 @@ void WiimoteDevice::SendConnectionRequest(u16 scid, u16 psm) SendCommandToACL(L2CAP_CONNECT_REQ, L2CAP_CONNECT_REQ, sizeof(l2cap_con_req_cp), (u8*)&cr); } -// We don't initially disconnect Wiimote though ... -void WiimoteDevice::SendDisconnectRequest(u16 scid) +void WiimoteDevice::SendConfigurationRequest(u16 cid, u16 mtu, u16 flush_time_out) { - // create the channel - const SChannel& channel = m_channel[scid]; - - l2cap_discon_req_cp cr; - cr.dcid = channel.dcid; - cr.scid = channel.scid; - - DEBUG_LOG(IOS_WIIMOTE, "[L2CAP] SendDisconnectionRequest"); - DEBUG_LOG(IOS_WIIMOTE, " Dcid: 0x%04x", cr.dcid); - DEBUG_LOG(IOS_WIIMOTE, " Scid: 0x%04x", cr.scid); - - SendCommandToACL(L2CAP_DISCONNECT_REQ, L2CAP_DISCONNECT_REQ, sizeof(l2cap_discon_req_cp), - (u8*)&cr); -} - -void WiimoteDevice::SendConfigurationRequest(u16 scid, u16 mtu, u16 flush_time_out) -{ - DEBUG_ASSERT(DoesChannelExist(scid)); - const SChannel& channel = m_channel[scid]; - u8 buffer[1024]; int offset = 0; l2cap_cfg_req_cp* cr = (l2cap_cfg_req_cp*)&buffer[offset]; - cr->dcid = channel.dcid; + cr->dcid = cid; cr->flags = 0; offset += sizeof(l2cap_cfg_req_cp); @@ -610,14 +700,8 @@ void WiimoteDevice::SendConfigurationRequest(u16 scid, u16 mtu, u16 flush_time_o l2cap_cfg_opt_t* options; - // (shuffle2) currently we end up not appending options. this is because we don't - // negotiate after trying to set MTU = 0 fails (stack will respond with - // "configuration failed" msg...). This is still fine, we'll just use whatever the - // Bluetooth stack defaults to. - if (mtu || channel.mtu) + if (mtu != L2CAP_MTU_DEFAULT) { - if (mtu == 0) - mtu = channel.mtu; options = (l2cap_cfg_opt_t*)&buffer[offset]; offset += sizeof(l2cap_cfg_opt_t); options->type = L2CAP_OPT_MTU; @@ -627,10 +711,8 @@ void WiimoteDevice::SendConfigurationRequest(u16 scid, u16 mtu, u16 flush_time_o DEBUG_LOG(IOS_WIIMOTE, " MTU: 0x%04x", mtu); } - if (flush_time_out || channel.flush_time_out) + if (flush_time_out != L2CAP_FLUSH_TIMO_DEFAULT) { - if (flush_time_out == 0) - flush_time_out = channel.flush_time_out; options = (l2cap_cfg_opt_t*)&buffer[offset]; offset += sizeof(l2cap_cfg_opt_t); options->type = L2CAP_OPT_FLUSH_TIMO; @@ -643,22 +725,11 @@ void WiimoteDevice::SendConfigurationRequest(u16 scid, u16 mtu, u16 flush_time_o SendCommandToACL(L2CAP_CONFIG_REQ, L2CAP_CONFIG_REQ, offset, buffer); } -// -// -// -// -// --- SDP -// -// -// -// -// - -#define SDP_UINT8 0x08 -#define SDP_UINT16 0x09 -#define SDP_UINT32 0x0A -#define SDP_SEQ8 0x35 -#define SDP_SEQ16 0x36 +constexpr u8 SDP_UINT8 = 0x08; +constexpr u8 SDP_UINT16 = 0x09; +constexpr u8 SDP_UINT32 = 0x0A; +constexpr u8 SDP_SEQ8 = 0x35; +constexpr u8 SDP_SEQ16 = 0x36; void WiimoteDevice::SDPSendServiceSearchResponse(u16 cid, u16 transaction_id, u8* service_search_pattern, @@ -699,7 +770,7 @@ void WiimoteDevice::SDPSendServiceSearchResponse(u16 cid, u16 transaction_id, offset++; // No continuation state; header->length = (u16)(offset - sizeof(l2cap_hdr_t)); - m_host->SendACLPacket(GetConnectionHandle(), data_frame, header->length + sizeof(l2cap_hdr_t)); + m_host->SendACLPacket(GetBD(), data_frame, header->length + sizeof(l2cap_hdr_t)); } static u32 ParseCont(u8* cont) @@ -793,7 +864,7 @@ void WiimoteDevice::SDPSendServiceAttributeResponse(u16 cid, u16 transaction_id, offset += packet_size; header->length = (u16)(offset - sizeof(l2cap_hdr_t)); - m_host->SendACLPacket(GetConnectionHandle(), data_frame, header->length + sizeof(l2cap_hdr_t)); + m_host->SendACLPacket(GetBD(), data_frame, header->length + sizeof(l2cap_hdr_t)); } void WiimoteDevice::HandleSDP(u16 cid, u8* data, u32 size) @@ -843,23 +914,12 @@ void WiimoteDevice::HandleSDP(u16 cid, u8* data, u32 size) break; default: - ERROR_LOG(IOS_WIIMOTE, "WIIMOTE: Unknown SDP command %x", data[0]); + ERROR_LOG(IOS_WIIMOTE, "Unknown SDP command %x", data[0]); PanicAlert("WIIMOTE: Unknown SDP command %x", data[0]); break; } } -// -// -// -// -// --- Data Send Functions -// -// -// -// -// - void WiimoteDevice::SendCommandToACL(u8 ident, u8 code, u8 command_length, u8* command_data) { u8 data_frame[1024]; @@ -882,50 +942,38 @@ void WiimoteDevice::SendCommandToACL(u8 ident, u8 code, u8 command_length, u8* c DEBUG_LOG(IOS_WIIMOTE, " Ident: 0x%02x", ident); DEBUG_LOG(IOS_WIIMOTE, " Code: 0x%02x", code); - // send .... - m_host->SendACLPacket(GetConnectionHandle(), data_frame, header->length + sizeof(l2cap_hdr_t)); + m_host->SendACLPacket(GetBD(), data_frame, header->length + sizeof(l2cap_hdr_t)); } -void WiimoteDevice::ReceiveL2capData(u16 scid, const void* data, u32 size) +void WiimoteDevice::InterruptDataInputCallback(u8 hid_type, const u8* data, u32 size) { - // Allocate DataFrame - u8 data_frame[1024]; - u32 offset = 0; - l2cap_hdr_t* header = (l2cap_hdr_t*)data_frame; - offset += sizeof(l2cap_hdr_t); + const auto* const channel = FindChannelWithPSM(L2CAP_PSM_HID_INTR); - // Check if we are already reporting on this channel - DEBUG_ASSERT(DoesChannelExist(scid)); - const SChannel& channel = m_channel[scid]; + if (!channel) + { + WARN_LOG(IOS_WIIMOTE, "Data callback with invalid L2CAP_PSM_HID_INTR channel."); + return; + } - // Add an additional 4 byte header to the Wiimote report - header->dcid = channel.dcid; - header->length = size; + struct DataFrame + { + l2cap_hdr_t header; + u8 hid_type; + std::array data; + } data_frame; - // Copy the Wiimote report to data_frame - memcpy(data_frame + offset, data, size); - // Update offset to the final size of the report - offset += size; + static_assert(sizeof(data_frame) == sizeof(data_frame.data) + sizeof(u8) + sizeof(l2cap_hdr_t)); - // Send the report - m_host->SendACLPacket(GetConnectionHandle(), data_frame, offset); + data_frame.header.dcid = channel->remote_cid; + data_frame.header.length = u16(sizeof(hid_type) + size); + data_frame.hid_type = hid_type; + std::copy_n(data, size, data_frame.data.data()); + + const u32 data_frame_size = data_frame.header.length + sizeof(l2cap_hdr_t); + + // This should never be a problem as l2cap requires a minimum MTU of 48 bytes. + DEBUG_ASSERT(data_frame_size <= channel->remote_mtu); + + m_host->SendACLPacket(GetBD(), reinterpret_cast(&data_frame), data_frame_size); } } // namespace IOS::HLE - -namespace Core -{ -// This is called continuously from the Wiimote plugin as soon as it has received -// a reporting mode. size is the byte size of the report. -void Callback_WiimoteInterruptChannel(int number, u16 channel_id, const u8* data, u32 size) -{ - DEBUG_LOG(WIIMOTE, "===================="); - DEBUG_LOG(WIIMOTE, "Callback_WiimoteInterruptChannel: (Wiimote: #%i)", number); - DEBUG_LOG(WIIMOTE, " Data: %s", ArrayToString(data, size, 50).c_str()); - DEBUG_LOG(WIIMOTE, " Channel: %x", channel_id); - - const auto bt = std::static_pointer_cast( - IOS::HLE::GetIOS()->GetDeviceByName("/dev/usb/oh1/57e/305")); - if (bt) - bt->AccessWiimoteByIndex(number)->ReceiveL2capData(channel_id, data, size); -} -} // namespace Core diff --git a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h index c6cadf0cf7..6573db08f2 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h @@ -9,6 +9,7 @@ #include #include "Common/CommonTypes.h" +#include "Core/HW/WiimoteCommon/WiimoteHid.h" #include "Core/IOS/USB/Bluetooth/hci.h" class PointerWrap; @@ -23,89 +24,129 @@ class BluetoothEmu; class WiimoteDevice { public: - WiimoteDevice(Device::BluetoothEmu* host, int number, bdaddr_t bd, bool ready = false); + using ClassType = std::array; + using FeaturesType = std::array; + using LinkKeyType = std::array; + + WiimoteDevice(Device::BluetoothEmu* host, int number, bdaddr_t bd); + ~WiimoteDevice(); + + WiimoteDevice(const WiimoteDevice&) = delete; + WiimoteDevice& operator=(const WiimoteDevice&) = delete; + WiimoteDevice(WiimoteDevice&&) = delete; + WiimoteDevice& operator=(WiimoteDevice&&) = delete; + + void Reset(); + + // Called every BluetoothEmu::Update. + void Update(); + + // Called every ~200hz. + void UpdateInput(); void DoState(PointerWrap& p); - // ugly Host handling.... - // we really have to clean all this code + bool IsInquiryScanEnabled() const; + bool IsPageScanEnabled() const; - bool IsConnected() const { return m_connection_state == ConnectionState::Complete; } - bool IsInactive() const { return m_connection_state == ConnectionState::Inactive; } - bool LinkChannel(); - void ResetChannels(); + u32 GetNumber() const; + + bool IsSourceValid() const; + bool IsConnected() const; + + // User-initiated. Produces UI messages. void Activate(bool ready); - void ExecuteL2capCmd(u8* ptr, u32 size); // From CPU - void ReceiveL2capData(u16 scid, const void* data, u32 size); // From Wiimote - void EventConnectionAccepted(); - void EventDisconnect(); - bool EventPagingChanged(u8 page_mode) const; + // From CPU + void ExecuteL2capCmd(u8* ptr, u32 size); + // From Wiimote + void InterruptDataInputCallback(u8 hid_type, const u8* data, u32 size); + + bool EventConnectionAccept(); + bool EventConnectionRequest(); + void EventDisconnect(u8 reason); + + // nullptr may be passed to disable the remote. + void SetSource(WiimoteCommon::HIDWiimote*); const bdaddr_t& GetBD() const { return m_bd; } - const u8* GetClass() const { return m_uclass; } - u16 GetConnectionHandle() const { return m_connection_handle; } - const u8* GetFeatures() const { return m_features; } const char* GetName() const { return m_name.c_str(); } u8 GetLMPVersion() const { return m_lmp_version; } u16 GetLMPSubVersion() const { return m_lmp_subversion; } - u16 GetManufactorID() const { return 0x000F; } // Broadcom Corporation - const u8* GetLinkKey() const { return m_link_key; } + // Broadcom Corporation + u16 GetManufactorID() const { return 0x000F; } + const ClassType& GetClass() const { return m_class; } + const FeaturesType& GetFeatures() const { return m_features; } + const LinkKeyType& GetLinkKey() const { return m_link_key; } private: - enum class ConnectionState + enum class BasebandState { - Inactive = -1, - Ready, + Inactive, + RequestConnection, + Complete, + }; + + enum class HIDState + { + Inactive, Linking, - Complete }; - struct HIDChannelState - { - bool connected = false; - bool connected_wait = false; - bool config = false; - bool config_wait = false; - }; - - ConnectionState m_connection_state; - - HIDChannelState m_hid_control_channel; - HIDChannelState m_hid_interrupt_channel; - - // STATE_TO_SAVE - bdaddr_t m_bd; - u16 m_connection_handle; - u8 m_uclass[HCI_CLASS_SIZE]; - u8 m_features[HCI_FEATURES_SIZE]; - u8 m_lmp_version; - u16 m_lmp_subversion; - u8 m_link_key[HCI_KEY_SIZE]; - std::string m_name; - Device::BluetoothEmu* m_host; - struct SChannel { - u16 scid; - u16 dcid; - u16 psm; + enum class State + { + Inactive, + ConfigurationPending, + Complete, + }; - u16 mtu; - u16 flush_time_out; + SChannel(); + + bool IsAccepted() const; + bool IsRemoteConfigured() const; + bool IsComplete() const; + + State state = State::Inactive; + u16 psm; + u16 remote_cid; + u16 remote_mtu = 0; }; - typedef std::map CChannelMap; - CChannelMap m_channel; + using ChannelMap = std::map; - bool DoesChannelExist(u16 scid) const { return m_channel.find(scid) != m_channel.end(); } + Device::BluetoothEmu* m_host; + WiimoteCommon::HIDWiimote* m_hid_source = nullptr; + + // State to save: + BasebandState m_baseband_state = BasebandState::Inactive; + HIDState m_hid_state = HIDState::Inactive; + bdaddr_t m_bd; + ClassType m_class; + FeaturesType m_features; + u8 m_lmp_version; + u16 m_lmp_subversion; + LinkKeyType m_link_key; + std::string m_name; + ChannelMap m_channels; + u8 m_connection_request_counter = 0; + + void SetBasebandState(BasebandState); + + const SChannel* FindChannelWithPSM(u16 psm) const; + SChannel* FindChannelWithPSM(u16 psm); + + bool LinkChannel(u16 psm); + u16 GenerateChannelID() const; + + bool DoesChannelExist(u16 scid) const { return m_channels.count(scid) != 0; } void SendCommandToACL(u8 ident, u8 code, u8 command_length, u8* command_data); void SignalChannel(u8* data, u32 size); - void SendConnectionRequest(u16 scid, u16 psm); - void SendConfigurationRequest(u16 scid, u16 mtu = 0, u16 flush_time_out = 0); - void SendDisconnectRequest(u16 scid); + void SendConnectionRequest(u16 psm); + void SendConfigurationRequest(u16 cid, u16 mtu, u16 flush_time_out); void ReceiveConnectionReq(u8 ident, u8* data, u32 size); void ReceiveConnectionResponse(u8 ident, u8* data, u32 size); @@ -113,8 +154,6 @@ private: void ReceiveConfigurationReq(u8 ident, u8* data, u32 size); void ReceiveConfigurationResponse(u8 ident, u8* data, u32 size); - // some new ugly stuff - // should be inside the plugin void HandleSDP(u16 cid, u8* data, u32 size); void SDPSendServiceSearchResponse(u16 cid, u16 transaction_id, u8* service_search_pattern, u16 maximum_service_record_count); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/hci.h b/Source/Core/Core/IOS/USB/Bluetooth/hci.h index 794b9af0d7..c36bb3d8d6 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/hci.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/hci.h @@ -2652,6 +2652,7 @@ struct SHCIEventInquiryComplete u8 EventType; u8 PayloadLength; u8 EventStatus; + u8 num_responses; }; struct SHCIEventReadClockOffsetComplete diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 789d08f275..95f052290d 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 122; // Last changed in PR 8571 +constexpr u32 STATE_VERSION = 123; // Last changed in PR 8985 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index db18ec8a02..4e654c8b64 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1706,10 +1706,12 @@ void MainWindow::OnConnectWiiRemote(int id) if (!ios || SConfig::GetInstance().m_bt_passthrough_enabled) return; Core::RunAsCPUThread([&] { - const auto bt = std::static_pointer_cast( - ios->GetDeviceByName("/dev/usb/oh1/57e/305")); - const bool is_connected = bt && bt->AccessWiimoteByIndex(id)->IsConnected(); - Wiimote::Connect(id, !is_connected); + if (const auto bt = std::static_pointer_cast( + ios->GetDeviceByName("/dev/usb/oh1/57e/305"))) + { + const auto wm = bt->AccessWiimoteByIndex(id); + wm->Activate(!wm->IsConnected()); + } }); } diff --git a/Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.cpp b/Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.cpp index 28426fc114..6398bfa9e4 100644 --- a/Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.cpp @@ -98,7 +98,7 @@ void Device::QueueReport(T&& report, std::function ack_callback // Maintain proper rumble state. report.rumble = m_rumble; - m_wiimote->QueueReport(std::forward(report)); + m_wiimote->QueueReport(report.REPORT_ID, &report, sizeof(report)); if (ack_callback) AddReportHandler(MakeAckHandler(report.REPORT_ID, std::move(ack_callback))); @@ -117,9 +117,7 @@ void AddDevice(std::unique_ptr wiimote) } wiimote->Prepare(); - - // Our silly real wiimote interface needs a non-zero "channel" to not drop input reports. - wiimote->SetChannel(26); + wiimote->EventLinked(); g_controller_interface.AddDevice(std::make_shared(std::move(wiimote))); } @@ -1063,13 +1061,13 @@ bool Device::IsMotionPlusInDesiredMode() const void Device::ProcessInputReport(WiimoteReal::Report& report) { - if (report.size() < WiimoteCommon::DataReportBuilder::HEADER_SIZE) + if (report.size() < WiimoteReal::REPORT_HID_HEADER_SIZE) { WARN_LOG(WIIMOTE, "WiiRemote: Bad report size."); return; } - auto report_id = InputReportID(report[1]); + auto report_id = InputReportID(report[WiimoteReal::REPORT_HID_HEADER_SIZE]); for (auto it = m_report_handlers.begin(); true;) { @@ -1077,8 +1075,8 @@ void Device::ProcessInputReport(WiimoteReal::Report& report) { if (report_id == InputReportID::Status) { - if (report.size() < - sizeof(InputReportStatus) + WiimoteCommon::DataReportBuilder::HEADER_SIZE) + if (report.size() - WiimoteReal::REPORT_HID_HEADER_SIZE < + sizeof(TypedInputData)) { WARN_LOG(WIIMOTE, "WiiRemote: Bad report size."); } @@ -1126,9 +1124,9 @@ void Device::ProcessInputReport(WiimoteReal::Report& report) } auto manipulator = MakeDataReportManipulator( - report_id, report.data() + WiimoteCommon::DataReportBuilder::HEADER_SIZE); + report_id, report.data() + WiimoteReal::REPORT_HID_HEADER_SIZE + sizeof(InputReportID)); - if (manipulator->GetDataSize() + WiimoteCommon::DataReportBuilder::HEADER_SIZE > report.size()) + if (manipulator->GetDataSize() + WiimoteReal::REPORT_HID_HEADER_SIZE > report.size()) { WARN_LOG(WIIMOTE, "WiiRemote: Bad report size."); return; @@ -1535,24 +1533,24 @@ template void Device::ReportHandler::AddHandler(std::function handler) { m_callbacks.emplace_back([handler = std::move(handler)](const WiimoteReal::Report& report) { - if (report[1] != u8(T::REPORT_ID)) + if (report[WiimoteReal::REPORT_HID_HEADER_SIZE] != u8(T::REPORT_ID)) return ReportHandler::HandlerResult::NotHandled; T data; - if (report.size() < sizeof(T) + WiimoteCommon::DataReportBuilder::HEADER_SIZE) + if (report.size() < sizeof(T) + WiimoteReal::REPORT_HID_HEADER_SIZE + 1) { // Off-brand "NEW 2in1" Wii Remote likes to shorten read data replies. WARN_LOG(WIIMOTE, "WiiRemote: Bad report size (%d) for report 0x%x. Zero-filling.", int(report.size()), int(T::REPORT_ID)); data = {}; - std::memcpy(&data, report.data() + WiimoteCommon::DataReportBuilder::HEADER_SIZE, - report.size() - WiimoteCommon::DataReportBuilder::HEADER_SIZE); + std::memcpy(&data, report.data() + WiimoteReal::REPORT_HID_HEADER_SIZE + 1, + report.size() - WiimoteReal::REPORT_HID_HEADER_SIZE + 1); } else { - data = Common::BitCastPtr(report.data() + WiimoteCommon::DataReportBuilder::HEADER_SIZE); + data = Common::BitCastPtr(report.data() + WiimoteReal::REPORT_HID_HEADER_SIZE + 1); } if constexpr (std::is_same_v)