public override void Send(ulong clientId, ArraySegment <byte> data, string channelName) { if (!channelNameToId.ContainsKey(channelName)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("SteamP2PTransport - Can't Send to client, channel with channelName: " + channelName + " is not present"); } return; } int channelId = channelNameToId[channelName]; P2PSend sendType = channelSendTypes[channelId]; if (clientId == ServerClientId) { SteamNetworking.SendP2PPacket(serverUser.SteamId, data.Array, data.Count, channelId, sendType); } else { if (connectedUsers.ContainsKey(clientId)) { SteamNetworking.SendP2PPacket(connectedUsers[clientId].SteamId, data.Array, data.Count, channelId, sendType); } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("SteamP2PTransport - Can't Send to client, client not connected, clientId: " + clientId); } } } }
/// <summary> /// Cleans up our instance count and warns if there instantiation issues /// </summary> public void Dispose() { Shutdown(); #if UNITY_EDITOR || DEVELOPMENT_BUILD if (s_RpcQueueContainerInstances > 0) { if (NetworkLog.CurrentLogLevel == LogLevel.Developer) { NetworkLog.LogInfo($"[Instance : {s_RpcQueueContainerInstances}] {nameof(RpcQueueContainer)} disposed."); } s_RpcQueueContainerInstances--; } else //This should never happen...if so something else has gone very wrong. { if (NetworkLog.CurrentLogLevel >= LogLevel.Normal) { NetworkLog.LogError($"[*** Warning ***] {nameof(RpcQueueContainer)} is being disposed twice?"); } throw new Exception("[*** Warning ***] System state is not stable! Check all references to the Dispose method!"); } #endif }
public override ulong GetCurrentRtt(ulong clientId) { if (isServer) { if (clientId == ServerClientId) { return(0); } if (connectedUsers.ContainsKey(clientId)) { return(connectedUsers[clientId].Ping.Get()); } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("SteamP2PTransport - Can't GetCurrentRtt from client, client not connected, clientId: " + clientId); } } } else { return(serverUser.Ping.Get()); } return(0ul); }
public override void DisconnectRemoteClient(ulong clientId) { if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogInfo("SteamP2PTransport - DisconnectRemoteClient clientId: " + clientId); } if (!connectedUsers.ContainsKey(clientId)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("SteamP2PTransport - Can't disconect client, client not connected, clientId: " + clientId); } return; } SteamNetworking.SendP2PPacket(connectedUsers[clientId].SteamId, new byte[] { 0 }, 1, (int)InternalChannelType.Disconnect, P2PSend.Reliable); SteamId steamId = connectedUsers[clientId].SteamId; NetworkingManager.Singleton.StartCoroutine(Delay(100, () => { //Need to delay the closing of the p2p sessions to not block the disconect message before it is sent. SteamNetworking.CloseP2PSessionWithUser(steamId); if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { NetworkLog.LogInfo("SteamP2PTransport - DisconnectRemoteClient - has Closed P2P Session With clientId: " + clientId); } })); connectedUsers.Remove(clientId); }
internal NetworkedBehaviour GetBehaviourAtOrderIndex(ushort index) { if (index >= childNetworkedBehaviours.Count) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Behaviour index (" + index + ") was out of bounds (" + childNetworkedBehaviours.Count + "). Did you mess up the order of your NetworkedBehaviours?"); } return(null); } return(childNetworkedBehaviours[index]); }
internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) { if (index >= ChildNetworkBehaviours.Count) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?"); } return(null); } return(ChildNetworkBehaviours[index]); }
// This method is responsible for unwrapping a message, that is extracting the messagebody. internal static NetworkBuffer UnwrapMessage(NetworkBuffer inputBuffer, out byte messageType) { using (var inputHeaderReader = PooledNetworkReader.Get(inputBuffer)) { if (inputBuffer.Length < 1) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("The incoming message was too small"); } messageType = NetworkConstants.INVALID; return(null); } messageType = inputHeaderReader.ReadByteDirect(); // The input stream is now ready to be read from. It's "safe" and has the correct position return(inputBuffer); } }
public override void Init() { if (!SteamClient.IsValid) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("SteamClient.IsValid - false - SteamClient is not Initialized, SteamP2PTransport can not run without it, init with SteamClient.Init()"); } return; } channelIdToName.Clear(); channelNameToId.Clear(); channelSendTypes.Clear(); channelCounter = 0; currentPollChannel = 0; // Add SteamP2PTransport internal channels for (int i = 0; i < (int)InternalChannelType.InternalChannelsCount; i++) { int channelId = AddChannel(ChannelType.Reliable); } // MLAPI Channels for (int i = 0; i < MLAPI_CHANNELS.Length; i++) { int channelId = AddChannel(MLAPI_CHANNELS[i].Type); channelIdToName.Add(channelId, MLAPI_CHANNELS[i].Name); channelNameToId.Add(MLAPI_CHANNELS[i].Name, channelId); } // User Channels for (int i = 0; i < UserChannels.Count; i++) { int channelId = AddChannel(UserChannels[i].Type); channelIdToName.Add(channelId, UserChannels[i].Name); channelNameToId.Add(UserChannels[i].Name, channelId); } }
public override SocketTasks StartServer() { var topology = new HostTopology(GetConfig(), MaxConnections); if (SupportWebsocket) { if (!UseMLAPIRelay) { int websocketHostId = UnityEngine.Networking.NetworkTransport.AddWebsocketHost(topology, ServerWebsocketListenPort); } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Cannot create websocket host when using MLAPI relay"); } } } int normalHostId = RelayTransport.AddHost(topology, ServerListenPort, true); return(SocketTask.Done.AsTasks()); }
private static ulong HashMethodNameAndValidate(string name) { ulong hash = NetworkedBehaviour.HashMethodName(name); if (hashResults.ContainsKey(hash)) { string hashResult = hashResults[hash]; if (hashResult != name) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Hash collision detected for RPC method. The method \"" + name + "\" collides with the method \"" + hashResult + "\". This can be solved by increasing the amount of bytes to use for hashing in the NetworkConfig or changing the name of one of the conflicting methods."); } } } else { hashResults.Add(hash, name); } return(hash); }
// Only ran on Client internal static NetworkObject CreateLocalNetworkObject(bool softCreate, ulong instanceId, ulong prefabHash, ulong?parentNetworkId, Vector3?position, Quaternion?rotation) { NetworkObject parentNetworkObject = null; if (parentNetworkId != null && SpawnedObjects.ContainsKey(parentNetworkId.Value)) { parentNetworkObject = SpawnedObjects[parentNetworkId.Value]; } else if (parentNetworkId != null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child"); } } if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync || !softCreate) { // Create the object if (CustomSpawnHandlers.ContainsKey(prefabHash)) { var networkObject = CustomSpawnHandlers[prefabHash](position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity)); if (!ReferenceEquals(parentNetworkObject, null)) { networkObject.transform.SetParent(parentNetworkObject.transform, true); } if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) { GameObject.DontDestroyOnLoad(networkObject.gameObject); } return(networkObject); } else { int prefabIndex = GetNetworkPrefabIndexOfHash(prefabHash); if (prefabIndex < 0) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError($"Failed to create object locally. [{nameof(prefabHash)}={prefabHash}]. Hash could not be found. Is the prefab registered?"); } return(null); } var prefab = NetworkManager.Singleton.NetworkConfig.NetworkPrefabs[prefabIndex].Prefab; var networkObject = ((position == null && rotation == null) ? MonoBehaviour.Instantiate(prefab) : MonoBehaviour.Instantiate(prefab, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent <NetworkObject>(); if (!ReferenceEquals(parentNetworkObject, null)) { networkObject.transform.SetParent(parentNetworkObject.transform, true); } if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) { GameObject.DontDestroyOnLoad(networkObject.gameObject); } return(networkObject); } } else { // SoftSync them by mapping if (!PendingSoftSyncObjects.ContainsKey(instanceId)) { // TODO: Fix this message if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Cannot find pending soft sync object. Is the projects the same?"); } return(null); } var networkObject = PendingSoftSyncObjects[instanceId]; PendingSoftSyncObjects.Remove(instanceId); if (!ReferenceEquals(parentNetworkObject, null)) { networkObject.transform.SetParent(parentNetworkObject.transform, true); } return(networkObject); } }
internal static BitStream WrapMessage(byte messageType, ulong clientId, BitStream messageBody, SecuritySendFlags flags) { try { bool encrypted = ((flags & SecuritySendFlags.Encrypted) == SecuritySendFlags.Encrypted) && NetworkingManager.Singleton.NetworkConfig.EnableEncryption; bool authenticated = (flags & SecuritySendFlags.Authenticated) == SecuritySendFlags.Authenticated && NetworkingManager.Singleton.NetworkConfig.EnableEncryption; PooledBitStream outStream = PooledBitStream.Get(); using (PooledBitWriter outWriter = PooledBitWriter.Get(outStream)) { outWriter.WriteBit(encrypted); outWriter.WriteBit(authenticated); #if !DISABLE_CRYPTOGRAPHY if (authenticated || encrypted) { outWriter.WritePadBits(); long hmacWritePos = outStream.Position; if (authenticated) { outStream.Write(HMAC_PLACEHOLDER, 0, HMAC_PLACEHOLDER.Length); } if (encrypted) { using (RijndaelManaged rijndael = new RijndaelManaged()) { rijndael.GenerateIV(); rijndael.Padding = PaddingMode.PKCS7; byte[] key = NetworkingManager.Singleton.IsServer ? CryptographyHelper.GetClientKey(clientId) : CryptographyHelper.GetServerKey(); if (key == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Failed to grab key"); } return(null); } rijndael.Key = key; outStream.Write(rijndael.IV); using (CryptoStream encryptionStream = new CryptoStream(outStream, rijndael.CreateEncryptor(), CryptoStreamMode.Write)) { encryptionStream.WriteByte(messageType); encryptionStream.Write(messageBody.GetBuffer(), 0, (int)messageBody.Length); } } } else { outStream.WriteByte(messageType); outStream.Write(messageBody.GetBuffer(), 0, (int)messageBody.Length); } if (authenticated) { byte[] key = NetworkingManager.Singleton.IsServer ? CryptographyHelper.GetClientKey(clientId) : CryptographyHelper.GetServerKey(); if (key == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Failed to grab key"); } return(null); } using (HMACSHA256 hmac = new HMACSHA256(key)) { byte[] computedHmac = hmac.ComputeHash(outStream.GetBuffer(), 0, (int)outStream.Length); outStream.Position = hmacWritePos; outStream.Write(computedHmac, 0, computedHmac.Length); } } } else { #endif outWriter.WriteBits(messageType, 6); outStream.Write(messageBody.GetBuffer(), 0, (int)messageBody.Length); #if !DISABLE_CRYPTOGRAPHY } #endif } return(outStream); } catch (Exception e) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("Error while wrapping headers"); } if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError(e.ToString()); } return(null); } }
// This method is responsible for unwrapping a message, that is extracting the messagebody. // Could include decrypting and/or authentication. internal static BitStream UnwrapMessage(BitStream inputStream, ulong clientId, out byte messageType, out SecuritySendFlags security) { using (PooledBitReader inputHeaderReader = PooledBitReader.Get(inputStream)) { try { if (inputStream.Length < 1) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("The incoming message was too small"); } messageType = MLAPIConstants.INVALID; security = SecuritySendFlags.None; return(null); } bool isEncrypted = inputHeaderReader.ReadBit(); bool isAuthenticated = inputHeaderReader.ReadBit(); if (isEncrypted && isAuthenticated) { security = SecuritySendFlags.Encrypted | SecuritySendFlags.Authenticated; } else if (isEncrypted) { security = SecuritySendFlags.Encrypted; } else if (isAuthenticated) { security = SecuritySendFlags.Authenticated; } else { security = SecuritySendFlags.None; } #if !DISABLE_CRYPTOGRAPHY if (isEncrypted || isAuthenticated) { if (!NetworkingManager.Singleton.NetworkConfig.EnableEncryption) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Got a encrypted and/or authenticated message but key exchange (\"encryption\") was not enabled"); } messageType = MLAPIConstants.INVALID; return(null); } // Skip last bits in first byte inputHeaderReader.SkipPadBits(); if (isAuthenticated) { long hmacStartPos = inputStream.Position; int readHmacLength = inputStream.Read(HMAC_BUFFER, 0, HMAC_BUFFER.Length); if (readHmacLength != HMAC_BUFFER.Length) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("HMAC length was invalid"); } messageType = MLAPIConstants.INVALID; return(null); } // Now we have read the HMAC, we need to set the hmac in the input to 0s to perform the HMAC. inputStream.Position = hmacStartPos; inputStream.Write(HMAC_PLACEHOLDER, 0, HMAC_PLACEHOLDER.Length); byte[] key = NetworkingManager.Singleton.IsServer ? CryptographyHelper.GetClientKey(clientId) : CryptographyHelper.GetServerKey(); if (key == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Failed to grab key"); } messageType = MLAPIConstants.INVALID; return(null); } using (HMACSHA256 hmac = new HMACSHA256(key)) { byte[] computedHmac = hmac.ComputeHash(inputStream.GetBuffer(), 0, (int)inputStream.Length); if (!CryptographyHelper.ConstTimeArrayEqual(computedHmac, HMAC_BUFFER)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Received HMAC did not match the computed HMAC"); } messageType = MLAPIConstants.INVALID; return(null); } } } if (isEncrypted) { int ivRead = inputStream.Read(IV_BUFFER, 0, IV_BUFFER.Length); if (ivRead != IV_BUFFER.Length) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("Invalid IV size"); } messageType = MLAPIConstants.INVALID; return(null); } PooledBitStream outputStream = PooledBitStream.Get(); using (RijndaelManaged rijndael = new RijndaelManaged()) { rijndael.IV = IV_BUFFER; rijndael.Padding = PaddingMode.PKCS7; byte[] key = NetworkingManager.Singleton.IsServer ? CryptographyHelper.GetClientKey(clientId) : CryptographyHelper.GetServerKey(); if (key == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError("Failed to grab key"); } messageType = MLAPIConstants.INVALID; return(null); } rijndael.Key = key; using (CryptoStream cryptoStream = new CryptoStream(outputStream, rijndael.CreateDecryptor(), CryptoStreamMode.Write)) { cryptoStream.Write(inputStream.GetBuffer(), (int)inputStream.Position, (int)(inputStream.Length - inputStream.Position)); } outputStream.Position = 0; if (outputStream.Length == 0) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("The incoming message was too small"); } messageType = MLAPIConstants.INVALID; return(null); } int msgType = outputStream.ReadByte(); messageType = msgType == -1 ? MLAPIConstants.INVALID : (byte)msgType; } return(outputStream); } else { if (inputStream.Length - inputStream.Position <= 0) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("The incoming message was too small"); } messageType = MLAPIConstants.INVALID; return(null); } int msgType = inputStream.ReadByte(); messageType = msgType == -1 ? MLAPIConstants.INVALID : (byte)msgType; return(inputStream); } } else { #endif messageType = inputHeaderReader.ReadByteBits(6); // The input stream is now ready to be read from. It's "safe" and has the correct position return(inputStream); #if !DISABLE_CRYPTOGRAPHY } #endif } catch (Exception e) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogError("Error while unwrapping headers"); } if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError(e.ToString()); } security = SecuritySendFlags.None; messageType = MLAPIConstants.INVALID; return(null); } } }
internal static void HandleNetworkVariableUpdate(List <INetworkVariable> networkVariableList, Stream stream, ulong clientId, NetworkBehaviour logInstance, NetworkManager networkManager) { using (var reader = PooledNetworkReader.Get(stream)) { for (int i = 0; i < networkVariableList.Count; i++) { ushort varSize = 0; if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { varSize = reader.ReadUInt16Packed(); if (varSize == 0) { continue; } } else { if (!reader.ReadBool()) { continue; } } if (networkManager.IsServer && !networkVariableList[i].CanClientWrite(clientId)) { if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"Client wrote to {nameof(NetworkVariable)} without permission. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}"); } stream.Position += varSize; continue; } //This client wrote somewhere they are not allowed. This is critical //We can't just skip this field. Because we don't actually know how to dummy read //That is, we don't know how many bytes to skip. Because the interface doesn't have a //Read that gives us the value. Only a Read that applies the value straight away //A dummy read COULD be added to the interface for this situation, but it's just being too nice. //This is after all a developer fault. A critical error should be fine. // - TwoTen if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError($"Client wrote to {nameof(NetworkVariable)} without permission. No more variables can be read. This is critical. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}"); } return; } long readStartPos = stream.Position; networkVariableList[i].ReadField(stream, NetworkTickSystem.NoTick, NetworkTickSystem.NoTick); PerformanceDataManager.Increment(ProfilerConstants.NetworkVarUpdates); ProfilerStatManager.NetworkVarsRcvd.Record(); if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { if (stream is NetworkBuffer networkBuffer) { networkBuffer.SkipPadBits(); } if (stream.Position > (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"Var update read too far. {stream.Position - (readStartPos + varSize)} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}"); } stream.Position = readStartPos + varSize; } else if (stream.Position < (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"Var update read too little. {(readStartPos + varSize) - stream.Position} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}"); } stream.Position = readStartPos + varSize; } } } } }
/// <summary> /// Should only run on the client /// </summary> internal NetworkObject CreateLocalNetworkObject(bool softCreate, uint prefabHash, ulong ownerClientId, ulong?parentNetworkId, Vector3?position, Quaternion?rotation) { NetworkObject parentNetworkObject = null; if (parentNetworkId != null) { if (SpawnedObjects.TryGetValue(parentNetworkId.Value, out NetworkObject networkObject)) { parentNetworkObject = networkObject; } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning("Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child"); } } } if (!NetworkManager.NetworkConfig.EnableSceneManagement || !softCreate) { // If the prefab hash has a registered INetworkPrefabInstanceHandler derived class if (NetworkManager.PrefabHandler.ContainsHandler(prefabHash)) { // Let the handler spawn the NetworkObject var networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(prefabHash, ownerClientId, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity)); if (parentNetworkObject != null) { networkObject.transform.SetParent(parentNetworkObject.transform, true); } if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); } return(networkObject); } else { // See if there is a valid registered NetworkPrefabOverrideLink associated with the provided prefabHash GameObject networkPrefabReference = null; if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.ContainsKey(prefabHash)) { switch (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[prefabHash].Override) { default: case NetworkPrefabOverride.None: networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[prefabHash].Prefab; break; case NetworkPrefabOverride.Hash: case NetworkPrefabOverride.Prefab: networkPrefabReference = NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks[prefabHash].OverridingTargetPrefab; break; } } // If not, then there is an issue (user possibly didn't register the prefab properly?) if (networkPrefabReference == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError($"Failed to create object locally. [{nameof(prefabHash)}={prefabHash}]. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {nameof(NetworkManager)}?"); } return(null); } // Otherwise, instantiate an instance of the NetworkPrefab linked to the prefabHash var networkObject = ((position == null && rotation == null) ? UnityEngine.Object.Instantiate(networkPrefabReference) : UnityEngine.Object.Instantiate(networkPrefabReference, position.GetValueOrDefault(Vector3.zero), rotation.GetValueOrDefault(Quaternion.identity))).GetComponent <NetworkObject>(); networkObject.NetworkManagerOwner = NetworkManager; if (parentNetworkObject != null) { networkObject.transform.SetParent(parentNetworkObject.transform, true); } if (NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); } return(networkObject); } } else { // SoftSync them by mapping if (!PendingSoftSyncObjects.TryGetValue(prefabHash, out NetworkObject networkObject)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {prefabHash}!"); } return(null); } PendingSoftSyncObjects.Remove(prefabHash); if (parentNetworkObject != null) { networkObject.transform.SetParent(parentNetworkObject.transform, true); } return(networkObject); } }