Merge pull request #12355 from fuzziqersoftware/tapserver-modem-adapter
Implement tapserver-based modem adapter
This commit is contained in:
commit
b510ac89a3
|
@ -23,6 +23,18 @@ 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_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,
|
||||
|
|
|
@ -1101,6 +1101,16 @@ 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(
|
||||
|
@ -1111,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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,10 @@
|
|||
<string name="xlink_kai_guide_header">For setup instructions, <a href="https://www.teamxlink.co.uk/wiki/Dolphin">refer to this page.</a></string>
|
||||
<string name="xlink_kai_bba_ip">XLink Kai IP Address/hostname</string>
|
||||
<string name="xlink_kai_bba_ip_description">IP address or hostname of device running the XLink Kai client</string>
|
||||
<string name="bba_tapserver_destination">Tapserver destination</string>
|
||||
<string name="bba_tapserver_destination_description">Enter the socket path or netloc (address:port) of the tapserver instance</string>
|
||||
<string name="modem_tapserver_destination">Tapserver destination</string>
|
||||
<string name="modem_tapserver_destination_description">Enter the socket path or netloc (address:port) of the tapserver instance</string>
|
||||
<string name="bba_builtin_dns">DNS Server</string>
|
||||
<string name="bba_builtin_dns_description">Use 8.8.8.8 for normal DNS, else enter your custom one</string>
|
||||
|
||||
|
|
|
@ -8,12 +8,27 @@ namespace Common
|
|||
#ifdef _WIN32
|
||||
SocketContext::SocketContext()
|
||||
{
|
||||
static_cast<void>(WSAStartup(MAKEWORD(2, 2), &m_data));
|
||||
std::lock_guard<std::mutex> g(s_lock);
|
||||
if (s_num_objects == 0)
|
||||
{
|
||||
static_cast<void>(WSAStartup(MAKEWORD(2, 2), &s_data));
|
||||
}
|
||||
s_num_objects++;
|
||||
}
|
||||
SocketContext::~SocketContext()
|
||||
{
|
||||
WSACleanup();
|
||||
std::lock_guard<std::mutex> 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;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
#include <WinSock2.h>
|
||||
#include <mutex>
|
||||
#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
|
||||
|
|
|
@ -189,6 +189,12 @@ add_library(core
|
|||
HW/DVD/DVDThread.h
|
||||
HW/DVD/FileMonitor.cpp
|
||||
HW/DVD/FileMonitor.h
|
||||
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/TAPServerModem.cpp
|
||||
HW/EXI/EXI_Channel.cpp
|
||||
HW/EXI/EXI_Channel.h
|
||||
HW/EXI/EXI_Device.cpp
|
||||
|
@ -209,6 +215,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
|
||||
|
@ -696,9 +704,6 @@ if(WIN32)
|
|||
target_sources(core PRIVATE
|
||||
HW/EXI/BBA/TAP_Win32.cpp
|
||||
HW/EXI/BBA/TAP_Win32.h
|
||||
HW/EXI/BBA/XLINK_KAI_BBA.cpp
|
||||
HW/EXI/BBA/BuiltIn.cpp
|
||||
HW/EXI/BBA/BuiltIn.h
|
||||
HW/WiimoteReal/IOWin.cpp
|
||||
HW/WiimoteReal/IOWin.h
|
||||
)
|
||||
|
@ -712,18 +717,11 @@ if(WIN32)
|
|||
elseif(APPLE)
|
||||
target_sources(core PRIVATE
|
||||
HW/EXI/BBA/TAP_Apple.cpp
|
||||
HW/EXI/BBA/TAPServer_Apple.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/XLINK_KAI_BBA.cpp
|
||||
HW/EXI/BBA/BuiltIn.cpp
|
||||
HW/EXI/BBA/BuiltIn.h
|
||||
)
|
||||
if(ANDROID)
|
||||
target_sources(core PRIVATE
|
||||
|
@ -778,4 +776,4 @@ endif()
|
|||
if(USE_RETRO_ACHIEVEMENTS)
|
||||
target_link_libraries(core PRIVATE rcheevos)
|
||||
target_compile_definitions(core PRIVATE -DUSE_RETRO_ACHIEVEMENTS)
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -137,6 +137,10 @@ const Info<bool> MAIN_BBA_XLINK_CHAT_OSD{{System::Main, "Core", "BBA_XLINK_CHAT_
|
|||
// Schthack PSO Server - https://schtserv.com/
|
||||
const Info<std::string> MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN_DNS"},
|
||||
"3.18.217.27"};
|
||||
const Info<std::string> MAIN_BBA_TAPSERVER_DESTINATION{
|
||||
{System::Main, "Core", "BBA_TAPSERVER_DESTINATION"}, "/tmp/dolphin-tap"};
|
||||
const Info<std::string> MAIN_MODEM_TAPSERVER_DESTINATION{
|
||||
{System::Main, "Core", "MODEM_TAPSERVER_DESTINATION"}, "/tmp/dolphin-modem-tap"};
|
||||
const Info<std::string> MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""};
|
||||
|
||||
const Info<SerialInterface::SIDevices>& GetInfoForSIDevice(int channel)
|
||||
|
|
|
@ -96,6 +96,8 @@ extern const Info<std::string> MAIN_BBA_XLINK_IP;
|
|||
extern const Info<bool> MAIN_BBA_XLINK_CHAT_OSD;
|
||||
extern const Info<std::string> MAIN_BBA_BUILTIN_DNS;
|
||||
extern const Info<std::string> MAIN_BBA_BUILTIN_IP;
|
||||
extern const Info<std::string> MAIN_BBA_TAPSERVER_DESTINATION;
|
||||
extern const Info<std::string> MAIN_MODEM_TAPSERVER_DESTINATION;
|
||||
const Info<SerialInterface::SIDevices>& GetInfoForSIDevice(int channel);
|
||||
const Info<bool>& GetInfoForAdapterRumble(int channel);
|
||||
const Info<bool>& GetInfoForSimulateKonga(int channel);
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2020 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Logging/Log.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)
|
||||
{
|
||||
const 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);
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(m_eth_ref->mRecvBuffer.get(), data.data(), data.size());
|
||||
m_eth_ref->mRecvBufferLength = static_cast<u32>(data.size());
|
||||
m_eth_ref->RecvHandlePacket();
|
||||
}
|
||||
|
||||
} // namespace ExpansionInterface
|
|
@ -0,0 +1,365 @@
|
|||
// Copyright 2020 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/HW/EXI/EXI_Device.h"
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
using ws_ssize_t = int;
|
||||
#else
|
||||
#define closesocket close
|
||||
using ws_ssize_t = ssize_t;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#define SEND_FLAGS MSG_NOSIGNAL
|
||||
#else
|
||||
#define SEND_FLAGS 0
|
||||
#endif
|
||||
|
||||
TAPServerConnection::TAPServerConnection(const std::string& destination,
|
||||
std::function<void(std::string&&)> 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())
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Cannot connect: destination is empty\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ss_size;
|
||||
sockaddr_storage ss;
|
||||
std::memset(&ss, 0, sizeof(ss));
|
||||
if (destination[0] != '/')
|
||||
{
|
||||
// IP address or hostname
|
||||
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;
|
||||
}
|
||||
|
||||
sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
|
||||
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;
|
||||
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
|
||||
}
|
||||
else
|
||||
{
|
||||
// UNIX socket
|
||||
sockaddr_un* sun = reinterpret_cast<sockaddr_un*>(&ss);
|
||||
if (destination.size() + 1 > sizeof(sun->sun_path))
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Socket path is too long; unable to create tapserver connection\n");
|
||||
return -1;
|
||||
}
|
||||
sun->sun_family = AF_UNIX;
|
||||
std::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
|
||||
}
|
||||
|
||||
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 create tapserver connection\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<sockaddr*>(&ss), ss_size) == -1)
|
||||
{
|
||||
INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to create tapserver connection\n",
|
||||
Common::StrNetworkError());
|
||||
closesocket(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
bool TAPServerConnection::Activate()
|
||||
{
|
||||
if (IsActivated())
|
||||
return true;
|
||||
|
||||
m_fd = ConnectToDestination(m_destination);
|
||||
if (m_fd < 0)
|
||||
return false;
|
||||
|
||||
return RecvInit();
|
||||
}
|
||||
|
||||
void TAPServerConnection::Deactivate()
|
||||
{
|
||||
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()
|
||||
{
|
||||
return (m_fd >= 0);
|
||||
}
|
||||
|
||||
bool TAPServerConnection::RecvInit()
|
||||
{
|
||||
m_read_thread = std::thread(&TAPServerConnection::ReadThreadHandler, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TAPServerConnection::RecvStart()
|
||||
{
|
||||
m_read_enabled.Set();
|
||||
}
|
||||
|
||||
void TAPServerConnection::RecvStop()
|
||||
{
|
||||
m_read_enabled.Clear();
|
||||
}
|
||||
|
||||
bool TAPServerConnection::SendAndRemoveAllHDLCFrames(std::string* send_buf)
|
||||
{
|
||||
while (!send_buf->empty())
|
||||
{
|
||||
const std::size_t start_offset = send_buf->find(0x7E);
|
||||
if (start_offset == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const std::size_t end_sentinel_offset = send_buf->find(0x7E, start_offset + 1);
|
||||
if (end_sentinel_offset == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
const std::size_t end_offset = end_sentinel_offset + 1;
|
||||
const std::size_t size = end_offset - start_offset;
|
||||
|
||||
const u8 size_bytes[2] = {static_cast<u8>(size), static_cast<u8>(size >> 8)};
|
||||
if (send(m_fd, reinterpret_cast<const char*>(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<int>(size), SEND_FLAGS);
|
||||
if (u32(written_bytes) != size)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1,
|
||||
"SendAndRemoveAllHDLCFrames(): expected to write {} bytes, instead wrote {}",
|
||||
size, written_bytes);
|
||||
return false;
|
||||
}
|
||||
*send_buf = send_buf->substr(end_offset);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAPServerConnection::SendFrame(const u8* frame, u32 size)
|
||||
{
|
||||
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.
|
||||
const u8 size_bytes[2] = {static_cast<u8>(size), static_cast<u8>(size >> 8)};
|
||||
if (send(m_fd, reinterpret_cast<const char*>(size_bytes), 2, SEND_FLAGS) != 2)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field");
|
||||
return false;
|
||||
}
|
||||
const int written_bytes =
|
||||
send(m_fd, reinterpret_cast<const char*>(frame), static_cast<ws_ssize_t>(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
|
||||
{
|
||||
SIZE,
|
||||
SIZE_HIGH,
|
||||
DATA,
|
||||
SKIP,
|
||||
};
|
||||
ReadState read_state = ReadState::SIZE;
|
||||
|
||||
std::size_t frame_bytes_received = 0;
|
||||
std::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;
|
||||
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());
|
||||
continue;
|
||||
}
|
||||
if (select_res == 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];
|
||||
const ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast<char*>(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 > m_max_frame_size)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected);
|
||||
read_state = ReadState::SKIP;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Failed to read size field from destination: {}",
|
||||
Common::StrNetworkError());
|
||||
}
|
||||
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;
|
||||
const ws_ssize_t bytes_read = recv(m_fd, reinterpret_cast<char*>(&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 = m_read_enabled.IsSet() ? ReadState::DATA : ReadState::SKIP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ReadState::DATA:
|
||||
case ReadState::SKIP:
|
||||
{
|
||||
const std::size_t bytes_to_read = frame_data.size() - frame_bytes_received;
|
||||
const ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received,
|
||||
static_cast<ws_ssize_t>(bytes_to_read), 0);
|
||||
if (bytes_read <= 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", Common::StrNetworkError());
|
||||
break;
|
||||
}
|
||||
frame_bytes_received += bytes_read;
|
||||
if (frame_bytes_received == frame_bytes_expected)
|
||||
{
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ExpansionInterface
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2020 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/SocketContext.h"
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
|
||||
class TAPServerConnection
|
||||
{
|
||||
public:
|
||||
using RecvCallback = std::function<void(std::string&&)>;
|
||||
|
||||
TAPServerConnection(const std::string& destination, RecvCallback 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,
|
||||
};
|
||||
|
||||
const std::string m_destination;
|
||||
const RecvCallback m_recv_cb;
|
||||
const 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
|
|
@ -1,129 +0,0 @@
|
|||
// Copyright 2020 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/HW/EXI/EXI_Device.h"
|
||||
|
||||
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";
|
||||
|
||||
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<sockaddr*>(&sun), sizeof(sun)) == -1)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA",
|
||||
Common::LastStrerrorString());
|
||||
close(fd);
|
||||
fd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(SP1, "BBA initialized.");
|
||||
return RecvInit();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
auto size16 = u16(size);
|
||||
if (write(fd, &size16, 2) != 2)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "SendFrame(): could not write size field");
|
||||
return false;
|
||||
}
|
||||
int written_bytes = write(fd, frame, size);
|
||||
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 (!readThreadShutdown.IsSet())
|
||||
{
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(fd, &rfds);
|
||||
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 50000;
|
||||
if (select(fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
|
||||
continue;
|
||||
|
||||
u16 size;
|
||||
if (read(fd, &size, 2) != 2)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Failed to read size field from BBA: {}", Common::LastStrerrorString());
|
||||
}
|
||||
else
|
||||
{
|
||||
int read_bytes = read(fd, m_eth_ref->mRecvBuffer.get(), size);
|
||||
if (read_bytes < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(SP1, "Failed to read packet data from BBA: {}", Common::LastStrerrorString());
|
||||
}
|
||||
else if (readEnabled.IsSet())
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CEXIETHERNET::TAPServerNetworkInterface::RecvInit()
|
||||
{
|
||||
readThread = std::thread(&CEXIETHERNET::TAPServerNetworkInterface::ReadThreadHandler, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ExpansionInterface
|
|
@ -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"
|
||||
|
||||
|
@ -137,11 +138,9 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(Core::System& system, const EXIDevi
|
|||
result = std::make_unique<CEXIETHERNET>(system, BBADeviceType::TAP);
|
||||
break;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
case EXIDeviceType::EthernetTapServer:
|
||||
result = std::make_unique<CEXIETHERNET>(system, BBADeviceType::TAPSERVER);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case EXIDeviceType::EthernetXLink:
|
||||
result = std::make_unique<CEXIETHERNET>(system, BBADeviceType::XLINK);
|
||||
|
@ -151,6 +150,10 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(Core::System& system, const EXIDevi
|
|||
result = std::make_unique<CEXIETHERNET>(system, BBADeviceType::BuiltIn);
|
||||
break;
|
||||
|
||||
case EXIDeviceType::ModemTapServer:
|
||||
result = std::make_unique<CEXIModem>(system, ModemDeviceType::TAPSERVER);
|
||||
break;
|
||||
|
||||
case EXIDeviceType::Gecko:
|
||||
result = std::make_unique<CEXIGecko>(system);
|
||||
break;
|
||||
|
|
|
@ -39,9 +39,9 @@ enum class EXIDeviceType : int
|
|||
MemoryCardFolder,
|
||||
AGP,
|
||||
EthernetXLink,
|
||||
// Only used on Apple devices.
|
||||
EthernetTapServer,
|
||||
EthernetBuiltIn,
|
||||
ModemTapServer,
|
||||
None = 0xFF
|
||||
};
|
||||
|
||||
|
@ -88,7 +88,7 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(Core::System& system, EXIDeviceType
|
|||
|
||||
template <>
|
||||
struct fmt::formatter<ExpansionInterface::EXIDeviceType>
|
||||
: EnumFormatter<ExpansionInterface::EXIDeviceType::EthernetBuiltIn>
|
||||
: EnumFormatter<ExpansionInterface::EXIDeviceType::ModemTapServer>
|
||||
{
|
||||
static constexpr array_type names = {
|
||||
_trans("Dummy"),
|
||||
|
@ -105,6 +105,7 @@ struct fmt::formatter<ExpansionInterface::EXIDeviceType>
|
|||
_trans("Broadband Adapter (XLink Kai)"),
|
||||
_trans("Broadband Adapter (tapserver)"),
|
||||
_trans("Broadband Adapter (HLE)"),
|
||||
_trans("Modem Adapter (tapserver)"),
|
||||
};
|
||||
|
||||
constexpr formatter() : EnumFormatter(names) {}
|
||||
|
|
|
@ -50,12 +50,11 @@ CEXIETHERNET::CEXIETHERNET(Core::System& system, BBADeviceType type) : IEXIDevic
|
|||
m_network_interface = std::make_unique<TAPNetworkInterface>(this);
|
||||
INFO_LOG_FMT(SP1, "Created TAP physical network interface.");
|
||||
break;
|
||||
#if defined(__APPLE__)
|
||||
case BBADeviceType::TAPSERVER:
|
||||
m_network_interface = std::make_unique<TAPServerNetworkInterface>(this);
|
||||
m_network_interface = std::make_unique<TAPServerNetworkInterface>(
|
||||
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<BuiltInBBAInterface>(
|
||||
this, Config::Get(Config::MAIN_BBA_BUILTIN_DNS), Config::Get(Config::MAIN_BBA_BUILTIN_IP));
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
#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;
|
||||
|
@ -205,9 +207,7 @@ enum class BBADeviceType
|
|||
{
|
||||
TAP,
|
||||
XLINK,
|
||||
#if defined(__APPLE__)
|
||||
TAPSERVER,
|
||||
#endif
|
||||
BuiltIn,
|
||||
};
|
||||
|
||||
|
@ -364,21 +364,25 @@ private:
|
|||
#endif
|
||||
};
|
||||
|
||||
#if defined(__APPLE__)
|
||||
class TAPServerNetworkInterface : public TAPNetworkInterface
|
||||
class TAPServerNetworkInterface : public NetworkInterface
|
||||
{
|
||||
public:
|
||||
explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref) : TAPNetworkInterface(eth_ref) {}
|
||||
TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& 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:
|
||||
void ReadThreadHandler();
|
||||
TAPServerConnection m_tapserver_if;
|
||||
|
||||
void HandleReceivedFrame(std::string&& data);
|
||||
};
|
||||
#endif
|
||||
|
||||
class XLinkNetworkInterface : public NetworkInterface
|
||||
{
|
||||
|
|
|
@ -0,0 +1,398 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceModem.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#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<TAPServerNetworkInterface>(
|
||||
this, Config::Get(Config::MAIN_MODEM_TAPSERVER_DESTINATION));
|
||||
INFO_LOG_FMT(SP1, "Created tapserver physical network interface.");
|
||||
break;
|
||||
}
|
||||
|
||||
m_regs[Register::DEVICE_TYPE] = 0x02;
|
||||
m_regs[Register::INTERRUPT_MASK] = 0x02;
|
||||
}
|
||||
|
||||
CEXIModem::~CEXIModem()
|
||||
{
|
||||
m_network_interface->RecvStop();
|
||||
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->RecvStop();
|
||||
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
|
||||
const u32 be_data = htonl(data);
|
||||
HandleWriteModemTransfer(&be_data, size);
|
||||
}
|
||||
else
|
||||
{ // Write device register
|
||||
u8 reg_num = static_cast<uint8_t>((m_transfer_descriptor >> 24) & 0x1F);
|
||||
bool should_update_interrupts = false;
|
||||
for (; size && reg_num < m_regs.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
|
||||
const u8 reg_num = static_cast<uint8_t>((m_transfer_descriptor >> 24) & 0x1F);
|
||||
if (reg_num == 0)
|
||||
{
|
||||
return 0x02020000; // Device ID (modem)
|
||||
}
|
||||
u32 ret = 0;
|
||||
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;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
const u16 bytes_requested_after_read = bytes_requested - size;
|
||||
|
||||
if ((m_transfer_descriptor & 0x0F000000) == 0x03000000)
|
||||
{
|
||||
// AT command buffer
|
||||
const std::size_t bytes_to_copy = std::min<std::size_t>(size, m_at_reply_data.size());
|
||||
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<u8>(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<std::mutex> g(m_receive_buffer_lock);
|
||||
const std::size_t bytes_to_copy = std::min<std::size_t>(size, m_receive_buffer.size());
|
||||
std::memcpy(data, m_receive_buffer.data(), bytes_to_copy);
|
||||
m_receive_buffer = m_receive_buffer.substr(bytes_to_copy);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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<const char*>(data), size);
|
||||
RunAllPendingATCommands();
|
||||
m_regs[Register::AT_COMMAND_SIZE] = static_cast<u8>(m_at_command_data.size());
|
||||
}
|
||||
else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000)
|
||||
{ // Packet send buffer
|
||||
m_send_buffer.append(reinterpret_cast<const char*>(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->SendAndRemoveAllHDLCFrames(&m_send_buffer);
|
||||
}
|
||||
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(u8 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.
|
||||
const u16 bytes_available =
|
||||
static_cast<u16>(std::min<std::size_t>(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<std::mutex> 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] = static_cast<u8>(m_at_reply_data.size());
|
||||
SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true);
|
||||
}
|
||||
|
||||
void CEXIModem::RunAllPendingATCommands()
|
||||
{
|
||||
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);
|
||||
|
||||
INFO_LOG_FMT(SP1, "Received AT command: {}", command);
|
||||
|
||||
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");
|
||||
}
|
||||
else if (command.substr(0, 3) == "ATD")
|
||||
{
|
||||
// Dial
|
||||
if (m_network_interface->Activate())
|
||||
{
|
||||
m_network_interface->RecvStart();
|
||||
AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate
|
||||
}
|
||||
else
|
||||
{
|
||||
AddATReply("OK\rNO ANSWER\r");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ExpansionInterface
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/Network.h"
|
||||
#include "Core/HW/EXI/BBA/TAPServerConnection.h"
|
||||
#include "Core/HW/EXI/EXI_Device.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
|
||||
static constexpr std::size_t 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:
|
||||
// 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
|
||||
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(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);
|
||||
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 SendAndRemoveAllHDLCFrames(std::string*) { return false; }
|
||||
virtual bool RecvInit() { return false; }
|
||||
virtual void RecvStart() {}
|
||||
virtual void RecvStop() {}
|
||||
|
||||
virtual ~NetworkInterface() = default;
|
||||
};
|
||||
|
||||
class TAPServerNetworkInterface : public NetworkInterface
|
||||
{
|
||||
public:
|
||||
TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination);
|
||||
|
||||
public:
|
||||
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:
|
||||
TAPServerConnection m_tapserver_if;
|
||||
|
||||
void HandleReceivedFrame(std::string&& data);
|
||||
};
|
||||
|
||||
std::unique_ptr<NetworkInterface> 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<u8, 0x20> m_regs{};
|
||||
};
|
||||
} // namespace ExpansionInterface
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceModem.h"
|
||||
|
||||
#include "Common/Logging/Log.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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
|
@ -284,6 +284,7 @@
|
|||
<ClInclude Include="Core\HW\EXI\EXI_DeviceIPL.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceModem.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI.h" />
|
||||
<ClInclude Include="Core\HW\GBACore.h" />
|
||||
<ClInclude Include="Core\HW\GBAPad.h" />
|
||||
|
@ -937,7 +938,10 @@
|
|||
<ClCompile Include="Core\HW\DVD\FileMonitor.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\BBA\BuiltIn.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\BBA\TAP_Win32.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\BBA\TAPServerConnection.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\BBA\TAPServerBBA.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\BBA\XLINK_KAI_BBA.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\Modem\TAPServerModem.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_Channel.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_Device.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceAD16.cpp" />
|
||||
|
@ -948,6 +952,7 @@
|
|||
<ClCompile Include="Core\HW\EXI\EXI_DeviceIPL.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMemoryCard.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceModem.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI.cpp" />
|
||||
<ClCompile Include="Core\HW\GBACore.cpp" />
|
||||
<ClCompile Include="Core\HW\GBAPad.cpp" />
|
||||
|
|
|
@ -48,6 +48,32 @@ void BroadbandAdapterSettingsDialog::InitControls()
|
|||
window_title = tr("Broadband Adapter MAC Address");
|
||||
break;
|
||||
|
||||
case Type::TapServer:
|
||||
case 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));
|
||||
#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 =
|
||||
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;
|
||||
}
|
||||
|
||||
case Type::BuiltIn:
|
||||
address_label = new QLabel(tr("Enter the DNS server to use:"));
|
||||
address_placeholder = QStringLiteral("8.8.8.8");
|
||||
|
@ -114,6 +140,12 @@ 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::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;
|
||||
|
|
|
@ -15,7 +15,9 @@ public:
|
|||
{
|
||||
Ethernet,
|
||||
XLinkKai,
|
||||
BuiltIn
|
||||
TapServer,
|
||||
BuiltIn,
|
||||
ModemTapServer
|
||||
};
|
||||
|
||||
explicit BroadbandAdapterSettingsDialog(QWidget* target, Type bba_type);
|
||||
|
|
|
@ -149,10 +149,9 @@ void GameCubePane::CreateWidgets()
|
|||
EXIDeviceType::Dummy,
|
||||
EXIDeviceType::Ethernet,
|
||||
EXIDeviceType::EthernetXLink,
|
||||
#ifdef __APPLE__
|
||||
EXIDeviceType::EthernetTapServer,
|
||||
#endif
|
||||
EXIDeviceType::EthernetBuiltIn,
|
||||
EXIDeviceType::ModemTapServer,
|
||||
})
|
||||
{
|
||||
m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()),
|
||||
|
@ -355,7 +354,9 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
|
|||
case ExpansionInterface::Slot::SP1:
|
||||
has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetXLink ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn);
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetTapServer ||
|
||||
device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn ||
|
||||
device == ExpansionInterface::EXIDeviceType::ModemTapServer);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -400,6 +401,21 @@ 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::ModemTapServer:
|
||||
{
|
||||
BroadbandAdapterSettingsDialog dialog(this,
|
||||
BroadbandAdapterSettingsDialog::Type::ModemTapServer);
|
||||
SetQWidgetWindowDecorations(&dialog);
|
||||
dialog.exec();
|
||||
return;
|
||||
}
|
||||
case ExpansionInterface::EXIDeviceType::EthernetBuiltIn:
|
||||
{
|
||||
BroadbandAdapterSettingsDialog dialog(this, BroadbandAdapterSettingsDialog::Type::BuiltIn);
|
||||
|
|
Loading…
Reference in New Issue