respond to review feedback on tapserver implementation
This commit is contained in:
parent
dcb7a72c14
commit
9cf8131b23
|
@ -189,11 +189,12 @@ add_library(core
|
||||||
HW/DVD/DVDThread.h
|
HW/DVD/DVDThread.h
|
||||||
HW/DVD/FileMonitor.cpp
|
HW/DVD/FileMonitor.cpp
|
||||||
HW/DVD/FileMonitor.h
|
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/XLINK_KAI_BBA.cpp
|
||||||
HW/EXI/BBA/BuiltIn.cpp
|
HW/EXI/BBA/BuiltIn.cpp
|
||||||
HW/EXI/BBA/BuiltIn.h
|
HW/EXI/BBA/BuiltIn.h
|
||||||
HW/EXI/Modem/TAPServer.cpp
|
HW/EXI/Modem/TAPServerModem.cpp
|
||||||
HW/EXI/EXI_Channel.cpp
|
HW/EXI/EXI_Channel.cpp
|
||||||
HW/EXI/EXI_Channel.h
|
HW/EXI/EXI_Channel.h
|
||||||
HW/EXI/EXI_Device.cpp
|
HW/EXI/EXI_Device.cpp
|
||||||
|
|
|
@ -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 <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 "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<struct sockaddr_in*>(&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<struct sockaddr_un*>(&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<sockaddr*>(&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<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;
|
|
||||||
}
|
|
||||||
int written_bytes = send(m_fd, reinterpret_cast<const char*>(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<char*>(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<char*>(&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<char*>(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<char*>(m_eth_ref->mRecvBuffer.get()),
|
|
||||||
std::min<int>(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
|
|
|
@ -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 <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 "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
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020 Dolphin Emulator Project
|
// Copyright 2020 Dolphin Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "Core/HW/EXI/EXI_DeviceModem.h"
|
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
@ -23,10 +23,9 @@ namespace ExpansionInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static constexpr auto pi_close = &closesocket;
|
|
||||||
using ws_ssize_t = int;
|
using ws_ssize_t = int;
|
||||||
#else
|
#else
|
||||||
static constexpr auto pi_close = &close;
|
#define closesocket close
|
||||||
using ws_ssize_t = ssize_t;
|
using ws_ssize_t = ssize_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -36,6 +35,13 @@ using ws_ssize_t = ssize_t;
|
||||||
#define SEND_FLAGS 0
|
#define SEND_FLAGS 0
|
||||||
#endif
|
#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)
|
static int ConnectToDestination(const std::string& destination)
|
||||||
{
|
{
|
||||||
if (destination.empty())
|
if (destination.empty())
|
||||||
|
@ -45,32 +51,33 @@ static int ConnectToDestination(const std::string& destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
int ss_size;
|
int ss_size;
|
||||||
struct sockaddr_storage ss;
|
sockaddr_storage ss;
|
||||||
memset(&ss, 0, sizeof(ss));
|
memset(&ss, 0, sizeof(ss));
|
||||||
if (destination[0] != '/')
|
if (destination[0] != '/')
|
||||||
{
|
{
|
||||||
// IP address or hostname
|
// IP address or hostname
|
||||||
size_t colon_offset = destination.find(':');
|
const std::size_t colon_offset = destination.find(':');
|
||||||
if (colon_offset == std::string::npos)
|
if (colon_offset == std::string::npos)
|
||||||
{
|
{
|
||||||
ERROR_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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(&ss);
|
sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(&ss);
|
||||||
sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger());
|
sin->sin_addr.s_addr = htonl(sf::IpAddress(destination.substr(0, colon_offset)).toInteger());
|
||||||
sin->sin_family = AF_INET;
|
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);
|
ss_size = sizeof(*sin);
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// UNIX socket
|
// UNIX socket
|
||||||
struct sockaddr_un* sun = reinterpret_cast<struct sockaddr_un*>(&ss);
|
sockaddr_un* sun = reinterpret_cast<sockaddr_un*>(&ss);
|
||||||
if (destination.size() + 1 > sizeof(sun->sun_path))
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
sun->sun_family = AF_UNIX;
|
sun->sun_family = AF_UNIX;
|
||||||
|
@ -85,10 +92,10 @@ static int ConnectToDestination(const std::string& destination)
|
||||||
#endif
|
#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)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,109 +107,129 @@ static int ConnectToDestination(const std::string& destination)
|
||||||
|
|
||||||
if (connect(fd, reinterpret_cast<sockaddr*>(&ss), ss_size) == -1)
|
if (connect(fd, reinterpret_cast<sockaddr*>(&ss), ss_size) == -1)
|
||||||
{
|
{
|
||||||
std::string s = Common::LastStrerrorString();
|
std::string s = Common::StrNetworkError();
|
||||||
INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to init BBA\n", s.c_str());
|
INFO_LOG_FMT(SP1, "Couldn't connect socket ({}), unable to create tapserver connection\n", s);
|
||||||
pi_close(fd);
|
closesocket(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIModem::TAPServerNetworkInterface::Activate()
|
bool TAPServerConnection::Activate()
|
||||||
{
|
{
|
||||||
if (IsActivated())
|
if (IsActivated())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
m_fd = ConnectToDestination(m_destination);
|
m_fd = ConnectToDestination(m_destination);
|
||||||
if (m_fd < 0)
|
if (m_fd < 0)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
INFO_LOG_FMT(SP1, "Modem initialized.");
|
|
||||||
return RecvInit();
|
return RecvInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIModem::TAPServerNetworkInterface::Deactivate()
|
void TAPServerConnection::Deactivate()
|
||||||
{
|
{
|
||||||
if (m_fd >= 0)
|
if (m_fd >= 0)
|
||||||
{
|
closesocket(m_fd);
|
||||||
pi_close(m_fd);
|
|
||||||
}
|
|
||||||
m_fd = -1;
|
m_fd = -1;
|
||||||
|
|
||||||
m_read_enabled.Clear();
|
m_read_enabled.Clear();
|
||||||
m_read_shutdown.Set();
|
m_read_shutdown.Set();
|
||||||
if (m_read_thread.joinable())
|
if (m_read_thread.joinable())
|
||||||
{
|
|
||||||
m_read_thread.join();
|
m_read_thread.join();
|
||||||
}
|
|
||||||
m_read_shutdown.Clear();
|
m_read_shutdown.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIModem::TAPServerNetworkInterface::IsActivated()
|
bool TAPServerConnection::IsActivated()
|
||||||
{
|
{
|
||||||
return (m_fd >= 0);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIModem::TAPServerNetworkInterface::RecvStart()
|
void TAPServerConnection::RecvStart()
|
||||||
{
|
{
|
||||||
m_read_enabled.Set();
|
m_read_enabled.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIModem::TAPServerNetworkInterface::RecvStop()
|
void TAPServerConnection::RecvStop()
|
||||||
{
|
{
|
||||||
m_read_enabled.Clear();
|
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)
|
if (start_offset == std::string::npos)
|
||||||
{
|
{
|
||||||
break;
|
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)
|
if (end_sentinel_offset == std::string::npos)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
size_t end_offset = end_sentinel_offset + 1;
|
std::size_t end_offset = end_sentinel_offset + 1;
|
||||||
size_t size = end_offset - start_offset;
|
std::size_t size = end_offset - start_offset;
|
||||||
|
|
||||||
uint8_t size_bytes[2] = {static_cast<u8>(size), static_cast<u8>(size >> 8)};
|
u8 size_bytes[2] = {static_cast<u8>(size), static_cast<u8>(size >> 8)};
|
||||||
if (send(m_fd, size_bytes, 2, SEND_FLAGS) != 2)
|
if (send(m_fd, reinterpret_cast<const char*>(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;
|
return false;
|
||||||
}
|
}
|
||||||
int written_bytes =
|
const int written_bytes =
|
||||||
send(m_fd, m_modem_ref->m_send_buffer.data() + start_offset, size, SEND_FLAGS);
|
send(m_fd, send_buf.data() + start_offset, static_cast<int>(size), SEND_FLAGS);
|
||||||
if (u32(written_bytes) != size)
|
if (u32(written_bytes) != size)
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SP1, "SendFrames(): expected to write {} bytes, instead wrote {}", size,
|
ERROR_LOG_FMT(SP1,
|
||||||
written_bytes);
|
"SendAndRemoveAllHDLCFrames(): expected to write {} bytes, instead wrote {}",
|
||||||
|
size, written_bytes);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_modem_ref->m_send_buffer = m_modem_ref->m_send_buffer.substr(end_offset);
|
send_buf = send_buf.substr(end_offset);
|
||||||
m_modem_ref->SendComplete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
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<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;
|
||||||
|
}
|
||||||
|
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
|
enum class ReadState
|
||||||
{
|
{
|
||||||
|
@ -213,8 +240,8 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler()
|
||||||
};
|
};
|
||||||
ReadState read_state = ReadState::SIZE;
|
ReadState read_state = ReadState::SIZE;
|
||||||
|
|
||||||
size_t frame_bytes_received = 0;
|
std::size_t frame_bytes_received = 0;
|
||||||
size_t frame_bytes_expected = 0;
|
std::size_t frame_bytes_expected = 0;
|
||||||
std::string frame_data;
|
std::string frame_data;
|
||||||
|
|
||||||
while (!m_read_shutdown.IsSet())
|
while (!m_read_shutdown.IsSet())
|
||||||
|
@ -246,7 +273,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler()
|
||||||
{
|
{
|
||||||
frame_bytes_expected = size_bytes[0] | (size_bytes[1] << 8);
|
frame_bytes_expected = size_bytes[0] | (size_bytes[1] << 8);
|
||||||
frame_data.resize(frame_bytes_expected, '\0');
|
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);
|
ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected);
|
||||||
read_state = ReadState::SKIP;
|
read_state = ReadState::SKIP;
|
||||||
|
@ -259,7 +286,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SP1, "Failed to read size field from destination: {}",
|
ERROR_LOG_FMT(SP1, "Failed to read size field from destination: {}",
|
||||||
Common::LastStrerrorString());
|
Common::StrNetworkError());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -273,7 +300,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler()
|
||||||
{
|
{
|
||||||
frame_bytes_expected |= (size_high << 8);
|
frame_bytes_expected |= (size_high << 8);
|
||||||
frame_data.resize(frame_bytes_expected, '\0');
|
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);
|
ERROR_LOG_FMT(SP1, "Packet is too large ({} bytes); dropping it", frame_bytes_expected);
|
||||||
read_state = ReadState::SKIP;
|
read_state = ReadState::SKIP;
|
||||||
|
@ -286,19 +313,19 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SP1, "Failed to read split size field from destination: {}",
|
ERROR_LOG_FMT(SP1, "Failed to read split size field from destination: {}",
|
||||||
Common::LastStrerrorString());
|
Common::StrNetworkError());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ReadState::DATA:
|
case ReadState::DATA:
|
||||||
case ReadState::SKIP:
|
case ReadState::SKIP:
|
||||||
{
|
{
|
||||||
ws_ssize_t bytes_read = recv(m_fd, frame_data.data() + frame_bytes_received,
|
ws_ssize_t bytes_to_read = frame_data.size() - frame_bytes_received;
|
||||||
frame_data.size() - frame_bytes_received, 0);
|
ws_ssize_t bytes_read =
|
||||||
|
recv(m_fd, frame_data.data() + frame_bytes_received, bytes_to_read, 0);
|
||||||
if (bytes_read <= 0)
|
if (bytes_read <= 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}",
|
ERROR_LOG_FMT(SP1, "Failed to read data from destination: {}", Common::StrNetworkError());
|
||||||
Common::LastStrerrorString());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -307,7 +334,7 @@ void CEXIModem::TAPServerNetworkInterface::ReadThreadHandler()
|
||||||
{
|
{
|
||||||
if (read_state == ReadState::DATA)
|
if (read_state == ReadState::DATA)
|
||||||
{
|
{
|
||||||
m_modem_ref->AddToReceiveBuffer(std::move(frame_data));
|
m_recv_cb(std::move(frame_data));
|
||||||
}
|
}
|
||||||
frame_data.clear();
|
frame_data.clear();
|
||||||
frame_bytes_received = 0;
|
frame_bytes_received = 0;
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2020 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#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 <functional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#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<void(std::string&&)> 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<void(std::string&&)> 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
|
|
@ -19,6 +19,7 @@
|
||||||
#include "Common/Network.h"
|
#include "Common/Network.h"
|
||||||
#include "Common/SocketContext.h"
|
#include "Common/SocketContext.h"
|
||||||
#include "Core/HW/EXI/BBA/BuiltIn.h"
|
#include "Core/HW/EXI/BBA/BuiltIn.h"
|
||||||
|
#include "Core/HW/EXI/BBA/TAPServerConnection.h"
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
@ -366,10 +367,7 @@ private:
|
||||||
class TAPServerNetworkInterface : public NetworkInterface
|
class TAPServerNetworkInterface : public NetworkInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination)
|
TAPServerNetworkInterface(CEXIETHERNET* eth_ref, const std::string& destination);
|
||||||
: NetworkInterface(eth_ref), m_destination(destination)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool Activate() override;
|
bool Activate() override;
|
||||||
|
@ -381,26 +379,9 @@ private:
|
||||||
void RecvStop() override;
|
void RecvStop() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class ReadState
|
TAPServerConnection m_tapserver_if;
|
||||||
{
|
|
||||||
Size,
|
|
||||||
SizeHigh,
|
|
||||||
Data,
|
|
||||||
Skip,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string m_destination;
|
void HandleReceivedFrame(std::string&& data);
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class XLinkNetworkInterface : public NetworkInterface
|
class XLinkNetworkInterface : public NetworkInterface
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2008 Dolphin Emulator Project
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "Core/HW/EXI/EXI_DeviceModem.h"
|
#include "Core/HW/EXI/EXI_DeviceModem.h"
|
||||||
|
@ -37,10 +37,6 @@ CEXIModem::CEXIModem(Core::System& system, ModemDeviceType type) : IEXIDevice(sy
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t z = 0; z < m_regs.size(); z++)
|
|
||||||
{
|
|
||||||
m_regs[z] = 0;
|
|
||||||
}
|
|
||||||
m_regs[Register::DEVICE_TYPE] = 0x02;
|
m_regs[Register::DEVICE_TYPE] = 0x02;
|
||||||
m_regs[Register::INTERRUPT_MASK] = 0x02;
|
m_regs[Register::INTERRUPT_MASK] = 0x02;
|
||||||
}
|
}
|
||||||
|
@ -84,15 +80,19 @@ void CEXIModem::ImmWrite(u32 data, u32 size)
|
||||||
}
|
}
|
||||||
else if (IsModemTransfer(m_transfer_descriptor))
|
else if (IsModemTransfer(m_transfer_descriptor))
|
||||||
{ // Write AT command buffer or packet send buffer
|
{ // Write AT command buffer or packet send buffer
|
||||||
u32 be_data = htonl(data);
|
const u32 be_data = htonl(data);
|
||||||
HandleWriteModemTransfer(&be_data, size);
|
HandleWriteModemTransfer(&be_data, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Write device register
|
{ // Write device register
|
||||||
uint8_t reg_num = static_cast<uint8_t>((m_transfer_descriptor >> 24) & 0x1F);
|
u8 reg_num = static_cast<uint8_t>((m_transfer_descriptor >> 24) & 0x1F);
|
||||||
bool should_update_interrupts = false;
|
bool should_update_interrupts = false;
|
||||||
for (; size; size--)
|
for (; size; size--)
|
||||||
{
|
{
|
||||||
|
if (reg_num >= m_regs.size())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
should_update_interrupts |=
|
should_update_interrupts |=
|
||||||
((reg_num == Register::INTERRUPT_MASK) || (reg_num == Register::PENDING_INTERRUPT_MASK));
|
((reg_num == Register::INTERRUPT_MASK) || (reg_num == Register::PENDING_INTERRUPT_MASK));
|
||||||
m_regs[reg_num++] = (data >> 24);
|
m_regs[reg_num++] = (data >> 24);
|
||||||
|
@ -154,14 +154,18 @@ u32 CEXIModem::ImmRead(u32 size)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // Read device register
|
{ // Read device register
|
||||||
uint8_t reg_num = static_cast<uint8_t>((m_transfer_descriptor >> 24) & 0x1F);
|
u8 reg_num = static_cast<uint8_t>((m_transfer_descriptor >> 24) & 0x1F);
|
||||||
if (reg_num == 0)
|
if (reg_num == 0)
|
||||||
{
|
{
|
||||||
return 0x02020000; // Device ID (modem)
|
return 0x02020000; // Device ID (modem)
|
||||||
}
|
}
|
||||||
u32 ret = 0;
|
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));
|
ret |= (m_regs[reg_num + z] << ((3 - z) * 8));
|
||||||
}
|
}
|
||||||
m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR;
|
m_transfer_descriptor = INVALID_TRANSFER_DESCRIPTOR;
|
||||||
|
@ -197,28 +201,31 @@ void CEXIModem::DMARead(u32 addr, u32 size)
|
||||||
|
|
||||||
void CEXIModem::HandleReadModemTransfer(void* data, 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)
|
if (size > bytes_requested)
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SP1, "More bytes requested ({}) than originally requested for transfer {:x}",
|
ERROR_LOG_FMT(SP1, "More bytes requested ({}) than originally requested for transfer {:x}",
|
||||||
size, m_transfer_descriptor);
|
size, m_transfer_descriptor);
|
||||||
size = bytes_requested;
|
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)
|
if ((m_transfer_descriptor & 0x0F000000) == 0x03000000)
|
||||||
{ // AT command buffer
|
{
|
||||||
memcpy(data, m_at_reply_data.data(), std::min<size_t>(size, m_at_reply_data.size()));
|
// AT command buffer
|
||||||
m_at_reply_data = m_at_reply_data.substr(size);
|
const std::size_t bytes_to_copy = std::min<std::size_t>(size, m_at_reply_data.size());
|
||||||
m_regs[Register::AT_REPLY_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<u8>(m_at_reply_data.size());
|
||||||
SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true);
|
SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true);
|
||||||
}
|
}
|
||||||
else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000)
|
else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000)
|
||||||
{ // Packet receive buffer
|
{
|
||||||
|
// Packet receive buffer
|
||||||
std::lock_guard<std::mutex> g(m_receive_buffer_lock);
|
std::lock_guard<std::mutex> g(m_receive_buffer_lock);
|
||||||
size_t bytes_to_copy = std::min<size_t>(size, m_receive_buffer.size());
|
const std::size_t bytes_to_copy = std::min<std::size_t>(size, m_receive_buffer.size());
|
||||||
memcpy(data, m_receive_buffer.data(), bytes_to_copy);
|
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);
|
OnReceiveBufferSizeChangedLocked(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -234,20 +241,20 @@ void CEXIModem::HandleReadModemTransfer(void* data, u32 size)
|
||||||
|
|
||||||
void CEXIModem::HandleWriteModemTransfer(const 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)
|
if (size > bytes_expected)
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(SP1, "More bytes received ({}) than expected for transfer {:x}", size,
|
ERROR_LOG_FMT(SP1, "More bytes received ({}) than expected for transfer {:x}", size,
|
||||||
m_transfer_descriptor);
|
m_transfer_descriptor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
u16 bytes_expected_after_write = bytes_expected - size;
|
const u16 bytes_expected_after_write = bytes_expected - size;
|
||||||
|
|
||||||
if ((m_transfer_descriptor & 0x0F000000) == 0x03000000)
|
if ((m_transfer_descriptor & 0x0F000000) == 0x03000000)
|
||||||
{ // AT command buffer
|
{ // AT command buffer
|
||||||
m_at_command_data.append(reinterpret_cast<const char*>(data), size);
|
m_at_command_data.append(reinterpret_cast<const char*>(data), size);
|
||||||
RunAllPendingATCommands();
|
RunAllPendingATCommands();
|
||||||
m_regs[Register::AT_COMMAND_SIZE] = m_at_command_data.size();
|
m_regs[Register::AT_COMMAND_SIZE] = static_cast<u8>(m_at_command_data.size());
|
||||||
}
|
}
|
||||||
else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000)
|
else if ((m_transfer_descriptor & 0x0F000000) == 0x08000000)
|
||||||
{ // Packet send buffer
|
{ // 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
|
// from the emulated program's perspective, so we always tell it the send
|
||||||
// FIFO is empty.
|
// FIFO is empty.
|
||||||
SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true);
|
SetInterruptFlag(Interrupt::SEND_BUFFER_BELOW_THRESHOLD, true, true);
|
||||||
m_network_interface->SendFrames();
|
m_network_interface->SendAndRemoveAllHDLCFrames(m_send_buffer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -289,7 +296,7 @@ u16 CEXIModem::GetRxThreshold() const
|
||||||
return (m_regs[Register::RX_THRESHOLD_HIGH] << 8) | m_regs[Register::RX_THRESHOLD_LOW];
|
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)
|
if (enabled)
|
||||||
{
|
{
|
||||||
|
@ -306,7 +313,7 @@ void CEXIModem::SetInterruptFlag(uint8_t what, bool enabled, bool from_cpu)
|
||||||
void CEXIModem::OnReceiveBufferSizeChangedLocked(bool from_cpu)
|
void CEXIModem::OnReceiveBufferSizeChangedLocked(bool from_cpu)
|
||||||
{
|
{
|
||||||
// The caller is expected to hold m_receive_buffer_lock when calling this.
|
// The caller is expected to hold m_receive_buffer_lock when calling this.
|
||||||
uint16_t bytes_available = std::min<size_t>(m_receive_buffer.size(), 0x200);
|
const u16 bytes_available = std::min<u16>(m_receive_buffer.size(), 0x200);
|
||||||
m_regs[Register::BYTES_AVAILABLE_HIGH] = (bytes_available >> 8) & 0xFF;
|
m_regs[Register::BYTES_AVAILABLE_HIGH] = (bytes_available >> 8) & 0xFF;
|
||||||
m_regs[Register::BYTES_AVAILABLE_LOW] = bytes_available & 0xFF;
|
m_regs[Register::BYTES_AVAILABLE_LOW] = bytes_available & 0xFF;
|
||||||
SetInterruptFlag(Interrupt::RECEIVE_BUFFER_ABOVE_THRESHOLD,
|
SetInterruptFlag(Interrupt::RECEIVE_BUFFER_ABOVE_THRESHOLD,
|
||||||
|
@ -343,25 +350,29 @@ void CEXIModem::AddToReceiveBuffer(std::string&& data)
|
||||||
void CEXIModem::AddATReply(const std::string& data)
|
void CEXIModem::AddATReply(const std::string& data)
|
||||||
{
|
{
|
||||||
m_at_reply_data += data;
|
m_at_reply_data += data;
|
||||||
m_regs[Register::AT_REPLY_SIZE] = m_at_reply_data.size();
|
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(), false);
|
SetInterruptFlag(Interrupt::AT_REPLY_DATA_AVAILABLE, !m_at_reply_data.empty(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIModem::RunAllPendingATCommands()
|
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"))
|
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);
|
std::string command = m_at_command_data.substr(0, newline_pos);
|
||||||
m_at_command_data = m_at_command_data.substr(newline_pos + 1);
|
m_at_command_data = m_at_command_data.substr(newline_pos + 1);
|
||||||
|
|
||||||
if (command == "ATZ")
|
INFO_LOG_FMT(SP1, "Received AT command: {}", command);
|
||||||
{ // Reset
|
|
||||||
|
if (command.substr(0, 3) == "ATZ")
|
||||||
|
{
|
||||||
|
// Reset
|
||||||
m_network_interface->Deactivate();
|
m_network_interface->Deactivate();
|
||||||
AddATReply("OK\r");
|
AddATReply("OK\r");
|
||||||
}
|
}
|
||||||
else if (command.substr(0, 3) == "ATD")
|
else if (command.substr(0, 3) == "ATD")
|
||||||
{ // Dial
|
{
|
||||||
|
// Dial
|
||||||
if (m_network_interface->Activate())
|
if (m_network_interface->Activate())
|
||||||
{
|
{
|
||||||
AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate
|
AddATReply("OK\rCONNECT 115200\r"); // Maximum baud rate
|
||||||
|
@ -373,7 +384,9 @@ void CEXIModem::RunAllPendingATCommands()
|
||||||
}
|
}
|
||||||
else
|
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");
|
AddATReply("OK\r");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,15 @@
|
||||||
// Copyright 2008 Dolphin Emulator Project
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <SFML/Network.hpp>
|
|
||||||
|
|
||||||
#include "Common/Flag.h"
|
#include "Common/Flag.h"
|
||||||
#include "Common/Network.h"
|
#include "Common/Network.h"
|
||||||
#include "Common/SocketContext.h"
|
#include "Core/HW/EXI/BBA/TAPServerConnection.h"
|
||||||
#include "Core/HW/EXI/BBA/BuiltIn.h"
|
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
@ -86,7 +77,7 @@ private:
|
||||||
|
|
||||||
u16 GetTxThreshold() const;
|
u16 GetTxThreshold() const;
|
||||||
u16 GetRxThreshold() 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 HandleReadModemTransfer(void* data, u32 size);
|
||||||
void HandleWriteModemTransfer(const void* data, u32 size);
|
void HandleWriteModemTransfer(const void* data, u32 size);
|
||||||
void OnReceiveBufferSizeChangedLocked(bool from_cpu);
|
void OnReceiveBufferSizeChangedLocked(bool from_cpu);
|
||||||
|
@ -97,19 +88,19 @@ private:
|
||||||
|
|
||||||
static inline bool TransferIsResetCommand(u32 transfer_descriptor)
|
static inline bool TransferIsResetCommand(u32 transfer_descriptor)
|
||||||
{
|
{
|
||||||
return (transfer_descriptor == 0x80000000);
|
return transfer_descriptor == 0x80000000;
|
||||||
}
|
}
|
||||||
static inline bool IsWriteTransfer(u32 transfer_descriptor)
|
static inline bool IsWriteTransfer(u32 transfer_descriptor)
|
||||||
{
|
{
|
||||||
return (transfer_descriptor & 0x40000000);
|
return transfer_descriptor & 0x40000000;
|
||||||
}
|
}
|
||||||
static inline bool IsModemTransfer(u32 transfer_descriptor)
|
static inline bool IsModemTransfer(u32 transfer_descriptor)
|
||||||
{
|
{
|
||||||
return (transfer_descriptor & 0x20000000);
|
return transfer_descriptor & 0x20000000;
|
||||||
}
|
}
|
||||||
static inline u16 GetModemTransferSize(u32 transfer_descriptor)
|
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)
|
static inline u32 SetModemTransferSize(u32 transfer_descriptor, u16 new_size)
|
||||||
{
|
{
|
||||||
|
@ -126,7 +117,7 @@ private:
|
||||||
virtual bool Activate() { return false; }
|
virtual bool Activate() { return false; }
|
||||||
virtual void Deactivate() {}
|
virtual void Deactivate() {}
|
||||||
virtual bool IsActivated() { return false; }
|
virtual bool IsActivated() { return false; }
|
||||||
virtual bool SendFrames() { return false; }
|
virtual bool SendAndRemoveAllHDLCFrames(std::string&) { return false; }
|
||||||
virtual bool RecvInit() { return false; }
|
virtual bool RecvInit() { return false; }
|
||||||
virtual void RecvStart() {}
|
virtual void RecvStart() {}
|
||||||
virtual void RecvStop() {}
|
virtual void RecvStop() {}
|
||||||
|
@ -137,30 +128,21 @@ private:
|
||||||
class TAPServerNetworkInterface : public NetworkInterface
|
class TAPServerNetworkInterface : public NetworkInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination)
|
TAPServerNetworkInterface(CEXIModem* modem_ref, const std::string& destination);
|
||||||
: NetworkInterface(modem_ref), m_destination(destination)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool Activate() override;
|
virtual bool Activate() override;
|
||||||
void Deactivate() override;
|
virtual void Deactivate() override;
|
||||||
bool IsActivated() override;
|
virtual bool IsActivated() override;
|
||||||
bool SendFrames() override;
|
virtual bool SendAndRemoveAllHDLCFrames(std::string& send_buffer) override;
|
||||||
bool RecvInit() override;
|
virtual bool RecvInit() override;
|
||||||
void RecvStart() override;
|
virtual void RecvStart() override;
|
||||||
void RecvStop() override;
|
virtual void RecvStop() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_destination;
|
TAPServerConnection m_tapserver_if;
|
||||||
Common::SocketContext m_socket_context;
|
|
||||||
|
|
||||||
int m_fd = -1;
|
void HandleReceivedFrame(std::string&& data);
|
||||||
std::thread m_read_thread;
|
|
||||||
Common::Flag m_read_enabled;
|
|
||||||
Common::Flag m_read_shutdown;
|
|
||||||
|
|
||||||
void ReadThreadHandler();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<NetworkInterface> m_network_interface;
|
std::unique_ptr<NetworkInterface> m_network_interface;
|
||||||
|
@ -174,6 +156,6 @@ private:
|
||||||
std::string m_send_buffer;
|
std::string m_send_buffer;
|
||||||
std::mutex m_receive_buffer_lock;
|
std::mutex m_receive_buffer_lock;
|
||||||
std::string m_receive_buffer;
|
std::string m_receive_buffer;
|
||||||
std::array<u8, 0x20> m_regs;
|
std::array<u8, 0x20> m_regs{};
|
||||||
};
|
};
|
||||||
} // namespace ExpansionInterface
|
} // namespace ExpansionInterface
|
||||||
|
|
|
@ -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 <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 "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
|
|
@ -938,9 +938,10 @@
|
||||||
<ClCompile Include="Core\HW\DVD\FileMonitor.cpp" />
|
<ClCompile Include="Core\HW\DVD\FileMonitor.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\BBA\BuiltIn.cpp" />
|
<ClCompile Include="Core\HW\EXI\BBA\BuiltIn.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\BBA\TAP_Win32.cpp" />
|
<ClCompile Include="Core\HW\EXI\BBA\TAP_Win32.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\BBA\TAPServer.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\BBA\XLINK_KAI_BBA.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\Modem\TAPServer.cpp" />
|
<ClCompile Include="Core\HW\EXI\Modem\TAPServerModem.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\EXI_Channel.cpp" />
|
<ClCompile Include="Core\HW\EXI\EXI_Channel.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\EXI_Device.cpp" />
|
<ClCompile Include="Core\HW\EXI\EXI_Device.cpp" />
|
||||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceAD16.cpp" />
|
<ClCompile Include="Core\HW\EXI\EXI_DeviceAD16.cpp" />
|
||||||
|
|
Loading…
Reference in New Issue