Преглед изворни кода

Fix merge conflict for plugins PR

Bepis пре 5 година
родитељ
комит
fe62f19b4b
46 измењених фајлова са 2852 додато и 2657 уклоњено
  1. 22 22
      BepInEx.Bootstrap/Entrypoint.cs
  2. 1 1
      BepInEx.Bootstrap/Properties/AssemblyInfo.cs
  3. 10 0
      BepInEx.Patcher/BepInEx.Patcher.csproj
  4. 14 14
      BepInEx.Patcher/EmbeddedResource.cs
  5. 195 185
      BepInEx.Patcher/Program.cs
  6. 2 2
      BepInEx.Patcher/Properties/AssemblyInfo.cs
  7. 1 0
      BepInEx.Patcher/packages.config
  8. 63 0
      BepInEx.Preloader/BepInEx.Preloader.csproj
  9. 52 0
      BepInEx.Preloader/Entrypoint.cs
  10. 176 0
      BepInEx.Preloader/Patcher/AssemblyPatcher.cs
  11. 36 0
      BepInEx.Preloader/Patcher/PatcherPlugin.cs
  12. 295 0
      BepInEx.Preloader/Preloader.cs
  13. 35 0
      BepInEx.Preloader/Properties/AssemblyInfo.cs
  14. 50 0
      BepInEx.Preloader/UnityPatches.cs
  15. 28 33
      BepInEx.sln
  16. 213 0
      BepInEx.sln.DotSettings
  17. 1 17
      BepInEx/BepInEx.csproj
  18. 0 161
      BepInEx/Bootstrap/AssemblyPatcher.cs
  19. 17 18
      BepInEx/Bootstrap/Chainloader.cs
  20. 0 51
      BepInEx/Bootstrap/Entrypoint.cs
  21. 0 342
      BepInEx/Bootstrap/Preloader.cs
  22. 63 61
      BepInEx/Bootstrap/TypeLoader.cs
  23. 0 29
      BepInEx/Bootstrap/UnityPatches.cs
  24. 191 190
      BepInEx/Config.cs
  25. 164 166
      BepInEx/ConfigWrapper.cs
  26. 54 54
      BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs
  27. 37 35
      BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs
  28. 77 77
      BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs
  29. 125 124
      BepInEx/ConsoleUtil/ConsoleWindow.cs
  30. 138 146
      BepInEx/ConsoleUtil/Kon.cs
  31. 45 45
      BepInEx/ConsoleUtil/SafeConsole.cs
  32. 163 166
      BepInEx/Contract/Attributes.cs
  33. 4 7
      BepInEx/Contract/BaseUnityPlugin.cs
  34. 0 58
      BepInEx/Deprecated/BepInLogger.cs
  35. 0 80
      BepInEx/Deprecated/Utility.cs
  36. 56 56
      BepInEx/Logging/BaseLogger.cs
  37. 47 47
      BepInEx/Logging/LogLevel.cs
  38. 19 19
      BepInEx/Logging/Logger.cs
  39. 126 127
      BepInEx/Logging/LoggerTraceListener.cs
  40. 90 89
      BepInEx/Logging/PreloaderLogWriter.cs
  41. 113 108
      BepInEx/Logging/UnityLogWriter.cs
  42. 34 34
      BepInEx/Paths.cs
  43. 3 1
      BepInEx/Properties/AssemblyInfo.cs
  44. 90 90
      BepInEx/Utility.cs
  45. 1 1
      doorstop/doorstop_config.ini
  46. 1 1
      submodules/BepInEx.Harmony

+ 22 - 22
BepInEx.Bootstrap/Entrypoint.cs

@@ -5,32 +5,32 @@ using System.Reflection;
 
 namespace BepInEx.Bootstrap
 {
-    public static class Entrypoint
-    {
-	    public static void Init()
-	    {
-		    AppDomain.CurrentDomain.AssemblyResolve += ResolveBepInEx;
+	public static class Entrypoint
+	{
+		public static void Init()
+		{
+			AppDomain.CurrentDomain.AssemblyResolve += ResolveBepInEx;
 
 			Linker.StartBepInEx();
-	    }
+		}
 
-	    private static readonly string LocalDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+		private static readonly string LocalDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
 
-	    private static Assembly ResolveBepInEx(object sender, ResolveEventArgs args)
-	    {
-		    string path = Path.Combine(LocalDirectory, $@"BepInEx\core\{new AssemblyName(args.Name).Name}.dll");
+		private static Assembly ResolveBepInEx(object sender, ResolveEventArgs args)
+		{
+			string path = Path.Combine(LocalDirectory, $@"BepInEx\core\{new AssemblyName(args.Name).Name}.dll");
 
-		    if (!File.Exists(path))
-			    return null;
+			if (!File.Exists(path))
+				return null;
 
-		    try
-		    {
-			    return Assembly.LoadFile(path);
-		    }
-		    catch (Exception)
-		    {
-			    return null;
-		    }
-	    }
-    }
+			try
+			{
+				return Assembly.LoadFile(path);
+			}
+			catch (Exception)
+			{
+				return null;
+			}
+		}
+	}
 }

+ 1 - 1
BepInEx.Bootstrap/Properties/AssemblyInfo.cs

@@ -33,4 +33,4 @@ using System.Runtime.InteropServices;
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 [assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 10 - 0
BepInEx.Patcher/BepInEx.Patcher.csproj

@@ -42,6 +42,16 @@
   <PropertyGroup>
     <StartupObject>BepInEx.Patcher.Program</StartupObject>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
+    <DebugSymbols>true</DebugSymbols>
+    <OutputPath>..\bin\patcher\</OutputPath>
+    <DefineConstants>TRACE;UNITY_2018</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>embedded</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
   <ItemGroup>
     <Reference Include="Mono.Cecil, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
       <HintPath>..\packages\Mono.Cecil.0.10.0\lib\net35\Mono.Cecil.dll</HintPath>

+ 14 - 14
BepInEx.Patcher/EmbeddedResource.cs

@@ -3,20 +3,20 @@ using System.Reflection;
 
 namespace BepInEx.Patcher
 {
-    internal static class EmbeddedResource
-    {
-        public static byte[] Get(string resourceName)
-        {
-            using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
-            {
-                int length = (int)stream.Length;
+	internal static class EmbeddedResource
+	{
+		public static byte[] Get(string resourceName)
+		{
+			using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
+			{
+				int length = (int)stream.Length;
 
-                byte[] buffer = new byte[length];
+				byte[] buffer = new byte[length];
 
-                stream.Read(buffer, 0, length);
+				stream.Read(buffer, 0, length);
 
-                return buffer;
-            }
-        }
-    }
-}
+				return buffer;
+			}
+		}
+	}
+}

+ 195 - 185
BepInEx.Patcher/Program.cs

@@ -8,189 +8,199 @@ using MethodAttributes = Mono.Cecil.MethodAttributes;
 
 namespace BepInEx.Patcher
 {
-    class Program
-    {
-        static void WriteError(string message)
-        {
-            Console.ForegroundColor = ConsoleColor.Red;
-            Console.WriteLine("Failed");
-            Console.ResetColor();
-            Console.WriteLine(message);
-            Console.WriteLine();
-        }
-
-        static void WriteSuccess()
-        {
-            Console.ForegroundColor = ConsoleColor.Green;
-            Console.WriteLine("Success");
-            Console.ResetColor();
-        }
-
-        static void Main(string[] args)
-        {
-            Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
-
-            if (args.Length >= 1) //short circuit for specific dll patch
-                Environment.Exit(PatchUnityExe(Path.GetDirectoryName(args[0]), args[0], out string message) ? 0 : 9999);
-
-            bool hasFound = false;
-            bool hasFailure = false;
-            int patchCount = 0;
-
-            foreach (string exePath in Directory.GetFiles(Directory.GetCurrentDirectory()))
-            {
-                string gameName = Path.GetFileNameWithoutExtension(exePath);
-
-                string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
-                string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll");
-
-                if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
-                    continue;
-
-                hasFound = true;
-
-                Console.Write($"Patching {gameName}... ");
-
-                if (PatchUnityExe(managedDir, unityOutputDLL, out string message))
-                {
-                    WriteSuccess();
-                    patchCount++;
-                }
-                else
-                {
-                    WriteError(message);
-                    hasFailure = true;
-                }
-            }
-
-            Console.WriteLine();
-
-            if (!hasFound)
-                Console.WriteLine("Didn't find any games to patch! Exiting.");
-            else
-                Console.WriteLine($"Patched {patchCount} assemblies!");
-
-            if (hasFailure)
-            {
-                Console.WriteLine("Press any key to continue...");
-                Console.ReadKey();
-            }
-            else
-                System.Threading.Thread.Sleep(3000);
-        }
-
-        static bool PatchUnityExe(string managedDir, string unityOutputDLL, out string message)
-        {
-            message = null;
-
-            try
-            {
-                string injectedDLL = Path.GetFullPath($"{managedDir}\\BepInEx.Bootstrap.dll");
-                File.WriteAllBytes(injectedDLL, EmbeddedResource.Get("BepInEx.Patcher.BepInEx.Bootstrap.dll"));
-
-                var defaultResolver = new DefaultAssemblyResolver();
-                defaultResolver.AddSearchDirectory(managedDir);
-                var rp = new ReaderParameters
-                {
-                    AssemblyResolver = defaultResolver
-                };
-
-                string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
-                
-                //determine which assembly to use as a base
-                AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
-
-                if (!VerifyAssembly(unity, out message))
-                {
-                    //try and fall back to .bak if exists
-                    if (File.Exists(unityBackupDLL))
-                    {
-                        unity.Dispose();
-                        unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
-
-                        if (!VerifyAssembly(unity, out message))
-                        {
-                            //can't use anything
-                            unity.Dispose();
-                            message += "\r\nThe backup is not usable.";
-                            return false;
-                        }
-                    }
-                    else
-                    {
-                        //can't use anything
-                        unity.Dispose();
-                        message += "\r\nNo backup exists.";
-                        return false;
-                    }
-                }
-                else
-                {
-                    //make a backup of the assembly
-                    File.Copy(unityOutputDLL, unityBackupDLL, true);
-                    unity.Dispose();
-                    unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
-                }
-                
-                //patch
-                using (unity)
-                using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(injectedDLL, rp))
-                {
-                    InjectAssembly(unity, injected);
-                    
-                    unity.Write(unityOutputDLL);
-                }
-            }
-            catch (Exception e)
-            {
-                message = e.ToString();
-                return false;
-            }
-
-            return true;
-        }
-
-        static void InjectAssembly(AssemblyDefinition unity, AssemblyDefinition injected)
-        {
-            //Entry point
-            var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Entrypoint")
-                .Methods.First(x => x.Name == "Init");
-
-            var injectMethod = unity.MainModule.ImportReference(originalInjectMethod);
-
-            var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
-
-            var voidType = unity.MainModule.ImportReference(typeof(void));
-            var cctor = new MethodDefinition(".cctor",
-                MethodAttributes.Static
-                | MethodAttributes.Private
-                | MethodAttributes.HideBySig
-                | MethodAttributes.SpecialName
-                | MethodAttributes.RTSpecialName,
-                voidType);
-
-            var ilp = cctor.Body.GetILProcessor();
-            ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
-            ilp.Append(ilp.Create(OpCodes.Ret));
-
-            sceneManager.Methods.Add(cctor);
-        }
-
-        static bool VerifyAssembly(AssemblyDefinition unity, out string message)
-        {
-            bool canPatch = true;
-            message = "";
-
-            //check if already patched
-            if (unity.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
-            {
-                canPatch = false;
-
-                message += "This assembly has already been patched by BepInEx.\r\n";
-            }
-
-	        message = message.Trim();
-            return canPatch;
-        }
-    }
+	class Program
+	{
+		static void WriteError(string message)
+		{
+			Console.ForegroundColor = ConsoleColor.Red;
+			Console.WriteLine("Failed");
+			Console.ResetColor();
+			Console.WriteLine(message);
+			Console.WriteLine();
+		}
+
+		static void WriteSuccess()
+		{
+			Console.ForegroundColor = ConsoleColor.Green;
+			Console.WriteLine("Success");
+			Console.ResetColor();
+		}
+
+		static void Main(string[] args)
+		{
+			Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
+
+			if (args.Length >= 1) //short circuit for specific dll patch
+				Environment.Exit(PatchUnityExe(Path.GetDirectoryName(args[0]), args[0], out string message) ? 0 : 9999);
+
+			bool hasFound = false;
+			bool hasFailure = false;
+			int patchCount = 0;
+
+			foreach (string exePath in Directory.GetFiles(Directory.GetCurrentDirectory()))
+			{
+				string gameName = Path.GetFileNameWithoutExtension(exePath);
+
+				string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
+
+#if UNITY_2018
+				string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll");
+#else
+				string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll");
+#endif
+
+
+				if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
+					continue;
+
+				hasFound = true;
+
+				Console.Write($"Patching {gameName}... ");
+
+				if (PatchUnityExe(managedDir, unityOutputDLL, out string message))
+				{
+					WriteSuccess();
+					patchCount++;
+				}
+				else
+				{
+					WriteError(message);
+					hasFailure = true;
+				}
+			}
+
+			Console.WriteLine();
+
+			if (!hasFound)
+				Console.WriteLine("Didn't find any games to patch! Exiting.");
+			else
+				Console.WriteLine($"Patched {patchCount} assemblies!");
+
+			if (hasFailure)
+			{
+				Console.WriteLine("Press any key to continue...");
+				Console.ReadKey();
+			}
+			else
+				System.Threading.Thread.Sleep(3000);
+		}
+
+		static bool PatchUnityExe(string managedDir, string unityOutputDLL, out string message)
+		{
+			message = null;
+
+			try
+			{
+				string injectedDLL = Path.GetFullPath($"{managedDir}\\BepInEx.Bootstrap.dll");
+				File.WriteAllBytes(injectedDLL, EmbeddedResource.Get("BepInEx.Patcher.BepInEx.Bootstrap.dll"));
+
+				var defaultResolver = new DefaultAssemblyResolver();
+				defaultResolver.AddSearchDirectory(managedDir);
+				var rp = new ReaderParameters
+				{
+					AssemblyResolver = defaultResolver
+				};
+
+#if UNITY_2018
+				string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll.bak");
+#else
+				string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
+#endif
+
+				//determine which assembly to use as a base
+				AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
+
+				if (!VerifyAssembly(unity, out message))
+				{
+					//try and fall back to .bak if exists
+					if (File.Exists(unityBackupDLL))
+					{
+						unity.Dispose();
+						unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
+
+						if (!VerifyAssembly(unity, out message))
+						{
+							//can't use anything
+							unity.Dispose();
+							message += "\r\nThe backup is not usable.";
+							return false;
+						}
+					}
+					else
+					{
+						//can't use anything
+						unity.Dispose();
+						message += "\r\nNo backup exists.";
+						return false;
+					}
+				}
+				else
+				{
+					//make a backup of the assembly
+					File.Copy(unityOutputDLL, unityBackupDLL, true);
+					unity.Dispose();
+					unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
+				}
+
+				//patch
+				using (unity)
+				using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(injectedDLL, rp))
+				{
+					InjectAssembly(unity, injected);
+
+					unity.Write(unityOutputDLL);
+				}
+			}
+			catch (Exception e)
+			{
+				message = e.ToString();
+				return false;
+			}
+
+			return true;
+		}
+
+		static void InjectAssembly(AssemblyDefinition unity, AssemblyDefinition injected)
+		{
+			//Entry point
+			var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Entrypoint")
+											   .Methods.First(x => x.Name == "Init");
+
+			var injectMethod = unity.MainModule.ImportReference(originalInjectMethod);
+
+			var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
+
+			var voidType = unity.MainModule.ImportReference(typeof(void));
+			var cctor = new MethodDefinition(".cctor",
+				MethodAttributes.Static
+				| MethodAttributes.Private
+				| MethodAttributes.HideBySig
+				| MethodAttributes.SpecialName
+				| MethodAttributes.RTSpecialName,
+				voidType);
+
+			var ilp = cctor.Body.GetILProcessor();
+			ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
+			ilp.Append(ilp.Create(OpCodes.Ret));
+
+			sceneManager.Methods.Add(cctor);
+		}
+
+		static bool VerifyAssembly(AssemblyDefinition unity, out string message)
+		{
+			bool canPatch = true;
+			message = "";
+
+			//check if already patched
+			if (unity.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
+			{
+				canPatch = false;
+
+				message += "This assembly has already been patched by BepInEx.\r\n";
+			}
+
+			message = message.Trim();
+			return canPatch;
+		}
+	}
 }

+ 2 - 2
BepInEx.Patcher/Properties/AssemblyInfo.cs

@@ -32,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("4.1.0.0")]
-[assembly: AssemblyFileVersion("4.1.0.0")]
+[assembly: AssemblyVersion("5.0.0.0")]
+[assembly: AssemblyFileVersion("5.0.0.0")]

+ 1 - 0
BepInEx.Patcher/packages.config

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <packages>
   <package id="ILRepack" version="2.0.15" targetFramework="net35" />
   <package id="ILRepack.MSBuild.Task" version="1.0.9" targetFramework="net35" />

+ 63 - 0
BepInEx.Preloader/BepInEx.Preloader.csproj

@@ -0,0 +1,63 @@
+<?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>{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BepInEx.Preloader</RootNamespace>
+    <AssemblyName>BepInEx.Preloader</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
+    <DebugType>none</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
+    <OutputPath>bin\v2018\</OutputPath>
+    <DefineConstants>TRACE;UNITY_2018</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>none</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Mono.Cecil">
+      <HintPath>..\lib\Mono.Cecil.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Patcher\AssemblyPatcher.cs" />
+    <Compile Include="Entrypoint.cs" />
+    <Compile Include="Patcher\PatcherPlugin.cs" />
+    <Compile Include="Preloader.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="UnityPatches.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx\BepInEx.csproj">
+      <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
+      <Name>BepInEx</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>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 52 - 0
BepInEx.Preloader/Entrypoint.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using System.Reflection;
+
+namespace BepInEx.Preloader
+{
+	internal static class Entrypoint
+	{
+		/// <summary>
+		///     The main entrypoint of BepInEx, called from Doorstop.
+		/// </summary>
+		/// <param name="args">
+		///     The arguments passed in from Doorstop. First argument is the path of the currently executing
+		///     process.
+		/// </param>
+		public static void Main(string[] args)
+		{
+			Paths.ExecutablePath(args[0]);
+			AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
+
+			Preloader.Run();
+		}
+
+		/// <summary>
+		///     A handler for <see cref="AppDomain.AssemblyResolve" /> to perform some special handling.
+		///     <para>
+		///         It attempts to check currently loaded assemblies (ignoring the version), and then checks the BepInEx/core path,
+		///         BepInEx/patchers path and the BepInEx folder, all in that order.
+		///     </para>
+		/// </summary>
+		/// <param name="sender"></param>
+		/// <param name="args"></param>
+		/// <returns></returns>
+		internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
+		{
+			var assemblyName = new AssemblyName(args.Name);
+
+			var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
+										 .FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
+
+			if (foundAssembly != null)
+				return foundAssembly;
+
+			if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
+				|| Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
+				|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
+				return foundAssembly;
+
+			return null;
+		}
+	}
+}

+ 176 - 0
BepInEx.Preloader/Patcher/AssemblyPatcher.cs

@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using BepInEx.Logging;
+using Mono.Cecil;
+
+namespace BepInEx.Preloader.Patcher
+{
+	/// <summary>
+	///     Delegate used in patching assemblies.
+	/// </summary>
+	/// <param name="assembly">The assembly that is being patched.</param>
+	internal 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
+	{
+		private static readonly List<PatcherPlugin> patchers = new List<PatcherPlugin>();
+
+		/// <summary>
+		///     Configuration value of whether assembly dumping is enabled or not.
+		/// </summary>
+		private static bool DumpingEnabled =>
+			Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
+
+		/// <summary>
+		///     Adds a single assembly patcher to the pool of applicable patches.
+		/// </summary>
+		/// <param name="patcher">Patcher to apply.</param>
+		public static void AddPatcher(PatcherPlugin patcher)
+		{
+			patchers.Add(patcher);
+		}
+
+		/// <summary>
+		///     Adds all patchers from all managed assemblies specified in a directory.
+		/// </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,
+			Func<Assembly, List<PatcherPlugin>> patcherLocator)
+		{
+			if (!Directory.Exists(directory))
+				return;
+
+			var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
+
+			foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
+				try
+				{
+					var assembly = Assembly.LoadFrom(assemblyPath);
+
+					foreach (var patcher in patcherLocator(assembly))
+						sortedPatchers.Add(patcher.Name, patcher);
+				}
+				catch (BadImageFormatException) { } //unmanaged DLL
+				catch (ReflectionTypeLoadException) { } //invalid references
+
+			foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
+				AddPatcher(patcher.Value);
+		}
+
+		private static void InitializePatchers()
+		{
+			foreach (var assemblyPatcher in patchers)
+				assemblyPatcher.Initializer?.Invoke();
+		}
+
+		private static void FinalizePatching()
+		{
+			foreach (var assemblyPatcher in patchers)
+				assemblyPatcher.Finalizer?.Invoke();
+		}
+
+		/// <summary>
+		///     Releases all patchers to let them be collected by GC.
+		/// </summary>
+		public static void DisposePatchers()
+		{
+			patchers.Clear();
+		}
+
+		/// <summary>
+		///     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)
+		{
+			// First, load patchable assemblies into Cecil
+			var assemblies = new Dictionary<string, AssemblyDefinition>();
+
+			foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
+			{
+				var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
+
+				//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.Log(LogLevel.Warning,
+						$"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));
+			}
+
+			// Next, initialize all the patchers
+			InitializePatchers();
+
+			// Then, perform the actual patching
+			var patchedAssemblies = new HashSet<string>();
+			foreach (var assemblyPatcher in patchers)
+				foreach (string targetDll in assemblyPatcher.TargetDLLs)
+					if (assemblies.TryGetValue(targetDll, out var assembly))
+					{
+						assemblyPatcher.Patcher?.Invoke(ref assembly);
+						assemblies[targetDll] = assembly;
+						patchedAssemblies.Add(targetDll);
+					}
+
+			// Finally, load all assemblies into memory
+			foreach (KeyValuePair<string, AssemblyDefinition> kv in assemblies)
+			{
+				string filename = kv.Key;
+				var assembly = kv.Value;
+
+				if (DumpingEnabled && patchedAssemblies.Contains(filename))
+					using (var mem = new MemoryStream())
+					{
+						string dirPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
+
+						if (!Directory.Exists(dirPath))
+							Directory.CreateDirectory(dirPath);
+
+						assembly.Write(mem);
+						File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
+					}
+
+				Load(assembly);
+				assembly.Dispose();
+			}
+
+			//run all finalizers
+			FinalizePatching();
+		}
+
+		/// <summary>
+		///     Loads an individual assembly defintion into the CLR.
+		/// </summary>
+		/// <param name="assembly">The assembly to load.</param>
+		public static void Load(AssemblyDefinition assembly)
+		{
+			using (var assemblyStream = new MemoryStream())
+			{
+				assembly.Write(assemblyStream);
+				Assembly.Load(assemblyStream.ToArray());
+			}
+		}
+	}
+}

