Browse Source

Prototype patchless loading via doorstop

Bepis 6 years ago
parent
commit
ed450906ff

+ 67 - 0
BepInEx.Common/Utility.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 
@@ -45,5 +46,71 @@ namespace BepInEx.Common
         {
             return self == null || self.Trim().Length == 0;
         }
+
+        public static IEnumerable<TNode> TopologicalSort<TNode>(IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> dependencySelector)
+        {
+            List<TNode> sorted_list = new List<TNode>();
+
+            HashSet<TNode> visited = new HashSet<TNode>();
+            HashSet<TNode> sorted = new HashSet<TNode>();
+
+            foreach (TNode input in nodes)
+                Visit(input);
+
+            return sorted_list;
+
+            void Visit(TNode node)
+            {
+                if (visited.Contains(node))
+                {
+                    if (!sorted.Contains(node))
+                        throw new Exception("Cyclic Dependency");
+                }
+                else
+                {
+                    visited.Add(node);
+
+                    foreach (var dep in dependencySelector(node))
+                        Visit(dep);
+
+                    sorted.Add(node);
+                    sorted_list.Add(node);
+                }
+            }
+        }
+
+        public static List<TNode> TopologicalSort<TNode>(List<TNode> nodes, Func<TNode, List<TNode>> dependencySelector)
+        {
+            List<TNode> sorted_list = new List<TNode>();
+
+            List<TNode> visited = new List<TNode>();
+            List<TNode> sorted = new List<TNode>();
+
+            foreach (TNode input in nodes)
+                Visit(input);
+
+            return sorted_list;
+
+            void Visit(TNode node)
+            {
+                if (visited.Contains(node))
+                {
+                    if (!sorted.Contains(node))
+                    {
+                        throw new Exception("Cyclic Dependency");
+                    }
+                }
+                else
+                {
+                    visited.Add(node);
+
+                    foreach (var dep in dependencySelector(node))
+                        Visit(dep);
+
+                    sorted.Add(node);
+                    sorted_list.Add(node);
+                }
+            }
+        }
     }
 }

+ 10 - 2
BepInEx/BepInEx.csproj

@@ -29,6 +29,9 @@
     <Reference Include="0Harmony">
       <HintPath>..\lib\0Harmony.dll</HintPath>
     </Reference>
+    <Reference Include="Mono.Cecil, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
+      <HintPath>..\packages\Mono.Cecil.0.10.0\lib\net35\Mono.Cecil.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Xml" />
     <Reference Include="UnityEngine">
@@ -38,6 +41,8 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="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" />
@@ -48,11 +53,14 @@
     <Compile Include="ConsoleUtil\Extensions.cs" />
     <Compile Include="ConsoleUtil\Kon.cs" />
     <Compile Include="ConsoleUtil\SafeConsole.cs" />
-    <Compile Include="Chainloader.cs" />
+    <Compile Include="Bootstrap\Chainloader.cs" />
     <Compile Include="BaseUnityPlugin.cs" />
     <Compile Include="Logger.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="TypeLoader.cs" />
+    <Compile Include="Bootstrap\TypeLoader.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
   </ItemGroup>
   <Import Project="..\BepInEx.Common\BepInEx.Common.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

+ 86 - 0
BepInEx/Bootstrap/AssemblyPatcher.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using BepInEx.Common;
+using Mono.Cecil;
+
+namespace BepInEx
+{
+    internal static class AssemblyPatcher
+    {
+        public delegate void AssemblyLoadEventHandler(AssemblyDefinition assembly);
+
+        public static AssemblyLoadEventHandler AssemblyLoad;
+
+
+        public static void PatchAll(string directory)
+        {
+            //load all the requested assemblies
+            List<AssemblyDefinition> assemblies = new List<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 == "System.Core"
+                    || assembly.Name.Name == "mscorlib")
+                {
+                    assembly.Dispose();
+                    continue;
+                }
+
+                assemblies.Add(assembly);
+            }
+
+            //generate a dictionary of each assembly's dependencies
+            Dictionary<AssemblyDefinition, List<AssemblyDefinition>> assemblyDependencyDict = new Dictionary<AssemblyDefinition, List<AssemblyDefinition>>();
+            
+            foreach (AssemblyDefinition assembly in assemblies)
+            {
+                assemblyDependencyDict[assembly] = new List<AssemblyDefinition>();
+
+                foreach (var dependencyRef in assembly.MainModule.AssemblyReferences)
+                {
+                    var dependencyAssembly = assemblies.FirstOrDefault(x => x.FullName == dependencyRef.FullName);
+
+                    if (dependencyAssembly != null)
+                    {
+                        assemblyDependencyDict[assembly].Add(dependencyAssembly);
+                    }
+                }
+            }
+
+            //sort the assemblies so load the assemblies that are dependant upon first
+            List<AssemblyDefinition> sortedAssemblies = Utility.TopologicalSort(assemblies, x => assemblyDependencyDict[x]);
+
+            //special casing for UnityEngine, needs to be reordered to the front
+            var unityEngine = sortedAssemblies.FirstOrDefault(x => x.Name.Name == "UnityEngine");
+            if (unityEngine != null)
+            {
+                sortedAssemblies.Remove(unityEngine);
+                sortedAssemblies.Insert(0, unityEngine);
+            }
+
+            //call the patchers on the assemblies
+            foreach (var assembly in sortedAssemblies)
+            {
+                using (MemoryStream assemblyStream = new MemoryStream())
+                {
+                    AssemblyLoad?.Invoke(assembly);
+                    
+                    assembly.Write(assemblyStream);
+                    Assembly.Load(assemblyStream.ToArray());
+                }
+
+                assembly.Dispose();
+            }
+        }
+    }
+}

