using System; using System.Linq; using System.Collections.Generic; using System.Threading; using System.Reflection; using System.IO; using Lidgren.Network; namespace CoopServer { class Server { private static readonly string CompatibleVersion = "V0_8_0_1"; 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; public static Resource MainResource; public static Dictionary> Commands; public static readonly List Clients = new(); public Server() { Logging.Info("================"); Logging.Info($"Server version: {Assembly.GetCallingAssembly().GetName().Version}"); Logging.Info($"Compatible GTACoOp:R versions: {CompatibleVersion.Replace('_', '.')}.x"); Logging.Info("================"); // 6d4ec318f1c43bd62fe13d5a7ab28650 = GTACOOP:R NetPeerConfiguration config = new("6d4ec318f1c43bd62fe13d5a7ab28650") { MaximumConnections = MainSettings.MaxPlayers, Port = MainSettings.ServerPort, EnableUPnP = MainSettings.UPnP }; config.EnableMessageType(NetIncomingMessageType.ConnectionApproval); config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated); MainNetServer = new NetServer(config); MainNetServer.Start(); Logging.Info(string.Format("Server listening on {0}:{1}", config.LocalAddress.ToString(), config.Port)); if (MainSettings.UPnP) { Logging.Info(string.Format("Attempting to forward port {0}", MainSettings.ServerPort)); if (MainNetServer.UPnP.ForwardPort(MainSettings.ServerPort, "GTACOOP:R server")) { Logging.Info(string.Format("Server available on {0}:{1}", MainNetServer.UPnP.GetExternalIP().ToString(), config.Port)); } else { Logging.Error("Port forwarding failed!"); Logging.Warning("If you and your friends can join this server, please ignore this error or set UPnP in CoopSettings.xml to \"false\"!"); } } if (!string.IsNullOrEmpty(MainSettings.Resource)) { try { Logging.Info("Loading resource..."); Assembly asm = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "resources" + Path.DirectorySeparatorChar + MainSettings.Resource + ".dll"); Type[] types = asm.GetExportedTypes(); IEnumerable validTypes = types.Where(t => !t.IsInterface && !t.IsAbstract).Where(t => typeof(ServerScript).IsAssignableFrom(t)); Type[] enumerable = validTypes as Type[] ?? validTypes.ToArray(); if (!enumerable.Any()) { Logging.Error("ERROR: No classes that inherit from ServerScript have been found in the assembly. Starting freeroam."); } else { Commands = new(); if (Activator.CreateInstance(enumerable.ToArray()[0]) is ServerScript script) { MainResource = new(script); } else { Logging.Warning("Could not create resource: it is null."); } } } catch (Exception e) { Logging.Error(e.Message); } } Listen(); } private void Listen() { Logging.Info("Listening for clients"); while (true) { 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.Address.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.Address.ToString(), e.Message)); message.SenderConnection.Deny(e.Message); } } break; case NetIncomingMessageType.StatusChanged: NetConnectionStatus status = (NetConnectionStatus)message.ReadByte(); long clientID = message.SenderConnection.RemoteUniqueIdentifier; if (status == NetConnectionStatus.Disconnected && Clients.Any(x => x.ID == clientID)) { SendPlayerDisconnectPacket(new PlayerDisconnectPacket() { ID = clientID }); } 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.FullSyncPlayerVehPacket: try { packet = new FullSyncPlayerVehPacket(); packet.NetIncomingMessageToPacket(message); FullSyncPlayerVeh((FullSyncPlayerVehPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } 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.LightSyncPlayerVehPacket: try { packet = new LightSyncPlayerVehPacket(); packet.NetIncomingMessageToPacket(message); LightSyncPlayerVeh((LightSyncPlayerVehPacket)packet); } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } break; case (byte)PacketTypes.ChatMessagePacket: try { packet = new ChatMessagePacket(); packet.NetIncomingMessageToPacket(message); SendChatMessage((ChatMessagePacket)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.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.ModPacket: if (MainSettings.ModsAllowed) { try { packet = new ModPacket(); packet.NetIncomingMessageToPacket(message); ModPacket modPacket = (ModPacket)packet; if (MainResource != null) { if (MainResource.InvokeModPacketReceived(modPacket.ID, modPacket.Target, modPacket.Mod, modPacket.CustomPacketID, modPacket.Bytes)) { break; } } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); modPacket.PacketToNetOutGoingMessage(outgoingMessage); if (modPacket.Target != 0) { NetConnection target = MainNetServer.Connections.FirstOrDefault(x => x.RemoteUniqueIdentifier == modPacket.Target); if (target == null) { Logging.Error($"[ModPacket] target \"{modPacket.Target}\" not found!"); return; } // Send back to target MainNetServer.SendMessage(outgoingMessage, target, NetDeliveryMethod.ReliableOrdered, 0); } else { // Send back to all players MainNetServer.SendMessage(outgoingMessage, MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0); } } catch (Exception e) { message.SenderConnection.Disconnect(e.Message); } } else { message.SenderConnection.Disconnect("Mods are not allowed!"); } break; default: Logging.Error("Unhandled Data / Packet type"); break; } break; case NetIncomingMessageType.ConnectionLatencyUpdated: Client client = Clients.FirstOrDefault(x => x.ID == message.SenderConnection.RemoteUniqueIdentifier); if (client != default) { client.Latency = message.ReadFloat(); } 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); } // 16 milliseconds to sleep to reduce CPU usage Thread.Sleep(1000 / 60); } } #region -- PLAYER -- // Before we approve the connection, we must shake hands private void GetHandshake(NetConnection local, HandshakePacket packet) { Logging.Debug("New handshake from: [SC: " + packet.SocialClubName + " | Name: " + packet.Username + " | Address: " + local.RemoteEndPoint.Address.ToString() + "]"); 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.StartsWith(CompatibleVersion)) { local.Deny($"GTACoOp:R version {CompatibleVersion.Replace('_', '.')}.x required!"); 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; } if (Clients.Any(x => x.Player.SocialClubName == packet.SocialClubName)) { local.Deny("The name of the Social Club is already taken!"); return; } else if (Clients.Any(x => x.Player.Username == packet.Username)) { local.Deny("Username is already taken!"); return; } long localID = local.RemoteUniqueIdentifier; // Add the player to Players Clients.Add( new Client() { ID = localID, Player = new() { SocialClubName = packet.SocialClubName, Username = packet.Username } } ); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); // Create a new handshake packet new HandshakePacket() { ID = localID, 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); } // 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 }); } if (MainResource != null) { MainResource.InvokePlayerConnected(Clients.Find(x => x.ID == packet.ID)); } List clients; if ((clients = Util.FilterAllLocal(local)).Count == 0) { return; } // Send all players to local clients.ForEach(targetPlayer => { long targetPlayerID = targetPlayer.RemoteUniqueIdentifier; Client targetEntity = Clients.First(x => x.ID == targetPlayerID); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); new PlayerConnectPacket() { ID = targetPlayerID, SocialClubName = targetEntity.Player.SocialClubName, Username = targetEntity.Player.Username }.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, local, NetDeliveryMethod.ReliableOrdered, 0); }); // Send local to all players Client localClient = Clients.First(x => x.ID == packet.ID); NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); new PlayerConnectPacket() { ID = packet.ID, SocialClubName = localClient.Player.SocialClubName, Username = localClient.Player.Username }.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0); } // Send all players a message that someone has left the server private static void SendPlayerDisconnectPacket(PlayerDisconnectPacket packet) { if (MainResource != null) { MainResource.InvokePlayerDisconnected(Clients.Find(x => x.ID == packet.ID)); } List clients; if ((clients = Util.FilterAllLocal(packet.ID)).Count > 0) { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.ReliableOrdered, 0); } Clients.Remove(Clients.Find(x => x.ID == packet.ID)); } private static void FullSyncPlayer(FullSyncPlayerPacket packet) { Client client = Clients.First(x => x.ID == packet.Extra.ID); client.Player.Position = packet.Extra.Position; PlayerPacket playerPacket = packet.Extra; playerPacket.Latency = client.Latency; packet.Extra = playerPacket; MainNetServer.Connections.FindAll(x => x.RemoteUniqueIdentifier != packet.Extra.ID).ForEach(x => { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); if (Clients.First(y => y.ID == x.RemoteUniqueIdentifier).Player.IsInRangeOf(packet.Extra.Position, 550f)) { packet.PacketToNetOutGoingMessage(outgoingMessage); } else { new SuperLightSyncPlayerPacket() { Extra = packet.Extra }.PacketToNetOutGoingMessage(outgoingMessage); } MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0); }); } private static void FullSyncPlayerVeh(FullSyncPlayerVehPacket packet) { Client client = Clients.First(x => x.ID == packet.Extra.ID); client.Player.Position = packet.Extra.Position; PlayerPacket playerPacket = packet.Extra; playerPacket.Latency = client.Latency; packet.Extra = playerPacket; MainNetServer.Connections.FindAll(x => x.RemoteUniqueIdentifier != packet.Extra.ID).ForEach(x => { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); if (Clients.First(y => y.ID == x.RemoteUniqueIdentifier).Player.IsInRangeOf(packet.Extra.Position, 550f)) { packet.PacketToNetOutGoingMessage(outgoingMessage); } else { new SuperLightSyncPlayerPacket() { Extra = packet.Extra }.PacketToNetOutGoingMessage(outgoingMessage); } MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0); }); } private static void LightSyncPlayer(LightSyncPlayerPacket packet) { Client client = Clients.First(x => x.ID == packet.Extra.ID); client.Player.Position = packet.Extra.Position; PlayerPacket playerPacket = packet.Extra; playerPacket.Latency = client.Latency; packet.Extra = playerPacket; MainNetServer.Connections.FindAll(x => x.RemoteUniqueIdentifier != packet.Extra.ID).ForEach(x => { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); if (Clients.First(y => y.ID == x.RemoteUniqueIdentifier).Player.IsInRangeOf(packet.Extra.Position, 550f)) { packet.PacketToNetOutGoingMessage(outgoingMessage); } else { new SuperLightSyncPlayerPacket() { Extra = packet.Extra }.PacketToNetOutGoingMessage(outgoingMessage); } MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0); }); } private static void LightSyncPlayerVeh(LightSyncPlayerVehPacket packet) { Client client = Clients.First(x => x.ID == packet.Extra.ID); client.Player.Position = packet.Extra.Position; PlayerPacket playerPacket = packet.Extra; playerPacket.Latency = client.Latency; packet.Extra = playerPacket; MainNetServer.Connections.FindAll(x => x.RemoteUniqueIdentifier != packet.Extra.ID).ForEach(x => { NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); if (Clients.First(y => y.ID == x.RemoteUniqueIdentifier).Player.IsInRangeOf(packet.Extra.Position, 550f)) { packet.PacketToNetOutGoingMessage(outgoingMessage); } else { new SuperLightSyncPlayerPacket() { Extra = packet.Extra }.PacketToNetOutGoingMessage(outgoingMessage); } MainNetServer.SendMessage(outgoingMessage, x, NetDeliveryMethod.UnreliableSequenced, 0); }); } // Send a message to targets or all players private static void SendChatMessage(ChatMessagePacket packet, List targets = null) { NetOutgoingMessage outgoingMessage; if (MainResource != null) { if (packet.Message.StartsWith("/")) { string[] cmdArgs = packet.Message.Split(" "); string cmdName = cmdArgs[0].Remove(0, 1); if (Commands.Any(x => x.Key.Name == cmdName)) { CommandContext ctx = new() { Client = Clients.First(x => x.Player.Username == packet.Username), Args = cmdArgs.Skip(1).ToArray() }; KeyValuePair> command = Commands.First(x => x.Key.Name == cmdName); command.Value.Invoke(ctx); } else { NetConnection userConnection = Util.GetConnectionByUsername(packet.Username); if (userConnection == default) { return; } outgoingMessage = MainNetServer.CreateMessage(); new ChatMessagePacket() { Username = "Server", Message = "Command not found!" }.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, userConnection, NetDeliveryMethod.ReliableOrdered, 0); } return; } if (MainResource.InvokeChatMessage(packet.Username, packet.Message)) { return; } } packet.Message = packet.Message.Replace("~", ""); outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, targets ?? MainNetServer.Connections, NetDeliveryMethod.ReliableOrdered, 0); Logging.Info(packet.Username + ": " + packet.Message); } #endregion #region -- NPC -- private static void FullSyncNpc(NetConnection local, FullSyncNpcPacket packet) { List clients; if ((clients = Util.GetAllInRange(packet.Position, 550f, local)).Count == 0) { return; } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.UnreliableSequenced, 0); } private static void FullSyncNpcVeh(NetConnection local, FullSyncNpcVehPacket packet) { List clients; if ((clients = Util.GetAllInRange(packet.Position, 550f, local)).Count == 0) { return; } NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); packet.PacketToNetOutGoingMessage(outgoingMessage); MainNetServer.SendMessage(outgoingMessage, clients, NetDeliveryMethod.UnreliableSequenced, 0); } #endregion public static void RegisterCommand(string name, Action callback) { Command command = new() { Name = name }; if (Commands.ContainsKey(command)) { throw new Exception("Command \"" + command.Name + "\" was already been registered!"); } Commands.Add(command, callback); } public static void RegisterCommands() { IEnumerable commands = typeof(T).GetMethods().Where(method => method.GetCustomAttributes(typeof(CommandAttribute), false).Any()); foreach (MethodInfo method in commands) { CommandAttribute attribute = method.GetCustomAttribute(true); RegisterCommand(attribute.Name, (Action)Delegate.CreateDelegate(typeof(Action), method)); } } } }