using MLAPI.Data; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Linq; using System.Security.Cryptography; using MLAPI.NetworkingManagerComponents.Cryptography; using MLAPI.NetworkingManagerComponents.Core; using UnityEngine.SceneManagement; using MLAPI.NetworkingManagerComponents.Binary; namespace MLAPI.MonoBehaviours.Core { /// /// The main component of the library /// [AddComponentMenu("MLAPI/NetworkingManager", -100)] public class NetworkingManager : MonoBehaviour { /// /// A syncronized time, represents the time in seconds since the server application started. Is replicated across all clients /// public float NetworkTime { get { return networkTime; } } internal float networkTime; /// /// Gets or sets if the NetworkingManager should be marked as DontDestroyOnLoad /// public bool DontDestroy = true; /// /// Gets or sets if the application should be set to run in background /// public bool RunInBackground = true; /// /// The singleton instance of the NetworkingManager /// public static NetworkingManager singleton { get { return _singleton; } } private static NetworkingManager _singleton; /// /// The clientId the server calls the local client by, only valid for clients /// public uint MyClientId { get { return myClientId; } } internal uint myClientId; internal Dictionary connectedClients; /// /// Gets a dictionary of connected clients /// public Dictionary ConnectedClients { get { return connectedClients; } } internal HashSet pendingClients; internal bool _isServer; internal bool _isClient; /// /// Gets if we are running as host /// public bool isHost { get { return isServer && isClient; } } /// /// Gets wheter or not a client is running /// public bool isClient { get { return _isClient; } } /// /// Gets wheter or not a server is running /// public bool isServer { get { return _isServer; } } private bool isListening; private byte[] messageBuffer; /// /// Gets if we are connected as a client /// public bool IsClientConnected { get { return _isClientConnected; } } internal bool _isClientConnected; /// /// The callback to invoke once a client connects /// public Action OnClientConnectedCallback = null; /// /// The callback to invoke when a client disconnects /// public Action OnClientDisconnectCallback = null; /// /// The callback to invoke once the server is ready /// public Action OnServerStarted = null; /// /// The callback to invoke during connection approval /// public Action> ConnectionApprovalCallback = null; /// /// The current NetworkingConfiguration /// public NetworkConfig NetworkConfig; internal EllipticDiffieHellman clientDiffieHellman; internal Dictionary diffieHellmanPublicKeys; internal byte[] clientAesKey; /// /// An inspector bool that acts as a Trigger for regenerating RSA keys. Should not be used outside Unity editor. /// public bool RegenerateRSAKeys = false; private void OnValidate() { if (NetworkConfig == null) return; //May occur when the component is added if(NetworkConfig.EnableSceneSwitching && !NetworkConfig.RegisteredScenes.Contains(SceneManager.GetActiveScene().name)) { Debug.LogWarning("MLAPI: The active scene is not registered as a networked scene. The MLAPI has added it"); NetworkConfig.RegisteredScenes.Add(SceneManager.GetActiveScene().name); } if(!NetworkConfig.EnableSceneSwitching && NetworkConfig.HandleObjectSpawning) { Debug.LogWarning("MLAPI: Please be aware that Scene objects are NOT supported if SceneManagement is turned on, even if HandleObjectSpawning is turned on"); } if(NetworkConfig.HandleObjectSpawning) { for (int i = 0; i < NetworkConfig.NetworkedPrefabs.Count; i++) { if (string.IsNullOrEmpty(NetworkConfig.NetworkedPrefabs[i].name)) { Debug.LogWarning("MLAPI: The NetworkedPrefab " + NetworkConfig.NetworkedPrefabs[i].prefab.name + " does not have a NetworkedPrefabName."); } } int playerPrefabCount = NetworkConfig.NetworkedPrefabs.Count(x => x.playerPrefab == true); if (playerPrefabCount == 0) { Debug.LogWarning("MLAPI: There is no NetworkedPrefab marked as a PlayerPrefab"); } else if (playerPrefabCount > 1) { Debug.LogWarning("MLAPI: Only one networked prefab can be marked as a player prefab"); } else NetworkConfig.PlayerPrefabName = NetworkConfig.NetworkedPrefabs.Find(x => x.playerPrefab == true).name; } if (!NetworkConfig.EnableEncryption) { RegenerateRSAKeys = false; } else { if(RegenerateRSAKeys) { using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { rsa.PersistKeyInCsp = false; NetworkConfig.RSAPrivateKey = rsa.ToXmlString(true); NetworkConfig.RSAPublicKey = rsa.ToXmlString(false); } RegenerateRSAKeys = false; } } } private object Init(bool server) { networkTime = 0f; lastSendTickTime = 0f; lastEventTickTime = 0f; lastReceiveTickTime = 0f; eventOvershootCounter = 0f; pendingClients = new HashSet(); connectedClients = new Dictionary(); messageBuffer = new byte[NetworkConfig.MessageBufferSize]; diffieHellmanPublicKeys = new Dictionary(); Data.Cache.messageAttributeHashes = new Dictionary(); Data.Cache.messageAttributeNames = new Dictionary(); MessageManager.channels = new Dictionary(); MessageManager.messageTypes = new Dictionary(); MessageManager.messageCallbacks = new Dictionary>>(); MessageManager.messageHandlerCounter = new Dictionary(); MessageManager.releasedMessageHandlerCounters = new Dictionary>(); MessageManager.reverseChannels = new Dictionary(); MessageManager.reverseMessageTypes = new Dictionary(); SpawnManager.spawnedObjects = new Dictionary(); SpawnManager.releasedNetworkObjectIds = new Stack(); NetworkPoolManager.Pools = new Dictionary(); NetworkPoolManager.PoolNamesToIndexes = new Dictionary(); NetworkSceneManager.registeredSceneNames = new HashSet(); NetworkSceneManager.sceneIndexToString = new Dictionary(); NetworkSceneManager.sceneNameToIndex = new Dictionary(); InternalMessageHandler.FinalMessageBuffer = new byte[NetworkConfig.MessageBufferSize]; object settings = NetworkConfig.NetworkTransport.GetSettings(); //Gets a new "settings" object for the transport currently used. if(NetworkConfig.HandleObjectSpawning) { NetworkConfig.NetworkPrefabIds = new Dictionary(); NetworkConfig.NetworkPrefabNames = new Dictionary(); NetworkConfig.NetworkedPrefabs.OrderBy(x => x.name); HashSet networkedPrefabName = new HashSet(); for (int i = 0; i < NetworkConfig.NetworkedPrefabs.Count; i++) { if (networkedPrefabName.Contains(NetworkConfig.NetworkedPrefabs[i].name)) { Debug.LogWarning("MLAPI: Duplicate NetworkedPrefabName " + NetworkConfig.NetworkedPrefabs[i].name); continue; } NetworkConfig.NetworkPrefabIds.Add(NetworkConfig.NetworkedPrefabs[i].name, i); NetworkConfig.NetworkPrefabNames.Add(i, NetworkConfig.NetworkedPrefabs[i].name); networkedPrefabName.Add(NetworkConfig.NetworkedPrefabs[i].name); } if (NetworkConfig.EnableSceneSwitching) { SpawnManager.MarkSceneObjects(); if (NetworkConfig.HandleObjectSpawning) { if (server) { bool isServerState = _isServer; _isServer = true; NetworkedObject[] networkedObjects = FindObjectsOfType(); for (int i = 0; i < networkedObjects.Length; i++) { if (networkedObjects[i].sceneObject == null || networkedObjects[i].sceneObject == true) networkedObjects[i].Spawn(); } _isServer = isServerState; } } } } //MLAPI channels and messageTypes List internalChannels = new List { new Channel() { Name = "MLAPI_INTERNAL", Type = NetworkConfig.NetworkTransport.InternalChannel }, new Channel() { Name = "MLAPI_POSITION_UPDATE", Type = ChannelType.StateUpdate }, new Channel() { Name = "MLAPI_ANIMATION_UPDATE", Type = ChannelType.ReliableSequenced }, new Channel() { Name = "MLAPI_NAV_AGENT_STATE", Type = ChannelType.ReliableSequenced }, new Channel() { Name = "MLAPI_NAV_AGENT_CORRECTION", Type = ChannelType.StateUpdate }, new Channel() { Name = "MLAPI_TIME_SYNC", Type = ChannelType.Unreliable } }; if (NetworkConfig.EnableEncryption) { HashSet addedEncryptedChannels = new HashSet(); for (int i = 0; i < NetworkConfig.Channels.Count; i++) { if (addedEncryptedChannels.Contains(NetworkConfig.Channels[i].Name)) { Debug.LogWarning("MLAPI: Duplicate encrypted channel name " + NetworkConfig.Channels[i].Name); continue; } if (NetworkConfig.Channels[i].Encrypted) { NetworkConfig.EncryptedChannels.Add(NetworkConfig.Channels[i].Name); NetworkConfig.EncryptedChannelsHashSet.Add(NetworkConfig.Channels[i].Name); } addedEncryptedChannels.Add(NetworkConfig.Channels[i].Name); } } if (NetworkConfig.AllowPassthroughMessages) { HashSet addedPassthroughMessages = new HashSet(); for (int i = 0; i < NetworkConfig.MessageTypes.Count; i++) { if (addedPassthroughMessages.Contains(NetworkConfig.MessageTypes[i].Name)) { Debug.LogWarning("MLAPI: Duplicate passthrough message type " + NetworkConfig.MessageTypes[i].Name); continue; } if (NetworkConfig.MessageTypes[i].Passthrough) { NetworkConfig.PassthroughMessageHashSet.Add(MessageManager.messageTypes[NetworkConfig.MessageTypes[i].Name]); addedPassthroughMessages.Add(NetworkConfig.MessageTypes[i].Name); } } } HashSet channelNames = new HashSet(); //Register internal channels for (int i = 0; i < internalChannels.Count; i++) { if (channelNames.Contains(internalChannels[i].Name)) { Debug.LogWarning("MLAPI: Duplicate channel name: " + NetworkConfig.Channels[i].Name); continue; } int channelId = NetworkConfig.NetworkTransport.AddChannel(internalChannels[i].Type, settings); MessageManager.channels.Add(internalChannels[i].Name, channelId); channelNames.Add(internalChannels[i].Name); MessageManager.reverseChannels.Add(channelId, internalChannels[i].Name); } MessageManager.messageTypes.Add("MLAPI_CONNECTION_REQUEST", 0); MessageManager.messageTypes.Add("MLAPI_CONNECTION_APPROVED", 1); MessageManager.messageTypes.Add("MLAPI_ADD_OBJECT", 2); MessageManager.messageTypes.Add("MLAPI_CLIENT_DISCONNECT", 3); MessageManager.messageTypes.Add("MLAPI_DESTROY_OBJECT", 4); MessageManager.messageTypes.Add("MLAPI_SWITCH_SCENE", 5); MessageManager.messageTypes.Add("MLAPI_SPAWN_POOL_OBJECT", 6); MessageManager.messageTypes.Add("MLAPI_DESTROY_POOL_OBJECT", 7); MessageManager.messageTypes.Add("MLAPI_CHANGE_OWNER", 8); MessageManager.messageTypes.Add("MLAPI_SYNC_VAR_UPDATE", 9); MessageManager.messageTypes.Add("MLAPI_ADD_OBJECTS", 10); MessageManager.messageTypes.Add("MLAPI_TIME_SYNC", 11); MessageManager.messageTypes.Add("MLAPI_COMMAND", 12); MessageManager.messageTypes.Add("MLAPI_RPC", 13); MessageManager.messageTypes.Add("MLAPI_TARGET", 14); MessageManager.messageTypes.Add("MLAPI_SET_VISIBILITY", 15); List messageTypes = new List(NetworkConfig.MessageTypes) { new MessageType() { Name = "MLAPI_OnRecieveTransformFromClient", Passthrough = false }, new MessageType() { Name = "MLAPI_OnRecieveTransformFromServer", Passthrough = false }, new MessageType() { Name = "MLAPI_HandleAnimationMessage", Passthrough = false }, new MessageType() { Name = "MLAPI_HandleAnimationParameterMessage", Passthrough = false }, new MessageType() { Name = "MLAPI_HandleAnimationTriggerMessage", Passthrough = false }, new MessageType() { Name = "MLAPI_OnNavMeshStateUpdate", Passthrough = false }, new MessageType() { Name = "MLAPI_OnNavMeshCorrectionUpdate", Passthrough = false } }; if (NetworkConfig.EnableSceneSwitching) { for (int i = 0; i < NetworkConfig.RegisteredScenes.Count; i++) { NetworkSceneManager.registeredSceneNames.Add(NetworkConfig.RegisteredScenes[i]); NetworkSceneManager.sceneIndexToString.Add((uint)i, NetworkConfig.RegisteredScenes[i]); NetworkSceneManager.sceneNameToIndex.Add(NetworkConfig.RegisteredScenes[i], (uint)i); } NetworkSceneManager.SetCurrentSceneIndex(); } //Register user channels for (int i = 0; i < NetworkConfig.Channels.Count; i++) { if(channelNames.Contains(NetworkConfig.Channels[i].Name)) { Debug.LogWarning("MLAPI: Duplicate channel name: " + NetworkConfig.Channels[i].Name); continue; } int channelId = NetworkConfig.NetworkTransport.AddChannel(NetworkConfig.Channels[i].Type, settings); MessageManager.channels.Add(NetworkConfig.Channels[i].Name, channelId); channelNames.Add(NetworkConfig.Channels[i].Name); MessageManager.reverseChannels.Add(channelId, NetworkConfig.Channels[i].Name); } //0-32 are reserved for MLAPI messages ushort messageId = 32; for (ushort i = 0; i < messageTypes.Count; i++) { MessageManager.messageTypes.Add(messageTypes[i].Name, messageId); MessageManager.reverseMessageTypes.Add(messageId, messageTypes[i].Name); messageId++; } return settings; } /// /// Starts a server /// public void StartServer() { if (isServer || isClient) { Debug.LogWarning("MLAPI: Cannot start server while an instance is already running"); return; } if (NetworkConfig.ConnectionApproval) { if (ConnectionApprovalCallback == null) { Debug.LogWarning("MLAPI: No ConnectionApproval callback defined. Connection approval will timeout"); } } object settings = Init(true); NetworkConfig.NetworkTransport.RegisterServerListenSocket(settings); _isServer = true; _isClient = false; isListening = true; if (OnServerStarted != null) OnServerStarted.Invoke(); } /// /// Starts a client /// public void StartClient() { if (isServer || isClient) { Debug.LogWarning("MLAPI: Cannot start client while an instance is already running"); return; } object settings = Init(false); byte error; NetworkConfig.NetworkTransport.Connect(NetworkConfig.ConnectAddress, NetworkConfig.ConnectPort, settings, out error); _isServer = false; _isClient = true; isListening = true; } /// /// Stops the running server /// public void StopServer() { HashSet disconnectedIds = new HashSet(); //Don't know if I have to disconnect the clients. I'm assuming the NetworkTransport does all the cleaning on shtudown. But this way the clients get a disconnect message from server (so long it does't get lost) foreach (KeyValuePair pair in connectedClients) { if(!disconnectedIds.Contains(pair.Key)) { disconnectedIds.Add(pair.Key); if (pair.Key == NetworkConfig.NetworkTransport.HostDummyId || pair.Key == NetworkConfig.NetworkTransport.InvalidDummyId) continue; NetworkConfig.NetworkTransport.DisconnectClient(pair.Key); } } foreach (uint clientId in pendingClients) { if (!disconnectedIds.Contains(clientId)) { disconnectedIds.Add(clientId); if (clientId == NetworkConfig.NetworkTransport.HostDummyId || clientId == NetworkConfig.NetworkTransport.InvalidDummyId) continue; NetworkConfig.NetworkTransport.DisconnectClient(clientId); } } _isServer = false; Shutdown(); } /// /// Stops the running host /// public void StopHost() { _isClient = false; _isServer = false; StopServer(); //We don't stop client since we dont actually have a transport connection to our own host. We just handle host messages directly in the MLAPI } /// /// Stops the running client /// public void StopClient() { _isClient = false; NetworkConfig.NetworkTransport.DisconnectFromServer(); Shutdown(); } /// /// Starts a Host /// public void StartHost(Vector3? pos = null, Quaternion? rot = null) { if (isServer || isClient) { Debug.LogWarning("MLAPI: Cannot start host while an instance is already running"); return; } if (NetworkConfig.ConnectionApproval) { if (ConnectionApprovalCallback == null) { Debug.LogWarning("MLAPI: No ConnectionApproval callback defined. Connection approval will timeout"); } } object settings = Init(true); NetworkConfig.NetworkTransport.RegisterServerListenSocket(settings); _isServer = true; _isClient = true; isListening = true; uint hostClientId = NetworkConfig.NetworkTransport.HostDummyId; connectedClients.Add(hostClientId, new NetworkedClient() { ClientId = hostClientId }); if (NetworkConfig.HandleObjectSpawning) { SpawnManager.SpawnPlayerObject(hostClientId, 0, pos.GetValueOrDefault(), rot.GetValueOrDefault()); } if (OnServerStarted != null) OnServerStarted.Invoke(); } private void OnEnable() { if (singleton != null) { Debug.LogWarning("MLAPI: Multiple NetworkingManagers"); Destroy(this); return; } _singleton = this; if (DontDestroy) DontDestroyOnLoad(gameObject); if (RunInBackground) Application.runInBackground = true; } private void OnDestroy() { _singleton = null; Shutdown(); } private void Shutdown() { isListening = false; _isClient = false; _isServer = false; SpawnManager.DestroyNonSceneObjects(); NetworkConfig.NetworkTransport.Shutdown(); } private float lastReceiveTickTime; private float lastSendTickTime; private float lastEventTickTime; private float eventOvershootCounter; private float lastTimeSyncTime; private void Update() { if(isListening) { if((NetworkTime - lastSendTickTime >= (1f / NetworkConfig.SendTickrate)) || NetworkConfig.SendTickrate <= 0) { foreach (KeyValuePair pair in connectedClients) { byte error; NetworkConfig.NetworkTransport.SendQueue(pair.Key, out error); } lastSendTickTime = NetworkTime; } if((NetworkTime - lastReceiveTickTime >= (1f / NetworkConfig.ReceiveTickrate)) || NetworkConfig.ReceiveTickrate <= 0) { NetEventType eventType; int processedEvents = 0; do { processedEvents++; uint clientId; int channelId; int receivedSize; byte error; eventType = NetworkConfig.NetworkTransport.PollReceive(out clientId, out channelId, ref messageBuffer, messageBuffer.Length, out receivedSize, out error); switch (eventType) { case NetEventType.Connect: if (isServer) { pendingClients.Add(clientId); StartCoroutine(ApprovalTimeout(clientId)); } else { byte[] diffiePublic = new byte[0]; if(NetworkConfig.EnableEncryption) { clientDiffieHellman = new EllipticDiffieHellman(EllipticDiffieHellman.DEFAULT_CURVE, EllipticDiffieHellman.DEFAULT_GENERATOR, EllipticDiffieHellman.DEFAULT_ORDER); diffiePublic = clientDiffieHellman.GetPublicKey(); } using (BitWriter writer = new BitWriter()) { writer.WriteByteArray(NetworkConfig.GetConfig(), true); if (NetworkConfig.EnableEncryption) writer.WriteByteArray(diffiePublic); if (NetworkConfig.ConnectionApproval) writer.WriteByteArray(NetworkConfig.ConnectionData); InternalMessageHandler.Send(clientId, "MLAPI_CONNECTION_REQUEST", "MLAPI_INTERNAL", writer.Finalize(), null, null, null, true); } } break; case NetEventType.Data: HandleIncomingData(clientId, messageBuffer, channelId); break; case NetEventType.Disconnect: if (isServer) OnClientDisconnect(clientId); else _isClientConnected = false; if (OnClientDisconnectCallback != null) OnClientDisconnectCallback.Invoke(clientId); break; } // Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum) } while (eventType != NetEventType.Nothing && (NetworkConfig.MaxReceiveEventsPerTickRate <= 0 || processedEvents < NetworkConfig.MaxReceiveEventsPerTickRate)); lastReceiveTickTime = NetworkTime; } if (isServer && ((NetworkTime - lastEventTickTime >= (1f / NetworkConfig.EventTickrate)))) { eventOvershootCounter += ((NetworkTime - lastEventTickTime) - (1f / NetworkConfig.EventTickrate)); LagCompensationManager.AddFrames(); NetworkedObject.InvokeSyncvarUpdate(); lastEventTickTime = NetworkTime; } else if (isServer && eventOvershootCounter >= ((1f / NetworkConfig.EventTickrate))) { //We run this one to compensate for previous update overshoots. eventOvershootCounter -= (1f / NetworkConfig.EventTickrate); LagCompensationManager.AddFrames(); } if (NetworkConfig.EnableTimeResync && NetworkTime - lastTimeSyncTime >= 30) { SyncTime(); lastTimeSyncTime = NetworkTime; } networkTime += Time.unscaledDeltaTime; } } private IEnumerator ApprovalTimeout(uint clientId) { float timeStarted = NetworkTime; //We yield every frame incase a pending client disconnects and someone else gets its connection id while (NetworkTime - timeStarted < NetworkConfig.ClientConnectionBufferTimeout && pendingClients.Contains(clientId)) { yield return null; } if(pendingClients.Contains(clientId) && !connectedClients.ContainsKey(clientId)) { //Timeout DisconnectClient(clientId); } } private void HandleIncomingData(uint clientId, byte[] data, int channelId) { BitReader reader = new BitReader(data); ushort messageType = reader.ReadUShort(); bool targeted = reader.ReadBool(); uint targetNetworkId = 0; ushort networkOrderId = 0; if (targeted) { targetNetworkId = reader.ReadUInt(); networkOrderId = reader.ReadUShort(); } bool isPassthrough = reader.ReadBool(); uint passthroughOrigin = 0; uint passthroughTarget = 0; if (isPassthrough && isServer) passthroughTarget = reader.ReadUInt(); else if (isPassthrough && !isServer) passthroughOrigin = reader.ReadUInt(); //Client tried to send a network message that was not the connection request before he was accepted. if (isServer && pendingClients.Contains(clientId) && messageType != 0) { Debug.LogWarning("MLAPI: Message recieved from clientId " + clientId + " before it has been accepted"); return; } //ushort bytesToRead = reader.ReadUShort(); reader.SkipPadded(); byte[] incommingData = reader.ReadByteArray(); if (NetworkConfig.EncryptedChannelsHashSet.Contains(MessageManager.reverseChannels[channelId])) { //Encrypted message if (isServer) incommingData = CryptographyHelper.Decrypt(incommingData, connectedClients[clientId].AesKey); else incommingData = CryptographyHelper.Decrypt(incommingData, clientAesKey); } if (isServer && isPassthrough && !NetworkConfig.PassthroughMessageHashSet.Contains(messageType)) { Debug.LogWarning("MLAPI: Client " + clientId + " tried to send a passthrough message for a messageType not registered as passthrough"); return; } else if (isClient && isPassthrough && !NetworkConfig.PassthroughMessageHashSet.Contains(messageType)) { Debug.LogWarning("MLAPI: Server tried to send a passthrough message for a messageType not registered as passthrough"); return; } else if (isServer && isPassthrough) { if (!connectedClients.ContainsKey(passthroughTarget)) { Debug.LogWarning("MLAPI: Passthrough message was sent with invalid target: " + passthroughTarget + " from client " + clientId); return; } uint? netIdTarget = null; ushort? netOrderId = null; if (targeted) { netIdTarget = targetNetworkId; netOrderId = networkOrderId; } InternalMessageHandler.PassthroughSend(passthroughTarget, clientId, messageType, channelId, incommingData, netIdTarget, netOrderId); return; } if (messageType >= 32) { #region CUSTOM MESSAGE //Custom message, invoke all message handlers if (targeted) { if (!SpawnManager.spawnedObjects.ContainsKey(targetNetworkId)) { Debug.LogWarning("MLAPI: No target for message found"); return; } else if (!SpawnManager.spawnedObjects[targetNetworkId].targetMessageActions.ContainsKey(networkOrderId)) { Debug.LogWarning("MLAPI: No target messageType for message found"); return; } else if (!SpawnManager.spawnedObjects[targetNetworkId].targetMessageActions[networkOrderId].ContainsKey(messageType)) { Debug.LogWarning("MLAPI: No target found with the given messageType"); return; } SpawnManager.spawnedObjects[targetNetworkId].targetMessageActions[networkOrderId][messageType].Invoke(clientId, incommingData); } else { foreach (KeyValuePair> pair in MessageManager.messageCallbacks[messageType]) { if (isPassthrough) pair.Value(passthroughOrigin, incommingData); else pair.Value(clientId, incommingData); } } #endregion } else { #region INTERNAL MESSAGE //MLAPI message switch (messageType) { case 0: //Client to server > sends connection buffer if (isServer) InternalMessageHandler.HandleConnectionRequest(clientId, incommingData, channelId); break; case 1: //Server informs client it has been approved: if (isClient) InternalMessageHandler.HandleConnectionApproved(clientId, incommingData, channelId); break; case 2: //Server informs client another client connected //MLAPI_ADD_OBJECT if (isClient) InternalMessageHandler.HandleAddObject(clientId, incommingData, channelId); break; case 3: //Server informs client another client disconnected //MLAPI_CLIENT_DISCONNECT if (isClient) InternalMessageHandler.HandleClientDisconnect(clientId, incommingData, channelId); break; case 4: //Server infroms clients to destroy an object if (isClient) InternalMessageHandler.HandleDestroyObject(clientId, incommingData, channelId); break; case 5: //Scene switch if (isClient) InternalMessageHandler.HandleSwitchScene(clientId, incommingData, channelId); break; case 6: //Spawn pool object if (isClient) InternalMessageHandler.HandleSpawnPoolObject(clientId, incommingData, channelId); break; case 7: //Destroy pool object if (isClient) InternalMessageHandler.HandleDestroyPoolObject(clientId, incommingData, channelId); break; case 8: //Change owner if (isClient) InternalMessageHandler.HandleChangeOwner(clientId, incommingData, channelId); break; case 9: //Syncvar if (isClient) InternalMessageHandler.HandleSyncVarUpdate(clientId, incommingData, channelId); break; case 10: if (isClient) //MLAPI_ADD_OBJECTS (plural) InternalMessageHandler.HandleAddObjects(clientId, incommingData, channelId); break; case 11: if (isClient) InternalMessageHandler.HandleTimeSync(clientId, incommingData, channelId); break; case 12: if (isServer) InternalMessageHandler.HandleCommand(clientId, incommingData, channelId); break; case 13: if (isClient) InternalMessageHandler.HandleRpc(clientId, incommingData, channelId); break; case 14: if (isClient) InternalMessageHandler.HandleTargetRpc(clientId, incommingData, channelId); break; case 15: if (isClient) InternalMessageHandler.HandleSetVisibility(clientId, incommingData, channelId); break; } #endregion } } internal void DisconnectClient(uint clientId) { if (!isServer) return; if (pendingClients.Contains(clientId)) pendingClients.Remove(clientId); if (connectedClients.ContainsKey(clientId)) connectedClients.Remove(clientId); if (diffieHellmanPublicKeys.ContainsKey(clientId)) diffieHellmanPublicKeys.Remove(clientId); foreach (KeyValuePair pair in SpawnManager.spawnedObjects) pair.Value.observers.Remove(clientId); NetworkConfig.NetworkTransport.DisconnectClient(clientId); } internal void OnClientDisconnect(uint clientId) { if (pendingClients.Contains(clientId)) pendingClients.Remove(clientId); if (connectedClients.ContainsKey(clientId)) { if(NetworkConfig.HandleObjectSpawning) { if (connectedClients[clientId].PlayerObject != null) Destroy(connectedClients[clientId].PlayerObject); for (int i = 0; i < connectedClients[clientId].OwnedObjects.Count; i++) { if (connectedClients[clientId].OwnedObjects[i] != null) Destroy(connectedClients[clientId].OwnedObjects[i].gameObject); } } connectedClients.Remove(clientId); } if (isServer) { foreach (KeyValuePair pair in SpawnManager.spawnedObjects) pair.Value.observers.Remove(clientId); using (BitWriter writer = new BitWriter()) { writer.WriteUInt(clientId); InternalMessageHandler.Send("MLAPI_CLIENT_DISCONNECT", "MLAPI_INTERNAL", writer.Finalize(), clientId, null); } } } private void SyncTime() { using (BitWriter writer = new BitWriter()) { writer.WriteFloat(NetworkTime); int timestamp = NetworkConfig.NetworkTransport.GetNetworkTimestamp(); writer.WriteInt(timestamp); byte[] buffer = writer.Finalize(); foreach (KeyValuePair pair in connectedClients) InternalMessageHandler.Send("MLAPI_TIME_SYNC", "MLAPI_TIME_SYNC", buffer, null); } } internal void HandleApproval(uint clientId, bool approved, Vector3 position, Quaternion rotation) { if(approved) { //Inform new client it got approved if (pendingClients.Contains(clientId)) pendingClients.Remove(clientId); byte[] aesKey = new byte[0]; byte[] publicKey = new byte[0]; byte[] publicKeySignature = new byte[0]; if (NetworkConfig.EnableEncryption) { EllipticDiffieHellman diffieHellman = new EllipticDiffieHellman(EllipticDiffieHellman.DEFAULT_CURVE, EllipticDiffieHellman.DEFAULT_GENERATOR, EllipticDiffieHellman.DEFAULT_ORDER); aesKey = diffieHellman.GetSharedSecret(diffieHellmanPublicKeys[clientId]); publicKey = diffieHellman.GetPublicKey(); if (diffieHellmanPublicKeys.ContainsKey(clientId)) diffieHellmanPublicKeys.Remove(clientId); if (NetworkConfig.SignKeyExchange) { using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider()) { rsa.PersistKeyInCsp = false; rsa.FromXmlString(NetworkConfig.RSAPrivateKey); publicKeySignature = rsa.SignData(publicKey, new SHA512CryptoServiceProvider()); } } } NetworkedClient client = new NetworkedClient() { ClientId = clientId, AesKey = aesKey }; connectedClients.Add(clientId, client); if(NetworkConfig.HandleObjectSpawning) { uint networkId = SpawnManager.GetNetworkObjectId(); GameObject go = SpawnManager.SpawnPlayerObject(clientId, networkId, position, rotation); connectedClients[clientId].PlayerObject = go; } int amountOfObjectsToSend = SpawnManager.spawnedObjects.Values.Count; using (BitWriter writer = new BitWriter()) { writer.WriteUInt(clientId); if (NetworkConfig.EnableSceneSwitching) writer.WriteUInt(NetworkSceneManager.CurrentSceneIndex); if (NetworkConfig.EnableEncryption) { writer.WriteByteArray(publicKey); if (NetworkConfig.SignKeyExchange) writer.WriteByteArray(publicKeySignature); } writer.WriteFloat(NetworkTime); writer.WriteInt(NetworkConfig.NetworkTransport.GetNetworkTimestamp()); writer.WriteInt(connectedClients.Count - 1); foreach (KeyValuePair item in connectedClients) { //Our own ID. Already added as the first one above if (item.Key == clientId) continue; writer.WriteUInt(item.Key); //ClientId } if (NetworkConfig.HandleObjectSpawning) { writer.WriteInt(amountOfObjectsToSend); foreach (KeyValuePair pair in SpawnManager.spawnedObjects) { pair.Value.RebuildObservers(clientId); //Rebuilds observers for the new client writer.WriteBool(pair.Value.isPlayerObject); writer.WriteUInt(pair.Value.NetworkId); writer.WriteUInt(pair.Value.OwnerClientId); writer.WriteInt(NetworkConfig.NetworkPrefabIds[pair.Value.NetworkedPrefabName]); writer.WriteBool(pair.Value.gameObject.activeInHierarchy); writer.WriteBool(pair.Value.sceneObject == null ? true : pair.Value.sceneObject.Value); writer.WriteBool(pair.Value.observers.Contains(clientId)); writer.WriteFloat(pair.Value.transform.position.x); writer.WriteFloat(pair.Value.transform.position.y); writer.WriteFloat(pair.Value.transform.position.z); writer.WriteFloat(pair.Value.transform.rotation.eulerAngles.x); writer.WriteFloat(pair.Value.transform.rotation.eulerAngles.y); writer.WriteFloat(pair.Value.transform.rotation.eulerAngles.z); } } InternalMessageHandler.Send(clientId, "MLAPI_CONNECTION_APPROVED", "MLAPI_INTERNAL", writer.Finalize(), null, null, null, true); if (OnClientConnectedCallback != null) OnClientConnectedCallback.Invoke(clientId); } //Inform old clients of the new player using (BitWriter writer = new BitWriter()) { if (NetworkConfig.HandleObjectSpawning) { writer.WriteBool(true); writer.WriteUInt(connectedClients[clientId].PlayerObject.GetComponent().NetworkId); writer.WriteUInt(clientId); writer.WriteInt(-1); writer.WriteBool(false); writer.WriteBool(connectedClients[clientId].PlayerObject.GetComponent().observers.Contains(clientId)); writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.position.x); writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.position.y); writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.position.z); writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.x); writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.y); writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.z); } else { writer.WriteUInt(clientId); } InternalMessageHandler.Send("MLAPI_ADD_OBJECT", "MLAPI_INTERNAL", writer.Finalize(), clientId, null); } //Flush syncvars: foreach (KeyValuePair networkedObject in SpawnManager.spawnedObjects) { networkedObject.Value.FlushToClient(clientId); } } else { if (pendingClients.Contains(clientId)) pendingClients.Remove(clientId); if (diffieHellmanPublicKeys.ContainsKey(clientId)) diffieHellmanPublicKeys.Remove(clientId); NetworkConfig.NetworkTransport.DisconnectClient(clientId); } } } }