#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 Server.Network; #endregion namespace Server { public delegate void Slice(); public static class Core { static Core() { DataDirectories = new List(); GlobalMaxUpdateRange = 24; GlobalUpdateRange = 18; GlobalRadarRange = 40; } public static Action CrashedHandler { get; set; } public static bool Crashed => _Crashed; 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 => _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 NoConsole { 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 DataDirectories { get; private set; } public static Assembly Assembly { get; set; } public static Version Version => 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 => _UseHRT && _HighRes && !Unix; public static long TickCount => (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 (var 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 => Expansion.EJ; public static bool T2A => Expansion >= Expansion.T2A; public static bool UOR => Expansion >= Expansion.UOR; public static bool UOTD => Expansion >= Expansion.UOTD; public static bool LBR => Expansion >= Expansion.LBR; public static bool AOS => Expansion >= Expansion.AOS; public static bool SE => Expansion >= Expansion.SE; public static bool ML => Expansion >= Expansion.ML; public static bool SA => Expansion >= Expansion.SA; public static bool HS => Expansion >= Expansion.HS; public static bool TOL => Expansion >= Expansion.TOL; public static bool EJ => Expansion >= Expansion.EJ; #endregion public static string ExePath => _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; var close = false; var args = new CrashedEventArgs(e.ExceptionObject as Exception); try { EventSink.InvokeCrashed(args); close = args.Close; } catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } if (CrashedHandler != null) { try { CrashedHandler(args); close = args.Close; } catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } } if (!close && !Service) { try { foreach (var 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 => _CyclesPerSecond[(_CycleIndex - 1) % _CyclesPerSecond.Length]; public static float AverageCPS => _CyclesPerSecond.Take(_CycleIndex).Average(); public static void Kill() { Kill(false); } #if MONO private static string[] SupportedTerminals => new string[] { "xfce4-terminal", "gnome-terminal", "xterm" }; private static void RebootTerminal(int i = 0) { if(SupportedTerminals.Length > i) { try { if(SupportedTerminals[i] != "xterm") Process.Start(SupportedTerminals[i], $"--working-directory={BaseDirectory} -x ./ServUO.sh"); else Process.Start(SupportedTerminals[i], $"-lcc {BaseDirectory} -e ./ServUO.sh"); Thread.Sleep(500); // a sleep here to not close the programm to quick, so that the new windows cant start. } catch(System.ComponentModel.Win32Exception) { RebootTerminal(i+1); } } } #endif public static void Kill(bool restart) { HandleClosed(); if (restart) { #if MONO RebootTerminal(); Environment.Exit(0); } #else Process.Start(ExePath, Arguments); } Process.Kill(); #endif } private static void HandleClosed() { if (Closing) { return; } Closing = true; if (Debug) Console.Write("Exiting..."); World.WaitForWriteCompletion(); if (!_Crashed) { EventSink.InvokeShutdown(new ShutdownEventArgs()); } Timer.TimerThread.Set(); if (Debug) Console.WriteLine("done"); } private static readonly AutoResetEvent _Signal = new AutoResetEvent(true); public static void Set() { _Signal.Set(); } public static void Setup(string[] args) { #if DEBUG Debug = true; #endif AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; foreach (var 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; } else if (Insensitive.Equals(a, "-noconsole")) { NoConsole = true; } else if (Insensitive.Equals(a, "-h") || Insensitive.Equals(a, "-help")) { Console.WriteLine("An Ultima Online server emulator written in C# - Visit https://www.servuo.com for more information.\n\n"); Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + " [Parameter]\n\n"); Console.WriteLine(" -debug Starting ServUO in Debug Mode. Debug Mode is being used in Core and Scripts to give extended inforamtion during runtime."); Console.WriteLine(" -haltonwarning ServUO halts if any warning is raised during compilation of scripts."); Console.WriteLine(" -h or -help Displays this help text."); Console.WriteLine(" -nocache No known effect."); Console.WriteLine(" -noconsole No user interaction during startup and runtime."); Console.WriteLine(" -profile Enables profiling allowing to get performance diagnostic information of packets, timers etc. in AdminGump -> Maintenance. Use with caution. This increases server load."); Console.WriteLine(" -service This parameter should be set if you're running ServUO as a Windows Service. No user interaction. *Windows only*"); Console.WriteLine(" -usehrt Enables High Resolution Timing if requirements are met. Increasing the resolution of the timer. *Windows only*"); Console.WriteLine(" -vb Enables compilation of VB.NET Scripts. Without this option VB.NET Scripts are skipped."); Environment.Exit(0); } } if (!Environment.UserInteractive || Service) { NoConsole = 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 (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } Thread = Thread.CurrentThread; Process = Process.GetCurrentProcess(); Assembly = Assembly.GetEntryAssembly(); if (Thread != null) { Thread.Name = "Core Thread"; } if (BaseDirectory.Length > 0) { Directory.SetCurrentDirectory(BaseDirectory); } var ttObj = new Timer.TimerThread(); _TimerThread = new Thread(ttObj.TimerMain) { Name = "Timer Thread" }; var ver = Assembly.GetName().Version; var buildDate = new DateTime(2000, 1, 1).AddDays(ver.Build).AddSeconds(ver.Revision * 2); Utility.PushColor(ConsoleColor.Cyan); #if DEBUG Console.WriteLine( "ServUO - [https://www.servuo.com] Version {0}.{1}, Build {2}.{3} - Build on {4} UTC - Debug", ver.Major, ver.Minor, ver.Build, ver.Revision, buildDate); #else Console.WriteLine( "ServUO - [https://www.servuo.com] Version {0}.{1}, Build {2}.{3} - Build on {4} UTC - Release", ver.Major, ver.Minor, ver.Build, ver.Revision, buildDate); #endif Utility.PopColor(); var 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(); } string dotnet = null; if (Type.GetType("Mono.Runtime") != null) { var displayName = Type.GetType("Mono.Runtime").GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); if (displayName != null) { dotnet = displayName.Invoke(null, null).ToString(); Utility.PushColor(ConsoleColor.Yellow); Console.WriteLine("Core: Unix environment detected"); Utility.PopColor(); Unix = true; } } else { m_ConsoleEventHandler = OnConsoleEvent; UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true); } #if NETFX_472 dotnet = "4.7.2"; #endif #if NETFX_48 dotnet = "4.8"; #endif if (String.IsNullOrEmpty(dotnet)) dotnet = "MONO/CSC/Unknown"; Utility.PushColor(ConsoleColor.Green); Console.WriteLine("Core: Compiled for " + (Unix ? "MONO and running on {0}" : ".NET {0}"), dotnet); Utility.PopColor(); if (GCSettings.IsServerGC) { Utility.PushColor(ConsoleColor.Green); 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.Green); 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 = new MessagePump(); foreach (var m in Map.AllMaps) { m.Tiles.Force(); } NetState.Initialize(); } public static void Run() { EventSink.InvokeServerStarted(); _TimerThread.Start(); 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 { var 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", " "); } if (NoConsole) { Utility.Separate(sb, "-noconsole", " "); } return sb.ToString(); } } public static int GlobalUpdateRange { get; set; } public static int GlobalMaxUpdateRange { get; set; } public static int GlobalRadarRange { get; set; } private static int m_ItemCount, m_MobileCount, m_CustomsCount; public static int ScriptItems => m_ItemCount; public static int ScriptMobiles => m_MobileCount; public static int ScriptCustoms => m_CustomsCount; public static void VerifySerialization() { m_ItemCount = 0; m_MobileCount = 0; m_CustomsCount = 0; VerifySerialization(Assembly.GetCallingAssembly()); foreach (var a in ScriptCompiler.Assemblies) { VerifySerialization(a); } } private static readonly Type[] m_SerialTypeArray = { typeof(Serial) }; private static void VerifyType(Type t) { var isItem = t.IsSubclassOf(typeof(Item)); if (isItem || t.IsSubclassOf(typeof(Mobile))) { if (isItem) { Interlocked.Increment(ref m_ItemCount); } else { Interlocked.Increment(ref m_MobileCount); } StringBuilder warningSb = null; try { if (t.GetConstructor(m_SerialTypeArray) == null) { warningSb = new StringBuilder(); warningSb.AppendLine(" - No serialization constructor"); } if ( t.GetMethod( "Serialize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) == null) { if (warningSb == null) { warningSb = new StringBuilder(); } warningSb.AppendLine(" - No Serialize() method"); } if ( t.GetMethod( "Deserialize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) == null) { if (warningSb == null) { warningSb = new StringBuilder(); } warningSb.AppendLine(" - No Deserialize() method"); } if (warningSb != null && warningSb.Length > 0) { Utility.PushColor(ConsoleColor.Yellow); Console.WriteLine("Warning: {0}\n{1}", t, warningSb); Utility.PopColor(); } } catch { Utility.PushColor(ConsoleColor.Yellow); Console.WriteLine("Warning: Exception in serialization verification of type {0}", t); Utility.PopColor(); } } } private static void VerifySerialization(Assembly a) { if (a != null) { Parallel.ForEach(a.GetTypes(), VerifyType); } } } public class FileLogger : TextWriter { public const string DateFormat = "[MMMM dd hh:mm:ss.f tt]: "; private bool _NewLine; public string FileName { get; private set; } public FileLogger(string file) : this(file, false) { } public FileLogger(string file, bool append) { FileName = file; using ( var writer = new StreamWriter( new FileStream(FileName, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read))) { writer.WriteLine(">>>Logging started on {0:f}.", DateTime.Now); //f = Tuesday, April 10, 2001 3:51 PM } _NewLine = true; } public override void Write(char ch) { using (var writer = new StreamWriter(new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.Read))) { if (_NewLine) { writer.Write(DateTime.UtcNow.ToString(DateFormat)); _NewLine = false; } writer.Write(ch); } } public override void Write(string str) { using (var writer = new StreamWriter(new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.Read))) { if (_NewLine) { writer.Write(DateTime.UtcNow.ToString(DateFormat)); _NewLine = false; } writer.Write(str); } } public override void WriteLine(string line) { using (var writer = new StreamWriter(new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.Read))) { if (_NewLine) { writer.Write(DateTime.UtcNow.ToString(DateFormat)); } writer.WriteLine(line); _NewLine = true; } } public override Encoding Encoding => Encoding.Default; } public class MultiTextWriter : TextWriter { private readonly List _Streams; public MultiTextWriter(params TextWriter[] streams) { _Streams = new List(streams); if (_Streams.Count < 0) { throw new ArgumentException("You must specify at least one stream."); } } public void Add(TextWriter tw) { _Streams.Add(tw); } public void Remove(TextWriter tw) { _Streams.Remove(tw); } public override void Write(char ch) { foreach (var t in _Streams) { t.Write(ch); } } public override void WriteLine(string line) { foreach (var t in _Streams) { t.WriteLine(line); } } public override void WriteLine(string line, params object[] args) { WriteLine(String.Format(line, args)); } public override Encoding Encoding => Encoding.Default; } }