Browse Source

Initial framework split

Bepis 4 years ago
parent
commit
dc5d9f444f
70 changed files with 1241 additions and 761 deletions
  1. 5 1
      BepInEx.Bootstrap/BepInEx.Bootstrap.csproj
  2. 5 2
      BepInEx.Bootstrap/Linker.cs
  3. 5 13
      BepInEx/BepInEx.csproj
  4. 352 0
      BepInEx.Core/Bootstrap/BaseChainloader.cs
  5. 24 13
      BepInEx/Bootstrap/TypeLoader.cs
  6. 0 0
      BepInEx.Core/Configuration/AcceptableValueBase.cs
  7. 0 0
      BepInEx.Core/Configuration/AcceptableValueList.cs
  8. 0 0
      BepInEx.Core/Configuration/AcceptableValueRange.cs
  9. 0 0
      BepInEx.Core/Configuration/ConfigDefinition.cs
  10. 0 0
      BepInEx.Core/Configuration/ConfigDescription.cs
  11. 0 0
      BepInEx.Core/Configuration/ConfigEntryBase.cs
  12. 1 1
      BepInEx/Configuration/ConfigFile.cs
  13. 0 0
      BepInEx.Core/Configuration/ConfigWrapper.cs
  14. 0 0
      BepInEx.Core/Configuration/SettingChangedEventArgs.cs
  15. 4 49
      BepInEx/Configuration/TomlTypeConverter.cs
  16. 0 0
      BepInEx.Core/Configuration/TypeConverter.cs
  17. 98 0
      BepInEx.Core/ConsoleManager.cs
  18. 0 0
      BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs
  19. 0 0
      BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs
  20. 0 0
      BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs
  21. 0 11
      BepInEx/ConsoleUtil/ConsoleWindow.cs
  22. 0 0
      BepInEx.Core/ConsoleUtil/Kon.cs
  23. 0 0
      BepInEx.Core/ConsoleUtil/SafeConsole.cs
  24. 1 1
      BepInEx/Contract/Attributes.cs
  25. 28 0
      BepInEx.Core/Contract/IPlugin.cs
  26. 2 2
      BepInEx/Contract/PluginInfo.cs
  27. 0 0
      BepInEx.Core/Logging/ConsoleLogListener.cs
  28. 6 6
      BepInEx/Logging/DiskLogListener.cs
  29. 0 0
      BepInEx.Core/Logging/ILogListener.cs
  30. 0 0
      BepInEx.Core/Logging/ILogSource.cs
  31. 0 0
      BepInEx.Core/Logging/LogEventArgs.cs
  32. 0 0
      BepInEx.Core/Logging/LogLevel.cs
  33. 0 0
      BepInEx.Core/Logging/Logger.cs
  34. 0 0
      BepInEx.Core/Logging/ManualLogSource.cs
  35. 0 0
      BepInEx.Core/Logging/TraceLogSource.cs
  36. 1 14
      BepInEx/Paths.cs
  37. 4 3
      BepInEx/Properties/AssemblyInfo.cs
  38. 13 0
      BepInEx/Utility.cs
  39. 0 0
      BepInEx.Core/packages.config
  40. 6 1
      BepInEx.Patcher/BepInEx.Patcher.csproj
  41. 72 0
      BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj
  42. 13 0
      BepInEx.Preloader.Core/InternalPreloaderLogger.cs
  43. 1 1
      BepInEx.Preloader/Logger/PreloaderLogWriter.cs
  44. 112 82
      BepInEx.Preloader/Patching/AssemblyPatcher.cs
  45. 2 2
      BepInEx.Preloader/Patching/PatcherPlugin.cs
  46. 36 0
      BepInEx.Preloader.Core/Properties/AssemblyInfo.cs
  47. 5 5
      BepInEx.Preloader/RuntimeFixes/HarmonyFixes.cs
  48. 29 22
      BepInEx.Preloader/BepInEx.Preloader.csproj
  49. 8 9
      BepInEx.Preloader/Entrypoint.cs
  50. 0 0
      BepInEx.Preloader.Unity/EnvVars.cs
  51. 36 0
      BepInEx.Preloader.Unity/Properties/AssemblyInfo.cs
  52. 0 0
      BepInEx.Preloader.Unity/RuntimeFixes/TraceFix.cs
  53. 1 1
      BepInEx.Preloader/RuntimeFixes/UnityPatches.cs
  54. 66 53
      BepInEx.Preloader/Preloader.cs
  55. 2 2
      BepInEx.Preloader/packages.config
  56. 7 12
      BepInEx/Contract/BaseUnityPlugin.cs
  57. 74 0
      BepInEx.Unity/BepInEx.Unity.csproj
  58. 13 0
      BepInEx.Unity/BepInExInstance.cs
  59. 118 0
      BepInEx.Unity/Bootstrap/UnityChainloader.cs
  60. 0 0
      BepInEx.Unity/Configuration/KeyboardShortcut.cs
  61. 3 2
      BepInEx/Logging/UnityLogListener.cs
  62. 3 2
      BepInEx/Logging/UnityLogSource.cs
  63. 7 6
      BepInEx.Preloader/Properties/AssemblyInfo.cs
  64. 0 0
      BepInEx.Unity/ThreadingHelper.cs
  65. 41 0
      BepInEx.Unity/UnityTomlTypeConverters.cs
  66. 4 0
      BepInEx.Unity/packages.config
  67. 28 12
      BepInEx.sln
  68. 0 428
      BepInEx/Bootstrap/Chainloader.cs
  69. 3 3
      BepInExTests/BepInExTests.csproj
  70. 2 2
      build.cake

