diff --git a/Source/Core/Common/Network.cpp b/Source/Core/Common/Network.cpp index faa2a33c05..d03b537e25 100644 --- a/Source/Core/Common/Network.cpp +++ b/Source/Core/Common/Network.cpp @@ -5,6 +5,7 @@ #include #include +#include #ifndef _WIN32 #include @@ -16,6 +17,7 @@ #include +#include "Common/BitUtils.h" #include "Common/Random.h" #include "Common/StringUtil.h" @@ -113,6 +115,11 @@ u16 IPv4Header::Size() const return static_cast(SIZE); } +u8 IPv4Header::DefinedSize() const +{ + return (version_ihl & 0xf) * 4; +} + TCPHeader::TCPHeader() = default; TCPHeader::TCPHeader(const sockaddr_in& from, const sockaddr_in& to, u32 seq, const u8* data, @@ -141,6 +148,23 @@ TCPHeader::TCPHeader(const sockaddr_in& from, const sockaddr_in& to, u32 seq, co checksum = htons(static_cast(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 = htons(flags); + + window_size = 0x7c; + checksum = 0; +} + +u8 TCPHeader::GetHeaderSize() const +{ + return (ntohs(properties) & 0xf000) >> 10; +} + u16 TCPHeader::Size() const { return static_cast(SIZE); @@ -170,6 +194,92 @@ u8 UDPHeader::IPProto() const return static_cast(IPPROTO_UDP); } +ARPHeader::ARPHeader() = default; + +ARPHeader::ARPHeader(u32 from_ip, const MACAddress& from_mac, u32 to_ip, const 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(SIZE); +} + +DHCPBody::DHCPBody() = default; + +DHCPBody::DHCPBody(u32 transaction, const 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; +} + +DHCPPacket::DHCPPacket() = default; + +DHCPPacket::DHCPPacket(const std::vector& data) +{ + if (data.size() < DHCPBody::SIZE) + return; + body = Common::BitCastPtr(data.data()); + std::size_t offset = DHCPBody::SIZE; + + while (offset < data.size() - 1) + { + const u8 fnc = data[offset]; + if (fnc == 0) + { + ++offset; + continue; + } + if (fnc == 255) + break; + const u8 len = data[offset + 1]; + const auto opt_begin = data.begin() + offset; + offset += 2 + len; + if (offset > data.size()) + break; + const auto opt_end = data.begin() + offset; + options.emplace_back(opt_begin, opt_end); + } +} + +void DHCPPacket::AddOption(u8 fnc, const std::vector& params) +{ + if (params.size() > 255) + return; + std::vector opt = {fnc, u8(params.size())}; + opt.insert(opt.end(), params.begin(), params.end()); + options.emplace_back(std::move(opt)); +} + +std::vector DHCPPacket::Build() const +{ + const u8* body_ptr = reinterpret_cast(&body); + std::vector result(body_ptr, body_ptr + DHCPBody::SIZE); + + for (auto& opt : options) + { + result.insert(result.end(), opt.begin(), opt.end()); + } + const std::vector no_option = {255, 0, 0, 0}; + result.insert(result.end(), no_option.begin(), no_option.end()); + + return result; +} + // 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 +297,257 @@ u16 ComputeNetworkChecksum(const void* data, u16 length, u32 initial_value) return ~static_cast(checksum); } +// Compute the TCP checksum with its pseudo header +u16 ComputeTCPNetworkChecksum(const IPAddress& from, const IPAddress& to, const void* data, + u16 length, u8 protocol) +{ + const u32 source_addr = ntohl(Common::BitCast(from)); + const u32 destination_addr = ntohl(Common::BitCast(to)); + 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(tcp_checksum)); +} + +ARPPacket::ARPPacket() = default; + +u16 ARPPacket::Size() const +{ + return static_cast(SIZE); +} + +ARPPacket::ARPPacket(const MACAddress& destination, const MACAddress& source) +{ + eth_header.destination = destination; + eth_header.source = source; + eth_header.ethertype = htons(ARP_ETHERTYPE); +} + +std::vector ARPPacket::Build() const +{ + std::vector result; + result.reserve(EthernetHeader::SIZE + ARPHeader::SIZE); + const u8* eth_ptr = reinterpret_cast(ð_header); + result.insert(result.end(), eth_ptr, eth_ptr + EthernetHeader::SIZE); + const u8* arp_ptr = reinterpret_cast(&arp_header); + result.insert(result.end(), arp_ptr, arp_ptr + ARPHeader::SIZE); + return result; +} + +TCPPacket::TCPPacket() = default; + +TCPPacket::TCPPacket(const MACAddress& destination, const MACAddress& source) +{ + eth_header.destination = destination; + eth_header.source = source; + eth_header.ethertype = htons(IPV4_ETHERTYPE); +} + +TCPPacket::TCPPacket(const MACAddress& destination, const MACAddress& source, + const sockaddr_in& from, const sockaddr_in& to, u32 seq, u32 ack, u16 flags) +{ + eth_header.destination = destination; + eth_header.source = source; + eth_header.ethertype = htons(IPV4_ETHERTYPE); + + ip_header = Common::IPv4Header(Common::TCPHeader::SIZE, IPPROTO_TCP, from, to); + tcp_header = Common::TCPHeader(from, to, seq, ack, flags); +} + +std::vector TCPPacket::Build() +{ + std::vector result; + result.reserve(Size()); + + // recalc size + ip_header.total_len = htons(static_cast(IPv4Header::SIZE + ipv4_options.size() + + TCPHeader::SIZE + tcp_options.size() + data.size())); + + // copy data + const u8* eth_ptr = reinterpret_cast(ð_header); + result.insert(result.end(), eth_ptr, eth_ptr + EthernetHeader::SIZE); + const u8* ip_ptr = reinterpret_cast(&ip_header); + result.insert(result.end(), ip_ptr, ip_ptr + IPv4Header::SIZE); + std::size_t offset = EthernetHeader::SIZE + IPv4Header::SIZE; + if (ipv4_options.size() > 0) + { + result.insert(result.end(), ipv4_options.begin(), ipv4_options.end()); + offset += ipv4_options.size(); + } + tcp_header.checksum = 0; + const u16 props = (ntohs(tcp_header.properties) & 0xfff) | + (static_cast((tcp_options.size() + TCPHeader::SIZE) & 0x3c) << 10); + tcp_header.properties = htons(props); + const u8* tcp_ptr = reinterpret_cast(&tcp_header); + result.insert(result.end(), tcp_ptr, tcp_ptr + TCPHeader::SIZE); + const std::size_t tcp_offset = offset; + offset += TCPHeader::SIZE; + if (tcp_options.size() > 0) + { + result.insert(result.end(), tcp_options.begin(), tcp_options.end()); + offset += tcp_options.size(); + } + if (data.size() > 0) + { + result.insert(result.end(), data.begin(), data.end()); + } + tcp_header.checksum = ComputeTCPNetworkChecksum( + ip_header.source_addr, ip_header.destination_addr, &result[tcp_offset], + static_cast(result.size() - tcp_offset), IPPROTO_TCP); + std::copy(tcp_ptr, tcp_ptr + TCPHeader::SIZE, result.begin() + tcp_offset); + return result; +} + +u16 TCPPacket::Size() const +{ + return static_cast(MIN_SIZE + data.size() + ipv4_options.size() + tcp_options.size()); +} + +UDPPacket::UDPPacket() = default; + +UDPPacket::UDPPacket(const MACAddress& destination, const MACAddress& source) +{ + eth_header.destination = destination; + eth_header.source = source; + eth_header.ethertype = htons(IPV4_ETHERTYPE); +} + +UDPPacket::UDPPacket(const MACAddress& destination, const MACAddress& source, + const sockaddr_in& from, const sockaddr_in& to, const std::vector& payload) +{ + eth_header.destination = destination; + eth_header.source = source; + eth_header.ethertype = htons(IPV4_ETHERTYPE); + + ip_header = Common::IPv4Header(static_cast(payload.size() + Common::UDPHeader::SIZE), + IPPROTO_UDP, from, to); + udp_header = Common::UDPHeader(from, to, static_cast(payload.size())); + data = payload; +} + +std::vector UDPPacket::Build() +{ + std::vector result; + result.reserve(Size()); + + // recalc size + ip_header.total_len = htons( + static_cast(IPv4Header::SIZE + ipv4_options.size() + UDPHeader::SIZE + data.size())); + udp_header.length = htons(static_cast(UDPHeader::SIZE + data.size())); + + // copy data + const u8* eth_ptr = reinterpret_cast(ð_header); + result.insert(result.end(), eth_ptr, eth_ptr + EthernetHeader::SIZE); + const u8* ip_ptr = reinterpret_cast(&ip_header); + result.insert(result.end(), ip_ptr, ip_ptr + IPv4Header::SIZE); + std::size_t offset = EthernetHeader::SIZE + IPv4Header::SIZE; + if (ipv4_options.size() > 0) + { + result.insert(result.end(), ipv4_options.begin(), ipv4_options.end()); + offset += ipv4_options.size(); + } + udp_header.checksum = 0; + const u8* udp_ptr = reinterpret_cast(&udp_header); + result.insert(result.end(), udp_ptr, udp_ptr + UDPHeader::SIZE); + const std::size_t udp_offset = offset; + offset += UDPHeader::SIZE; + if (data.size() > 0) + { + result.insert(result.end(), data.begin(), data.end()); + } + udp_header.checksum = ComputeTCPNetworkChecksum( + ip_header.source_addr, ip_header.destination_addr, &result[udp_offset], + static_cast(result.size() - udp_offset), IPPROTO_UDP); + std::copy(udp_ptr, udp_ptr + UDPHeader::SIZE, result.begin() + udp_offset); + + return result; +} + +u16 UDPPacket::Size() const +{ + return static_cast(MIN_SIZE + data.size() + ipv4_options.size()); +} + +PacketView::PacketView(const u8* ptr, std::size_t size) : m_ptr(ptr), m_size(size) +{ +} + +std::optional PacketView::GetEtherType() const +{ + if (m_size < EthernetHeader::SIZE) + return std::nullopt; + const std::size_t offset = offsetof(EthernetHeader, ethertype); + return ntohs(Common::BitCastPtr(m_ptr + offset)); +} + +std::optional PacketView::GetARPPacket() const +{ + if (m_size < ARPPacket::SIZE) + return std::nullopt; + return Common::BitCastPtr(m_ptr); +} + +std::optional PacketView::GetIPProto() const +{ + if (m_size < EthernetHeader::SIZE + IPv4Header::SIZE) + return std::nullopt; + return m_ptr[EthernetHeader::SIZE + offsetof(IPv4Header, protocol)]; +} + +std::optional PacketView::GetTCPPacket() const +{ + if (m_size < TCPPacket::MIN_SIZE) + return std::nullopt; + TCPPacket result; + result.eth_header = Common::BitCastPtr(m_ptr); + result.ip_header = Common::BitCastPtr(m_ptr + EthernetHeader::SIZE); + const u16 offset = result.ip_header.DefinedSize() + EthernetHeader::SIZE; + if (m_size < offset + TCPHeader::SIZE) + return std::nullopt; + result.ipv4_options = + std::vector(m_ptr + EthernetHeader::SIZE + IPv4Header::SIZE, m_ptr + offset); + result.tcp_header = Common::BitCastPtr(m_ptr + offset); + const u16 data_offset = result.tcp_header.GetHeaderSize() + offset; + + const u16 total_len = ntohs(result.ip_header.total_len); + const std::size_t end = EthernetHeader::SIZE + total_len; + + if (m_size < end || end < data_offset) + return std::nullopt; + + result.tcp_options = std::vector(m_ptr + offset + TCPHeader::SIZE, m_ptr + data_offset); + result.data = std::vector(m_ptr + data_offset, m_ptr + end); + + return result; +} + +std::optional PacketView::GetUDPPacket() const +{ + if (m_size < UDPPacket::MIN_SIZE) + return std::nullopt; + UDPPacket result; + result.eth_header = Common::BitCastPtr(m_ptr); + result.ip_header = Common::BitCastPtr(m_ptr + EthernetHeader::SIZE); + const u16 offset = result.ip_header.DefinedSize() + EthernetHeader::SIZE; + if (m_size < offset + UDPHeader::SIZE) + return std::nullopt; + result.ipv4_options = + std::vector(m_ptr + EthernetHeader::SIZE + IPv4Header::SIZE, m_ptr + offset); + result.udp_header = Common::BitCastPtr(m_ptr + offset); + const u16 data_offset = UDPHeader::SIZE + offset; + + const u16 total_len = ntohs(result.udp_header.length); + const std::size_t end = offset + total_len; + + if (m_size < end || end < data_offset) + return std::nullopt; + + result.data = std::vector(m_ptr + data_offset, m_ptr + end); + + return result; +} + NetworkErrorState SaveNetworkErrorState() { return { diff --git a/Source/Core/Common/Network.h b/Source/Core/Common/Network.h index 0b56e09d34..f9706c0328 100644 --- a/Source/Core/Common/Network.h +++ b/Source/Core/Common/Network.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" @@ -22,11 +23,25 @@ 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; constexpr std::size_t IPV4_ADDR_LEN = 4; +using IPAddress = std::array; +constexpr IPAddress IP_ADDR_ANY = {0, 0, 0, 0}; +constexpr IPAddress IP_ADDR_BROADCAST = {255, 255, 255, 255}; +constexpr IPAddress IP_ADDR_SSDP = {239, 255, 255, 250}; +constexpr u16 IPV4_ETHERTYPE = 0x800; +constexpr u16 ARP_ETHERTYPE = 0x806; struct EthernetHeader { @@ -47,6 +62,7 @@ struct IPv4Header IPv4Header(); IPv4Header(u16 data_size, u8 ip_proto, const sockaddr_in& from, const sockaddr_in& to); u16 Size() const; + u8 DefinedSize() const; static constexpr std::size_t SIZE = 20; @@ -58,8 +74,8 @@ struct IPv4Header u8 ttl = 0; u8 protocol = 0; u16 header_checksum = 0; - std::array source_addr{}; - std::array destination_addr{}; + IPAddress source_addr{}; + IPAddress destination_addr{}; }; static_assert(sizeof(IPv4Header) == IPv4Header::SIZE); @@ -67,6 +83,8 @@ 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); + u8 GetHeaderSize() const; u16 Size() const; u8 IPProto() const; @@ -99,6 +117,134 @@ struct UDPHeader }; static_assert(sizeof(UDPHeader) == UDPHeader::SIZE); +#pragma pack(push, 1) +struct ARPHeader +{ + ARPHeader(); + ARPHeader(u32 from_ip, const MACAddress& from_mac, u32 to_ip, const 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, const MACAddress& client_address, u32 new_ip, u32 serv_ip); + static constexpr std::size_t SIZE = 240; + u8 message_type = 0; + u8 hardware_type = 0; + u8 hardware_addr = 0; + u8 hops = 0; + u32 transaction_id = 0; + u16 seconds = 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]{}; + u8 magic_cookie[4] = {0x63, 0x82, 0x53, 0x63}; +}; +static_assert(sizeof(DHCPBody) == DHCPBody::SIZE); + +struct DHCPPacket +{ + DHCPPacket(); + DHCPPacket(const std::vector& data); + void AddOption(u8 fnc, const std::vector& params); + std::vector Build() const; + + DHCPBody body; + std::vector> options; +}; + +// The compiler might add 2 bytes after EthernetHeader to enforce 16-bytes alignment +#pragma pack(push, 1) +struct ARPPacket +{ + ARPPacket(); + ARPPacket(const MACAddress& destination, const MACAddress& source); + std::vector Build() const; + u16 Size() const; + + EthernetHeader eth_header; + ARPHeader arp_header; + + static constexpr std::size_t SIZE = EthernetHeader::SIZE + ARPHeader::SIZE; +}; +static_assert(sizeof(ARPPacket) == ARPPacket::SIZE); +#pragma pack(pop) + +struct TCPPacket +{ + TCPPacket(); + TCPPacket(const MACAddress& destination, const MACAddress& source); + TCPPacket(const MACAddress& destination, const MACAddress& source, const sockaddr_in& from, + const sockaddr_in& to, u32 seq, u32 ack, u16 flags); + std::vector Build(); + u16 Size() const; + + EthernetHeader eth_header; + IPv4Header ip_header; + TCPHeader tcp_header; + std::vector ipv4_options; + std::vector tcp_options; + std::vector data; + + static constexpr std::size_t MIN_SIZE = EthernetHeader::SIZE + IPv4Header::SIZE + TCPHeader::SIZE; +}; + +struct UDPPacket +{ + UDPPacket(); + UDPPacket(const MACAddress& destination, const MACAddress& source); + UDPPacket(const MACAddress& destination, const MACAddress& source, const sockaddr_in& from, + const sockaddr_in& to, const std::vector& payload); + std::vector Build(); + u16 Size() const; + + EthernetHeader eth_header; + IPv4Header ip_header; + UDPHeader udp_header; + + std::vector ipv4_options; + std::vector data; + + static constexpr std::size_t MIN_SIZE = EthernetHeader::SIZE + IPv4Header::SIZE + UDPHeader::SIZE; +}; + +class PacketView +{ +public: + PacketView(const u8* ptr, std::size_t size); + + std::optional GetEtherType() const; + std::optional GetARPPacket() const; + std::optional GetIPProto() const; + std::optional GetTCPPacket() const; + std::optional GetUDPPacket() const; + +private: + const u8* m_ptr; + std::size_t m_size; +}; + struct NetworkErrorState { int error; @@ -111,6 +257,8 @@ MACAddress GenerateMacAddress(MACConsumer type); std::string MacAddressToString(const MACAddress& mac); std::optional StringToMacAddress(std::string_view mac_string); u16 ComputeNetworkChecksum(const void* data, u16 length, u32 initial_value = 0); +u16 ComputeTCPNetworkChecksum(const IPAddress& from, const IPAddress& 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..99a143f2bd 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 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 MAIN_BBA_MAC{{System::Main, "Core", "BBA_MAC"}, ""}; const Info MAIN_BBA_XLINK_IP{{System::Main, "Core", "BBA_XLINK_IP"}, "127.0.0.1"}; const Info MAIN_BBA_XLINK_CHAT_OSD{{System::Main, "Core", "BBA_XLINK_CHAT_OSD"}, true}; +// Schthack PSO Server - https://schtserv.com/ +const Info MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN_DNS"}, + "149.56.167.128"}; +const Info MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""}; + const Info& GetInfoForSIDevice(int channel) { static const std::array, 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& GetInfoForEXIDevice(ExpansionInte extern const Info MAIN_BBA_MAC; extern const Info MAIN_BBA_XLINK_IP; extern const Info MAIN_BBA_XLINK_CHAT_OSD; +extern const Info MAIN_BBA_BUILTIN_DNS; +extern const Info MAIN_BBA_BUILTIN_IP; const Info& GetInfoForSIDevice(int channel); const Info& GetInfoForAdapterRumble(int channel); const Info& 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..8dfbf59764 --- /dev/null +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -0,0 +1,687 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "Common/BitUtils.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Core/HW/EXI/BBA/BuiltIn.h" +#include "Core/HW/EXI/EXI_Device.h" +#include "Core/HW/EXI/EXI_DeviceEthernet.h" + +namespace ExpansionInterface +{ +u64 GetTickCountStd() +{ + using namespace std::chrono; + return duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +bool CEXIETHERNET::BuiltInBBAInterface::Activate() +{ + if (IsActivated()) + return true; + + m_active = true; + for (auto& buf : m_queue_data) + buf.reserve(2048); + m_fake_mac = Common::GenerateMacAddress(Common::MACConsumer::BBA); + + // Workaround to get the host IP (might not be accurate) + 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 (auto& ref : network_ref) + { + ref.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(); + m_active = false; + + // kill all active socket + for (auto& ref : network_ref) + { + if (ref.ip != 0) + { + ref.type == IPPROTO_TCP ? ref.tcp_socket.disconnect() : ref.udp_socket.unbind(); + } + ref.ip = 0; + } + + // Wait for read thread to exit. + if (m_read_thread.joinable()) + m_read_thread.join(); +} + +bool CEXIETHERNET::BuiltInBBAInterface::IsActivated() +{ + return m_active; +} + +void CEXIETHERNET::BuiltInBBAInterface::WriteToQueue(const std::vector& data) +{ + m_queue_data[m_queue_write] = data; + const u8 next_write_index = (m_queue_write + 1) & 15; + if (next_write_index != m_queue_read) + m_queue_write = next_write_index; +} + +void CEXIETHERNET::BuiltInBBAInterface::HandleARP(const Common::ARPPacket& packet) +{ + const auto& [hwdata, arpdata] = packet; + const Common::MACAddress bba_mac = + Common::BitCastPtr(&m_eth_ref->mBbaMem[BBA_NAFR_PAR0]); + Common::ARPPacket response(bba_mac, m_fake_mac); + + if (arpdata.target_ip == m_current_ip) + { + // game asked for himself, reply with his mac address + response.arp_header = Common::ARPHeader(arpdata.target_ip, bba_mac, m_current_ip, bba_mac); + } + else + { + response.arp_header = Common::ARPHeader(arpdata.target_ip, m_fake_mac, m_current_ip, bba_mac); + } + + WriteToQueue(response.Build()); +} + +void CEXIETHERNET::BuiltInBBAInterface::HandleDHCP(const Common::UDPPacket& packet) +{ + const auto& [hwdata, ip, udp_header, ip_options, data] = packet; + const Common::DHCPPacket dhcp(packet.data); + const Common::DHCPBody& request = dhcp.body; + sockaddr_in from; + sockaddr_in to; + 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 = udp_header.source_port; + + const u8* router_ip_ptr = reinterpret_cast(&m_router_ip); + const std::vector ip_part(router_ip_ptr, router_ip_ptr + sizeof(m_router_ip)); + + const std::vector timeout_24h = {0, 1, 0x51, 0x80}; + + const Common::MACAddress bba_mac = + Common::BitCastPtr(&m_eth_ref->mBbaMem[BBA_NAFR_PAR0]); + Common::DHCPPacket reply; + reply.body = Common::DHCPBody(request.transaction_id, bba_mac, m_current_ip, m_router_ip); + + // options + // send our emulated lan settings + + (dhcp.options.size() == 0 || dhcp.options[0].size() < 2 || dhcp.options[0].at(2) == 1) ? + reply.AddOption(53, {2}) : // default, send a suggestion + reply.AddOption(53, {5}); + reply.AddOption(54, ip_part); // dhcp server ip + reply.AddOption(51, timeout_24h); // lease time 24h + reply.AddOption(58, timeout_24h); // renewal time + reply.AddOption(59, timeout_24h); // rebind time + reply.AddOption(1, {255, 255, 255, 0}); // submask + reply.AddOption(28, {ip_part[0], ip_part[1], ip_part[2], 255}); // broadcast ip + reply.AddOption(6, ip_part); // dns server + reply.AddOption(15, {0x6c, 0x61, 0x6e}); // domain name "lan" + reply.AddOption(3, ip_part); // router ip + reply.AddOption(255, {}); // end + + Common::UDPPacket response(bba_mac, m_fake_mac, from, to, reply.Build()); + + WriteToQueue(response.Build()); +} + +StackRef* CEXIETHERNET::BuiltInBBAInterface::GetAvailableSlot(u16 port) +{ + if (port > 0) // existing connection? + { + for (auto& ref : network_ref) + { + if (ref.ip != 0 && ref.local == port) + return &ref; + } + } + for (auto& ref : network_ref) + { + if (ref.ip == 0) + return &ref; + } + return nullptr; +} + +StackRef* CEXIETHERNET::BuiltInBBAInterface::GetTCPSlot(u16 src_port, u16 dst_port, u32 ip) +{ + for (auto& ref : network_ref) + { + if (ref.ip == ip && ref.remote == dst_port && ref.local == src_port) + { + return &ref; + } + } + return nullptr; +} + +std::vector BuildFINFrame(StackRef* ref) +{ + Common::TCPPacket result(ref->bba_mac, ref->my_mac, ref->from, ref->to, ref->seq_num, + ref->ack_num, TCP_FLAG_FIN | TCP_FLAG_ACK | TCP_FLAG_RST); + + for (auto& tcp_buf : ref->tcp_buffers) + tcp_buf.used = false; + return result.Build(); +} + +std::vector BuildAckFrame(StackRef* ref) +{ + Common::TCPPacket result(ref->bba_mac, ref->my_mac, ref->from, ref->to, ref->seq_num, + ref->ack_num, TCP_FLAG_ACK); + return result.Build(); +} + +void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& packet) +{ + const auto& [hwdata, ip_header, tcp_header, ip_options, tcp_options, data] = packet; + sf::IpAddress target; + StackRef* ref = GetTCPSlot(tcp_header.source_port, tcp_header.destination_port, + Common::BitCast(ip_header.destination_addr)); + const u16 properties = ntohs(tcp_header.properties); + if (properties & (TCP_FLAG_FIN | TCP_FLAG_RST)) + { + if (ref == nullptr) + return; // not found + + ref->ack_num++; + WriteToQueue(BuildFINFrame(ref)); + ref->ip = 0; + ref->tcp_socket.disconnect(); + } + else if (properties & TCP_FLAG_SIN) + { + // new connection + if (ref != nullptr) + return; + ref = GetAvailableSlot(0); + + ref->delay = GetTickCountStd(); + ref->local = tcp_header.source_port; + ref->remote = tcp_header.destination_port; + ref->ack_num = ntohl(tcp_header.sequence_number) + 1; + ref->ack_base = ref->ack_num; + ref->seq_num = 0x1000000; + ref->window_size = ntohl(tcp_header.window_size); + ref->type = IPPROTO_TCP; + for (auto& tcp_buf : ref->tcp_buffers) + tcp_buf.used = false; + ref->from.sin_addr.s_addr = Common::BitCast(ip_header.destination_addr); + ref->from.sin_port = tcp_header.destination_port; + ref->to.sin_addr.s_addr = Common::BitCast(ip_header.source_addr); + ref->to.sin_port = tcp_header.source_port; + ref->bba_mac = Common::BitCastPtr(&m_eth_ref->mBbaMem[BBA_NAFR_PAR0]); + ref->my_mac = m_fake_mac; + ref->tcp_socket.setBlocking(false); + + // reply with a sin_ack + Common::TCPPacket result(ref->bba_mac, ref->my_mac, ref->from, ref->to, ref->seq_num, + ref->ack_num, TCP_FLAG_SIN | TCP_FLAG_ACK); + + result.tcp_options = {0x02, 0x04, 0x05, 0xb4, 0x01, 0x01, 0x01, 0x01}; + + ref->seq_num++; + target = sf::IpAddress(ntohl(Common::BitCast(ip_header.destination_addr))); + ref->tcp_socket.connect(target, ntohs(tcp_header.destination_port)); + ref->ready = false; + ref->ip = Common::BitCast(ip_header.destination_addr); + + ref->tcp_buffers[0].data = result.Build(); + 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 size = + ntohs(ip_header.total_len) - ip_header.DefinedSize() - tcp_header.GetHeaderSize(); + const u32 this_seq = ntohl(tcp_header.sequence_number); + + if (size > 0) + { + // only if contain data + if (static_cast(this_seq - ref->ack_num) >= 0 && data.size() >= size) + { + ref->tcp_socket.send(data.data(), size); + ref->ack_num += size; + } + + // send ack + WriteToQueue(BuildAckFrame(ref)); + } + // update windows size + ref->window_size = ntohs(tcp_header.window_size); + + // clear any ack data + if (ntohs(tcp_header.properties) & TCP_FLAG_ACK) + { + const u32 ack_num = ntohl(tcp_header.acknowledgement_number); + for (auto& tcp_buf : ref->tcp_buffers) + { + if (!tcp_buf.used || tcp_buf.seq_id >= ack_num) + continue; + + Common::PacketView view(tcp_buf.data.data(), tcp_buf.data.size()); + auto tcp_packet = view.GetTCPPacket(); // This is always a tcp packet + if (!tcp_packet.has_value()) // should never happen but just in case + continue; + + const u32 seq_end = static_cast(tcp_buf.seq_id + tcp_packet->data.size()); + 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; + tcp_packet->data.erase(tcp_packet->data.begin(), tcp_packet->data.begin() + ack_size); + + tcp_buf.seq_id += ack_size; + tcp_packet->tcp_header.sequence_number = htonl(tcp_buf.seq_id); + tcp_buf.data = tcp_packet->Build(); + } + } + } +} + +// This is a little hack, some games open a UDP port +// and listen to it. We open it on our side manually. +void CEXIETHERNET::BuiltInBBAInterface::InitUDPPort(u16 port) +{ + StackRef* ref = GetAvailableSlot(htons(port)); + if (ref == nullptr || ref->ip != 0) + return; + ref->ip = m_router_ip; // change for ip + ref->local = htons(port); + ref->remote = htons(port); + ref->type = IPPROTO_UDP; + ref->bba_mac = Common::BitCastPtr(&m_eth_ref->mBbaMem[BBA_NAFR_PAR0]); + ref->my_mac = m_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"); + PanicAlertFmt("Could't open port {:x}, this game might not work proprely in LAN mode.", port); + return; + } +} + +void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(const Common::UDPPacket& packet) +{ + const auto& [hwdata, ip_header, udp_header, ip_options, data] = packet; + sf::IpAddress target; + const u32 destination_addr = ip_header.destination_addr == Common::IP_ADDR_ANY ? + m_router_ip : // dns request + Common::BitCast(ip_header.destination_addr); + + StackRef* ref = GetAvailableSlot(udp_header.source_port); + if (ref->ip == 0) + { + ref->ip = destination_addr; // change for ip + ref->local = udp_header.source_port; + ref->remote = udp_header.destination_port; + ref->type = IPPROTO_UDP; + ref->bba_mac = Common::BitCastPtr(&m_eth_ref->mBbaMem[BBA_NAFR_PAR0]); + ref->my_mac = m_fake_mac; + ref->from.sin_addr.s_addr = destination_addr; + ref->from.sin_port = udp_header.destination_port; + ref->to.sin_addr.s_addr = Common::BitCast(ip_header.source_addr); + ref->to.sin_port = udp_header.source_port; + ref->udp_socket.setBlocking(false); + if (ref->udp_socket.bind(htons(udp_header.source_port)) != sf::Socket::Done) + { + PanicAlertFmt( + "Port {:x} is already in use, this game might not work as intented in LAN Mode.", + htons(udp_header.source_port)); + if (ref->udp_socket.bind(sf::Socket::AnyPort) != sf::Socket::Done) + { + ERROR_LOG_FMT(SP1, "Couldn't open UDP socket"); + return; + } + if (ntohs(udp_header.destination_port) == 1900) + { + InitUDPPort(26512); // MK DD and 1080 + InitUDPPort(26502); // Air Ride + if (udp_header.length > 150) + { + // Quick hack to unlock the connection, throw it back at him + Common::UDPPacket reply = packet; + reply.eth_header.destination = hwdata.source; + reply.eth_header.source = hwdata.destination; + reply.ip_header.destination_addr = ip_header.source_addr; + if (ip_header.destination_addr == Common::IP_ADDR_SSDP) + reply.ip_header.source_addr = Common::IP_ADDR_BROADCAST; + else + reply.ip_header.source_addr = Common::BitCast(destination_addr); + WriteToQueue(reply.Build()); + } + } + } + } + if (ntohs(udp_header.destination_port) == 53) + target = sf::IpAddress(m_dns_ip.c_str()); // dns server ip + else if (ip_header.destination_addr == Common::IP_ADDR_SSDP) + target = sf::IpAddress(0xFFFFFFFF); // force real broadcast + else + target = sf::IpAddress(ntohl(Common::BitCast(ip_header.destination_addr))); + ref->udp_socket.send(data.data(), data.size(), target, ntohs(udp_header.destination_port)); +} + +bool CEXIETHERNET::BuiltInBBAInterface::SendFrame(const u8* frame, u32 size) +{ + std::lock_guard lock(m_mtx); + const Common::PacketView view(frame, size); + + const std::optional ethertype = view.GetEtherType(); + if (!ethertype.has_value()) + { + ERROR_LOG_FMT(SP1, "Unable to send frame with invalid ethernet header"); + return false; + } + + switch (*ethertype) + { + case Common::IPV4_ETHERTYPE: + { + const std::optional ip_proto = view.GetIPProto(); + if (!ip_proto.has_value()) + { + ERROR_LOG_FMT(SP1, "Unable to send frame with invalid IP header"); + return false; + } + + switch (*ip_proto) + { + case IPPROTO_UDP: + { + const auto udp_packet = view.GetUDPPacket(); + if (!udp_packet.has_value()) + { + ERROR_LOG_FMT(SP1, "Unable to send frame with invalid UDP header"); + return false; + } + + if (ntohs(udp_packet->udp_header.destination_port) == 67) + { + HandleDHCP(*udp_packet); + } + else + { + HandleUDPFrame(*udp_packet); + } + break; + } + + case IPPROTO_TCP: + { + const auto tcp_packet = view.GetTCPPacket(); + if (!tcp_packet.has_value()) + { + ERROR_LOG_FMT(SP1, "Unable to send frame with invalid TCP header"); + return false; + } + + HandleTCPFrame(*tcp_packet); + break; + } + } + break; + } + + case Common::ARP_ETHERTYPE: + { + const auto arp_packet = view.GetARPPacket(); + if (!arp_packet.has_value()) + { + ERROR_LOG_FMT(SP1, "Unable to send frame with invalid ARP header"); + return false; + } + + HandleARP(*arp_packet); + break; + } + + default: + ERROR_LOG_FMT(SP1, "Unsupported EtherType {#06x}", *ethertype); + return false; + } + + m_eth_ref->SendComplete(); + return true; +} + +std::optional> TryGetDataFromSocket(StackRef* ref) +{ + size_t datasize = 0; // Set by socket.receive using a non-const reference + unsigned short remote_port; + + switch (ref->type) + { + case IPPROTO_UDP: + { + std::array buffer; + ref->udp_socket.receive(buffer.data(), MAX_UDP_LENGTH, datasize, ref->target, remote_port); + if (datasize > 0) + { + ref->from.sin_port = htons(remote_port); + ref->from.sin_addr.s_addr = htonl(ref->target.toInteger()); + const std::vector udp_data(buffer.begin(), buffer.begin() + datasize); + Common::UDPPacket packet(ref->bba_mac, ref->my_mac, ref->from, ref->to, udp_data); + return packet.Build(); + } + 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); + std::array buffer; + if (tcp_buffer != nullptr && ref->ready && can_go) + st = ref->tcp_socket.receive(buffer.data(), MAX_TCP_LENGTH, datasize); + + if (datasize > 0) + { + Common::TCPPacket packet(ref->bba_mac, ref->my_mac, ref->from, ref->to, ref->seq_num, + ref->ack_num, TCP_FLAG_ACK); + packet.data = std::vector(buffer.begin(), buffer.begin() + datasize); + + // build buffer + tcp_buffer->seq_id = ref->seq_num; + tcp_buffer->tick = GetTickCountStd(); + tcp_buffer->data = packet.Build(); + tcp_buffer->seq_id = ref->seq_num; + tcp_buffer->used = true; + ref->seq_num += static_cast(datasize); + ref->poke_time = GetTickCountStd(); + return tcp_buffer->data; + } + if (GetTickCountStd() - ref->delay > 3000) + { + if (st == sf::Socket::Disconnected || st == sf::Socket::Error) + { + ref->ip = 0; + ref->tcp_socket.disconnect(); + return BuildFINFrame(ref); + } + } + break; + } + + return std::nullopt; +} + +// Change the IP identification and recompute the checksum +static void SetIPIdentification(u8* ptr, std::size_t size, u16 value) +{ + if (size < Common::EthernetHeader::SIZE + Common::IPv4Header::SIZE) + return; + + u8* const ip_ptr = ptr + Common::EthernetHeader::SIZE; + const u8 ip_header_size = (*ip_ptr & 0xf) * 4; + if (size < Common::EthernetHeader::SIZE + ip_header_size) + return; + + u8* const ip_id_ptr = ip_ptr + offsetof(Common::IPv4Header, identification); + Common::BitCastPtr(ip_id_ptr) = htons(value); + + u8* const ip_checksum_ptr = ip_ptr + offsetof(Common::IPv4Header, header_checksum); + auto checksum_bitcast_ptr = Common::BitCastPtr(ip_checksum_ptr); + checksum_bitcast_ptr = u16(0); + checksum_bitcast_ptr = htons(Common::ComputeNetworkChecksum(ip_ptr, ip_header_size)); +} + +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 lock(self->m_mtx); + // process queue file first + if (self->m_queue_read != self->m_queue_write) + { + datasize = self->m_queue_data[self->m_queue_read].size(); + if (datasize > BBA_RECV_SIZE) + { + ERROR_LOG_FMT(SP1, "Frame size is exceiding BBA capacity, frame stack might be corrupted" + "Killing Dolphin..."); + std::exit(0); + } + std::memcpy(self->m_eth_ref->mRecvBuffer.get(), self->m_queue_data[self->m_queue_read].data(), + datasize); + self->m_queue_read++; + self->m_queue_read &= 15; + } + else + { + // test connections data + for (auto& net_ref : self->network_ref) + { + if (net_ref.ip == 0) + continue; + const auto socket_data = TryGetDataFromSocket(&net_ref); + if (socket_data.has_value()) + { + datasize = socket_data->size(); + std::memcpy(self->m_eth_ref->mRecvBuffer.get(), socket_data->data(), datasize); + 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(); + // timmed out packet, resend + if (((self->m_queue_write + 1) & 15) != self->m_queue_read) + { + self->WriteToQueue(tcp_buf.data); + } + } + } + + if (datasize > 0) + { + u8* buffer = reinterpret_cast(self->m_eth_ref->mRecvBuffer.get()); + Common::PacketView packet(buffer, datasize); + const auto packet_type = packet.GetEtherType(); + if (packet_type.has_value() && packet_type == IP_PROTOCOL) + { + SetIPIdentification(buffer, datasize, ++self->m_ip_frame_id); + } + self->m_eth_ref->mRecvBufferLength = datasize > 64 ? static_cast(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; + } + m_queue_read = 0; + m_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..e1117c8b7e --- /dev/null +++ b/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 +#else +#include +#endif + +#include +#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 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 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 EXIDevice_Create(const EXIDeviceType device_type, co result = std::make_unique(BBADeviceType::XLINK); break; + case EXIDeviceType::EthernetBuiltIn: + result = std::make_unique(BBADeviceType::BuiltIn); + break; + case EXIDeviceType::Gecko: result = std::make_unique(); 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 EXIDevice_Create(EXIDeviceType device_type, int chan template <> struct fmt::formatter - : EnumFormatter + : EnumFormatter { static constexpr array_type names = { _trans("Dummy"), @@ -95,6 +96,7 @@ struct fmt::formatter _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..416b2c362d 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.cpp @@ -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( + 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 +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; @@ -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; } @@ -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"); @@ -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); @@ -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), @@ -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; diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 6d8be1f65b..3ca1f26b01 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -13,7 +13,10 @@ #include +#include #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 mBbaMem; @@ -413,6 +418,54 @@ 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)) + { + } + 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, 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 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& 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 m_network_interface; std::unique_ptr mRecvBuffer; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 59c8c0bce0..8cb5919edf 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -254,6 +254,7 @@ + @@ -869,6 +870,7 @@ + diff --git a/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp b/Source/Core/DolphinQt/Settings/BroadbandAdapterSettingsDialog.cpp index 0c3af49df1..8a8d0c752c 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 = 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"); @@ -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;