Changeset 2050


Ignore:
Timestamp:
5/4/2010 4:30:46 AM (5 years ago)
Author:
lowjoel
Message:

Implemented Core plugins as a list of plugins which must be loaded, and is dictated by the loading assembly. Addresses #363.

  • Plugins are always only loaded once
  • Only Core plugins, as defined by the loading assembly, is given the ability to be set as a Core plugin
Location:
trunk/eraser/Eraser.Manager
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/eraser/Eraser.Manager/Plugins.cs

    r2010 r2050  
    9999        /// <param name="filePath">The absolute or relative file path to the 
    100100        /// DLL.</param> 
    101         public abstract void LoadPlugin(string filePath); 
     101        /// <returns>True if the plugin is loaded, false otherwise.</returns> 
     102        /// <remarks>If a plugin is loaded twice, this function should do nothing 
     103        /// and return True.</remarks> 
     104        public abstract bool LoadPlugin(string filePath); 
    102105    } 
    103106 
     
    136139        public override void Load() 
    137140        { 
     141            //Specify additional places to load assemblies from 
    138142            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; 
    139143            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency; 
    140             string pluginsFolder = Path.Combine( 
    141                 Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location 
    142                 PLUGINSFOLDER //Plugins folder 
    143             ); 
    144  
    145             foreach (string fileName in Directory.GetFiles(pluginsFolder)) 
     144 
     145            try 
     146            { 
     147                //Load all core plugins first 
     148                foreach (KeyValuePair<string, string> plugin in CorePlugins) 
     149                { 
     150                    LoadCorePlugin(Path.Combine(PluginsFolder, plugin.Key), plugin.Value); 
     151                } 
     152 
     153                //Then load the rest 
     154                foreach (string fileName in Directory.GetFiles(PluginsFolder)) 
     155                { 
     156                    FileInfo file = new FileInfo(fileName); 
     157                    if (file.Extension.Equals(".dll")) 
     158                        try 
     159                        { 
     160                            LoadPlugin(file.FullName); 
     161                        } 
     162                        catch (BadImageFormatException) 
     163                        { 
     164                        } 
     165                        catch (FileLoadException) 
     166                        { 
     167                        } 
     168                } 
     169            } 
     170            finally 
     171            { 
     172                AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolve; 
     173                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= ResolveReflectionDependency; 
     174            } 
     175        } 
     176 
     177        protected override void Dispose(bool disposing) 
     178        { 
     179            if (plugins == null) 
     180                return; 
     181 
     182            if (disposing) 
     183            { 
     184                //Unload all the plugins. This will cause all the plugins to execute 
     185                //the cleanup code. 
     186                foreach (PluginInstance plugin in plugins) 
     187                    if (plugin.Plugin != null) 
     188                        plugin.Plugin.Dispose(); 
     189            } 
     190 
     191            plugins = null; 
     192        } 
     193 
     194        /// <summary> 
     195        /// The path to the folder containing the plugins. 
     196        /// </summary> 
     197        public readonly string PluginsFolder = Path.Combine( 
     198            Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location 
     199            "Plugins" //Plugins folder 
     200        ); 
     201 
     202        /// <summary> 
     203        /// The list of plugins which are core, the key is the file name, the value 
     204        /// is the assembly name. 
     205        /// </summary> 
     206        private readonly KeyValuePair<string, string>[] CorePlugins = 
     207            new KeyValuePair<string, string>[] 
     208            { 
     209                new KeyValuePair<string, string>( 
     210                    "Eraser.DefaultPlugins.dll", 
     211                    "Eraser.DefaultPlugins" 
     212                ) 
     213            }; 
     214 
     215        public override IList<PluginInstance> Plugins 
     216        { 
     217            get { return plugins.AsReadOnly(); } 
     218        } 
     219 
     220        /// <summary> 
     221        /// Verifies whether the provided assembly is a plugin. 
     222        /// </summary> 
     223        /// <param name="assembly">The assembly to verify.</param> 
     224        /// <returns>True if the assembly provided is a plugin, false otherwise.</returns> 
     225        private bool IsPlugin(Assembly assembly) 
     226        { 
     227            //Iterate over every exported type, checking if it implements IPlugin 
     228            Type typePlugin = assembly.GetExportedTypes().FirstOrDefault( 
     229                    type => type.GetInterface("Eraser.Manager.Plugin.IPlugin", true) != null); 
     230 
     231            //If the typePlugin type is empty the assembly doesn't implement IPlugin it's not 
     232            //a plugin. 
     233            return typePlugin != null; 
     234        } 
     235 
     236        /// <summary> 
     237        /// Loads the assembly at the specified path, and verifying its assembly name, 
     238        /// ensuring that the assembly contains a core plugin. 
     239        /// </summary> 
     240        /// <param name="filePath">The path to the assembly.</param> 
     241        /// <param name="assemblyName">The name of the assembly.</param> 
     242        private void LoadCorePlugin(string filePath, string assemblyName) 
     243        { 
     244            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(filePath); 
     245            if (assembly.GetName().FullName.Substring(0, assemblyName.Length + 1) != 
     246                assemblyName + ",") 
     247            { 
     248                throw new FileLoadException(S._("The Core plugin assembly is not one which" + 
     249                    "Eraser expects.\n\nCheck that the Eraser installation is not corrupt, or " + 
     250                    "reinstall the program.")); 
     251            } 
     252 
     253            //Create the PluginInstance structure 
     254            PluginInstance instance = new PluginInstance(assembly, null); 
     255 
     256            //Ignore non-plugins 
     257            if (!IsPlugin(instance.Assembly)) 
     258                throw new FileLoadException(S._("The provided Core plugin assembly is not a " + 
     259                    "plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall " + 
     260                    "the program.")); 
     261 
     262            //OK this assembly is a plugin 
     263            lock (plugins) 
     264                plugins.Add(instance); 
     265 
     266            //Check for the presence of a valid signature: Core plugins must have the same 
     267            //public key as the current assembly 
     268            if (!assembly.GetName().GetPublicKey().SequenceEqual( 
     269                    Assembly.GetExecutingAssembly().GetName().GetPublicKey())) 
     270            { 
     271                throw new FileLoadException(S._("The provided Core plugin does not have an " + 
     272                    "identical public key as the Eraser assembly.\n\nCheck that the Eraser " + 
     273                    "installation is not corrupt, or reinstall the program.")); 
     274            } 
     275 
     276            //Okay, everything's fine, initialise the plugin 
     277            instance.Assembly = Assembly.Load(instance.Assembly.GetName()); 
     278            instance.LoadingPolicy = LoadingPolicy.Core; 
     279            InitialisePlugin(instance); 
     280        } 
     281 
     282        public override bool LoadPlugin(string filePath) 
     283        { 
     284            //Create the PluginInstance structure 
     285            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath); 
     286            PluginInstance instance = new PluginInstance(reflectAssembly, null); 
     287 
     288            //Check that the plugin hasn't yet been loaded. 
     289            if (Plugins.Count( 
     290                    plugin => plugin.Assembly.GetName().FullName == 
     291                    reflectAssembly.GetName().FullName) > 0) 
     292            { 
     293                return true; 
     294            } 
     295 
     296            //Ignore non-plugins 
     297            if (!IsPlugin(instance.Assembly)) 
     298                return false; 
     299 
     300            //OK this assembly is a plugin 
     301            lock (plugins) 
     302                plugins.Add(instance); 
     303 
     304            //If the plugin does not have an approval or denial, check for the presence of 
     305            //a valid signature. 
     306            IDictionary<Guid, bool> approvals = ManagerLibrary.Settings.PluginApprovals; 
     307            if (!approvals.ContainsKey(instance.AssemblyInfo.Guid) && 
     308                (reflectAssembly.GetName().GetPublicKey().Length == 0 || 
     309                !Security.VerifyStrongName(filePath) || 
     310                instance.AssemblyAuthenticode == null)) 
     311            { 
     312                return false; 
     313            } 
     314 
     315            //Preliminary checks to verify whether the plugin can be loaded (safely) passes, 
     316            //Load the assembly fully, and then initialise it. 
     317            instance.Assembly = Assembly.Load(reflectAssembly.GetName()); 
     318             
     319            //The plugin either is explicitly allowed or disallowed to load, or 
     320            //it has an Authenticode Signature as well as a Strong Name. Get the 
     321            //loading policy of the plugin. 
     322            { 
     323                 
     324            } 
     325 
     326            bool initialisePlugin = false; 
     327 
     328            //Is there an approval or denial? 
     329            if (approvals.ContainsKey(instance.AssemblyInfo.Guid)) 
     330                initialisePlugin = approvals[instance.AssemblyInfo.Guid]; 
     331 
     332            //There's no approval or denial, what is the specified loading policy? 
     333            else 
     334                initialisePlugin = instance.LoadingPolicy != LoadingPolicy.DefaultOff; 
     335 
     336            if (initialisePlugin) 
     337            { 
     338                InitialisePlugin(instance); 
     339                return true; 
     340            } 
     341 
     342            return false; 
     343        } 
     344 
     345        /// <summary> 
     346        /// Initialises the given plugin from the plugin's description. 
     347        /// </summary> 
     348        /// <param name="instance">The <see cref="PluginInstance"/> structure to fill.</param> 
     349        private void InitialisePlugin(PluginInstance instance) 
     350        { 
     351            try 
     352            { 
     353                //Iterate over every exported type, checking for the IPlugin implementation 
     354                Type typePlugin = instance.Assembly.GetExportedTypes().First( 
     355                    type => type.GetInterface("Eraser.Manager.Plugin.IPlugin", true) != null); 
     356                if (typePlugin == null) 
     357                    return; 
     358 
     359                //Initialize the plugin 
     360                instance.Plugin = (IPlugin)Activator.CreateInstance( 
     361                    instance.Assembly.GetType(typePlugin.ToString())); 
     362                instance.Plugin.Initialize(this); 
     363 
     364                //And broadcast the plugin load event 
     365                OnPluginLoaded(this, new PluginLoadedEventArgs(instance)); 
     366            } 
     367            catch (System.Security.SecurityException e) 
     368            { 
     369                MessageBox.Show(S._("Could not load the plugin {0}.\n\nThe error returned was: {1}", 
     370                    instance.Assembly.Location, e.Message), S._("Eraser"), MessageBoxButtons.OK, 
     371                    MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 
     372                    Localisation.IsRightToLeft(null) ? 
     373                        MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0); 
     374            } 
     375        } 
     376 
     377        private Assembly AssemblyResolve(object sender, ResolveEventArgs args) 
     378        { 
     379            //Check the plugins folder 
     380            foreach (string fileName in Directory.GetFiles(PluginsFolder)) 
    146381            { 
    147382                FileInfo file = new FileInfo(fileName); 
     
    149384                    try 
    150385                    { 
    151                         LoadPlugin(file.FullName); 
     386                        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName); 
     387                        if (assembly.GetName().FullName == args.Name) 
     388                            return Assembly.LoadFile(file.FullName); 
    152389                    } 
    153390                    catch (BadImageFormatException) 
     
    158395                    } 
    159396            } 
    160         } 
    161  
    162         protected override void Dispose(bool disposing) 
    163         { 
    164             if (plugins == null) 
    165                 return; 
    166  
    167             if (disposing) 
    168             { 
    169                 //Unload all the plugins. This will cause all the plugins to execute 
    170                 //the cleanup code. 
    171                 foreach (PluginInstance plugin in plugins) 
    172                     if (plugin.Plugin != null) 
    173                         plugin.Plugin.Dispose(); 
    174             } 
    175  
    176             plugins = null; 
    177         } 
    178  
    179         /// <summary> 
    180         /// The path to the folder containing the plugins. 
    181         /// </summary> 
    182         public const string PLUGINSFOLDER = "Plugins"; 
    183  
    184         public override IList<PluginInstance> Plugins 
    185         { 
    186             get { return plugins.AsReadOnly(); } 
    187         } 
    188  
    189         public override void LoadPlugin(string filePath) 
    190         { 
    191             //Create the PluginInstance structure 
    192             Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath); 
    193             PluginInstance instance = new PluginInstance(reflectAssembly, null); 
    194             Type typePlugin = null; 
    195  
    196             //Iterate over every exported type, checking if it implements IPlugin 
    197             foreach (Type type in instance.Assembly.GetExportedTypes()) 
    198             { 
    199                 //Check for an implementation of IPlugin 
    200                 Type typeInterface = type.GetInterface("Eraser.Manager.Plugin.IPlugin", true); 
    201                 if (typeInterface != null) 
    202                 { 
    203                     typePlugin = type; 
    204                     break; 
    205                 } 
    206             } 
    207  
    208             //If the typePlugin type is empty the assembly doesn't implement IPlugin; we 
    209             //aren't interested. 
    210             if (typePlugin == null) 
    211                 return; 
    212  
    213             //OK this assembly is a plugin 
    214             lock (plugins) 
    215                 plugins.Add(instance); 
    216  
    217             //If the plugin does not have an approval or denial, check for the presence of 
    218             //a valid signature. 
    219             IDictionary<Guid, bool> approvals = ManagerLibrary.Settings.PluginApprovals; 
    220             if (!approvals.ContainsKey(instance.AssemblyInfo.Guid) && 
    221                 (reflectAssembly.GetName().GetPublicKey().Length == 0 || 
    222                 !Security.VerifyStrongName(filePath) || 
    223                 instance.AssemblyAuthenticode == null)) 
    224             { 
    225                 return; 
    226             } 
    227  
    228             //The plugin either is explicitly allowed or disallowed to load, or 
    229             //it has an Authenticode Signature as well as a Strong Name. Get the 
    230             //loading policy of the plugin. 
    231             instance.Assembly = Assembly.LoadFrom(filePath); 
    232             { 
    233                 object[] attr = instance.Assembly.GetCustomAttributes(typeof(LoadingPolicyAttribute), true); 
    234                 if (attr.Length != 0) 
    235                 { 
    236                     instance.LoadingPolicy = ((LoadingPolicyAttribute)attr[0]).Policy; 
    237  
    238                     //If the loading policy is that the plugin is Core, we need to verify 
    239                     //the public key of the assembly. 
    240                     if (instance.LoadingPolicy == LoadingPolicy.Core && 
    241                         !reflectAssembly.GetName().GetPublicKey().SequenceEqual( 
    242                             Assembly.GetExecutingAssembly().GetName().GetPublicKey())) 
    243                     { 
    244                         instance.LoadingPolicy = LoadingPolicy.None; 
    245                     } 
    246                 } 
    247             } 
    248  
    249             bool loadPlugin = false; 
    250  
    251             //If the loading policy is such that the plugin is a core plugin, ALWAYS load it. 
    252             if (instance.LoadingPolicy == LoadingPolicy.Core) 
    253                 loadPlugin = true; 
    254  
    255             //The plugin is not a core plugin, is there an approval or denial? 
    256             else if (approvals.ContainsKey(instance.AssemblyInfo.Guid)) 
    257                 loadPlugin = approvals[instance.AssemblyInfo.Guid]; 
    258  
    259             //There's no approval or denial, what is the specified loading policy? 
    260             else 
    261                 loadPlugin = instance.LoadingPolicy != LoadingPolicy.DefaultOff; 
    262  
    263  
    264             if (loadPlugin) 
    265             { 
    266                 try 
    267                 { 
    268                     //Initialize the plugin 
    269                     IPlugin pluginInterface = (IPlugin)Activator.CreateInstance( 
    270                         instance.Assembly.GetType(typePlugin.ToString())); 
    271                     pluginInterface.Initialize(this); 
    272                     instance.Plugin = pluginInterface; 
    273  
    274                     //And broadcast the plugin load event 
    275                     OnPluginLoaded(this, new PluginLoadedEventArgs(instance)); 
    276                 } 
    277                 catch (System.Security.SecurityException e) 
    278                 { 
    279                     MessageBox.Show(S._("Could not load the plugin {0}.\n\nThe error returned was: {1}", 
    280                         filePath, e.Message), S._("Eraser"), MessageBoxButtons.OK, MessageBoxIcon.Error, 
    281                         MessageBoxDefaultButton.Button1, Localisation.IsRightToLeft(null) ? 
    282                             MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign : 0); 
    283                 } 
    284             } 
    285         } 
    286  
    287         private Assembly AssemblyResolve(object sender, ResolveEventArgs args) 
    288         { 
    289             lock (plugins) 
    290                 foreach (PluginInstance instance in plugins) 
    291                     if (instance.Assembly.FullName == args.Name) 
    292                         return instance.Assembly; 
     397 
    293398            return null; 
    294399        } 
     
    348453                    else if (attr.Constructor.DeclaringType == typeof(AssemblyCompanyAttribute)) 
    349454                        info.Author = (string)attr.ConstructorArguments[0].Value; 
     455                    else if (attr.Constructor.DeclaringType == typeof(LoadingPolicyAttribute)) 
     456                    { 
     457                        LoadingPolicy = (LoadingPolicy)attr.ConstructorArguments[0].Value; 
     458                        if (LoadingPolicy == LoadingPolicy.Core) 
     459                            LoadingPolicy = LoadingPolicy.None; 
     460                    } 
    350461 
    351462                this.AssemblyInfo = info; 
     
    458569        /// The author of the plug-in, used for display in the UI and for users 
    459570        /// to contact the author about bugs. Must be in the format: 
    460         ///     (.+) \<([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\> 
     571        ///     (.+) \&lt;([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\&gt; 
    461572        /// </summary> 
    462573        /// <example>Joel Low <joel@joelsplace.sg></example> 
     
    505616        /// The host must always load the plugin. 
    506617        /// </summary> 
    507         /// <remarks>For this policy to have an effect, the plugin assembly must 
    508         /// have the same Strong Name as the loading assembly, otherwise it defaults 
    509         /// to None.</remarks> 
     618        /// <remarks>This policy does not have an effect when declared in the 
     619        /// <see cref="LoadingPolicyAttribute"/> attribute and will be equivalent 
     620        /// to <see cref="None"/>.</remarks> 
    510621        Core 
    511622    } 
  • trunk/eraser/Eraser.Manager/Strings.en.resx

    r2036 r2050  
    148148    <value>The file system on the drive {0} is not supported.</value> 
    149149  </data> 
     150  <data name="The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     151    <value>The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program.</value> 
     152  </data> 
     153  <data name="The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     154    <value>The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program.</value> 
     155  </data> 
     156  <data name="The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     157    <value>The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program.</value> 
     158  </data> 
    150159  <data name="Could not load the plugin {0}.\n\nThe error returned was: {1}" xml:space="preserve"> 
    151160    <value>Could not load the plugin {0}.\n\nThe error returned was: {1}</value> 
  • trunk/eraser/Eraser.Manager/Strings.it.resx

    r2036 r2050  
    148148    <value>Il tipo di file system sul disco {0} non è supportato.</value> 
    149149  </data> 
     150  <data name="The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     151    <value>(Untranslated)</value> 
     152  </data> 
     153  <data name="The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     154    <value>(Untranslated)</value> 
     155  </data> 
     156  <data name="The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     157    <value>(Untranslated)</value> 
     158  </data> 
    150159  <data name="Could not load the plugin {0}.\n\nThe error returned was: {1}" xml:space="preserve"> 
    151160    <value>Impossibile caricare il plugin {0}.\n\nL'errore ricevuto è stato: {1}</value> 
  • trunk/eraser/Eraser.Manager/Strings.nl.resx

    r2036 r2050  
    148148    <value>(Untranslated)</value> 
    149149  </data> 
     150  <data name="The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     151    <value>(Untranslated)</value> 
     152  </data> 
     153  <data name="The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     154    <value>(Untranslated)</value> 
     155  </data> 
     156  <data name="The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     157    <value>(Untranslated)</value> 
     158  </data> 
    150159  <data name="Could not load the plugin {0}.\n\nThe error returned was: {1}" xml:space="preserve"> 
    151160    <value>(Untranslated)</value> 
  • trunk/eraser/Eraser.Manager/Strings.pl.resx

    r2036 r2050  
    148148    <value>System plików napędu {0} nie jest wspierany.</value> 
    149149  </data> 
     150  <data name="The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     151    <value>(Untranslated)</value> 
     152  </data> 
     153  <data name="The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     154    <value>(Untranslated)</value> 
     155  </data> 
     156  <data name="The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     157    <value>(Untranslated)</value> 
     158  </data> 
    150159  <data name="Could not load the plugin {0}.\n\nThe error returned was: {1}" xml:space="preserve"> 
    151160    <value>Nie można było załadować wtyczki {0}.\n\nWystąpił błąd: {1}</value> 
  • trunk/eraser/Eraser.Manager/Strings.resx

    r2036 r2050  
    148148    <value>The file system on the drive {0} is not supported.</value> 
    149149  </data> 
     150  <data name="The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     151    <value>The Core plugin assembly is not one whichEraser expects.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program.</value> 
     152  </data> 
     153  <data name="The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     154    <value>The provided Core plugin assembly is not a plugin.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program.</value> 
     155  </data> 
     156  <data name="The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program." xml:space="preserve"> 
     157    <value>The provided Core plugin does not have an identical public key as the Eraser assembly.\n\nCheck that the Eraser installation is not corrupt, or reinstall the program.</value> 
     158  </data> 
    150159  <data name="Could not load the plugin {0}.\n\nThe error returned was: {1}" xml:space="preserve"> 
    151160    <value>Could not load the plugin {0}.\n\nThe error returned was: {1}</value> 
Note: See TracChangeset for help on using the changeset viewer.