+ 36 - 0
BepInEx.Preloader/Patcher/PatcherPlugin.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+
+namespace BepInEx.Preloader.Patcher
+{
+	/// <summary>
+	///     A single assembly patcher.
+	/// </summary>
+	internal class PatcherPlugin
+	{
+		/// <summary>
+		///     Target assemblies to patch.
+		/// </summary>
+		public IEnumerable<string> TargetDLLs { get; set; } = null;
+
+		/// <summary>
+		///     Initializer method that is run before any patching occurs.
+		/// </summary>
+		public Action Initializer { get; set; } = null;
+
+		/// <summary>
+		///     Finalizer method that is run after all patching is done.
+		/// </summary>
+		public Action Finalizer { get; set; } = null;
+
+		/// <summary>
+		///     The main patcher method that is called on every DLL defined in <see cref="TargetDLLs" />.
+		/// </summary>
+		public AssemblyPatcherDelegate Patcher { get; set; } = null;
+
+		/// <summary>
+		///     Name of the patcher.
+		/// </summary>
+		public string Name { get; set; } = string.Empty;
+	}
+}

+ 295 - 0
BepInEx.Preloader/Preloader.cs

@@ -0,0 +1,295 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using BepInEx.Logging;
+using BepInEx.Preloader.Patcher;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using UnityInjector.ConsoleUtil;
+using MethodAttributes = Mono.Cecil.MethodAttributes;
+
+namespace BepInEx.Preloader
+{
+	/// <summary>
+	///     The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
+	/// </summary>
+	internal static class Preloader
+	{
+		/// <summary>
+		///     The log writer that is specific to the preloader.
+		/// </summary>
+		private static PreloaderLogWriter PreloaderLog { get; set; }
+
+		public static void Run()
+		{
+			try
+			{
+				AllocateConsole();
+
+				UnityPatches.Apply();
+
+				PreloaderLog =
+					new PreloaderLogWriter(
+						Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
+				PreloaderLog.Enabled = true;
+
+				string consoleTile =
+					$"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
+				ConsoleWindow.Title = consoleTile;
+
+				Logger.SetLogger(PreloaderLog);
+
+				PreloaderLog.WriteLine(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];
+
+					PreloaderLog.WriteLine(attribute.Info);
+				}
+
+				Logger.Log(LogLevel.Message, "Preloader started");
+
+				string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
+
+				AssemblyPatcher.AddPatcher(new PatcherPlugin
+					{ TargetDLLs = new[] { entrypointAssembly }, Patcher = PatchEntrypoint });
+				AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods);
+
+				AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
+
+				AssemblyPatcher.DisposePatchers();
+			}
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Fatal, "Could not run preloader!");
+				Logger.Log(LogLevel.Fatal, ex);
+
+				PreloaderLog.Enabled = false;
+
+				try
+				{
+					if (!ConsoleWindow.IsAttatched)
+					{
+						//if we've already attached the console, then the log will already be written to the console
+						AllocateConsole();
+						Console.Write(PreloaderLog);
+					}
+				}
+				finally
+				{
+					File.WriteAllText(
+						Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
+						PreloaderLog + "\r\n" + ex);
+
+					PreloaderLog.Dispose();
+					PreloaderLog = null;
+				}
+			}
+		}
+
+		/// <summary>
+		///     Scans the assembly for classes that use the patcher contract, and returns a list of valid patchers.
+		/// </summary>
+		/// <param name="assembly">The assembly to scan.</param>
+		/// <returns>A list of assembly patchers that were found in the assembly.</returns>
+		public static List<PatcherPlugin> GetPatcherMethods(Assembly assembly)
+		{
+			var patcherMethods = new List<PatcherPlugin>();
+			var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase;
+
+			foreach (var type in assembly.GetExportedTypes())
+				try
+				{
+					if (type.IsInterface)
+						continue;
+
+					var targetsProperty = type.GetProperty("TargetDLLs",
+						flags,
+						null,
+						typeof(IEnumerable<string>),
+						Type.EmptyTypes,
+						null);
+
+					//first try get the ref patcher method
+					var patcher = type.GetMethod("Patch",
+						flags,
+						null,
+						CallingConventions.Any,
+						new[] { typeof(AssemblyDefinition).MakeByRefType() },
+						null);
+
+					if (patcher == null) //otherwise try getting the non-ref patcher method
+						patcher = type.GetMethod("Patch",
+							flags,
+							null,
+							CallingConventions.Any,
+							new[] { typeof(AssemblyDefinition) },
+							null);
+
+					if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
+						continue;
+
+					var assemblyPatcher = new PatcherPlugin();
+
+					assemblyPatcher.Name = $"{assembly.GetName().Name}{type.FullName}";
+					assemblyPatcher.Patcher = (ref AssemblyDefinition ass) =>
+					{
+						//we do the array fuckery here to get the ref result out
+						object[] args = { ass };
+
+						patcher.Invoke(null, args);
+
+						ass = (AssemblyDefinition)args[0];
+					};
+
+					assemblyPatcher.TargetDLLs = (IEnumerable<string>)targetsProperty.GetValue(null, null);
+
+					var initMethod = type.GetMethod("Initialize",
+						flags,
+						null,
+						CallingConventions.Any,
+						Type.EmptyTypes,
+						null);
+
+					if (initMethod != null)
+						assemblyPatcher.Initializer = () => initMethod.Invoke(null, null);
+
+					var finalizeMethod = type.GetMethod("Finish",
+						flags,
+						null,
+						CallingConventions.Any,
+						Type.EmptyTypes,
+						null);
+
+					if (finalizeMethod != null)
+						assemblyPatcher.Finalizer = () => finalizeMethod.Invoke(null, null);
+				}
+				catch (Exception ex)
+				{
+					Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
+					Logger.Log(LogLevel.Warning, $"{ex}");
+				}
+
+			Logger.Log(LogLevel.Info,
+				$"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}");
+
+			return patcherMethods;
+		}
+
+		/// <summary>
+		///     Inserts BepInEx's own chainloader entrypoint into UnityEngine.
+		/// </summary>
+		/// <param name="assembly">The assembly that will be attempted to be patched.</param>
+		public static void PatchEntrypoint(ref AssemblyDefinition assembly)
+		{
+			if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
+				throw new Exception(
+					"BepInEx has been detected to be patched! Please unpatch before using a patchless variant!");
+
+			string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
+			string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader");
+
+			bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor";
+
+
+			var entryType = assembly.MainModule.Types.FirstOrDefault(x => x.Name == entrypointType);
+
+			if (entryType == null)
+				throw new Exception("The entrypoint type is invalid! Please check your config.ini");
+
+			using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
+			{
+				var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
+												 .First(x => x.Name == "Initialize");
+
+				var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
+												  .First(x => x.Name == "Start");
+
+				var initMethod = assembly.MainModule.ImportReference(originalInitMethod);
+				var startMethod = assembly.MainModule.ImportReference(originalStartMethod);
+
+				var methods = new List<MethodDefinition>();
+
+				if (isCctor)
+				{
+					var cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
+
+					if (cctor == null)
+					{
+						cctor = new MethodDefinition(".cctor",
+							MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
+							| MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+							assembly.MainModule.ImportReference(typeof(void)));
+
+						entryType.Methods.Add(cctor);
+						var il = cctor.Body.GetILProcessor();
+						il.Append(il.Create(OpCodes.Ret));
+					}
+
+					methods.Add(cctor);
+				}
+				else
+				{
+					methods.AddRange(entryType.Methods.Where(x => x.Name == entrypointMethod));
+				}
+
+				if (!methods.Any())
+					throw new Exception("The entrypoint method is invalid! Please check your config.ini");
+
+				foreach (var method in methods)
+				{
+					var il = method.Body.GetILProcessor();
+
+					var ins = il.Body.Instructions.First();
+
+					il.InsertBefore(ins, il.Create(OpCodes.Ldstr, Paths.ExecutablePath)); //containerExePath
+					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,
+							initMethod)); //Chainloader.Initialize(string containerExePath, bool startConsole = true)
+					il.InsertBefore(ins, il.Create(OpCodes.Call, startMethod));
+				}
+			}
+		}
+
+		/// <summary>
+		///     Allocates a console window for use by BepInEx safely.
+		/// </summary>
+		public static void AllocateConsole()
+		{
+			bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
+			bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));
+
+			if (!console)
+				return;
+
+			try
+			{
+				ConsoleWindow.Attach();
+
+				var encoding = (uint)Encoding.UTF8.CodePage;
+
+				if (shiftjis)
+					encoding = 932;
+
+				ConsoleEncoding.ConsoleCodePage = encoding;
+				Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
+			}
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Error, "Failed to allocate console!");
+				Logger.Log(LogLevel.Error, ex);
+			}
+		}
+	}
+}

+ 35 - 0
BepInEx.Preloader/Properties/AssemblyInfo.cs

@@ -0,0 +1,35 @@
+using System.Reflection;
+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: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BepInEx.Preloader")]
+[assembly: AssemblyCopyright("Copyright ©  2019")]
+[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("f7abbe07-c02f-4f7c-bf6e-c6656bf588ca")]
+
+// 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")]

+ 50 - 0
BepInEx.Preloader/UnityPatches.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using BepInEx.Harmony;
+using Harmony;
+
+namespace BepInEx.Preloader
+{
+	internal static class UnityPatches
+	{
+		public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepinex.unitypatches");
+
+		public static Dictionary<string, string> AssemblyLocations { get; } =
+			new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
+
+		public static void Apply()
+		{
+			HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance);
+		}
+
+		[HarmonyPostfix]
+		[HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)]
+		public static void GetLocation(ref string __result, Assembly __instance)
+		{
+			if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
+				__result = location;
+		}
+
+		[HarmonyPostfix]
+		[HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)]
+		public static void GetCodeBase(ref string __result, Assembly __instance)
+		{
+			if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
+				__result = $"file://{location.Replace('\\', '/')}";
+		}
+
+#if UNITY_2018
+/*
+ * DESC: Workaround for Trace class not working because of missing .config file
+ * AFFECTS: Unity 2018+
+ */
+		[HarmonyPostfix, HarmonyPatch(typeof(AppDomain), nameof(AppDomain.SetupInformation), MethodType.Getter)]
+		public static void GetExeConfigName(AppDomainSetup __result)
+		{
+			__result.ApplicationBase = $"file://{Paths.GameRootPath}";
+			__result.ConfigurationFile = "app.config";
+		}
+#endif
+	}
+}

+ 28 - 33
BepInEx.sln

@@ -21,43 +21,38 @@ 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
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-		v2018 Release|Any CPU = v2018 Release|Any CPU
+		Legacy|Any CPU = Legacy|Any CPU
+		v2018|Any CPU = v2018|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
-		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.ActiveCfg = v2018|Any CPU
-		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.Build.0 = v2018|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
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = Release|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
-		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.ActiveCfg = Release|Any CPU
-		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.Build.0 = Release|Any CPU
-		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.Build.0 = Release|Any CPU
-		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
-		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
-		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.ActiveCfg = Release|Any CPU
-		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.Build.0 = Release|Any CPU
-		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.Build.0 = Release|Any CPU
-		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
-		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
-		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Release|Any CPU
-		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Release|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
-		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
-		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.Build.0 = v2018|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.Build.0 = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = Release|Any CPU
+		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
+		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.Build.0 = Release|Any CPU
+		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Any CPU.ActiveCfg = Release|Any CPU
+		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018|Any CPU.Build.0 = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Legacy|Any CPU.Build.0 = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.ActiveCfg = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.Build.0 = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Legacy|Any CPU.Build.0 = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.ActiveCfg = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.Build.0 = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.Build.0 = v2018|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 213 - 0
BepInEx.sln.DotSettings

@@ -0,0 +1,213 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/CodeEditing/Intellisense/CodeCompletion/IntelliSenseCompletingCharacters/CSharpCompletingCharacters/UpgradedFromVSSettings/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeEditing/Intellisense/CodeCompletion/TabKeyInsertType/@EntryValue">Insert</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeCleanup/RecentlyUsedProfile/@EntryValue">Built-in: Reformat Code</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGNMENT_TAB_FILL_STYLE/@EntryValue">OPTIMAL_FILL</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_FOR_STMT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_FOREACH_STMT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_WHILE_STMT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_ARRANGEMENT/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&#xD;
+&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;&#xD;
+  &lt;TypePattern DisplayName="Non-reorderable types"&gt;&#xD;
+    &lt;TypePattern.Match&gt;&#xD;
+      &lt;Or&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Interface" /&gt;&#xD;
+          &lt;Or&gt;&#xD;
+            &lt;HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /&gt;&#xD;
+            &lt;HasAttribute Name="System.Runtime.InteropServices.ComImport" /&gt;&#xD;
+          &lt;/Or&gt;&#xD;
+        &lt;/And&gt;&#xD;
+        &lt;Kind Is="Struct" /&gt;&#xD;
+        &lt;HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /&gt;&#xD;
+        &lt;HasAttribute Name="JetBrains.Annotations.NoReorder" /&gt;&#xD;
+      &lt;/Or&gt;&#xD;
+    &lt;/TypePattern.Match&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+  &lt;TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"&gt;&#xD;
+    &lt;TypePattern.Match&gt;&#xD;
+      &lt;And&gt;&#xD;
+        &lt;Kind Is="Class" /&gt;&#xD;
+        &lt;HasMember&gt;&#xD;
+          &lt;And&gt;&#xD;
+            &lt;Kind Is="Method" /&gt;&#xD;
+            &lt;HasAttribute Name="Xunit.FactAttribute" Inherited="True" /&gt;&#xD;
+          &lt;/And&gt;&#xD;
+        &lt;/HasMember&gt;&#xD;
+      &lt;/And&gt;&#xD;
+    &lt;/TypePattern.Match&gt;&#xD;
+    &lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Or&gt;&#xD;
+          &lt;Kind Is="Constructor" /&gt;&#xD;
+          &lt;And&gt;&#xD;
+            &lt;Kind Is="Method" /&gt;&#xD;
+            &lt;ImplementsInterface Name="System.IDisposable" /&gt;&#xD;
+          &lt;/And&gt;&#xD;
+        &lt;/Or&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Kind Order="Constructor" /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="All other members" /&gt;&#xD;
+    &lt;Entry Priority="100" DisplayName="Test Methods"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;HasAttribute Name="Xunit.FactAttribute" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+  &lt;TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"&gt;&#xD;
+    &lt;TypePattern.Match&gt;&#xD;
+      &lt;And&gt;&#xD;
+        &lt;Kind Is="Class" /&gt;&#xD;
+        &lt;HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /&gt;&#xD;
+      &lt;/And&gt;&#xD;
+    &lt;/TypePattern.Match&gt;&#xD;
+    &lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;Or&gt;&#xD;
+            &lt;HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /&gt;&#xD;
+            &lt;HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /&gt;&#xD;
+            &lt;HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /&gt;&#xD;
+            &lt;HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /&gt;&#xD;
+          &lt;/Or&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="All other members" /&gt;&#xD;
+    &lt;Entry Priority="100" DisplayName="Test Methods"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Method" /&gt;&#xD;
+          &lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+  &lt;TypePattern DisplayName="Default Pattern"&gt;&#xD;
+    &lt;Entry DisplayName="Static Fields and Constants"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Or&gt;&#xD;
+          &lt;Kind Is="Constant" /&gt;&#xD;
+          &lt;And&gt;&#xD;
+            &lt;Kind Is="Field" /&gt;&#xD;
+            &lt;Static /&gt;&#xD;
+          &lt;/And&gt;&#xD;
+        &lt;/Or&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Kind Order="Constant Field" /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Fields"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Field" /&gt;&#xD;
+          &lt;Not&gt;&#xD;
+            &lt;Static /&gt;&#xD;
+          &lt;/Not&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Readonly /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry Priority="100" DisplayName="Public Delegates"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Access Is="Public" /&gt;&#xD;
+          &lt;Kind Is="Delegate" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry Priority="100" DisplayName="Public Enums"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Access Is="Public" /&gt;&#xD;
+          &lt;Kind Is="Enum" /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Name /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Properties, Indexers"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Or&gt;&#xD;
+          &lt;Kind Is="Property" /&gt;&#xD;
+          &lt;Kind Is="Indexer" /&gt;&#xD;
+        &lt;/Or&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="Constructors"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Kind Is="Constructor" /&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;Static /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry Priority="100" DisplayName="Interface Implementations"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;And&gt;&#xD;
+          &lt;Kind Is="Member" /&gt;&#xD;
+          &lt;ImplementsInterface /&gt;&#xD;
+        &lt;/And&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+      &lt;Entry.SortBy&gt;&#xD;
+        &lt;ImplementsInterface Immediate="True" /&gt;&#xD;
+      &lt;/Entry.SortBy&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+    &lt;Entry DisplayName="All other members" /&gt;&#xD;
+    &lt;Entry DisplayName="Nested Types"&gt;&#xD;
+      &lt;Entry.Match&gt;&#xD;
+        &lt;Kind Is="Type" /&gt;&#xD;
+      &lt;/Entry.Match&gt;&#xD;
+    &lt;/Entry&gt;&#xD;
+  &lt;/TypePattern&gt;&#xD;
+&lt;/Patterns&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/MakeFieldPrivate/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UpdateExternalUsagesOnly/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/Generate/=EqualityMembers/@KeyIndexDefined">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/Generate/=EqualityMembers/Options/=ChangeEquals/@EntryIndexedValue">Skip</s:String>
+	<s:String x:Key="/Default/CodeStyle/Generate/=EqualityMembers/Options/=EqualityOperators/@EntryIndexedValue">False</s:String>
+	<s:String x:Key="/Default/CodeStyle/Generate/=EqualityMembers/Options/=ImplementIEquatable/@EntryIndexedValue">False</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/Generate/=Implementations/@KeyIndexDefined">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Async/@EntryIndexedValue">False</s:String>
+	<s:String x:Key="/Default/CodeStyle/Generate/=Implementations/Options/=Mutable/@EntryIndexedValue">False</s:String>
+	<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=2873BCACE542AE46A9AD6DC42B704221/@KeyIndexDefined">True</s:Boolean>
+	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=2873BCACE542AE46A9AD6DC42B704221/RelativePath/@EntryValue"></s:String>
+	<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File2873BCACE542AE46A9AD6DC42B704221/@KeyIndexDefined">True</s:Boolean>
+	<s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=File2873BCACE542AE46A9AD6DC42B704221/RelativePriority/@EntryValue">1</s:Double>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 1 - 17
BepInEx/BepInEx.csproj

@@ -13,7 +13,7 @@
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
     <DebugType>none</DebugType>
     <Optimize>true</Optimize>
@@ -27,16 +27,6 @@
   <PropertyGroup>
     <StartupObject />
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
-    <OutputPath>..\bin\</OutputPath>
-    <DefineConstants>TRACE;DEBUG</DefineConstants>
-    <Optimize>true</Optimize>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <ErrorReport>prompt</ErrorReport>
-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
-    <DebugSymbols>false</DebugSymbols>
-    <DocumentationFile>..\bin\BepInEx.xml</DocumentationFile>
-  </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
     <OutputPath>..\bin\</OutputPath>
     <DefineConstants>TRACE;UNITY_2018</DefineConstants>
@@ -56,11 +46,7 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Bootstrap\Entrypoint.cs" />
-    <Compile Include="Bootstrap\UnityPatches.cs" />
     <Compile Include="Contract\Attributes.cs" />
-    <Compile Include="Bootstrap\AssemblyPatcher.cs" />
-    <Compile Include="Bootstrap\Preloader.cs" />
     <Compile Include="Config.cs" />
     <Compile Include="ConfigWrapper.cs" />
     <Compile Include="ConsoleUtil\ConsoleEncoding\ConsoleEncoding.Buffers.cs" />
@@ -71,8 +57,6 @@
     <Compile Include="ConsoleUtil\SafeConsole.cs" />
     <Compile Include="Bootstrap\Chainloader.cs" />
     <Compile Include="Contract\BaseUnityPlugin.cs" />
-    <Compile Include="Deprecated\BepInLogger.cs" />
-    <Compile Include="Deprecated\Utility.cs" />
     <Compile Include="Logging\BaseLogger.cs" />
     <Compile Include="Logging\Logger.cs" />
     <Compile Include="Logging\LogLevel.cs" />

+ 0 - 161
BepInEx/Bootstrap/AssemblyPatcher.cs

