using System; using System.Linq; using System.Collections.Generic; using System.Threading; using System.Net; using System.Net.Sockets; using System.Text; using Lidgren.Network; using CoopServer.Entities; namespace CoopServer { class MasterServer { private Thread MainThread; public void Start() { MainThread = new Thread(Listen); MainThread.Start(); } private void Listen() { try { IPHostEntry host = Dns.GetHostEntry(Server.MainSettings.MasterServer); IPAddress ipAddress = host.AddressList[0]; IPEndPoint remoteEP = new(ipAddress, 11000); // Create a TCP/IP socket Socket sender = new(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); sender.Connect(remoteEP); Logging.Info("Server connected to MasterServer"); while (sender.Connected) { // Encode the data string into a byte array byte[] msg = Encoding.ASCII.GetBytes( "{ \"method\": \"POST\", \"data\": { " + "\"Port\": \"" + Server.MainSettings.ServerPort + "\", " + "\"Name\": \"" + Server.MainSettings.ServerName + "\", " + "\"Version\": \"" + Server.CurrentModVersion.Replace("_", ".") + "\", " + "\"Players\": " + Server.MainNetServer.ConnectionsCount + ", " + "\"MaxPlayers\": " + Server.MainSettings.MaxPlayers + ", " + "\"NpcsAllowed\": \"" + Server.MainSettings.NpcsAllowed + "\" } }"); // Send the data sender.Send(msg); // Sleep for 15 seconds Thread.Sleep(15000); } } catch (SocketException se) { Logging.Error(se.Message); } catch (Exception e) { Logging.Error(e.Message); } } } class Server { public static readonly string CurrentModVersion = Enum.GetValues(typeof(ModVersion)).Cast().Last().ToString(); public static readonly Settings MainSettings = Util.Read("CoopSettings.xml"); private readonly Blocklist MainBlocklist = Util.Read("Blocklist.xml"); private readonly Allowlist MainAllowlist = Util.Read("Allowlist.xml"); public static NetServer MainNetServer; private readonly MasterServer MainMasterServer = new(); private static readonly Dictionary Players = new(); public Server() { // 6d4ec318f1c43bd62fe13d5a7ab28650 = GTACOOP:R NetPeerConfiguration config = new("6d4ec318f1c43bd62fe13d5a7ab28650") { MaximumConnections = MainSettings.MaxPlayers, Port = MainSettings.ServerPort }; config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); MainNetServer = new NetServer(config); MainNetServer.Start(); Logging.Info(string.Format("Server listening on {0}:{1}", config.LocalAddress.ToString(), config.Port)); if (MainSettings.AnnounceSelf) { MainMasterServer.Start(); } Listen(); } private void Listen() { Logging.Info("Listening for clients"); while (!Console.KeyAvailable || Console.ReadKey().Key != ConsoleKey.Escape) { // 16 milliseconds to sleep to reduce CPU usage Thread.Sleep(1000 / 60); NetIncomingMessage message; while ((message = MainNetServer.ReadMessage()) != null) { switch (message.MessageType) { case NetIncomingMessageType.ConnectionApproval: Logging.Info("New incoming connection from: " + message.SenderConnection.RemoteEndPoint.ToString()); if (message.ReadByte() != (byte)PacketTypes.HandshakePacket) { Logging.Info(string.Format("Player with IP {0} blocked, reason: Wrong packet!", message.SenderConnection.RemoteEndPoint.ToString())); message.SenderConnection.Deny("Wrong packet!"); } else { try { Packet approvalPacket; approvalPacket = new HandshakePacket(); approvalPacket.NetIncomingMessageToPacket(message); GetHandshake(message.SenderConnection, (HandshakePacket)approvalPacket); } catch (Exception e) { Logging.Info(string.Format("Player with IP {0} blocked, reason: {1}", message.SenderConnection.RemoteEndPoint.ToString(), e.Message)); message.SenderConnection.Deny(e.Message); } } break; case NetIncomingMessageType.StatusChanged: NetConnectionStatus status = (NetConnectionStatus)message.ReadByte(); string player = NetUtility.ToHexString(message.SenderConnection.RemoteUniqueIdentifier); if (status == NetConnectionStatus.Disconnected && Players.ContainsKey(player)) { SendPlayerDisconnectPacket(new PlayerDisconnectPacket() { Player = player }, message.ReadString()); } break; case NetIncomingMessageType.Data: // Get packet type byte type = message.ReadByte(); // Create packet Packet packet; switch (type) { case (byte)PacketTypes.PlayerConnectPacket: try { packet = new PlayerConnectPacket(); packet.NetIncomingMessageToPacket(message); SendPlayerConnectPacket(message.SenderConnection, (PlayerConnectPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } break; case (byte)PacketTypes.PlayerDisconnectPacket: try { packet = new PlayerDisconnectPacket(); packet.NetIncomingMessageToPacket(message); SendPlayerDisconnectPacket((PlayerDisconnectPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } break; case (byte)PacketTypes.FullSyncPlayerPacket: try { packet = new FullSyncPlayerPacket(); packet.NetIncomingMessageToPacket(message); FullSyncPlayer((FullSyncPlayerPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } break; case (byte)PacketTypes.FullSyncNpcPacket: if (MainSettings.NpcsAllowed) { try { packet = new FullSyncNpcPacket(); packet.NetIncomingMessageToPacket(message); FullSyncNpc(message.SenderConnection, (FullSyncNpcPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } } else { message.SenderConnection.Disconnect("Npcs are not allowed!"); } break; case (byte)PacketTypes.LightSyncPlayerPacket: try { packet = new LightSyncPlayerPacket(); packet.NetIncomingMessageToPacket(message); LightSyncPlayer((LightSyncPlayerPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } break; case (byte)PacketTypes.FullSyncNpcVehPacket: if (MainSettings.NpcsAllowed) { try { packet = new FullSyncNpcVehPacket(); packet.NetIncomingMessageToPacket(message); FullSyncNpcVeh(message.SenderConnection, (FullSyncNpcVehPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } } else { message.SenderConnection.Disconnect("Npcs are not allowed!"); } break; case (byte)PacketTypes.ChatMessagePacket: try { packet = new ChatMessagePacket(); packet.NetIncomingMessageToPacket(message); SendChatMessage((ChatMessagePacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } break; default: Logging.Error("Unhandled Data / Packet type"); break; } break; case NetIncomingMessageType.ErrorMessage: Logging.Error(message.ReadString()); break; case NetIncomingMessageType.WarningMessage: Logging.Warning(message.ReadString()); break; case NetIncomingMessageType.DebugMessage: case NetIncomingMessageType.VerboseDebugMessage: Logging.Debug(message.ReadString()); break; default: Logging.Error(string.Format("Unhandled type: {0} {1} bytes {2} | {3}", message.MessageType, message.LengthBytes, message.DeliveryMethod, message.SequenceChannel)); break; } MainNetServer.Recycle(message); } } } // Return a list of all connections but not the local connection private static List FilterAllLocal(NetConnection local) { return new(MainNetServer.Connections.Where(e => e != local)); } private static List FilterAllLocal(string local) { return new(MainNetServer.Connections.Where(e => NetUtility.ToHexString(e.RemoteUniqueIdentifier) != local)); } // Return a list of players within range of ... private static List GetAllInRange(LVector3 position, float range) { return new(MainNetServer.Connections.FindAll(e => Players[NetUtility.ToHexString(e.RemoteUniqueIdentifier)].Ped.IsInRangeOf(position, range))); } // Return a list of players within range of ... but not the local one private static List GetAllInRange(LVector3 position, float range, NetConnection local) { return new(MainNetServer.Connections.Where(e => e != local && Players[NetUtility.ToHexString(e.RemoteUniqueIdentifier)].Ped.IsInRangeOf(position, range))); } // Before we approve the connection, we must shake hands private void GetHandshake(NetConnection local, HandshakePacket packet) { Logging.Debug("New handshake from: [" + packet.SocialClubName + " | " + packet.Username + "]"); if (string.IsNullOrWhiteSpace(packet.Username)) { local.Deny("Username is empty or contains spaces!"); return; } else if (packet.Username.Any(p => !char.IsLetterOrDigit(p))) { local.Deny("Username contains special chars!"); return; } if (MainSettings.Allowlist) { if (!MainAllowlist.SocialClubName.Contains(packet.SocialClubName)) { local.Deny("This Social Club name is not on the allow list!"); return; } } if (packet.ModVersion != CurrentModVersion) { local.Deny("Please update GTACoop:R to " + CurrentModVersion.Replace("_", ".")); return; } if (MainBlocklist.SocialClubName.Contains(packet.SocialClubName)) { local.Deny("This Social Club name has been blocked by this server!"); return; } else if (MainBlocklist.Username.Contains(packet.Username)) { local.Deny("This Username has been blocked by this server!"); return; } else if (MainBlocklist.IP.Contains(local.RemoteEndPoint.ToString().Split(":")[0])) { local.Deny("This IP was blocked by this server!"); return; } foreach (KeyValuePair player in Players) { if (player.Value.SocialClubName == packet.SocialClubName) { local.Deny("The name of the Social Club is already taken!"); return; } else if (player.Value.Username == packet.Username) { local.Deny("Username is already taken!"); return; } } string localPlayerID = NetUtility.ToHexString(local.RemoteUniqueIdentifier); // Add the player to Players Players.Add(localPlayerID, new EntitiesPlayer() { SocialClubName = packet.SocialClubName, Username = packet.Username } ); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); // Create a new handshake packet new HandshakePacket() { ID = localPlayerID, SocialClubName = string.Empty, Username = string.Empty, ModVersion = string.Empty, NpcsAllowed = MainSettings.NpcsAllowed }.PacketToNetOutGoingMessage(outgoingMessage); // Accept the connection and send back a new handshake packet with the connection ID local.Approve(outgoingMessage); Logging.Info("New player [" + packet.SocialClubName + " | " + packet.Username + "] connected!"); } // The connection has been approved, now we need to send all other players to the new player and the new player to all players private static void SendPlayerConnectPacket(NetConnection local, PlayerConnectPacket packet) { if (!string.IsNullOrEmpty(MainSettings.WelcomeMessage)) { SendChatMessage(new ChatMessagePacket() { Username = "Server", Message = MainSettings.WelcomeMessage }, new List() { local }); } List playerList = FilterAllLocal(local); if (playerList.Count == 0) { return; } // Send all players to local playerList.ForEach(targetPlayer => { string targetPlayerID = NetUtility.ToHexString(targetPlayer.RemoteUniqueIdentifier); EntitiesPlayer targetEntity = Players[targetPlayerID]; NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); new PlayerConnectPacket() { Player = targetPlayerID, SocialClubName = targetEntity.SocialClubName, Username = targetEntity.Username }.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, local, NetDeliveryMethod.ReliableOrdered, 0); }); // Send local to all players NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); new PlayerConnectPacket() { Player = packet.Player, SocialClubName = Players[packet.Player].SocialClubName, Username = Players[packet.Player].Username }.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } // Send all players a message that someone has left the server private static void SendPlayerDisconnectPacket(PlayerDisconnectPacket packet, string reason = "Disconnected") { List playerList = FilterAllLocal(packet.Player); if (playerList.Count != 0) { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } Logging.Info(Players[packet.Player].Username + " left the server, reason: " + reason); Players.Remove(packet.Player); } private static void FullSyncPlayer(FullSyncPlayerPacket packet) { Players[packet.Player].Ped.Position = packet.Position; List playerList = FilterAllLocal(packet.Player); if (playerList.Count == 0) { return; } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } private static void FullSyncNpc(NetConnection local, FullSyncNpcPacket packet) { List playerList = GetAllInRange(packet.Position, 300f, local); if (playerList.Count == 0) { return; } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } private static void LightSyncPlayer(LightSyncPlayerPacket packet) { Players[packet.Player].Ped.Position = packet.Position; List playerList = FilterAllLocal(packet.Player); if (playerList.Count == 0) { return; } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } private static void FullSyncNpcVeh(NetConnection local, FullSyncNpcVehPacket packet) { List playerList = GetAllInRange(packet.Position, 300f, local); if (playerList.Count == 0) { return; } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } // Send a message to targets or all players private static void SendChatMessage(ChatMessagePacket packet, List targets = null) { packet.Message = packet.Message.Replace("~", ""); Logging.Info(packet.Username + ": " + packet.Message); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0); } } }