Browse Source

Merge with cecil branch

ManlyMarco 4 years ago
parent
commit
49c9749d38

+ 1 - 1
.gitmodules

@@ -3,4 +3,4 @@
 	url = https://github.com/BepInEx/BepInEx.Harmony
 	url = https://github.com/BepInEx/BepInEx.Harmony
 [submodule "submodules/MonoMod"]
 [submodule "submodules/MonoMod"]
 	path = submodules/MonoMod
 	path = submodules/MonoMod
-	url = https://github.com/0x0ade/MonoMod.git
+	url = https://github.com/MonoMod/MonoMod.git

+ 4 - 34
BepInEx.Patcher/BepInEx.Patcher.csproj

@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <Import Project="..\packages\ILRepack.2.0.16\build\ILRepack.props" Condition="Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" />
   <Import Project="..\packages\ILRepack.2.0.16\build\ILRepack.props" Condition="Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" />
-  <Import Project="..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props" Condition="Exists('..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props')" />
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
   <PropertyGroup>
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -20,7 +19,7 @@
   <PropertyGroup>
   <PropertyGroup>
     <StartupObject>BepInEx.Patcher.Program</StartupObject>
     <StartupObject>BepInEx.Patcher.Program</StartupObject>
   </PropertyGroup>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Legacy|AnyCPU'">
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
     <DebugSymbols>true</DebugSymbols>
     <OutputPath>..\bin\patcher\</OutputPath>
     <OutputPath>..\bin\patcher\</OutputPath>
     <DefineConstants>TRACE</DefineConstants>
     <DefineConstants>TRACE</DefineConstants>
@@ -30,29 +29,10 @@
     <ErrorReport>prompt</ErrorReport>
     <ErrorReport>prompt</ErrorReport>
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   </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>
   <ItemGroup>
     <Reference Include="Mono.Cecil, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
     <Reference Include="Mono.Cecil, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
       <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll</HintPath>
       <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="Mono.Cecil.Mdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
-      <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll</HintPath>
-    </Reference>
-    <Reference Include="Mono.Cecil.Pdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
-      <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll</HintPath>
-    </Reference>
-    <Reference Include="Mono.Cecil.Rocks, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
-      <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll</HintPath>
-    </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Core" />
   </ItemGroup>
   </ItemGroup>
@@ -62,6 +42,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <None Include="ILRepack.targets" />
     <None Include="packages.config" />
     <None Include="packages.config" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
@@ -73,19 +54,8 @@
     <PropertyGroup>
     <PropertyGroup>
       <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
       <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
     </PropertyGroup>
     </PropertyGroup>
-    <Error Condition="!Exists('..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.MSBuild.Task.2.0.13\build\ILRepack.MSBuild.Task.props'))" />
     <Error Condition="!Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.16\build\ILRepack.props'))" />
     <Error Condition="!Exists('..\packages\ILRepack.2.0.16\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.16\build\ILRepack.props'))" />
+    <Error Condition="!Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets'))" />
   </Target>
   </Target>
-  <!-- ILRepack -->
-  <Target Name="AfterBuild">
-    <Move SourceFiles="$(OutputPath)\$(AssemblyName).exe" DestinationFiles="$(OutputPath)\$(AssemblyName)1.exe" />
-    <ItemGroup>
-      <InputAssemblies Include="$(OutputPath)\$(AssemblyName)1.exe" />
-      <InputAssemblies Include="$(OutputPath)\Mono.Cecil.dll" />
-    </ItemGroup>
-    <ILRepack Parallel="true" Internalize="true" DebugInfo="true" PrimaryAssemblyFile="$(OutputPath)\$(AssemblyName)1.exe" InputAssemblies="@(InputAssemblies)" TargetKind="Exe" TargetPlatformVersion="v2" OutputFile="$(OutputPath)\$(AssemblyName).exe" />
-    <Delete Files="@(InputAssemblies)" />
-    <Delete Files="$(OutputPath)\BepInEx.Bootstrap.dll" />
-  </Target>
-  <!-- /ILRepack -->
+  <Import Project="..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets" Condition="Exists('..\packages\ILRepack.Lib.MSBuild.Task.2.0.16.1\build\ILRepack.Lib.MSBuild.Task.targets')" />
 </Project>
 </Project>

+ 26 - 0
BepInEx.Patcher/ILRepack.targets

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Target Name="ILRepacker" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
+
+    <Copy SourceFiles="$(OutputPath)\$(AssemblyName).exe" DestinationFiles="$(OutputPath)\$(AssemblyName)1.exe"/>
+
+    <ItemGroup>
+      <InputAssemblies Include="$(OutputPath)\$(AssemblyName)1.exe" />
+      <InputAssemblies Include="$(OutputPath)\Mono.Cecil.dll" />
+    </ItemGroup>
+
+    <ILRepack
+      Parallel="false"
+      Internalize="true"
+      DebugInfo="true"
+      InputAssemblies="@(InputAssemblies)"
+      TargetKind="Exe"
+      TargetPlatformVersion="v2"
+      OutputFile="$(OutputPath)\$(AssemblyName).exe" />
+
+    <Delete Files="@(InputAssemblies)" />
+    <Delete Files="$(OutputPath)\BepInEx.Bootstrap.dll" />
+
+  </Target>
+</Project>

+ 10 - 11
BepInEx.Patcher/Program.cs

@@ -26,6 +26,14 @@ namespace BepInEx.Patcher
 			Console.ResetColor();
 			Console.ResetColor();
 		}
 		}
 
 
+		static string GetUnityEngineAssembly(string managedDir)
+		{
+			var path = Path.Combine(managedDir, "UnityEngine.CoreModule.dll");
+			if (File.Exists(path))
+				return path;
+			return Path.Combine(managedDir, "UnityEngine.dll");
+		}
+
 		static void Main(string[] args)
 		static void Main(string[] args)
 		{
 		{
 			Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
 			Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
@@ -43,12 +51,7 @@ namespace BepInEx.Patcher
 
 
 				string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
 				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
-
+				string unityOutputDLL = GetUnityEngineAssembly(managedDir);
 
 
 				if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
 				if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
 					continue;
 					continue;
@@ -101,11 +104,7 @@ namespace BepInEx.Patcher
 					AssemblyResolver = defaultResolver
 					AssemblyResolver = defaultResolver
 				};
 				};
 
 
-#if UNITY_2018
-				string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll.bak");
-#else
-				string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
-#endif
+				string unityBackupDLL = $"{GetUnityEngineAssembly(managedDir)}.bak";
 
 
 				//determine which assembly to use as a base
 				//determine which assembly to use as a base
 				AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
 				AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);

+ 2 - 1
BepInEx.Patcher/packages.config

@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
+
 <packages>
 <packages>
   <package id="ILRepack" version="2.0.16" targetFramework="net35" />
   <package id="ILRepack" version="2.0.16" targetFramework="net35" />
-  <package id="ILRepack.MSBuild.Task" version="2.0.13" targetFramework="net35" />
+  <package id="ILRepack.Lib.MSBuild.Task" version="2.0.16.1" targetFramework="net35" />
   <package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
   <package id="Mono.Cecil" version="0.10.3" targetFramework="net35" />
 </packages>
 </packages>

+ 1 - 27
BepInEx.Preloader/BepInEx.Preloader.csproj

@@ -13,7 +13,7 @@
     <FileAlignment>512</FileAlignment>
     <FileAlignment>512</FileAlignment>
     <Deterministic>true</Deterministic>
     <Deterministic>true</Deterministic>
   </PropertyGroup>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>none</DebugType>
     <DebugType>none</DebugType>
     <Optimize>true</Optimize>
     <Optimize>true</Optimize>
     <OutputPath>..\bin\</OutputPath>
     <OutputPath>..\bin\</OutputPath>
@@ -22,36 +22,10 @@
     <WarningLevel>4</WarningLevel>
     <WarningLevel>4</WarningLevel>
     <DocumentationFile>..\bin\BepInEx.Preloader.xml</DocumentationFile>
     <DocumentationFile>..\bin\BepInEx.Preloader.xml</DocumentationFile>
   </PropertyGroup>
   </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
-    <OutputPath>..\bin\</OutputPath>
-    <DefineConstants>TRACE;UNITY_2018</DefineConstants>
-    <Optimize>true</Optimize>
-    <DebugType>none</DebugType>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <ErrorReport>prompt</ErrorReport>
-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
-    <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>TRACE;UNITY_2018</DefineConstants>
-    <Optimize>true</Optimize>
-    <PlatformTarget>AnyCPU</PlatformTarget>
-    <ErrorReport>prompt</ErrorReport>
-    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
-  </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <Reference Include="Mono.Cecil, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
     <Reference Include="Mono.Cecil, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
       <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll</HintPath>
       <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="Mono.Cecil.Mdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
-      <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Mdb.dll</HintPath>
-    </Reference>
-    <Reference Include="Mono.Cecil.Pdb, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
-      <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Pdb.dll</HintPath>
-    </Reference>
-    <Reference Include="Mono.Cecil.Rocks, Version=0.10.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
-      <HintPath>..\packages\Mono.Cecil.0.10.3\lib\net35\Mono.Cecil.Rocks.dll</HintPath>
-    </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Core" />
   </ItemGroup>
   </ItemGroup>

