/// <summary>
 /// Checks if a given plugin is disabled.
 /// </summary>
 /// <param name="meta">the plugin to check</param>
 /// <returns><see langword="true"/> if the plugin is disabled, <see langword="false"/> otherwise.</returns>
 public static bool IsDisabled(PluginMetadata meta) => DisabledPlugins.Contains(meta);
 /// <summary>
 /// Checks if a given plugin is enabled.
 /// </summary>
 /// <param name="meta">the plugin to check</param>
 /// <returns><see langword="true"/> if the plugin is enabled, <see langword="false"/> otherwise.</returns>
 public static bool IsEnabled(PluginMetadata meta) => BSMetas.Any(p => p.Metadata == meta);
Exemple #3
0
 public object?Inject(object?prev, ParameterInfo info, PluginMetadata meta, InjectedValueProvider provider)
 => Injector(prev, info, meta, provider);
        /// <summary>
        /// Enables a plugin that had been previously disabled.
        /// </summary>
        /// <param name="plugin">the plugin to enable</param>
        /// <returns>whether a restart is needed to activate</returns>
        public static bool EnablePlugin(PluginMetadata plugin)
        {
            if (plugin == null)
            {
                return(false);
            }

            if (plugin.IsBare)
            {
                Logger.loader.Warn($"Trying to enable bare manifest");
                return(false);
            }

            if (!IsDisabled(plugin))
            {
                return(false);
            }

            Logger.loader.Info($"Enabling {plugin.Name}");

            DisabledConfig.Ref.Value.DisabledModIds.Remove(plugin.Id ?? plugin.Name);
            DisabledConfig.Provider.Store(DisabledConfig.Ref.Value);

            var needsRestart = true;

            var depsNeedRestart = plugin.Dependencies.Aggregate(false, (b, p) => EnablePlugin(p) || b);

            var runtimeInfo = runtimeDisabled.FirstOrDefault(p => p.Metadata == plugin);

            if (runtimeInfo != null && runtimeInfo.Plugin is IDisablablePlugin disable)
            {
                try
                {
                    disable.OnEnable();
                }
                catch (Exception e)
                {
                    Logger.loader.Error($"Error occurred trying to enable {plugin.Name}");
                    Logger.loader.Error(e);
                }
                needsRestart = false;
            }
            else
            {
                PluginLoader.DisabledPlugins.Remove(plugin);
                if (runtimeInfo == null)
                {
                    runtimeInfo  = InitPlugin(plugin);
                    needsRestart = false;
                }
            }

            if (runtimeInfo != null)
            {
                runtimeDisabled.Remove(runtimeInfo);
            }

            _bsPlugins.Add(runtimeInfo);

            try
            {
                PluginEnabled?.Invoke(runtimeInfo, needsRestart || depsNeedRestart);
            }
            catch (Exception e)
            {
                Logger.loader.Error($"Error occurred invoking enable event for {plugin.Name}");
                Logger.loader.Error(e);
            }

            return(needsRestart || depsNeedRestart);
        }
Exemple #5
0
 /// <summary>
 /// Enables a plugin in this transaction.
 /// </summary>
 /// <param name="meta">the plugin to enable</param>
 /// <param name="autoDeps">whether or not to automatically enable all dependencies of the plugin</param>
 /// <returns><see langword="true"/> if the transaction's state was changed, <see langword="false"/> otherwise</returns>
 /// <exception cref="ObjectDisposedException">if this object has been disposed</exception>
 /// <exception cref="ArgumentException">if <paramref name="meta"/> is not loadable</exception>
 /// <seealso cref="Enable(PluginMetadata, out IEnumerable{PluginMetadata}, bool)"/>
 public bool Enable(PluginMetadata meta, bool autoDeps = true)
 => Enable(meta, out var _, autoDeps);
Exemple #6
0
 private bool IsDisabledInternal(PluginMetadata meta)
 => (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta)) ||
 toDisable.Contains(meta);