@@ -1,161 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using BepInEx.Harmony;
-using Harmony;
-using Mono.Cecil;
-
-namespace BepInEx.Bootstrap
-{
-    /// <summary>
-    /// Delegate used in patching assemblies.
-    /// </summary>
-    /// <param name="assembly">The assembly that is being patched.</param>
-    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>
-    public static class AssemblyPatcher
-    {
-        /// <summary>
-        /// Configuration value of whether assembly dumping is enabled or not.
-        /// </summary>
-        private static bool DumpingEnabled => Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
-
-        /// <summary>
-        /// Patches and loads an entire directory of assemblies.
-        /// </summary>
-        /// <param name="directory">The directory to load assemblies from.</param>
-        /// <param name="patcherMethodDictionary">The dictionary of patchers and their targeted assembly filenames which they are patching.</param>
-        /// <param name="initializers">List of initializers to run before any patching starts</param>
-        /// <param name="finalizers">List of finalizers to run before returning</param>
-        public static void PatchAll(string directory, IDictionary<AssemblyPatcherDelegate, IEnumerable<string>> patcherMethodDictionary, IEnumerable<Action> initializers = null, IEnumerable<Action> finalizers = null)
-        {
-            //run all initializers
-            if (initializers != null)
-                foreach (Action init in initializers)
-                    init.Invoke();
-
-            //load all the requested assemblies
-            Dictionary<string, AssemblyDefinition> assemblies = new Dictionary<string, AssemblyDefinition>();
-
-            foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
-            {
-                var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
-
-                //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;
-                }
-
-                assemblies.Add(Path.GetFileName(assemblyPath), assembly);
-                PatchedAssemblyResolver.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
-            }
-
-            HashSet<string> patchedAssemblies = new HashSet<string>();
-
-            //call the patchers on the assemblies
-            foreach (var patcherMethod in patcherMethodDictionary)
-            {
-                foreach (string assemblyFilename in patcherMethod.Value)
-                {
-                    if (assemblies.TryGetValue(assemblyFilename, out var assembly))
-                    {
-                        Patch(ref assembly, patcherMethod.Key);
-                        assemblies[assemblyFilename] = assembly;
-                        patchedAssemblies.Add(assemblyFilename);
-                    }
-                }
-            }
-
-            // Finally, load all assemblies into memory
-            foreach (var kv in assemblies)
-            {
-                string filename = kv.Key;
-                var assembly = kv.Value;
-
-                if (DumpingEnabled && patchedAssemblies.Contains(filename))
-                {
-                    using (MemoryStream mem = new MemoryStream())
-                    {
-                        string dirPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
-
-                        if (!Directory.Exists(dirPath))
-                            Directory.CreateDirectory(dirPath);
-
-                        assembly.Write(mem);
-                        File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
-                    }
-                }
-
-                Load(assembly);
-                assembly.Dispose();
-            }
-
-            // Patch Assembly.Location and Assembly.CodeBase only if the assemblies were loaded from memory
-            PatchedAssemblyResolver.ApplyPatch();
-
-            //run all finalizers
-            if (finalizers != null)
-                foreach (Action finalizer in finalizers)
-                    finalizer.Invoke();
-        }
-
-        /// <summary>
-        /// Patches an individual assembly, without loading it.
-        /// </summary>
-        /// <param name="assembly">The assembly definition to apply the patch to.</param>
-        /// <param name="patcherMethod">The patcher to use to patch the assembly definition.</param>
-        public static void Patch(ref AssemblyDefinition assembly, AssemblyPatcherDelegate patcherMethod)
-        {
-            patcherMethod.Invoke(ref assembly);
-        }
-
-        /// <summary>
-        /// Loads an individual assembly defintion into the CLR.
-        /// </summary>
-        /// <param name="assembly">The assembly to load.</param>
-        public static void Load(AssemblyDefinition assembly)
-        {
-            using (MemoryStream assemblyStream = new MemoryStream())
-            {
-                assembly.Write(assemblyStream);
-                Assembly.Load(assemblyStream.ToArray());
-            }
-        }
-    }
-
-    internal static class PatchedAssemblyResolver
-	{
-		public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepis.bepinex.asmlocationfix");
-		
-		public static Dictionary<string, string> AssemblyLocations { get; } = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
-
-        public static void ApplyPatch()
-        {
-			HarmonyWrapper.PatchAll(typeof(PatchedAssemblyResolver), HarmonyInstance);
-        }
-
-        [HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)]
-        public static void GetLocation(ref string __result, Assembly __instance)
-        {
-            if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
-                __result = location;
-        }
-
-		[HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)]
-        public static void GetCodeBase(ref string __result, Assembly __instance)
-        {
-            if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
-                __result = $"file://{location.Replace('\\', '/')}";
-        }
-    }
-}

+ 17 - 18
BepInEx/Bootstrap/Chainloader.cs

@@ -15,17 +15,17 @@ namespace BepInEx.Bootstrap
 	/// <summary>
 	/// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
 	/// </summary>
-	public class Chainloader
+	public static class Chainloader
 	{
 		/// <summary>
 		/// The loaded and initialized list of plugins.
 		/// </summary>
-		public static List<BaseUnityPlugin> Plugins { get; protected set; } = new List<BaseUnityPlugin>();
+		public static List<BaseUnityPlugin> Plugins { get; private set; } = new List<BaseUnityPlugin>();
 
 		/// <summary>
 		/// The GameObject that all plugins are attached to as components.
 		/// </summary>
-		public static GameObject ManagerObject { get; protected set; } = new GameObject("BepInEx_Manager");
+		public static GameObject ManagerObject { get; private set; } = new GameObject("BepInEx_Manager");
 
 
 		private static bool _loaded = false;
@@ -49,19 +49,18 @@ namespace BepInEx.Bootstrap
 			if (startConsole)
 			{
 				ConsoleWindow.Attach();
-				
+
 				ConsoleEncoding.ConsoleCodePage = (uint)Encoding.UTF8.CodePage;
 				Console.OutputEncoding = Encoding.UTF8;
 			}
-			
+
 			UnityLogWriter unityLogWriter = new UnityLogWriter();
 
-			if (Preloader.PreloaderLog != null)
-				unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n");
+			if (Logger.CurrentLogger != null && Logger.CurrentLogger is PreloaderLogWriter preloaderLogger)
+				unityLogWriter.WriteToLog($"{preloaderLogger}\r\n");
 
 			Logger.SetLogger(unityLogWriter);
 
-
 			_initialized = true;
 		}
 
@@ -101,17 +100,17 @@ namespace BepInEx.Bootstrap
 				string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
 
 				var pluginTypes = TypeLoader.LoadTypes<BaseUnityPlugin>(Paths.PluginPath)
-					.Where(plugin =>
-					{
-						//Perform a filter for currently running process
-						var filters = MetadataHelper.GetAttributes<BepInProcess>(plugin);
+											.Where(plugin =>
+											{
+												//Perform a filter for currently running process
+												var filters = MetadataHelper.GetAttributes<BepInProcess>(plugin);
 
-						if (!filters.Any())
-							return true;
+												if (!filters.Any())
+													return true;
 
-						return filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
-					})
-					.ToList();
+												return filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
+											})
+											.ToList();
 
 				Logger.Log(LogLevel.Info, $"{pluginTypes.Count} plugins selected");
 
@@ -142,7 +141,7 @@ namespace BepInEx.Bootstrap
 					{
 						var metadata = MetadataHelper.GetMetadata(t);
 
-						var plugin = (BaseUnityPlugin) ManagerObject.AddComponent(t);
+						var plugin = (BaseUnityPlugin)ManagerObject.AddComponent(t);
 
 						Plugins.Add(plugin);
 						Logger.Log(LogLevel.Info, $"Loaded [{metadata.Name} {metadata.Version}]");

+ 0 - 51
BepInEx/Bootstrap/Entrypoint.cs

@@ -1,51 +0,0 @@
-using System;
-using System.Linq;
-using System.Reflection;
-
-namespace BepInEx.Bootstrap
-{
-    public static class Entrypoint
-    {
-        /// <summary>
-        ///     The main entrypoint of BepInEx, called from Doorstop.
-        /// </summary>
-        /// <param name="args">
-        ///     The arguments passed in from Doorstop. First argument is the path of the currently executing
-        ///     process.
-        /// </param>
-        public static void Main(string[] args)
-        {
-            Paths.SetExecutablePath(args[0]);
-			AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
-
-			Preloader.Run();
-        }
-
-        /// <summary>
-        ///     A handler for <see cref="AppDomain.AssemblyResolve" /> to perform some special handling.
-        ///     <para>
-        ///         It attempts to check currently loaded assemblies (ignoring the version), and then checks the BepInEx/core path,
-        ///         BepInEx/patchers path and the BepInEx folder, all in that order.
-        ///     </para>
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="args"></param>
-        /// <returns></returns>
-        internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
-        {
-            var assemblyName = new AssemblyName(args.Name);
-
-            var foundAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
-
-            if (foundAssembly != null)
-                return foundAssembly;
-
-            if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
-                || Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
-                || Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
-                return foundAssembly;
-
-            return null;
-        }
-    }
-}

+ 0 - 342
BepInEx/Bootstrap/Preloader.cs

@@ -1,342 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using BepInEx.Logging;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using UnityInjector.ConsoleUtil;
-using MethodAttributes = Mono.Cecil.MethodAttributes;
-
-namespace BepInEx.Bootstrap
-{
-    /// <summary>
-    ///     The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
-    /// </summary>
-    internal static class Preloader
-	{
-		/// <summary>
-		///     The list of finalizers that were loaded from the patcher contract.
-		/// </summary>
-		public static List<Action> Finalizers { get; } = new List<Action>();
-
-		/// <summary>
-		///     The list of initializers that were loaded from the patcher contract.
-		/// </summary>
-		public static List<Action> Initializers { get; } = new List<Action>();
-
-		/// <summary>
-		///     The dictionary of currently loaded patchers. The key is the patcher delegate that will be used to patch, and the
-		///     value is a list of filenames of assemblies that the patcher is targeting.
-		/// </summary>
-		public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> PatcherDictionary { get; } =
-			new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
-
-		/// <summary>
-		///     The log writer that is specific to the preloader.
-		/// </summary>
-		public static PreloaderLogWriter PreloaderLog { get; private set; }
-
-		public static void Run()
-		{
-		    try
-		    {
-		        AllocateConsole();
-
-		        UnityPatches.Apply();
-
-		        PreloaderLog = 
-		                new PreloaderLogWriter(Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
-		        PreloaderLog.Enabled = true;
-
-		        string consoleTile =
-		                $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
-		        ConsoleWindow.Title = consoleTile;
-
-		        Logger.SetLogger(PreloaderLog);
-
-		        PreloaderLog.WriteLine(consoleTile);
-
-#if DEBUG
-
-				object[] attributes = typeof(DebugInfoAttribute).Assembly.GetCustomAttributes(typeof(DebugInfoAttribute), false);
-				
-				if (attributes.Length > 0)
-				{
-					var attribute = (DebugInfoAttribute)attributes[0];
-
-					PreloaderLog.WriteLine(attribute.Info);
-				}
-
-#endif
-
-				Logger.Log(LogLevel.Message, "Preloader started");
-
-				string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
-
-				AddPatcher(new[] {entrypointAssembly}, PatchEntrypoint);
-
-				if (Directory.Exists(Paths.PatcherPluginPath))
-				{
-					var sortedPatchers = new SortedDictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
-
-					foreach (string assemblyPath in Directory.GetFiles(Paths.PatcherPluginPath, "*.dll"))
-						try
-						{
-							var assembly = Assembly.LoadFrom(assemblyPath);
-
-							foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in GetPatcherMethods(assembly))
-							    try
-							    {
-							        sortedPatchers.Add(kv.Key, kv.Value);
-							    }
-							    catch (ArgumentException)
-							    {
-                                    Logger.Log(LogLevel.Warning, $"Found duplicate of patcher {kv.Key}!");
-							    }
-						}
-						catch (BadImageFormatException) { } //unmanaged DLL
-						catch (ReflectionTypeLoadException) { } //invalid references
-
-					foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in sortedPatchers)
-						AddPatcher(kv.Value.Value, kv.Value.Key);
-				}
-
-				AssemblyPatcher.PatchAll(Paths.ManagedPath, PatcherDictionary, Initializers, Finalizers);
-			}
-			catch (Exception ex)
-			{
-				Logger.Log(LogLevel.Fatal, "Could not run preloader!");
-				Logger.Log(LogLevel.Fatal, ex);
-
-				PreloaderLog.Enabled = false;
-
-				try
-				{
-					if (!ConsoleWindow.IsAttatched)
-					{
-						//if we've already attached the console, then the log will already be written to the console
-						AllocateConsole();
-						Console.Write(PreloaderLog);
-					}
-				}
-				finally
-				{
-					File.WriteAllText(Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
-						PreloaderLog.ToString());
-
-					PreloaderLog.Dispose();
-				}
-			}
-		}
-
-		/// <summary>
-		///     Scans the assembly for classes that use the patcher contract, and returns a dictionary of the patch methods.
-		/// </summary>
-		/// <param name="assembly">The assembly to scan.</param>
-		/// <returns>A dictionary of delegates which will be used to patch the targeted assemblies.</returns>
-		public static Dictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> GetPatcherMethods(Assembly assembly)
-		{
-			var patcherMethods = new Dictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
-
-			foreach (var type in assembly.GetExportedTypes())
-				try
-				{
-					if (type.IsInterface)
-						continue;
-
-					var targetsProperty = type.GetProperty("TargetDLLs",
-						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-						null,
-						typeof(IEnumerable<string>),
-						Type.EmptyTypes,
-						null);
-
-					//first try get the ref patcher method
-					var patcher = type.GetMethod("Patch",
-						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-						null,
-						CallingConventions.Any,
-						new[] {typeof(AssemblyDefinition).MakeByRefType()},
-						null);
-
-					if (patcher == null) //otherwise try getting the non-ref patcher method
-						patcher = type.GetMethod("Patch",
-							BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-							null,
-							CallingConventions.Any,
-							new[] {typeof(AssemblyDefinition)},
-							null);
-
-					if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
-						continue;
-
-					AssemblyPatcherDelegate patchDelegate = (ref AssemblyDefinition ass) =>
-					{
-						//we do the array fuckery here to get the ref result out
-						object[] args = {ass};
-
-						patcher.Invoke(null, args);
-
-						ass = (AssemblyDefinition) args[0];
-					};
-
-					var targets = (IEnumerable<string>) targetsProperty.GetValue(null, null);
-
-					patcherMethods[$"{assembly.GetName().Name}{type.FullName}"] = new KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>(patchDelegate, targets);
-
-					var initMethod = type.GetMethod("Initialize",
-						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-						null,
-						CallingConventions.Any,
-						Type.EmptyTypes,
-						null);
-
-					if (initMethod != null)
-						Initializers.Add(() => initMethod.Invoke(null, null));
-
-					var finalizeMethod = type.GetMethod("Finish",
-						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-						null,
-						CallingConventions.Any,
-						Type.EmptyTypes,
-						null);
-
-					if (finalizeMethod != null)
-						Finalizers.Add(() => finalizeMethod.Invoke(null, null));
-				}
-				catch (Exception ex)
-				{
-					Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
-					Logger.Log(LogLevel.Warning, $"{ex}");
-				}
-
-			Logger.Log(LogLevel.Info,
-				$"Loaded {patcherMethods.Select(x => x.Key).Distinct().Count()} patcher methods from {assembly.GetName().Name}");
-
-			return patcherMethods;
-		}
-
-		/// <summary>
-		///     Inserts BepInEx's own chainloader entrypoint into UnityEngine.
-		/// </summary>
-		/// <param name="assembly">The assembly that will be attempted to be patched.</param>
-		public static void PatchEntrypoint(ref AssemblyDefinition assembly)
-		{
-			if (assembly.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
-			{
-				throw new Exception("BepInEx has been detected to be patched! Please unpatch before using a patchless variant!");
-			}
-
-			string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
-			string entrypointMethod = Config.GetEntry("entrypoint-method", ".cctor", "Preloader");
-
-			bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor";
-
-
-			var entryType = assembly.MainModule.Types.FirstOrDefault(x => x.Name == entrypointType);
-
-			if (entryType == null)
-			{
-				throw new Exception("The entrypoint type is invalid! Please check your config.ini");
-			}
-			
-			using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
-			{
-				var originalInitMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
-					.First(x => x.Name == "Initialize");
-
-				var originalStartMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
-					.First(x => x.Name == "Start");
-
-				var initMethod = assembly.MainModule.ImportReference(originalInitMethod);
-				var startMethod = assembly.MainModule.ImportReference(originalStartMethod);
-				
-				List<MethodDefinition> methods = new List<MethodDefinition>();
-
-				if (isCctor)
-				{
-					MethodDefinition cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
-
-					if (cctor == null)
-					{
-						cctor = new MethodDefinition(".cctor",
-							MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
-							| MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
-							assembly.MainModule.ImportReference(typeof(void)));
-
-						entryType.Methods.Add(cctor);
-						ILProcessor il = cctor.Body.GetILProcessor();
-						il.Append(il.Create(OpCodes.Ret));
-					}
-
-					methods.Add(cctor);
-				}
-				else
-				{
-					methods.AddRange(entryType.Methods.Where(x => x.Name == entrypointMethod));
-				}
-
-				if (!methods.Any())
-				{
-					throw new Exception("The entrypoint method is invalid! Please check your config.ini");
-				}
-
-				foreach (var method in methods)
-				{
-					var il = method.Body.GetILProcessor();
-
-					Instruction ins = il.Body.Instructions.First();
-						
-					il.InsertBefore(ins, il.Create(OpCodes.Ldstr, Paths.ExecutablePath)); //containerExePath
-					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, initMethod)); //Chainloader.Initialize(string containerExePath, bool startConsole = true)
-					il.InsertBefore(ins, il.Create(OpCodes.Call, startMethod));
-				}
-			}
-		}
-
-		/// <summary>
-		///     Allocates a console window for use by BepInEx safely.
-		/// </summary>
-		public static void AllocateConsole()
-		{
-			bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
-			bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));
-
-			if (!console) 
-				return;
-
-			try
-			{
-				ConsoleWindow.Attach();
-
-				var encoding = (uint) Encoding.UTF8.CodePage;
-
-				if (shiftjis)
-					encoding = 932;
-
-				ConsoleEncoding.ConsoleCodePage = encoding;
-				Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
-			}
-			catch (Exception ex)
-			{
-				Logger.Log(LogLevel.Error, "Failed to allocate console!");
-				Logger.Log(LogLevel.Error, ex);
-			}
-		}
-
-		/// <summary>
-		///     Adds the patcher to the patcher dictionary.
-		/// </summary>
-		/// <param name="dllNames">The list of DLL filenames to be patched.</param>
-		/// <param name="patcher">The method that will perform the patching.</param>
-		public static void AddPatcher(IEnumerable<string> dllNames, AssemblyPatcherDelegate patcher)
-		{
-			PatcherDictionary[patcher] = dllNames;
-		}
-	}
-}

+ 63 - 61
BepInEx/Bootstrap/TypeLoader.cs

@@ -10,68 +10,70 @@ namespace BepInEx.Bootstrap
 	/// <summary>
 	/// Provides methods for loading specified types from an assembly.
 	/// </summary>
-    public static class TypeLoader
-    {
-        /// <summary>
-        /// Loads a list of types from a directory containing assemblies, that derive from a base type.
-        /// </summary>
-        /// <typeparam name="T">The specific base type to search for.</typeparam>
-        /// <param name="directory">The directory to search for assemblies.</param>
-        /// <returns>Returns a list of found derivative types.</returns>
-        public static IEnumerable<Type> LoadTypes<T>(string directory)
-        {
-            List<Type> types = new List<Type>();
-            Type pluginType = typeof(T);
+	public static class TypeLoader
+	{
+		/// <summary>
+		/// Loads a list of types from a directory containing assemblies, that derive from a base type.
+		/// </summary>
+		/// <typeparam name="T">The specific base type to search for.</typeparam>
+		/// <param name="directory">The directory to search for assemblies.</param>
+		/// <returns>Returns a list of found derivative types.</returns>
+		public static IEnumerable<Type> LoadTypes<T>(string directory)
+		{
+			List<Type> types = new List<Type>();
+			Type pluginType = typeof(T);
 
-            foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
-            {
-                try
-                {
-                    AssemblyName an = AssemblyName.GetAssemblyName(dll);
-                    Assembly assembly = Assembly.Load(an);
+			foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
+			{
+				try
+				{
+					AssemblyName an = AssemblyName.GetAssemblyName(dll);
+					Assembly assembly = Assembly.Load(an);
 
-                    foreach (Type type in assembly.GetTypes())
-                    {
-                        if (!type.IsInterface && !type.IsAbstract && type.BaseType == pluginType)
-                            types.Add(type);
-                    }
-                }
-                catch (BadImageFormatException) { } //unmanaged DLL
-                catch (ReflectionTypeLoadException ex)
-                {
-                    Logger.Log(LogLevel.Error, $"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
-                    Logger.Log(LogLevel.Debug, TypeLoadExceptionToString(ex));
-                }
-            }
+					foreach (Type type in assembly.GetTypes())
+					{
+						if (!type.IsInterface && !type.IsAbstract && type.BaseType == pluginType)
+							types.Add(type);
+					}
+				}
+				catch (BadImageFormatException) { } //unmanaged DLL
+				catch (ReflectionTypeLoadException ex)
+				{
+					Logger.Log(LogLevel.Error, $"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
+					Logger.Log(LogLevel.Debug, TypeLoadExceptionToString(ex));
+				}
+			}
 
-            return types;
-        }
+			return types;
+		}
 
-        private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
-        {
-            StringBuilder sb = new StringBuilder();
-            foreach (Exception exSub in ex.LoaderExceptions)
-            {
-                sb.AppendLine(exSub.Message);
-                if (exSub is FileNotFoundException exFileNotFound)
-                {
-                    if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
-                    {
-                        sb.AppendLine("Fusion Log:");
-                        sb.AppendLine(exFileNotFound.FusionLog);
-                    }
-                }
-                else if (exSub is FileLoadException exLoad)
-                {
-                    if (!string.IsNullOrEmpty(exLoad.FusionLog))
-                    {
-                        sb.AppendLine("Fusion Log:");
-                        sb.AppendLine(exLoad.FusionLog);
-                    }
-                }
-                sb.AppendLine();
-            }
-            return sb.ToString();
-        }
-    }
-}
+		private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
+		{
+			StringBuilder sb = new StringBuilder();
+			foreach (Exception exSub in ex.LoaderExceptions)
+			{
+				sb.AppendLine(exSub.Message);
+				if (exSub is FileNotFoundException exFileNotFound)
+				{
+					if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
+					{
+						sb.AppendLine("Fusion Log:");
+						sb.AppendLine(exFileNotFound.FusionLog);
+					}
+				}
+				else if (exSub is FileLoadException exLoad)
+				{
+					if (!string.IsNullOrEmpty(exLoad.FusionLog))
+					{
+						sb.AppendLine("Fusion Log:");
+						sb.AppendLine(exLoad.FusionLog);
+					}
+				}
+
+				sb.AppendLine();
+			}
+
+			return sb.ToString();
+		}
+	}
+}

+ 0 - 29
BepInEx/Bootstrap/UnityPatches.cs

@@ -1,29 +0,0 @@
-using System;
-using BepInEx.Harmony;
-using Harmony;
-
-namespace BepInEx.Bootstrap
-{
-    internal static class UnityPatches
-	{
-		public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepinex.unitypatches");
-
-		public static void Apply()
-        {
-            HarmonyWrapper.PatchAll(typeof(UnityPatches), HarmonyInstance);
-        }
-
-#if UNITY_2018
-        /*
-         * DESC: Workaround for Trace class not working because of missing .config file
-         * AFFECTS: Unity 2018+
-         */
-        [HarmonyPostfix, HarmonyPatch(typeof(AppDomain), nameof(AppDomain.SetupInformation), MethodType.Getter)]
-        public static void GetExeConfigName(AppDomainSetup __result)
-        {
-            __result.ApplicationBase = $"file://{Paths.GameRootPath}";
-            __result.ConfigurationFile = "app.config";
-        }
-#endif
-    }
-}

+ 191 - 190
BepInEx/Config.cs