+ 47 - 23
BepInEx.Preloader/Entrypoint.cs

@@ -5,38 +5,18 @@ using System.Reflection;
 
 
 namespace BepInEx.Preloader
 namespace BepInEx.Preloader
 {
 {
-	internal static class Entrypoint
+	internal static class PreloaderRunner
 	{
 	{
-		/// <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)
+		public static void PreloaderMain(string[] args)
 		{
 		{
-			EnvVars.LoadVars();
-
 			string bepinPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
 			string bepinPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH)));
 
 
 			Paths.SetExecutablePath(args[0], bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
 			Paths.SetExecutablePath(args[0], bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
 			AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
 			AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
-
 			Preloader.Run();
 			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)
+		private static Assembly LocalResolve(object sender, ResolveEventArgs args)
 		{
 		{
 			var assemblyName = new AssemblyName(args.Name);
 			var assemblyName = new AssemblyName(args.Name);
 
 
@@ -54,4 +34,48 @@ namespace BepInEx.Preloader
 			return null;
 			return null;
 		}
 		}
 	}
 	}
+
+	internal static class Entrypoint
+	{
+		private static string preloaderPath;
+
+		/// <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)
+		{
+			EnvVars.LoadVars();
+
+			// Get the path of this DLL via Doorstop env var because Assembly.Location mangles non-ASCII characters on some versions of Mono for unknown reasons
+			preloaderPath = Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH));
+
+			AppDomain.CurrentDomain.AssemblyResolve += ResolveCurrentDirectory;
+
+			// In some versions of Unity 4, Mono tries to resolve BepInEx.dll prematurely because of the call to Paths.SetExecutablePath
+			// To prevent that, we have to use reflection and a separate startup class so that we can install required assembly resolvers before the main code
+			typeof(Entrypoint).Assembly.GetType($"BepInEx.Preloader.{nameof(PreloaderRunner)}")
+							  ?.GetMethod(nameof(PreloaderRunner.PreloaderMain))
+							  ?.Invoke(null, new object[] { args });
+
+			AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;
+		}
+
+		private static Assembly ResolveCurrentDirectory(object sender, ResolveEventArgs args)
+		{
+			var name = new AssemblyName(args.Name);
+
+			try
+			{
+				return Assembly.LoadFile(Path.Combine(preloaderPath, $"{name.Name}.dll"));
+			}
+			catch (Exception)
+			{
+				return null;
+			}
+		}
+	}
 }
 }

+ 0 - 1
BepInEx.Preloader/Logger/PreloaderLogWriter.cs

