context: #region Header
  // **********
  // ServUO - Main.cs
  // **********
  #endregion
  
  #region References
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Reflection;
  using System.Runtime;
  using System.Runtime.InteropServices;
  using System.Text;
  using System.Threading;
  using System.Threading.Tasks;
  
  using CustomsFramework;
  
  using Server.Network;
  using System.Collections;
  #endregion
  
  namespace Server
  {
  	public delegate void Slice();
  
  	public static class Core
  	{
  		static Core()
  		{
  			DataDirectories = new List<string>();
  
  			GlobalMaxUpdateRange = 24;
  			GlobalUpdateRange = 18;
  		}
  
  		private static bool _Crashed;
  		private static Thread _TimerThread;
  		private static string _BaseDirectory;
  		private static string _ExePath;
  
  		private static bool _Cache = true;
  
  		private static bool _Profiling;
  		private static DateTime _ProfileStart;
  		private static TimeSpan _ProfileTime;
  
  		public static MessagePump MessagePump { get; set; }
  
  		public static Slice Slice;
  
  		public static bool Profiling
  		{
  			get { return _Profiling; }
  			set
  			{
  				if (_Profiling == value)
  				{
  					return;
  				}
  
  				_Profiling = value;
  
  				if (_ProfileStart > DateTime.MinValue)
  				{
  					_ProfileTime += DateTime.UtcNow - _ProfileStart;
  				}
  
  				_ProfileStart = (_Profiling ? DateTime.UtcNow : DateTime.MinValue);
  			}
  		}
  
  		public static TimeSpan ProfileTime
  		{
  			get
  			{
  				if (_ProfileStart > DateTime.MinValue)
  				{
  					return _ProfileTime + (DateTime.UtcNow - _ProfileStart);
  				}
  
  				return _ProfileTime;
  			}
  		}
  
  		public static bool Service { get; private set; }
  		public static bool Debug { get; private set; }
  
  		public static bool HaltOnWarning { get; private set; }
  		public static bool VBdotNet { get; private set; }
  
  		public static List<string> DataDirectories { get; private set; }
  
  		public static Assembly Assembly { get; set; }
  
  		public static Version Version { get { return Assembly.GetName().Version; } }
  
  		public static Process Process { get; private set; }
  		public static Thread Thread { get; private set; }
  
  		public static MultiTextWriter MultiConsoleOut { get; private set; }
  
  		/* 
  		 * DateTime.Now and DateTime.UtcNow are based on actual system clock time.
  		 * The resolution is acceptable but large clock jumps are possible and cause issues.
  		 * GetTickCount and GetTickCount64 have poor resolution.
  		 * GetTickCount64 is unavailable on Windows XP and Windows Server 2003.
  		 * Stopwatch.GetTimestamp() (QueryPerformanceCounter) is high resolution, but
  		 * somewhat expensive to call because of its defference to DateTime.Now,
  		 * which is why Stopwatch has been used to verify HRT before calling GetTimestamp(),
  		 * enabling the usage of DateTime.UtcNow instead.
  		 */
  
  		private static readonly bool _HighRes = Stopwatch.IsHighResolution;
  
  		private static readonly double _HighFrequency = 1000.0 / Stopwatch.Frequency;
  		private const double _LowFrequency = 1000.0 / TimeSpan.TicksPerSecond;
  
  		private static bool _UseHRT;
  
  		public static bool UsingHighResolutionTiming { get { return _UseHRT && _HighRes && !Unix; } }
  
  		public static long TickCount { get { return (long)Ticks; } }
  
  		public static double Ticks
  		{
  			get
  			{
  				if (_UseHRT && _HighRes && !Unix)
  				{
  					return Stopwatch.GetTimestamp() * _HighFrequency;
  				}
  
  				return DateTime.UtcNow.Ticks * _LowFrequency;
  			}
  		}
  
  		public static readonly bool Is64Bit = Environment.Is64BitProcess;
  
  		public static bool MultiProcessor { get; private set; }
  		public static int ProcessorCount { get; private set; }
  
  		public static bool Unix { get; private set; }
  
  		public static string FindDataFile(string path)
  		{
  			if (DataDirectories.Count == 0)
  			{
  				throw new InvalidOperationException("Attempted to FindDataFile before DataDirectories list has been filled.");
  			}
  
  			string fullPath = null;
  
  			foreach (string p in DataDirectories)
  			{
  				fullPath = Path.Combine(p, path);
  
  				if (File.Exists(fullPath))
  				{
  					break;
  				}
  
  				fullPath = null;
  			}
  
  			return fullPath;
  		}
  
  		public static string FindDataFile(string format, params object[] args)
  		{
  			return FindDataFile(String.Format(format, args));
  		}
  
  		#region Expansions
  		public static Expansion Expansion { get; set; }
  
  		public static bool T2A { get { return Expansion >= Expansion.T2A; } }
  		public static bool UOR { get { return Expansion >= Expansion.UOR; } }
  		public static bool UOTD { get { return Expansion >= Expansion.UOTD; } }
  		public static bool LBR { get { return Expansion >= Expansion.LBR; } }
  		public static bool AOS { get { return Expansion >= Expansion.AOS; } }
  		public static bool SE { get { return Expansion >= Expansion.SE; } }
  		public static bool ML { get { return Expansion >= Expansion.ML; } }
  		public static bool SA { get { return Expansion >= Expansion.SA; } }
  		public static bool HS { get { return Expansion >= Expansion.HS; } }
  		public static bool TOL { get { return Expansion >= Expansion.TOL; } }
  		#endregion
  
  		public static string ExePath { get { return _ExePath ?? (_ExePath = Assembly.Location); } }
  
  		public static string BaseDirectory
  		{
  			get
  			{
  				if (_BaseDirectory == null)
  				{
  					try
  					{
  						_BaseDirectory = ExePath;
  
  						if (_BaseDirectory.Length > 0)
  						{
  							_BaseDirectory = Path.GetDirectoryName(_BaseDirectory);
  						}
  					}
  					catch
  					{
  						_BaseDirectory = "";
  					}
  				}
  
  				return _BaseDirectory;
  			}
  		}
  
  		private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
  		{
  			Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:");
  			Console.WriteLine(e.ExceptionObject);
  
  			if (e.IsTerminating)
  			{
  				_Crashed = true;
  
  				bool close = false;
  
  				try
  				{
  					CrashedEventArgs args = new CrashedEventArgs(e.ExceptionObject as Exception);
  
  					EventSink.InvokeCrashed(args);
  
  					close = args.Close;
  				}
  				catch
  				{ }
  
  				if (!close && !Service)
  				{
  					try
  					{
  						foreach (Listener l in MessagePump.Listeners)
  						{
  							l.Dispose();
  						}
  					}
  					catch
  					{ }
  
  					Console.WriteLine("This exception is fatal, press return to exit");
  					Console.ReadLine();
  				}
  
  				Kill();
  			}
  		}
  
  		private enum ConsoleEventType
  		{
  			CTRL_C_EVENT,
  			CTRL_BREAK_EVENT,
  			CTRL_CLOSE_EVENT,
  			CTRL_LOGOFF_EVENT = 5,
  			CTRL_SHUTDOWN_EVENT
  		}
  
  		private delegate bool ConsoleEventHandler(ConsoleEventType type);
  
  		private static ConsoleEventHandler m_ConsoleEventHandler;
  
  		private static class UnsafeNativeMethods
  		{
  			[DllImport("Kernel32")]
  			public static extern bool SetConsoleCtrlHandler(ConsoleEventHandler callback, bool add);
  		}
  
  		private static bool OnConsoleEvent(ConsoleEventType type)
  		{
  			if (World.Saving || (Service && type == ConsoleEventType.CTRL_LOGOFF_EVENT))
  			{
  				return true;
  			}
  
  			Kill(); //Kill -> HandleClosed will handle waiting for the completion of flushing to disk
  
  			return true;
  		}
  
  		private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
  		{
  			HandleClosed();
  		}
  
  		public static bool Closing { get; private set; }
  
  		private static int _CycleIndex = 1;
  		private static readonly float[] _CyclesPerSecond = new float[100];
  
  		public static float CyclesPerSecond { get { return _CyclesPerSecond[(_CycleIndex - 1) % _CyclesPerSecond.Length]; } }
  
  		public static float AverageCPS { get { return _CyclesPerSecond.Take(_CycleIndex).Average(); } }
  
  		public static void Kill()
  		{
  			Kill(false);
  		}
  
  		public static void Kill(bool restart)
  		{
  			HandleClosed();
  
  			if (restart)
  			{
  				Process.Start(ExePath, Arguments);
  			}
  
  			Process.Kill();
  		}
  
  		private static void HandleClosed()
  		{
  			if (Closing)
  			{
  				return;
  			}
  
  			Closing = true;
  
  			Console.Write("Exiting...");
  
  			World.WaitForWriteCompletion();
  
  			if (!_Crashed)
  			{
  				EventSink.InvokeShutdown(new ShutdownEventArgs());
  			}
  
  			Timer.TimerThread.Set();
  
  			Console.WriteLine("done");
  		}
  
  		private static readonly AutoResetEvent _Signal = new AutoResetEvent(true);
  
  		public static void Set()
  		{
  			_Signal.Set();
  		}
  
  		public static void Main(string[] args)
  		{
  			AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
  			AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
  
  			foreach (string a in args)
  			{
  				if (Insensitive.Equals(a, "-debug"))
  				{
  					Debug = true;
  				}
  				else if (Insensitive.Equals(a, "-service"))
  				{
  					Service = true;
  				}
  				else if (Insensitive.Equals(a, "-profile"))
  				{
  					Profiling = true;
  				}
  				else if (Insensitive.Equals(a, "-nocache"))
  				{
  					_Cache = false;
  				}
  				else if (Insensitive.Equals(a, "-haltonwarning"))
  				{
  					HaltOnWarning = true;
  				}
  				else if (Insensitive.Equals(a, "-vb"))
  				{
  					VBdotNet = true;
  				}
  				else if (Insensitive.Equals(a, "-usehrt"))
  				{
  					_UseHRT = true;
  				}
  			}
  
  			try
  			{
  				if (Service)
  				{
  					if (!Directory.Exists("Logs"))
  					{
  						Directory.CreateDirectory("Logs");
  					}
  
  					Console.SetOut(MultiConsoleOut = new MultiTextWriter(new FileLogger("Logs/Console.log")));
  				}
  				else
  				{
  					Console.SetOut(MultiConsoleOut = new MultiTextWriter(Console.Out));
  				}
  			}
  			catch
  			{ }
  
  			Thread = Thread.CurrentThread;
  			Process = Process.GetCurrentProcess();
  			Assembly = Assembly.GetEntryAssembly();
  
  			if (Thread != null)
  			{
  				Thread.Name = "Core Thread";
  			}
  
  			if (BaseDirectory.Length > 0)
  			{
  				Directory.SetCurrentDirectory(BaseDirectory);
  			}
  
  			Timer.TimerThread ttObj = new Timer.TimerThread();
  
  			_TimerThread = new Thread(ttObj.TimerMain)
  			{
  				Name = "Timer Thread"
  			};
  
  			Version ver = Assembly.GetName().Version;
  
  			// Added to help future code support on forums, as a 'check' people can ask for to it see if they recompiled core or not
  			Utility.PushColor(ConsoleColor.DarkGreen);
  			Console.WriteLine(new String('-', Console.BufferWidth));
  			Utility.PopColor();
  			Utility.PushColor(ConsoleColor.Cyan);
  			Console.WriteLine(
  				"ServUO - [https://www.servuo.com] Version {0}.{1}, Build {2}.{3}",
  				ver.Major,
  				ver.Minor,
  				ver.Build,
  				ver.Revision);
  			Utility.PopColor();
  
  			string s = Arguments;
  
  			if (s.Length > 0)
  			{
  				Utility.PushColor(ConsoleColor.Yellow);
  				Console.WriteLine("Core: Running with arguments: {0}", s);
  				Utility.PopColor();
  			}
  
  			ProcessorCount = Environment.ProcessorCount;
  
  			if (ProcessorCount > 1)
  			{
  				MultiProcessor = true;
  			}
  
  			if (MultiProcessor || Is64Bit)
  			{
  				Utility.PushColor(ConsoleColor.Green);
  				Console.WriteLine(
  					"Core: Optimizing for {0} {2}processor{1}",
  					ProcessorCount,
  					ProcessorCount == 1 ? "" : "s",
  					Is64Bit ? "64-bit " : "");
  				Utility.PopColor();
  			}
  
  			int platform = (int)Environment.OSVersion.Platform;
  
  			if (platform == 4 || platform == 128)
  			{
  				// MS 4, MONO 128
  				Unix = true;
  				Utility.PushColor(ConsoleColor.Yellow);
  				Console.WriteLine("Core: Unix environment detected");
  				Utility.PopColor();
  			}
  			else
  			{
  				m_ConsoleEventHandler = OnConsoleEvent;
  				UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true);
  			}
  
  			if (GCSettings.IsServerGC)
  			{
  				Utility.PushColor(ConsoleColor.DarkYellow);
  				Console.WriteLine("Core: Server garbage collection mode enabled");
  				Utility.PopColor();
  			}
  
  			if (_UseHRT)
  			{
  				Utility.PushColor(ConsoleColor.DarkYellow);
  				Console.WriteLine(
  					"Core: Requested high resolution timing ({0})",
  					UsingHighResolutionTiming ? "Supported" : "Unsupported");
  				Utility.PopColor();
  			}
  
  			Utility.PushColor(ConsoleColor.DarkYellow);
  			Console.WriteLine("RandomImpl: {0} ({1})", RandomImpl.Type.Name, RandomImpl.IsHardwareRNG ? "Hardware" : "Software");
  			Utility.PopColor();
  
  			Utility.PushColor(ConsoleColor.DarkYellow);
  			Console.WriteLine("Core: Loading config...");
  			Config.Load();
  			Utility.PopColor();
  
  			while (!ScriptCompiler.Compile(Debug, _Cache))
  			{
  				Utility.PushColor(ConsoleColor.Red);
  				Console.WriteLine("Scripts: One or more scripts failed to compile or no script files were found.");
  				Utility.PopColor();
  
  				if (Service)
  				{
  					return;
  				}
  
  				Console.WriteLine(" - Press return to exit, or R to try again.");
  
  				if (Console.ReadKey(true).Key != ConsoleKey.R)
  				{
  					return;
  				}
  			}
  
  			ScriptCompiler.Invoke("Configure");
  
  			Region.Load();
  			World.Load();
  
  			ScriptCompiler.Invoke("Initialize");
  
  			MessagePump messagePump = MessagePump = new MessagePump();
  
  			_TimerThread.Start();
  
  			foreach (Map m in Map.AllMaps)
  			{
  				m.Tiles.Force();
  			}
  
  			NetState.Initialize();
  
  			EventSink.InvokeServerStarted();
  
  			try
  			{
  				long now, last = TickCount;
  
  				const int sampleInterval = 100;
  				const float ticksPerSecond = 1000.0f * sampleInterval;
  
  				long sample = 0;
  
  				while (!Closing)
  				{
  					_Signal.WaitOne();
  
  					Mobile.ProcessDeltaQueue();
  					Item.ProcessDeltaQueue();
  
  					Timer.Slice();
  					messagePump.Slice();
  
  					NetState.FlushAll();
  					NetState.ProcessDisposedQueue();
  
  					if (Slice != null)
  					{
  						Slice();
  					}
  
  					if (sample++ % sampleInterval != 0)
  					{
  						continue;
  					}
  
  					now = TickCount;
  					_CyclesPerSecond[_CycleIndex++ % _CyclesPerSecond.Length] = ticksPerSecond / (now - last);
  					last = now;
  				}
  			}
  			catch (Exception e)
  			{
  				CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true));
  			}
  		}
  
  		public static string Arguments
  		{
  			get
  			{
  				StringBuilder sb = new StringBuilder();
  
  				if (Debug)
  				{
  					Utility.Separate(sb, "-debug", " ");
  				}
  
  				if (Service)
  				{
  					Utility.Separate(sb, "-service", " ");
  				}
  
  				if (Profiling)
  				{
  					Utility.Separate(sb, "-profile", " ");
  				}
  
  				if (!_Cache)
  				{
  					Utility.Separate(sb, "-nocache", " ");
  				}
  
  				if (HaltOnWarning)
  				{
  					Utility.Separate(sb, "-haltonwarning", " ");
  				}
  
  				if (VBdotNet)
  				{
  					Utility.Separate(sb, "-vb", " ");
  				}
  
  				if (_UseHRT)
  				{
  					Utility.Separate(sb, "-usehrt", " ");
  				}
  
  				return sb.ToString();
  			}
  		}
  
  		public static int GlobalUpdateRange { get; set; }
  		public static int GlobalMaxUpdateRange { get; set; }
  		
  		private static int m_ItemCount, m_MobileCount, m_CustomsCount;
  
  		public static int ScriptItems { get { return m_ItemCount; } }
  		public static int ScriptMobiles { get { return m_MobileCount; } }
  		public static int ScriptCustoms { get { return m_CustomsCount; } }
  
  		public static void VerifySerialization()
  		{
  			m_ItemCount = 0;
  			m_MobileCount = 0;
  			m_CustomsCount = 0;
  
  			VerifySerialization(Assembly.GetCallingAssembly());
  
  			foreach (Assembly a in ScriptCompiler.Assemblies)
  			{
  				VerifySerialization(a);
  			}
  		}
  
  		private static readonly Type[] m_SerialTypeArray = {typeof(Serial)};
  		private static readonly Type[] m_CustomsSerialTypeArray = {typeof(CustomSerial)};
  
  		private static void VerifyType(Type t)
  		{
  
gt: 			bool isItem = t.IsSubclassOf(typeof(Item));