@@ -6,55 +6,55 @@ using BepInEx.Logging;
 
 namespace BepInEx
 {
-    /// <summary>
-    /// A helper class to handle persistent data.
-    /// </summary>
-    public static class Config
-    {
-        private static readonly Dictionary<string, Dictionary<string, string>> cache = new Dictionary<string, Dictionary<string, string>>();
+	/// <summary>
+	/// A helper class to handle persistent data.
+	/// </summary>
+	public static class Config
+	{
+		private static readonly Dictionary<string, Dictionary<string, string>> cache = new Dictionary<string, Dictionary<string, string>>();
 
-        private static string ConfigPath => Path.Combine(Paths.BepInExRootPath, "config.ini");
+		private static string ConfigPath => Path.Combine(Paths.BepInExRootPath, "config.ini");
 
-        private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
+		private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
 
-        private static void RaiseConfigReloaded()
-        {
-            ConfigReloaded?.Invoke();
-        }
+		private static void RaiseConfigReloaded()
+		{
+			ConfigReloaded?.Invoke();
+		}
 
 		/// <summary>
 		/// An event that is fired every time the config is reloaded.
 		/// </summary>
-        public static event Action ConfigReloaded;
-
-        /// <summary>
-        /// If enabled, writes the config to disk every time a value is set.
-        /// </summary>
-        public static bool SaveOnConfigSet { get; set; } = true;
-
-        static Config()
-        {
-            if (File.Exists(ConfigPath))
-            {
-                ReloadConfig();
-            }
-            else
-            {
-                SaveConfig();
-            }
-        }
-
-	    /// <summary>
-	    /// Returns the value of the key if found, otherwise returns the default value.
-	    /// </summary>
-	    /// <param name="key">The key to search for.</param>
-	    /// <param name="defaultValue">The default value to return if the key is not found.</param>
-	    /// <param name="section">The section of the config to search the key for.</param>
-	    /// <returns>The value of the key.</returns>
-	    public static string GetEntry(string key, string defaultValue = "", string section = "")
-        {
-	        try
-	        {
+		public static event Action ConfigReloaded;
+
+		/// <summary>
+		/// If enabled, writes the config to disk every time a value is set.
+		/// </summary>
+		public static bool SaveOnConfigSet { get; set; } = true;
+
+		static Config()
+		{
+			if (File.Exists(ConfigPath))
+			{
+				ReloadConfig();
+			}
+			else
+			{
+				SaveConfig();
+			}
+		}
+
+		/// <summary>
+		/// Returns the value of the key if found, otherwise returns the default value.
+		/// </summary>
+		/// <param name="key">The key to search for.</param>
+		/// <param name="defaultValue">The default value to return if the key is not found.</param>
+		/// <param name="section">The section of the config to search the key for.</param>
+		/// <returns>The value of the key.</returns>
+		public static string GetEntry(string key, string defaultValue = "", string section = "")
+		{
+			try
+			{
 				key = Sanitize(key);
 				section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
 
@@ -70,157 +70,158 @@ namespace BepInEx
 				SetEntry(key, defaultValue, section);
 				return defaultValue;
 			}
-	        catch (Exception ex)
-	        {
-		        Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to read config entry!");
-		        Logger.Log(LogLevel.Error, ex);
-		        return defaultValue;
-	        }
-        }
-
-        /// <summary>
-        /// Reloads the config from disk. Unwritten changes are lost.
-        /// </summary>
-        public static void ReloadConfig()
-        {
-            cache.Clear();
-
-            string currentSection = "";
-
-            foreach (string rawLine in File.ReadAllLines(ConfigPath))
-            {
-                string line = rawLine.Trim();
-
-                bool commentIndex = line.StartsWith(";") || line.StartsWith("#");
-
-                if (commentIndex) //trim comment
-                    continue;
-
-                if (line.StartsWith("[") && line.EndsWith("]")) //section
-                {
-                    currentSection = line.Substring(1, line.Length - 2);
-                    continue;
-                }
-
-                string[] split = line.Split('='); //actual config line
-                if (split.Length != 2)
-                    continue; //empty/invalid line
-
-                if (!cache.ContainsKey(currentSection))
-                    cache[currentSection] = new Dictionary<string, string>();
-
-                cache[currentSection][split[0]] = split[1];
-            }
-
-            RaiseConfigReloaded();
-        }
-
-        /// <summary>
-        /// Writes the config to disk.
-        /// </summary>
-        public static void SaveConfig()
-        {
-            using (StreamWriter writer = new StreamWriter(File.Create(ConfigPath), System.Text.Encoding.UTF8))
-                foreach (var sectionKv in cache)
-                {
-                    writer.WriteLine($"[{sectionKv.Key}]");
-
-                    foreach (var entryKv in sectionKv.Value)
-                        writer.WriteLine($"{entryKv.Key}={entryKv.Value}");
-
-                    writer.WriteLine();
-                }
-        }
-
-        /// <summary>
-        /// Sets the value of the key in the config.
-        /// </summary>
-        /// <param name="key">The key to set the value to.</param>
-        /// <param name="value">The value to set.</param>
-        public static void SetEntry(string key, string value, string section = "")
-        {
-	        try
-	        {
-		        key = Sanitize(key);
-		        section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
-
-		        if (!cache.TryGetValue(section, out Dictionary<string, string> subdict))
-		        {
-			        subdict = new Dictionary<string, string>();
-			        cache[section] = subdict;
-		        }
-
-		        subdict[key] = value;
-
-		        if (SaveOnConfigSet)
-			        SaveConfig();
-	        }
-	        catch (Exception ex)
-	        {
-		        Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to save config entry!");
-		        Logger.Log(LogLevel.Error, ex);
-	        }
-        }
-       
-        /// <summary>
-        /// Returns wether a value is currently set.
-        /// </summary>
-        /// <param name="key">The key to check against</param>
-        /// <param name="section">The section to check in</param>
-        /// <returns>True if the key is present</returns>
-        public static bool HasEntry(string key, string section = "")
-        {
-            key = Sanitize(key);
-            section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
-
-            return cache.ContainsKey(section) && cache[section].ContainsKey(key);
-        }
-
-
-        /// <summary>
-        /// Removes a value from the config.
-        /// </summary>
-        /// <param name="key">The key to remove</param>
-        /// <param name="section">The section to remove from</param>
-        /// <returns>True if the key was removed</returns>
-        public static bool UnsetEntry(string key, string section = "")
-        {
-            key = Sanitize(key);
-            section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
-
-            if (!HasEntry(key, section))
-                return false;
-
-            cache[section].Remove(key);
-            return true;
-        }
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to read config entry!");
+				Logger.Log(LogLevel.Error, ex);
+				return defaultValue;
+			}
+		}
+
+		/// <summary>
+		/// Reloads the config from disk. Unwritten changes are lost.
+		/// </summary>
+		public static void ReloadConfig()
+		{
+			cache.Clear();
+
+			string currentSection = "";
+
+			foreach (string rawLine in File.ReadAllLines(ConfigPath))
+			{
+				string line = rawLine.Trim();
+
+				bool commentIndex = line.StartsWith(";") || line.StartsWith("#");
+
+				if (commentIndex) //trim comment
+					continue;
+
+				if (line.StartsWith("[") && line.EndsWith("]")) //section
+				{
+					currentSection = line.Substring(1, line.Length - 2);
+					continue;
+				}
+
+				string[] split = line.Split('='); //actual config line
+				if (split.Length != 2)
+					continue; //empty/invalid line
+
+				if (!cache.ContainsKey(currentSection))
+					cache[currentSection] = new Dictionary<string, string>();
+
+				cache[currentSection][split[0]] = split[1];
+			}
+
+			RaiseConfigReloaded();
+		}
+
+		/// <summary>
+		/// Writes the config to disk.
+		/// </summary>
+		public static void SaveConfig()
+		{
+			using (StreamWriter writer = new StreamWriter(File.Create(ConfigPath), System.Text.Encoding.UTF8))
+				foreach (var sectionKv in cache)
+				{
+					writer.WriteLine($"[{sectionKv.Key}]");
+
+					foreach (var entryKv in sectionKv.Value)
+						writer.WriteLine($"{entryKv.Key}={entryKv.Value}");
+
+					writer.WriteLine();
+				}
+		}
+
+		/// <summary>
+		/// Sets the value of the key in the config.
+		/// </summary>
+		/// <param name="key">The key to set the value to.</param>
+		/// <param name="value">The value to set.</param>
+		public static void SetEntry(string key, string value, string section = "")
+		{
+			try
+			{
+				key = Sanitize(key);
+				section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
+
+				if (!cache.TryGetValue(section, out Dictionary<string, string> subdict))
+				{
+					subdict = new Dictionary<string, string>();
+					cache[section] = subdict;
+				}
+
+				subdict[key] = value;
+
+				if (SaveOnConfigSet)
+					SaveConfig();
+			}
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to save config entry!");
+				Logger.Log(LogLevel.Error, ex);
+			}
+		}
+
+		/// <summary>
+		/// Returns wether a value is currently set.
+		/// </summary>
+		/// <param name="key">The key to check against</param>
+		/// <param name="section">The section to check in</param>
+		/// <returns>True if the key is present</returns>
+		public static bool HasEntry(string key, string section = "")
+		{
+			key = Sanitize(key);
+			section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
+
+			return cache.ContainsKey(section) && cache[section].ContainsKey(key);
+		}
+
+
+		/// <summary>
+		/// Removes a value from the config.
+		/// </summary>
+		/// <param name="key">The key to remove</param>
+		/// <param name="section">The section to remove from</param>
+		/// <returns>True if the key was removed</returns>
+		public static bool UnsetEntry(string key, string section = "")
+		{
+			key = Sanitize(key);
+			section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
+
+			if (!HasEntry(key, section))
+				return false;
+
+			cache[section].Remove(key);
+			return true;
+		}
 
 		/// <summary>
 		/// Replaces any potentially breaking input with underscores.
 		/// </summary>
 		/// <param name="text">The text to sanitize.</param>
 		/// <returns>Sanitized text.</returns>
-        public static string Sanitize(string text)
-        {
-            return sanitizeKeyRegex.Replace(text, "_");
-        }
-
-        #region Extensions
-
-        public static string GetEntry(this BaseUnityPlugin plugin, string key, string defaultValue = "")
-        {
-            return GetEntry(key, defaultValue, MetadataHelper.GetMetadata(plugin).GUID);
-        }
-
-        public static void SetEntry(this BaseUnityPlugin plugin, string key, string value)
-        {
-            SetEntry(key, value, MetadataHelper.GetMetadata(plugin).GUID);
-        }
-
-        public static bool HasEntry(this BaseUnityPlugin plugin, string key)
-        {
-            return HasEntry(key, MetadataHelper.GetMetadata(plugin).GUID);
-        }
-        #endregion Extensions
-    }
-}
+		public static string Sanitize(string text)
+		{
+			return sanitizeKeyRegex.Replace(text, "_");
+		}
+
+		#region Extensions
+
+		public static string GetEntry(this BaseUnityPlugin plugin, string key, string defaultValue = "")
+		{
+			return GetEntry(key, defaultValue, MetadataHelper.GetMetadata(plugin).GUID);
+		}
+
+		public static void SetEntry(this BaseUnityPlugin plugin, string key, string value)
+		{
+			SetEntry(key, value, MetadataHelper.GetMetadata(plugin).GUID);
+		}
+
+		public static bool HasEntry(this BaseUnityPlugin plugin, string key)
+		{
+			return HasEntry(key, MetadataHelper.GetMetadata(plugin).GUID);
+		}
+
+		#endregion Extensions
+	}
+}

+ 164 - 166
BepInEx/ConfigWrapper.cs

@@ -4,169 +4,167 @@ using BepInEx.Logging;
 
 namespace BepInEx
 {
-    public interface IConfigConverter<T>
-    {
-        string ConvertToString(T value);
-        T ConvertFromString(string str);
-    }
-    
-    public class ConfigWrapper<T>
-    {
-        private readonly Func<string, T> _strToObj;
-        private readonly Func<T, string> _objToStr;
-        private readonly string _defaultStr;
-        private readonly T _default;
-        private T _lastValue;
-        private bool _lastValueSet;
-
-        public string Key { get; protected set; }
-
-        public string Section { get; protected set; }
-
-        public T Value
-        {
-            get { return GetValue(); }
-            set { SetValue(value); }
-        }
-
-        public ConfigWrapper(string key, T @default = default(T))
-        {
-            var cvt = TypeDescriptor.GetConverter(typeof(T));
-
-            if (!cvt.CanConvertFrom(typeof(string)))
-                throw new ArgumentException("Default TypeConverter can't convert from String");
-
-            if (!cvt.CanConvertTo(typeof(string)))
-                throw new ArgumentException("Default TypeConverter can't convert to String");
-
-            _strToObj = (str) => (T)cvt.ConvertFromInvariantString(str);
-            _objToStr = (obj) => cvt.ConvertToInvariantString(obj);
-
-            _defaultStr = _objToStr(@default);
-            _default = @default;
-            Key = key;
-        }
-
-        public ConfigWrapper(string key, Func<string, T> strToObj, Func<T, string> objToStr, T @default = default(T))
-        {
-            if (objToStr == null)
-                throw new ArgumentNullException("objToStr");
-
-            if (strToObj == null)
-                throw new ArgumentNullException("strToObj");
-
-            _strToObj = strToObj;
-            _objToStr = objToStr;
-
-            _defaultStr = _objToStr(@default);
-            Key = key;
-        }
-
-        public ConfigWrapper(string key, IConfigConverter<T> converter, T @default = default(T))
-            : this(key, converter.ConvertFromString, converter.ConvertToString, @default)
-        {
-
-        }
-
-
-        public ConfigWrapper(string key, BaseUnityPlugin plugin, T @default = default(T))
-            : this(key, @default)
-        {
-            Section = MetadataHelper.GetMetadata(plugin).GUID;
-        }
-
-        public ConfigWrapper(string key, BaseUnityPlugin plugin, Func<string, T> strToObj, Func<T, string> objToStr, T @default = default(T))
-          : this(key, strToObj, objToStr, @default)
-        {
-            Section = MetadataHelper.GetMetadata(plugin).GUID;
-        }
-
-        public ConfigWrapper(string key, BaseUnityPlugin plugin, IConfigConverter<T> converter, T @default = default(T))
-          : this(key, converter.ConvertFromString, converter.ConvertToString, @default)
-        {
-            Section = MetadataHelper.GetMetadata(plugin).GUID;
-        }
-
-        public ConfigWrapper(string key, string section, T @default = default(T))
-            : this(key, @default)
-        {
-            Section = section;
-        }
-
-        public ConfigWrapper(string key, string section, Func<string, T> strToObj, Func<T, string> objToStr, T @default = default(T))
-           : this(key, strToObj, objToStr, @default)
-        {
-            Section = section;
-        }
-
-        public ConfigWrapper(string key, string section, IConfigConverter<T> converter, T @default = default(T))
-           : this(key, converter.ConvertFromString, converter.ConvertToString, @default)
-        {
-            Section = section;
-        }
-
-        protected virtual bool GetKeyExists()
-        {
-            return Config.HasEntry(Key, Section);
-        }
-
-        protected virtual T GetValue()
-        {
-            try
-            {
-                var strVal = Config.GetEntry(Key, _defaultStr, Section);
-                var obj = _strToObj(strVal);
-
-                // Always update in case config was changed from outside
-                _lastValue = obj;
-                _lastValueSet = true;
-
-                return obj;
-            }
-            catch (Exception ex)
-            {
-                Logger.Log(LogLevel.Error, "ConfigWrapper Get Converter Exception: " + ex.Message);
-                return _default;
-            }
-        }
-
-        protected virtual void SetValue(T value)
-        {
-            try
-            {
-                // Always write just in case config was changed from outside
-                var strVal = _objToStr(value);
-                Config.SetEntry(Key, strVal, Section);
-
-                if (_lastValueSet && Equals(_lastValue, value)) return;
-
-                _lastValue = value;
-                _lastValueSet = true;
-
-                OnSettingChanged();
-            }
-            catch (Exception ex)
-            {
-                Logger.Log(LogLevel.Error, "ConfigWrapper Set Converter Exception: " + ex.Message);
-            }
-        }
-
-        public void Clear()
-        {
-            Config.UnsetEntry(Key, Section);
-
-            _lastValueSet = false;
-            OnSettingChanged();
-        }
-
-        /// <summary>
-        /// Fired when the setting is changed. Does not detect changes made outside from this object.
-        /// </summary>
-        public event EventHandler SettingChanged;
-
-        private void OnSettingChanged()
-        {
-            SettingChanged?.Invoke(this, EventArgs.Empty);
-        }
-    }
-}
+	public interface IConfigConverter<T>
+	{
+		string ConvertToString(T value);
+		T ConvertFromString(string str);
+	}
+
+	public class ConfigWrapper<T>
+	{
+		private readonly Func<string, T> _strToObj;
+		private readonly Func<T, string> _objToStr;
+		private readonly string _defaultStr;
+		private readonly T _default;
+		private T _lastValue;
+		private bool _lastValueSet;
+
+		public string Key { get; protected set; }
+
+		public string Section { get; protected set; }
+
+		public T Value
+		{
+			get { return GetValue(); }
+			set { SetValue(value); }
+		}
+
+		public ConfigWrapper(string key, T @default = default(T))
+		{
+			var cvt = TypeDescriptor.GetConverter(typeof(T));
+
+			if (!cvt.CanConvertFrom(typeof(string)))
+				throw new ArgumentException("Default TypeConverter can't convert from String");
+
+			if (!cvt.CanConvertTo(typeof(string)))
+				throw new ArgumentException("Default TypeConverter can't convert to String");
+
+			_strToObj = (str) => (T)cvt.ConvertFromInvariantString(str);
+			_objToStr = (obj) => cvt.ConvertToInvariantString(obj);
+
+			_defaultStr = _objToStr(@default);
+			_default = @default;
+			Key = key;
+		}
+
+		public ConfigWrapper(string key, Func<string, T> strToObj, Func<T, string> objToStr, T @default = default(T))
+		{
+			if (objToStr == null)
+				throw new ArgumentNullException("objToStr");
+
+			if (strToObj == null)
+				throw new ArgumentNullException("strToObj");
+
+			_strToObj = strToObj;
+			_objToStr = objToStr;
+
+			_defaultStr = _objToStr(@default);
+			Key = key;
+		}
+
+		public ConfigWrapper(string key, IConfigConverter<T> converter, T @default = default(T))
+			: this(key, converter.ConvertFromString, converter.ConvertToString, @default) { }
+
+
+		public ConfigWrapper(string key, BaseUnityPlugin plugin, T @default = default(T))
+			: this(key, @default)
+		{
+			Section = MetadataHelper.GetMetadata(plugin).GUID;
+		}
+
+		public ConfigWrapper(string key, BaseUnityPlugin plugin, Func<string, T> strToObj, Func<T, string> objToStr, T @default = default(T))
+			: this(key, strToObj, objToStr, @default)
+		{
+			Section = MetadataHelper.GetMetadata(plugin).GUID;
+		}
+
+		public ConfigWrapper(string key, BaseUnityPlugin plugin, IConfigConverter<T> converter, T @default = default(T))
+			: this(key, converter.ConvertFromString, converter.ConvertToString, @default)
+		{
+			Section = MetadataHelper.GetMetadata(plugin).GUID;
+		}
+
+		public ConfigWrapper(string key, string section, T @default = default(T))
+			: this(key, @default)
+		{
+			Section = section;
+		}
+
+		public ConfigWrapper(string key, string section, Func<string, T> strToObj, Func<T, string> objToStr, T @default = default(T))
+			: this(key, strToObj, objToStr, @default)
+		{
+			Section = section;
+		}
+
+		public ConfigWrapper(string key, string section, IConfigConverter<T> converter, T @default = default(T))
+			: this(key, converter.ConvertFromString, converter.ConvertToString, @default)
+		{
+			Section = section;
+		}
+
+		protected virtual bool GetKeyExists()
+		{
+			return Config.HasEntry(Key, Section);
+		}
+
+		protected virtual T GetValue()
+		{
+			try
+			{
+				var strVal = Config.GetEntry(Key, _defaultStr, Section);
+				var obj = _strToObj(strVal);
+
+				// Always update in case config was changed from outside
+				_lastValue = obj;
+				_lastValueSet = true;
+
+				return obj;
+			}
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Error, "ConfigWrapper Get Converter Exception: " + ex.Message);
+				return _default;
+			}
+		}
+
+		protected virtual void SetValue(T value)
+		{
+			try
+			{
+				// Always write just in case config was changed from outside
+				var strVal = _objToStr(value);
+				Config.SetEntry(Key, strVal, Section);
+
+				if (_lastValueSet && Equals(_lastValue, value))
+					return;
+
+				_lastValue = value;
+				_lastValueSet = true;
+
+				OnSettingChanged();
+			}
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Error, "ConfigWrapper Set Converter Exception: " + ex.Message);
+			}
+		}
+
+		public void Clear()
+		{
+			Config.UnsetEntry(Key, Section);
+
+			_lastValueSet = false;
+			OnSettingChanged();
+		}
+
+		/// <summary>
+		/// Fired when the setting is changed. Does not detect changes made outside from this object.
+		/// </summary>
+		public event EventHandler SettingChanged;
+
+		private void OnSettingChanged()
+		{
+			SettingChanged?.Invoke(this, EventArgs.Empty);
+		}
+	}
+}

+ 54 - 54
BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.Buffers.cs

