using FishNet.Broadcast; using FishNet.Broadcast.Helping; using FishNet.Connection; using FishNet.Managing.Logging; using FishNet.Managing.Utility; using FishNet.Object; using FishNet.Serializing; using FishNet.Serializing.Helping; using FishNet.Transporting; using GameKit.Dependencies.Utilities; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Managing.Server { public sealed partial class ServerManager : MonoBehaviour { #region Private. /// /// Handler for registered broadcasts. /// private readonly Dictionary _broadcastHandlers = new(); /// /// Connections which can be broadcasted to after having excluded removed. /// private HashSet _connectionsWithoutExclusionsCache = new(); #endregion /// /// Registers a method to call when a Broadcast arrives. /// /// Type of broadcast being registered. /// Method to call. /// True if the client must be authenticated for the method to call. public void RegisterBroadcast(Action handler, bool requireAuthentication = true) where T : struct, IBroadcast { if (handler == null) { NetworkManager.LogError($"Broadcast cannot be registered because handler is null. This may occur when trying to register to objects which require initialization, such as events."); return; } ushort key = BroadcastExtensions.GetKey(); //Create new IBroadcastHandler if needed. BroadcastHandlerBase bhs; if (!_broadcastHandlers.TryGetValueIL2CPP(key, out bhs)) { bhs = new ClientBroadcastHandler(requireAuthentication); _broadcastHandlers.Add(key, bhs); } //Register handler to IBroadcastHandler. bhs.RegisterHandler(handler); } /// /// Unregisters a method call from a Broadcast type. /// /// Type of broadcast being unregistered. /// Method to unregister. public void UnregisterBroadcast(Action handler) where T : struct, IBroadcast { ushort key = BroadcastExtensions.GetKey(); if (_broadcastHandlers.TryGetValueIL2CPP(key, out BroadcastHandlerBase bhs)) bhs.UnregisterHandler(handler); } /// /// Parses a received broadcast. /// private void ParseBroadcast(PooledReader reader, NetworkConnection conn, Channel channel) { ushort key = reader.ReadUInt16(); int dataLength = Packets.GetPacketLength((ushort)PacketId.Broadcast, reader, channel); // try to invoke the handler for that message if (_broadcastHandlers.TryGetValueIL2CPP(key, out BroadcastHandlerBase bhs)) { if (bhs.RequireAuthentication && !conn.IsAuthenticated) conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a broadcast which requires authentication, but client was not authenticated. Client has been disconnected."); else bhs.InvokeHandlers(conn, reader, channel); } else { reader.Skip(dataLength); } } /// /// Sends a broadcast to a connection. /// /// Type of broadcast to send. /// Connection to send to. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the client must be authenticated for this broadcast to send. /// Channel to send on. public void Broadcast(NetworkConnection connection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } if (requireAuthenticated && !connection.IsAuthenticated) { NetworkManager.LogWarning($"Cannot send broadcast to client because they are not authenticated."); return; } PooledWriter writer = WriterPool.Retrieve(); BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel); ArraySegment segment = writer.GetArraySegment(); NetworkManager.TransportManager.SendToClient((byte)channel, segment, connection); writer.Store(); } /// /// Sends a broadcast to connections. /// /// Type of broadcast to send. /// Connections to send to. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void Broadcast(HashSet connections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } bool failedAuthentication = false; PooledWriter writer = WriterPool.Retrieve(); BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel); ArraySegment segment = writer.GetArraySegment(); foreach (NetworkConnection conn in connections) { if (requireAuthenticated && !conn.IsAuthenticated) failedAuthentication = true; else NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn); } writer.Store(); if (failedAuthentication) { NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated."); return; } } /// /// Sends a broadcast to connections except excluded. /// /// Type of broadcast to send. /// Connections to send to. /// Connection to exclude. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void BroadcastExcept(HashSet connections, NetworkConnection excludedConnection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } //Fast exit if no exclusions. if (excludedConnection == null || !excludedConnection.IsValid) { Broadcast(connections, message, requireAuthenticated, channel); return; } connections.Remove(excludedConnection); Broadcast(connections, message, requireAuthenticated, channel); } /// /// Sends a broadcast to connections except excluded. /// /// Type of broadcast to send. /// Connections to send to. /// Connections to exclude. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void BroadcastExcept(HashSet connections, HashSet excludedConnections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } //Fast exit if no exclusions. if (excludedConnections == null || excludedConnections.Count == 0) { Broadcast(connections, message, requireAuthenticated, channel); return; } /* I'm not sure if the hashset API such as intersect generates * GC or not but I'm betting doing remove locally is faster, or * just as fast. */ foreach (NetworkConnection ec in excludedConnections) connections.Remove(ec); Broadcast(connections, message, requireAuthenticated, channel); } /// /// Sends a broadcast to all connections except excluded. /// /// Type of broadcast to send. /// Connection to exclude. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void BroadcastExcept(NetworkConnection excludedConnection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } //Fast exit if there are no excluded. if (excludedConnection == null || !excludedConnection.IsValid) { Broadcast(message, requireAuthenticated, channel); return; } _connectionsWithoutExclusionsCache.Clear(); /* It will be faster to fill the entire list then * remove vs checking if each connection is contained within excluded. */ foreach (NetworkConnection c in Clients.Values) _connectionsWithoutExclusionsCache.Add(c); //Remove _connectionsWithoutExclusionsCache.Remove(excludedConnection); Broadcast(_connectionsWithoutExclusionsCache, message, requireAuthenticated, channel); } /// /// Sends a broadcast to all connections except excluded. /// /// Type of broadcast to send. /// Connections to send to. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void BroadcastExcept(HashSet excludedConnections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } //Fast exit if there are no excluded. if (excludedConnections == null || excludedConnections.Count == 0) { Broadcast(message, requireAuthenticated, channel); return; } _connectionsWithoutExclusionsCache.Clear(); /* It will be faster to fill the entire list then * remove vs checking if each connection is contained within excluded. */ foreach (NetworkConnection c in Clients.Values) _connectionsWithoutExclusionsCache.Add(c); //Remove foreach (NetworkConnection c in excludedConnections) _connectionsWithoutExclusionsCache.Remove(c); Broadcast(_connectionsWithoutExclusionsCache, message, requireAuthenticated, channel); } /// /// Sends a broadcast to observers. /// /// Type of broadcast to send. /// NetworkObject to use Observers from. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void Broadcast(NetworkObject networkObject, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (networkObject == null) { NetworkManager.LogWarning($"Cannot send broadcast because networkObject is null."); return; } Broadcast(networkObject.Observers, message, requireAuthenticated, channel); } /// /// Sends a broadcast to all clients. /// /// Type of broadcast to send. /// Broadcast data being sent; for example: an instance of your broadcast type. /// True if the clients must be authenticated for this broadcast to send. /// Channel to send on. public void Broadcast(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast { if (!Started) { NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active."); return; } bool failedAuthentication = false; PooledWriter writer = WriterPool.Retrieve(); BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel); ArraySegment segment = writer.GetArraySegment(); foreach (NetworkConnection conn in Clients.Values) { // if (requireAuthenticated && !conn.IsAuthenticated) failedAuthentication = true; else NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn); } writer.Store(); if (failedAuthentication) { NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated."); return; } } } }