context: #region Copyright & License Information
  /*
   * Copyright 2007-2020 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.Globalization;
  using System.IO;
  using System.Linq;
  using System.Net;
  using System.Net.Sockets;
  using System.Text;
  using System.Threading;
  using OpenRA.Network;
  using OpenRA.Primitives;
  using OpenRA.Support;
  
  namespace OpenRA.Server
  {
  	public enum ServerState
  	{
  		WaitingPlayers = 1,
  		GameStarted = 2,
  		ShuttingDown = 3
  	}
  
  	public enum ServerType
  	{
  		Local = 0,
  		Multiplayer = 1,
  		Dedicated = 2
  	}
  
  	public class Server
  	{
  		public readonly string TwoHumansRequiredText = "This server requires at least two human players to start a match.";
  
  		public readonly MersenneTwister Random = new MersenneTwister();
  		public readonly ServerType Type;
  
  		// Valid player connections
  		public List<Connection> Conns = new List<Connection>();
  
  		// Pre-verified player connections
  		public List<Connection> PreConns = new List<Connection>();
  
  		public Session LobbyInfo;
  		public ServerSettings Settings;
  		public ModData ModData;
  		public List<string> TempBans = new List<string>();
  
  		// Managed by LobbyCommands
  		public MapPreview Map;
  		public GameSave GameSave = null;
  
  		readonly int randomSeed;
  		readonly List<TcpListener> listeners = new List<TcpListener>();
  		readonly TypeDictionary serverTraits = new TypeDictionary();
  		readonly PlayerDatabase playerDatabase;
  
  		protected volatile ServerState internalState = ServerState.WaitingPlayers;
  
  		volatile ActionQueue delayedActions = new ActionQueue();
  		int waitingForAuthenticationCallback = 0;
  
  		public ServerState State
  		{
  			get { return internalState; }
  			protected set { internalState = value; }
  		}
  
  		public static void SyncClientToPlayerReference(Session.Client c, PlayerReference pr)
  		{
  			if (pr == null)
  				return;
  
  			if (pr.LockFaction)
  				c.Faction = pr.Faction;
  			if (pr.LockSpawn)
  				c.SpawnPoint = pr.Spawn;
  			if (pr.LockTeam)
  				c.Team = pr.Team;
  
  			c.Color = pr.LockColor ? pr.Color : c.PreferredColor;
  		}
  
  		static void SendData(Socket s, byte[] data)
  		{
  			var start = 0;
  			var length = data.Length;
  
  			// Non-blocking sends are free to send only part of the data
  			while (start < length)
  			{
  				SocketError error;
  				var sent = s.Send(data, start, length - start, SocketFlags.None, out error);
  				if (error == SocketError.WouldBlock)
  				{
  					Log.Write("server", "Non-blocking send of {0} bytes failed. Falling back to blocking send.", length - start);
  					s.Blocking = true;
  					sent = s.Send(data, start, length - start, SocketFlags.None);
  					s.Blocking = false;
  				}
  				else if (error != SocketError.Success)
  					throw new SocketException((int)error);
  
  				start += sent;
  			}
  		}
  
  		public void Shutdown()
  		{
  			State = ServerState.ShuttingDown;
  		}
  
  		public void EndGame()
  		{
  			foreach (var t in serverTraits.WithInterface<IEndGame>())
  				t.GameEnded(this);
  		}
  
  		public Server(List<IPEndPoint> endpoints, ServerSettings settings, ModData modData, ServerType type)
  		{
  			Log.AddChannel("server", "server.log", true);
  
  			SocketException lastException = null;
  			var checkReadServer = new List<Socket>();
  			foreach (var endpoint in endpoints)
  			{
  				var listener = new TcpListener(endpoint);
  				try
  				{
  					try
  					{
  						listener.Server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 1);
  					}
  					catch (Exception ex)
  					{
  						if (ex is SocketException || ex is ArgumentException)
  							Log.Write("server", "Failed to set socket option on {0}: {1}", endpoint.ToString(), ex.Message);
  						else
  							throw;
  					}
  
  					listener.Start();
  					listeners.Add(listener);
  					checkReadServer.Add(listener.Server);
  				}
  				catch (SocketException ex)
  				{
  					lastException = ex;
  					Log.Write("server", "Failed to listen on {0}: {1}", endpoint.ToString(), ex.Message);
  				}
  			}
  
  			if (listeners.Count == 0)
  				throw lastException;
  
  			Type = type;
  			Settings = settings;
  
  			Settings.Name = OpenRA.Settings.SanitizedServerName(Settings.Name);
  
  			ModData = modData;
  
  			playerDatabase = modData.Manifest.Get<PlayerDatabase>();
  
  			randomSeed = (int)DateTime.Now.ToBinary();
  
  			if (type != ServerType.Local && settings.EnableGeoIP)
  				GeoIP.Initialize();
  
  			if (UPnP.Status == UPnPStatus.Enabled)
  				UPnP.ForwardPort(Settings.ListenPort, Settings.ListenPort).Wait();
  
  			foreach (var trait in modData.Manifest.ServerTraits)
  				serverTraits.Add(modData.ObjectCreator.CreateObject<ServerTrait>(trait));
  
  			serverTraits.TrimExcess();
  
  			LobbyInfo = new Session
  			{
  				GlobalSettings =
  				{
  					RandomSeed = randomSeed,
  					Map = settings.Map,
  					ServerName = settings.Name,
  					EnableSingleplayer = settings.EnableSingleplayer || Type != ServerType.Dedicated,
  					EnableSyncReports = settings.EnableSyncReports,
  					GameUid = Guid.NewGuid().ToString(),
  					Dedicated = Type == ServerType.Dedicated
  				}
  			};
  
  			new Thread(_ =>
  			{
  				foreach (var t in serverTraits.WithInterface<INotifyServerStart>())
  					t.ServerStarted(this);
  
  				Log.Write("server", "Initial mod: {0}", ModData.Manifest.Id);
  				Log.Write("server", "Initial map: {0}", LobbyInfo.GlobalSettings.Map);
  
  				while (true)
  				{
  					var checkRead = new List<Socket>();
  					if (State == ServerState.WaitingPlayers)
  						checkRead.AddRange(checkReadServer);
  
  					checkRead.AddRange(Conns.Select(c => c.Socket));
  					checkRead.AddRange(PreConns.Select(c => c.Socket));
  
  					// Block for at most 1 second in order to guarantee a minimum tick rate for ServerTraits
  					// Decrease this to 100ms to improve responsiveness if we are waiting for an authentication query
  					var localTimeout = waitingForAuthenticationCallback > 0 ? 100000 : 1000000;
  
gt: 					if (checkRead.Count > 0)