@@ -5,57 +5,57 @@
 
 namespace UnityInjector.ConsoleUtil
 {
-    // --------------------------------------------------
-    // Code ported from
-    // https://gist.github.com/asm256/9bfb88336a1433e2328a
-    // Which in turn was seemingly ported from
-    // http://jonskeet.uk/csharp/ebcdic/
-    // using only safe (managed) code
-    // --------------------------------------------------
-
-    partial class ConsoleEncoding
-    {
-        private byte[] _byteBuffer = new byte[256];
-        private char[] _charBuffer = new char[256];
-        private byte[] _zeroByte = new byte[0];
-        private char[] _zeroChar = new char[0];
-
-        private void ExpandByteBuffer(int count)
-        {
-            if (_byteBuffer.Length < count)
-                _byteBuffer = new byte[count];
-        }
-
-        private void ExpandCharBuffer(int count)
-        {
-            if (_charBuffer.Length < count)
-                _charBuffer = new char[count];
-        }
-
-        private void ReadByteBuffer(byte[] bytes, int index, int count)
-        {
-            for (int i = 0; i < count; i++)
-                bytes[index + i] = _byteBuffer[i];
-        }
-
-        private void ReadCharBuffer(char[] chars, int index, int count)
-        {
-            for (int i = 0; i < count; i++)
-                chars[index + i] = _charBuffer[i];
-        }
-
-        private void WriteByteBuffer(byte[] bytes, int index, int count)
-        {
-            ExpandByteBuffer(count);
-            for (int i = 0; i < count; i++)
-                _byteBuffer[i] = bytes[index + i];
-        }
-
-        private void WriteCharBuffer(char[] chars, int index, int count)
-        {
-            ExpandCharBuffer(count);
-            for (int i = 0; i < count; i++)
-                _charBuffer[i] = chars[index + i];
-        }
-    }
-}
+	// --------------------------------------------------
+	// Code ported from
+	// https://gist.github.com/asm256/9bfb88336a1433e2328a
+	// Which in turn was seemingly ported from
+	// http://jonskeet.uk/csharp/ebcdic/
+	// using only safe (managed) code
+	// --------------------------------------------------
+
+	partial class ConsoleEncoding
+	{
+		private byte[] _byteBuffer = new byte[256];
+		private char[] _charBuffer = new char[256];
+		private byte[] _zeroByte = new byte[0];
+		private char[] _zeroChar = new char[0];
+
+		private void ExpandByteBuffer(int count)
+		{
+			if (_byteBuffer.Length < count)
+				_byteBuffer = new byte[count];
+		}
+
+		private void ExpandCharBuffer(int count)
+		{
+			if (_charBuffer.Length < count)
+				_charBuffer = new char[count];
+		}
+
+		private void ReadByteBuffer(byte[] bytes, int index, int count)
+		{
+			for (int i = 0; i < count; i++)
+				bytes[index + i] = _byteBuffer[i];
+		}
+
+		private void ReadCharBuffer(char[] chars, int index, int count)
+		{
+			for (int i = 0; i < count; i++)
+				chars[index + i] = _charBuffer[i];
+		}
+
+		private void WriteByteBuffer(byte[] bytes, int index, int count)
+		{
+			ExpandByteBuffer(count);
+			for (int i = 0; i < count; i++)
+				_byteBuffer[i] = bytes[index + i];
+		}
+
+		private void WriteCharBuffer(char[] chars, int index, int count)
+		{
+			ExpandCharBuffer(count);
+			for (int i = 0; i < count; i++)
+				_charBuffer[i] = chars[index + i];
+		}
+	}
+}

+ 37 - 35
BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.PInvoke.cs

@@ -8,42 +8,44 @@ using System.Runtime.InteropServices;
 
 namespace UnityInjector.ConsoleUtil
 {
-    // --------------------------------------------------
-    // Code ported from
-    // https://gist.github.com/asm256/9bfb88336a1433e2328a
-    // Which in turn was seemingly ported from
-    // http://jonskeet.uk/csharp/ebcdic/
-    // using only safe (managed) code
-    // --------------------------------------------------
-    partial class ConsoleEncoding
-    {
-        [DllImport("kernel32.dll")]
-        private static extern uint GetConsoleOutputCP();
+	// --------------------------------------------------
+	// Code ported from
+	// https://gist.github.com/asm256/9bfb88336a1433e2328a
+	// Which in turn was seemingly ported from
+	// http://jonskeet.uk/csharp/ebcdic/
+	// using only safe (managed) code
+	// --------------------------------------------------
+	partial class ConsoleEncoding
+	{
+		[DllImport("kernel32.dll")]
+		private static extern uint GetConsoleOutputCP();
 
-        [DllImport("kernel32.dll")]
-        private static extern uint GetACP();
+		[DllImport("kernel32.dll")]
+		private static extern uint GetACP();
 
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern int MultiByteToWideChar(
-            uint codePage,
-            uint dwFlags,
-            [In, MarshalAs(UnmanagedType.LPArray)] byte[] lpMultiByteStr,
-            int cbMultiByte,
-            [Out, MarshalAs(UnmanagedType.LPArray)] char[] lpWideCharStr,
-            int cchWideChar);
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern int MultiByteToWideChar(
+			uint codePage,
+			uint dwFlags,
+			[In, MarshalAs(UnmanagedType.LPArray)] byte[] lpMultiByteStr,
+			int cbMultiByte,
+			[Out, MarshalAs(UnmanagedType.LPArray)]
+			char[] lpWideCharStr,
+			int cchWideChar);
 
-        [DllImport("kernel32.dll")]
-        private static extern IntPtr SetConsoleOutputCP(uint codepage);
+		[DllImport("kernel32.dll")]
+		private static extern IntPtr SetConsoleOutputCP(uint codepage);
 
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern int WideCharToMultiByte(
-            uint codePage,
-            uint dwFlags,
-            [In, MarshalAs(UnmanagedType.LPArray)] char[] lpWideCharStr,
-            int cchWideChar,
-            [Out, MarshalAs(UnmanagedType.LPArray)] byte[] lpMultiByteStr,
-            int cbMultiByte,
-            IntPtr lpDefaultChar,
-            IntPtr lpUsedDefaultChar);
-    }
-}
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern int WideCharToMultiByte(
+			uint codePage,
+			uint dwFlags,
+			[In, MarshalAs(UnmanagedType.LPArray)] char[] lpWideCharStr,
+			int cchWideChar,
+			[Out, MarshalAs(UnmanagedType.LPArray)]
+			byte[] lpMultiByteStr,
+			int cbMultiByte,
+			IntPtr lpDefaultChar,
+			IntPtr lpUsedDefaultChar);
+	}
+}

+ 77 - 77
BepInEx/ConsoleUtil/ConsoleEncoding/ConsoleEncoding.cs

@@ -8,80 +8,80 @@ using System.Text;
 
 namespace UnityInjector.ConsoleUtil
 {
-    // --------------------------------------------------
-    // Code ported from
-    // https://gist.github.com/asm256/9bfb88336a1433e2328a
-    // Which in turn was seemingly ported from
-    // http://jonskeet.uk/csharp/ebcdic/
-    // using only safe (managed) code
-    // --------------------------------------------------
-    internal partial class ConsoleEncoding : Encoding
-    {
-        private readonly uint _codePage;
-        public override int CodePage => (int) _codePage;
-
-        public static uint ConsoleCodePage
-        {
-            get { return GetConsoleOutputCP(); }
-            set { SetConsoleOutputCP(value); }
-        }
-
-        public static uint GetActiveCodePage()
-        {
-            return GetACP();
-        }
-
-        private ConsoleEncoding(uint codePage)
-        {
-            _codePage = codePage;
-        }
-
-        public static ConsoleEncoding GetEncoding(uint codePage)
-        {
-            return new ConsoleEncoding(codePage);
-        }
-
-        public override int GetByteCount(char[] chars, int index, int count)
-        {
-            WriteCharBuffer(chars, index, count);
-            int result = WideCharToMultiByte(_codePage, 0, _charBuffer, count, _zeroByte, 0, IntPtr.Zero, IntPtr.Zero);
-            return result;
-        }
-
-        public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
-        {
-            var byteCount = GetMaxByteCount(charCount);
-
-            WriteCharBuffer(chars, charIndex, charCount);
-
-            ExpandByteBuffer(byteCount);
-            int result = WideCharToMultiByte(_codePage, 0, chars, charCount, _byteBuffer, byteCount, IntPtr.Zero, IntPtr.Zero);
-            ReadByteBuffer(bytes, byteIndex, byteCount);
-
-            return result;
-        }
-
-        public override int GetCharCount(byte[] bytes, int index, int count)
-        {
-            WriteByteBuffer(bytes, index, count);
-            int result = MultiByteToWideChar(_codePage, 0, bytes, count, _zeroChar, 0);
-            return result;
-        }
-
-        public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
-        {
-            var charCount = GetMaxCharCount(byteCount);
-
-            WriteByteBuffer(bytes, byteIndex, byteCount);
-
-            ExpandCharBuffer(charCount);
-            int result = MultiByteToWideChar(_codePage, 0, bytes, byteCount, _charBuffer, charCount);
-            ReadCharBuffer(chars, charIndex, charCount);
-
-            return result;
-        }
-
-        public override int GetMaxByteCount(int charCount) => charCount * 2;
-        public override int GetMaxCharCount(int byteCount) => byteCount;
-    }
-}
+	// --------------------------------------------------
+	// Code ported from
+	// https://gist.github.com/asm256/9bfb88336a1433e2328a
+	// Which in turn was seemingly ported from
+	// http://jonskeet.uk/csharp/ebcdic/
+	// using only safe (managed) code
+	// --------------------------------------------------
+	public partial class ConsoleEncoding : Encoding
+	{
+		private readonly uint _codePage;
+		public override int CodePage => (int)_codePage;
+
+		public static uint ConsoleCodePage
+		{
+			get { return GetConsoleOutputCP(); }
+			set { SetConsoleOutputCP(value); }
+		}
+
+		public static uint GetActiveCodePage()
+		{
+			return GetACP();
+		}
+
+		private ConsoleEncoding(uint codePage)
+		{
+			_codePage = codePage;
+		}
+
+		public static ConsoleEncoding GetEncoding(uint codePage)
+		{
+			return new ConsoleEncoding(codePage);
+		}
+
+		public override int GetByteCount(char[] chars, int index, int count)
+		{
+			WriteCharBuffer(chars, index, count);
+			int result = WideCharToMultiByte(_codePage, 0, _charBuffer, count, _zeroByte, 0, IntPtr.Zero, IntPtr.Zero);
+			return result;
+		}
+
+		public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
+		{
+			var byteCount = GetMaxByteCount(charCount);
+
+			WriteCharBuffer(chars, charIndex, charCount);
+
+			ExpandByteBuffer(byteCount);
+			int result = WideCharToMultiByte(_codePage, 0, chars, charCount, _byteBuffer, byteCount, IntPtr.Zero, IntPtr.Zero);
+			ReadByteBuffer(bytes, byteIndex, byteCount);
+
+			return result;
+		}
+
+		public override int GetCharCount(byte[] bytes, int index, int count)
+		{
+			WriteByteBuffer(bytes, index, count);
+			int result = MultiByteToWideChar(_codePage, 0, bytes, count, _zeroChar, 0);
+			return result;
+		}
+
+		public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
+		{
+			var charCount = GetMaxCharCount(byteCount);
+
+			WriteByteBuffer(bytes, byteIndex, byteCount);
+
+			ExpandCharBuffer(charCount);
+			int result = MultiByteToWideChar(_codePage, 0, bytes, byteCount, _charBuffer, charCount);
+			ReadCharBuffer(chars, charIndex, charCount);
+
+			return result;
+		}
+
+		public override int GetMaxByteCount(int charCount) => charCount * 2;
+		public override int GetMaxCharCount(int byteCount) => byteCount;
+	}
+}

+ 125 - 124
BepInEx/ConsoleUtil/ConsoleWindow.cs