@@ -56,7 +56,6 @@ namespace BepInEx.Preloader
 
 
 		public void Dispose()
 		public void Dispose()
 		{
 		{
-
 			if (LoggerSource != null)
 			if (LoggerSource != null)
 			{
 			{
 				Console.SetOut(StandardOut);
 				Console.SetOut(StandardOut);

+ 94 - 13
BepInEx.Preloader/Patching/AssemblyPatcher.cs

@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
+using BepInEx.Bootstrap;
 using BepInEx.Configuration;
 using BepInEx.Configuration;
 using BepInEx.Logging;
 using BepInEx.Logging;
 using BepInEx.Preloader.RuntimeFixes;
 using BepInEx.Preloader.RuntimeFixes;
@@ -23,6 +24,8 @@ namespace BepInEx.Preloader.Patching
 	/// </summary>
 	/// </summary>
 	internal static class AssemblyPatcher
 	internal static class AssemblyPatcher
 	{
 	{
+		private const BindingFlags ALL = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.IgnoreCase;
+
 		public static List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
 		public static List<PatcherPlugin> PatcherPlugins { get; } = new List<PatcherPlugin>();
 
 
 		private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
 		private static readonly string DumpedAssembliesPath = Path.Combine(Paths.BepInExRootPath, "DumpedAssemblies");
@@ -36,29 +39,108 @@ namespace BepInEx.Preloader.Patching
 			PatcherPlugins.Add(patcher);
 			PatcherPlugins.Add(patcher);
 		}
 		}
 
 
+		private static T CreateDelegate<T>(MethodInfo method) where T : class => method != null ? Delegate.CreateDelegate(typeof(T), method) as T : null;
+
+		private static PatcherPlugin ToPatcherPlugin(TypeDefinition type)
+		{
+			if (type.IsInterface || type.IsAbstract && !type.IsSealed)
+				return null;
+
+			var targetDlls = type.Methods.FirstOrDefault(m => m.Name.Equals("get_TargetDLLs", StringComparison.InvariantCultureIgnoreCase) &&
+															  m.IsPublic &&
+															  m.IsStatic);
+
+			if (targetDlls == null ||
+				targetDlls.ReturnType.FullName != "System.Collections.Generic.IEnumerable`1<System.String>")
+				return null;
+
+			var patch = type.Methods.FirstOrDefault(m => m.Name.Equals("Patch") &&
+														 m.IsPublic &&
+														 m.IsStatic &&
+														 m.ReturnType.FullName == "System.Void" &&
+														 m.Parameters.Count == 1 &&
+														 (m.Parameters[0].ParameterType.FullName == "Mono.Cecil.AssemblyDefinition&" ||
+														  m.Parameters[0].ParameterType.FullName == "Mono.Cecil.AssemblyDefinition"));
+
+			if (patch == null)
+				return null;
+
+			return new PatcherPlugin
+			{
+				TypeName = type.FullName
+			};
+		}
+
 		/// <summary>
 		/// <summary>
 		///     Adds all patchers from all managed assemblies specified in a directory.
 		///     Adds all patchers from all managed assemblies specified in a directory.
 		/// </summary>
 		/// </summary>
 		/// <param name="directory">Directory to search patcher DLLs from.</param>
 		/// <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>
 		/// <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)
+		public static void AddPatchersFromDirectory(string directory)
 		{
 		{
 			if (!Directory.Exists(directory))
 			if (!Directory.Exists(directory))
 				return;
 				return;
 
 
 			var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
 			var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
 
 
-			foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories))
-				try
+			var patchers = TypeLoader.FindPluginTypes(directory, ToPatcherPlugin);
+
+			foreach (var keyValuePair in patchers)
+			{
+				var assemblyPath = keyValuePair.Key;
+				var patcherCollection = keyValuePair.Value;
+
+				var ass = Assembly.LoadFile(assemblyPath);
+
+				foreach (var patcherPlugin in patcherCollection)
 				{
 				{
-					var assembly = Assembly.LoadFrom(assemblyPath);
+					try
+					{
+						var type = ass.GetType(patcherPlugin.TypeName);
+
+						var methods = type.GetMethods(ALL);
+
+						patcherPlugin.Initializer = CreateDelegate<Action>(methods.FirstOrDefault(m => m.Name.Equals("Initialize", StringComparison.InvariantCultureIgnoreCase) &&
+																									   m.GetParameters().Length == 0 &&
+																									   m.ReturnType == typeof(void)));
+
+						patcherPlugin.Finalizer = CreateDelegate<Action>(methods.FirstOrDefault(m => m.Name.Equals("Finish", StringComparison.InvariantCultureIgnoreCase) &&
+																									 m.GetParameters().Length == 0 &&
+																									 m.ReturnType == typeof(void)));
+
+						patcherPlugin.TargetDLLs = CreateDelegate<Func<IEnumerable<string>>>(type.GetProperty("TargetDLLs", ALL).GetGetMethod());
+
+						var patcher = methods.FirstOrDefault(m => m.Name.Equals("Patch", StringComparison.CurrentCultureIgnoreCase) &&
+																  m.ReturnType == typeof(void) &&
+																  m.GetParameters().Length == 1 &&
+																  (m.GetParameters()[0].ParameterType == typeof(AssemblyDefinition) ||
+																   m.GetParameters()[0].ParameterType == typeof(AssemblyDefinition).MakeByRefType()));
+
+						patcherPlugin.Patcher = (ref AssemblyDefinition pAss) =>
+						{
+							//we do the array fuckery here to get the ref result out
+							object[] args = { pAss };
+
+							patcher.Invoke(null, args);
 
 
-					foreach (var patcher in patcherLocator(assembly))
-						sortedPatchers.Add(patcher.Name, patcher);
+							pAss = (AssemblyDefinition)args[0];
+						};
+
+						sortedPatchers.Add($"{ass.GetName().Name}/{type.FullName}", patcherPlugin);
+					}
+					catch (Exception e)
+					{
+						Logger.LogError($"Failed to load patcher [{patcherPlugin.TypeName}]: {e.Message}");
+						if (e is ReflectionTypeLoadException re)
+							Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re));
+						else
+							Logger.LogDebug(e.ToString());
+					}
 				}
 				}
-				catch (BadImageFormatException) { } //unmanaged DLL
-				catch (ReflectionTypeLoadException) { } //invalid references
+
+				Logger.Log(patcherCollection.Any() ? LogLevel.Info : LogLevel.Debug,
+					$"Loaded {patcherCollection.Count} patcher methods from {ass.GetName().FullName}");
+			}
 
 
 			foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
 			foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
 				AddPatcher(patcher.Value);
 				AddPatcher(patcher.Value);
@@ -123,10 +205,10 @@ namespace BepInEx.Preloader.Patching
 			// Then, perform the actual patching
 			// Then, perform the actual patching
 			var patchedAssemblies = new HashSet<string>();
 			var patchedAssemblies = new HashSet<string>();
 			foreach (var assemblyPatcher in PatcherPlugins)
 			foreach (var assemblyPatcher in PatcherPlugins)
-				foreach (string targetDll in assemblyPatcher.TargetDLLs)
+				foreach (string targetDll in assemblyPatcher.TargetDLLs())
 					if (assemblies.TryGetValue(targetDll, out var assembly))
 					if (assemblies.TryGetValue(targetDll, out var assembly))
 					{
 					{
-						Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.Name}]");
+						Logger.LogInfo($"Patching [{assembly.Name.Name}] with [{assemblyPatcher.TypeName}]");
 
 
 						assemblyPatcher.Patcher?.Invoke(ref assembly);
 						assemblyPatcher.Patcher?.Invoke(ref assembly);
 						assemblies[targetDll] = assembly;
 						assemblies[targetDll] = assembly;
@@ -135,7 +217,6 @@ namespace BepInEx.Preloader.Patching
 
 
 
 
 			// Finally, load patched assemblies into memory
 			// Finally, load patched assemblies into memory
-
 			if (ConfigDumpAssemblies.Value || ConfigLoadDumpedAssemblies.Value)
 			if (ConfigDumpAssemblies.Value || ConfigLoadDumpedAssemblies.Value)
 			{
 			{
 				if (!Directory.Exists(DumpedAssembliesPath))
 				if (!Directory.Exists(DumpedAssembliesPath))
@@ -153,7 +234,7 @@ namespace BepInEx.Preloader.Patching
 
 
 			if (ConfigBreakBeforeLoadAssemblies.Value)
 			if (ConfigBreakBeforeLoadAssemblies.Value)
 			{
 			{
-				Logger.LogInfo($"BepInEx is about load the following assemblies:\n{string.Join("\n", patchedAssemblies.ToArray())}");
+				Logger.LogInfo(data: $"BepInEx is about load the following assemblies:\n{String.Join("\n", patchedAssemblies.ToArray())}");
 				Logger.LogInfo($"The assemblies were dumped into {DumpedAssembliesPath}");
 				Logger.LogInfo($"The assemblies were dumped into {DumpedAssembliesPath}");
 				Logger.LogInfo("Load any assemblies into the debugger, set breakpoints and continue execution.");
 				Logger.LogInfo("Load any assemblies into the debugger, set breakpoints and continue execution.");
 				Debugger.Break();
 				Debugger.Break();

+ 16 - 4
BepInEx.Preloader/Patching/PatcherPlugin.cs

@@ -1,17 +1,19 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.IO;
+using BepInEx.Bootstrap;
 
 
 namespace BepInEx.Preloader.Patching
 namespace BepInEx.Preloader.Patching
 {
 {
 	/// <summary>
 	/// <summary>
 	///     A single assembly patcher.
 	///     A single assembly patcher.
 	/// </summary>
 	/// </summary>
-	internal class PatcherPlugin
+	internal class PatcherPlugin : ICacheable
 	{
 	{
 		/// <summary>
 		/// <summary>
 		///     Target assemblies to patch.
 		///     Target assemblies to patch.
 		/// </summary>
 		/// </summary>
-		public IEnumerable<string> TargetDLLs { get; set; } = null;
+		public Func<IEnumerable<string>> TargetDLLs { get; set; } = null;
 
 
 		/// <summary>
 		/// <summary>
 		///     Initializer method that is run before any patching occurs.
 		///     Initializer method that is run before any patching occurs.
@@ -29,8 +31,18 @@ namespace BepInEx.Preloader.Patching
 		public AssemblyPatcherDelegate Patcher { get; set; } = null;
 		public AssemblyPatcherDelegate Patcher { get; set; } = null;
 
 
 		/// <summary>
 		/// <summary>
-		///     Name of the patcher.
+		///     Type name of the patcher.
 		/// </summary>
 		/// </summary>
-		public string Name { get; set; } = string.Empty;
+		public string TypeName { get; set; } = string.Empty;
+
+		public void Save(BinaryWriter bw)
+		{
+			bw.Write(TypeName);
+		}
+
+		public void Load(BinaryReader br)
+		{
+			TypeName = br.ReadString();
+		}
 	}
 	}
 }
 }

+ 39 - 122
BepInEx.Preloader/Preloader.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
-using System.Reflection;
 using System.Text;
 using System.Text;
 using BepInEx.Configuration;
 using BepInEx.Configuration;
 using BepInEx.Logging;
 using BepInEx.Logging;
@@ -11,6 +10,7 @@ using BepInEx.Preloader.Patching;
 using BepInEx.Preloader.RuntimeFixes;
 using BepInEx.Preloader.RuntimeFixes;
 using Mono.Cecil;
 using Mono.Cecil;
 using Mono.Cecil.Cil;
 using Mono.Cecil.Cil;
+using MonoMod.RuntimeDetour;
 using UnityInjector.ConsoleUtil;
 using UnityInjector.ConsoleUtil;
 using MethodAttributes = Mono.Cecil.MethodAttributes;
 using MethodAttributes = Mono.Cecil.MethodAttributes;
 
 
@@ -26,24 +26,32 @@ namespace BepInEx.Preloader
 		/// </summary>
 		/// </summary>
 		private static PreloaderConsoleListener PreloaderLog { get; set; }
 		private static PreloaderConsoleListener PreloaderLog { get; set; }
 
 
+		public static bool IsPostUnity2017 { get; } = File.Exists(Path.Combine(Paths.ManagedPath, "UnityEngine.CoreModule.dll"));
+
 		public static void Run()
 		public static void Run()
 		{
 		{
 			try
 			try
 			{
 			{
 				AllocateConsole();
 				AllocateConsole();
 
 
-				if (ConfigApplyRuntimePatches.Value)
-					UnityPatches.Apply();
+				Utility.TryDo(() =>
+				{
+					if (ConfigShimHarmony.Value)
+						HarmonyDetourBridge.Init();
+				}, out var harmonyBridgeException);
+
+				Utility.TryDo(() =>
+				{
+					if (ConfigApplyRuntimePatches.Value)
+						UnityPatches.Apply();
+				}, out var runtimePatchException);
 
 
 				Logger.Sources.Add(TraceLogSource.CreateSource());
 				Logger.Sources.Add(TraceLogSource.CreateSource());
 
 
 				PreloaderLog = new PreloaderConsoleListener(ConfigPreloaderCOutLogging.Value);
 				PreloaderLog = new PreloaderConsoleListener(ConfigPreloaderCOutLogging.Value);
-
 				Logger.Listeners.Add(PreloaderLog);
 				Logger.Listeners.Add(PreloaderLog);
 
 
-				
-				string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} RC1 - {Process.GetCurrentProcess().ProcessName}";
-
+				string consoleTile = $"BepInEx {typeof(Paths).Assembly.GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
 				ConsoleWindow.Title = consoleTile;
 				ConsoleWindow.Title = consoleTile;
 				Logger.LogMessage(consoleTile);
 				Logger.LogMessage(consoleTile);
 
 
@@ -53,35 +61,33 @@ namespace BepInEx.Preloader
 				if (attributes.Length > 0)
 				if (attributes.Length > 0)
 				{
 				{
 					var attribute = (BuildInfoAttribute)attributes[0];
 					var attribute = (BuildInfoAttribute)attributes[0];
-
 					Logger.LogMessage(attribute.Info);
 					Logger.LogMessage(attribute.Info);
 				}
 				}
 
 
-#if UNITY_2018
-				Logger.LogMessage("Compiled in Unity v2018 mode");
-#else
-				Logger.LogMessage("Compiled in Legacy Unity mode");
-#endif
+				Logger.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
+				Logger.LogInfo($"CLR runtime version: {Environment.Version}");
+				Logger.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
 
 
-				Logger.LogInfo($"Running under Unity v{Process.GetCurrentProcess().MainModule.FileVersionInfo.FileVersion}");
+				if (harmonyBridgeException != null)
+					Logger.LogWarning($"Failed to enable fix for Harmony for .NET Standard API. Error message: {harmonyBridgeException.Message}");
 
 
-				Logger.LogMessage("Preloader started");
+				if (runtimePatchException != null)
+					Logger.LogWarning($"Failed to apply runtime patches for Mono. See more info in the output log. Error message: {runtimePatchException.Message}");
 
 
+				Logger.LogMessage("Preloader started");
 
 
 				AssemblyPatcher.AddPatcher(new PatcherPlugin
 				AssemblyPatcher.AddPatcher(new PatcherPlugin
-					{ TargetDLLs = new[] { ConfigEntrypointAssembly.Value },
-						Patcher = PatchEntrypoint,
-						Name = "BepInEx.Chainloader"
-					});
+				{
+					TargetDLLs = () => new[] { ConfigEntrypointAssembly.Value },
+					Patcher = PatchEntrypoint,
+					TypeName = "BepInEx.Chainloader"
+				});
 
 
-				AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath, GetPatcherMethods);
+				AssemblyPatcher.AddPatchersFromDirectory(Paths.PatcherPluginPath);
 
 
 				Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
 				Logger.LogInfo($"{AssemblyPatcher.PatcherPlugins.Count} patcher plugin(s) loaded");
 
 
-
 				AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
 				AssemblyPatcher.PatchAndLoad(Paths.ManagedPath);
-
-
 				AssemblyPatcher.DisposePatchers();
 				AssemblyPatcher.DisposePatchers();
 
 
 
 
@@ -123,97 +129,6 @@ namespace BepInEx.Preloader
 		}
 		}
 
 
 		/// <summary>
 		/// <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);
-
-					patcherMethods.Add(assemblyPatcher);
-				}
-				catch (Exception ex)
-				{
-					Logger.LogWarning($"Could not load patcher methods from {assembly.GetName().Name}");
-					Logger.LogWarning(ex);
-				}
-
-			Logger.Log(patcherMethods.Count > 0 ? LogLevel.Info : LogLevel.Debug,
-				$"Loaded {patcherMethods.Count} patcher methods from {assembly.GetName().Name}");
-
-			return patcherMethods;
-		}
-
-		/// <summary>
 		///     Inserts BepInEx's own chainloader entrypoint into UnityEngine.
 		///     Inserts BepInEx's own chainloader entrypoint into UnityEngine.
 		/// </summary>
 		/// </summary>
 		/// <param name="assembly">The assembly that will be attempted to be patched.</param>
 		/// <param name="assembly">The assembly that will be attempted to be patched.</param>
@@ -281,9 +196,9 @@ namespace BepInEx.Preloader
 					il.InsertBefore(ins,
 					il.InsertBefore(ins,
 						il.Create(OpCodes.Ldnull)); // gameExePath (always null, we initialize the Paths class in Entrypoint
 						il.Create(OpCodes.Ldnull)); // gameExePath (always null, we initialize the Paths class in Entrypoint
 					il.InsertBefore(ins,
 					il.InsertBefore(ins,
-						il.Create(OpCodes.Ldc_I4_0)); // startConsole (always false, we already load the console in Preloader)
+						il.Create(OpCodes.Ldc_I4_0)); //startConsole (always false, we already load the console in Preloader)
 					il.InsertBefore(ins,
 					il.InsertBefore(ins,
-						il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gameExePath, bool startConsole = true)
+						il.Create(OpCodes.Call, initMethod)); // Chainloader.Initialize(string gamePath, string managedPath = null, bool startConsole = true)
 					il.InsertBefore(ins,
 					il.InsertBefore(ins,
 						il.Create(OpCodes.Call, startMethod));
 						il.Create(OpCodes.Call, startMethod));
 				}
 				}
@@ -323,12 +238,8 @@ namespace BepInEx.Preloader
 			"Preloader.Entrypoint",
 			"Preloader.Entrypoint",
 			"Assembly",
 			"Assembly",
 			"The local filename of the assembly to target.",
 			"The local filename of the assembly to target.",
-#if UNITY_2018
-			"UnityEngine.CoreModule.dll"
-#else
-			"UnityEngine.dll"
-#endif
-			);
+			IsPostUnity2017 ? "UnityEngine.CoreModule.dll" : "UnityEngine.dll"
+		);
 
 
 		private static readonly ConfigWrapper<string> ConfigEntrypointType = ConfigFile.CoreConfig.Wrap(
 		private static readonly ConfigWrapper<string> ConfigEntrypointType = ConfigFile.CoreConfig.Wrap(
 			"Preloader.Entrypoint",
 			"Preloader.Entrypoint",
@@ -348,6 +259,12 @@ namespace BepInEx.Preloader
 			"Enables or disables runtime patches.\nThis should always be true, unless you cannot start the game due to a Harmony related issue (such as running .NET Standard runtime) or you know what you're doing.",
 			"Enables or disables runtime patches.\nThis should always be true, unless you cannot start the game due to a Harmony related issue (such as running .NET Standard runtime) or you know what you're doing.",
 			true);
 			true);
 
 
