687 changes: 687 additions & 0 deletions Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions Source/Core/Core/HW/EXI/BBA/BuiltIn.h
@@ -0,0 +1,58 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#ifdef _WIN32
#include <WinSock2.h>
#else
#include <netinet/in.h>
#endif

#include <SFML/Network.hpp>
#include "Common/CommonTypes.h"
#include "Common/Network.h"

constexpr u16 TCP_FLAG_SIN = 0x2;
constexpr u16 TCP_FLAG_ACK = 0x10;
constexpr u16 TCP_FLAG_PSH = 0x8;
constexpr u16 TCP_FLAG_FIN = 0x1;
constexpr u16 TCP_FLAG_RST = 0x4;

constexpr u16 IP_PROTOCOL = 0x800;
constexpr u16 ARP_PROTOCOL = 0x806;

constexpr u8 MAX_TCP_BUFFER = 4;
constexpr u16 MAX_UDP_LENGTH = 1500;
constexpr u16 MAX_TCP_LENGTH = 440;

struct TcpBuffer
{
bool used;
u64 tick;
u32 seq_id;
std::vector<u8> data;
};

struct StackRef
{
u32 ip;
u16 local;
u16 remote;
u16 type;
sf::IpAddress target;
u32 seq_num;
u32 ack_num;
u32 ack_base;
u16 window_size;
u64 delay;
std::array<TcpBuffer, MAX_TCP_BUFFER> tcp_buffers;
bool ready;
sockaddr_in from;
sockaddr_in to;
Common::MACAddress bba_mac{};
Common::MACAddress my_mac{};
sf::UdpSocket udp_socket;
sf::TcpSocket tcp_socket;
u64 poke_time;
};
4 changes: 4 additions & 0 deletions Source/Core/Core/HW/EXI/EXI_Device.cpp
Expand Up @@ -143,6 +143,10 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(const EXIDeviceType device_type, co
result = std::make_unique<CEXIETHERNET>(BBADeviceType::XLINK);
break;

case EXIDeviceType::EthernetBuiltIn:
result = std::make_unique<CEXIETHERNET>(BBADeviceType::BuiltIn);
break;

case EXIDeviceType::Gecko:
result = std::make_unique<CEXIGecko>();
break;
Expand Down
4 changes: 3 additions & 1 deletion Source/Core/Core/HW/EXI/EXI_Device.h
Expand Up @@ -37,6 +37,7 @@ enum class EXIDeviceType : int
EthernetXLink,
// Only used on Apple devices.
EthernetTapServer,
EthernetBuiltIn,
None = 0xFF
};

Expand Down Expand Up @@ -79,7 +80,7 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(EXIDeviceType device_type, int chan

template <>
struct fmt::formatter<ExpansionInterface::EXIDeviceType>
: EnumFormatter<ExpansionInterface::EXIDeviceType::EthernetTapServer>
: EnumFormatter<ExpansionInterface::EXIDeviceType::EthernetBuiltIn>
{
static constexpr array_type names = {
_trans("Dummy"),
Expand All @@ -95,6 +96,7 @@ struct fmt::formatter<ExpansionInterface::EXIDeviceType>
_trans("Advance Game Port"),
_trans("Broadband Adapter (XLink Kai)"),
_trans("Broadband Adapter (tapserver)"),
_trans("Broadband Adapter (Built In)"),
};

constexpr formatter() : EnumFormatter(names) {}
Expand Down
98 changes: 58 additions & 40 deletions Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
Expand Up @@ -53,6 +53,11 @@ CEXIETHERNET::CEXIETHERNET(BBADeviceType type)
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));
INFO_LOG_FMT(SP1, "Created Built in network interface.");
break;
case BBADeviceType::XLINK:
// TODO start BBA with network link down, bring it up after "connected" response from XLink

Expand Down Expand Up @@ -155,6 +160,17 @@ void CEXIETHERNET::ImmWrite(u32 data, u32 size)
{
case INTERRUPT:
exi_status.interrupt &= data ^ 0xff;
// raise back if there is still data
if (page_ptr(BBA_RRP) != page_ptr(BBA_RWP))
{
if (mBbaMem[BBA_IMR] & INT_R)
{
mBbaMem[BBA_IR] |= INT_R;

exi_status.interrupt |= exi_status.TRANSFER;
}
}

break;
case INTERRUPT_MASK:
exi_status.interrupt_mask = data;
Expand Down Expand Up @@ -228,9 +244,7 @@ void CEXIETHERNET::DMAWrite(u32 addr, u32 size)
void CEXIETHERNET::DMARead(u32 addr, u32 size)
{
DEBUG_LOG_FMT(SP1, "DMA read: {:08x} {:x}", addr, size);

Memory::CopyToEmu(addr, &mBbaMem[transfer.address], size);

transfer.address += size;
}

Expand Down Expand Up @@ -348,7 +362,6 @@ void CEXIETHERNET::MXCommandHandler(u32 data, u32 size)
// MXSoftReset();
m_network_interface->Activate();
}