+ 1 - 36
BepInEx/Chainloader.cs

@@ -96,7 +96,7 @@ namespace BepInEx
 					}
 				}
 
-				pluginTypes = TopologicalSort(dependencyDict.Keys, x => dependencyDict[x]).ToList();
+				pluginTypes = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict[x]).ToList();
 
 				foreach (Type t in pluginTypes)
 				{
@@ -125,40 +125,5 @@ namespace BepInEx
 
 			loaded = true;
 		}
-
-		protected static IEnumerable<TNode> TopologicalSort<TNode>(
-			IEnumerable<TNode> nodes,
-			Func<TNode, IEnumerable<TNode>> dependencySelector)
-		{
-
-			List<TNode> sorted_list = new List<TNode>();
-
-			HashSet<TNode> visited = new HashSet<TNode>();
-			HashSet<TNode> sorted = new HashSet<TNode>();
-
-			foreach (TNode input in nodes)
-				Visit(input);
-
-			return sorted_list;
-
-			void Visit(TNode node)
-			{
-				if (visited.Contains(node))
-				{
-					if (!sorted.Contains(node))
-						throw new Exception("Cyclic Dependency");
-				}
-				else
-				{
-					visited.Add(node);
-
-					foreach (var dep in dependencySelector(node))
-						Visit(dep);
-
-					sorted.Add(node);
-					sorted_list.Add(node);
-				}
-			}
-		}
 	}
 }

+ 72 - 0
BepInEx/Bootstrap/Preloader.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using MethodAttributes = Mono.Cecil.MethodAttributes;
+
+namespace BepInEx.Bootstrap
+{
+    internal static class Preloader
+    {
+        public static string ExecutablePath { get; private set; }
+
+        internal static string CurrentExecutingAssemblyPath => Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", "").Replace('/', '\\');
+
+        internal static string GameName => Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName);
+
+        internal static string GameRootPath => Path.GetDirectoryName(ExecutablePath);
+
+        internal static string ManagedPath => Path.Combine(GameRootPath, Path.Combine($"{GameName}_Data", "Managed"));
+
+
+        public static void Main(string[] args)
+        {
+            ExecutablePath = args[0];
+            
+            try
+            {
+                AssemblyPatcher.AssemblyLoad += PatchEntrypoint;
+
+                AssemblyPatcher.PatchAll(ManagedPath);
+            }
+            catch (Exception ex)
+            {
+                //File.WriteAllText("B:\\test.txt", ex.ToString());
+            }
+        }
+
+        static void PatchEntrypoint(AssemblyDefinition assembly)
+        {
+            if (assembly.Name.Name == "UnityEngine")
+            {
+                using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(CurrentExecutingAssemblyPath))
+                {
+                    var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Chainloader")
+                        .Methods.First(x => x.Name == "Initialize");
+
+                    var injectMethod = assembly.MainModule.ImportReference(originalInjectMethod);
+
+                    var sceneManager = assembly.MainModule.Types.First(x => x.Name == "Application");
+
+                    var voidType = assembly.MainModule.ImportReference(typeof(void));
+                    var cctor = new MethodDefinition(".cctor",
+                        MethodAttributes.Static
+                        | MethodAttributes.Private
+                        | MethodAttributes.HideBySig
+                        | MethodAttributes.SpecialName
+                        | MethodAttributes.RTSpecialName,
+                        voidType);
+
+                    var ilp = cctor.Body.GetILProcessor();
+                    ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
+                    ilp.Append(ilp.Create(OpCodes.Ret));
+
+                    sceneManager.Methods.Add(cctor);
+                }
+            }
+        }
+    }
+}

+ 2 - 9
BepInEx/TypeLoader.cs

@@ -29,15 +29,8 @@ namespace BepInEx
 
                     foreach (Type type in assembly.GetTypes())
                     {
-                        if (type.IsInterface || type.IsAbstract)
-                        {
-                            continue;
-                        }
-                        else
-                        {
-                            if (type.BaseType == pluginType)
-                                types.Add(type);
-                        }
+                        if (!type.IsInterface && !type.IsAbstract && type.BaseType == pluginType)
+                            types.Add(type);
                     }
                 }
                 catch (BadImageFormatException) { } //unmanaged DLL

+ 4 - 0
BepInEx/packages.config

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