mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
This reverts commit2a61b61497, reversing changes made to6b38cee127.
This commit is contained in:
1 parent
d9b95a0ac7
commit
544f909a80
34 files changed
+153
-1095
No files matched your search
@@ -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,84 +331,16 @@ 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)
|
||||
{
|
||||
if (!CheckDependencies(pluginInfo, processedPlugins, invalidPlugins, existingPluginInfos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Log(LogLevel.Info, $"Loading [{pluginInfo}]");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
existingPluginInfos[pluginInfo.Metadata.GUID] = pluginInfo;
|
||||
TryRunModuleCtor(pluginInfo, ass);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
invalidPlugins.Add(pluginInfo.Metadata.GUID);
|
||||
existingPluginInfos.Remove(pluginInfo.Metadata.GUID);
|
||||
|
||||
Logger.Log(LogLevel.Error,
|
||||
$"Error loading [{pluginInfo}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckDependencies<T>(T plugin, Dictionary<string, SemanticVersioning.Version> processedPlugins, HashSet<string> invalidPlugins, Dictionary<string, T> existingPlugins)
|
||||
where T : PluginInfo
|
||||
foreach (var plugin in sortedPlugins)
|
||||
{
|
||||
var dependsOnInvalidPlugin = false;
|
||||
var missingDependencies = new List<BepInDependency>();
|
||||
@@ -463,7 +355,7 @@ public abstract class BaseChainloader<TPlugin>
|
||||
// Alternatively, if the dependency hasn't been loaded before, it's missing too
|
||||
if (!dependencyExists)
|
||||
{
|
||||
dependencyExists = existingPlugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
|
||||
dependencyExists = Plugins.TryGetValue(dependency.DependencyGUID, out var pluginInfo);
|
||||
pluginVersion = pluginInfo?.Metadata.Version;
|
||||
}
|
||||
|
||||
@@ -492,7 +384,7 @@ public abstract class BaseChainloader<TPlugin>
|
||||
$"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;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (missingDependencies.Count != 0)
|
||||
@@ -504,10 +396,52 @@ public abstract class BaseChainloader<TPlugin>
|
||||
Logger.Log(LogLevel.Error, message);
|
||||
|
||||
invalidPlugins.Add(plugin.Metadata.GUID);
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
try
|
||||
{
|
||||
Logger.Log(LogLevel.Info, $"Loading [{plugin}]");
|
||||
|
||||
if (!loadedAssemblies.TryGetValue(plugin.Location, out var ass))
|
||||
loadedAssemblies[plugin.Location] = ass = Assembly.LoadFrom(plugin.Location);
|
||||
|
||||
Plugins[plugin.Metadata.GUID] = plugin;
|
||||
TryRunModuleCtor(plugin, ass);
|
||||
plugin.Instance = LoadPlugin(plugin, ass);
|
||||
loadedPlugins.Add(plugin);
|
||||
|
||||
PluginLoaded?.Invoke(plugin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
invalidPlugins.Add(plugin.Metadata.GUID);
|
||||
Plugins.Remove(plugin.Metadata.GUID);
|
||||
|
||||
Logger.Log(LogLevel.Error,
|
||||
$"Error loading [{plugin}]: {(ex is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : ex.ToString())}");
|
||||
}
|
||||
}
|
||||
|
||||
return loadedPlugins;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -17,17 +17,6 @@ public class PluginInfo : ICacheable
|
||||
/// </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.
|
||||
/// </summary>
|
||||
@@ -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}";
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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,33 +109,37 @@ 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.
|
||||
/// Adds all patchers from all managed assemblies specified in a directory.
|
||||
/// </summary>
|
||||
public void AddPatchersFromProviders()
|
||||
/// <param name="directory">Directory to search patcher DLLs from.</param>
|
||||
public void AddPatchersFromDirectory(string directory)
|
||||
{
|
||||
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;
|
||||
if (!Directory.Exists(directory))
|
||||
return;
|
||||
|
||||
foreach (var keyValuePair in allProviders)
|
||||
var sortedPatchers = new List<PatchDefinition>();
|
||||
|
||||
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherPlugins);
|
||||
|
||||
// TODO: Add dependency ordering and process attribute filtering
|
||||
|
||||
foreach (var keyValuePair in patchers)
|
||||
{
|
||||
var assemblyPath = keyValuePair.Key;
|
||||
var patcherCollection = keyValuePair.Value;
|
||||
@@ -163,78 +150,10 @@ public class AssemblyPatcher : IDisposable
|
||||
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;
|
||||
|
||||
@@ -279,7 +198,7 @@ public class AssemblyPatcher : IDisposable
|
||||
void AddDefinition(PatchDefinition definition)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug, $"Discovered patch [{definition.FullName}]");
|
||||
patchDefinitions.Add(definition);
|
||||
sortedPatchers.Add(definition);
|
||||
}
|
||||
|
||||
foreach (var targetAssembly in targetAssemblies)
|
||||
@@ -291,41 +210,7 @@ public class AssemblyPatcher : IDisposable
|
||||
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.
|
||||
/// </summary>
|
||||
/// <param name="directory">Directory to search patcher DLLs from.</param>
|
||||
public void AddPatchersFromDirectory(string directory)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
return;
|
||||
|
||||
var sortedPatchers = new List<PatchDefinition>();
|
||||
|
||||
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin<BasePatcher>, HasPatcherType<BasePatcher>);
|
||||
|
||||
// TODO: Add dependency ordering and process attribute filtering
|
||||
|
||||
foreach (var keyValuePair in patchers)
|
||||
{
|
||||
var assemblyPath = keyValuePair.Key;
|
||||
var patcherCollection = keyValuePair.Value;
|
||||
|
||||
if (patcherCollection.Count == 0)
|
||||
continue;
|
||||
|
||||
var ass = Assembly.LoadFrom(assemblyPath);
|
||||
|
||||
foreach (var patcherPlugin in patcherCollection)
|
||||
{
|
||||
var patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
|
||||
sortedPatchers.AddRange(patchDefinitions);
|
||||
$"Failed to load patchers from type [{patcherPlugin.TypeName}]: {(e is ReflectionTypeLoadException re ? TypeLoader.TypeLoadExceptionToString(re) : e.ToString())}");
|
||||
}
|
||||
|
||||
var assName = ass.GetName();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user