Browse Source

Merge pull request #40 from BepInEx/feature-custom-entrypoint

Feature: Custom entrypoint
Bepis 5 years ago
parent
commit
416f367a6d

+ 11 - 0
BepInEx.Common/Utility.cs

@@ -29,6 +29,17 @@ namespace BepInEx.Common
         /// <returns>A combined path.</returns>
         public static string CombinePaths(params string[] parts) => parts.Aggregate(Path.Combine);
 
+		/// <summary>
+		/// Tries to parse a bool, with a default value if unable to parse.
+		/// </summary>
+		/// <param name="input">The string to parse</param>
+		/// <param name="defaultValue">The value to return if parsing is unsuccessful.</param>
+		/// <returns>Boolean value of input if able to be parsed, otherwise default value.</returns>
+	    public static bool SafeParseBool(string input, bool defaultValue = false)
+	    {
+		    return bool.TryParse(input, out bool result) ? result : defaultValue;
+	    }
+
         /// <summary>
         /// Converts a file path into a UnityEngine.WWW format.
         /// </summary>

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

@@ -66,6 +66,7 @@
   <ItemGroup>
     <EmbeddedResource Include="..\bin\0Harmony.dll" />
     <EmbeddedResource Include="..\bin\BepInEx.dll" />
+    <EmbeddedResource Include="..\bin\Mono.Cecil.dll" />
   </ItemGroup>
   <Import Project="..\BepInEx.Common\BepInEx.Common.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

+ 0 - 2
BepInEx/BepInEx.csproj

@@ -46,10 +46,8 @@
       <HintPath>..\lib\Cecil 10\Mono.Cecil.dll</HintPath>
     </Reference>
     <Reference Include="System" />
-    <Reference Include="System.Xml" />
     <Reference Include="UnityEngine">
       <HintPath>..\lib\UnityEngine.dll</HintPath>
-      <Private>False</Private>
     </Reference>
   </ItemGroup>
   <ItemGroup>

+ 1 - 1
BepInEx/Bootstrap/AssemblyPatcher.cs

@@ -22,7 +22,7 @@ namespace BepInEx.Bootstrap
 		/// <summary>
 		/// Configuration value of whether assembly dumping is enabled or not.
 		/// </summary>
-        private static bool DumpingEnabled => bool.TryParse(Config.GetEntry("preloader-dumpassemblies", "false"), out bool result) ? result : false;
+        private static bool DumpingEnabled => Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
 
         /// <summary>
         /// Patches and loads an entire directory of assemblies.

+ 30 - 27
BepInEx/Bootstrap/Chainloader.cs

@@ -38,50 +38,53 @@ namespace BepInEx.Bootstrap
 			if (_loaded)
 				return;
 
-		    if (!Directory.Exists(Utility.PluginsDirectory))
-		        Directory.CreateDirectory(Utility.PluginsDirectory);
+			if (!Directory.Exists(Utility.PluginsDirectory))
+				Directory.CreateDirectory(Utility.PluginsDirectory);
 
-            Preloader.AllocateConsole();
+			Preloader.AllocateConsole();
 
 			try
 			{
-                UnityLogWriter unityLogWriter = new UnityLogWriter();
+				UnityLogWriter unityLogWriter = new UnityLogWriter();
 
-			    if (Preloader.PreloaderLog != null)
-			        unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n");
+				if (Preloader.PreloaderLog != null)
+					unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n");
 
-                Logger.SetLogger(unityLogWriter);
+				Logger.SetLogger(unityLogWriter);
 
-                if(bool.Parse(Config.GetEntry("log_unity_messages", "false", "Global")))
-                    UnityLogWriter.ListenUnityLogs();
+				if (bool.Parse(Config.GetEntry("chainloader-log-unity-messages", "false", "BepInEx")))
+					UnityLogWriter.ListenUnityLogs();
+
+				var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static);
+				if (productNameProp != null)
+					ConsoleWindow.Title =
+						$"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}";
 