+ 5 - 1
BepInEx.Bootstrap/BepInEx.Bootstrap.csproj

@@ -30,11 +30,15 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\BepInEx\BepInEx.csproj">
+    <ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">
       <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
       <Name>BepInEx</Name>
       <Private>False</Private>
     </ProjectReference>
+    <ProjectReference Include="..\BepInEx.Unity\BepInEx.Unity.csproj">
+      <Project>{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}</Project>
+      <Name>BepInEx.Unity</Name>
+    </ProjectReference>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 5 - 2
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();
 		}
 	}
 }

+ 5 - 13
BepInEx/BepInEx.csproj

@@ -6,8 +6,8 @@
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ProjectGuid>{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}</ProjectGuid>
     <OutputType>Library</OutputType>
-    <RootNamespace>BepInEx</RootNamespace>
-    <AssemblyName>BepInEx</AssemblyName>
+    <RootNamespace>BepInEx.Core</RootNamespace>
+    <AssemblyName>BepInEx.Core</AssemblyName>
     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -21,7 +21,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>..\bin\BepInEx.xml</DocumentationFile>
+    <DocumentationFile>..\bin\BepInEx.Core.xml</DocumentationFile>
   </PropertyGroup>
   <PropertyGroup>
     <StartupObject />
@@ -52,22 +52,19 @@
       <HintPath>..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll</HintPath>
     </Reference>
     <Reference Include="System" />
-    <Reference Include="UnityEngine">
-      <HintPath>..\lib\UnityEngine.dll</HintPath>
-      <Private>False</Private>
-    </Reference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Bootstrap\BaseChainloader.cs" />
     <Compile Include="Configuration\AcceptableValueBase.cs" />
     <Compile Include="Configuration\AcceptableValueList.cs" />
     <Compile Include="Configuration\AcceptableValueRange.cs" />
     <Compile Include="Configuration\ConfigEntryBase.cs" />
+    <Compile Include="ConsoleManager.cs" />
     <Compile Include="Contract\PluginInfo.cs" />
     <Compile Include="Configuration\ConfigDefinition.cs" />
     <Compile Include="Configuration\ConfigDescription.cs" />
     <Compile Include="Configuration\ConfigFile.cs" />
     <Compile Include="Configuration\ConfigWrapper.cs" />
-    <Compile Include="Configuration\KeyboardShortcut.cs" />
     <Compile Include="Configuration\SettingChangedEventArgs.cs" />
     <Compile Include="Configuration\TomlTypeConverter.cs" />
     <Compile Include="Configuration\TypeConverter.cs" />
@@ -78,8 +75,6 @@
     <Compile Include="ConsoleUtil\ConsoleWindow.cs" />
     <Compile Include="ConsoleUtil\Kon.cs" />
     <Compile Include="ConsoleUtil\SafeConsole.cs" />
-    <Compile Include="Bootstrap\Chainloader.cs" />
-    <Compile Include="Contract\BaseUnityPlugin.cs" />
     <Compile Include="Logging\DiskLogListener.cs" />
     <Compile Include="Logging\LogEventArgs.cs" />
     <Compile Include="Logging\Logger.cs" />
@@ -89,12 +84,9 @@
     <Compile Include="Logging\ManualLogSource.cs" />
     <Compile Include="Logging\TraceLogSource.cs" />
     <Compile Include="Logging\ConsoleLogListener.cs" />
-    <Compile Include="Logging\UnityLogListener.cs" />
-    <Compile Include="Logging\UnityLogSource.cs" />
     <Compile Include="Paths.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Bootstrap\TypeLoader.cs" />
-    <Compile Include="ThreadingHelper.cs" />
     <Compile Include="Utility.cs" />
   </ItemGroup>
   <ItemGroup>

