using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using LiteNetLib; using LiteNetLib.Utils; using NitroxModel.Constants; namespace NitroxClient.Communication; public static class LANBroadcastClient { private static event Action serverFound; public static event Action ServerFound { add { serverFound += value; // Trigger event for servers already found. foreach (IPEndPoint server in DiscoveredServers) { value?.Invoke(server); } } remove => serverFound -= value; } private static Task> lastTask; public static ConcurrentQueue DiscoveredServers = []; public static async Task> SearchAsync(bool force = false, CancellationToken cancellationToken = default) { if (!force && lastTask != null) { DiscoveredServers = []; foreach (IPEndPoint ipEndPoint in await lastTask) { DiscoveredServers.Enqueue(ipEndPoint); } return DiscoveredServers; } return await (lastTask = SearchInternalAsync(cancellationToken)); } private static async Task> SearchInternalAsync(CancellationToken cancellationToken = default) { static void ReceivedResponse(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { if (messageType != UnconnectedMessageType.Broadcast) { return; } string responseString = reader.GetString(); if (responseString != LANDiscoveryConstants.BROADCAST_RESPONSE_STRING) { return; } int serverPort = reader.GetInt(); IPEndPoint serverEndPoint = new(remoteEndPoint.Address, serverPort); if (DiscoveredServers.Contains(serverEndPoint)) { return; } Log.Debug($"Found LAN server at {serverEndPoint}."); DiscoveredServers.Enqueue(serverEndPoint); OnServerFound(serverEndPoint); } cancellationToken = cancellationToken == default ? new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token : cancellationToken; EventBasedNetListener listener = new(); NetManager client = new(listener) { AutoRecycle = true, BroadcastReceiveEnabled = true, UnconnectedMessagesEnabled = true }; // Try start client on an available, predefined, port foreach (int port in LANDiscoveryConstants.BROADCAST_PORTS) { if (client.Start(port)) { break; } } if (!client.IsRunning) { Log.Warn("Failed to start LAN discover client: none of the defined ports are available"); return []; } Log.Info("Searching for LAN servers..."); listener.NetworkReceiveUnconnectedEvent += ReceivedResponse; Task broadcastTask = Task.Run(async () => { while (!cancellationToken.IsCancellationRequested) { NetDataWriter writer = new(); writer.Put(LANDiscoveryConstants.BROADCAST_REQUEST_STRING); foreach (int port in LANDiscoveryConstants.BROADCAST_PORTS) { client.SendBroadcast(writer, port); } try { await Task.Delay(5000, cancellationToken); } catch (TaskCanceledException) { // ignore } } }, cancellationToken); Task receiveTask = Task.Run(async () => { while (!cancellationToken.IsCancellationRequested) { client.PollEvents(); try { await Task.Delay(100, cancellationToken); } catch (TaskCanceledException) { // ignore } } }, cancellationToken); await Task.WhenAll(broadcastTask, receiveTask); // Cleanup listener.ClearNetworkReceiveUnconnectedEvent(); client.Stop(); listener.NetworkReceiveUnconnectedEvent -= ReceivedResponse; return DiscoveredServers; } private static void OnServerFound(IPEndPoint obj) { serverFound?.Invoke(obj); } }