if (((mBbaMem[BBA_NCRA] & NCRA_SR) ^ (data & NCRA_SR)) != 0)
{
DEBUG_LOG_FMT(SP1, "{} rx", (data & NCRA_SR) ? "start" : "stop");
Expand Down Expand Up @@ -415,7 +428,6 @@ void CEXIETHERNET::DirectFIFOWrite(const u8* data, u32 size)
{
// In direct mode, the hardware handles creating the state required by the
// GMAC instead of finagling with packet descriptors and such

u16* tx_fifo_count = (u16*)&mBbaMem[BBA_TXFIFOCNT];

memcpy(tx_fifo.get() + *tx_fifo_count, data, size);
Expand Down Expand Up @@ -510,76 +522,80 @@ inline void CEXIETHERNET::inc_rwp()
{
u16* rwp = (u16*)&mBbaMem[BBA_RWP];

if (*rwp + 1 == page_ptr(BBA_RHBP))
if (*rwp == page_ptr(BBA_RHBP))
*rwp = page_ptr(BBA_BP);
else
(*rwp)++;
}

inline void CEXIETHERNET::set_rwp(u16 value)
{
u16* rwp = (u16*)&mBbaMem[BBA_RWP];
*rwp = value;
}

// This function is on the critical path for receiving data.
// Be very careful about calling into the logger and other slow things
bool CEXIETHERNET::RecvHandlePacket()
{
u8* write_ptr;
u8* end_ptr;
u8* read_ptr;
Descriptor* descriptor;
u32 status = 0;
u16 rwp_initial = page_ptr(BBA_RWP);

u16 current_rwp = 0;
u32 off = 4;
if (!RecvMACFilter())
goto wait_for_next;

#ifdef BBA_TRACK_PAGE_PTRS
INFO_LOG_FMT(SP1, "RecvHandlePacket {:x}\n{}", mRecvBufferLength,
ArrayToString(mRecvBuffer, mRecvBufferLength, 0x100));
ArrayToString(mRecvBuffer.get(), mRecvBufferLength, 16));

INFO_LOG_FMT(SP1, "{:x} {:x} {:x} {:x}", page_ptr(BBA_BP), page_ptr(BBA_RRP), page_ptr(BBA_RWP),
page_ptr(BBA_RHBP));
#endif

write_ptr = ptr_from_page_ptr(BBA_RWP);
end_ptr = ptr_from_page_ptr(BBA_RHBP);
read_ptr = ptr_from_page_ptr(BBA_RRP);
write_ptr = &mBbaMem[page_ptr(BBA_RWP) << 8];

descriptor = (Descriptor*)write_ptr;
write_ptr += 4;

for (u32 i = 0, off = 4; i < mRecvBufferLength; ++i, ++off)
current_rwp = page_ptr(BBA_RWP);
DEBUG_LOG_FMT(SP1, "Frame recv: {:x}", mRecvBufferLength);
for (u32 i = 0; i < mRecvBufferLength; i++)
{
*write_ptr++ = mRecvBuffer[i];

if (off == 0xff)
write_ptr[off] = mRecvBuffer[i];
off++;
if (off == 0x100)
{
off = 0;
inc_rwp();
}
// avoid increasing the BBA register while copying
// sometime the OS can try to process when it's not completed
current_rwp = current_rwp == page_ptr(BBA_RHBP) ? page_ptr(BBA_BP) : ++current_rwp;

if (write_ptr == end_ptr)
write_ptr = ptr_from_page_ptr(BBA_BP);
write_ptr = &mBbaMem[current_rwp << 8];

if (write_ptr == read_ptr)
{
/*
halt copy
if (cur_packet_size >= PAGE_SIZE)
desc.status |= FO | BF
if (RBFIM)
raise RBFI
if (AUTORCVR)
discard bad packet
else
inc MPC instead of receiving packets
*/
status |= DESC_FO | DESC_BF;
mBbaMem[BBA_IR] |= mBbaMem[BBA_IMR] & INT_RBF;
break;
if (page_ptr(BBA_RRP) == current_rwp)
{
/*
halt copy
if (cur_packet_size >= PAGE_SIZE)
desc.status |= FO | BF
if (RBFIM)
raise RBFI
if (AUTORCVR)
discard bad packet
else
inc MPC instead of receiving packets
*/
status |= DESC_FO | DESC_BF;
mBbaMem[BBA_IR] |= mBbaMem[BBA_IMR] & INT_RBF;
break;
}
}
}

// Align up to next page
if ((mRecvBufferLength + 4) % 256)
inc_rwp();
current_rwp = current_rwp == page_ptr(BBA_RHBP) ? page_ptr(BBA_BP) : ++current_rwp;

#ifdef BBA_TRACK_PAGE_PTRS
INFO_LOG_FMT(SP1, "{:x} {:x} {:x} {:x}", page_ptr(BBA_BP), page_ptr(BBA_RRP), page_ptr(BBA_RWP),
Expand All @@ -602,7 +618,9 @@ bool CEXIETHERNET::RecvHandlePacket()
}
}

descriptor->set(*(u16*)&mBbaMem[BBA_RWP], 4 + mRecvBufferLength, status);
descriptor->set(current_rwp, 4 + mRecvBufferLength, status);

set_rwp(current_rwp);

mBbaMem[BBA_LRPS] = status;

Expand Down
55 changes: 54 additions & 1 deletion Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
Expand Up @@ -13,7 +13,10 @@

#include <SFML/Network.hpp>

#include <mutex>
#include "Common/Flag.h"
#include "Common/Network.h"
#include "Core/HW/EXI/BBA/BuiltIn.h"
#include "Core/HW/EXI/EXI_Device.h"

class PointerWrap;
Expand Down Expand Up @@ -204,6 +207,7 @@ enum class BBADeviceType
#if defined(__APPLE__)
TAPSERVER,
#endif
BuiltIn,
};