+		private static readonly ConfigWrapper<bool> ConfigShimHarmony = ConfigFile.CoreConfig.Wrap(
+			"Preloader",
+			"ShimHarmonySupport",
+			"If enabled, basic Harmony functionality is patched to use MonoMod's RuntimeDetour instead.\nTry using this if Harmony does not work in a game.",
+			!Utility.CLRSupportsDynamicAssemblies);
+
 		private static readonly ConfigWrapper<bool> ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Wrap(
 		private static readonly ConfigWrapper<bool> ConfigPreloaderCOutLogging = ConfigFile.CoreConfig.Wrap(
 			"Logging",
 			"Logging",
 			"PreloaderConsoleOutRedirection",
 			"PreloaderConsoleOutRedirection",

+ 0 - 13
BepInEx.Preloader/RuntimeFixes/UnityPatches.cs

@@ -39,18 +39,5 @@ namespace BepInEx.Preloader.RuntimeFixes
 			if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
 			if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
 				__result = $"file://{location.Replace('\\', '/')}";
 				__result = $"file://{location.Replace('\\', '/')}";
 		}
 		}
-
-#if UNITY_2018
-/*
- * DESC: Workaround for Trace class not working because of missing .config file
- * AFFECTS: Unity 2018+ (not .NET Standard / MonoBleedingEdge runtimes)
- */
-		[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
 	}
 	}
 }
 }

+ 1 - 0
BepInEx.Preloader/packages.config

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

+ 11 - 49
BepInEx.sln

@@ -1,7 +1,7 @@
 
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
 # Visual Studio 15
-VisualStudioVersion = 15.0.27130.2027
+VisualStudioVersion = 15.0.28307.757
 MinimumVisualStudioVersion = 10.0.40219.1
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx", "BepInEx\BepInEx.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx", "BepInEx\BepInEx.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
 EndProject
 EndProject
@@ -35,82 +35,44 @@ EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
-		Legacy|Any CPU = Legacy|Any CPU
 		Release|Any CPU = Release|Any CPU
 		Release|Any CPU = Release|Any CPU
-		v2018|Any CPU = v2018|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = v2018|Any CPU
-		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = v2018|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}.Debug|Any CPU.ActiveCfg = v2018|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = v2018|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = v2018|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = v2018|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
-		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = v2018|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.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.ActiveCfg = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.Build.0 = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|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}.Release|Any CPU.ActiveCfg = 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}.Release|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}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = 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}.Release|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}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = 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}.Release|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}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = v2018|Any CPU
-		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = v2018|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
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Debug|Any CPU.Build.0 = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = Release|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
-		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Legacy|Any CPU.Build.0 = Release|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.Build.0 = Release|Any CPU
 		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.Release|Any CPU.Build.0 = Release|Any CPU
-		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.ActiveCfg = Release|Any CPU
-		{D0C584C0-81D7-486E-B70E-D7F9256E0909}.v2018|Any CPU.Build.0 = Release|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
-		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Legacy|Any CPU.Build.0 = Release|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.Build.0 = Release|Any CPU
 		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.Release|Any CPU.Build.0 = Release|Any CPU
-		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Any CPU.ActiveCfg = Release|Any CPU
-		{1839CFE2-3DB0-45A8-B03D-9AA797479A3A}.v2018|Any CPU.Build.0 = Release|Any CPU
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.ActiveCfg = Debug|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.ActiveCfg = Debug|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.Build.0 = Debug|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Debug|Any CPU.Build.0 = Debug|x86
-		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.ActiveCfg = Release|x86
-		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Legacy|Any CPU.Build.0 = Release|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|x86
 		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.Release|Any CPU.ActiveCfg = Release|x86
-		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.ActiveCfg = Release|x86
-		{E7CD429A-D057-48E3-8C51-E5C934E8E07B}.v2018|Any CPU.Build.0 = Release|x86
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE

+ 3 - 2
BepInEx/BepInEx.csproj

@@ -13,7 +13,7 @@
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <TargetFrameworkProfile />
     <TargetFrameworkProfile />
   </PropertyGroup>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <PlatformTarget>AnyCPU</PlatformTarget>
     <PlatformTarget>AnyCPU</PlatformTarget>
     <DebugType>none</DebugType>
     <DebugType>none</DebugType>
     <Optimize>true</Optimize>
     <Optimize>true</Optimize>
@@ -37,7 +37,7 @@
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
     <OutputPath>bin\Debug\</OutputPath>
     <OutputPath>bin\Debug\</OutputPath>
-    <DefineConstants>DEBUG;TRACE;UNITY_2018</DefineConstants>
+    <DefineConstants>TRACE;DEBUG</DefineConstants>
     <DocumentationFile>
     <DocumentationFile>
     </DocumentationFile>
     </DocumentationFile>
     <Optimize>false</Optimize>
     <Optimize>false</Optimize>
