using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using BepInEx.Bootstrap; using Mono.Cecil; using Range = SemanticVersioning.Range; using Version = SemanticVersioning.Version; namespace BepInEx; #region BaseUnityPlugin /// /// This attribute denotes that a class is a plugin, and specifies the required metadata. /// [AttributeUsage(AttributeTargets.Class)] public class BepInPlugin : Attribute { /// The unique identifier of the plugin. Should not change between plugin versions. /// The user friendly name of the plugin. Is able to be changed between versions. /// The specific version of the plugin. public BepInPlugin(string GUID, string Name, string Version) { this.GUID = GUID; this.Name = Name; this.Version = TryParseLongVersion(Version); } /// /// The unique identifier of the plugin. Should not change between plugin versions. /// public string GUID { get; protected set; } /// /// The user friendly name of the plugin. Is able to be changed between versions. /// public string Name { get; protected set; } /// /// The specific version of the plugin. /// public Version Version { get; protected set; } private static Version TryParseLongVersion(string version) { if (Version.TryParse(version, out var v)) return v; // no System.Version.TryParse() on .NET 3.5 try { var longVersion = new System.Version(version); return new Version(longVersion.Major, longVersion.Minor, longVersion.Build != -1 ? longVersion.Build : 0); } catch { } return null; } internal static BepInPlugin FromCecilType(TypeDefinition td) { var attr = MetadataHelper.GetCustomAttributes(td, false).FirstOrDefault(); if (attr == null) return null; return new BepInPlugin((string) attr.ConstructorArguments[0].Value, (string) attr.ConstructorArguments[1].Value, (string) attr.ConstructorArguments[2].Value); } } /// /// This attribute specifies any dependencies that this plugin has on other plugins. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class BepInDependency : Attribute, ICacheable { /// /// Flags that are applied to a dependency /// [Flags] public enum DependencyFlags { /// /// The plugin has a hard dependency on the referenced plugin, and will not run without it. /// HardDependency = 1, /// /// This plugin has a soft dependency on the referenced plugin, and is able to run without it. /// SoftDependency = 2 } /// /// Marks this as dependent on another plugin. The other plugin will be loaded before /// this one. /// If the other plugin doesn't exist, what happens depends on the parameter. /// /// The GUID of the referenced plugin. /// The flags associated with this dependency definition. public BepInDependency(string DependencyGUID, DependencyFlags Flags = DependencyFlags.HardDependency) { this.DependencyGUID = DependencyGUID; this.Flags = Flags; VersionRange = null; } /// /// Marks this as dependent on another plugin. The other plugin will be loaded before /// this one. /// If the other plugin doesn't exist or is of a version not satisfying , this plugin will /// not load and an error will be logged instead. /// /// The GUID of the referenced plugin. /// The version range of the referenced plugin. /// When version is supplied the dependency is always treated as HardDependency public BepInDependency(string guid, string version) : this(guid) { VersionRange = Range.Parse(version); } /// /// The GUID of the referenced plugin. /// public string DependencyGUID { get; protected set; } /// /// The flags associated with this dependency definition. /// public DependencyFlags Flags { get; protected set; } /// /// The version range of the referenced plugin. /// public Range VersionRange { get; protected set; } void ICacheable.Save(BinaryWriter bw) { bw.Write(DependencyGUID); bw.Write((int) Flags); bw.Write(VersionRange?.ToString() ?? string.Empty); } void ICacheable.Load(BinaryReader br) { DependencyGUID = br.ReadString(); Flags = (DependencyFlags) br.ReadInt32(); var versionRange = br.ReadString(); VersionRange = versionRange == string.Empty ? null : Range.Parse(versionRange); } internal static IEnumerable FromCecilType(TypeDefinition td) { var attrs = MetadataHelper.GetCustomAttributes(td, true); 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(); } } /// /// This attribute specifies other plugins that are incompatible with this plugin. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class BepInIncompatibility : Attribute, ICacheable { /// /// Marks this as incompatible with another plugin. /// If the other plugin exists, this plugin will not be loaded and a warning will be shown. /// /// The GUID of the referenced plugin. public BepInIncompatibility(string IncompatibilityGUID) { this.IncompatibilityGUID = IncompatibilityGUID; } /// /// The GUID of the referenced plugin. /// public string IncompatibilityGUID { get; protected set; } void ICacheable.Save(BinaryWriter bw) => bw.Write(IncompatibilityGUID); void ICacheable.Load(BinaryReader br) => IncompatibilityGUID = br.ReadString(); internal static IEnumerable FromCecilType(TypeDefinition td) { var attrs = MetadataHelper.GetCustomAttributes(td, true); return attrs.Select(customAttribute => { var dependencyGuid = (string) customAttribute.ConstructorArguments[0].Value; return new BepInIncompatibility(dependencyGuid); }).ToList(); } } /// /// This attribute specifies which processes this plugin should be run for. Not specifying this attribute will load the /// plugin under every process. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class BepInProcess : Attribute { /// The name of the process that this plugin will run under. public BepInProcess(string ProcessName) { this.ProcessName = ProcessName; } /// /// The name of the process that this plugin will run under. /// public string ProcessName { get; protected set; } internal static List FromCecilType(TypeDefinition td) { var attrs = MetadataHelper.GetCustomAttributes(td, true); return attrs.Select(customAttribute => new BepInProcess((string) customAttribute.ConstructorArguments[0].Value)).ToList(); } } #endregion #region MetadataHelper /// /// Helper class to use for retrieving metadata about a plugin, defined as attributes. /// public static class MetadataHelper { internal static IEnumerable GetCustomAttributes(TypeDefinition td, bool inherit) where T : Attribute { var result = new List(); var type = typeof(T); var currentType = td; do { result.AddRange(currentType.CustomAttributes.Where(ca => ca.AttributeType.FullName == type.FullName)); currentType = currentType.BaseType?.Resolve(); } while (inherit && currentType?.FullName != "System.Object"); return result; } /// /// Retrieves the BepInPlugin metadata from a plugin type. /// /// The plugin type. /// The BepInPlugin metadata of the plugin type. public static BepInPlugin GetMetadata(Type pluginType) { var attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false); if (attributes.Length == 0) return null; return (BepInPlugin) attributes[0]; } /// /// Retrieves the BepInPlugin metadata from a plugin instance. /// /// The plugin instance. /// The BepInPlugin metadata of the plugin instance. public static BepInPlugin GetMetadata(object plugin) => GetMetadata(plugin.GetType()); /// /// Gets the specified attributes of a type, if they exist. /// /// The attribute type to retrieve. /// The plugin type. /// The attributes of the type, if existing. public static T[] GetAttributes(Type pluginType) where T : Attribute => (T[]) pluginType.GetCustomAttributes(typeof(T), true); /// /// Gets the specified attributes of an assembly, if they exist. /// /// The assembly. /// The attribute type to retrieve. /// The attributes of the type, if existing. public static T[] GetAttributes(Assembly assembly) where T : Attribute => (T[]) assembly.GetCustomAttributes(typeof(T), true); /// /// Gets the specified attributes of an instance, if they exist. /// /// The attribute type to retrieve. /// The plugin instance. /// The attributes of the instance, if existing. public static IEnumerable GetAttributes(object plugin) where T : Attribute => GetAttributes(plugin.GetType()); /// /// Gets the specified attributes of a reflection metadata type, if they exist. /// /// The attribute type to retrieve. /// The reflection metadata instance. /// The attributes of the instance, if existing. public static T[] GetAttributes(MemberInfo member) where T : Attribute => (T[]) member.GetCustomAttributes(typeof(T), true); /// /// Retrieves the dependencies of the specified plugin type. /// /// The plugin type. /// A list of all plugin types that the specified plugin type depends upon. public static IEnumerable GetDependencies(Type plugin) => plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast(); } #endregion