#region Copyright & License Information /* * Copyright 2007-2021 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using OpenRA.Primitives; using OpenRA.Server; namespace OpenRA.Network { public class Session { public List Clients = new List(); public List ClientPings = new List(); // Keyed by the PlayerReference id that the slot corresponds to public Dictionary Slots = new Dictionary(); public HashSet DisabledSpawnPoints = new HashSet(); public Global GlobalSettings = new Global(); public static string AnonymizeIP(IPAddress ip) { if (ip.AddressFamily == AddressFamily.InterNetwork) { // Follow convention used by Google Analytics: remove last octet var b = ip.GetAddressBytes(); return $"{b[0]}.{b[1]}.{b[2]}.*"; } return null; } public static Session Deserialize(string data) { try { var session = new Session(); var nodes = MiniYaml.FromString(data); foreach (var node in nodes) { var strings = node.Key.Split('@'); switch (strings[0]) { case "Client": session.Clients.Add(Client.Deserialize(node.Value)); break; case "ClientPing": session.ClientPings.Add(ClientPing.Deserialize(node.Value)); break; case "GlobalSettings": session.GlobalSettings = Global.Deserialize(node.Value); break; case "Slot": var s = Slot.Deserialize(node.Value); session.Slots.Add(s.PlayerReference, s); break; case "DisabledSpawnPoints": session.DisabledSpawnPoints = FieldLoader.GetValue>("DisabledSpawnPoints", node.Value.Value); break; } } return session; } catch (YamlException) { throw new YamlException($"Session deserialized invalid MiniYaml:\n{data}"); } catch (InvalidOperationException) { throw new YamlException($"Session deserialized invalid MiniYaml:\n{data}"); } } public Client ClientWithIndex(int clientID) { return Clients.SingleOrDefault(c => c.Index == clientID); } public Client ClientInSlot(string slot) { return Clients.SingleOrDefault(c => c.Slot == slot); } public string FirstEmptySlot() { return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null).Key; } public string FirstEmptyBotSlot() { return Slots.FirstOrDefault(s => !s.Value.Closed && ClientInSlot(s.Key) == null && s.Value.AllowBots).Key; } public IEnumerable NonBotClients { get { return Clients.Where(c => c.Bot == null); } } public IEnumerable NonBotPlayers { get { return Clients.Where(c => c.Bot == null && c.Slot != null); } } public enum ClientState { NotReady, Invalid, Ready, Disconnected = 1000 } public class Client { public static Client Deserialize(MiniYaml data) { return FieldLoader.Load(data); } public int Index; public Color PreferredColor; // Color that the client normally uses from settings.yaml. public Color Color; // Actual color that the client is using. Usually the same as PreferredColor but can be different on maps with locked colors. public string Faction; public int SpawnPoint; public string Name; // The full IP address is required for the IP banning moderation feature // but we must not share the un-anonymized address with other players. [FieldLoader.Ignore] public string IPAddress; public string AnonymizedIPAddress; public string Location; public ClientState State = ClientState.Invalid; public int Team; public int Handicap; public string Slot; // Slot ID, or null for observer public string Bot; // Bot type, null for real clients public int BotControllerClientIndex; // who added the bot to the slot public bool IsAdmin; public bool IsReady => State == ClientState.Ready; public bool IsInvalid => State == ClientState.Invalid; public bool IsObserver => Slot == null; public bool IsBot => Bot != null; // Linked to the online player database public string Fingerprint; public MiniYamlNode Serialize() { return new MiniYamlNode($"Client@{Index}", FieldSaver.Save(this)); } } public ClientPing PingFromClient(Client client) { return ClientPings.SingleOrDefault(p => p.Index == client.Index); } public class ClientPing { public int Index; public long Latency = -1; public long LatencyJitter = -1; public long[] LatencyHistory = { }; public static ClientPing Deserialize(MiniYaml data) { return FieldLoader.Load(data); } public MiniYamlNode Serialize() { return new MiniYamlNode($"ClientPing@{Index}", FieldSaver.Save(this)); } } public class Slot { public string PlayerReference; // PlayerReference to bind against. public bool Closed; // Host has explicitly closed this slot. public bool AllowBots; public bool LockFaction; public bool LockColor; public bool LockTeam; public bool LockHandicap; public bool LockSpawn; public bool Required; public static Slot Deserialize(MiniYaml data) { return FieldLoader.Load(data); } public MiniYamlNode Serialize() { return new MiniYamlNode($"Slot@{PlayerReference}", FieldSaver.Save(this)); } } public class LobbyOptionState { public string Value; public string PreferredValue; public bool IsLocked; public bool IsEnabled => Value == "True"; } [Flags] public enum MapStatus { Unknown = 0, Validating = 1, Playable = 2, Incompatible = 4, UnsafeCustomRules = 8, } public class Global { public string ServerName; public string Map; public MapStatus MapStatus; public int RandomSeed = 0; public bool AllowSpectators = true; public string GameUid; public bool EnableSingleplayer; public bool EnableSyncReports; public bool Dedicated; public bool GameSavesEnabled; [FieldLoader.Ignore] public Dictionary LobbyOptions = new Dictionary(); public static Global Deserialize(MiniYaml data) { var gs = FieldLoader.Load(data); var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options"); if (optionsNode != null) foreach (var n in optionsNode.Value.Nodes) gs.LobbyOptions[n.Key] = FieldLoader.Load(n.Value); return gs; } public MiniYamlNode Serialize() { var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this)); var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList(); data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options))); return data; } public bool OptionOrDefault(string id, bool def) { if (LobbyOptions.TryGetValue(id, out var option)) return option.IsEnabled; return def; } public string OptionOrDefault(string id, string def) { if (LobbyOptions.TryGetValue(id, out var option)) return option.Value; return def; } } public string Serialize() { var sessionData = new List() { new MiniYamlNode("DisabledSpawnPoints", FieldSaver.FormatValue(DisabledSpawnPoints)) }; foreach (var client in Clients) sessionData.Add(client.Serialize()); foreach (var clientPing in ClientPings) sessionData.Add(clientPing.Serialize()); foreach (var slot in Slots) sessionData.Add(slot.Value.Serialize()); sessionData.Add(GlobalSettings.Serialize()); return sessionData.WriteToString(); } } }