+ 352 - 0
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<TPlugin>
+	{
+		#region Contract
+
+		protected virtual string ConsoleTitle => $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
+
+		private bool _initialized = false;
+
+		public Dictionary<string, PluginInfo> Plugins { get; } = new Dictionary<string, PluginInfo>();
+
+		public List<string> DependencyErrors { get; } = new List<string>();
+
+		//protected BaseChainloader()
+		//{
+		//	Plugins = new ReadOnlyCollection<TPlugin>(_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<PluginInfo> DiscoverPlugins()
+		{
+			var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader");
+
+			return pluginsToLoad.SelectMany(p => p.Value).ToList();
+		}
+
+		protected virtual IList<PluginInfo> ModifyLoadOrder(IList<PluginInfo> plugins)
+		{
+			// We use a sorted dictionary to ensure consistent load order
+			var dependencyDict = new SortedDictionary<string, IEnumerable<string>>(StringComparer.InvariantCultureIgnoreCase);
+			var pluginsByGuid = new Dictionary<string, PluginInfo>();
+
+			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<string>();
+				var processedPlugins = new Dictionary<string, Version>();
+				var loadedAssemblies = new Dictionary<string, Assembly>();
+
+				foreach (var plugin in plugins)
+				{
+					var dependsOnInvalidPlugin = false;
+					var missingDependencies = new List<BepInDependency>();
+					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<bool> ConfigDiskAppend = ConfigFile.CoreConfig.Bind(
+			"Logging.Disk", "AppendLog",
+			false,
+			"Appends to the log file instead of overwriting, on game startup.");
+
+		private static readonly ConfigEntry<bool> ConfigDiskLogging = ConfigFile.CoreConfig.Bind(
+			"Logging.Disk", "Enabled",
+			true,
+			"Enables writing log messages to disk.");
+
+		private static readonly ConfigEntry<LogLevel> ConfigDiskConsoleDisplayedLevel = ConfigFile.CoreConfig.Bind(
+			"Logging.Disk", "DisplayedLogLevel",
+			LogLevel.Info,
+			"Only displays the specified log level and above in the console output.");
+
+		#endregion
+	}
+}

+ 24 - 13
BepInEx/Bootstrap/TypeLoader.cs

@@ -51,25 +51,34 @@ namespace BepInEx.Bootstrap
 	/// </summary>
 	public static class TypeLoader
 	{
-		private static readonly DefaultAssemblyResolver resolver;
+		public static readonly DefaultAssemblyResolver CecilResolver;
 		private static readonly ReaderParameters readerParameters;
 
+		public static HashSet<string> SearchDirectories = new HashSet<string>();
+
 		static TypeLoader()
 		{
-			resolver = new DefaultAssemblyResolver();
-			readerParameters = new ReaderParameters { AssemblyResolver = resolver };
+			CecilResolver = new DefaultAssemblyResolver();
+			readerParameters = new ReaderParameters { AssemblyResolver = CecilResolver };
 
-			resolver.ResolveFailure += (sender, reference) =>
-			{
-				var name = new AssemblyName(reference.FullName);
+			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;
 
-				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))
+			foreach (var dir in SearchDirectories)
+			{
+				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
         /// <param name="assemblyFilter">A filter function to quickly determine if the assembly can be loaded.</param>
         /// <param name="cacheName">The name of the cache to get cached types from.</param>
 		/// <returns>A dictionary of all assemblies in the directory and the list of type metadatas of types that match the selector.</returns>
-        public static Dictionary<string, List<T>> FindPluginTypes<T>(string directory, Func<TypeDefinition, T> typeSelector, Func<AssemblyDefinition, bool> assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
+        public static Dictionary<string, List<T>> FindPluginTypes<T>(string directory, Func<TypeDefinition, string, T> typeSelector, Func<AssemblyDefinition, bool> assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
 		{
 			var result = new Dictionary<string, List<T>>();
 			Dictionary<string, CachedAssembly<T>> 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();
 				}

BepInEx/Configuration/AcceptableValueBase.cs → BepInEx.Core/Configuration/AcceptableValueBase.cs


BepInEx/Configuration/AcceptableValueList.cs → BepInEx.Core/Configuration/AcceptableValueList.cs


BepInEx/Configuration/AcceptableValueRange.cs → BepInEx.Core/Configuration/AcceptableValueRange.cs


BepInEx/Configuration/ConfigDefinition.cs → BepInEx.Core/Configuration/ConfigDefinition.cs


BepInEx/Configuration/ConfigDescription.cs → BepInEx.Core/Configuration/ConfigDescription.cs


BepInEx/Configuration/ConfigEntryBase.cs → BepInEx.Core/Configuration/ConfigEntryBase.cs


+ 1 - 1
BepInEx/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);
 
 		/// <summary>
 		/// All config entries inside 

BepInEx/Configuration/ConfigWrapper.cs → BepInEx.Core/Configuration/ConfigWrapper.cs


BepInEx/Configuration/SettingChangedEventArgs.cs → BepInEx.Core/Configuration/SettingChangedEventArgs.cs


+ 4 - 49
BepInEx/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
 		/// </summary>
 		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();
 		}
 	}
-
-	/// <summary>
-	/// For types that are in assemblies that can't get loaded before preloader runs (or it won't work on these assemblies)
-	/// </summary>
-	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);
-		}
-	}
 }