@@ -70,6 +70,7 @@
     <Compile Include="Configuration\AcceptableValueBase.cs" />
     <Compile Include="Configuration\AcceptableValueBase.cs" />
     <Compile Include="Configuration\AcceptableValueList.cs" />
     <Compile Include="Configuration\AcceptableValueList.cs" />
     <Compile Include="Configuration\AcceptableValueRange.cs" />
     <Compile Include="Configuration\AcceptableValueRange.cs" />
+    <Compile Include="Contract\PluginInfo.cs" />
     <Compile Include="Configuration\ConfigDefinition.cs" />
     <Compile Include="Configuration\ConfigDefinition.cs" />
     <Compile Include="Configuration\ConfigDescription.cs" />
     <Compile Include="Configuration\ConfigDescription.cs" />
     <Compile Include="Configuration\ConfigEntry.cs" />
     <Compile Include="Configuration\ConfigEntry.cs" />

+ 118 - 94
BepInEx/Bootstrap/Chainloader.cs

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

+ 209 - 20
BepInEx/Bootstrap/TypeLoader.cs

@@ -1,56 +1,235 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
+using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using System.Text;
 using System.Text;
+using BepInEx.Configuration;
 using BepInEx.Logging;
 using BepInEx.Logging;
+using Mono.Cecil;
 
 
 namespace BepInEx.Bootstrap
 namespace BepInEx.Bootstrap
 {
 {
 	/// <summary>
 	/// <summary>
-	/// Provides methods for loading specified types from an assembly.
+	/// A cacheable metadata item. Can be used with <see cref="TypeLoader.LoadAssemblyCache{T}"/> and <see cref="TypeLoader.SaveAssemblyCache{T}"/> to cache plugin metadata.
+	/// </summary>
+	public interface ICacheable
+	{
+		/// <summary>
+		/// Serialize the object into a binary format.
+		/// </summary>
+		/// <param name="bw"></param>
+		void Save(BinaryWriter bw);
+
+		/// <summary>
+		/// Loads the object from binary format.
+		/// </summary>
+		/// <param name="br"></param>
+		void Load(BinaryReader br);
+	}
+
+	/// <summary>
+	/// A cached assembly.
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	public class CachedAssembly<T> where T : ICacheable
+	{
+		/// <summary>
+		/// List of cached items inside the assembly.
+		/// </summary>
+		public List<T> CacheItems { get; set; }
+
+
+		/// <summary>
+		/// Timestamp of the assembly. Used to check the age of the cache.
+		/// </summary>
+		public long Timestamp { get; set; }
+	}
+
+	/// <summary>
+	///     Provides methods for loading specified types from an assembly.
 	/// </summary>
 	/// </summary>
 	public static class TypeLoader
 	public static class TypeLoader
 	{
 	{
+		private static readonly DefaultAssemblyResolver resolver;
+		private static readonly ReaderParameters readerParameters;
+
+		static TypeLoader()
+		{
+			resolver = new DefaultAssemblyResolver();
+			readerParameters = new ReaderParameters { AssemblyResolver = resolver };
+
+			resolver.ResolveFailure += (sender, reference) =>
+			{
+				var name = new AssemblyName(reference.FullName);
+
+				if (Utility.TryResolveDllAssembly(name, Paths.BepInExAssemblyDirectory, readerParameters, out var assembly) ||
+					Utility.TryResolveDllAssembly(name, Paths.PluginPath, readerParameters, out assembly) ||
+					Utility.TryResolveDllAssembly(name, Paths.ManagedPath, readerParameters, out assembly))
+					return assembly;
+
+				return AssemblyResolve?.Invoke(sender, reference);
+			};
+		}
+
+		public static event AssemblyResolveEventHandler AssemblyResolve;
+
 		/// <summary>
 		/// <summary>
-		/// Loads a list of types from a directory containing assemblies, that derive from a base type.
+		///     Looks up assemblies in the given directory and locates all types that can be loaded and collects their metadata.
 		/// </summary>
 		/// </summary>
 		/// <typeparam name="T">The specific base type to search for.</typeparam>
 		/// <typeparam name="T">The specific base type to search for.</typeparam>
 		/// <param name="directory">The directory to search for assemblies.</param>
 		/// <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)
+		/// <param name="typeSelector">A function to check if a type should be selected and to build the type metadata.</param>
+		/// <param name="assemblyFilter">A filter function to quickly determine if the assembly can be loaded.</param>
+		/// <param name="cacheName">The name of the cache to get cached types from.</param>
+		/// <returns>A list of all loadable type metadatas indexed by the full path to the assembly that contains the types.</returns>
+		public static Dictionary<string, List<T>> FindPluginTypes<T>(string directory, Func<TypeDefinition, T> typeSelector, Func<AssemblyDefinition, bool> assemblyFilter = null, string cacheName = null) where T : ICacheable, new()
 		{
 		{
-			List<Type> types = new List<Type>();
-			Type pluginType = typeof(T);
+			var result = new Dictionary<string, List<T>>();
+			Dictionary<string, CachedAssembly<T>> cache = null;
+
+			if (cacheName != null)
+				cache = LoadAssemblyCache<T>(cacheName);
 
 
 			foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
 			foreach (string dll in Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories))
-			{
 				try
 				try
 				{
 				{
-					AssemblyName an = AssemblyName.GetAssemblyName(dll);
-					Assembly assembly = Assembly.Load(an);
+					if (cache != null && cache.TryGetValue(dll, out var cacheEntry))
+					{
+						long lastWrite = File.GetLastWriteTimeUtc(dll).Ticks;
+						if (lastWrite == cacheEntry.Timestamp)
+						{
+							result[dll] = cacheEntry.CacheItems;
+							continue;
+						}
+					}
+
+					var ass = AssemblyDefinition.ReadAssembly(dll, readerParameters);
+
+					if (!assemblyFilter?.Invoke(ass) ?? false)
+					{
+						ass.Dispose();
+						continue;
+					}
+
+					var matches = ass.MainModule.Types.Select(typeSelector).Where(t => t != null).ToList();
 
 
-					foreach (Type type in assembly.GetTypes())
+					if (matches.Count == 0)
 					{
 					{
-						if (!type.IsInterface && !type.IsAbstract && pluginType.IsAssignableFrom(type))
-							types.Add(type);
+						ass.Dispose();
+						continue;
 					}
 					}
+
+					result[dll] = matches;
+					ass.Dispose();
+				}
+				catch (Exception e)
+				{
+					Logger.LogError(e.ToString());
 				}
 				}
-				catch (BadImageFormatException) { } //unmanaged DLL
-				catch (ReflectionTypeLoadException ex)
+
+			if (cacheName != null)
+				SaveAssemblyCache(cacheName, result);
+
+			return result;
+		}
+
+		/// <summary>
+		///     Loads an index of type metadatas from a cache.
+		/// </summary>
+		/// <param name="cacheName">Name of the cache</param>
+		/// <typeparam name="T">Cacheable item</typeparam>
+		/// <returns>Cached type metadatas indexed by the path of the assembly that defines the type. If no cache is defined, return null.</returns>
+		public static Dictionary<string, CachedAssembly<T>> LoadAssemblyCache<T>(string cacheName) where T : ICacheable, new()
+		{
+			if (!EnableAssemblyCache.Value)
+				return null;
+
+			var result = new Dictionary<string, CachedAssembly<T>>();
+			try
+			{
+				string path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
+				if (!File.Exists(path))
+					return null;
+
+				using (var br = new BinaryReader(File.OpenRead(path)))
 				{
 				{
-					Logger.LogError($"Could not load \"{Path.GetFileName(dll)}\" as a plugin!");
-					Logger.LogDebug(TypeLoadExceptionToString(ex));
+					int entriesCount = br.ReadInt32();
+
+					for (var i = 0; i < entriesCount; i++)
+					{
+						string entryIdentifier = br.ReadString();
+						long entryDate = br.ReadInt64();
+						int itemsCount = br.ReadInt32();
+						var items = new List<T>();
+
+						for (var j = 0; j < itemsCount; j++)
+						{
+							var entry = new T();
+							entry.Load(br);
+							items.Add(entry);
+						}
+
+						result[entryIdentifier] = new CachedAssembly<T> { Timestamp = entryDate, CacheItems = items };
+					}
 				}
 				}
 			}
 			}
+			catch (Exception e)
+			{
+				Logger.LogWarning($"Failed to load cache \"{cacheName}\"; skipping loading cache. Reason: {e.Message}.");
+			}
 
 
-			return types;
+			return result;
 		}
 		}
 
 
-		private static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
+		/// <summary>
+		///     Saves indexed type metadata into a cache.
+		/// </summary>
+		/// <param name="cacheName">Name of the cache</param>
+		/// <param name="entries">List of plugin metadatas indexed by the path to the assembly that contains the types</param>
+		/// <typeparam name="T">Cacheable item</typeparam>
+		public static void SaveAssemblyCache<T>(string cacheName, Dictionary<string, List<T>> entries) where T : ICacheable
 		{
 		{
-			StringBuilder sb = new StringBuilder();
-			foreach (Exception exSub in ex.LoaderExceptions)
+			if (!EnableAssemblyCache.Value)
+				return;
+
+			try
+			{
+				if (!Directory.Exists(Paths.CachePath))
+					Directory.CreateDirectory(Paths.CachePath);
+
+				string path = Path.Combine(Paths.CachePath, $"{cacheName}_typeloader.dat");
+
+				using (var bw = new BinaryWriter(File.OpenWrite(path)))
+				{
+					bw.Write(entries.Count);
+
+					foreach (var kv in entries)
+					{
+						bw.Write(kv.Key);
+						bw.Write(File.GetLastWriteTimeUtc(kv.Key).Ticks);
+						bw.Write(kv.Value.Count);
+
+						foreach (var item in kv.Value)
+							item.Save(bw);
+					}
+				}
+			}
+			catch (Exception e)
+			{
+				Logger.LogWarning($"Failed to save cache \"{cacheName}\"; skipping saving cache. Reason: {e.Message}.");
+			}
+		}
+
+		/// <summary>
+		///     Converts TypeLoadException to a readable string.
+		/// </summary>
+		/// <param name="ex">TypeLoadException</param>
+		/// <returns>Readable representation of the exception</returns>
+		public static string TypeLoadExceptionToString(ReflectionTypeLoadException ex)
+		{
+			var sb = new StringBuilder();
+			foreach (var exSub in ex.LoaderExceptions)
 			{
 			{
 				sb.AppendLine(exSub.Message);
 				sb.AppendLine(exSub.Message);
 				if (exSub is FileNotFoundException exFileNotFound)
 				if (exSub is FileNotFoundException exFileNotFound)
@@ -75,5 +254,15 @@ namespace BepInEx.Bootstrap
 
 
 			return sb.ToString();
 			return sb.ToString();
 		}
 		}
+
+		#region Config
+
+		private static ConfigWrapper<bool> EnableAssemblyCache = ConfigFile.CoreConfig.Wrap(
+			"Caching", 
+			"EnableAssemblyCache", 
+			"Enable/disable assembly metadata cache\nEnabling this will speed up discovery of plugins and patchers by caching the metadata of all types BepInEx discovers.", 
+			true);
+
+		#endregion
 	}
 	}
 }
 }

+ 1 - 1
BepInEx/Configuration/ConfigFile.cs

@@ -54,7 +54,7 @@ namespace BepInEx.Configuration
 		/// <param name="owner">The plugin that owns this setting.</param>
 		/// <param name="owner">The plugin that owns this setting.</param>
 		public ConfigFile(string configPath, bool saveOnInit, BaseUnityPlugin owner = null)
 		public ConfigFile(string configPath, bool saveOnInit, BaseUnityPlugin owner = null)
 		{
 		{
-			_ownerMetadata = owner?.Metadata;
+			_ownerMetadata = owner?.Info.Metadata;
 
 
 			if (configPath == null) throw new ArgumentNullException(nameof(configPath));
 			if (configPath == null) throw new ArgumentNullException(nameof(configPath));
 			configPath = Path.GetFullPath(configPath);
 			configPath = Path.GetFullPath(configPath);

+ 1 - 1
BepInEx/Configuration/TomlTypeConverter.cs

@@ -150,4 +150,4 @@ namespace BepInEx.Configuration
 			return TypeConverters.Keys;
 			return TypeConverters.Keys;
 		}
 		}
 	}
 	}
