From 0c364cbb4cc0bc6ef2937826d67c1483cf17551a Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 12 Feb 2023 17:58:17 -0800 Subject: [PATCH 01/13] implement tapserver BBA on all platforms This expands the tapserver BBA interface to be available on all platforms. tapserver itself is still macOS-only, but newserv (the PSO server) is not, and it can directly accept local and remote tapserver connections as well. This makes the tapserver interface potentially useful on all platforms. --- .../features/settings/model/StringSetting.kt | 6 + .../settings/ui/SettingsFragmentPresenter.kt | 11 ++ .../app/src/main/res/values/strings.xml | 2 + Source/Core/Core/CMakeLists.txt | 6 +- Source/Core/Core/Config/MainSettings.cpp | 2 + Source/Core/Core/Config/MainSettings.h | 1 + .../{TAPServer_Apple.cpp => TAPServer.cpp} | 106 +++++++++++++----- Source/Core/Core/HW/EXI/EXI_Device.cpp | 2 - Source/Core/Core/HW/EXI/EXI_Device.h | 1 - .../Core/Core/HW/EXI/EXI_DeviceEthernet.cpp | 5 +- Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 11 +- .../BroadbandAdapterSettingsDialog.cpp | 14 +++ .../Settings/BroadbandAdapterSettingsDialog.h | 1 + .../Core/DolphinQt/Settings/GameCubePane.cpp | 10 +- 14 files changed, 134 insertions(+), 44 deletions(-) rename Source/Core/Core/HW/EXI/BBA/{TAPServer_Apple.cpp => TAPServer.cpp} (54%) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt index ea685b817d..8dc9e619c5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt @@ -23,6 +23,12 @@ enum class StringSetting( "BBA_BUILTIN_DNS", "3.18.217.27" ), + MAIN_BBA_TAPSERVER_DESTINATION( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_CORE, + "BBA_TAPSERVER_DESTINATION", + "/tmp/dolphin-tap" + ), MAIN_CUSTOM_RTC_VALUE( Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 44e2b2b915..ef395ed6a4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1101,6 +1101,17 @@ class SettingsFragmentPresenter( R.string.xlink_kai_bba_ip_description ) ) + } else if (serialPort1Type == 11) { + // Broadband Adapter (tapserver) + sl.add( + InputStringSetting( + context, + StringSetting.MAIN_BBA_TAPSERVER_DESTINATION, + R.string.bba_tapserver_destination, + R.string.bba_tapserver_destination_description + ) + ) + } } else if (serialPort1Type == 12) { // Broadband Adapter (Built In) sl.add( diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 251886b98a..2e8fc6856e 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -133,6 +133,8 @@ For setup instructions, refer to this page. XLink Kai IP Address/hostname IP address or hostname of device running the XLink Kai client + Tapserver destination + Enter the socket path or netloc (address:port) of the tapserver instance DNS Server Use 8.8.8.8 for normal DNS, else enter your custom one diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 290947e722..945c57760d 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -696,6 +696,7 @@ if(WIN32) target_sources(core PRIVATE HW/EXI/BBA/TAP_Win32.cpp HW/EXI/BBA/TAP_Win32.h + HW/EXI/BBA/TAPServer.cpp HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/EXI/BBA/BuiltIn.cpp HW/EXI/BBA/BuiltIn.h @@ -712,7 +713,7 @@ if(WIN32) elseif(APPLE) target_sources(core PRIVATE HW/EXI/BBA/TAP_Apple.cpp - HW/EXI/BBA/TAPServer_Apple.cpp + HW/EXI/BBA/TAPServer.cpp HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/EXI/BBA/BuiltIn.cpp HW/EXI/BBA/BuiltIn.h @@ -721,6 +722,7 @@ elseif(APPLE) elseif(UNIX) target_sources(core PRIVATE HW/EXI/BBA/TAP_Unix.cpp + HW/EXI/BBA/TAPServer.cpp HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/EXI/BBA/BuiltIn.cpp HW/EXI/BBA/BuiltIn.h @@ -778,4 +780,4 @@ endif() if(USE_RETRO_ACHIEVEMENTS) target_link_libraries(core PRIVATE rcheevos) target_compile_definitions(core PRIVATE -DUSE_RETRO_ACHIEVEMENTS) -endif() \ No newline at end of file +endif() diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 21c453b6c7..cdd852c260 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -137,6 +137,8 @@ const Info MAIN_BBA_XLINK_CHAT_OSD{{System::Main, "Core", "BBA_XLINK_CHAT_ // Schthack PSO Server - https://schtserv.com/ const Info MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN_DNS"}, "3.18.217.27"}; +const Info MAIN_BBA_TAPSERVER_DESTINATION{ + {System::Main, "Core", "BBA_TAPSERVER_DESTINATION"}, "/tmp/dolphin-tap"}; const Info MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""}; const Info& GetInfoForSIDevice(int channel) diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index dddda8ae7a..b13615df5d 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -96,6 +96,7 @@ extern const Info MAIN_BBA_XLINK_IP; extern const Info MAIN_BBA_XLINK_CHAT_OSD; extern const Info MAIN_BBA_BUILTIN_DNS; extern const Info MAIN_BBA_BUILTIN_IP; +extern const Info MAIN_BBA_TAPSERVER_DESTINATION; const Info& GetInfoForSIDevice(int channel); const Info& GetInfoForAdapterRumble(int channel); const Info& GetInfoForSimulateKonga(int channel); diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp similarity index 54% rename from Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp rename to Source/Core/Core/HW/EXI/BBA/TAPServer.cpp index de71067c1b..3e0644a41b 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServer_Apple.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp @@ -3,10 +3,16 @@ #include "Core/HW/EXI/EXI_DeviceEthernet.h" +#ifdef _WIN32 +#include +#include +#else #include #include +#include #include #include +#endif #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" @@ -15,42 +21,84 @@ namespace ExpansionInterface { -// This interface is only implemented on macOS, since macOS needs a replacement -// for TunTap when the kernel extension is no longer supported. This interface -// only appears in the menu on macOS, so on other platforms, it does nothing and -// refuses to activate. -constexpr char socket_path[] = "/tmp/dolphin-tap"; +static int ConnectToDestination(const std::string& destination) +{ + if (destination.empty()) + { + INFO_LOG_FMT(SP1, "Cannot connect: destination is empty\n"); + return -1; + } + + size_t ss_size; + struct sockaddr_storage ss; + memset(&ss, 0, sizeof(ss)); + if (destination[0] != '/') + { // IP address or hostname + size_t colon_offset = destination.find(':'); + if (colon_offset == std::string::npos) + { + INFO_LOG_FMT(SP1, "Destination IP address does not include port\n"); + return -1; + } + + struct sockaddr_in* sin = reinterpret_cast(&ss); + sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); + sin->sin_family = AF_INET; + sin->sin_port = htons(stoul(destination.substr(colon_offset + 1))); + ss_size = sizeof(*sin); +#ifndef _WIN32 + } + else + { // UNIX socket + struct sockaddr_un* sun = reinterpret_cast(&ss); + if (destination.size() + 1 > sizeof(sun->sun_path)) + { + INFO_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); + return -1; + } + sun->sun_family = AF_UNIX; + strcpy(sun->sun_path, destination.c_str()); + ss_size = sizeof(*sun); +#else + } + else + { + INFO_LOG_FMT(SP1, "UNIX sockets are not supported on Windows\n"); + return -1; +#endif + } + + int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); + if (fd == -1) + { + INFO_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); + return -1; + } + +#ifdef __APPLE__ + int opt_no_sigpipe = 1; + if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) + INFO_LOG_FMT(SP1, "Failed to set SO_NOSIGPIPE on socket\n"); +#endif + + if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) + { + std::string s = Common::LastStrerrorString(); + INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); + close(fd); + return -1; + } + + return fd; +} bool CEXIETHERNET::TAPServerNetworkInterface::Activate() { if (IsActivated()) return true; - sockaddr_un sun = {}; - if (sizeof(socket_path) > sizeof(sun.sun_path)) - { - ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA"); - return false; - } - sun.sun_family = AF_UNIX; - strcpy(sun.sun_path, socket_path); - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd == -1) - { - ERROR_LOG_FMT(SP1, "Couldn't create socket, unable to init BBA"); - return false; - } - - if (connect(fd, reinterpret_cast(&sun), sizeof(sun)) == -1) - { - ERROR_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA", - Common::LastStrerrorString()); - close(fd); - fd = -1; - return false; - } + fd = ConnectToDestination(m_destination); INFO_LOG_FMT(SP1, "BBA initialized."); return RecvInit(); diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp index 425ce9dbb2..ad25a94522 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp @@ -137,11 +137,9 @@ std::unique_ptr EXIDevice_Create(Core::System& system, const EXIDevi result = std::make_unique(system, BBADeviceType::TAP); break; -#if defined(__APPLE__) case EXIDeviceType::EthernetTapServer: result = std::make_unique(system, BBADeviceType::TAPSERVER); break; -#endif case EXIDeviceType::EthernetXLink: result = std::make_unique(system, BBADeviceType::XLINK); diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h index 2c90d772f9..c927a255d3 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.h +++ b/Source/Core/Core/HW/EXI/EXI_Device.h @@ -39,7 +39,6 @@ enum class EXIDeviceType : int MemoryCardFolder, AGP, EthernetXLink, - // Only used on Apple devices. EthernetTapServer, EthernetBuiltIn, None = 0xFF diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp index 549ace3a6e..fb2660ed04 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp @@ -50,12 +50,11 @@ CEXIETHERNET::CEXIETHERNET(Core::System& system, BBADeviceType type) : IEXIDevic m_network_interface = std::make_unique(this); INFO_LOG_FMT(SP1, "Created TAP physical network interface."); break; -#if defined(__APPLE__) case BBADeviceType::TAPSERVER: - m_network_interface = std::make_unique(this); + m_network_interface = std::make_unique( + this, Config::Get(Config::MAIN_BBA_TAPSERVER_DESTINATION)); INFO_LOG_FMT(SP1, "Created tapserver physical network interface."); break; -#endif case BBADeviceType::BuiltIn: m_network_interface = std::make_unique( this, Config::Get(Config::MAIN_BBA_BUILTIN_DNS), Config::Get(Config::MAIN_BBA_BUILTIN_IP)); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index cdbf645aee..301420fbea 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -205,9 +205,7 @@ enum class BBADeviceType { TAP, XLINK, -#if defined(__APPLE__) TAPSERVER, -#endif BuiltIn, }; @@ -364,11 +362,13 @@ private: #endif }; -#if defined(__APPLE__) class TAPServerNetworkInterface : public TAPNetworkInterface { public: - explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref) : TAPNetworkInterface(eth_ref) {} + explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination) + : TAPNetworkInterface(eth_ref), m_destination(destination) + { + } public: bool Activate() override; @@ -376,9 +376,10 @@ private: bool RecvInit() override; private: + std::string m_destination; + void ReadThreadHandler(); }; -#endif class XLinkNetworkInterface : public NetworkInterface { diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp index 91aeae8a22..f8abbc77ab 100644 --- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp @@ -48,6 +48,17 @@ void BroadbandAdapterSettingsDialog::InitControls() window_title = tr("Broadband Adapter MAC Address"); break; + case Type::TapServer: + address_label = new QLabel(tr("UNIX socket path or netloc (address:port):")); + address_placeholder = QStringLiteral("/tmp/dolphin-tap"); + current_address = QString::fromStdString(Config::Get(Config::MAIN_BBA_TAPSERVER_DESTINATION)); + description = + new QLabel(tr("On macOS and Linux, the default value \"/tmp/dolphin-tap\" will work with " + "tapserver and newserv. On Windows, you must enter an IP address and port.")); + + window_title = tr("BBA destination address"); + break; + case Type::BuiltIn: address_label = new QLabel(tr("Enter the DNS server to use:")); address_placeholder = QStringLiteral("8.8.8.8"); @@ -114,6 +125,9 @@ void BroadbandAdapterSettingsDialog::SaveAddress() Config::SetBaseOrCurrent(Config::MAIN_BBA_MAC, bba_new_address); break; } + case Type::TapServer: + Config::SetBaseOrCurrent(Config::MAIN_BBA_TAPSERVER_DESTINATION, bba_new_address); + break; case Type::BuiltIn: Config::SetBaseOrCurrent(Config::MAIN_BBA_BUILTIN_DNS, bba_new_address); break; diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h index ca8d813cbe..53863f1d15 100644 --- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h +++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h @@ -15,6 +15,7 @@ public: { Ethernet, XLinkKai, + TapServer, BuiltIn }; diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 8c89badf00..944efa7a9f 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -149,9 +149,7 @@ void GameCubePane::CreateWidgets() EXIDeviceType::Dummy, EXIDeviceType::Ethernet, EXIDeviceType::EthernetXLink, -#ifdef __APPLE__ EXIDeviceType::EthernetTapServer, -#endif EXIDeviceType::EthernetBuiltIn, }) { @@ -355,6 +353,7 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot) case ExpansionInterface::Slot::SP1: has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet || device == ExpansionInterface::EXIDeviceType::EthernetXLink || + device == ExpansionInterface::EXIDeviceType::EthernetTapServer || device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn); break; } @@ -400,6 +399,13 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot) dialog.exec(); return; } + case ExpansionInterface::EXIDeviceType::EthernetTapServer: + { + BroadbandAdapterSettingsDialog dialog(this, BroadbandAdapterSettingsDialog::Type::TapServer); + SetQWidgetWindowDecorations(&dialog); + dialog.exec(); + return; + } case ExpansionInterface::EXIDeviceType::EthernetBuiltIn: { BroadbandAdapterSettingsDialog dialog(this, BroadbandAdapterSettingsDialog::Type::BuiltIn); From f90812d8e12125c613bb0dd548ed2fb05f49be0b Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 13 Oct 2023 10:16:07 -0700 Subject: [PATCH 02/13] fix android syntax error --- .../dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index ef395ed6a4..a0de70a723 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1111,7 +1111,6 @@ class SettingsFragmentPresenter( R.string.bba_tapserver_destination_description ) ) - } } else if (serialPort1Type == 12) { // Broadband Adapter (Built In) sl.add( From a36600ae503ee8a947d1058e8ed48614809b24fb Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 13 Oct 2023 10:16:28 -0700 Subject: [PATCH 03/13] add TAPServer.cpp to Windows build --- Source/Core/DolphinLib.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 50a6eb5494..2229ee9d06 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -937,6 +937,7 @@ + From 083116a89ce922b540ca471a98a8a1e5501491b1 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 14 Oct 2023 17:52:26 -0700 Subject: [PATCH 04/13] rewrite tapserver interface for better error handling --- Source/Core/Common/SocketContext.cpp | 19 +- Source/Core/Common/SocketContext.h | 5 +- Source/Core/Core/CMakeLists.txt | 16 +- Source/Core/Core/HW/EXI/BBA/TAPServer.cpp | 196 +++++++++++++++--- Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 26 ++- .../BroadbandAdapterSettingsDialog.cpp | 18 +- 6 files changed, 223 insertions(+), 57 deletions(-) diff --git a/Source/Core/Common/SocketContext.cpp b/Source/Core/Common/SocketContext.cpp index defc333c11..15f9fd9010 100644 --- a/Source/Core/Common/SocketContext.cpp +++ b/Source/Core/Common/SocketContext.cpp @@ -8,12 +8,27 @@ namespace Common #ifdef _WIN32 SocketContext::SocketContext() { - static_cast(WSAStartup(MAKEWORD(2, 2), &m_data)); + std::lock_guard g(s_lock); + if (s_num_objects == 0) + { + static_cast(WSAStartup(MAKEWORD(2, 2), &s_data)); + } + s_num_objects++; } SocketContext::~SocketContext() { - WSACleanup(); + std::lock_guard g(s_lock); + s_num_objects--; + if (s_num_objects == 0) + { + WSACleanup(); + } } + +std::mutex SocketContext::s_lock; +size_t SocketContext::s_num_objects = 0; +WSADATA SocketContext::s_data; + #else SocketContext::SocketContext() = default; SocketContext::~SocketContext() = default; diff --git a/Source/Core/Common/SocketContext.h b/Source/Core/Common/SocketContext.h index 7e072fd8c0..0aa4929e89 100644 --- a/Source/Core/Common/SocketContext.h +++ b/Source/Core/Common/SocketContext.h @@ -5,6 +5,7 @@ #ifdef _WIN32 #include +#include #endif namespace Common @@ -23,7 +24,9 @@ public: private: #ifdef _WIN32 - WSADATA m_data; + static std::mutex s_lock; + static size_t s_num_objects; + static WSADATA s_data; #endif }; } // namespace Common diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 945c57760d..10b60a1a1c 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -189,6 +189,10 @@ add_library(core HW/DVD/DVDThread.h HW/DVD/FileMonitor.cpp HW/DVD/FileMonitor.h + HW/EXI/BBA/TAPServer.cpp + HW/EXI/BBA/XLINK_KAI_BBA.cpp + HW/EXI/BBA/BuiltIn.cpp + HW/EXI/BBA/BuiltIn.h HW/EXI/EXI_Channel.cpp HW/EXI/EXI_Channel.h HW/EXI/EXI_Device.cpp @@ -696,10 +700,6 @@ if(WIN32) target_sources(core PRIVATE HW/EXI/BBA/TAP_Win32.cpp HW/EXI/BBA/TAP_Win32.h - HW/EXI/BBA/TAPServer.cpp - HW/EXI/BBA/XLINK_KAI_BBA.cpp - HW/EXI/BBA/BuiltIn.cpp - HW/EXI/BBA/BuiltIn.h HW/WiimoteReal/IOWin.cpp HW/WiimoteReal/IOWin.h ) @@ -713,19 +713,11 @@ if(WIN32) elseif(APPLE) target_sources(core PRIVATE HW/EXI/BBA/TAP_Apple.cpp - HW/EXI/BBA/TAPServer.cpp - HW/EXI/BBA/XLINK_KAI_BBA.cpp - HW/EXI/BBA/BuiltIn.cpp - HW/EXI/BBA/BuiltIn.h ) target_link_libraries(core PUBLIC ${IOB_LIBRARY}) elseif(UNIX) target_sources(core PRIVATE HW/EXI/BBA/TAP_Unix.cpp - HW/EXI/BBA/TAPServer.cpp - HW/EXI/BBA/XLINK_KAI_BBA.cpp - HW/EXI/BBA/BuiltIn.cpp - HW/EXI/BBA/BuiltIn.h ) if(ANDROID) target_sources(core PRIVATE diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp index 3e0644a41b..f81c1f479f 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp @@ -22,23 +22,38 @@ namespace ExpansionInterface { +#ifdef _WIN32 +static constexpr auto pi_close = &closesocket; +using ws_ssize_t = int; +#else +static constexpr auto pi_close = &close; +using ws_ssize_t = ssize_t; +#endif + +#ifdef __LINUX__ +#define SEND_FLAGS MSG_NOSIGNAL +#else +#define SEND_FLAGS 0 +#endif + static int ConnectToDestination(const std::string& destination) { if (destination.empty()) { - INFO_LOG_FMT(SP1, "Cannot connect: destination is empty\n"); + ERROR_LOG_FMT(SP1, "Cannot connect: destination is empty\n"); return -1; } - size_t ss_size; + int ss_size; struct sockaddr_storage ss; memset(&ss, 0, sizeof(ss)); if (destination[0] != '/') - { // IP address or hostname + { + // IP address or hostname size_t colon_offset = destination.find(':'); if (colon_offset == std::string::npos) { - INFO_LOG_FMT(SP1, "Destination IP address does not include port\n"); + ERROR_LOG_FMT(SP1, "Destination IP address does not include port\n"); return -1; } @@ -50,11 +65,12 @@ static int ConnectToDestination(const std::string& destination) #ifndef _WIN32 } else - { // UNIX socket + { + // UNIX socket struct sockaddr_un* sun = reinterpret_cast(&ss); if (destination.size() + 1 > sizeof(sun->sun_path)) { - INFO_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); + ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); return -1; } sun->sun_family = AF_UNIX; @@ -64,7 +80,7 @@ static int ConnectToDestination(const std::string& destination) } else { - INFO_LOG_FMT(SP1, "UNIX sockets are not supported on Windows\n"); + ERROR_LOG_FMT(SP1, "UNIX sockets are not supported on Windows\n"); return -1; #endif } @@ -72,7 +88,7 @@ static int ConnectToDestination(const std::string& destination) int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); if (fd == -1) { - INFO_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); + ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); return -1; } @@ -86,7 +102,7 @@ static int ConnectToDestination(const std::string& destination) { std::string s = Common::LastStrerrorString(); INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); - close(fd); + pi_close(fd); return -1; } @@ -98,12 +114,44 @@ bool CEXIETHERNET::TAPServerNetworkInterface::Activate() if (IsActivated()) return true; - fd = ConnectToDestination(m_destination); + m_fd = ConnectToDestination(m_destination); INFO_LOG_FMT(SP1, "BBA initialized."); return RecvInit(); } +void CEXIETHERNET::TAPServerNetworkInterface::Deactivate() +{ + pi_close(m_fd); + m_fd = -1; + + m_read_enabled.Clear(); + m_read_shutdown.Set(); + if (m_read_thread.joinable()) + m_read_thread.join(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::IsActivated() +{ + return (m_fd >= 0); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() +{ + m_read_thread = std::thread(&CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler, this); + return true; +} + +void CEXIETHERNET::TAPServerNetworkInterface::RecvStart() +{ + m_read_enabled.Set(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::RecvStop() +{ + m_read_enabled.Clear(); +} + bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) { { @@ -111,13 +159,16 @@ bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 siz INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, s); } - auto size16 = u16(size); - if (write(fd, &size16, 2) != 2) + // On Windows, the data pointer is of type const char*; on other systems it is + // of type const void*. This is the reason for the reinterpret_cast here and + // in the other send/recv calls in this file. + u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) { ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field"); return false; } - int written_bytes = write(fd, frame, size); + int written_bytes = send(m_fd, reinterpret_cast(frame), size, SEND_FLAGS); if (u32(written_bytes) != size) { ERROR_LOG_FMT(SP1, "SendFrame(): expected to write {} bytes, instead wrote {}", size, @@ -133,45 +184,122 @@ bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 siz void CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler() { - while (!readThreadShutdown.IsSet()) + while (!m_read_shutdown.IsSet()) { fd_set rfds; FD_ZERO(&rfds); - FD_SET(fd, &rfds); + FD_SET(m_fd, &rfds); timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 50000; - if (select(fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) + if (select(m_fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) continue; - u16 size; - if (read(fd, &size, 2) != 2) + // The tapserver protocol is very simple: there is a 16-bit little-endian + // size field, followed by that many bytes of packet data + switch (m_read_state) { - ERROR_LOG_FMT(SP1, "Failed to read size field from BBA: {}", Common::LastStrerrorString()); + case ReadState::Size: + { + u8 size_bytes[2]; + ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(size_bytes), 2, 0); + if (bytes_read == 1) + { + m_read_state = ReadState::SizeHigh; + m_read_packet_bytes_remaining = size_bytes[0]; + } + else if (bytes_read == 2) + { + m_read_packet_bytes_remaining = size_bytes[0] | (size_bytes[1] << 8); + if (m_read_packet_bytes_remaining > BBA_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", + m_read_packet_bytes_remaining); + m_read_state = ReadState::Skip; + } + else + { + m_read_state = ReadState::Data; + } + } + else + { + ERROR_LOG_FMT(SP1, "Failed to read size field from BBA: {}", Common::LastStrerrorString()); + } + break; } - else + case ReadState::SizeHigh: { - int read_bytes = read(fd, m_eth_ref->mRecvBuffer.get(), size); - if (read_bytes < 0) + // This handles the annoying case where only one byte of the size field + // was available earlier. + u8 size_high = 0; + ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(&size_high), 1, 0); + if (bytes_read == 1) { - ERROR_LOG_FMT(SP1, "Failed to read packet data from BBA: {}", Common::LastStrerrorString()); + m_read_packet_bytes_remaining |= (size_high << 8); + if (m_read_packet_bytes_remaining > BBA_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", + m_read_packet_bytes_remaining); + m_read_state = ReadState::Skip; + } + else + { + m_read_state = ReadState::Data; + } } - else if (readEnabled.IsSet()) + else { - std::string data_string = ArrayToString(m_eth_ref->mRecvBuffer.get(), read_bytes, 0x10); - INFO_LOG_FMT(SP1, "Read data: {}", data_string); - m_eth_ref->mRecvBufferLength = read_bytes; - m_eth_ref->RecvHandlePacket(); + ERROR_LOG_FMT(SP1, "Failed to read split size field from BBA: {}", + Common::LastStrerrorString()); } + break; + } + case ReadState::Data: + { + ws_ssize_t bytes_read = + recv(m_fd, reinterpret_cast(m_eth_ref->mRecvBuffer.get() + m_read_packet_offset), + m_read_packet_bytes_remaining, 0); + if (bytes_read <= 0) + { + ERROR_LOG_FMT(SP1, "Failed to read data from BBA: {}", Common::LastStrerrorString()); + } + else + { + m_read_packet_offset += bytes_read; + m_read_packet_bytes_remaining -= bytes_read; + if (m_read_packet_bytes_remaining == 0) + { + m_eth_ref->mRecvBufferLength = m_read_packet_offset; + m_eth_ref->RecvHandlePacket(); + m_read_state = ReadState::Size; + m_read_packet_offset = 0; + } + } + break; + } + case ReadState::Skip: + { + ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(m_eth_ref->mRecvBuffer.get()), + std::min(m_read_packet_bytes_remaining, BBA_RECV_SIZE), 0); + if (bytes_read <= 0) + { + ERROR_LOG_FMT(SP1, "Failed to read data from BBA: {}", Common::LastStrerrorString()); + } + else + { + m_read_packet_bytes_remaining -= bytes_read; + if (m_read_packet_bytes_remaining == 0) + { + m_read_state = ReadState::Size; + m_read_packet_offset = 0; + } + } + break; + } } } } -bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() -{ - readThread = std::thread(&CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler, this); - return true; -} - } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 301420fbea..067f5d59c0 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -17,6 +17,7 @@ #include "Common/Flag.h" #include "Common/Network.h" +#include "Common/SocketContext.h" #include "Core/HW/EXI/BBA/BuiltIn.h" #include "Core/HW/EXI/EXI_Device.h" @@ -362,21 +363,42 @@ private: #endif }; - class TAPServerNetworkInterface : public TAPNetworkInterface + class TAPServerNetworkInterface : public NetworkInterface { public: explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination) - : TAPNetworkInterface(eth_ref), m_destination(destination) + : NetworkInterface(eth_ref), m_destination(destination) { } 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: + enum class ReadState + { + Size, + SizeHigh, + Data, + Skip, + }; + std::string m_destination; + Common::SocketContext m_socket_context; + + int m_fd = -1; + ReadState m_read_state = ReadState::Size; + u16 m_read_packet_offset; + u16 m_read_packet_bytes_remaining; + std::thread m_read_thread; + Common::Flag m_read_enabled; + Common::Flag m_read_shutdown; void ReadThreadHandler(); }; diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp index f8abbc77ab..9716d8f211 100644 --- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp @@ -49,13 +49,19 @@ void BroadbandAdapterSettingsDialog::InitControls() break; case Type::TapServer: - address_label = new QLabel(tr("UNIX socket path or netloc (address:port):")); - address_placeholder = QStringLiteral("/tmp/dolphin-tap"); current_address = QString::fromStdString(Config::Get(Config::MAIN_BBA_TAPSERVER_DESTINATION)); - description = - new QLabel(tr("On macOS and Linux, the default value \"/tmp/dolphin-tap\" will work with " - "tapserver and newserv. On Windows, you must enter an IP address and port.")); - +#ifdef _WIN32 + address_label = new QLabel(tr("Destination (address:port):")); + address_placeholder = QStringLiteral(""); + description = new QLabel( + tr("Enter the IP address and port of the tapserver instance you want to connect to.")); +#else + address_label = new QLabel(tr("Destination (UNIX socket path or address:port):")); + address_placeholder = QStringLiteral("/tmp/dolphin-tap"); + description = new QLabel(tr( + "The default value \"/tmp/dolphin-tap\" will work with a local tapserver and newserv. You " + "can also enter a network location (address:port) to connect to a remote tapserver.")); +#endif window_title = tr("BBA destination address"); break; From 02deaa6748c44ffb2a370e4fadc8633a2ed25ac1 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 2 Dec 2023 23:37:28 -0800 Subject: [PATCH 05/13] Implement GC modem adapter This implements the GameCube modem adapter. This implementation is stable but not perfect; it drops frames if the receive FIFO length is exceeded. This is probably due to the unimplemented interrupt mentioned in the comments. If the tapserver end of the connection is aware of this limitation, it's easily circumvented by lowering the MTU of the link, but ideally this wouldn't be necessary. This has been tested with a couple of different versions of Phantasy Star Online, including Episodes 1 & 2 Trial Edition. The Trial Edition is the only version of the game that supports the Modem Adapter and not the Broadband Adapter, which is what made this commit necessary in the first place. --- .../features/settings/model/StringSetting.kt | 6 + .../settings/ui/SettingsFragmentPresenter.kt | 10 + .../app/src/main/res/values/strings.xml | 2 + Source/Core/Core/CMakeLists.txt | 3 + Source/Core/Core/Config/MainSettings.cpp | 2 + Source/Core/Core/Config/MainSettings.h | 1 + Source/Core/Core/HW/EXI/EXI_Device.cpp | 5 + Source/Core/Core/HW/EXI/EXI_Device.h | 4 +- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 382 ++++++++++++++++++ Source/Core/Core/HW/EXI/EXI_DeviceModem.h | 179 ++++++++ Source/Core/Core/HW/EXI/Modem/TAPServer.cpp | 324 +++++++++++++++ .../BroadbandAdapterSettingsDialog.cpp | 28 +- .../Settings/BroadbandAdapterSettingsDialog.h | 3 +- .../Core/DolphinQt/Settings/GameCubePane.cpp | 12 +- 14 files changed, 954 insertions(+), 7 deletions(-) create mode 100644 Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp create mode 100644 Source/Core/Core/HW/EXI/EXI_DeviceModem.h create mode 100644 Source/Core/Core/HW/EXI/Modem/TAPServer.cpp diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt index 8dc9e619c5..f959764564 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt @@ -29,6 +29,12 @@ enum class StringSetting( "BBA_TAPSERVER_DESTINATION", "/tmp/dolphin-tap" ), + MAIN_MODEM_TAPSERVER_DESTINATION( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_CORE, + "MODEM_TAPSERVER_DESTINATION", + "/tmp/dolphin-modem-tap" + ), MAIN_CUSTOM_RTC_VALUE( Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index a0de70a723..a3ec018b46 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1121,6 +1121,16 @@ class SettingsFragmentPresenter( R.string.bba_builtin_dns_description ) ) + } else if (serialPort1Type == 13) { + // Modem Adapter (tapserver) + sl.add( + InputStringSetting( + context, + StringSetting.MAIN_MODEM_TAPSERVER_DESTINATION, + R.string.modem_tapserver_destination, + R.string.modem_tapserver_destination_description + ) + ) } } diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 2e8fc6856e..89ddc6b705 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -135,6 +135,8 @@ IP address or hostname of device running the XLink Kai client Tapserver destination Enter the socket path or netloc (address:port) of the tapserver instance + Tapserver destination + Enter the socket path or netloc (address:port) of the tapserver instance DNS Server Use 8.8.8.8 for normal DNS, else enter your custom one diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 10b60a1a1c..f36235d7d4 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -193,6 +193,7 @@ add_library(core HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/EXI/BBA/BuiltIn.cpp HW/EXI/BBA/BuiltIn.h + HW/EXI/Modem/TAPServer.cpp HW/EXI/EXI_Channel.cpp HW/EXI/EXI_Channel.h HW/EXI/EXI_Device.cpp @@ -213,6 +214,8 @@ add_library(core HW/EXI/EXI_DeviceMemoryCard.h HW/EXI/EXI_DeviceMic.cpp HW/EXI/EXI_DeviceMic.h + HW/EXI/EXI_DeviceModem.cpp + HW/EXI/EXI_DeviceModem.h HW/EXI/EXI.cpp HW/EXI/EXI.h HW/GBAPad.cpp diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index cdd852c260..432abbeb24 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -139,6 +139,8 @@ const Info MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN "3.18.217.27"}; const Info MAIN_BBA_TAPSERVER_DESTINATION{ {System::Main, "Core", "BBA_TAPSERVER_DESTINATION"}, "/tmp/dolphin-tap"}; +const Info MAIN_MODEM_TAPSERVER_DESTINATION{ + {System::Main, "Core", "MODEM_TAPSERVER_DESTINATION"}, "/tmp/dolphin-modem-tap"}; const Info MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""}; const Info& GetInfoForSIDevice(int channel) diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index b13615df5d..5c028d9b36 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -97,6 +97,7 @@ extern const Info MAIN_BBA_XLINK_CHAT_OSD; extern const Info MAIN_BBA_BUILTIN_DNS; extern const Info MAIN_BBA_BUILTIN_IP; extern const Info MAIN_BBA_TAPSERVER_DESTINATION; +extern const Info MAIN_MODEM_TAPSERVER_DESTINATION; const Info& GetInfoForSIDevice(int channel); const Info& GetInfoForAdapterRumble(int channel); const Info& GetInfoForSimulateKonga(int channel); diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp index ad25a94522..0d3ba0f8f9 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp @@ -14,6 +14,7 @@ #include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/EXI/EXI_DeviceMemoryCard.h" #include "Core/HW/EXI/EXI_DeviceMic.h" +#include "Core/HW/EXI/EXI_DeviceModem.h" #include "Core/HW/Memmap.h" #include "Core/System.h" @@ -149,6 +150,10 @@ std::unique_ptr EXIDevice_Create(Core::System& system, const EXIDevi result = std::make_unique(system, BBADeviceType::BuiltIn); break; + case EXIDeviceType::ModemTapServer: + result = std::make_unique(system, ModemDeviceType::TAPSERVER); + break; + case EXIDeviceType::Gecko: result = std::make_unique(system); break; diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h index c927a255d3..f405de167e 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.h +++ b/Source/Core/Core/HW/EXI/EXI_Device.h @@ -41,6 +41,7 @@ enum class EXIDeviceType : int EthernetXLink, EthernetTapServer, EthernetBuiltIn, + ModemTapServer, None = 0xFF }; @@ -87,7 +88,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 (XLink Kai)"), _trans("Broadband Adapter (tapserver)"), _trans("Broadband Adapter (HLE)"), + _trans("Modem Adapter (tapserver)"), }; constexpr formatter() : EnumFormatter(names) {} diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp new file mode 100644 index 0000000000..867044ec89 --- /dev/null +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -0,0 +1,382 @@ +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/EXI/EXI_DeviceModem.h" + +#include + +#include +#include +#include +#include + +#include "Common/BitUtils.h" +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/Network.h" +#include "Common/StringUtil.h" +#include "Core/Config/MainSettings.h" +#include "Core/CoreTiming.h" +#include "Core/HW/EXI/EXI.h" +#include "Core/HW/Memmap.h" +#include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" + +namespace ExpansionInterface +{ + +CEXIModem::CEXIModem(Core::System& system, ModemDeviceType type) : IEXIDevice(system) +{ + switch (type) + { + case ModemDeviceType::TAPSERVER: + m_network_interface = std::make_unique( + this, Config::Get(Config::MAIN_MODEM_TAPSERVER_DESTINATION)); + INFO_LOG_FMT(SP1, "Created tapserver physical network interface."); + break; + } + + for (size_t z = 0; z < m_regs.size(); z++) + { + m_regs[z] = 0; + } + m_regs[Register::DEVICE_TYPE] = 0x02; + m_regs[Register::INTERRUPT_MASK] = 0x02; +} + +CEXIModem::~CEXIModem() +{ + m_network_interface->Deactivate(); +} + +bool CEXIModem::IsPresent() const +{ + return true; +} + +void CEXIModem::SetCS(int cs) +{ + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; +} + +bool CEXIModem::IsInterruptSet() +{ + return !!(m_regs[Register::INTERRUPT_MASK] & m_regs[Register::PENDING_INTERRUPT_MASK]); +} + +void CEXIModem::ImmWrite(u32 data, u32 size) +{ + if (m_transfer_descriptor == INVALID_TRANSFER_DESCRIPTOR) + { + m_transfer_descriptor = data; + if (m_transfer_descriptor == 0x00008000) + { // Reset + m_network_interface->Deactivate(); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } + } + else if (!IsWriteTransfer(m_transfer_descriptor)) + { + ERROR_LOG_FMT(SP1, "Received EXI IMM write {:x} ({} bytes) after read command {:x}", data, size, + m_transfer_descriptor); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } + else if (IsModemTransfer(m_transfer_descriptor)) + { // Write AT command buffer or packet send buffer + u32 be_data = htonl(data); + HandleWriteModemTransfer(&be_data, size); + } + else + { // Write device register + uint8_t reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + bool should_update_interrupts = false; + for (; size; size--) + { + should_update_interrupts |= + ((reg_num == Register::INTERRUPT_MASK) || (reg_num == Register::PENDING_INTERRUPT_MASK)); + m_regs[reg_num++] = (data >> 24); + data <<= 8; + } + if (should_update_interrupts) + { + m_system.GetExpansionInterface().ScheduleUpdateInterrupts(CoreTiming::FromThread::CPU, 0); + } + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } +} + +void CEXIModem::DMAWrite(u32 addr, u32 size) +{ + if (m_transfer_descriptor == INVALID_TRANSFER_DESCRIPTOR) + { + ERROR_LOG_FMT(SP1, "Received EXI DMA write {:x} ({} bytes) after read command {:x}", addr, size, + m_transfer_descriptor); + } + else if (!IsWriteTransfer(m_transfer_descriptor)) + { + ERROR_LOG_FMT(SP1, "Received EXI DMA write {:x} ({} bytes) after read command {:x}", addr, size, + m_transfer_descriptor); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } + else if (!IsModemTransfer(m_transfer_descriptor)) + { + ERROR_LOG_FMT(SP1, "Received EXI DMA write {:x} ({} bytes) to registers {:x}", addr, size, + m_transfer_descriptor); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } + else + { + auto& memory = m_system.GetMemory(); + HandleWriteModemTransfer(memory.GetPointer(addr), size); + } +} + +u32 CEXIModem::ImmRead(u32 size) +{ + if (m_transfer_descriptor == INVALID_TRANSFER_DESCRIPTOR) + { + ERROR_LOG_FMT(SP1, "Received EXI IMM read ({} bytes) with no pending transfer", size); + return 0; + } + else if (IsWriteTransfer(m_transfer_descriptor)) + { + ERROR_LOG_FMT(SP1, "Received EXI IMM read ({} bytes) after write command {:x}", size, + m_transfer_descriptor); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + return 0; + } + else if (IsModemTransfer(m_transfer_descriptor)) + { + u32 be_data = 0; + HandleReadModemTransfer(&be_data, size); + return ntohl(be_data); + } + else + { // Read device register + uint8_t reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + if (reg_num == 0) + { + return 0x02020000; // Device ID (modem) + } + u32 ret = 0; + for (size_t z = 0; z < size; z++) + { + ret |= (m_regs[reg_num + z] << ((3 - z) * 8)); + } + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + return ret; + } +} + +void CEXIModem::DMARead(u32 addr, u32 size) +{ + if (m_transfer_descriptor == INVALID_TRANSFER_DESCRIPTOR) + { + ERROR_LOG_FMT(SP1, "Received EXI DMA read {:x} ({} bytes) with no pending transfer", addr, + size); + } + else if (IsWriteTransfer(m_transfer_descriptor)) + { + ERROR_LOG_FMT(SP1, "Received EXI DMA read {:x} ({} bytes) after write command {:x}", addr, size, + m_transfer_descriptor); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } + else if (!IsModemTransfer(m_transfer_descriptor)) + { + ERROR_LOG_FMT(SP1, "Received EXI DMA read {:x} ({} bytes) to registers {:x}", addr, size, + m_transfer_descriptor); + m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + } + else + { + auto& memory = m_system.GetMemory(); + HandleReadModemTransfer(memory.GetPointer(addr), size); + } +} + +void CEXIModem::HandleReadModemTransfer(void* data, u32 size) +{ + u16 bytes_requested = GetModemTransferSize(m_transfer_descriptor); + if (size > bytes_requested) + { + ERROR_LOG_FMT(SP1, "More bytes requested ({}) than originally requested for transfer {:x}", + size, m_transfer_descriptor); + size = bytes_requested; + } + u16 bytes_requested_after_read = bytes_requested - size; + + if ((m_transfer_descriptor & 0x0F000000) == 0x03000000) + { // AT command buffer + memcpy(data, m_at_reply_data.data(), std::min(size, m_at_reply_data.size())); + m_at_reply_data = m_at_reply_data.substr(size); + m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size(); + SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true); + } + else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000) + { // Packet receive buffer + std::lock_guard g(m_receive_buffer_lock); + size_t bytes_to_copy = std::min(size, m_receive_buffer.size()); + memcpy(data, m_receive_buffer.data(), bytes_to_copy); + m_receive_buffer = m_receive_buffer.substr(size); + OnReceiveBufferSizeChangedLocked(true); + } + else + { + ERROR_LOG_FMT(SP1, "Invalid modem read transfer type {:x}", m_transfer_descriptor); + } + + m_transfer_descriptor = + (bytes_requested_after_read == 0) ? + INVALID_TRANSFER_DESCRIPTOR : + SetModemTransferSize(m_transfer_descriptor, bytes_requested_after_read); +} + +void CEXIModem::HandleWriteModemTransfer(const void* data, u32 size) +{ + u16 bytes_expected = GetModemTransferSize(m_transfer_descriptor); + if (size > bytes_expected) + { + ERROR_LOG_FMT(SP1, "More bytes received ({}) than expected for transfer {:x}", size, + m_transfer_descriptor); + return; + } + u16 bytes_expected_after_write = bytes_expected - size; + + if ((m_transfer_descriptor & 0x0F000000) == 0x03000000) + { // AT command buffer + m_at_command_data.append(reinterpret_cast(data), size); + RunAllPendingATCommands(); + m_regs[Register::AT_COMMAND_SIZE] = m_at_command_data.size(); + } + else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000) + { // Packet send buffer + m_send_buffer.append(reinterpret_cast(data), size); + // A more accurate implementation would only set this interrupt if the send + // FIFO has enough space; however, we can clear the send FIFO "instantly" + // from the emulated program's perspective, so we always tell it the send + // FIFO is empty. + SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true); + m_network_interface->SendFrames(); + } + else + { + ERROR_LOG_FMT(SP1, "Invalid modem write transfer type {:x}", m_transfer_descriptor); + } + + m_transfer_descriptor = + (bytes_expected_after_write == 0) ? + INVALID_TRANSFER_DESCRIPTOR : + SetModemTransferSize(m_transfer_descriptor, bytes_expected_after_write); +} + +void CEXIModem::DoState(PointerWrap& p) +{ + // There isn't really any state to save. The registers depend on the state of + // the external connection, which Dolphin doesn't have control over. What + // should happen when the user saves a state during an online session and + // loads it later? The remote server presumably doesn't support point-in-time + // snapshots and reloading thereof. +} + +u16 CEXIModem::GetTxThreshold() const +{ + return (m_regs[Register::TX_THRESHOLD_HIGH] << 8) | m_regs[Register::TX_THRESHOLD_LOW]; +} + +u16 CEXIModem::GetRxThreshold() const +{ + return (m_regs[Register::RX_THRESHOLD_HIGH] << 8) | m_regs[Register::RX_THRESHOLD_LOW]; +} + +void CEXIModem::SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu) +{ + if (enabled) + { + m_regs[Register::PENDING_INTERRUPT_MASK] |= what; + } + else + { + m_regs[Register::PENDING_INTERRUPT_MASK] &= (~what); + } + m_system.GetExpansionInterface().ScheduleUpdateInterrupts( + from_cpu ? CoreTiming::FromThread::CPU : CoreTiming::FromThread::NON_CPU, 0); +} + +void CEXIModem::OnReceiveBufferSizeChangedLocked(bool from_cpu) +{ + // The caller is expected to hold m_receive_buffer_lock when calling this. + uint16_t bytes_available = std::min(m_receive_buffer.size(), 0x200); + m_regs[Register::BYTES_AVAILABLE_HIGH] = (bytes_available >> 8) & 0xFF; + m_regs[Register::BYTES_AVAILABLE_LOW] = bytes_available & 0xFF; + SetInterruptFlag(Interrupt::RECEIVE_BUFFER_ABOVE_THRESHOLD, + m_receive_buffer.size() >= GetRxThreshold(), from_cpu); + // TODO: There is a second interrupt here, which the GameCube modem library + // expects to be used when large frames are received. However, the correct + // semantics for this interrupt aren't obvious. Reverse-engineering some games + // that use the modem adapter makes it look like this interrupt should trigger + // when there's any data at all in the receive buffer, but implementing the + // interrupt this way causes them to crash. Further research is needed. + // SetInterruptFlag(Interrupt::RECEIVE_BUFFER_NOT_EMPTY, !m_receive_buffer.empty(), from_cpu); +} + +void CEXIModem::SendComplete() +{ + // See comment in HandleWriteModemTransfer about why this is always true. + SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true); +} + +void CEXIModem::AddToReceiveBuffer(std::string&& data) +{ + std::lock_guard g(m_receive_buffer_lock); + if (m_receive_buffer.empty()) + { + m_receive_buffer = std::move(data); + } + else + { + m_receive_buffer += data; + } + OnReceiveBufferSizeChangedLocked(false); +} + +void CEXIModem::AddATReply(const std::string& data) +{ + m_at_reply_data += data; + m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size(); + SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), false); +} + +void CEXIModem::RunAllPendingATCommands() +{ + for (size_t newline_pos = m_at_command_data.find_first_of("\r\n"); + newline_pos != std::string::npos; newline_pos = m_at_command_data.find_first_of("\r\n")) + { + std::string command = m_at_command_data.substr(0, newline_pos); + m_at_command_data = m_at_command_data.substr(newline_pos + 1); + + if (command == "ATZ") + { // Reset + m_network_interface->Deactivate(); + AddATReply("OK\r"); + } + else if (command.substr(0, 3) == "ATD") + { // Dial + if (m_network_interface->Activate()) + { + AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate + } + else + { + AddATReply("OK\rNO ANSWER\r"); + } + } + else + { + INFO_LOG_FMT(SP1, "Unhandled AT command: {}", command); + AddATReply("OK\r"); + } + } +} + +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h new file mode 100644 index 0000000000..d6ba896667 --- /dev/null +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h @@ -0,0 +1,179 @@ +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include + +#include "Common/Flag.h" +#include "Common/Network.h" +#include "Common/SocketContext.h" +#include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Core/HW/EXI/EXI_Device.h" + +class PointerWrap; + +namespace ExpansionInterface +{ + +#define MODEM_RECV_SIZE 0x800 + +enum +{ + EXI_DEVTYPE_MODEM = 0x02020000, +}; + +enum class ModemDeviceType +{ + TAPSERVER, +}; + +class CEXIModem : public IEXIDevice +{ +public: + CEXIModem(Core::System& system, ModemDeviceType type); + virtual ~CEXIModem(); + void SetCS(int cs) override; + bool IsPresent() const override; + bool IsInterruptSet() override; + void ImmWrite(u32 data, u32 size) override; + u32 ImmRead(u32 size) override; + void DMAWrite(u32 addr, u32 size) override; + void DMARead(u32 addr, u32 size) override; + void DoState(PointerWrap& p) override; + +private: + enum Interrupt + { // Used for Register::INTERRUPT_MASK and Register::PENDING_INTERRUPT_MASK + AT_REPLY_DATA_AVAILABLE = 0x02, + SEND_BUFFER_BELOW_THRESHOLD = 0x10, + RECEIVE_BUFFER_ABOVE_THRESHOLD = 0x20, + RECEIVE_BUFFER_NOT_EMPTY = 0x40, + }; + enum Register + { + DEVICE_TYPE = 0x00, + INTERRUPT_MASK = 0x01, + PENDING_INTERRUPT_MASK = 0x02, + UNKNOWN_03 = 0x03, + AT_COMMAND_SIZE = 0x04, + AT_REPLY_SIZE = 0x05, + UNKNOWN_06 = 0x06, + UNKNOWN_07 = 0x07, + UNKNOWN_08 = 0x08, + BYTES_SENT_HIGH = 0x09, + BYTES_SENT_LOW = 0x0A, + BYTES_AVAILABLE_HIGH = 0x0B, + BYTES_AVAILABLE_LOW = 0x0C, + ESR = 0x0D, + TX_THRESHOLD_HIGH = 0x0E, + TX_THRESHOLD_LOW = 0x0F, + RX_THRESHOLD_HIGH = 0x10, + RX_THRESHOLD_LOW = 0x11, + STATUS = 0x12, + FWT = 0x13, + }; + + u16 GetTxThreshold() const; + u16 GetRxThreshold() const; + void SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu); + void HandleReadModemTransfer(void* data, u32 size); + void HandleWriteModemTransfer(const void* data, u32 size); + void OnReceiveBufferSizeChangedLocked(bool from_cpu); + void SendComplete(); + void AddToReceiveBuffer(std::string&& data); + void RunAllPendingATCommands(); + void AddATReply(const std::string& data); + + static inline bool TransferIsResetCommand(u32 transfer_descriptor) + { + return (transfer_descriptor == 0x80000000); + } + static inline bool IsWriteTransfer(u32 transfer_descriptor) + { + return (transfer_descriptor & 0x40000000); + } + static inline bool IsModemTransfer(u32 transfer_descriptor) + { + return (transfer_descriptor & 0x20000000); + } + static inline u16 GetModemTransferSize(u32 transfer_descriptor) + { + return ((transfer_descriptor >> 8) & 0xFFFF); + } + static inline u32 SetModemTransferSize(u32 transfer_descriptor, u16 new_size) + { + return (transfer_descriptor & 0xFF000000) | (new_size << 8); + } + + class NetworkInterface + { + protected: + CEXIModem* m_modem_ref = nullptr; + explicit NetworkInterface(CEXIModem* modem_ref) : m_modem_ref{modem_ref} {} + + public: + virtual bool Activate() { return false; } + virtual void Deactivate() {} + virtual bool IsActivated() { return false; } + virtual bool SendFrames() { return false; } + virtual bool RecvInit() { return false; } + virtual void RecvStart() {} + virtual void RecvStop() {} + + virtual ~NetworkInterface() = default; + }; + + class TAPServerNetworkInterface : public NetworkInterface + { + public: + explicit TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination) + : NetworkInterface(modem_ref), m_destination(destination) + { + } + + public: + bool Activate() override; + void Deactivate() override; + bool IsActivated() override; + bool SendFrames() override; + bool RecvInit() override; + void RecvStart() override; + void RecvStop() override; + + private: + std::string m_destination; + Common::SocketContext m_socket_context; + + int m_fd = -1; + std::thread m_read_thread; + Common::Flag m_read_enabled; + Common::Flag m_read_shutdown; + + void ReadThreadHandler(); + }; + + std::unique_ptr m_network_interface; + + static constexpr u32 INVALID_TRANSFER_DESCRIPTOR = 0xFFFFFFFF; + + u32 m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; + + std::string m_at_command_data; + std::string m_at_reply_data; + std::string m_send_buffer; + std::mutex m_receive_buffer_lock; + std::string m_receive_buffer; + std::array m_regs; +}; +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp b/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp new file mode 100644 index 0000000000..9db6e5e76e --- /dev/null +++ b/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp @@ -0,0 +1,324 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/EXI/EXI_DeviceModem.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" + +namespace ExpansionInterface +{ + +#ifdef _WIN32 +static constexpr auto pi_close = &closesocket; +using ws_ssize_t = int; +#else +static constexpr auto pi_close = &close; +using ws_ssize_t = ssize_t; +#endif + +#ifdef __LINUX__ +#define SEND_FLAGS MSG_NOSIGNAL +#else +#define SEND_FLAGS 0 +#endif + +static int ConnectToDestination(const std::string& destination) +{ + if (destination.empty()) + { + ERROR_LOG_FMT(SP1, "Cannot connect: destination is empty\n"); + return -1; + } + + int ss_size; + struct sockaddr_storage ss; + memset(&ss, 0, sizeof(ss)); + if (destination[0] != '/') + { + // IP address or hostname + size_t colon_offset = destination.find(':'); + if (colon_offset == std::string::npos) + { + ERROR_LOG_FMT(SP1, "Destination IP address does not include port\n"); + return -1; + } + + struct sockaddr_in* sin = reinterpret_cast(&ss); + sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); + sin->sin_family = AF_INET; + sin->sin_port = htons(stoul(destination.substr(colon_offset + 1))); + ss_size = sizeof(*sin); +#ifndef _WIN32 + } + else + { + // UNIX socket + struct sockaddr_un* sun = reinterpret_cast(&ss); + if (destination.size() + 1 > sizeof(sun->sun_path)) + { + ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); + return -1; + } + sun->sun_family = AF_UNIX; + strcpy(sun->sun_path, destination.c_str()); + ss_size = sizeof(*sun); +#else + } + else + { + ERROR_LOG_FMT(SP1, "UNIX sockets are not supported on Windows\n"); + return -1; +#endif + } + + int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); + if (fd == -1) + { + ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); + return -1; + } + +#ifdef __APPLE__ + int opt_no_sigpipe = 1; + if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) + INFO_LOG_FMT(SP1, "Failed to set SO_NOSIGPIPE on socket\n"); +#endif + + if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) + { + std::string s = Common::LastStrerrorString(); + INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); + pi_close(fd); + return -1; + } + + return fd; +} + +bool CEXIModem::TAPServerNetworkInterface::Activate() +{ + if (IsActivated()) + return true; + + m_fd = ConnectToDestination(m_destination); + if (m_fd < 0) + { + return false; + } + + INFO_LOG_FMT(SP1, "Modem initialized."); + return RecvInit(); +} + +void CEXIModem::TAPServerNetworkInterface::Deactivate() +{ + if (m_fd >= 0) + { + pi_close(m_fd); + } + m_fd = -1; + + m_read_enabled.Clear(); + m_read_shutdown.Set(); + if (m_read_thread.joinable()) + { + m_read_thread.join(); + } + m_read_shutdown.Clear(); +} + +bool CEXIModem::TAPServerNetworkInterface::IsActivated() +{ + return (m_fd >= 0); +} + +bool CEXIModem::TAPServerNetworkInterface::RecvInit() +{ + m_read_thread = std::thread(&CEXIModem::TAPServerNetworkInterface::ReadThreadHandler, this); + return true; +} + +void CEXIModem::TAPServerNetworkInterface::RecvStart() +{ + m_read_enabled.Set(); +} + +void CEXIModem::TAPServerNetworkInterface::RecvStop() +{ + m_read_enabled.Clear(); +} + +bool CEXIModem::TAPServerNetworkInterface::SendFrames() +{ + while (!m_modem_ref->m_send_buffer.empty()) + { + size_t start_offset = m_modem_ref->m_send_buffer.find(0x7E); + if (start_offset == std::string::npos) + { + break; + } + size_t end_sentinel_offset = m_modem_ref->m_send_buffer.find(0x7E, start_offset + 1); + if (end_sentinel_offset == std::string::npos) + { + break; + } + size_t end_offset = end_sentinel_offset + 1; + size_t size = end_offset - start_offset; + + uint8_t size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + if (send(m_fd, size_bytes, 2, SEND_FLAGS) != 2) + { + ERROR_LOG_FMT(SP1, "SendFrames(): could not write size field"); + return false; + } + int written_bytes = + send(m_fd, m_modem_ref->m_send_buffer.data() + start_offset, size, SEND_FLAGS); + if (u32(written_bytes) != size) + { + ERROR_LOG_FMT(SP1, "SendFrames(): expected to write {} bytes, instead wrote {}", size, + written_bytes); + return false; + } + else + { + m_modem_ref->m_send_buffer = m_modem_ref->m_send_buffer.substr(end_offset); + m_modem_ref->SendComplete(); + } + } + return true; +} + +void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() +{ + enum class ReadState + { + SIZE, + SIZE_HIGH, + DATA, + SKIP, + }; + ReadState read_state = ReadState::SIZE; + + size_t frame_bytes_received = 0; + size_t frame_bytes_expected = 0; + std::string frame_data; + + while (!m_read_shutdown.IsSet()) + { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(m_fd, &rfds); + + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 50000; + if (select(m_fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) + continue; + + // The tapserver protocol is very simple: there is a 16-bit little-endian + // size field, followed by that many bytes of packet data + switch (read_state) + { + case ReadState::SIZE: + { + u8 size_bytes[2]; + ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(size_bytes), 2, 0); + if (bytes_read == 1) + { + read_state = ReadState::SIZE_HIGH; + frame_bytes_expected = size_bytes[0]; + } + else if (bytes_read == 2) + { + frame_bytes_expected = size_bytes[0] | (size_bytes[1] << 8); + frame_data.resize(frame_bytes_expected, '\0'); + if (frame_bytes_expected > MODEM_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); + read_state = ReadState::SKIP; + } + else + { + read_state = ReadState::DATA; + } + } + else + { + ERROR_LOG_FMT(SP1, "Failed to read size field from destination: {}", + Common::LastStrerrorString()); + } + break; + } + case ReadState::SIZE_HIGH: + { + // This handles the annoying case where only one byte of the size field + // was available earlier. + u8 size_high = 0; + ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(&size_high), 1, 0); + if (bytes_read == 1) + { + frame_bytes_expected |= (size_high << 8); + frame_data.resize(frame_bytes_expected, '\0'); + if (frame_bytes_expected > MODEM_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); + read_state = ReadState::SKIP; + } + else + { + read_state = ReadState::DATA; + } + } + else + { + ERROR_LOG_FMT(SP1, "Failed to read split size field from destination: {}", + Common::LastStrerrorString()); + } + break; + } + case ReadState::DATA: + case ReadState::SKIP: + { + ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received, + frame_data.size() - frame_bytes_received, 0); + if (bytes_read <= 0) + { + ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", + Common::LastStrerrorString()); + } + else + { + frame_bytes_received += bytes_read; + if (frame_bytes_received == frame_bytes_expected) + { + if (read_state == ReadState::DATA) + { + m_modem_ref->AddToReceiveBuffer(std::move(frame_data)); + } + frame_data.clear(); + frame_bytes_received = 0; + frame_bytes_expected = 0; + read_state = ReadState::SIZE; + } + } + break; + } + } + } +} + +} // namespace ExpansionInterface diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp index 9716d8f211..27579b5ac8 100644 --- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp @@ -49,7 +49,12 @@ void BroadbandAdapterSettingsDialog::InitControls() break; case Type::TapServer: - current_address = QString::fromStdString(Config::Get(Config::MAIN_BBA_TAPSERVER_DESTINATION)); + case Type::ModemTapServer: + { + bool is_modem = (m_bba_type == Type::ModemTapServer); + current_address = + QString::fromStdString(Config::Get(is_modem ? Config::MAIN_MODEM_TAPSERVER_DESTINATION : + Config::MAIN_BBA_TAPSERVER_DESTINATION)); #ifdef _WIN32 address_label = new QLabel(tr("Destination (address:port):")); address_placeholder = QStringLiteral(""); @@ -58,12 +63,24 @@ void BroadbandAdapterSettingsDialog::InitControls() #else address_label = new QLabel(tr("Destination (UNIX socket path or address:port):")); address_placeholder = QStringLiteral("/tmp/dolphin-tap"); - description = new QLabel(tr( - "The default value \"/tmp/dolphin-tap\" will work with a local tapserver and newserv. You " - "can also enter a network location (address:port) to connect to a remote tapserver.")); + if (is_modem) + { + description = new QLabel( + tr("The default value \"/tmp/dolphin-modem-tap\" will work with a local tapserver and " + "newserv. You " + "can also enter a network location (address:port) to connect to a remote tapserver.")); + } + else + { + description = new QLabel( + tr("The default value \"/tmp/dolphin-tap\" will work with a local tapserver and newserv. " + "You " + "can also enter a network location (address:port) to connect to a remote tapserver.")); + } #endif window_title = tr("BBA destination address"); break; + } case Type::BuiltIn: address_label = new QLabel(tr("Enter the DNS server to use:")); @@ -134,6 +151,9 @@ void BroadbandAdapterSettingsDialog::SaveAddress() case Type::TapServer: Config::SetBaseOrCurrent(Config::MAIN_BBA_TAPSERVER_DESTINATION, bba_new_address); break; + case Type::ModemTapServer: + Config::SetBaseOrCurrent(Config::MAIN_MODEM_TAPSERVER_DESTINATION, bba_new_address); + break; case Type::BuiltIn: Config::SetBaseOrCurrent(Config::MAIN_BBA_BUILTIN_DNS, bba_new_address); break; diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h index 53863f1d15..08a8793a08 100644 --- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h +++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h @@ -16,7 +16,8 @@ public: Ethernet, XLinkKai, TapServer, - BuiltIn + BuiltIn, + ModemTapServer }; explicit BroadbandAdapterSettingsDialog(QWidget* target, Type bba_type); diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 944efa7a9f..c268625959 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -151,6 +151,7 @@ void GameCubePane::CreateWidgets() EXIDeviceType::EthernetXLink, EXIDeviceType::EthernetTapServer, EXIDeviceType::EthernetBuiltIn, + EXIDeviceType::ModemTapServer, }) { m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()), @@ -354,7 +355,8 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot) has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet || device == ExpansionInterface::EXIDeviceType::EthernetXLink || device == ExpansionInterface::EXIDeviceType::EthernetTapServer || - device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn); + device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn || + device == ExpansionInterface::EXIDeviceType::ModemTapServer); break; } @@ -406,6 +408,14 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot) dialog.exec(); return; } + case ExpansionInterface::EXIDeviceType::ModemTapServer: + { + BroadbandAdapterSettingsDialog dialog(this, + BroadbandAdapterSettingsDialog::Type::ModemTapServer); + SetQWidgetWindowDecorations(&dialog); + dialog.exec(); + return; + } case ExpansionInterface::EXIDeviceType::EthernetBuiltIn: { BroadbandAdapterSettingsDialog dialog(this, BroadbandAdapterSettingsDialog::Type::BuiltIn); From dcb7a72c14b58a6c2e395183b10a6b91116e48cd Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 6 Dec 2023 16:12:48 -0800 Subject: [PATCH 06/13] add modem files to windows build --- Source/Core/DolphinLib.props | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 2229ee9d06..72c2f0ba1d 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -284,6 +284,7 @@ + @@ -939,6 +940,7 @@ + @@ -949,6 +951,7 @@ + From 9cf8131b239e1704a0898cd3805400357ecf8ef2 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 28 Jan 2024 17:26:57 -0800 Subject: [PATCH 07/13] respond to review feedback on tapserver implementation --- Source/Core/Core/CMakeLists.txt | 5 +- Source/Core/Core/HW/EXI/BBA/TAPServer.cpp | 305 ------------------ Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp | 88 +++++ .../TAPServerConnection.cpp} | 139 ++++---- .../Core/HW/EXI/BBA/TAPServerConnection.h | 66 ++++ Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 27 +- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 75 +++-- Source/Core/Core/HW/EXI/EXI_DeviceModem.h | 56 ++-- .../Core/Core/HW/EXI/Modem/TAPServerModem.cpp | 79 +++++ Source/Core/DolphinLib.props | 5 +- 10 files changed, 389 insertions(+), 456 deletions(-) delete mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServer.cpp create mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp rename Source/Core/Core/HW/EXI/{Modem/TAPServer.cpp => BBA/TAPServerConnection.cpp} (57%) create mode 100644 Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h create mode 100644 Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f36235d7d4..e87983e15b 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -189,11 +189,12 @@ add_library(core HW/DVD/DVDThread.h HW/DVD/FileMonitor.cpp HW/DVD/FileMonitor.h - HW/EXI/BBA/TAPServer.cpp + HW/EXI/BBA/TAPServerConnection.cpp + HW/EXI/BBA/TAPServerBBA.cpp HW/EXI/BBA/XLINK_KAI_BBA.cpp HW/EXI/BBA/BuiltIn.cpp HW/EXI/BBA/BuiltIn.h - HW/EXI/Modem/TAPServer.cpp + HW/EXI/Modem/TAPServerModem.cpp HW/EXI/EXI_Channel.cpp HW/EXI/EXI_Channel.h HW/EXI/EXI_Device.cpp diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp deleted file mode 100644 index f81c1f479f..0000000000 --- a/Source/Core/Core/HW/EXI/BBA/TAPServer.cpp +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2020 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/HW/EXI/EXI_DeviceEthernet.h" - -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif - -#include "Common/CommonFuncs.h" -#include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Core/HW/EXI/EXI_Device.h" - -namespace ExpansionInterface -{ - -#ifdef _WIN32 -static constexpr auto pi_close = &closesocket; -using ws_ssize_t = int; -#else -static constexpr auto pi_close = &close; -using ws_ssize_t = ssize_t; -#endif - -#ifdef __LINUX__ -#define SEND_FLAGS MSG_NOSIGNAL -#else -#define SEND_FLAGS 0 -#endif - -static int ConnectToDestination(const std::string& destination) -{ - if (destination.empty()) - { - ERROR_LOG_FMT(SP1, "Cannot connect: destination is empty\n"); - return -1; - } - - int ss_size; - struct sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); - if (destination[0] != '/') - { - // IP address or hostname - size_t colon_offset = destination.find(':'); - if (colon_offset == std::string::npos) - { - ERROR_LOG_FMT(SP1, "Destination IP address does not include port\n"); - return -1; - } - - struct sockaddr_in* sin = reinterpret_cast(&ss); - sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); - sin->sin_family = AF_INET; - sin->sin_port = htons(stoul(destination.substr(colon_offset + 1))); - ss_size = sizeof(*sin); -#ifndef _WIN32 - } - else - { - // UNIX socket - struct sockaddr_un* sun = reinterpret_cast(&ss); - if (destination.size() + 1 > sizeof(sun->sun_path)) - { - ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); - return -1; - } - sun->sun_family = AF_UNIX; - strcpy(sun->sun_path, destination.c_str()); - ss_size = sizeof(*sun); -#else - } - else - { - ERROR_LOG_FMT(SP1, "UNIX sockets are not supported on Windows\n"); - return -1; -#endif - } - - int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); - if (fd == -1) - { - ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); - return -1; - } - -#ifdef __APPLE__ - int opt_no_sigpipe = 1; - if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &opt_no_sigpipe, sizeof(opt_no_sigpipe)) < 0) - INFO_LOG_FMT(SP1, "Failed to set SO_NOSIGPIPE on socket\n"); -#endif - - if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) - { - std::string s = Common::LastStrerrorString(); - INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); - pi_close(fd); - return -1; - } - - return fd; -} - -bool CEXIETHERNET::TAPServerNetworkInterface::Activate() -{ - if (IsActivated()) - return true; - - m_fd = ConnectToDestination(m_destination); - - INFO_LOG_FMT(SP1, "BBA initialized."); - return RecvInit(); -} - -void CEXIETHERNET::TAPServerNetworkInterface::Deactivate() -{ - pi_close(m_fd); - m_fd = -1; - - m_read_enabled.Clear(); - m_read_shutdown.Set(); - if (m_read_thread.joinable()) - m_read_thread.join(); -} - -bool CEXIETHERNET::TAPServerNetworkInterface::IsActivated() -{ - return (m_fd >= 0); -} - -bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() -{ - m_read_thread = std::thread(&CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler, this); - return true; -} - -void CEXIETHERNET::TAPServerNetworkInterface::RecvStart() -{ - m_read_enabled.Set(); -} - -void CEXIETHERNET::TAPServerNetworkInterface::RecvStop() -{ - m_read_enabled.Clear(); -} - -bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) -{ - { - const std::string s = ArrayToString(frame, size, 0x10); - INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, s); - } - - // On Windows, the data pointer is of type const char*; on other systems it is - // of type const void*. This is the reason for the reinterpret_cast here and - // in the other send/recv calls in this file. - u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; - if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) - { - ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field"); - return false; - } - int written_bytes = send(m_fd, reinterpret_cast(frame), size, SEND_FLAGS); - if (u32(written_bytes) != size) - { - ERROR_LOG_FMT(SP1, "SendFrame(): expected to write {} bytes, instead wrote {}", size, - written_bytes); - return false; - } - else - { - m_eth_ref->SendComplete(); - return true; - } -} - -void CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler() -{ - while (!m_read_shutdown.IsSet()) - { - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(m_fd, &rfds); - - timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 50000; - if (select(m_fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) - continue; - - // The tapserver protocol is very simple: there is a 16-bit little-endian - // size field, followed by that many bytes of packet data - switch (m_read_state) - { - case ReadState::Size: - { - u8 size_bytes[2]; - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(size_bytes), 2, 0); - if (bytes_read == 1) - { - m_read_state = ReadState::SizeHigh; - m_read_packet_bytes_remaining = size_bytes[0]; - } - else if (bytes_read == 2) - { - m_read_packet_bytes_remaining = size_bytes[0] | (size_bytes[1] << 8); - if (m_read_packet_bytes_remaining > BBA_RECV_SIZE) - { - ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", - m_read_packet_bytes_remaining); - m_read_state = ReadState::Skip; - } - else - { - m_read_state = ReadState::Data; - } - } - else - { - ERROR_LOG_FMT(SP1, "Failed to read size field from BBA: {}", Common::LastStrerrorString()); - } - break; - } - case ReadState::SizeHigh: - { - // This handles the annoying case where only one byte of the size field - // was available earlier. - u8 size_high = 0; - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(&size_high), 1, 0); - if (bytes_read == 1) - { - m_read_packet_bytes_remaining |= (size_high << 8); - if (m_read_packet_bytes_remaining > BBA_RECV_SIZE) - { - ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", - m_read_packet_bytes_remaining); - m_read_state = ReadState::Skip; - } - else - { - m_read_state = ReadState::Data; - } - } - else - { - ERROR_LOG_FMT(SP1, "Failed to read split size field from BBA: {}", - Common::LastStrerrorString()); - } - break; - } - case ReadState::Data: - { - ws_ssize_t bytes_read = - recv(m_fd, reinterpret_cast(m_eth_ref->mRecvBuffer.get() + m_read_packet_offset), - m_read_packet_bytes_remaining, 0); - if (bytes_read <= 0) - { - ERROR_LOG_FMT(SP1, "Failed to read data from BBA: {}", Common::LastStrerrorString()); - } - else - { - m_read_packet_offset += bytes_read; - m_read_packet_bytes_remaining -= bytes_read; - if (m_read_packet_bytes_remaining == 0) - { - m_eth_ref->mRecvBufferLength = m_read_packet_offset; - m_eth_ref->RecvHandlePacket(); - m_read_state = ReadState::Size; - m_read_packet_offset = 0; - } - } - break; - } - case ReadState::Skip: - { - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(m_eth_ref->mRecvBuffer.get()), - std::min(m_read_packet_bytes_remaining, BBA_RECV_SIZE), 0); - if (bytes_read <= 0) - { - ERROR_LOG_FMT(SP1, "Failed to read data from BBA: {}", Common::LastStrerrorString()); - } - else - { - m_read_packet_bytes_remaining -= bytes_read; - if (m_read_packet_bytes_remaining == 0) - { - m_read_state = ReadState::Size; - m_read_packet_offset = 0; - } - } - break; - } - } - } -} - -} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp new file mode 100644 index 0000000000..f0c0a4ce0b --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp @@ -0,0 +1,88 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/EXI/EXI_DeviceEthernet.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" + +namespace ExpansionInterface +{ + +CEXIETHERNET::TAPServerNetworkInterface::TAPServerNetworkInterface(CEXIETHERNET* eth_ref, + const std::string& destination) + : NetworkInterface(eth_ref), + m_tapserver_if( + destination, + std::bind(&TAPServerNetworkInterface::HandleReceivedFrame, this, std::placeholders::_1), + BBA_RECV_SIZE) +{ +} + +bool CEXIETHERNET::TAPServerNetworkInterface::Activate() +{ + return m_tapserver_if.Activate(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::Deactivate() +{ + m_tapserver_if.Deactivate(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::IsActivated() +{ + return m_tapserver_if.IsActivated(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit() +{ + return m_tapserver_if.RecvInit(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::RecvStart() +{ + m_tapserver_if.RecvStart(); +} + +void CEXIETHERNET::TAPServerNetworkInterface::RecvStop() +{ + m_tapserver_if.RecvStop(); +} + +bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) +{ + bool ret = m_tapserver_if.SendFrame(frame, size); + if (ret) + m_eth_ref->SendComplete(); + return ret; +} + +void CEXIETHERNET::TAPServerNetworkInterface::HandleReceivedFrame(std::string&& data) +{ + if (data.size() > BBA_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Received BBA frame of size {}, which is larger than maximum size {}", + data.size(), BBA_RECV_SIZE); + } + else + { + memcpy(m_eth_ref->mRecvBuffer.get(), data.data(), data.size()); + m_eth_ref->mRecvBufferLength = data.size(); + m_eth_ref->RecvHandlePacket(); + } +} + +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp similarity index 57% rename from Source/Core/Core/HW/EXI/Modem/TAPServer.cpp rename to Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 9db6e5e76e..13b03a9349 100644 --- a/Source/Core/Core/HW/EXI/Modem/TAPServer.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -1,7 +1,7 @@ // Copyright 2020 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "Core/HW/EXI/EXI_DeviceModem.h" +#include "Core/HW/EXI/EXI_DeviceEthernet.h" #ifdef _WIN32 #include @@ -23,10 +23,9 @@ namespace ExpansionInterface { #ifdef _WIN32 -static constexpr auto pi_close = &closesocket; using ws_ssize_t = int; #else -static constexpr auto pi_close = &close; +#define closesocket close using ws_ssize_t = ssize_t; #endif @@ -36,6 +35,13 @@ using ws_ssize_t = ssize_t; #define SEND_FLAGS 0 #endif +TAPServerConnection::TAPServerConnection(const std::string& destination, + std::function recv_cb, + std::size_t max_frame_size) + : m_destination(destination), m_recv_cb(recv_cb), m_max_frame_size(max_frame_size) +{ +} + static int ConnectToDestination(const std::string& destination) { if (destination.empty()) @@ -45,32 +51,33 @@ static int ConnectToDestination(const std::string& destination) } int ss_size; - struct sockaddr_storage ss; + sockaddr_storage ss; memset(&ss, 0, sizeof(ss)); if (destination[0] != '/') { // IP address or hostname - size_t colon_offset = destination.find(':'); + const std::size_t colon_offset = destination.find(':'); if (colon_offset == std::string::npos) { ERROR_LOG_FMT(SP1, "Destination IP address does not include port\n"); return -1; } - struct sockaddr_in* sin = reinterpret_cast(&ss); + sockaddr_in* sin = reinterpret_cast(&ss); sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); sin->sin_family = AF_INET; - sin->sin_port = htons(stoul(destination.substr(colon_offset + 1))); + std::string port_str = destination.substr(colon_offset + 1); + sin->sin_port = htons(atoi(port_str.c_str())); ss_size = sizeof(*sin); #ifndef _WIN32 } else { // UNIX socket - struct sockaddr_un* sun = reinterpret_cast(&ss); + sockaddr_un* sun = reinterpret_cast(&ss); if (destination.size() + 1 > sizeof(sun->sun_path)) { - ERROR_LOG_FMT(SP1, "Socket path is too long, unable to init BBA\n"); + ERROR_LOG_FMT(SP1, "Socket path is too long; unable to create tapserver connection\n"); return -1; } sun->sun_family = AF_UNIX; @@ -85,10 +92,10 @@ static int ConnectToDestination(const std::string& destination) #endif } - int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); + const int fd = socket(ss.ss_family, SOCK_STREAM, (ss.ss_family == AF_INET) ? IPPROTO_TCP : 0); if (fd == -1) { - ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to init BBA\n"); + ERROR_LOG_FMT(SP1, "Couldn't create socket; unable to create tapserver connection\n"); return -1; } @@ -100,109 +107,129 @@ static int ConnectToDestination(const std::string& destination) if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) { - std::string s = Common::LastStrerrorString(); - INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str()); - pi_close(fd); + std::string s = Common::StrNetworkError(); + INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to create tapserver connection\n", s); + closesocket(fd); return -1; } return fd; } -bool CEXIModem::TAPServerNetworkInterface::Activate() +bool TAPServerConnection::Activate() { if (IsActivated()) return true; m_fd = ConnectToDestination(m_destination); if (m_fd < 0) - { return false; - } - INFO_LOG_FMT(SP1, "Modem initialized."); return RecvInit(); } -void CEXIModem::TAPServerNetworkInterface::Deactivate() +void TAPServerConnection::Deactivate() { if (m_fd >= 0) - { - pi_close(m_fd); - } + closesocket(m_fd); m_fd = -1; m_read_enabled.Clear(); m_read_shutdown.Set(); if (m_read_thread.joinable()) - { m_read_thread.join(); - } m_read_shutdown.Clear(); } -bool CEXIModem::TAPServerNetworkInterface::IsActivated() +bool TAPServerConnection::IsActivated() { return (m_fd >= 0); } -bool CEXIModem::TAPServerNetworkInterface::RecvInit() +bool TAPServerConnection::RecvInit() { - m_read_thread = std::thread(&CEXIModem::TAPServerNetworkInterface::ReadThreadHandler, this); + m_read_thread = std::thread(&TAPServerConnection::ReadThreadHandler, this); return true; } -void CEXIModem::TAPServerNetworkInterface::RecvStart() +void TAPServerConnection::RecvStart() { m_read_enabled.Set(); } -void CEXIModem::TAPServerNetworkInterface::RecvStop() +void TAPServerConnection::RecvStop() { m_read_enabled.Clear(); } -bool CEXIModem::TAPServerNetworkInterface::SendFrames() +bool TAPServerConnection::SendAndRemoveAllHDLCFrames(std::string& send_buf) { - while (!m_modem_ref->m_send_buffer.empty()) + while (!send_buf.empty()) { - size_t start_offset = m_modem_ref->m_send_buffer.find(0x7E); + std::size_t start_offset = send_buf.find(0x7E); if (start_offset == std::string::npos) { break; } - size_t end_sentinel_offset = m_modem_ref->m_send_buffer.find(0x7E, start_offset + 1); + std::size_t end_sentinel_offset = send_buf.find(0x7E, start_offset + 1); if (end_sentinel_offset == std::string::npos) { break; } - size_t end_offset = end_sentinel_offset + 1; - size_t size = end_offset - start_offset; + std::size_t end_offset = end_sentinel_offset + 1; + std::size_t size = end_offset - start_offset; - uint8_t size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; - if (send(m_fd, size_bytes, 2, SEND_FLAGS) != 2) + u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) { - ERROR_LOG_FMT(SP1, "SendFrames(): could not write size field"); + ERROR_LOG_FMT(SP1, "SendAndRemoveAllHDLCFrames(): could not write size field"); return false; } - int written_bytes = - send(m_fd, m_modem_ref->m_send_buffer.data() + start_offset, size, SEND_FLAGS); + const int written_bytes = + send(m_fd, send_buf.data() + start_offset, static_cast(size), SEND_FLAGS); if (u32(written_bytes) != size) { - ERROR_LOG_FMT(SP1, "SendFrames(): expected to write {} bytes, instead wrote {}", size, - written_bytes); + ERROR_LOG_FMT(SP1, + "SendAndRemoveAllHDLCFrames(): expected to write {} bytes, instead wrote {}", + size, written_bytes); return false; } else { - m_modem_ref->m_send_buffer = m_modem_ref->m_send_buffer.substr(end_offset); - m_modem_ref->SendComplete(); + send_buf = send_buf.substr(end_offset); } } return true; } -void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() +bool TAPServerConnection::SendFrame(const u8* frame, u32 size) +{ + { + const std::string s = ArrayToString(frame, size, 0x10); + INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, s); + } + + // On Windows, the data pointer is of type const char*; on other systems it is + // of type const void*. This is the reason for the reinterpret_cast here and + // in the other send/recv calls in this file. + u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) + { + ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field"); + return false; + } + int written_bytes = + send(m_fd, reinterpret_cast(frame), static_cast(size), SEND_FLAGS); + if (u32(written_bytes) != size) + { + ERROR_LOG_FMT(SP1, "SendFrame(): expected to write {} bytes, instead wrote {}", size, + written_bytes); + return false; + } + return true; +} + +void TAPServerConnection::ReadThreadHandler() { enum class ReadState { @@ -213,8 +240,8 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() }; ReadState read_state = ReadState::SIZE; - size_t frame_bytes_received = 0; - size_t frame_bytes_expected = 0; + std::size_t frame_bytes_received = 0; + std::size_t frame_bytes_expected = 0; std::string frame_data; while (!m_read_shutdown.IsSet()) @@ -246,7 +273,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() { frame_bytes_expected = size_bytes[0] | (size_bytes[1] << 8); frame_data.resize(frame_bytes_expected, '\0'); - if (frame_bytes_expected > MODEM_RECV_SIZE) + if (frame_bytes_expected > m_max_frame_size) { ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); read_state = ReadState::SKIP; @@ -259,7 +286,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() else { ERROR_LOG_FMT(SP1, "Failed to read size field from destination: {}", - Common::LastStrerrorString()); + Common::StrNetworkError()); } break; } @@ -273,7 +300,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() { frame_bytes_expected |= (size_high << 8); frame_data.resize(frame_bytes_expected, '\0'); - if (frame_bytes_expected > MODEM_RECV_SIZE) + if (frame_bytes_expected > m_max_frame_size) { ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); read_state = ReadState::SKIP; @@ -286,19 +313,19 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() else { ERROR_LOG_FMT(SP1, "Failed to read split size field from destination: {}", - Common::LastStrerrorString()); + Common::StrNetworkError()); } break; } case ReadState::DATA: case ReadState::SKIP: { - ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received, - frame_data.size() - frame_bytes_received, 0); + ws_ssize_t bytes_to_read = frame_data.size() - frame_bytes_received; + ws_ssize_t bytes_read = + recv(m_fd, frame_data.data() + frame_bytes_received, bytes_to_read, 0); if (bytes_read <= 0) { - ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", - Common::LastStrerrorString()); + ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", Common::StrNetworkError()); } else { @@ -307,7 +334,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler() { if (read_state == ReadState::DATA) { - m_modem_ref->AddToReceiveBuffer(std::move(frame_data)); + m_recv_cb(std::move(frame_data)); } frame_data.clear(); frame_bytes_received = 0; diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h new file mode 100644 index 0000000000..ae10fc93a8 --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h @@ -0,0 +1,66 @@ +// Copyright 2020 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/SocketContext.h" +#include "Common/StringUtil.h" + +namespace ExpansionInterface +{ + +class TAPServerConnection +{ +public: + TAPServerConnection(const std::string& destination, std::function recv_cb, + std::size_t max_frame_size); + + bool Activate(); + void Deactivate(); + bool IsActivated(); + bool RecvInit(); + void RecvStart(); + void RecvStop(); + bool SendAndRemoveAllHDLCFrames(std::string& send_buf); + bool SendFrame(const u8* frame, u32 size); + +private: + enum class ReadState + { + Size, + SizeHigh, + Data, + Skip, + }; + + std::string m_destination; + std::function m_recv_cb; + std::size_t m_max_frame_size; + Common::SocketContext m_socket_context; + + int m_fd = -1; + std::thread m_read_thread; + Common::Flag m_read_enabled; + Common::Flag m_read_shutdown; + + bool StartReadThread(); + void ReadThreadHandler(); +}; + +} // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 067f5d59c0..465f050521 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -19,6 +19,7 @@ #include "Common/Network.h" #include "Common/SocketContext.h" #include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Core/HW/EXI/BBA/TAPServerConnection.h" #include "Core/HW/EXI/EXI_Device.h" class PointerWrap; @@ -366,10 +367,7 @@ private: class TAPServerNetworkInterface : public NetworkInterface { public: - explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination) - : NetworkInterface(eth_ref), m_destination(destination) - { - } + TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination); public: bool Activate() override; @@ -381,26 +379,9 @@ private: void RecvStop() override; private: - enum class ReadState - { - Size, - SizeHigh, - Data, - Skip, - }; + TAPServerConnection m_tapserver_if; - std::string m_destination; - Common::SocketContext m_socket_context; - - int m_fd = -1; - ReadState m_read_state = ReadState::Size; - u16 m_read_packet_offset; - u16 m_read_packet_bytes_remaining; - std::thread m_read_thread; - Common::Flag m_read_enabled; - Common::Flag m_read_shutdown; - - void ReadThreadHandler(); + void HandleReceivedFrame(std::string&& data); }; class XLinkNetworkInterface : public NetworkInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp index 867044ec89..e488570a42 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -1,4 +1,4 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2024 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HW/EXI/EXI_DeviceModem.h" @@ -37,10 +37,6 @@ CEXIModem::CEXIModem(Core::System& system, ModemDeviceType type) : IEXIDevice(sy break; } - for (size_t z = 0; z < m_regs.size(); z++) - { - m_regs[z] = 0; - } m_regs[Register::DEVICE_TYPE] = 0x02; m_regs[Register::INTERRUPT_MASK] = 0x02; } @@ -84,15 +80,19 @@ void CEXIModem::ImmWrite(u32 data, u32 size) } else if (IsModemTransfer(m_transfer_descriptor)) { // Write AT command buffer or packet send buffer - u32 be_data = htonl(data); + const u32 be_data = htonl(data); HandleWriteModemTransfer(&be_data, size); } else { // Write device register - uint8_t reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); bool should_update_interrupts = false; for (; size; size--) { + if (reg_num >= m_regs.size()) + { + break; + } should_update_interrupts |= ((reg_num == Register::INTERRUPT_MASK) || (reg_num == Register::PENDING_INTERRUPT_MASK)); m_regs[reg_num++] = (data >> 24); @@ -154,14 +154,18 @@ u32 CEXIModem::ImmRead(u32 size) } else { // Read device register - uint8_t reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); if (reg_num == 0) { return 0x02020000; // Device ID (modem) } u32 ret = 0; - for (size_t z = 0; z < size; z++) + for (u8 z = 0; z < size; z++) { + if (reg_num + z >= m_regs.size()) + { + break; + } ret |= (m_regs[reg_num + z] << ((3 - z) * 8)); } m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; @@ -197,28 +201,31 @@ void CEXIModem::DMARead(u32 addr, u32 size) void CEXIModem::HandleReadModemTransfer(void* data, u32 size) { - u16 bytes_requested = GetModemTransferSize(m_transfer_descriptor); + const u16 bytes_requested = GetModemTransferSize(m_transfer_descriptor); if (size > bytes_requested) { ERROR_LOG_FMT(SP1, "More bytes requested ({}) than originally requested for transfer {:x}", size, m_transfer_descriptor); size = bytes_requested; } - u16 bytes_requested_after_read = bytes_requested - size; + const u16 bytes_requested_after_read = bytes_requested - size; if ((m_transfer_descriptor & 0x0F000000) == 0x03000000) - { // AT command buffer - memcpy(data, m_at_reply_data.data(), std::min(size, m_at_reply_data.size())); - m_at_reply_data = m_at_reply_data.substr(size); - m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size(); + { + // AT command buffer + const std::size_t bytes_to_copy = std::min(size, m_at_reply_data.size()); + memcpy(data, m_at_reply_data.data(), bytes_to_copy); + m_at_reply_data = m_at_reply_data.substr(bytes_to_copy); + m_regs[Register::AT_REPLY_SIZE] = static_cast(m_at_reply_data.size()); SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true); } else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000) - { // Packet receive buffer + { + // Packet receive buffer std::lock_guard g(m_receive_buffer_lock); - size_t bytes_to_copy = std::min(size, m_receive_buffer.size()); + const std::size_t bytes_to_copy = std::min(size, m_receive_buffer.size()); memcpy(data, m_receive_buffer.data(), bytes_to_copy); - m_receive_buffer = m_receive_buffer.substr(size); + m_receive_buffer = m_receive_buffer.substr(bytes_to_copy); OnReceiveBufferSizeChangedLocked(true); } else @@ -234,20 +241,20 @@ void CEXIModem::HandleReadModemTransfer(void* data, u32 size) void CEXIModem::HandleWriteModemTransfer(const void* data, u32 size) { - u16 bytes_expected = GetModemTransferSize(m_transfer_descriptor); + const u16 bytes_expected = GetModemTransferSize(m_transfer_descriptor); if (size > bytes_expected) { ERROR_LOG_FMT(SP1, "More bytes received ({}) than expected for transfer {:x}", size, m_transfer_descriptor); return; } - u16 bytes_expected_after_write = bytes_expected - size; + const u16 bytes_expected_after_write = bytes_expected - size; if ((m_transfer_descriptor & 0x0F000000) == 0x03000000) { // AT command buffer m_at_command_data.append(reinterpret_cast(data), size); RunAllPendingATCommands(); - m_regs[Register::AT_COMMAND_SIZE] = m_at_command_data.size(); + m_regs[Register::AT_COMMAND_SIZE] = static_cast(m_at_command_data.size()); } else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000) { // Packet send buffer @@ -257,7 +264,7 @@ void CEXIModem::HandleWriteModemTransfer(const void* data, u32 size) // from the emulated program's perspective, so we always tell it the send // FIFO is empty. SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true); - m_network_interface->SendFrames(); + m_network_interface->SendAndRemoveAllHDLCFrames(m_send_buffer); } else { @@ -289,7 +296,7 @@ u16 CEXIModem::GetRxThreshold() const return (m_regs[Register::RX_THRESHOLD_HIGH] << 8) | m_regs[Register::RX_THRESHOLD_LOW]; } -void CEXIModem::SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu) +void CEXIModem::SetInterruptFlag(u8 what, bool enabled, bool from_cpu) { if (enabled) { @@ -306,7 +313,7 @@ void CEXIModem::SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu) void CEXIModem::OnReceiveBufferSizeChangedLocked(bool from_cpu) { // The caller is expected to hold m_receive_buffer_lock when calling this. - uint16_t bytes_available = std::min(m_receive_buffer.size(), 0x200); + const u16 bytes_available = std::min(m_receive_buffer.size(), 0x200); m_regs[Register::BYTES_AVAILABLE_HIGH] = (bytes_available >> 8) & 0xFF; m_regs[Register::BYTES_AVAILABLE_LOW] = bytes_available & 0xFF; SetInterruptFlag(Interrupt::RECEIVE_BUFFER_ABOVE_THRESHOLD, @@ -343,25 +350,29 @@ void CEXIModem::AddToReceiveBuffer(std::string&& data) void CEXIModem::AddATReply(const std::string& data) { m_at_reply_data += data; - m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size(); - SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), false); + m_regs[Register::AT_REPLY_SIZE] = static_cast(m_at_reply_data.size()); + SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true); } void CEXIModem::RunAllPendingATCommands() { - for (size_t newline_pos = m_at_command_data.find_first_of("\r\n"); + for (std::size_t newline_pos = m_at_command_data.find_first_of("\r\n"); newline_pos != std::string::npos; newline_pos = m_at_command_data.find_first_of("\r\n")) { std::string command = m_at_command_data.substr(0, newline_pos); m_at_command_data = m_at_command_data.substr(newline_pos + 1); - if (command == "ATZ") - { // Reset + INFO_LOG_FMT(SP1, "Received AT command: {}", command); + + if (command.substr(0, 3) == "ATZ") + { + // Reset m_network_interface->Deactivate(); AddATReply("OK\r"); } else if (command.substr(0, 3) == "ATD") - { // Dial + { + // Dial if (m_network_interface->Activate()) { AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate @@ -373,7 +384,9 @@ void CEXIModem::RunAllPendingATCommands() } else { - INFO_LOG_FMT(SP1, "Unhandled AT command: {}", command); + // PSO sends several other AT commands during modem setup, but in our + // implementation we don't actually have to do anything in response to + // them, so we just pretend we did. AddATReply("OK\r"); } } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h index d6ba896667..7d008f869d 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h @@ -1,24 +1,15 @@ -// Copyright 2008 Dolphin Emulator Project +// Copyright 2024 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include #include #include #include -#ifdef _WIN32 -#include -#endif - -#include - #include "Common/Flag.h" #include "Common/Network.h" -#include "Common/SocketContext.h" -#include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Core/HW/EXI/BBA/TAPServerConnection.h" #include "Core/HW/EXI/EXI_Device.h" class PointerWrap; @@ -86,7 +77,7 @@ private: u16 GetTxThreshold() const; u16 GetRxThreshold() const; - void SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu); + void SetInterruptFlag(u8 what, bool enabled, bool from_cpu); void HandleReadModemTransfer(void* data, u32 size); void HandleWriteModemTransfer(const void* data, u32 size); void OnReceiveBufferSizeChangedLocked(bool from_cpu); @@ -97,19 +88,19 @@ private: static inline bool TransferIsResetCommand(u32 transfer_descriptor) { - return (transfer_descriptor == 0x80000000); + return transfer_descriptor == 0x80000000; } static inline bool IsWriteTransfer(u32 transfer_descriptor) { - return (transfer_descriptor & 0x40000000); + return transfer_descriptor & 0x40000000; } static inline bool IsModemTransfer(u32 transfer_descriptor) { - return (transfer_descriptor & 0x20000000); + return transfer_descriptor & 0x20000000; } static inline u16 GetModemTransferSize(u32 transfer_descriptor) { - return ((transfer_descriptor >> 8) & 0xFFFF); + return (transfer_descriptor >> 8) & 0xFFFF; } static inline u32 SetModemTransferSize(u32 transfer_descriptor, u16 new_size) { @@ -126,7 +117,7 @@ private: virtual bool Activate() { return false; } virtual void Deactivate() {} virtual bool IsActivated() { return false; } - virtual bool SendFrames() { return false; } + virtual bool SendAndRemoveAllHDLCFrames(std::string&) { return false; } virtual bool RecvInit() { return false; } virtual void RecvStart() {} virtual void RecvStop() {} @@ -137,30 +128,21 @@ private: class TAPServerNetworkInterface : public NetworkInterface { public: - explicit TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination) - : NetworkInterface(modem_ref), m_destination(destination) - { - } + TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination); public: - bool Activate() override; - void Deactivate() override; - bool IsActivated() override; - bool SendFrames() override; - bool RecvInit() override; - void RecvStart() override; - void RecvStop() override; + virtual bool Activate() override; + virtual void Deactivate() override; + virtual bool IsActivated() override; + virtual bool SendAndRemoveAllHDLCFrames(std::string& send_buffer) override; + virtual bool RecvInit() override; + virtual void RecvStart() override; + virtual void RecvStop() override; private: - std::string m_destination; - Common::SocketContext m_socket_context; + TAPServerConnection m_tapserver_if; - int m_fd = -1; - std::thread m_read_thread; - Common::Flag m_read_enabled; - Common::Flag m_read_shutdown; - - void ReadThreadHandler(); + void HandleReceivedFrame(std::string&& data); }; std::unique_ptr m_network_interface; @@ -174,6 +156,6 @@ private: std::string m_send_buffer; std::mutex m_receive_buffer_lock; std::string m_receive_buffer; - std::array m_regs; + std::array m_regs{}; }; } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp b/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp new file mode 100644 index 0000000000..4718c34852 --- /dev/null +++ b/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp @@ -0,0 +1,79 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/EXI/EXI_DeviceModem.h" + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/HW/EXI/EXI_Device.h" + +namespace ExpansionInterface +{ + +CEXIModem::TAPServerNetworkInterface::TAPServerNetworkInterface(CEXIModem* modem_ref, + const std::string& destination) + : NetworkInterface(modem_ref), + m_tapserver_if( + destination, + std::bind(&TAPServerNetworkInterface::HandleReceivedFrame, this, std::placeholders::_1), + MODEM_RECV_SIZE) +{ +} + +bool CEXIModem::TAPServerNetworkInterface::Activate() +{ + return m_tapserver_if.Activate(); +} + +void CEXIModem::TAPServerNetworkInterface::Deactivate() +{ + m_tapserver_if.Deactivate(); +} + +bool CEXIModem::TAPServerNetworkInterface::IsActivated() +{ + return m_tapserver_if.IsActivated(); +} + +bool CEXIModem::TAPServerNetworkInterface::SendAndRemoveAllHDLCFrames(std::string& send_buffer) +{ + std::size_t orig_size = send_buffer.size(); + bool send_succeeded = m_tapserver_if.SendAndRemoveAllHDLCFrames(send_buffer); + if (send_succeeded && (send_buffer.size() < orig_size)) + m_modem_ref->SendComplete(); + return send_succeeded; +} + +bool CEXIModem::TAPServerNetworkInterface::RecvInit() +{ + return m_tapserver_if.RecvInit(); +} + +void CEXIModem::TAPServerNetworkInterface::RecvStart() +{ + m_tapserver_if.RecvStart(); +} + +void CEXIModem::TAPServerNetworkInterface::RecvStop() +{ + m_tapserver_if.RecvStop(); +} + +void CEXIModem::TAPServerNetworkInterface::HandleReceivedFrame(std::string&& data) +{ + m_modem_ref->AddToReceiveBuffer(std::move(data)); +} + +} // namespace ExpansionInterface diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 72c2f0ba1d..5622bef028 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -938,9 +938,10 @@ - + + - + From 12a7e17a41cdd9c943e39c669251c763648826bc Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 28 Jan 2024 20:59:39 -0800 Subject: [PATCH 08/13] fix warnings in windows build --- Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp | 2 +- Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp | 6 +++--- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp index f0c0a4ce0b..89d8816910 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp @@ -80,7 +80,7 @@ void CEXIETHERNET::TAPServerNetworkInterface::HandleReceivedFrame(std::string&& else { memcpy(m_eth_ref->mRecvBuffer.get(), data.data(), data.size()); - m_eth_ref->mRecvBufferLength = data.size(); + m_eth_ref->mRecvBufferLength = static_cast(data.size()); m_eth_ref->RecvHandlePacket(); } } diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 13b03a9349..864ef55dae 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -320,9 +320,9 @@ void TAPServerConnection::ReadThreadHandler() case ReadState::DATA: case ReadState::SKIP: { - ws_ssize_t bytes_to_read = frame_data.size() - frame_bytes_received; - ws_ssize_t bytes_read = - recv(m_fd, frame_data.data() + frame_bytes_received, bytes_to_read, 0); + const std::size_t bytes_to_read = frame_data.size() - frame_bytes_received; + ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received, + static_cast(bytes_to_read), 0); if (bytes_read <= 0) { ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", Common::StrNetworkError()); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp index e488570a42..3efabc3d7a 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -313,7 +313,8 @@ void CEXIModem::SetInterruptFlag(u8 what, bool enabled, bool from_cpu) void CEXIModem::OnReceiveBufferSizeChangedLocked(bool from_cpu) { // The caller is expected to hold m_receive_buffer_lock when calling this. - const u16 bytes_available = std::min(m_receive_buffer.size(), 0x200); + const u16 bytes_available = + static_cast(std::min(m_receive_buffer.size(), 0x200)); m_regs[Register::BYTES_AVAILABLE_HIGH] = (bytes_available >> 8) & 0xFF; m_regs[Register::BYTES_AVAILABLE_LOW] = bytes_available & 0xFF; SetInterruptFlag(Interrupt::RECEIVE_BUFFER_ABOVE_THRESHOLD, From 9d0d2f0a40c9507289e94fa8cbebb0a7d0c68251 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 7 Feb 2024 20:42:09 -0800 Subject: [PATCH 09/13] handle ATH0 command --- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp index 3efabc3d7a..925354c828 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -365,9 +365,9 @@ void CEXIModem::RunAllPendingATCommands() INFO_LOG_FMT(SP1, "Received AT command: {}", command); - if (command.substr(0, 3) == "ATZ") + if (command.substr(0, 3) == "ATZ" || command == "ATH0") { - // Reset + // Reset (ATZ) or hang up (ATH0) m_network_interface->Deactivate(); AddATReply("OK\r"); } From 7775ea325f1abaf4eae0c3a129c18001783428a1 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 7 Feb 2024 20:50:59 -0800 Subject: [PATCH 10/13] close modem socket after joining read thread --- Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 864ef55dae..4cae9255da 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -130,15 +130,15 @@ bool TAPServerConnection::Activate() void TAPServerConnection::Deactivate() { - if (m_fd >= 0) - closesocket(m_fd); - m_fd = -1; - m_read_enabled.Clear(); m_read_shutdown.Set(); if (m_read_thread.joinable()) m_read_thread.join(); m_read_shutdown.Clear(); + + if (m_fd >= 0) + closesocket(m_fd); + m_fd = -1; } bool TAPServerConnection::IsActivated() From 5d8a01cba7731cd979b18c4773814730af16fca3 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Mon, 19 Feb 2024 22:08:42 -0800 Subject: [PATCH 11/13] respond to further review feedback --- Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp | 27 ++-- .../Core/HW/EXI/BBA/TAPServerConnection.cpp | 115 +++++++++--------- .../Core/HW/EXI/BBA/TAPServerConnection.h | 28 ++--- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 18 ++- Source/Core/Core/HW/EXI/EXI_DeviceModem.h | 19 ++- .../Core/Core/HW/EXI/Modem/TAPServerModem.cpp | 22 +--- .../BroadbandAdapterSettingsDialog.cpp | 24 ++-- 7 files changed, 111 insertions(+), 142 deletions(-) diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp index 89d8816910..badbbf1ca0 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerBBA.cpp @@ -3,21 +3,9 @@ #include "Core/HW/EXI/EXI_DeviceEthernet.h" -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif +#include -#include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Core/HW/EXI/EXI_Device.h" namespace ExpansionInterface { @@ -64,7 +52,7 @@ void CEXIETHERNET::TAPServerNetworkInterface::RecvStop() bool CEXIETHERNET::TAPServerNetworkInterface::SendFrame(const u8* frame, u32 size) { - bool ret = m_tapserver_if.SendFrame(frame, size); + const bool ret = m_tapserver_if.SendFrame(frame, size); if (ret) m_eth_ref->SendComplete(); return ret; @@ -76,13 +64,12 @@ void CEXIETHERNET::TAPServerNetworkInterface::HandleReceivedFrame(std::string&& { ERROR_LOG_FMT(SP1, "Received BBA frame of size {}, which is larger than maximum size {}", data.size(), BBA_RECV_SIZE); + return; } - else - { - memcpy(m_eth_ref->mRecvBuffer.get(), data.data(), data.size()); - m_eth_ref->mRecvBufferLength = static_cast(data.size()); - m_eth_ref->RecvHandlePacket(); - } + + std::memcpy(m_eth_ref->mRecvBuffer.get(), data.data(), data.size()); + m_eth_ref->mRecvBufferLength = static_cast(data.size()); + m_eth_ref->RecvHandlePacket(); } } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 4cae9255da..077b306db4 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -14,6 +14,9 @@ #include #endif +#include +#include + #include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" @@ -52,7 +55,7 @@ static int ConnectToDestination(const std::string& destination) int ss_size; sockaddr_storage ss; - memset(&ss, 0, sizeof(ss)); + std::memset(&ss, 0, sizeof(ss)); if (destination[0] != '/') { // IP address or hostname @@ -64,10 +67,22 @@ static int ConnectToDestination(const std::string& destination) } sockaddr_in* sin = reinterpret_cast(&ss); - sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger()); + const sf::IpAddress dest_ip(destination.substr(0, colon_offset)); + if (dest_ip == sf::IpAddress::None || dest_ip == sf::IpAddress::Any) + { + ERROR_LOG_FMT(SP1, "Destination IP address is not valid\n"); + return -1; + } + sin->sin_addr.s_addr = htonl(dest_ip.toInteger()); sin->sin_family = AF_INET; - std::string port_str = destination.substr(colon_offset + 1); - sin->sin_port = htons(atoi(port_str.c_str())); + const std::string port_str = destination.substr(colon_offset + 1); + const int dest_port = std::atoi(port_str.c_str()); + if (dest_port < 1 || dest_port > 65535) + { + ERROR_LOG_FMT(SP1, "Destination port is not valid\n"); + return -1; + } + sin->sin_port = htons(dest_port); ss_size = sizeof(*sin); #ifndef _WIN32 } @@ -81,7 +96,7 @@ static int ConnectToDestination(const std::string& destination) return -1; } sun->sun_family = AF_UNIX; - strcpy(sun->sun_path, destination.c_str()); + std::strcpy(sun->sun_path, destination.c_str()); ss_size = sizeof(*sun); #else } @@ -107,8 +122,8 @@ static int ConnectToDestination(const std::string& destination) if (connect(fd, reinterpret_cast(&ss), ss_size) == -1) { - std::string s = Common::StrNetworkError(); - INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to create tapserver connection\n", s); + INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to create tapserver connection\n", + Common::StrNetworkError()); closesocket(fd); return -1; } @@ -162,31 +177,31 @@ void TAPServerConnection::RecvStop() m_read_enabled.Clear(); } -bool TAPServerConnection::SendAndRemoveAllHDLCFrames(std::string& send_buf) +bool TAPServerConnection::SendAndRemoveAllHDLCFrames(std::string* send_buf) { - while (!send_buf.empty()) + while (!send_buf->empty()) { - std::size_t start_offset = send_buf.find(0x7E); + const std::size_t start_offset = send_buf->find(0x7E); if (start_offset == std::string::npos) { break; } - std::size_t end_sentinel_offset = send_buf.find(0x7E, start_offset + 1); + const std::size_t end_sentinel_offset = send_buf->find(0x7E, start_offset + 1); if (end_sentinel_offset == std::string::npos) { break; } - std::size_t end_offset = end_sentinel_offset + 1; - std::size_t size = end_offset - start_offset; + const std::size_t end_offset = end_sentinel_offset + 1; + const std::size_t size = end_offset - start_offset; - u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + const u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) { ERROR_LOG_FMT(SP1, "SendAndRemoveAllHDLCFrames(): could not write size field"); return false; } const int written_bytes = - send(m_fd, send_buf.data() + start_offset, static_cast(size), SEND_FLAGS); + send(m_fd, send_buf->data() + start_offset, static_cast(size), SEND_FLAGS); if (u32(written_bytes) != size) { ERROR_LOG_FMT(SP1, @@ -194,31 +209,25 @@ bool TAPServerConnection::SendAndRemoveAllHDLCFrames(std::string& send_buf) size, written_bytes); return false; } - else - { - send_buf = send_buf.substr(end_offset); - } + *send_buf = send_buf->substr(end_offset); } return true; } bool TAPServerConnection::SendFrame(const u8* frame, u32 size) { - { - const std::string s = ArrayToString(frame, size, 0x10); - INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, s); - } + INFO_LOG_FMT(SP1, "SendFrame {}\n{}", size, ArrayToString(frame, size, 0x10)); // On Windows, the data pointer is of type const char*; on other systems it is // of type const void*. This is the reason for the reinterpret_cast here and // in the other send/recv calls in this file. - u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; + const u8 size_bytes[2] = {static_cast(size), static_cast(size >> 8)}; if (send(m_fd, reinterpret_cast(size_bytes), 2, SEND_FLAGS) != 2) { ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field"); return false; } - int written_bytes = + const int written_bytes = send(m_fd, reinterpret_cast(frame), static_cast(size), SEND_FLAGS); if (u32(written_bytes) != size) { @@ -263,7 +272,7 @@ void TAPServerConnection::ReadThreadHandler() case ReadState::SIZE: { u8 size_bytes[2]; - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(size_bytes), 2, 0); + const ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(size_bytes), 2, 0); if (bytes_read == 1) { read_state = ReadState::SIZE_HIGH; @@ -295,25 +304,23 @@ void TAPServerConnection::ReadThreadHandler() // This handles the annoying case where only one byte of the size field // was available earlier. u8 size_high = 0; - ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(&size_high), 1, 0); - if (bytes_read == 1) - { - frame_bytes_expected |= (size_high << 8); - frame_data.resize(frame_bytes_expected, '\0'); - if (frame_bytes_expected > m_max_frame_size) - { - ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); - read_state = ReadState::SKIP; - } - else - { - read_state = ReadState::DATA; - } - } - else + const ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast(&size_high), 1, 0); + if (bytes_read != 1) { ERROR_LOG_FMT(SP1, "Failed to read split size field from destination: {}", Common::StrNetworkError()); + break; + } + frame_bytes_expected |= (size_high << 8); + frame_data.resize(frame_bytes_expected, '\0'); + if (frame_bytes_expected > m_max_frame_size) + { + ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected); + read_state = ReadState::SKIP; + } + else + { + read_state = ReadState::DATA; } break; } @@ -321,26 +328,24 @@ void TAPServerConnection::ReadThreadHandler() case ReadState::SKIP: { const std::size_t bytes_to_read = frame_data.size() - frame_bytes_received; - ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received, - static_cast(bytes_to_read), 0); + const ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received, + static_cast(bytes_to_read), 0); if (bytes_read <= 0) { ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", Common::StrNetworkError()); + break; } - else + frame_bytes_received += bytes_read; + if (frame_bytes_received == frame_bytes_expected) { - frame_bytes_received += bytes_read; - if (frame_bytes_received == frame_bytes_expected) + if (read_state == ReadState::DATA) { - if (read_state == ReadState::DATA) - { - m_recv_cb(std::move(frame_data)); - } - frame_data.clear(); - frame_bytes_received = 0; - frame_bytes_expected = 0; - read_state = ReadState::SIZE; + m_recv_cb(std::move(frame_data)); } + frame_data.clear(); + frame_bytes_received = 0; + frame_bytes_expected = 0; + read_state = ReadState::SIZE; } break; } diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h index ae10fc93a8..f622c312c9 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h @@ -3,24 +3,12 @@ #pragma once -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif - #include +#include #include -#include "Common/CommonFuncs.h" -#include "Common/Logging/Log.h" +#include "Common/Flag.h" #include "Common/SocketContext.h" -#include "Common/StringUtil.h" namespace ExpansionInterface { @@ -28,7 +16,9 @@ namespace ExpansionInterface class TAPServerConnection { public: - TAPServerConnection(const std::string& destination, std::function recv_cb, + using RecvCallback = std::function; + + TAPServerConnection(const std::string& destination, RecvCallback recv_cb, std::size_t max_frame_size); bool Activate(); @@ -37,7 +27,7 @@ public: bool RecvInit(); void RecvStart(); void RecvStop(); - bool SendAndRemoveAllHDLCFrames(std::string& send_buf); + bool SendAndRemoveAllHDLCFrames(std::string* send_buf); bool SendFrame(const u8* frame, u32 size); private: @@ -49,9 +39,9 @@ private: Skip, }; - std::string m_destination; - std::function m_recv_cb; - std::size_t m_max_frame_size; + const std::string m_destination; + const std::function m_recv_cb; + const std::size_t m_max_frame_size; Common::SocketContext m_socket_context; int m_fd = -1; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp index 925354c828..02f2e33613 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -87,12 +88,8 @@ void CEXIModem::ImmWrite(u32 data, u32 size) { // Write device register u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); bool should_update_interrupts = false; - for (; size; size--) + for (; size && reg_num < m_regs.size(); size--) { - if (reg_num >= m_regs.size()) - { - break; - } should_update_interrupts |= ((reg_num == Register::INTERRUPT_MASK) || (reg_num == Register::PENDING_INTERRUPT_MASK)); m_regs[reg_num++] = (data >> 24); @@ -153,8 +150,9 @@ u32 CEXIModem::ImmRead(u32 size) return ntohl(be_data); } else - { // Read device register - u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); + { + // Read device register + const u8 reg_num = static_cast((m_transfer_descriptor >> 24) & 0x1F); if (reg_num == 0) { return 0x02020000; // Device ID (modem) @@ -214,7 +212,7 @@ void CEXIModem::HandleReadModemTransfer(void* data, u32 size) { // AT command buffer const std::size_t bytes_to_copy = std::min(size, m_at_reply_data.size()); - memcpy(data, m_at_reply_data.data(), bytes_to_copy); + std::memcpy(data, m_at_reply_data.data(), bytes_to_copy); m_at_reply_data = m_at_reply_data.substr(bytes_to_copy); m_regs[Register::AT_REPLY_SIZE] = static_cast(m_at_reply_data.size()); SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true); @@ -224,7 +222,7 @@ void CEXIModem::HandleReadModemTransfer(void* data, u32 size) // Packet receive buffer std::lock_guard g(m_receive_buffer_lock); const std::size_t bytes_to_copy = std::min(size, m_receive_buffer.size()); - memcpy(data, m_receive_buffer.data(), bytes_to_copy); + std::memcpy(data, m_receive_buffer.data(), bytes_to_copy); m_receive_buffer = m_receive_buffer.substr(bytes_to_copy); OnReceiveBufferSizeChangedLocked(true); } @@ -264,7 +262,7 @@ void CEXIModem::HandleWriteModemTransfer(const void* data, u32 size) // from the emulated program's perspective, so we always tell it the send // FIFO is empty. SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true); - m_network_interface->SendAndRemoveAllHDLCFrames(m_send_buffer); + m_network_interface->SendAndRemoveAllHDLCFrames(&m_send_buffer); } else { diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h index 7d008f869d..786d8db119 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.h @@ -17,7 +17,7 @@ class PointerWrap; namespace ExpansionInterface { -#define MODEM_RECV_SIZE 0x800 +static constexpr std::size_t MODEM_RECV_SIZE = 0x800; enum { @@ -44,13 +44,20 @@ public: void DoState(PointerWrap& p) override; private: + // Note: The names in these enums are based on reverse-engineering of PSO and + // not on any documentation of the GC modem or its chipset. If official + // documentation is found, any names in there probably will not match these + // names. + enum Interrupt - { // Used for Register::INTERRUPT_MASK and Register::PENDING_INTERRUPT_MASK + { + // Used for Register::INTERRUPT_MASK and Register::PENDING_INTERRUPT_MASK AT_REPLY_DATA_AVAILABLE = 0x02, SEND_BUFFER_BELOW_THRESHOLD = 0x10, RECEIVE_BUFFER_ABOVE_THRESHOLD = 0x20, RECEIVE_BUFFER_NOT_EMPTY = 0x40, }; + enum Register { DEVICE_TYPE = 0x00, @@ -90,18 +97,22 @@ private: { return transfer_descriptor == 0x80000000; } + static inline bool IsWriteTransfer(u32 transfer_descriptor) { return transfer_descriptor & 0x40000000; } + static inline bool IsModemTransfer(u32 transfer_descriptor) { return transfer_descriptor & 0x20000000; } + static inline u16 GetModemTransferSize(u32 transfer_descriptor) { return (transfer_descriptor >> 8) & 0xFFFF; } + static inline u32 SetModemTransferSize(u32 transfer_descriptor, u16 new_size) { return (transfer_descriptor & 0xFF000000) | (new_size << 8); @@ -117,7 +128,7 @@ private: virtual bool Activate() { return false; } virtual void Deactivate() {} virtual bool IsActivated() { return false; } - virtual bool SendAndRemoveAllHDLCFrames(std::string&) { return false; } + virtual bool SendAndRemoveAllHDLCFrames(std::string*) { return false; } virtual bool RecvInit() { return false; } virtual void RecvStart() {} virtual void RecvStop() {} @@ -134,7 +145,7 @@ private: virtual bool Activate() override; virtual void Deactivate() override; virtual bool IsActivated() override; - virtual bool SendAndRemoveAllHDLCFrames(std::string& send_buffer) override; + virtual bool SendAndRemoveAllHDLCFrames(std::string* send_buffer) override; virtual bool RecvInit() override; virtual void RecvStart() override; virtual void RecvStop() override; diff --git a/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp b/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp index 4718c34852..5723146b24 100644 --- a/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp +++ b/Source/Core/Core/HW/EXI/Modem/TAPServerModem.cpp @@ -3,21 +3,7 @@ #include "Core/HW/EXI/EXI_DeviceModem.h" -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#endif - -#include "Common/CommonFuncs.h" #include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "Core/HW/EXI/EXI_Device.h" namespace ExpansionInterface { @@ -47,11 +33,11 @@ bool CEXIModem::TAPServerNetworkInterface::IsActivated() return m_tapserver_if.IsActivated(); } -bool CEXIModem::TAPServerNetworkInterface::SendAndRemoveAllHDLCFrames(std::string& send_buffer) +bool CEXIModem::TAPServerNetworkInterface::SendAndRemoveAllHDLCFrames(std::string* send_buffer) { - std::size_t orig_size = send_buffer.size(); - bool send_succeeded = m_tapserver_if.SendAndRemoveAllHDLCFrames(send_buffer); - if (send_succeeded && (send_buffer.size() < orig_size)) + const std::size_t orig_size = send_buffer->size(); + const bool send_succeeded = m_tapserver_if.SendAndRemoveAllHDLCFrames(send_buffer); + if (send_succeeded && (send_buffer->size() < orig_size)) m_modem_ref->SendComplete(); return send_succeeded; } diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp index 27579b5ac8..0eecfdf455 100644 --- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp +++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp @@ -51,7 +51,7 @@ void BroadbandAdapterSettingsDialog::InitControls() case Type::TapServer: case Type::ModemTapServer: { - bool is_modem = (m_bba_type == Type::ModemTapServer); + const bool is_modem = (m_bba_type == Type::ModemTapServer); current_address = QString::fromStdString(Config::Get(is_modem ? Config::MAIN_MODEM_TAPSERVER_DESTINATION : Config::MAIN_BBA_TAPSERVER_DESTINATION)); @@ -62,21 +62,13 @@ void BroadbandAdapterSettingsDialog::InitControls() tr("Enter the IP address and port of the tapserver instance you want to connect to.")); #else address_label = new QLabel(tr("Destination (UNIX socket path or address:port):")); - address_placeholder = QStringLiteral("/tmp/dolphin-tap"); - if (is_modem) - { - description = new QLabel( - tr("The default value \"/tmp/dolphin-modem-tap\" will work with a local tapserver and " - "newserv. You " - "can also enter a network location (address:port) to connect to a remote tapserver.")); - } - else - { - description = new QLabel( - tr("The default value \"/tmp/dolphin-tap\" will work with a local tapserver and newserv. " - "You " - "can also enter a network location (address:port) to connect to a remote tapserver.")); - } + address_placeholder = + is_modem ? QStringLiteral(u"/tmp/dolphin-modem-tap") : QStringLiteral(u"/tmp/dolphin-tap"); + description = + new QLabel(tr("The default value \"%1\" will work with a local tapserver and newserv." + " You can also enter a network location (address:port) to connect to a " + "remote tapserver.") + .arg(address_placeholder)); #endif window_title = tr("BBA destination address"); break; From 3e9ac1aaf33c814df7b1720b239205f1988c8dbc Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Mon, 26 Feb 2024 20:45:40 -0800 Subject: [PATCH 12/13] fix tapserver SIGPIPE handling on Linux --- Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp | 10 ++++++++-- Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 077b306db4..1f76340222 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -32,7 +32,7 @@ using ws_ssize_t = int; using ws_ssize_t = ssize_t; #endif -#ifdef __LINUX__ +#ifdef __linux__ #define SEND_FLAGS MSG_NOSIGNAL #else #define SEND_FLAGS 0 @@ -262,7 +262,13 @@ void TAPServerConnection::ReadThreadHandler() timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 50000; - if (select(m_fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) + int select_res = select(m_fd + 1, &rfds, nullptr, nullptr, &timeout); + if (select_res < 0) + { + ERROR_LOG_FMT(SP1, "Can\'t poll tapserver fd: {}", Common::StrNetworkError()); + break; + } + if (select_res == 0) continue; // The tapserver protocol is very simple: there is a 16-bit little-endian diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h index f622c312c9..42c2020ffa 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.h @@ -40,7 +40,7 @@ private: }; const std::string m_destination; - const std::function m_recv_cb; + const RecvCallback m_recv_cb; const std::size_t m_max_frame_size; Common::SocketContext m_socket_context; From f7a01471767400d025f2467a611e58b344e0afc4 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 17 Mar 2024 18:32:10 -0700 Subject: [PATCH 13/13] prevent backpressure when m_read_enabled is not set --- Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp | 9 ++++++--- Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp index 1f76340222..b67a94dff1 100644 --- a/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp +++ b/Source/Core/Core/HW/EXI/BBA/TAPServerConnection.cpp @@ -266,7 +266,7 @@ void TAPServerConnection::ReadThreadHandler() if (select_res < 0) { ERROR_LOG_FMT(SP1, "Can\'t poll tapserver fd: {}", Common::StrNetworkError()); - break; + continue; } if (select_res == 0) continue; @@ -295,7 +295,10 @@ void TAPServerConnection::ReadThreadHandler() } else { - read_state = ReadState::DATA; + // If read is disabled, we still need to actually read the frame in + // order to avoid applying backpressure on the remote end, but we + // should drop the frame instead of forwarding it to the client. + read_state = m_read_enabled.IsSet() ? ReadState::DATA : ReadState::SKIP; } } else @@ -326,7 +329,7 @@ void TAPServerConnection::ReadThreadHandler() } else { - read_state = ReadState::DATA; + read_state = m_read_enabled.IsSet() ? ReadState::DATA : ReadState::SKIP; } break; } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp index 02f2e33613..17d4a7c484 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceModem.cpp @@ -44,6 +44,7 @@ CEXIModem::CEXIModem(Core::System& system, ModemDeviceType type) : IEXIDevice(sy CEXIModem::~CEXIModem() { + m_network_interface->RecvStop(); m_network_interface->Deactivate(); } @@ -69,6 +70,7 @@ void CEXIModem::ImmWrite(u32 data, u32 size) m_transfer_descriptor = data; if (m_transfer_descriptor == 0x00008000) { // Reset + m_network_interface->RecvStop(); m_network_interface->Deactivate(); m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR; } @@ -366,6 +368,7 @@ void CEXIModem::RunAllPendingATCommands() if (command.substr(0, 3) == "ATZ" || command == "ATH0") { // Reset (ATZ) or hang up (ATH0) + m_network_interface->RecvStop(); m_network_interface->Deactivate(); AddATReply("OK\r"); } @@ -374,6 +377,7 @@ void CEXIModem::RunAllPendingATCommands() // Dial if (m_network_interface->Activate()) { + m_network_interface->RecvStart(); AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate } else