diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp index 9e2a4dcca7..c02cb8585f 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -56,8 +56,161 @@ void SetIPIdentification(u8* ptr, std::size_t size, u16 value) checksum_bitcast_ptr = u16(0); checksum_bitcast_ptr = htons(Common::ComputeNetworkChecksum(ip_ptr, ip_header_size)); } +} // namespace -std::optional> TryGetDataFromSocket(StackRef* ref) +bool CEXIETHERNET::BuiltInBBAInterface::Activate() +{ + if (IsActivated()) + return true; + + m_active = true; + for (auto& buf : m_queue_data) + buf.reserve(2048); + + // 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_current_mac = Common::BitCastPtr(&m_eth_ref->mBbaMem[BBA_NAFR_PAR0]); + m_arp_table[m_current_ip] = m_current_mac; + m_router_ip = (m_current_ip & 0xFFFFFF) | 0x01000000; + m_router_mac = Common::GenerateMacAddress(Common::MACConsumer::BBA); + m_arp_table[m_router_ip] = m_router_mac; + + // 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; + } + + m_arp_table.clear(); + + // 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; + Common::ARPPacket response(m_current_mac, m_router_mac); + response.arp_header = Common::ARPHeader(arpdata.target_ip, ResolveAddress(arpdata.target_ip), + m_current_ip, m_current_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}; + + Common::DHCPPacket reply; + reply.body = Common::DHCPBody(request.transaction_id, m_current_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 + + const Common::UDPPacket response(m_current_mac, m_router_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::optional> +CEXIETHERNET::BuiltInBBAInterface::TryGetDataFromSocket(StackRef* ref) { size_t datasize = 0; // Set by socket.receive using a non-const reference unsigned short remote_port; @@ -71,7 +224,9 @@ std::optional> TryGetDataFromSocket(StackRef* ref) if (datasize > 0) { ref->from.sin_port = htons(remote_port); - ref->from.sin_addr.s_addr = htonl(ref->target.toInteger()); + const u32 remote_ip = htonl(ref->target.toInteger()); + ref->from.sin_addr.s_addr = remote_ip; + ref->my_mac = ResolveAddress(remote_ip); const std::vector udp_data(buffer.begin(), buffer.begin() + datasize); const Common::UDPPacket packet(ref->bba_mac, ref->my_mac, ref->from, ref->to, udp_data); return packet.Build(); @@ -127,166 +282,6 @@ std::optional> TryGetDataFromSocket(StackRef* ref) return std::nullopt; } -} // namespace - -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 - - const 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; -} void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& packet) { @@ -322,12 +317,13 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& 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); + const u32 destination_ip = Common::BitCast(ip_header.destination_addr); + ref->from.sin_addr.s_addr = destination_ip; 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->bba_mac = m_current_mac; + ref->my_mac = ResolveAddress(destination_ip); ref->tcp_socket.setBlocking(false); // reply with a sin_ack @@ -337,7 +333,7 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& result.tcp_options = {0x02, 0x04, 0x05, 0xb4, 0x01, 0x01, 0x01, 0x01}; ref->seq_num++; - target = sf::IpAddress(ntohl(Common::BitCast(ip_header.destination_addr))); + target = sf::IpAddress(ntohl(destination_ip)); ref->tcp_socket.connect(target, ntohs(tcp_header.destination_port)); ref->ready = false; ref->ip = Common::BitCast(ip_header.destination_addr); @@ -417,8 +413,8 @@ void CEXIETHERNET::BuiltInBBAInterface::InitUDPPort(u16 port) 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->bba_mac = m_current_mac; + ref->my_mac = m_router_mac; ref->from.sin_addr.s_addr = 0; ref->from.sin_port = htons(port); ref->to.sin_addr.s_addr = m_current_ip; @@ -447,8 +443,8 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(const Common::UDPPacket& 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->bba_mac = m_current_mac; + ref->my_mac = m_router_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); @@ -488,6 +484,21 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(const Common::UDPPacket& ref->udp_socket.send(data.data(), data.size(), target, ntohs(udp_header.destination_port)); } +const Common::MACAddress& CEXIETHERNET::BuiltInBBAInterface::ResolveAddress(u32 inet_ip) +{ + auto it = m_arp_table.lower_bound(inet_ip); + if (it != m_arp_table.end() && it->first == inet_ip) + { + return it->second; + } + else + { + return m_arp_table + .emplace_hint(it, inet_ip, Common::GenerateMacAddress(Common::MACConsumer::BBA)) + ->second; + } +} + bool CEXIETHERNET::BuiltInBBAInterface::SendFrame(const u8* frame, u32 size) { std::lock_guard lock(m_mtx); @@ -613,7 +624,7 @@ void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(CEXIETHERNET::BuiltInB { if (net_ref.ip == 0) continue; - const auto socket_data = TryGetDataFromSocket(&net_ref); + const auto socket_data = self->TryGetDataFromSocket(&net_ref); if (socket_data.has_value()) { datasize = socket_data->size(); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 3ca1f26b01..4a6c4019f5 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -4,6 +4,8 @@ #pragma once #include +#include +#include #include #include @@ -13,7 +15,6 @@ #include -#include #include "Common/Flag.h" #include "Common/Network.h" #include "Core/HW/EXI/BBA/BuiltIn.h" @@ -444,7 +445,10 @@ private: std::mutex m_mtx; std::string m_local_ip; u32 m_current_ip = 0; + Common::MACAddress m_current_mac{}; u32 m_router_ip = 0; + Common::MACAddress m_router_mac{}; + std::map m_arp_table; #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 @@ -452,18 +456,19 @@ private: 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); + std::optional> TryGetDataFromSocket(StackRef* ref); 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); + const Common::MACAddress& ResolveAddress(u32 inet_ip); }; std::unique_ptr m_network_interface;