BepInEx/Configuration/TypeConverter.cs → BepInEx.Core/Configuration/TypeConverter.cs


+ 98 - 0
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<bool> ConfigConsoleEnabled = ConfigFile.CoreConfig.Bind(
+			"Logging.Console", "Enabled",
+			false,
+			"Enables showing a console for log output.");
+
+		public static readonly ConfigEntry<bool> ConfigConsoleShiftJis = ConfigFile.CoreConfig.Bind(
+			"Logging.Console", "ShiftJisEncoding",
+			false,
+			"If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.");
+	}
+}

BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs → BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs


BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs → BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs


BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs → BepInEx.Core/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs


+ 0 - 11
BepInEx/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<bool> ConfigConsoleEnabled = ConfigFile.CoreConfig.Bind(
-			"Logging.Console", "Enabled",
-			false,
-			"Enables showing a console for log output.");
-
-		public static readonly ConfigEntry<bool> 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;

BepInEx/ConsoleUtil/Kon.cs → BepInEx.Core/ConsoleUtil/Kon.cs


BepInEx/ConsoleUtil/SafeConsole.cs → BepInEx.Core/ConsoleUtil/SafeConsole.cs


+ 1 - 1
BepInEx/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.
 	/// </summary>
-	internal class BuildInfoAttribute : Attribute
+	public class BuildInfoAttribute : Attribute
 	{
 		public string Info { get; }
 

+ 28 - 0
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
+  {
+	  /// <summary>
+	  /// Information about this plugin as it was loaded.
+	  /// </summary>
+	  PluginInfo Info { get; }
+
+	  /// <summary>
+	  /// Logger instance tied to this plugin.
+	  /// </summary>
+	  ManualLogSource Logger { get; }
+
+	  /// <summary>
+	  /// Default config file tied to this plugin. The config file will not be created until 
+	  /// any settings are added and changed, or <see cref="ConfigFile.Save"/> is called.
+	  /// </summary>
+	  ConfigFile Config { get; }
+  }
+}

+ 2 - 2
BepInEx/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; }
 

BepInEx/Logging/ConsoleLogListener.cs → BepInEx.Core/Logging/ConsoleLogListener.cs


+ 6 - 6
BepInEx/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<string> BlacklistedSources = new HashSet<string>();
+
 		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)

BepInEx/Logging/ILogListener.cs → BepInEx.Core/Logging/ILogListener.cs


BepInEx/Logging/ILogSource.cs → BepInEx.Core/Logging/ILogSource.cs


BepInEx/Logging/LogEventArgs.cs → BepInEx.Core/Logging/LogEventArgs.cs


BepInEx/Logging/LogLevel.cs → BepInEx.Core/Logging/LogLevel.cs


BepInEx/Logging/Logger.cs → BepInEx.Core/Logging/Logger.cs


BepInEx/Logging/ManualLogSource.cs → BepInEx.Core/Logging/ManualLogSource.cs


BepInEx/Logging/TraceLogSource.cs → BepInEx.Core/Logging/TraceLogSource.cs


+ 1 - 14
BepInEx/Paths.cs

@@ -8,12 +8,11 @@ namespace BepInEx
 	/// </summary>
 	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);
@@ -62,11 +54,6 @@ namespace BepInEx
 		public static string GameRootPath { get; private set; }
 
 		/// <summary>
-		///     The path to the Managed folder of the currently running Unity game.
-		/// </summary>
-		public static string ManagedPath { get; private set; }
-
-		/// <summary>
 		///		The path to the config directory.
 		/// </summary>
 		public static string ConfigPath { get; private set; }

+ 4 - 3
BepInEx/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")]
+[assembly: AssemblyVersion("6.0.0.0")]
+[assembly: AssemblyFileVersion("6.0.0.0")]

+ 13 - 0
BepInEx/Utility.cs

@@ -237,5 +237,18 @@ namespace BepInEx
 				return false;
 			}
 		}
+
+		public static IEnumerable<MethodDefinition> EnumerateAllMethods(this TypeDefinition type)
+		{
+			var currentType = type;
+
+			while (currentType != null)
+			{
+				foreach (var method in currentType.Methods)
+					yield return method;
+
+				currentType = currentType.BaseType?.Resolve();
+			}
+		}
 	}
 }

BepInEx/packages.config → BepInEx.Core/packages.config


+ 6 - 1
BepInEx.Patcher/BepInEx.Patcher.csproj

@@ -45,7 +45,12 @@
     <None Include="ILRepack.targets" />
     <None Include="packages.config" />
   </ItemGroup>
-  <ItemGroup />
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx.Unity\BepInEx.Unity.csproj">
+      <Project>{eae9fae6-8011-45a3-8b6e-0c7f14210533}</Project>
+      <Name>BepInEx.Unity</Name>
+    </ProjectReference>
+  </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="..\bin\patcher\BepInEx.Bootstrap.dll" />
   </ItemGroup>

