Преглед изворни кода

Merge pull request #90 from ManlyMarco/pluginVersions

 Added MinimumVersion support to BepInDependency and added BepInIncompatibility
MarC0 пре 5 година
родитељ
комит
cf634c521a
3 измењених фајлова са 162 додато и 29 уклоњено
  1. 47 17
      BepInEx/Bootstrap/Chainloader.cs
  2. 88 5
      BepInEx/Contract/Attributes.cs
  3. 27 7
      BepInEx/Contract/PluginInfo.cs

+ 47 - 17
BepInEx/Bootstrap/Chainloader.cs

@@ -2,7 +2,6 @@
 using BepInEx.Logging;
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Reflection;
@@ -28,6 +27,8 @@ namespace BepInEx.Bootstrap
 
 		public static List<BaseUnityPlugin> Plugins { get; } = new List<BaseUnityPlugin>();
 
+		public static List<string> DependencyErrors { get; } = new List<string>();
+
 		/// <summary>
 		/// The GameObject that all plugins are attached to as components.
 		/// </summary>
@@ -126,12 +127,14 @@ namespace BepInEx.Bootstrap
 
 			var filters = BepInProcess.FromCecilType(type);
 			var dependencies = BepInDependency.FromCecilType(type);
+			var incompatibilities = BepInIncompatibility.FromCecilType(type);
 
 			return new PluginInfo
 			{
 				Metadata = metadata,
 				Processes = filters,
 				Dependencies = dependencies,
+				Incompatibilities = incompatibilities,
 				TypeName = type.FullName
 			};
 		}
@@ -207,7 +210,7 @@ namespace BepInEx.Bootstrap
 						continue;
 					}
 
-					dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
+					dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID).Concat(pluginInfo.Incompatibilities.Select(i => i.IncompatibilityGUID));
 					pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo;
 				}
 
@@ -218,7 +221,7 @@ namespace BepInEx.Bootstrap
 				var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList();
 
 				var invalidPlugins = new HashSet<string>();
-				var processedPlugins = new HashSet<string>();
+				var processedPlugins = new Dictionary<string, Version>();
 
 				foreach (var pluginGUID in sortedPlugins)
 				{
@@ -227,15 +230,16 @@ namespace BepInEx.Bootstrap
 						continue;
 
 					var dependsOnInvalidPlugin = false;
-					var missingDependencies = new List<string>();
+					var missingDependencies = new List<BepInDependency>();
 					foreach (var dependency in pluginInfo.Dependencies)
 					{
 						// If the depenency wasn't already processed, it's missing altogether
-						if (!processedPlugins.Contains(dependency.DependencyGUID))
+						bool depenencyExists = processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
+						if (!depenencyExists || pluginVersion < dependency.MinimumVersion)
 						{
 							// If the dependency is hard, collect it into a list to show
 							if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0)
-								missingDependencies.Add(dependency.DependencyGUID);
+								missingDependencies.Add(dependency);
 							continue;
 						}
 
@@ -247,19 +251,45 @@ namespace BepInEx.Bootstrap
 						}
 					}
 
