diff --git a/UnityTransport/Editor/LRMInspector.cs b/UnityTransport/Editor/LRMInspector.cs index bd8f380..df63971 100644 --- a/UnityTransport/Editor/LRMInspector.cs +++ b/UnityTransport/Editor/LRMInspector.cs @@ -228,7 +228,13 @@ namespace LightReflectiveMirror break; case 3: // Other tab... - //EditorGUIUtility.LookLikeControls(); + + GUI.enabled = false; + EditorGUILayout.TextField("Server ID", lrm.serverId == -1 ? "Not Hosting" : lrm.serverId.ToString()); + GUI.enabled = true; + + EditorGUILayout.Space(); + lrm.serverName = EditorGUILayout.TextField("Server Name", lrm.serverName); lrm.extraServerData = EditorGUILayout.TextField("Extra Server Data", lrm.extraServerData); lrm.maxServerPlayers = EditorGUILayout.IntField("Max Server Players", lrm.maxServerPlayers); diff --git a/UnityTransport/LRMTransport/LRMTransportDirectConnect.cs b/UnityTransport/LRMTransport/LRMTransportDirectConnect.cs new file mode 100644 index 0000000..24ad39f --- /dev/null +++ b/UnityTransport/LRMTransport/LRMTransportDirectConnect.cs @@ -0,0 +1,70 @@ +using Mirror; +using System; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + public void DirectAddClient(int clientID) + { + if (!_isServer) + return; + + _connectedDirectClients.Add(clientID, _currentMemberId); + OnServerConnected?.Invoke(_currentMemberId); + _currentMemberId++; + } + + public void DirectRemoveClient(int clientID) + { + if (!_isServer) + return; + + OnServerDisconnected?.Invoke(_connectedDirectClients.GetByFirst(clientID)); + _connectedDirectClients.Remove(clientID); + } + + public void DirectReceiveData(ArraySegment data, int channel, int clientID = -1) + { + if (_isServer) + OnServerDataReceived?.Invoke(_connectedDirectClients.GetByFirst(clientID), data, channel); + + if (_isClient) + OnClientDataReceived?.Invoke(data, channel); + } + + public void DirectClientConnected() + { + _directConnected = true; + OnClientConnected?.Invoke(); + } + + public void DirectDisconnected() + { + if (_directConnected) + { + _isClient = false; + _directConnected = false; + OnClientDisconnected?.Invoke(); + } + else + { + int pos = 0; + _directConnected = false; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); + _clientSendBuffer.WriteInt(ref pos, _cachedHostID); + _clientSendBuffer.WriteBool(ref pos, false); // Direct failed, use relay + + _isClient = true; + + clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); + } + + if (_clientProxy != null) + { + _clientProxy.Dispose(); + _clientProxy = null; + } + } + } +} \ No newline at end of file diff --git a/UnityTransport/LRMTransport/LRMTransportNATPuncher.cs b/UnityTransport/LRMTransport/LRMTransportNATPuncher.cs new file mode 100644 index 0000000..de95188 --- /dev/null +++ b/UnityTransport/LRMTransport/LRMTransportNATPuncher.cs @@ -0,0 +1,67 @@ +using Mirror; +using System; +using System.Collections; +using System.Net; +using UnityEngine; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + IEnumerator NATPunch(IPEndPoint remoteAddress) + { + for (int i = 0; i < 10; i++) + { + _NATPuncher.Send(_punchData, 1, remoteAddress); + yield return new WaitForSeconds(0.25f); + } + } + + void RecvData(IAsyncResult result) + { + IPEndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); + var data = _NATPuncher.EndReceive(result, ref newClientEP); + _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); + + if (!newClientEP.Address.Equals(_relayPuncherIP.Address)) + { + if (_isServer) + { + if (_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy)) + { + if (data.Length > 2) + foundProxy.RelayData(data, data.Length); + } + else + { + _serverProxies.Add(newClientEP, new SocketProxy(_NATIP.Port + 1, newClientEP)); + _serverProxies.GetByFirst(newClientEP).dataReceived += ServerProcessProxyData; + } + } + + if (_isClient) + { + if (_clientProxy == null) + { + _clientProxy = new SocketProxy(_NATIP.Port - 1); + _clientProxy.dataReceived += ClientProcessProxyData; + } + else + { + _clientProxy.ClientRelayData(data, data.Length); + } + } + } + } + + void ServerProcessProxyData(IPEndPoint remoteEndpoint, byte[] data) + { + _NATPuncher.Send(data, data.Length, remoteEndpoint); + } + + void ClientProcessProxyData(IPEndPoint _, byte[] data) + { + _NATPuncher.Send(data, data.Length, _directConnectEndpoint); + } + } +} \ No newline at end of file diff --git a/UnityTransport/LRMTransport/LRMTransportOverrides.cs b/UnityTransport/LRMTransport/LRMTransportOverrides.cs new file mode 100644 index 0000000..267bb5e --- /dev/null +++ b/UnityTransport/LRMTransport/LRMTransportOverrides.cs @@ -0,0 +1,252 @@ +using Mirror; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using UnityEngine; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + public override bool ServerActive() => _isServer; + public override bool Available() => _connectedToRelay; + public override void ClientConnect(Uri uri) => ClientConnect(uri.Host); + public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId); + public override bool ClientConnected() => _isClient; + + public override void ServerLateUpdate() + { + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ServerLateUpdate(); + } + + public override string ServerGetClientAddress(int connectionId) + { + if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) + return relayId.ToString(); + + if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + return "DIRECT-" + directId; + + // Shouldn't ever get here. + return "?"; + } + + public override void ClientEarlyUpdate() + { + clientToServerTransport.ClientEarlyUpdate(); + + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ClientEarlyUpdate(); + } + + public override void ClientLateUpdate() + { + clientToServerTransport.ClientLateUpdate(); + + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ClientLateUpdate(); + } + + public override void ServerEarlyUpdate() + { + if (_directConnectModule != null) + _directConnectModule.directConnectTransport.ServerEarlyUpdate(); + } + + public override void ClientConnect(string address) + { + if (!Available() || !int.TryParse(address, out _cachedHostID)) + { + Debug.Log("Not connected to relay or invalid server id!"); + OnClientDisconnected?.Invoke(); + return; + } + + if (_isClient || _isServer) + throw new Exception("Cannot connect while hosting/already connected!"); + + int pos = 0; + _directConnected = false; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); + _clientSendBuffer.WriteInt(ref pos, _cachedHostID); + _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null); + + if (GetLocalIp() == null) + _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); + else + _clientSendBuffer.WriteString(ref pos, GetLocalIp()); + + _isClient = true; + + clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); + } + + public override void ClientDisconnect() + { + _isClient = false; + + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); + + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + + if (_directConnectModule != null) + _directConnectModule.ClientDisconnect(); + } + + public override void ClientSend(int channelId, ArraySegment segment) + { + if (_directConnected) + { + _directConnectModule.ClientSend(segment, channelId); + } + else + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); + _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); + _clientSendBuffer.WriteInt(ref pos, 0); + + clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); + } + } + + public override bool ServerDisconnect(int connectionId) + { + if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer); + _clientSendBuffer.WriteInt(ref pos, relayId); + return true; + } + + if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + return _directConnectModule.KickClient(directId); + + return false; + } + + public override void ServerSend(int connectionId, int channelId, ArraySegment segment) + { + if (_directConnectModule != null && _connectedDirectClients.TryGetBySecond(connectionId, out int directId)) + { + _directConnectModule.ServerSend(directId, segment, channelId); + } + else + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); + _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); + _clientSendBuffer.WriteInt(ref pos, _connectedRelayClients.GetBySecond(connectionId)); + + clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); + } + } + + public override void ServerStart() + { + if (!Available()) + { + Debug.Log("Not connected to relay! Server failed to start."); + return; + } + + if (_isClient || _isServer) + { + Debug.Log("Cannot host while already hosting or connected!"); + return; + } + + _isServer = true; + _connectedRelayClients = new BiDictionary(); + _currentMemberId = 1; + _connectedDirectClients = new BiDictionary(); + + var keys = new List(_serverProxies.GetAllKeys()); + + for (int i = 0; i < keys.Count; i++) + { + _serverProxies.GetByFirst(keys[i]).Dispose(); + _serverProxies.Remove(keys[i]); + } + + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.CreateRoom); + _clientSendBuffer.WriteInt(ref pos, maxServerPlayers); + _clientSendBuffer.WriteString(ref pos, serverName); + _clientSendBuffer.WriteBool(ref pos, isPublicServer); + _clientSendBuffer.WriteString(ref pos, extraServerData); + // If we have direct connect module, and our local IP isnt null, tell server. Only time local IP is null is on cellular networks, such as IOS and Android. + _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null ? GetLocalIp() != null ? true : false : false); + + if (_directConnectModule != null && GetLocalIp() != null) + { + _clientSendBuffer.WriteString(ref pos, GetLocalIp()); + // Transport port will be NAT port + 1 for the proxy connections. + _directConnectModule.StartServer(useNATPunch ? _NATIP.Port + 1 : -1); + } + else + _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); + + if (useNATPunch) + { + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteInt(ref pos, 0); + } + else + { + _clientSendBuffer.WriteBool(ref pos, false); + _clientSendBuffer.WriteInt(ref pos, _directConnectModule == null ? 1 : _directConnectModule.SupportsNATPunch() ? _directConnectModule.GetTransportPort() : 1); + } + + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + } + + public override void ServerStop() + { + if (_isServer) + { + _isServer = false; + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); + + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + + if (_directConnectModule != null) + _directConnectModule.StopServer(); + + var keys = new List(_serverProxies.GetAllKeys()); + + for (int i = 0; i < keys.Count; i++) + { + _serverProxies.GetByFirst(keys[i]).Dispose(); + _serverProxies.Remove(keys[i]); + } + } + } + + public override Uri ServerUri() + { + UriBuilder builder = new UriBuilder + { + Scheme = "LRM", + Host = serverId.ToString() + }; + + return builder.Uri; + } + + public override void Shutdown() + { + _isAuthenticated = false; + _isClient = false; + _isServer = false; + _connectedToRelay = false; + clientToServerTransport.Shutdown(); + } + } +} \ No newline at end of file diff --git a/UnityTransport/LRMTransport/LRMTransportRequests.cs b/UnityTransport/LRMTransport/LRMTransportRequests.cs new file mode 100644 index 0000000..6d6a19d --- /dev/null +++ b/UnityTransport/LRMTransport/LRMTransportRequests.cs @@ -0,0 +1,155 @@ +using Mirror; +using Newtonsoft.Json; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Networking; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + + public void RequestServerList() + { + if (_isAuthenticated && _connectedToRelay) + StartCoroutine(GetServerList()); + else + Debug.Log("You must be connected to Relay to request server list!"); + } + + IEnumerator RelayConnect() + { + string url = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/join/"; + + using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) + { + // Request and wait for the desired page. + yield return webRequest.SendWebRequest(); + var result = webRequest.downloadHandler.text; +#if UNITY_2020_1_OR_NEWER + switch (webRequest.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.DataProcessingError: + case UnityWebRequest.Result.ProtocolError: + Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); + break; + + case UnityWebRequest.Result.Success: + var parsedAddress = JsonConvert.DeserializeObject(result); + Connect(parsedAddress.Address, parsedAddress.Port); + endpointServerPort = parsedAddress.EndpointPort; + break; + } +#else + if (webRequest.isNetworkError || webRequest.isHttpError) + { + Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); + } + else + { + // join here + var parsedAddress = JsonConvert.DeserializeObject(result); + Connect(parsedAddress.Address, parsedAddress.Port); + endpointServerPort = parsedAddress.EndpointPort; + } +#endif + } + } + + IEnumerator GetServerList() + { + if (!useLoadBalancer) + { + string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers"; + + using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) + { + // Request and wait for the desired page. + yield return webRequest.SendWebRequest(); + var result = webRequest.downloadHandler.text; + +#if UNITY_2020_1_OR_NEWER + switch (webRequest.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.DataProcessingError: + case UnityWebRequest.Result.ProtocolError: + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + break; + + case UnityWebRequest.Result.Success: + relayServerList?.Clear(); + relayServerList = JsonConvert.DeserializeObject>(result.Decompress()); + serverListUpdated?.Invoke(); + break; + } +#else + if (webRequest.isNetworkError || webRequest.isHttpError) + { + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + } + else + { + relayServerList?.Clear(); + relayServerList = JsonConvert.DeserializeObject>(result.Decompress()); + serverListUpdated?.Invoke(); + } +#endif + } + } + else // get master list from load balancer + { + yield return StartCoroutine(RetrieveMasterServerListFromLoadBalancer()); + } + + } + + /// + /// Gets master list from the LB. + /// This can be optimized but for now it is it's + /// own separate method, so i can understand wtf is going on :D + /// + /// + IEnumerator RetrieveMasterServerListFromLoadBalancer() + { + string uri = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/masterlist/"; + + using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) + { + // Request and wait for the desired page. + yield return webRequest.SendWebRequest(); + var result = webRequest.downloadHandler.text; + +#if UNITY_2020_1_OR_NEWER + switch (webRequest.result) + { + case UnityWebRequest.Result.ConnectionError: + case UnityWebRequest.Result.DataProcessingError: + case UnityWebRequest.Result.ProtocolError: + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + break; + + case UnityWebRequest.Result.Success: + relayServerList?.Clear(); + relayServerList = JsonConvert.DeserializeObject>(result); + serverListUpdated?.Invoke(); + break; + } +#else + if (webRequest.isNetworkError || webRequest.isHttpError) + { + Debug.LogWarning("LRM | Network Error while retreiving the server list!"); + } + else + { + relayServerList?.Clear(); + relayServerList = JsonConvert.DeserializeObject>(result); + serverListUpdated?.Invoke(); + } +#endif + } + } + } +} \ No newline at end of file diff --git a/UnityTransport/LRMTransport/LRMTransportVariables.cs b/UnityTransport/LRMTransport/LRMTransportVariables.cs new file mode 100644 index 0000000..fb9c0ec --- /dev/null +++ b/UnityTransport/LRMTransport/LRMTransportVariables.cs @@ -0,0 +1,63 @@ +using Mirror; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using UnityEngine.Events; + +namespace LightReflectiveMirror +{ + public partial class LightReflectiveMirrorTransport : Transport + { + // Connection/auth variables + public Transport clientToServerTransport; + public string serverIP = null; + public ushort endpointServerPort = 8080; + public float heartBeatInterval = 3; + public bool connectOnAwake = true; + public string authenticationKey = "Secret Auth Key"; + public UnityEvent diconnectedFromRelay; + + // NAT Puncher variables + public bool useNATPunch = false; + public int NATPunchtroughPort = -1; + + // LLB variables (LRM Load Balancer) + public bool useLoadBalancer = false; + public ushort loadBalancerPort = 7070; + public string loadBalancerAddress = null; + + // Server hosting variables + public string serverName = "My awesome server!"; + public string extraServerData = "Map 1"; + public int maxServerPlayers = 10; + public bool isPublicServer = true; + + // Server list variables + public UnityEvent serverListUpdated; + public List relayServerList { private set; get; } = new List(); + + // Current Server Information + public int serverId = -1; + + private LRMDirectConnectModule _directConnectModule; + + private byte[] _clientSendBuffer; + private bool _connectedToRelay = false; + private bool _isClient = false; + private bool _isServer = false; + private bool _directConnected = false; + private bool _isAuthenticated = false; + private int _currentMemberId; + private bool _callbacksInitialized = false; + private int _cachedHostID; + private UdpClient _NATPuncher; + private IPEndPoint _NATIP; + private IPEndPoint _relayPuncherIP; + private byte[] _punchData = new byte[1] { 1 }; + private IPEndPoint _directConnectEndpoint; + private SocketProxy _clientProxy; + private BiDictionary _serverProxies = new BiDictionary(); + private BiDictionary _connectedRelayClients = new BiDictionary(); + private BiDictionary _connectedDirectClients = new BiDictionary(); + } +} \ No newline at end of file diff --git a/UnityTransport/LRMTransport/LightReflectiveMirrorTransport.cs b/UnityTransport/LRMTransport/LightReflectiveMirrorTransport.cs new file mode 100644 index 0000000..3dfd34b --- /dev/null +++ b/UnityTransport/LRMTransport/LightReflectiveMirrorTransport.cs @@ -0,0 +1,371 @@ +using kcp2k; +using Mirror; +using Mirror.SimpleWeb; +using Newtonsoft.Json; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.Networking; + +namespace LightReflectiveMirror +{ + [DefaultExecutionOrder(1001)] + public partial class LightReflectiveMirrorTransport : Transport + { + private void OnConnectedToRelay() => _connectedToRelay = true; + public bool IsAuthenticated() => _isAuthenticated; + private void Awake() + { + if (clientToServerTransport is LightReflectiveMirrorTransport) + throw new Exception("Haha real funny... Use a different transport."); + + _directConnectModule = GetComponent(); + + if (_directConnectModule != null) + { + if (useNATPunch && !_directConnectModule.SupportsNATPunch()) + { + Debug.LogWarning("LRM | NATPunch is turned on but the transport used does not support it. It will be disabled."); + useNATPunch = false; + } + } + + SetupCallbacks(); + + if (connectOnAwake) + ConnectToRelay(); + + InvokeRepeating(nameof(SendHeartbeat), heartBeatInterval, heartBeatInterval); + } + + private void SetupCallbacks() + { + if (_callbacksInitialized) + return; + + _callbacksInitialized = true; + clientToServerTransport.OnClientConnected = OnConnectedToRelay; + clientToServerTransport.OnClientDataReceived = DataReceived; + clientToServerTransport.OnClientDisconnected = Disconnected; + } + + void Disconnected() + { + _connectedToRelay = false; + _isAuthenticated = false; + diconnectedFromRelay?.Invoke(); + } + + public void ConnectToRelay() + { + if (!useLoadBalancer) + { + if (!_connectedToRelay) + { + Connect(serverIP); + } + else + { + Debug.LogWarning("LRM | Already connected to relay!"); + } + } + else + { + if (!_connectedToRelay) + { + StartCoroutine(RelayConnect()); + } + else + { + Debug.LogWarning("LRM | Already connected to relay!"); + } + } + } + + /// + /// Connects to the desired relay + /// + /// + private void Connect(string serverIP, ushort port = 7777) + { + // need to implement custom port + if (clientToServerTransport is LightReflectiveMirrorTransport) + throw new Exception("LRM | Client to Server Transport cannot be LRM."); + + SetTransportPort(port); + + _clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()]; + clientToServerTransport.ClientConnect(serverIP); + } + + void SendHeartbeat() + { + if (_connectedToRelay) + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, 200); + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + + if (_NATPuncher != null) + _NATPuncher.Send(new byte[] { 0 }, 1, _relayPuncherIP); + + var keys = new List(_serverProxies.GetAllKeys()); + + for (int i = 0; i < keys.Count; i++) + { + if (DateTime.Now.Subtract(_serverProxies.GetByFirst(keys[i]).lastInteractionTime).TotalSeconds > 10) + { + _serverProxies.GetByFirst(keys[i]).Dispose(); + _serverProxies.Remove(keys[i]); + } + } + } + } + + void DataReceived(ArraySegment segmentData, int channel) + { + try + { + var data = segmentData.Array; + int pos = segmentData.Offset; + + OpCodes opcode = (OpCodes)data.ReadByte(ref pos); + + switch (opcode) + { + case OpCodes.Authenticated: + _isAuthenticated = true; + break; + case OpCodes.AuthenticationRequest: + SendAuthKey(); + break; + case OpCodes.GetData: + var recvData = data.ReadBytes(ref pos); + + if (_isServer) + { + if (_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID)) + OnServerDataReceived?.Invoke(clientID, new ArraySegment(recvData), channel); + } + + if (_isClient) + OnClientDataReceived?.Invoke(new ArraySegment(recvData), channel); + break; + case OpCodes.ServerLeft: + if (_isClient) + { + _isClient = false; + OnClientDisconnected?.Invoke(); + } + break; + case OpCodes.PlayerDisconnected: + if (_isServer) + { + int user = data.ReadInt(ref pos); + if (_connectedRelayClients.TryGetByFirst(user, out int clientID)) + { + OnServerDisconnected?.Invoke(clientID); + _connectedRelayClients.Remove(user); + } + } + break; + case OpCodes.RoomCreated: + serverId = data.ReadInt(ref pos); + break; + case OpCodes.ServerJoined: + int clientId = data.ReadInt(ref pos); + if (_isClient) + { + OnClientConnected?.Invoke(); + } + if (_isServer) + { + _connectedRelayClients.Add(clientId, _currentMemberId); + OnServerConnected?.Invoke(_currentMemberId); + _currentMemberId++; + } + break; + case OpCodes.DirectConnectIP: + var ip = data.ReadString(ref pos); + int port = data.ReadInt(ref pos); + bool attemptNatPunch = data.ReadBool(ref pos); + + _directConnectEndpoint = new IPEndPoint(IPAddress.Parse(ip), port); + + if (useNATPunch && attemptNatPunch) + { + StartCoroutine(NATPunch(_directConnectEndpoint)); + } + + if (!_isServer) + { + if (_clientProxy == null && useNATPunch && attemptNatPunch) + { + _clientProxy = new SocketProxy(_NATIP.Port - 1); + _clientProxy.dataReceived += ClientProcessProxyData; + } + + if (useNATPunch && attemptNatPunch) + _directConnectModule.JoinServer("127.0.0.1", _NATIP.Port - 1); + else + _directConnectModule.JoinServer(ip, port); + } + + break; + case OpCodes.RequestNATConnection: + if (GetLocalIp() != null && _directConnectModule != null) + { + byte[] initalData = new byte[150]; + int sendPos = 0; + + initalData.WriteBool(ref sendPos, true); + initalData.WriteString(ref sendPos, data.ReadString(ref pos)); + NATPunchtroughPort = data.ReadInt(ref pos); + + if (_NATPuncher == null) + { + _NATPuncher = new UdpClient { ExclusiveAddressUse = false }; + while (true) + { + try + { + _NATIP = new IPEndPoint(IPAddress.Parse(GetLocalIp()), UnityEngine.Random.Range(16000, 17000)); + _NATPuncher.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + _NATPuncher.Client.Bind(_NATIP); + break; + } + catch {} // Binding port is in use, keep trying :P + } + } + + IPAddress serverAddr; + + if (!IPAddress.TryParse(serverIP, out serverAddr)) + serverAddr = Dns.GetHostEntry(serverIP).AddressList[0]; + + _relayPuncherIP = new IPEndPoint(IPAddress.Parse(serverIP), NATPunchtroughPort); + + // Send 3 to lower chance of it being dropped or corrupted when received on server. + _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); + _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); + _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); + _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); + } + break; + } + } + catch (Exception e) { print(e); } + } + + public void SetTransportPort(ushort port) + { + if (clientToServerTransport is KcpTransport kcp) + kcp.Port = port; + + if (clientToServerTransport is TelepathyTransport telepathy) + telepathy.port = port; + + if (clientToServerTransport is SimpleWebTransport swt) + swt.port = port; + } + + public void UpdateRoomInfo(string newServerName = null, string newServerData = null, bool? newServerIsPublic = null, int? newPlayerCap = null) + { + if (_isServer) + { + int pos = 0; + + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); + + if (!string.IsNullOrEmpty(newServerName)) + { + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteString(ref pos, newServerName); + } + else + _clientSendBuffer.WriteBool(ref pos, false); + + if (!string.IsNullOrEmpty(newServerData)) + { + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteString(ref pos, newServerData); + } + else + _clientSendBuffer.WriteBool(ref pos, false); + + if (newServerIsPublic != null) + { + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteBool(ref pos, newServerIsPublic.Value); + } + else + _clientSendBuffer.WriteBool(ref pos, false); + + if (newPlayerCap != null) + { + _clientSendBuffer.WriteBool(ref pos, true); + _clientSendBuffer.WriteInt(ref pos, newPlayerCap.Value); + } + else + _clientSendBuffer.WriteBool(ref pos, false); + + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + } + } + + void SendAuthKey() + { + int pos = 0; + _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationResponse); + _clientSendBuffer.WriteString(ref pos, authenticationKey); + clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); + } + + public enum OpCodes + { + Default = 0, RequestID = 1, JoinServer = 2, SendData = 3, GetID = 4, ServerJoined = 5, GetData = 6, CreateRoom = 7, ServerLeft = 8, PlayerDisconnected = 9, RoomCreated = 10, + LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20, + DirectConnectIP = 21 + } + + private static string GetLocalIp() + { + var host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (var ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + return ip.ToString(); + } + } + + return null; + } + } + + [Serializable] + public struct Room + { + public string serverName; + public int currentPlayers; + public int maxPlayers; + public int serverId; + public string serverData; + + public RelayAddress relayInfo; + } + + [Serializable] + public struct RelayAddress + { + public ushort Port; + public ushort EndpointPort; + public string Address; + } + +} diff --git a/UnityTransport/LightReflectiveMirrorTransport.cs b/UnityTransport/LightReflectiveMirrorTransport.cs deleted file mode 100644 index a8e93cf..0000000 --- a/UnityTransport/LightReflectiveMirrorTransport.cs +++ /dev/null @@ -1,928 +0,0 @@ -using kcp2k; -using Mirror; -using Mirror.SimpleWeb; -using Newtonsoft.Json; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using UnityEngine; -using UnityEngine.Events; -using UnityEngine.Networking; - -namespace LightReflectiveMirror -{ - [DefaultExecutionOrder(1001)] - public class LightReflectiveMirrorTransport : Transport - { - [Header("Connection Variables")] - public Transport clientToServerTransport; - public string serverIP = null; - public ushort endpointServerPort = 8080; - public float heartBeatInterval = 3; - public bool connectOnAwake = true; - public string authenticationKey = "Secret Auth Key"; - public UnityEvent diconnectedFromRelay; - - [Header("NAT Punchthrough")] - [Help("NAT Punchthrough will require the Direct Connect module attached.")] - public bool useNATPunch = false; - public int NATPunchtroughPort = -1; - - [Header("Load Balancer")] - public bool useLoadBalancer = false; - public ushort loadBalancerPort = 7070; - public string loadBalancerAddress = null; - - [Header("Server Hosting Data")] - public string serverName = "My awesome server!"; - public string extraServerData = "Map 1"; - public int maxServerPlayers = 10; - public bool isPublicServer = true; - - [Header("Server List")] - public UnityEvent serverListUpdated; - public List relayServerList { private set; get; } = new List(); - - [Header("Server Information")] - public int serverId = -1; - - private LRMDirectConnectModule _directConnectModule; - - private byte[] _clientSendBuffer; - private bool _connectedToRelay = false; - private bool _isClient = false; - private bool _isServer = false; - private bool _directConnected = false; - private bool _isAuthenticated = false; - private int _currentMemberId; - private bool _callbacksInitialized = false; - private int _cachedHostID; - private UdpClient _NATPuncher; - private IPEndPoint _NATIP; - private IPEndPoint _relayPuncherIP; - private byte[] _punchData = new byte[1] { 1 }; - private IPEndPoint _directConnectEndpoint; - private SocketProxy _clientProxy; - private BiDictionary _serverProxies = new BiDictionary(); - private BiDictionary _connectedRelayClients = new BiDictionary(); - private BiDictionary _connectedDirectClients = new BiDictionary(); - - public override bool ClientConnected() => _isClient; - private void OnConnectedToRelay() => _connectedToRelay = true; - public bool IsAuthenticated() => _isAuthenticated; - public override bool ServerActive() => _isServer; - public override bool Available() => _connectedToRelay; - public override void ClientConnect(Uri uri) => ClientConnect(uri.Host); - public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId); - - public override string ServerGetClientAddress(int connectionId) - { - if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) - return relayId.ToString(); - - if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - return "DIRECT-" + directId; - - // Shouldn't ever get here. - return "?"; - } - - public override void ClientEarlyUpdate() - { - clientToServerTransport.ClientEarlyUpdate(); - - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ClientEarlyUpdate(); - } - - public override void ClientLateUpdate() - { - clientToServerTransport.ClientLateUpdate(); - - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ClientLateUpdate(); - } - - public override void ServerEarlyUpdate() - { - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ServerEarlyUpdate(); - } - - void RecvData(IAsyncResult result) - { - IPEndPoint newClientEP = new IPEndPoint(IPAddress.Any, 0); - var data = _NATPuncher.EndReceive(result, ref newClientEP); - _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); - - if (!newClientEP.Address.Equals(_relayPuncherIP.Address)) - { - if (_isServer) - { - if (_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy)) - { - if (data.Length > 2) - foundProxy.RelayData(data, data.Length); - } - else - { - _serverProxies.Add(newClientEP, new SocketProxy(_NATIP.Port + 1, newClientEP)); - _serverProxies.GetByFirst(newClientEP).dataReceived += ServerProcessProxyData; - } - } - - if (_isClient) - { - if (_clientProxy == null) - { - _clientProxy = new SocketProxy(_NATIP.Port - 1); - _clientProxy.dataReceived += ClientProcessProxyData; - } - else - { - _clientProxy.ClientRelayData(data, data.Length); - } - } - } - } - - void ServerProcessProxyData(IPEndPoint remoteEndpoint, byte[] data) - { - _NATPuncher.Send(data, data.Length, remoteEndpoint); - } - - void ClientProcessProxyData(IPEndPoint _, byte[] data) - { - _NATPuncher.Send(data, data.Length, _directConnectEndpoint); - } - - public override void ServerLateUpdate() - { - if (_directConnectModule != null) - _directConnectModule.directConnectTransport.ServerLateUpdate(); - } - - private void Awake() - { - if (clientToServerTransport is LightReflectiveMirrorTransport) - throw new Exception("Haha real funny... Use a different transport."); - - _directConnectModule = GetComponent(); - - if (_directConnectModule != null) - { - if (useNATPunch && !_directConnectModule.SupportsNATPunch()) - { - Debug.LogWarning("LRM | NATPunch is turned on but the transport used does not support it. It will be disabled."); - useNATPunch = false; - } - } - - SetupCallbacks(); - - if (connectOnAwake) - ConnectToRelay(); - - InvokeRepeating(nameof(SendHeartbeat), heartBeatInterval, heartBeatInterval); - } - - private void SetupCallbacks() - { - if (_callbacksInitialized) - return; - - _callbacksInitialized = true; - clientToServerTransport.OnClientConnected = OnConnectedToRelay; - clientToServerTransport.OnClientDataReceived = DataReceived; - clientToServerTransport.OnClientDisconnected = Disconnected; - } - - void Disconnected() - { - _connectedToRelay = false; - _isAuthenticated = false; - diconnectedFromRelay?.Invoke(); - } - - public void ConnectToRelay() - { - if (!useLoadBalancer) - { - if (!_connectedToRelay) - { - Connect(serverIP); - } - else - { - Debug.LogWarning("LRM | Already connected to relay!"); - } - } - else - { - if (!_connectedToRelay) - { - StartCoroutine(RelayConnect()); - } - else - { - Debug.LogWarning("LRM | Already connected to relay!"); - } - } - } - - /// - /// Connects to the desired relay - /// - /// - private void Connect(string serverIP, ushort port = 7777) - { - // need to implement custom port - if (clientToServerTransport is LightReflectiveMirrorTransport) - throw new Exception("LRM | Client to Server Transport cannot be LRM."); - - if (clientToServerTransport is kcp2k.KcpTransport kcp) - { - kcp.Port = port; - } - - _clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()]; - clientToServerTransport.ClientConnect(serverIP); - } - - IEnumerator RelayConnect() - { - string url = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/join/"; - - using (UnityWebRequest webRequest = UnityWebRequest.Get(url)) - { - // Request and wait for the desired page. - yield return webRequest.SendWebRequest(); - var result = webRequest.downloadHandler.text; -#if UNITY_2020_1_OR_NEWER - switch (webRequest.result) - { - case UnityWebRequest.Result.ConnectionError: - case UnityWebRequest.Result.DataProcessingError: - case UnityWebRequest.Result.ProtocolError: - Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); - break; - - case UnityWebRequest.Result.Success: - var parsedAddress = JsonConvert.DeserializeObject(result); - Connect(parsedAddress.Address, parsedAddress.Port); - endpointServerPort = parsedAddress.EndpointPort; - break; - } -#else - if (webRequest.isNetworkError || webRequest.isHttpError) - { - Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer."); - } - else - { - // join here - var parsedAddress = JsonConvert.DeserializeObject(result); - Connect(parsedAddress.Address, parsedAddress.Port); - endpointServerPort = parsedAddress.EndpointPort; - } -#endif - } - } - - void SendHeartbeat() - { - if (_connectedToRelay) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, 200); - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - - if (_NATPuncher != null) - _NATPuncher.Send(new byte[] { 0 }, 1, _relayPuncherIP); - - var keys = new List(_serverProxies.GetAllKeys()); - - for (int i = 0; i < keys.Count; i++) - { - if (DateTime.Now.Subtract(_serverProxies.GetByFirst(keys[i]).lastInteractionTime).TotalSeconds > 10) - { - _serverProxies.GetByFirst(keys[i]).Dispose(); - _serverProxies.Remove(keys[i]); - } - } - } - } - - public void RequestServerList() - { - if (_isAuthenticated && _connectedToRelay) - StartCoroutine(GetServerList()); - else - Debug.Log("You must be connected to Relay to request server list!"); - } - - IEnumerator NATPunch(IPEndPoint remoteAddress) - { - for (int i = 0; i < 10; i++) - { - _NATPuncher.Send(_punchData, 1, remoteAddress); - yield return new WaitForSeconds(0.25f); - } - } - - void DataReceived(ArraySegment segmentData, int channel) - { - try - { - var data = segmentData.Array; - int pos = segmentData.Offset; - - OpCodes opcode = (OpCodes)data.ReadByte(ref pos); - - switch (opcode) - { - case OpCodes.Authenticated: - _isAuthenticated = true; - break; - case OpCodes.AuthenticationRequest: - SendAuthKey(); - break; - case OpCodes.GetData: - var recvData = data.ReadBytes(ref pos); - - if (_isServer) - { - if (_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID)) - OnServerDataReceived?.Invoke(clientID, new ArraySegment(recvData), channel); - } - - if (_isClient) - OnClientDataReceived?.Invoke(new ArraySegment(recvData), channel); - break; - case OpCodes.ServerLeft: - if (_isClient) - { - _isClient = false; - OnClientDisconnected?.Invoke(); - } - break; - case OpCodes.PlayerDisconnected: - if (_isServer) - { - int user = data.ReadInt(ref pos); - if (_connectedRelayClients.TryGetByFirst(user, out int clientID)) - { - OnServerDisconnected?.Invoke(clientID); - _connectedRelayClients.Remove(user); - } - } - break; - case OpCodes.RoomCreated: - serverId = data.ReadInt(ref pos); - break; - case OpCodes.ServerJoined: - int clientId = data.ReadInt(ref pos); - if (_isClient) - { - OnClientConnected?.Invoke(); - } - if (_isServer) - { - _connectedRelayClients.Add(clientId, _currentMemberId); - OnServerConnected?.Invoke(_currentMemberId); - _currentMemberId++; - } - break; - case OpCodes.DirectConnectIP: - var ip = data.ReadString(ref pos); - int port = data.ReadInt(ref pos); - bool attemptNatPunch = data.ReadBool(ref pos); - - _directConnectEndpoint = new IPEndPoint(IPAddress.Parse(ip), port); - - if (useNATPunch && attemptNatPunch) - { - StartCoroutine(NATPunch(_directConnectEndpoint)); - } - - if (!_isServer) - { - if (_clientProxy == null && useNATPunch && attemptNatPunch) - { - _clientProxy = new SocketProxy(_NATIP.Port - 1); - _clientProxy.dataReceived += ClientProcessProxyData; - } - - if (useNATPunch && attemptNatPunch) - _directConnectModule.JoinServer("127.0.0.1", _NATIP.Port - 1); - else - _directConnectModule.JoinServer(ip, port); - } - - break; - case OpCodes.RequestNATConnection: - if (GetLocalIp() != null && _directConnectModule != null) - { - byte[] initalData = new byte[150]; - int sendPos = 0; - - initalData.WriteBool(ref sendPos, true); - initalData.WriteString(ref sendPos, data.ReadString(ref pos)); - NATPunchtroughPort = data.ReadInt(ref pos); - - if (_NATPuncher == null) - { - _NATPuncher = new UdpClient { ExclusiveAddressUse = false }; - while (true) - { - try - { - _NATIP = new IPEndPoint(IPAddress.Parse(GetLocalIp()), UnityEngine.Random.Range(16000, 17000)); - _NATPuncher.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - _NATPuncher.Client.Bind(_NATIP); - break; - } - catch {} // Binding port is in use, keep trying :P - } - } - - IPAddress serverAddr; - - if (!IPAddress.TryParse(serverIP, out serverAddr)) - serverAddr = Dns.GetHostEntry(serverIP).AddressList[0]; - - _relayPuncherIP = new IPEndPoint(IPAddress.Parse(serverIP), NATPunchtroughPort); - - // Send 3 to lower chance of it being dropped or corrupted when received on server. - _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); - _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); - _NATPuncher.Send(initalData, sendPos, _relayPuncherIP); - _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); - } - break; - } - } - catch (Exception e) { print(e); } - } - - public void SetTransportPort(ushort port) - { - if (clientToServerTransport is KcpTransport kcp) - kcp.Port = port; - - if (clientToServerTransport is TelepathyTransport telepathy) - telepathy.port = port; - - if (clientToServerTransport is SimpleWebTransport swt) - swt.port = port; - } - - IEnumerator GetServerList() - { - if (!useLoadBalancer) - { - string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers"; - - using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) - { - // Request and wait for the desired page. - yield return webRequest.SendWebRequest(); - var result = webRequest.downloadHandler.text; - -#if UNITY_2020_1_OR_NEWER - switch (webRequest.result) - { - case UnityWebRequest.Result.ConnectionError: - case UnityWebRequest.Result.DataProcessingError: - case UnityWebRequest.Result.ProtocolError: - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - break; - - case UnityWebRequest.Result.Success: - relayServerList?.Clear(); - relayServerList = JsonConvert.DeserializeObject>(result.Decompress()); - serverListUpdated?.Invoke(); - break; - } -#else - if (webRequest.isNetworkError || webRequest.isHttpError) - { - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - } - else - { - relayServerList?.Clear(); - relayServerList = JsonConvert.DeserializeObject>(result.Decompress()); - serverListUpdated?.Invoke(); - } -#endif - } - } - else // get master list from load balancer - { - yield return StartCoroutine(RetrieveMasterServerListFromLoadBalancer()); - } - - } - - /// - /// Gets master list from the LB. - /// This can be optimized but for now it is it's - /// own separate method, so i can understand wtf is going on :D - /// - /// - IEnumerator RetrieveMasterServerListFromLoadBalancer() - { - string uri = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/masterlist/"; - - using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) - { - // Request and wait for the desired page. - yield return webRequest.SendWebRequest(); - var result = webRequest.downloadHandler.text; - -#if UNITY_2020_1_OR_NEWER - switch (webRequest.result) - { - case UnityWebRequest.Result.ConnectionError: - case UnityWebRequest.Result.DataProcessingError: - case UnityWebRequest.Result.ProtocolError: - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - break; - - case UnityWebRequest.Result.Success: - relayServerList?.Clear(); - relayServerList = JsonConvert.DeserializeObject>(result); - serverListUpdated?.Invoke(); - break; - } -#else - if (webRequest.isNetworkError || webRequest.isHttpError) - { - Debug.LogWarning("LRM | Network Error while retreiving the server list!"); - } - else - { - relayServerList?.Clear(); - relayServerList = JsonConvert.DeserializeObject>(result); - serverListUpdated?.Invoke(); - } -#endif - } - } - - public void UpdateRoomInfo(string newServerName = null, string newServerData = null, bool? newServerIsPublic = null, int? newPlayerCap = null) - { - if (_isServer) - { - int pos = 0; - - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.UpdateRoomData); - - if (!string.IsNullOrEmpty(newServerName)) - { - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteString(ref pos, newServerName); - } - else - _clientSendBuffer.WriteBool(ref pos, false); - - if (!string.IsNullOrEmpty(newServerData)) - { - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteString(ref pos, newServerData); - } - else - _clientSendBuffer.WriteBool(ref pos, false); - - if (newServerIsPublic != null) - { - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteBool(ref pos, newServerIsPublic.Value); - } - else - _clientSendBuffer.WriteBool(ref pos, false); - - if (newPlayerCap != null) - { - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteInt(ref pos, newPlayerCap.Value); - } - else - _clientSendBuffer.WriteBool(ref pos, false); - - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - } - } - - void SendAuthKey() - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.AuthenticationResponse); - _clientSendBuffer.WriteString(ref pos, authenticationKey); - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - } - - public override void ClientConnect(string address) - { - if (!Available() || !int.TryParse(address, out _cachedHostID)) - { - Debug.Log("Not connected to relay or invalid server id!"); - OnClientDisconnected?.Invoke(); - return; - } - - if (_isClient || _isServer) - throw new Exception("Cannot connect while hosting/already connected!"); - - int pos = 0; - _directConnected = false; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); - _clientSendBuffer.WriteInt(ref pos, _cachedHostID); - _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null); - - if (GetLocalIp() == null) - _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); - else - _clientSendBuffer.WriteString(ref pos, GetLocalIp()); - - _isClient = true; - - clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); - } - - public override void ClientDisconnect() - { - _isClient = false; - - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); - - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - - if (_directConnectModule != null) - _directConnectModule.ClientDisconnect(); - } - - public override void ClientSend(int channelId, ArraySegment segment) - { - if (_directConnected) - { - _directConnectModule.ClientSend(segment, channelId); - } - else - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); - _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); - _clientSendBuffer.WriteInt(ref pos, 0); - - clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); - } - } - - public override bool ServerDisconnect(int connectionId) - { - if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.KickPlayer); - _clientSendBuffer.WriteInt(ref pos, relayId); - return true; - } - - if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - return _directConnectModule.KickClient(directId); - - return false; - } - - public override void ServerSend(int connectionId, int channelId, ArraySegment segment) - { - if (_directConnectModule != null && _connectedDirectClients.TryGetBySecond(connectionId, out int directId)) - { - _directConnectModule.ServerSend(directId, segment, channelId); - } - else - { - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.SendData); - _clientSendBuffer.WriteBytes(ref pos, segment.Array.Take(segment.Count).ToArray()); - _clientSendBuffer.WriteInt(ref pos, _connectedRelayClients.GetBySecond(connectionId)); - - clientToServerTransport.ClientSend(channelId, new ArraySegment(_clientSendBuffer, 0, pos)); - } - } - - public override void ServerStart() - { - if (!Available()) - { - Debug.Log("Not connected to relay! Server failed to start."); - return; - } - - if (_isClient || _isServer) - { - Debug.Log("Cannot host while already hosting or connected!"); - return; - } - - _isServer = true; - _connectedRelayClients = new BiDictionary(); - _currentMemberId = 1; - _connectedDirectClients = new BiDictionary(); - - var keys = new List(_serverProxies.GetAllKeys()); - - for (int i = 0; i < keys.Count; i++) - { - _serverProxies.GetByFirst(keys[i]).Dispose(); - _serverProxies.Remove(keys[i]); - } - - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.CreateRoom); - _clientSendBuffer.WriteInt(ref pos, maxServerPlayers); - _clientSendBuffer.WriteString(ref pos, serverName); - _clientSendBuffer.WriteBool(ref pos, isPublicServer); - _clientSendBuffer.WriteString(ref pos, extraServerData); - // If we have direct connect module, and our local IP isnt null, tell server. Only time local IP is null is on cellular networks, such as IOS and Android. - _clientSendBuffer.WriteBool(ref pos, _directConnectModule != null ? GetLocalIp() != null ? true : false : false); - - if (_directConnectModule != null && GetLocalIp() != null) - { - _clientSendBuffer.WriteString(ref pos, GetLocalIp()); - // Transport port will be NAT port + 1 for the proxy connections. - _directConnectModule.StartServer(useNATPunch ? _NATIP.Port + 1 : -1); - } - else - _clientSendBuffer.WriteString(ref pos, "0.0.0.0"); - - if (useNATPunch) - { - _clientSendBuffer.WriteBool(ref pos, true); - _clientSendBuffer.WriteInt(ref pos, 0); - } - else - { - _clientSendBuffer.WriteBool(ref pos, false); - _clientSendBuffer.WriteInt(ref pos, _directConnectModule == null ? 1 : _directConnectModule.SupportsNATPunch() ? _directConnectModule.GetTransportPort() : 1); - } - - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - } - - public override void ServerStop() - { - if (_isServer) - { - _isServer = false; - int pos = 0; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.LeaveRoom); - - clientToServerTransport.ClientSend(0, new ArraySegment(_clientSendBuffer, 0, pos)); - - if (_directConnectModule != null) - _directConnectModule.StopServer(); - - var keys = new List(_serverProxies.GetAllKeys()); - - for (int i = 0; i < keys.Count; i++) - { - _serverProxies.GetByFirst(keys[i]).Dispose(); - _serverProxies.Remove(keys[i]); - } - } - } - - public override Uri ServerUri() - { - UriBuilder builder = new UriBuilder - { - Scheme = "LRM", - Host = serverId.ToString() - }; - - return builder.Uri; - } - - public override void Shutdown() - { - _isAuthenticated = false; - _isClient = false; - _isServer = false; - _connectedToRelay = false; - clientToServerTransport.Shutdown(); - } - - public enum OpCodes - { - Default = 0, RequestID = 1, JoinServer = 2, SendData = 3, GetID = 4, ServerJoined = 5, GetData = 6, CreateRoom = 7, ServerLeft = 8, PlayerDisconnected = 9, RoomCreated = 10, - LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20, - DirectConnectIP = 21 - } - - private static string GetLocalIp() - { - var host = Dns.GetHostEntry(Dns.GetHostName()); - foreach (var ip in host.AddressList) - { - if (ip.AddressFamily == AddressFamily.InterNetwork) - { - return ip.ToString(); - } - } - - return null; - } - - #region Direct Connect Module - public void DirectAddClient(int clientID) - { - if (!_isServer) - return; - - _connectedDirectClients.Add(clientID, _currentMemberId); - OnServerConnected?.Invoke(_currentMemberId); - _currentMemberId++; - } - - public void DirectRemoveClient(int clientID) - { - if (!_isServer) - return; - - OnServerDisconnected?.Invoke(_connectedDirectClients.GetByFirst(clientID)); - _connectedDirectClients.Remove(clientID); - } - - public void DirectReceiveData(ArraySegment data, int channel, int clientID = -1) - { - if (_isServer) - OnServerDataReceived?.Invoke(_connectedDirectClients.GetByFirst(clientID), data, channel); - - if (_isClient) - OnClientDataReceived?.Invoke(data, channel); - } - - public void DirectClientConnected() - { - _directConnected = true; - OnClientConnected?.Invoke(); - } - - public void DirectDisconnected() - { - if (_directConnected) - { - _isClient = false; - _directConnected = false; - OnClientDisconnected?.Invoke(); - } - else - { - int pos = 0; - _directConnected = false; - _clientSendBuffer.WriteByte(ref pos, (byte)OpCodes.JoinServer); - _clientSendBuffer.WriteInt(ref pos, _cachedHostID); - _clientSendBuffer.WriteBool(ref pos, false); // Direct failed, use relay - - _isClient = true; - - clientToServerTransport.ClientSend(0, new System.ArraySegment(_clientSendBuffer, 0, pos)); - } - - if (_clientProxy != null) - { - _clientProxy.Dispose(); - _clientProxy = null; - } - } - #endregion - } - - [Serializable] - public struct Room - { - public string serverName; - public int currentPlayers; - public int maxPlayers; - public int serverId; - public string serverData; - - public RelayAddress relayInfo; - } - - [Serializable] - public struct RelayAddress - { - public ushort Port; - public ushort EndpointPort; - public string Address; - } - -}