class CEXIETHERNET : public IEXIDevice
Expand Down Expand Up @@ -289,7 +293,6 @@ class CEXIETHERNET : public IEXIDevice
return ((u16)mBbaMem[index + 1] << 8) | mBbaMem[index];
}

inline u8* ptr_from_page_ptr(int const index) const { return &mBbaMem[page_ptr(index) << 8]; }
bool IsMXCommand(u32 const data);
bool IsWriteCommand(u32 const data);
const char* GetRegisterName() const;
Expand All @@ -299,9 +302,11 @@ class CEXIETHERNET : public IEXIDevice
void SendFromDirectFIFO();
void SendFromPacketBuffer();
void SendComplete();
void SendCompleteBack();
u8 HashIndex(const u8* dest_eth_addr);
bool RecvMACFilter();
void inc_rwp();
void set_rwp(u16 value);
bool RecvHandlePacket();

std::unique_ptr<u8[]> mBbaMem;
Expand Down Expand Up @@ -413,6 +418,54 @@ class CEXIETHERNET : public IEXIDevice
#endif
};

class BuiltInBBAInterface : public NetworkInterface
{
public:
BuiltInBBAInterface(CEXIETHERNET* eth_ref, std::string dns_ip, std::string local_ip)
: NetworkInterface(eth_ref), m_dns_ip(std::move(dns_ip)), m_local_ip(std::move(local_ip))
{
}
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:
std::string m_mac_id;
std::string m_dns_ip;
bool m_active = false;
u16 m_ip_frame_id = 0;
u8 m_queue_read = 0;
u8 m_queue_write = 0;
std::array<std::vector<u8>, 16> m_queue_data;
std::mutex m_mtx;
std::string m_local_ip;
u32 m_current_ip = 0;
u32 m_router_ip = 0;
#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__)
std::array<StackRef, 10> network_ref{}; // max 10 at same time, i think most gc game had a
// limit of 8 in the gc framework
std::thread m_read_thread;
Common::Flag m_read_enabled;
Common::Flag m_read_thread_shutdown;
Common::MACAddress m_fake_mac{};
static void ReadThreadHandler(BuiltInBBAInterface* self);
#endif
void WriteToQueue(const std::vector<u8>& data);
StackRef* GetAvailableSlot(u16 port);
StackRef* GetTCPSlot(u16 src_port, u16 dst_port, u32 ip);

void HandleARP(const Common::ARPPacket& packet);
void HandleDHCP(const Common::UDPPacket& packet);
void HandleTCPFrame(const Common::TCPPacket& packet);
void InitUDPPort(u16 port);
void HandleUDPFrame(const Common::UDPPacket& packet);
};

