Quellcode durchsuchen

Merge pull request #64 from BepInEx/feature-preloader-refactor

Refactor preloader and separate it into its own DLL
Bepis vor 5 Jahren
Ursprung
Commit
b598800dd4

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

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{F7ABBE07-C02F-4F7C-BF6E-C6656BF588CA}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BepInEx.Preloader</RootNamespace>
+    <AssemblyName>BepInEx.Preloader</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Legacy|AnyCPU' ">
+    <DebugType>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;
+    }
+}

+ 294 - 0
BepInEx.Preloader/Preloader.cs

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

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

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

+ 50 - 0
BepInEx.Preloader/UnityPatches.cs

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

+ 29 - 33
BepInEx.sln

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

+ 1 - 15
BepInEx/BepInEx.csproj

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

+ 0 - 161
BepInEx/Bootstrap/AssemblyPatcher.cs

@@ -1,161 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using BepInEx.Harmony;
-using Harmony;
-using Mono.Cecil;
-
-namespace BepInEx.Bootstrap
-{
-    /// <summary>
-    /// Delegate used in patching assemblies.
-    /// </summary>
-    /// <param name="assembly">The assembly that is being patched.</param>
-    public delegate void AssemblyPatcherDelegate(ref AssemblyDefinition assembly);
-
-    /// <summary>
-    /// Worker class which is used for loading and patching entire folders of assemblies, or alternatively patching and loading assemblies one at a time.
-    /// </summary>
-    public static class AssemblyPatcher
-    {
-        /// <summary>
-        /// Configuration value of whether assembly dumping is enabled or not.
-        /// </summary>
-        private static bool DumpingEnabled => Utility.SafeParseBool(Config.GetEntry("dump-assemblies", "false", "Preloader"));
-
-        /// <summary>
-        /// Patches and loads an entire directory of assemblies.
-        /// </summary>
-        /// <param name="directory">The directory to load assemblies from.</param>
-        /// <param name="patcherMethodDictionary">The dictionary of patchers and their targeted assembly filenames which they are patching.</param>
-        /// <param name="initializers">List of initializers to run before any patching starts</param>
-        /// <param name="finalizers">List of finalizers to run before returning</param>
-        public static void PatchAll(string directory, IDictionary<AssemblyPatcherDelegate, IEnumerable<string>> patcherMethodDictionary, IEnumerable<Action> initializers = null, IEnumerable<Action> finalizers = null)
-        {
-            //run all initializers
-            if (initializers != null)
-                foreach (Action init in initializers)
-                    init.Invoke();
-
-            //load all the requested assemblies
-            Dictionary<string, AssemblyDefinition> assemblies = new Dictionary<string, AssemblyDefinition>();
-
-            foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll"))
-            {
-                var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
-
-                //NOTE: this is special cased here because the dependency handling for System.dll is a bit wonky
-                //System has an assembly reference to itself, and it also has a reference to Mono.Security causing a circular dependency
-                //It's also generally dangerous to change system.dll since so many things rely on it, 
-                // and it's already loaded into the appdomain since this loader references it, so we might as well skip it
-                if (assembly.Name.Name == "System"
-                    || assembly.Name.Name == "mscorlib") //mscorlib is already loaded into the appdomain so it can't be patched
-                {
-                    assembly.Dispose();
-                    continue;
-                }
-
-                assemblies.Add(Path.GetFileName(assemblyPath), assembly);
-                PatchedAssemblyResolver.AssemblyLocations.Add(assembly.FullName, Path.GetFullPath(assemblyPath));
-            }
-
-            HashSet<string> patchedAssemblies = new HashSet<string>();
-
-            //call the patchers on the assemblies
-            foreach (var patcherMethod in patcherMethodDictionary)
-            {
-                foreach (string assemblyFilename in patcherMethod.Value)
-                {
-                    if (assemblies.TryGetValue(assemblyFilename, out var assembly))
-                    {
-                        Patch(ref assembly, patcherMethod.Key);
-                        assemblies[assemblyFilename] = assembly;
-                        patchedAssemblies.Add(assemblyFilename);
-                    }
-                }
-            }
-
-            // Finally, load all assemblies into memory
-            foreach (var kv in assemblies)
-            {
-                string filename = kv.Key;
-                var assembly = kv.Value;
-
-                if (DumpingEnabled && patchedAssemblies.Contains(filename))
-                {
-                    using (MemoryStream mem = new MemoryStream())
-                    {
-                        string dirPath = Path.Combine(Paths.PluginPath, "DumpedAssemblies");
-
-                        if (!Directory.Exists(dirPath))
-                            Directory.CreateDirectory(dirPath);
-
-                        assembly.Write(mem);
-                        File.WriteAllBytes(Path.Combine(dirPath, filename), mem.ToArray());
-                    }
-                }
-
-                Load(assembly);
-                assembly.Dispose();
-            }
-
-            // Patch Assembly.Location and Assembly.CodeBase only if the assemblies were loaded from memory
-            PatchedAssemblyResolver.ApplyPatch();
-
-            //run all finalizers
-            if (finalizers != null)
-                foreach (Action finalizer in finalizers)
-                    finalizer.Invoke();
-        }
-
-        /// <summary>
-        /// Patches an individual assembly, without loading it.
-        /// </summary>
-        /// <param name="assembly">The assembly definition to apply the patch to.</param>
-        /// <param name="patcherMethod">The patcher to use to patch the assembly definition.</param>
-        public static void Patch(ref AssemblyDefinition assembly, AssemblyPatcherDelegate patcherMethod)
-        {
-            patcherMethod.Invoke(ref assembly);
-        }
-
-        /// <summary>
-        /// Loads an individual assembly defintion into the CLR.
-        /// </summary>
-        /// <param name="assembly">The assembly to load.</param>
-        public static void Load(AssemblyDefinition assembly)
-        {
-            using (MemoryStream assemblyStream = new MemoryStream())
-            {
-                assembly.Write(assemblyStream);
-                Assembly.Load(assemblyStream.ToArray());
-            }
-        }
-    }
-
-    internal static class PatchedAssemblyResolver
-	{
-		public static HarmonyInstance HarmonyInstance { get; } = HarmonyInstance.Create("com.bepis.bepinex.asmlocationfix");
-		
-		public static Dictionary<string, string> AssemblyLocations { get; } = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
-
-        public static void ApplyPatch()
-        {
-			HarmonyWrapper.PatchAll(typeof(PatchedAssemblyResolver), HarmonyInstance);
-        }
-
-        [HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.Location), MethodType.Getter)]
-        public static void GetLocation(ref string __result, Assembly __instance)
-        {
-            if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
-                __result = location;
-        }
-
-		[HarmonyPostfix, HarmonyPatch(typeof(Assembly), nameof(Assembly.CodeBase), MethodType.Getter)]
-        public static void GetCodeBase(ref string __result, Assembly __instance)
-        {
-            if (AssemblyLocations.TryGetValue(__instance.FullName, out string location))
-                __result = $"file://{location.Replace('\\', '/')}";
-        }
-    }
-}