Exemple #7
0
 /// <summary>
 /// Checks if a plugin is disabled according to this transaction's current state.
 /// </summary>
 /// <remarks>
 /// <para>This should be roughly equivalent to <c>DisabledPlugins.Contains(meta)</c>, but more performant.</para>
 /// <para>This should also always return the inverse of <see cref="IsEnabled(PluginMetadata)"/> for valid plugins.</para>
 /// </remarks>
 /// <param name="meta">the plugin to check</param>
 /// <returns><see langword="true"/> if the plugin is disabled, <see langword="false"/> otherwise</returns>
 /// <exception cref="ObjectDisposedException">if this object has been disposed</exception>
 /// <seealso cref="DisabledPlugins"/>
 /// <seealso cref="IsEnabled(PluginMetadata)"/>
 public bool IsDisabled(PluginMetadata meta)
 => ThrowIfDisposed <bool>() || IsDisabledInternal(meta);
        internal static void LoadMetadata()
        {
            string[] plugins = Directory.GetFiles(UnityGame.PluginsPath, "*.dll");

            try
            {
                var selfMeta = new PluginMetadata
                {
                    Assembly   = Assembly.GetExecutingAssembly(),
                    File       = new FileInfo(Path.Combine(UnityGame.InstallPath, "IPA.exe")),
                    PluginType = null,
                    IsSelf     = true
                };

                string manifest;
                using (var manifestReader =
                           new StreamReader(
                               selfMeta.Assembly.GetManifestResourceStream(typeof(PluginLoader), "manifest.json") ??
                               throw new InvalidOperationException()))
                    manifest = manifestReader.ReadToEnd();

                selfMeta.Manifest = JsonConvert.DeserializeObject <PluginManifest>(manifest);

                PluginsMetadata.Add(selfMeta);
            }
            catch (Exception e)
            {
                Logger.loader.Critical("Error loading own manifest");
                Logger.loader.Critical(e);
            }

            foreach (var plugin in plugins)
            {
                var metadata = new PluginMetadata
                {
                    File   = new FileInfo(Path.Combine(UnityGame.PluginsPath, plugin)),
                    IsSelf = false
                };

                try
                {
                    var pluginModule = AssemblyDefinition.ReadAssembly(plugin, new ReaderParameters
                    {
                        ReadingMode      = ReadingMode.Immediate,
                        ReadWrite        = false,
                        AssemblyResolver = new CecilLibLoader()
                    }).MainModule;

                    string pluginNs = "";

                    foreach (var resource in pluginModule.Resources)
                    {
                        const string manifestSuffix = ".manifest.json";
                        if (!(resource is EmbeddedResource embedded) ||
                            !embedded.Name.EndsWith(manifestSuffix))
                        {
                            continue;
                        }

                        pluginNs = embedded.Name.Substring(0, embedded.Name.Length - manifestSuffix.Length);

                        string manifest;
                        using (var manifestReader = new StreamReader(embedded.GetResourceStream()))
                            manifest = manifestReader.ReadToEnd();

                        metadata.Manifest = JsonConvert.DeserializeObject <PluginManifest>(manifest);
                        break;
                    }

                    if (metadata.Manifest == null)
                    {
#if DIRE_LOADER_WARNINGS
                        Logger.loader.Error($"Could not find manifest.json for {Path.GetFileName(plugin)}");
#else
                        Logger.loader.Notice($"No manifest.json in {Path.GetFileName(plugin)}");
#endif
                        continue;
                    }

                    void TryGetNamespacedPluginType(string ns, PluginMetadata meta)
                    {
                        foreach (var type in pluginModule.Types)
                        {
                            if (type.Namespace != ns)
                            {
                                continue;
                            }

                            if (type.HasCustomAttributes)
                            {
                                var attr = type.CustomAttributes.FirstOrDefault(a => a.Constructor.DeclaringType.FullName == typeof(PluginAttribute).FullName);
                                if (attr != null)
                                {
                                    if (!attr.HasConstructorArguments)
                                    {
                                        Logger.loader.Warn($"Attribute plugin found in {type.FullName}, but attribute has no arguments");
                                        return;
                                    }

                                    var args = attr.ConstructorArguments;
                                    if (args.Count != 1)
                                    {
                                        Logger.loader.Warn($"Attribute plugin found in {type.FullName}, but attribute has unexpected number of arguments");
                                        return;
                                    }
                                    var rtOptionsArg = args[0];
                                    if (rtOptionsArg.Type.FullName != typeof(RuntimeOptions).FullName)
                                    {
                                        Logger.loader.Warn($"Attribute plugin found in {type.FullName}, but first argument is of unexpected type {rtOptionsArg.Type.FullName}");
                                        return;
                                    }

                                    var rtOptionsValInt = (int)rtOptionsArg.Value; // `int` is the underlying type of RuntimeOptions

                                    meta.RuntimeOptions = (RuntimeOptions)rtOptionsValInt;
                                    meta.PluginType     = type;
                                    return;
                                }
                            }
                        }
                    }

                    var hint = metadata.Manifest.Misc?.PluginMainHint;

                    if (hint != null)
                    {
                        var type = pluginModule.GetType(hint);
                        if (type != null)
                        {
                            TryGetNamespacedPluginType(hint, metadata);
                        }
                    }

                    if (metadata.PluginType == null)
                    {
                        TryGetNamespacedPluginType(pluginNs, metadata);
                    }

                    if (metadata.PluginType == null)
                    {
                        Logger.loader.Error($"No plugin found in the manifest {(hint != null ? $"hint path ({hint}) or " : "")}namespace ({pluginNs}) in {Path.GetFileName(plugin)}");
                        continue;
                    }

                    Logger.loader.Debug($"Adding info for {Path.GetFileName(plugin)}");
                    PluginsMetadata.Add(metadata);
                }
                catch (Exception e)
                {
                    Logger.loader.Error($"Could not load data for plugin {Path.GetFileName(plugin)}");
                    Logger.loader.Error(e);
                    ignoredPlugins.Add(metadata, new IgnoreReason(Reason.Error)
                    {
                        ReasonText = "An error ocurred loading the data",
                        Error      = e
                    });
                }
            }

            IEnumerable <string> bareManifests = Directory.GetFiles(UnityGame.PluginsPath, "*.json");
            bareManifests = bareManifests.Concat(Directory.GetFiles(UnityGame.PluginsPath, "*.manifest"));
            foreach (var manifest in bareManifests)
            { // TODO: maybe find a way to allow a bare manifest to specify an associated file
                try
                {
                    var metadata = new PluginMetadata
                    {
                        File   = new FileInfo(Path.Combine(UnityGame.PluginsPath, manifest)),
                        IsSelf = false,
                        IsBare = true,
                    };

                    metadata.Manifest = JsonConvert.DeserializeObject <PluginManifest>(File.ReadAllText(manifest));

                    if (metadata.Manifest.Files.Length < 1)
                    {
                        Logger.loader.Warn($"Bare manifest {Path.GetFileName(manifest)} does not declare any files. " +
                                           $"Dependency resolution and verification cannot be completed.");
                    }

                    Logger.loader.Debug($"Adding info for bare manifest {Path.GetFileName(manifest)}");
                    PluginsMetadata.Add(metadata);
                }
                catch (Exception e)
                {
                    Logger.loader.Error($"Could not load data for bare manifest {Path.GetFileName(manifest)}");
                    Logger.loader.Error(e);
                }
            }

            foreach (var meta in PluginsMetadata)
            { // process description include
                var lines = meta.Manifest.Description.Split('\n');
                var m     = embeddedTextDescriptionPattern.Match(lines[0]);
                if (m.Success)
                {
                    if (meta.IsBare)
                    {
                        Logger.loader.Warn($"Bare manifest cannot specify description file");
                        meta.Manifest.Description = string.Join("\n", lines.Skip(1).StrJP()); // ignore first line
                        continue;
                    }

                    var    name = m.Groups[1].Value;
                    string description;
                    if (!meta.IsSelf)
                    {
                        var resc = meta.PluginType.Module.Resources.Select(r => r as EmbeddedResource)
                                   .NonNull()
                                   .FirstOrDefault(r => r.Name == name);
                        if (resc == null)
                        {
                            Logger.loader.Warn($"Could not find description file for plugin {meta.Name} ({name}); ignoring include");
                            meta.Manifest.Description = string.Join("\n", lines.Skip(1).StrJP()); // ignore first line
                            continue;
                        }

                        using var reader = new StreamReader(resc.GetResourceStream());
                        description      = reader.ReadToEnd();
                    }
                    else
                    {
                        using var descriptionReader = new StreamReader(meta.Assembly.GetManifestResourceStream(name));
                        description = descriptionReader.ReadToEnd();
                    }

                    meta.Manifest.Description = description;
                }
            }
        }
 /// <summary>
 /// Initializes an <see cref="IgnoreReason"/> with the provided data.
 /// </summary>
 /// <param name="reason">the <see cref="Loader.Reason"/> enum value that describes this reason</param>
 /// <param name="reasonText">the textual description of this ignore reason, if any</param>
 /// <param name="error">the <see cref="Exception"/> that caused this <see cref="IgnoreReason"/>, if any</param>
 /// <param name="relatedTo">the <see cref="PluginMetadata"/> this reason is related to, if any</param>
 public IgnoreReason(Reason reason, string reasonText = null, Exception error = null, PluginMetadata relatedTo = null)
 {
     Reason     = reason;
     ReasonText = reasonText;
     Error      = error;
     RelatedTo  = relatedTo;
 }