diff --git a/.gitignore b/.gitignore index 76af4a723..23f4efad6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /Distribution/ModernUO /Distribution/ModernUO.* /Distribution/Assemblies +/Distribution/Configuration /Distribution/Logs /Distribution/Backups /Distribution/Saves @@ -41,4 +42,4 @@ .DS_Store /packages/* -!/packages/do_not_delete +!do_not_delete diff --git a/Distribution/Configuration/do_not_delete b/Distribution/Configuration/do_not_delete new file mode 100644 index 000000000..e69de29bb diff --git a/Projects/Scripts/Commands/ConvertPlayers.cs b/Projects/Scripts/Commands/ConvertPlayers.cs deleted file mode 100644 index 80804f37d..000000000 --- a/Projects/Scripts/Commands/ConvertPlayers.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Server.Mobiles; -using Server.Network; - -namespace Server.Commands -{ - public class ConvertPlayers - { - public static void Initialize() - { - CommandSystem.Register("ConvertPlayers", AccessLevel.Administrator, Convert_OnCommand); - } - - public static void Convert_OnCommand(CommandEventArgs e) - { - e.Mobile.SendMessage( - "Converting all players to PlayerMobile. You will be disconnected. Please Restart the server after the world has finished saving."); - List mobs = new List(World.Mobiles.Values); - int count = 0; - - foreach (Mobile m in mobs) - if (m.Player && !(m is PlayerMobile)) - { - count++; - m.NetState?.Dispose(); - - PlayerMobile pm = new PlayerMobile(m.Serial); - pm.DefaultMobileInit(); - - List copy = new List(m.Items); - for (int i = 0; i < copy.Count; i++) - pm.AddItem(copy[i]); - - CopyProps(pm, m); - - for (int i = 0; i < m.Skills.Length; i++) - { - pm.Skills[i].Base = m.Skills[i].Base; - pm.Skills[i].SetLockNoRelay(m.Skills[i].Lock); - } - - World.Mobiles[m.Serial] = pm; - } - - if (count > 0) - { - NetState.ProcessDisposedQueue(); - World.Save(); - - Console.WriteLine("{0} players have been converted to PlayerMobile. {1}.", count, - Core.Service ? "The server is now restarting" : "Press any key to restart the server"); - - if (!Core.Service) - Console.ReadKey(true); - - Core.Kill(true); - } - else - { - e.Mobile.SendMessage("Couldn't find any Players to convert."); - } - } - - private static readonly PropertyInfo[] _mobProps = - typeof(Mobile).GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(prop => prop.CanRead && prop.CanWrite).ToArray(); - - private static void CopyProps(Mobile to, Mobile from) - { - foreach (PropertyInfo prop in _mobProps) - try - { - prop.SetValue(to, prop.GetValue(from, null), null); - } - catch - { - // ignored - } - } - } -} diff --git a/Projects/Scripts/Configuration/EmailConfiguration.cs b/Projects/Scripts/Configuration/EmailConfiguration.cs new file mode 100644 index 000000000..ea75d8e3c --- /dev/null +++ b/Projects/Scripts/Configuration/EmailConfiguration.cs @@ -0,0 +1,75 @@ +using System.IO; +using System.Text.Json.Serialization; +using MimeKit; +using Server.Json; + +namespace Server.Configurations +{ + public static class EmailConfiguration + { + public static readonly bool EmailEnabled; + public static readonly MailboxAddress FromAddress; + public static readonly MailboxAddress CrashAddress; + public static readonly MailboxAddress SpeechLogPageAddress; + public static readonly string EmailServer; + public static readonly int EmailPort; + public static readonly string EmailServerUsername; + public static readonly string EmailServerPassword; + public static readonly int EmailSendRetryCount = 5; // seconds + public static readonly int EmailSendRetryDelay = 2; // seconds + + static EmailConfiguration() + { + string filePath = Path.Join(Core.BaseDirectory, "Configuration/email-settings.json"); + Settings settings = JsonConfig.Deserialize(filePath) ?? new Settings(); + + if (settings.emailServer == null || settings.fromAddress == null) + { + JsonConfig.Serialize(filePath, settings); + return; + } + + EmailEnabled = true; + FromAddress = new MailboxAddress(settings.fromName, settings.fromAddress); + CrashAddress = new MailboxAddress(settings.crashName, settings.crashAddress); + SpeechLogPageAddress = new MailboxAddress(settings.speechLogPageName, settings.speechLogPageAddress); + EmailServer = settings.emailServer; + EmailPort = settings.emailPort; + EmailServerUsername = settings.emailUsername; + EmailServerPassword = settings.emailPassword; + } + + internal class Settings + { + [JsonPropertyName("fromAddress")] + internal string fromAddress { get; set; } + + [JsonPropertyName("fromName")] + internal string fromName { get; set; } + + [JsonPropertyName("crashAddress")] + internal string crashAddress { get; set; } + + [JsonPropertyName("crashName")] + internal string crashName { get; set; } + + [JsonPropertyName("speechLogPageAddress")] + internal string speechLogPageAddress { get; set; } + + [JsonPropertyName("speechLogPageName")] + internal string speechLogPageName { get; set; } + + [JsonPropertyName("emailServer")] + internal string emailServer { get; set; } + + [JsonPropertyName("emailPort")] + internal int emailPort { get; set; } + + [JsonPropertyName("emailUsername")] + internal string emailUsername { get; set; } + + [JsonPropertyName("emailPassword")] + internal string emailPassword { get; set; } + } + } +} diff --git a/Projects/Scripts/Engines/Help/PageQueue.cs b/Projects/Scripts/Engines/Help/PageQueue.cs index 377222f43..f00d1b0c0 100644 --- a/Projects/Scripts/Engines/Help/PageQueue.cs +++ b/Projects/Scripts/Engines/Help/PageQueue.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Server.Configurations; using Server.Misc; using Server.Mobiles; using Server.Network; @@ -227,7 +228,7 @@ namespace Server.Engines.Help entry.Sender.SendMessage( "We are sorry, but no staff members are currently available to assist you. Your page will remain in the queue until one becomes available, or until you cancel it manually."); - if (Email.FROM_ADDRESS != null && Email.SPEECH_LOG_PAGE_ADDRESS != null && entry.SpeechLog != null) + if (entry.SpeechLog != null) Email.SendQueueEmail(entry, GetPageTypeName(entry.Type)); } } diff --git a/Projects/Scripts/Misc/AccountPrompt.cs b/Projects/Scripts/Misc/AccountPrompt.cs index 1c5df4dab..6c1e84a1a 100644 --- a/Projects/Scripts/Misc/AccountPrompt.cs +++ b/Projects/Scripts/Misc/AccountPrompt.cs @@ -7,7 +7,7 @@ namespace Server.Misc { public static void Initialize() { - if (Accounts.Count == 0 && !Core.Service) + if (Accounts.Count == 0) { Console.WriteLine("This server has no accounts."); Console.Write("Do you want to create the owner account now? (y/n)"); @@ -36,4 +36,4 @@ namespace Server.Misc } } } -} \ No newline at end of file +} diff --git a/Projects/Scripts/Misc/CrashGuard.cs b/Projects/Scripts/Misc/CrashGuard.cs index f45b60a28..6b2e59495 100644 --- a/Projects/Scripts/Misc/CrashGuard.cs +++ b/Projects/Scripts/Misc/CrashGuard.cs @@ -41,8 +41,7 @@ namespace Server.Misc { Console.Write("Crash: Sending email..."); - if (Email.FROM_ADDRESS != null && Email.CRASH_ADDRESS != null) - Email.SendCrashEmail(filePath); + Email.SendCrashEmail(filePath); } private static string GetRoot() @@ -229,8 +228,7 @@ namespace Server.Misc Console.WriteLine("done"); - if (Email.FROM_ADDRESS != null && Email.CRASH_ADDRESS != null) - SendEmail(filePath); + SendEmail(filePath); } catch { diff --git a/Projects/Scripts/Misc/Email.cs b/Projects/Scripts/Misc/Email.cs index 7e8c77865..248ebffe5 100644 --- a/Projects/Scripts/Misc/Email.cs +++ b/Projects/Scripts/Misc/Email.cs @@ -4,22 +4,13 @@ using System.Threading.Tasks; using MailKit.Net.Smtp; using MimeKit; using Server.Accounting; +using Server.Configurations; using Server.Engines.Help; namespace Server.Misc { public static class Email { - public static readonly MailboxAddress FROM_ADDRESS = new MailboxAddress(Configuration.Instance.emailSettings.FromName, Configuration.Instance.emailSettings.FromAddress); - public static readonly MailboxAddress CRASH_ADDRESS = new MailboxAddress(Configuration.Instance.emailSettings.crashName, Configuration.Instance.emailSettings.crashAddress); - public static readonly MailboxAddress SPEECH_LOG_PAGE_ADDRESS = new MailboxAddress(Configuration.Instance.emailSettings.speechLogPageName, Configuration.Instance.emailSettings.speechLogPageAddress); - public static readonly string EMAIL_SERVER = Configuration.Instance.emailSettings.emailServer; - public static readonly int EMAIL_PORT = Configuration.Instance.emailSettings.emailPort; - public static readonly string EMAIL_SERVER_USERNAME = Configuration.Instance.emailSettings.emailUsername; - public static readonly string EMAIL_SERVER_PASSWORD = Configuration.Instance.emailSettings.emailPassword; - public static readonly int RETRY_SEND_EMAIL_COUNT = 5; - public static readonly int SEND_DELAY_SECONDS = 2; - /// /// Sends Queue-Page request using Email /// @@ -27,12 +18,14 @@ namespace Server.Misc /// public static void SendQueueEmail(PageEntry entry, string pageType) { + if (!EmailConfiguration.EmailEnabled) return; + Mobile sender = entry.Sender; DateTime time = DateTime.UtcNow; var message = new MimeMessage(); - message.From.Add(FROM_ADDRESS); - message.To.Add(SPEECH_LOG_PAGE_ADDRESS); + message.From.Add(EmailConfiguration.FromAddress); + message.To.Add(EmailConfiguration.SpeechLogPageAddress); message.Subject = "ModernUO Speech Log Page Forwarding"; using (StringWriter writer = new StringWriter()) @@ -77,9 +70,11 @@ namespace Server.Misc /// public static void SendCrashEmail(string filePath) { + if (EmailConfiguration.EmailEnabled) return; + var message = new MimeMessage(); - message.From.Add(FROM_ADDRESS); - message.To.Add(CRASH_ADDRESS); + message.From.Add(EmailConfiguration.FromAddress); + message.To.Add(EmailConfiguration.CrashAddress); message.Subject = "Automated ModernUO Crash Report"; var builder = new BodyBuilder { @@ -96,19 +91,21 @@ namespace Server.Misc /// private static async void SendAsync(MimeMessage message) { + if (!EmailConfiguration.EmailEnabled) return; + DateTime now = DateTime.UtcNow; - string messageID = $"<{now:yyyyMMdd}.{now:HHmmssff}@{EMAIL_SERVER}>"; + string messageID = $"<{now:yyyyMMdd}.{now:HHmmssff}@{EmailConfiguration.EmailServer}>"; message.Headers.Add("Message-ID", messageID); - message.From.Add(FROM_ADDRESS); + message.From.Add(EmailConfiguration.FromAddress); - int delay = SEND_DELAY_SECONDS; + int delay = EmailConfiguration.EmailSendRetryDelay; - for (int i = 0; i < RETRY_SEND_EMAIL_COUNT; i++) + for (int i = 0; i < EmailConfiguration.EmailSendRetryCount; i++) try { using SmtpClient client = new SmtpClient(); - await client.ConnectAsync(EMAIL_SERVER, EMAIL_PORT, true); - await client.AuthenticateAsync(EMAIL_SERVER_USERNAME, EMAIL_SERVER_PASSWORD); + await client.ConnectAsync(EmailConfiguration.EmailServer, EmailConfiguration.EmailPort, true); + await client.AuthenticateAsync(EmailConfiguration.EmailServerUsername, EmailConfiguration.EmailServerPassword); await client.SendAsync(message); await client.DisconnectAsync(true); return; diff --git a/Projects/Server/Configuration.cs b/Projects/Server/Configuration.cs deleted file mode 100644 index 6b9b80b51..000000000 --- a/Projects/Server/Configuration.cs +++ /dev/null @@ -1,133 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Configuration.cs - Created: 2019/10/04 - Updated: 2020/01/19 * - * * - * 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 . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server -{ - public class Configuration - { - private static Configuration m_Configuration; - - public static Configuration Instance => m_Configuration ??= ReadConfiguration(); - - [JsonPropertyName("dataDirectories")] - public List DataDirectories { get; set; } = new List(); - - [JsonPropertyName("emailSettings")] - public EmailSettings emailSettings { get; set; } = new EmailSettings(); - - private static string FilePath => Path.Join(Core.BaseDirectory, "Data/modernuo.json"); - - private static void PromptDataDirectories(Configuration config) - { - Console.WriteLine("Please enter the Ultima Online directory:"); - - string directory; - do - { - Console.Write("> "); - directory = Console.ReadLine(); - } while (!Directory.Exists(directory)); - - config.DataDirectories.Add(directory); - } - - private static Configuration ReadConfiguration() - { - var relPath = new Uri($"{Core.BaseDirectory}/").MakeRelativeUri(new Uri(FilePath)).ToString(); - Console.Write($"Reading configuration from {relPath}..."); - Configuration config; - - if (File.Exists(FilePath)) - { - using var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); - Span configBytes = stackalloc byte[(int)fs.Length]; - fs.Read(configBytes); - config = JsonSerializer.Deserialize(Utility.UTF8WithEncoding.GetString(configBytes)); - Console.WriteLine("done"); - } - else - { - Console.WriteLine("not found"); - config = new Configuration(); - } - - // TODO: Extend with a config read verification function that can be extended. - if (config.DataDirectories.Count == 0) - { - PromptDataDirectories(config); - config.Flush(); - } - - return config; - } - - public void Flush() - { - using var fs = new FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write); - var configJson = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }); - Span data = stackalloc byte[Utility.UTF8WithEncoding.GetMaxByteCount(configJson.Length)]; - var bytesWritten = Utility.UTF8WithEncoding.GetBytes(configJson, data); - fs.Write(data.Slice(0, bytesWritten)); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"Configuration saved to {FilePath}"); - Console.ResetColor(); - } - } - - // TODO: Make configuration pluggable. Move this to scripts - public class EmailSettings - { - [JsonPropertyName("fromAddress")] - public string FromAddress { get; set; } - - [JsonPropertyName("fromName")] - public string FromName { get; set; } - - [JsonPropertyName("crashAddress")] - public string crashAddress { get; set; } - - [JsonPropertyName("crashName")] - public string crashName { get; set; } - - [JsonPropertyName("speechLogPageAddress")] - public string speechLogPageAddress { get; set; } - - [JsonPropertyName("speechLogPageName")] - public string speechLogPageName { get; set; } - - [JsonPropertyName("emailServer")] - public string emailServer { get; set; } - - [JsonPropertyName("emailPort")] - public int emailPort { get; set; } - - [JsonPropertyName("emailUsername")] - public string emailUsername { get; set; } - - [JsonPropertyName("emailPassword")] - public string emailPassword { get; set; } - } -} diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs new file mode 100644 index 000000000..8a7e698d1 --- /dev/null +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -0,0 +1,110 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Configuration.cs - Created: 2019/10/04 - Updated: 2020/01/19 * + * * + * 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 . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Serialization; +using Server.Json; + +namespace Server +{ + public static class ServerConfiguration + { + private const string m_RelPath = "Configuration/modernuo.json"; + private static readonly string m_FilePath = Path.Join(Core.BaseDirectory, m_RelPath); + private static ServerSettings m_Settings; + + public static List DataDirectories => m_Settings.dataDirectories; + public static Dictionary Settings => m_Settings.settings; + public static Dictionary Metadata => m_Settings.metadata; + + public static void LoadConfiguration() + { + bool updated = false; + + if (File.Exists(m_FilePath)) + { + Console.Write($"Core: Reading configuration from {m_RelPath}..."); + m_Settings = JsonConfig.Deserialize(m_FilePath); + + if (m_Settings == null) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("failed"); + Console.ResetColor(); + throw new Exception("Core: Server configuration failed to deserialize."); + } + + Console.WriteLine("done"); + Console.WriteLine("Settings: {0}", m_Settings.settings["stuff"]); + } + else + { + updated = true; + m_Settings = new ServerSettings(); + } + + if (m_Settings.dataDirectories.Count == 0) + { + updated = true; + Console.WriteLine("Core: Server configuration is missing data directories."); + m_Settings.dataDirectories.Add(GetDataDirectory()); + } + + if (updated) + SaveConfiguration(); + } + + internal class ServerSettings + { + [JsonPropertyName("dataDirectories")] + public List dataDirectories { get; set; } = new List(); + + [JsonPropertyName("settings")] + public Dictionary settings { get; set; } = new Dictionary(); + + [JsonExtensionData] + public Dictionary metadata { get; set; } = new Dictionary(); + } + + private static string GetDataDirectory() + { + Console.WriteLine("Please enter the Ultima Online directory:"); + + string directory; + do + { + Console.Write("> "); + directory = Console.ReadLine(); + } while (!Directory.Exists(directory)); + + return directory; + } + + public static void SaveConfiguration() + { + JsonConfig.Serialize(m_FilePath, m_Settings); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Core: Configuration saved to {m_RelPath}."); + Console.ResetColor(); + } + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/MapConverter.cs b/Projects/Server/JsonConfiguration/Converters/MapConverter.cs new file mode 100644 index 000000000..06706f9b8 --- /dev/null +++ b/Projects/Server/JsonConfiguration/Converters/MapConverter.cs @@ -0,0 +1,35 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MapConverter.cs - Created: 2020/04/12 - Updated: 2020/05/02 * + * * + * 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 . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class MapConverter : JsonConverter + { + public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => Map.Parse(reader.GetString()); + + public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) + => writer.WriteStringValue(value.Name); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Point3dConverter.cs b/Projects/Server/JsonConfiguration/Converters/Point3dConverter.cs new file mode 100644 index 000000000..5fd00d1ee --- /dev/null +++ b/Projects/Server/JsonConfiguration/Converters/Point3dConverter.cs @@ -0,0 +1,67 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MapConverter.cs - Created: 2020/04/12 - Updated: 2020/05/02 * + * * + * 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 . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Point3dConverter : JsonConverter + { + public override Point3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException("Point3d must be an array of x, y, z"); + + var data = new int[3]; + var count = 0; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (reader.TokenType == JsonTokenType.Number) + { + if (count < 3) + data[count] = reader.GetInt32(); + + count++; + } + } + + if (count < 2 || count > 3) + throw new JsonException("Point3d must be an array of x, y, z"); + + return new Point3D(data[0], data[1], data[2]); + } + + public override void Write(Utf8JsonWriter writer, Point3D value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.X); + writer.WriteNumberValue(value.Y); + writer.WriteNumberValue(value.Z); + writer.WriteEndArray(); + } + } +} diff --git a/Projects/Server/JsonConfiguration/JsonConfig.cs b/Projects/Server/JsonConfiguration/JsonConfig.cs new file mode 100644 index 000000000..8cf2071a1 --- /dev/null +++ b/Projects/Server/JsonConfiguration/JsonConfig.cs @@ -0,0 +1,49 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: JsonConfig.cs - Created: 2020/05/02 - Updated: 2020/05/02 * + * * + * 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 . * + *************************************************************************/ + +using System.IO; +using System.Text.Json; + +namespace Server.Json +{ + public static class JsonConfig + { + public static readonly JsonSerializerOptions Options = new JsonSerializerOptions + { + ReadCommentHandling = JsonCommentHandling.Skip, + WriteIndented = true, + AllowTrailingCommas = true, + }; + + public static T Deserialize(string filePath, JsonSerializerOptions options = null) + { + if (!File.Exists(filePath)) return default; + string text = File.ReadAllText(filePath, Utility.UTF8); + return JsonSerializer.Deserialize(text, options ?? Options); + } + + public static void Serialize(string filePath, object value, JsonSerializerOptions options = null) + { + if (File.Exists(filePath)) File.Delete(filePath); + + File.WriteAllText(filePath, JsonSerializer.Serialize(value, options ?? Options)); + } + } +} diff --git a/Projects/Server/JsonConverters/MapConverter.cs b/Projects/Server/JsonConverters/MapConverter.cs deleted file mode 100644 index 2b0fdc5dd..000000000 --- a/Projects/Server/JsonConverters/MapConverter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class MapConverter : JsonConverter - { - public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => Map.Parse(reader.GetString()); - - public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) - => writer.WriteStringValue(value.Name); - } -} diff --git a/Projects/Server/JsonConverters/Point3dConverter.cs b/Projects/Server/JsonConverters/Point3dConverter.cs deleted file mode 100644 index 049cacf8a..000000000 --- a/Projects/Server/JsonConverters/Point3dConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Point3dConverter : JsonConverter - { - public override Point3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException("Point3d must be an array of x, y, z"); - - var data = new int[3]; - var count = 0; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndArray) - break; - - if (reader.TokenType == JsonTokenType.Number) - { - if (count < 3) - data[count] = reader.GetInt32(); - - count++; - } - } - - if (count < 2 || count > 3) - throw new JsonException("Point3d must be an array of x, y, z"); - - return new Point3D(data[0], data[1], data[2]); - } - - public override void Write(Utf8JsonWriter writer, Point3D value, JsonSerializerOptions options) - { - writer.WriteStartArray(); - writer.WriteNumberValue(value.X); - writer.WriteNumberValue(value.Y); - writer.WriteNumberValue(value.Z); - writer.WriteEndArray(); - } - } -} diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index 296af60d9..e2957e016 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -45,8 +45,6 @@ namespace Server private static string m_BaseDirectory; private static string m_ExePath; - private static bool m_Cache = true; - private static bool m_Profiling; private static DateTime m_ProfileStart; private static TimeSpan m_ProfileTime; @@ -69,9 +67,6 @@ namespace Server private static readonly double m_HighFrequency = 1000.0 / Stopwatch.Frequency; private static readonly double m_LowFrequency = 1000.0 / TimeSpan.TicksPerSecond; - private static bool m_UseHRT; - - public static readonly bool Is64Bit = Environment.Is64BitProcess; internal static ConsoleEventHandler m_ConsoleEventHandler; private static int m_CycleIndex = 1; @@ -111,8 +106,6 @@ namespace Server } } - public static bool Service { get; private set; } - public static bool Debug { get; private set; } internal static bool HaltOnWarning { get; private set; } @@ -126,7 +119,7 @@ namespace Server public static MultiTextWriter MultiConsoleOut { get; private set; } - public static bool UsingHighResolutionTiming => m_UseHRT && m_HighRes && !Unix; + public static bool UsingHighResolutionTiming => m_HighRes && !Unix; public static long TickCount => (long)Ticks; @@ -134,7 +127,7 @@ namespace Server { get { - if (m_UseHRT && m_HighRes && !Unix) return Stopwatch.GetTimestamp() * m_HighFrequency; + if (m_HighRes && !Unix) return Stopwatch.GetTimestamp() * m_HighFrequency; return DateTime.UtcNow.Ticks * m_LowFrequency; } @@ -188,21 +181,12 @@ namespace Server if (Debug) Utility.Separate(sb, "-debug", " "); - if (Service) - Utility.Separate(sb, "-service", " "); - if (m_Profiling) Utility.Separate(sb, "-profile", " "); - if (!m_Cache) - Utility.Separate(sb, "-nocache", " "); - if (HaltOnWarning) Utility.Separate(sb, "-haltonwarning", " "); - if (m_UseHRT) - Utility.Separate(sb, "-usehrt", " "); - return sb.ToString(); } } @@ -216,14 +200,9 @@ namespace Server public static string FindDataFile(string path) { - var config = Configuration.Instance; - if (config.DataDirectories.Count == 0) - throw new InvalidOperationException( - "Attempted to FindDataFile before DataDirectories list has been filled."); - string fullPath = null; - foreach (var p in config.DataDirectories) + foreach (var p in ServerConfiguration.DataDirectories) { fullPath = Path.Combine(p, path); @@ -262,7 +241,7 @@ namespace Server // ignored } - if (!close && !Service) + if (!close) { try { @@ -283,7 +262,7 @@ namespace Server private static bool OnConsoleEvent(ConsoleEventType type) { - if (World.Saving || (Service && type == ConsoleEventType.CTRL_LOGOFF_EVENT)) + if (World.Saving || type == ConsoleEventType.CTRL_LOGOFF_EVENT) return true; Kill(); // Kill -> HandleClosed will handle waiting for the completion of flushing to disk @@ -338,47 +317,25 @@ namespace Server 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")) - m_Cache = false; else if (Insensitive.Equals(a, "-haltonwarning")) HaltOnWarning = true; - else if (Insensitive.Equals(a, "-usehrt")) - m_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 - { - // ignored - } Thread = Thread.CurrentThread; Process = Process.GetCurrentProcess(); Assembly = Assembly.GetEntryAssembly(); + if (Assembly == null) + throw new Exception("Core: Assembly entry is missing."); + if (Thread != null) Thread.Name = "Core Thread"; if (BaseDirectory.Length > 0) Directory.SetCurrentDirectory(BaseDirectory); - var ver = Assembly.GetName().Version; + var ver = Assembly.GetName().Version ?? new Version(); Console.ForegroundColor = ConsoleColor.Green; // Added to help future code support on forums, as a 'check' people can ask for to it see if they recompiled core or not @@ -389,14 +346,6 @@ namespace Server Console.ResetColor(); Console.WriteLine(); - var config = Configuration.Instance; - foreach (var dir in config.DataDirectories.Where(dir => !Directory.Exists(dir))) - { - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine("Core: Config directory {0} does not exist.", dir); - Console.ResetColor(); - } - var ttObj = new Timer.TimerThread(); timerThread = new Thread(ttObj.TimerMain) { @@ -413,9 +362,8 @@ namespace Server if (ProcessorCount > 1) MultiProcessor = true; - if (MultiProcessor || Is64Bit) - Console.WriteLine("Core: Optimizing for {0} {2}processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s", - Is64Bit ? "64-bit " : ""); + if (MultiProcessor) + Console.WriteLine("Core: Optimizing for {0} processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s"); if (IsWindows) { @@ -426,13 +374,14 @@ namespace Server if (GCSettings.IsServerGC) Console.WriteLine("Core: Server garbage collection mode enabled"); - if (m_UseHRT) - Console.WriteLine("Core: Requested high resolution timing ({0})", - UsingHighResolutionTiming ? "Supported" : "Unsupported"); + Console.WriteLine("Core: High resolution timing ({0})", + UsingHighResolutionTiming ? "Supported" : "Unsupported"); Console.WriteLine("SecureRandomImpl: {0} ({1})", SecureRandomImpl.Name, SecureRandomImpl.IsHardwareRNG ? "Hardware" : "Software"); + ServerConfiguration.LoadConfiguration(); + // Load Assembly Scripts.CS.dll AssemblyHandler.LoadScripts(); diff --git a/Projects/Server/TileData.cs b/Projects/Server/TileData.cs index ab3e85e1f..f09b47d88 100644 --- a/Projects/Server/TileData.cs +++ b/Projects/Server/TileData.cs @@ -208,7 +208,7 @@ namespace Server for (var i = 0; i < 0x4000; ++i) { - if (i == 1 || (i > 0 && (i & 0x1F) == 0)) bin.ReadInt32(); // header + if (i == 1 || i > 0 && (i & 0x1F) == 0) bin.ReadInt32(); // header var flags = (TileFlag)bin.ReadInt64(); bin.ReadInt16(); // skip 2 bytes -- textureID diff --git a/Projects/Server/World.cs b/Projects/Server/World.cs index 4a8753667..4237e1f9c 100644 --- a/Projects/Server/World.cs +++ b/Projects/Server/World.cs @@ -130,24 +130,17 @@ namespace Server { Console.WriteLine("failed"); - if (!Core.Service) - { - Console.WriteLine("Error: Type '{0}' was not found. Delete all of those types? (y/n)", typeName); + Console.WriteLine("Error: Type '{0}' was not found. Delete all of those types? (y/n)", typeName); - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - types.Add(null); - Console.Write("World: Loading..."); - continue; - } - - Console.WriteLine("Types will not be deleted. An exception will be thrown."); - } - else + if (Console.ReadKey(true).Key == ConsoleKey.Y) { - Console.WriteLine("Error: Type '{0}' was not found.", typeName); + types.Add(null); + Console.Write("World: Loading..."); + continue; } + Console.WriteLine("Types will not be deleted. An exception will be thrown."); + throw new Exception($"Bad type '{typeName}'"); } @@ -454,46 +447,39 @@ namespace Server Console.WriteLine(" - Type: {0}", failedType); Console.WriteLine(" - Serial: {0}", failedSerial); - if (!Core.Service) + Console.WriteLine("Delete the object? (y/n)"); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) { - Console.WriteLine("Delete the object? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) + if (failedType != typeof(BaseGuild)) { - if (failedType != typeof(BaseGuild)) + Console.WriteLine("Delete all objects of that type? (y/n)"); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) { - Console.WriteLine("Delete all objects of that type? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - if (failedMobiles) - for (var i = 0; i < mobiles.Count;) - if (mobiles[i].TypeID == failedTypeID) - mobiles.RemoveAt(i); - else - ++i; - else if (failedItems) - for (var i = 0; i < items.Count;) - if (items[i].TypeID == failedTypeID) - items.RemoveAt(i); - else - ++i; - } + if (failedMobiles) + for (var i = 0; i < mobiles.Count;) + if (mobiles[i].TypeID == failedTypeID) + mobiles.RemoveAt(i); + else + ++i; + else if (failedItems) + for (var i = 0; i < items.Count;) + if (items[i].TypeID == failedTypeID) + items.RemoveAt(i); + else + ++i; } - - SaveIndex(mobiles, MobileIndexPath); - SaveIndex(items, ItemIndexPath); - SaveIndex(guilds, GuildIndexPath); } - Console.WriteLine("After pressing return an exception will be thrown and the server will terminate."); - Console.ReadLine(); - } - else - { - Console.WriteLine("An exception will be thrown and the server will terminate."); + SaveIndex(mobiles, MobileIndexPath); + SaveIndex(items, ItemIndexPath); + SaveIndex(guilds, GuildIndexPath); } + Console.WriteLine("After pressing return an exception will be thrown and the server will terminate."); + Console.ReadLine(); + throw new Exception( $"Load failed (items={failedItems}, mobiles={failedMobiles}, guilds={failedGuilds}, type={failedType}, serial={failedSerial})", failed);