diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 01f5591258..a7c06b71bb 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -593,6 +593,7 @@ if(WIN32)
target_sources(core PRIVATE
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,6 +607,7 @@ if(WIN32)
elseif(APPLE)
target_sources(core PRIVATE
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,6 +616,7 @@ elseif(APPLE)
elseif(UNIX)
target_sources(core PRIVATE
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 3f3f1c238f..fa5e181efe 100644
--- a/Source/Core/Core/ConfigManager.cpp
+++ b/Source/Core/Core/ConfigManager.cpp
@@ -223,7 +223,9 @@ void SConfig::SaveCoreSettings(IniFile& ini)
core->Set("SlotA", m_EXIDevice[0]);
core->Set("SlotB", m_EXIDevice[1]);
core->Set("SerialPort1", m_EXIDevice[2]);
- core->Set("BBA_TAP_MAC", m_bba_tap_mac);
+ 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]);
@@ -488,7 +490,9 @@ void SConfig::LoadCoreSettings(IniFile& ini)
core->Get("SlotA", (int*)&m_EXIDevice[0], ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER);
core->Get("SlotB", (int*)&m_EXIDevice[1], ExpansionInterface::EXIDEVICE_NONE);
core->Get("SerialPort1", (int*)&m_EXIDevice[2], ExpansionInterface::EXIDEVICE_NONE);
- core->Get("BBA_TAP_MAC", &m_bba_tap_mac);
+ 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 9c60dd50a1..58073b8d42 100644
--- a/Source/Core/Core/ConfigManager.h
+++ b/Source/Core/Core/ConfigManager.h
@@ -227,7 +227,9 @@ struct SConfig
ExpansionInterface::TEXIDevices m_EXIDevice[3];
SerialInterface::SIDevices m_SIDevice[4];
- std::string m_bba_tap_mac;
+ 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 4828f5d1dc..3b192dd8ff 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -141,6 +141,7 @@
+
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index 9f64208ece..d13d032ac1 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -993,6 +993,9 @@
PowerPC\Jit64
+
+ HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA
+
HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA
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 9995d6c156..5fe23b7fdd 100644
--- a/Source/Core/Core/HW/EXI/EXI_Device.cpp
+++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp
@@ -132,7 +132,11 @@ std::unique_ptr EXIDevice_Create(const TEXIDevices device_type, cons
break;
case EXIDEVICE_ETH:
- result = std::make_unique(BBADeviceType::BBA_TAP);
+ 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 0eb30904ca..bef1c53601 100644
--- a/Source/Core/Core/HW/EXI/EXI_Device.h
+++ b/Source/Core/Core/HW/EXI/EXI_Device.h
@@ -27,6 +27,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 d303dbcdfb..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"
@@ -25,11 +27,14 @@ namespace ExpansionInterface
CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
{
- // Parse BBA (TAP) MAC address from config, and generate a new one if it doesn't
+ // 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_tap_mac;
+ 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);
@@ -39,10 +44,32 @@ CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
switch (type)
{
- case BBADeviceType::BBA_TAP:
- network_interface = std::make_unique(this);
+ 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);
@@ -60,7 +87,7 @@ CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
CEXIETHERNET::~CEXIETHERNET()
{
- network_interface->Deactivate();
+ m_network_interface->Deactivate();
}
void CEXIETHERNET::SetCS(int cs)
@@ -311,7 +338,7 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
{
INFO_LOG(SP1, "Software reset");
// MXSoftReset();
- network_interface->Activate();
+ m_network_interface->Activate();
}
if ((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR))
@@ -319,9 +346,9 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
DEBUG_LOG(SP1, "%s rx", (data & NCRA_SR) ? "start" : "stop");
if (data & NCRA_SR)
- network_interface->RecvStart();
+ m_network_interface->RecvStart();
else
- network_interface->RecvStop();
+ m_network_interface->RecvStop();
}
// Only start transfer if there isn't one currently running
@@ -394,7 +421,7 @@ void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size)
void CEXIETHERNET::SendFromDirectFIFO()
{
- network_interface->SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]);
+ m_network_interface->SendFrame(tx_fifo.get(), *(u16*)&mBbaMem[BBA_TXFIFOCNT]);
}
void CEXIETHERNET::SendFromPacketBuffer()
@@ -587,7 +614,7 @@ bool CEXIETHERNET::RecvHandlePacket()
wait_for_next:
if (mBbaMem[BBA_NCRA] & NCRA_SR)
- network_interface->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 a4d7efa789..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"
@@ -198,7 +200,8 @@ enum RecvStatus
enum class BBADeviceType
{
- BBA_TAP,
+ TAP,
+ XLINK,
};
class CEXIETHERNET : public IEXIDevice
@@ -323,9 +326,7 @@ private:
class TAPNetworkInterface : public NetworkInterface
{
public:
- explicit TAPNetworkInterface(CEXIETHERNET* eth_ref) : NetworkInterface(eth_ref)
- {
- }
+ explicit TAPNetworkInterface(CEXIETHERNET* eth_ref) : NetworkInterface(eth_ref) {}
public:
bool Activate() override;
@@ -355,15 +356,46 @@ private:
#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__)
- std::thread readThread;
- Common::Flag readEnabled;
- Common::Flag readThreadShutdown;
+ 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 network_interface;
+ std::unique_ptr m_network_interface;
std::unique_ptr mRecvBuffer;
u32 mRecvBufferLength = 0;
diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
index d17009290f..4dcc5c1bbf 100644
--- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp
+++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
@@ -104,6 +104,8 @@ void GameCubePane::CreateWidgets()
{std::make_pair(tr(""), ExpansionInterface::EXIDEVICE_NONE),
std::make_pair(tr("Dummy"), ExpansionInterface::EXIDEVICE_DUMMY),
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,13 +189,24 @@ void GameCubePane::OnConfigPressed(int slot)
{
bool ok;
const auto new_mac = QInputDialog::getText(
- this, tr("Broadband Adapter (TAP) MAC address"),
- tr("Enter new Broadband Adapter (TAP) MAC address:"), QLineEdit::Normal,
- QString::fromStdString(SConfig::GetInstance().m_bba_tap_mac), &ok);
+ // 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_tap_mac = new_mac.toStdString();
+ 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: