summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorschthack <schthack@yahoo.com>2022-05-22 12:21:28 -0400
committerschthack <schthack@yahoo.com>2022-07-02 18:02:50 -0400
commit01ada3850ff07c0225819fee2898f355aaf3707a (patch)
tree16175a35b4060a0c788eff71fa5004c4c4c3c01a /Source
parentd625c612c4a40a8c8db3d90b313f5643aec9c890 (diff)
BBA: Added BuiltIn device that allow BBA emulation without the need of a TapDevice Configuration include a dns server setting
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Common/Network.cpp80
-rw-r--r--Source/Core/Common/Network.h62
-rw-r--r--Source/Core/Core/CMakeLists.txt8
-rw-r--r--Source/Core/Core/Config/MainSettings.cpp5
-rw-r--r--Source/Core/Core/Config/MainSettings.h2
-rw-r--r--Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp2
-rw-r--r--Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp700
-rw-r--r--Source/Core/Core/HW/EXI/BBA/BuiltIn.h56
-rw-r--r--Source/Core/Core/HW/EXI/EXI_Device.cpp4
-rw-r--r--Source/Core/Core/HW/EXI/EXI_Device.h4
-rw-r--r--Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp100
-rw-r--r--Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h61
-rw-r--r--Source/Core/DolphinLib.props2
-rw-r--r--Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp12
-rw-r--r--Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h1
-rw-r--r--Source/Core/DolphinQt/Settings/GameCubePane.cpp9
16 files changed, 1063 insertions, 45 deletions
diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp
index faa2a33c05..756dd0381e 100644
--- a/Source/Core/Common/Network.cpp
+++ b/Source/Core/Common/Network.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <string_view>
+#include <vector>
#ifndef _WIN32
#include <netinet/in.h>
@@ -141,6 +142,18 @@ TCPHeader::TCPHeader(const sockaddr_in& from, const sockaddr_in& to, u32 seq, co
checksum = htons(static_cast<u16>(tcp_checksum));
}
+TCPHeader::TCPHeader(const sockaddr_in& from, const sockaddr_in& to, u32 seq, u32 ack, u16 flags)
+{
+ source_port = from.sin_port;
+ destination_port = to.sin_port;
+ sequence_number = htonl(seq);
+ acknowledgement_number = htonl(ack);
+ properties = 0x50 | flags;
+
+ window_size = 0x7c;
+ checksum = 0;
+}
+
u16 TCPHeader::Size() const
{
return static_cast<u16>(SIZE);
@@ -170,6 +183,59 @@ u8 UDPHeader::IPProto() const
return static_cast<u8>(IPPROTO_UDP);
}
+ARPHeader::ARPHeader() = default;
+
+ARPHeader::ARPHeader(u32 from_ip, MACAddress from_mac, u32 to_ip, MACAddress to_mac)
+{
+ hardware_type = htons(BBA_HARDWARE_TYPE);
+ protocol_type = IPV4_HEADER_TYPE;
+ hardware_size = MAC_ADDRESS_SIZE;
+ protocol_size = IPV4_ADDR_LEN;
+ opcode = 0x200;
+ sender_ip = from_ip;
+ target_ip = to_ip;
+ targer_address = to_mac;
+ sender_address = from_mac;
+}
+
+u16 ARPHeader::Size() const
+{
+ return static_cast<u16>(SIZE);
+}
+
+DHCPBody::DHCPBody() = default;
+
+DHCPBody::DHCPBody(u32 transaction, MACAddress client_address, u32 new_ip, u32 serv_ip)
+{
+ transaction_id = transaction;
+ message_type = DHCPConst::MESSAGE_REPLY;
+ hardware_type = BBA_HARDWARE_TYPE;
+ hardware_addr = MAC_ADDRESS_SIZE;
+ client_mac = client_address;
+ your_ip = new_ip;
+ server_ip = serv_ip;
+}
+
+// Add an option to the DHCP Body
+bool DHCPBody::AddDHCPOption(u8 size, u8 fnc, const std::vector<u8>& params)
+{
+ int i = 0;
+ while (options[i] != 0)
+ {
+ i += options[i + 1] + 2;
+ if (i >= std::size(options))
+ {
+ return false;
+ }
+ }
+
+ options[i++] = fnc;
+ options[i++] = size;
+ for (auto val : params)
+ options[i++] = val;
+ return true;
+}
+
// Compute the network checksum with a 32-bit accumulator using the
// "Normal" order, see RFC 1071 for more details.
u16 ComputeNetworkChecksum(const void* data, u16 length, u32 initial_value)
@@ -187,6 +253,20 @@ u16 ComputeNetworkChecksum(const void* data, u16 length, u32 initial_value)
return ~static_cast<u16>(checksum);
}
+// Compute the TCP network checksum with a fake header
+u16 ComputeTCPNetworkChecksum(const sockaddr_in& from, const sockaddr_in& to, const void* data,
+ u16 length, u8 protocol)
+{
+ // Compute the TCP checksum with its pseudo header
+ const u32 source_addr = ntohl(from.sin_addr.s_addr);
+ const u32 destination_addr = ntohl(to.sin_addr.s_addr);
+ const u32 initial_value = (source_addr >> 16) + (source_addr & 0xFFFF) +
+ (destination_addr >> 16) + (destination_addr & 0xFFFF) + protocol +
+ length;
+ const u32 tcp_checksum = ComputeNetworkChecksum(data, length, initial_value);
+ return htons(static_cast<u16>(tcp_checksum));
+}
+
NetworkErrorState SaveNetworkErrorState()
{
return {
diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h
index 0b56e09d34..eec09234a7 100644
--- a/Source/Core/Common/Network.h
+++ b/Source/Core/Common/Network.h
@@ -7,6 +7,7 @@
#include <optional>
#include <string>
#include <string_view>
+#include <vector>
#include "Common/CommonTypes.h"
@@ -22,7 +23,15 @@ enum class MACConsumer
enum
{
- MAC_ADDRESS_SIZE = 6
+ BBA_HARDWARE_TYPE = 1,
+ MAC_ADDRESS_SIZE = 6,
+ IPV4_HEADER_TYPE = 8
+};
+
+enum DHCPConst
+{
+ MESSAGE_QUERY = 1,
+ MESSAGE_REPLY = 2
};
using MACAddress = std::array<u8, MAC_ADDRESS_SIZE>;
@@ -67,6 +76,7 @@ struct TCPHeader
{
TCPHeader();
TCPHeader(const sockaddr_in& from, const sockaddr_in& to, u32 seq, const u8* data, u16 length);
+ TCPHeader(const sockaddr_in& from, const sockaddr_in& to, u32 seq, u32 ack, u16 flags);
u16 Size() const;
u8 IPProto() const;
@@ -99,6 +109,54 @@ struct UDPHeader
};
static_assert(sizeof(UDPHeader) == UDPHeader::SIZE);
+#pragma pack(push, 1)
+struct ARPHeader
+{
+ ARPHeader();
+ ARPHeader(u32 from_ip, MACAddress from_mac, u32 to_ip, MACAddress to_mac);
+ u16 Size() const;
+
+ static constexpr std::size_t SIZE = 28;
+
+ u16 hardware_type = 0;
+ u16 protocol_type = 0;
+ u8 hardware_size = 0;
+ u8 protocol_size = 0;
+ u16 opcode = 0;
+ MACAddress sender_address{};
+ u32 sender_ip = 0;
+ MACAddress targer_address{};
+ u32 target_ip = 0;
+};
+static_assert(sizeof(ARPHeader) == ARPHeader::SIZE);
+#pragma pack(pop)
+
+struct DHCPBody
+{
+ DHCPBody();
+ DHCPBody(u32 transaction, MACAddress client_address, u32 new_ip, u32 serv_ip);
+ bool AddDHCPOption(u8 size, u8 fnc, const std::vector<u8>& params);
+ static constexpr std::size_t SIZE = 540;
+ u8 message_type = 0;
+ u8 hardware_type = 0;
+ u8 hardware_addr = 0;
+ u8 hops = 0;
+ u32 transaction_id = 0;
+ u16 secondes = 0;
+ u16 boot_flag = 0;
+ u32 client_ip = 0;
+ u32 your_ip = 0;
+ u32 server_ip = 0;
+ u32 relay_ip = 0;
+ MACAddress client_mac{};
+ unsigned char padding[10]{};
+ unsigned char hostname[0x40]{};
+ unsigned char boot_file[0x80]{};
+ u32 magic_cookie = 0x63538263;
+ u8 options[300]{};
+};
+static_assert(sizeof(DHCPBody) == DHCPBody::SIZE);
+
struct NetworkErrorState
{
int error;
@@ -111,6 +169,8 @@ MACAddress GenerateMacAddress(MACConsumer type);
std::string MacAddressToString(const MACAddress& mac);
std::optional<MACAddress> StringToMacAddress(std::string_view mac_string);
u16 ComputeNetworkChecksum(const void* data, u16 length, u32 initial_value = 0);
+u16 ComputeTCPNetworkChecksum(const sockaddr_in& from, const sockaddr_in& to, const void* data,
+ u16 length, u8 protocol);
NetworkErrorState SaveNetworkErrorState();
void RestoreNetworkErrorState(const NetworkErrorState& state);
} // namespace Common
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 8a4db4c961..0dba56339b 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -647,6 +647,8 @@ if(WIN32)
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
)
@@ -662,12 +664,16 @@ elseif(APPLE)
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
@@ -717,4 +723,4 @@ endif()
if(MSVC)
# Add precompiled header
target_link_libraries(core PRIVATE use_pch)
-endif()
+endif() \ No newline at end of file
diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp
index d7d146e3e2..c3161e924e 100644
--- a/Source/Core/Core/Config/MainSettings.cpp
+++ b/Source/Core/Core/Config/MainSettings.cpp
@@ -116,6 +116,11 @@ const Info<std::string> MAIN_BBA_MAC{{System::Main, "Core", "BBA_MAC"}, ""};
const Info<std::string> MAIN_BBA_XLINK_IP{{System::Main, "Core", "BBA_XLINK_IP"}, "127.0.0.1"};
const Info<bool> MAIN_BBA_XLINK_CHAT_OSD{{System::Main, "Core", "BBA_XLINK_CHAT_OSD"}, true};
+// Schthack PSO Server - https://schtserv.com/
+const Info<std::string> MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN_DNS"},
+ "149.56.167.128"};
+const Info<std::string> MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""};
+
const Info<SerialInterface::SIDevices>& GetInfoForSIDevice(int channel)
{
static const std::array<const Info<SerialInterface::SIDevices>, 4> infos{
diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h
index e1b538f2b6..4261eed960 100644
--- a/Source/Core/Core/Config/MainSettings.h
+++ b/Source/Core/Core/Config/MainSettings.h
@@ -86,6 +86,8 @@ const Info<ExpansionInterface::EXIDeviceType>& GetInfoForEXIDevice(ExpansionInte
extern const Info<std::string> MAIN_BBA_MAC;
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;
const Info<SerialInterface::SIDevices>& GetInfoForSIDevice(int channel);
const Info<bool>& GetInfoForAdapterRumble(int channel);
const Info<bool>& GetInfoForSimulateKonga(int channel);
diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
index 0026205144..fce77dd35f 100644
--- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
+++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
@@ -81,6 +81,8 @@ bool IsSettingSaveable(const Config::Location& config_location)
&Config::MAIN_AGP_CART_B_PATH.GetLocation(),
&Config::MAIN_BBA_MAC.GetLocation(),
&Config::MAIN_BBA_XLINK_IP.GetLocation(),
+ &Config::MAIN_BBA_BUILTIN_DNS.GetLocation(),
+ &Config::MAIN_BBA_BUILTIN_IP.GetLocation(),
&Config::MAIN_BBA_XLINK_CHAT_OSD.GetLocation(),
&Config::MAIN_OVERRIDE_REGION_SETTINGS.GetLocation(),
&Config::MAIN_CUSTOM_RTC_ENABLE.GetLocation(),
diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp
new file mode 100644
index 0000000000..bba928e660
--- /dev/null
+++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp
@@ -0,0 +1,700 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <SFML/Network.hpp>
+
+#include "Common/Logging/Log.h"
+#include "Core/HW/EXI/BBA/BuiltIn.h"
+#include "Core/HW/EXI/EXI_Device.h"
+#include "Core/HW/EXI/EXI_DeviceEthernet.h"
+
+// #define BBA_TRACK_PAGE_PTRS
+
+namespace ExpansionInterface
+{
+u64 GetTickCountStd()
+{
+ using namespace std::chrono;
+ return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
+}
+
+void SetHardwareInfo(u8* data, Common::MACAddress dest, Common::MACAddress src)
+{
+ Common::EthernetHeader* hwpart = (Common::EthernetHeader*)data;
+ *hwpart = Common::EthernetHeader(IP_PROTOCOL);
+ hwpart->destination = dest;
+ hwpart->source = src;
+}
+
+std::tuple<Common::EthernetHeader*, Common::IPv4Header*, Common::TCPHeader*>
+getTcpHeaders(u8* data, Common::MACAddress dest, Common::MACAddress src)
+{
+ SetHardwareInfo(data, dest, src);
+ return std::tuple<Common::EthernetHeader*, Common::IPv4Header*, Common::TCPHeader*>(
+ (Common::EthernetHeader*)data, (Common::IPv4Header*)&data[14],
+ (Common::TCPHeader*)&data[0x22]);
+}
+
+std::tuple<Common::EthernetHeader*, Common::IPv4Header*, Common::UDPHeader*>
+getUdpHeaders(u8* data, Common::MACAddress dest, Common::MACAddress src)
+{
+ SetHardwareInfo(data, dest, src);
+ return std::tuple<Common::EthernetHeader*, Common::IPv4Header*, Common::UDPHeader*>(
+ (Common::EthernetHeader*)data, (Common::IPv4Header*)&data[14],
+ (Common::UDPHeader*)&data[0x22]);
+}
+
+bool CEXIETHERNET::BuiltInBBAInterface::Activate()
+{
+ if (IsActivated())
+ return true;
+
+ active = true;
+ m_in_frame = std::make_unique<u8[]>(9004);
+ m_out_frame = std::make_unique<u8[]>(9004);
+ for (auto& buf : queue_data)
+ buf.reserve(2048);
+ fake_mac = Common::GenerateMacAddress(Common::MACConsumer::BBA);
+ const u32 ip = m_local_ip.empty() ? sf::IpAddress::getLocalAddress().toInteger() :
+ sf::IpAddress(m_local_ip).toInteger();
+ m_current_ip = htonl(ip);
+ m_router_ip = (m_current_ip & 0xFFFFFF) | 0x01000000;
+ // clear all ref
+ for (int i = 0; i < std::size(network_ref); i++)
+ {
+ network_ref[i].ip = 0;
+ }
+
+ return RecvInit();
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::Deactivate()
+{
+ // Is the BBA Active? If not skip shutdown
+ if (!IsActivated())
+ return;
+ // Signal read thread to exit.
+ m_read_enabled.Clear();
+ m_read_thread_shutdown.Set();
+ active = false;
+
+ // kill all active socket
+ for (int i = 0; i < std::size(network_ref); i++)
+ {
+ if (network_ref[i].ip != 0)
+ {
+ network_ref[i].type == IPPROTO_TCP ? network_ref[i].tcp_socket.disconnect() :
+ network_ref[i].udp_socket.unbind();
+ }
+ network_ref[i].ip = 0;
+ }
+
+ // Wait for read thread to exit.
+ if (m_read_thread.joinable())
+ m_read_thread.join();
+}
+
+bool CEXIETHERNET::BuiltInBBAInterface::IsActivated()
+{
+ return active;
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::WriteToQueue(const u8* data, int length)
+{
+ queue_data[queue_write].resize(length);
+ std::memcpy(&queue_data[queue_write][0], data, length);
+ if (((queue_write + 1) & 15) == queue_read)
+ {
+ return;
+ }
+ queue_write = (queue_write + 1) & 15;
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::HandleARP(Common::EthernetHeader* hwdata,
+ Common::ARPHeader* arpdata)
+{
+ std::memset(m_in_frame.get(), 0, 0x30);
+ Common::EthernetHeader* hwpart = (Common::EthernetHeader*)m_in_frame.get();
+ *hwpart = Common::EthernetHeader(ARP_PROTOCOL);
+ hwpart->destination = *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0];
+ hwpart->source = fake_mac;
+
+ Common::ARPHeader* arppart = (Common::ARPHeader*)&m_in_frame[14];
+ Common::MACAddress bba_mac = *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0];
+ if (arpdata->target_ip == m_current_ip)
+ {
+ // game asked for himself, reply with his mac address
+ *arppart = Common::ARPHeader(arpdata->target_ip, bba_mac, m_current_ip, bba_mac);
+ }
+ else
+ {
+ *arppart = Common::ARPHeader(arpdata->target_ip, fake_mac, m_current_ip, bba_mac);
+ }
+
+ WriteToQueue(&m_in_frame[0], 0x2a);
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::HandleDHCP(Common::EthernetHeader* hwdata,
+ Common::UDPHeader* udpdata,
+ Common::DHCPBody* request)
+{
+ Common::DHCPBody* reply = (Common::DHCPBody*)&m_in_frame[0x2a];
+ sockaddr_in from;
+ sockaddr_in to;
+ std::memset(m_in_frame.get(), 0, 0x156);
+
+ // build layer
+ auto [hwpart, ippart, udppart] = getUdpHeaders(
+ m_in_frame.get(), *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0], fake_mac);
+
+ from.sin_addr.s_addr = m_router_ip;
+ from.sin_family = IPPROTO_UDP;
+ from.sin_port = htons(67);
+ to.sin_addr.s_addr = m_current_ip;
+ to.sin_family = IPPROTO_UDP;
+ to.sin_port = udpdata->source_port;
+ const std::vector<u8> ip_part = {((u8*)&m_router_ip)[0], ((u8*)&m_router_ip)[1],
+ ((u8*)&m_router_ip)[2], ((u8*)&m_router_ip)[3]};
+
+ *ippart = Common::IPv4Header(308, IPPROTO_UDP, from, to);
+
+ *udppart = Common::UDPHeader(from, to, 300);
+
+ *reply = Common::DHCPBody(request->transaction_id,
+ *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0], m_current_ip,
+ m_router_ip);
+
+ // options
+ request->options[2] == 1 ? reply->AddDHCPOption(1, 53, std::vector<u8>{2}) :
+ reply->AddDHCPOption(1, 53, std::vector<u8>{5});
+ reply->AddDHCPOption(4, 54, ip_part); // dhcp server ip
+ reply->AddDHCPOption(4, 51, std::vector<u8>{0, 1, 0x51, 0x80}); // lease time 24h
+ reply->AddDHCPOption(4, 58, std::vector<u8>{0, 1, 0x51, 0x80}); // renewal
+ reply->AddDHCPOption(4, 59, std::vector<u8>{0, 1, 0x51, 0x80}); // rebind
+ reply->AddDHCPOption(4, 1, std::vector<u8>{255, 255, 255, 0}); // submask
+ reply->AddDHCPOption(4, 28,
+ std::vector<u8>{ip_part[0], ip_part[1], ip_part[2], 255}); // broadcast ip
+ reply->AddDHCPOption(4, 6, ip_part); // dns server
+ reply->AddDHCPOption(3, 15, std::vector<u8>{0x6c, 0x61, 0x6e}); // domaine name "lan"
+ reply->AddDHCPOption(4, 3, ip_part); // router ip
+ reply->AddDHCPOption(0, 255, {}); // end
+
+ udppart->checksum = Common::ComputeTCPNetworkChecksum(from, to, udppart, 308, IPPROTO_UDP);
+
+ WriteToQueue(m_in_frame.get(), 0x156);
+}
+
+StackRef* CEXIETHERNET::BuiltInBBAInterface::GetAvaibleSlot(u16 port)
+{
+ if (port > 0) // existing connection?
+ {
+ for (int i = 0; i < std::size(network_ref); i++)
+ {
+ if (network_ref[i].ip != 0 && network_ref[i].local == port)
+ return &network_ref[i];
+ }
+ }
+ for (int i = 0; i < std::size(network_ref); i++)
+ {
+ if (network_ref[i].ip == 0)
+ return &network_ref[i];
+ }
+ return nullptr;
+}
+
+StackRef* CEXIETHERNET::BuiltInBBAInterface::GetTCPSlot(u16 src_port, u16 dst_port, u32 ip)
+{
+ for (int i = 0; i < std::size(network_ref); i++)
+ {
+ if (network_ref[i].ip == ip && network_ref[i].remote == dst_port &&
+ network_ref[i].local == src_port)
+ {
+ return &network_ref[i];
+ }
+ }
+ return nullptr;
+}
+
+int BuildFINFrame(StackRef* ref, u8* buf)
+{
+ std::memset(buf, 0, 0x36);
+ auto [hwpart, ippart, tcppart] = getTcpHeaders(buf, ref->bba_mac, ref->my_mac);
+
+ *ippart = Common::IPv4Header(20, IPPROTO_TCP, ref->from, ref->to);
+
+ *tcppart = Common::TCPHeader(ref->from, ref->to, ref->seq_num, ref->ack_num,
+ TCP_FLAG_FIN | TCP_FLAG_ACK | TCP_FLAG_RST);
+ tcppart->checksum =
+ Common::ComputeTCPNetworkChecksum(ref->from, ref->to, tcppart, 20, IPPROTO_TCP);
+
+ for (auto& tcp_buf : ref->tcp_buffers)
+ tcp_buf.used = false;
+ return 0x36;
+}
+
+int BuildAckFrame(StackRef* ref, u8* buf)
+{
+ std::memset(buf, 0, 0x36);
+ auto [hwpart, ippart, tcppart] = getTcpHeaders(buf, ref->bba_mac, ref->my_mac);
+
+ *ippart = Common::IPv4Header(20, IPPROTO_TCP, ref->from, ref->to);
+
+ *tcppart = Common::TCPHeader(ref->from, ref->to, ref->seq_num, ref->ack_num, TCP_FLAG_ACK);
+ tcppart->checksum =
+ Common::ComputeTCPNetworkChecksum(ref->from, ref->to, tcppart, 20, IPPROTO_TCP);
+
+ return 0x36;
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(Common::EthernetHeader* hwdata,
+ Common::IPv4Header* ipdata,
+ Common::TCPHeader* tcpdata, u8* data)
+{
+ sf::IpAddress target;
+ StackRef* ref =
+ GetTCPSlot(tcpdata->source_port, tcpdata->destination_port, *(u32*)&ipdata->destination_addr);
+ if (tcpdata->properties & (TCP_FLAG_FIN | TCP_FLAG_RST))
+ {
+ if (ref == nullptr)
+ return; // not found
+
+ ref->ack_num++;
+ const int size = BuildFINFrame(ref, m_in_frame.get());
+ WriteToQueue(m_in_frame.get(), size);
+ ref->ip = 0;
+ ref->tcp_socket.disconnect();
+ }
+ else if (tcpdata->properties & TCP_FLAG_SIN)
+ {
+ // new connection
+ if (ref != nullptr)
+ return;
+ ref = GetAvaibleSlot(0);
+
+ ref->delay = GetTickCountStd();
+ ref->local = tcpdata->source_port;
+ ref->remote = tcpdata->destination_port;
+ ref->ack_num = htonl(tcpdata->sequence_number) + 1;
+ ref->ack_base = ref->ack_num;
+ ref->seq_num = 0x1000000;
+ ref->window_size = htons(tcpdata->window_size);
+ ref->type = IPPROTO_TCP;
+ for (auto& tcp_buf : ref->tcp_buffers)
+ tcp_buf.used = false;
+ ref->from.sin_addr.s_addr = *(u32*)&ipdata->destination_addr;
+ ref->from.sin_port = tcpdata->destination_port;
+ ref->to.sin_addr.s_addr = *(u32*)&ipdata->source_addr;
+ ref->to.sin_port = tcpdata->source_port;
+ ref->bba_mac = *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0];
+ ref->my_mac = fake_mac;
+ ref->tcp_socket.setBlocking(false);
+
+ // reply with a sin_ack
+ std::memset(m_in_frame.get(), 0, 0x100);
+ auto [hwpart, ippart, tcppart] = getTcpHeaders(m_in_frame.get(), ref->bba_mac, ref->my_mac);
+
+ *ippart = Common::IPv4Header(28, IPPROTO_TCP, ref->from, ref->to);
+
+ *tcppart = Common::TCPHeader(ref->from, ref->to, ref->seq_num, ref->ack_num,
+ 0x70 | TCP_FLAG_SIN | TCP_FLAG_ACK);
+ const u8 options[] = {0x02, 0x04, 0x05, 0xb4, 0x01, 0x01, 0x01, 0x01};
+ std::memcpy(&m_in_frame[0x36], options, std::size(options));
+
+ // do checksum
+ tcppart->checksum =
+ Common::ComputeTCPNetworkChecksum(ref->from, ref->to, tcppart, 28, IPPROTO_TCP);
+
+ ref->seq_num++;
+ target = sf::IpAddress(htonl(*(u32*)&ipdata->destination_addr));
+ ref->tcp_socket.connect(target, ntohs(tcpdata->destination_port));
+ ref->ready = false;
+ ref->ip = *(u32*)ipdata->destination_addr;
+
+ std::memcpy(&ref->tcp_buffers[0].data, m_in_frame.get(), 0x3e);
+ ref->tcp_buffers[0].data_size = 0x3e;
+ ref->tcp_buffers[0].seq_id = ref->seq_num - 1;
+ ref->tcp_buffers[0].tick = GetTickCountStd() - 900; // delay
+ ref->tcp_buffers[0].used = true;
+ }
+ else
+ {
+ // data packet
+ if (ref == nullptr)
+ return; // not found
+
+ const int c = (tcpdata->properties & 0xf0) >> 2; // header size
+ const int size = ntohs(ipdata->total_len) - 20 - c;
+ const u32 this_seq = ntohl(tcpdata->sequence_number);
+
+ if (size > 0)
+ {
+ // only if data
+ if ((int)(this_seq - ref->ack_num) >= 0)
+ {
+ ref->tcp_socket.send(data, size);
+ ref->ack_num += size;
+ }
+
+ // send ack
+ BuildAckFrame(ref, m_in_frame.get());
+
+ WriteToQueue(m_in_frame.get(), 0x36);
+ }
+ // update windows size
+ ref->window_size = ntohs(tcpdata->window_size);
+
+ // clear any ack data
+ if (tcpdata->properties & TCP_FLAG_ACK)
+ {
+ const u32 ack_num = ntohl(tcpdata->acknowledgement_number);
+ for (auto& tcp_buf : ref->tcp_buffers)
+ {
+ if (!tcp_buf.used || tcp_buf.seq_id >= ack_num)
+ continue;
+ Common::TCPHeader* tcppart = (Common::TCPHeader*)&tcp_buf.data[0x22];
+ const u32 seq_end =
+ tcp_buf.seq_id + tcp_buf.data_size - ((tcppart->properties & 0xf0) >> 2) - 34;
+ if (seq_end <= ack_num)
+ {
+ tcp_buf.used = false; // confirmed data received
+ if (!ref->ready && !ref->tcp_buffers[0].used)
+ ref->ready = true;
+ continue;
+ }
+ // partial data, adjust the packet for next ack
+ const u16 ack_size = ack_num - tcp_buf.seq_id;
+ const u16 new_data_size = tcp_buf.data_size - 0x36 - ack_size;
+ std::memmove(&tcp_buf.data[0x36], &tcp_buf.data[0x36 + ack_size], new_data_size);
+ tcp_buf.data_size -= ack_size;
+ tcp_buf.seq_id += ack_size;
+ tcppart->sequence_number = htonl(tcp_buf.seq_id);
+ Common::IPv4Header* ippart = (Common::IPv4Header*)&tcp_buf.data[14];
+ ippart->total_len = htons(tcp_buf.data_size - 14);
+ tcppart->checksum = 0;
+ tcppart->checksum = Common::ComputeTCPNetworkChecksum(ref->from, ref->to, tcppart,
+ new_data_size + 20, IPPROTO_TCP);
+ }
+ }
+ }
+}
+
+/// <summary>
+/// This is a litle hack, Mario Kart open some UDP port
+/// and listen to it. We open it on our side manualy.
+/// </summary>
+void CEXIETHERNET::BuiltInBBAInterface::InitUDPPort(u16 port)
+{
+ StackRef* ref = GetAvaibleSlot(htons(port));
+ if (ref == nullptr || ref->ip != 0)
+ return;
+ ref->ip = 0x08080808; // change for ip
+ ref->local = htons(port);
+ ref->remote = htons(port);
+ ref->type = 17;
+ ref->bba_mac = *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0];
+ ref->my_mac = fake_mac;
+ ref->from.sin_addr.s_addr = 0;
+ ref->from.sin_port = htons(port);
+ ref->to.sin_addr.s_addr = m_current_ip;
+ ref->to.sin_port = htons(port);
+ ref->udp_socket.setBlocking(false);
+ if (ref->udp_socket.bind(port) != sf::Socket::Done)
+ {
+ ERROR_LOG_FMT(SP1, "Couldn't open UDP socket");
+ return;
+ }
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(Common::EthernetHeader* hwdata,
+ Common::IPv4Header* ipdata,
+ Common::UDPHeader* udpdata, u8* data)
+{
+ sf::IpAddress target;
+
+ if (*(u32*)ipdata->destination_addr == 0)
+ *(u32*)ipdata->destination_addr = m_router_ip;
+ // dns request
+ StackRef* ref = GetAvaibleSlot(udpdata->source_port);
+ if (ref->ip == 0)
+ {
+ ref->ip = *(u32*)ipdata->destination_addr; // change for ip
+ ref->local = udpdata->source_port;
+ ref->remote = udpdata->destination_port;
+ ref->type = 17;
+ ref->bba_mac = *(Common::MACAddress*)&m_eth_ref->mBbaMem[BBA_NAFR_PAR0];
+ ref->my_mac = fake_mac;
+ ref->from.sin_addr.s_addr = *(u32*)&ipdata->destination_addr;
+ ref->from.sin_port = udpdata->destination_port;
+ ref->to.sin_addr.s_addr = *(u32*)&ipdata->source_addr;
+ ref->to.sin_port = udpdata->source_port;
+ ref->udp_socket.setBlocking(false);
+ if (ref->udp_socket.bind(htons(udpdata->source_port)) != sf::Socket::Done &&
+ ref->udp_socket.bind(sf::Socket::AnyPort) != sf::Socket::Done)
+ {
+ ERROR_LOG_FMT(SP1, "Couldn't open UDP socket");
+ return;
+ }
+ }
+ if (ntohs(udpdata->destination_port) == 1900)
+ {
+ InitUDPPort(26512); // MK DD and 1080
+ InitUDPPort(26502); // Air Ride
+ if (*(u32*)ipdata->destination_addr == 0xFAFFFFEF) // force real broadcast
+ *(u32*)ipdata->destination_addr = 0xFFFFFFFF; // Multi cast cannot be read
+ if (udpdata->length > 150)
+ {
+ // Quick hack to unlock the connection, throw it back at him
+ Common::EthernetHeader* hwpart = (Common::EthernetHeader*)m_in_frame.get();
+ Common::IPv4Header* ippart = (Common::IPv4Header*)&m_in_frame[14];
+ std::memcpy(m_in_frame.get(), hwdata, ntohs(ipdata->total_len) + 14);
+ hwpart->destination = hwdata->source;
+ hwpart->source = hwdata->destination;
+ *(u32*)ippart->destination_addr = *(u32*)ipdata->source_addr;
+ *(u32*)ippart->source_addr = *(u32*)ipdata->destination_addr;
+ WriteToQueue(m_in_frame.get(), ntohs(ipdata->total_len) + 14);
+ }
+ }
+ if (ntohs(udpdata->destination_port) == 53)
+ {
+ target = sf::IpAddress(m_dns_ip.c_str()); // dns server ip
+ }
+ else
+ {
+ target = sf::IpAddress(ntohl(*(u32*)ipdata->destination_addr));
+ }
+ ref->udp_socket.send(data, ntohs(udpdata->length) - 8, target, ntohs(udpdata->destination_port));
+}
+
+bool CEXIETHERNET::BuiltInBBAInterface::SendFrame(const u8* frame, u32 size)
+{
+ int offset = 0;
+
+ std::memcpy(m_out_frame.get(), frame, size);
+ std::lock_guard<std::mutex> lock(mtx);
+
+ // handle the packet data
+ Common::EthernetHeader* hwdata = (Common::EthernetHeader*)m_out_frame.get();
+ if (hwdata->ethertype == 0x08) // IPV4
+ {
+ // IP sub
+ Common::IPv4Header* ipdata = (Common::IPv4Header*)&m_out_frame[14];
+ offset = Common::EthernetHeader::SIZE + (ipdata->version_ihl & 0xf) * 4;
+ switch (ipdata->protocol)
+ {
+ case IPPROTO_UDP:
+ {
+ Common::UDPHeader* udpdata = (Common::UDPHeader*)&m_out_frame[offset];
+ offset += Common::UDPHeader::SIZE;
+ if (ntohs(udpdata->destination_port) == 67)
+ {
+ Common::DHCPBody* request = (Common::DHCPBody*)&m_out_frame[offset];
+ HandleDHCP(hwdata, udpdata, request);
+ }
+ else
+ {
+ HandleUDPFrame(hwdata, ipdata, udpdata, &m_out_frame[offset]);
+ }
+ break;
+ }
+ case IPPROTO_TCP:
+ {
+ Common::TCPHeader* tcpdata = (Common::TCPHeader*)&m_out_frame[offset];
+ offset += (tcpdata->properties & 0xf0) >> 2;
+ HandleTCPFrame(hwdata, ipdata, tcpdata, &m_out_frame[offset]);
+ break;
+ }
+ }
+ }
+ if (hwdata->ethertype == 0x608) // arp
+ {
+ Common::ARPHeader* arpdata = (Common::ARPHeader*)&m_out_frame[14];
+ HandleARP(hwdata, arpdata);
+ }
+
+ m_eth_ref->SendComplete();
+ return true;
+}
+
+size_t TryGetDataFromSocket(StackRef* ref, u8* buffer)
+{
+ size_t datasize = 0; // this will be filled by the socket read later
+ unsigned short remote_port;
+
+ switch (ref->type)
+ {
+ case IPPROTO_UDP:
+ ref->udp_socket.receive(&buffer[0x2a], 1500, datasize, ref->target, remote_port);
+ if (datasize > 0)
+ {
+ std::memset(buffer, 0, 0x2a);
+ auto [hwpart, ipdata, udpdata] = getUdpHeaders(buffer, ref->bba_mac, ref->my_mac);
+
+ ref->from.sin_port = htons(remote_port);
+ ref->from.sin_addr.s_addr = htonl(ref->target.toInteger());
+ *ipdata = Common::IPv4Header((u16)(datasize + 8), IPPROTO_UDP, ref->from, ref->to);
+
+ *udpdata = Common::UDPHeader(ref->from, ref->to, (u16)datasize);
+ udpdata->checksum = Common::ComputeTCPNetworkChecksum(ref->from, ref->to, udpdata,
+ (u16)(datasize + 8), IPPROTO_UDP);
+ datasize += 0x2a;
+ }
+ break;
+
+ case IPPROTO_TCP:
+ sf::Socket::Status st = sf::Socket::Status::Done;
+ TcpBuffer* tcp_buffer = nullptr;
+ for (auto& tcp_buf : ref->tcp_buffers)
+ {
+ if (tcp_buf.used)
+ continue;
+ tcp_buffer = &tcp_buf;
+ break;
+ }
+
+ // set default size to 0 to avoid issue
+ datasize = 0;
+ const bool can_go = (GetTickCountStd() - ref->poke_time > 100 || ref->window_size > 2000);
+ if (tcp_buffer != nullptr && ref->ready && can_go)
+ st = ref->tcp_socket.receive(&buffer[0x36], 440, datasize);
+
+ if (datasize > 0)
+ {
+ std::memset(buffer, 0, 0x36);
+ auto [hwpart, ipdata, tcpdata] = getTcpHeaders(buffer, ref->bba_mac, ref->my_mac);
+
+ *ipdata = Common::IPv4Header((u16)(datasize + 20), IPPROTO_TCP, ref->from, ref->to);
+
+ *tcpdata = Common::TCPHeader(ref->from, ref->to, ref->seq_num, ref->ack_num, TCP_FLAG_ACK);
+ tcpdata->checksum = Common::ComputeTCPNetworkChecksum(ref->from, ref->to, tcpdata,
+ (u16)(datasize + 20), IPPROTO_TCP);
+
+ // build buffer
+ tcp_buffer->seq_id = ref->seq_num;
+ tcp_buffer->data_size = (u16)datasize + 0x36;
+ tcp_buffer->tick = GetTickCountStd();
+ std::memcpy(&tcp_buffer->data[0], buffer, datasize + 0x36);
+ tcp_buffer->seq_id = ref->seq_num;
+ tcp_buffer->used = true;
+ ref->seq_num += (u32)datasize;
+ ref->poke_time = GetTickCountStd();
+ datasize += 0x36;
+ }
+ if (GetTickCountStd() - ref->delay > 3000)
+ {
+ if (st == sf::Socket::Disconnected || st == sf::Socket::Error)
+ {
+ ref->ip = 0;
+ ref->tcp_socket.disconnect();
+ datasize = BuildFINFrame(ref, buffer);
+ }
+ }
+ break;
+ }
+
+ return datasize;
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(CEXIETHERNET::BuiltInBBAInterface* self)
+{
+ while (!self->m_read_thread_shutdown.IsSet())
+ {
+ // make thread less cpu hungry
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+
+ if (!self->m_read_enabled.IsSet())
+ continue;
+ size_t datasize = 0;
+
+ u8 wp = self->m_eth_ref->page_ptr(BBA_RWP);
+ const u8 rp = self->m_eth_ref->page_ptr(BBA_RRP);
+ if (rp > wp)
+ wp += 16;
+
+ if ((wp - rp) >= 8)
+ continue;
+
+ std::lock_guard<std::mutex> lock(self->mtx);
+ // process queue file first
+ if (self->queue_read != self->queue_write)
+ {
+ datasize = self->queue_data[self->queue_read].size();
+ std::memcpy(self->m_eth_ref->mRecvBuffer.get(), &self->queue_data[self->queue_read][0],
+ datasize);
+ self->queue_read++;
+ self->queue_read &= 15;
+ }
+ else
+ {
+ // test connections data
+ for (auto& net_ref : self->network_ref)
+ {
+ if (net_ref.ip == 0)
+ continue;
+ datasize = TryGetDataFromSocket(&net_ref, self->m_eth_ref->mRecvBuffer.get());
+ if (datasize > 0)
+ break;
+ }
+ }
+
+ // test and add any sleeping tcp data
+ for (auto& net_ref : self->network_ref)
+ {
+ if (net_ref.ip == 0 || net_ref.type != IPPROTO_TCP)
+ continue;
+ for (auto& tcp_buf : net_ref.tcp_buffers)
+ {
+ if (!tcp_buf.used || (GetTickCountStd() - tcp_buf.tick) <= 1000)
+ continue;
+
+ tcp_buf.tick = GetTickCountStd();
+ // late data, resend
+ if (((self->queue_write + 1) & 15) != self->queue_read)
+ {
+ self->WriteToQueue(&tcp_buf.data[0], tcp_buf.data_size);
+ }
+ }
+ }
+
+ if (datasize > 0)
+ {
+ u8* b = &self->m_eth_ref->mRecvBuffer[0];
+ Common::EthernetHeader* hwdata = (Common::EthernetHeader*)b;
+ if (hwdata->ethertype == 0x8) // IP_PROTOCOL
+ {
+ Common::IPv4Header* ipdata = (Common::IPv4Header*)&b[14];
+ ipdata->identification = ntohs(++self->ip_frame_id);
+ ipdata->header_checksum = 0;
+ ipdata->header_checksum = htons(Common::ComputeNetworkChecksum(ipdata, 20));
+ }
+ self->m_eth_ref->mRecvBufferLength = datasize > 64 ? (u32)datasize : 64;
+ self->m_eth_ref->RecvHandlePacket();
+ }
+ }
+}
+
+bool CEXIETHERNET::BuiltInBBAInterface::RecvInit()
+{
+ m_read_thread = std::thread(ReadThreadHandler, this);
+ return true;
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::RecvStart()
+{
+ m_read_enabled.Set();
+}
+
+void CEXIETHERNET::BuiltInBBAInterface::RecvStop()
+{
+ m_read_enabled.Clear();
+ for (auto& net_ref : network_ref)
+ {
+ if (net_ref.ip != 0)
+ {
+ net_ref.type == IPPROTO_TCP ? net_ref.tcp_socket.disconnect() : net_ref.udp_socket.unbind();
+ }
+ net_ref.ip = 0;
+ }
+ queue_read = 0;
+ queue_write = 0;
+}
+} // namespace ExpansionInterface
diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.h b/Source/Core/Core/HW/EXI/BBA/BuiltIn.h
new file mode 100644
index 0000000000..7b3b5415c5
--- /dev/null
+++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.h
@@ -0,0 +1,56 @@
+// 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 = 0x200;
+constexpr u16 TCP_FLAG_ACK = 0x1000;
+constexpr u16 TCP_FLAG_PSH = 0x800;
+constexpr u16 TCP_FLAG_FIN = 0x100;
+constexpr u16 TCP_FLAG_RST = 0x400;
+
+constexpr u16 IP_PROTOCOL = 0x800;
+constexpr u16 ARP_PROTOCOL = 0x806;
+
+constexpr u8 MAX_TCP_BUFFER = 4;
+
+struct TcpBuffer
+{
+ bool used;
+ u64 tick;
+ u32 seq_id;
+ u16 data_size;
+ std::array<u8, 2048> 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;
+};
diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp
index e43d47a8b3..33b5e70dd0 100644
--- a/Source/Core/Core/HW/EXI/EXI_Device.cpp
+++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp
@@ -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;
diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h
index 232f260166..ce110a731e 100644
--- a/Source/Core/Core/HW/EXI/EXI_Device.h
+++ b/Source/Core/Core/HW/EXI/EXI_Device.h
@@ -37,6 +37,7 @@ enum class EXIDeviceType : int
EthernetXLink,
// Only used on Apple devices.
EthernetTapServer,
+ EthernetBuiltIn,
None = 0xFF
};
@@ -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"),
@@ -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) {}
diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
index 2151b8f013..311d7ccda2 100644
--- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
+++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp
@@ -18,6 +18,8 @@
#include "Core/HW/EXI/EXI.h"
#include "Core/HW/Memmap.h"
+//#define BBA_TRACK_PAGE_PTRS
+
namespace ExpansionInterface
{
// XXX: The BBA stores multi-byte elements as little endian.
@@ -53,6 +55,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
@@ -155,6 +162,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;
@@ -228,9 +246,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;
}
@@ -348,7 +364,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");
@@ -415,7 +430,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);
@@ -510,76 +524,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),
@@ -602,7 +620,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;
diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
index 6d8be1f65b..ea29383cbf 100644
--- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
+++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h
@@ -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;
@@ -204,6 +207,7 @@ enum class BBADeviceType
#if defined(__APPLE__)
TAPSERVER,
#endif
+ BuiltIn,
};
class CEXIETHERNET : public IEXIDevice
@@ -289,7 +293,6 @@ private:
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;
@@ -299,9 +302,11 @@ private:
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;
@@ -413,6 +418,60 @@ private:
#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))
+ {
+ }
+
+ 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:
+ std::string m_mac_id;
+ std::string m_dns_ip;
+ bool active = false;
+ u16 ip_frame_id = 0;
+ u8 queue_read = 0;
+ u8 queue_write = 0;
+ std::array<std::vector<u8>, 16> queue_data;
+ std::mutex 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::unique_ptr<u8[]> m_in_frame;
+ std::unique_ptr<u8[]> m_out_frame;
+ std::thread m_read_thread;
+ Common::Flag m_read_enabled;
+ Common::Flag m_read_thread_shutdown;
+ static void ReadThreadHandler(BuiltInBBAInterface* self);
+ Common::MACAddress fake_mac{};
+#endif
+ void WriteToQueue(const u8* data, int length);
+ void HandleARP(Common::EthernetHeader* hwdata, Common::ARPHeader* arpdata);
+ void HandleDHCP(Common::EthernetHeader* hwdata, Common::UDPHeader* udpdata,
+ Common::DHCPBody* request);
+ StackRef* GetAvaibleSlot(u16 port);
+ StackRef* GetTCPSlot(u16 src_port, u16 dst_port, u32 ip);
+ void HandleTCPFrame(Common::EthernetHeader* hwdata, Common::IPv4Header* ipdata,
+ Common::TCPHeader* tcpdata, u8* data);
+ void InitUDPPort(u16 port);
+ void HandleUDPFrame(Common::EthernetHeader* hwdata, Common::IPv4Header* ipdata,
+ Common::UDPHeader* udpdata, u8* data);
+ };
+
std::unique_ptr<NetworkInterface> m_network_interface;
std::unique_ptr<u8[]> mRecvBuffer;
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index ca81c1ff48..1695b50bcd 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -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" />
@@ -868,6 +869,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" />
diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp
index 0c3af49df1..8b6d96df02 100644
--- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp
+++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp
@@ -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 = QString::fromStdString("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");
@@ -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;
diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h
index 0d532e2da0..ca8d813cbe 100644
--- a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h
+++ b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.h
@@ -15,6 +15,7 @@ public:
{
Ethernet,
XLinkKai,
+ BuiltIn
};
explicit BroadbandAdapterSettingsDialog(QWidget* target, Type bba_type);
diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
index 94591c7721..cd5cd258df 100644
--- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp
+++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
@@ -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()),
@@ -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;
}
@@ -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;