+ 72 - 0
BepInEx.Preloader.Core/BepInEx.Preloader.Core.csproj

@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{15F8BC38-A761-4F93-8903-1B531AC5D9F9}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BepInEx.Preloader.Core</RootNamespace>
+    <AssemblyName>BepInEx.Preloader.Core</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>none</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <DocumentationFile>..\bin\BepInEx.Preloader.Core.xml</DocumentationFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Logging\PreloaderLogWriter.cs" />
+    <Compile Include="Patching\AssemblyPatcher.cs" />
+    <Compile Include="Patching\PatcherPlugin.cs" />
+    <Compile Include="InternalPreloaderLogger.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="RuntimeFixes\HarmonyFixes.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">
+      <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
+      <Name>BepInEx.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\submodules\BepInEx.Harmony\BepInEx.Harmony\BepInEx.Harmony.csproj">
+      <Project>{54161cfe-ff42-4dde-b161-3a49545db5cd}</Project>
+      <Name>BepInEx.Harmony</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\submodules\BepInEx.Harmony\submodules\Harmony\Harmony\Harmony.csproj">
+      <Project>{a15d6ee6-f954-415b-8605-8a8470cc87dc}</Project>
+      <Name>Harmony</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="Mono.Cecil">
+      <Version>0.10.4</Version>
+    </PackageReference>
+    <PackageReference Include="MonoMod.RuntimeDetour">
+      <Version>20.4.3.1</Version>
+    </PackageReference>
+    <PackageReference Include="MonoMod.Utils">
+      <Version>20.4.3.1</Version>
+    </PackageReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 13 - 0
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");
+	}
+}

+ 1 - 1
BepInEx.Preloader/Logger/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
 	{

+ 112 - 82
BepInEx.Preloader/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
 {
 	/// <summary>
 	///     Delegate used in patching assemblies.
 	/// </summary>
 	/// <param name="assembly">The assembly that is being patched.</param>
-	internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
+	public delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
 
 	/// <summary>
 	///     Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and
 	///     loading assemblies one at a time.
 	/// </summary>
-	internal static class AssemblyPatcher
+	public class AssemblyPatcher : IDisposable
 	{
 		private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
 
-		public static List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
+		/// <summary>
+		/// A list of plugins that will be initialized and executed, in the order of the list.
+		/// </summary>
+		public List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
 
-		private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
+		/// <summary>
+		/// <para>Contains a list of assemblies that will be patched and loaded into the runtime.</para>
+		/// <para>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.</para>
+		/// <para>Order is not respected, as it will be sorted by dependencies.</para>
+		/// </summary>
+		public Dictionary<string, AssemblyDefinition> AssembliesToPatch { get; } = new Dictionary<string, AssemblyDefinition>();
 
 		/// <summary>
-		///     Adds a single assembly patcher to the pool of applicable patches.
+		/// The directory location as to where patched assemblies will be saved to and loaded from disk, for debugging purposes. Defaults to BepInEx/DumpedAssemblies
 		/// </summary>
-		/// <param name="patcher">Patcher to apply.</param>
-		public static void AddPatcher(PatcherPlugin patcher)
-		{
-			PatcherPlugins.Add(patcher);
-		}
+		public string DumpedAssembliesPath { get; set; } = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
+
+		public ManualLogSource Logger { get; } = new ManualLogSource("AssemblyPatcher");
 
 		private static T CreateDelegate<T>(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
 		/// </summary>
 		/// <param name="directory">Directory to search patcher DLLs from.</param>
 		/// <param name="patcherLocator">A function that locates assembly patchers in a given managed assembly.</param>
-		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<string, PatcherPlugin> patcher in sortedPatchers)
-				AddPatcher(patcher.Value);
+				PatcherPlugins.Add(patcher.Value);
 		}
 
-		private static void InitializePatchers()
+
+		/// <summary>
+		/// Adds all .dll assemblies in a directory to be patched and loaded by this patcher instance. Non-managed assemblies are skipped.
+		/// </summary>
+		/// <param name="directory">The directory to search.</param>
+		public void LoadAssemblyDirectory(string directory)
 		{
-			foreach (var assemblyPatcher in PatcherPlugins)
-				assemblyPatcher.Initializer?.Invoke();
+			LoadAssemblyDirectory(directory, "dll");
 		}
 
-		private static void FinalizePatching()
+		/// <summary>
+		/// Adds all assemblies in a directory to be patched and loaded by this patcher instance. Non-managed assemblies are skipped.
+		/// </summary>
+		/// <param name="directory">The directory to search.</param>
+		/// <param name="assemblyExtensions">The file extensions to attempt to load.</param>
+		public void LoadAssemblyDirectory(string directory, params string[] assemblyExtensions)
 		{
-			foreach (var assemblyPatcher in PatcherPlugins)
-				assemblyPatcher.Finalizer?.Invoke();
+			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));
+			}
+		}
+
+		/// <summary>
+		/// Attempts to load a managed assembly as an <see cref="AssemblyDefinition"/>. Returns true if successful.
+		/// </summary>
+		/// <param name="path">The path of the assembly.</param>
+		/// <param name="assembly">The loaded assembly. Null if not successful in loading.</param>
+		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;
+			}
 		}
 
 		/// <summary>