-			    string consoleTile = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Application.productName}";
-			    ConsoleWindow.Title = consoleTile;
-                
 				Logger.Log(LogLevel.Message, "Chainloader started");
 
 				UnityEngine.Object.DontDestroyOnLoad(ManagerObject);
 
 
-			    string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
+				string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower();
 
 				var pluginTypes = TypeLoader.LoadTypes<BaseUnityPlugin>(Utility.PluginsDirectory)
-				    .Where(plugin =>
-				    {
-                        //Perform a filter for currently running process
-				        var filters = MetadataHelper.GetAttributes<BepInProcess>(plugin);
+					.Where(plugin =>
+					{
+						//Perform a filter for currently running process
+						var filters = MetadataHelper.GetAttributes<BepInProcess>(plugin);
 
-				        if (!filters.Any())
-				            return true;
+						if (!filters.Any())
+							return true;
 
-				        return filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
-				    })
-				    .ToList();
+						return filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess);
+					})
+					.ToList();
 
-			    Logger.Log(LogLevel.Info, $"{pluginTypes.Count} plugins selected");
+				Logger.Log(LogLevel.Info, $"{pluginTypes.Count} plugins selected");
 
 				Dictionary<Type, IEnumerable<Type>> dependencyDict = new Dictionary<Type, IEnumerable<Type>>();
 
+
 				foreach (Type t in pluginTypes)
 				{
 					try
@@ -94,7 +97,7 @@ namespace BepInEx.Bootstrap
 					{
 						var metadata = MetadataHelper.GetMetadata(t);
 
-					    Logger.Log(LogLevel.Info, $"Cannot load [{metadata.Name}] due to missing dependencies.");
+						Logger.Log(LogLevel.Info, $"Cannot load [{metadata.Name}] due to missing dependencies.");
 					}
 				}
 
@@ -109,17 +112,17 @@ namespace BepInEx.Bootstrap
 						var plugin = (BaseUnityPlugin) ManagerObject.AddComponent(t);
 
 						Plugins.Add(plugin);
