diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/BiDictionary.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/BiDictionary.cs new file mode 100644 index 0000000..1bdd4b8 --- /dev/null +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/BiDictionary.cs @@ -0,0 +1,51 @@ +// +// Source: https://stackoverflow.com/questions/255341/getting-multiple-keys-of-specified-value-of-a-generic-dictionary#255630 +// +using System; +using System.Collections.Generic; + +namespace LightReflectiveMirror +{ + class BiDictionary + { + IDictionary firstToSecond = new Dictionary(); + IDictionary secondToFirst = new Dictionary(); + + public void Add(TFirst first, TSecond second) + { + if (firstToSecond.ContainsKey(first) || + secondToFirst.ContainsKey(second)) + { + throw new ArgumentException("Duplicate first or second"); + } + firstToSecond.Add(first, second); + secondToFirst.Add(second, first); + } + + public bool TryGetByFirst(TFirst first, out TSecond second) + { + return firstToSecond.TryGetValue(first, out second); + } + + public void Remove(TFirst first) + { + secondToFirst.Remove(firstToSecond[first]); + firstToSecond.Remove(first); + } + + public bool TryGetBySecond(TSecond second, out TFirst first) + { + return secondToFirst.TryGetValue(second, out first); + } + + public TSecond GetByFirst(TFirst first) + { + return firstToSecond[first]; + } + + public TFirst GetBySecond(TSecond second) + { + return secondToFirst[second]; + } + } +} \ No newline at end of file diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/Config.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/Config.cs index 7e54ad1..6e07a41 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/Config.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/Config.cs @@ -11,8 +11,10 @@ namespace LightReflectiveMirror public string AuthenticationKey = "Secret Auth Key"; public int UpdateLoopTime = 10; public int UpdateHeartbeatInterval = 100; - public bool UseEndpoint = false; - public ushort EndpointPort = 6969; - public bool EndpointServerList = false; + public bool UseEndpoint = true; + public ushort EndpointPort = 8080; + public bool EndpointServerList = true; + public bool EnableNATPunchtroughServer = true; + public ushort NATPunchtroughPort = 7776; } } diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/Endpoint.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/Endpoint.cs index 459db41..1518683 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/Endpoint.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/Endpoint.cs @@ -1,4 +1,7 @@ using Grapevine; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Threading.Tasks; @@ -49,26 +52,29 @@ namespace LightReflectiveMirror.Endpoints public class EndpointServer { - public bool Start(ushort port = 6969) + public bool Start(ushort port = 8080) { try { - var server = RestServerBuilder.UseDefaults().Build(); - server.Prefixes.Remove($"http://localhost:{1234}/"); - server.Prefixes.Add($"http://*:{port}/"); - server.Router.Options.SendExceptionMessages = false; + var config = new ConfigurationBuilder() + .SetBasePath(System.IO.Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .Build(); - server.AfterStarting += (s) => + Action configServices = (services) => { - string startup = @" -******************************************************************************** -* Endpoint Server listening on "+$"{string.Join(", ", server.Prefixes)}" + @" -* Be sure to Port Forward! :^) -******************************************************************************** -"; - Console.WriteLine(startup); + services.AddLogging(configure => configure.AddConsole()); + services.Configure(options => options.MinLevel = LogLevel.None); }; + Action configServer = (server) => + { + server.Prefixes.Add($"http://*:{port}/"); + }; + + var server = new RestServerBuilder(new ServiceCollection(), config, configServices, configServer).Build(); + server.Router.Options.SendExceptionMessages = false; + server.Start(); return true; diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/Program.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/Program.cs index e9ae165..1cae8e0 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/Program.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/Program.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; +using System.Net.Sockets; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using LightReflectiveMirror.Endpoints; using Mirror; @@ -25,8 +28,15 @@ namespace LightReflectiveMirror private DateTime _startupTime; private List _currentConnections = new List(); + public Dictionary NATConnections = new Dictionary(); + private BiDictionary _pendingNATPunches = new BiDictionary(); private int _currentHeartbeatTimer = 0; + private byte[] _NATRequest = new byte[500]; + private int _NATRequestPosition = 0; + + private UdpClient _punchServer; + private const string CONFIG_PATH = "config.json"; public static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); @@ -52,33 +62,34 @@ namespace LightReflectiveMirror else { conf = JsonConvert.DeserializeObject(File.ReadAllText(CONFIG_PATH)); + WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true); try { var asm = Assembly.LoadFile(Directory.GetCurrentDirectory() + @"\" + conf.TransportDLL); - WriteLogMessage($"Loaded Assembly: {asm.FullName}", ConsoleColor.Green); + WriteLogMessage($"OK", ConsoleColor.Green); + + WriteLogMessage("\nLoading Transport Class... ", ConsoleColor.White, true); transport = asm.CreateInstance(conf.TransportClass) as Transport; if (transport != null) { var transportClass = asm.GetType(conf.TransportClass); + WriteLogMessage("OK", ConsoleColor.Green); - WriteLogMessage($"Loaded Transport: {transportClass.Name}! Loading Methods...", ConsoleColor.Green); + WriteLogMessage("\nLoading Transport Methods... ", ConsoleColor.White, true); CheckMethods(transportClass); + WriteLogMessage("OK", ConsoleColor.Green); + + WriteLogMessage("\nInvoking Transport Methods..."); if (_awakeMethod != null) - { _awakeMethod.Invoke(transport, null); - WriteLogMessage("Called Awake on transport.", ConsoleColor.Yellow); - } if (_startMethod != null) - { - _awakeMethod.Invoke(transport, null); - WriteLogMessage("Called Start on transport.", ConsoleColor.Yellow); - } + _startMethod.Invoke(transport, null); - WriteLogMessage("Starting Transport...", ConsoleColor.Green); + WriteLogMessage("\nStarting Transport... ", ConsoleColor.White, true); transport.OnServerError = (clientID, error) => { @@ -90,6 +101,16 @@ namespace LightReflectiveMirror WriteLogMessage($"Transport Connected, Client: {clientID}", ConsoleColor.Cyan); _currentConnections.Add(clientID); _relay.ClientConnected(clientID); + + if (conf.EnableNATPunchtroughServer) + { + string natID = Guid.NewGuid().ToString(); + _pendingNATPunches.Add(clientID, natID); + _NATRequestPosition = 0; + _NATRequest.WriteByte(ref _NATRequestPosition, (byte)OpCodes.RequestNATConnection); + _NATRequest.WriteString(ref _NATRequestPosition, natID); + transport.ServerSend(clientID, 0, new ArraySegment(_NATRequest, 0, _NATRequestPosition)); + } }; _relay = new RelayHandler(transport.GetMaxPacketSize(0)); @@ -99,32 +120,71 @@ namespace LightReflectiveMirror { _currentConnections.Remove(clientID); _relay.HandleDisconnect(clientID); + + if(NATConnections.ContainsKey(clientID)) + NATConnections.Remove(clientID); + + if(_pendingNATPunches.TryGetByFirst(clientID, out _)) + _pendingNATPunches.Remove(clientID); }; transport.ServerStart(); - WriteLogMessage("Transport Started!", ConsoleColor.Green); + WriteLogMessage("OK", ConsoleColor.Green); if (conf.UseEndpoint) { + WriteLogMessage("\nStarting Endpoint Service... ", ConsoleColor.White, true); var endpoint = new EndpointServer(); if (endpoint.Start(conf.EndpointPort)) - WriteLogMessage("Endpoint Service Started!", ConsoleColor.Green); + { + WriteLogMessage("OK", ConsoleColor.Green); + } else - WriteLogMessage("Endpoint failure, please run as administrator.", ConsoleColor.Red); + { + WriteLogMessage("FAILED\nPlease run as administrator or check if port is in use.", ConsoleColor.DarkRed); + } + } + + if (conf.EnableNATPunchtroughServer) + { + WriteLogMessage("\nStarting NatPunchthrough Socket... ", ConsoleColor.White, true); + + try + { + _punchServer = new UdpClient(conf.NATPunchtroughPort); + + WriteLogMessage("OK\n", ConsoleColor.Green, true); + + WriteLogMessage("\nStarting NatPunchthrough Thread... ", ConsoleColor.White, true); + var natThread = new Thread(new ThreadStart(RunNATPunchLoop)); + + try + { + natThread.Start(); + }catch(Exception err) + { + WriteLogMessage("FAILED\n" + err, ConsoleColor.DarkRed); + } + } + catch(Exception error) + { + WriteLogMessage("FAILED\nCheck if port is in use.", ConsoleColor.DarkRed, true); + Console.WriteLine(error); + } } } else { - WriteLogMessage("Transport Class not found! Please make sure to include namespaces.", ConsoleColor.Red); + WriteLogMessage("FAILED\nClass not found, make sure to included namespaces!", ConsoleColor.DarkRed); Console.ReadKey(); Environment.Exit(0); } } catch(Exception e) { - WriteLogMessage("Exception: " + e, ConsoleColor.Red); + WriteLogMessage("FAILED\nException: " + e, ConsoleColor.DarkRed); Console.ReadKey(); Environment.Exit(0); } @@ -151,10 +211,55 @@ namespace LightReflectiveMirror } } - static void WriteLogMessage(string message, ConsoleColor color = ConsoleColor.White) + void RunNATPunchLoop() + { + WriteLogMessage("OK\n", ConsoleColor.Green); + IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, conf.NATPunchtroughPort); + + // Stock Data server sends to everyone: + var serverResponse = new byte[1] { 1 }; + + byte[] readData; + bool isConnectionEstablishment; + int pos; + string connectionID; + + while (true) + { + readData = _punchServer.Receive(ref remoteEndpoint); + pos = 0; + try + { + isConnectionEstablishment = readData.ReadBool(ref pos); + + if (isConnectionEstablishment) + { + connectionID = readData.ReadString(ref pos); + + if (_pendingNATPunches.TryGetBySecond(connectionID, out pos)) + { + NATConnections.Add(pos, new IPEndPoint(remoteEndpoint.Address, remoteEndpoint.Port)); + _pendingNATPunches.Remove(pos); + Console.WriteLine("Client Successfully Established Puncher Connection. " + remoteEndpoint.ToString()); + } + } + + _punchServer.Send(serverResponse, 1, remoteEndpoint); + } + catch + { + // ignore, packet got fucked up or something. + } + } + } + + static void WriteLogMessage(string message, ConsoleColor color = ConsoleColor.White, bool oneLine = false) { Console.ForegroundColor = color; - Console.WriteLine(message); + if (oneLine) + Console.Write(message); + else + Console.WriteLine(message); } void CheckMethods(Type type) @@ -163,11 +268,6 @@ namespace LightReflectiveMirror _startMethod = type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _updateMethod = type.GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _lateUpdateMethod = type.GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - - if (_awakeMethod != null) WriteLogMessage("'Awake' Loaded!", ConsoleColor.Yellow); - if (_startMethod != null) WriteLogMessage("'Start' Loaded!", ConsoleColor.Yellow); - if (_updateMethod != null) WriteLogMessage("'Update' Loaded!", ConsoleColor.Yellow); - if (_lateUpdateMethod != null) WriteLogMessage("'LateUpdate' Loaded!", ConsoleColor.Yellow); } void WriteTitle() @@ -184,8 +284,8 @@ namespace LightReflectiveMirror "; string load = $"Chimp Event Listener Initializing... OK" + - "\nHarambe Memorial Initializing... OK" + - "\nBananas initializing... OK\n"; + "\nHarambe Memorial Initializing... OK" + + "\nBananas initializing... OK\n"; WriteLogMessage(t, ConsoleColor.Green); WriteLogMessage(load, ConsoleColor.Cyan); diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/RelayHandler.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/RelayHandler.cs index 0ffbe76..b216194 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/RelayHandler.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/RelayHandler.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Net; using System.Text; namespace LightReflectiveMirror @@ -57,7 +58,7 @@ namespace LightReflectiveMirror switch (opcode) { case OpCodes.CreateRoom: - CreateRoom(clientId, data.ReadInt(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos)); + CreateRoom(clientId, data.ReadInt(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos), data.ReadBool(ref pos), data.ReadInt(ref pos)); break; case OpCodes.RequestID: SendClientID(clientId); @@ -66,7 +67,7 @@ namespace LightReflectiveMirror LeaveRoom(clientId); break; case OpCodes.JoinServer: - JoinRoom(clientId, data.ReadInt(ref pos)); + JoinRoom(clientId, data.ReadInt(ref pos), data.ReadBool(ref pos), data.ReadString(ref pos)); break; case OpCodes.KickPlayer: LeaveRoom(data.ReadInt(ref pos), clientId); @@ -74,9 +75,6 @@ namespace LightReflectiveMirror case OpCodes.SendData: ProcessData(clientId, data.ReadBytes(ref pos), channel, data.ReadInt(ref pos)); break; - case OpCodes.RequestServers: - SendServerList(clientId); - break; case OpCodes.UpdateRoomData: var plyRoom = GetRoomForPlayer(clientId); @@ -110,28 +108,6 @@ namespace LightReflectiveMirror public void HandleDisconnect(int clientId) => LeaveRoom(clientId); - void SendServerList(int clientId) - { - int pos = 0; - var buffer = _sendBuffers.Rent(500); - buffer.WriteByte(ref pos, (byte)OpCodes.ServerListReponse); - for(int i = 0; i < rooms.Count; i++) - { - if (rooms[i].isPublic) - { - buffer.WriteBool(ref pos, true); - buffer.WriteString(ref pos, rooms[i].serverName); - buffer.WriteString(ref pos, rooms[i].serverData); - buffer.WriteInt(ref pos, rooms[i].serverId); - buffer.WriteInt(ref pos, rooms[i].maxPlayers); - buffer.WriteInt(ref pos, rooms[i].clients.Count + 1); - } - } - buffer.WriteBool(ref pos, false); - Program.transport.ServerSend(clientId, 0, new ArraySegment(buffer, 0, pos)); - _sendBuffers.Return(buffer); - } - void ProcessData(int clientId, byte[] clientData, int channel, int sendTo = -1) { Room playersRoom = GetRoomForPlayer(clientId); @@ -184,7 +160,7 @@ namespace LightReflectiveMirror return null; } - void JoinRoom(int clientId, int serverId) + void JoinRoom(int clientId, int serverId, bool canDirectConnect, string localIP) { LeaveRoom(clientId); @@ -197,15 +173,44 @@ namespace LightReflectiveMirror rooms[i].clients.Add(clientId); int sendJoinPos = 0; - byte[] sendJoinBuffer = _sendBuffers.Rent(5); + byte[] sendJoinBuffer = _sendBuffers.Rent(500); - sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.ServerJoined); - sendJoinBuffer.WriteInt(ref sendJoinPos, clientId); + if (canDirectConnect && Program.instance.NATConnections.ContainsKey(clientId)) + { + sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.DirectConnectIP); - Program.transport.ServerSend(clientId, 0, new ArraySegment(sendJoinBuffer, 0, sendJoinPos)); - Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment(sendJoinBuffer, 0, sendJoinPos)); - _sendBuffers.Return(sendJoinBuffer); - return; + if (Program.instance.NATConnections[clientId].Address.Equals(rooms[i].hostIP.Address)) + sendJoinBuffer.WriteString(ref sendJoinPos, rooms[i].hostLocalIP == localIP ? "127.0.0.1" : rooms[i].hostLocalIP); + else + sendJoinBuffer.WriteString(ref sendJoinPos, rooms[i].hostIP.Address.ToString()); + + sendJoinBuffer.WriteInt(ref sendJoinPos, rooms[i].useNATPunch ? rooms[i].hostIP.Port : rooms[i].port); + + Program.transport.ServerSend(clientId, 0, new ArraySegment(sendJoinBuffer, 0, sendJoinPos)); + + sendJoinPos = 0; + sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.DirectConnectIP); + Console.WriteLine(Program.instance.NATConnections[clientId].Address.ToString()); + sendJoinBuffer.WriteString(ref sendJoinPos, Program.instance.NATConnections[clientId].Address.ToString()); + sendJoinBuffer.WriteInt(ref sendJoinPos, Program.instance.NATConnections[clientId].Port); + + Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment(sendJoinBuffer, 0, sendJoinPos)); + + _sendBuffers.Return(sendJoinBuffer); + + return; + } + else + { + + sendJoinBuffer.WriteByte(ref sendJoinPos, (byte)OpCodes.ServerJoined); + sendJoinBuffer.WriteInt(ref sendJoinPos, clientId); + + Program.transport.ServerSend(clientId, 0, new ArraySegment(sendJoinBuffer, 0, sendJoinPos)); + Program.transport.ServerSend(rooms[i].hostId, 0, new ArraySegment(sendJoinBuffer, 0, sendJoinPos)); + _sendBuffers.Return(sendJoinBuffer); + return; + } } } } @@ -220,10 +225,13 @@ namespace LightReflectiveMirror _sendBuffers.Return(sendBuffer); } - void CreateRoom(int clientId, int maxPlayers, string serverName, bool isPublic, string serverData) + void CreateRoom(int clientId, int maxPlayers, string serverName, bool isPublic, string serverData, bool useDirectConnect, string hostLocalIP, bool useNatPunch, int port) { LeaveRoom(clientId); + IPEndPoint hostIP = null; + Program.instance.NATConnections.TryGetValue(clientId, out hostIP); + Room room = new Room { hostId = clientId, @@ -232,8 +240,12 @@ namespace LightReflectiveMirror isPublic = isPublic, serverData = serverData, clients = new List(), - - serverId = GetRandomServerID() + serverId = GetRandomServerID(), + hostIP = hostIP, + hostLocalIP = hostLocalIP, + supportsDirectConnect = hostIP == null ? false : useDirectConnect, + port = port, + useNATPunch = useNatPunch }; rooms.Add(room); @@ -322,6 +334,7 @@ namespace LightReflectiveMirror 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, RequestServers = 15, ServerListReponse = 16, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19 + LeaveRoom = 11, KickPlayer = 12, AuthenticationRequest = 13, AuthenticationResponse = 14, Authenticated = 17, UpdateRoomData = 18, ServerConnectionData = 19, RequestNATConnection = 20, + DirectConnectIP = 21 } } diff --git a/ServerProject-DONT-IMPORT-INTO-UNITY/Room.cs b/ServerProject-DONT-IMPORT-INTO-UNITY/Room.cs index 32c032b..e2d4973 100644 --- a/ServerProject-DONT-IMPORT-INTO-UNITY/Room.cs +++ b/ServerProject-DONT-IMPORT-INTO-UNITY/Room.cs @@ -1,10 +1,10 @@ -using System; +using Newtonsoft.Json; using System.Collections.Generic; -using System.Text; +using System.Net; namespace LightReflectiveMirror { - [Serializable] + [JsonObject(MemberSerialization.OptOut)] public class Room { public int serverId; @@ -14,5 +14,15 @@ namespace LightReflectiveMirror public bool isPublic; public int maxPlayers; public List clients; + [JsonIgnore] + public bool supportsDirectConnect = false; + [JsonIgnore] + public IPEndPoint hostIP; + [JsonIgnore] + public string hostLocalIP; + [JsonIgnore] + public bool useNATPunch = false; + [JsonIgnore] + public int port; } }