-		///     Releases all patchers to let them be collected by GC.
+		/// Performs work to dispose collection objects.
 		/// </summary>
-		public static void DisposePatchers()
+		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.
 		/// </summary>
 		/// <param name="directory">Directory to load CLR assemblies from.</param>
-		public static void PatchAndLoad(string directory)
+		public void PatchAndLoad()
 		{
-			// First, load patchable assemblies into Cecil
-			var assemblies = new Dictionary<string, AssemblyDefinition>();
-
-			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<string, AssemblyDefinition>(AssembliesToPatch);
 
 			// Next, initialize all the patchers
-			InitializePatchers();
+			foreach (var assemblyPatcher1 in PatcherPlugins)
+				assemblyPatcher1.Initializer?.Invoke();
 
 			// Then, perform the actual patching
 			var patchedAssemblies = new HashSet<string>();
 			var resolvedAssemblies = new Dictionary<string, string>();
 			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();
-		}
-
-		/// <summary>
-		///     Loads an individual assembly definition into the CLR.
-		/// </summary>
-		/// <param name="assembly">The assembly to load.</param>
-		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

+ 2 - 2
BepInEx.Preloader/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
 {
 	/// <summary>
 	///     A single assembly patcher.
 	/// </summary>
-	internal class PatcherPlugin : ICacheable
+	public class PatcherPlugin : ICacheable
 	{
 		/// <summary>
 		///     Target assemblies to patch.

+ 36 - 0
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")]

+ 5 - 5
BepInEx.Preloader/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());
 		}
 	}
 }

+ 29 - 22
BepInEx.Preloader/BepInEx.Preloader.csproj

@@ -4,15 +4,24 @@
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProjectGuid>{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}</ProjectGuid>
+    <ProjectGuid>{D404C973-441D-48ED-B266-C21320BA0D87}</ProjectGuid>
     <OutputType>Library</OutputType>
     <AppDesignerFolder>Properties</AppDesignerFolder>
-    <RootNamespace>BepInEx.Preloader</RootNamespace>
-    <AssemblyName>BepInEx.Preloader</AssemblyName>
+    <RootNamespace>BepInEx.Preloader.Unity</RootNamespace>
+    <AssemblyName>BepInEx.Preloader.Unity</AssemblyName>
     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <Deterministic>true</Deterministic>
   </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>none</DebugType>
     <Optimize>true</Optimize>
@@ -20,7 +29,7 @@
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
-    <DocumentationFile>..\bin\BepInEx.Preloader.xml</DocumentationFile>
+    <DocumentationFile>..\bin\BepInEx.Preloader.Unity.xml</DocumentationFile>
   </PropertyGroup>
   <ItemGroup>
     <Reference Include="Mono.Cecil, Version=0.10.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
@@ -35,33 +44,34 @@
     <Reference Include="Mono.Cecil.Rocks, Version=0.10.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
       <HintPath>..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll</HintPath>
     </Reference>
-    <Reference Include="MonoMod.RuntimeDetour, Version=20.3.5.1, Culture=neutral, PublicKeyToken=null">
-      <HintPath>..\packages\MonoMod.RuntimeDetour.20.3.5.1\lib\net35\MonoMod.RuntimeDetour.dll</HintPath>
-      <Private>True</Private>
+    <Reference Include="MonoMod.RuntimeDetour, Version=20.4.3.1, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MonoMod.RuntimeDetour.20.4.3.1\lib\net35\MonoMod.RuntimeDetour.dll</HintPath>
     </Reference>
-    <Reference Include="MonoMod.Utils, Version=20.3.5.1, Culture=neutral, PublicKeyToken=null">
-      <HintPath>..\packages\MonoMod.Utils.20.3.5.1\lib\net35\MonoMod.Utils.dll</HintPath>
-      <Private>True</Private>
+    <Reference Include="MonoMod.Utils, Version=20.4.3.1, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MonoMod.Utils.20.4.3.1\lib\net35\MonoMod.Utils.dll</HintPath>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="DoorstopEntrypoint.cs" />
     <Compile Include="EnvVars.cs" />
-    <Compile Include="Patching\AssemblyPatcher.cs" />
-    <Compile Include="Entrypoint.cs" />
-    <Compile Include="Patching\PatcherPlugin.cs" />
-    <Compile Include="RuntimeFixes\HarmonyFixes.cs" />
+    <Compile Include="UnityPreloader.cs" />
     <Compile Include="RuntimeFixes\TraceFix.cs" />
