|
@@ -7,7 +7,8 @@ using System.IO;
|
|
|
using System.Linq;
|
|
|
using System.Reflection;
|
|
|
using System.Text;
|
|
|
-using System.Text.RegularExpressions;
|
|
|
+using BepInEx.Contract;
|
|
|
+using Mono.Cecil;
|
|
|
using UnityEngine;
|
|
|
using UnityInjector.ConsoleUtil;
|
|
|
using Logger = BepInEx.Logging.Logger;
|
|
@@ -22,7 +23,7 @@ namespace BepInEx.Bootstrap
|
|
|
/// <summary>
|
|
|
/// The loaded and initialized list of plugins.
|
|
|
/// </summary>
|
|
|
- public static List<BaseUnityPlugin> Plugins { get; private set; } = new List<BaseUnityPlugin>();
|
|
|
+ public static Dictionary<string, PluginInfo> Plugins { get; private set; } = new Dictionary<string, PluginInfo>();
|
|
|
|
|
|
/// <summary>
|
|
|
/// The GameObject that all plugins are attached to as components.
|
|
@@ -36,30 +37,24 @@ namespace BepInEx.Bootstrap
|
|
|
/// <summary>
|
|
|
/// Initializes BepInEx to be able to start the chainloader.
|
|
|
/// </summary>
|
|
|
- public static void Initialize(string gameExePath, bool startConsole = true)
|
|
|
+ public static void Initialize(string containerExePath, bool startConsole = true)
|
|
|
{
|
|
|
if (_initialized)
|
|
|
return;
|
|
|
|
|
|
- // Set vitals
|
|
|
-
|
|
|
- if (gameExePath != null)
|
|
|
- {
|
|
|
- // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize
|
|
|
- // This is used by Preloader to use environment variables, for example
|
|
|
- Paths.SetExecutablePath(gameExePath);
|
|
|
- }
|
|
|
+ //Set vitals
|
|
|
+ Paths.SetExecutablePath(containerExePath);
|
|
|
|
|
|
Paths.SetPluginPath(ConfigPluginsDirectory.Value);
|
|
|
|
|
|
- // Start logging
|
|
|
+ //Start logging
|
|
|
if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole)
|
|
|
{
|
|
|
ConsoleWindow.Attach();
|
|
|
Logger.Listeners.Add(new ConsoleLogListener());
|
|
|
}
|
|
|
|
|
|
- // Fix for standard output getting overwritten by UnityLogger
|
|
|
+ //Fix for standard output getting overwritten by UnityLogger
|
|
|
if (ConsoleWindow.StandardOut != null)
|
|
|
{
|
|
|
Console.SetOut(ConsoleWindow.StandardOut);
|
|
@@ -84,8 +79,6 @@ namespace BepInEx.Bootstrap
|
|
|
_initialized = true;
|
|
|
}
|
|
|
|
|
|
- private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._]+$");
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// The entrypoint for the BepInEx plugin system.
|
|
|
/// </summary>
|
|
@@ -107,8 +100,7 @@ namespace BepInEx.Bootstrap
|
|
|
{
|
|
|
var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
|
|
|
if (productNameProp != null)
|
|
|
- ConsoleWindow.Title =
|
|
|
- $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
|
|
|
+ ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
|
|
|
|
|
|
Logger.LogMessage("Chainloader started");
|
|
|
|
|
@@ -116,76 +108,33 @@ namespace BepInEx.Bootstrap
|
|
|
|
|
|
UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
|
|
|
|
|
|
+ var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath);
|
|
|
|
|
|
- string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
|
|
|
-
|
|
|
- var globalPluginTypes = TypeLoader.LoadTypes<BaseUnityPlugin>(Paths.PluginPath).ToList();
|
|
|
-
|
|
|
- Dictionary<Type, BepInPlugin> selectedPluginTypes = new Dictionary<Type, BepInPlugin>(globalPluginTypes.Count);
|
|
|
+ var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList();
|
|
|
|
|
|
- foreach (var pluginType in globalPluginTypes)
|
|
|
- {
|
|
|
- //Ensure metadata exists
|
|
|
- var metadata = MetadataHelper.GetMetadata(pluginType);
|
|
|
-
|
|
|
- if (metadata == null)
|
|
|
- {
|
|
|
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] as no metadata attribute is specified");
|
|
|
- continue;
|
|
|
- }
|
|
|
+ var loadedAssemblies = new Dictionary<AssemblyDefinition, Assembly>();
|
|
|
|
|
|
- if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
|
|
|
- {
|
|
|
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
|
|
|
- continue;
|
|
|
- }
|
|
|
+ Logger.LogInfo($"{pluginInfos.Count} / {pluginInfos.Count} plugins to load");
|
|
|
|
|
|
- if (selectedPluginTypes.Any(x => x.Value.GUID.Equals(metadata.GUID, StringComparison.OrdinalIgnoreCase)))
|
|
|
- {
|
|
|
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its GUID [{metadata.GUID}] is already used by another plugin.");
|
|
|
- continue;
|
|
|
- }
|
|
|
+ var dependencyDict = new Dictionary<string, IEnumerable<string>>();
|
|
|
+ var pluginsByGUID = new Dictionary<string, PluginInfo>();
|
|
|
|
|
|
- if (metadata.Version == null)
|
|
|
+ foreach (var pluginInfo in pluginInfos)
|
|
|
+ {
|
|
|
+ if (pluginInfo.Metadata.GUID == null)
|
|
|
{
|
|
|
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its version is invalid.");
|
|
|
+ Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it does not have a valid GUID.");
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- if (metadata.Name == null)
|
|
|
+ if (dependencyDict.ContainsKey(pluginInfo.Metadata.GUID))
|
|
|
{
|
|
|
- Logger.LogWarning($"Skipping type [{pluginType.FullName}] because its name is null.");
|
|
|
+ Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because its GUID ({pluginInfo.Metadata.GUID}) is already used by another plugin.");
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- //Perform a filter for currently running process
|
|
|
- var filters = MetadataHelper.GetAttributes<BepInProcess>(pluginType);
|
|
|
-
|
|
|
- if (filters.Length != 0)
|
|
|
- {
|
|
|
- var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
|
|
|
-
|
|
|
- if (!result)
|
|
|
- {
|
|
|
- Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter");
|
|
|
- continue;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- selectedPluginTypes.Add(pluginType, metadata);
|
|
|
- }
|
|
|
-
|
|
|
- Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load");
|
|
|
-
|
|
|
- var dependencyDict = new Dictionary<string, IEnumerable<string>>();
|
|
|
- var pluginsByGUID = new Dictionary<string, Type>();
|
|
|
-
|
|
|
- foreach (var kv in selectedPluginTypes)
|
|
|
- {
|
|
|
- var dependencies = MetadataHelper.GetDependencies(kv.Key, selectedPluginTypes.Keys);
|
|
|
-
|
|
|
- dependencyDict[kv.Value.GUID] = dependencies.Select(d => d.DependencyGUID);
|
|
|
- pluginsByGUID[kv.Value.GUID] = kv.Key;
|
|
|
+ dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
|
|
|
+ pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo;
|
|
|
}
|
|
|
|
|
|
var emptyDependencies = new string[0];
|
|
@@ -200,14 +149,12 @@ namespace BepInEx.Bootstrap
|
|
|
foreach (var pluginGUID in sortedPlugins)
|
|
|
{
|
|
|
// If the plugin is missing, don't process it
|
|
|
- if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginType))
|
|
|
+ if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo))
|
|
|
continue;
|
|
|
|
|
|
- var metadata = MetadataHelper.GetMetadata(pluginType);
|
|
|
- var dependencies = MetadataHelper.GetDependencies(pluginType, selectedPluginTypes.Keys);
|
|
|
var dependsOnInvalidPlugin = false;
|
|
|
var missingDependencies = new List<string>();
|
|
|
- foreach (var dependency in dependencies)
|
|
|
+ foreach (var dependency in pluginInfo.Dependencies)
|
|
|
{
|
|
|
// If the depenency wasn't already processed, it's missing altogether
|
|
|
if (!processedPlugins.Contains(dependency.DependencyGUID))
|
|
@@ -230,15 +177,15 @@ namespace BepInEx.Bootstrap
|
|
|
|
|
|
if (dependsOnInvalidPlugin)
|
|
|
{
|
|
|
- Logger.LogWarning($"Skipping [{metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
|
|
|
+ Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details.");
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
if (missingDependencies.Count != 0)
|
|
|
{
|
|
|
- Logger.LogError($@"Missing the following dependencies for [{metadata.Name}]: {"\r\n"}{
|
|
|
- string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
|
|
|
- }{"\r\n"}Loading will be skipped; expect further errors and unstabilities.");
|
|
|
+ Logger.LogError($@"Missing the following dependencies for [{pluginInfo.Metadata.Name}]: {"\r\n"}{
|
|
|
+ string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray())
|
|
|
+ }{"\r\n"}Loading will be skipped; expect further errors and unstabilities.");
|
|
|
|
|
|
invalidPlugins.Add(pluginGUID);
|
|
|
continue;
|
|
@@ -246,16 +193,29 @@ namespace BepInEx.Bootstrap
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]");
|
|
|
- Plugins.Add((BaseUnityPlugin)ManagerObject.AddComponent(pluginType));
|
|
|
+ Logger.LogInfo($"Loading [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
|
|
|
+
|
|
|
+ if (!loadedAssemblies.TryGetValue(pluginInfo.CecilType.Module.Assembly, out var ass))
|
|
|
+ loadedAssemblies[pluginInfo.CecilType.Module.Assembly] = ass = Assembly.LoadFile(pluginInfo.Location);
|
|
|
+
|
|
|
+ Plugins[pluginGUID] = pluginInfo;
|
|
|
+ pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.CecilType.FullName));
|
|
|
+ pluginInfo.CecilType = null;
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
invalidPlugins.Add(pluginGUID);
|
|
|
- Logger.LogError($"Error loading [{metadata.Name}] : {ex.Message}");
|
|
|
+ Plugins.Remove(pluginGUID);
|
|
|
+
|
|
|
+ Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}");
|
|
|
Logger.LogDebug(ex);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ foreach (var selectedTypesInfo in pluginsToLoad)
|
|
|
+ {
|
|
|
+ selectedTypesInfo.Key.Dispose();
|
|
|
+ }
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
@@ -272,17 +232,9 @@ namespace BepInEx.Bootstrap
|
|
|
|
|
|
#region Config
|
|
|
|
|
|
- private static readonly ConfigWrapper<string> ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap(
|
|
|
- "Paths",
|
|
|
- "PluginsDirectory",
|
|
|
- "The relative directory to the BepInEx folder where plugins are loaded.",
|
|
|
- "plugins");
|
|
|
-
|
|
|
- private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap(
|
|
|
- "Logging",
|
|
|
- "UnityLogListening",
|
|
|
- "Enables showing unity log messages in the BepInEx logging system.",
|
|
|
- true);
|
|
|
+ private static readonly ConfigWrapper<string> ConfigPluginsDirectory = ConfigFile.CoreConfig.Wrap("Paths", "PluginsDirectory", "The relative directory to the BepInEx folder where plugins are loaded.", "plugins");
|
|
|
+
|
|
|
+ private static readonly ConfigWrapper<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Wrap("Logging", "UnityLogListening", "Enables showing unity log messages in the BepInEx logging system.", true);
|
|
|
|
|
|
#endregion
|
|
|
}
|