Browse Source

Implement automatic assembly unhollower

Bepis 3 years ago
parent
commit
065f2ea961

+ 32 - 12
BepInEx.Core/Utility.cs

@@ -4,6 +4,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Reflection.Emit;
+using System.Text;
 using Mono.Cecil;
 
 namespace BepInEx
@@ -162,21 +163,30 @@ namespace BepInEx
 
 			foreach (string subDirectory in potentialDirectories)
 			{
-				string path = Path.Combine(subDirectory, $"{assemblyName.Name}.dll");
-
-				if (!File.Exists(path))
-					continue;
-
-				try
+				string[] potentialPaths = new[]
 				{
-					assembly = loader(path);
-				}
-				catch (Exception)
+					$"{assemblyName.Name}.dll",
+					$"{assemblyName.Name}.exe"
+				};
+
+				foreach (var potentialPath in potentialPaths)
 				{
-					continue;
-				}
+					string path = Path.Combine(subDirectory, potentialPath);
 
-				return true;
+					if (!File.Exists(path))
+						continue;
+
+					try
+					{
+						assembly = loader(path);
+					}
+					catch (Exception)
+					{
+						continue;
+					}
+
+					return true;
+				}
 			}
 
 			return false;
@@ -250,5 +260,15 @@ namespace BepInEx
 				currentType = currentType.BaseType?.Resolve();
 			}
 		}
+
+		public static string ByteArrayToString(byte[] data)
+		{
+			StringBuilder builder = new StringBuilder(data.Length * 2);
+
+			foreach (byte b in data)
+				builder.AppendFormat("{0:x2}", b);
+
+			return builder.ToString();
+		}
 	}
 }

+ 10 - 1
BepInEx.IL2CPP/BepInEx.IL2CPP.csproj

@@ -23,6 +23,7 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <LangVersion>8</LangVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>portable</DebugType>
@@ -33,8 +34,15 @@
     <WarningLevel>4</WarningLevel>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DebugSymbols>true</DebugSymbols>
+    <LangVersion>8</LangVersion>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="AssemblyUnhollower">
+      <HintPath>..\lib\AssemblyUnhollower.exe</HintPath>
+    </Reference>
+    <Reference Include="Il2CppDumper">
+      <HintPath>..\lib\Il2CppDumper.dll</HintPath>
+    </Reference>
     <Reference Include="Il2Cppmscorlib">
       <HintPath>..\lib\Il2Cppmscorlib.dll</HintPath>
       <Private>False</Private>
@@ -44,7 +52,7 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="UnhollowerBaseLib">
       <HintPath>..\lib\UnhollowerBaseLib.dll</HintPath>
-      <Private>False</Private>
+      <Private>True</Private>
     </Reference>
     <Reference Include="UnityEngine-IL2CPP">
       <HintPath>..\lib\UnityEngine-IL2CPP.dll</HintPath>
@@ -66,6 +74,7 @@
     <Compile Include="Preloader.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Hook\DetourGenerator.cs" />
+    <Compile Include="ProxyAssemblyGenerator.cs" />
     <Compile Include="UnityEngine\Input.cs" />
   </ItemGroup>
   <ItemGroup>

+ 8 - 1
BepInEx.IL2CPP/DoorstopEntrypoint.cs

@@ -17,10 +17,17 @@ namespace BepInEx.IL2CPP
 			AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
 			AppDomain.CurrentDomain.AssemblyResolve -= DoorstopEntrypoint.ResolveCurrentDirectory;
 
+			File.WriteAllText("B:\\a.txt", "a");
+
+			//AppDomain.CurrentDomain.TypeResolve += (sender, eventArgs) =>
+			//{
+			//	eventArgs.
+			//}
+
 			Preloader.Run();
 		}
 
-		private static Assembly LocalResolve(object sender, ResolveEventArgs args)
+		internal static Assembly LocalResolve(object sender, ResolveEventArgs args)
 		{
 			var assemblyName = new AssemblyName(args.Name);
 

+ 12 - 8
BepInEx.IL2CPP/IL2CPPChainloader.cs

@@ -34,8 +34,6 @@ namespace BepInEx.IL2CPP
 
 		private static RuntimeInvokeDetourDelegate originalInvoke;
 
-		private static readonly ManualLogSource unhollowerLogSource = Logger.CreateLogSource("Unhollower");
-
 		private static FastNativeDetour RuntimeInvokeDetour { get; set; }
 
 		private static IL2CPPChainloader Instance { get; set; }
@@ -132,12 +130,6 @@ namespace BepInEx.IL2CPP
 
 			Logger.Sources.Add(UnityLogSource);
 
-
-			UnhollowerBaseLib.LogSupport.InfoHandler += unhollowerLogSource.LogInfo;
-			UnhollowerBaseLib.LogSupport.WarningHandler += unhollowerLogSource.LogWarning;
-			UnhollowerBaseLib.LogSupport.TraceHandler += unhollowerLogSource.LogDebug;
-			UnhollowerBaseLib.LogSupport.ErrorHandler += unhollowerLogSource.LogError;
-
 			base.InitializeLoggers();
 
 
@@ -147,11 +139,23 @@ namespace BepInEx.IL2CPP
 			//}
 
 
+
+
+			// Temporarily disable the console log listener as we replay the preloader logs
+
+			var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener);
+
+			if (logListener != null)
+				Logger.Listeners.Remove(logListener);
+
 			foreach (var preloaderLogEvent in PreloaderConsoleListener.LogEvents)
 			{
 				PreloaderLogger.Log.Log(preloaderLogEvent.Level, preloaderLogEvent.Data);
 			}
 