-    <Compile Include="Preloader.cs" />
-    <Compile Include="Logger\PreloaderLogWriter.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="RuntimeFixes\UnityPatches.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\BepInEx\BepInEx.csproj">
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">
       <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
-      <Name>BepInEx</Name>
+      <Name>BepInEx.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj">
+      <Project>{15f8bc38-a761-4f93-8903-1b531ac5d9f9}</Project>
+      <Name>BepInEx.Preloader.Core</Name>
     </ProjectReference>
     <ProjectReference Include="..\submodules\BepInEx.Harmony\BepInEx.Harmony\BepInEx.Harmony.csproj">
       <Project>{54161cfe-ff42-4dde-b161-3a49545db5cd}</Project>
@@ -72,8 +82,5 @@
       <Name>Harmony</Name>
     </ProjectReference>
   </ItemGroup>
-  <ItemGroup>
-    <None Include="packages.config" />
-  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 8 - 9
BepInEx.Preloader/Entrypoint.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;
 			}
 		}
-
 	}
 }

BepInEx.Preloader/EnvVars.cs → BepInEx.Preloader.Unity/EnvVars.cs


+ 36 - 0
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")]

BepInEx.Preloader/RuntimeFixes/TraceFix.cs → BepInEx.Preloader.Unity/RuntimeFixes/TraceFix.cs


+ 1 - 1
BepInEx.Preloader/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
 			{

+ 66 - 53
BepInEx.Preloader/Preloader.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
 {
 	/// <summary>
 	///     The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
 	/// </summary>
-	internal static class Preloader
+	internal static class UnityPreloader
 	{
 		/// <summary>
 		///     The log writer that is specific to the preloader.
 		/// </summary>
 		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<MethodDefinition>();
@@ -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
 		/// </summary>
 		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);
 			}
 		}
 

+ 2 - 2
BepInEx.Preloader/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="Mono.Cecil" version="0.10.4" targetFramework="net35" />
-  <package id="MonoMod.RuntimeDetour" version="20.3.5.1" targetFramework="net35" />
-  <package id="MonoMod.Utils" version="20.3.5.1" targetFramework="net35" />
+  <package id="MonoMod.RuntimeDetour" version="20.4.3.1" targetFramework="net35" />
+  <package id="MonoMod.Utils" version="20.4.3.1" targetFramework="net35" />
 </packages>

+ 7 - 12
BepInEx/Contract/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<BepInProcess>(GetType()),
-					Location = GetType().Assembly.Location
-				};
-			}
+				Metadata = metadata,
+				Instance = this,
+				Dependencies = MetadataHelper.GetDependencies(GetType()),
+				Processes = MetadataHelper.GetAttributes<BepInProcess>(GetType()),
+				Location = GetType().Assembly.Location
+			};
 
 			Logger = Logging.Logger.CreateLogSource(metadata.Name);
 

+ 74 - 0
BepInEx.Unity/BepInEx.Unity.csproj

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BepInEx.Unity</RootNamespace>
+    <AssemblyName>BepInEx.Unity</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>none</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <DocumentationFile>..\bin\BepInEx.Unity.xml</DocumentationFile>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Mono.Cecil, Version=0.10.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="UnityEngine">
+      <HintPath>..\lib\UnityEngine.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BaseUnityPlugin.cs" />
+    <Compile Include="Bootstrap\UnityChainloader.cs" />
+    <Compile Include="BepInExInstance.cs" />
+    <Compile Include="Configuration\KeyboardShortcut.cs" />
+    <Compile Include="Logging\UnityLogListener.cs" />
+    <Compile Include="Logging\UnityLogSource.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ThreadingHelper.cs" />
+    <Compile Include="UnityTomlTypeConverters.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">
+      <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
+      <Name>BepInEx.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj">
+      <Project>{15F8BC38-A761-4F93-8903-1B531AC5D9F9}</Project>
+      <Name>BepInEx.Preloader.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\BepInEx.Preloader.Unity\BepInEx.Preloader.Unity.csproj">
+      <Project>{d404c973-441d-48ed-b266-c21320ba0d87}</Project>
+      <Name>BepInEx.Preloader.Unity</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 13 - 0
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; }
+    }
+}

+ 118 - 0
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
+{
+	/// <summary>
+	/// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
+	/// </summary>
+	public class UnityChainloader : BaseChainloader<BaseUnityPlugin>
+	{
+		/// <summary>
+		/// The GameObject that all plugins are attached to as components.
+		/// </summary>
+		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<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Bind(
+			"Logging", "UnityLogListening",
+			true,
+			"Enables showing unity log messages in the BepInEx logging system.");
+
+		private static readonly ConfigEntry<bool> ConfigDiskWriteUnityLog = ConfigFile.CoreConfig.Bind(
+			"Logging.Disk", "WriteUnityLog",
+			false,
+			"Include unity log messages in log file output.");
+	}
+}

