diff --git a/.gitmodules b/.gitmodules index d9cdcb20bb6..594b64f20d7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,6 +14,10 @@ path = Externals/libusb/libusb url = https://github.com/libusb/libusb.git shallow = true +[submodule "Externals/libzmq/libzmq"] + path = Externals/libzmq/libzmq + url = https://github.com/zeromq/libzmq.git + shallow = true [submodule "Externals/spirv_cross/SPIRV-Cross"] path = Externals/spirv_cross/SPIRV-Cross url = https://github.com/KhronosGroup/SPIRV-Cross.git @@ -34,6 +38,10 @@ path = Externals/VulkanMemoryAllocator url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git shallow = true +[submodule "Externals/Cross-Compatible-FileLock-Windows-and-Linux"] + path = Externals/Cross-Compatible-FileLock-Windows-and-Linux + url = https://github.com/KaganCanSit/Cross-Compatible-FileLock-Windows-and-Linux.git + shallow = true [submodule "Externals/cubeb/cubeb"] path = Externals/cubeb/cubeb url = https://github.com/mozilla/cubeb.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fa3769f8fd..50cface0ffe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -786,8 +786,10 @@ endif() add_subdirectory(Externals/watcher) -if(WIN32 OR LINUX) +if(NOT ANDROID AND NOT APPLE) add_subdirectory(Externals/cpp-ipc) +elseif(APPLE) + add_subdirectory(Externals/libzmq) endif() ######################################## diff --git a/Externals/Cross-Compatible-FileLock-Windows-and-Linux b/Externals/Cross-Compatible-FileLock-Windows-and-Linux new file mode 160000 index 00000000000..42ffdc24b06 --- /dev/null +++ b/Externals/Cross-Compatible-FileLock-Windows-and-Linux @@ -0,0 +1 @@ +Subproject commit 42ffdc24b06a85383e1b7a20e09931d7849533e7 diff --git a/Externals/cpp-ipc/cpp-ipc b/Externals/cpp-ipc/cpp-ipc index a0c7725a144..ce0773b3e6d 160000 --- a/Externals/cpp-ipc/cpp-ipc +++ b/Externals/cpp-ipc/cpp-ipc @@ -1 +1 @@ -Subproject commit a0c7725a1441d18bc768d748a93e512a0fa7ab52 +Subproject commit ce0773b3e6d5abaa8d104100c5704321113853ca diff --git a/Externals/libzmq/CMakeLists.txt b/Externals/libzmq/CMakeLists.txt new file mode 100644 index 00000000000..09ae19aa8b3 --- /dev/null +++ b/Externals/libzmq/CMakeLists.txt @@ -0,0 +1,17 @@ +set(ENABLE_DRAFTS OFF) +set(BUILD_TESTS OFF) +set(WITH_DOCS OFF) +set(BUILD_SHARED OFF) +set(BUILD_STATIC ON) + +add_subdirectory(libzmq) + +dolphin_disable_warnings(objects) +dolphin_disable_warnings(libzmq-static) + +if (NOT MSVC) + target_compile_options(objects PRIVATE "-fexceptions") + target_compile_options(libzmq-static PRIVATE "-fexceptions") +endif () + +add_library(libzmq::libzmq ALIAS libzmq-static) diff --git a/Externals/libzmq/libzmq b/Externals/libzmq/libzmq new file mode 160000 index 00000000000..7a7bfa10e6b --- /dev/null +++ b/Externals/libzmq/libzmq @@ -0,0 +1 @@ +Subproject commit 7a7bfa10e6b0e99210ed9397369b59f9e69cef8e diff --git a/Externals/licenses.md b/Externals/licenses.md index 727a41e2a18..0eb4a892801 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -10,6 +10,8 @@ Dolphin includes or links code of the following third-party software projects: [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) +- [Cross-Compatible-FileLock-Windows-and-Linux](https://github.com/KaganCanSit/Cross-Compatible-FileLock-Windows-and-Linux): + [MIT](https://github.com/KaganCanSit/Cross-Compatible-FileLock-Windows-and-Linux/blob/main/LICENSE) - [cubeb](https://github.com/kinetiknz/cubeb): [ISC](https://github.com/kinetiknz/cubeb/blob/master/LICENSE) - [Discord-RPC](https://github.com/discordapp/discord-rpc): @@ -44,6 +46,8 @@ Dolphin includes or links code of the following third-party software projects: [University of Illinois/NCSA Open Source license](http://llvm.org/docs/DeveloperPolicy.html#license) - [LZO](http://www.oberhumer.com/opensource/lzo/): [GPLv2+](http://www.oberhumer.com/opensource/gpl.html) +- [libzmq](https://github.com/zeromq/libzmq): + [MPL 2.0](https://github.com/zeromq/libzmq/blob/master/LICENSE) - [mGBA](http://mgba.io) [MPL 2.0](https://github.com/mgba-emu/mgba/blob/master/LICENSE) - [MiniUPnPc](http://miniupnp.free.fr/): diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index be75ba89d95..1ea17ee670b 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -801,9 +801,13 @@ if(UNIX) ) endif() -if(WIN32 OR LINUX) +if(NOT ANDROID AND NOT APPLE) target_sources(core PRIVATE HW/EXI/BBA/IPC.cpp) target_link_libraries(core PRIVATE cpp-ipc::ipc) +elseif(APPLE) + target_sources(core PRIVATE HW/EXI/BBA/IPC_zmq.cpp) + target_link_libraries(core PRIVATE libzmq::libzmq) + target_include_directories(core PRIVATE "${CMAKE_SOURCE_DIR}/Externals/Cross-Compatible-FileLock-Windows-and-Linux/include") endif() if(MSVC) diff --git a/Source/Core/Core/HW/EXI/BBA/IPC_zmq.cpp b/Source/Core/Core/HW/EXI/BBA/IPC_zmq.cpp new file mode 100644 index 00000000000..bb872052b6b --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/IPC_zmq.cpp @@ -0,0 +1,209 @@ +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include +#include + +#include "Core/HW/EXI/EXI_DeviceEthernet.h" + +namespace ExpansionInterface +{ + +CEXIETHERNET::IPCBBAInterface::IPCBBAInterface(CEXIETHERNET* const eth_ref) + : CEXIETHERNET::NetworkInterface(eth_ref), m_context(zmq_ctx_new()), + m_proxy_thread(&CEXIETHERNET::IPCBBAInterface::ProxyThreadHandler, this) +{ +} + +CEXIETHERNET::IPCBBAInterface::~IPCBBAInterface() +{ + m_proxy_thread_shutdown.Set(); + + { + std::lock_guard lock{m_proxy_mutex}; + + if (m_proxy_publisher) + { + zmq_close(m_proxy_publisher); + m_proxy_publisher = nullptr; + } + if (m_proxy_subscriber) + { + zmq_close(m_proxy_subscriber); + m_proxy_subscriber = nullptr; + } + } + + zmq_ctx_term(m_context); + m_context = nullptr; + + if (m_proxy_thread.joinable()) + { + m_proxy_thread.join(); + } +} + +bool CEXIETHERNET::IPCBBAInterface::Activate() +{ + if (m_active) + return false; + + const u32 linger{0}; + + m_publisher = zmq_socket(m_context, ZMQ_PUB); + zmq_setsockopt(m_publisher, ZMQ_LINGER, &linger, sizeof(linger)); + zmq_connect(m_publisher, "ipc:///tmp/dolphin-bba-outbox"); + + m_subscriber = zmq_socket(m_context, ZMQ_SUB); + zmq_setsockopt(m_subscriber, ZMQ_LINGER, &linger, sizeof(linger)); + zmq_setsockopt(m_subscriber, ZMQ_SUBSCRIBE, "", 0); + zmq_connect(m_subscriber, "ipc:///tmp/dolphin-bba-inbox"); + + m_read_enabled.Clear(); + m_read_thread_shutdown.Clear(); + + m_active = true; + + return RecvInit(); +} + +void CEXIETHERNET::IPCBBAInterface::Deactivate() +{ + if (!m_active) + return; + + m_read_enabled.Clear(); + m_read_thread_shutdown.Set(); + + if (m_read_thread.joinable()) + { + m_read_thread.join(); + } + + zmq_close(m_publisher); + zmq_close(m_subscriber); + m_publisher = nullptr; + m_subscriber = nullptr; + + 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; + + std::vector message; + const u64 self{std::bit_cast(this)}; + message.resize(sizeof(self) + static_cast(size)); + std::memcpy(message.data(), &self, sizeof(self)); + std::memcpy(message.data() + sizeof(self), frame, static_cast(size)); + + zmq_send(m_publisher, message.data(), message.size(), 0); + 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() +{ + const u64 self{std::bit_cast(this)}; + + std::vector buffer; + buffer.resize(sizeof(self) + BBA_RECV_SIZE); + + while (!m_read_thread_shutdown.IsSet()) + { + zmq_pollitem_t pollitem{}; + pollitem.socket = m_subscriber; + pollitem.events = ZMQ_POLLIN; + + const int event_count{zmq_poll(&pollitem, 1, 50)}; + for (int i{0}; i < event_count; ++i) + { + const int read{zmq_recv(m_subscriber, buffer.data(), buffer.size(), 0)}; + if (read < static_cast(sizeof(self))) + continue; + + u64 id{}; + std::memcpy(&id, buffer.data(), sizeof(self)); + + const bool self_message{id == self}; + if (self_message) + continue; + + if (!m_read_enabled.IsSet()) + continue; + + const u8* const frame{buffer.data() + sizeof(self)}; + const u64 size{read - sizeof(self)}; + + std::memcpy(m_eth_ref->mRecvBuffer.get(), frame, size); + m_eth_ref->mRecvBufferLength = static_cast(size); + m_eth_ref->RecvHandlePacket(); + } + } +} + +void CEXIETHERNET::IPCBBAInterface::ProxyThreadHandler() +{ + while (!m_proxy_thread_shutdown.IsSet()) + { + // The first instance that acquires the filelock becomes the proxy. + const auto filelock = file_lock::FileLockFactory::CreateTimedLockContext( + "/tmp/dolphin-bba-filelock", std::chrono::seconds(1)); + if (!filelock) + continue; + + void* proxy_subscriber; + void* proxy_publisher; + + { + std::lock_guard lock{m_proxy_mutex}; + + const u32 linger{0}; + + m_proxy_subscriber = zmq_socket(m_context, ZMQ_XSUB); + zmq_setsockopt(m_proxy_subscriber, ZMQ_LINGER, &linger, sizeof(linger)); + zmq_setsockopt(m_proxy_subscriber, ZMQ_SUBSCRIBE, "", 0); + zmq_bind(m_proxy_subscriber, "ipc:///tmp/dolphin-bba-outbox"); + + m_proxy_publisher = zmq_socket(m_context, ZMQ_XPUB); + zmq_setsockopt(m_proxy_publisher, ZMQ_LINGER, &linger, sizeof(linger)); + zmq_bind(m_proxy_publisher, "ipc:///tmp/dolphin-bba-inbox"); + + proxy_subscriber = m_proxy_subscriber; + proxy_publisher = m_proxy_publisher; + } + + zmq_proxy(proxy_subscriber, proxy_publisher, nullptr); + } +} +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 67a03178312..42251bb341e 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -15,7 +15,7 @@ #endif #include -#if defined(WIN32) || (defined(__linux__) && !defined(__ANDROID__)) +#if !defined(__ANDROID__) && !defined(__APPLE__) #include #endif @@ -478,13 +478,13 @@ private: const Common::MACAddress& ResolveAddress(u32 inet_ip); }; +#if !defined(__ANDROID__) && !defined(__APPLE__) + 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; @@ -501,9 +501,50 @@ private: std::thread m_read_thread; Common::Flag m_read_enabled; Common::Flag m_read_thread_shutdown; + }; + +#elif defined(__APPLE__) + + class IPCBBAInterface : public NetworkInterface + { + public: + explicit IPCBBAInterface(CEXIETHERNET* eth_ref); + ~IPCBBAInterface() override; + + 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(); + void ProxyThreadHandler(); + + bool m_active{}; + void* m_context{}; + void* m_publisher{}; + void* m_subscriber{}; + std::thread m_read_thread; + Common::Flag m_read_enabled; + Common::Flag m_read_thread_shutdown; + + std::thread m_proxy_thread; + Common::Flag m_proxy_thread_shutdown; + std::mutex m_proxy_mutex; + void* m_proxy_publisher{}; + void* m_proxy_subscriber{}; + }; #else + class IPCBBAInterface : public NetworkInterface + { + public: + explicit IPCBBAInterface(CEXIETHERNET* const eth_ref) : NetworkInterface(eth_ref) {} + bool Activate() override { return false; } void Deactivate() override {} bool IsActivated() override { return false; } @@ -511,9 +552,9 @@ private: bool RecvInit() override { return false; } void RecvStart() override {} void RecvStop() override {} + }; #endif - }; std::unique_ptr m_network_interface; diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 98460f6be07..0d630c11988 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -143,9 +143,7 @@ void GameCubePane::CreateWidgets() EXIDeviceType::EthernetXLink, EXIDeviceType::EthernetTapServer, EXIDeviceType::EthernetBuiltIn, -#if defined(WIN32) || (defined(__linux__) && !defined(__ANDROID__)) EXIDeviceType::EthernetIPC, -#endif EXIDeviceType::ModemTapServer, }) {