diff --git a/.gitmodules b/.gitmodules
index deef328f15..b7de90145e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -114,6 +114,10 @@
path = Externals/pugixml/pugixml
url = https://github.com/zeux/pugixml.git
shallow = true
+[submodule "Externals/cpp-ipc/cpp-ipc"]
+ path = Externals/cpp-ipc/cpp-ipc
+ url = https://github.com/mutouyun/cpp-ipc.git
+ shallow = true
[submodule "Externals/cpp-optparse/cpp-optparse"]
path = Externals/cpp-optparse/cpp-optparse
url = https://github.com/weisslj/cpp-optparse.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7c708e9987..2f4572f5f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -793,6 +793,10 @@ endif()
add_subdirectory(Externals/watcher)
+if(WIN32 OR LINUX)
+ add_subdirectory(Externals/cpp-ipc)
+endif()
+
########################################
# Pre-build events: Define configuration variables and write SCM info header
#
diff --git a/Externals/cpp-ipc/CMakeLists.txt b/Externals/cpp-ipc/CMakeLists.txt
new file mode 100644
index 0000000000..ab776d4f89
--- /dev/null
+++ b/Externals/cpp-ipc/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_subdirectory(cpp-ipc)
+
+dolphin_disable_warnings(ipc)
+
+if (NOT MSVC)
+ target_compile_options(ipc PRIVATE "-fexceptions")
+endif ()
+
+add_library(cpp-ipc::ipc ALIAS ipc)
diff --git a/Externals/cpp-ipc/cpp-ipc b/Externals/cpp-ipc/cpp-ipc
new file mode 160000
index 0000000000..a0c7725a14
--- /dev/null
+++ b/Externals/cpp-ipc/cpp-ipc
@@ -0,0 +1 @@
+Subproject commit a0c7725a1441d18bc768d748a93e512a0fa7ab52
diff --git a/Externals/cpp-ipc/cpp-ipc.vcxproj b/Externals/cpp-ipc/cpp-ipc.vcxproj
new file mode 100644
index 0000000000..61dc5eda6e
--- /dev/null
+++ b/Externals/cpp-ipc/cpp-ipc.vcxproj
@@ -0,0 +1,78 @@
+
+
+
+
+
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ cpp-ipc\include;cpp-ipc\src;%(AdditionalIncludeDirectories)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/cpp-ipc/exports.props b/Externals/cpp-ipc/exports.props
new file mode 100644
index 0000000000..bbcdc43b66
--- /dev/null
+++ b/Externals/cpp-ipc/exports.props
@@ -0,0 +1,13 @@
+
+
+
+
+ $(ExternalsDir)cpp-ipc\cpp-ipc\include;%(AdditionalIncludeDirectories)
+
+
+
+
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}
+
+
+
diff --git a/Externals/licenses.md b/Externals/licenses.md
index b3d1913fd3..727a41e2a1 100644
--- a/Externals/licenses.md
+++ b/Externals/licenses.md
@@ -8,6 +8,8 @@ Dolphin includes or links code of the following third-party software projects:
[LGPLv2.1+](http://bochs.sourceforge.net/cgi-bin/lxr/source/COPYING)
- [bzip2](https://www.sourceware.org/bzip2/):
[bzip2 license](https://www.sourceware.org/git/?p=bzip2.git;a=blob;f=LICENSE;hb=HEAD) (similar to 3-clause BSD)
+- [cpp-ipc](https://github.com/mutouyun/cpp-ipc):
+ [MIT](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE)
- [cubeb](https://github.com/kinetiknz/cubeb):
[ISC](https://github.com/kinetiknz/cubeb/blob/master/LICENSE)
- [Discord-RPC](https://github.com/discordapp/discord-rpc):
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 6e7ff1a0bc..cb2a3ac58a 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -797,6 +797,11 @@ if(UNIX)
)
endif()
+if(WIN32 OR LINUX)
+ target_sources(core PRIVATE HW/EXI/BBA/IPC.cpp)
+ target_link_libraries(core PRIVATE cpp-ipc::ipc)
+endif()
+
if(MSVC)
# Add precompiled header
target_link_libraries(core PRIVATE use_pch)
diff --git a/Source/Core/Core/HW/EXI/BBA/IPC.cpp b/Source/Core/Core/HW/EXI/BBA/IPC.cpp
new file mode 100644
index 0000000000..e23795e7eb
--- /dev/null
+++ b/Source/Core/Core/HW/EXI/BBA/IPC.cpp
@@ -0,0 +1,101 @@
+// Copyright 2025 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+
+#include
+
+#include "Common/Logging/Log.h"
+#include "Core/HW/EXI/EXI_DeviceEthernet.h"
+
+namespace ExpansionInterface
+{
+
+bool CEXIETHERNET::IPCBBAInterface::Activate()
+{
+ if (m_active)
+ return false;
+
+ m_channel.connect("dolphin-emu-bba-ipc", ipc::sender | ipc::receiver);
+ if (!m_channel.valid())
+ return false;
+
+ m_read_enabled.Clear();
+ m_read_thread_shutdown.Clear();
+
+ m_active = true;
+
+ return RecvInit();
+}
+
+void CEXIETHERNET::IPCBBAInterface::Deactivate()
+{
+ m_read_enabled.Clear();
+ m_read_thread_shutdown.Set();
+
+ if (m_read_thread.joinable())
+ {
+ m_read_thread.join();
+ }
+
+ m_channel.disconnect();
+
+ m_active = false;
+}
+
+bool CEXIETHERNET::IPCBBAInterface::IsActivated()
+{
+ return m_active;
+}
+
+bool CEXIETHERNET::IPCBBAInterface::SendFrame(const u8* const frame, const u32 size)
+{
+ if (!m_active)
+ return false;
+
+ static constexpr u64 TIMEOUT_IN_MS{3000};
+ if (!m_channel.send(frame, size, TIMEOUT_IN_MS))
+ {
+ ERROR_LOG_FMT(SP1, "Failed to send frame");
+ return false;
+ }
+
+ m_eth_ref->SendComplete();
+
+ return true;
+}
+
+bool CEXIETHERNET::IPCBBAInterface::RecvInit()
+{
+ m_read_thread = std::thread(&CEXIETHERNET::IPCBBAInterface::ReadThreadHandler, this);
+ return true;
+}
+
+void CEXIETHERNET::IPCBBAInterface::RecvStart()
+{
+ m_read_enabled.Set();
+}
+
+void CEXIETHERNET::IPCBBAInterface::RecvStop()
+{
+ m_read_enabled.Clear();
+}
+
+void CEXIETHERNET::IPCBBAInterface::ReadThreadHandler()
+{
+ while (!m_read_thread_shutdown.IsSet())
+ {
+ const ipc::buff_t buffer{m_channel.recv(50)};
+ if (buffer.empty() || !m_read_enabled.IsSet())
+ continue;
+
+ const u8* const frame{reinterpret_cast(buffer.data())};
+ const u64 size{buffer.size()};
+
+ std::memcpy(m_eth_ref->mRecvBuffer.get(), frame, size);
+ m_eth_ref->mRecvBufferLength = static_cast(size);
+ m_eth_ref->RecvHandlePacket();
+ }
+}
+
+} // namespace ExpansionInterface
diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp
index ac92c7b723..c1300a363f 100644
--- a/Source/Core/Core/HW/EXI/EXI_Device.cpp
+++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp
@@ -160,6 +160,10 @@ std::unique_ptr EXIDevice_Create(Core::System& system, const EXIDevi
result = std::make_unique(system, BBADeviceType::BuiltIn);
break;
+ case EXIDeviceType::EthernetIPC:
+ result = std::make_unique(system, BBADeviceType::IPC);
+ break;
+
case EXIDeviceType::ModemTapServer:
result = std::make_unique(system, ModemDeviceType::TAPSERVER);
break;
diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h
index 25263af425..129f7a2308 100644
--- a/Source/Core/Core/HW/EXI/EXI_Device.h
+++ b/Source/Core/Core/HW/EXI/EXI_Device.h
@@ -40,6 +40,7 @@ enum class EXIDeviceType : int
EthernetTapServer,
EthernetBuiltIn,
ModemTapServer,
+ EthernetIPC,
None = 0xFF
};
@@ -86,7 +87,7 @@ std::unique_ptr EXIDevice_Create(Core::System& system, EXIDeviceType
template <>
struct fmt::formatter
- : EnumFormatter
+ : EnumFormatter
{
static constexpr array_type names = {
_trans("Dummy"),
@@ -104,6 +105,7 @@ struct fmt::formatter
_trans("Broadband Adapter (tapserver)"),
_trans("Broadband Adapter (HLE)"),
_trans("Modem Adapter (tapserver)"),
+ _trans("Broadband Adapter (IPC)"),
};
constexpr formatter() : EnumFormatter(names) {}
diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
index 202dc557f8..311f1e2226 100644
--- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
+++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
@@ -60,6 +60,11 @@ CEXIETHERNET::CEXIETHERNET(Core::System& system, BBADeviceType type) : IEXIDevic
this, Config::Get(Config::MAIN_BBA_BUILTIN_DNS), Config::Get(Config::MAIN_BBA_BUILTIN_IP));
INFO_LOG_FMT(SP1, "Created Built in network interface.");
break;
+ case BBADeviceType::IPC:
+ mac_addr = Common::GenerateMacAddress(Common::MACConsumer::BBA); // Always randomize
+ m_network_interface = std::make_unique(this);
+ INFO_LOG_FMT(SP1, "Created IPC-based network interface.");
+ break;
case BBADeviceType::XLINK:
// TODO start BBA with network link down, bring it up after "connected" response from XLink
diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
index 21852f341c..67a0317831 100644
--- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
+++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
@@ -15,6 +15,9 @@
#endif
#include
+#if defined(WIN32) || (defined(__linux__) && !defined(__ANDROID__))
+#include
+#endif
#include "Common/Flag.h"
#include "Common/Network.h"
@@ -210,6 +213,7 @@ enum class BBADeviceType
XLINK,
TAPSERVER,
BuiltIn,
+ IPC,
};
class CEXIETHERNET : public IEXIDevice
@@ -474,6 +478,43 @@ private:
const Common::MACAddress& ResolveAddress(u32 inet_ip);
};
+ class IPCBBAInterface : public NetworkInterface
+ {
+ public:
+ explicit IPCBBAInterface(CEXIETHERNET* const eth_ref) : NetworkInterface(eth_ref) {}
+
+#if defined(WIN32) || (defined(__linux__) && !defined(__ANDROID__))
+
+ 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:
+ void ReadThreadHandler();
+
+ bool m_active{};
+ ipc::channel m_channel;
+ std::thread m_read_thread;
+ Common::Flag m_read_enabled;
+ Common::Flag m_read_thread_shutdown;
+
+#else
+
+ bool Activate() override { return false; }
+ void Deactivate() override {}
+ bool IsActivated() override { return false; }
+ bool SendFrame(const u8* const frame, const u32 size) override { return false; }
+ bool RecvInit() override { return false; }
+ void RecvStart() override {}
+ void RecvStop() override {}
+
+#endif
+ };
+
std::unique_ptr m_network_interface;
std::unique_ptr mRecvBuffer;
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 062d7f979a..35d170a0d5 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -962,6 +962,7 @@
+
diff --git a/Source/Core/DolphinLib.vcxproj b/Source/Core/DolphinLib.vcxproj
index 0d4925c47e..104b2d86ed 100644
--- a/Source/Core/DolphinLib.vcxproj
+++ b/Source/Core/DolphinLib.vcxproj
@@ -30,6 +30,7 @@
+
diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
index c84475600e..3df220243b 100644
--- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp
+++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
@@ -143,6 +143,9 @@ void GameCubePane::CreateWidgets()
EXIDeviceType::EthernetXLink,
EXIDeviceType::EthernetTapServer,
EXIDeviceType::EthernetBuiltIn,
+#if defined(WIN32) || (defined(__linux__) && !defined(__ANDROID__))
+ EXIDeviceType::EthernetIPC,
+#endif
EXIDeviceType::ModemTapServer,
})
{
diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln
index f1183c287f..64650e330f 100644
--- a/Source/dolphin-emu.sln
+++ b/Source/dolphin-emu.sln
@@ -49,6 +49,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "curl", "..\Externals\curl\c
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glslang", "..\Externals\glslang\glslang.vcxproj", "{D178061B-84D3-44F9-BEED-EFD18D9033F0}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpp-ipc", "..\Externals\cpp-ipc\cpp-ipc.vcxproj", "{7299DDD3-BBEC-4027-AF30-8DACC5415F96}"
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cpp-optparse", "..\Externals\cpp-optparse\cpp-optparse.vcxproj", "{C636D9D1-82FE-42B5-9987-63B7D4836341}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cubeb", "..\Externals\cubeb\msvc\cubeb.vcxproj", "{8EA11166-6512-44FC-B7A5-A4D1ECC81170}"
@@ -271,6 +273,14 @@ Global
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|ARM64.Build.0 = Release|ARM64
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x64.ActiveCfg = Release|x64
{D178061B-84D3-44F9-BEED-EFD18D9033F0}.Release|x64.Build.0 = Release|x64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Debug|ARM64.Build.0 = Debug|ARM64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Debug|x64.ActiveCfg = Debug|x64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Debug|x64.Build.0 = Debug|x64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Release|ARM64.ActiveCfg = Release|ARM64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Release|ARM64.Build.0 = Release|ARM64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Release|x64.ActiveCfg = Release|x64
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96}.Release|x64.Build.0 = Release|x64
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|ARM64.ActiveCfg = Debug|ARM64
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|ARM64.Build.0 = Debug|ARM64
{C636D9D1-82FE-42B5-9987-63B7D4836341}.Debug|x64.ActiveCfg = Debug|x64
@@ -456,6 +466,7 @@ Global
{CBC76802-C128-4B17-BF6C-23B08C313E5E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{BB00605C-125F-4A21-B33B-7BF418322DCB} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{D178061B-84D3-44F9-BEED-EFD18D9033F0} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
+ {7299DDD3-BBEC-4027-AF30-8DACC5415F96} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{C636D9D1-82FE-42B5-9987-63B7D4836341} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{8EA11166-6512-44FC-B7A5-A4D1ECC81170} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{38FEE76F-F347-484B-949C-B4649381CFFB} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}