From dc5d9f444f7981647d47a9e82b63ef5e85d05a47 Mon Sep 17 00:00:00 2001
From: Bepis <36346617+bbepis@users.noreply.github.com>
Date: Mon, 27 Apr 2020 20:07:10 +1000
Subject: [PATCH] Initial framework split
---
BepInEx.Bootstrap/BepInEx.Bootstrap.csproj | 6 +-
BepInEx.Bootstrap/Linker.cs | 7 +-
.../BepInEx.Core.csproj | 18 +-
BepInEx.Core/Bootstrap/BaseChainloader.cs | 352 ++++++++++++++
.../Bootstrap/TypeLoader.cs | 37 +-
.../Configuration/AcceptableValueBase.cs | 0
.../Configuration/AcceptableValueList.cs | 0
.../Configuration/AcceptableValueRange.cs | 0
.../Configuration/ConfigDefinition.cs | 0
.../Configuration/ConfigDescription.cs | 0
.../Configuration/ConfigEntryBase.cs | 0
.../Configuration/ConfigFile.cs | 2 +-
.../Configuration/ConfigWrapper.cs | 0
.../Configuration/SettingChangedEventArgs.cs | 0
.../Configuration/TomlTypeConverter.cs | 53 +--
.../Configuration/TypeConverter.cs | 0
BepInEx.Core/ConsoleManager.cs | 98 ++++
.../ConsoleEncoding.Buffers.cs | 0
.../ConsoleEncoding.PInvoke.cs | 0
.../ConsoleEncoding/ConsoleEncoding.cs | 0
.../ConsoleUtil/ConsoleWindow.cs | 11 -
{BepInEx => BepInEx.Core}/ConsoleUtil/Kon.cs | 0
.../ConsoleUtil/SafeConsole.cs | 0
.../Contract/Attributes.cs | 2 +-
BepInEx.Core/Contract/IPlugin.cs | 28 ++
.../Contract/PluginInfo.cs | 4 +-
.../Logging/ConsoleLogListener.cs | 0
.../Logging/DiskLogListener.cs | 12 +-
.../Logging/ILogListener.cs | 0
.../Logging/ILogSource.cs | 0
.../Logging/LogEventArgs.cs | 0
{BepInEx => BepInEx.Core}/Logging/LogLevel.cs | 0
{BepInEx => BepInEx.Core}/Logging/Logger.cs | 0
.../Logging/ManualLogSource.cs | 0
.../Logging/TraceLogSource.cs | 0
{BepInEx => BepInEx.Core}/Paths.cs | 15 +-
.../Properties/AssemblyInfo.cs | 7 +-
{BepInEx => BepInEx.Core}/Utility.cs | 13 +
{BepInEx => BepInEx.Core}/packages.config | 0
BepInEx.Patcher/BepInEx.Patcher.csproj | 7 +-
.../BepInEx.Preloader.Core.csproj | 72 +++
.../InternalPreloaderLogger.cs | 13 +
.../Logging}/PreloaderLogWriter.cs | 2 +-
.../Patching/AssemblyPatcher.cs | 200 ++++----
.../Patching/PatcherPlugin.cs | 4 +-
.../Properties/AssemblyInfo.cs | 36 ++
.../RuntimeFixes/HarmonyFixes.cs | 10 +-
.../BepInEx.Preloader.Unity.csproj | 51 ++-
.../DoorstopEntrypoint.cs | 17 +-
.../EnvVars.cs | 0
.../Properties/AssemblyInfo.cs | 36 ++
.../RuntimeFixes/TraceFix.cs | 0
.../RuntimeFixes/UnityPatches.cs | 2 +-
.../UnityPreloader.cs | 119 ++---
.../packages.config | 4 +-
.../BaseUnityPlugin.cs | 19 +-
BepInEx.Unity/BepInEx.Unity.csproj | 74 +++
BepInEx.Unity/BepInExInstance.cs | 13 +
BepInEx.Unity/Bootstrap/UnityChainloader.cs | 118 +++++
.../Configuration/KeyboardShortcut.cs | 0
.../Logging/UnityLogListener.cs | 5 +-
.../Logging/UnityLogSource.cs | 5 +-
.../Properties/AssemblyInfo.cs | 13 +-
{BepInEx => BepInEx.Unity}/ThreadingHelper.cs | 0
BepInEx.Unity/UnityTomlTypeConverters.cs | 41 ++
BepInEx.Unity/packages.config | 4 +
BepInEx.sln | 40 +-
BepInEx/Bootstrap/Chainloader.cs | 428 ------------------
BepInExTests/BepInExTests.csproj | 6 +-
build.cake | 4 +-
70 files changed, 1244 insertions(+), 764 deletions(-)
rename BepInEx/BepInEx.csproj => BepInEx.Core/BepInEx.Core.csproj (89%)
create mode 100644 BepInEx.Core/Bootstrap/BaseChainloader.cs
rename {BepInEx => BepInEx.Core}/Bootstrap/TypeLoader.cs (86%)
rename {BepInEx => BepInEx.Core}/Configuration/AcceptableValueBase.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/AcceptableValueList.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/AcceptableValueRange.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/ConfigDefinition.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/ConfigDescription.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/ConfigEntryBase.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/ConfigFile.cs (99%)
rename {BepInEx => BepInEx.Core}/Configuration/ConfigWrapper.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/SettingChangedEventArgs.cs (100%)
rename {BepInEx => BepInEx.Core}/Configuration/TomlTypeConverter.cs (81%)
rename {BepInEx => BepInEx.Core}/Configuration/TypeConverter.cs (100%)
create mode 100644 BepInEx.Core/ConsoleManager.cs
rename {BepInEx => BepInEx.Core}/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs (100%)
rename {BepInEx => BepInEx.Core}/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs (100%)
rename {BepInEx => BepInEx.Core}/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs (100%)
rename {BepInEx => BepInEx.Core}/ConsoleUtil/ConsoleWindow.cs (88%)
rename {BepInEx => BepInEx.Core}/ConsoleUtil/Kon.cs (100%)
rename {BepInEx => BepInEx.Core}/ConsoleUtil/SafeConsole.cs (100%)
rename {BepInEx => BepInEx.Core}/Contract/Attributes.cs (99%)
create mode 100644 BepInEx.Core/Contract/IPlugin.cs
rename {BepInEx => BepInEx.Core}/Contract/PluginInfo.cs (95%)
rename {BepInEx => BepInEx.Core}/Logging/ConsoleLogListener.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/DiskLogListener.cs (86%)
rename {BepInEx => BepInEx.Core}/Logging/ILogListener.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/ILogSource.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/LogEventArgs.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/LogLevel.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/Logger.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/ManualLogSource.cs (100%)
rename {BepInEx => BepInEx.Core}/Logging/TraceLogSource.cs (100%)
rename {BepInEx => BepInEx.Core}/Paths.cs (84%)
rename {BepInEx => BepInEx.Core}/Properties/AssemblyInfo.cs (88%)
rename {BepInEx => BepInEx.Core}/Utility.cs (96%)
rename {BepInEx => BepInEx.Core}/packages.config (100%)
create mode 100644 BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
create mode 100644 BepInEx.Preloader.Core/InternalPreloaderLogger.cs
rename {BepInEx.Preloader/Logger => BepInEx.Preloader.Core/Logging}/PreloaderLogWriter.cs (98%)
rename {BepInEx.Preloader => BepInEx.Preloader.Core}/Patching/AssemblyPatcher.cs (67%)
rename {BepInEx.Preloader => BepInEx.Preloader.Core}/Patching/PatcherPlugin.cs (93%)
create mode 100644 BepInEx.Preloader.Core/Properties/AssemblyInfo.cs
rename {BepInEx.Preloader => BepInEx.Preloader.Core}/RuntimeFixes/HarmonyFixes.cs (66%)
rename BepInEx.Preloader/BepInEx.Preloader.csproj => BepInEx.Preloader.Unity/BepInEx.Preloader.Unity.csproj (69%)
rename BepInEx.Preloader/Entrypoint.cs => BepInEx.Preloader.Unity/DoorstopEntrypoint.cs (87%)
rename {BepInEx.Preloader => BepInEx.Preloader.Unity}/EnvVars.cs (100%)
create mode 100644 BepInEx.Preloader.Unity/Properties/AssemblyInfo.cs
rename {BepInEx.Preloader => BepInEx.Preloader.Unity}/RuntimeFixes/TraceFix.cs (100%)
rename {BepInEx.Preloader => BepInEx.Preloader.Unity}/RuntimeFixes/UnityPatches.cs (98%)
rename BepInEx.Preloader/Preloader.cs => BepInEx.Preloader.Unity/UnityPreloader.cs (68%)
rename {BepInEx.Preloader => BepInEx.Preloader.Unity}/packages.config (56%)
rename {BepInEx/Contract => BepInEx.Unity}/BaseUnityPlugin.cs (80%)
create mode 100644 BepInEx.Unity/BepInEx.Unity.csproj
create mode 100644 BepInEx.Unity/BepInExInstance.cs
create mode 100644 BepInEx.Unity/Bootstrap/UnityChainloader.cs
rename {BepInEx => BepInEx.Unity}/Configuration/KeyboardShortcut.cs (100%)
rename {BepInEx => BepInEx.Unity}/Logging/UnityLogListener.cs (94%)
rename {BepInEx => BepInEx.Unity}/Logging/UnityLogSource.cs (96%)
rename {BepInEx.Preloader => BepInEx.Unity}/Properties/AssemblyInfo.cs (77%)
rename {BepInEx => BepInEx.Unity}/ThreadingHelper.cs (100%)
create mode 100644 BepInEx.Unity/UnityTomlTypeConverters.cs
create mode 100644 BepInEx.Unity/packages.config
delete mode 100644 BepInEx/Bootstrap/Chainloader.cs
diff --git a/BepInEx.Bootstrap/BepInEx.Bootstrap.csproj b/BepInEx.Bootstrap/BepInEx.Bootstrap.csproj
index 856be7b..d0f2bea 100644
--- a/BepInEx.Bootstrap/BepInEx.Bootstrap.csproj
+++ b/BepInEx.Bootstrap/BepInEx.Bootstrap.csproj
@@ -30,11 +30,15 @@
-
+
{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}
BepInEx
False
+
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}
+ BepInEx.Unity
+
\ No newline at end of file
diff --git a/BepInEx.Bootstrap/Linker.cs b/BepInEx.Bootstrap/Linker.cs
index 2cad113..c0fab04 100644
--- a/BepInEx.Bootstrap/Linker.cs
+++ b/BepInEx.Bootstrap/Linker.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using BepInEx.Unity.Bootstrap;
namespace BepInEx.Bootstrap
{
@@ -6,8 +7,10 @@ namespace BepInEx.Bootstrap
{
public static void StartBepInEx()
{
- Chainloader.Initialize(Process.GetCurrentProcess().MainModule.FileName);
- Chainloader.Start();
+ var chainloader = new UnityChainloader();
+
+ chainloader.Initialize(Process.GetCurrentProcess().MainModule.FileName);
+ chainloader.Execute();
}
}
}
\ No newline at end of file
diff --git a/BepInEx/BepInEx.csproj b/BepInEx.Core/BepInEx.Core.csproj
similarity index 89%
rename from BepInEx/BepInEx.csproj
rename to BepInEx.Core/BepInEx.Core.csproj
index 441a322..c7c1bd7 100644
--- a/BepInEx/BepInEx.csproj
+++ b/BepInEx.Core/BepInEx.Core.csproj
@@ -6,8 +6,8 @@
AnyCPU
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}
Library
- BepInEx
- BepInEx
+ BepInEx.Core
+ BepInEx.Core
v3.5
512
true
@@ -21,7 +21,7 @@
TRACE
prompt
4
- ..\bin\BepInEx.xml
+ ..\bin\BepInEx.Core.xml
@@ -52,22 +52,19 @@
..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll
-
- ..\lib\UnityEngine.dll
- False
-
+
+
-
@@ -78,8 +75,6 @@
-
-
@@ -89,12 +84,9 @@
-
-
-
diff --git a/BepInEx.Core/Bootstrap/BaseChainloader.cs b/BepInEx.Core/Bootstrap/BaseChainloader.cs
new file mode 100644
index 0000000..e8e51ec
--- /dev/null
+++ b/BepInEx.Core/Bootstrap/BaseChainloader.cs
@@ -0,0 +1,352 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using BepInEx.Configuration;
+using BepInEx.Logging;
+using Mono.Cecil;
+
+namespace BepInEx.Bootstrap
+{
+ public abstract class BaseChainloader
+ {
+ #region Contract
+
+ protected virtual string ConsoleTitle => $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
+
+ private bool _initialized = false;
+
+ public Dictionary Plugins { get; } = new Dictionary();
+
+ public List DependencyErrors { get; } = new List();
+
+ //protected BaseChainloader()
+ //{
+ // Plugins = new ReadOnlyCollection(_plugins);
+ //}
+
+ public virtual void Initialize(string gameExePath = null)
+ {
+ if (_initialized)
+ throw new InvalidOperationException("Chainloader cannot be initialized multiple times");
+
+ // 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);
+ }
+
+ InitializeLoggers();
+
+ if (!Directory.Exists(Paths.PluginPath))
+ Directory.CreateDirectory(Paths.PluginPath);
+
+ if (!Directory.Exists(Paths.PatcherPluginPath))
+ Directory.CreateDirectory(Paths.PatcherPluginPath);
+
+ _initialized = true;
+
+ Logger.LogMessage("Chainloader initialized");
+ }
+
+ protected virtual void InitializeLoggers()
+ {
+ if (ConsoleManager.ConfigConsoleEnabled.Value && !ConsoleManager.ConsoleActive)
+ ConsoleManager.CreateConsole();
+
+ if (ConsoleManager.ConsoleActive)
+ {
+ if (!Logger.Listeners.Any(x => x is ConsoleLogListener))
+ Logger.Listeners.Add(new ConsoleLogListener());
+
+ ConsoleManager.SetConsoleTitle(ConsoleTitle);
+ }
+
+ if (ConfigDiskLogging.Value)
+ Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value));
+
+ if (!TraceLogSource.IsListening)
+ Logger.Sources.Add(TraceLogSource.CreateSource());
+ }
+
+ protected virtual IList DiscoverPlugins()
+ {
+ var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader");
+
+ return pluginsToLoad.SelectMany(p => p.Value).ToList();
+ }
+
+ protected virtual IList ModifyLoadOrder(IList plugins)
+ {
+ // We use a sorted dictionary to ensure consistent load order
+ var dependencyDict = new SortedDictionary>(StringComparer.InvariantCultureIgnoreCase);
+ var pluginsByGuid = new Dictionary();
+
+ foreach (var pluginInfoGroup in plugins.GroupBy(info => info.Metadata.GUID))
+ {
+ var alreadyLoaded = false;
+ foreach (var pluginInfo in pluginInfoGroup.OrderByDescending(x => x.Metadata.Version))
+ {
+ if (alreadyLoaded)
+ {
+ Logger.LogWarning($"Skipping because a newer version exists [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
+ continue;
+ }
+
+ alreadyLoaded = true;
+
+ // Perform checks that will prevent loading plugins in this run
+ var filters = pluginInfo.Processes.ToList();
+ bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase));
+
+ if (invalidProcessName)
+ {
+ Logger.LogWarning($"Skipping because of process filters [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
+ continue;
+ }
+
+ dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
+ pluginsByGuid[pluginInfo.Metadata.GUID] = pluginInfo;
+ }
+ }
+
+ foreach (var pluginInfo in pluginsByGuid.Values.ToList())
+ {
+ if (pluginInfo.Incompatibilities.Any(incompatibility => pluginsByGuid.ContainsKey(incompatibility.IncompatibilityGUID)))
+ {
+ pluginsByGuid.Remove(pluginInfo.Metadata.GUID);
+ dependencyDict.Remove(pluginInfo.Metadata.GUID);
+
+ var incompatiblePlugins = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID).Where(x => pluginsByGuid.ContainsKey(x)).ToArray();
+ string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}";
+ DependencyErrors.Add(message);
+ Logger.LogError(message);
+ }
+ else if (PluginTargetsWrongBepin(pluginInfo))
+ {
+ string message = $@"Plugin [{pluginInfo.Metadata.Name}] targets a wrong version of BepInEx ({pluginInfo.TargettedBepInExVersion}) and might not work until you update";
+ DependencyErrors.Add(message);
+ Logger.LogWarning(message);
+ }
+ }
+
+ var emptyDependencies = new string[0];
+
+ // Sort plugins by their dependencies.
+ // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list.
+ var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList();
+
+ return sortedPlugins.Select(x => pluginsByGuid[x]).ToList();
+ }
+
+ public virtual void Execute()
+ {
+ try
+ {
+ var plugins = DiscoverPlugins();
+
+ Logger.LogInfo($"{plugins.Count} plugins to load");
+
+ ModifyLoadOrder(plugins);
+
+ var invalidPlugins = new HashSet();
+ var processedPlugins = new Dictionary();
+ var loadedAssemblies = new Dictionary();
+
+ foreach (var plugin in plugins)
+ {
+ var dependsOnInvalidPlugin = false;
+ var missingDependencies = new List();
+ foreach (var dependency in plugin.Dependencies)
+ {
+ // If the depenency wasn't already processed, it's missing altogether
+ bool depenencyExists = processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
+ if (!depenencyExists || pluginVersion < dependency.MinimumVersion)
+ {
+ // If the dependency is hard, collect it into a list to show
+ if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0)
+ missingDependencies.Add(dependency);
+ continue;
+ }
+
+ // If the dependency is invalid (e.g. has missing depedencies), report that to the user
+ if (invalidPlugins.Contains(dependency.DependencyGUID))
+ {
+ dependsOnInvalidPlugin = true;
+ break;
+ }
+ }
+
+ processedPlugins.Add(plugin.Metadata.GUID, plugin.Metadata.Version);
+
+ if (dependsOnInvalidPlugin)
+ {
+ string message = $"Skipping [{plugin.Metadata.Name}] because it has a dependency that was not loaded. See previous errors for details.";
+ DependencyErrors.Add(message);
+ Logger.LogWarning(message);
+ continue;
+ }
+
+ if (missingDependencies.Count != 0)
+ {
+ bool IsEmptyVersion(Version v) => v.Major == 0 && v.Minor == 0 && v.Build <= 0 && v.Revision <= 0;
+
+ string message = $@"Could not load [{plugin.Metadata.Name}] because it has missing dependencies: {
+ string.Join(", ", missingDependencies.Select(s => IsEmptyVersion(s.MinimumVersion) ? s.DependencyGUID : $"{s.DependencyGUID} (v{s.MinimumVersion} or newer)").ToArray())
+ }";
+ DependencyErrors.Add(message);
+ Logger.LogError(message);
+
+ invalidPlugins.Add(plugin.Metadata.GUID);
+ continue;
+ }
+
+ try
+ {
+ Logger.LogInfo($"Loading [{plugin.Metadata.Name} {plugin.Metadata.Version}]");
+
+ if (!loadedAssemblies.TryGetValue(plugin.Location, out var ass))
+ loadedAssemblies[plugin.Location] = ass = Assembly.LoadFile(plugin.Location);
+
+ Plugins[plugin.Metadata.GUID] = plugin;
+ plugin.Instance = LoadPlugin(plugin, ass);
+
+ //_plugins.Add((TPlugin)plugin.Instance);
+ }
+ catch (Exception ex)
+ {
+ invalidPlugins.Add(plugin.Metadata.GUID);
+ Plugins.Remove(plugin.Metadata.GUID);
+
+ Logger.LogError($"Error loading [{plugin.Metadata.Name}] : {ex.Message}");
+ if (ex is ReflectionTypeLoadException re)
+ Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
+ else
+ Logger.LogDebug(ex);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Error occurred starting the game");
+ Logger.LogDebug(ex);
+ }
+
+ Logger.LogMessage("Chainloader startup complete");
+ }
+
+ public abstract TPlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly);
+
+ #endregion
+
+ private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._\-]+$");
+
+ public static PluginInfo ToPluginInfo(TypeDefinition type, string assemblyLocation)
+ {
+ if (type.IsInterface || type.IsAbstract)
+ return null;
+
+ try
+ {
+ if (!type.IsSubtypeOf(typeof(TPlugin)))
+ return null;
+ }
+ catch (AssemblyResolutionException)
+ {
+ // Can happen if this type inherits a type from an assembly that can't be found. Safe to assume it's not a plugin.
+ return null;
+ }
+
+ var metadata = BepInPlugin.FromCecilType(type);
+
+ // Perform checks that will prevent the plugin from being loaded in ALL cases
+ if (metadata == null)
+ {
+ Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified");
+ return null;
+ }
+
+ if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
+ {
+ Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
+ return null;
+ }
+
+ if (metadata.Version == null)
+ {
+ Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid.");
+ return null;
+ }
+
+ if (metadata.Name == null)
+ {
+ Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null.");
+ return null;
+ }
+
+ var filters = BepInProcess.FromCecilType(type);
+ var dependencies = BepInDependency.FromCecilType(type);
+ var incompatibilities = BepInIncompatibility.FromCecilType(type);
+
+ var bepinVersion = type.Module.AssemblyReferences.FirstOrDefault(reference => reference.Name == "BepInEx")?.Version ?? new Version();
+
+ return new PluginInfo
+ {
+ Metadata = metadata,
+ Processes = filters,
+ Dependencies = dependencies,
+ Incompatibilities = incompatibilities,
+ TypeName = type.FullName,
+ TargettedBepInExVersion = bepinVersion,
+ Location = assemblyLocation
+ };
+ }
+
+ protected static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
+ protected static readonly Version CurrentAssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
+
+ 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(TPlugin).FullName))
+ return false;
+
+ return true;
+ }
+
+ protected static bool PluginTargetsWrongBepin(PluginInfo pluginInfo)
+ {
+ var pluginTarget = pluginInfo.TargettedBepInExVersion;
+ // X.X.X.x - compare normally. x.x.x.X - nightly build number, ignore
+ if (pluginTarget.Major != CurrentAssemblyVersion.Major) return true;
+ if (pluginTarget.Minor > CurrentAssemblyVersion.Minor) return true;
+ if (pluginTarget.Minor < CurrentAssemblyVersion.Minor) return false;
+ return pluginTarget.Build > CurrentAssemblyVersion.Build;
+ }
+
+ #region Config
+
+ private static readonly ConfigEntry ConfigDiskAppend = ConfigFile.CoreConfig.Bind(
+ "Logging.Disk", "AppendLog",
+ false,
+ "Appends to the log file instead of overwriting, on game startup.");
+
+ private static readonly ConfigEntry ConfigDiskLogging = ConfigFile.CoreConfig.Bind(
+ "Logging.Disk", "Enabled",
+ true,
+ "Enables writing log messages to disk.");
+
+ private static readonly ConfigEntry ConfigDiskConsoleDisplayedLevel = ConfigFile.CoreConfig.Bind(
+ "Logging.Disk", "DisplayedLogLevel",
+ LogLevel.Info,
+ "Only displays the specified log level and above in the console output.");
+
+ #endregion
+ }
+}
diff --git a/BepInEx/Bootstrap/TypeLoader.cs b/BepInEx.Core/Bootstrap/TypeLoader.cs
similarity index 86%
rename from BepInEx/Bootstrap/TypeLoader.cs
rename to BepInEx.Core/Bootstrap/TypeLoader.cs
index 9a4b865..ce8ade8 100644
--- a/BepInEx/Bootstrap/TypeLoader.cs
+++ b/BepInEx.Core/Bootstrap/TypeLoader.cs
@@ -51,25 +51,34 @@ namespace BepInEx.Bootstrap
///
public static class TypeLoader
{
- private static readonly DefaultAssemblyResolver resolver;
+ public static readonly DefaultAssemblyResolver CecilResolver;
private static readonly ReaderParameters readerParameters;
+ public static HashSet SearchDirectories = new HashSet();
+
static TypeLoader()
{
- resolver = new DefaultAssemblyResolver();
- readerParameters = new ReaderParameters { AssemblyResolver = resolver };
+ CecilResolver = new DefaultAssemblyResolver();
+ readerParameters = new ReaderParameters { AssemblyResolver = CecilResolver };
- resolver.ResolveFailure += (sender, reference) =>
+ CecilResolver.ResolveFailure += CecilResolveOnFailure;
+ }
+
+ public static AssemblyDefinition CecilResolveOnFailure(object sender, AssemblyNameReference reference)
+ {
+ var name = new AssemblyName(reference.FullName);
+
+ if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out var assembly) ||
+ Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly))
+ return assembly;
+
+ foreach (var dir in SearchDirectories)
{
- var name = new AssemblyName(reference.FullName);
-
- if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out var assembly) ||
- Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly) ||
- Utility.TryResolveDllAssembly(name, Paths.ManagedPath, readerParameters, out assembly))
+ if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out assembly))
return assembly;
+ }
- return AssemblyResolve?.Invoke(sender, reference);
- };
+ return AssemblyResolve?.Invoke(sender, reference);
}
public static event AssemblyResolveEventHandler AssemblyResolve;
@@ -83,7 +92,7 @@ namespace BepInEx.Bootstrap
/// A filter function to quickly determine if the assembly can be loaded.
/// The name of the cache to get cached types from.
/// A dictionary of all assemblies in the directory and the list of type metadatas of types that match the selector.
- public static Dictionary> FindPluginTypes(string directory, Func typeSelector, Func assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
+ public static Dictionary> FindPluginTypes(string directory, Func typeSelector, Func assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
{
var result = new Dictionary>();
Dictionary> cache = null;
@@ -113,7 +122,9 @@ namespace BepInEx.Bootstrap
continue;
}
- var matches = ass.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
+ var matches = ass.MainModule.Types
+ .Select(t => typeSelector(t, dll))
+ .Where(t => t != null).ToList();
result[dll] = matches;
ass.Dispose();
}
diff --git a/BepInEx/Configuration/AcceptableValueBase.cs b/BepInEx.Core/Configuration/AcceptableValueBase.cs
similarity index 100%
rename from BepInEx/Configuration/AcceptableValueBase.cs
rename to BepInEx.Core/Configuration/AcceptableValueBase.cs
diff --git a/BepInEx/Configuration/AcceptableValueList.cs b/BepInEx.Core/Configuration/AcceptableValueList.cs
similarity index 100%
rename from BepInEx/Configuration/AcceptableValueList.cs
rename to BepInEx.Core/Configuration/AcceptableValueList.cs
diff --git a/BepInEx/Configuration/AcceptableValueRange.cs b/BepInEx.Core/Configuration/AcceptableValueRange.cs
similarity index 100%
rename from BepInEx/Configuration/AcceptableValueRange.cs
rename to BepInEx.Core/Configuration/AcceptableValueRange.cs
diff --git a/BepInEx/Configuration/ConfigDefinition.cs b/BepInEx.Core/Configuration/ConfigDefinition.cs
similarity index 100%
rename from BepInEx/Configuration/ConfigDefinition.cs
rename to BepInEx.Core/Configuration/ConfigDefinition.cs
diff --git a/BepInEx/Configuration/ConfigDescription.cs b/BepInEx.Core/Configuration/ConfigDescription.cs
similarity index 100%
rename from BepInEx/Configuration/ConfigDescription.cs
rename to BepInEx.Core/Configuration/ConfigDescription.cs
diff --git a/BepInEx/Configuration/ConfigEntryBase.cs b/BepInEx.Core/Configuration/ConfigEntryBase.cs
similarity index 100%
rename from BepInEx/Configuration/ConfigEntryBase.cs
rename to BepInEx.Core/Configuration/ConfigEntryBase.cs
diff --git a/BepInEx/Configuration/ConfigFile.cs b/BepInEx.Core/Configuration/ConfigFile.cs
similarity index 99%
rename from BepInEx/Configuration/ConfigFile.cs
rename to BepInEx.Core/Configuration/ConfigFile.cs
index 15e7de0..3a9ed6d 100644
--- a/BepInEx/Configuration/ConfigFile.cs
+++ b/BepInEx.Core/Configuration/ConfigFile.cs
@@ -16,7 +16,7 @@ namespace BepInEx.Configuration
{
private readonly BepInPlugin _ownerMetadata;
- internal static ConfigFile CoreConfig { get; } = new ConfigFile(Paths.BepInExConfigPath, true);
+ public static ConfigFile CoreConfig { get; } = new ConfigFile(Paths.BepInExConfigPath, true);
///
/// All config entries inside
diff --git a/BepInEx/Configuration/ConfigWrapper.cs b/BepInEx.Core/Configuration/ConfigWrapper.cs
similarity index 100%
rename from BepInEx/Configuration/ConfigWrapper.cs
rename to BepInEx.Core/Configuration/ConfigWrapper.cs
diff --git a/BepInEx/Configuration/SettingChangedEventArgs.cs b/BepInEx.Core/Configuration/SettingChangedEventArgs.cs
similarity index 100%
rename from BepInEx/Configuration/SettingChangedEventArgs.cs
rename to BepInEx.Core/Configuration/SettingChangedEventArgs.cs
diff --git a/BepInEx/Configuration/TomlTypeConverter.cs b/BepInEx.Core/Configuration/TomlTypeConverter.cs
similarity index 81%
rename from BepInEx/Configuration/TomlTypeConverter.cs
rename to BepInEx.Core/Configuration/TomlTypeConverter.cs
index 1e31e5f..c2a990f 100644
--- a/BepInEx/Configuration/TomlTypeConverter.cs
+++ b/BepInEx.Core/Configuration/TomlTypeConverter.cs
@@ -4,7 +4,6 @@ using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
-using UnityEngine;
using Logger = BepInEx.Logging.Logger;
namespace BepInEx.Configuration
@@ -146,17 +145,14 @@ namespace BepInEx.Configuration
///
public static TypeConverter GetConverter(Type valueType)
{
- if (valueType == null) throw new ArgumentNullException(nameof(valueType));
+ if (valueType == null)
+ throw new ArgumentNullException(nameof(valueType));
if (valueType.IsEnum)
return TypeConverters[typeof(Enum)];
- if (!TypeConverters.TryGetValue(valueType, out var result) && !_lazyLoadedConverters)
- {
- _lazyLoadedConverters = true;
- LazyLoadConverters();
- TypeConverters.TryGetValue(valueType, out result);
- }
+ TypeConverters.TryGetValue(valueType, out var result);
+
return result;
}
@@ -194,13 +190,6 @@ namespace BepInEx.Configuration
return TypeConverters.Keys;
}
- private static bool _lazyLoadedConverters;
- private static void LazyLoadConverters()
- {
- try { LazyTomlConverterLoader.AddUnityEngineConverters(); }
- catch (Exception ex) { Logger.LogWarning("Failed to load UnityEngine Toml converters - " + ex.Message); }
- }
-
private static string Escape(this string txt)
{
if (string.IsNullOrEmpty(txt)) return string.Empty;
@@ -309,38 +298,4 @@ namespace BepInEx.Configuration
return stringBuilder.ToString();
}
}
-
- ///
- /// For types that are in assemblies that can't get loaded before preloader runs (or it won't work on these assemblies)
- ///
- internal static class LazyTomlConverterLoader
- {
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static void AddUnityEngineConverters()
- {
- var colorConverter = new TypeConverter
- {
- ConvertToString = (obj, type) => ColorUtility.ToHtmlStringRGBA((Color)obj),
- ConvertToObject = (str, type) =>
- {
- if (!ColorUtility.TryParseHtmlString("#" + str.Trim('#', ' '), out var c))
- throw new FormatException("Invalid color string, expected hex #RRGGBBAA");
- return c;
- },
- };
-
- TomlTypeConverter.AddConverter(typeof(Color), colorConverter);
-
- var jsonConverter = new TypeConverter
- {
- ConvertToString = (obj, type) => JsonUtility.ToJson(obj),
- ConvertToObject = (str, type) => JsonUtility.FromJson(type: type, json: str),
- };
-
- TomlTypeConverter.AddConverter(typeof(Vector2), jsonConverter);
- TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
- TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
- TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
- }
- }
}
\ No newline at end of file
diff --git a/BepInEx/Configuration/TypeConverter.cs b/BepInEx.Core/Configuration/TypeConverter.cs
similarity index 100%
rename from BepInEx/Configuration/TypeConverter.cs
rename to BepInEx.Core/Configuration/TypeConverter.cs
diff --git a/BepInEx.Core/ConsoleManager.cs b/BepInEx.Core/ConsoleManager.cs
new file mode 100644
index 0000000..2a02a09
--- /dev/null
+++ b/BepInEx.Core/ConsoleManager.cs
@@ -0,0 +1,98 @@
+using System;
+using System.IO;
+using System.Text;
+using BepInEx.Configuration;
+using UnityInjector.ConsoleUtil;
+
+namespace BepInEx
+{
+ public static class ConsoleManager
+ {
+ public static bool ConsoleActive { get; private set; }
+
+ public static TextWriter StandardOut => ConsoleWindow.StandardOut;
+
+ public static void CreateConsole()
+ {
+ if (ConsoleActive)
+ return;
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.Win32NT:
+ {
+ ConsoleWindow.Attach();
+ break;
+ }
+ default:
+ throw new PlatformNotSupportedException("Spawning a console is not currently supported on this platform");
+ }
+
+ ConsoleActive = true;
+ }
+
+ public static void DetachConsole()
+ {
+ if (!ConsoleActive)
+ return;
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.Win32NT:
+ {
+ ConsoleWindow.Detach();
+ break;
+ }
+ default:
+ throw new PlatformNotSupportedException("Spawning a console is not currently supported on this platform");
+ }
+
+ ConsoleActive = false;
+ }
+
+ public static void SetConsoleEncoding(uint encodingCodePage)
+ {
+ if (!ConsoleActive)
+ throw new InvalidOperationException("Console is not currently active");
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.Win32NT:
+ {
+ ConsoleEncoding.ConsoleCodePage = encodingCodePage;
+ Console.OutputEncoding = Encoding.GetEncoding((int)encodingCodePage);
+ break;
+ }
+ default:
+ throw new PlatformNotSupportedException("Spawning a console is not currently supported on this platform");
+ }
+ }
+
+ public static void SetConsoleTitle(string title)
+ {
+ if (!ConsoleActive)
+ throw new InvalidOperationException("Console is not currently active");
+
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.Win32NT:
+ {
+ ConsoleWindow.Title = title;
+ break;
+ }
+ default:
+ throw new PlatformNotSupportedException("Spawning a console is not currently supported on this platform");
+ }
+ }
+
+ public static readonly ConfigEntry ConfigConsoleEnabled = ConfigFile.CoreConfig.Bind(
+ "Logging.Console", "Enabled",
+ false,
+ "Enables showing a console for log output.");
+
+ public static readonly ConfigEntry ConfigConsoleShiftJis = ConfigFile.CoreConfig.Bind(
+ "Logging.Console", "ShiftJisEncoding",
+ false,
+ "If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.");
+ }
+}
diff --git a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs b/BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs
similarity index 100%
rename from BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs
rename to BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs
diff --git a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs b/BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs
similarity index 100%
rename from BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs
rename to BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs
diff --git a/BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs b/BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs
similarity index 100%
rename from BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs
rename to BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs
diff --git a/BepInEx/ConsoleUtil/ConsoleWindow.cs b/BepInEx.Core/ConsoleUtil/ConsoleWindow.cs
similarity index 88%
rename from BepInEx/ConsoleUtil/ConsoleWindow.cs
rename to BepInEx.Core/ConsoleUtil/ConsoleWindow.cs
index 23f7ea4..e29fd5b 100644
--- a/BepInEx/ConsoleUtil/ConsoleWindow.cs
+++ b/BepInEx.Core/ConsoleUtil/ConsoleWindow.cs
@@ -7,22 +7,11 @@ using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
-using BepInEx.Configuration;
namespace UnityInjector.ConsoleUtil
{
internal class ConsoleWindow
{
- public static readonly ConfigEntry ConfigConsoleEnabled = ConfigFile.CoreConfig.Bind(
- "Logging.Console", "Enabled",
- false,
- "Enables showing a console for log output.");
-
- public static readonly ConfigEntry ConfigConsoleShiftJis = ConfigFile.CoreConfig.Bind(
- "Logging.Console", "ShiftJisEncoding",
- false,
- "If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.");
-
public static bool IsAttached { get; private set; }
private static IntPtr _cOut;
private static IntPtr _oOut;
diff --git a/BepInEx/ConsoleUtil/Kon.cs b/BepInEx.Core/ConsoleUtil/Kon.cs
similarity index 100%
rename from BepInEx/ConsoleUtil/Kon.cs
rename to BepInEx.Core/ConsoleUtil/Kon.cs
diff --git a/BepInEx/ConsoleUtil/SafeConsole.cs b/BepInEx.Core/ConsoleUtil/SafeConsole.cs
similarity index 100%
rename from BepInEx/ConsoleUtil/SafeConsole.cs
rename to BepInEx.Core/ConsoleUtil/SafeConsole.cs
diff --git a/BepInEx/Contract/Attributes.cs b/BepInEx.Core/Contract/Attributes.cs
similarity index 99%
rename from BepInEx/Contract/Attributes.cs
rename to BepInEx.Core/Contract/Attributes.cs
index ca65584..3516484 100644
--- a/BepInEx/Contract/Attributes.cs
+++ b/BepInEx.Core/Contract/Attributes.cs
@@ -300,7 +300,7 @@ namespace BepInEx
/// This class is appended to AssemblyInfo.cs when BepInEx is built via a CI pipeline.
/// It is mainly intended to signify that the current build is not a release build and is special, like for instance a bleeding edge build.
///
- internal class BuildInfoAttribute : Attribute
+ public class BuildInfoAttribute : Attribute
{
public string Info { get; }
diff --git a/BepInEx.Core/Contract/IPlugin.cs b/BepInEx.Core/Contract/IPlugin.cs
new file mode 100644
index 0000000..6043fb0
--- /dev/null
+++ b/BepInEx.Core/Contract/IPlugin.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using BepInEx.Configuration;
+using BepInEx.Logging;
+
+namespace BepInEx.Contract
+{
+ public interface IPlugin
+ {
+ ///
+ /// Information about this plugin as it was loaded.
+ ///
+ PluginInfo Info { get; }
+
+ ///
+ /// Logger instance tied to this plugin.
+ ///
+ ManualLogSource Logger { get; }
+
+ ///
+ /// Default config file tied to this plugin. The config file will not be created until
+ /// any settings are added and changed, or is called.
+ ///
+ ConfigFile Config { get; }
+ }
+}
diff --git a/BepInEx/Contract/PluginInfo.cs b/BepInEx.Core/Contract/PluginInfo.cs
similarity index 95%
rename from BepInEx/Contract/PluginInfo.cs
rename to BepInEx.Core/Contract/PluginInfo.cs
index 8d81579..9540849 100644
--- a/BepInEx/Contract/PluginInfo.cs
+++ b/BepInEx.Core/Contract/PluginInfo.cs
@@ -18,9 +18,9 @@ namespace BepInEx
public string Location { get; internal set; }
- public BaseUnityPlugin Instance { get; internal set; }
+ public object Instance { get; internal set; }
- internal string TypeName { get; set; }
+ public string TypeName { get; internal set; }
internal Version TargettedBepInExVersion { get; set; }
diff --git a/BepInEx/Logging/ConsoleLogListener.cs b/BepInEx.Core/Logging/ConsoleLogListener.cs
similarity index 100%
rename from BepInEx/Logging/ConsoleLogListener.cs
rename to BepInEx.Core/Logging/ConsoleLogListener.cs
diff --git a/BepInEx/Logging/DiskLogListener.cs b/BepInEx.Core/Logging/DiskLogListener.cs
similarity index 86%
rename from BepInEx/Logging/DiskLogListener.cs
rename to BepInEx.Core/Logging/DiskLogListener.cs
index 17a1d31..d38c372 100644
--- a/BepInEx/Logging/DiskLogListener.cs
+++ b/BepInEx.Core/Logging/DiskLogListener.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System.Collections.Generic;
+using System.IO;
using System.Text;
using System.Threading;
@@ -15,11 +16,8 @@ namespace BepInEx.Logging
public Timer FlushTimer { get; protected set; }
- public bool WriteFromUnityLog { get; set; }
-
- public DiskLogListener(string localPath, LogLevel displayedLogLevel = LogLevel.Info, bool appendLog = false, bool includeUnityLog = false)
+ public DiskLogListener(string localPath, LogLevel displayedLogLevel = LogLevel.Info, bool appendLog = false)
{
- WriteFromUnityLog = includeUnityLog;
DisplayedLogLevel = displayedLogLevel;
int counter = 1;
@@ -46,9 +44,11 @@ namespace BepInEx.Logging
FlushTimer = new Timer(o => { LogWriter?.Flush(); }, null, 2000, 2000);
}
+ public static HashSet BlacklistedSources = new HashSet();
+
public void LogEvent(object sender, LogEventArgs eventArgs)
{
- if (!WriteFromUnityLog && eventArgs.Source is UnityLogSource)
+ if (BlacklistedSources.Contains(eventArgs.Source.SourceName))
return;
if (eventArgs.Level.GetHighestLevel() > DisplayedLogLevel)
diff --git a/BepInEx/Logging/ILogListener.cs b/BepInEx.Core/Logging/ILogListener.cs
similarity index 100%
rename from BepInEx/Logging/ILogListener.cs
rename to BepInEx.Core/Logging/ILogListener.cs
diff --git a/BepInEx/Logging/ILogSource.cs b/BepInEx.Core/Logging/ILogSource.cs
similarity index 100%
rename from BepInEx/Logging/ILogSource.cs
rename to BepInEx.Core/Logging/ILogSource.cs
diff --git a/BepInEx/Logging/LogEventArgs.cs b/BepInEx.Core/Logging/LogEventArgs.cs
similarity index 100%
rename from BepInEx/Logging/LogEventArgs.cs
rename to BepInEx.Core/Logging/LogEventArgs.cs
diff --git a/BepInEx/Logging/LogLevel.cs b/BepInEx.Core/Logging/LogLevel.cs
similarity index 100%
rename from BepInEx/Logging/LogLevel.cs
rename to BepInEx.Core/Logging/LogLevel.cs
diff --git a/BepInEx/Logging/Logger.cs b/BepInEx.Core/Logging/Logger.cs
similarity index 100%
rename from BepInEx/Logging/Logger.cs
rename to BepInEx.Core/Logging/Logger.cs
diff --git a/BepInEx/Logging/ManualLogSource.cs b/BepInEx.Core/Logging/ManualLogSource.cs
similarity index 100%
rename from BepInEx/Logging/ManualLogSource.cs
rename to BepInEx.Core/Logging/ManualLogSource.cs
diff --git a/BepInEx/Logging/TraceLogSource.cs b/BepInEx.Core/Logging/TraceLogSource.cs
similarity index 100%
rename from BepInEx/Logging/TraceLogSource.cs
rename to BepInEx.Core/Logging/TraceLogSource.cs
diff --git a/BepInEx/Paths.cs b/BepInEx.Core/Paths.cs
similarity index 84%
rename from BepInEx/Paths.cs
rename to BepInEx.Core/Paths.cs
index b53616c..bc5026c 100644
--- a/BepInEx/Paths.cs
+++ b/BepInEx.Core/Paths.cs
@@ -8,12 +8,11 @@ namespace BepInEx
///
public static class Paths
{
- internal static void SetExecutablePath(string executablePath, string bepinRootPath = null, string managedPath = null)
+ public static void SetExecutablePath(string executablePath, string bepinRootPath = null)
{
ExecutablePath = executablePath;
ProcessName = Path.GetFileNameWithoutExtension(executablePath);
GameRootPath = Path.GetDirectoryName(executablePath);
- ManagedPath = managedPath ?? Utility.CombinePaths(GameRootPath, $"{ProcessName}_Data", "Managed");
BepInExRootPath = bepinRootPath ?? Path.Combine(GameRootPath, "BepInEx");
ConfigPath = Path.Combine(BepInExRootPath, "config");
BepInExConfigPath = Path.Combine(ConfigPath, "BepInEx.cfg");
@@ -24,13 +23,6 @@ namespace BepInEx
CachePath = Path.Combine(BepInExRootPath, "cache");
}
- internal static void SetManagedPath(string managedPath)
- {
- if (managedPath == null)
- return;
- ManagedPath = managedPath;
- }
-
internal static void SetPluginPath(string pluginPath)
{
PluginPath = Utility.CombinePaths(BepInExRootPath, pluginPath);
@@ -61,11 +53,6 @@ namespace BepInEx
///
public static string GameRootPath { get; private set; }
- ///
- /// The path to the Managed folder of the currently running Unity game.
- ///
- public static string ManagedPath { get; private set; }
-
///
/// The path to the config directory.
///
diff --git a/BepInEx/Properties/AssemblyInfo.cs b/BepInEx.Core/Properties/AssemblyInfo.cs
similarity index 88%
rename from BepInEx/Properties/AssemblyInfo.cs
rename to BepInEx.Core/Properties/AssemblyInfo.cs
index 5184dff..29ae258 100644
--- a/BepInEx/Properties/AssemblyInfo.cs
+++ b/BepInEx.Core/Properties/AssemblyInfo.cs
@@ -24,7 +24,8 @@ using BepInEx;
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4ffba620-f5ed-47f9-b90c-dad1316fd9b9")]
-[assembly: InternalsVisibleTo("BepInEx.Preloader")]
+[assembly: InternalsVisibleTo("BepInEx.Preloader.Core")]
+[assembly: InternalsVisibleTo("BepInEx.Unity")]
[assembly: InternalsVisibleTo("BepInExTests")]
// Version information for an assembly consists of the following four values:
@@ -37,5 +38,5 @@ using BepInEx;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.0.1.0")]
-[assembly: AssemblyFileVersion("5.0.1.0")]
\ No newline at end of file
+[assembly: AssemblyVersion("6.0.0.0")]
+[assembly: AssemblyFileVersion("6.0.0.0")]
\ No newline at end of file
diff --git a/BepInEx/Utility.cs b/BepInEx.Core/Utility.cs
similarity index 96%
rename from BepInEx/Utility.cs
rename to BepInEx.Core/Utility.cs
index b4f69f0..649c648 100644
--- a/BepInEx/Utility.cs
+++ b/BepInEx.Core/Utility.cs
@@ -237,5 +237,18 @@ namespace BepInEx
return false;
}
}
+
+ public static IEnumerable EnumerateAllMethods(this TypeDefinition type)
+ {
+ var currentType = type;
+
+ while (currentType != null)
+ {
+ foreach (var method in currentType.Methods)
+ yield return method;
+
+ currentType = currentType.BaseType?.Resolve();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/BepInEx/packages.config b/BepInEx.Core/packages.config
similarity index 100%
rename from BepInEx/packages.config
rename to BepInEx.Core/packages.config
diff --git a/BepInEx.Patcher/BepInEx.Patcher.csproj b/BepInEx.Patcher/BepInEx.Patcher.csproj
index d45766b..6a03f81 100644
--- a/BepInEx.Patcher/BepInEx.Patcher.csproj
+++ b/BepInEx.Patcher/BepInEx.Patcher.csproj
@@ -45,7 +45,12 @@
-
+
+
+ {eae9fae6-8011-45a3-8b6e-0c7f14210533}
+ BepInEx.Unity
+
+
diff --git a/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj b/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
new file mode 100644
index 0000000..b3198e1
--- /dev/null
+++ b/BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
@@ -0,0 +1,72 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {15F8BC38-A761-4F93-8903-1B531AC5D9F9}
+ Library
+ Properties
+ BepInEx.Preloader.Core
+ BepInEx.Preloader.Core
+ v3.5
+ 512
+ true
+
+
+ true
+ full
+ false
+ ..\bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ none
+ true
+ ..\bin\
+ TRACE
+ prompt
+ 4
+ ..\bin\BepInEx.Preloader.Core.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {4ffba620-f5ed-47f9-b90c-dad1316fd9b9}
+ BepInEx.Core
+
+
+ {54161cfe-ff42-4dde-b161-3a49545db5cd}
+ BepInEx.Harmony
+
+
+ {a15d6ee6-f954-415b-8605-8a8470cc87dc}
+ Harmony
+
+
+
+
+ 0.10.4
+
+
+ 20.4.3.1
+
+
+ 20.4.3.1
+
+
+
+
\ No newline at end of file
diff --git a/BepInEx.Preloader.Core/InternalPreloaderLogger.cs b/BepInEx.Preloader.Core/InternalPreloaderLogger.cs
new file mode 100644
index 0000000..f8cc6b0
--- /dev/null
+++ b/BepInEx.Preloader.Core/InternalPreloaderLogger.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using BepInEx.Logging;
+
+namespace BepInEx.Preloader.Core
+{
+ public static class PreloaderLogger
+ {
+ public static ManualLogSource Log { get; } = Logger.CreateLogSource("Preloader");
+ }
+}
diff --git a/BepInEx.Preloader/Logger/PreloaderLogWriter.cs b/BepInEx.Preloader.Core/Logging/PreloaderLogWriter.cs
similarity index 98%
rename from BepInEx.Preloader/Logger/PreloaderLogWriter.cs
rename to BepInEx.Preloader.Core/Logging/PreloaderLogWriter.cs
index ba2a7de..0426b2e 100644
--- a/BepInEx.Preloader/Logger/PreloaderLogWriter.cs
+++ b/BepInEx.Preloader.Core/Logging/PreloaderLogWriter.cs
@@ -5,7 +5,7 @@ using System.Text;
using BepInEx.ConsoleUtil;
using BepInEx.Logging;
-namespace BepInEx.Preloader
+namespace BepInEx.Preloader.Core.Logging
{
public class PreloaderConsoleListener : ILogListener
{
diff --git a/BepInEx.Preloader/Patching/AssemblyPatcher.cs b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
similarity index 67%
rename from BepInEx.Preloader/Patching/AssemblyPatcher.cs
rename to BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
index 0e27e3c..09a8d68 100644
--- a/BepInEx.Preloader/Patching/AssemblyPatcher.cs
+++ b/BepInEx.Preloader.Core/Patching/AssemblyPatcher.cs
@@ -8,41 +8,46 @@ using System.Text;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
-using BepInEx.Preloader.RuntimeFixes;
using Mono.Cecil;
-namespace BepInEx.Preloader.Patching
+namespace BepInEx.Preloader.Core
{
///
/// Delegate used in patching assemblies.
///
/// The assembly that is being patched.
- internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
+ public delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
///
/// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and
/// loading assemblies one at a time.
///
- internal static class AssemblyPatcher
+ public class AssemblyPatcher : IDisposable
{
private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
- public static List PatcherPlugins { get; } = new List();
-
- private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
+ ///
+ /// A list of plugins that will be initialized and executed, in the order of the list.
+ ///
+ public List PatcherPlugins { get; } = new List();
///
- /// Adds a single assembly patcher to the pool of applicable patches.
+ /// Contains a list of assemblies that will be patched and loaded into the runtime.
+ /// The dictionary has the name of the file, without any directories. These are used by the dumping functionality, and as such, these are also required to be unique. They do not have to be exactly the same as the real filename, however they have to be mapped deterministically.
+ /// Order is not respected, as it will be sorted by dependencies.
///
- /// Patcher to apply.
- public static void AddPatcher(PatcherPlugin patcher)
- {
- PatcherPlugins.Add(patcher);
- }
+ public Dictionary AssembliesToPatch { get; } = new Dictionary();
+
+ ///
+ /// The directory location as to where patched assemblies will be saved to and loaded from disk, for debugging purposes. Defaults to BepInEx/DumpedAssemblies
+ ///
+ public string DumpedAssembliesPath { get; set; } = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
+
+ public ManualLogSource Logger { get; } = new ManualLogSource("AssemblyPatcher");
private static T CreateDelegate(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
- private static PatcherPlugin ToPatcherPlugin(TypeDefinition type)
+ private static PatcherPlugin ToPatcherPlugin(TypeDefinition type, string assemblyPath)
{
if (type.IsInterface || type.IsAbstract && !type.IsSealed)
return null;
@@ -77,7 +82,7 @@ namespace BepInEx.Preloader.Patching
///
/// Directory to search patcher DLLs from.
/// A function that locates assembly patchers in a given managed assembly.
- public static void AddPatchersFromDirectory(string directory)
+ public void AddPatchersFromDirectory(string directory)
{
if (!Directory.Exists(directory))
return;
@@ -147,26 +152,88 @@ namespace BepInEx.Preloader.Patching
}
foreach (KeyValuePair patcher in sortedPatchers)
- AddPatcher(patcher.Value);
+ PatcherPlugins.Add(patcher.Value);
}
- private static void InitializePatchers()
- {
- foreach (var assemblyPatcher in PatcherPlugins)
- assemblyPatcher.Initializer?.Invoke();
- }
- private static void FinalizePatching()
+ ///
+ /// Adds all .dll assemblies in a directory to be patched and loaded by this patcher instance. Non-managed assemblies are skipped.
+ ///
+ /// The directory to search.
+ public void LoadAssemblyDirectory(string directory)
{
- foreach (var assemblyPatcher in PatcherPlugins)
- assemblyPatcher.Finalizer?.Invoke();
+ LoadAssemblyDirectory(directory, "dll");
}
///
- /// Releases all patchers to let them be collected by GC.
+ /// Adds all assemblies in a directory to be patched and loaded by this patcher instance. Non-managed assemblies are skipped.
///
- public static void DisposePatchers()
+ /// The directory to search.
+ /// The file extensions to attempt to load.
+ public void LoadAssemblyDirectory(string directory, params string[] assemblyExtensions)
{
+ var filesToSearch = assemblyExtensions
+ .SelectMany(ext => Directory.GetFiles(directory, "*." + ext));
+
+ foreach (string assemblyPath in filesToSearch)
+ {
+ if (!TryLoadAssembly(assemblyPath, out var assembly))
+ continue;
+
+ // NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
+ // System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
+ // It's also generally dangerous to change system.dll since so many things rely on it,
+ // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
+ if (assembly.Name.Name == "System" || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
+ {
+ assembly.Dispose();
+ continue;
+ }
+
+ AssembliesToPatch.Add(Path.GetFileName(assemblyPath), assembly);
+
+ //if (UnityPatches.AssemblyLocations.ContainsKey(assembly.FullName))
+ //{
+ // Logger.LogWarning($"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
+ // continue;
+ //}
+
+ //assemblies.Add(Path.GetFileName(assemblyPath), assembly);
+ //UnityPatches.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
+ }
+ }
+
+ ///
+ /// Attempts to load a managed assembly as an . Returns true if successful.
+ ///
+ /// The path of the assembly.
+ /// The loaded assembly. Null if not successful in loading.
+ public static bool TryLoadAssembly(string path, out AssemblyDefinition assembly)
+ {
+ try
+ {
+ assembly = AssemblyDefinition.ReadAssembly(path);
+ return true;
+ }
+ catch (BadImageFormatException)
+ {
+ // Not a managed assembly
+ assembly = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Performs work to dispose collection objects.
+ ///
+ public void Dispose()
+ {
+ foreach (var assembly in AssembliesToPatch)
+ assembly.Value.Dispose();
+
+ AssembliesToPatch.Clear();
+
+ // Clear to allow GC collection.
PatcherPlugins.Clear();
}
@@ -187,59 +254,26 @@ namespace BepInEx.Preloader.Patching
/// Applies patchers to all assemblies in the given directory and loads patched assemblies into memory.
///
/// Directory to load CLR assemblies from.
- public static void PatchAndLoad(string directory)
+ public void PatchAndLoad()
{
- // First, load patchable assemblies into Cecil
- var assemblies = new Dictionary();
-
- foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
- {
- AssemblyDefinition assembly;
-
- try
- {
- assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
- }
- catch (BadImageFormatException)
- {
- // Not a managed assembly, skip
- continue;
- }
-
- //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
- //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
- //It's also generally dangerous to change system.dll since so many things rely on it,
- // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
- if (assembly.Name.Name == "System" || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
- {
- assembly.Dispose();
- continue;
- }
-
- if (UnityPatches.AssemblyLocations.ContainsKey(assembly.FullName))
- {
- Logger.LogWarning($"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
- continue;
- }
-
- assemblies.Add(Path.GetFileName(assemblyPath), assembly);
- UnityPatches.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
- }
+ // First, create a copy of the assembly dictionary as the initializer can change them
+ var assemblies = new Dictionary(AssembliesToPatch);
// Next, initialize all the patchers
- InitializePatchers();
+ foreach (var assemblyPatcher1 in PatcherPlugins)
+ assemblyPatcher1.Initializer?.Invoke();
// Then, perform the actual patching
var patchedAssemblies = new HashSet();
var resolvedAssemblies = new Dictionary();
foreach (var assemblyPatcher in PatcherPlugins)
foreach (string targetDll in assemblyPatcher.TargetDLLs())
- if (assemblies.TryGetValue(targetDll, out var assembly))
+ if (AssembliesToPatch.TryGetValue(targetDll, out var assembly))
{
Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.TypeName}]");
assemblyPatcher.Patcher?.Invoke(ref assembly);
- assemblies[targetDll] = assembly;
+ AssembliesToPatch[targetDll] = assembly;
patchedAssemblies.Add(targetDll);
foreach (var resolvedAss in AppDomain.CurrentDomain.GetAssemblies())
@@ -298,30 +332,26 @@ namespace BepInEx.Preloader.Patching
// Not loading all assemblies is very important not only because of memory reasons,
// but because some games *rely* on that because of messed up internal dependencies.
if (patchedAssemblies.Contains(filename))
- Load(assembly, filename);
+ {
+ if (ConfigLoadDumpedAssemblies.Value)
+ Assembly.LoadFile(Path.Combine(DumpedAssembliesPath, filename));
+ else
+ {
+ using (var assemblyStream = new MemoryStream())
+ {
+ assembly.Write(assemblyStream);
+ Assembly.Load(assemblyStream.ToArray());
+ }
+ }
+ }
// Though we have to dispose of all assemblies regardless of them being patched or not
assembly.Dispose();
}
- //run all finalizers
- FinalizePatching();
- }
-
- ///
- /// Loads an individual assembly definition into the CLR.
- ///
- /// The assembly to load.
- public static void Load(AssemblyDefinition assembly, string filename)
- {
- if (ConfigLoadDumpedAssemblies.Value)
- Assembly.LoadFile(Path.Combine(DumpedAssembliesPath, filename));
- else
- using (var assemblyStream = new MemoryStream())
- {
- assembly.Write(assemblyStream);
- Assembly.Load(assemblyStream.ToArray());
- }
+ // Finally, run all finalizers
+ foreach (var assemblyPatcher2 in PatcherPlugins)
+ assemblyPatcher2.Finalizer?.Invoke();
}
#region Config
diff --git a/BepInEx.Preloader/Patching/PatcherPlugin.cs b/BepInEx.Preloader.Core/Patching/PatcherPlugin.cs
similarity index 93%
rename from BepInEx.Preloader/Patching/PatcherPlugin.cs
rename to BepInEx.Preloader.Core/Patching/PatcherPlugin.cs
index 390cc88..5ee01bd 100644
--- a/BepInEx.Preloader/Patching/PatcherPlugin.cs
+++ b/BepInEx.Preloader.Core/Patching/PatcherPlugin.cs
@@ -3,12 +3,12 @@ using System.Collections.Generic;
using System.IO;
using BepInEx.Bootstrap;
-namespace BepInEx.Preloader.Patching
+namespace BepInEx.Preloader.Core
{
///
/// A single assembly patcher.
///
- internal class PatcherPlugin : ICacheable
+ public class PatcherPlugin : ICacheable
{
///
/// Target assemblies to patch.
diff --git a/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs b/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..6007b08
--- /dev/null
+++ b/BepInEx.Preloader.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BepInEx.Preloader.Core")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BepInEx.Preloader.Core")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("15f8bc38-a761-4f93-8903-1b531ac5d9f9")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BepInEx.Preloader/RuntimeFixes/HarmonyFixes.cs b/BepInEx.Preloader.Core/RuntimeFixes/HarmonyFixes.cs
similarity index 66%
rename from BepInEx.Preloader/RuntimeFixes/HarmonyFixes.cs
rename to BepInEx.Preloader.Core/RuntimeFixes/HarmonyFixes.cs
index c010b24..2134459 100644
--- a/BepInEx.Preloader/RuntimeFixes/HarmonyFixes.cs
+++ b/BepInEx.Preloader.Core/RuntimeFixes/HarmonyFixes.cs
@@ -2,9 +2,9 @@
using System.Diagnostics;
using HarmonyLib;
-namespace BepInEx.Preloader.RuntimeFixes
+namespace BepInEx.Preloader.Core.RuntimeFixes
{
- internal static class HarmonyFixes
+ public static class HarmonyFixes
{
public static void Apply()
{
@@ -16,21 +16,21 @@ namespace BepInEx.Preloader.RuntimeFixes
}
catch (Exception e)
{
- Logging.Logger.LogError(e);
+ PreloaderLogger.Log.LogError(e);
}
}
private static void GetValue(Traverse __instance)
{
if (!__instance.FieldExists() && !__instance.MethodExists() && !__instance.TypeExists())
- Logging.Logger.LogWarning("Traverse.GetValue was called while not pointing at an existing Field, Property, Method or Type. The return value can be unexpected.\n" + new StackTrace());
+ PreloaderLogger.Log.LogWarning("Traverse.GetValue was called while not pointing at an existing Field, Property, Method or Type. The return value can be unexpected.\n" + new StackTrace());
}
private static void SetValue(Traverse __instance)
{
// If method exists it will crash inside traverse so only need to mention the field missing
if (!__instance.FieldExists() && !__instance.MethodExists())
- Logging.Logger.LogWarning("Traverse.SetValue was called while not pointing at an existing Field or Property. The call will have no effect.\n" + new StackTrace());
+ PreloaderLogger.Log.LogWarning("Traverse.SetValue was called while not pointing at an existing Field or Property. The call will have no effect.\n" + new StackTrace());
}
}
}
\ No newline at end of file
diff --git a/BepInEx.Preloader/BepInEx.Preloader.csproj b/BepInEx.Preloader.Unity/BepInEx.Preloader.Unity.csproj
similarity index 69%
rename from BepInEx.Preloader/BepInEx.Preloader.csproj
rename to BepInEx.Preloader.Unity/BepInEx.Preloader.Unity.csproj
index 87a9a4f..9533c81 100644
--- a/BepInEx.Preloader/BepInEx.Preloader.csproj
+++ b/BepInEx.Preloader.Unity/BepInEx.Preloader.Unity.csproj
@@ -4,15 +4,24 @@
Debug
AnyCPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}
+ {D404C973-441D-48ED-B266-C21320BA0D87}
Library
Properties
- BepInEx.Preloader
- BepInEx.Preloader
+ BepInEx.Preloader.Unity
+ BepInEx.Preloader.Unity
v3.5
512
true
+
+ true
+ full
+ false
+ ..\bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
none
true
@@ -20,7 +29,7 @@
TRACE
prompt
4
- ..\bin\BepInEx.Preloader.xml
+ ..\bin\BepInEx.Preloader.Unity.xml
@@ -35,33 +44,34 @@
..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll
-
- ..\packages\MonoMod.RuntimeDetour.20.3.5.1\lib\net35\MonoMod.RuntimeDetour.dll
- True
+
+ ..\packages\MonoMod.RuntimeDetour.20.4.3.1\lib\net35\MonoMod.RuntimeDetour.dll
-
- ..\packages\MonoMod.Utils.20.3.5.1\lib\net35\MonoMod.Utils.dll
- True
+
+ ..\packages\MonoMod.Utils.20.4.3.1\lib\net35\MonoMod.Utils.dll
+
-
-
-
-
+
-
-
-
+
-
+
+
+
+
{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}
- BepInEx
+ BepInEx.Core
+
+
+ {15f8bc38-a761-4f93-8903-1b531ac5d9f9}
+ BepInEx.Preloader.Core
{54161cfe-ff42-4dde-b161-3a49545db5cd}
@@ -72,8 +82,5 @@
Harmony
-
-
-
\ No newline at end of file
diff --git a/BepInEx.Preloader/Entrypoint.cs b/BepInEx.Preloader.Unity/DoorstopEntrypoint.cs
similarity index 87%
rename from BepInEx.Preloader/Entrypoint.cs
rename to BepInEx.Preloader.Unity/DoorstopEntrypoint.cs
index a50b60b..0057623 100644
--- a/BepInEx.Preloader/Entrypoint.cs
+++ b/BepInEx.Preloader.Unity/DoorstopEntrypoint.cs
@@ -1,20 +1,20 @@
using System;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
-namespace BepInEx.Preloader
+namespace BepInEx.Preloader.Unity
{
- internal static class PreloaderRunner
+ internal static class UnityPreloaderRunner
{
public static void PreloaderMain(string[] args)
{
string bepinPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
- Paths.SetExecutablePath(args[0], bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
+ Paths.SetExecutablePath(args[0], bepinPath);
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
- Preloader.Run();
+
+ UnityPreloader.Run(EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
}
private static Assembly LocalResolve(object sender, ResolveEventArgs args)
@@ -36,7 +36,7 @@ namespace BepInEx.Preloader
}
}
- internal static class Entrypoint
+ internal static class DoorstopEntrypoint
{
private static string preloaderPath;
@@ -65,8 +65,8 @@ namespace BepInEx.Preloader
// In some versions of Unity 4, Mono tries to resolve BepInEx.dll prematurely because of the call to Paths.SetExecutablePath
// To prevent that, we have to use reflection and a separate startup class so that we can install required assembly resolvers before the main code
- typeof(Entrypoint).Assembly.GetType($"BepInEx.Preloader.{nameof(PreloaderRunner)}")
- ?.GetMethod(nameof(PreloaderRunner.PreloaderMain))
+ typeof(DoorstopEntrypoint).Assembly.GetType($"BepInEx.Preloader.Unity.{nameof(UnityPreloaderRunner)}")
+ ?.GetMethod(nameof(UnityPreloaderRunner.PreloaderMain))
?.Invoke(null, new object[] { args });
AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;
@@ -90,6 +90,5 @@ namespace BepInEx.Preloader
return null;
}
}
-
}
}
\ No newline at end of file
diff --git a/BepInEx.Preloader/EnvVars.cs b/BepInEx.Preloader.Unity/EnvVars.cs
similarity index 100%
rename from BepInEx.Preloader/EnvVars.cs
rename to BepInEx.Preloader.Unity/EnvVars.cs
diff --git a/BepInEx.Preloader.Unity/Properties/AssemblyInfo.cs b/BepInEx.Preloader.Unity/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..5d342d3
--- /dev/null
+++ b/BepInEx.Preloader.Unity/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BepInEx.Preloader.Unity")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BepInEx.Preloader.Unity")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d404c973-441d-48ed-b266-c21320ba0d87")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BepInEx.Preloader/RuntimeFixes/TraceFix.cs b/BepInEx.Preloader.Unity/RuntimeFixes/TraceFix.cs
similarity index 100%
rename from BepInEx.Preloader/RuntimeFixes/TraceFix.cs
rename to BepInEx.Preloader.Unity/RuntimeFixes/TraceFix.cs
diff --git a/BepInEx.Preloader/RuntimeFixes/UnityPatches.cs b/BepInEx.Preloader.Unity/RuntimeFixes/UnityPatches.cs
similarity index 98%
rename from BepInEx.Preloader/RuntimeFixes/UnityPatches.cs
rename to BepInEx.Preloader.Unity/RuntimeFixes/UnityPatches.cs
index 821175b..4f26828 100644
--- a/BepInEx.Preloader/RuntimeFixes/UnityPatches.cs
+++ b/BepInEx.Preloader.Unity/RuntimeFixes/UnityPatches.cs
@@ -14,7 +14,7 @@ namespace BepInEx.Preloader.RuntimeFixes
public static void Apply()
{
- HarmonyInstance = HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance);
+ HarmonyInstance = HarmonyWrapper.PatchAll(typeof(UnityPatches));
try
{
diff --git a/BepInEx.Preloader/Preloader.cs b/BepInEx.Preloader.Unity/UnityPreloader.cs
similarity index 68%
rename from BepInEx.Preloader/Preloader.cs
rename to BepInEx.Preloader.Unity/UnityPreloader.cs
index ec27ba9..f8b73a0 100644
--- a/BepInEx.Preloader/Preloader.cs
+++ b/BepInEx.Preloader.Unity/UnityPreloader.cs
@@ -3,38 +3,48 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Text;
+using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
-using BepInEx.Preloader.Patching;
+using BepInEx.Preloader.Core;
+using BepInEx.Preloader.Core.Logging;
+using BepInEx.Preloader.Core.RuntimeFixes;
using BepInEx.Preloader.RuntimeFixes;
-using HarmonyLib;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using Mono.Cecil.Rocks;
using MonoMod.RuntimeDetour;
-using UnityInjector.ConsoleUtil;
using MethodAttributes = Mono.Cecil.MethodAttributes;
-namespace BepInEx.Preloader
+namespace BepInEx.Preloader.Unity
{
///
/// The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
///
- internal static class Preloader
+ internal static class UnityPreloader
{
///
/// The log writer that is specific to the preloader.
///
private static PreloaderConsoleListener PreloaderLog { get; set; }
- public static bool IsPostUnity2017 { get; } = File.Exists(Path.Combine(Paths.ManagedPath, "UnityEngine.CoreModule.dll"));
+ private static ManualLogSource Log => PreloaderLogger.Log;
- public static void Run()
+ public static string ManagedPath { get; private set; } = Utility.CombinePaths(Paths.GameRootPath, $"{Paths.ProcessName}_Data", "Managed");
+
+ public static bool IsPostUnity2017 { get; } = File.Exists(Path.Combine(ManagedPath, "UnityEngine.CoreModule.dll"));
+
+ public static void Run(string managedDirectory)
{
try
{
AllocateConsole();
+ if (managedDirectory != null)
+ ManagedPath = managedDirectory;
+
bool bridgeInitialized = Utility.TryDo(() =>
{
if (ConfigShimHarmony.Value)
@@ -57,46 +67,56 @@ namespace BepInEx.Preloader
Logger.Listeners.Add(PreloaderLog);
string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
- ConsoleWindow.Title = consoleTile;
- Logger.LogMessage(consoleTile);
+ Log.LogMessage(consoleTile);
+ if (ConsoleManager.ConsoleActive)
+ ConsoleManager.SetConsoleTitle(consoleTile);
+
//See BuildInfoAttribute for more information about this section.
object[] attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false);
if (attributes.Length > 0)
{
var attribute = (BuildInfoAttribute)attributes[0];
- Logger.LogMessage(attribute.Info);
+ Log.LogMessage(attribute.Info);
}
- Logger.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
- Logger.LogInfo($"CLR runtime version: {Environment.Version}");
- Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
+ Log.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
+ Log.LogInfo($"CLR runtime version: {Environment.Version}");
+ Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
if (harmonyBridgeException != null)
- Logger.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}");
+ Log.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}");
if (runtimePatchException != null)
- Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");
+ Log.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");
- Logger.LogMessage("Preloader started");
+ Log.LogMessage("Preloader started");
- AssemblyPatcher.AddPatcher(new PatcherPlugin
+ TypeLoader.SearchDirectories.Add(ManagedPath);
+
+ using (var assemblyPatcher = new AssemblyPatcher())
{
- TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
- Patcher = PatchEntrypoint,
- TypeName = "BepInEx.Chainloader"
- });
+ assemblyPatcher.PatcherPlugins.Add(new PatcherPlugin
+ {
+ TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
+ Patcher = PatchEntrypoint,
+ TypeName = "BepInEx.Chainloader"
+ });
- AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
+ assemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
- Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
+ Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
- AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
- AssemblyPatcher.DisposePatchers();
+ assemblyPatcher.LoadAssemblyDirectory(ManagedPath);
+
+ Log.LogInfo($"{assemblyPatcher.PatcherPlugins.Count} assemblies discovered");
+
+ assemblyPatcher.PatchAndLoad();
+ }
- Logger.LogMessage("Preloader finished");
+ Log.LogMessage("Preloader finished");
Logger.Listeners.Remove(PreloaderLog);
Logger.Listeners.Add(new ConsoleLogListener());
@@ -107,12 +127,12 @@ namespace BepInEx.Preloader
{
try
{
- Logger.LogFatal("Could not run preloader!");
- Logger.LogFatal(ex);
+ Log.LogFatal("Could not run preloader!");
+ Log.LogFatal(ex);
PreloaderLog?.Dispose();
- if (!ConsoleWindow.IsAttached)
+ if (!ConsoleManager.ConsoleActive)
{
//if we've already attached the console, then the log will already be written to the console
AllocateConsole();
@@ -153,15 +173,19 @@ namespace BepInEx.Preloader
if (entryType == null)
throw new Exception("The entrypoint type is invalid! Please check your config.ini");
- using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
+ string chainloaderAssemblyPath = Path.Combine(Paths.BepInExAssemblyDirectory, "BepInEx.Unity.dll");
+
+ var readerParameters = new ReaderParameters
{
- var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
- .First(x => x.Name == "Initialize");
+ AssemblyResolver = TypeLoader.CecilResolver
+ };
- var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
- .First(x => x.Name == "Start");
+ using (var chainloaderAssemblyDefinition = AssemblyDefinition.ReadAssembly(chainloaderAssemblyPath, readerParameters))
+ {
+ var chainloaderType = chainloaderAssemblyDefinition.MainModule.Types.First(x => x.Name == "UnityChainloader");
+
+ var originalStartMethod = chainloaderType.EnumerateAllMethods().First(x => x.Name == "StaticStart");
- var initMethod = assembly.MainModule.ImportReference(originalInitMethod);
var startMethod = assembly.MainModule.ImportReference(originalStartMethod);
var methods = new List();
@@ -201,18 +225,8 @@ namespace BepInEx.Preloader
il.InsertBefore(ins,
il.Create(OpCodes.Ldnull)); // gameExePath (always null, we initialize the Paths class in Entrypoint
- il.InsertBefore(ins,
- il.Create(OpCodes.Ldc_I4_0)); //startConsole (always false, we already load the console in Preloader)
-
- il.InsertBefore(ins,
- il.Create(OpCodes.Call, assembly.MainModule.ImportReference(
- AccessTools.PropertyGetter(typeof(PreloaderConsoleListener), nameof(PreloaderConsoleListener.LogEvents))))); // preloaderLogEvents (load from Preloader.PreloaderLog.LogEvents)
-
il.InsertBefore(ins,
- il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gamePath, string managedPath = null, bool startConsole = true)
-
- il.InsertBefore(ins,
- il.Create(OpCodes.Call, startMethod));
+ il.Create(OpCodes.Call, startMethod)); // UnityChainloader.StaticStart(string gameExePath)
}
}
}
@@ -222,25 +236,24 @@ namespace BepInEx.Preloader
///
public static void AllocateConsole()
{
- if (!ConsoleWindow.ConfigConsoleEnabled.Value)
+ if (!ConsoleManager.ConfigConsoleEnabled.Value)
return;
try
{
- ConsoleWindow.Attach();
+ ConsoleManager.CreateConsole();
var encoding = (uint)Encoding.UTF8.CodePage;
- if (ConsoleWindow.ConfigConsoleShiftJis.Value)
+ if (ConsoleManager.ConfigConsoleShiftJis.Value)
encoding = 932;
- ConsoleEncoding.ConsoleCodePage = encoding;
- Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
+ ConsoleManager.SetConsoleEncoding(encoding);
}
catch (Exception ex)
{
- Logger.LogError("Failed to allocate console!");
- Logger.LogError(ex);
+ Log.LogError("Failed to allocate console!");
+ Log.LogError(ex);
}
}
diff --git a/BepInEx.Preloader/packages.config b/BepInEx.Preloader.Unity/packages.config
similarity index 56%
rename from BepInEx.Preloader/packages.config
rename to BepInEx.Preloader.Unity/packages.config
index 4259171..ad05d53 100644
--- a/BepInEx.Preloader/packages.config
+++ b/BepInEx.Preloader.Unity/packages.config
@@ -1,6 +1,6 @@
-
-
+
+
\ No newline at end of file
diff --git a/BepInEx/Contract/BaseUnityPlugin.cs b/BepInEx.Unity/BaseUnityPlugin.cs
similarity index 80%
rename from BepInEx/Contract/BaseUnityPlugin.cs
rename to BepInEx.Unity/BaseUnityPlugin.cs
index 83dca63..06aff49 100644
--- a/BepInEx/Contract/BaseUnityPlugin.cs
+++ b/BepInEx.Unity/BaseUnityPlugin.cs
@@ -36,19 +36,14 @@ namespace BepInEx
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.");
- if (Chainloader.PluginInfos.TryGetValue(metadata.GUID, out var info))
- Info = info;
- else
+ Info = new PluginInfo
{
- Info = new PluginInfo
- {
- Metadata = metadata,
- Instance = this,
- Dependencies = MetadataHelper.GetDependencies(GetType()),
- Processes = MetadataHelper.GetAttributes(GetType()),
- Location = GetType().Assembly.Location
- };
- }
+ Metadata = metadata,
+ Instance = this,
+ Dependencies = MetadataHelper.GetDependencies(GetType()),
+ Processes = MetadataHelper.GetAttributes(GetType()),
+ Location = GetType().Assembly.Location
+ };
Logger = Logging.Logger.CreateLogSource(metadata.Name);
diff --git a/BepInEx.Unity/BepInEx.Unity.csproj b/BepInEx.Unity/BepInEx.Unity.csproj
new file mode 100644
index 0000000..0d4ee84
--- /dev/null
+++ b/BepInEx.Unity/BepInEx.Unity.csproj
@@ -0,0 +1,74 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}
+ Library
+ Properties
+ BepInEx.Unity
+ BepInEx.Unity
+ v3.5
+ 512
+ true
+
+
+ true
+ full
+ false
+ ..\bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ none
+ true
+ ..\bin\
+ TRACE
+ prompt
+ 4
+ ..\bin\BepInEx.Unity.xml
+
+
+
+ ..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.dll
+
+
+
+
+ ..\lib\UnityEngine.dll
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {4ffba620-f5ed-47f9-b90c-dad1316fd9b9}
+ BepInEx.Core
+
+
+ {15F8BC38-A761-4F93-8903-1B531AC5D9F9}
+ BepInEx.Preloader.Core
+
+
+ {d404c973-441d-48ed-b266-c21320ba0d87}
+ BepInEx.Preloader.Unity
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BepInEx.Unity/BepInExInstance.cs b/BepInEx.Unity/BepInExInstance.cs
new file mode 100644
index 0000000..4cd467b
--- /dev/null
+++ b/BepInEx.Unity/BepInExInstance.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using BepInEx.Unity.Bootstrap;
+
+namespace BepInEx.Unity
+{
+ public static class BepInExInstance
+ {
+ public static UnityChainloader Chainloader { get; }
+ }
+}
diff --git a/BepInEx.Unity/Bootstrap/UnityChainloader.cs b/BepInEx.Unity/Bootstrap/UnityChainloader.cs
new file mode 100644
index 0000000..58bf6c4
--- /dev/null
+++ b/BepInEx.Unity/Bootstrap/UnityChainloader.cs
@@ -0,0 +1,118 @@
+using BepInEx.Configuration;
+using BepInEx.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using BepInEx.Bootstrap;
+using BepInEx.Preloader.Core;
+using BepInEx.Preloader.Core.Logging;
+using BepInEx.Unity.Logging;
+using UnityEngine;
+using Logger = BepInEx.Logging.Logger;
+
+namespace BepInEx.Unity.Bootstrap
+{
+ ///
+ /// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
+ ///
+ public class UnityChainloader : BaseChainloader
+ {
+ ///
+ /// The GameObject that all plugins are attached to as components.
+ ///
+ public static GameObject ManagerObject { get; private set; }
+
+ private static void StaticStart(string gameExePath = null)
+ {
+ var instance = new UnityChainloader();
+ instance.Initialize(gameExePath);
+ instance.Execute();
+ }
+
+ private string _consoleTitle;
+ protected override string ConsoleTitle => _consoleTitle;
+
+ public override void Initialize(string gameExePath = null)
+ {
+ UnityTomlTypeConverters.AddUnityEngineConverters();
+
+ ThreadingHelper.Initialize();
+
+ ManagerObject = new GameObject("BepInEx_Manager");
+ UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
+
+ var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
+ _consoleTitle = $"{CurrentAssemblyName} {CurrentAssemblyVersion} - {productNameProp?.GetValue(null, null) ?? Process.GetCurrentProcess().ProcessName}";
+
+ base.Initialize(gameExePath);
+ }
+
+ protected override void InitializeLoggers()
+ {
+ if (ConsoleManager.ConfigConsoleEnabled.Value)
+ {
+ ConsoleManager.CreateConsole();
+
+ if (!Logger.Listeners.Any(x => x is ConsoleLogListener))
+ Logger.Listeners.Add(new ConsoleLogListener());
+ }
+
+ // Fix for standard output getting overwritten by UnityLogger
+ if (ConsoleManager.StandardOut != null)
+ {
+ Console.SetOut(ConsoleManager.StandardOut);
+
+ var encoding = ConsoleManager.ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage;
+ ConsoleManager.SetConsoleEncoding(encoding);
+ }
+
+ Logger.Listeners.Add(new UnityLogListener());
+
+ if (ConfigUnityLogging.Value)
+ Logger.Sources.Add(new UnityLogSource());
+
+
+ base.InitializeLoggers();
+
+
+ if (!ConfigDiskWriteUnityLog.Value)
+ {
+ DiskLogListener.BlacklistedSources.Add("Unity Log");
+ }
+
+
+ // Temporarily disable the console log listener as we replay the preloader logs
+
+ var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener);
+
+ if (logListener != null)
+ Logger.Listeners.Remove(logListener);
+
+ foreach (var preloaderLogEvent in PreloaderConsoleListener.LogEvents)
+ {
+ PreloaderLogger.Log.Log(preloaderLogEvent.Level, preloaderLogEvent.Data);
+ }
+
+ if (logListener != null)
+ Logger.Listeners.Add(logListener);
+ }
+
+ public override BaseUnityPlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly)
+ {
+ return (BaseUnityPlugin)ManagerObject.AddComponent(pluginAssembly.GetType(pluginInfo.TypeName));
+ }
+
+ private static readonly ConfigEntry ConfigUnityLogging = ConfigFile.CoreConfig.Bind(
+ "Logging", "UnityLogListening",
+ true,
+ "Enables showing unity log messages in the BepInEx logging system.");
+
+ private static readonly ConfigEntry ConfigDiskWriteUnityLog = ConfigFile.CoreConfig.Bind(
+ "Logging.Disk", "WriteUnityLog",
+ false,
+ "Include unity log messages in log file output.");
+ }
+}
\ No newline at end of file
diff --git a/BepInEx/Configuration/KeyboardShortcut.cs b/BepInEx.Unity/Configuration/KeyboardShortcut.cs
similarity index 100%
rename from BepInEx/Configuration/KeyboardShortcut.cs
rename to BepInEx.Unity/Configuration/KeyboardShortcut.cs
diff --git a/BepInEx/Logging/UnityLogListener.cs b/BepInEx.Unity/Logging/UnityLogListener.cs
similarity index 94%
rename from BepInEx/Logging/UnityLogListener.cs
rename to BepInEx.Unity/Logging/UnityLogListener.cs
index bcf802e..be76d7c 100644
--- a/BepInEx/Logging/UnityLogListener.cs
+++ b/BepInEx.Unity/Logging/UnityLogListener.cs
@@ -1,8 +1,9 @@
-using System;
+using BepInEx.Logging;
+using System;
using System.Reflection;
using System.Runtime.CompilerServices;
-namespace BepInEx.Logging
+namespace BepInEx.Unity.Logging
{
///
/// Logs entries using Unity specific outputs.
diff --git a/BepInEx/Logging/UnityLogSource.cs b/BepInEx.Unity/Logging/UnityLogSource.cs
similarity index 96%
rename from BepInEx/Logging/UnityLogSource.cs
rename to BepInEx.Unity/Logging/UnityLogSource.cs
index fe04cf3..04a22d3 100644
--- a/BepInEx/Logging/UnityLogSource.cs
+++ b/BepInEx.Unity/Logging/UnityLogSource.cs
@@ -1,8 +1,9 @@
-using System;
+using BepInEx.Logging;
+using System;
using System.Reflection;
using UnityEngine;
-namespace BepInEx.Logging
+namespace BepInEx.Unity.Logging
{
///
/// Logs entries using Unity specific outputs.
diff --git a/BepInEx.Preloader/Properties/AssemblyInfo.cs b/BepInEx.Unity/Properties/AssemblyInfo.cs
similarity index 77%
rename from BepInEx.Preloader/Properties/AssemblyInfo.cs
rename to BepInEx.Unity/Properties/AssemblyInfo.cs
index 4471445..8cd8a54 100644
--- a/BepInEx.Preloader/Properties/AssemblyInfo.cs
+++ b/BepInEx.Unity/Properties/AssemblyInfo.cs
@@ -1,15 +1,16 @@
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("BepInEx.Preloader")]
+[assembly: AssemblyTitle("BepInEx.Unity")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("BepInEx.Preloader")]
-[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyProduct("BepInEx.Unity")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -19,7 +20,7 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("f7abbe07-c02f-4f7c-bf6e-c6656bf588ca")]
+[assembly: Guid("eae9fae6-8011-45a3-8b6e-0c7f14210533")]
// Version information for an assembly consists of the following four values:
//
@@ -31,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("5.0.1.0")]
-[assembly: AssemblyFileVersion("5.0.1.0")]
\ No newline at end of file
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BepInEx/ThreadingHelper.cs b/BepInEx.Unity/ThreadingHelper.cs
similarity index 100%
rename from BepInEx/ThreadingHelper.cs
rename to BepInEx.Unity/ThreadingHelper.cs
diff --git a/BepInEx.Unity/UnityTomlTypeConverters.cs b/BepInEx.Unity/UnityTomlTypeConverters.cs
new file mode 100644
index 0000000..a5efe46
--- /dev/null
+++ b/BepInEx.Unity/UnityTomlTypeConverters.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Runtime.CompilerServices;
+using BepInEx.Configuration;
+using UnityEngine;
+
+namespace BepInEx.Unity
+{
+ ///
+ /// Config types that are unity specific
+ ///
+ internal static class UnityTomlTypeConverters
+ {
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void AddUnityEngineConverters()
+ {
+ var colorConverter = new TypeConverter
+ {
+ ConvertToString = (obj, type) => ColorUtility.ToHtmlStringRGBA((Color)obj),
+ ConvertToObject = (str, type) =>
+ {
+ if (!ColorUtility.TryParseHtmlString("#" + str.Trim('#', ' '), out var c))
+ throw new FormatException("Invalid color string, expected hex #RRGGBBAA");
+ return c;
+ },
+ };
+
+ TomlTypeConverter.AddConverter(typeof(Color), colorConverter);
+
+ var jsonConverter = new TypeConverter
+ {
+ ConvertToString = (obj, type) => JsonUtility.ToJson(obj),
+ ConvertToObject = (str, type) => JsonUtility.FromJson(type: type, json: str),
+ };
+
+ TomlTypeConverter.AddConverter(typeof(Vector2), jsonConverter);
+ TomlTypeConverter.AddConverter(typeof(Vector3), jsonConverter);
+ TomlTypeConverter.AddConverter(typeof(Vector4), jsonConverter);
+ TomlTypeConverter.AddConverter(typeof(Quaternion), jsonConverter);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BepInEx.Unity/packages.config b/BepInEx.Unity/packages.config
new file mode 100644
index 0000000..791f6c3
--- /dev/null
+++ b/BepInEx.Unity/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/BepInEx.sln b/BepInEx.sln
index f70057f..474b7bd 100644
--- a/BepInEx.sln
+++ b/BepInEx.sln
@@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28922.388
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx", "BepInEx\BepInEx.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Patcher", "BepInEx.Patcher\BepInEx.Patcher.csproj", "{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}"
ProjectSection(ProjectDependencies) = postProject
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9} = {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}
@@ -21,23 +19,27 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harmony", "submodules\BepIn
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Patcher", "Patcher", "{A9071994-3533-4C1B-89DC-D817B676AB41}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Preloader", "BepInEx.Preloader\BepInEx.Preloader.csproj", "{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInExTests", "BepInExTests\BepInExTests.csproj", "{E7CD429A-D057-48E3-8C51-E5C934E8E07B}"
ProjectSection(ProjectDependencies) = postProject
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9} = {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Preloader.Core", "BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj", "{15F8BC38-A761-4F93-8903-1B531AC5D9F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Unity", "BepInEx.Unity\BepInEx.Unity.csproj", "{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Preloader.Unity", "BepInEx.Preloader.Unity\BepInEx.Preloader.Unity.csproj", "{D404C973-441D-48ED-B266-C21320BA0D87}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Core", "BepInEx.Core\BepInEx.Core.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Unity", "Unity", "{6E2DD21E-0854-4F4A-B925-D90E0016D03D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = Release|Any CPU
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -54,13 +56,25 @@ Global
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.Build.0 = Release|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.ActiveCfg = Release|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Release|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = Release|Any CPU
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.ActiveCfg = Debug|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.Build.0 = Debug|x86
{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|x86
+ {15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {15F8BC38-A761-4F93-8903-1B531AC5D9F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D404C973-441D-48ED-B266-C21320BA0D87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D404C973-441D-48ED-B266-C21320BA0D87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D404C973-441D-48ED-B266-C21320BA0D87}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D404C973-441D-48ED-B266-C21320BA0D87}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -70,6 +84,8 @@ Global
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB} = {A9071994-3533-4C1B-89DC-D817B676AB41}
{54161CFE-FF42-4DDE-B161-3A49545DB5CD} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
{A15D6EE6-F954-415B-8605-8A8470CC87DC} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533} = {6E2DD21E-0854-4F4A-B925-D90E0016D03D}
+ {D404C973-441D-48ED-B266-C21320BA0D87} = {6E2DD21E-0854-4F4A-B925-D90E0016D03D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1}
diff --git a/BepInEx/Bootstrap/Chainloader.cs b/BepInEx/Bootstrap/Chainloader.cs
deleted file mode 100644
index 81bc538..0000000
--- a/BepInEx/Bootstrap/Chainloader.cs
+++ /dev/null
@@ -1,428 +0,0 @@
-using BepInEx.Configuration;
-using BepInEx.Logging;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Text.RegularExpressions;
-using Mono.Cecil;
-using UnityEngine;
-using UnityInjector.ConsoleUtil;
-using Logger = BepInEx.Logging.Logger;
-
-namespace BepInEx.Bootstrap
-{
- ///
- /// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
- ///
- public static class Chainloader
- {
- ///
- /// The loaded and initialized list of plugins.
- ///
- public static Dictionary PluginInfos { get; } = new Dictionary();
-
- private static readonly List _plugins = new List();
- [Obsolete("Use PluginInfos instead")]
- public static List Plugins
- {
- get
- {
- lock (_plugins)
- {
- _plugins.RemoveAll(x => x == null);
- return _plugins.ToList();
- }
- }
- }
-
- public static List DependencyErrors { get; } = new List();
-
- ///
- /// The GameObject that all plugins are attached to as components.
- ///
- public static GameObject ManagerObject { get; private set; }
-
-
- private static bool _loaded = false;
- private static bool _initialized = false;
-
- ///
- /// Initializes BepInEx to be able to start the chainloader.
- ///
- public static void Initialize(string gameExePath, bool startConsole = true, ICollection preloaderLogEvents = null)
- {
- if (_initialized)
- return;
-
- ThreadingHelper.Initialize();
-
- // 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);
- }
-
- // Start logging
- if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole)
- {
- ConsoleWindow.Attach();
- Logger.Listeners.Add(new ConsoleLogListener());
- }
-
- // Fix for standard output getting overwritten by UnityLogger
- if (ConsoleWindow.StandardOut != null)
- {
- Console.SetOut(ConsoleWindow.StandardOut);
-
- var encoding = ConsoleWindow.ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage;
- ConsoleEncoding.ConsoleCodePage = encoding;
- Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
- }
-
- Logger.Listeners.Add(new UnityLogListener());
-
- if (ConfigDiskLogging.Value)
- Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value));
-
- if (!TraceLogSource.IsListening)
- Logger.Sources.Add(TraceLogSource.CreateSource());
-
- if (ConfigUnityLogging.Value)
- Logger.Sources.Add(new UnityLogSource());
-
-
- // Temporarily disable the console log listener as we replay the preloader logs
-
- var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener);
-
- if (logListener != null)
- Logger.Listeners.Remove(logListener);
-
- var preloaderLogSource = Logger.CreateLogSource("Preloader");
-
- foreach (var preloaderLogEvent in preloaderLogEvents)
- {
- preloaderLogSource.Log(preloaderLogEvent.Level, preloaderLogEvent.Data);
- }
-
- Logger.Sources.Remove(preloaderLogSource);
-
- if (logListener != null)
- Logger.Listeners.Add(logListener);
-
-
-
- Logger.LogMessage("Chainloader ready");
-
- _initialized = true;
- }
-
- private static Regex allowedGuidRegex { get; } = new Regex(@"^[a-zA-Z0-9\._\-]+$");
-
- public static PluginInfo ToPluginInfo(TypeDefinition type)
- {
- if (type.IsInterface || type.IsAbstract)
- return null;
-
- try
- {
- if (!type.IsSubtypeOf(typeof(BaseUnityPlugin)))
- return null;
- }
- catch (AssemblyResolutionException)
- {
- // Can happen if this type inherits a type from an assembly that can't be found. Safe to assume it's not a plugin.
- return null;
- }
-
- var metadata = BepInPlugin.FromCecilType(type);
-
- // Perform checks that will prevent the plugin from being loaded in ALL cases
- if (metadata == null)
- {
- Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified");
- return null;
- }
-
- if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID))
- {
- Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{metadata.GUID}] is of an illegal format.");
- return null;
- }
-
- if (metadata.Version == null)
- {
- Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid.");
- return null;
- }
-
- if (metadata.Name == null)
- {
- Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null.");
- return null;
- }
-
- var filters = BepInProcess.FromCecilType(type);
- var dependencies = BepInDependency.FromCecilType(type);
- var incompatibilities = BepInIncompatibility.FromCecilType(type);
-
- var bepinVersion = type.Module.AssemblyReferences.FirstOrDefault(reference => reference.Name == "BepInEx")?.Version ?? new Version();
-
- return new PluginInfo
- {
- Metadata = metadata,
- Processes = filters,
- Dependencies = dependencies,
- Incompatibilities = incompatibilities,
- TypeName = type.FullName,
- TargettedBepInExVersion = bepinVersion
- };
- }
-
- private static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
- private static readonly Version CurrentAssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
-
- private 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(BaseUnityPlugin).FullName))
- return false;
-
- return true;
- }
-
- private static bool PluginTargetsWrongBepin(PluginInfo pluginInfo)
- {
- var pluginTarget = pluginInfo.TargettedBepInExVersion;
- // X.X.X.x - compare normally. x.x.x.X - nightly build number, ignore
- if (pluginTarget.Major != CurrentAssemblyVersion.Major) return true;
- if (pluginTarget.Minor > CurrentAssemblyVersion.Minor) return true;
- if (pluginTarget.Minor < CurrentAssemblyVersion.Minor) return false;
- return pluginTarget.Build > CurrentAssemblyVersion.Build;
- }
-
- ///
- /// The entrypoint for the BepInEx plugin system.
- ///
- public static void Start()
- {
- if (_loaded)
- return;
-
- if (!_initialized)
- throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance.");
-
- if (!Directory.Exists(Paths.PluginPath))
- Directory.CreateDirectory(Paths.PluginPath);
-
- if (!Directory.Exists(Paths.PatcherPluginPath))
- Directory.CreateDirectory(Paths.PatcherPluginPath);
-
- try
- {
- var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
- ConsoleWindow.Title = $"{CurrentAssemblyName} {CurrentAssemblyVersion} - {productNameProp?.GetValue(null, null) ?? Process.GetCurrentProcess().ProcessName}";
-
- Logger.LogMessage("Chainloader started");
-
- ManagerObject = new GameObject("BepInEx_Manager");
-
- UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
-
- var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader");
- foreach (var keyValuePair in pluginsToLoad)
- foreach (var pluginInfo in keyValuePair.Value)
- pluginInfo.Location = keyValuePair.Key;
- var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList();
- var loadedAssemblies = new Dictionary();
-
- Logger.LogInfo($"{pluginInfos.Count} plugins to load");
-
- // We use a sorted dictionary to ensure consistent load order
- var dependencyDict = new SortedDictionary>(StringComparer.InvariantCultureIgnoreCase);
- var pluginsByGUID = new Dictionary();
-
- foreach (var pluginInfoGroup in pluginInfos.GroupBy(info => info.Metadata.GUID))
- {
- var alreadyLoaded = false;
- foreach (var pluginInfo in pluginInfoGroup.OrderByDescending(x => x.Metadata.Version))
- {
- if (alreadyLoaded)
- {
- Logger.LogWarning($"Skipping because a newer version exists [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
- continue;
- }
-
- alreadyLoaded = true;
-
- // Perform checks that will prevent loading plugins in this run
- var filters = pluginInfo.Processes.ToList();
- bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase));
-
- if (invalidProcessName)
- {
- Logger.LogWarning($"Skipping because of process filters [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
- continue;
- }
-
- dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID);
- pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo;
- }
- }
-
- foreach (var pluginInfo in pluginsByGUID.Values.ToList())
- {
- if (pluginInfo.Incompatibilities.Any(incompatibility => pluginsByGUID.ContainsKey(incompatibility.IncompatibilityGUID)))
- {
- pluginsByGUID.Remove(pluginInfo.Metadata.GUID);
- dependencyDict.Remove(pluginInfo.Metadata.GUID);
-
- var incompatiblePlugins = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID).Where(x => pluginsByGUID.ContainsKey(x)).ToArray();
- string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}";
- DependencyErrors.Add(message);
- Logger.LogError(message);
- }
- else if (PluginTargetsWrongBepin(pluginInfo))
- {
- string message = $@"Plugin [{pluginInfo.Metadata.Name}] targets a wrong version of BepInEx ({pluginInfo.TargettedBepInExVersion}) and might not work until you update";
- DependencyErrors.Add(message);
- Logger.LogWarning(message);
- }
- }
-
- var emptyDependencies = new string[0];
-
- // Sort plugins by their dependencies.
- // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list.
- var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList();
-
- var invalidPlugins = new HashSet();
- var processedPlugins = new Dictionary();
-
- foreach (var pluginGUID in sortedPlugins)
- {
- // If the plugin is missing, don't process it
- if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo))
- continue;
-
- var dependsOnInvalidPlugin = false;
- var missingDependencies = new List();
- foreach (var dependency in pluginInfo.Dependencies)
- {
- // If the depenency wasn't already processed, it's missing altogether
- bool depenencyExists = processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion);
- if (!depenencyExists || pluginVersion < dependency.MinimumVersion)
- {
- // If the dependency is hard, collect it into a list to show
- if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0)
- missingDependencies.Add(dependency);
- continue;
- }
-
- // If the dependency is invalid (e.g. has missing depedencies), report that to the user
- if (invalidPlugins.Contains(dependency.DependencyGUID))
- {
- dependsOnInvalidPlugin = true;
- break;
- }
- }
-
- processedPlugins.Add(pluginGUID, pluginInfo.Metadata.Version);
-
- if (dependsOnInvalidPlugin)
- {
- string message = $"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See previous errors for details.";
- DependencyErrors.Add(message);
- Logger.LogWarning(message);
- continue;
- }
-
- if (missingDependencies.Count != 0)
- {
- bool IsEmptyVersion(Version v) => v.Major == 0 && v.Minor == 0 && v.Build <= 0 && v.Revision <= 0;
-
- string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it has missing dependencies: {
- string.Join(", ", missingDependencies.Select(s => IsEmptyVersion(s.MinimumVersion) ? s.DependencyGUID : $"{s.DependencyGUID} (v{s.MinimumVersion} or newer)").ToArray())
- }";
- DependencyErrors.Add(message);
- Logger.LogError(message);
-
- invalidPlugins.Add(pluginGUID);
- continue;
- }
-
- try
- {
- Logger.LogInfo($"Loading [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]");
-
- if (!loadedAssemblies.TryGetValue(pluginInfo.Location, out var ass))
- loadedAssemblies[pluginInfo.Location] = ass = Assembly.LoadFile(pluginInfo.Location);
-
- PluginInfos[pluginGUID] = pluginInfo;
- pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.TypeName));
-
- _plugins.Add(pluginInfo.Instance);
- }
- catch (Exception ex)
- {
- invalidPlugins.Add(pluginGUID);
- PluginInfos.Remove(pluginGUID);
-
- Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}");
- if (ex is ReflectionTypeLoadException re)
- Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
- else
- Logger.LogDebug(ex);
- }
- }
- }
- catch (Exception ex)
- {
- ConsoleWindow.Attach();
-
- Console.WriteLine("Error occurred starting the game");
- Console.WriteLine(ex.ToString());
- }
-
- Logger.LogMessage("Chainloader startup complete");
-
- _loaded = true;
- }
-
- #region Config
-
-
- private static readonly ConfigEntry ConfigUnityLogging = ConfigFile.CoreConfig.Bind(
- "Logging", "UnityLogListening",
- true,
- "Enables showing unity log messages in the BepInEx logging system.");
-
- private static readonly ConfigEntry ConfigDiskWriteUnityLog = ConfigFile.CoreConfig.Bind(
- "Logging.Disk", "WriteUnityLog",
- false,
- "Include unity log messages in log file output.");
-
- private static readonly ConfigEntry ConfigDiskAppend = ConfigFile.CoreConfig.Bind(
- "Logging.Disk", "AppendLog",
- false,
- "Appends to the log file instead of overwriting, on game startup.");
-
- private static readonly ConfigEntry ConfigDiskLogging = ConfigFile.CoreConfig.Bind(
- "Logging.Disk", "Enabled",
- true,
- "Enables writing log messages to disk.");
-
- private static readonly ConfigEntry ConfigDiskConsoleDisplayedLevel = ConfigFile.CoreConfig.Bind(
- "Logging.Disk", "DisplayedLogLevel",
- LogLevel.Info,
- "Only displays the specified log level and above in the console output.");
- #endregion
- }
-}
\ No newline at end of file
diff --git a/BepInExTests/BepInExTests.csproj b/BepInExTests/BepInExTests.csproj
index a7c1be5..55953c0 100644
--- a/BepInExTests/BepInExTests.csproj
+++ b/BepInExTests/BepInExTests.csproj
@@ -73,9 +73,9 @@
-
- {4ffba620-f5ed-47f9-b90c-dad1316fd9b9}
- BepInEx
+
+ {EAE9FAE6-8011-45A3-8B6E-0C7F14210533}
+ BepInEx.Unity
diff --git a/build.cake b/build.cake
index d0f8e46..6be148a 100644
--- a/build.cake
+++ b/build.cake
@@ -51,7 +51,7 @@ Task("Build")
.IsDependentOn("PullDependencies")
.Does(() =>
{
- var bepinExProperties = Directory("./BepInEx/Properties");
+ var bepinExProperties = Directory("./BepInEx.Core/Properties");
if(isBleedingEdge)
{
@@ -77,7 +77,7 @@ Task("Build")
})
.Finally(() =>
{
- var bepinExProperties = Directory("./BepInEx/Properties");
+ var bepinExProperties = Directory("./BepInEx.Core/Properties");
if(isBleedingEdge)
{
DeleteFile(bepinExProperties + File("AssemblyInfo.cs"));