@@ -10,128 +10,129 @@ using System.Text;
 
 namespace UnityInjector.ConsoleUtil
 {
-    internal class ConsoleWindow
-    {
-        public static bool IsAttatched { get; private set; }
-        private static IntPtr _cOut;
-        private static IntPtr _oOut;
-
-        public static void Attach()
-        {
-            if (IsAttatched)
-                return;
-
-            if (_oOut == IntPtr.Zero)
-                _oOut = GetStdHandle(-11);
-
-            // Store Current Window
-            IntPtr currWnd = GetForegroundWindow();
-
-            //Check for existing console before allocating
-            if (GetConsoleWindow() == IntPtr.Zero)
-                if (!AllocConsole())
-                    throw new Exception("AllocConsole() failed");
-
-            // Restore Foreground
-            SetForegroundWindow(currWnd);
-
-            _cOut = CreateFile("CONOUT$", 0x80000000 | 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
-            BepInEx.ConsoleUtil.Kon.conOut = _cOut;
-
-            if (!SetStdHandle(-11, _cOut))
-                throw new Exception("SetStdHandle() failed");
-            Init();
-
-	        IsAttatched = true;
-        }
-
-        public static string Title
-        {
-            set
-            {
-	            if (!IsAttatched)
-		            return;
-
-	            if (value == null)
-	            {
-		            throw new ArgumentNullException(nameof(value));
-	            }
-	            if (value.Length > 24500)
-	            {
-		            throw new InvalidOperationException("Console title too long");
-	            }
-
-	            if (!SetConsoleTitle(value))
-	            {
-		            throw new InvalidOperationException("Console title invalid");
-	            }
-            }
-        }
-
-        public static void Detach()
-        {
-            if (!IsAttatched)
-                return;
-
-            if (!CloseHandle(_cOut))
-                throw new Exception("CloseHandle() failed");
-            _cOut = IntPtr.Zero;
-            if (!FreeConsole())
-                throw new Exception("FreeConsole() failed");
-            if (!SetStdHandle(-11, _oOut))
-                throw new Exception("SetStdHandle() failed");
-            Init();
-
-	        IsAttatched = false;
-        }
-
-        [DllImport("user32.dll")]
-        private static extern IntPtr GetForegroundWindow();
-
-        [DllImport("user32.dll")]
-        [return: MarshalAs(UnmanagedType.Bool)]
-        static extern bool SetForegroundWindow(IntPtr hWnd);
-
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern bool AllocConsole();
-
-        [DllImport("kernel32.dll")]
-        private static extern IntPtr GetConsoleWindow();
-
-        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
-        private static extern bool CloseHandle(IntPtr handle);
-
-        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
-        private static extern IntPtr CreateFile(
-            string fileName,
-            uint desiredAccess,
-            int shareMode,
-            IntPtr securityAttributes,
-            int creationDisposition,
-            int flagsAndAttributes,
-            IntPtr templateFile);
-
-        [DllImport("kernel32.dll", SetLastError = false)]
-        private static extern bool FreeConsole();
-
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern IntPtr GetStdHandle(int nStdHandle);
-
-        private static void Init()
-        {
-            var stdOut = Console.OpenStandardOutput();
-            var stdWriter = new StreamWriter(stdOut, Encoding.Default)
-            {
-                AutoFlush = true
-            };
-            Console.SetOut(stdWriter);
-            Console.SetError(stdWriter);
-        }
-
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern bool SetStdHandle(int nStdHandle, IntPtr hConsoleOutput);
-        
-        [DllImport("kernel32.dll", BestFitMapping = true, CharSet = CharSet.Auto, SetLastError = true)]
-        private static extern bool SetConsoleTitle(string title);
-    }
+	public class ConsoleWindow
+	{
+		public static bool IsAttatched { get; private set; }
+		private static IntPtr _cOut;
+		private static IntPtr _oOut;
+
+		public static void Attach()
+		{
+			if (IsAttatched)
+				return;
+
+			if (_oOut == IntPtr.Zero)
+				_oOut = GetStdHandle(-11);
+
+			// Store Current Window
+			IntPtr currWnd = GetForegroundWindow();
+
+			//Check for existing console before allocating
+			if (GetConsoleWindow() == IntPtr.Zero)
+				if (!AllocConsole())
+					throw new Exception("AllocConsole() failed");
+
+			// Restore Foreground
+			SetForegroundWindow(currWnd);
+
+			_cOut = CreateFile("CONOUT$", 0x80000000 | 0x40000000, 2, IntPtr.Zero, 3, 0, IntPtr.Zero);
+			BepInEx.ConsoleUtil.Kon.conOut = _cOut;
+
+			if (!SetStdHandle(-11, _cOut))
+				throw new Exception("SetStdHandle() failed");
+			Init();
+
+			IsAttatched = true;
+		}
+
+		public static string Title
+		{
+			set
+			{
+				if (!IsAttatched)
+					return;
+
+				if (value == null)
+				{
+					throw new ArgumentNullException(nameof(value));
+				}
+
+				if (value.Length > 24500)
+				{
+					throw new InvalidOperationException("Console title too long");
+				}
+
+				if (!SetConsoleTitle(value))
+				{
+					throw new InvalidOperationException("Console title invalid");
+				}
+			}
+		}
+
+		public static void Detach()
+		{
+			if (!IsAttatched)
+				return;
+
+			if (!CloseHandle(_cOut))
+				throw new Exception("CloseHandle() failed");
+			_cOut = IntPtr.Zero;
+			if (!FreeConsole())
+				throw new Exception("FreeConsole() failed");
+			if (!SetStdHandle(-11, _oOut))
+				throw new Exception("SetStdHandle() failed");
+			Init();
+
+			IsAttatched = false;
+		}
+
+		[DllImport("user32.dll")]
+		private static extern IntPtr GetForegroundWindow();
+
+		[DllImport("user32.dll")]
+		[return: MarshalAs(UnmanagedType.Bool)]
+		static extern bool SetForegroundWindow(IntPtr hWnd);
+
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern bool AllocConsole();
+
+		[DllImport("kernel32.dll")]
+		private static extern IntPtr GetConsoleWindow();
+
+		[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
+		private static extern bool CloseHandle(IntPtr handle);
+
+		[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+		private static extern IntPtr CreateFile(
+			string fileName,
+			uint desiredAccess,
+			int shareMode,
+			IntPtr securityAttributes,
+			int creationDisposition,
+			int flagsAndAttributes,
+			IntPtr templateFile);
+
+		[DllImport("kernel32.dll", SetLastError = false)]
+		private static extern bool FreeConsole();
+
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern IntPtr GetStdHandle(int nStdHandle);
+
+		private static void Init()
+		{
+			var stdOut = Console.OpenStandardOutput();
+			var stdWriter = new StreamWriter(stdOut, Encoding.Default)
+			{
+				AutoFlush = true
+			};
+			Console.SetOut(stdWriter);
+			Console.SetError(stdWriter);
+		}
+
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern bool SetStdHandle(int nStdHandle, IntPtr hConsoleOutput);
+
+		[DllImport("kernel32.dll", BestFitMapping = true, CharSet = CharSet.Auto, SetLastError = true)]
+		private static extern bool SetConsoleTitle(string title);
+	}
 }

+ 138 - 146
BepInEx/ConsoleUtil/Kon.cs

@@ -4,149 +4,141 @@ using System.Security.Permissions;
 
 namespace BepInEx.ConsoleUtil
 {
-    internal class Kon
-    {
-        #region pinvoke
-
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
-
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern bool SetConsoleTextAttribute(IntPtr hConsoleOutput, short attributes);
-
-        [DllImport("kernel32.dll", SetLastError = true)]
-        private static extern IntPtr GetStdHandle(int nStdHandle);
-
-        #endregion
-
-        #region Types
-
-        private struct CONSOLE_SCREEN_BUFFER_INFO
-        {
-            internal COORD dwSize;
-            internal COORD dwCursorPosition;
-            internal short wAttributes;
-            internal SMALL_RECT srWindow;
-            internal COORD dwMaximumWindowSize;
-        }
-
-        private struct COORD
-        {
-            internal short X;
-            internal short Y;
-        }
-
-        private struct SMALL_RECT
-        {
-            internal short Left;
-            internal short Top;
-            internal short Right;
-            internal short Bottom;
-        }
-
-        private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
-
-        #endregion
-
-        #region Private
-
-        private static short ConsoleColorToColorAttribute(short color, bool isBackground)
-        {
-            if ((color & -16) != 0)
-                throw new ArgumentException("Arg_InvalidConsoleColor");
-            if (isBackground)
-                color <<= 4;
-            return color;
-        }
-
-        private static CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(bool throwOnNoConsole, out bool succeeded)
-        {
-            succeeded = false;
-            if (!(conOut == INVALID_HANDLE_VALUE))
-            {
-                CONSOLE_SCREEN_BUFFER_INFO console_SCREEN_BUFFER_INFO;
-                if (!GetConsoleScreenBufferInfo(conOut, out console_SCREEN_BUFFER_INFO))
-                {
-                    bool consoleScreenBufferInfo = GetConsoleScreenBufferInfo(GetStdHandle(-12), out console_SCREEN_BUFFER_INFO);
-                    if (!consoleScreenBufferInfo)
-                        consoleScreenBufferInfo = GetConsoleScreenBufferInfo(GetStdHandle(-10), out console_SCREEN_BUFFER_INFO);
-
-                    if (!consoleScreenBufferInfo)
-                        if (Marshal.GetLastWin32Error() == 6 && !throwOnNoConsole)
-                            return default(CONSOLE_SCREEN_BUFFER_INFO);
-                }
-                succeeded = true;
-                return console_SCREEN_BUFFER_INFO;
-            }
-            if (!throwOnNoConsole)
-                return default(CONSOLE_SCREEN_BUFFER_INFO);
-            throw new Exception("IO.IO_NoConsole");
-        }
-
-        private static void SetConsoleColor(bool isBackground, ConsoleColor c)
-        {
-            new UIPermission(UIPermissionWindow.SafeTopLevelWindows).Demand();
-            var color = ConsoleColorToColorAttribute((short)c, isBackground);
-            bool flag;
-            var bufferInfo = GetBufferInfo(false, out flag);
-            if (!flag) return;
-            var num = bufferInfo.wAttributes;
-            num &= (short)(isBackground ? -241 : -16);
-            num = (short)((ushort)num | (ushort)color);
-            SetConsoleTextAttribute(conOut, num);
-        }
-
-        private static ConsoleColor GetConsoleColor(bool isBackground)
-        {
-            bool flag;
-            var bufferInfo = GetBufferInfo(false, out flag);
-            if (!flag) return isBackground ? ConsoleColor.Black : ConsoleColor.Gray;
-            return ColorAttributeToConsoleColor((short)(bufferInfo.wAttributes & 240));
-        }
-
-        private static ConsoleColor ColorAttributeToConsoleColor(short c)
-        {
-            if ((short)(c & 255) != 0)
-                c >>= 4;
-            return (ConsoleColor)c;
-        }
-
-        internal static IntPtr conOut = IntPtr.Zero;
-
-        #endregion
-
-        #region Public
-
-        public static void ResetConsoleColor()
-        {
-            SetConsoleColor(true, ConsoleColor.Black);
-            SetConsoleColor(false, ConsoleColor.Gray);
-        }
-
-        public static ConsoleColor ForegroundColor
-        {
-            get
-            {
-                return GetConsoleColor(false);
-            }
-            set
-            {
-                SetConsoleColor(false, value);
-            }
-        }
-
-        public static ConsoleColor BackgroundColor
-        {
-            get
-            {
-                return GetConsoleColor(true);
-            }
-            set
-            {
-                SetConsoleColor(true, value);
-            }
-        }
-
-        #endregion
-    }
-}
+	internal class Kon
+	{
+		#region pinvoke
+
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
+
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern bool SetConsoleTextAttribute(IntPtr hConsoleOutput, short attributes);
+
+		[DllImport("kernel32.dll", SetLastError = true)]
+		private static extern IntPtr GetStdHandle(int nStdHandle);
+
+		#endregion
+
+		#region Types
+
+		private struct CONSOLE_SCREEN_BUFFER_INFO
+		{
+			internal COORD dwSize;
+			internal COORD dwCursorPosition;
+			internal short wAttributes;
+			internal SMALL_RECT srWindow;
+			internal COORD dwMaximumWindowSize;
+		}
+
+		private struct COORD
+		{
+			internal short X;
+			internal short Y;
+		}
+
+		private struct SMALL_RECT
+		{
+			internal short Left;
+			internal short Top;
+			internal short Right;
+			internal short Bottom;
+		}
+
+		private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
+
+		#endregion
+
+		#region Private
+
+		private static short ConsoleColorToColorAttribute(short color, bool isBackground)
+		{
+			if ((color & -16) != 0)
+				throw new ArgumentException("Arg_InvalidConsoleColor");
+			if (isBackground)
+				color <<= 4;
+			return color;
+		}
+
+		private static CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(bool throwOnNoConsole, out bool succeeded)
+		{
+			succeeded = false;
+			if (!(conOut == INVALID_HANDLE_VALUE))
+			{
+				CONSOLE_SCREEN_BUFFER_INFO console_SCREEN_BUFFER_INFO;
+				if (!GetConsoleScreenBufferInfo(conOut, out console_SCREEN_BUFFER_INFO))
+				{
+					bool consoleScreenBufferInfo = GetConsoleScreenBufferInfo(GetStdHandle(-12), out console_SCREEN_BUFFER_INFO);
+					if (!consoleScreenBufferInfo)
+						consoleScreenBufferInfo = GetConsoleScreenBufferInfo(GetStdHandle(-10), out console_SCREEN_BUFFER_INFO);
+
+					if (!consoleScreenBufferInfo)
+						if (Marshal.GetLastWin32Error() == 6 && !throwOnNoConsole)
+							return default(CONSOLE_SCREEN_BUFFER_INFO);
+				}
+
+				succeeded = true;
+				return console_SCREEN_BUFFER_INFO;
+			}
+
+			if (!throwOnNoConsole)
+				return default(CONSOLE_SCREEN_BUFFER_INFO);
+			throw new Exception("IO.IO_NoConsole");
+		}
+
+		private static void SetConsoleColor(bool isBackground, ConsoleColor c)
+		{
+			new UIPermission(UIPermissionWindow.SafeTopLevelWindows).Demand();
+			var color = ConsoleColorToColorAttribute((short)c, isBackground);
+			bool flag;
+			var bufferInfo = GetBufferInfo(false, out flag);
+			if (!flag)
+				return;
+			var num = bufferInfo.wAttributes;
+			num &= (short)(isBackground ? -241 : -16);
+			num = (short)((ushort)num | (ushort)color);
+			SetConsoleTextAttribute(conOut, num);
+		}
+
+		private static ConsoleColor GetConsoleColor(bool isBackground)
+		{
+			bool flag;
+			var bufferInfo = GetBufferInfo(false, out flag);
+			if (!flag)
+				return isBackground ? ConsoleColor.Black : ConsoleColor.Gray;
+			return ColorAttributeToConsoleColor((short)(bufferInfo.wAttributes & 240));
+		}
+
+		private static ConsoleColor ColorAttributeToConsoleColor(short c)
+		{
+			if ((short)(c & 255) != 0)
+				c >>= 4;
+			return (ConsoleColor)c;
+		}
+
+		internal static IntPtr conOut = IntPtr.Zero;
+
+		#endregion
+
+		#region Public
+
+		public static void ResetConsoleColor()
+		{
+			SetConsoleColor(true, ConsoleColor.Black);
+			SetConsoleColor(false, ConsoleColor.Gray);
+		}
+
+		public static ConsoleColor ForegroundColor
+		{
+			get { return GetConsoleColor(false); }
+			set { SetConsoleColor(false, value); }
+		}
+
+		public static ConsoleColor BackgroundColor
+		{
+			get { return GetConsoleColor(true); }
+			set { SetConsoleColor(true, value); }
+		}
+
+		#endregion
+	}
+}

+ 45 - 45
BepInEx/ConsoleUtil/SafeConsole.cs

@@ -8,59 +8,59 @@ using System.Reflection;
 
 namespace UnityInjector.ConsoleUtil
 {
-    public static class SafeConsole
-    {
-        private static GetColorDelegate _getBackgroundColor;
-        private static GetColorDelegate _getForegroundColor;
-        private static SetColorDelegate _setBackgroundColor;
-        private static SetColorDelegate _setForegroundColor;
+	public static class SafeConsole
+	{
+		private static GetColorDelegate _getBackgroundColor;
+		private static GetColorDelegate _getForegroundColor;
+		private static SetColorDelegate _setBackgroundColor;
+		private static SetColorDelegate _setForegroundColor;
 
-        public static ConsoleColor BackgroundColor
-        {
-            get { return _getBackgroundColor(); }
-            set { _setBackgroundColor(value); }
-        }
+		public static ConsoleColor BackgroundColor
+		{
+			get { return _getBackgroundColor(); }
+			set { _setBackgroundColor(value); }
+		}
 
-        public static ConsoleColor ForegroundColor
-        {
-            get { return _getForegroundColor(); }
-            set { _setForegroundColor(value); }
-        }
+		public static ConsoleColor ForegroundColor
+		{
+			get { return _getForegroundColor(); }
+			set { _setForegroundColor(value); }
+		}
 
-        static SafeConsole()
-        {
-            var tConsole = typeof(Console);
-            InitColors(tConsole);
-        }
+		static SafeConsole()
+		{
+			var tConsole = typeof(Console);
+			InitColors(tConsole);
+		}
 
-        private static void InitColors(Type tConsole)
-        {
-            const BindingFlags BINDING_FLAGS = BindingFlags.Public | BindingFlags.Static;
+		private static void InitColors(Type tConsole)
+		{
+			const BindingFlags BINDING_FLAGS = BindingFlags.Public | BindingFlags.Static;
 
-            var sfc = tConsole.GetMethod("set_ForegroundColor", BINDING_FLAGS);
-            var sbc = tConsole.GetMethod("set_BackgroundColor", BINDING_FLAGS);
-            var gfc = tConsole.GetMethod("get_ForegroundColor", BINDING_FLAGS);
-            var gbc = tConsole.GetMethod("get_BackgroundColor", BINDING_FLAGS);
+			var sfc = tConsole.GetMethod("set_ForegroundColor", BINDING_FLAGS);
+			var sbc = tConsole.GetMethod("set_BackgroundColor", BINDING_FLAGS);
+			var gfc = tConsole.GetMethod("get_ForegroundColor", BINDING_FLAGS);
+			var gbc = tConsole.GetMethod("get_BackgroundColor", BINDING_FLAGS);
 
-            _setForegroundColor = sfc != null
-                                      ? (SetColorDelegate) Delegate.CreateDelegate(typeof(SetColorDelegate), sfc)
-                                      : (value => {});
+			_setForegroundColor = sfc != null
+				? (SetColorDelegate)Delegate.CreateDelegate(typeof(SetColorDelegate), sfc)
+				: (value => { });
 
-            _setBackgroundColor = sbc != null
-                                      ? (SetColorDelegate) Delegate.CreateDelegate(typeof(SetColorDelegate), sbc)
-                                      : (value => {});
+			_setBackgroundColor = sbc != null
+				? (SetColorDelegate)Delegate.CreateDelegate(typeof(SetColorDelegate), sbc)
+				: (value => { });
 
-            _getForegroundColor = gfc != null
-                                      ? (GetColorDelegate) Delegate.CreateDelegate(typeof(GetColorDelegate), gfc)
-                                      : (() => ConsoleColor.Gray);
+			_getForegroundColor = gfc != null
+				? (GetColorDelegate)Delegate.CreateDelegate(typeof(GetColorDelegate), gfc)
+				: (() => ConsoleColor.Gray);
 
-            _getBackgroundColor = gbc != null
-                                      ? (GetColorDelegate) Delegate.CreateDelegate(typeof(GetColorDelegate), gbc)
-                                      : (() => ConsoleColor.Black);
-        }
+			_getBackgroundColor = gbc != null
+				? (GetColorDelegate)Delegate.CreateDelegate(typeof(GetColorDelegate), gbc)
+				: (() => ConsoleColor.Black);
+		}
 
-        private delegate ConsoleColor GetColorDelegate();
+		private delegate ConsoleColor GetColorDelegate();
 
-        private delegate void SetColorDelegate(ConsoleColor value);
-    }
-}
+		private delegate void SetColorDelegate(ConsoleColor value);
+	}
+}

+ 163 - 166
BepInEx/Contract/Attributes.cs

@@ -4,131 +4,131 @@ using System.Linq;
 
 namespace BepInEx
 {
-    #region BaseUnityPlugin
-
-    /// <summary>
-    /// This attribute denotes that a class is a plugin, and specifies the required metadata.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
-    public class BepInPlugin : Attribute
-    {
-        /// <summary>
-        /// The unique identifier of the plugin. Should not change between plugin versions.
-        /// </summary>
-        public string GUID { get; protected set; }
-
-        
-        /// <summary>
-        /// The user friendly name of the plugin. Is able to be changed between versions.
-        /// </summary>
-        public string Name { get; protected set; }
-
-        
-        /// <summary>
-        /// The specfic version of the plugin.
-        /// </summary>
-        public Version Version { get; protected set; }
-        
-        /// <param name="GUID">The unique identifier of the plugin. Should not change between plugin versions.</param>
-        /// <param name="Name">The user friendly name of the plugin. Is able to be changed between versions.</param>
-        /// <param name="Version">The specfic version of the plugin.</param>
-        public BepInPlugin(string GUID, string Name, string Version)
-        {
-            this.GUID = GUID;
-            this.Name = Name;
-            this.Version = new Version(Version);
-        }
-    }
-
-    /// <summary>
-    /// This attribute specifies any dependencies that this plugin has on other plugins.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-    public class BepInDependency : Attribute
-    {
-        public enum DependencyFlags
-        {
-            /// <summary>
-            /// The plugin has a hard dependency on the referenced plugin, and will not run without it.
-            /// </summary>
-            HardDependency = 1,
-
-            /// <summary>
-            /// This plugin has a soft dependency on the referenced plugin, and is able to run without it.
-            /// </summary>
-            SoftDependency = 2,
-        }
-
-        /// <summary>
-        /// The GUID of the referenced plugin.
-        /// </summary>
-        public string DependencyGUID { get; protected set; }
-
-        /// <summary>
-        /// The flags associated with this dependency definition.
-        /// </summary>
-        public DependencyFlags Flags { get; protected set; }
-        
-        /// <param name="DependencyGUID">The GUID of the referenced plugin.</param>
-        /// <param name="Flags">The flags associated with this dependency definition.</param>
-        public BepInDependency(string DependencyGUID, DependencyFlags Flags = DependencyFlags.HardDependency)
-        {
-            this.DependencyGUID = DependencyGUID;
-            this.Flags = Flags;
-        }
-    }
-
-    /// <summary>
-    /// This attribute specifies which processes this plugin should be run for. Not specifying this attribute will load the plugin under every process.
-    /// </summary>
-    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-    public class BepInProcess : Attribute
-    {
-        /// <summary>
-        /// The name of the process that this plugin will run under.
-        /// </summary>
-        public string ProcessName { get; protected set; }
-        
-        /// <param name="ProcessName">The name of the process that this plugin will run under.</param>
-        public BepInProcess(string ProcessName)
-        {
-            this.ProcessName = ProcessName;
-        }
-    }
-
-    #endregion
-
-    #region MetadataHelper
+	#region BaseUnityPlugin
+
+	/// <summary>
+	/// This attribute denotes that a class is a plugin, and specifies the required metadata.
+	/// </summary>
+	[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
+	public class BepInPlugin : Attribute
+	{
+		/// <summary>
+		/// The unique identifier of the plugin. Should not change between plugin versions.
+		/// </summary>
+		public string GUID { get; protected set; }
+
+
+		/// <summary>
+		/// The user friendly name of the plugin. Is able to be changed between versions.
+		/// </summary>
+		public string Name { get; protected set; }
+
+
+		/// <summary>
+		/// The specfic version of the plugin.
+		/// </summary>
+		public Version Version { get; protected set; }
+
+		/// <param name="GUID">The unique identifier of the plugin. Should not change between plugin versions.</param>
+		/// <param name="Name">The user friendly name of the plugin. Is able to be changed between versions.</param>
+		/// <param name="Version">The specfic version of the plugin.</param>
+		public BepInPlugin(string GUID, string Name, string Version)
+		{
+			this.GUID = GUID;
+			this.Name = Name;
+			this.Version = new Version(Version);
+		}
+	}
+
+	/// <summary>
+	/// This attribute specifies any dependencies that this plugin has on other plugins.
+	/// </summary>
+	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+	public class BepInDependency : Attribute
+	{
+		public enum DependencyFlags
+		{
+			/// <summary>
+			/// The plugin has a hard dependency on the referenced plugin, and will not run without it.
+			/// </summary>
+			HardDependency = 1,
+
+			/// <summary>
+			/// This plugin has a soft dependency on the referenced plugin, and is able to run without it.
+			/// </summary>
+			SoftDependency = 2,
+		}
+
+		/// <summary>
+		/// The GUID of the referenced plugin.
+		/// </summary>
+		public string DependencyGUID { get; protected set; }
+
+		/// <summary>
+		/// The flags associated with this dependency definition.
+		/// </summary>
+		public DependencyFlags Flags { get; protected set; }
+
+		/// <param name="DependencyGUID">The GUID of the referenced plugin.</param>
+		/// <param name="Flags">The flags associated with this dependency definition.</param>
+		public BepInDependency(string DependencyGUID, DependencyFlags Flags = DependencyFlags.HardDependency)
+		{
+			this.DependencyGUID = DependencyGUID;
+			this.Flags = Flags;
+		}
+	}
+
+	/// <summary>
+	/// This attribute specifies which processes this plugin should be run for. Not specifying this attribute will load the plugin under every process.
+	/// </summary>
+	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+	public class BepInProcess : Attribute
+	{
+		/// <summary>
+		/// The name of the process that this plugin will run under.
+		/// </summary>
+		public string ProcessName { get; protected set; }
+
+		/// <param name="ProcessName">The name of the process that this plugin will run under.</param>
+		public BepInProcess(string ProcessName)
+		{
+			this.ProcessName = ProcessName;
+		}
+	}
+
+	#endregion
+
+	#region MetadataHelper
 
 	/// <summary>
 	/// Helper class to use for retrieving metadata about a plugin, defined as attributes.
 	/// </summary>
-    public static class MetadataHelper
-    {
+	public static class MetadataHelper
+	{
 		/// <summary>
 		/// Retrieves the BepInPlugin metadata from a plugin instance.
 		/// </summary>
 		/// <param name="plugin">The plugin instance.</param>
 		/// <returns>The BepInPlugin metadata of the plugin instance.</returns>
-        public static BepInPlugin GetMetadata(object plugin)
-        {
-            return GetMetadata(plugin.GetType());
-        }
-		
-	    /// <summary>
-	    /// Retrieves the BepInPlugin metadata from a plugin type.
-	    /// </summary>
-	    /// <param name="plugin">The plugin type.</param>
-	    /// <returns>The BepInPlugin metadata of the plugin type.</returns>
-        public static BepInPlugin GetMetadata(Type pluginType)
-        {
-            object[] attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false);
-
-            if (attributes.Length == 0)
-                return null;
-
-            return (BepInPlugin)attributes[0];
-        }
+		public static BepInPlugin GetMetadata(object plugin)
+		{
+			return GetMetadata(plugin.GetType());
+		}
+
+		/// <summary>
+		/// Retrieves the BepInPlugin metadata from a plugin type.
+		/// </summary>
+		/// <param name="plugin">The plugin type.</param>
+		/// <returns>The BepInPlugin metadata of the plugin type.</returns>
+		public static BepInPlugin GetMetadata(Type pluginType)
+		{
+			object[] attributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), false);
+
+			if (attributes.Length == 0)
+				return null;
+
+			return (BepInPlugin)attributes[0];
+		}
 
 		/// <summary>
 		/// Gets the specified attributes of an instance, if they exist.
@@ -136,21 +136,21 @@ namespace BepInEx
 		/// <typeparam name="T">The attribute type to retrieve.</typeparam>
 		/// <param name="plugin">The plugin instance.</param>
 		/// <returns>The attributes of the instance, if existing.</returns>
-        public static IEnumerable<T> GetAttributes<T>(object plugin) where T : Attribute
-        {
-            return GetAttributes<T>(plugin.GetType());
-        }
-		
-	    /// <summary>
-	    /// Gets the specified attributes of a type, if they exist.
-	    /// </summary>
-	    /// <typeparam name="T">The attribute type to retrieve.</typeparam>
-	    /// <param name="plugin">The plugin type.</param>
-	    /// <returns>The attributes of the type, if existing.</returns>
-        public static IEnumerable<T> GetAttributes<T>(Type pluginType) where T : Attribute
-        {
-            return pluginType.GetCustomAttributes(typeof(T), true).Cast<T>();
-        }
+		public static IEnumerable<T> GetAttributes<T>(object plugin) where T : Attribute
+		{
+			return GetAttributes<T>(plugin.GetType());
+		}
+
+		/// <summary>
+		/// Gets the specified attributes of a type, if they exist.
+		/// </summary>
+		/// <typeparam name="T">The attribute type to retrieve.</typeparam>
+		/// <param name="plugin">The plugin type.</param>
+		/// <returns>The attributes of the type, if existing.</returns>
+		public static IEnumerable<T> GetAttributes<T>(Type pluginType) where T : Attribute
+		{
+			return pluginType.GetCustomAttributes(typeof(T), true).Cast<T>();
+		}
 
 		/// <summary>
 		/// Retrieves the dependencies of the specified plugin type.
@@ -158,60 +158,57 @@ namespace BepInEx
 		/// <param name="Plugin">The plugin type.</param>
 		/// <param name="AllPlugins">All currently loaded plugin types.</param>
 		/// <returns>A list of all plugin types that the specified plugin type depends upon.</returns>
-        public static IEnumerable<Type> GetDependencies(Type Plugin, IEnumerable<Type> AllPlugins)
-        {
-            object[] attributes = Plugin.GetCustomAttributes(typeof(BepInDependency), true);
+		public static IEnumerable<Type> GetDependencies(Type Plugin, IEnumerable<Type> AllPlugins)
+		{
+			object[] attributes = Plugin.GetCustomAttributes(typeof(BepInDependency), true);
 
-            List<Type> dependencyTypes = new List<Type>();
+			List<Type> dependencyTypes = new List<Type>();
 
-            foreach (BepInDependency dependency in attributes)
-            {
-                Type dependencyType = AllPlugins.FirstOrDefault(x => GetMetadata(x)?.GUID == dependency.DependencyGUID);
+			foreach (BepInDependency dependency in attributes)
+			{
+				Type dependencyType = AllPlugins.FirstOrDefault(x => GetMetadata(x)?.GUID == dependency.DependencyGUID);
 
-                if (dependencyType == null)
-                {
-                    if ((dependency.Flags & BepInDependency.DependencyFlags.SoftDependency) != 0)
-                        continue; //skip on soft dependencies
+				if (dependencyType == null)
+				{
+					if ((dependency.Flags & BepInDependency.DependencyFlags.SoftDependency) != 0)
+						continue; //skip on soft dependencies
 
-                    throw new MissingDependencyException("Cannot find dependency type.");
-                }
-                    
+					throw new MissingDependencyException("Cannot find dependency type.");
+				}
 
-                dependencyTypes.Add(dependencyType);
-            }
 
-            return dependencyTypes;
-        }
-    }
+				dependencyTypes.Add(dependencyType);
+			}
+
+			return dependencyTypes;
+		}
+	}
 
 	/// <summary>
 	/// An exception which is thrown when a plugin's dependencies cannot be found.
 	/// </summary>
-    public class MissingDependencyException : Exception
-    {
-        public MissingDependencyException(string message) : base(message)
-        {
-
-        }
-    }
-
-    #endregion
+	public class MissingDependencyException : Exception
+	{
+		public MissingDependencyException(string message) : base(message) { }
+	}
 
-	#region Debug
+	#endregion
 
-	#if DEBUG
+	#region Build configuration
 
-	public class DebugInfoAttribute : Attribute
+	/// <summary>
+	/// 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 string Info { get; }
 
-		public DebugInfoAttribute(string info)
+		public BuildInfoAttribute(string info)
 		{
 			Info = info;
 		}
 	}
 
-	#endif
-
 	#endregion
-}
+}

+ 4 - 7
BepInEx/Contract/BaseUnityPlugin.cs

@@ -2,11 +2,8 @@
 
 namespace BepInEx
 {
-    /// <summary>
-    /// The base plugin type that is used by the BepInEx plugin loader.
-    /// </summary>
-    public abstract class BaseUnityPlugin : MonoBehaviour
-    {
-
-    }
+	/// <summary>
+	/// The base plugin type that is used by the BepInEx plugin loader.
+	/// </summary>
+	public abstract class BaseUnityPlugin : MonoBehaviour { }
 }

+ 0 - 58
BepInEx/Deprecated/BepInLogger.cs

@@ -1,58 +0,0 @@
-using System;
-using BepInEx.Logging;
-
-namespace BepInEx
-{
-    /// <summary>
-    /// A helper class to use for logging.
-    /// </summary>
-    [Obsolete("This class has been deprecated; please use the Logger static class and BaseLogger implementations", true)]
-    public static class BepInLogger
-    {
-        /// <summary>
-        /// The handler for a entry logged event.
-        /// </summary>
-        /// <param name="entry">The text element of the log itself.</param>
-        /// <param name="show">Whether or not it should be dislpayed to the user.</param>
-        public delegate void EntryLoggedEventHandler(string entry, bool show = false);
-
-        /// <summary>
-        /// The listener event for an entry being logged.
-        /// </summary>
-        public static event EntryLoggedEventHandler EntryLogged;
-
-        /// <summary>
-        /// Logs an entry to the logger, and any listeners are notified of the entry.
-        /// </summary>
-        /// <param name="entry">The text element of the log itself.</param>
-        /// <param name="show">Whether or not it should be dislpayed to the user.</param>
-        public static void Log(string entry, bool show = false)
-        {
-	        Logger.Log(show ? LogLevel.Message : LogLevel.Info, entry);
-
-	        EntryLogged?.Invoke(entry, show);
-        }
-
-        /// <summary>
-        /// Logs an entry to the logger, and any listeners are notified of the entry.
-        /// </summary>
-        /// <param name="entry">The text element of the log itself. Uses .ToString().</param>
-        /// <param name="show">Whether or not it should be dislpayed to the user.</param>
-        /// <param name="color">The color of the text to show in the console.</param>
-        public static void Log(object entry, bool show, ConsoleColor color)
-        {
-	        Log(entry.ToString(), show);
-        }
-
-        /// <summary>
-        /// Logs an entry to the logger, and any listeners are notified of the entry.
-        /// </summary>
-        /// <param name="entry">The text element of the log itself.</param>
-        /// <param name="show">Whether or not it should be dislpayed to the user.</param>
-        /// <param name="color">The color of the text to show in the console.</param>
-        public static void Log(string entry, bool show, ConsoleColor color)
-        {
-			Log(entry, show);
-        }
-    }
-}

