Sfoglia il codice sorgente

Move preloader to a separate project

ghorsington 5 anni fa
parent
commit
bd7597fa56

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

@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BepInEx.Preloader</RootNamespace>
+    <AssemblyName>BepInEx.Preloader</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\bin\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
+    <OutputPath>bin\v2018\</OutputPath>
+    <DefineConstants>TRACE;UNITY_2018</DefineConstants>
+    <Optimize>true</Optimize>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <ErrorReport>prompt</ErrorReport>
+    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Mono.Cecil">
+      <HintPath>..\lib\Mono.Cecil.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Patcher\AssemblyPatcher.cs" />
+    <Compile Include="Entrypoint.cs" />
+    <Compile Include="Patcher\PatcherPlugin.cs" />
+    <Compile Include="Preloader.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="UnityPatches.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\BepInEx\BepInEx.csproj">
+      <Project>{4ffba620-f5ed-47f9-b90c-dad1316fd9b9}</Project>
+      <Name>BepInEx</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\submodules\BepInEx.Harmony\BepInEx.Harmony\BepInEx.Harmony.csproj">
+      <Project>{54161cfe-ff42-4dde-b161-3a49545db5cd}</Project>
+      <Name>BepInEx.Harmony</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\submodules\BepInEx.Harmony\submodules\Harmony\Harmony\Harmony.csproj">
+      <Project>{a15d6ee6-f954-415b-8605-8a8470cc87dc}</Project>
+      <Name>Harmony</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 7 - 4
BepInEx/Bootstrap/Entrypoint.cs

@@ -2,9 +2,9 @@
 using System.Linq;
 using System.Reflection;
 
