// // Radegast Metaverse Client // Copyright (c) 2009-2013, Radegast Development Team // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the application "Radegast", nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // $Id$ // using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.CodeDom.Compiler; using System.Windows.Forms; using OpenMetaverse; using Microsoft.CSharp; namespace Radegast { /// /// Information about loaded plugin /// public class PluginInfo { /// File name from which the plugin was loaded, cannot load plugin twice from the same file public string FileName { get; set; } /// Plugin class public IRadegastPlugin Plugin { get; set; } /// Is plugin started public bool Started { get; set; } /// Plugin class public PluginAttribute Attribures { get { if (Plugin == null) return null; return PluginManager.GetAttributes(Plugin); } } public AppDomain Domain; } /// /// Handles loading Radegast plugins /// public class PluginManager : IDisposable { /// List of files that should not be scanned for plugins public static readonly List PluginBlackList = new List(new string[] { "AIMLbot.dll", "CommandLine.dll", "Meebey.SmartIrc4net.dll", "Monobjc.Cocoa.dll", "Monobjc.dll", "OpenMetaverse.Rendering.Meshmerizer.dll", "OpenMetaverse.StructuredData.dll", "OpenMetaverse.dll", "OpenMetaverseTypes.dll", "PrimMesher.dll", "RadSpeechLin.dll", "RadSpeechMac.dll", "RadSpeechWin.dll", "Tao.OpenGl.dll", "Tao.Platform.Windows.dll", "Tools.dll", "XMLRPC.dll", "fmodex-dotnet.dll", "fmodex.dll", "log4net.dll", "openjpeg-dotnet-x86_64.dll", "openjpeg-dotnet.dll", "OpenCyc.dll", "IKVM.", "OpenTK", "zlib.net.dll", "SmartThreadPool", }); /// List of file extensions that could potentially hold plugins public static readonly List AllowedPluginExtensions = new List(new string[] { ".cs", ".dll", ".exe" }); List PluginsLoaded = new List(); RadegastInstance instance; /// /// Gets the list of currently loaded plugins /// public List Plugins { get { return PluginsLoaded; } } /// /// Creates new PluginManager /// /// Radegast instance PluginManager is associated with public PluginManager(RadegastInstance instance) { this.instance = instance; } /// /// Unloads all plugins /// public void Dispose() { lock (PluginsLoaded) { List unload = new List(PluginsLoaded); unload.ForEach(plug => { UnloadPlugin(plug); }); } } /// /// Unloads a plugin /// /// Plugin to unload public void UnloadPlugin(PluginInfo plug) { lock (PluginsLoaded) { var pluginInfos = PluginsLoaded.FindAll(info => { return info.Plugin == plug.Plugin; }); foreach (var info in pluginInfos) { AppDomain domain = info.Domain; try { info.Plugin.StopPlugin(instance); } catch (Exception ex) { Logger.Log("ERROR in unloading plugin: " + info.Plugin.GetType().Name + " because " + ex, Helpers.LogLevel.Debug, ex); } PluginsLoaded.Remove(info); if (domain != null && PluginsLoaded.Find(dinfo => { return dinfo.Domain == domain; }) == null) { try { AppDomain.Unload(domain); } catch (Exception ex) { Logger.Log("ERROR unloading application domain for : " + plug.FileName + "\n" + ex.Message, Helpers.LogLevel.Debug); } } } } } /// /// Gets extended atributes for plugin /// /// Plugin to lookup extra attributes /// Extended atributes for plugin public static PluginAttribute GetAttributes(IRadegastPlugin plug) { PluginAttribute a = null; foreach (Attribute attr in Attribute.GetCustomAttributes(plug.GetType())) { if (attr is PluginAttribute) a = (PluginAttribute)attr; } if (a == null) { a = new PluginAttribute(); a.Name = plug.GetType().FullName; } return a; } /// /// Starts all loaded plugins /// public void StartPlugins() { lock (PluginsLoaded) { foreach (PluginInfo plug in PluginsLoaded) { Logger.DebugLog("Starting " + plug.Plugin.GetType().FullName); try { plug.Plugin.StartPlugin(instance); plug.Started = true; } catch (Exception ex) { Logger.Log("ERROR in Starting Radegast Plugin: " + plug + " because " + ex, Helpers.LogLevel.Debug); } } } } /// /// Loads a plugin for a precompiled assembly or source file /// /// File to load /// Start plugins that are found in the assembly public void LoadPluginFile(string loadFileName, bool stratPlugins) { string ext = Path.GetExtension(loadFileName).ToLower(); if (ext == ".cs") { LoadCSharpScriptFile(loadFileName, stratPlugins); } else if (ext == ".dll" || ext == ".exe") { try { LoadAssembly(loadFileName, stratPlugins); } catch (BadImageFormatException) { // non .NET .dlls } catch (ReflectionTypeLoadException) { // Out of date or dlls missing sub dependencies } catch (TypeLoadException) { // Another version of: Out of date or dlls missing sub dependencies } catch (Exception ex) { Logger.Log("ERROR in Radegast Plugin: " + loadFileName + " because " + ex, Helpers.LogLevel.Debug); } } } /// /// Scans and load plugins from Radegast application folder without starting them /// public void ScanAndLoadPlugins() { string dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (!Directory.Exists(dirName)) return; foreach (string loadFileName in Directory.GetFiles(dirName)) { if (IsUnusedFile(loadFileName)) { continue; } LoadPluginFile(loadFileName, false); } } private static bool IsUnusedFile(string loadFileName) { if (!AllowedPluginExtensions.Contains(Path.GetExtension(loadFileName).ToLower())) return true; loadFileName = Path.GetFileName(loadFileName).ToLower(); foreach (string blackList in PluginBlackList) { if (loadFileName.StartsWith(blackList.ToLower())) { return true; } } return false; } /// /// Loads and compiles a plugin from a C# source file /// /// Load plugin from this filename /// Start plugins found in the assembly after complilation public void LoadCSharpScriptFile(string fileName, bool startPlugins) { try { LoadCSharpScript(fileName, File.ReadAllText(fileName), startPlugins); } catch (Exception ex) { Logger.Log("Failed loading C# script " + fileName + ": ", Helpers.LogLevel.Warning, ex); } } /// /// Compiles plugin from string source code /// /// File name from which source was loaded /// Source code /// Start plugins found in the assembly after complilation public void LoadCSharpScript(string fileName, string code, bool startPlugins) { try { // *** Generate dynamic compiler Dictionary loCompilerOptions = new Dictionary(); loCompilerOptions.Add("CompilerVersion", "v3.5"); CSharpCodeProvider loCompiler = new CSharpCodeProvider(loCompilerOptions); CompilerParameters loParameters = new CompilerParameters(); // *** Start by adding any referenced assemblies loParameters.ReferencedAssemblies.Add("OpenMetaverse.StructuredData.dll"); loParameters.ReferencedAssemblies.Add("OpenMetaverseTypes.dll"); loParameters.ReferencedAssemblies.Add("OpenMetaverse.dll"); loParameters.ReferencedAssemblies.Add("Radegast.exe"); loParameters.ReferencedAssemblies.Add("System.dll"); loParameters.ReferencedAssemblies.Add("System.Core.dll"); loParameters.ReferencedAssemblies.Add("System.Xml.dll"); loParameters.ReferencedAssemblies.Add("System.Drawing.dll"); loParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll"); // *** Load the resulting assembly into memory loParameters.GenerateInMemory = true; loParameters.GenerateExecutable = false; // *** Now compile the whole thing CompilerResults loCompiled = loCompiler.CompileAssemblyFromSource(loParameters, code); // *** Check for compilation erros if (loCompiled.Errors.HasErrors) { string lcErrorMsg = ""; lcErrorMsg = "Compilation failed: " + loCompiled.Errors.Count.ToString() + " errors:"; for (int x = 0; x < loCompiled.Errors.Count; x++) lcErrorMsg += "\r\nLine: " + loCompiled.Errors[x].Line.ToString() + " - " + loCompiled.Errors[x].ErrorText; instance.TabConsole.DisplayNotificationInChat(lcErrorMsg, ChatBufferTextStyle.Alert); return; } instance.TabConsole.DisplayNotificationInChat("Compilation successful."); Assembly loAssembly = loCompiled.CompiledAssembly; LoadAssembly(fileName, loAssembly, startPlugins); } catch (Exception ex) { Logger.Log("Failed loading C# script: ", Helpers.LogLevel.Warning, ex); } } /// /// Scans assembly for supported types /// /// File name from which assembly was loaded /// Assembly to scan for supported types /// Start plugins found in the assembly after complilation public void LoadAssembly(string loadfilename, bool startPlugins) { LoadAssembly(loadfilename, null, startPlugins); } /// /// Scans assembly for supported types and loads it into it's own domain /// /// File name from which assembly was loaded /// Start plugins found in the assembly after complilation public void LoadAssembly(string loadfilename, Assembly assembly, bool startPlugins) { if (null != PluginsLoaded.Find((PluginInfo info) => { return info.FileName == loadfilename; })) { Logger.Log("Plugin already loaded, skipping: " + loadfilename, Helpers.LogLevel.Info); if (startPlugins) { instance.TabConsole.DisplayNotificationInChat("Plugin already loaded, skipping: " + loadfilename); } return; } AppDomain domain = null; if (assembly == null) { // Don't load ourselves into a domain if (Path.GetFileName(Assembly.GetEntryAssembly().Location) == Path.GetFileName(loadfilename)) { assembly = Assembly.GetEntryAssembly(); } else { assembly = Assembly.Load(File.ReadAllBytes(loadfilename)); /* Disable creation of domains for now domain = AppDomain.CreateDomain("Domain for: " + loadfilename); var loader = (RemoteLoader)domain.CreateInstanceAndUnwrap("Radegast", "Radegast.RemoteLoader"); assembly = loader.Load(loadfilename); */ } } bool loadedTypesFromAssembly = false; foreach (Type type in assembly.GetTypes()) { if (typeof(IRadegastPlugin).IsAssignableFrom(type)) { if (type.IsInterface) continue; try { IRadegastPlugin plug = null; ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(RadegastInstance) }); if (constructorInfo != null) { plug = (IRadegastPlugin)constructorInfo.Invoke(new[] { instance }); } else { constructorInfo = type.GetConstructor(new Type[] { }); if (constructorInfo != null) { plug = (IRadegastPlugin)constructorInfo.Invoke(new object[0]); } else { Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + type + " has no usable constructor.", Helpers.LogLevel.Debug); continue; } } loadedTypesFromAssembly = true; PluginInfo info = new PluginInfo() { FileName = loadfilename, Plugin = plug, Started = false, Domain = domain }; lock (PluginsLoaded) PluginsLoaded.Add(info); if (startPlugins && plug != null) { try { plug.StartPlugin(instance); info.Started = true; } catch (Exception ex) { Logger.Log(string.Format("Failed starting plugin {0}:", type), Helpers.LogLevel.Error, ex); } } } catch (Exception ex) { Logger.Log("ERROR Constructing Radegast Plugin: " + loadfilename + " because " + ex, Helpers.LogLevel.Debug); } } else { try { loadedTypesFromAssembly |= instance.CommandsManager.LoadType(type); loadedTypesFromAssembly |= instance.ContextActionManager.LoadType(type); } catch (Exception ex) { Logger.Log("ERROR in Radegast Plugin: " + loadfilename + " Command: " + type + " because " + ex.Message + " " + ex.StackTrace, Helpers.LogLevel.Debug); } } } if (domain != null && !loadedTypesFromAssembly) { AppDomain.Unload(domain); } } } public class RemoteLoader : MarshalByRefObject { public Assembly Load(string loadfilename) { return AppDomain.CurrentDomain.Load(File.ReadAllBytes(loadfilename)); } } }