+ 0 - 80
BepInEx/Deprecated/Utility.cs

@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-
-namespace BepInEx.Common
-{
-	/// <summary>
-	/// Generic helper properties and methods.
-	/// </summary>
-	[Obsolete("This class has moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-	public static class Utility
-	{
-		/// <summary>
-		/// The directory that the game .exe is being run from.
-		/// </summary>
-		[Obsolete("This property has been moved, please use Paths.GameRootPath instead", true)]
-		public static string ExecutingDirectory
-			=> Paths.GameRootPath;
-
-		/// <summary>
-		/// The path that the plugins folder is located.
-		/// </summary>
-		[Obsolete("This property has been moved, please use Paths.PluginPath instead", true)]
-		public static string PluginsDirectory
-			=> Paths.PluginPath;
-
-		/// <summary>
-		/// Combines multiple paths together, as the specfic method is not availble in .NET 3.5.
-		/// </summary>
-		/// <param name="parts">The multiple paths to combine together.</param>
-		/// <returns>A combined path.</returns>
-		[Obsolete("This method has been moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-		public static string CombinePaths(params string[] parts) =>
-			BepInEx.Utility.CombinePaths(parts);
-
-		/// <summary>
-		/// Tries to parse a bool, with a default value if unable to parse.
-		/// </summary>
-		/// <param name="input">The string to parse</param>
-		/// <param name="defaultValue">The value to return if parsing is unsuccessful.</param>
-		/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
-		[Obsolete("This method has been moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-		public static bool SafeParseBool(string input, bool defaultValue = false) =>
-			BepInEx.Utility.SafeParseBool(input, defaultValue);
-
-		/// <summary>
-		/// Converts a file path into a UnityEngine.WWW format.
-		/// </summary>
-		/// <param name="path">The file path to convert.</param>
-		/// <returns>A converted file path.</returns>
-		[Obsolete("This method has been moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-		public static string ConvertToWWWFormat(string path)
-			=> BepInEx.Utility.ConvertToWWWFormat(path);
-
-		/// <summary>
-		/// Indicates whether a specified string is null, empty, or consists only of white-space characters.
-		/// </summary>
-		/// <param name="self">The string to test.</param>
-		/// <returns>True if the value parameter is null or empty, or if value consists exclusively of white-space characters.</returns>
-		[Obsolete("This method has been moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-		public static bool IsNullOrWhiteSpace(this string self)
-			=> BepInEx.Utility.IsNullOrWhiteSpace(self);
-
-		[Obsolete("This method has been moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-		public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes,
-			Func<TNode, IEnumerable<TNode>> dependencySelector)
-			=> BepInEx.Utility.TopologicalSort(nodes, dependencySelector);
-
-		/// <summary>
-		/// Try to resolve and load the given assembly DLL.
-		/// </summary>
-		/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
-		/// <param name="directory">Directory to search the assembly from.</param>
-		/// <param name="assembly">The loaded assembly.</param>
-		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
-		[Obsolete("This method has been moved, please use BepInEx.Utility instead of BepInEx.Common.Utility", true)]
-		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
-			=> BepInEx.Utility.TryResolveDllAssembly(assemblyName, directory, out assembly);
-	}
-}

+ 56 - 56
BepInEx/Logging/BaseLogger.cs

@@ -7,93 +7,93 @@ namespace BepInEx.Logging
 	/// <summary>
 	/// The base implementation of a logging class.
 	/// </summary>
-    public abstract class BaseLogger : TextWriter
-    {
-	    protected BaseLogger()
-	    {
-		    DisplayedLevels = GetDefaultLogLevel();
-	    }
+	public abstract class BaseLogger : TextWriter
+	{
+		protected BaseLogger()
+		{
+			DisplayedLevels = GetDefaultLogLevel();
+		}
 
 		/// <summary>
 		/// The encoding that the underlying text writer should use. Defaults to UTF-8 BOM.
 		/// </summary>
-        public override Encoding Encoding { get; } = new UTF8Encoding(true);
+		public override Encoding Encoding { get; } = new UTF8Encoding(true);
 
 
-        /// <summary>
-        /// The handler for a entry logged event.
-        /// </summary>
-        /// <param name="entry">The text element of the log itself.</param>
-        /// <param name="show">Whether or not it should be dislpayed to the user.</param>
-        public delegate void EntryLoggedEventHandler(LogLevel level, object entry);
+		/// <summary>
+		/// The handler for a entry logged event.
+		/// </summary>
+		/// <param name="entry">The text element of the log itself.</param>
+		/// <param name="show">Whether or not it should be dislpayed to the user.</param>
+		public delegate void EntryLoggedEventHandler(LogLevel level, object entry);
 
-        /// <summary>
-        /// The listener event for an entry being logged.
-        /// </summary>
-        public event EntryLoggedEventHandler EntryLogged;
+		/// <summary>
+		/// The listener event for an entry being logged.
+		/// </summary>
+		public event EntryLoggedEventHandler EntryLogged;
 
 		/// <summary>
 		/// Retrieves the default log level to use for this logger.
 		/// </summary>
 		/// <returns>The default log level to use.</returns>
-	    protected virtual LogLevel GetDefaultLogLevel()
+		protected virtual LogLevel GetDefaultLogLevel()
 		{
 			LogLevel lowest;
 
-		    try
-		    {
-			    lowest = (LogLevel)Enum.Parse(typeof(LogLevel), Config.GetEntry("logger-displayed-levels", nameof(LogLevel.Info), "BepInEx"));
-		    }
-		    catch
-		    {
-			    return LogLevel.All;
-		    }
+			try
+			{
+				lowest = (LogLevel)Enum.Parse(typeof(LogLevel), Config.GetEntry("logger-displayed-levels", nameof(LogLevel.Info), "BepInEx"));
+			}
+			catch
+			{
+				return LogLevel.All;
+			}
 
-		    if (lowest == LogLevel.None)
-			    return LogLevel.None;
+			if (lowest == LogLevel.None)
+				return LogLevel.None;
 
-		    LogLevel value = lowest;
+			LogLevel value = lowest;
 
-		    foreach (LogLevel e in Enum.GetValues(typeof(LogLevel)))
-		    {
-			    if (lowest > e)
-				    value |= e;
-		    }
+			foreach (LogLevel e in Enum.GetValues(typeof(LogLevel)))
+			{
+				if (lowest > e)
+					value |= e;
+			}
 
-		    return value;
-	    }
+			return value;
+		}
 
-        /// <summary>
+		/// <summary>
 		/// A filter which is used to specify which log levels are not ignored by the logger.
 		/// </summary>
-        public LogLevel DisplayedLevels { get; set; }
+		public LogLevel DisplayedLevels { get; set; }
 
-        private readonly object logLockObj = new object();
+		private readonly object logLockObj = new object();
 
 		/// <summary>
 		/// Logs an entry to the Logger instance.
 		/// </summary>
 		/// <param name="level">The level of the entry.</param>
 		/// <param name="entry">The textual value of the entry.</param>
-        public virtual void Log(LogLevel level, object entry)
-        {
-            if ((DisplayedLevels & level) != LogLevel.None)
-            {
-                lock (logLockObj)
-                {
-                    EntryLogged?.Invoke(level, entry);
-                    WriteLine($"[{level.GetHighestLevel()}] {entry}");
-                }
-            }
-        }
+		public virtual void Log(LogLevel level, object entry)
+		{
+			if ((DisplayedLevels & level) != LogLevel.None)
+			{
+				lock (logLockObj)
+				{
+					EntryLogged?.Invoke(level, entry);
+					WriteLine($"[{level.GetHighestLevel()}] {entry}");
+				}
+			}
+		}
 
 		/// <summary>
 		/// Logs an entry to the Logger instance, with a <see cref="LogLevel"/> of Message.
 		/// </summary>
 		/// <param name="entry">The text value of this log entry.</param>
-        public virtual void Log(object entry)
-        {
-            Log(LogLevel.Message, entry);
-        }
-    }
-}
+		public virtual void Log(object entry)
+		{
+			Log(LogLevel.Message, entry);
+		}
+	}
+}

+ 47 - 47
BepInEx/Logging/LogLevel.cs

@@ -5,96 +5,96 @@ namespace BepInEx.Logging
 	/// <summary>
 	/// The level, or severity of a log entry.
 	/// </summary>
-    [Flags]
-    public enum LogLevel
-    {
+	[Flags]
+	public enum LogLevel
+	{
 		/// <summary>
 		/// No level selected.
 		/// </summary>
-        None = 0,
+		None = 0,
 
 		/// <summary>
 		/// A fatal error has occurred, which cannot be recovered from.
 		/// </summary>
-        Fatal = 1,
+		Fatal = 1,
 
 		/// <summary>
 		/// An error has occured, but can be recovered from.
 		/// </summary>
-        Error = 2,
+		Error = 2,
 
 		/// <summary>
 		/// A warning has been produced, but does not necessarily mean that something wrong has happened.
 		/// </summary>
-        Warning = 4,
+		Warning = 4,
 
 		/// <summary>
 		/// An important message that should be displayed to the user.
 		/// </summary>
-        Message = 8,
+		Message = 8,
 
 		/// <summary>
 		/// A message of low importance.
 		/// </summary>
-        Info = 16,
+		Info = 16,
 
 		/// <summary>
 		/// A message that would likely only interest a developer.
 		/// </summary>
-        Debug = 32,
+		Debug = 32,
 
 		/// <summary>
 		/// All log levels.
 		/// </summary>
-        All = Fatal | Error | Warning | Message | Info | Debug
-    }
+		All = Fatal | Error | Warning | Message | Info | Debug
+	}
 
-    public static class LogLevelExtensions
-    {
+	public static class LogLevelExtensions
+	{
 		/// <summary>
 		/// Gets the highest log level when there could potentially be multiple levels provided.
 		/// </summary>
 		/// <param name="levels">The log level(s).</param>
 		/// <returns>The highest log level supplied.</returns>
-        public static LogLevel GetHighestLevel(this LogLevel levels)
-        {
-            var enums = Enum.GetValues(typeof(LogLevel));
-            Array.Sort(enums);
+		public static LogLevel GetHighestLevel(this LogLevel levels)
+		{
+			var enums = Enum.GetValues(typeof(LogLevel));
+			Array.Sort(enums);
 
-            foreach (LogLevel e in enums)
-            {
-                if ((levels & e) != LogLevel.None)
-                    return e;
-            }
+			foreach (LogLevel e in enums)
+			{
+				if ((levels & e) != LogLevel.None)
+					return e;
+			}
 
-            return LogLevel.None;
-        }
+			return LogLevel.None;
+		}
 
 		/// <summary>
 		/// Returns a translation of a log level to it's associated console colour.
 		/// </summary>
 		/// <param name="level">The log level(s).</param>
 		/// <returns>A console color associated with the highest log level supplied.</returns>
-        public static ConsoleColor GetConsoleColor(this LogLevel level)
-        {
-            level = GetHighestLevel(level);
+		public static ConsoleColor GetConsoleColor(this LogLevel level)
+		{
+			level = GetHighestLevel(level);
 
-            switch (level)
-            {
-                case LogLevel.Fatal:
-                    return ConsoleColor.Red;
-                case LogLevel.Error:
-                    return ConsoleColor.DarkRed;
-                case LogLevel.Warning:
-                    return ConsoleColor.Yellow;
-                case LogLevel.Message:
-                    return ConsoleColor.White;
-                case LogLevel.Info:
-                default:
-                    return ConsoleColor.Gray;
-                case LogLevel.Debug:
-                    return ConsoleColor.DarkGray;
-            }
-        }
-    }
-}
+			switch (level)
+			{
+				case LogLevel.Fatal:
+					return ConsoleColor.Red;
+				case LogLevel.Error:
+					return ConsoleColor.DarkRed;
+				case LogLevel.Warning:
+					return ConsoleColor.Yellow;
+				case LogLevel.Message:
+					return ConsoleColor.White;
+				case LogLevel.Info:
+				default:
+					return ConsoleColor.Gray;
+				case LogLevel.Debug:
+					return ConsoleColor.DarkGray;
+			}
+		}
+	}
+}

+ 19 - 19
BepInEx/Logging/Logger.cs

@@ -5,39 +5,39 @@ namespace BepInEx
 	/// <summary>
 	/// A static <see cref="BaseLogger"/> instance.
 	/// </summary>
-    public static class Logger
-    {
+	public static class Logger
+	{
 		/// <summary>
 		/// The current instance of a <see cref="BaseLogger"/> that is being used.
 		/// </summary>
-        public static BaseLogger CurrentLogger { get; set; }
+		public static BaseLogger CurrentLogger { get; set; }
 
-        /// <summary>
-        /// The listener event for an entry being logged.
-        /// </summary>
-        public static event BaseLogger.EntryLoggedEventHandler EntryLogged;
+		/// <summary>
+		/// The listener event for an entry being logged.
+		/// </summary>
+		public static event BaseLogger.EntryLoggedEventHandler EntryLogged;
 
 		/// <summary>
 		/// Logs an entry to the current logger instance.
 		/// </summary>
 		/// <param name="level">The level of the entry.</param>
 		/// <param name="entry">The textual value of the entry.</param>
-        public static void Log(LogLevel level, object entry)
-        {
-            EntryLogged?.Invoke(level, entry);
+		public static void Log(LogLevel level, object entry)
+		{
+			EntryLogged?.Invoke(level, entry);
 
-            CurrentLogger?.Log(level, entry);
-        }
+			CurrentLogger?.Log(level, entry);
+		}
 
 		/// <summary>
 		/// Sets the instance being used by the static <see cref="Logger"/> class.
 		/// </summary>
 		/// <param name="logger">The instance to use in the static class.</param>
-        public static void SetLogger(BaseLogger logger)
-        {
-            CurrentLogger?.Dispose();
+		public static void SetLogger(BaseLogger logger)
+		{
+			CurrentLogger?.Dispose();
 
-            CurrentLogger = logger;
-        }
-    }
-}
+			CurrentLogger = logger;
+		}
+	}
+}

+ 126 - 127
BepInEx/Logging/LoggerTraceListener.cs

@@ -10,142 +10,141 @@ namespace BepInEx.Logging
 	/// A trace listener that writes to an underlying <see cref="BaseLogger"/> instance.
 	/// </summary>
 	/// <inheritdoc cref="TraceListener"/>
-    public class LoggerTraceListener : TraceListener
-    {
+	public class LoggerTraceListener : TraceListener
+	{
 		/// <summary>
 		/// The logger instance that is being written to.
 		/// </summary>
-        public BaseLogger Logger { get; }
-
-        static LoggerTraceListener()
-        {
-            try
-            {
-                TraceFixer.ApplyFix();
-            }
-            catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this
-        }
-		
+		public BaseLogger Logger { get; }
+
+		static LoggerTraceListener()
+		{
+			try
+			{
+				TraceFixer.ApplyFix();
+			}
+			catch { } //ignore everything, if it's thrown an exception, we're using an assembly that has already fixed this
+		}
+
 		/// <param name="logger">The logger instance to write to.</param>
-        public LoggerTraceListener(BaseLogger logger)
-        {
-            Logger = logger;
-        }
-		
+		public LoggerTraceListener(BaseLogger logger)
+		{
+			Logger = logger;
+		}
+
 		/// <summary>
 		/// Writes a message to the underlying <see cref="BaseLogger"/> instance.
 		/// </summary>
 		/// <param name="message">The message to write.</param>
-        public override void Write(string message)
-        {
-            Logger.Write(message);
-        }
-		
-	    /// <summary>
-	    /// Writes a message and a newline to the underlying <see cref="BaseLogger"/> instance.
-	    /// </summary>
-	    /// <param name="message">The message to write.</param>
-        public override void WriteLine(string message)
-        {
-            Logger.WriteLine(message);
-        }
-
-        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
-            => TraceEvent(eventCache, source, eventType, id, string.Format(format, args));
-
-        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
-        {
-            LogLevel level;
-
-            switch (eventType)
-            {
-                case TraceEventType.Critical:
-                    level = LogLevel.Fatal;
-                    break;
-                case TraceEventType.Error:
-                    level = LogLevel.Error;
-                    break;
-                case TraceEventType.Warning:
-                    level = LogLevel.Warning;
-                    break;
-                case TraceEventType.Information:
-                    level = LogLevel.Info;
-                    break;
-                case TraceEventType.Verbose:
-                default:
-                    level = LogLevel.Debug;
-                    break;
-            }
-
-            Logger.Log(level, $"{source} : {message}");
-        }
+		public override void Write(string message)
+		{
+			Logger.Write(message);
+		}
+
+		/// <summary>
+		/// Writes a message and a newline to the underlying <see cref="BaseLogger"/> instance.
+		/// </summary>
+		/// <param name="message">The message to write.</param>
+		public override void WriteLine(string message)
+		{
+			Logger.WriteLine(message);
+		}
+
+		public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
+			=> TraceEvent(eventCache, source, eventType, id, string.Format(format, args));
+
+		public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
+		{
+			LogLevel level;
+
+			switch (eventType)
+			{
+				case TraceEventType.Critical:
+					level = LogLevel.Fatal;
+					break;
+				case TraceEventType.Error:
+					level = LogLevel.Error;
+					break;
+				case TraceEventType.Warning:
+					level = LogLevel.Warning;
+					break;
+				case TraceEventType.Information:
+					level = LogLevel.Info;
+					break;
+				case TraceEventType.Verbose:
+				default:
+					level = LogLevel.Debug;
+					break;
+			}
+
+			Logger.Log(level, $"{source} : {message}");
+		}
 
 		/// <summary>
 		/// This exists because the Mono implementation of <see cref="Trace"/> is/was broken, and would call Write directly instead of calling TraceEvent. This class fixes that with a <see cref="Harmony"/> hook.
 		/// </summary>
-        private static class TraceFixer
-        {
-            private static Type TraceImplType;
-            
-            private static object ListenersSyncRoot;
-            private static TraceListenerCollection Listeners;
-            private static PropertyInfo prop_AutoFlush;
-
-            private static bool AutoFlush => (bool)prop_AutoFlush.GetValue(null, null);
-
-
-            public static void ApplyFix()
-            {
-                TraceImplType = AppDomain.CurrentDomain.GetAssemblies()
-                    .First(x => x.GetName().Name == "System")
-                    .GetTypes()
-                    .First(x => x.Name == "TraceImpl");
-
-                
-                ListenersSyncRoot = AccessTools.Property(TraceImplType, "ListenersSyncRoot").GetValue(null, null);
-                
-                Listeners = (TraceListenerCollection)AccessTools.Property(TraceImplType, "Listeners").GetValue(null, null);
-                
-                prop_AutoFlush = AccessTools.Property(TraceImplType, "AutoFlush");
-
-
-
-                HarmonyInstance instance = HarmonyInstance.Create("com.bepis.bepinex.tracefix");
-
-                instance.Patch(
-                    typeof(Trace).GetMethod("DoTrace", BindingFlags.Static | BindingFlags.NonPublic),
-                    new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.Public)),
-                    null);
-            }
-
-
-            public static bool DoTraceReplacement(string kind, Assembly report, string message)
-            {
-                string arg = string.Empty;
-                try
-                {
-                    arg = report.GetName().Name;
-                }
-                catch (MethodAccessException) { }
-
-                TraceEventType type = (TraceEventType)Enum.Parse(typeof(TraceEventType), kind);
-
-                lock (ListenersSyncRoot)
-                {
-                    foreach (object obj in Listeners)
-                    {
-                        TraceListener traceListener = (TraceListener)obj;
-                        traceListener.TraceEvent(new TraceEventCache(), arg, type, 0, message);
-
-                        if (AutoFlush)
-                        {
-                            traceListener.Flush();
-                        }
-                    }
-                }
-
-                return false;
-            }
-        }
-    }
-}
+		private static class TraceFixer
+		{
+			private static Type TraceImplType;
+
+			private static object ListenersSyncRoot;
+			private static TraceListenerCollection Listeners;
+			private static PropertyInfo prop_AutoFlush;
+
+			private static bool AutoFlush => (bool)prop_AutoFlush.GetValue(null, null);
+
+
+			public static void ApplyFix()
+			{
+				TraceImplType = AppDomain.CurrentDomain.GetAssemblies()
+										 .First(x => x.GetName().Name == "System")
+										 .GetTypes()
+										 .First(x => x.Name == "TraceImpl");
+
+
+				ListenersSyncRoot = AccessTools.Property(TraceImplType, "ListenersSyncRoot").GetValue(null, null);
+
+				Listeners = (TraceListenerCollection)AccessTools.Property(TraceImplType, "Listeners").GetValue(null, null);
+
+				prop_AutoFlush = AccessTools.Property(TraceImplType, "AutoFlush");
+
+
+				HarmonyInstance instance = HarmonyInstance.Create("com.bepis.bepinex.tracefix");
+
+				instance.Patch(
+					typeof(Trace).GetMethod("DoTrace", BindingFlags.Static | BindingFlags.NonPublic),
+					new HarmonyMethod(typeof(TraceFixer).GetMethod(nameof(DoTraceReplacement), BindingFlags.Static | BindingFlags.Public)),
+					null);
+			}
+
+
+			public static bool DoTraceReplacement(string kind, Assembly report, string message)
+			{
+				string arg = string.Empty;
+				try
+				{
+					arg = report.GetName().Name;
+				}
+				catch (MethodAccessException) { }
+
+				TraceEventType type = (TraceEventType)Enum.Parse(typeof(TraceEventType), kind);
+
+				lock (ListenersSyncRoot)
+				{
+					foreach (object obj in Listeners)
+					{
+						TraceListener traceListener = (TraceListener)obj;
+						traceListener.TraceEvent(new TraceEventCache(), arg, type, 0, message);
+
+						if (AutoFlush)
+						{
+							traceListener.Flush();
+						}
+					}
+				}
+
+				return false;
+			}
+		}
+	}
+}

+ 90 - 89
BepInEx/Logging/PreloaderLogWriter.cs

