mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-20 01:46:13 +00:00
Implement Patcher and plugin providers
This commit is contained in:
1 parent
53625800b8
commit
952c74f6fe
29 files changed
+864
-216
No files matched your search
@@ -25,14 +25,14 @@ public abstract class BaseChainloader<TPlugin>
|
||||
/// <param name="type">Type definition to analyze.</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(TypeDefinition type, string assemblyLocation)
|
||||
public static PluginInfo ToPluginInfo<T>(TypeDefinition type, IPluginLoader loader, string assemblyLocation)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!type.IsSubtypeOf(typeof(TPlugin)))
|
||||
if (!type.IsSubtypeOf(typeof(T)))
|
||||
return null;
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
@@ -41,8 +41,13 @@ public abstract class BaseChainloader<TPlugin>
|
||||
return null;
|
||||
}
|
||||
|
||||
var metadata = BepInPlugin.FromCecilType(type);
|
||||
if (type.IsSubtypeOf(typeof(BasePluginProvider)))
|
||||
return GetPluginInfo(type, BepInPluginProvider.FromCecilType(type), loader, assemblyLocation);
|
||||
return GetPluginInfo(type, BepInPlugin.FromCecilType(type), loader, assemblyLocation);
|
||||
}
|
||||
|
||||
private static PluginInfo GetPluginInfo(TypeDefinition type, BepInPlugin metadata, IPluginLoader loader, string assemblyLocation)
|
||||
{
|
||||
// Perform checks that will prevent the plugin from being loaded in ALL cases
|
||||
if (metadata == null)
|
||||
{
|
||||
@@ -85,15 +90,16 @@ public abstract class BaseChainloader<TPlugin>
|
||||
Incompatibilities = incompatibilities,
|
||||
TypeName = type.FullName,
|
||||
TargettedBepInExVersion = bepinVersion,
|
||||
Location = assemblyLocation
|
||||
Location = assemblyLocation,
|
||||
Loader = loader
|
||||
};
|
||||
}
|
||||
|
||||
protected static bool HasBepinPlugins(AssemblyDefinition ass)
|
||||
protected static bool HasBepinPluginType<T>(AssemblyDefinition ass)
|
||||
{
|
||||
if (ass.MainModule.AssemblyReferences.All(r => r.Name != CurrentAssemblyName))
|
||||
return false;
|
||||
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(BepInPlugin).FullName))
|
||||
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).FullName))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -116,10 +122,20 @@ public abstract class BaseChainloader<TPlugin>
|
||||
private bool _initialized;
|
||||
|
||||
/// <summary>
|
||||
/// List of all <see cref="PluginInfo" /> instances loaded via the chainloader.
|
||||
/// List of all <see cref="PluginInfo" /> instances of all plugins 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.
|
||||
/// </summary>
|
||||
public Dictionary<string, PluginInfo> Providers { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of all <see cref="IPluginLoader" /> discovered from all the providers
|
||||
/// </summary>
|
||||
public List<IPluginLoader> Loaders { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Collection of error chainloader messages that occured during plugin loading.
|
||||
/// Contains information about what certain plugins were not loaded.
|
||||
@@ -194,38 +210,30 @@ 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 IList<PluginInfo> DiscoverPluginsFrom(string path, string cacheName = "chainloader")
|
||||
protected List<PluginInfo> DiscoverPluginProvidersFrom(string path, string cacheName = "chainloader_meta")
|
||||
{
|
||||
var pluginsToLoad =
|
||||
TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPlugins, cacheName);
|
||||
TypeLoader.FindPluginTypes(path, ToPluginInfo<BasePluginProvider>, HasBepinPluginType<BasePluginProvider>, 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 IList<PluginInfo> ModifyLoadOrder(IList<PluginInfo> plugins)
|
||||
protected virtual List<T> ModifyLoadOrder<T>(IList<T> plugins, Dictionary<string, T> existingPlugins)
|
||||
where T : PluginInfo
|
||||
{
|
||||
// We use a sorted dictionary to ensure consistent load order
|
||||
var dependencyDict =
|
||||
new SortedDictionary<string, IEnumerable<string>>(StringComparer.InvariantCultureIgnoreCase);
|
||||
var pluginsByGuid = new Dictionary<string, PluginInfo>();
|
||||
var pluginsByGuid = new Dictionary<string, T>();
|
||||
|
||||
foreach (var pluginInfoGroup in plugins.GroupBy(info => info.Metadata.GUID))
|
||||
{
|
||||
if (Plugins.TryGetValue(pluginInfoGroup.Key, out var loadedPlugin))
|
||||
if (existingPlugins.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.");
|
||||
@@ -266,7 +274,7 @@ public abstract class BaseChainloader<TPlugin>
|
||||
foreach (var pluginInfo in pluginsByGuid.Values.ToList())
|
||||
if (pluginInfo.Incompatibilities.Any(incompatibility =>
|
||||
pluginsByGuid.ContainsKey(incompatibility.IncompatibilityGUID)
|
||||
|| Plugins.ContainsKey(incompatibility.IncompatibilityGUID))
|
||||
|| existingPlugins.ContainsKey(incompatibility.IncompatibilityGUID))
|
||||
)
|
||||
{
|
||||
pluginsByGuid.Remove(pluginInfo.Metadata.GUID);
|
||||
@@ -275,7 +283,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 => Plugins.ContainsKey(x));
|
||||
.Where(x => existingPlugins.ContainsKey(x));
|
||||
var incompatiblePlugins = incompatiblePluginsNew.Concat(incompatiblePluginsExisting).ToArray();
|
||||
var message =
|
||||
$@"Could not load [{pluginInfo}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}";
|
||||
@@ -306,15 +314,23 @@ public abstract class BaseChainloader<TPlugin>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run the chainloader and load all plugins from the plugins folder.
|
||||
/// Run the chainloader and load all plugins from all providers.
|
||||
/// </summary>
|
||||
public virtual void Execute()
|
||||
{
|
||||
try
|
||||
{
|
||||
var plugins = DiscoverPlugins();
|
||||
var builtinProviders = DiscoverPluginProvidersFrom(Paths.BepInExAssemblyDirectory);
|
||||
var providers = DiscoverPluginProvidersFrom(Paths.PluginProviderPath);
|
||||
Logger.Log(LogLevel.Info, $"{providers.Count + builtinProviders.Count} " +
|
||||
$"meta plugin{(providers.Count + builtinProviders.Count == 1 ? "" : "s")} to load");
|
||||
|
||||
LoadPlugins(builtinProviders, provider: true);
|
||||
LoadPlugins(providers, provider: true);
|
||||
|
||||
var plugins = TypeLoader.GetPluginsFromLoaders(Loaders, ToPluginInfo<TPlugin>, HasBepinPluginType<BepInPlugin>, "chainloader");
|
||||
Logger.Log(LogLevel.Info, $"{plugins.Count} plugin{(plugins.Count == 1 ? "" : "s")} to load");
|
||||
LoadPlugins(plugins);
|
||||
LoadPlugins(plugins, provider: false);
|
||||
Finished?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -331,71 +347,19 @@ public abstract class BaseChainloader<TPlugin>
|
||||
Logger.Log(LogLevel.Message, "Chainloader startup complete");
|
||||
}
|
||||
|
||||
private IList<PluginInfo> LoadPlugins(IList<PluginInfo> plugins)
|
||||
private void LoadPlugins(List<PluginInfo> plugins, bool provider)
|
||||
{
|
||||
var sortedPlugins = ModifyLoadOrder(plugins);
|
||||
|
||||
var existingPlugins = provider ? Providers : Plugins;
|
||||
var sortedPlugins = ModifyLoadOrder(plugins, existingPlugins);
|
||||
|
||||
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 plugin in sortedPlugins)
|
||||
{
|
||||
var dependsOnInvalidPlugin = false;
|
||||
var missingDependencies = new List<BepInDependency>();
|
||||
foreach (var dependency in plugin.Dependencies)
|
||||
if (!CheckDependencies(plugin, processedPlugins, invalidPlugins, existingPlugins))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -403,45 +367,107 @@ public abstract class BaseChainloader<TPlugin>
|
||||
{
|
||||
Logger.Log(LogLevel.Info, $"Loading [{plugin}]");
|
||||
|
||||
if (!loadedAssemblies.TryGetValue(plugin.Location, out var ass))
|
||||
loadedAssemblies[plugin.Location] = ass = Assembly.LoadFrom(plugin.Location);
|
||||
Assembly ass = null;
|
||||
if (provider && !loadedAssemblies.TryGetValue(plugin.Location, out ass))
|
||||
{
|
||||
ass = Assembly.LoadFrom(plugin.Location);
|
||||
loadedAssemblies[plugin.Location] = ass;
|
||||
}
|
||||
else if (!provider && !loadedAssemblies.TryGetValue(plugin.Loader.AssemblyIdentifier, out ass))
|
||||
{
|
||||
ass = Assembly.Load(plugin.Loader.GetAssemblyData());
|
||||
loadedAssemblies[plugin.Loader.AssemblyIdentifier] = ass;
|
||||
}
|
||||
|
||||
Plugins[plugin.Metadata.GUID] = plugin;
|
||||
existingPlugins[plugin.Metadata.GUID] = plugin;
|
||||
TryRunModuleCtor(plugin, ass);
|
||||
plugin.Instance = LoadPlugin(plugin, ass);
|
||||
loadedPlugins.Add(plugin);
|
||||
|
||||
if (provider)
|
||||
{
|
||||
var type = ass.GetType(plugin.TypeName);
|
||||
plugin.Instance = Activator.CreateInstance(type);
|
||||
AppDomain.CurrentDomain.AssemblyResolve += (_, args) => ((BasePluginProvider)plugin.Instance).ResolveAssembly(args.Name);
|
||||
var requestedPluginsStream = ((BasePluginProvider)plugin.Instance).GetPlugins();
|
||||
Loaders.AddRange(requestedPluginsStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.Instance = LoadPlugin(plugin, ass);
|
||||
}
|
||||
PluginLoaded?.Invoke(plugin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
invalidPlugins.Add(plugin.Metadata.GUID);
|
||||
Plugins.Remove(plugin.Metadata.GUID);
|
||||
existingPlugins.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)
|
||||
private bool CheckDependencies<T>(T plugin, Dictionary<string, SemanticVersioning.Version> processedPlugins, HashSet<string> invalidPlugins, Dictionary<string, T> existingPlugins)
|
||||
where T : PluginInfo
|
||||
{
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
private static void TryRunModuleCtor(PluginInfo plugin, Assembly assembly)
|
||||
|
||||
@@ -89,7 +89,9 @@ public static class TypeLoader
|
||||
{
|
||||
Paths.BepInExAssemblyDirectory,
|
||||
Paths.PluginPath,
|
||||
Paths.PluginProviderPath,
|
||||
Paths.PatcherPluginPath,
|
||||
Paths.PatcherProviderPath,
|
||||
Paths.ManagedPath
|
||||
}.Concat(SearchDirectories);
|
||||
|
||||
@@ -126,9 +128,8 @@ public static class TypeLoader
|
||||
/// selector.
|
||||
/// </returns>
|
||||
public static Dictionary<string, List<T>> FindPluginTypes<T>(string directory,
|
||||
Func<TypeDefinition, string, T> typeSelector,
|
||||
Func<AssemblyDefinition, bool> assemblyFilter =
|
||||
null,
|
||||
Func<TypeDefinition, IPluginLoader, string, T> typeSelector,
|
||||
Func<AssemblyDefinition, bool> assemblyFilter = null,
|
||||
string cacheName = null)
|
||||
where T : ICacheable, new()
|
||||
{
|
||||
@@ -153,19 +154,7 @@ public static class TypeLoader
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
result[dll] = ExamineStream(typeSelector, assemblyFilter, dllMs, null, dll);
|
||||
}
|
||||
catch (BadImageFormatException e)
|
||||
{
|
||||
@@ -183,6 +172,82 @@ 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="loaders">The loaders 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> GetPluginsFromLoaders<T>(IList<IPluginLoader> loaders,
|
||||
Func<TypeDefinition, IPluginLoader, 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 (IPluginLoader pluginLoader in loaders)
|
||||
{
|
||||
IList<T> plugins;
|
||||
if (cache != null && pluginLoader.AssemblyHash != null &&
|
||||
cache.TryGetValue(pluginLoader.AssemblyIdentifier, out var cacheEntry) &&
|
||||
pluginLoader.AssemblyHash == cacheEntry.Hash)
|
||||
{
|
||||
plugins = cacheEntry.CacheItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
var assemblyData = pluginLoader.GetAssemblyData();
|
||||
using var memory = new MemoryStream(assemblyData);
|
||||
plugins = ExamineStream(typeSelector, assemblyFilter, memory, pluginLoader, null);
|
||||
}
|
||||
|
||||
foreach (T pluginInfo in plugins)
|
||||
{
|
||||
if (!result.ContainsKey(pluginLoader.AssemblyIdentifier))
|
||||
result[pluginLoader.AssemblyIdentifier] = new();
|
||||
result[pluginLoader.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, IPluginLoader, string, T> typeSelector,
|
||||
Func<AssemblyDefinition, bool> assemblyFilter,
|
||||
MemoryStream dllMs,
|
||||
IPluginLoader loader,
|
||||
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, loader, location))
|
||||
.Where(t => t != null).ToList();
|
||||
return matches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads an index of type metadatas from a cache.
|
||||
/// </summary>
|
||||
|
||||
@@ -76,6 +76,32 @@ 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 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>
|
||||
@@ -264,7 +290,7 @@ public static class MetadataHelper
|
||||
/// </summary>
|
||||
/// <param name="pluginType">The plugin type.</param>
|
||||
/// <returns>The BepInPlugin metadata of the plugin type.</returns>
|
||||
public static BepInPlugin GetMetadata(Type pluginType)
|
||||
public static BepInPlugin GetPluginMetadata(Type pluginType)
|
||||
{
|
||||
var attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false);
|
||||
|
||||
@@ -279,7 +305,29 @@ public static class MetadataHelper
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin instance.</param>
|
||||
/// <returns>The BepInPlugin metadata of the plugin instance.</returns>
|
||||
public static BepInPlugin GetMetadata(object plugin) => GetMetadata(plugin.GetType());
|
||||
public static BepInPlugin GetPluginMetadata(object plugin) => GetPluginMetadata(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.
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
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 loaders, one per assembly</returns>
|
||||
public abstract IList<IPluginLoader> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace BepInEx;
|
||||
|
||||
/// <summary>
|
||||
/// An interface that allows to dynamically load and track an assembly
|
||||
/// which can be obtained from a provided
|
||||
/// </summary>
|
||||
public interface IPluginLoader
|
||||
{
|
||||
/// <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();
|
||||
}
|
||||
@@ -16,6 +16,11 @@ public class PluginInfo : ICacheable
|
||||
/// General metadata about a plugin.
|
||||
/// </summary>
|
||||
public BepInPlugin Metadata { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The loader used to load this plugin, null if it is a provider
|
||||
/// </summary>
|
||||
public IPluginLoader Loader { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of <see cref="BepInProcess" /> attributes that describe what processes the plugin can run on.
|
||||
@@ -33,24 +38,23 @@ public class PluginInfo : ICacheable
|
||||
/// </summary>
|
||||
public IEnumerable<BepInIncompatibility> Incompatibilities { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// File path to the plugin DLL
|
||||
/// </summary>
|
||||
public string Location { get; internal set; }
|
||||
|
||||
public string TypeName { 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; }
|
||||
|
||||
internal string Location { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
|
||||
|
||||
void ICacheable.Save(BinaryWriter bw)
|
||||
public virtual void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(TypeName);
|
||||
bw.Write(Location);
|
||||
|
||||
bw.Write(Metadata.GUID);
|
||||
bw.Write(Metadata.Name);
|
||||
@@ -74,10 +78,9 @@ public class PluginInfo : ICacheable
|
||||
bw.Write(TargettedBepInExVersion.ToString(4));
|
||||
}
|
||||
|
||||
void ICacheable.Load(BinaryReader br)
|
||||
public virtual void Load(BinaryReader br)
|
||||
{
|
||||
TypeName = br.ReadString();
|
||||
Location = br.ReadString();
|
||||
|
||||
Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString());
|
||||
|
||||
@@ -111,7 +114,4 @@ public class PluginInfo : ICacheable
|
||||
|
||||
TargettedBepInExVersion = new Version(br.ReadString());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => $"{Metadata?.Name} {Metadata?.Version}";
|
||||
}
|
||||
@@ -75,6 +75,16 @@ 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>
|
||||
@@ -113,7 +123,9 @@ 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,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net35;netstandard2.0</TargetFrameworks>
|
||||
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
|
||||
<Description>BepInEx patcher provider module</Description>
|
||||
</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>
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.IO;
|
||||
|
||||
namespace BepInEx.PatcherProvider;
|
||||
|
||||
public class BepInExPatcherLoader : IPluginLoader
|
||||
{
|
||||
public string AssemblyIdentifier { get; internal set; }
|
||||
public string AssemblyHash { get; internal set; }
|
||||
private byte[] assemblyData;
|
||||
public byte[] GetAssemblyData()
|
||||
{
|
||||
if (assemblyData == null)
|
||||
{
|
||||
assemblyData = File.ReadAllBytes(AssemblyIdentifier);
|
||||
}
|
||||
|
||||
return assemblyData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using BepInEx.Logging;
|
||||
using BepInEx.Preloader.Core.Patching;
|
||||
|
||||
namespace BepInEx.PatcherProvider;
|
||||
|
||||
[PatcherProviderPluginInfo("io.bepinex.bepinexpatcherprovider", "BepInExPatcherProvider", "1.0")]
|
||||
public class BepInExPatcherProvider : BasePatcherProvider
|
||||
{
|
||||
private static readonly Dictionary<string, string> AssemblyLocationsByFilename = new();
|
||||
|
||||
public override IList<IPluginLoader> GetPatchers()
|
||||
{
|
||||
var loaders = new List<IPluginLoader>();
|
||||
foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PatcherPluginPath), "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
try
|
||||
{
|
||||
AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
|
||||
loaders.Add(new BepInExPatcherLoader
|
||||
{
|
||||
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
|
||||
AssemblyIdentifier = dll
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Log(LogLevel.Error, e);
|
||||
}
|
||||
}
|
||||
|
||||
return loaders;
|
||||
}
|
||||
|
||||
public override Assembly ResolveAssembly(string name)
|
||||
{
|
||||
if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
|
||||
return null;
|
||||
|
||||
if (!Utility.TryResolveDllAssembly(new(name), location, out var ass))
|
||||
return null;
|
||||
|
||||
return ass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net35;netstandard2.0</TargetFrameworks>
|
||||
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
|
||||
<Description>BepInEx plugin provider module</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.IO;
|
||||
|
||||
namespace BepInEx.PluginProvider;
|
||||
|
||||
public class BepInExPluginLoader : IPluginLoader
|
||||
{
|
||||
public string AssemblyIdentifier { get; internal set; }
|
||||
public string AssemblyHash { get; internal set; }
|
||||
private byte[] assemblyData;
|
||||
public byte[] GetAssemblyData()
|
||||
{
|
||||
if (assemblyData == null)
|
||||
{
|
||||
assemblyData = File.ReadAllBytes(AssemblyIdentifier);
|
||||
}
|
||||
|
||||
return assemblyData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using BepInEx.Logging;
|
||||
|
||||
namespace BepInEx.PluginProvider;
|
||||
|
||||
[BepInPluginProvider("io.bepinex.bepinexpluginprovider", "BepInExPluginProvider", "1.0")]
|
||||
public class BepInExPluginProvider : BasePluginProvider
|
||||
{
|
||||
private static readonly Dictionary<string, string> AssemblyLocationsByFilename = new();
|
||||
|
||||
public override IList<IPluginLoader> GetPlugins()
|
||||
{
|
||||
var loaders = new List<IPluginLoader>();
|
||||
foreach (var dll in Directory.GetFiles(Path.GetFullPath(Paths.PluginPath), "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
try
|
||||
{
|
||||
AssemblyLocationsByFilename.Add(Path.GetFileNameWithoutExtension(dll), Path.GetDirectoryName(dll));
|
||||
loaders.Add(new BepInExPluginLoader
|
||||
{
|
||||
AssemblyHash = File.GetLastWriteTimeUtc(dll).ToString("O"),
|
||||
AssemblyIdentifier = dll
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(LogLevel.Error, e);
|
||||
}
|
||||
}
|
||||
|
||||
return loaders;
|
||||
}
|
||||
|
||||
public override Assembly ResolveAssembly(string name)
|
||||
{
|
||||
if (!AssemblyLocationsByFilename.TryGetValue(name, out var location))
|
||||
return null;
|
||||
|
||||
if (!Utility.TryResolveDllAssembly(new(name), location, out var ass))
|
||||
return null;
|
||||
|
||||
return ass;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using Mono.Cecil;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace BepInEx.Preloader.Core.Patching;
|
||||
|
||||
@@ -24,6 +25,9 @@ public class AssemblyPatcher : IDisposable
|
||||
|
||||
private Func<byte[], string, Assembly> assemblyLoader;
|
||||
|
||||
private readonly List<IPluginLoader> loaders = new();
|
||||
private readonly List<BasePatcherProvider> providers = new();
|
||||
|
||||
public AssemblyPatcher(Func<byte[], string, Assembly> assemblyLoader)
|
||||
{
|
||||
this.assemblyLoader = assemblyLoader;
|
||||
@@ -61,16 +65,18 @@ public class AssemblyPatcher : IDisposable
|
||||
|
||||
// Clear to allow GC collection.
|
||||
PatcherContext.PatcherPlugins.Clear();
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= PatcherProvidersAssemblyResolver;
|
||||
}
|
||||
|
||||
private PatcherPluginMetadata ToPatcherPlugin(TypeDefinition type, string assemblyPath)
|
||||
private PatcherPluginMetadata ToPatcherPlugin<T>(TypeDefinition type, IPluginLoader loader, string assemblyPath)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!type.IsSubtypeOf(typeof(BasePatcher)))
|
||||
if (!type.IsSubtypeOf(typeof(T)))
|
||||
return null;
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
@@ -79,8 +85,13 @@ public class AssemblyPatcher : IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
var metadata = PatcherPluginInfoAttribute.FromCecilType(type);
|
||||
if (type.IsSubtypeOf(typeof(BasePatcherProvider)))
|
||||
return GetPatcherInfo(type, loader, PatcherProviderPluginInfoAttribute.FromCecilType(type));
|
||||
return GetPatcherInfo(type, loader, PatcherPluginInfoAttribute.FromCecilType(type));
|
||||
}
|
||||
|
||||
private PatcherPluginMetadata GetPatcherInfo(TypeDefinition type, IPluginLoader loader, PatcherPluginInfoAttribute metadata)
|
||||
{
|
||||
// Perform checks that will prevent the plugin from being loaded in ALL cases
|
||||
if (metadata == null)
|
||||
{
|
||||
@@ -109,20 +120,160 @@ public class AssemblyPatcher : IDisposable
|
||||
|
||||
return new PatcherPluginMetadata
|
||||
{
|
||||
TypeName = type.FullName
|
||||
TypeName = type.FullName,
|
||||
Loader = loader
|
||||
};
|
||||
}
|
||||
|
||||
private bool HasPatcherPlugins(AssemblyDefinition ass)
|
||||
private bool HasPatcherType<T>(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(BasePatcher).FullName))
|
||||
if (ass.MainModule.GetTypeReferences().All(r => r.FullName != typeof(T).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);
|
||||
loaders.AddRange(instance.GetPatchers());
|
||||
}
|
||||
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.GetPluginsFromLoaders(loaders, 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.Loader.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" : "")}");
|
||||
}
|
||||
|
||||
private Assembly PatcherProvidersAssemblyResolver(object sender, ResolveEventArgs args)
|
||||
{
|
||||
foreach (BasePatcherProvider provider in providers)
|
||||
{
|
||||
var ass = provider.ResolveAssembly(args.Name);
|
||||
if (ass != null)
|
||||
return ass;
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -135,7 +286,7 @@ public class AssemblyPatcher : IDisposable
|
||||
|
||||
var sortedPatchers = new List<PatchDefinition>();
|
||||
|
||||
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin, HasPatcherPlugins);
|
||||
var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin<BasePatcher>, HasPatcherType<BasePatcher>);
|
||||
|
||||
// TODO: Add dependency ordering and process attribute filtering
|
||||
|
||||
@@ -150,68 +301,10 @@ public class AssemblyPatcher : IDisposable
|
||||
var ass = Assembly.LoadFrom(assemblyPath);
|
||||
|
||||
foreach (var patcherPlugin in patcherCollection)
|
||||
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 patchDefinitions = LoadPatcherPlugin(ass, patcherPlugin);
|
||||
sortedPatchers.AddRange(patchDefinitions);
|
||||
}
|
||||
|
||||
var assName = ass.GetName();
|
||||
Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
|
||||
|
||||
@@ -79,6 +79,31 @@ 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 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>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
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 loaders, one per assembly</returns>
|
||||
public abstract IList<IPluginLoader> 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,6 +13,11 @@ internal class PatcherPluginMetadata : ICacheable
|
||||
/// </summary>
|
||||
public string TypeName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The loader used to load this patcher, null if it is a provider
|
||||
/// </summary>
|
||||
public IPluginLoader Loader { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save(BinaryWriter bw) => bw.Write(TypeName);
|
||||
|
||||
|
||||
+20
@@ -51,6 +51,10 @@ 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
|
||||
@@ -147,6 +151,22 @@ 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
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace BepInEx.NET.Common
|
||||
{
|
||||
protected BasePlugin()
|
||||
{
|
||||
var metadata = MetadataHelper.GetMetadata(this);
|
||||
var metadata = MetadataHelper.GetPluginMetadata(this);
|
||||
|
||||
HarmonyInstance = new Harmony("BepInEx.Plugin." + metadata.GUID);
|
||||
|
||||
|
||||
@@ -12,6 +12,18 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BepInEx.Core\BepInEx.Core.csproj"/>
|
||||
<ProjectReference Include="..\..\..\BepInEx.PluginProvider\BepInEx.PluginProvider.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<OutputItemType>Content</OutputItemType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Targets>Build;DocumentationProjectOutputGroup</Targets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<OutputItemType>Content</OutputItemType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Targets>Build;DocumentationProjectOutputGroup</Targets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace BepInEx.NET.CoreCLR
|
||||
|
||||
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
|
||||
{
|
||||
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
|
||||
assemblyPatcher.AddPatchersFromProviders();
|
||||
|
||||
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.AddPatchersFromDirectory(Paths.PatcherPluginPath);
|
||||
assemblyPatcher.AddPatchersFromProviders();
|
||||
|
||||
Log.Log(LogLevel.Info,
|
||||
$"{assemblyPatcher.PatcherContext.PatchDefinitions.Count} patcher definition(s) loaded");
|
||||
|
||||
@@ -8,7 +8,7 @@ public abstract class BasePlugin
|
||||
{
|
||||
protected BasePlugin()
|
||||
{
|
||||
var metadata = MetadataHelper.GetMetadata(this);
|
||||
var metadata = MetadataHelper.GetPluginMetadata(this);
|
||||
|
||||
Log = Logger.CreateLogSource(metadata.Name);
|
||||
|
||||
|
||||
@@ -9,6 +9,18 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BepInEx.Core\BepInEx.Core.csproj"/>
|
||||
<ProjectReference Include="..\..\..\BepInEx.PluginProvider\BepInEx.PluginProvider.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<OutputItemType>Content</OutputItemType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Targets>Build;DocumentationProjectOutputGroup</Targets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<OutputItemType>Content</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>
|
||||
|
||||
@@ -67,7 +67,7 @@ public static class Preloader
|
||||
|
||||
using (var assemblyPatcher = new AssemblyPatcher((data, _) => Assembly.Load(data)))
|
||||
{
|
||||
assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
|
||||
assemblyPatcher.AddPatchersFromProviders();
|
||||
|
||||
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.AddPatchersFromDirectory(Paths.PatcherPluginPath);
|
||||
assemblyPatcher.AddPatchersFromProviders();
|
||||
|
||||
Log.Log(LogLevel.Info,
|
||||
$"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherContext.PatcherPlugins.Count == 1 ? "" : "s")} loaded");
|
||||
|
||||
@@ -16,7 +16,7 @@ public abstract class BaseUnityPlugin : MonoBehaviour
|
||||
/// <exception cref="InvalidOperationException">BepInPlugin attribute is missing.</exception>
|
||||
protected BaseUnityPlugin()
|
||||
{
|
||||
var metadata = MetadataHelper.GetMetadata(this);
|
||||
var metadata = MetadataHelper.GetPluginMetadata(this);
|
||||
if (metadata == null)
|
||||
throw new InvalidOperationException("Can't create an instance of " + GetType().FullName +
|
||||
" because it inherits from BaseUnityPlugin and the BepInPlugin attribute is missing.");
|
||||
@@ -27,7 +27,6 @@ public abstract class BaseUnityPlugin : MonoBehaviour
|
||||
Instance = this,
|
||||
Dependencies = MetadataHelper.GetDependencies(GetType()),
|
||||
Processes = MetadataHelper.GetAttributes<BepInProcess>(GetType()),
|
||||
Location = GetType().Assembly.Location
|
||||
};
|
||||
|
||||
Logger = BepInEx.Logging.Logger.CreateLogSource(metadata.Name);
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BepInEx.Core\BepInEx.Core.csproj"/>
|
||||
<ProjectReference Include="..\..\..\BepInEx.PluginProvider\BepInEx.PluginProvider.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<OutputItemType>Content</OutputItemType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Targets>Build;DocumentationProjectOutputGroup</Targets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\BepInEx.PatcherProvider\BepInEx.PatcherProvider.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<OutputItemType>Content</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>
|
||||
|
||||
@@ -239,8 +239,10 @@ 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