-}
+}

+ 1 - 1
BepInEx/ConsoleUtil/ConsoleWindow.cs

@@ -25,7 +25,7 @@ namespace UnityInjector.ConsoleUtil
 			"If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.",
 			"If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding.",
 			false);
 			false);
 
 
-        public static bool IsAttached { get; private set; }
+		public static bool IsAttached { get; private set; }
 		private static IntPtr _cOut;
 		private static IntPtr _cOut;
 		private static IntPtr _oOut;
 		private static IntPtr _oOut;
 
 

+ 44 - 3
BepInEx/Contract/Attributes.cs

@@ -1,6 +1,10 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Reflection;
+using BepInEx.Logging;
+using Mono.Cecil;
+using Mono.Collections.Generic;
 
 
 namespace BepInEx
 namespace BepInEx
 {
 {
@@ -46,6 +50,16 @@ namespace BepInEx
 				Version = null;
 				Version = null;
 			}
 			}
 		}
 		}
+
+		internal static BepInPlugin FromCecilType(TypeDefinition td)
+		{
+			var attr = MetadataHelper.GetCustomAttributes<BepInPlugin>(td, false).FirstOrDefault();
+
+			if (attr == null)
+				return null;
+
+			return new BepInPlugin((string)attr.ConstructorArguments[0].Value, (string)attr.ConstructorArguments[1].Value, (string)attr.ConstructorArguments[2].Value);
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -84,6 +98,12 @@ namespace BepInEx
 			this.DependencyGUID = DependencyGUID;
 			this.DependencyGUID = DependencyGUID;
 			this.Flags = Flags;
 			this.Flags = Flags;
 		}
 		}
+
+		internal static IEnumerable<BepInDependency> FromCecilType(TypeDefinition td)
+		{
+			var attrs = MetadataHelper.GetCustomAttributes<BepInDependency>(td, true);
+			return attrs.Select(customAttribute => new BepInDependency((string)customAttribute.ConstructorArguments[0].Value, (DependencyFlags)customAttribute.ConstructorArguments[1].Value)).ToList();
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -102,6 +122,12 @@ namespace BepInEx
 		{
 		{
 			this.ProcessName = ProcessName;
 			this.ProcessName = ProcessName;
 		}
 		}
+
+		internal static List<BepInProcess> FromCecilType(TypeDefinition td)
+		{
+			var attrs = MetadataHelper.GetCustomAttributes<BepInProcess>(td, true);
+			return attrs.Select(customAttribute => new BepInProcess((string)customAttribute.ConstructorArguments[0].Value)).ToList();
+		}
 	}
 	}
 
 
 	#endregion
 	#endregion
@@ -113,6 +139,22 @@ namespace BepInEx
 	/// </summary>
 	/// </summary>
 	public static class MetadataHelper
 	public static class MetadataHelper
 	{
 	{
+		internal static IEnumerable<CustomAttribute> GetCustomAttributes<T>(TypeDefinition td, bool inherit) where T : Attribute
+		{
+			var result = new List<CustomAttribute>();
+			var type = typeof(T);
+			var currentType = td;
+
+			do
+			{
+				result.AddRange(currentType.CustomAttributes.Where(ca => ca.AttributeType.FullName == type.FullName));
+				currentType = currentType.BaseType?.Resolve();
+			} while (inherit && currentType?.FullName != "System.Object");
+
+
+			return result;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Retrieves the BepInPlugin metadata from a plugin type.
 		/// Retrieves the BepInPlugin metadata from a plugin type.
 		/// </summary>
 		/// </summary>
@@ -159,10 +201,9 @@ namespace BepInEx
 		/// <summary>
 		/// <summary>
 		/// Retrieves the dependencies of the specified plugin type.
 		/// Retrieves the dependencies of the specified plugin type.
 		/// </summary>
 		/// </summary>
-		/// <param name="plugin">The plugin type.</param>
-		/// <param name="allPlugins">All currently loaded plugin types.</param>
+		/// <param name="Plugin">The plugin type.</param>
 		/// <returns>A list of all plugin types that the specified plugin type depends upon.</returns>
 		/// <returns>A list of all plugin types that the specified plugin type depends upon.</returns>
-		public static IEnumerable<BepInDependency> GetDependencies(Type plugin, IEnumerable<Type> allPlugins)
+		public static IEnumerable<BepInDependency> GetDependencies(Type plugin)
 		{
 		{
 			return plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast<BepInDependency>();
 			return plugin.GetCustomAttributes(typeof(BepInDependency), true).Cast<BepInDependency>();
 		}
 		}

+ 21 - 6
BepInEx/Contract/BaseUnityPlugin.cs

@@ -1,4 +1,6 @@
-using BepInEx.Configuration;
+using BepInEx.Bootstrap;
+using BepInEx.Configuration;
+using BepInEx.Contract;
 using BepInEx.Logging;
 using BepInEx.Logging;
 using UnityEngine;
 using UnityEngine;
 
 
@@ -12,8 +14,7 @@ namespace BepInEx
 		/// <summary>
 		/// <summary>
 		/// Information about this plugin as it was loaded.
 		/// Information about this plugin as it was loaded.
 		/// </summary>
 		/// </summary>
-		public BepInPlugin Metadata { get; }
-
+		public PluginInfo Info { get; }
 		/// <summary>
 		/// <summary>
 		/// Logger instance tied to this plugin.
 		/// Logger instance tied to this plugin.
 		/// </summary>
 		/// </summary>
@@ -30,11 +31,25 @@ namespace BepInEx
 		/// </summary>
 		/// </summary>
 		protected BaseUnityPlugin()
 		protected BaseUnityPlugin()
 		{
 		{
-			Metadata = MetadataHelper.GetMetadata(this);
+			var metadata = MetadataHelper.GetMetadata(this);
+
+			if (Chainloader.PluginInfos.TryGetValue(metadata.GUID, out var info))
+				Info = info;
+			else
+			{
+				Info = new PluginInfo
+				{
+					Metadata = metadata,
+					Instance = this,
+					Dependencies = MetadataHelper.GetDependencies(GetType()),
+					Processes = MetadataHelper.GetAttributes<BepInProcess>(GetType()),
+					Location = GetType().Assembly.Location
+				};
+			}
 
 
-			Logger = Logging.Logger.CreateLogSource(Metadata.Name);
+			Logger = Logging.Logger.CreateLogSource(metadata.Name);
 
 
-			Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, Metadata.GUID + ".cfg"), false, this);
+			Config = new ConfigFile(Utility.CombinePaths(Paths.ConfigPath, metadata.GUID + ".cfg"), false, this);
 		}
 		}
 	}
 	}
 }
 }