-					    Logger.Log(LogLevel.Info, $"Loaded [{metadata.Name} {metadata.Version}]");
+						Logger.Log(LogLevel.Info, $"Loaded [{metadata.Name} {metadata.Version}]");
 					}
 					catch (Exception ex)
 					{
-					    Logger.Log(LogLevel.Info, $"Error loading [{t.Name}] : {ex.Message}");
+						Logger.Log(LogLevel.Info, $"Error loading [{t.Name}] : {ex.Message}");
 					}
 				}
 			}
 			catch (Exception ex)
 			{
-				UnityInjector.ConsoleUtil.ConsoleWindow.Attach();
+				ConsoleWindow.Attach();
 
 				Console.WriteLine("Error occurred starting the game");
 				Console.WriteLine(ex.ToString());

+ 280 - 272
BepInEx/Bootstrap/Preloader.cs

@@ -5,6 +5,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using BepInEx.Common;
 using BepInEx.Logging;
 using Mono.Cecil;
 using Mono.Cecil.Cil;
@@ -13,279 +14,286 @@ using MethodAttributes = Mono.Cecil.MethodAttributes;
 
 namespace BepInEx.Bootstrap
 {
-    /// <summary>
-    ///     The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
-    /// </summary>
-    internal static class Preloader
-    {
-        /// <summary>
-        ///     The list of finalizers that were loaded from the patcher contract.
-        /// </summary>
-        public static List<Action> Finalizers { get; } = new List<Action>();
-
-        /// <summary>
-        ///     The list of initializers that were loaded from the patcher contract.
-        /// </summary>
-        public static List<Action> Initializers { get; } = new List<Action>();
-
-        /// <summary>
-        ///     The dictionary of currently loaded patchers. The key is the patcher delegate that will be used to patch, and the
-        ///     value is a list of filenames of assemblies that the patcher is targeting.
-        /// </summary>
-        public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> PatcherDictionary { get; } =
-            new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
-
-        /// <summary>
-        ///     The log writer that is specific to the preloader.
-        /// </summary>
-        public static PreloaderLogWriter PreloaderLog { get; private set; }
-
-        public static void Run()
-        {
-            try
-            {
-                AllocateConsole();
-
-                PreloaderLog = new PreloaderLogWriter(SafeGetConfigBool("preloader-logconsole", "false"));
-                PreloaderLog.Enabled = true;
-
-                string consoleTile =
-                        $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
-                ConsoleWindow.Title = consoleTile;
-
-                Logger.SetLogger(PreloaderLog);
-
-                PreloaderLog.WriteLine(consoleTile);
-                Logger.Log(LogLevel.Message, "Preloader started");
-
-                AddPatcher(new[] {"UnityEngine.dll"}, PatchEntrypoint);
-
-                if (Directory.Exists(Paths.PatcherPluginPath))
-                {
-                    var sortedPatchers = new SortedDictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
-
-                    foreach (string assemblyPath in Directory.GetFiles(Paths.PatcherPluginPath, "*.dll"))
-                        try
-                        {
-                            var assembly = Assembly.LoadFrom(assemblyPath);
-
-                            foreach (KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>> kv in GetPatcherMethods(assembly))
-                                sortedPatchers.Add(assembly.GetName().Name, kv);
-                        }
-                        catch (BadImageFormatException) { } //unmanaged DLL
-                        catch (ReflectionTypeLoadException) { } //invalid references
-
-                    foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in sortedPatchers)
-                        AddPatcher(kv.Value.Value, kv.Value.Key);
-                }
-
-                AssemblyPatcher.PatchAll(Paths.ManagedPath, PatcherDictionary, Initializers, Finalizers);
-            }
-            catch (Exception ex)
-            {
-                Logger.Log(LogLevel.Fatal, "Could not run preloader!");
-                Logger.Log(LogLevel.Fatal, ex);
-
-                PreloaderLog.Enabled = false;
-
-                try
-                {
-                    AllocateConsole();
-                    Console.Write(PreloaderLog);
-                }
-                finally
-                {
-                    File.WriteAllText(Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
-                                      PreloaderLog.ToString());
-
-                    PreloaderLog.Dispose();
-                }
-            }
-        }
-
-        /// <summary>
-        ///     Scans the assembly for classes that use the patcher contract, and returns a dictionary of the patch methods.
-        /// </summary>
-        /// <param name="assembly">The assembly to scan.</param>
-        /// <returns>A dictionary of delegates which will be used to patch the targeted assemblies.</returns>
-        public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> GetPatcherMethods(Assembly assembly)
-        {
-            var patcherMethods = new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
-
-            foreach (var type in assembly.GetExportedTypes())
-                try
-                {
-                    if (type.IsInterface)
-                        continue;
-
-                    var targetsProperty = type.GetProperty("TargetDLLs",
-                                                           BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-                                                           null,
-                                                           typeof(IEnumerable<string>),
-                                                           Type.EmptyTypes,
-                                                           null);
-
-                    //first try get the ref patcher method
-                    var patcher = type.GetMethod("Patch",
-                                                 BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-                                                 null,
-                                                 CallingConventions.Any,
-                                                 new[] {typeof(AssemblyDefinition).MakeByRefType()},
-                                                 null);
-
-                    if (patcher == null) //otherwise try getting the non-ref patcher method
-                        patcher = type.GetMethod("Patch",
-                                                 BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-                                                 null,
-                                                 CallingConventions.Any,
-                                                 new[] {typeof(AssemblyDefinition)},
-                                                 null);
-
-                    if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
-                        continue;
-
-                    AssemblyPatcherDelegate patchDelegate = (ref AssemblyDefinition ass) =>
-                    {
-                        //we do the array fuckery here to get the ref result out
-                        object[] args = {ass};
-
-                        patcher.Invoke(null, args);
-
-                        ass = (AssemblyDefinition) args[0];
-                    };
-
-                    var targets = (IEnumerable<string>) targetsProperty.GetValue(null, null);
-
-                    patcherMethods[patchDelegate] = targets;
-
-                    var initMethod = type.GetMethod("Initialize",
-                                                    BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-                                                    null,
-                                                    CallingConventions.Any,
-                                                    Type.EmptyTypes,
-                                                    null);
-
-                    if (initMethod != null)
-                        Initializers.Add(() => initMethod.Invoke(null, null));
-
-                    var finalizeMethod = type.GetMethod("Finish",
-                                                        BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
-                                                        null,
-                                                        CallingConventions.Any,
-                                                        Type.EmptyTypes,
-                                                        null);
-
-                    if (finalizeMethod != null)
-                        Finalizers.Add(() => finalizeMethod.Invoke(null, null));
-                }
-                catch (Exception ex)
-                {
-                    Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
-                    Logger.Log(LogLevel.Warning, $"{ex}");
-                }
-
-            Logger.Log(LogLevel.Info,
-                       $"Loaded {patcherMethods.Select(x => x.Key).Distinct().Count()} patcher methods from {assembly.GetName().Name}");
-
-            return patcherMethods;
-        }
-
-        /// <summary>
-        ///     Inserts BepInEx's own chainloader entrypoint into UnityEngine.
-        /// </summary>
-        /// <param name="assembly">The assembly that will be attempted to be patched.</param>
-        public static void PatchEntrypoint(ref AssemblyDefinition assembly)
-        {
-            if (assembly.Name.Name == "UnityEngine")
-            {
+	/// <summary>
+	///     The main entrypoint of BepInEx, and initializes all patchers and the chainloader.
+	/// </summary>
+	internal static class Preloader
+	{
+		/// <summary>
+		///     The list of finalizers that were loaded from the patcher contract.
+		/// </summary>
+		public static List<Action> Finalizers { get; } = new List<Action>();
+
+		/// <summary>
+		///     The list of initializers that were loaded from the patcher contract.
+		/// </summary>
+		public static List<Action> Initializers { get; } = new List<Action>();
+
+		/// <summary>
+		///     The dictionary of currently loaded patchers. The key is the patcher delegate that will be used to patch, and the
+		///     value is a list of filenames of assemblies that the patcher is targeting.
+		/// </summary>
+		public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> PatcherDictionary { get; } =
+			new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
+
+		/// <summary>
+		///     The log writer that is specific to the preloader.
+		/// </summary>
+		public static PreloaderLogWriter PreloaderLog { get; private set; }
+
+		public static void Run()
+		{
+			try
+			{
+				AllocateConsole();
+
+				PreloaderLog =
+					new PreloaderLogWriter(Utility.SafeParseBool(Config.GetEntry("preloader-logconsole", "false", "BepInEx")));
+				PreloaderLog.Enabled = true;
+
+				string consoleTile =
+					$"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {Process.GetCurrentProcess().ProcessName}";
+				ConsoleWindow.Title = consoleTile;
+
+				Logger.SetLogger(PreloaderLog);
+
+				PreloaderLog.WriteLine(consoleTile);
+				Logger.Log(LogLevel.Message, "Preloader started");
+
+				string entrypointAssembly = Config.GetEntry("entrypoint-assembly", "UnityEngine.dll", "Preloader");
+
+				AddPatcher(new[] {entrypointAssembly}, PatchEntrypoint);
+
+				if (Directory.Exists(Paths.PatcherPluginPath))
+				{
+					var sortedPatchers = new SortedDictionary<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>>();
+
+					foreach (string assemblyPath in Directory.GetFiles(Paths.PatcherPluginPath, "*.dll"))
+						try
+						{
+							var assembly = Assembly.LoadFrom(assemblyPath);
+
+							foreach (KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>> kv in GetPatcherMethods(assembly))
+								sortedPatchers.Add(assembly.GetName().Name, kv);
+						}
+						catch (BadImageFormatException)
+						{
+						} //unmanaged DLL
+						catch (ReflectionTypeLoadException)
+						{
+						} //invalid references
+
+					foreach (KeyValuePair<string, KeyValuePair<AssemblyPatcherDelegate, IEnumerable<string>>> kv in sortedPatchers)
+						AddPatcher(kv.Value.Value, kv.Value.Key);
+				}
+
+				AssemblyPatcher.PatchAll(Paths.ManagedPath, PatcherDictionary, Initializers, Finalizers);
+			}
+			catch (Exception ex)
+			{
+				Logger.Log(LogLevel.Fatal, "Could not run preloader!");
+				Logger.Log(LogLevel.Fatal, ex);
+
+				PreloaderLog.Enabled = false;
+
+				try
+				{
+					AllocateConsole();
+					Console.Write(PreloaderLog);
+				}
+				finally
+				{
+					File.WriteAllText(Path.Combine(Paths.GameRootPath, $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log"),
+						PreloaderLog.ToString());
+
+					PreloaderLog.Dispose();
+				}
+			}
+		}
+
+		/// <summary>
+		///     Scans the assembly for classes that use the patcher contract, and returns a dictionary of the patch methods.
+		/// </summary>
+		/// <param name="assembly">The assembly to scan.</param>
+		/// <returns>A dictionary of delegates which will be used to patch the targeted assemblies.</returns>
+		public static Dictionary<AssemblyPatcherDelegate, IEnumerable<string>> GetPatcherMethods(Assembly assembly)
+		{
+			var patcherMethods = new Dictionary<AssemblyPatcherDelegate, IEnumerable<string>>();
+
+			foreach (var type in assembly.GetExportedTypes())
+				try
+				{
+					if (type.IsInterface)
+						continue;
+
+					var targetsProperty = type.GetProperty("TargetDLLs",
+						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
+						null,
+						typeof(IEnumerable<string>),
+						Type.EmptyTypes,
+						null);
+
+					//first try get the ref patcher method
+					var patcher = type.GetMethod("Patch",
+						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
+						null,
+						CallingConventions.Any,
+						new[] {typeof(AssemblyDefinition).MakeByRefType()},
+						null);
+
+					if (patcher == null) //otherwise try getting the non-ref patcher method
+						patcher = type.GetMethod("Patch",
+							BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
+							null,
+							CallingConventions.Any,
+							new[] {typeof(AssemblyDefinition)},
+							null);
+
+					if (targetsProperty == null || !targetsProperty.CanRead || patcher == null)
+						continue;
+
+					AssemblyPatcherDelegate patchDelegate = (ref AssemblyDefinition ass) =>
+					{
+						//we do the array fuckery here to get the ref result out
+						object[] args = {ass};
+
+						patcher.Invoke(null, args);
+
+						ass = (AssemblyDefinition) args[0];
+					};
+
+					var targets = (IEnumerable<string>) targetsProperty.GetValue(null, null);
+
+					patcherMethods[patchDelegate] = targets;
+
+					var initMethod = type.GetMethod("Initialize",
+						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
+						null,
+						CallingConventions.Any,
+						Type.EmptyTypes,
+						null);
+
+					if (initMethod != null)
+						Initializers.Add(() => initMethod.Invoke(null, null));
+
+					var finalizeMethod = type.GetMethod("Finish",
+						BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
+						null,
+						CallingConventions.Any,
+						Type.EmptyTypes,
+						null);
+
+					if (finalizeMethod != null)
+						Finalizers.Add(() => finalizeMethod.Invoke(null, null));
+				}
+				catch (Exception ex)
+				{
+					Logger.Log(LogLevel.Warning, $"Could not load patcher methods from {assembly.GetName().Name}");
+					Logger.Log(LogLevel.Warning, $"{ex}");
+				}
+
+			Logger.Log(LogLevel.Info,
+				$"Loaded {patcherMethods.Select(x => x.Key).Distinct().Count()} patcher methods from {assembly.GetName().Name}");
+
+			return patcherMethods;
+		}
+
+		/// <summary>
+		///     Inserts BepInEx's own chainloader entrypoint into UnityEngine.
+		/// </summary>
+		/// <param name="assembly">The assembly that will be attempted to be patched.</param>
+		public static void PatchEntrypoint(ref AssemblyDefinition assembly)
+		{
+			string entrypointType = Config.GetEntry("entrypoint-type", "Application", "Preloader");
+			string entrypointMethod = Config.HasEntry("entrypoint-method")
+				? Config.GetEntry("entrypoint-method", section: "Preloader")
+				: "";
+			bool isCctor = entrypointMethod.IsNullOrWhiteSpace() || entrypointMethod == ".cctor";
+
 #if CECIL_10
-                using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
+			using (var injected = AssemblyDefinition.ReadAssembly(Paths.BepInExAssemblyPath))
 #elif CECIL_9
-                AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(BepInExAssemblyPath);
+            AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(BepInExAssemblyPath);
 #endif
-                {
-                    var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
-                                                       .First(x => x.Name == "Initialize");
-
-                    var injectMethod = assembly.MainModule.ImportReference(originalInjectMethod);
-
-                    var sceneManager = assembly.MainModule.Types.First(x => x.Name == "Application");
-
-                    var voidType = assembly.MainModule.ImportReference(typeof(void));
-                    var cctor = new MethodDefinition(".cctor",
-                                                     MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
-                                                     | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
-                                                     voidType);
-
-                    var ilp = cctor.Body.GetILProcessor();
-                    ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
-                    ilp.Append(ilp.Create(OpCodes.Ret));
-
-                    sceneManager.Methods.Add(cctor);
-                }
-            }
-        }
-
-        /// <summary>
-        ///     Allocates a console window for use by BepInEx safely.
-        /// </summary>
-        public static void AllocateConsole()
-        {
-            bool console = SafeGetConfigBool("console", "false");
-            bool shiftjis = SafeGetConfigBool("console-shiftjis", "false");
-
-            if (console)
-                try
-                {
-                    ConsoleWindow.Attach();
-
-                    var encoding = (uint) Encoding.UTF8.CodePage;
-
-                    if (shiftjis)
-                        encoding = 932;
-
-                    ConsoleEncoding.ConsoleCodePage = encoding;
-                    Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
-                }
-                catch (Exception ex)
-                {
-                    Logger.Log(LogLevel.Error, "Failed to allocate console!");
-                    Logger.Log(LogLevel.Error, ex);
-                }
-        }
-
-        /// <summary>
-        ///     Adds the patcher to the patcher dictionary.
-        /// </summary>
-        /// <param name="dllNames">The list of DLL filenames to be patched.</param>
-        /// <param name="patcher">The method that will perform the patching.</param>
-        public static void AddPatcher(IEnumerable<string> dllNames, AssemblyPatcherDelegate patcher)
-        {
-            PatcherDictionary[patcher] = dllNames;
-        }
-
-
-        /// <summary>
-        ///     Safely retrieves a boolean value from the config. Returns false if not able to retrieve safely.
-        /// </summary>
-        /// <param name="key">The key to retrieve from the config.</param>
-        /// <param name="defaultValue">The default value to both return and set if the key does not exist in the config.</param>
-        /// <returns>
-        ///     The value of the key if found in the config, or the default value specified if not found, or false if it was
-        ///     unable to safely retrieve the value from the config.
-        /// </returns>
-        private static bool SafeGetConfigBool(string key, string defaultValue)
-        {
-            try
-            {
-                string result = Config.GetEntry(key, defaultValue);
-
-                return bool.Parse(result);
-            }
-            catch
-            {
-                return false;
-            }
-        }
-    }
+			{
+				var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader").Methods
+					.First(x => x.Name == "Initialize");
+
+				var injectMethod = assembly.MainModule.ImportReference(originalInjectMethod);
+
+
+				var entryType = assembly.MainModule.Types.First(x => x.Name == entrypointType);
+
+
+				if (isCctor)
+				{
+					MethodDefinition cctor = entryType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
+					ILProcessor il;
+
+					if (cctor == null)
+					{
+						cctor = new MethodDefinition(".cctor",
+							MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig
+							| MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+							assembly.MainModule.ImportReference(typeof(void)));
+
+						entryType.Methods.Add(cctor);
+						il = cctor.Body.GetILProcessor();
+						il.Append(il.Create(OpCodes.Ret));
+					}
+
+					Instruction ins = cctor.Body.Instructions.First();
+					il = cctor.Body.GetILProcessor();
+					il.InsertBefore(ins, il.Create(OpCodes.Call, injectMethod));
+				}
+				else
+				{
+					foreach (var loadScene in entryType.Methods.Where(x => x.Name == entrypointMethod))
+					{
+						var il = loadScene.Body.GetILProcessor();
+
+						il.InsertBefore(loadScene.Body.Instructions[0], il.Create(OpCodes.Call, injectMethod));
+					}
+				}
+			}
+		}
+
+		/// <summary>
+		///     Allocates a console window for use by BepInEx safely.
+		/// </summary>
+		public static void AllocateConsole()
+		{
+			bool console = Utility.SafeParseBool(Config.GetEntry("console", "false", "BepInEx"));
+			bool shiftjis = Utility.SafeParseBool(Config.GetEntry("console-shiftjis", "false", "BepInEx"));
+
+			if (console)
+				try
+				{
+					ConsoleWindow.Attach();
+
+					var encoding = (uint) Encoding.UTF8.CodePage;
+
+					if (shiftjis)
+						encoding = 932;
+
+					ConsoleEncoding.ConsoleCodePage = encoding;
+					Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding);
+				}
+				catch (Exception ex)
+				{
+					Logger.Log(LogLevel.Error, "Failed to allocate console!");
+					Logger.Log(LogLevel.Error, ex);
+				}
+		}
+
+		/// <summary>
+		///     Adds the patcher to the patcher dictionary.
+		/// </summary>
+		/// <param name="dllNames">The list of DLL filenames to be patched.</param>
+		/// <param name="patcher">The method that will perform the patching.</param>
+		public static void AddPatcher(IEnumerable<string> dllNames, AssemblyPatcherDelegate patcher)
+		{
+			PatcherDictionary[patcher] = dllNames;
+		}
+	}
 }