std::unique_ptr<NetworkInterface> m_network_interface;

std::unique_ptr<u8[]> mRecvBuffer;
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/DolphinLib.props
Expand Up @@ -254,6 +254,7 @@
<ClInclude Include="Core\HW\DVD\DVDMath.h" />
<ClInclude Include="Core\HW\DVD\DVDThread.h" />
<ClInclude Include="Core\HW\DVD\FileMonitor.h" />
<ClInclude Include="Core\HW\EXI\BBA\BuiltIn.h" />
<ClInclude Include="Core\HW\EXI\BBA\TAP_Win32.h" />
<ClInclude Include="Core\HW\EXI\EXI_Channel.h" />
<ClInclude Include="Core\HW\EXI\EXI_Device.h" />
Expand Down Expand Up @@ -869,6 +870,7 @@
<ClCompile Include="Core\HW\DVD\DVDMath.cpp" />
<ClCompile Include="Core\HW\DVD\DVDThread.cpp" />
<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\XLINK_KAI_BBA.cpp" />
<ClCompile Include="Core\HW\EXI\EXI_Channel.cpp" />
Expand Down
12 changes: 12 additions & 0 deletions Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp
Expand Up @@ -48,6 +48,15 @@ void BroadbandAdapterSettingsDialog::InitControls()
window_title = tr("Broadband Adapter MAC Address");
break;

case Type::BuiltIn:
address_label = new QLabel(tr("Enter the DNS server to use:"));
address_placeholder = QStringLiteral("8.8.8.8");
current_address = QString::fromStdString(Config::Get(Config::MAIN_BBA_BUILTIN_DNS));
description = new QLabel(tr("Use 8.8.8.8 for normal DNS, else enter your custom one"));

window_title = tr("Broadband Adapter DNS setting");
break;

case Type::XLinkKai:
address_label = new QLabel(tr("Enter IP address of device running the XLink Kai Client:"));
address_placeholder = QString::fromStdString("127.0.0.1");
Expand Down Expand Up @@ -103,6 +112,9 @@ void BroadbandAdapterSettingsDialog::SaveAddress()
Config::SetBaseOrCurrent(Config::MAIN_BBA_MAC, bba_new_address);
break;

case Type::BuiltIn:
Config::SetBaseOrCurrent(Config::MAIN_BBA_BUILTIN_DNS, bba_new_address);
break;
case Type::XLinkKai:
Config::SetBaseOrCurrent(Config::MAIN_BBA_XLINK_IP, bba_new_address);
break;
Expand Down
Expand Up @@ -15,6 +15,7 @@ class BroadbandAdapterSettingsDialog final : public QDialog
{
Ethernet,
XLinkKai,
BuiltIn
};

explicit BroadbandAdapterSettingsDialog(QWidget* target, Type bba_type);
Expand Down
9 changes: 8 additions & 1 deletion Source/Core/DolphinQt/Settings/GameCubePane.cpp
Expand Up @@ -120,6 +120,7 @@ void GameCubePane::CreateWidgets()
#ifdef __APPLE__
EXIDeviceType::EthernetTapServer,
#endif
EXIDeviceType::EthernetBuiltIn,
})
{
m_slot_combos[ExpansionInterface::Slot::SP1]->addItem(tr(fmt::format("{:n}", device).c_str()),
Expand Down Expand Up @@ -259,7 +260,8 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
break;
case ExpansionInterface::Slot::SP1:
has_config = (device == ExpansionInterface::EXIDeviceType::Ethernet ||
device == ExpansionInterface::EXIDeviceType::EthernetXLink);
device == ExpansionInterface::EXIDeviceType::EthernetXLink ||
device == ExpansionInterface::EXIDeviceType::EthernetBuiltIn);
break;
}

Expand Down Expand Up @@ -293,6 +295,11 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
BroadbandAdapterSettingsDialog(this, BroadbandAdapterSettingsDialog::Type::XLinkKai).exec();
return;
}
case ExpansionInterface::EXIDeviceType::EthernetBuiltIn:
{
BroadbandAdapterSettingsDialog(this, BroadbandAdapterSettingsDialog::Type::BuiltIn).exec();
return;
}
default:
PanicAlertFmt("Unknown settings pressed for {}", device);
return;
Expand Down