// Copyright 2017 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include #include "common/archives.h" #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_page.h" #include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" #include "core/hle/service/nwm/uds_beacon.h" #include "core/hle/service/nwm/uds_connection.h" #include "core/hle/service/nwm/uds_data.h" #include "core/memory.h" SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_UDS) SERVICE_CONSTRUCT_IMPL(Service::NWM::NWM_UDS) namespace Service::NWM { template void NWM_UDS::serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); ar& node_map; ar& connection_event; ar& received_beacons; // wifi_packet_received set in constructor } namespace ErrCodes { enum { NotInitialized = 2, WrongStatus = 490, }; } // namespace ErrCodes // Number of beacons to store before we start dropping the old ones. // TODO(Subv): Find a more accurate value for this limit. constexpr std::size_t MaxBeaconFrames = 15; // Network node id used when a SecureData packet is addressed to every connected node. constexpr u16 BroadcastNetworkNodeId = 0xFFFF; // The Host has always dest_node_id 1 constexpr u16 HostDestNodeId = 1; std::list NWM_UDS::GetReceivedBeacons(const MacAddress& sender) { std::scoped_lock lock(beacon_mutex); if (sender != Network::BroadcastMac) { std::list filtered_list; const auto beacon = std::find_if(received_beacons.begin(), received_beacons.end(), [&sender](const Network::WifiPacket& packet) { return packet.transmitter_address == sender; }); if (beacon != received_beacons.end()) { filtered_list.push_back(*beacon); // TODO(B3N30): Check if the complete deque is cleared or just the fetched entries received_beacons.erase(beacon); } return filtered_list; } return std::move(received_beacons); } /// Sends a WifiPacket to the room we're currently connected to. void SendPacket(Network::WifiPacket& packet) { if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->GetState() == Network::RoomMember::State::Joined || room_member->GetState() == Network::RoomMember::State::Moderator) { packet.transmitter_address = room_member->GetMacAddress(); room_member->SendWifiPacket(packet); } } } u16 NWM_UDS::GetNextAvailableNodeId() { for (u16 index = 0; index < connection_status.max_nodes; ++index) { if ((connection_status.node_bitmask & (1 << index)) == 0) return index + 1; } // Any connection attempts to an already full network should have been refused. UNREACHABLE_MSG("No available connection slots in the network"); return 0; } void NWM_UDS::BroadcastNodeMap() { // Note: This is not how UDS on a 3ds does it but it shouldn't be // necessary for citra Network::WifiPacket packet; packet.channel = network_channel; packet.type = Network::WifiPacket::PacketType::NodeMap; packet.destination_address = Network::BroadcastMac; std::size_t num_entries = std::count_if(node_map.begin(), node_map.end(), [](const auto& node) { return node.second.connected; }); using node_t = decltype(node_map)::value_type; packet.data.resize(sizeof(num_entries) + (sizeof(node_t::first) + sizeof(node_t::second.node_id)) * num_entries); std::memcpy(packet.data.data(), &num_entries, sizeof(num_entries)); std::size_t offset = sizeof(num_entries); for (const auto& node : node_map) { if (node.second.connected) { std::memcpy(packet.data.data() + offset, node.first.data(), sizeof(node.first)); std::memcpy(packet.data.data() + offset + sizeof(node.first), &node.second.node_id, sizeof(node.second.node_id)); offset += sizeof(node.first) + sizeof(node.second.node_id); } } SendPacket(packet); } void NWM_UDS::HandleNodeMapPacket(const Network::WifiPacket& packet) { std::scoped_lock lock(connection_status_mutex); if (connection_status.status == NetworkStatus::ConnectedAsHost) { LOG_DEBUG(Service_NWM, "Ignored NodeMapPacket since connection_status is host"); return; } node_map.clear(); std::size_t num_entries; Network::MacAddress address; u16 id; std::memcpy(&num_entries, packet.data.data(), sizeof(num_entries)); std::size_t offset = sizeof(num_entries); for (std::size_t i = 0; i < num_entries; ++i) { std::memcpy(&address, packet.data.data() + offset, sizeof(address)); std::memcpy(&id, packet.data.data() + offset + sizeof(address), sizeof(id)); node_map[address].connected = true; node_map[address].node_id = id; offset += sizeof(address) + sizeof(id); } } void NWM_UDS::HandleBeaconFrame(const Network::WifiPacket& packet) { std::scoped_lock lock(beacon_mutex); const auto unique_beacon = std::find_if(received_beacons.begin(), received_beacons.end(), [&packet](const Network::WifiPacket& new_packet) { return new_packet.transmitter_address == packet.transmitter_address; }); if (unique_beacon != received_beacons.end()) { // We already have a beacon from the same mac in the deque, remove the old one; received_beacons.erase(unique_beacon); } received_beacons.emplace_back(packet); // Discard old beacons if the buffer is full. if (received_beacons.size() > MaxBeaconFrames) received_beacons.pop_front(); } void NWM_UDS::HandleAssociationResponseFrame(const Network::WifiPacket& packet) { auto assoc_result = GetAssociationResult(packet.data); ASSERT_MSG(std::get(assoc_result) == AssocStatus::Successful, "Could not join network"); { std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::Connecting) { LOG_DEBUG(Service_NWM, "Ignored AssociationResponseFrame because connection status is {}", static_cast(connection_status.status)); return; } } // Send the EAPoL-Start packet to the server. using Network::WifiPacket; WifiPacket eapol_start; eapol_start.channel = network_channel; eapol_start.data = GenerateEAPoLStartFrame(std::get(assoc_result), current_node); // TODO(B3N30): Encrypt the packet. eapol_start.destination_address = packet.transmitter_address; eapol_start.type = WifiPacket::PacketType::Data; SendPacket(eapol_start); } void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { std::scoped_lock lock{connection_status_mutex, system.Kernel().GetHLELock()}; if (GetEAPoLFrameType(packet.data) == EAPoLStartMagic) { if (connection_status.status != NetworkStatus::ConnectedAsHost) { LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is {}", static_cast(connection_status.status)); return; } auto node_it = node_map.find(packet.transmitter_address); if (node_it == node_map.end()) { LOG_DEBUG(Service_NWM, "Connection sequence aborted, because the AuthenticationFrame " "of the client wasn't recieved"); return; } if (node_it->second.connected) { LOG_DEBUG(Service_NWM, "Connection sequence aborted, because the client is already connected"); return; } ASSERT(connection_status.max_nodes != connection_status.total_nodes); auto node = DeserializeNodeInfoFromFrame(packet.data); // Get an unused network node id u16 node_id = GetNextAvailableNodeId(); node.network_node_id = node_id; connection_status.node_bitmask |= 1 << (node_id - 1); connection_status.changed_nodes |= 1 << (node_id - 1); connection_status.nodes[node_id - 1] = node.network_node_id; connection_status.total_nodes++; node_info[node_id - 1] = node; network_info.total_nodes++; node_map[packet.transmitter_address].node_id = node.network_node_id; node_map[packet.transmitter_address].connected = true; BroadcastNodeMap(); // Send the EAPoL-Logoff packet. using Network::WifiPacket; WifiPacket eapol_logoff; eapol_logoff.channel = network_channel; eapol_logoff.data = GenerateEAPoLLogoffFrame(packet.transmitter_address, node.network_node_id, node_info, network_info.max_nodes, network_info.total_nodes); // TODO(Subv): Encrypt the packet. // TODO(B3N30): send the eapol packet just to the new client and implement a proper // broadcast packet for all other clients // On a 3ds the eapol packet is only sent to packet.transmitter_address // while a packet containing the node information is broadcasted // For now we will broadcast the eapol packet instead eapol_logoff.destination_address = Network::BroadcastMac; eapol_logoff.type = WifiPacket::PacketType::Data; SendPacket(eapol_logoff); connection_status_event->Signal(); } else if (connection_status.status == NetworkStatus::Connecting) { auto logoff = ParseEAPoLLogoffFrame(packet.data); network_info.host_mac_address = packet.transmitter_address; network_info.total_nodes = logoff.connected_nodes; network_info.max_nodes = logoff.max_nodes; connection_status.network_node_id = logoff.assigned_node_id; connection_status.total_nodes = logoff.connected_nodes; connection_status.max_nodes = logoff.max_nodes; node_info.clear(); node_info.resize(network_info.max_nodes); for (const auto& node : logoff.nodes) { const u16 index = node.network_node_id; if (!index) { continue; } connection_status.node_bitmask |= 1 << (index - 1); connection_status.changed_nodes |= 1 << (index - 1); connection_status.nodes[index - 1] = index; node_info[index - 1] = DeserializeNodeInfo(node); } // We're now connected, signal the application connection_status.status = NetworkStatus::ConnectedAsClient; connection_status.status_change_reason = NetworkStatusChangeReason::ConnectionEstablished; // Some games require ConnectToNetwork to block, for now it doesn't // If blocking is implemented this lock needs to be changed, // otherwise it might cause deadlocks connection_status_event->Signal(); connection_event->Signal(); } else if (connection_status.status == NetworkStatus::ConnectedAsClient) { // TODO(B3N30): Remove that section and send/receive a proper connection_status packet // On a 3ds this packet wouldn't be addressed to already connected clients // We use this information because in the current implementation the host // isn't broadcasting the node information auto logoff = ParseEAPoLLogoffFrame(packet.data); network_info.total_nodes = logoff.connected_nodes; connection_status.total_nodes = logoff.connected_nodes; std::memset(connection_status.nodes, 0, sizeof(connection_status.nodes)); const auto old_bitmask = connection_status.node_bitmask; connection_status.node_bitmask = 0; node_info.clear(); node_info.resize(network_info.max_nodes); for (const auto& node : logoff.nodes) { const u16 index = node.network_node_id; if (!index) { continue; } connection_status.node_bitmask |= 1 << (index - 1); connection_status.nodes[index - 1] = index; node_info[index - 1] = DeserializeNodeInfo(node); } connection_status.changed_nodes = old_bitmask ^ connection_status.node_bitmask; connection_status_event->Signal(); } } void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { const auto secure_data = ParseSecureDataHeader(packet.data); std::scoped_lock lock{connection_status_mutex, system.Kernel().GetHLELock()}; if (connection_status.status != NetworkStatus::ConnectedAsHost && connection_status.status != NetworkStatus::ConnectedAsClient) { // TODO(B3N30): Handle spectators LOG_DEBUG(Service_NWM, "Ignored SecureDataPacket, because connection status is {}", static_cast(connection_status.status)); return; } if (secure_data.src_node_id == connection_status.network_node_id) { // Ignore packets that came from ourselves. return; } if (secure_data.dest_node_id != connection_status.network_node_id && secure_data.dest_node_id != BroadcastNetworkNodeId) { // The packet wasn't addressed to us, we can only act as a router if we're the host. // However, we might have received this packet due to a broadcast from the host, in that // case just ignore it. if (packet.destination_address != Network::BroadcastMac && connection_status.status != NetworkStatus::ConnectedAsHost) { LOG_ERROR(Service_NWM, "Received packet addressed to others but we're not a host"); return; } if (connection_status.status == NetworkStatus::ConnectedAsHost && secure_data.dest_node_id != BroadcastNetworkNodeId) { // Broadcast the packet so the right receiver can get it. // TODO(B3N30): Is there a flag that makes this kind of routing be unicast instead of // multicast? Perhaps this is a way to allow spectators to see some of the packets. Network::WifiPacket out_packet = packet; out_packet.destination_address = Network::BroadcastMac; SendPacket(out_packet); } return; } // The packet is addressed to us (or to everyone using the broadcast node id), handle it. // TODO(B3N30): We don't currently send nor handle management frames. ASSERT(!secure_data.is_management); // TODO(B3N30): Allow more than one bind node per channel. auto channel_info = channel_data.find(secure_data.data_channel); // Ignore packets from channels we're not interested in. if (channel_info == channel_data.end()) return; if (channel_info->second.network_node_id != BroadcastNetworkNodeId && channel_info->second.network_node_id != secure_data.src_node_id) return; // Add the received packet to the data queue. channel_info->second.received_packets.emplace_back(packet.data); // Signal the data event. We can do this directly because we locked hle_lock channel_info->second.event->Signal(); } void NWM_UDS::StartConnectionSequence(const MacAddress& server) { using Network::WifiPacket; WifiPacket auth_request; { std::scoped_lock lock(connection_status_mutex); connection_status.status = NetworkStatus::Connecting; // TODO(Subv): Handle timeout. // Send an authentication frame with SEQ1 auth_request.channel = network_channel; auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1); auth_request.destination_address = server; auth_request.type = WifiPacket::PacketType::Authentication; } SendPacket(auth_request); } void NWM_UDS::SendAssociationResponseFrame(const MacAddress& address) { using Network::WifiPacket; WifiPacket assoc_response; { std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { LOG_ERROR(Service_NWM, "Connection sequence aborted, because connection status is {}", static_cast(connection_status.status)); return; } assoc_response.channel = network_channel; // TODO(Subv): This will cause multiple clients to end up with the same association id, but // we're not using that for anything. u16 association_id = 1; assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id, network_info.network_id); assoc_response.destination_address = address; assoc_response.type = WifiPacket::PacketType::AssociationResponse; } SendPacket(assoc_response); } void NWM_UDS::HandleAuthenticationFrame(const Network::WifiPacket& packet) { // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { using Network::WifiPacket; WifiPacket auth_request; { std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { LOG_ERROR(Service_NWM, "Connection sequence aborted, because connection status is {}", static_cast(connection_status.status)); return; } if (node_map.find(packet.transmitter_address) != node_map.end()) { LOG_ERROR(Service_NWM, "Connection sequence aborted, because there is already a " "connected client with that MAC-Adress"); return; } if (connection_status.max_nodes == connection_status.total_nodes) { // Reject connection attempt LOG_ERROR(Service_NWM, "Reached maximum nodes, but reject packet wasn't sent."); // TODO(B3N30): Figure out what packet is sent here return; } // Respond with an authentication response frame with SEQ2 auth_request.channel = network_channel; auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); auth_request.destination_address = packet.transmitter_address; auth_request.type = WifiPacket::PacketType::Authentication; node_map[packet.transmitter_address].connected = false; } SendPacket(auth_request); SendAssociationResponseFrame(packet.transmitter_address); } } void NWM_UDS::HandleDeauthenticationFrame(const Network::WifiPacket& packet) { LOG_DEBUG(Service_NWM, "called"); std::scoped_lock lock{connection_status_mutex, system.Kernel().GetHLELock()}; if (connection_status.status != NetworkStatus::ConnectedAsHost) { LOG_ERROR(Service_NWM, "Got deauthentication frame but we are not the host"); return; } if (node_map.find(packet.transmitter_address) == node_map.end()) { LOG_ERROR(Service_NWM, "Got deauthentication frame from unknown node"); return; } Node node = node_map[packet.transmitter_address]; node_map.erase(packet.transmitter_address); if (!node.connected) { LOG_DEBUG(Service_NWM, "Received DeauthenticationFrame from a not connected MAC Address"); return; } auto node_it = std::find_if(node_info.begin(), node_info.end(), [&node](const NodeInfo& info) { return info.network_node_id == node.node_id; }); if (node_it == node_info.end()) { LOG_ERROR(Service_NWM, "node_it is last node of node_info"); return; } connection_status.node_bitmask &= ~(1 << (node.node_id - 1)); connection_status.changed_nodes |= 1 << (node.node_id - 1); connection_status.total_nodes--; connection_status.nodes[node.node_id - 1] = 0; network_info.total_nodes--; // TODO(B3N30): broadcast new connection_status to clients node_it->Reset(); connection_status_event->Signal(); } void NWM_UDS::HandleDataFrame(const Network::WifiPacket& packet) { switch (GetFrameEtherType(packet.data)) { case EtherType::EAPoL: HandleEAPoLPacket(packet); break; case EtherType::SecureData: HandleSecureDataPacket(packet); break; } } /// Callback to parse and handle a received wifi packet. void NWM_UDS::OnWifiPacketReceived(const Network::WifiPacket& packet) { if (!initialized) { return; } switch (packet.type) { case Network::WifiPacket::PacketType::Beacon: HandleBeaconFrame(packet); break; case Network::WifiPacket::PacketType::Authentication: HandleAuthenticationFrame(packet); break; case Network::WifiPacket::PacketType::AssociationResponse: HandleAssociationResponseFrame(packet); break; case Network::WifiPacket::PacketType::Data: HandleDataFrame(packet); break; case Network::WifiPacket::PacketType::Deauthentication: HandleDeauthenticationFrame(packet); break; case Network::WifiPacket::PacketType::NodeMap: HandleNodeMapPacket(packet); break; } } boost::optional NWM_UDS::GetNodeMacAddress(u16 dest_node_id, u8 flags) { constexpr u8 BroadcastFlag = 0x2; if ((flags & BroadcastFlag) || dest_node_id == BroadcastNetworkNodeId) { // Broadcast return Network::BroadcastMac; } else if (dest_node_id == HostDestNodeId) { // Destination is host return network_info.host_mac_address; } // Destination is a specific client auto destination = std::find_if(node_map.begin(), node_map.end(), [dest_node_id](const auto& node) { return node.second.node_id == dest_node_id && node.second.connected; }); if (destination == node_map.end()) { return {}; } return destination->first; } void NWM_UDS::Shutdown(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); initialized = false; for (auto& bind_node : channel_data) { bind_node.second.event->Signal(); } channel_data.clear(); node_map.clear(); recv_buffer_memory.reset(); SharedPage::Handler& shared_page = system.Kernel().GetSharedPageHandler(); shared_page.SetWifiLinkLevel(SharedPage::WifiLinkLevel::Off); shared_page.SetWifiState(SharedPage::WifiState::Enabled); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::RecvBeaconBroadcastData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 out_buffer_size = rp.Pop(); u32 unk1 = rp.Pop(); u32 unk2 = rp.Pop(); MacAddress mac_address; rp.PopRaw(mac_address); rp.Skip(9, false); u32 wlan_comm_id = rp.Pop(); u32 id = rp.Pop(); // From 3dbrew: // 'Official user processes create a new event handle which is then passed to this command. // However, those user processes don't save that handle anywhere afterwards.' // So we don't save/use that event too. std::shared_ptr input_event = rp.PopObject(); Kernel::MappedBuffer out_buffer = rp.PopMappedBuffer(); ASSERT(out_buffer.GetSize() == out_buffer_size); std::size_t cur_buffer_size = sizeof(BeaconDataReplyHeader); // Retrieve all beacon frames that were received from the desired mac address. auto beacons = GetReceivedBeacons(mac_address); BeaconDataReplyHeader data_reply_header{}; data_reply_header.total_entries = static_cast(beacons.size()); data_reply_header.max_output_size = out_buffer_size; // Write each of the received beacons into the buffer for (const auto& beacon : beacons) { BeaconEntryHeader entry{}; // TODO(Subv): Figure out what this size is used for. entry.unk_size = static_cast(sizeof(BeaconEntryHeader) + beacon.data.size()); entry.total_size = static_cast(sizeof(BeaconEntryHeader) + beacon.data.size()); entry.wifi_channel = beacon.channel; entry.header_size = sizeof(BeaconEntryHeader); entry.mac_address = beacon.transmitter_address; ASSERT(cur_buffer_size < out_buffer_size); out_buffer.Write(&entry, cur_buffer_size, sizeof(BeaconEntryHeader)); cur_buffer_size += sizeof(BeaconEntryHeader); const unsigned char* beacon_data = beacon.data.data(); out_buffer.Write(beacon_data, cur_buffer_size, beacon.data.size()); cur_buffer_size += beacon.data.size(); } // Update the total size in the structure and write it to the buffer again. data_reply_header.total_size = static_cast(cur_buffer_size); out_buffer.Write(&data_reply_header, 0, sizeof(BeaconDataReplyHeader)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultSuccess); rb.PushMappedBuffer(out_buffer); LOG_DEBUG(Service_NWM, "called out_buffer_size=0x{:08X}, wlan_comm_id=0x{:08X}, id=0x{:08X}," "unk1=0x{:08X}, unk2=0x{:08X}, offset={}", out_buffer_size, wlan_comm_id, id, unk1, unk2, cur_buffer_size); } ResultVal> NWM_UDS::Initialize( u32 sharedmem_size, const NodeInfo& node, u16 version, std::shared_ptr sharedmem) { current_node = node; initialized = true; recv_buffer_memory = std::move(sharedmem); ASSERT_MSG(recv_buffer_memory->GetSize() == sharedmem_size, "Invalid shared memory size."); { std::scoped_lock lock(connection_status_mutex); // Reset the connection status, it contains all zeros after initialization, // except for the actual status value. connection_status = {}; connection_status.status = NetworkStatus::NotConnected; node_info.clear(); node_info.push_back(current_node); channel_data.clear(); } SharedPage::Handler& shared_page = system.Kernel().GetSharedPageHandler(); shared_page.SetWifiLinkLevel(SharedPage::WifiLinkLevel::Best); shared_page.SetWifiState(SharedPage::WifiState::Local1); return connection_status_event; } void NWM_UDS::InitializeWithVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 sharedmem_size = rp.Pop(); auto node = rp.PopRaw(); u16 version = rp.Pop(); auto sharedmem = rp.PopObject(); auto result = Initialize(sharedmem_size, node, version, std::move(sharedmem)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(result.Code()); rb.PushCopyObjects(result.ValueOr(nullptr)); LOG_DEBUG(Service_NWM, "called sharedmem_size=0x{:08X}, version=0x{:08X}", sharedmem_size, version); } void NWM_UDS::InitializeDeprecated(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 sharedmem_size = rp.Pop(); auto node = rp.PopRaw(); auto sharedmem = rp.PopObject(); // The deprecated version uses fixed 0x100 as the version auto result = Initialize(sharedmem_size, node, 0x100, std::move(sharedmem)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(result.Code()); rb.PushCopyObjects(result.ValueOr(nullptr)); LOG_DEBUG(Service_NWM, "called sharedmem_size=0x{:08X}", sharedmem_size); } void NWM_UDS::GetConnectionStatus(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); rb.Push(ResultSuccess); { std::scoped_lock lock(connection_status_mutex); rb.PushRaw(connection_status); // Reset the bitmask of changed nodes after each call to this // function to prevent falsely informing games of outstanding // changes in subsequent calls. // TODO(Subv): Find exactly where the NWM module resets this value. connection_status.changed_nodes = 0; } LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::GetNodeInformation(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u16 network_node_id = rp.Pop(); if (!initialized) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotInitialized, ErrorModule::UDS, ErrorSummary::StatusChanged, ErrorLevel::Status)); return; } { std::scoped_lock lock(connection_status_mutex); auto itr = std::find_if(node_info.begin(), node_info.end(), [network_node_id](const NodeInfo& node) { return node.network_node_id == network_node_id; }); if (itr == node_info.end()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Status)); return; } IPC::RequestBuilder rb = rp.MakeBuilder(11, 0); rb.Push(ResultSuccess); rb.PushRaw(*itr); } LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 bind_node_id = rp.Pop(); u32 recv_buffer_size = rp.Pop(); u8 data_channel = rp.Pop(); u16 network_node_id = rp.Pop(); LOG_DEBUG(Service_NWM, "called"); if (data_channel == 0 || bind_node_id == 0) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); LOG_WARNING(Service_NWM, "data_channel = {}, bind_node_id = {}", data_channel, bind_node_id); return; } constexpr std::size_t MaxBindNodes = 16; if (channel_data.size() >= MaxBindNodes) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::OutOfMemory, ErrorModule::UDS, ErrorSummary::OutOfResource, ErrorLevel::Status)); LOG_WARNING(Service_NWM, "max bind nodes"); return; } constexpr u32 MinRecvBufferSize = 0x5F4; if (recv_buffer_size < MinRecvBufferSize) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); LOG_WARNING(Service_NWM, "MinRecvBufferSize"); return; } // Create a new event for this bind node. auto event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NWM::BindNodeEvent" + std::to_string(bind_node_id)); std::scoped_lock lock(connection_status_mutex); ASSERT(channel_data.find(data_channel) == channel_data.end()); // TODO(B3N30): Support more than one bind node per channel. channel_data[data_channel] = {bind_node_id, data_channel, network_node_id, event}; IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultSuccess); rb.PushCopyObjects(event); } void NWM_UDS::Unbind(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 bind_node_id = rp.Pop(); if (bind_node_id == 0) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); return; } std::scoped_lock lock(connection_status_mutex); auto itr = std::find_if(channel_data.begin(), channel_data.end(), [bind_node_id](const auto& data) { return data.second.bind_node_id == bind_node_id; }); if (itr != channel_data.end()) { // TODO(B3N30): Check out what Unbind does if the bind_node_id wasn't in the map itr->second.event->Signal(); channel_data.erase(itr); } IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); rb.Push(ResultSuccess); rb.Push(bind_node_id); // TODO(B3N30): Find out what the other return values are rb.Push(0); rb.Push(0); rb.Push(0); } Result NWM_UDS::BeginHostingNetwork(std::span network_info_buffer, std::vector passphrase) { // TODO(Subv): Store the passphrase and verify it when attempting a connection. { std::scoped_lock lock(connection_status_mutex); network_info = {}; std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size()); // The real UDS module throws a fatal error if this assert fails. ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); connection_status.status = NetworkStatus::ConnectedAsHost; connection_status.status_change_reason = NetworkStatusChangeReason::ConnectionEstablished; // Ensure the application data size is less than the maximum value. ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); // Set up basic information for this network. network_info.oui_value = NintendoOUI; network_info.oui_type = static_cast(NintendoTagId::NetworkInfo); connection_status.max_nodes = network_info.max_nodes; // Resize the nodes list to hold max_nodes. node_info.clear(); node_info.resize(network_info.max_nodes); // There's currently only one node in the network (the host). connection_status.total_nodes = 1; network_info.total_nodes = 1; // The host is always the first node connection_status.network_node_id = 1; current_node.network_node_id = 1; connection_status.nodes[0] = connection_status.network_node_id; // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. connection_status.node_bitmask |= 1; // Notify the application that the first node was set. connection_status.changed_nodes |= 1; if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { network_info.host_mac_address = room_member->GetMacAddress(); } else { network_info.host_mac_address = {{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; } } node_info[0] = current_node; // If the game has a preferred channel, use that instead. if (network_info.channel != 0) network_channel = network_info.channel; else network_info.channel = DefaultNetworkChannel; } connection_status_event->Signal(); // Start broadcasting the network, send a beacon frame every 102.4ms. system.CoreTiming().ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU), beacon_broadcast_event, 0); return ResultSuccess; } void NWM_UDS::BeginHostingNetwork(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 passphrase_size = rp.Pop(); const std::vector network_info_buffer = rp.PopStaticBuffer(); ASSERT(network_info_buffer.size() == sizeof(NetworkInfo)); std::vector passphrase = rp.PopStaticBuffer(); ASSERT(passphrase.size() == passphrase_size); LOG_DEBUG(Service_NWM, "called"); auto result = BeginHostingNetwork(network_info_buffer, std::move(passphrase)); LOG_DEBUG(Service_NWM, "An UDS network has been created."); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(result); } void NWM_UDS::BeginHostingNetworkDeprecated(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); // Real NWM module reads 0x108 bytes from the command buffer into the network info, where the // last 0xCC bytes (application_data and size) are undefined values. Here we just read the first // 0x3C defined bytes and zero application_data in BeginHostingNetwork. const auto network_info_buffer = rp.PopRaw>(); const u32 passphrase_size = rp.Pop(); std::vector passphrase = rp.PopStaticBuffer(); ASSERT(passphrase.size() == passphrase_size); LOG_DEBUG(Service_NWM, "called"); auto result = BeginHostingNetwork(network_info_buffer, std::move(passphrase)); LOG_DEBUG(Service_NWM, "An UDS network has been created."); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(result); } void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u16 network_node_id = rp.Pop(); LOG_WARNING(Service_NWM, "(stubbed) called"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); // The host can not be kicked. if (network_node_id == 1) { rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); return; } std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { // Only the host can kick people. rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::InvalidState, ErrorLevel::Usage)); LOG_WARNING(Service_NWM, "called with status {}", connection_status.status); return; } // This function always returns success if the status is valid. rb.Push(ResultSuccess); using Network::WifiPacket; Network::MacAddress dest_address = Network::BroadcastMac; if (network_node_id != BroadcastNetworkNodeId) { auto address = GetNodeMacAddress(network_node_id, 0); if (!address) { // There is no error if the network node id was not found. return; } dest_address = *address; } WifiPacket deauth; deauth.channel = network_channel; deauth.destination_address = dest_address; deauth.type = WifiPacket::PacketType::Deauthentication; SendPacket(deauth); if (network_node_id == BroadcastNetworkNodeId) { SendPacket(deauth); SendPacket(deauth); } } void NWM_UDS::UpdateNetworkAttribute(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); rp.Skip(2, false); LOG_WARNING(Service_NWM, "stubbed"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); // Unschedule the beacon broadcast event. system.CoreTiming().UnscheduleEvent(beacon_broadcast_event, 0); // Only a host can destroy std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState, ErrorLevel::Status)); LOG_WARNING(Service_NWM, "called with status {}", static_cast(connection_status.status)); return; } // TODO(B3N30): Send 3 Deauth packets u16_le tmp_node_id = connection_status.network_node_id; connection_status = {}; connection_status.status = NetworkStatus::NotConnected; connection_status.network_node_id = tmp_node_id; node_map.clear(); connection_status_event->Signal(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); for (auto& bind_node : channel_data) { bind_node.second.event->Signal(); } channel_data.clear(); rb.Push(ResultSuccess); LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::DisconnectNetwork(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); using Network::WifiPacket; WifiPacket deauth; { std::scoped_lock lock(connection_status_mutex); if (connection_status.status == NetworkStatus::ConnectedAsHost) { // A real 3ds makes strange things here. We do the same u16_le tmp_node_id = connection_status.network_node_id; connection_status = {}; connection_status.status = NetworkStatus::ConnectedAsHost; connection_status.network_node_id = tmp_node_id; node_map.clear(); LOG_DEBUG(Service_NWM, "called as a host"); rb.Push(Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState, ErrorLevel::Status)); return; } u16_le tmp_node_id = connection_status.network_node_id; connection_status = {}; connection_status.status = NetworkStatus::NotConnected; connection_status.network_node_id = tmp_node_id; node_map.clear(); connection_status_event->Signal(); deauth.channel = network_channel; // TODO(B3N30): Add disconnect reason deauth.data = {}; deauth.destination_address = network_info.host_mac_address; deauth.type = WifiPacket::PacketType::Deauthentication; } SendPacket(deauth); for (auto& bind_node : channel_data) { bind_node.second.event->Signal(); } channel_data.clear(); rb.Push(ResultSuccess); LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); rp.Skip(1, false); u16 dest_node_id = rp.Pop(); u8 data_channel = rp.Pop(); rp.Skip(1, false); u32 data_size = rp.Pop(); u8 flags = rp.Pop(); std::vector input_buffer = rp.PopStaticBuffer(); ASSERT(input_buffer.size() >= data_size); input_buffer.resize(data_size); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsClient && connection_status.status != NetworkStatus::ConnectedAsHost) { rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::InvalidState, ErrorLevel::Status)); return; } // There should never be a dest_node_id of 0 if (dest_node_id == 0) { rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Status)); LOG_ERROR(Service_NWM, "dest_node_id is 0"); return; } if (dest_node_id == connection_status.network_node_id) { LOG_ERROR(Service_NWM, "tried to send packet to itself"); rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Status)); return; } if (flags >> 2) { LOG_ERROR(Service_NWM, "Unexpected flags 0x{:02X}", flags); } auto dest_address = GetNodeMacAddress(dest_node_id, flags); if (!dest_address) { rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Status)); return; } constexpr std::size_t MaxSize = 0x5C6; if (data_size > MaxSize) { rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); return; } // TODO(B3N30): Increment the sequence number after each sent packet. u16 sequence_number = 0; std::vector data_payload = GenerateDataPayload(input_buffer, data_channel, dest_node_id, connection_status.network_node_id, sequence_number); // TODO(B3N30): Use the MAC address of the dest_node_id and our own to encrypt // and encapsulate the payload. Network::WifiPacket packet; packet.destination_address = *dest_address; packet.channel = network_channel; packet.data = std::move(data_payload); packet.type = Network::WifiPacket::PacketType::Data; SendPacket(packet); rb.Push(ResultSuccess); } void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 bind_node_id = rp.Pop(); u32 max_out_buff_size_aligned = rp.Pop(); u32 max_out_buff_size = rp.Pop(); // This size is hard coded into the uds module. We don't know the meaning yet. u32 buff_size = std::min(max_out_buff_size_aligned, 0x172) << 2; std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost && connection_status.status != NetworkStatus::ConnectedAsClient && connection_status.status != NetworkStatus::ConnectedAsSpectator) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::InvalidState, ErrorLevel::Status)); return; } auto channel = std::find_if(channel_data.begin(), channel_data.end(), [bind_node_id](const auto& data) { return data.second.bind_node_id == bind_node_id; }); if (channel == channel_data.end()) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); return; } if (channel->second.received_packets.empty()) { std::vector output_buffer(buff_size); IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(ResultSuccess); rb.Push(0); rb.Push(0); rb.PushStaticBuffer(std::move(output_buffer), 0); return; } const auto& next_packet = channel->second.received_packets.front(); auto secure_data = ParseSecureDataHeader(next_packet); auto data_size = secure_data.GetActualDataSize(); if (data_size > max_out_buff_size) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); return; } IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); std::vector output_buffer(buff_size); // Write the actual data. std::memcpy(output_buffer.data(), next_packet.data() + sizeof(LLCHeader) + sizeof(SecureDataHeader), data_size); rb.Push(ResultSuccess); rb.Push(data_size); rb.Push(secure_data.src_node_id); rb.PushStaticBuffer(std::move(output_buffer), 0); channel->second.received_packets.pop_front(); } void NWM_UDS::GetChannel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); std::scoped_lock lock(connection_status_mutex); bool is_connected = connection_status.status != NetworkStatus::NotConnected; u8 channel = is_connected ? network_channel : 0; rb.Push(ResultSuccess); rb.Push(channel); LOG_DEBUG(Service_NWM, "called"); } class NWM_UDS::ThreadCallback : public Kernel::HLERequestContext::WakeupCallback { public: explicit ThreadCallback(u16 command_id_) : command_id(command_id_) {} void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { // TODO(B3N30): Add error handling for host full and timeout IPC::RequestBuilder rb(ctx, command_id, 1, 0); rb.Push(ResultSuccess); LOG_DEBUG(Service_NWM, "connection sequence finished"); } private: ThreadCallback() = default; u16 command_id; template void serialize(Archive& ar, const unsigned int) { ar& boost::serialization::base_object(*this); ar& command_id; } friend class boost::serialization::access; }; void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id, std::span network_info_buffer, u8 connection_type, std::vector passphrase) { network_info = {}; std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size()); // Start the connection sequence StartConnectionSequence(network_info.host_mac_address); // 300 ms // Since this timing is handled by core_timing it could differ from the 'real world' time static constexpr std::chrono::nanoseconds UDSConnectionTimeout{300000000}; connection_event = ctx.SleepClientThread("uds::ConnectToNetwork", UDSConnectionTimeout, std::make_shared(command_id)); } void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto connection_type = rp.Pop(); [[maybe_unused]] const auto passphrase_size = rp.Pop(); const std::vector network_info_buffer = rp.PopStaticBuffer(); ASSERT(network_info_buffer.size() == sizeof(NetworkInfo)); std::vector passphrase = rp.PopStaticBuffer(); ConnectToNetwork(ctx, 0x1E, network_info_buffer, connection_type, std::move(passphrase)); LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::ConnectToNetworkDeprecated(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); // Similar to BeginHostingNetworkDeprecated, we only read the first 0x3C bytes into the network // info const auto network_info_buffer = rp.PopRaw>(); const auto connection_type = rp.Pop(); [[maybe_unused]] const auto passphrase_size = rp.Pop(); std::vector passphrase = rp.PopStaticBuffer(); ConnectToNetwork(ctx, 0x09, network_info_buffer, connection_type, std::move(passphrase)); LOG_DEBUG(Service_NWM, "called"); } void NWM_UDS::SetApplicationData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 size = rp.Pop(); const std::vector application_data = rp.PopStaticBuffer(); ASSERT(application_data.size() == size); LOG_DEBUG(Service_NWM, "called"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (size > ApplicationDataSize) { rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Usage)); return; } network_info.application_data_size = static_cast(size); std::memcpy(network_info.application_data.data(), application_data.data(), size); rb.Push(ResultSuccess); } void NWM_UDS::GetApplicationData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 input_size = rp.Pop(); u8 appdata_size = network_info.application_data_size; IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(ResultSuccess); if (input_size < appdata_size) { rb.Push(0); return; } rb.Push(appdata_size); std::vector appdata(appdata_size); std::memcpy(appdata.data(), network_info.application_data.data(), appdata_size); rb.PushStaticBuffer(std::move(appdata), 0); } void NWM_UDS::DecryptBeaconData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const std::vector network_struct_buffer = rp.PopStaticBuffer(); ASSERT(network_struct_buffer.size() == sizeof(NetworkInfo)); const std::vector encrypted_data0_buffer = rp.PopStaticBuffer(); const std::vector encrypted_data1_buffer = rp.PopStaticBuffer(); LOG_DEBUG(Service_NWM, "called"); NetworkInfo net_info; std::memcpy(&net_info, network_struct_buffer.data(), sizeof(net_info)); // Read the encrypted data. // The first 4 bytes should be the OUI and the OUI Type of the tags. std::array oui; std::memcpy(oui.data(), encrypted_data0_buffer.data(), oui.size()); ASSERT_MSG(oui == NintendoOUI, "Unexpected OUI"); ASSERT_MSG(encrypted_data0_buffer[3] == static_cast(NintendoTagId::EncryptedData0), "Unexpected tag id"); std::vector beacon_data(encrypted_data0_buffer.size() - 4 + encrypted_data1_buffer.size() - 4); std::memcpy(beacon_data.data(), encrypted_data0_buffer.data() + 4, encrypted_data0_buffer.size() - 4); std::memcpy(beacon_data.data() + encrypted_data0_buffer.size() - 4, encrypted_data1_buffer.data() + 4, encrypted_data1_buffer.size() - 4); // Decrypt the data DecryptBeacon(net_info, beacon_data); // The beacon data header contains the MD5 hash of the data. BeaconData beacon_header; std::memcpy(&beacon_header, beacon_data.data(), sizeof(beacon_header)); // TODO(Subv): Verify the MD5 hash of the data and return 0xE1211005 if invalid. const std::size_t num_nodes = net_info.max_nodes; std::vector nodes; nodes.reserve(num_nodes); for (std::size_t i = 0; i < num_nodes; ++i) { BeaconNodeInfo info; std::memcpy(&info, beacon_data.data() + sizeof(beacon_header) + i * sizeof(info), sizeof(info)); // Deserialize the node information. auto& node = nodes.emplace_back(); node.friend_code_seed = info.friend_code_seed; node.network_node_id = info.network_node_id; for (std::size_t j = 0; j < info.username.size(); ++j) { node.username[j] = info.username[j]; } } IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultSuccess); std::vector output_buffer(sizeof(NodeInfo) * UDSMaxNodes); std::memcpy(output_buffer.data(), nodes.data(), sizeof(NodeInfo) * nodes.size()); rb.PushStaticBuffer(std::move(output_buffer), 0); } // Sends a 802.11 beacon frame with information about the current network. void NWM_UDS::BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late) { // Don't do anything if we're not actually hosting a network if (connection_status.status != NetworkStatus::ConnectedAsHost) return; std::vector frame = GenerateBeaconFrame(network_info, node_info); using Network::WifiPacket; WifiPacket packet; packet.type = WifiPacket::PacketType::Beacon; packet.data = std::move(frame); packet.destination_address = Network::BroadcastMac; packet.channel = network_channel; SendPacket(packet); // Start broadcasting the network, send a beacon frame every 102.4ms. system.CoreTiming().ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, beacon_broadcast_event, 0); } NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(system) { static const FunctionInfo functions[] = { // clang-format off {0x0001, &NWM_UDS::InitializeDeprecated, "Initialize (deprecated)"}, {0x0002, nullptr, "Scrap"}, {0x0003, &NWM_UDS::Shutdown, "Shutdown"}, {0x0004, &NWM_UDS::BeginHostingNetworkDeprecated, "BeginHostingNetwork (deprecated)"}, {0x0005, &NWM_UDS::EjectClient, "EjectClient"}, {0x0006, nullptr, "EjectSpectator"}, {0x0007, &NWM_UDS::UpdateNetworkAttribute, "UpdateNetworkAttribute"}, {0x0008, &NWM_UDS::DestroyNetwork, "DestroyNetwork"}, {0x0009, &NWM_UDS::ConnectToNetworkDeprecated, "ConnectToNetwork (deprecated)"}, {0x000A, &NWM_UDS::DisconnectNetwork, "DisconnectNetwork"}, {0x000B, &NWM_UDS::GetConnectionStatus, "GetConnectionStatus"}, {0x000D, &NWM_UDS::GetNodeInformation, "GetNodeInformation"}, {0x000E, &NWM_UDS::DecryptBeaconData, "DecryptBeaconData (deprecated)"}, {0x000F, &NWM_UDS::RecvBeaconBroadcastData, "RecvBeaconBroadcastData"}, {0x0010, &NWM_UDS::SetApplicationData, "SetApplicationData"}, {0x0011, &NWM_UDS::GetApplicationData, "GetApplicationData"}, {0x0012, &NWM_UDS::Bind, "Bind"}, {0x0013, &NWM_UDS::Unbind, "Unbind"}, {0x0014, &NWM_UDS::PullPacket, "PullPacket"}, {0x0015, nullptr, "SetMaxSendDelay"}, {0x0017, &NWM_UDS::SendTo, "SendTo"}, {0x001A, &NWM_UDS::GetChannel, "GetChannel"}, {0x001B, &NWM_UDS::InitializeWithVersion, "InitializeWithVersion"}, {0x001D, &NWM_UDS::BeginHostingNetwork, "BeginHostingNetwork"}, {0x001E, &NWM_UDS::ConnectToNetwork, "ConnectToNetwork"}, {0x001F, &NWM_UDS::DecryptBeaconData, "DecryptBeaconData"}, {0x0020, nullptr, "Flush"}, {0x0021, nullptr, "SetProbeResponseParam"}, {0x0022, nullptr, "ScanOnConnection"}, // clang-format on }; connection_status_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NWM::connection_status_event"); RegisterHandlers(functions); beacon_broadcast_event = system.CoreTiming().RegisterEvent( "UDS::BeaconBroadcastCallback", [this](std::uintptr_t user_data, s64 cycles_late) { BeaconBroadcastCallback(user_data, cycles_late); }); CryptoPP::AutoSeededRandomPool rng; auto mac = SharedPage::DefaultMac; // Keep the Nintendo 3DS MAC header and randomly generate the last 3 bytes rng.GenerateBlock(static_cast(mac.data() + 3), 3); if (auto room_member = Network::GetRoomMember().lock()) { if (room_member->IsConnected()) { mac = room_member->GetMacAddress(); } } system.Kernel().GetSharedPageHandler().SetMacAddress(mac); if (auto room_member = Network::GetRoomMember().lock()) { wifi_packet_received = room_member->BindOnWifiPacketReceived( [this](const Network::WifiPacket& packet) { OnWifiPacketReceived(packet); }); } else { LOG_ERROR(Service_NWM, "Network isn't initalized"); } } NWM_UDS::~NWM_UDS() { if (auto room_member = Network::GetRoomMember().lock()) room_member->Unbind(wifi_packet_received); system.CoreTiming().UnscheduleEvent(beacon_broadcast_event, 0); } } // namespace Service::NWM SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_UDS::ThreadCallback)