+ 50 - 54
BepInEx/Config.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Text.RegularExpressions;
 using BepInEx.Common;
+using BepInEx.Logging;
 
 namespace BepInEx
 {
@@ -11,17 +12,15 @@ namespace BepInEx
     /// </summary>
     public static class Config
     {
-        private static Dictionary<string, Dictionary<string, string>> cache = new Dictionary<string, Dictionary<string, string>>();
+        private static readonly Dictionary<string, Dictionary<string, string>> cache = new Dictionary<string, Dictionary<string, string>>();
 
         private static string configPath => Path.Combine(Common.Utility.PluginsDirectory, "config.ini");
 
-        private static Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
+        private static readonly Regex sanitizeKeyRegex = new Regex(@"[^a-zA-Z0-9\-\.]+");
 
         private static void RaiseConfigReloaded()
         {
-            var handler = ConfigReloaded;
-            if (handler != null)
-                handler.Invoke();
+            ConfigReloaded?.Invoke();
         }
 
 		/// <summary>
@@ -55,29 +54,29 @@ namespace BepInEx
 	    /// <returns>The value of the key.</returns>
 	    public static string GetEntry(string key, string defaultValue = "", string section = "")
         {
-            key = Sanitize(key);
-            if (section.IsNullOrWhiteSpace())
-                section = "Global";
-            else
-                section = Sanitize(section);
-
-            Dictionary<string, string> subdict;
-
-            if (!cache.TryGetValue(section, out subdict))
-            {
-                SetEntry(key, defaultValue, section);
-                return defaultValue;
-            }
-
-            if (subdict.TryGetValue(key, out string value))
-            {
-                return value;
-            }
-            else
-            {
-                SetEntry(key, defaultValue, section);
-                return defaultValue;
-            }
+	        try
+	        {
+				key = Sanitize(key);
+				section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
+
+				if (!cache.TryGetValue(section, out Dictionary<string, string> subdict))
+				{
+					SetEntry(key, defaultValue, section);
+					return defaultValue;
+				}
+
+				if (subdict.TryGetValue(key, out string value))
+					return value;
+
+				SetEntry(key, defaultValue, section);
+				return defaultValue;
+			}
+	        catch (Exception ex)
+	        {
+		        Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to read config entry!");
+		        Logger.Log(LogLevel.Error, ex);
+		        return defaultValue;
+	        }
         }
 
         /// <summary>
@@ -141,24 +140,27 @@ namespace BepInEx
         /// <param name="value">The value to set.</param>
         public static void SetEntry(string key, string value, string section = "")
         {
-            key = Sanitize(key);
-            if (section.IsNullOrWhiteSpace())
-                section = "Global";
-            else
-                section = Sanitize(section);
-
-            Dictionary<string, string> subdict;
-
-            if (!cache.TryGetValue(section, out subdict))
-            {
-                subdict = new Dictionary<string, string>();
-                cache[section] = subdict;
-            }
-
-            subdict[key] = value;
-
-            if (SaveOnConfigSet)
-                SaveConfig();
+	        try
+	        {
+		        key = Sanitize(key);
+		        section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
+
+		        if (!cache.TryGetValue(section, out Dictionary<string, string> subdict))
+		        {
+			        subdict = new Dictionary<string, string>();
+			        cache[section] = subdict;
+		        }
+
+		        subdict[key] = value;
+
+		        if (SaveOnConfigSet)
+			        SaveConfig();
+	        }
+	        catch (Exception ex)
+	        {
+		        Logger.Log(LogLevel.Error | LogLevel.Message, "Unable to save config entry!");
+		        Logger.Log(LogLevel.Error, ex);
+	        }
         }
        
         /// <summary>
@@ -170,10 +172,7 @@ namespace BepInEx
         public static bool HasEntry(string key, string section = "")
         {
             key = Sanitize(key);
-            if (section.IsNullOrWhiteSpace())
-                section = "Global";
-            else
-                section = Sanitize(section);
+            section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
 
             return cache.ContainsKey(section) && cache[section].ContainsKey(key);
         }
@@ -188,10 +187,7 @@ namespace BepInEx
         public static bool UnsetEntry(string key, string section = "")
         {
             key = Sanitize(key);
-            if (section.IsNullOrWhiteSpace())
-                section = "Global";
-            else
-                section = Sanitize(section);
+            section = section.IsNullOrWhiteSpace() ? "Global" : Sanitize(section);
 
             if (!HasEntry(key, section))
                 return false;

+ 1 - 1
BepInEx/Logging/BaseLogger.cs

@@ -42,7 +42,7 @@ namespace BepInEx.Logging
 
 		    try
 		    {
-			    lowest = (LogLevel)Enum.Parse(typeof(LogLevel), Config.GetEntry("logger-displayed-levels", nameof(LogLevel.Info)));
+			    lowest = (LogLevel)Enum.Parse(typeof(LogLevel), Config.GetEntry("logger-displayed-levels", nameof(LogLevel.Info), "BepInEx"));
 		    }
 		    catch
 		    {

+ 1 - 1
BepInEx/Logging/UnityLogWriter.cs

@@ -48,7 +48,7 @@ namespace BepInEx.Logging
         public static void ListenUnityLogs()
         {
             Type application = typeof(Application);
-
+			
             EventInfo logEvent = application.GetEvent("logMessageReceived", BindingFlags.Public | BindingFlags.Static);
             if (logEvent != null)
             {

+ 2 - 2
BepInEx/Properties/AssemblyInfo.cs

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("4.0.0.0")]
-[assembly: AssemblyFileVersion("4.0.0.0")]
+[assembly: AssemblyVersion("4.1.0.0")]
+[assembly: AssemblyFileVersion("4.1.0.0")]