Revert "Merge pull request #960 from aldelaro5/metaplugins" (#995)

This reverts commit 2a61b61497, reversing
changes made to 6b38cee127.
This commit is contained in:
aldelaro5 authored and GitHub committed 2024-09-24 20:25:56 +02:00
1 parent d9b95a0ac7
commit 544f909a80
35 files changed
+257 -1199

No files matched your search

+114 -180
View File
@@ -19,23 +19,20 @@ public abstract class BaseChainloader<TPlugin>
private static Regex allowedGuidRegex { get; } = new(@"^[a-zA-Z0-9\._\-]+$");
internal BaseChainloader() { }
/// <summary>
/// Analyzes the given type definition and attempts to convert it to a valid <see cref="PluginInfo" />
/// </summary>
/// <param name="type">Type definition to analyze.</param>
/// <param name="loadContext">The load context of the plugin</param>
/// <param name="assemblyLocation">The filepath of the assembly, to keep as metadata.</param>
/// <returns>If the type represent a valid plugin, returns a <see cref="PluginInfo" /> instance. Otherwise, return null.</returns>
public static PluginInfo ToPluginInfo<T>(TypeDefinition type, IPluginLoadContext loadContext, string assemblyLocation)
public static PluginInfo ToPluginInfo(TypeDefinition type, string assemblyLocation)
{
if (type.IsInterface || type.IsAbstract)
return null;
try
{
if (!type.IsSubtypeOf(typeof(T)))
if (!type.IsSubtypeOf(typeof(TPlugin)))
return null;
}
catch (AssemblyResolutionException)
@@ -44,13 +41,8 @@ public abstract class BaseChainloader<TPlugin>
return null;
}
if (type.IsSubtypeOf(typeof(BasePluginProvider)))
return GetPluginInfo(type, BepInPluginProvider.FromCecilType(type), loadContext, assemblyLocation);
return GetPluginInfo(type, BepInPlugin.FromCecilType(type), loadContext, assemblyLocation);
}
var metadata = BepInPlugin.FromCecilType(type);
private static PluginInfo GetPluginInfo(TypeDefinition type, BepInPlugin metadata, IPluginLoadContext loadContext, string assemblyLocation)
{
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
{
@@ -93,16 +85,15 @@ public abstract class BaseChainloader<TPlugin>
Incompatibilities = incompatibilities,
TypeName = type.FullName,
TargettedBepInExVersion = bepinVersion,
_location = assemblyLocation,
LoadContext = loadContext
Location = assemblyLocation
};
}
protected static bool HasBepinPluginType<T>(AssemblyDefinition ass)
protected static bool HasBepinPlugins(AssemblyDefinition ass)
{
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName))
return false;
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).FullName))
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BepInPlugin).FullName))
return false;
return true;
@@ -125,47 +116,16 @@ public abstract class BaseChainloader<TPlugin>
private bool _initialized;
/// <summary>
/// The current chainloader instance
/// </summary>
public static BaseChainloader<TPlugin> Instance { get; protected set; }
/// <summary>
/// List of all <see cref="PluginInfo" /> instances of all plugins loaded via the chainloader.
/// If a plugin fails to load, it is removed. This list only contains the plugins that were actually
/// loaded and have their dependencies satisfied.
/// List of all <see cref="PluginInfo" /> instances loaded via the chainloader.
/// </summary>
public Dictionary<string, PluginInfo> Plugins { get; } = new();
/// <summary>
/// List of all <see cref="PluginInfo" /> instances of all providers loaded via the chainloader.
/// If a provider fails to load, it is removed. This list only contains the providers that were actually
/// loaded and have their dependencies satisfied.
/// </summary>
public Dictionary<string, PluginInfo> Providers { get; } = new();
/// <summary>
/// List of all <see cref="IPluginLoadContext" /> objects received from plugin providers.
/// Value holds info of the originating provider. These contexts are used by chainloader to
/// load plugins. Plugins that are loaded successfully are added to <see cref="Plugins" />.
/// </summary>
internal Dictionary<CachedPluginLoadContext, PluginInfo> LoadContexts { get; } = new();
/// <summary>
/// Collection of error chainloader messages that occured during plugin loading.
/// Contains information about what certain plugins were not loaded.
/// </summary>
public List<string> DependencyErrors { get; } = new();
/// <summary>
/// Occurs after a plugin provider is loaded.
/// </summary>
public event Action<PluginInfo> ProviderLoaded;
/// <summary>
/// Occurs after all plugins providers are loaded.
/// </summary>
public event Action FinishedProviders;
/// <summary>
/// Occurs after a plugin is loaded.
/// </summary>
@@ -189,12 +149,12 @@ public abstract class BaseChainloader<TPlugin>
InitializeLoggers();
if (!Directory.Exists(Paths.PluginProviderPath))
Directory.CreateDirectory(Paths.PluginProviderPath);
if (!Directory.Exists(Paths.PluginPath))
Directory.CreateDirectory(Paths.PluginPath);
if (!Directory.Exists(Paths.PatcherPluginPath))
Directory.CreateDirectory(Paths.PatcherPluginPath);
_initialized = true;
Logger.Log(LogLevel.Message, "Chainloader initialized");
@@ -234,20 +194,29 @@ public abstract class BaseChainloader<TPlugin>
/// <param name="path">Path from which to search the plugins.</param>
/// <param name="cacheName">Cache name to use. If null, results are not cached.</param>
/// <returns>List of discovered plugins and their metadata.</returns>
protected List<PluginInfo> DiscoverPluginProvidersFrom(string path, string cacheName = "chainloader_meta")
protected IList<PluginInfo> DiscoverPluginsFrom(string path, string cacheName = "chainloader")
{
var pluginsToLoad =
TypeLoader.FindPluginTypes(path, ToPluginInfo<BasePluginProvider>, HasBepinPluginType<BasePluginProvider>, cacheName);
TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPlugins, cacheName);
return pluginsToLoad.SelectMany(p => p.Value).ToList();
}
/// <summary>
/// Discovers plugins to load.
/// </summary>
/// <returns>List of plugins to be loaded.</returns>
protected virtual IList<PluginInfo> DiscoverPlugins()
{
return DiscoverPluginsFrom(Paths.PluginPath);
}
/// <summary>
/// Preprocess the plugins and modify the load order.
/// </summary>
/// <remarks>Some plugins may be skipped if they cannot be loaded (wrong metadata, etc).</remarks>
/// <param name="plugins">Plugins to process.</param>
/// <returns>List of plugins to load in the correct load order.</returns>
protected virtual List<PluginInfo> ModifyLoadOrder(IList<PluginInfo> plugins, Dictionary<string, PluginInfo> existingPlugins)
protected virtual IList<PluginInfo> ModifyLoadOrder(IList<PluginInfo> plugins)
{
// We use a sorted dictionary to ensure consistent load order
var dependencyDict =
@@ -256,7 +225,7 @@ public abstract class BaseChainloader<TPlugin>
foreach (var pluginInfoGroup in plugins.GroupBy(info => info.Metadata.GUID))
{
if (existingPlugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
if (Plugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
{
Logger.Log(LogLevel.Warning,
$"Skipping [{pluginInfoGroup.Key}] because a plugin with a similar GUID ([{loadedPlugin}]) has been already loaded.");
@@ -297,7 +266,7 @@ public abstract class BaseChainloader<TPlugin>
foreach (var pluginInfo in pluginsByGuid.Values.ToList())
if (pluginInfo.Incompatibilities.Any(incompatibility =>
pluginsByGuid.ContainsKey(incompatibility.IncompatibilityGUID)
|| existingPlugins.ContainsKey(incompatibility.IncompatibilityGUID))
|| Plugins.ContainsKey(incompatibility.IncompatibilityGUID))
)
{
pluginsByGuid.Remove(pluginInfo.Metadata.GUID);
@@ -306,7 +275,7 @@ public abstract class BaseChainloader<TPlugin>
var incompatiblePluginsNew = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID)
.Where(x => pluginsByGuid.ContainsKey(x));
var incompatiblePluginsExisting = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID)
.Where(x => existingPlugins.ContainsKey(x));
.Where(x => Plugins.ContainsKey(x));
var incompatiblePlugins = incompatiblePluginsNew.Concat(incompatiblePluginsExisting).ToArray();
var message =
$@"Could not load [{pluginInfo}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}";
@@ -337,24 +306,15 @@ public abstract class BaseChainloader<TPlugin>
}
/// <summary>
/// Run the chainloader and load all plugins from all providers.
/// Run the chainloader and load all plugins from the plugins folder.
/// </summary>
public virtual void Execute()
{
try
{
var builtinProviders = DiscoverPluginProvidersFrom(Paths.BepInExAssemblyDirectory);
var providers = DiscoverPluginProvidersFrom(Paths.PluginProviderPath);
Logger.Log(LogLevel.Info, $"{providers.Count + builtinProviders.Count} " +
$"plugin provider{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
LoadPlugins(builtinProviders, provider: true);
LoadPlugins(providers, provider: true);
FinishedProviders?.Invoke();
var plugins = TypeLoader.GetPluginsFromLoadContexts(LoadContexts.Keys.Cast<IPluginLoadContext>(), ToPluginInfo<TPlugin>, HasBepinPluginType<BepInPlugin>, "chainloader");
var plugins = DiscoverPlugins();
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
LoadPlugins(plugins, provider: false);
LoadPlugins(plugins);
Finished?.Invoke();
}
catch (Exception ex)
@@ -371,143 +331,117 @@ public abstract class BaseChainloader<TPlugin>
Logger.Log(LogLevel.Message, "Chainloader startup complete");
}
[Obsolete("Use a plugin provider instead", true)]
public IList<PluginInfo> LoadPlugins(params string[] pluginsPaths)
private IList<PluginInfo> LoadPlugins(IList<PluginInfo> plugins)
{
throw new NotSupportedException("Use a plugin provider instead");
}
private void LoadPlugins(List<PluginInfo> plugins, bool provider)
{
var existingPluginInfos = provider ? Providers : Plugins;
var sortedPluginInfos = ModifyLoadOrder(plugins, existingPluginInfos);
var sortedPlugins = ModifyLoadOrder(plugins);
var invalidPlugins = new HashSet<string>();
var processedPlugins = new Dictionary<string, SemanticVersioning.Version>();
var loadedAssemblies = new Dictionary<string, Assembly>();
var loadedPlugins = new List<PluginInfo>();
foreach (var pluginInfo in sortedPluginInfos)
foreach (var plugin in sortedPlugins)
{
if (!CheckDependencies(pluginInfo, processedPlugins, invalidPlugins, existingPluginInfos))
var dependsOnInvalidPlugin = false;
var missingDependencies = new List<BepInDependency>();
foreach (var dependency in plugin.Dependencies)
{
static bool IsHardDependency(BepInDependency dep) =>
(dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0;
// If the dependency wasn't already processed, it's missing altogether
var dependencyExists =
processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
// Alternatively, if the dependency hasn't been loaded before, it's missing too
if (!dependencyExists)
{
dependencyExists = Plugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
pluginVersion = pluginInfo?.Metadata.Version;
}
if (!dependencyExists || dependency.VersionRange != null &&
!dependency.VersionRange.IsSatisfied(pluginVersion))
{
// If the dependency is hard, collect it into a list to show
if (IsHardDependency(dependency))
missingDependencies.Add(dependency);
continue;
}
// If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
{
dependsOnInvalidPlugin = true;
break;
}
}
processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
if (dependsOnInvalidPlugin)
{
var message =
$"Skipping [{plugin}] because it has a dependency that was not loaded. See previous errors for details.";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Warning, message);
continue;
}
if (missingDependencies.Count != 0)
{
var message = $@"Could not load [{plugin}] because it has missing dependencies: {
string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
}";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Error, message);
invalidPlugins.Add(plugin.Metadata.GUID);
continue;
}
try
{
Logger.Log(LogLevel.Info, $"Loading [{pluginInfo}]");
Logger.Log(LogLevel.Info, $"Loading [{plugin}]");
Assembly ass = null;
if (provider && !loadedAssemblies.TryGetValue(pluginInfo._location, out ass))
{
ass = Assembly.LoadFrom(pluginInfo._location);
loadedAssemblies[pluginInfo._location] = ass;
}
else if (!provider && !loadedAssemblies.TryGetValue(pluginInfo.LoadContext.AssemblyIdentifier, out ass))
{
var symbols = pluginInfo.LoadContext.GetAssemblySymbolsData();
ass = symbols != null ? Assembly.Load(pluginInfo.LoadContext.GetAssemblyData(), symbols)
: Assembly.Load(pluginInfo.LoadContext.GetAssemblyData());
loadedAssemblies[pluginInfo.LoadContext.AssemblyIdentifier] = ass;
}
if (!loadedAssemblies.TryGetValue(plugin.Location, out var ass))
loadedAssemblies[plugin.Location] = ass = Assembly.LoadFrom(plugin.Location);
existingPluginInfos[pluginInfo.Metadata.GUID] = pluginInfo;
TryRunModuleCtor(pluginInfo, ass);
Plugins[plugin.Metadata.GUID] = plugin;
TryRunModuleCtor(plugin, ass);
plugin.Instance = LoadPlugin(plugin, ass);
loadedPlugins.Add(plugin);
if (provider)
{
var type = ass.GetType(pluginInfo.TypeName);
pluginInfo.Instance = Activator.CreateInstance(type);
AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)pluginInfo.Instance).ResolveAssembly(args.Name);
var pluginLoadContexts = ((BasePluginProvider)pluginInfo.Instance).GetPlugins();
foreach (IPluginLoadContext context in pluginLoadContexts)
{
var cachedContext = new CachedPluginLoadContext(context);
LoadContexts[cachedContext] = pluginInfo;
}
ProviderLoaded?.Invoke(pluginInfo);
}
else
{
pluginInfo.Source = LoadContexts[(CachedPluginLoadContext)pluginInfo.LoadContext];
pluginInfo.Instance = LoadPlugin(pluginInfo, ass);
PluginLoaded?.Invoke(pluginInfo);
foreach (CachedPluginLoadContext context in LoadContexts.Keys)
context.Dispose();
}
PluginLoaded?.Invoke(plugin);
}
catch (Exception ex)
{
invalidPlugins.Add(pluginInfo.Metadata.GUID);
existingPluginInfos.Remove(pluginInfo.Metadata.GUID);
invalidPlugins.Add(plugin.Metadata.GUID);
Plugins.Remove(plugin.Metadata.GUID);
Logger.Log(LogLevel.Error,
$"Error loading [{pluginInfo}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
$"Error loading [{plugin}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
}
}
return loadedPlugins;
}
private bool CheckDependencies<T>(T plugin, Dictionary<string, SemanticVersioning.Version> processedPlugins, HashSet<string> invalidPlugins, Dictionary<string, T> existingPlugins)
where T : PluginInfo
/// <summary>
/// Detects and loads all plugins in the specified directories.
/// </summary>
/// <remarks>
/// It is better to collect all paths at once and use a single call to LoadPlugins than multiple calls.
/// This allows to run proper dependency resolving and to load all plugins in one go.
/// </remarks>
/// <param name="pluginsPaths">Directories to search the plugins from.</param>
/// <returns>List of loaded plugin infos.</returns>
public IList<PluginInfo> LoadPlugins(params string[] pluginsPaths)
{
var dependsOnInvalidPlugin = false;
var missingDependencies = new List<BepInDependency>();
foreach (var dependency in plugin.Dependencies)
{
static bool IsHardDependency(BepInDependency dep) =>
(dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0;
// If the dependency wasn't already processed, it's missing altogether
var dependencyExists =
processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
// Alternatively, if the dependency hasn't been loaded before, it's missing too
if (!dependencyExists)
{
dependencyExists = existingPlugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
pluginVersion = pluginInfo?.Metadata.Version;
}
if (!dependencyExists || dependency.VersionRange != null &&
!dependency.VersionRange.IsSatisfied(pluginVersion))
{
// If the dependency is hard, collect it into a list to show
if (IsHardDependency(dependency))
missingDependencies.Add(dependency);
continue;
}
// If the dependency is a hard and is invalid (e.g. has missing dependencies), report that to the user
if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency))
{
dependsOnInvalidPlugin = true;
break;
}
}
processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
if (dependsOnInvalidPlugin)
{
var message =
$"Skipping [{plugin}] because it has a dependency that was not loaded. See previous errors for details.";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Warning, message);
return false;
}
if (missingDependencies.Count != 0)
{
var message = $@"Could not load [{plugin}] because it has missing dependencies: {
string.Join(", ", missingDependencies.Select(s => s.VersionRange == null ? s.DependencyGUID : $"{s.DependencyGUID} ({s.VersionRange})").ToArray())
}";
DependencyErrors.Add(message);
Logger.Log(LogLevel.Error, message);
invalidPlugins.Add(plugin.Metadata.GUID);
return false;
}
return true;
// TODO: This is a temporary solution for 3rd party loaders. Instead, this should be done via metaplugins.
var plugins = new List<PluginInfo>();
foreach (var pluginsPath in pluginsPaths)
plugins.AddRange(DiscoverPluginsFrom(pluginsPath));
return LoadPlugins(plugins);
}
private static void TryRunModuleCtor(PluginInfo plugin, Assembly assembly)
+17 -125
View File
@@ -10,41 +10,6 @@ using Mono.Cecil;
namespace BepInEx.Bootstrap;
internal class CachedPluginLoadContext : IPluginLoadContext, IDisposable
{
public IPluginLoadContext PluginLoadContext { get; }
private byte[] assemblyData;
private byte[] assemblySymbolsData;
public CachedPluginLoadContext(IPluginLoadContext pluginLoadContext)
{
PluginLoadContext = pluginLoadContext;
}
public string AssemblyIdentifier => PluginLoadContext.AssemblyIdentifier;
public string AssemblyHash => PluginLoadContext.AssemblyHash;
public byte[] GetAssemblyData()
{
return assemblyData ??= PluginLoadContext.GetAssemblyData();
}
public byte[] GetAssemblySymbolsData()
{
return assemblySymbolsData ??= PluginLoadContext.GetAssemblySymbolsData();
}
public byte[] GetFile(string relativePath)
{
return PluginLoadContext.GetFile(relativePath);
}
public void Dispose()
{
assemblyData = null;
assemblySymbolsData = null;
}
}
/// <summary>
/// A cacheable metadata item. Can be used with <see cref="TypeLoader.LoadAssemblyCache{T}" /> and
/// <see cref="TypeLoader.SaveAssemblyCache{T}" /> to cache plugin metadata.
@@ -70,11 +35,6 @@ public interface ICacheable
/// <typeparam name="T"></typeparam>
public class CachedAssembly<T> where T : ICacheable
{
/// <summary>
/// The version of the cache which increments on each format changes
/// </summary>
public const int Version = 0;
/// <summary>
/// List of cached items inside the assembly.
/// </summary>
@@ -129,9 +89,7 @@ public static class TypeLoader
{
Paths.BepInExAssemblyDirectory,
Paths.PluginPath,
Paths.PluginProviderPath,
Paths.PatcherPluginPath,
Paths.PatcherProviderPath,
Paths.ManagedPath
}.Concat(SearchDirectories);
@@ -168,8 +126,9 @@ public static class TypeLoader
/// selector.
/// </returns>
public static Dictionary<string, List<T>> FindPluginTypes<T>(string directory,
Func<TypeDefinition, IPluginLoadContext, string, T> typeSelector,
Func<AssemblyDefinition, bool> assemblyFilter = null,
Func<TypeDefinition, string, T> typeSelector,
Func<AssemblyDefinition, bool> assemblyFilter =
null,
string cacheName = null)
where T : ICacheable, new()
{
@@ -194,7 +153,19 @@ public static class TypeLoader
continue;
}
result[dll] = ExamineStream(typeSelector, assemblyFilter, dllMs, null, dll);
using var ass = AssemblyDefinition.ReadAssembly(dllMs, ReaderParameters);
Logger.Log(LogLevel.Debug, $"Examining '{dll}'");
if (!assemblyFilter?.Invoke(ass) ?? false)
{
result[dll] = new List<T>();
continue;
}
var matches = ass.MainModule.Types
.Select(t => typeSelector(t, dll))
.Where(t => t != null).ToList();
result[dll] = matches;
}
catch (BadImageFormatException e)
{
@@ -212,82 +183,6 @@ public static class TypeLoader
return result;
}
/// <summary>
/// Looks up assemblies using the given loaders and locates all types that can be loaded and collects their metadata.
/// </summary>
/// <typeparam name="T">The specific base type to search for.</typeparam>
/// <param name="loadContexts">The load contexts to obtain the assemblies from.</param>
/// <param name="typeSelector">A function to check if a type should be selected and to build the type metadata.</param>
/// <param name="assemblyFilter">A filter function to quickly determine if the assembly can be loaded.</param>
/// <param name="cacheName">The name of the cache to get cached types from.</param>
/// <returns>
/// A dictionary of all assemblies in the directory and the list of type metadatas of types that match the
/// selector.
/// </returns>
public static List<T> GetPluginsFromLoadContexts<T>(IEnumerable<IPluginLoadContext> loadContexts,
Func<TypeDefinition, IPluginLoadContext, string, T> typeSelector,
Func<AssemblyDefinition, bool> assemblyFilter = null,
string cacheName = null)
where T : ICacheable, new()
{
var result = new Dictionary<string, List<T>>();
var hashes = new Dictionary<string, string>();
Dictionary<string, CachedAssembly<T>> cache = null;
if (cacheName != null)
cache = LoadAssemblyCache<T>(cacheName);
foreach (IPluginLoadContext loadContext in loadContexts)
{
IList<T> plugins;
if (cache != null && loadContext.AssemblyHash != null &&
cache.TryGetValue(loadContext.AssemblyIdentifier, out var cacheEntry) &&
loadContext.AssemblyHash == cacheEntry.Hash)
{
plugins = cacheEntry.CacheItems;
}
else
{
var assemblyData = loadContext.GetAssemblyData();
using var memory = new MemoryStream(assemblyData);
plugins = ExamineStream(typeSelector, assemblyFilter, memory, loadContext, null);
}
foreach (T pluginInfo in plugins)
{
if (!result.ContainsKey(loadContext.AssemblyIdentifier))
result[loadContext.AssemblyIdentifier] = new();
result[loadContext.AssemblyIdentifier].Add(pluginInfo);
}
}
if (cache != null)
SaveAssemblyCache(cacheName, result, hashes);
return result.SelectMany(x => x.Value).ToList();
}
private static List<T> ExamineStream<T>(Func<TypeDefinition, IPluginLoadContext, string, T> typeSelector,
Func<AssemblyDefinition, bool> assemblyFilter,
MemoryStream dllMs,
IPluginLoadContext loadContext,
string location)
where T : ICacheable, new()
{
using var ass = AssemblyDefinition.ReadAssembly(dllMs, ReaderParameters);
Logger.Log(LogLevel.Debug, $"Examining '{ass.Name}'");
if (!assemblyFilter?.Invoke(ass) ?? false)
{
return new List<T>();
}
var matches = ass.MainModule.Types
.Select(t => typeSelector(t, loadContext, location))
.Where(t => t != null).ToList();
return matches;
}
/// <summary>
/// Loads an index of type metadatas from a cache.
/// </summary>
@@ -310,9 +205,7 @@ public static class TypeLoader
if (!File.Exists(path))
return null;
using var br = new BinaryReader(File.OpenRead(path));
var version = br.ReadInt32();
if (version == CachedAssembly<T>.Version)
using (var br = new BinaryReader(File.OpenRead(path)))
{
var entriesCount = br.ReadInt32();
@@ -366,7 +259,6 @@ public static class TypeLoader
var path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
using var bw = new BinaryWriter(File.OpenWrite(path));
bw.Write(CachedAssembly<T>.Version);
bw.Write(entries.Count);
foreach (var kv in entries)
-48
View File
@@ -76,32 +76,6 @@ public class BepInPlugin : Attribute
}
}
/// <summary>
/// This attribute denotes that a class is a plugin provider, and specifies the required metadata.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class BepInPluginProvider : BepInPlugin
{
/// <param name="GUID">The unique identifier of the plugin. Should not change between plugin versions.</param>
/// <param name="Name">The user friendly name of the plugin. Is able to be changed between versions.</param>
/// <param name="Version">The specific version of the plugin.</param>
public BepInPluginProvider(string GUID, string Name, string Version) : base(GUID, Name, Version)
{
}
internal new static BepInPluginProvider FromCecilType(TypeDefinition td)
{
var attr = MetadataHelper.GetCustomAttributes<BepInPluginProvider>(td, false).FirstOrDefault();
if (attr == null)
return null;
return new BepInPluginProvider((string) attr.ConstructorArguments[0].Value,
(string) attr.ConstructorArguments[1].Value,
(string) attr.ConstructorArguments[2].Value);
}
}
/// <summary>
/// This attribute specifies any dependencies that this plugin has on other plugins.
/// </summary>
@@ -307,28 +281,6 @@ public static class MetadataHelper
/// <returns>The BepInPlugin metadata of the plugin instance.</returns>
public static BepInPlugin GetMetadata(object plugin) => GetMetadata(plugin.GetType());
/// <summary>
/// Retrieves the BepInPluginProvider metadata from a plugin type.
/// </summary>
/// <param name="pluginType">The plugin type.</param>
/// <returns>The BepInPluginProvider metadata of the plugin type.</returns>
public static BepInPlugin GetPluginProviderMetadata(Type pluginType)
{
var attributes = pluginType.GetCustomAttributes(typeof(BepInPluginProvider), false);
if (attributes.Length == 0)
return null;
return (BepInPlugin) attributes[0];
}
/// <summary>
/// Retrieves the BepInPluginProvider metadata from a plugin instance.
/// </summary>
/// <param name="plugin">The plugin instance.</param>
/// <returns>The BepInPluginProvider metadata of the plugin instance.</returns>
public static BepInPlugin GetPluginProviderMetadata(object plugin) => GetPluginProviderMetadata(plugin.GetType());
/// <summary>
/// Gets the specified attributes of a type, if they exist.
/// </summary>
@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using BepInEx.Configuration;
using BepInEx.Logging;
namespace BepInEx;
/// <summary>
/// The base plugin provider type that is used by the BepInEx plugin loader.
/// </summary>
public abstract class BasePluginProvider
{
/// <summary>
/// Create a new instance of a plugin provider and all of its tied in objects.
/// </summary>
/// <exception cref="InvalidOperationException">BepInPluginProvider attribute is missing.</exception>
protected BasePluginProvider()
{
var metadata = MetadataHelper.GetPluginProviderMetadata(this);
if (metadata == null)
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BasePluginProvider and the BepInPluginProvider attribute is missing.");
Info = new PluginInfo
{
Metadata = metadata,
Instance = this,
Dependencies = MetadataHelper.GetDependencies(GetType()),
Processes = MetadataHelper.GetAttributes<BepInProcess>(GetType()),
};
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
/// <summary>
/// Information about this plugin provider as it was loaded.
/// </summary>
public PluginInfo Info { get; }
/// <summary>
/// Logger instance tied to this plugin provider.
/// </summary>
protected ManualLogSource Logger { get; }
/// <summary>
/// Default config file tied to this plugin provider. The config file will not be created until
/// any settings are added and changed, or <see cref="ConfigFile.Save" /> is called.
/// </summary>
public ConfigFile Config { get; }
/// <summary>
/// Obtains a list of assemblies containing plugins to load
/// </summary>
/// <returns>A list of load context, one per assembly</returns>
public abstract IList<IPluginLoadContext> GetPlugins();
/// <summary>
/// A custom assembly resolver that can be used by this provider to resolve assemblies
/// that have failed to resolve
/// </summary>
/// <param name="name">The assembly's name</param>
/// <returns>The resolved assembly or null if the assembly couldn't be resolved</returns>
public virtual Assembly ResolveAssembly(string name)
{
return null;
}
}
+16 -16
View File
@@ -7,22 +7,22 @@ using BepInEx.Logging;
namespace BepInEx.Contract
{
public interface IPlugin
{
/// <summary>
/// Information about this plugin as it was loaded.
/// </summary>
PluginInfo Info { get; }
public interface IPlugin
{
/// <summary>
/// Information about this plugin as it was loaded.
/// </summary>
PluginInfo Info { get; }
/// <summary>
/// Logger instance tied to this plugin.
/// </summary>
ManualLogSource Logger { get; }
/// <summary>
/// Logger instance tied to this plugin.
/// </summary>
ManualLogSource Logger { get; }
/// <summary>
/// Default config file tied to this plugin. The config file will not be created until
/// any settings are added and changed, or <see cref="ConfigFile.Save"/> is called.
/// </summary>
ConfigFile Config { get; }
}
/// <summary>
/// Default config file tied to this plugin. The config file will not be created until
/// any settings are added and changed, or <see cref="ConfigFile.Save"/> is called.
/// </summary>
ConfigFile Config { get; }
}
}
@@ -1,41 +0,0 @@
using System;
namespace BepInEx;
/// <summary>
/// An interface that allows to dynamically load and track an assembly
/// which can be obtained from a provider
/// </summary>
public interface IPluginLoadContext
{
/// <summary>
/// An identifier that uniquely identifies an assembly from a provider no matter its revision
/// </summary>
public string AssemblyIdentifier { get; }
/// <summary>
/// An optional hash that changes each time the assembly changes which can be tracked for cache
/// invalidation purposes. If this is null, no caching occurs for this assembly load
/// </summary>
public string AssemblyHash { get; }
/// <summary>
/// Obtains the assembly's raw data without loading it into the appdomain.
/// This may be called multiple times by the chainloader
/// </summary>
/// <returns>The assembly's raw data in bytes</returns>
public byte[] GetAssemblyData();
/// <summary>
/// Obtains the assembly's symbols data which will be loaded by the chainloader alongside the assembly
/// </summary>
/// <returns>The assembly's symbols data in bytes (either in portable pdb or mdb format). Null if no symbols exists for this assembly</returns>
public byte[] GetAssemblySymbolsData();
/// <summary>
/// Obtains a file's raw data using a relative path from the plugin assembly
/// </summary>
/// <param name="relativePath">The relative path from the plugin assembly used to locate the file</param>
/// <returns>The file's raw data if resolved successfully, null if the file couldn't be loaded</returns>
public byte[] GetFile(string relativePath);
}
+13 -22
View File
@@ -16,17 +16,6 @@ public class PluginInfo : ICacheable
/// General metadata about a plugin.
/// </summary>
public BepInPlugin Metadata { get; internal set; }
/// <summary>
/// The <see cref="PluginInfo"/> of the plugin provider that loaded this plugin.
/// This is null if this plugin is a provider
/// </summary>
public PluginInfo Source { get; internal set; }
/// <summary>
/// The load context used to load this plugin, null if it is a provider
/// </summary>
public IPluginLoadContext LoadContext { get; internal set; }
/// <summary>
/// Collection of <see cref="BepInProcess" /> attributes that describe what processes the plugin can run on.
@@ -44,26 +33,24 @@ public class PluginInfo : ICacheable
/// </summary>
public IEnumerable<BepInIncompatibility> Incompatibilities { get; internal set; }
public string TypeName { get; internal set; }
/// <summary>
/// File path to the plugin DLL
/// </summary>
public string Location { get; internal set; }
/// <summary>
/// Instance of the plugin that represents this info. NULL if no plugin is instantiated from info (yet)
/// </summary>
public object Instance { get; internal set; }
public string TypeName { get; internal set; }
internal Version TargettedBepInExVersion { get; set; }
[Obsolete("Use LoadContext.AssemblyIdentifier instead", true)]
public string Location => LoadContext?.AssemblyIdentifier;
internal string _location { get; set; }
/// <inheritdoc />
public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
void ICacheable.Save(BinaryWriter bw)
{
bw.Write(TypeName);
bw.Write(Location);
bw.Write(Metadata.GUID);
bw.Write(Metadata.Name);
@@ -90,6 +77,7 @@ public class PluginInfo : ICacheable
void ICacheable.Load(BinaryReader br)
{
TypeName = br.ReadString();
Location = br.ReadString();
Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString());
@@ -123,4 +111,7 @@ public class PluginInfo : ICacheable
TargettedBepInExVersion = new Version(br.ReadString());
}
/// <inheritdoc />
public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
}
-12
View File
@@ -75,16 +75,6 @@ public static class Paths
/// </summary>
public static string PatcherPluginPath { get; private set; }
/// <summary>
/// The path to the plugin provider folder which resides in the BepInEx folder.
/// </summary>
public static string PluginProviderPath { get; private set; }
/// <summary>
/// The path to the patcher plugin provider folder which resides in the BepInEx folder.
/// </summary>
public static string PatcherProviderPath { get; private set; }
/// <summary>
/// The path to the plugin folder which resides in the BepInEx folder.
/// <para>
@@ -137,9 +127,7 @@ public static class Paths
BepInExRootPath = bepinRootPath ?? Path.Combine(GameRootPath, "BepInEx");
ConfigPath = Path.Combine(BepInExRootPath, "config");
BepInExConfigPath = Path.Combine(ConfigPath, "BepInEx.cfg");
PluginProviderPath = Path.Combine(BepInExRootPath, "plugins-providers");
PluginPath = Path.Combine(BepInExRootPath, "plugins");
PatcherProviderPath = Path.Combine(BepInExRootPath, "patchers-providers");
PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers");
BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core");
BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory,
-1
View File
@@ -2,7 +2,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("BepInEx.Preloader.Core")]
[assembly: InternalsVisibleTo("BepInEx.Unity.Mono")]
[assembly: InternalsVisibleTo("BepInEx.NET.Common")]
[assembly: InternalsVisibleTo("BepInEx.NET.Framework.Launcher")]
[assembly: InternalsVisibleTo("BepInEx.NET.CoreCLR")]
[assembly: InternalsVisibleTo("BepInEx.Unity.IL2CPP")]
-57
View File
@@ -221,53 +221,6 @@ public static class Utility
return false;
}
/// <summary>
/// Try to resolve the symbols data from an assembly DLL.
/// </summary>
/// <param name="dllPath">File path of the DLL assembly.</param>
/// <param name="symbolsData">The symbols of the DLL assembly.</param>
/// <returns>True, if the symbols were found. Otherwise, false.</returns>
public static bool TryResolveAssemblySymbols(string dllPath, out byte[] symbolsData)
{
var subDirectory = Path.GetDirectoryName(dllPath);
var potentialSymbolsFileNames = new[]
{
$"{dllPath}.mdb",
Path.ChangeExtension(dllPath, ".pdb")
};
foreach (var symbolFileName in potentialSymbolsFileNames)
{
var symbolPath = Path.Combine(subDirectory, symbolFileName);
if (!File.Exists(symbolPath))
continue;
try
{
symbolsData = File.ReadAllBytes(symbolPath);
}
catch (Exception)
{
continue;
}
return true;
}
symbolsData = null;
return false;
}
private static Assembly LoadAssemblyWithSymbols(string assemblyFilePath)
{
var assemblyData = File.ReadAllBytes(assemblyFilePath);
return TryResolveAssemblySymbols(assemblyFilePath, out byte[] symbolsData)
? Assembly.Load(assemblyData, symbolsData)
: Assembly.Load(assemblyData);
}
/// <summary>
/// Checks whether a given cecil type definition is a subtype of a provided type.
/// </summary>
@@ -306,16 +259,6 @@ public static class Utility
TryResolveDllAssembly(assemblyName, directory,
s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly);
/// <summary>
/// Try to resolve and load the given assembly DLL including their symbols file.
/// </summary>
/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
/// <param name="directory">Directory to search the assembly from.</param>
/// <param name="assembly">The loaded assembly.</param>
/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
public static bool TryResolveDllAssemblyWithSymbols(AssemblyName assemblyName, string directory, out Assembly assembly) =>
TryResolveDllAssembly(assemblyName, directory, LoadAssemblyWithSymbols, out assembly);
/// <summary>
/// Tries to create a file with the given name
/// </summary>
@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net35;netstandard2.0</TargetFrameworks>
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
<Description>BepInEx patcher provider module</Description>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
</ItemGroup>
</Project>
@@ -1,32 +0,0 @@
using System;
using System.IO;
namespace BepInEx.PatcherProvider;
internal class BepInExPatcherLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
public byte[] GetAssemblyData()
{
return File.ReadAllBytes(AssemblyIdentifier);
}
public byte[] GetAssemblySymbolsData()
{
if (Utility.TryResolveAssemblySymbols(AssemblyIdentifier, out var assemblySymbolsData))
return assemblySymbolsData;
return assemblySymbolsData;
}
public byte[] GetFile(string relativePath)
{
if (relativePath == null)
throw new ArgumentNullException(nameof(relativePath));
string assemblyFolder = Path.GetDirectoryName(AssemblyIdentifier);
string filePath = Path.Combine(assemblyFolder, relativePath);
return File.ReadAllBytes(filePath);
}
}
@@ -1,70 +1 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Logging;
using BepInEx.Preloader.Core.Patching;
namespace BepInEx.PatcherProvider;
[PatcherProviderPluginInfo("BepInExPatcherProvider", "BepInExPatcherProvider", "1.0")]
internal class BepInExPatcherProvider : BasePatcherProvider
{
private static readonly Dictionary<string, string> AssemblyLocationsByFilename = new();
public override IList<IPluginLoadContext> GetPatchers()
{
var loadContexts = new List<IPluginLoadContext>();
foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PatcherPluginPath), "*.dll", SearchOption.AllDirectories))
{
try
{
var filename = Path.GetFileNameWithoutExtension(dll);
var foundDirectory = Path.GetDirectoryName(dll);
// Prioritize the shallowest path of each assembly name
if (AssemblyLocationsByFilename.TryGetValue(filename, out var existingDirectory))
{
int levelExistingDirectory = existingDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
int levelFoundDirectory = foundDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
bool shallowerPathFound = levelExistingDirectory > levelFoundDirectory;
Log.LogWarning($"Found duplicate assemblies filenames: {filename} was found at {foundDirectory} " +
$"while it exists already at {AssemblyLocationsByFilename[filename]}. " +
$"Only the {(shallowerPathFound ? "first" : "second")} will be examined and resolved");
if (levelExistingDirectory > levelFoundDirectory)
AssemblyLocationsByFilename[filename] = foundDirectory;
}
else
{
AssemblyLocationsByFilename.Add(filename, foundDirectory);
}
loadContexts.Add(new BepInExPatcherLoadContext
{
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
AssemblyIdentifier = dll
});
}
catch (Exception e)
{
Log.Log(LogLevel.Error, e);
}
}
return loadContexts;
}
public override Assembly ResolveAssembly(string name)
{
if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
return null;
if (!Utility.TryResolveDllAssemblyWithSymbols(new(name), location, out var ass))
return null;
return ass;
}
}

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net35;netstandard2.0</TargetFrameworks>
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
<Description>BepInEx plugin provider module</Description>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
</ItemGroup>
</Project>
@@ -1,32 +0,0 @@
using System;
using System.IO;
namespace BepInEx.PluginProvider;
internal class BepInExPluginLoadContext : IPluginLoadContext
{
public string AssemblyIdentifier { get; internal set; }
public string AssemblyHash { get; internal set; }
public byte[] GetAssemblyData()
{
return File.ReadAllBytes(AssemblyIdentifier);
}
public byte[] GetAssemblySymbolsData()
{
if (Utility.TryResolveAssemblySymbols(AssemblyIdentifier, out var assemblySymbolsData))
return assemblySymbolsData;
return assemblySymbolsData;
}
public byte[] GetFile(string relativePath)
{
if (relativePath == null)
throw new ArgumentNullException(nameof(relativePath));
string assemblyFolder = Path.GetDirectoryName(AssemblyIdentifier);
string filePath = Path.Combine(assemblyFolder, relativePath);
return File.ReadAllBytes(filePath);
}
}
@@ -1,69 +1 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using BepInEx.Logging;
namespace BepInEx.PluginProvider;
[BepInPluginProvider("BepInExPluginProvider", "BepInExPluginProvider", "1.0")]
internal class BepInExPluginProvider : BasePluginProvider
{
private static readonly Dictionary<string, string> AssemblyLocationsByFilename = new();
public override IList<IPluginLoadContext> GetPlugins()
{
var loadContexts = new List<IPluginLoadContext>();
foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PluginPath), "*.dll", SearchOption.AllDirectories))
{
try
{
var filename = Path.GetFileNameWithoutExtension(dll);
var foundDirectory = Path.GetDirectoryName(dll);
// Prioritize the shallowest path of each assembly name
if (AssemblyLocationsByFilename.TryGetValue(filename, out var existingDirectory))
{
int levelExistingDirectory = existingDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
int levelFoundDirectory = foundDirectory?.Count(x => x == Path.DirectorySeparatorChar) ?? 0;
bool shallowerPathFound = levelExistingDirectory > levelFoundDirectory;
Logger.LogWarning($"Found duplicate assemblies filenames: {filename} was found at {foundDirectory} " +
$"while it exists already at {AssemblyLocationsByFilename[filename]}. " +
$"Only the {(shallowerPathFound ? "first" : "second")} will be examined and resolved");
if (levelExistingDirectory > levelFoundDirectory)
AssemblyLocationsByFilename[filename] = foundDirectory;
}
else
{
AssemblyLocationsByFilename.Add(filename, foundDirectory);
}
loadContexts.Add(new BepInExPluginLoadContext
{
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
AssemblyIdentifier = dll
});
}
catch (Exception e)
{
Logger.Log(LogLevel.Error, e);
}
}
return loadContexts;
}
public override Assembly ResolveAssembly(string name)
{
if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
return null;
if (!Utility.TryResolveDllAssemblyWithSymbols(new(name), location, out var ass))
return null;
return ass;
}
}

