context: /*
  TShock, a server mod for Terraria
  Copyright (C) 2011-2019 Pryaxis & TShock Contributors
  
  This program is free software: you can redistribute it and/or modify
  it 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.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Data;
  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Net;
  using System.Reflection;
  using MaxMind;
  using Mono.Data.Sqlite;
  using MySql.Data.MySqlClient;
  using Newtonsoft.Json;
  using Rests;
  using Terraria;
  using Terraria.ID;
  using Terraria.Localization;
  using TerrariaApi.Server;
  using TShockAPI.DB;
  using TShockAPI.Hooks;
  using Terraria.Utilities;
  using Microsoft.Xna.Framework;
  using TShockAPI.Sockets;
  using TShockAPI.CLI;
  using TShockAPI.Localization;
  using TShockAPI.Configuration;
  
  namespace TShockAPI
  {
  	/// <summary>
  	/// This is the TShock main class. TShock is a plugin on the TerrariaServerAPI, so it extends the base TerrariaPlugin.
  	/// TShock also complies with the API versioning system, and defines its required API version here.
  	/// </summary>
  	[ApiVersion(2, 1)]
  	public class TShock : TerrariaPlugin
  	{
  		/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
  		public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
  		/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
  		public static readonly string VersionCodename = "Now with less velocity, thanks to Off + Quake. Usual thanks to Chris/White <3";
  
  		/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
  		public static string SavePath = "tshock";
  		/// <summary>LogFormatDefault - This is the default log file naming format. Actually, this is the only log format, because it never gets set again.</summary>
  		private const string LogFormatDefault = "yyyy-MM-dd_HH-mm-ss";
  		//TODO: Set the log path in the config file.
  		/// <summary>LogFormat - This is the log format, which is never set again.</summary>
  		private static string LogFormat = LogFormatDefault;
  		/// <summary>LogPathDefault - The default log path.</summary>
  		private const string LogPathDefault = "tshock";
  		/// <summary>This is the log path, which is initially set to the default log path, and then to the config file log path later.</summary>
  		private static string LogPath = LogPathDefault;
  		/// <summary>LogClear - Determines whether or not the log file should be cleared on initialization.</summary>
  		private static bool LogClear;
  
  		/// <summary>Will be set to true once Utils.StopServer() is called.</summary>
  		public static bool ShuttingDown;
  
  		/// <summary>Players - Contains all TSPlayer objects for accessing TSPlayers currently on the server</summary>
  		public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers];
  		/// <summary>Bans - Static reference to the ban manager for accessing bans & related functions.</summary>
  		public static BanManager Bans;
  		/// <summary>Warps - Static reference to the warp manager for accessing the warp system.</summary>
  		public static WarpManager Warps;
  		/// <summary>Regions - Static reference to the region manager for accessing the region system.</summary>
  		public static RegionManager Regions;
  		/// <summary>Backups - Static reference to the backup manager for accessing the backup system.</summary>
  		public static BackupManager Backups;
  		/// <summary>Groups - Static reference to the group manager for accessing the group system.</summary>
  		public static GroupManager Groups;
  		/// <summary>Users - Static reference to the user manager for accessing the user database system.</summary>
  		public static UserAccountManager UserAccounts;
  		/// <summary>ProjectileBans - Static reference to the projectile ban system.</summary>
  		public static ProjectileManagager ProjectileBans;
  		/// <summary>TileBans - Static reference to the tile ban system.</summary>
  		public static TileManager TileBans;
  		/// <summary>RememberedPos - Static reference to the remembered position manager.</summary>
  		public static RememberedPosManager RememberedPos;
  		/// <summary>CharacterDB - Static reference to the SSC character manager.</summary>
  		public static CharacterManager CharacterDB;
  		/// <summary>Contains the information about what research has been performed in Journey mode.</summary>
  		public static ResearchDatastore ResearchDatastore;
  		/// <summary>Config - Static reference to the config system, for accessing values set in users' config files.</summary>
  		public static TShockConfig Config { get; set; }
  		/// <summary>ServerSideCharacterConfig - Static reference to the server side character config, for accessing values set by users to modify SSC.</summary>
  		public static ServerSideConfig ServerSideCharacterConfig;
  		/// <summary>DB - Static reference to the database.</summary>
  		public static IDbConnection DB;
  		/// <summary>OverridePort - Determines if TShock should override the server port.</summary>
  		public static bool OverridePort;
  		/// <summary>Geo - Static reference to the GeoIP system which determines the location of an IP address.</summary>
  		public static GeoIPCountry Geo;
  		/// <summary>RestApi - Static reference to the Rest API authentication manager.</summary>
  		public static SecureRest RestApi;
  		/// <summary>RestManager - Static reference to the Rest API manager.</summary>
  		public static RestManager RestManager;
  		/// <summary>Utils - Static reference to the utilities class, which contains a variety of utility functions.</summary>
  		public static Utils Utils = Utils.Instance;
  		/// <summary>UpdateManager - Static reference to the update checker, which checks for updates and notifies server admins of updates.</summary>
  		public static UpdateManager UpdateManager;
  		/// <summary>Log - Static reference to the log system, which outputs to either SQL or a text file, depending on user config.</summary>
  		public static ILog Log;
  		/// <summary>instance - Static reference to the TerrariaPlugin instance.</summary>
  		public static TerrariaPlugin instance;
  		/// <summary>
  		/// Static reference to a <see cref="CommandLineParser"/> used for simple command-line parsing
  		/// </summary>
  		public static CommandLineParser CliParser { get; } = new CommandLineParser();
  		/// <summary>
  		/// Used for implementing REST Tokens prior to the REST system starting up.
  		/// </summary>
  		public static Dictionary<string, SecureRest.TokenData> RESTStartupTokens = new Dictionary<string, SecureRest.TokenData>();
  
  		/// <summary>The TShock anti-cheat/anti-exploit system.</summary>
  		internal Bouncer Bouncer;
  
  		/// <summary>The TShock item ban system.</summary>
  		public static ItemBans ItemBans;
  
  		/// <summary>
  		/// TShock's Region subsystem.
  		/// </summary>
  		internal RegionHandler RegionSystem;
  
  		/// <summary>
  		/// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded.
  		/// </summary>
  		public static event Action Initialized;
  
  		/// <summary>Version - The version required by the TerrariaAPI to be passed back for checking & loading the plugin.</summary>
  		/// <value>value - The version number specified in the Assembly, based on the VersionNum variable set in this class.</value>
  		public override Version Version
  		{
  			get { return VersionNum; }
  		}
  
  		/// <summary>Name - The plugin name.</summary>
  		/// <value>value - "TShock"</value>
  		public override string Name
  		{
  			get { return "TShock"; }
  		}
  
  		/// <summary>Author - The author of the plugin.</summary>
  		/// <value>value - "The TShock Team"</value>
  		public override string Author
  		{
  			get { return "The TShock Team"; }
  		}
  
  		/// <summary>Description - The plugin description.</summary>
  		/// <value>value - "The administration modification of the future."</value>
  		public override string Description
  		{
  			get { return "The administration modification of the future."; }
  		}
  
  		/// <summary>TShock - The constructor for the TShock plugin.</summary>
  		/// <param name="game">game - The Terraria main game.</param>
  		public TShock(Main game)
  			: base(game)
  		{
  			Config = new TShockConfig();
  			ServerSideCharacterConfig = new ServerSideConfig();
  			ServerSideCharacterConfig.Settings.StartingInventory.Add(new NetItem(-15, 1, 0));
  			ServerSideCharacterConfig.Settings.StartingInventory.Add(new NetItem(-13, 1, 0));
  			ServerSideCharacterConfig.Settings.StartingInventory.Add(new NetItem(-16, 1, 0));
  			Order = 0;
  			instance = this;
  		}
  
  		/// <summary>Initialize - Called by the TerrariaServerAPI during initialization.</summary>
  		[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
  		public override void Initialize()
  		{
  			string logFilename;
  			string logPathSetupWarning;
  
  			OTAPI.Hooks.Net.Socket.Create = () =>
  			{
  				//Console.WriteLine($"Creating socket {nameof(LinuxTcpSocket)}");
  				return new LinuxTcpSocket();
  				//return new OTAPI.Sockets.PoolSocket();
  				//return new Terraria.Net.Sockets.TcpSocket();
  			};
  			OTAPI.Hooks.Player.Announce = (int playerId) =>
  			{
  				//TShock handles this
  				return OTAPI.HookResult.Cancel;
  			};
  
  			Main.SettingsUnlock_WorldEvil = true;
  
  			TerrariaApi.Reporting.CrashReporter.HeapshotRequesting += CrashReporter_HeapshotRequesting;
  
  			try
  			{
  				CliParser.Reset();
  				HandleCommandLine(Environment.GetCommandLineArgs());
  
  				if (!Directory.Exists(SavePath))
  					Directory.CreateDirectory(SavePath);
  
  				TShockConfig.OnConfigRead += OnConfigRead;
  				FileTools.SetupConfig();
  
  				Main.ServerSideCharacter = ServerSideCharacterConfig.Settings.Enabled;
  
  				//TSAPI previously would do this automatically, but the vanilla server wont
  				if (Netplay.ServerIP == null)
  					Netplay.ServerIP = IPAddress.Any;
  
  				DateTime now = DateTime.Now;
  				// Log path was not already set by the command line parameter?
  				if (LogPath == LogPathDefault)
  					LogPath = Config.Settings.LogPath;
  				try
  				{
  					logFilename = Path.Combine(LogPath, now.ToString(LogFormat) + ".log");
  					if (!Directory.Exists(LogPath))
  						Directory.CreateDirectory(LogPath);
  				}
  				catch (Exception ex)
  				{
  					logPathSetupWarning =
  						"Could not apply the given log path / log format, defaults will be used. Exception details:\n" + ex;
  
  					ServerApi.LogWriter.PluginWriteLine(this, logPathSetupWarning, TraceLevel.Error);
  
  					// Problem with the log path or format use the default
  					logFilename = Path.Combine(LogPathDefault, now.ToString(LogFormatDefault) + ".log");
  				}
  
  				AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
  			}
  			catch (Exception ex)
  			{
  				// Will be handled by the server api and written to its crashlog.txt.
  				throw new Exception("Fatal TShock initialization exception. See inner exception for details.", ex);
  			}
  
  			// Further exceptions are written to TShock's log from now on.
  			try
  			{
  				if (Config.Settings.StorageType.ToLower() == "sqlite")
  				{
  					string sql = Path.Combine(SavePath, Config.Settings.SqliteDBPath);
  					Directory.CreateDirectory(Path.GetDirectoryName(sql));
  					DB = new SqliteConnection(string.Format("uri=file://{0},Version=3", sql));
  				}
  				else if (Config.Settings.StorageType.ToLower() == "mysql")
  				{
  					try
  					{
  						var hostport = Config.Settings.MySqlHost.Split(':');
  						DB = new MySqlConnection();
  						DB.ConnectionString =
  							String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};",
  								hostport[0],
  								hostport.Length > 1 ? hostport[1] : "3306",
  								Config.Settings.MySqlDbName,
  								Config.Settings.MySqlUsername,
  								Config.Settings.MySqlPassword
  								);
  					}
  					catch (MySqlException ex)
  					{
  						ServerApi.LogWriter.PluginWriteLine(this, ex.ToString(), TraceLevel.Error);
  						throw new Exception("MySql not setup correctly");
  					}
  				}
  				else
  				{
  					throw new Exception("Invalid storage type");
  				}
  
  				if (Config.Settings.UseSqlLogs)
  					Log = new SqlLog(DB, logFilename, LogClear);
  				else
  					Log = new TextLog(logFilename, LogClear);
  
  				if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
  				{
  					Log.ConsoleInfo(
  						"TShock was improperly shut down. Please use the exit command in the future to prevent this.");
  					File.Delete(Path.Combine(SavePath, "tshock.pid"));
  				}
  				File.WriteAllText(Path.Combine(SavePath, "tshock.pid"),
  					Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));
  
  				CliParser.Reset();
  				HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs());
  
  				Backups = new BackupManager(Path.Combine(SavePath, "backups"));
  				Backups.KeepFor = Config.Settings.BackupKeepFor;
  				Backups.Interval = Config.Settings.BackupInterval;
  				Bans = new BanManager(DB);
  				Warps = new WarpManager(DB);
  				Regions = new RegionManager(DB);
  				UserAccounts = new UserAccountManager(DB);
  				Groups = new GroupManager(DB);
  				ProjectileBans = new ProjectileManagager(DB);
  				TileBans = new TileManager(DB);
  				RememberedPos = new RememberedPosManager(DB);
  				CharacterDB = new CharacterManager(DB);
  				ResearchDatastore = new ResearchDatastore(DB);
  				RestApi = new SecureRest(Netplay.ServerIP, Config.Settings.RestApiPort);
  				RestManager = new RestManager(RestApi);
  				RestManager.RegisterRestfulCommands();
  				Bouncer = new Bouncer();
  				RegionSystem = new RegionHandler(Regions);
  				ItemBans = new ItemBans(this, DB);
  
  				var geoippath = "GeoIP.dat";
  				if (Config.Settings.EnableGeoIP && File.Exists(geoippath))
  					Geo = new GeoIPCountry(geoippath);
  
  				Log.ConsoleInfo("TShock {0} ({1}) now running.", Version, VersionCodename);
  
  				ServerApi.Hooks.GamePostInitialize.Register(this, OnPostInit);
  				ServerApi.Hooks.GameUpdate.Register(this, OnUpdate);
  				ServerApi.Hooks.GameHardmodeTileUpdate.Register(this, OnHardUpdate);
  				ServerApi.Hooks.GameStatueSpawn.Register(this, OnStatueSpawn);
  				ServerApi.Hooks.ServerConnect.Register(this, OnConnect);
  				ServerApi.Hooks.ServerJoin.Register(this, OnJoin);
  				ServerApi.Hooks.ServerLeave.Register(this, OnLeave);
  				ServerApi.Hooks.ServerChat.Register(this, OnChat);
  				ServerApi.Hooks.ServerCommand.Register(this, ServerHooks_OnCommand);
  				ServerApi.Hooks.NetGetData.Register(this, OnGetData);
  				ServerApi.Hooks.NetSendData.Register(this, NetHooks_SendData);
  				ServerApi.Hooks.NetGreetPlayer.Register(this, OnGreetPlayer);
  				ServerApi.Hooks.NpcStrike.Register(this, NpcHooks_OnStrikeNpc);
  				ServerApi.Hooks.ProjectileSetDefaults.Register(this, OnProjectileSetDefaults);
  				ServerApi.Hooks.WorldStartHardMode.Register(this, OnStartHardMode);
  				ServerApi.Hooks.WorldSave.Register(this, SaveManager.Instance.OnSaveWorld);
  				ServerApi.Hooks.WorldChristmasCheck.Register(this, OnXmasCheck);
  				ServerApi.Hooks.WorldHalloweenCheck.Register(this, OnHalloweenCheck);
  				ServerApi.Hooks.NetNameCollision.Register(this, NetHooks_NameCollision);
  				ServerApi.Hooks.ItemForceIntoChest.Register(this, OnItemForceIntoChest);
  				ServerApi.Hooks.WorldGrassSpread.Register(this, OnWorldGrassSpread);
  				Hooks.PlayerHooks.PlayerPreLogin += OnPlayerPreLogin;
  				Hooks.PlayerHooks.PlayerPostLogin += OnPlayerLogin;
  				Hooks.AccountHooks.AccountDelete += OnAccountDelete;
  				Hooks.AccountHooks.AccountCreate += OnAccountCreate;
  
  				GetDataHandlers.InitGetDataHandler();
  				Commands.InitCommands();
  
  				EnglishLanguage.Initialize();
  
  				if (Config.Settings.RestApiEnabled)
  					RestApi.Start();
  
  				Log.ConsoleInfo("AutoSave " + (Config.Settings.AutoSave ? "Enabled" : "Disabled"));
  				Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled"));
  
  				Initialized?.Invoke();
  
  				Log.ConsoleInfo("Welcome to TShock for Terraria!");
  				Log.ConsoleInfo("TShock comes with no warranty & is free software.");
  				Log.ConsoleInfo("You can modify & distribute it under the terms of the GNU GPLv3.");
  
  			}
  			catch (Exception ex)
  			{
  				Log.Error("Fatal Startup Exception");
  				Log.Error(ex.ToString());
  				Environment.Exit(1);
  			}
  		}
  
  		protected void CrashReporter_HeapshotRequesting(object sender, EventArgs e)
  		{
  			foreach (TSPlayer player in TShock.Players)
  			{
  				player.Account = null;
  			}
  		}
  
  		/// <summary>Dispose - Called when disposing.</summary>
  		/// <param name="disposing">disposing - If set, disposes of all hooks and other systems.</param>
  		protected override void Dispose(bool disposing)
  		{
  			if (disposing)
  			{
  				// NOTE: order is important here
  				if (Geo != null)
  				{
  					Geo.Dispose();
  				}
  				SaveManager.Instance.Dispose();
  
  				ServerApi.Hooks.GamePostInitialize.Deregister(this, OnPostInit);
  				ServerApi.Hooks.GameUpdate.Deregister(this, OnUpdate);
  				ServerApi.Hooks.GameHardmodeTileUpdate.Deregister(this, OnHardUpdate);
  				ServerApi.Hooks.GameStatueSpawn.Deregister(this, OnStatueSpawn);
  				ServerApi.Hooks.ServerConnect.Deregister(this, OnConnect);
  				ServerApi.Hooks.ServerJoin.Deregister(this, OnJoin);
  				ServerApi.Hooks.ServerLeave.Deregister(this, OnLeave);
  				ServerApi.Hooks.ServerChat.Deregister(this, OnChat);
  				ServerApi.Hooks.ServerCommand.Deregister(this, ServerHooks_OnCommand);
  				ServerApi.Hooks.NetGetData.Deregister(this, OnGetData);
  				ServerApi.Hooks.NetSendData.Deregister(this, NetHooks_SendData);
  				ServerApi.Hooks.NetGreetPlayer.Deregister(this, OnGreetPlayer);
  				ServerApi.Hooks.NpcStrike.Deregister(this, NpcHooks_OnStrikeNpc);
  				ServerApi.Hooks.ProjectileSetDefaults.Deregister(this, OnProjectileSetDefaults);
  				ServerApi.Hooks.WorldStartHardMode.Deregister(this, OnStartHardMode);
  				ServerApi.Hooks.WorldSave.Deregister(this, SaveManager.Instance.OnSaveWorld);
  				ServerApi.Hooks.WorldChristmasCheck.Deregister(this, OnXmasCheck);
  				ServerApi.Hooks.WorldHalloweenCheck.Deregister(this, OnHalloweenCheck);
  				ServerApi.Hooks.NetNameCollision.Deregister(this, NetHooks_NameCollision);
  				ServerApi.Hooks.ItemForceIntoChest.Deregister(this, OnItemForceIntoChest);
  				ServerApi.Hooks.WorldGrassSpread.Deregister(this, OnWorldGrassSpread);
  				TShockAPI.Hooks.PlayerHooks.PlayerPostLogin -= OnPlayerLogin;
  
  				if (File.Exists(Path.Combine(SavePath, "tshock.pid")))
  				{
  					File.Delete(Path.Combine(SavePath, "tshock.pid"));
  				}
  
  				RestApi.Dispose();
  				Log.Dispose();
  
  				RegionSystem.Dispose();
  			}
  			base.Dispose(disposing);
  		}
  
  		/// <summary>OnPlayerLogin - Fires the PlayerLogin hook to listening plugins.</summary>
  		/// <param name="args">args - The PlayerPostLoginEventArgs object.</param>
  		private void OnPlayerLogin(PlayerPostLoginEventArgs args)
  		{
  			List<String> KnownIps = new List<string>();
  			if (!string.IsNullOrWhiteSpace(args.Player.Account.KnownIps))
  			{
  				KnownIps = JsonConvert.DeserializeObject<List<String>>(args.Player.Account.KnownIps);
  			}
  
  			if (KnownIps.Count == 0)
  			{
  				KnownIps.Add(args.Player.IP);
  			}
  			else
  			{
  				bool last = KnownIps.Last() == args.Player.IP;
  				if (!last)
  				{
  					if (KnownIps.Count == 100)
  					{
  						KnownIps.RemoveAt(0);
  					}
  
  					KnownIps.Add(args.Player.IP);
  				}
  			}
  
  			args.Player.Account.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
  			UserAccounts.UpdateLogin(args.Player.Account);
  
  			Bans.CheckBan(args.Player);
  		}
  
  		/// <summary>OnAccountDelete - Internal hook fired on account delete.</summary>
  		/// <param name="args">args - The AccountDeleteEventArgs object.</param>
  		private void OnAccountDelete(Hooks.AccountDeleteEventArgs args)
  		{
  			CharacterDB.RemovePlayer(args.Account.ID);
  		}
  
  		/// <summary>OnAccountCreate - Internal hook fired on account creation.</summary>
  		/// <param name="args">args - The AccountCreateEventArgs object.</param>
  		private void OnAccountCreate(Hooks.AccountCreateEventArgs args)
  		{
  			CharacterDB.SeedInitialData(UserAccounts.GetUserAccount(args.Account));
  		}
  
  		/// <summary>OnPlayerPreLogin - Internal hook fired when on player pre login.</summary>
  		/// <param name="args">args - The PlayerPreLoginEventArgs object.</param>
  		private void OnPlayerPreLogin(Hooks.PlayerPreLoginEventArgs args)
  		{
  			if (args.Player.IsLoggedIn)
  				args.Player.SaveServerCharacter();
  		}
  
  		/// <summary>NetHooks_NameCollision - Internal hook fired when a name collision happens.</summary>
  		/// <param name="args">args - The NameCollisionEventArgs object.</param>
  		private void NetHooks_NameCollision(NameCollisionEventArgs args)
  		{
  			if (args.Handled)
  			{
  				return;
  			}
  
  			string ip = Utils.GetRealIP(Netplay.Clients[args.Who].Socket.GetRemoteAddress().ToString());
  
  			var player = Players.First(p => p != null && p.Name == args.Name && p.Index != args.Who);
  			if (player != null)
  			{
  				if (player.IP == ip)
  				{
  					Netplay.Clients[player.Index].PendingTermination = true;
  					args.Handled = true;
  					return;
  				}
  				if (player.IsLoggedIn)
  				{
  					var ips = JsonConvert.DeserializeObject<List<string>>(player.Account.KnownIps);
  					if (ips.Contains(ip))
  					{
  						Netplay.Clients[player.Index].PendingTermination = true;
  						args.Handled = true;
  					}
  				}
  			}
  		}
  
  		/// <summary>OnItemForceIntoChest - Internal hook fired when a player quick stacks items into a chest.</summary>
  		/// <param name="args">The <see cref="ForceItemIntoChestEventArgs"/> object.</param>
  		private void OnItemForceIntoChest(ForceItemIntoChestEventArgs args)
  		{
  			if (args.Handled)
  			{
  				return;
  			}
  
  			if (args.Player == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			TSPlayer tsplr = Players[args.Player.whoAmI];
  			if (tsplr == null)
  			{
  				args.Handled = true;
  				return;
  			}
  
  			if (args.Chest != null)
  			{
  				if (Config.Settings.RegionProtectChests && !Regions.CanBuild((int)args.WorldPosition.X, (int)args.WorldPosition.Y, tsplr))
  				{
  					args.Handled = true;
  					return;
  				}
  
  				if (!tsplr.IsInRange(args.Chest.x, args.Chest.y))
  				{
  					args.Handled = true;
  					return;
  				}
  			}
  		}
  
  		/// <summary>OnXmasCheck - Internal hook fired when the XMasCheck happens.</summary>
  		/// <param name="args">args - The ChristmasCheckEventArgs object.</param>
  		private void OnXmasCheck(ChristmasCheckEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if (Config.Settings.ForceXmas)
  			{
  				args.Xmas = true;
  				args.Handled = true;
  			}
  		}
  
  		/// <summary>OnHalloweenCheck - Internal hook fired when the HalloweenCheck happens.</summary>
  		/// <param name="args">args - The HalloweenCheckEventArgs object.</param>
  		private void OnHalloweenCheck(HalloweenCheckEventArgs args)
  		{
  			if (args.Handled)
  				return;
  
  			if (Config.Settings.ForceHalloween)
  			{
  				args.Halloween = true;
  				args.Handled = true;
  			}
  		}
  
  		/// <summary>
  		/// Handles exceptions that we didn't catch earlier in the code, or in Terraria.
  		/// </summary>
  		/// <param name="sender">sender - The object that sent the exception.</param>
  		/// <param name="e">e - The UnhandledExceptionEventArgs object.</param>
  		private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
  		{
  			Log.Error(e.ExceptionObject.ToString());
  
  			if (e.ExceptionObject.ToString().Contains("Terraria.Netplay.ListenForClients") ||
  				e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop"))
  			{
  				var sb = new List<string>();
  				for (int i = 0; i < Netplay.Clients.Length; i++)
  				{
  					if (Netplay.Clients[i] == null)
  					{
  						sb.Add("Client[" + i + "]");
  					}
  					else if (Netplay.Clients[i].Socket == null)
  					{
  						sb.Add("Tcp[" + i + "]");
  					}
  				}
  				Log.Error(string.Join(", ", sb));
  			}
  
  			if (e.IsTerminating)
  			{
  				if (Main.worldPathName != null && Config.Settings.SaveWorldOnCrash)
  				{
  					Main.ActiveWorldFileData._path += ".crash";
  					SaveManager.Instance.SaveWorld();
  				}
  			}
  		}
  
  		/// <summary>HandleCommandLine - Handles the command line parameters passed to the server.</summary>
  		/// <param name="parms">parms - The array of arguments passed in through the command line.</param>
  		private void HandleCommandLine(string[] parms)
  		{
  			string path = null;
  
  			//Generic method for doing a path sanity check
  
gt: 			Action<string> pathChecker = (p) =>