+ 63 - 0
BepInEx/Contract/PluginInfo.cs

@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BepInEx.Bootstrap;
+
+namespace BepInEx.Contract
+{
+	public class PluginInfo : ICacheable
+	{
+		public BepInPlugin Metadata { get; internal set; }
+
+		public IEnumerable<BepInProcess> Processes { get; internal set; }
+
+		public IEnumerable<BepInDependency> Dependencies { get; internal set; }
+
+		public string Location { get; internal set; }
+
+		public BaseUnityPlugin Instance { get; internal set; }
+
+		internal string TypeName { get; set; }
+
+		public void Save(BinaryWriter bw)
+		{
+			bw.Write(TypeName);
+
+			bw.Write(Metadata.GUID);
+			bw.Write(Metadata.Name);
+			bw.Write(Metadata.Version.ToString());
+
+			var processList = Processes.ToList();
+			bw.Write(processList.Count);
+			foreach (var bepInProcess in processList)
+				bw.Write(bepInProcess.ProcessName);
+
+			var depList = Dependencies.ToList();
+			bw.Write(depList.Count);
+			foreach (var bepInDependency in depList)
+			{
+				bw.Write(bepInDependency.DependencyGUID);
+				bw.Write((int)bepInDependency.Flags);
+			}
+		}
+
+		public void Load(BinaryReader br)
+		{
+			TypeName = br.ReadString();
+
+			Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString());
+
+			var processListCount = br.ReadInt32();
+			var processList = new List<BepInProcess>(processListCount);
+			for (int i = 0; i < processListCount; i++)
+				processList.Add(new BepInProcess(br.ReadString()));
+			Processes = processList;
+
+			var depCount = br.ReadInt32();
+			var depList = new List<BepInDependency>(depCount);
+			for (int i = 0; i < depCount; i++)
+				depList.Add(new BepInDependency(br.ReadString(), (BepInDependency.DependencyFlags) br.ReadInt32()));
+			Dependencies = depList;
+		}
+	}
+}

+ 1 - 1
BepInEx/Logging/ConsoleLogListener.cs

@@ -16,7 +16,7 @@ namespace BepInEx.Logging
 			if (eventArgs.Level.GetHighestLevel() > DisplayedLogLevel)
 			if (eventArgs.Level.GetHighestLevel() > DisplayedLogLevel)
 				return;
 				return;
 
 
-			string log = $"[{eventArgs.Level, -7}:{((ILogSource)sender).SourceName, 10}] {eventArgs.Data}\r\n";
+			string log = $"[{eventArgs.Level,-7}:{((ILogSource)sender).SourceName,10}] {eventArgs.Data}\r\n";
 
 
 			Kon.ForegroundColor = eventArgs.Level.GetConsoleColor();
 			Kon.ForegroundColor = eventArgs.Level.GetConsoleColor();
 			Console.Write(log);
 			Console.Write(log);

+ 2 - 5
BepInEx/Logging/DiskLogListener.cs

@@ -39,12 +39,9 @@ namespace BepInEx.Logging
 			}
 			}
 
 
 			LogWriter = TextWriter.Synchronized(new StreamWriter(fileStream, Encoding.UTF8));
 			LogWriter = TextWriter.Synchronized(new StreamWriter(fileStream, Encoding.UTF8));
-			
 
 
-			FlushTimer = new Timer(o =>
-			{
-				LogWriter?.Flush();
-			}, null, 2000, 2000);
+
+			FlushTimer = new Timer(o => { LogWriter?.Flush(); }, null, 2000, 2000);
 		}
 		}
 
 
 		public void LogEvent(object sender, LogEventArgs eventArgs)
 		public void LogEvent(object sender, LogEventArgs eventArgs)

+ 1 - 1
BepInEx/Logging/TraceLogSource.cs

@@ -76,7 +76,7 @@ namespace BepInEx.Logging
 					level = LogLevel.Debug;
 					level = LogLevel.Debug;
 					break;
 					break;
 			}
 			}
-			
+
 			LogSource.Log(level, $"{message}".Trim());
 			LogSource.Log(level, $"{message}".Trim());
 		}
 		}
 	}
 	}

+ 1 - 1
BepInEx/Logging/UnityLogListener.cs

@@ -27,7 +27,7 @@ namespace BepInEx.Logging
 				WriteStringToUnityLog = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
 				WriteStringToUnityLog = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
 				break;
 				break;
 			}
 			}
-			
+
 			if (WriteStringToUnityLog == null)
 			if (WriteStringToUnityLog == null)
 				Logger.LogError("Unable to start Unity log writer");
 				Logger.LogError("Unable to start Unity log writer");
 		}
 		}

+ 1 - 0
BepInEx/Logging/UnityLogSource.cs

@@ -24,6 +24,7 @@ namespace BepInEx.Logging
 		}
 		}
 
 
 		private bool disposed = false;
 		private bool disposed = false;
+
 		public void Dispose()
 		public void Dispose()
 		{
 		{
 			if (!disposed)
 			if (!disposed)

+ 13 - 0
BepInEx/Paths.cs

@@ -21,6 +21,14 @@ namespace BepInEx
 			PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers");
 			PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers");
 			BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core");
 			BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core");
 			BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.dll");
 			BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.dll");
+			CachePath = Path.Combine(BepInExRootPath, "cache");
+		}
+
+		internal static void SetManagedPath(string managedPath)
+		{
+			if (managedPath == null)
+				return;
+			ManagedPath = managedPath;
 		}
 		}
 
 
 		internal static void SetPluginPath(string pluginPath)
 		internal static void SetPluginPath(string pluginPath)
@@ -69,6 +77,11 @@ namespace BepInEx
 		public static string BepInExConfigPath { get; private set; }
 		public static string BepInExConfigPath { get; private set; }
 
 
 		/// <summary>
 		/// <summary>
+        ///		The path to temporary cache files.
+        /// </summary>
+		public static string CachePath { get; private set; }
+
+		/// <summary>
 		///     The path to the patcher plugin folder which resides in the BepInEx folder.
 		///     The path to the patcher plugin folder which resides in the BepInEx folder.
 		/// </summary>
 		/// </summary>
 		public static string PatcherPluginPath { get; private set; }
 		public static string PatcherPluginPath { get; private set; }

+ 92 - 12
BepInEx/Utility.cs