-namespace BepInEx.Bootstrap
+namespace BepInEx.Preloader
 {
-    public static class Entrypoint
+    internal static class Entrypoint
     {
         /// <summary>
         ///     The main entrypoint of BepInEx, called from Doorstop.
@@ -15,7 +15,9 @@ namespace BepInEx.Bootstrap
         /// </param>
         public static void Main(string[] args)
         {
-            Paths.ExecutablePath = args[0];
+            // Manually set up the path for patchers to work
+            typeof(Paths).GetProperty(nameof(Paths.ExecutablePath)).SetValue(null, args[0], null);
+            //Paths.ExecutablePath = args[0];
             AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
 
             Preloader.Run();
@@ -35,7 +37,8 @@ namespace BepInEx.Bootstrap
         {
             var assemblyName = new AssemblyName(args.Name);
 
-            var foundAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
+            var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
+                .FirstOrDefault(x => x.GetName().Name == assemblyName.Name);
 
             if (foundAssembly != null)
                 return foundAssembly;

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

@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using BepInEx.Logging;
+using Mono.Cecil;
+
+namespace BepInEx.Preloader.Patcher
+{
+    /// <summary>
+    ///     Delegate used in patching assemblies.
+    /// </summary>
+    /// <param name="assembly">The assembly that is being patched.</param>
+    internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
+
+    /// <summary>
+    ///     Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and
+    ///     loading assemblies one at a time.
+    /// </summary>
+    internal static class AssemblyPatcher
+    {
+        private static readonly List<PatcherPlugin> patchers = new List<PatcherPlugin>();
+
+        /// <summary>
+        ///     Configuration value of whether assembly dumping is enabled or not.
+        /// </summary>
+        private static bool DumpingEnabled =>
+            Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
+
+        /// <summary>
+        ///     Adds a single assembly patcher to the pool of applicable patches.
+        /// </summary>
+        /// <param name="patcher">Patcher to apply.</param>
+        public static void AddPatcher(PatcherPlugin patcher)
+        {
+            patchers.Add(patcher);
+        }
+
+        /// <summary>
+        ///     Adds all patchers from all managed assemblies specified in a directory.
+        /// </summary>
+        /// <param name="directory">Directory to search patcher DLLs from.</param>
+        /// <param name="patcherLocator">A function that locates assembly patchers in a given managed assembly.</param>
+        public static void AddPatchersFromDirectory(string directory,
+            Func<Assembly, List<PatcherPlugin>> patcherLocator)
+        {
+            if (!Directory.Exists(directory))
+                return;
+
+            var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
+
+            foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
+                try
+                {
+                    var assembly = Assembly.LoadFrom(assemblyPath);
+
+                    foreach (var patcher in patcherLocator(assembly))
+                        sortedPatchers.Add(patcher.Name, patcher);
+                }
+                catch (BadImageFormatException)
+                {
+                } //unmanaged DLL
+                catch (ReflectionTypeLoadException)
+                {
+                } //invalid references
+
+            foreach (KeyValuePair<string, PatcherPlugin> patcher in sortedPatchers)
+                AddPatcher(patcher.Value);
+        }
+
+        private static void InitializePatchers()
+        {
+            foreach (var assemblyPatcher in patchers)
+                assemblyPatcher.Initializer?.Invoke();
+        }
+
+        private static void FinalizePatching()
+        {
+            foreach (var assemblyPatcher in patchers)
+                assemblyPatcher.Finalizer?.Invoke();
+        }
+
+        /// <summary>
+        ///     Releases all patchers to let them be collected by GC.
+        /// </summary>
+        public static void DisposePatchers()
+        {
+            patchers.Clear();
+        }
+
+        /// <summary>
+        ///     Applies patchers to all assemblies in the given directory and loads patched assemblies into memory.
+        /// </summary>
+        /// <param name="directory">Directory to load CLR assemblies from.</param>
+        public static void PatchAndLoad(string directory)
+        {
+            // First, load patchable assemblies into Cecil
+            var assemblies = new Dictionary<string, AssemblyDefinition>();
+
+            foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
+            {
+                var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
+
+                //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
+                //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
+                //It's also generally dangerous to change system.dll since so many things rely on it, 
+                // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
+                if (assembly.Name.Name == "System"
+                    || assembly.Name.Name == "mscorlib"
+                ) //mscorlib is already loaded into the appdomain so it can't be patched
+                {
+                    assembly.Dispose();
+                    continue;
+                }
+
+                if (UnityPatches.AssemblyLocations.ContainsKey(assembly.FullName))
+                {
+                    Logger.Log(LogLevel.Warning,
+                        $"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
+                    continue;
+                }
+
+                assemblies.Add(Path.GetFileName(assemblyPath), assembly);
+                UnityPatches.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
+            }
+
+            // Next, initialize all the patchers
+            InitializePatchers();
+
+            // Then, perform the actual patching
+            var patchedAssemblies = new HashSet<string>();
+            foreach (var assemblyPatcher in patchers)
+            foreach (string targetDll in assemblyPatcher.TargetDLLs)
+                if (assemblies.TryGetValue(targetDll, out var assembly))
+                {
+                    assemblyPatcher.Patcher?.Invoke(ref assembly);
+                    assemblies[targetDll] = assembly;
+                    patchedAssemblies.Add(targetDll);
+                }
+
+            // Finally, load all assemblies into memory
+            foreach (KeyValuePair<string, AssemblyDefinition> kv in assemblies)
+            {
+                string filename = kv.Key;
+                var assembly = kv.Value;
+
+                if (DumpingEnabled && patchedAssemblies.Contains(filename))
+                    using (var mem = new MemoryStream())
+                    {
+                        string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies");
+
+                        if (!Directory.Exists(dirPath))
+                            Directory.CreateDirectory(dirPath);
+
+                        assembly.Write(mem);
+                        File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
+                    }
+
+                Load(assembly);
+                assembly.Dispose();
+            }
+
+            //run all finalizers
+            FinalizePatching();
+        }
+
+        /// <summary>
+        ///     Loads an individual assembly defintion into the CLR.
+        /// </summary>
+        /// <param name="assembly">The assembly to load.</param>
+        public static void Load(AssemblyDefinition assembly)
+        {
+            using (var assemblyStream = new MemoryStream())
+            {
+                assembly.Write(assemblyStream);
+                Assembly.Load(assemblyStream.ToArray());
+            }
+        }
+    }
+}

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

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

+ 296 - 0
BepInEx.Preloader/Preloader.cs

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

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

@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BepInEx.Preloader")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BepInEx.Preloader")]
+[assembly: AssemblyCopyright("Copyright ©  2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f7abbe07-c02f-4f7c-bf6e-c6656bf588ca")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 50 - 0
BepInEx.Preloader/UnityPatches.cs

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

+ 22 - 0
BepInEx.sln

@@ -21,11 +21,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Harmony", "submodules\BepIn
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Patcher", "Patcher", "{A9071994-3533-4C1B-89DC-D817B676AB41}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Preloader", "BepInEx.Preloader\BepInEx.Preloader.csproj", "{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Release|Any CPU = Release|Any CPU
 		v2018 Release|Any CPU = v2018 Release|Any CPU
+		v2018|Any CPU = v2018|Any CPU
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -34,30 +37,48 @@ Global
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Release|Any CPU.Build.0 = Release|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.ActiveCfg = v2018|Any CPU
 		{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018 Release|Any CPU.Build.0 = v2018|Any CPU
+		{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 = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Debug|Any CPU.Build.0 = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Release|Any CPU.Build.0 = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
 		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = Debug|Any CPU
+		{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.Build.0 = Debug|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.ActiveCfg = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Debug|Any CPU.Build.0 = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Release|Any CPU.Build.0 = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
+		{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 = Release|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Debug|Any CPU.Build.0 = Release|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.Release|Any CPU.Build.0 = Release|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.ActiveCfg = Debug|Any CPU
+		{54161CFE-FF42-4DDE-B161-3A49545DB5CD}.v2018|Any CPU.Build.0 = Debug|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.ActiveCfg = Release|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Debug|Any CPU.Build.0 = Release|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.Release|Any CPU.Build.0 = Release|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.ActiveCfg = Debug|Any CPU
+		{A15D6EE6-F954-415B-8605-8A8470CC87DC}.v2018|Any CPU.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018 Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018 Release|Any CPU.Build.0 = Release|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}.v2018|Any CPU.Build.0 = v2018|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -67,6 +88,7 @@ Global
 		{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB} = {A9071994-3533-4C1B-89DC-D817B676AB41}
 		{54161CFE-FF42-4DDE-B161-3A49545DB5CD} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
 		{A15D6EE6-F954-415B-8605-8A8470CC87DC} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
+		{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA} = {A9071994-3533-4C1B-89DC-D817B676AB41}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {55AC11EF-F568-4C79-A356-7ED9510145B1}

+ 0 - 4
BepInEx/BepInEx.csproj

@@ -56,11 +56,7 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Bootstrap\Entrypoint.cs" />
-    <Compile Include="Bootstrap\UnityPatches.cs" />
     <Compile Include="Contract\Attributes.cs" />
-    <Compile Include="Bootstrap\AssemblyPatcher.cs" />
-    <Compile Include="Bootstrap\Preloader.cs" />
     <Compile Include="Config.cs" />
     <Compile Include="ConfigWrapper.cs" />
     <Compile Include="ConsoleUtil\ConsoleEncoding\ConsoleEncoding.Buffers.cs" />

+ 0 - 239
BepInEx/Bootstrap/AssemblyPatcher.cs

@@ -1,239 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using BepInEx.Harmony;
-using BepInEx.Logging;
-using Harmony;
-using Mono.Cecil;
-
-namespace BepInEx.Bootstrap
-{
-    /// <summary>
-    /// Delegate used in patching assemblies.
-    /// </summary>
-    /// <param name="assembly">The assembly that is being patched.</param>
-    internal delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
-
-    /// <summary>
-    /// A single assembly patcher.
-    /// </summary>
-    internal class PatcherPlugin
-    {
-        /// <summary>
-        /// Target assemblies to patch.
-        /// </summary>
-        public IEnumerable<string> TargetDLLs { get; set; } = null;
-
-        /// <summary>
-        /// Initializer method that is run before any patching occurs.
-        /// </summary>
-        public Action Initializer { get; set; } = null;
-
-        /// <summary>
-        /// Finalizer method that is run after all patching is done.
-        /// </summary>
-        public Action Finalizer { get; set; } = null;
-
-        /// <summary>
-        /// The main patcher method that is called on every DLL defined in <see cref="TargetDLLs"/>.
-        /// </summary>
-        public AssemblyPatcherDelegate Patcher { get; set; } = null;
-
-        /// <summary>
-        /// Name of the patcher.
-        /// </summary>
-        public string Name { get; set; } = string.Empty;
-    }
-
-    /// <summary>
-    /// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and loading assemblies one at a time.
-    /// </summary>
-    internal static class AssemblyPatcher
-    {
-        private static List<PatcherPlugin> patchers = new List<PatcherPlugin>();
-
-        /// <summary>
-        /// Configuration value of whether assembly dumping is enabled or not.
-        /// </summary>
-        private static bool DumpingEnabled => Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
-
-        /// <summary>
-        /// Adds a single assembly patcher to the pool of applicable patches.
-        /// </summary>
-        /// <param name="patcher">Patcher to apply.</param>
-        public static void AddPatcher(PatcherPlugin patcher)
-        {
-            patchers.Add(patcher);
-        }
-
-        /// <summary>
-        /// Adds all patchers from all managed assemblies specified in a directory.
-        /// </summary>
-        /// <param name="directory">Directory to search patcher DLLs from.</param>
-        /// <param name="patcherLocator">A function that locates assembly patchers in a given managed assembly.</param>
-        public static void AddPatchersFromDirectory(string directory, Func<Assembly, List<PatcherPlugin>> patcherLocator)
-        {
-            if (!Directory.Exists(directory))
-                return;
-
-            var sortedPatchers = new SortedDictionary<string, PatcherPlugin>();
-
-            foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
-                try
-                {
-                    var assembly = Assembly.LoadFrom(assemblyPath);
-
-                    foreach (var patcher in patcherLocator(assembly))
-                        sortedPatchers.Add(patcher.Name, patcher);
-                }
-                catch (BadImageFormatException) { } //unmanaged DLL
-                catch (ReflectionTypeLoadException) { } //invalid references
-
-            foreach (var patcher in sortedPatchers)
-                AddPatcher(patcher.Value);
-        }
-
-        private static void InitializePatchers()
-        {
-            foreach (var assemblyPatcher in patchers)
-                assemblyPatcher.Initializer?.Invoke();
-        }
-
-        private static void FinalizePatching()
-        {
-            foreach (var assemblyPatcher in patchers)
-                assemblyPatcher.Finalizer?.Invoke();
-        }
-
-        /// <summary>
-        /// Releases all patchers to let them be collected by GC.
-        /// </summary>
-        public static void DisposePatchers()
-        {
-            patchers.Clear();
-        }
-
-        /// <summary>
-        /// Applies patchers to all assemblies in the given directory and loads patched assemblies into memory.
-        /// </summary>
-        /// <param name="directory">Directory to load CLR assemblies from.</param>
-        public static void PatchAndLoad(string directory)
-        {
-
-            // First, load patchable assemblies into Cecil
-            Dictionary<string, AssemblyDefinition> assemblies = new Dictionary<string, AssemblyDefinition>();
-
-            foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
-            {
-                var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
-
-                //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
-                //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
-                //It's also generally dangerous to change system.dll since so many things rely on it, 
-                // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
-                if (assembly.Name.Name == "System"
-                    || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
-                {
-                    assembly.Dispose();
-                    continue;
-                }
-                if (PatchedAssemblyResolver.AssemblyLocations.ContainsKey(assembly.FullName))
-                {
-                    Logger.Log(LogLevel.Warning, $"Tried to load duplicate assembly {Path.GetFileName(assemblyPath)} from Managed folder! Skipping...");
-                    continue;
-                }
-
-                assemblies.Add(Path.GetFileName(assemblyPath), assembly);
-                PatchedAssemblyResolver.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
-            }
-
-            // Next, initialize all the patchers
-            InitializePatchers();
-
-            // Then, perform the actual patching
-            HashSet<string> patchedAssemblies = new HashSet<string>();
-            foreach (var assemblyPatcher in patchers)
-            {
-                foreach (string targetDll in assemblyPatcher.TargetDLLs)
-                {
-                    if (assemblies.TryGetValue(targetDll, out var assembly))
-                    {
-                        assemblyPatcher.Patcher?.Invoke(ref assembly);
-                        assemblies[targetDll] = assembly;
-                        patchedAssemblies.Add(targetDll);
-                    }
-                }
-            }
-
-            // Finally, load all assemblies into memory
-            foreach (var kv in assemblies)
-            {
-                string filename = kv.Key;
-                var assembly = kv.Value;
-
-                if (DumpingEnabled && patchedAssemblies.Contains(filename))
-                {
-                    using (MemoryStream mem = new MemoryStream())
-                    {
-                        string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies");
-
-                        if (!Directory.Exists(dirPath))
-                            Directory.CreateDirectory(dirPath);
-
-                        assembly.Write(mem);
-                        File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
-                    }
-                }
-
-                Load(assembly);
-                assembly.Dispose();
-            }
-
-            // Apply assembly location resolver patch
-            PatchedAssemblyResolver.ApplyPatch();
-
-            //run all finalizers
-            FinalizePatching();
-        }
-
-        /// <summary>
-        /// Loads an individual assembly defintion into the CLR.
-        /// </summary>
-        /// <param name="assembly">The assembly to load.</param>
-        public static void Load(AssemblyDefinition assembly)
-        {
-            using (MemoryStream assemblyStream = new MemoryStream())
-            {
-                assembly.Write(assemblyStream);
-                Assembly.Load(assemblyStream.ToArray());
-            }
-        }
-    }
-
-    internal static class PatchedAssemblyResolver
-	{
-		public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepis.bepinex.asmlocationfix");
-		
-		public static Dictionary<string, string> AssemblyLocations { get; } = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
-
-        public static void ApplyPatch()
-        {
-			HarmonyWrapper.PatchAll(typeof(PatchedAssemblyResolver), HarmonyInstance);
-        }
-
-        [HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)]
-        public static void GetLocation(ref string __result, Assembly __instance)
-        {
-            if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
-                __result = location;
-        }
-
-		[HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)]
-        public static void GetCodeBase(ref string __result, Assembly __instance)
-        {
-            if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
-                __result = $"file://{location.Replace('\\', '/')}";
-        }
-    }
-}

+ 2 - 3
BepInEx/Bootstrap/Chainloader.cs

@@ -54,12 +54,11 @@ namespace BepInEx.Bootstrap
 			
 			UnityLogWriter unityLogWriter = new UnityLogWriter();
 
-			if (Preloader.PreloaderLog != null)
-				unityLogWriter.WriteToLog($"{Preloader.PreloaderLog}\r\n");
+		    if (Logger.CurrentLogger != null && Logger.CurrentLogger is PreloaderLogWriter preloaderLogger)
+                unityLogWriter.WriteToLog($"{preloaderLogger}\r\n");
 
 			Logger.SetLogger(unityLogWriter);
 
-
 			_initialized = true;
 		}
 

+ 0 - 293
BepInEx/Bootstrap/Preloader.cs

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

+ 0 - 29
BepInEx/Bootstrap/UnityPatches.cs

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

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

@@ -15,7 +15,7 @@ namespace UnityInjector.ConsoleUtil
     // http://jonskeet.uk/csharp/ebcdic/
     // using only safe (managed) code
     // --------------------------------------------------
-    internal partial class ConsoleEncoding : Encoding
+    public partial class ConsoleEncoding : Encoding
     {
         private readonly uint _codePage;
         public override int CodePage => (int) _codePage;

+ 1 - 1
BepInEx/ConsoleUtil/ConsoleWindow.cs

@@ -10,7 +10,7 @@ using System.Text;
 
 namespace UnityInjector.ConsoleUtil
 {
-    internal class ConsoleWindow
+    public class ConsoleWindow
     {
         public static bool IsAttatched { get; private set; }
         private static IntPtr _cOut;

+ 2 - 2
BepInEx/Logging/PreloaderLogWriter.cs

@@ -62,7 +62,7 @@ namespace BepInEx.Logging
             if (IsRedirectingConsole)
                 Console.SetOut(this);
             else
-                Console.SetOut(TextWriter.Null);
+                Console.SetOut(Null);
 
             Trace.Listeners.Add(traceListener);
 
@@ -76,7 +76,7 @@ namespace BepInEx.Logging
         {
             if (!_enabled)
                 return;
-            
+
             Console.SetOut(stdout);
 
             Trace.Listeners.Remove(traceListener);

+ 3 - 5
BepInEx/Logging/UnityLogWriter.cs

@@ -11,10 +11,8 @@ namespace BepInEx.Logging
 	/// Logs entries using Unity specific outputs.
 	/// </summary>
     public class UnityLogWriter : BaseLogger
-    {
-        private delegate void WriteStringToUnityLogDelegate(string s);
-
-        private static readonly WriteStringToUnityLogDelegate WriteStringToUnityLog;
+	{
+	    private static readonly Action<string> WriteStringToUnityLog;
 
         [DllImport("mono.dll", EntryPoint = "mono_lookup_internal_call")]
         private static extern IntPtr MonoLookupInternalCall(IntPtr gconstpointer);
@@ -26,7 +24,7 @@ namespace BepInEx.Logging
                 if (MonoLookupInternalCall(methodInfo.MethodHandle.Value) == IntPtr.Zero)
                     continue;
 
-                WriteStringToUnityLog = (WriteStringToUnityLogDelegate) Delegate.CreateDelegate(typeof(WriteStringToUnityLogDelegate), methodInfo);
+                WriteStringToUnityLog = (Action<string>) Delegate.CreateDelegate(typeof(Action<string>), methodInfo);
                 break;
             }
         }