+			if (logListener != null)
+				Logger.Listeners.Add(logListener);
+
 
 			//UnityEngine.Application.s_LogCallbackHandler = DelegateSupport.ConvertDelegate<Application.LogCallback>(new Action<string>(UnityLogCallback));
 			//UnityEngine.Application.s_LogCallbackHandler = (Application.LogCallback)new Action<string>(UnityLogCallback);

+ 23 - 4
BepInEx.IL2CPP/Preloader.cs

@@ -12,7 +12,8 @@ namespace BepInEx.IL2CPP
 
 		private static PreloaderConsoleListener PreloaderLog { get; set; }
 
-		private static ManualLogSource Log => PreloaderLogger.Log;
+		internal static ManualLogSource Log => PreloaderLogger.Log;
+		internal static ManualLogSource UnhollowerLog { get; set; }
 
 		public static IL2CPPChainloader Chainloader { get; private set; }
 
@@ -20,14 +21,18 @@ namespace BepInEx.IL2CPP
 		{
 			try
 			{
-
-				PreloaderLog = new PreloaderConsoleListener(false);
+				PreloaderLog = new PreloaderConsoleListener(true);
 				Logger.Listeners.Add(PreloaderLog);
 
 
-				BasicLogInfo.PrintLogInfo(Log);
 
+				if (ConsoleManager.ConfigConsoleEnabled.Value && !ConsoleManager.ConsoleActive)
+				{
+					ConsoleManager.CreateConsole();
+					Logger.Listeners.Add(new ConsoleLogListener());
+				}
 
+				BasicLogInfo.PrintLogInfo(Log);
 
 				Log.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
 
@@ -35,9 +40,23 @@ namespace BepInEx.IL2CPP
 				Log.LogDebug($"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
 				Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
 
+
+
+				UnhollowerLog = Logger.CreateLogSource("Unhollower");
+				UnhollowerBaseLib.LogSupport.InfoHandler += UnhollowerLog.LogInfo;
+				UnhollowerBaseLib.LogSupport.WarningHandler += UnhollowerLog.LogWarning;
+				UnhollowerBaseLib.LogSupport.TraceHandler += UnhollowerLog.LogDebug;
+				UnhollowerBaseLib.LogSupport.ErrorHandler += UnhollowerLog.LogError;
+
+
+				if (ProxyAssemblyGenerator.CheckIfGenerationRequired())
+					ProxyAssemblyGenerator.GenerateAssemblies();
+
+
 				Logger.Listeners.Remove(PreloaderLog);
 
 
+
 				Chainloader = new IL2CPPChainloader();
 
 				Chainloader.Initialize();

+ 149 - 0
BepInEx.IL2CPP/ProxyAssemblyGenerator.cs

@@ -0,0 +1,149 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using AssemblyUnhollower;
+using BepInEx.Logging;
+using Il2CppDumper;
+
+namespace BepInEx.IL2CPP
+{
+	internal static class ProxyAssemblyGenerator
+	{
+		public static string GameAssemblyPath => Path.Combine(Paths.GameRootPath, "GameAssembly.dll");
+
+		private static string HashPath => Path.Combine(Preloader.IL2CPPUnhollowedPath, "assembly-hash.txt");
+
+		private static string tempDumperDirectory => Path.Combine(Preloader.IL2CPPUnhollowedPath, "temp");
+
+		private static ManualLogSource Il2cppDumperLogger = Logger.CreateLogSource("Il2CppDumper");
+
+
+		private static string ComputeGameAssemblyHash()
+		{
+			using var md5 = MD5.Create();
+			using var assemblyStream = File.OpenRead(GameAssemblyPath);
+
+			var hash = md5.ComputeHash(assemblyStream);
+
+			return Utility.ByteArrayToString(hash);
+		}
+
+		public static bool CheckIfGenerationRequired()
+		{
+			if (!Directory.Exists(Preloader.IL2CPPUnhollowedPath))
+				return true;
+
+			if (!File.Exists(HashPath))
+				return true;
+
+			if (ComputeGameAssemblyHash() != File.ReadAllText(HashPath))
+			{
+				Preloader.Log.LogInfo("Detected a game update, will regenerate proxy assemblies");
+				return true;
+			}
+
+			return false;
+		}
+
+		public static void GenerateAssemblies()
+		{
+			var domain = AppDomain.CreateDomain("GeneratorDomain", null, new AppDomainSetup
+			{
+				ApplicationBase = Paths.BepInExAssemblyDirectory
+			});
+
+			var runner = (AppDomainRunner)domain.CreateInstanceAndUnwrap(typeof(AppDomainRunner).Assembly.FullName, typeof(AppDomainRunner).FullName);
+			
+			runner.Setup(Paths.ExecutablePath, Preloader.IL2CPPUnhollowedPath);
+			runner.GenerateAssembliesInternal(new AppDomainListener());
+
+			AppDomain.Unload(domain);
+
+			Directory.Delete(tempDumperDirectory, true);
+
+			File.WriteAllText(HashPath, ComputeGameAssemblyHash());
+		}
+
+		[Serializable]
+		private class AppDomainListener : MarshalByRefObject
+		{
+			public void DoPreloaderLog(object data, LogLevel level)
+			{
+				Preloader.Log.Log(level, data);
+			}
+
+			public void DoDumperLog(object data, LogLevel level)
+			{
+				Il2cppDumperLogger.Log(level, data);
+			}
+
+			public void DoUnhollowerLog(object data, LogLevel level)
+			{
+				Preloader.UnhollowerLog.Log(level, data);
+			}
+		}
+
+		[Serializable]
+		private class AppDomainRunner : MarshalByRefObject
+		{
+			public void Setup(string executablePath, string unhollowedPath)
+			{
+				Paths.SetExecutablePath(executablePath);
+				Preloader.IL2CPPUnhollowedPath = unhollowedPath;
+			}
+
+			public void GenerateAssembliesInternal(AppDomainListener listener)
+			{
+				listener.DoPreloaderLog("Generating Il2CppUnhollower assemblies", LogLevel.Message);
+
+				System.Threading.Thread.Sleep(5000);
+
+				if (Directory.Exists(Preloader.IL2CPPUnhollowedPath))
+					Directory.Delete(Preloader.IL2CPPUnhollowedPath, true);
+
+				Directory.CreateDirectory(Preloader.IL2CPPUnhollowedPath);
+
+				string tempDumperDirectory = Path.Combine(Preloader.IL2CPPUnhollowedPath, "temp");
+				Directory.CreateDirectory(tempDumperDirectory);
+
+
+
+
+				var dumperConfig = new Config
+				{
+					GenerateScript = false,
+					GenerateDummyDll = true
+				};
+
+				listener.DoPreloaderLog("Generating Il2CppDumper intermediate assemblies", LogLevel.Info);
+
+				Il2CppDumper.Il2CppDumper.PerformDump(GameAssemblyPath,
+					Path.Combine(Paths.GameRootPath, $"{Paths.ProcessName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"),
+					tempDumperDirectory,
+					dumperConfig,
+					s => listener.DoDumperLog(s, LogLevel.Debug));
+
+
+
+				listener.DoPreloaderLog("Executing Il2CppUnhollower generator", LogLevel.Info);
+
+				System.Threading.Thread.Sleep(5000);
+
+				UnhollowerBaseLib.LogSupport.InfoHandler += s => listener.DoUnhollowerLog(s, LogLevel.Info);
+				UnhollowerBaseLib.LogSupport.WarningHandler += s => listener.DoUnhollowerLog(s, LogLevel.Warning);
+				UnhollowerBaseLib.LogSupport.TraceHandler += s => listener.DoUnhollowerLog(s, LogLevel.Debug);
+				UnhollowerBaseLib.LogSupport.ErrorHandler += s => listener.DoUnhollowerLog(s, LogLevel.Error);
+
+				var unhollowerOptions = new UnhollowerOptions
+				{
+					GameAssemblyPath = GameAssemblyPath,
+					MscorlibPath = Path.Combine(Paths.GameRootPath, "mono", "Managed", "mscorlib.dll"),
+					SourceDir = Path.Combine(tempDumperDirectory, "DummyDll"),
+					OutputDir = Preloader.IL2CPPUnhollowedPath
+				};
+
+				AssemblyUnhollower.Program.Main(unhollowerOptions);
+			}
+		}
+	}
+}

BIN
lib/AssemblyUnhollower.exe


BIN
lib/Il2CppDumper.dll


BIN
lib/UnhollowerBaseLib.dll


BIN
lib/UnhollowerRuntimeLib.dll