@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
+using System.Reflection.Emit;
+using Mono.Cecil;
 
 
 namespace BepInEx
 namespace BepInEx
 {
 {
@@ -12,11 +14,50 @@ namespace BepInEx
 	public static class Utility
 	public static class Utility
 	{
 	{
 		/// <summary>
 		/// <summary>
-		/// Combines multiple paths together, as the specific method is not available in .NET 3.5.
+		/// Whether current Common Language Runtime supports dynamic method generation using <see cref="System.Reflection.Emit"/> namespace.
 		/// </summary>
 		/// </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);
+		public static bool CLRSupportsDynamicAssemblies { get; }
+
+		static Utility()
+		{
+			try
+			{
+				var m = new DynamicMethod("SRE_Test", null, null);
+				CLRSupportsDynamicAssemblies = true;
+			}
+			catch (PlatformNotSupportedException)
+			{
+				CLRSupportsDynamicAssemblies = false;
+			}
+		}
+
+		/// <summary>
+		/// Try to perform an action.
+		/// </summary>
+		/// <param name="action">Action to perform.</param>
+		/// <param name="exception">Possible exception that gets returned.</param>
+		/// <returns>True, if action succeeded, false if an exception occured.</returns>
+		public static bool TryDo(Action action, out Exception exception)
+		{
+			exception = null;
+			try
+			{
+				action();
+				return true;
+			}
+			catch (Exception e)
+			{
+				exception = e;
+				return false;
+			}
+		}
+
+        /// <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>
 		/// <summary>
 		/// Tries to parse a bool, with a default value if unable to parse.
 		/// Tries to parse a bool, with a default value if unable to parse.
@@ -26,7 +67,7 @@ namespace BepInEx
 		/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
 		/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
 		public static bool SafeParseBool(string input, bool defaultValue = false)
 		public static bool SafeParseBool(string input, bool defaultValue = false)
 		{
 		{
-			return bool.TryParse(input, out bool result) ? result : defaultValue;
+			return Boolean.TryParse(input, out bool result) ? result : defaultValue;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -46,7 +87,7 @@ namespace BepInEx
 		/// <returns>True if the value parameter is null or empty, or if value consists exclusively of white-space characters.</returns>
 		/// <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)
 		public static bool IsNullOrWhiteSpace(this string self)
 		{
 		{
-			return self == null || self.All(char.IsWhiteSpace);
+			return self == null || self.All(Char.IsWhiteSpace);
 		}
 		}
 
 
 		public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
 		public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
@@ -61,9 +102,8 @@ namespace BepInEx
 				Stack<TNode> currentStack = new Stack<TNode>();
 				Stack<TNode> currentStack = new Stack<TNode>();
 				if (!Visit(input, currentStack))
 				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
+					throw new Exception("Cyclic Dependency:\r\n" + currentStack.Select(x => $" - {x}") //append dashes
+																			   .Aggregate((a, b) => $"{a}\r\n{b}")); //add new lines inbetween
 				}
 				}
 			}
 			}
 
 
@@ -107,7 +147,7 @@ namespace BepInEx
 		/// <param name="directory">Directory to search the assembly from.</param>
 		/// <param name="directory">Directory to search the assembly from.</param>
 		/// <param name="assembly">The loaded assembly.</param>
 		/// <param name="assembly">The loaded assembly.</param>
 		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
 		/// <returns>True, if the assembly was found and loaded. Otherwise, false.</returns>
-		public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly)
+		private static bool TryResolveDllAssembly<T>(AssemblyName assemblyName, string directory, Func<string, T> loader, out T assembly) where T : class
 		{
 		{
 			assembly = null;
 			assembly = null;
 
 
@@ -124,7 +164,7 @@ namespace BepInEx
 
 
 				try
 				try
 				{
 				{
-					assembly = Assembly.LoadFile(path);
+					assembly = loader(path);
 				}
 				}
 				catch (Exception)
 				catch (Exception)
 				{
 				{
@@ -137,7 +177,47 @@ namespace BepInEx
 			return false;
 			return false;
 		}
 		}
 
 
-		public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.None)
+		public static bool IsSubtypeOf(this TypeDefinition self, Type td)
+		{
+			if (self.FullName == td.FullName)
+				return true;
+			return self.FullName != "System.Object" && (self.BaseType?.Resolve()?.IsSubtypeOf(td) ?? false);
+		}
+
+		/// <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)
+		{
+			return TryResolveDllAssembly(assemblyName, directory, Assembly.LoadFile, out assembly);
+		}
+
+		/// <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, ReaderParameters readerParameters, out AssemblyDefinition assembly)
+		{
+			return TryResolveDllAssembly(assemblyName, directory, s => AssemblyDefinition.ReadAssembly(s, readerParameters), out assembly);
+		}
+
+		/// <summary>
+		/// Tries to create a file with the given name
+		/// </summary>
+		/// <param name="path">Path of the file to create</param>
+		/// <param name="mode">File open mode</param>
+		/// <param name="fileStream">Resulting filestream</param>
+		/// <param name="access">File access options</param>
+		/// <param name="share">File share options</param>
+		/// <returns></returns>
+		public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.Read)
 		{
 		{
 			try
 			try
 			{
 			{

+ 1 - 0
BepInEx/packages.config

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

+ 2 - 2
README.md

@@ -21,9 +21,9 @@ Unity plugin framework
 **[User and developer guides](https://github.com/BepInEx/BepInEx/wiki)**
 **[User and developer guides](https://github.com/BepInEx/BepInEx/wiki)**
 
 
 ## Used libraries
 ## Used libraries
-- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - 2.7.1.0 ([df7d636](https://github.com/NeighTools/UnityDoorstop/commit/df7d6366d8dc69f024c61cd31e6f690eb44ce57a))
+- [NeighTools/UnityDoorstop](https://github.com/NeighTools/UnityDoorstop) - 2.11.0.0 ([68a4e2d](https://github.com/NeighTools/UnityDoorstop/commit/68a4e2db1f09e5f5cc2f479d293b09764d09c80b))
 - [pardeike/Harmony](https://github.com/pardeike/Harmony) - pre-2.0 ([443f551](https://github.com/pardeike/Harmony/commit/443f551ec45ecf409755b5979a4466343197de03))
 - [pardeike/Harmony](https://github.com/pardeike/Harmony) - pre-2.0 ([443f551](https://github.com/pardeike/Harmony/commit/443f551ec45ecf409755b5979a4466343197de03))
-- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v19.05.01.01 ([934a8ae](https://github.com/0x0ade/MonoMod/commit/934a8ae921affac0093757d23c6f3ead34e996ac))
+- [0x0ade/MonoMod](https://github.com/0x0ade/MonoMod) - v19.08.02.03 ([a70072c](https://github.com/MonoMod/MonoMod/tree/a70072cdf759ac0cfa80991fcd2cca67d3eec130))
 - [jbevain/cecil](https://github.com/jbevain/cecil) - 0.10.3 ([fb289a7](https://github.com/jbevain/cecil/commit/fb289a7cd80ceb6af5c86e7c7ecce9bf1e98b8fe))
 - [jbevain/cecil](https://github.com/jbevain/cecil) - 0.10.3 ([fb289a7](https://github.com/jbevain/cecil/commit/fb289a7cd80ceb6af5c86e7c7ecce9bf1e98b8fe))
 
 
 ## Credits
 ## Credits

+ 8 - 8
scripts/jenkins_master.groovy

@@ -46,12 +46,12 @@ Changes since ${latestTag}:
                 }
                 }
 
 
                 dir('Doorstop') {
                 dir('Doorstop') {
-                    sh '''  tag="v2.7.1.0";
-                    version="2.7.1.0";
+                    sh '''  tag="v2.11.0.0";
+                    version="2.11.0.0";
                     wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x64_$version.zip;
                     wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x64_$version.zip;
                     wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x86_$version.zip;
                     wget https://github.com/NeighTools/UnityDoorstop/releases/download/$tag/Doorstop_x86_$version.zip;
-                    unzip -o Doorstop_x86_$version.zip winhttp.dll -d x86;
-                    unzip -o Doorstop_x64_$version.zip winhttp.dll -d x64;'''
+                    unzip -o Doorstop_x86_$version.zip version.dll -d x86;
+                    unzip -o Doorstop_x64_$version.zip version.dll -d x64;'''
                 }
                 }
             }
             }
         }
         }
@@ -120,7 +120,7 @@ Changes since ${latestTag}:
                     sh 'rm -rf BepInEx/core/patcher'
                     sh 'rm -rf BepInEx/core/patcher'
 
 
                     sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
                     sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
-                    sh 'cp -f ../../../Doorstop/x86/winhttp.dll winhttp.dll'
+                    sh 'cp -f ../../../Doorstop/x86/version.dll version.dll'
                     
                     
                     script {
                     script {
                         if(params.IS_BE) {
                         if(params.IS_BE) {
@@ -134,7 +134,7 @@ Changes since ${latestTag}:
 
 
                     sh "zip -r9 BepInEx_Legacy_x86${commitPrefix}${versionNumber}.zip ./*"
                     sh "zip -r9 BepInEx_Legacy_x86${commitPrefix}${versionNumber}.zip ./*"
                     
                     
-                    sh 'cp -f ../../../Doorstop/x64/winhttp.dll winhttp.dll'
+                    sh 'cp -f ../../../Doorstop/x64/version.dll version.dll'
                     
                     
                     sh 'unix2dos doorstop_config.ini'
                     sh 'unix2dos doorstop_config.ini'
                     
                     
@@ -167,7 +167,7 @@ Changes since ${latestTag}:
                     sh 'rm -f BepInEx/core/UnityEngine.CoreModule.dll'
                     sh 'rm -f BepInEx/core/UnityEngine.CoreModule.dll'
 
 
                     sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
                     sh 'cp -f ../../../BepInEx/doorstop/doorstop_config.ini doorstop_config.ini'
-                    sh 'cp -f ../../../Doorstop/x86/winhttp.dll winhttp.dll'
+                    sh 'cp -f ../../../Doorstop/x86/version.dll version.dll'
                     
                     
                     script {
                     script {
                         if(params.IS_BE) {
                         if(params.IS_BE) {
@@ -181,7 +181,7 @@ Changes since ${latestTag}:
 
 
                     sh "zip -r9 BepInEx_v2018_x86${commitPrefix}${versionNumber}.zip ./*"
                     sh "zip -r9 BepInEx_v2018_x86${commitPrefix}${versionNumber}.zip ./*"
                     
                     
-                    sh 'cp -f ../../../Doorstop/x64/winhttp.dll winhttp.dll'
+                    sh 'cp -f ../../../Doorstop/x64/version.dll version.dll'
                     
                     
                     sh 'unix2dos doorstop_config.ini'
                     sh 'unix2dos doorstop_config.ini'
                     
                     

+ 1 - 1
submodules/MonoMod

@@ -1 +1 @@
-Subproject commit 934a8ae921affac0093757d23c6f3ead34e996ac
+Subproject commit a70072cdf759ac0cfa80991fcd2cca67d3eec130