@@ -11,7 +11,6 @@ using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
using MonoMod.Utils;
namespace BepInEx.Preloader.Core.Patching;
@@ -25,18 +24,9 @@ public class AssemblyPatcher : IDisposable
private Func<byte[], string, Assembly> assemblyLoader;
private readonly List<CachedPluginLoadContext> loadContexts = new();
private readonly List<BasePatcherProvider> providers = new();
public AssemblyPatcher(Func<byte[], string, Assembly> assemblyLoader)
{
this.assemblyLoader = assemblyLoader;
if (!Directory.Exists(Paths.PatcherProviderPath))
Directory.CreateDirectory(Paths.PatcherProviderPath);
if (!Directory.Exists(Paths.PatcherPluginPath))
Directory.CreateDirectory(Paths.PatcherPluginPath);
}
/// <summary>
@@ -71,18 +61,16 @@ public class AssemblyPatcher : IDisposable
// Clear to allow GC collection.
PatcherContext.PatcherPlugins.Clear();
AppDomain.CurrentDomain.AssemblyResolve -= PatcherProvidersAssemblyResolver;
}
private PatcherPluginMetadata ToPatcherPlugin<T>(TypeDefinition type, IPluginLoadContext loadContext, string assemblyPath)
private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, string assemblyPath)
{
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
return null;
try
{
if (!type.IsSubtypeOf(typeof(T)))
if (!type.IsSubtypeOf(typeof(BasePatcher)))
return null;
}
catch (AssemblyResolutionException)
@@ -91,13 +79,8 @@ public class AssemblyPatcher : IDisposable
return null;
}
if (type.IsSubtypeOf(typeof(BasePatcherProvider)))
return GetPatcherInfo(type, loadContext, PatcherProviderPluginInfoAttribute.FromCecilType(type));
return GetPatcherInfo(type, loadContext, PatcherPluginInfoAttribute.FromCecilType(type));
}
var metadata = PatcherPluginInfoAttribute.FromCecilType(type);
private PatcherPluginMetadata GetPatcherInfo(TypeDefinition type, IPluginLoadContext loadContext, PatcherPluginInfoAttribute metadata)
{
// Perform checks that will prevent the plugin from being loaded in ALL cases
if (metadata == null)
{
@@ -126,176 +109,20 @@ public class AssemblyPatcher : IDisposable
return new PatcherPluginMetadata
{
TypeName = type.FullName,
LoadContext = loadContext
TypeName = type.FullName
};
}
private bool HasPatcherType<T>(AssemblyDefinition ass)
private bool HasPatcherPlugins(AssemblyDefinition ass)
{
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName) &&
ass.Name.Name != CurrentAssemblyName)
return false;
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).FullName))
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BasePatcher).FullName))
return false;
return true;
}
/// <summary>
/// Adds all patchers from all managed assemblies obtained from all patcher providers.
/// </summary>
public void AddPatchersFromProviders()
{
var allProviders = TypeLoader.FindPluginTypes(Paths.BepInExAssemblyDirectory, ToPatcherPlugin<BasePatcherProvider>, HasPatcherType<BasePatcherProvider>);
var foundProviders = TypeLoader.FindPluginTypes(Paths.PatcherProviderPath, ToPatcherPlugin<BasePatcherProvider>, HasPatcherType<BasePatcherProvider>);
allProviders.AddRange(foundProviders);
AppDomain.CurrentDomain.AssemblyResolve += PatcherProvidersAssemblyResolver;
foreach (var keyValuePair in allProviders)
{
var assemblyPath = keyValuePair.Key;
var patcherCollection = keyValuePair.Value;
if (patcherCollection.Count == 0)
continue;
var ass = Assembly.LoadFrom(assemblyPath);
foreach (var patcherPlugin in patcherCollection)
{
try
{
var type = ass.GetType(patcherPlugin.TypeName);
var instance = (BasePatcherProvider) Activator.CreateInstance(type);
providers.Add(instance);
loadContexts.AddRange(instance.GetPatchers().Select(x => new CachedPluginLoadContext(x)));
}
catch (Exception e)
{
Logger.Log(LogLevel.Error,
$"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
}
}
}
var patchers = TypeLoader.GetPluginsFromLoadContexts(loadContexts.Cast<IPluginLoadContext>(), ToPatcherPlugin<BasePatcher>, HasPatcherType<BasePatcher>);
var sortedPatchers = new List<PatchDefinition>();
// TODO: Add dependency ordering and process attribute filtering
foreach (var patcherPlugin in patchers)
{
var ass = Assembly.Load(patcherPlugin.LoadContext.GetAssemblyData());
var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
sortedPatchers.AddRange(patchDefinitions);
var assName = ass.GetName();
Logger.Log(LogLevel.Debug, $"Loaded patcher {patcherPlugin.TypeName} from [{assName.Name} {assName.Version}]");
}
PatcherContext.PatchDefinitions.AddRange(sortedPatchers);
Logger.Log(LogLevel.Info, $"Loaded {sortedPatchers.Count} patcher{(sortedPatchers.Count > 0 ? "s" : "")}");
foreach (CachedPluginLoadContext loadContext in loadContexts)
{
loadContext.Dispose();
}
}
private Assembly PatcherProvidersAssemblyResolver(object sender, ResolveEventArgs args)
{
BasePatcherProvider currentProvider = null;
try
{
foreach (BasePatcherProvider provider in providers)
{
currentProvider = provider;
var ass = provider.ResolveAssembly(args.Name);
if (ass != null)
return ass;
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error, $"The assembly resolver of the patcher provider [{currentProvider?.Info.GUID}] " +
$"threw an exception while resolving {args.Name}: {e}");
return null;
}
return null;
}
private List<PatchDefinition> LoadPatcherPlugin(Assembly ass, PatcherPluginMetadata patcher)
{
var patchDefinitions = new List<PatchDefinition>();
try
{
var type = ass.GetType(patcher.TypeName);
var instance = (BasePatcher) Activator.CreateInstance(type);
instance.Context = PatcherContext;
PatcherContext.PatcherPlugins.Add(instance);
var methods =
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
var targetAssemblies = MetadataHelper.GetAttributes<TargetAssemblyAttribute>(method);
var targetTypes = MetadataHelper.GetAttributes<TargetTypeAttribute>(method);
if (targetAssemblies.Length == 0 && targetTypes.Length == 0)
continue;
var parameters = method.GetParameters();
if (parameters.Length < 1 || parameters.Length > 2
// Next few lines ensure that the first parameter is AssemblyDefinition and does not have any
// target type attributes, and vice versa
|| !(
parameters[0].ParameterType == typeof(AssemblyDefinition)
|| parameters[0].ParameterType ==
typeof(AssemblyDefinition).MakeByRefType()
&& targetTypes.Length == 0
|| parameters[0].ParameterType == typeof(TypeDefinition)
&& targetAssemblies.Length == 0
)
|| parameters.Length == 2 &&
parameters[1].ParameterType != typeof(string)
|| method.ReturnType != typeof(void) &&
method.ReturnType != typeof(bool)
)
{
Logger
.Log(LogLevel.Warning,
$"Skipping method [{method.FullDescription()}] as it is not a valid patcher method");
continue;
}
void AddDefinition(PatchDefinition definition)
{
Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
patchDefinitions.Add(definition);
}
foreach (var targetAssembly in targetAssemblies)
AddDefinition(new PatchDefinition(targetAssembly, instance, method));
foreach (var targetType in targetTypes)
AddDefinition(new PatchDefinition(targetType, instance, method));
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error,
$"Failed to load patchers from type [{patcher.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
}
return patchDefinitions;
}
/// <summary>
/// Adds all patchers from all managed assemblies specified in a directory.
@@ -308,7 +135,7 @@ public class AssemblyPatcher : IDisposable
var sortedPatchers = new List<PatchDefinition>();
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin<BasePatcher>, HasPatcherType<BasePatcher>);
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherPlugins);
// TODO: Add dependency ordering and process attribute filtering
@@ -323,10 +150,68 @@ public class AssemblyPatcher : IDisposable
var ass = Assembly.LoadFrom(assemblyPath);
foreach (var patcherPlugin in patcherCollection)
{
var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
sortedPatchers.AddRange(patchDefinitions);
}
try
{
var type = ass.GetType(patcherPlugin.TypeName);
var instance = (BasePatcher) Activator.CreateInstance(type);
instance.Context = PatcherContext;
PatcherContext.PatcherPlugins.Add(instance);
var methods =
type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
var targetAssemblies = MetadataHelper.GetAttributes<TargetAssemblyAttribute>(method);
var targetTypes = MetadataHelper.GetAttributes<TargetTypeAttribute>(method);
if (targetAssemblies.Length == 0 && targetTypes.Length == 0)
continue;
var parameters = method.GetParameters();
if (parameters.Length < 1 || parameters.Length > 2
// Next few lines ensure that the first parameter is AssemblyDefinition and does not have any
// target type attributes, and vice versa
|| !(
parameters[0].ParameterType == typeof(AssemblyDefinition)
|| parameters[0].ParameterType ==
typeof(AssemblyDefinition).MakeByRefType()
&& targetTypes.Length == 0
|| parameters[0].ParameterType == typeof(TypeDefinition)
&& targetAssemblies.Length == 0
)
|| parameters.Length == 2 &&
parameters[1].ParameterType != typeof(string)
|| method.ReturnType != typeof(void) &&
method.ReturnType != typeof(bool)
)
{
Logger
.Log(LogLevel.Warning,
$"Skipping method [{method.FullDescription()}] as it is not a valid patcher method");
continue;
}
void AddDefinition(PatchDefinition definition)
{
Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
sortedPatchers.Add(definition);
}
foreach (var targetAssembly in targetAssemblies)
AddDefinition(new PatchDefinition(targetAssembly, instance, method));
foreach (var targetType in targetTypes)
AddDefinition(new PatchDefinition(targetType, instance, method));
}
}
catch (Exception e)
{
Logger.Log(LogLevel.Error,
$"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
}
var assName = ass.GetName();
Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
@@ -79,31 +79,6 @@ public class PatcherPluginInfoAttribute : Attribute
}
}
/// <summary>
/// This attribute denotes that a class is a patcher plugin provider, and specifies the required metadata.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PatcherProviderPluginInfoAttribute : PatcherPluginInfoAttribute {
/// <param name="GUID">The unique identifier of the plugin. Should not change between plugin versions.</param>
/// <param name="Name">The user friendly name of the plugin. Is able to be changed between versions.</param>
/// <param name="Version">The specific version of the plugin.</param>
public PatcherProviderPluginInfoAttribute(string GUID, string Name, string Version) : base(GUID, Name, Version)
{
}
internal new static PatcherProviderPluginInfoAttribute FromCecilType(TypeDefinition td)
{
var attr = MetadataHelper.GetCustomAttributes<PatcherProviderPluginInfoAttribute>(td, false).FirstOrDefault();
if (attr == null)
return null;
return new PatcherProviderPluginInfoAttribute((string) attr.ConstructorArguments[0].Value,
(string) attr.ConstructorArguments[1].Value,
(string) attr.ConstructorArguments[2].Value);
}
}
/// <summary>
/// Defines an assembly that a patch method will target.
/// </summary>
@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Reflection;
using BepInEx.Configuration;
using BepInEx.Logging;
namespace BepInEx.Preloader.Core.Patching;
/// <summary>
/// A patcher provider that can discover and load assemblies containing patcher plugins
/// </summary>
public abstract class BasePatcherProvider
{
protected BasePatcherProvider()
{
Info = PatcherPluginInfoAttribute.FromType(GetType());
Log = Logger.CreateLogSource(Info.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, Info.GUID + ".cfg"), false,
new BepInPlugin(Info.GUID, Info.Name, Info.Version.ToString()));
}
/// <summary>
/// A <see cref="ILogSource" /> instance created for use by this patcher plugin provider.
/// </summary>
public ManualLogSource Log { get; }
/// <summary>
/// A configuration file binding created with the <see cref="PatcherPluginInfoAttribute.GUID" /> of this provider as the
/// filename.
/// </summary>
public ConfigFile Config { get; }
/// <summary>
/// Metadata associated with this patcher plugin provider.
/// </summary>
public PatcherPluginInfoAttribute Info { get; }
/// <summary>
/// Obtains a list of assemblies containing patchers to load
/// </summary>
/// <returns>A list of load context, one per assembly</returns>
public abstract IList<IPluginLoadContext> GetPatchers();
/// <summary>
/// A custom assembly resolver that can be used by this provider to resolve assemblies
/// that have failed to resolve
/// </summary>
/// <param name="name">The assembly's name</param>
/// <returns>The resolved assembly or null if the assembly couldn't be resolved</returns>
public virtual Assembly ResolveAssembly(string name)
{
return null;
}
}
@@ -13,11 +13,6 @@ internal class PatcherPluginMetadata : ICacheable
/// </summary>
public string TypeName { get; set; } = string.Empty;
/// <summary>
/// The load context used to load this patcher, null if it is a provider
/// </summary>
public IPluginLoadContext LoadContext { get; set; }
/// <inheritdoc />
public void Save(BinaryWriter bw) => bw.Write(TypeName);
-20
View File
@@ -51,10 +51,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Doorstop", "Doorstop", "{CE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "build\Build.csproj", "{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.PluginProvider", "BepInEx.PluginProvider\BepInEx.PluginProvider.csproj", "{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.PatcherProvider", "BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj", "{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
BepInEx.NetLauncher.Shared\BepInEx.NetLauncher.Shared.projitems*{067bcc8c-fc25-4929-b4f4-c6a5a7496994}*SharedItemsImports = 13
@@ -151,22 +147,6 @@ Global
{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}.Release_Unity|Any CPU.ActiveCfg = Debug|Any CPU
{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}.Release_Unity|Any CPU.Build.0 = Debug|Any CPU
{0C38DDF2-EAAA-43FA-BA0A-985636AC6040}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_NetFramework|Any CPU.ActiveCfg = Debug|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_NetFramework|Any CPU.Build.0 = Debug|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_Unity|Any CPU.ActiveCfg = Debug|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release_Unity|Any CPU.Build.0 = Debug|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBE495BC-0258-4F36-8B3D-1CA64D97E90A}.Release|Any CPU.Build.0 = Release|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_NetFramework|Any CPU.ActiveCfg = Debug|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_NetFramework|Any CPU.Build.0 = Debug|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_Unity|Any CPU.ActiveCfg = Debug|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release_Unity|Any CPU.Build.0 = Debug|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F88995B3-A5A2-4FC1-A11B-E2AA17483B20}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+2 -19
View File
@@ -1,5 +1,3 @@
using System;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
@@ -14,28 +12,13 @@ namespace BepInEx.NET.Common
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
if (BaseChainloader<BasePlugin>.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
Info = pluginInfo;
Info.Instance = this;
}
else
{
throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
}
Log = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Log = Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
/// <inheritdoc />
public PluginInfo Info { get; }
/// <inheritdoc />
public ManualLogSource Log { get; }
/// <inheritdoc />
public ConfigFile Config { get; }
public Harmony HarmonyInstance { get; set; }
@@ -12,18 +12,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\BepInEx.Core\BepInEx.Core.csproj"/>
<ProjectReference Include="..\..\..\BepInEx.PluginProvider\BepInEx.PluginProvider.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>None</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DocumentationProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="..\..\..\BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>None</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DocumentationProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="..\..\..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj"/>
</ItemGroup>
@@ -7,6 +7,9 @@ namespace BepInEx.NET.Common
{
public class NetChainloader : BaseChainloader<BasePlugin>
{
// TODO: Remove once proper instance handling exists
public static NetChainloader Instance { get; set; }
public override void Initialize(string gameExePath = null)
{
Instance = this;
@@ -54,7 +54,7 @@ namespace BepInEx.NET.CoreCLR
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
{
assemblyPatcher.AddPatchersFromProviders();
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");
@@ -110,7 +110,7 @@ public static class NetPreloader
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
{
assemblyPatcher.AddPatchersFromProviders();
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
Log.Log(LogLevel.Info,
$"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");
@@ -1,5 +1,4 @@
using System;
using BepInEx.Configuration;
using BepInEx.Configuration;
using BepInEx.Logging;
using Il2CppInterop.Runtime.InteropTypes;
@@ -11,23 +10,11 @@ public abstract class BasePlugin
{
var metadata = MetadataHelper.GetMetadata(this);
if (IL2CPPChainloader.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
{
Info = pluginInfo;
Info.Instance = this;
}
else
{
throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
}
Log = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
Log = Logger.CreateLogSource(metadata.Name);
Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, metadata);
}
public PluginInfo Info { get; }
public ManualLogSource Log { get; }
public ConfigFile Config { get; }
@@ -9,18 +9,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BepInEx.Core\BepInEx.Core.csproj"/>
<ProjectReference Include="..\..\..\BepInEx.PluginProvider\BepInEx.PluginProvider.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>None</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DocumentationProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="..\..\..\BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>None</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DocumentationProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="..\..\..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj" PrivateAssets="All"/>
<ProjectReference Include="..\BepInEx.Unity.Common\BepInEx.Unity.Common.csproj"/>
</ItemGroup>
@@ -32,11 +32,7 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
private static INativeDetour RuntimeInvokeDetour { get; set; }
public static new IL2CPPChainloader Instance
{
get => (IL2CPPChainloader)BaseChainloader<BasePlugin>.Instance;
set => BaseChainloader<BasePlugin>.Instance = value;
}
public static IL2CPPChainloader Instance { get; set; }
/// <summary>
/// Register and add a Unity Component (for example MonoBehaviour) into BepInEx global manager.
@@ -67,7 +67,7 @@ public static class Preloader
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
{
assemblyPatcher.AddPatchersFromProviders();
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherContext.PatcherPlugins.Count == 1 ? "" : "s")} loaded");
@@ -77,7 +77,7 @@ internal static class UnityPreloader
using (var assemblyPatcher = new AssemblyPatcher(MonoAssemblyHelper.LoadFromMemory))
{
assemblyPatcher.AddPatchersFromDirectory(Paths.BepInExAssemblyDirectory);
assemblyPatcher.AddPatchersFromProviders();
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
Log.Log(LogLevel.Info,
$"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherContext.PatcherPlugins.Count == 1 ? "" : "s")} loaded");
@@ -1,5 +1,4 @@
using System;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using UnityEngine;
@@ -22,15 +21,14 @@ public abstract class BaseUnityPlugin : MonoBehaviour
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
if (BaseChainloader<BaseUnityPlugin>.Instance.Plugins.TryGetValue(metadata.GUID, out var pluginInfo))
Info = new PluginInfo
{
Info = pluginInfo;
Info.Instance = this;
}
else
{
throw new InvalidOperationException($"The plugin information for {metadata.GUID} couldn't be found on the chainloader");
}
Metadata = metadata,
Instance = this,
Dependencies = MetadataHelper.GetDependencies(GetType()),
Processes = MetadataHelper.GetAttributes<BepInProcess>(GetType()),
Location = GetType().Assembly.Location
};
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
@@ -45,7 +43,7 @@ public abstract class BaseUnityPlugin : MonoBehaviour
/// <summary>
/// Logger instance tied to this plugin.
/// </summary>
public ManualLogSource Logger { get; }
protected ManualLogSource Logger { get; }
/// <summary>
/// Default config file tied to this plugin. The config file will not be created until
@@ -11,18 +11,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BepInEx.Core\BepInEx.Core.csproj"/>
<ProjectReference Include="..\..\..\BepInEx.PluginProvider\BepInEx.PluginProvider.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>None</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DocumentationProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="..\..\..\BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>None</OutputItemType>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Targets>Build;DocumentationProjectOutputGroup</Targets>
</ProjectReference>
<ProjectReference Include="..\..\..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj" PrivateAssets="All"/>
<ProjectReference Include="..\BepInEx.Unity.Mono.Preloader\BepInEx.Unity.Mono.Preloader.csproj" PrivateAssets="All"/>
</ItemGroup>
@@ -37,17 +37,14 @@ public class UnityChainloader : BaseChainloader<BaseUnityPlugin>
private string _consoleTitle;
// TODO: Remove once proper instance handling exists
public static UnityChainloader Instance { get; set; }
/// <summary>
/// The GameObject that all plugins are attached to as components.
/// </summary>
public static GameObject ManagerObject { get; private set; }
public static new UnityChainloader Instance
{
get => (UnityChainloader)BaseChainloader<BaseUnityPlugin>.Instance;
set => BaseChainloader<BaseUnityPlugin>.Instance = value;
}
protected override string ConsoleTitle => _consoleTitle;
@@ -73,9 +70,9 @@ public class UnityChainloader : BaseChainloader<BaseUnityPlugin>
Logger.Log(LogLevel.Debug, "Entering chainloader StaticStart");
Instance = new UnityChainloader();
Instance.Initialize(gameExePath);
Instance.Execute();
var instance = new UnityChainloader();
instance.Initialize(gameExePath);
instance.Execute();
Logger.Log(LogLevel.Debug, "Exiting chainloader StaticStart");
}
-2
View File
@@ -239,10 +239,8 @@ public sealed class MakeDistTask : FrostingTask<BuildContext>
var bepInExCoreDir = bepInExDir.Combine("core");
ctx.CreateDirectory(bepInExDir);
ctx.CreateDirectory(bepInExCoreDir);
ctx.CreateDirectory(bepInExDir.Combine("plugins-providers"));
ctx.CreateDirectory(bepInExDir.Combine("plugins"));
ctx.CreateDirectory(bepInExDir.Combine("patchers"));
ctx.CreateDirectory(bepInExDir.Combine("patchers-providers"));
File.WriteAllText(targetDir.CombineWithFilePath("changelog.txt").FullPath, changelog);