#region References using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using Microsoft.CSharp; #endregion namespace Server { public static class ScriptCompiler { public static Assembly[] Assemblies { get; set; } private static readonly List m_AdditionalReferences = new List(); public static string[] GetReferenceAssemblies() { var list = new List(); var path = Path.Combine(Core.BaseDirectory, "Data/Assemblies.cfg"); if (File.Exists(path)) { using (var ip = new StreamReader(path)) { string line; while ((line = ip.ReadLine()) != null) { if (line.Length > 0 && !line.StartsWith("#")) { list.Add(line); } } } } list.Add(Core.ExePath); list.AddRange(m_AdditionalReferences); return list.ToArray(); } public static string GetCompilerOptions(bool debug) { StringBuilder sb = null; AppendCompilerOption(ref sb, "/d:ServUO"); AppendCompilerOption(ref sb, "/unsafe"); if (!debug) { AppendCompilerOption(ref sb, "/optimize"); } else { AppendCompilerOption(ref sb, "/debug"); AppendCompilerOption(ref sb, "/d:DEBUG"); AppendCompilerOption(ref sb, "/d:TRACE"); } AppendCompilerOption(ref sb, "/langversion:7.3"); #if MONO AppendCompilerOption( ref sb, "/d:MONO" ); #endif if (Core.Is64Bit) { AppendCompilerOption(ref sb, "/d:x64"); } #if NEWTIMERS AppendCompilerOption(ref sb, "/d:NEWTIMERS"); #endif #if NEWPARENT AppendCompilerOption(ref sb, "/d:NEWPARENT"); #endif return (sb == null ? null : sb.ToString()); } private static void AppendCompilerOption(ref StringBuilder sb, string define) { if (sb == null) { sb = new StringBuilder(); } else { sb.Append(' '); } sb.Append(define); } private static byte[] GetHashCode(string compiledFile, string[] scriptFiles, bool debug) { using (var ms = new MemoryStream()) { using (var bin = new BinaryWriter(ms)) { var fileInfo = new FileInfo(compiledFile); bin.Write(fileInfo.LastWriteTimeUtc.Ticks); foreach (var scriptFile in scriptFiles) { fileInfo = new FileInfo(scriptFile); bin.Write(fileInfo.LastWriteTimeUtc.Ticks); } bin.Write(debug); bin.Write(Core.Version.ToString()); ms.Position = 0; using (var sha1 = SHA1.Create()) { return sha1.ComputeHash(ms); } } } } public static bool CompileCSScripts(out Assembly assembly) { return CompileCSScripts(false, true, out assembly); } public static bool CompileCSScripts(bool debug, out Assembly assembly) { return CompileCSScripts(debug, true, out assembly); } public static bool CompileCSScripts(bool debug, bool cache, out Assembly assembly) { Utility.PushColor(ConsoleColor.Yellow); Console.Write("Scripts: Compiling C# scripts..."); Utility.PopColor(); var files = GetScripts("*.cs"); if (files.Length == 0) { Utility.PushColor(ConsoleColor.Red); Console.WriteLine("no files found."); Utility.PopColor(); assembly = null; return true; } if (File.Exists("Scripts/Output/Scripts.CS.dll")) { if (cache && File.Exists("Scripts/Output/Scripts.CS.hash")) { try { var hashCode = GetHashCode("Scripts/Output/Scripts.CS.dll", files, debug); using (var fs = new FileStream("Scripts/Output/Scripts.CS.hash", FileMode.Open, FileAccess.Read, FileShare.Read)) { using (var bin = new BinaryReader(fs)) { var bytes = bin.ReadBytes(hashCode.Length); if (bytes.Length == hashCode.Length) { var valid = true; for (var i = 0; i < bytes.Length; ++i) { if (bytes[i] != hashCode[i]) { valid = false; break; } } if (valid) { assembly = Assembly.LoadFrom("Scripts/Output/Scripts.CS.dll"); if (!m_AdditionalReferences.Contains(assembly.Location)) { m_AdditionalReferences.Add(assembly.Location); } Utility.PushColor(ConsoleColor.Green); Console.WriteLine("done (cached)"); Utility.PopColor(); return true; } } } } } catch { } } } DeleteFiles("Scripts.CS*.dll"); #if !MONO using (CodeDomProvider provider = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider()) #else using (CSharpCodeProvider provider = new CSharpCodeProvider()) #endif { var path = GetUnusedPath("Scripts.CS"); var parms = new CompilerParameters(GetReferenceAssemblies(), path, debug); var options = GetCompilerOptions(debug); if (options != null) { parms.CompilerOptions = options; } if (Core.HaltOnWarning) { parms.WarningLevel = 4; } if (Core.Unix) { parms.CompilerOptions = String.Format( "{0} /nowarn:169,219,414 /recurse:Scripts/*.cs", parms.CompilerOptions ); files = new string[0]; } var results = provider.CompileAssemblyFromFile(parms, files); m_AdditionalReferences.Add(path); Display(results); if (results.Errors.Count > 0 && !Core.Unix) { assembly = null; return false; } if (results.Errors.Count > 0 && Core.Unix) { foreach( CompilerError err in results.Errors ) { if ( !err.IsWarning ) { assembly = null; return false; } } } if (cache && Path.GetFileName(path) == "Scripts.CS.dll") { try { var hashCode = GetHashCode(path, files, debug); using ( var fs = new FileStream("Scripts/Output/Scripts.CS.hash", FileMode.Create, FileAccess.Write, FileShare.None)) { using (var bin = new BinaryWriter(fs)) { bin.Write(hashCode, 0, hashCode.Length); } } } catch { } } assembly = results.CompiledAssembly; return true; } } public static void Display(CompilerResults results) { if (results.Errors.Count > 0) { var errors = new Dictionary>(results.Errors.Count, StringComparer.OrdinalIgnoreCase); var warnings = new Dictionary>(results.Errors.Count, StringComparer.OrdinalIgnoreCase); foreach (CompilerError e in results.Errors) { var file = e.FileName; // Ridiculous. FileName is null if the warning/error is internally generated in csc. if (string.IsNullOrEmpty(file)) { Console.WriteLine("ScriptCompiler: {0}: {1}", e.ErrorNumber, e.ErrorText); continue; } var table = (e.IsWarning ? warnings : errors); List list = null; table.TryGetValue(file, out list); if (list == null) { table[file] = list = new List(); } list.Add(e); } if (errors.Count > 0) { Utility.PushColor(ConsoleColor.Red); Console.WriteLine("Failed with: {0} errors, {1} warnings", errors.Count, warnings.Count); Utility.PopColor(); } else { Utility.PushColor(ConsoleColor.Green); Console.WriteLine("Finished with: {0} errors, {1} warnings", errors.Count, warnings.Count); Utility.PopColor(); } var scriptRoot = Path.GetFullPath(Path.Combine(Core.BaseDirectory, "Scripts" + Path.DirectorySeparatorChar)); var scriptRootUri = new Uri(scriptRoot); Utility.PushColor(ConsoleColor.Yellow); if (warnings.Count > 0) { Console.WriteLine("Warnings:"); } foreach (var kvp in warnings) { var fileName = kvp.Key; var list = kvp.Value; var fullPath = Path.GetFullPath(fileName); var usedPath = Uri.UnescapeDataString(scriptRootUri.MakeRelativeUri(new Uri(fullPath)).OriginalString); Console.WriteLine(" + {0}:", usedPath); Utility.PushColor(ConsoleColor.DarkYellow); foreach (var e in list) { Console.WriteLine(" {0}: Line {1}: {2}", e.ErrorNumber, e.Line, e.ErrorText); } Utility.PopColor(); } Utility.PopColor(); Utility.PushColor(ConsoleColor.Red); if (errors.Count > 0) { Console.WriteLine("Errors:"); } foreach (var kvp in errors) { var fileName = kvp.Key; var list = kvp.Value; var fullPath = Path.GetFullPath(fileName); var usedPath = Uri.UnescapeDataString(scriptRootUri.MakeRelativeUri(new Uri(fullPath)).OriginalString); Console.WriteLine(" + {0}:", usedPath); Utility.PushColor(ConsoleColor.Red); foreach (var e in list) { Console.WriteLine(" {0}: Line {1}: {2}", e.ErrorNumber, e.Line, e.ErrorText); } Utility.PopColor(); } Utility.PopColor(); } else { Utility.PushColor(ConsoleColor.Green); Console.WriteLine("Finished with: 0 errors, 0 warnings"); Utility.PopColor(); } } public static string GetUnusedPath(string name) { var path = Path.Combine(Core.BaseDirectory, String.Format("Scripts/Output/{0}.dll", name)); for (var i = 2; File.Exists(path) && i <= 1000; ++i) { path = Path.Combine(Core.BaseDirectory, String.Format("Scripts/Output/{0}.{1}.dll", name, i)); } return path; } public static void DeleteFiles(string mask) { try { var files = Directory.GetFiles(Path.Combine(Core.BaseDirectory, "Scripts/Output"), mask); foreach (var file in files) { try { File.Delete(file); } catch { } } } catch { } } private delegate CompilerResults Compiler(bool debug); public static bool Compile() { return Compile(false); } public static bool Compile(bool debug) { return Compile(debug, true); } public static bool Compile(bool debug, bool cache) { EnsureDirectory("Scripts/"); EnsureDirectory("Scripts/Output/"); if (m_AdditionalReferences.Count > 0) { m_AdditionalReferences.Clear(); } var assemblies = new List(); Assembly assembly; if (CompileCSScripts(debug, cache, out assembly)) { if (assembly != null) { assemblies.Add(assembly); } } else { return false; } if (assemblies.Count == 0) { return false; } Assemblies = assemblies.ToArray(); Utility.PushColor(ConsoleColor.Yellow); Console.WriteLine("Scripts: Verifying..."); Utility.PopColor(); var watch = Stopwatch.StartNew(); Core.VerifySerialization(); watch.Stop(); Utility.PushColor(ConsoleColor.Green); Console.WriteLine( "Finished ({0} items, {1} mobiles, {2} customs) ({3:F2} seconds)", Core.ScriptItems, Core.ScriptMobiles, Core.ScriptCustoms, watch.Elapsed.TotalSeconds); Utility.PopColor(); return true; } public static void Invoke(string method) { var invoke = new List(); foreach (var a in Assemblies) { var types = a.GetTypes(); foreach (var t in types) { var m = t.GetMethod(method, BindingFlags.Static | BindingFlags.Public); if (m != null) { invoke.Add(m); } } } invoke.Sort(new CallPriorityComparer()); foreach (var m in invoke) { m.Invoke(null, null); } } private static readonly Dictionary m_TypeCaches = new Dictionary(); private static TypeCache m_NullCache; public static TypeCache GetTypeCache(Assembly asm) { if (asm == null) { return m_NullCache ?? (m_NullCache = new TypeCache(null)); } TypeCache c; m_TypeCaches.TryGetValue(asm, out c); if (c == null) { m_TypeCaches[asm] = c = new TypeCache(asm); } return c; } public static Type FindTypeByFullName(string fullName) { return FindTypeByFullName(fullName, true); } public static Type FindTypeByFullName(string fullName, bool ignoreCase) { if (string.IsNullOrWhiteSpace(fullName)) { return null; } Type type = null; for (var i = 0; type == null && i < Assemblies.Length; ++i) { type = GetTypeCache(Assemblies[i]).GetTypeByFullName(fullName, ignoreCase); } return type ?? GetTypeCache(Core.Assembly).GetTypeByFullName(fullName, ignoreCase); } public static IEnumerable FindTypesByFullName(string name) { return FindTypesByFullName(name, true); } public static IEnumerable FindTypesByFullName(string name, bool ignoreCase) { if (string.IsNullOrWhiteSpace(name)) { yield break; } for (var i = 0; i < Assemblies.Length; ++i) { foreach (var t in GetTypeCache(Assemblies[i]).GetTypesByFullName(name, ignoreCase)) { yield return t; } } foreach (var t in GetTypeCache(Core.Assembly).GetTypesByFullName(name, ignoreCase)) { yield return t; } } public static Type FindTypeByName(string name) { return FindTypeByName(name, true); } public static Type FindTypeByName(string name, bool ignoreCase) { if (string.IsNullOrWhiteSpace(name)) { return null; } Type type = null; for (var i = 0; type == null && i < Assemblies.Length; ++i) { type = GetTypeCache(Assemblies[i]).GetTypeByName(name, ignoreCase); } return type ?? GetTypeCache(Core.Assembly).GetTypeByName(name, ignoreCase); } public static IEnumerable FindTypesByName(string name) { return FindTypesByName(name, true); } public static IEnumerable FindTypesByName(string name, bool ignoreCase) { if (string.IsNullOrWhiteSpace(name)) { yield break; } for (var i = 0; i < Assemblies.Length; ++i) { foreach (var t in GetTypeCache(Assemblies[i]).GetTypesByName(name, ignoreCase)) { yield return t; } } foreach (var t in GetTypeCache(Core.Assembly).GetTypesByName(name, ignoreCase)) { yield return t; } } public static void EnsureDirectory(string dir) { var path = Path.Combine(Core.BaseDirectory, dir); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } } public static string[] GetScripts(string filter) { var list = new List(); GetScripts(list, Path.Combine(Core.BaseDirectory, "Scripts"), filter); return list.ToArray(); } public static void GetScripts(List list, string path, string filter) { foreach (var dir in Directory.GetDirectories(path)) { GetScripts(list, dir, filter); } list.AddRange(Directory.GetFiles(path, filter)); } } public class TypeCache { private readonly Type[] m_Types; private readonly TypeTable m_Names; private readonly TypeTable m_FullNames; public Type[] Types { get { return m_Types; } } public TypeTable Names { get { return m_Names; } } public TypeTable FullNames { get { return m_FullNames; } } public Type GetTypeByName(string name, bool ignoreCase) { return GetTypesByName(name, ignoreCase).FirstOrDefault(t => t != null); } public IEnumerable GetTypesByName(string name, bool ignoreCase) { return m_Names.Get(name, ignoreCase); } public Type GetTypeByFullName(string fullName, bool ignoreCase) { return GetTypesByFullName(fullName, ignoreCase).FirstOrDefault(t => t != null); } public IEnumerable GetTypesByFullName(string fullName, bool ignoreCase) { return m_FullNames.Get(fullName, ignoreCase); } public TypeCache(Assembly asm) { if (asm == null) { m_Types = Type.EmptyTypes; } else { m_Types = asm.GetTypes(); } m_Names = new TypeTable(m_Types.Length); m_FullNames = new TypeTable(m_Types.Length); foreach (var g in m_Types.ToLookup(t => t.Name)) { m_Names.Add(g.Key, g); foreach (var type in g) { m_FullNames.Add(type.FullName, type); var attr = type.GetCustomAttribute(false); if (attr != null) { foreach (var a in attr.Aliases) { m_FullNames.Add(a, type); } } } } m_Names.Prune(); m_FullNames.Prune(); m_Names.Sort(); m_FullNames.Sort(); } } public class TypeTable { private readonly Dictionary> m_Sensitive; private readonly Dictionary> m_Insensitive; public void Prune() { Prune(m_Sensitive); Prune(m_Insensitive); } private static void Prune(Dictionary> types) { var buffer = new List(); foreach (var list in types.Values) { if (list.Count == 1) { continue; } buffer.AddRange(list.Distinct()); list.Clear(); list.AddRange(buffer); buffer.Clear(); } buffer.TrimExcess(); } public void Sort() { Sort(m_Sensitive); Sort(m_Insensitive); } private static void Sort(Dictionary> types) { foreach (var list in types.Values) { list.Sort(InternalSort); } } private static int InternalSort(Type l, Type r) { if (l == r) { return 0; } if (l != null && r == null) { return -1; } if (l == null && r != null) { return 1; } var a = IsEntity(l); var b = IsEntity(r); if (a && b) { a = IsConstructable(l, out var x); b = IsConstructable(r, out var y); if (a && !b) { return -1; } if (!a && b) { return 1; } return x > y ? -1 : x < y ? 1 : 0; } return a ? -1 : b ? 1 : 0; } private static bool IsEntity(Type type) { return type.GetInterface("IEntity") != null; } private static bool IsConstructable(Type type, out AccessLevel access) { foreach (var ctor in type.GetConstructors().OrderBy(o => o.GetParameters().Length)) { var attr = ctor.GetCustomAttribute(false); if (attr != null) { access = attr.AccessLevel; return true; } } access = 0; return false; } public void Add(string key, IEnumerable types) { if (!string.IsNullOrWhiteSpace(key) && types != null) { Add(key, types.ToArray()); } } public void Add(string key, params Type[] types) { if (string.IsNullOrWhiteSpace(key) || types == null || types.Length == 0) { return; } if (!m_Sensitive.TryGetValue(key, out var sensitive) || sensitive == null) { m_Sensitive[key] = new List(types); } else if (types.Length == 1) { sensitive.Add(types[0]); } else { sensitive.AddRange(types); } if (!m_Insensitive.TryGetValue(key, out var insensitive) || insensitive == null) { m_Insensitive[key] = new List(types); } else if (types.Length == 1) { insensitive.Add(types[0]); } else { insensitive.AddRange(types); } } public IEnumerable Get(string key, bool ignoreCase) { if (string.IsNullOrWhiteSpace(key)) { return Type.EmptyTypes; } List t; if (ignoreCase) { m_Insensitive.TryGetValue(key, out t); } else { m_Sensitive.TryGetValue(key, out t); } if (t == null) { return Type.EmptyTypes; } return t.AsEnumerable(); } public TypeTable(int capacity) { m_Sensitive = new Dictionary>(capacity); m_Insensitive = new Dictionary>(capacity, StringComparer.OrdinalIgnoreCase); } } }