From e87e1c8c6f90071eee4cbcb79d1b3d6038a0e48f Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Mon, 7 Jan 2019 12:15:38 -0600 Subject: [PATCH] Reworked plugin preloading to use Mono.Cecil instead of Assembly.ReflectionOnlyLoad --- IPA.Loader/Loader/PluginLoader.cs | 106 ++++++++++++++---------------- 1 file changed, 49 insertions(+), 57 deletions(-) diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index e4d96cc5..7a25585b 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using IPA.Config; using IPA.Logging; using IPA.Utilities; +using Mono.Cecil; using Newtonsoft.Json; using Version = SemVer.Version; @@ -29,16 +30,14 @@ namespace IPA.Loader /// public class PluginMetadata { - //TODO: rework this to load using Mono.Cecil to prevent multiples of each module being loaded into memory - // ReSharper disable once UnusedAutoPropertyAccessor.Global /// /// The assembly the plugin was loaded from. /// public Assembly Assembly { get; internal set; } /// - /// The Type that is the main type for the plugin. + /// The TypeDefinition for the main type of the plugin. /// - public Type PluginType { get; internal set; } + public TypeDefinition PluginType { get; internal set; } /// /// The human readable name of the plugin. /// @@ -76,7 +75,7 @@ namespace IPA.Loader } /// - public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.AssemblyQualifiedName}) from '{LoneFunctions.GetRelativePath(File.FullName, BeatSaber.InstallPath)}'"; + public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.FullName}) from '{LoneFunctions.GetRelativePath(File?.FullName, BeatSaber.InstallPath)}'"; } /// @@ -99,10 +98,9 @@ namespace IPA.Loader try { - var selfmeta = new PluginMetadata + var selfMeta = new PluginMetadata { - Assembly = Assembly.ReflectionOnlyLoadFrom(Assembly.GetExecutingAssembly() - .Location), // load self as reflection only + Assembly = Assembly.GetExecutingAssembly(), File = new FileInfo(Path.Combine(BeatSaber.InstallPath, "IPA.exe")), PluginType = null }; @@ -110,13 +108,13 @@ namespace IPA.Loader string manifest; using (var manifestReader = new StreamReader( - selfmeta.Assembly.GetManifestResourceStream(typeof(PluginLoader), "manifest.json") ?? + selfMeta.Assembly.GetManifestResourceStream(typeof(PluginLoader), "manifest.json") ?? throw new InvalidOperationException())) manifest = manifestReader.ReadToEnd(); - selfmeta.Manifest = JsonConvert.DeserializeObject(manifest); + selfMeta.Manifest = JsonConvert.DeserializeObject(manifest); - PluginsMetadata.Add(selfmeta); + PluginsMetadata.Add(selfMeta); } catch (Exception e) { @@ -125,32 +123,35 @@ namespace IPA.Loader } foreach (var plugin in plugins) - { // should probably do patching first /shrug + { try { - var metadata = new PluginMetadata(); - - var assembly = Assembly.ReflectionOnlyLoadFrom(plugin); - metadata.Assembly = assembly; - metadata.File = new FileInfo(plugin); - - Type[] types; - try + var metadata = new PluginMetadata { - types = assembly.GetTypes(); - } - catch (ReflectionTypeLoadException e) + File = new FileInfo(Path.Combine(BeatSaber.PluginsPath, plugin)) + }; + + var pluginModule = AssemblyDefinition.ReadAssembly(plugin, new ReaderParameters { - types = e.Types; - } - foreach (var type in types) + ReadingMode = ReadingMode.Immediate, + ReadWrite = false + }).MainModule; + + var iBeatSaberPlugin = pluginModule.ImportReference(typeof(IBeatSaberPlugin)); + foreach (var type in pluginModule.Types) { - if (type == null) continue; + foreach (var inter in type.Interfaces) + { + var ifType = inter.InterfaceType; - var iInterface = type.GetInterface(nameof(IBeatSaberPlugin)); - if (iInterface == null) continue; - metadata.PluginType = type; - break; + if (iBeatSaberPlugin.FullName == ifType.FullName) + { + metadata.PluginType = type; + break; + } + } + + if (metadata.PluginType != null) break; } if (metadata.PluginType == null) @@ -159,27 +160,18 @@ namespace IPA.Loader continue; } - Stream metadataStream; - try + foreach (var resource in pluginModule.Resources) { - metadataStream = assembly.GetManifestResourceStream(metadata.PluginType, "manifest.json"); - if (metadataStream == null) - { - Logger.loader.Error($"manifest.json not found in plugin {Path.GetFileName(plugin)}"); - continue; - } - } - catch (FileNotFoundException) - { - Logger.loader.Error($"manifest.json not found in plugin {Path.GetFileName(plugin)}"); - continue; - } + if (!(resource is EmbeddedResource embedded) || + embedded.Name != $"{metadata.PluginType.Namespace}.manifest.json") continue; - string manifest; - using (var manifestReader = new StreamReader(metadataStream)) - manifest = manifestReader.ReadToEnd(); + string manifest; + using (var manifestReader = new StreamReader(embedded.GetResourceStream())) + manifest = manifestReader.ReadToEnd(); - metadata.Manifest = JsonConvert.DeserializeObject(manifest); + metadata.Manifest = JsonConvert.DeserializeObject(manifest); + break; + } PluginsMetadata.Add(metadata); } @@ -312,10 +304,10 @@ namespace IPA.Loader try { - Logger.loader.Debug(meta.Assembly.GetName().ToString()); - meta.Assembly = Assembly.Load(meta.Assembly.GetName()); + Logger.loader.Debug(meta.File.FullName); + meta.Assembly = Assembly.LoadFrom(meta.File.FullName); - var type = meta.PluginType; + var type = meta.Assembly.GetType(meta.PluginType.FullName); var instance = (IBeatSaberPlugin)Activator.CreateInstance(type); info.Metadata = meta; @@ -334,18 +326,18 @@ namespace IPA.Loader foreach (var param in initParams) { - var ptype = param.ParameterType; - if (ptype.IsAssignableFrom(typeof(Logger))) + var paramType = param.ParameterType; + if (paramType.IsAssignableFrom(typeof(Logger))) { if (modLogger == null) modLogger = new StandardLogger(meta.Name); initArgs.Add(modLogger); } - else if (ptype.IsAssignableFrom(typeof(IModPrefs))) + else if (paramType.IsAssignableFrom(typeof(IModPrefs))) { if (modPrefs == null) modPrefs = new ModPrefs(instance); initArgs.Add(modPrefs); } - else if (ptype.IsAssignableFrom(typeof(IConfigProvider))) + else if (paramType.IsAssignableFrom(typeof(IConfigProvider))) { if (cfgProvider == null) { @@ -354,7 +346,7 @@ namespace IPA.Loader initArgs.Add(cfgProvider); } else - initArgs.Add(ptype.GetDefault()); + initArgs.Add(paramType.GetDefault()); } init.Invoke(instance, initArgs.ToArray());