BepInEx/Configuration/KeyboardShortcut.cs → BepInEx.Unity/Configuration/KeyboardShortcut.cs


+ 3 - 2
BepInEx/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
 {
 	/// <summary>
 	/// Logs entries using Unity specific outputs.

+ 3 - 2
BepInEx/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
 {
 	/// <summary>
 	/// Logs entries using Unity specific outputs.

+ 7 - 6
BepInEx.Preloader/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")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

BepInEx/ThreadingHelper.cs → BepInEx.Unity/ThreadingHelper.cs


+ 41 - 0
BepInEx.Unity/UnityTomlTypeConverters.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Runtime.CompilerServices;
+using BepInEx.Configuration;
+using UnityEngine;
+
+namespace BepInEx.Unity
+{
+	/// <summary>
+	/// Config types that are unity specific
+	/// </summary>
+	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);
+		}
+	}
+}

+ 4 - 0
BepInEx.Unity/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Mono.Cecil" version="0.10.4" targetFramework="net35" />
+</packages>

+ 28 - 12
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}

+ 0 - 428
BepInEx/Bootstrap/Chainloader.cs

@@ -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
-{
-	/// <summary>
-	/// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
-	/// </summary>
-	public static class Chainloader
-	{
-		/// <summary>
-		/// The loaded and initialized list of plugins.
-		/// </summary>
-		public static Dictionary<string, PluginInfo> PluginInfos { get; } = new Dictionary<string, PluginInfo>();
-
-		private static readonly List<BaseUnityPlugin> _plugins = new List<BaseUnityPlugin>();
-		[Obsolete("Use PluginInfos instead")]
-		public static List<BaseUnityPlugin> Plugins
-		{
-			get
-			{
-				lock (_plugins)
-				{
-					_plugins.RemoveAll(x => x == null);
-					return _plugins.ToList();
-				}
-			}
-		}
-
-		public static List<string> DependencyErrors { get; } = new List<string>();
-
-		/// <summary>
-		/// The GameObject that all plugins are attached to as components.
-		/// </summary>
-		public static GameObject ManagerObject { get; private set; }
-
-
-		private static bool _loaded = false;
-		private static bool _initialized = false;
-
-		/// <summary>
-		/// Initializes BepInEx to be able to start the chainloader.
-		/// </summary>
-		public static void Initialize(string gameExePath, bool startConsole = true, ICollection<LogEventArgs> 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;
-		}
-
-		/// <summary>
-		/// The entrypoint for the BepInEx plugin system.
-		/// </summary>
-		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<string, Assembly>();
-
-				Logger.LogInfo($"{pluginInfos.Count} plugins to load");
-
-				// We use a sorted dictionary to ensure consistent load order
-				var dependencyDict = new SortedDictionary<string, IEnumerable<string>>(StringComparer.InvariantCultureIgnoreCase);
-				var pluginsByGUID = new Dictionary<string, PluginInfo>();
-
-				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<string>();
-				var processedPlugins = new Dictionary<string, Version>();
-
-				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<BepInDependency>();
-					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<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Bind(
-			"Logging", "UnityLogListening",
-			true,
-			"Enables showing unity log messages in the BepInEx logging system.");
-
-		private static readonly ConfigEntry<bool> ConfigDiskWriteUnityLog = ConfigFile.CoreConfig.Bind(
-			"Logging.Disk", "WriteUnityLog",
-			false,
-			"Include unity log messages in log file output.");
-
-		private static readonly ConfigEntry<bool> ConfigDiskAppend = ConfigFile.CoreConfig.Bind(
-			"Logging.Disk", "AppendLog",
-			false,
-			"Appends to the log file instead of overwriting, on game startup.");
-
-		private static readonly ConfigEntry<bool> ConfigDiskLogging = ConfigFile.CoreConfig.Bind(
-			"Logging.Disk", "Enabled",
-			true,
-			"Enables writing log messages to disk.");
-
-		private static readonly ConfigEntry<LogLevel> ConfigDiskConsoleDisplayedLevel = ConfigFile.CoreConfig.Bind(
-			"Logging.Disk", "DisplayedLogLevel",
-			LogLevel.Info,
-			"Only displays the specified log level and above in the console output.");
-		#endregion
-	}
-}

+ 3 - 3
BepInExTests/BepInExTests.csproj

@@ -73,9 +73,9 @@
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\BepInEx\BepInEx.csproj">
-      <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
-      <Name>BepInEx</Name>
+    <ProjectReference Include="..\BepInEx.Unity\BepInEx.Unity.csproj">
+      <Project>{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}</Project>
+      <Name>BepInEx.Unity</Name>
     </ProjectReference>
   </ItemGroup>
   <Choose>

+ 2 - 2
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"));