diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 0750be3eba..74ea1bfd86 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -592,8 +592,9 @@ endif() if(WIN32) target_sources(core PRIVATE - HW/EXI/BBA-TAP/TAP_Win32.cpp - HW/EXI/BBA-TAP/TAP_Win32.h + HW/EXI/BBA/TAP_Win32.cpp + HW/EXI/BBA/TAP_Win32.h + HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/WiimoteReal/IOWin.cpp HW/WiimoteReal/IOWin.h ) @@ -606,7 +607,8 @@ if(WIN32) target_compile_definitions(core PRIVATE "-D_WINSOCK_DEPRECATED_NO_WARNINGS") elseif(APPLE) target_sources(core PRIVATE - HW/EXI/BBA-TAP/TAP_Apple.cpp + HW/EXI/BBA/TAP_Apple.cpp + HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/WiimoteReal/IOdarwin.h HW/WiimoteReal/IOdarwin_private.h HW/WiimoteReal/IOdarwin.mm @@ -614,7 +616,8 @@ elseif(APPLE) target_link_libraries(core PUBLIC ${IOB_LIBRARY}) elseif(UNIX) target_sources(core PRIVATE - HW/EXI/BBA-TAP/TAP_Unix.cpp + HW/EXI/BBA/TAP_Unix.cpp + HW/EXI/BBA/XLINK_KAI_BBA.cpp ) if(ANDROID) target_sources(core PRIVATE diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index f8e40dccd4..c85d8a296c 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -227,6 +227,8 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("SlotB", m_EXIDevice[1]); core->Set("SerialPort1", m_EXIDevice[2]); core->Set("BBA_MAC", m_bba_mac); + core->Set("BBA_XLINK_IP", m_bba_xlink_ip); + core->Set("BBA_XLINK_CHAT_OSD", m_bba_xlink_chat_osd); for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { core->Set(fmt::format("SIDevice{}", i), m_SIDevice[i]); @@ -495,6 +497,8 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("SlotB", (int*)&m_EXIDevice[1], ExpansionInterface::EXIDEVICE_NONE); core->Get("SerialPort1", (int*)&m_EXIDevice[2], ExpansionInterface::EXIDEVICE_NONE); core->Get("BBA_MAC", &m_bba_mac); + core->Get("BBA_XLINK_IP", &m_bba_xlink_ip, "127.0.0.1"); + core->Get("BBA_XLINK_CHAT_OSD", &m_bba_xlink_chat_osd, true); for (size_t i = 0; i < std::size(m_SIDevice); ++i) { core->Get(fmt::format("SIDevice{}", i), &m_SIDevice[i], diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index ac3ae684bb..c5e1c2ccca 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -226,7 +226,10 @@ struct SConfig std::string m_strGbaCartB; ExpansionInterface::TEXIDevices m_EXIDevice[3]; SerialInterface::SIDevices m_SIDevice[4]; + std::string m_bba_mac; + std::string m_bba_xlink_ip; + bool m_bba_xlink_chat_osd = true; // interface language std::string m_InterfaceLanguage; diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index fe33616c2f..4d199d6ac1 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -1,4 +1,4 @@ - + @@ -140,7 +140,8 @@ - + + @@ -498,7 +499,7 @@ - + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index b820c04802..42ef8c5ef2 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -993,6 +993,12 @@ PowerPC\Jit64 + + HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA + + + HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA + @@ -1743,6 +1749,9 @@ PowerPC\JitArmCommon + + HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA + diff --git a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Apple.cpp b/Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp similarity index 69% rename from Source/Core/Core/HW/EXI/BBA-TAP/TAP_Apple.cpp rename to Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp index 6dbed04f20..4024447893 100644 --- a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Apple.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAP_Apple.cpp @@ -12,7 +12,7 @@ namespace ExpansionInterface { -bool CEXIETHERNET::Activate() +bool CEXIETHERNET::TAPNetworkInterface::Activate() { if (IsActivated()) return true; @@ -30,7 +30,7 @@ bool CEXIETHERNET::Activate() return RecvInit(); } -void CEXIETHERNET::Deactivate() +void CEXIETHERNET::TAPNetworkInterface::Deactivate() { close(fd); fd = -1; @@ -41,12 +41,12 @@ void CEXIETHERNET::Deactivate() readThread.join(); } -bool CEXIETHERNET::IsActivated() +bool CEXIETHERNET::TAPNetworkInterface::IsActivated() { return fd != -1; } -bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) +bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size) { INFO_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str()); @@ -58,12 +58,12 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) } else { - SendComplete(); + m_eth_ref->SendComplete(); return true; } } -void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) +void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self) { while (!self->readThreadShutdown.IsSet()) { @@ -77,7 +77,7 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) continue; - int readBytes = read(self->fd, self->mRecvBuffer.get(), BBA_RECV_SIZE); + int readBytes = read(self->fd, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE); if (readBytes < 0) { ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes); @@ -85,25 +85,25 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) else if (self->readEnabled.IsSet()) { INFO_LOG(SP1, "Read data: %s", - ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str()); - self->mRecvBufferLength = readBytes; - self->RecvHandlePacket(); + ArrayToString(self->m_eth_ref->mRecvBuffer.get(), readBytes, 0x10).c_str()); + self->m_eth_ref->mRecvBufferLength = readBytes; + self->m_eth_ref->RecvHandlePacket(); } } } -bool CEXIETHERNET::RecvInit() +bool CEXIETHERNET::TAPNetworkInterface::RecvInit() { readThread = std::thread(ReadThreadHandler, this); return true; } -void CEXIETHERNET::RecvStart() +void CEXIETHERNET::TAPNetworkInterface::RecvStart() { readEnabled.Set(); } -void CEXIETHERNET::RecvStop() +void CEXIETHERNET::TAPNetworkInterface::RecvStop() { readEnabled.Clear(); } diff --git a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Unix.cpp b/Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp similarity index 80% rename from Source/Core/Core/HW/EXI/BBA-TAP/TAP_Unix.cpp rename to Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp index f13db04b65..4911f370bb 100644 --- a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Unix.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAP_Unix.cpp @@ -28,7 +28,7 @@ namespace ExpansionInterface #define NOTIMPLEMENTED(Name) \ NOTICE_LOG(SP1, "CEXIETHERNET::%s not implemented for your UNIX", Name); -bool CEXIETHERNET::Activate() +bool CEXIETHERNET::TAPNetworkInterface::Activate() { #ifdef __linux__ if (IsActivated()) @@ -50,7 +50,7 @@ bool CEXIETHERNET::Activate() const int MAX_INTERFACES = 32; for (int i = 0; i < MAX_INTERFACES; ++i) { - strncpy(ifr.ifr_name, StringFromFormat("Dolphin%d", i).c_str(), IFNAMSIZ - 1); + strncpy(ifr.ifr_name, StringFromFormat("Dolphin%d", i).c_str(), IFNAMSIZ); int err; if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) < 0) @@ -78,7 +78,7 @@ bool CEXIETHERNET::Activate() #endif } -void CEXIETHERNET::Deactivate() +void CEXIETHERNET::TAPNetworkInterface::Deactivate() { #ifdef __linux__ close(fd); @@ -93,7 +93,7 @@ void CEXIETHERNET::Deactivate() #endif } -bool CEXIETHERNET::IsActivated() +bool CEXIETHERNET::TAPNetworkInterface::IsActivated() { #ifdef __linux__ return fd != -1 ? true : false; @@ -102,7 +102,7 @@ bool CEXIETHERNET::IsActivated() #endif } -bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) +bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size) { #ifdef __linux__ DEBUG_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str()); @@ -115,7 +115,7 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) } else { - SendComplete(); + m_eth_ref->SendComplete(); return true; } #else @@ -125,7 +125,7 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) } #ifdef __linux__ -void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) +void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self) { while (!self->readThreadShutdown.IsSet()) { @@ -139,7 +139,7 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) continue; - int readBytes = read(self->fd, self->mRecvBuffer.get(), BBA_RECV_SIZE); + int readBytes = read(self->fd, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE); if (readBytes < 0) { ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes); @@ -147,15 +147,15 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) else if (self->readEnabled.IsSet()) { DEBUG_LOG(SP1, "Read data: %s", - ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str()); - self->mRecvBufferLength = readBytes; - self->RecvHandlePacket(); + ArrayToString(self->m_eth_ref->mRecvBuffer.get(), readBytes, 0x10).c_str()); + self->m_eth_ref->mRecvBufferLength = readBytes; + self->m_eth_ref->RecvHandlePacket(); } } } #endif -bool CEXIETHERNET::RecvInit() +bool CEXIETHERNET::TAPNetworkInterface::RecvInit() { #ifdef __linux__ readThread = std::thread(ReadThreadHandler, this); @@ -166,7 +166,7 @@ bool CEXIETHERNET::RecvInit() #endif } -void CEXIETHERNET::RecvStart() +void CEXIETHERNET::TAPNetworkInterface::RecvStart() { #ifdef __linux__ readEnabled.Set(); @@ -175,7 +175,7 @@ void CEXIETHERNET::RecvStart() #endif } -void CEXIETHERNET::RecvStop() +void CEXIETHERNET::TAPNetworkInterface::RecvStop() { #ifdef __linux__ readEnabled.Clear(); diff --git a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Win32.cpp b/Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp similarity index 92% rename from Source/Core/Core/HW/EXI/BBA-TAP/TAP_Win32.cpp rename to Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp index 3e8b4d5442..a421484005 100644 --- a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Win32.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAP_Win32.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "Core/HW/EXI/BBA-TAP/TAP_Win32.h" +#include "Core/HW/EXI/BBA/TAP_Win32.h" #include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" @@ -167,7 +167,7 @@ bool OpenTAP(HANDLE& adapter, const std::basic_string& device_guid) namespace ExpansionInterface { -bool CEXIETHERNET::Activate() +bool CEXIETHERNET::TAPNetworkInterface::Activate() { if (IsActivated()) return true; @@ -233,7 +233,7 @@ bool CEXIETHERNET::Activate() return RecvInit(); } -void CEXIETHERNET::Deactivate() +void CEXIETHERNET::TAPNetworkInterface::Deactivate() { if (!IsActivated()) return; @@ -258,19 +258,19 @@ void CEXIETHERNET::Deactivate() memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped)); } -bool CEXIETHERNET::IsActivated() +bool CEXIETHERNET::TAPNetworkInterface::IsActivated() { return mHAdapter != INVALID_HANDLE_VALUE; } -void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) +void CEXIETHERNET::TAPNetworkInterface::ReadThreadHandler(TAPNetworkInterface* self) { while (!self->readThreadShutdown.IsSet()) { DWORD transferred; // Read from TAP into internal buffer. - if (ReadFile(self->mHAdapter, self->mRecvBuffer.get(), BBA_RECV_SIZE, &transferred, + if (ReadFile(self->mHAdapter, self->m_eth_ref->mRecvBuffer.get(), BBA_RECV_SIZE, &transferred, &self->mReadOverlapped)) { // Returning immediately is not likely to happen, but if so, reset the event state manually. @@ -300,16 +300,16 @@ void CEXIETHERNET::ReadThreadHandler(CEXIETHERNET* self) // Copy to BBA buffer, and fire interrupt if enabled. DEBUG_LOG(SP1, "Received %u bytes:\n %s", transferred, - ArrayToString(self->mRecvBuffer.get(), transferred, 0x10).c_str()); + ArrayToString(self->m_eth_ref->mRecvBuffer.get(), transferred, 0x10).c_str()); if (self->readEnabled.IsSet()) { - self->mRecvBufferLength = transferred; - self->RecvHandlePacket(); + self->m_eth_ref->mRecvBufferLength = transferred; + self->m_eth_ref->RecvHandlePacket(); } } } -bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) +bool CEXIETHERNET::TAPNetworkInterface::SendFrame(const u8* frame, u32 size) { DEBUG_LOG(SP1, "SendFrame %u bytes:\n%s", size, ArrayToString(frame, size, 0x10).c_str()); @@ -345,22 +345,22 @@ bool CEXIETHERNET::SendFrame(const u8* frame, u32 size) } // Always report the packet as being sent successfully, even though it might be a lie - SendComplete(); + m_eth_ref->SendComplete(); return true; } -bool CEXIETHERNET::RecvInit() +bool CEXIETHERNET::TAPNetworkInterface::RecvInit() { readThread = std::thread(ReadThreadHandler, this); return true; } -void CEXIETHERNET::RecvStart() +void CEXIETHERNET::TAPNetworkInterface::RecvStart() { readEnabled.Set(); } -void CEXIETHERNET::RecvStop() +void CEXIETHERNET::TAPNetworkInterface::RecvStop() { readEnabled.Clear(); } diff --git a/Source/Core/Core/HW/EXI/BBA-TAP/TAP_Win32.h b/Source/Core/Core/HW/EXI/BBA/TAP_Win32.h similarity index 100% rename from Source/Core/Core/HW/EXI/BBA-TAP/TAP_Win32.h rename to Source/Core/Core/HW/EXI/BBA/TAP_Win32.h diff --git a/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp b/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp new file mode 100644 index 0000000000..d02c139f31 --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/XLINK_KAI_BBA.cpp @@ -0,0 +1,306 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/Assert.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" +#include "Core/HW/EXI/EXI_DeviceEthernet.h" + +#include "VideoCommon/OnScreenDisplay.h" + +#include + +#include + +// BBA implementation with UDP interface to XLink Kai PC/MAC/RaspberryPi client +// For more information please see: https://www.teamxlink.co.uk/wiki/Emulator_Integration_Protocol +// Still have questions? Please email crunchbite@teamxlink.co.uk +// When editing this file please maintain proper capitalization of "XLink Kai" + +namespace ExpansionInterface +{ +bool CEXIETHERNET::XLinkNetworkInterface::Activate() +{ + if (IsActivated()) + return true; + + if (m_sf_socket.bind(sf::Socket::AnyPort) != sf::Socket::Done) + { + ERROR_LOG(SP1, "Couldn't open XLink Kai UDP socket, unable to initialize BBA"); + return false; + } + + m_sf_recipient_ip = m_dest_ip.c_str(); + + // Send connect command with unique local name + // connect;locally_unique_name;emulator_name;optional_padding + u8 buffer[255] = {}; + std::string cmd = + "connect;" + m_client_identifier + ";dolphin;000000000000000000000000000000000000000000"; + + const auto size = u32(cmd.length()); + memmove(buffer, cmd.c_str(), size); + + DEBUG_LOG(SP1, "SendCommandPayload %x\n%s", size, ArrayToString(buffer, size, 0x10).c_str()); + + if (m_sf_socket.send(buffer, size, m_sf_recipient_ip, m_dest_port) != sf::Socket::Done) + { + ERROR_LOG(SP1, "Activate(): failed to send connect message to XLink Kai client"); + } + + INFO_LOG(SP1, "BBA initialized."); + + return RecvInit(); +} + +void CEXIETHERNET::XLinkNetworkInterface::Deactivate() +{ + // Send d; to tell XLink we want to disconnect cleanly + // disconnect;optional_locally_unique_name;optional_padding + std::string cmd = + "disconnect;" + m_client_identifier + ";0000000000000000000000000000000000000000000"; + const auto size = u32(cmd.length()); + u8 buffer[255] = {}; + memmove(buffer, cmd.c_str(), size); + + DEBUG_LOG(SP1, "SendCommandPayload %x\n%s", size, ArrayToString(buffer, size, 0x10).c_str()); + + if (m_sf_socket.send(buffer, size, m_sf_recipient_ip, m_dest_port) != sf::Socket::Done) + { + ERROR_LOG(SP1, "Deactivate(): failed to send disconnect message to XLink Kai client"); + } + + NOTICE_LOG(SP1, "XLink Kai BBA deactivated"); + + m_bba_link_up = false; + + // Disable socket blocking + m_sf_socket.setBlocking(false); + + // Set flags for clean shutdown of the read thread + m_read_enabled.Clear(); + m_read_thread_shutdown.Set(); + + // Detach the thread and let it die on its own (leaks ~9kb of memory) + m_read_thread.detach(); + + // Close the socket + m_sf_socket.unbind(); +} + +bool CEXIETHERNET::XLinkNetworkInterface::IsActivated() +{ + return m_sf_socket.getLocalPort() > 0; +} + +bool CEXIETHERNET::XLinkNetworkInterface::SendFrame(const u8* frame, u32 size) +{ + // Is the BBA actually connected? + if (!m_bba_link_up) + { + // Has the user been told the connection failed? + if (!m_bba_failure_notified) + { + OSD::AddMessage("XLink Kai BBA not connected", 30000); + m_bba_failure_notified = true; + } + + // Return true because this isnt an error dolphin needs to handle + return true; + } + + // Overwrite buffer and add header + memmove(m_out_frame, "e;e;", 4); + memmove(m_out_frame + 4, frame, size); + size += 4; + + // Only uncomment for debugging, the performance hit is too big otherwise + // INFO_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(m_out_frame, size, 0x10).c_str()); + + if (m_sf_socket.send(m_out_frame, size, m_sf_recipient_ip, m_dest_port) != sf::Socket::Done) + { + ERROR_LOG(SP1, "SendFrame(): expected to write %u bytes, but failed, errno %d", size, errno); + return false; + } + else + { + m_eth_ref->SendComplete(); + return true; + } +} + +void CEXIETHERNET::XLinkNetworkInterface::ReadThreadHandler( + CEXIETHERNET::XLinkNetworkInterface* self) +{ + sf::IpAddress sender; + u16 port; + + while (!self->m_read_thread_shutdown.IsSet()) + { + if (!self->IsActivated()) + break; + + // XLink supports jumboframes but Gamecube BBA does not. We need to support jumbo frames + // *here* because XLink *could* send one + std::size_t bytes_read = 0; + if (self->m_sf_socket.receive(self->m_in_frame, std::size(self->m_in_frame), bytes_read, sender, + port) != sf::Socket::Done && + self->m_bba_link_up) + { + ERROR_LOG(SP1, "Failed to read from BBA, err=%zu", bytes_read); + } + + // Make sure *anything* was recieved before going any further + if (bytes_read < 1) + continue; + + // Did we get an ethernet frame? Copy the first 4 bytes to check the header + char temp_check[4]; + memmove(temp_check, self->m_in_frame, std::size(temp_check)); + + // Check for e;e; header; this indicates the received data is an ethernet frame + // e;e;raw_ethernet_frame_data + if (temp_check[2] == 'e' && temp_check[3] == ';') + { + // Is the frame larger than BBA_RECV_SIZE? + if ((bytes_read - 4) < BBA_RECV_SIZE) + { + // Copy payload into BBA buffer as an ethernet frame + memmove(self->m_eth_ref->mRecvBuffer.get(), self->m_in_frame + 4, bytes_read - 4); + + // Check the frame size again after the header is removed + if (bytes_read < 1) + { + ERROR_LOG(SP1, "Failed to read from BBA, err=%zu", bytes_read - 4); + } + else if (self->m_read_enabled.IsSet()) + { + // Only uncomment for debugging, the performance hit is too big otherwise + // DEBUG_LOG(SP1, "Read data: %s", ArrayToString(self->m_eth_ref->mRecvBuffer.get(), + // u32(bytes_read - 4), 0x10).c_str()); + self->m_eth_ref->mRecvBufferLength = u32(bytes_read - 4); + self->m_eth_ref->RecvHandlePacket(); + } + } + } + // Otherwise we recieved control data or junk + else + { + std::string control_msg(self->m_in_frame, self->m_in_frame + bytes_read); + INFO_LOG(SP1, "Received XLink Kai control data: %s", control_msg.c_str()); + + // connected;identifier; + if (StringBeginsWith(control_msg, "connected")) + { + NOTICE_LOG(SP1, "XLink Kai BBA connected"); + OSD::AddMessage("XLink Kai BBA connected", 4500); + + self->m_bba_link_up = true; + // TODO (in EXI_DeviceEthernet.cpp) bring the BBA link up here + + // Send any extra settings now + // Enable XLink chat messages in OSD if set + if (self->m_chat_osd_enabled) + { + constexpr std::string_view cmd = "setting;chat;true;"; + const auto size = u32(cmd.length()); + u8 buffer[255] = {}; + memmove(buffer, cmd.data(), size); + + DEBUG_LOG(SP1, "SendCommandPayload %x\n%s", size, + ArrayToString(buffer, size, 0x10).c_str()); + + if (self->m_sf_socket.send(buffer, size, self->m_sf_recipient_ip, self->m_dest_port) != + sf::Socket::Done) + { + ERROR_LOG(SP1, + "ReadThreadHandler(): failed to send setting message to XLink Kai client"); + } + } + } + // disconnected;optional_identifier;optional_message; + else if (StringBeginsWith(control_msg, "disconnected")) + { + NOTICE_LOG(SP1, "XLink Kai BBA disconnected"); + // Show OSD message for 15 seconds to make sure the user sees it + OSD::AddMessage("XLink Kai BBA disconnected", 15000); + + // TODO (TODO in EXI_DeviceEthernet.cpp) bring the BBA link down here + self->m_bba_link_up = false; + + // Disable socket blocking + self->m_sf_socket.setBlocking(false); + + // Shut down the read thread + self->m_read_enabled.Clear(); + self->m_read_thread_shutdown.Set(); + + // Close the socket + self->m_sf_socket.unbind(); + break; + } + // keepalive; + else if (StringBeginsWith(control_msg, "keepalive")) + { + DEBUG_LOG(SP1, "XLink Kai BBA keepalive"); + + // Only uncomment for debugging, just clogs the log otherwise + // INFO_LOG(SP1, "SendCommandPayload %x\n%s", 2, ArrayToString(m_in_frame, 2, + // 0x10).c_str()); + + // Reply (using the message that came in!) + if (self->m_sf_socket.send(self->m_in_frame, 10, self->m_sf_recipient_ip, + self->m_dest_port) != sf::Socket::Done) + { + ERROR_LOG(SP1, "ReadThreadHandler(): failed to reply to XLink Kai client keepalive"); + } + } + // message;message_text; + else if (StringBeginsWith(control_msg, "message")) + { + std::string msg = control_msg.substr(8, control_msg.length() - 1); + + NOTICE_LOG(SP1, "XLink Kai message: %s", msg.c_str()); + // Show OSD message for 15 seconds to make sure the user sees it + OSD::AddMessage(std::move(msg), 15000); + } + // chat;message_text; + else if (StringBeginsWith(control_msg, "chat")) + { + std::string msg = control_msg.substr(5, control_msg.length() - 1); + + NOTICE_LOG(SP1, "XLink Kai chat: %s", msg.c_str()); + OSD::AddMessage(std::move(msg), 5000); + } + // directmessage;message_text; + else if (StringBeginsWith(control_msg, "directmessage")) + { + std::string msg = control_msg.substr(14, control_msg.length() - 1); + + NOTICE_LOG(SP1, "XLink Kai direct message: %s", msg.c_str()); + OSD::AddMessage(std::move(msg), 5000); + } + // else junk/unsupported control message + } + } +} + +bool CEXIETHERNET::XLinkNetworkInterface::RecvInit() +{ + m_read_thread = std::thread(ReadThreadHandler, this); + return true; +} + +void CEXIETHERNET::XLinkNetworkInterface::RecvStart() +{ + m_read_enabled.Set(); +} + +void CEXIETHERNET::XLinkNetworkInterface::RecvStop() +{ + m_read_enabled.Clear(); +} +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp index 83d1362be9..c590b75c3b 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp @@ -133,7 +133,11 @@ std::unique_ptr EXIDevice_Create(const TEXIDevices device_type, cons break; case EXIDEVICE_ETH: - result = std::make_unique(); + result = std::make_unique(BBADeviceType::TAP); + break; + + case EXIDEVICE_ETHXLINK: + result = std::make_unique(BBADeviceType::XLINK); break; case EXIDEVICE_GECKO: diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h index 9312643945..cf3476c15b 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.h +++ b/Source/Core/Core/HW/EXI/EXI_Device.h @@ -32,6 +32,7 @@ enum TEXIDevices : int // Converted to EXIDEVICE_MEMORYCARD internally. EXIDEVICE_MEMORYCARDFOLDER, EXIDEVICE_AGP, + EXIDEVICE_ETHXLINK, EXIDEVICE_NONE = 0xFF }; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp index 1972e3e3ea..bba4f38791 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp @@ -4,6 +4,7 @@ #include "Core/HW/EXI/EXI_DeviceEthernet.h" +#include #include #include #include @@ -12,6 +13,7 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/Network.h" +#include "Common/StringUtil.h" #include "Core/ConfigManager.h" #include "Core/CoreTiming.h" #include "Core/HW/EXI/EXI.h" @@ -23,19 +25,16 @@ namespace ExpansionInterface // Multiple parts of this implementation depend on Dolphin // being compiled for a little endian host. -CEXIETHERNET::CEXIETHERNET() +CEXIETHERNET::CEXIETHERNET(BBADeviceType type) { - tx_fifo = std::make_unique(BBA_TXFIFO_SIZE); - mBbaMem = std::make_unique(BBA_MEM_SIZE); - mRecvBuffer = std::make_unique(BBA_RECV_SIZE); - - MXHardReset(); - // Parse MAC address from config, and generate a new one if it doesn't // exist or can't be parsed. std::string& mac_addr_setting = SConfig::GetInstance().m_bba_mac; std::optional mac_addr = Common::StringToMacAddress(mac_addr_setting); + std::transform(mac_addr_setting.begin(), mac_addr_setting.end(), mac_addr_setting.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (!mac_addr) { mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA); @@ -43,6 +42,42 @@ CEXIETHERNET::CEXIETHERNET() SConfig::GetInstance().SaveSettings(); } + switch (type) + { + case BBADeviceType::TAP: + m_network_interface = std::make_unique(this); + INFO_LOG(SP1, "Created TAP physical network interface."); + break; + case BBADeviceType::XLINK: + // TODO start BBA with network link down, bring it up after "connected" response from XLink + + // Perform sanity check on BBA MAC address, XLink requires the vendor OUI to be Nintendo's and + // to be one of the two used for the GameCube. + // Don't actually stop the BBA from initializing though + if (!StringBeginsWith(mac_addr_setting, "00:09:bf") && + !StringBeginsWith(mac_addr_setting, "00:17:ab")) + { + PanicAlertT("BBA MAC address %s invalid for XLink Kai. A valid Nintendo GameCube MAC address " + "must be used. Generate a new MAC address starting with 00:09:bf or 00:17:ab.", + mac_addr_setting.c_str()); + } + + // m_client_mdentifier should be unique per connected emulator from the XLink kai client's + // perspective so lets use "dolphin" + m_network_interface = std::make_unique( + this, SConfig::GetInstance().m_bba_xlink_ip, 34523, + "dolphin" + SConfig::GetInstance().m_bba_mac, SConfig::GetInstance().m_bba_xlink_chat_osd); + INFO_LOG(SP1, "Created XLink Kai BBA network interface connection to %s:34523", + SConfig::GetInstance().m_bba_xlink_ip.c_str()); + break; + } + + tx_fifo = std::make_unique(BBA_TXFIFO_SIZE); + mBbaMem = std::make_unique(BBA_MEM_SIZE); + mRecvBuffer = std::make_unique(BBA_RECV_SIZE); + + MXHardReset(); + const auto& mac = mac_addr.value(); memcpy(&mBbaMem[BBA_NAFR_PAR0], mac.data(), mac.size()); @@ -52,7 +87,7 @@ CEXIETHERNET::CEXIETHERNET() CEXIETHERNET::~CEXIETHERNET() { - Deactivate(); + m_network_interface->Deactivate(); } void CEXIETHERNET::SetCS(int cs) @@ -303,7 +338,7 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size) { INFO_LOG(SP1, "Software reset"); // MXSoftReset(); - Activate(); + m_network_interface->Activate(); } if ((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR)) @@ -311,9 +346,9 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size) DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop"); if (data & NCRA_SR) - RecvStart(); + m_network_interface->RecvStart(); else - RecvStop(); + m_network_interface->RecvStop(); } // Only start transfer if there isn't one currently running @@ -386,7 +421,7 @@ void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size) void CEXIETHERNET::SendFromDirectFIFO() { - SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]); + m_network_interface->SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]); } void CEXIETHERNET::SendFromPacketBuffer() @@ -579,7 +614,7 @@ bool CEXIETHERNET::RecvHandlePacket() wait_for_next: if (mBbaMem[BBA_NCRA] & NCRA_SR) - RecvStart(); + m_network_interface->RecvStart(); return true; } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 38a27bd7e0..ed79b9a9b1 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -12,6 +12,8 @@ #include #endif +#include + #include "Common/Flag.h" #include "Core/HW/EXI/EXI_Device.h" @@ -196,10 +198,16 @@ enum RecvStatus #define BBA_RECV_SIZE 0x800 +enum class BBADeviceType +{ + TAP, + XLINK, +}; + class CEXIETHERNET : public IEXIDevice { public: - CEXIETHERNET(); + explicit CEXIETHERNET(BBADeviceType type); virtual ~CEXIETHERNET(); void SetCS(int cs) override; bool IsPresent() const override; @@ -297,34 +305,99 @@ private: std::unique_ptr mBbaMem; std::unique_ptr tx_fifo; - // TAP interface - static void ReadThreadHandler(CEXIETHERNET* self); - bool Activate(); - void Deactivate(); - bool IsActivated(); - bool SendFrame(const u8* frame, u32 size); - bool RecvInit(); - void RecvStart(); - void RecvStop(); + class NetworkInterface + { + protected: + CEXIETHERNET* m_eth_ref = nullptr; + explicit NetworkInterface(CEXIETHERNET* eth_ref) : m_eth_ref{eth_ref} {} + + public: + virtual bool Activate() { return false; } + virtual void Deactivate() {} + virtual bool IsActivated() { return false; } + virtual bool SendFrame(const u8* frame, u32 size) { return false; } + virtual bool RecvInit() { return false; } + virtual void RecvStart() {} + virtual void RecvStop() {} + + virtual ~NetworkInterface() = default; + }; + + class TAPNetworkInterface : public NetworkInterface + { + public: + explicit TAPNetworkInterface(CEXIETHERNET* eth_ref) : NetworkInterface(eth_ref) {} + + public: + bool Activate() override; + void Deactivate() override; + bool IsActivated() override; + bool SendFrame(const u8* frame, u32 size) override; + bool RecvInit() override; + void RecvStart() override; + void RecvStop() override; + + private: +#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ + defined(__OpenBSD__) + std::thread readThread; + Common::Flag readEnabled; + Common::Flag readThreadShutdown; + static void ReadThreadHandler(TAPNetworkInterface* self); +#endif +#if defined(_WIN32) + HANDLE mHAdapter = INVALID_HANDLE_VALUE; + OVERLAPPED mReadOverlapped = {}; + OVERLAPPED mWriteOverlapped = {}; + std::vector mWriteBuffer; + bool mWritePending = false; +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) + int fd = -1; +#endif + }; + + class XLinkNetworkInterface : public NetworkInterface + { + public: + XLinkNetworkInterface(CEXIETHERNET* eth_ref, std::string dest_ip, int dest_port, + std::string identifier, bool chat_osd_enabled) + : NetworkInterface(eth_ref), m_dest_ip(std::move(dest_ip)), m_dest_port(dest_port), + m_client_identifier(identifier), m_chat_osd_enabled(chat_osd_enabled) + { + } + + public: + bool Activate() override; + void Deactivate() override; + bool IsActivated() override; + bool SendFrame(const u8* frame, u32 size) override; + bool RecvInit() override; + void RecvStart() override; + void RecvStop() override; + + private: + std::string m_dest_ip; + int m_dest_port; + std::string m_client_identifier; + bool m_chat_osd_enabled; + bool m_bba_link_up = false; + bool m_bba_failure_notified = false; +#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ + defined(__OpenBSD__) + sf::UdpSocket m_sf_socket; + sf::IpAddress m_sf_recipient_ip; + char m_in_frame[9004]; + char m_out_frame[9004]; + std::thread m_read_thread; + Common::Flag m_read_enabled; + Common::Flag m_read_thread_shutdown; + static void ReadThreadHandler(XLinkNetworkInterface* self); +#endif + }; + + std::unique_ptr m_network_interface; std::unique_ptr mRecvBuffer; u32 mRecvBufferLength = 0; - -#if defined(_WIN32) - HANDLE mHAdapter = INVALID_HANDLE_VALUE; - OVERLAPPED mReadOverlapped = {}; - OVERLAPPED mWriteOverlapped = {}; - std::vector mWriteBuffer; - bool mWritePending = false; -#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) - int fd = -1; -#endif - -#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ - defined(__OpenBSD__) - std::thread readThread; - Common::Flag readEnabled; - Common::Flag readThreadShutdown; -#endif }; } // namespace ExpansionInterface diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 1304793432..abcec0d0a8 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -103,7 +103,9 @@ void GameCubePane::CreateWidgets() for (const auto& entry : {std::make_pair(tr(""), ExpansionInterface::EXIDEVICE_NONE), std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY), - std::make_pair(tr("Broadband Adapter"), ExpansionInterface::EXIDEVICE_ETH)}) + std::make_pair(tr("Broadband Adapter (TAP)"), ExpansionInterface::EXIDEVICE_ETH), + std::make_pair(tr("Broadband Adapter (XLink Kai)"), + ExpansionInterface::EXIDEVICE_ETHXLINK)}) { m_slot_combos[2]->addItem(entry.first, entry.second); } @@ -158,7 +160,8 @@ void GameCubePane::UpdateButton(int slot) value == ExpansionInterface::EXIDEVICE_AGP || value == ExpansionInterface::EXIDEVICE_MIC); break; case SLOT_SP1_INDEX: - has_config = (value == ExpansionInterface::EXIDEVICE_ETH); + has_config = (value == ExpansionInterface::EXIDEVICE_ETH || + value == ExpansionInterface::EXIDEVICE_ETHXLINK); break; } @@ -186,12 +189,26 @@ void GameCubePane::OnConfigPressed(int slot) { bool ok; const auto new_mac = QInputDialog::getText( + // i18n: MAC stands for Media Access Control. A MAC address uniquely identifies a network + // interface (physical) like a serial number. "MAC" should be kept in translations. this, tr("Broadband Adapter MAC address"), tr("Enter new Broadband Adapter MAC address:"), QLineEdit::Normal, QString::fromStdString(SConfig::GetInstance().m_bba_mac), &ok); if (ok) SConfig::GetInstance().m_bba_mac = new_mac.toStdString(); return; } + case ExpansionInterface::EXIDEVICE_ETHXLINK: + { + bool ok; + const auto new_dest = QInputDialog::getText( + this, tr("Broadband Adapter (XLink Kai) Destination Address"), + tr("Enter IP address of device running the XLink Kai Client.\nFor more information see" + " https://www.teamxlink.co.uk/wiki/Dolphin"), + QLineEdit::Normal, QString::fromStdString(SConfig::GetInstance().m_bba_xlink_ip), &ok); + if (ok) + SConfig::GetInstance().m_bba_xlink_ip = new_dest.toStdString(); + return; + } default: qFatal("unknown settings pressed"); }