@@ -11,117 +11,118 @@ namespace BepInEx.Logging
 	/// A log writer specific to the <see cref="Preloader"/>.
 	/// </summary>
 	/// <inheritdoc cref="BaseLogger"/>
-    public class PreloaderLogWriter : BaseLogger
-    {
+	public class PreloaderLogWriter : BaseLogger
+	{
 		/// <summary>
 		/// The <see cref="System.Text.StringBuilder"/> which contains all logged entries, so it may be passed onto another log writer.
 		/// </summary>
-        public StringBuilder StringBuilder { get; protected set; } = new StringBuilder();
+		public StringBuilder StringBuilder { get; protected set; } = new StringBuilder();
 
 		/// <summary>
 		/// Whether or not the log writer is redirecting console output, so it can be logged.
 		/// </summary>
-        public bool IsRedirectingConsole { get; protected set; }
+		public bool IsRedirectingConsole { get; protected set; }
 
-        protected TextWriter stdout;
-        protected LoggerTraceListener traceListener;
+		protected TextWriter stdout;
+		protected LoggerTraceListener traceListener;
 
-        private bool _enabled = false;
+		private bool _enabled = false;
 
 		/// <summary>
 		/// Whether or not the log writer is writing and/or redirecting.
 		/// </summary>
-        public bool Enabled {
-            get => _enabled;
-            set
-            {
-                if (value)
-                    Enable();
-                else
-                    Disable();
-            }
-        }
-		
+		public bool Enabled
+		{
+			get => _enabled;
+			set
+			{
+				if (value)
+					Enable();
+				else
+					Disable();
+			}
+		}
+
 		/// <param name="redirectConsole">Whether or not to redirect the console standard output.</param>
-        public PreloaderLogWriter(bool redirectConsole)
-        {
-            IsRedirectingConsole = redirectConsole;
-            
-            stdout = Console.Out;
-            traceListener = new LoggerTraceListener(this);
-        }
+		public PreloaderLogWriter(bool redirectConsole)
+		{
+			IsRedirectingConsole = redirectConsole;
+
+			stdout = Console.Out;
+			traceListener = new LoggerTraceListener(this);
+		}
 
 		/// <summary>
 		/// Enables the log writer.
 		/// </summary>
-        public void Enable()
-        {
-            if (_enabled)
-                return;
+		public void Enable()
+		{
+			if (_enabled)
+				return;
 
-            if (IsRedirectingConsole)
-                Console.SetOut(this);
-            else
-                Console.SetOut(TextWriter.Null);
+			if (IsRedirectingConsole)
+				Console.SetOut(this);
+			else
+				Console.SetOut(Null);
 
-            Trace.Listeners.Add(traceListener);
+			Trace.Listeners.Add(traceListener);
 
-            _enabled = true;
-        }
+			_enabled = true;
+		}
 
 		/// <summary>
 		/// Disables the log writer.
 		/// </summary>
-        public void Disable()
-        {
-            if (!_enabled)
-                return;
-            
-            Console.SetOut(stdout);
-
-            Trace.Listeners.Remove(traceListener);
-
-            _enabled = false;
-        }
-		
-	    /// <summary>
-	    /// Logs an entry to the Logger instance.
-	    /// </summary>
-	    /// <param name="level">The level of the entry.</param>
-	    /// <param name="entry">The textual value of the entry.</param>
-        public override void Log(LogLevel level, object entry)
-        {
-            Kon.ForegroundColor = level.GetConsoleColor();
-            base.Log(level, entry);
-            Kon.ForegroundColor = ConsoleColor.Gray;
-        }
-
-        public override void Write(char value)
-        {
-            StringBuilder.Append(value);
-
-            stdout.Write(value);
-        }
-
-        public override void Write(string value)
-        {
-            StringBuilder.Append(value);
-
-            stdout.Write(value);
-        }
-
-        protected override void Dispose(bool disposing)
-        {
-            Disable();
-            StringBuilder.Length = 0;
-            
-            traceListener?.Dispose();
-            traceListener = null;
-        }
-
-        public override string ToString()
-        {
-            return StringBuilder.ToString().Trim();
-        }
-    }
-}
+		public void Disable()
+		{
+			if (!_enabled)
+				return;
+
+			Console.SetOut(stdout);
+
+			Trace.Listeners.Remove(traceListener);
+
+			_enabled = false;
+		}
+
+		/// <summary>
+		/// Logs an entry to the Logger instance.
+		/// </summary>
+		/// <param name="level">The level of the entry.</param>
+		/// <param name="entry">The textual value of the entry.</param>
+		public override void Log(LogLevel level, object entry)
+		{
+			Kon.ForegroundColor = level.GetConsoleColor();
+			base.Log(level, entry);
+			Kon.ForegroundColor = ConsoleColor.Gray;
+		}
+
+		public override void Write(char value)
+		{
+			StringBuilder.Append(value);
+
+			stdout.Write(value);
+		}
+
+		public override void Write(string value)
+		{
+			StringBuilder.Append(value);
+
+			stdout.Write(value);
+		}
+
+		protected override void Dispose(bool disposing)
+		{
+			Disable();
+			StringBuilder.Length = 0;
+
+			traceListener?.Dispose();
+			traceListener = null;
+		}
+
+		public override string ToString()
+		{
+			return StringBuilder.ToString().Trim();
+		}
+	}
+}

+ 113 - 108
BepInEx/Logging/UnityLogWriter.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Reflection;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using BepInEx.ConsoleUtil;
 using UnityEngine;
 
@@ -9,112 +10,116 @@ namespace BepInEx.Logging
 	/// <summary>
 	/// Logs entries using Unity specific outputs.
 	/// </summary>
-    public class UnityLogWriter : BaseLogger
-    {
-        private delegate void WriteStringToUnityLogDelegate(string s);
-
-        private static readonly WriteStringToUnityLogDelegate WriteStringToUnityLog;
-
-        static UnityLogWriter()
-        {
-            Type logWriter = typeof(UnityEngine.Logger).Assembly.GetType("UnityEngine.UnityLogWriter");
-
-            MethodInfo writeLog = logWriter.GetMethod("WriteStringToUnityLog",
-                BindingFlags.Static
-                | BindingFlags.Public
-                | BindingFlags.NonPublic);
-            if (writeLog == null)
-            {
-                writeLog = logWriter.GetMethod("WriteStringToUnityLogImpl",
-                    BindingFlags.Static
-                    | BindingFlags.Public
-                    | BindingFlags.NonPublic);
-                if (writeLog == null)
-                    return;
-            }
-
-            WriteStringToUnityLog = (WriteStringToUnityLogDelegate)Delegate.CreateDelegate(typeof(WriteStringToUnityLogDelegate), writeLog);
-        }
-
-        /// <summary>
-        /// Writes a string specifically to the game output log.
-        /// </summary>
-        /// <param name="value">The value to write.</param>
-        public void WriteToLog(string value)
-        {
-            WriteStringToUnityLog?.Invoke(value);
-        }
-
-        protected void InternalWrite(string value)
-        {
-            Console.Write(value);
-            WriteToLog(value);
-        }
-
-	    /// <summary>
-	    /// Logs an entry to the Logger instance.
-	    /// </summary>
-	    /// <param name="level">The level of the entry.</param>
-	    /// <param name="entry">The textual value of the entry.</param>
-        public override void Log(LogLevel level, object entry)
-        {
-            Kon.ForegroundColor = level.GetConsoleColor();
-            base.Log(level, entry);
-            Kon.ForegroundColor = ConsoleColor.Gray;
-
-            // If the display level got ignored, still write it to the log
-            if ((DisplayedLevels & level) == LogLevel.None)
-                WriteToLog($"[{level.GetHighestLevel()}] {entry}\r\n");
-        }
-
-        public override void WriteLine(string value) => InternalWrite($"{value}\r\n");
-        public override void Write(char value) => InternalWrite(value.ToString());
-        public override void Write(string value) => InternalWrite(value);
-
-        /// <summary>
-        /// Start listening to Unity's log message events and sending the messages to BepInEx logger.
-        /// </summary>
-        public static void ListenUnityLogs()
-        {
-            Type application = typeof(Application);
-			
-            EventInfo logEvent = application.GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static);
-            if (logEvent != null)
-            {
-                logEvent.AddEventHandler(null, new Application.LogCallback(OnUnityLogMessageReceived));
-            }
-            else
-            {
-                MethodInfo registerLogCallback = application.GetMethod("RegisterLogCallback", BindingFlags.Public | BindingFlags.Static);
-                registerLogCallback.Invoke(null, new object[] { new Application.LogCallback(OnUnityLogMessageReceived) });
-            }
-        }
-
-        private static void OnUnityLogMessageReceived(string message, string stackTrace, LogType type)
-        {
-            LogLevel logLevel = LogLevel.Message;
-
-            switch (type)
-            {
-                case LogType.Error:
-                case LogType.Assert:
-                case LogType.Exception:
-                    logLevel = LogLevel.Error;
-                    break;
-                case LogType.Warning:
-                    logLevel = LogLevel.Warning;
-                    break;
-                case LogType.Log:
-                default:
-                    logLevel = LogLevel.Info;
-                    break;
-            }
-
-            Logger.Log(logLevel, message);
-            if (type == LogType.Exception)
-            {
-                Logger.Log(logLevel, $"Stack trace:\n{stackTrace}");
-            }
-        }
-    }
+	public class UnityLogWriter : BaseLogger
+	{
+		private static readonly Action<string> WriteStringToUnityLog;
+
+		[DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")]
+		private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer);
+
+		static UnityLogWriter()
+		{
+			foreach (MethodInfo methodInfo in typeof(UnityEngine.UnityLogWriter).GetMethods(BindingFlags.Static | BindingFlags.Public))
+			{
+				if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero)
+					continue;
+
+				WriteStringToUnityLog = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
+				break;
+			}
+		}
+
+		/// <summary>
+		/// Writes a string specifically to the game output log.
+		/// </summary>
+		/// <param name="value">The value to write.</param>
+		public void WriteToLog(string value)
+		{
+			WriteStringToUnityLog?.Invoke(value);
+		}
+
+		protected void InternalWrite(string value)
+		{
+			Console.Write(value);
+			WriteToLog(value);
+		}
+
+		/// <summary>
+		/// Logs an entry to the Logger instance.
+		/// </summary>
+		/// <param name="level">The level of the entry.</param>
+		/// <param name="entry">The textual value of the entry.</param>
+		public override void Log(LogLevel level, object entry)
+		{
+			Kon.ForegroundColor = level.GetConsoleColor();
+			base.Log(level, entry);
+			Kon.ForegroundColor = ConsoleColor.Gray;
+
+			// If the display level got ignored, still write it to the log
+			if ((DisplayedLevels & level) == LogLevel.None)
+				WriteToLog($"[{level.GetHighestLevel()}] {entry}\r\n");
+		}
+
+		public override void WriteLine(string value) => InternalWrite($"{value}\r\n");
+		public override void Write(char value) => InternalWrite(value.ToString());
+		public override void Write(string value) => InternalWrite(value);
+
+		/// <summary>
+		/// Start listening to Unity's log message events and sending the messages to BepInEx logger.
+		/// </summary>
+		public static void ListenUnityLogs()
+		{
+			Type application = typeof(Application);
+
+			EventInfo logEvent = application.GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static);
+			if (logEvent != null)
+			{
+				logEvent.AddEventHandler(null, new Application.LogCallback(OnUnityLogMessageReceived));
+			}
+			else
+			{
+				MethodInfo registerLogCallback = application.GetMethod("RegisterLogCallback", BindingFlags.Public | BindingFlags.Static);
+				registerLogCallback.Invoke(null, new object[] { new Application.LogCallback(OnUnityLogMessageReceived) });
+			}
+		}
+
+		private static void OnUnityLogMessageReceived(string message, string stackTrace, LogType type)
+		{
+			LogLevel logLevel = LogLevel.Message;
+
+			switch (type)
+			{
+				case LogType.Error:
+				case LogType.Assert:
+				case LogType.Exception:
+					logLevel = LogLevel.Error;
+					break;
+				case LogType.Warning:
+					logLevel = LogLevel.Warning;
+					break;
+				case LogType.Log:
+				default:
+					logLevel = LogLevel.Info;
+					break;
+			}
+
+			Logger.Log(logLevel, message);
+			if (type == LogType.Exception)
+			{
+				Logger.Log(logLevel, $"Stack trace:\n{stackTrace}");
+			}
+		}
+	}
 }
+
+namespace UnityEngine
+{
+	internal sealed class UnityLogWriter
+	{
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		public static extern void WriteStringToUnityLogImpl(string s);
+
+		[MethodImpl(MethodImplOptions.InternalCall)]
+		public static extern void WriteStringToUnityLog(string s);
+	}
+}

+ 34 - 34
BepInEx/Paths.cs

@@ -3,11 +3,11 @@ using System.Reflection;
 
 namespace BepInEx
 {
-    /// <summary>
-    ///     Paths used by BepInEx
-    /// </summary>
-    public static class Paths
-    {
+	/// <summary>
+	///     Paths used by BepInEx
+	/// </summary>
+	public static class Paths
+	{
 		internal static void SetExecutablePath(string executablePath)
 		{
 			ExecutablePath = executablePath;
@@ -26,40 +26,40 @@ namespace BepInEx
 			PluginPath = Utility.CombinePaths(BepInExRootPath, pluginPath);
 		}
 
-        /// <summary>
-        ///     The directory that the core BepInEx DLLs reside in.
-        /// </summary>
-        public static string BepInExAssemblyDirectory { get; private set; }
+		/// <summary>
+		///     The directory that the core BepInEx DLLs reside in.
+		/// </summary>
+		public static string BepInExAssemblyDirectory { get; private set; }
 
-        /// <summary>
-        ///     The path to the core BepInEx DLL.
-        /// </summary>
-        public static string BepInExAssemblyPath { get; private set; }
+		/// <summary>
+		///     The path to the core BepInEx DLL.
+		/// </summary>
+		public static string BepInExAssemblyPath { get; private set; }
 
 		/// <summary>
 		///     The path to the main BepInEx folder.
 		/// </summary>
 		public static string BepInExRootPath { get; private set; }
 
-        /// <summary>
-        ///     The path of the currently executing program BepInEx is encapsulated in.
-        /// </summary>
-        public static string ExecutablePath { get; private set; }
+		/// <summary>
+		///     The path of the currently executing program BepInEx is encapsulated in.
+		/// </summary>
+		public static string ExecutablePath { get; private set; }
 
-        /// <summary>
-        ///     The directory that the currently executing process resides in.
-        /// </summary>
-        public static string GameRootPath { get; private set; }
+		/// <summary>
+		///     The directory that the currently executing process resides in.
+		/// </summary>
+		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 Managed folder of the currently running Unity game.
+		/// </summary>
+		public static string ManagedPath { get; private set; }
 
-        /// <summary>
-        ///     The path to the patcher plugin folder which resides in the BepInEx folder.
-        /// </summary>
-        public static string PatcherPluginPath { get; private set; }
+		/// <summary>
+		///     The path to the patcher plugin folder which resides in the BepInEx folder.
+		/// </summary>
+		public static string PatcherPluginPath { get; private set; }
 
 		/// <summary>
 		///     The path to the plugin folder which resides in the BepInEx folder.
@@ -69,9 +69,9 @@ namespace BepInEx
 		/// </summary>
 		public static string PluginPath { get; private set; }
 
-        /// <summary>
-        ///     The name of the currently executing process.
-        /// </summary>
-        public static string ProcessName { get; private set; }
-    }
+		/// <summary>
+		///     The name of the currently executing process.
+		/// </summary>
+		public static string ProcessName { get; private set; }
+	}
 }

+ 3 - 1
BepInEx/Properties/AssemblyInfo.cs

@@ -24,6 +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")]
+
 // Version information for an assembly consists of the following four values:
 //
 //      Major Version
@@ -35,4 +37,4 @@ using BepInEx;
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 [assembly: AssemblyVersion("5.0.0.0")]
-[assembly: AssemblyFileVersion("5.0.0.0")]
+[assembly: AssemblyFileVersion("5.0.0.0")]

+ 90 - 90
BepInEx/Utility.cs

@@ -6,17 +6,17 @@ using System.Reflection;
 
 namespace BepInEx
 {
-    /// <summary>
-    /// Generic helper properties and methods.
-    /// </summary>
-    public static class Utility
-    {
-        /// <summary>
-        /// Combines multiple paths together, as the specific method is not available in .NET 3.5.
-        /// </summary>
-        /// <param name="parts">The multiple paths to combine together.</param>
-        /// <returns>A combined path.</returns>
-        public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
+	/// <summary>
+	/// Generic helper properties and methods.
+	/// </summary>
+	public static class Utility
+	{
+		/// <summary>
+		/// Combines multiple paths together, as the specific method is not available in .NET 3.5.
+		/// </summary>
+		/// <param name="parts">The multiple paths to combine together.</param>
+		/// <returns>A combined path.</returns>
+		public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
 
 		/// <summary>
 		/// Tries to parse a bool, with a default value if unable to parse.
@@ -24,92 +24,92 @@ namespace BepInEx
 		/// <param name="input">The string to parse</param>
 		/// <param name="defaultValue">The value to return if parsing is unsuccessful.</param>
 		/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
-	    public static bool SafeParseBool(string input, bool defaultValue = false)
-	    {
-		    return bool.TryParse(input, out bool result) ? result : defaultValue;
-	    }
-
-        /// <summary>
-        /// Converts a file path into a UnityEngine.WWW format.
-        /// </summary>
-        /// <param name="path">The file path to convert.</param>
-        /// <returns>A converted file path.</returns>
-        public static string ConvertToWWWFormat(string path)
-        {
-            return $"file://{path.Replace('\\', '/')}";
-        }
-
-        /// <summary>
-        /// Indicates whether a specified string is null, empty, or consists only of white-space characters.
-        /// </summary>
-        /// <param name="self">The string to test.</param>
-        /// <returns>True if the value parameter is null or empty, or if value consists exclusively of white-space characters.</returns>
-        public static bool IsNullOrWhiteSpace(this string self)
-        {
-            return self == null || self.Trim().Length == 0;
-        }
-
-        public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
-        {
-		    List<TNode> sorted_list = new List<TNode>();
-
-		    HashSet<TNode> visited = new HashSet<TNode>();
-		    HashSet<TNode> sorted = new HashSet<TNode>();
-			
-	        foreach (TNode input in nodes)
-	        {
-		        Stack<TNode> currentStack = new Stack<TNode>();
-		        if (!Visit(input, currentStack))
-		        {
+		public static bool SafeParseBool(string input, bool defaultValue = false)
+		{
+			return bool.TryParse(input, out bool result) ? result : defaultValue;
+		}
+
+		/// <summary>
+		/// Converts a file path into a UnityEngine.WWW format.
+		/// </summary>
+		/// <param name="path">The file path to convert.</param>
+		/// <returns>A converted file path.</returns>
+		public static string ConvertToWWWFormat(string path)
+		{
+			return $"file://{path.Replace('\\', '/')}";
+		}
+
+		/// <summary>
+		/// Indicates whether a specified string is null, empty, or consists only of white-space characters.
+		/// </summary>
+		/// <param name="self">The string to test.</param>
+		/// <returns>True if the value parameter is null or empty, or if value consists exclusively of white-space characters.</returns>
+		public static bool IsNullOrWhiteSpace(this string self)
+		{
+			return self == null || self.Trim().Length == 0;
+		}
+
+		public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
+		{
+			List<TNode> sorted_list = new List<TNode>();
+
+			HashSet<TNode> visited = new HashSet<TNode>();
+			HashSet<TNode> sorted = new HashSet<TNode>();
+
+			foreach (TNode input in nodes)
+			{
+				Stack<TNode> currentStack = new Stack<TNode>();
+				if (!Visit(input, currentStack))
+				{
 					throw new Exception("Cyclic Dependency:\r\n" + currentStack
-						                    .Select(x => $" - {x}") //append dashes
-						                    .Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
-		        }
-	        }
-			    
-
-		    return sorted_list;
-
-		    bool Visit(TNode node, Stack<TNode> stack)
-		    {
-			    if (visited.Contains(node))
-			    {
-				    if (!sorted.Contains(node))
-				    {
-					    return false;
-				    }
-			    }
-			    else
-			    {
-				    visited.Add(node);
+																   .Select(x => $" - {x}") //append dashes
+																   .Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
+				}
+			}
+
+
+			return sorted_list;
+
+			bool Visit(TNode node, Stack<TNode> stack)
+			{
+				if (visited.Contains(node))
+				{
+					if (!sorted.Contains(node))
+					{
+						return false;
+					}
+				}
+				else
+				{
+					visited.Add(node);
 
 					stack.Push(node);
 
-				    foreach (var dep in dependencySelector(node))
-					    if (!Visit(dep, stack))
-						    return false;
+					foreach (var dep in dependencySelector(node))
+						if (!Visit(dep, stack))
+							return false;
+
 
+					sorted.Add(node);
+					sorted_list.Add(node);
 
-				    sorted.Add(node);
-				    sorted_list.Add(node);
+					stack.Pop();
+				}
 
-				    stack.Pop();
-			    }
-				
-			    return true;
-		    }
-        }
+				return true;
+			}
+		}
 
-        /// <summary>
-        /// Try to resolve and load the given assembly DLL.
-        /// </summary>
-        /// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
-        /// <param name="directory">Directory to search the assembly from.</param>
-        /// <param name="assembly">The loaded assembly.</param>
-        /// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
-        public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
-        {
-            assembly = null;
+		/// <summary>
+		/// Try to resolve and load the given assembly DLL.
+		/// </summary>
+		/// <param name="assemblyName">Name of the assembly, of the type <see cref="AssemblyName" />.</param>
+		/// <param name="directory">Directory to search the assembly from.</param>
+		/// <param name="assembly">The loaded assembly.</param>
+		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
+		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
+		{
+			assembly = null;
 
 			var potentialDirectories = new List<string> { directory };
 
@@ -136,5 +136,5 @@ namespace BepInEx
 
 			return false;
 		}
-    }
+	}
 }

+ 1 - 1
doorstop/doorstop_config.ini

@@ -2,4 +2,4 @@
 # Specifies whether assembly executing is enabled
 enabled=true
 # Specifies the path (absolute, or relative to the game's exe) to the DLL/EXE that should be executed by Doorstop
-targetAssembly=BepInEx\core\BepInEx.dll
+targetAssembly=BepInEx\core\BepInEx.Preloader.dll

+ 1 - 1
submodules/BepInEx.Harmony

@@ -1 +1 @@
-Subproject commit fda68e8458bec7d5850b175255535810dda46c6a
+Subproject commit 95863891d65bd3f02338845668ca1cbd26a17362