#region References using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; #endregion namespace Server { public static class Config { public sealed class Entry : IEquatable, IComparable { public int FileIndex { get; private set; } public string File { get; private set; } public string Scope { get; private set; } public string Desc { get; set; } public string Key { get; set; } public string Value { get; set; } public bool UseDefault { get; set; } public Entry(string file, int fileIndex, string scope, string desc, string key, string value, bool useDefault) { File = file; FileIndex = fileIndex; Scope = scope; Desc = desc; Key = key; Value = value; UseDefault = useDefault; } public override string ToString() { return String.Format("{0}.{1}{2}={3}", Scope, UseDefault ? "@" : "", Key, Value); } public override int GetHashCode() { unchecked { var hash = -1; hash = (hash * 397) ^ File.GetHashCode(); hash = (hash * 397) ^ Scope.GetHashCode(); hash = (hash * 397) ^ Key.GetHashCode(); return hash; } } public override bool Equals(object obj) { return obj is Entry && Equals((Entry)obj); } public bool Equals(Entry other) { if (ReferenceEquals(other, null)) { return false; } if (ReferenceEquals(other, this)) { return true; } return Insensitive.Equals(File, other.File) && // Insensitive.Equals(Scope, other.Scope) && // Insensitive.Equals(Key, other.Key); } public int CompareTo(Entry other) { if (other == null) { return -1; } if (!Insensitive.Equals(File, other.File)) { return Insensitive.Compare(File, other.File); } return FileIndex.CompareTo(other.FileIndex); } } private static bool _Initialized; private static readonly string _Path = Path.Combine(Core.BaseDirectory, "Config"); private static readonly IFormatProvider _NumFormatter = CultureInfo.InvariantCulture.NumberFormat; private static readonly Dictionary _Entries = new Dictionary(StringComparer.OrdinalIgnoreCase); public static IEnumerable Entries => _Entries.Values; public static void Load() { if (_Initialized) { return; } _Initialized = true; if (!Directory.Exists(_Path)) { Directory.CreateDirectory(_Path); } IEnumerable files; try { files = Directory.EnumerateFiles(_Path, "*.cfg", SearchOption.AllDirectories); } catch (DirectoryNotFoundException) { Console.WriteLine("Warning: No configuration files found!"); return; } foreach (var path in files) { try { LoadFile(path); } catch (Exception e) { Console.WriteLine("Warning: Failed to load configuration file:"); Console.WriteLine(path); Utility.PushColor(ConsoleColor.Red); Console.WriteLine(e.Message); Utility.PopColor(); ConsoleKey key; do { Console.WriteLine("Ignore this warning? (y/n)"); key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Y && key != ConsoleKey.N); if (key != ConsoleKey.Y) { Console.WriteLine("Press any key to exit..."); Console.ReadKey(); Core.Kill(false); return; } } } if (Core.Debug) { Console.WriteLine(); foreach (var e in _Entries.Values) { Console.WriteLine(e); } Console.WriteLine(); } } private static void LoadFile(string path) { var info = new FileInfo(path); if (!info.Exists) { throw new FileNotFoundException(); } path = info.Directory != null ? info.Directory.FullName : String.Empty; var io = path.IndexOf(_Path, StringComparison.OrdinalIgnoreCase); if (io > -1) { path = path.Substring(io + _Path.Length); } var parts = path.Split(Path.DirectorySeparatorChar); var scope = String.Join(".", parts.Where(p => !String.IsNullOrWhiteSpace(p))); if (scope.Length > 0) { scope += "."; } scope += Path.GetFileNameWithoutExtension(info.Name); var lines = File.ReadAllLines(info.FullName); var desc = new List(0x10); for (int i = 0, idx = 0; i < lines.Length; i++) { var line = lines[i].Trim(); if (String.IsNullOrWhiteSpace(line)) { desc.Clear(); continue; } if (line.StartsWith("#")) { desc.Add(line.TrimStart('#').Trim()); continue; } var useDef = false; if (line.StartsWith("@")) { useDef = true; line = line.TrimStart('@').Trim(); } io = line.IndexOf('='); if (io < 0) { throw new FormatException(String.Format("Bad format at line {0}", i + 1)); } var key = line.Substring(0, io); var val = line.Substring(io + 1); if (String.IsNullOrWhiteSpace(key)) { throw new NullReferenceException(String.Format("Key can not be null at line {0}", i + 1)); } key = key.Trim(); if (String.IsNullOrEmpty(val)) { val = null; } var e = new Entry(info.FullName, idx++, scope, String.Join(String.Empty, desc), key, val, useDef); _Entries[String.Format("{0}.{1}", e.Scope, e.Key)] = e; desc.Clear(); } } public static void Save() { if (!_Initialized) { Load(); } if (!Directory.Exists(_Path)) { Directory.CreateDirectory(_Path); } foreach (var g in _Entries.Values.ToLookup(e => e.File)) { try { SaveFile(g.Key, g.OrderBy(e => e.FileIndex)); } catch (Exception e) { Console.WriteLine("Warning: Failed to save configuration file:"); Console.WriteLine(g.Key); Utility.PushColor(ConsoleColor.Red); Console.WriteLine(e.Message); Utility.PopColor(); } } } private static void SaveFile(string path, IEnumerable entries) { var content = new StringBuilder(0x1000); var line = new StringBuilder(0x80); foreach (var e in entries) { content.AppendLine(); if (!String.IsNullOrWhiteSpace(e.Desc)) { line.Clear(); foreach (var word in e.Desc.Split(' ')) { if ((line + word).Length > 100) { content.AppendLine(String.Format("# {0}", line)); line.Clear(); } line.AppendFormat("{0} ", word); } if (line.Length > 0) { content.AppendLine(String.Format("# {0}", line)); line.Clear(); } } content.AppendLine(String.Format("{0}{1}={2}", e.UseDefault ? "@" : String.Empty, e.Key, e.Value)); } File.WriteAllText(path, content.ToString()); } public static Entry Find(string key) { _Entries.TryGetValue(key, out var e); return e; } private static void InternalSet(string key, string value) { var e = Find(key); if (e != null) { e.Value = value; e.UseDefault = false; return; } var parts = key.Split('.'); var realKey = parts.Last(); parts = parts.Take(parts.Length - 1).ToArray(); var file = new FileInfo(Path.Combine(_Path, Path.Combine(parts) + ".cfg")); var idx = _Entries.Values.Where(o => Insensitive.Equals(o.File, file.FullName)).Select(o => o.FileIndex).DefaultIfEmpty().Max(); _Entries[key] = new Entry(file.FullName, idx, String.Join(".", parts), String.Empty, realKey, value, false); } public static void Set(string key, string value) { InternalSet(key, value); } public static void Set(string key, char value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, sbyte value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, byte value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, short value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, ushort value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, int value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, uint value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, long value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, ulong value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, float value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, double value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, decimal value) { InternalSet(key, value.ToString(_NumFormatter)); } public static void Set(string key, bool value) { InternalSet(key, value ? "true" : "false"); } public static void Set(string key, TimeSpan value) { InternalSet(key, value.ToString()); } public static void Set(string key, DateTime value) { InternalSet(key, value.ToString(CultureInfo.InvariantCulture)); } public static void SetEnum(string key, T value) where T : struct, IConvertible { var t = typeof(T); if (!t.IsEnum) { throw new ArgumentException("T must be an enumerated type"); } var vals = Enum.GetValues(t).Cast(); foreach (var o in vals.Where(o => o.Equals(value))) { InternalSet(key, o.ToString(CultureInfo.InvariantCulture)); return; } throw new ArgumentException("Enumerated value not found"); } private static void Warn(string key) { Utility.PushColor(ConsoleColor.Yellow); Console.WriteLine("Config: Warning, '{0}' invalid value for '{1}'", typeof(T), key); Utility.PopColor(); } private static string InternalGet(string key) { if (!_Initialized) { Load(); } if (_Entries.TryGetValue(key, out var e) && e != null) { return e.UseDefault ? null : e.Value; } Utility.PushColor(ConsoleColor.Yellow); Console.WriteLine("Config: Warning, using default value for {0}", key); Utility.PopColor(); return null; } public static string Get(string key, string defaultValue) { return InternalGet(key) ?? defaultValue; } public static sbyte Get(string key, sbyte defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || SByte.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static byte Get(string key, byte defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Byte.TryParse(value, NumberStyles.Any & ~NumberStyles.AllowLeadingSign, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static short Get(string key, short defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Int16.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static ushort Get(string key, ushort defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || UInt16.TryParse(value, NumberStyles.Any & ~NumberStyles.AllowLeadingSign, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static int Get(string key, int defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Int32.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static uint Get(string key, uint defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || UInt32.TryParse(value, NumberStyles.Any & ~NumberStyles.AllowLeadingSign, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static long Get(string key, long defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Int64.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static ulong Get(string key, ulong defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || UInt64.TryParse(value, NumberStyles.Any & ~NumberStyles.AllowLeadingSign, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static float Get(string key, float defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Single.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static double Get(string key, double defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Double.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static decimal Get(string key, decimal defaultValue) { var ret = defaultValue; var value = InternalGet(key); if (value == null || Decimal.TryParse(value, NumberStyles.Any, _NumFormatter, out ret)) { return ret; } Warn(key); return defaultValue; } public static bool Get(string key, bool defaultValue) { var value = InternalGet(key); if (value == null) { return defaultValue; } if (Regex.IsMatch(value, @"(true|yes|on|1|enabled)", RegexOptions.IgnoreCase)) { return true; } if (Regex.IsMatch(value, @"(false|no|off|0|disabled)", RegexOptions.IgnoreCase)) { return false; } Warn(key); return defaultValue; } public static TimeSpan Get(string key, TimeSpan defaultValue) { var value = InternalGet(key); if (TimeSpan.TryParse(value, out var ts)) { return ts; } Warn(key); return defaultValue; } public static DateTime Get(string key, DateTime defaultValue) { var value = InternalGet(key); if (DateTime.TryParse(value, out var dt)) { return dt; } Warn(key); return defaultValue; } public static Type Get(string key, Type defaultValue) { var value = InternalGet(key); var t = FindType(value); if (t != null) { return t; } Warn(key); return defaultValue; } public static T GetEnum(string key, T defaultValue) where T : struct, IConvertible { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enumerated type"); } var value = InternalGet(key); if (value == null) { return defaultValue; } value = value.Trim(); var vals = Enum.GetValues(typeof(T)).Cast(); foreach (var o in vals.Where(o => Insensitive.Equals(value, o.ToString(CultureInfo.InvariantCulture)))) { return o; } Warn(key); return defaultValue; } public static T GetDelegate(string key, T defaultValue) { if (!typeof(MulticastDelegate).IsAssignableFrom(typeof(T).BaseType)) { throw new ArgumentException("T must be a delegate type"); } var value = InternalGet(key); if (value == null) { return defaultValue; } value = value.Trim(); var i = value.LastIndexOf('.'); if (i <= 0) { Warn(key); return defaultValue; } try { var method = value.Substring(i + 1); var target = FindType(value.Remove(i)); if (target != null) { var info = target.GetMethod(method, (BindingFlags)0x38); if (info != null) { return (T)(object)Delegate.CreateDelegate(typeof(T), info); } } } catch { } Warn(key); return defaultValue; } private static Type FindType(string value) { var type = Type.GetType(value, false); if (type != null) { return type; } if (value.IndexOf('.') < 0) { return ScriptCompiler.FindTypeByName(value); } return ScriptCompiler.FindTypeByFullName(value); } } }