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 #include +#include #ifndef _WIN32 #include @@ -141,6 +142,18 @@ 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 = 0x50 | flags; + + window_size = 0x7c; + checksum = 0; +} + u16 TCPHeader::Size() const { return static_cast(SIZE); @@ -170,6 +183,59 @@ u8 UDPHeader::IPProto() const return static_cast(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(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& 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(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(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 #include #include +#include #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; @@ -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& 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 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 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..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 + +#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(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 +getTcpHeaders(u8* data, Common::MACAddress dest, Common::MACAddress src) +{ + SetHardwareInfo(data, dest, src); + return std::tuple( + (Common::EthernetHeader*)data, (Common::IPv4Header*)&data[14], + (Common::TCPHeader*)&data[0x22]); +} + +std::tuple +getUdpHeaders(u8* data, Common::MACAddress dest, Common::MACAddress src) +{ + SetHardwareInfo(data, dest, src); + return std::tuple( + (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(9004); + m_out_frame = std::make_unique(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 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{2}) : + reply->AddDHCPOption(1, 53, std::vector{5}); + reply->AddDHCPOption(4, 54, ip_part); // dhcp server ip + reply->AddDHCPOption(4, 51, std::vector{0, 1, 0x51, 0x80}); // lease time 24h + reply->AddDHCPOption(4, 58, std::vector{0, 1, 0x51, 0x80}); // renewal + reply->AddDHCPOption(4, 59, std::vector{0, 1, 0x51, 0x80}); // rebind + reply->AddDHCPOption(4, 1, std::vector{255, 255, 255, 0}); // submask + reply->AddDHCPOption(4, 28, + std::vector{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{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); + } + } + } +} + +/// +/// This is a litle hack, Mario Kart open some UDP port +/// and listen to it. We open it on our side manualy. +/// +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 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 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 +#else +#include +#endif + +#include +#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 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..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( + 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 +#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,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, 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 network_ref{}; // max 10 at same time, i think most gc game had a + // limit of 8 in the gc framework + std::unique_ptr m_in_frame; + std::unique_ptr 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 m_network_interface; std::unique_ptr 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 @@ + @@ -868,6 +869,7 @@ + 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;