+ 5 - 6
BepInEx/Bootstrap/Chainloader.cs

@@ -15,17 +15,17 @@ namespace BepInEx.Bootstrap
 	/// <summary>
 	/// The manager and loader for all plugins, and the entry point for BepInEx plugin system.
 	/// </summary>
-	public class Chainloader
+	public static class Chainloader
 	{
 		/// <summary>
 		/// The loaded and initialized list of plugins.
 		/// </summary>
-		public static List<BaseUnityPlugin> Plugins { get; protected set; } = new List<BaseUnityPlugin>();
+		public static List<BaseUnityPlugin> Plugins { get; private set; } = new List<BaseUnityPlugin>();
 
 		/// <summary>
 		/// The GameObject that all plugins are attached to as components.
 		/// </summary>
-		public static GameObject ManagerObject { get; protected set; } = new GameObject("BepInEx_Manager");
+		public static GameObject ManagerObject { get; private set; } = new GameObject("BepInEx_Manager");
 
 
 		private static bool _loaded = false;
@@ -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 - 342
BepInEx/Bootstrap/Preloader.cs

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

+ 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;

+ 8 - 8
BepInEx/Contract/Attributes.cs

@@ -197,21 +197,21 @@ namespace BepInEx
 
     #endregion
 
-	#region Debug
+	#region Build configuration
 
-	#if DEBUG
-
-	public class DebugInfoAttribute : Attribute
+	/// <summary>
+	/// This class is appended to AssemblyInfo.cs when BepInEx is built via a CI pipeline.
+	/// It is mainly intended to signify that the current build is not a release build and is special, like for instance a bleeding edge build.
+	/// </summary>
+	internal class BuildInfoAttribute : Attribute
 	{
 		public string Info { get; }
 
-		public DebugInfoAttribute(string info)
+		public BuildInfoAttribute(string info)
 		{
 			Info = info;
 		}
 	}
 
-	#endif
-
 	#endregion
-}
+}

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

+ 2 - 0
BepInEx/Properties/AssemblyInfo.cs

@@ -24,6 +24,8 @@ using BepInEx;
 // The following GUID is for the ID of the typelib if this project is exposed to COM
 [assembly: Guid("4ffba620-f5ed-47f9-b90c-dad1316fd9b9")]
 
+[assembly: InternalsVisibleTo("BepInEx.Preloader")]
+
 // Version information for an assembly consists of the following four values:
 //
 //      Major Version

+ 1 - 1
doorstop/doorstop_config.ini

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