-					processedPlugins.Add(pluginGUID);
+					var incompatibilities = new List<BepInIncompatibility>();
+					foreach (var incompatibility in pluginInfo.Incompatibilities)
+					{
+						if (processedPlugins.ContainsKey(incompatibility.IncompatibilityGUID))
+							incompatibilities.Add(incompatibility);
+					}
+
+					processedPlugins.Add(pluginGUID, pluginInfo.Metadata.Version);
 
 					if (dependsOnInvalidPlugin)
 					{
-						Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
+						string message = $"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details.";
+						DependencyErrors.Add(message);
+						Logger.LogWarning(message);
 						continue;
 					}
 
 					if (missingDependencies.Count != 0)
 					{
-						Logger.LogError($@"Missing the following dependencies for [{pluginInfo.Metadata.Name}]: {"\r\n"}{
-								string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
-							}{"\r\n"}Loading will be skipped; expect further errors and unstabilities.");
+						string ToMissingString(BepInDependency s)
+						{
+							bool emptyVersion = s.MinimumVersion.Major == 0 && s.MinimumVersion.Minor == 0 && s.MinimumVersion.Build == 0 && s.MinimumVersion.Revision == 0;
+							if (emptyVersion) return "- " + s.DependencyGUID;
+							return $"- {s.DependencyGUID} (at least v{s.MinimumVersion})";
+						}
+
+						string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it has missing dependencies: {string.Join(", ", missingDependencies.Select(ToMissingString).ToArray())}";
+						DependencyErrors.Add(message);
+						Logger.LogError(message);
+
+						invalidPlugins.Add(pluginGUID);
+						continue;
+					}
+
+					if (incompatibilities.Count != 0)
+					{
+						string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it is incompatible with: {string.Join(", ", incompatibilities.Select(i => i.IncompatibilityGUID).ToArray())}";
+						DependencyErrors.Add(message);
+						Logger.LogError(message);
 
 						invalidPlugins.Add(pluginGUID);
 						continue;
@@ -306,15 +336,15 @@ namespace BepInEx.Bootstrap
 		#region Config
 
 		private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap(
-			"Logging", 
-			"UnityLogListening", 
-			"Enables showing unity log messages in the BepInEx logging system.", 
+			"Logging",
+			"UnityLogListening",
+			"Enables showing unity log messages in the BepInEx logging system.",
 			true);
 
 		private static readonly ConfigWrapper<bool> ConfigDiskLogging = ConfigFile.CoreConfig.Wrap(
-			"Logging.Disk", 
-			"Enabled", 
-			"Enables writing log messages to disk.", 
+			"Logging.Disk",
+			"Enabled",
+			"Enables writing log messages to disk.",
 			true);
 
 		private static readonly ConfigWrapper<string> ConfigDiskConsoleDisplayedLevel = ConfigFile.CoreConfig.Wrap(

+ 88 - 5
BepInEx/Contract/Attributes.cs

@@ -1,10 +1,9 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
-using System.Reflection;
-using BepInEx.Logging;
+using BepInEx.Bootstrap;
 using Mono.Cecil;
-using Mono.Collections.Generic;
 
 namespace BepInEx
 {
@@ -66,7 +65,7 @@ namespace BepInEx
 	/// This attribute specifies any dependencies that this plugin has on other plugins.
 	/// </summary>
 	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-	public class BepInDependency : Attribute
+	public class BepInDependency : Attribute, ICacheable
 	{
 		public enum DependencyFlags
 		{
@@ -91,18 +90,102 @@ namespace BepInEx
 		/// </summary>
 		public DependencyFlags Flags { get; protected set; }
 
+		/// <summary>
+		/// The minimum version of the referenced plugin.
+		/// </summary>
+		public Version MinimumVersion { get; protected set; }
+
+		/// <summary>
+		/// Marks this <see cref="BaseUnityPlugin"/> as depenant on another plugin. The other plugin will be loaded before this one.
+		/// If the other plugin doesn't exist, what happens depends on the <see cref="Flags"/> parameter.
+		/// </summary>
 		/// <param name="DependencyGUID">The GUID of the referenced plugin.</param>
 		/// <param name="Flags">The flags associated with this dependency definition.</param>
 		public BepInDependency(string DependencyGUID, DependencyFlags Flags = DependencyFlags.HardDependency)
 		{
 			this.DependencyGUID = DependencyGUID;
 			this.Flags = Flags;
+			MinimumVersion = new Version();
+		}
+
+		/// <summary>
+		/// Marks this <see cref="BaseUnityPlugin"/> as depenant on another plugin. The other plugin will be loaded before this one.
+		/// If the other plugin doesn't exist or is of a version below <see cref="MinimumDependencyVersion"/>, this plugin will not load and an error will be logged instead.
+		/// </summary>
+		/// <param name="DependencyGUID">The GUID of the referenced plugin.</param>
+		/// <param name="MinimumDependencyVersion">The minimum version of the referenced plugin.</param>
+		/// <remarks>When version is supplied the dependency is always treated as HardDependency</remarks>
+		public BepInDependency(string DependencyGUID, string MinimumDependencyVersion) : this(DependencyGUID)
+		{
+			MinimumVersion = new Version(MinimumDependencyVersion);
 		}
 
 		internal static IEnumerable<BepInDependency> FromCecilType(TypeDefinition td)
 		{
 			var attrs = MetadataHelper.GetCustomAttributes<BepInDependency>(td, true);
-			return attrs.Select(customAttribute => new BepInDependency((string)customAttribute.ConstructorArguments[0].Value, (DependencyFlags)customAttribute.ConstructorArguments[1].Value)).ToList();
+			return attrs.Select(customAttribute =>
+			{
+				var dependencyGuid = (string)customAttribute.ConstructorArguments[0].Value;
+				var secondArg = customAttribute.ConstructorArguments[1].Value;
+				if (secondArg is string minVersion) return new BepInDependency(dependencyGuid, minVersion);
+				return new BepInDependency(dependencyGuid, (DependencyFlags)secondArg);
+			}).ToList();
+		}
+
+		void ICacheable.Save(BinaryWriter bw)
+		{
+			bw.Write(DependencyGUID);
+			bw.Write((int)Flags);
+			bw.Write(MinimumVersion.ToString());
+		}
+
+		void ICacheable.Load(BinaryReader br)
+		{
+			DependencyGUID = br.ReadString();
+			Flags = (DependencyFlags)br.ReadInt32();
+			MinimumVersion = new Version(br.ReadString());
+		}
+	}
+
+	/// <summary>
+	/// This attribute specifies other plugins that are incompatible with this plugin.
+	/// </summary>
+	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+	public class BepInIncompatibility : Attribute, ICacheable
+	{
+		/// <summary>
+		/// The GUID of the referenced plugin.
+		/// </summary>
+		public string IncompatibilityGUID { get; protected set; }
+		
+		/// <summary>
+		/// Marks this <see cref="BaseUnityPlugin"/> as incompatible with another plugin. 
+		/// If the other plugin exists, this plugin will not be loaded and a warning will be shown.
+		/// </summary>
+		/// <param name="IncompatibilityGUID">The GUID of the referenced plugin.</param>
+		public BepInIncompatibility(string IncompatibilityGUID)
+		{
+			this.IncompatibilityGUID = IncompatibilityGUID;
+		}
+
+		internal static IEnumerable<BepInIncompatibility> FromCecilType(TypeDefinition td)
+		{
+			var attrs = MetadataHelper.GetCustomAttributes<BepInIncompatibility>(td, true);
+			return attrs.Select(customAttribute =>
+			{
+				var dependencyGuid = (string)customAttribute.ConstructorArguments[0].Value;
+				return new BepInIncompatibility(dependencyGuid);
+			}).ToList();
+		}
+
+		void ICacheable.Save(BinaryWriter bw)
+		{
+			bw.Write(IncompatibilityGUID);
+		}
+
+		void ICacheable.Load(BinaryReader br)
+		{
+			IncompatibilityGUID = br.ReadString();
 		}
 	}
 

+ 27 - 7
BepInEx/Contract/PluginInfo.cs

@@ -13,13 +13,15 @@ namespace BepInEx.Contract
 
 		public IEnumerable<BepInDependency> Dependencies { get; internal set; }
 
+		public IEnumerable<BepInIncompatibility> Incompatibilities { get; internal set; }
+
 		public string Location { get; internal set; }
 
 		public BaseUnityPlugin Instance { get; internal set; }
 
 		internal string TypeName { get; set; }
 
-		public void Save(BinaryWriter bw)
+		void ICacheable.Save(BinaryWriter bw)
 		{
 			bw.Write(TypeName);
 
@@ -35,13 +37,15 @@ namespace BepInEx.Contract
 			var depList = Dependencies.ToList();
 			bw.Write(depList.Count);
 			foreach (var bepInDependency in depList)
-			{
-				bw.Write(bepInDependency.DependencyGUID);
-				bw.Write((int)bepInDependency.Flags);
-			}
+				((ICacheable)bepInDependency).Save(bw);
+
+			var incList = Incompatibilities.ToList();
+			bw.Write(incList.Count);
+			foreach (var bepInIncompatibility in incList)
+				((ICacheable)bepInIncompatibility).Save(bw);
 		}
 
-		public void Load(BinaryReader br)
+		void ICacheable.Load(BinaryReader br)
 		{
 			TypeName = br.ReadString();
 
@@ -56,8 +60,24 @@ namespace BepInEx.Contract
 			var depCount = br.ReadInt32();
 			var depList = new List<BepInDependency>(depCount);
 			for (int i = 0; i < depCount; i++)
-				depList.Add(new BepInDependency(br.ReadString(), (BepInDependency.DependencyFlags) br.ReadInt32()));
+			{
+				var dep = new BepInDependency("");
+				((ICacheable)dep).Load(br);
+				depList.Add(dep);
+			}
+
 			Dependencies = depList;
+
+			var incCount = br.ReadInt32();
+			var incList = new List<BepInIncompatibility>(incCount);
+			for (int i = 0; i < incCount; i++)
+			{
+				var inc = new BepInIncompatibility("");
+				((ICacheable)inc).Load(br);
+				incList.Add(inc);
+			}
+
+			Incompatibilities = incList;
 		}
 	}
 }