/// <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);
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); }
/// <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);
private bool IsDisabledInternal(PluginMetadata meta) => (currentlyDisabled.Contains(meta) && !toEnable.Contains(meta)) || toDisable.Contains(meta);
/// <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; }