Ver código fonte

Implement basic trampoline generator

Bepis 3 anos atrás
pai
commit
b5406ff440

+ 4 - 4
BepInEx.IL2CPP/BepInEx.IL2CPP.csproj

@@ -34,6 +34,9 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="Iced, Version=1.6.0.0, Culture=neutral, PublicKeyToken=5baba79f4264913b, processorArchitecture=MSIL">
+      <HintPath>..\packages\Iced.1.6.0\lib\net45\Iced.dll</HintPath>
+    </Reference>
     <Reference Include="Il2Cppmscorlib">
       <HintPath>..\lib\Il2Cppmscorlib.dll</HintPath>
       <Private>False</Private>
@@ -63,10 +66,6 @@
       <HintPath>..\lib\UnhollowerBaseLib.dll</HintPath>
       <Private>False</Private>
     </Reference>
-    <Reference Include="UnhollowerRuntimeLib">
-      <HintPath>..\lib\UnhollowerRuntimeLib.dll</HintPath>
-      <Private>False</Private>
-    </Reference>
     <Reference Include="UnityEngine-IL2CPP">
       <HintPath>..\lib\UnityEngine-IL2CPP.dll</HintPath>
       <Private>False</Private>
@@ -83,6 +82,7 @@
     <Compile Include="IL2CPPChainloader.cs" />
     <Compile Include="Preloader.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TrampolineGenerator.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\BepInEx.Core\BepInEx.Core.csproj">

+ 112 - 39
BepInEx.IL2CPP/IL2CPPChainloader.cs

@@ -8,8 +8,8 @@ using BepInEx.Bootstrap;
 using BepInEx.Logging;
 using BepInEx.Preloader.Core;
 using BepInEx.Preloader.Core.Logging;
+using Iced.Intel;
 using MonoMod.RuntimeDetour;
-using MonoMod.Utils;
 using UnhollowerBaseLib.Runtime;
 using UnhollowerRuntimeLib;
 using UnityEngine;
@@ -21,74 +21,146 @@ namespace BepInEx.IL2CPP
 	{
 		private static ManualLogSource UnityLogSource = new ManualLogSource("Unity");
 
-		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
-		private delegate void UnityLogCallbackDelegate([In] [MarshalAs(UnmanagedType.LPStr)] string log);
-		private static void UnityLogCallback([In] [MarshalAs(UnmanagedType.LPStr)] string log)
+		public static void UnityLogCallback(string logLine, string exception, LogType type)
 		{
-			UnityLogSource.LogInfo(log.Trim());
+			UnityLogSource.LogInfo(logLine.Trim());
 		}
 
-		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+
+//		public const CallingConvention ArchConvention =
+//#if X64
+//			CallingConvention.Stdcall;
+//#else
+//			CallingConvention.Cdecl;
+//#endif
+
+		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
 		private delegate IntPtr RuntimeInvokeDetour(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc);
 
 		[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
 		private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
 
 		private static RuntimeInvokeDetour originalInvoke;
-		
-		public unsafe IL2CPPChainloader()
+
+		private static ManualLogSource unhollowerLogSource = Logger.CreateLogSource("Unhollower");
+
+		private class DetourHandler : UnhollowerRuntimeLib.ManagedDetour
 		{
-			UnityVersionHandler.Initialize(2019, 3, 15);
-			File.AppendAllText("log.log", "Initialized unhollower\n");
-			ClassInjector.DoHook = (ptr, intPtr) =>
+			public T Detour<T>(IntPtr from, T to) where T : Delegate
 			{
-				var detour = new NativeDetour(new IntPtr(*((int**)ptr)), intPtr);
+				var detour = new NativeDetour(from, to, new NativeDetourConfig { ManualApply = true });
+
+				var trampoline = detour.GenerateTrampoline<T>();
+
 				detour.Apply();
-			};
 
-			foreach (var processModule in Process.GetCurrentProcess().Modules.Cast<ProcessModule>())
+				return trampoline;
+			}
+		}
+
+		private static void Disassemble(ManualLogSource logSource, IntPtr memoryPtr, int size)
+		{
+			byte[] data = new byte[size];
+			Marshal.Copy(memoryPtr, data, 0, size);
+
+			var formatter = new NasmFormatter();
+			var output = new StringOutput();
+			var codeReader = new ByteArrayCodeReader(data);
+			var decoder = Decoder.Create(64, codeReader);
+			decoder.IP = (ulong)memoryPtr.ToInt64();
+			while (codeReader.CanReadByte)
 			{
-				File.AppendAllText("wew.log", $"{processModule.ModuleName}\n");
+				decoder.Decode(out var instr);
+				formatter.Format(instr, output);
+				logSource.LogDebug($"{instr.IP:X16} {output.ToStringAndReset()}");
 			}
-			
+		}
+
+		public override unsafe void Initialize(string gameExePath = null)
+		{
+			base.Initialize(gameExePath);
+
+			UnityVersionHandler.Initialize(2019, 2, 17);
+
+			// One or the other here for Unhollower to work correctly
+
+			////////ClassInjector.Detour = new DetourHandler();
+
+			ClassInjector.DoHook = (ptr, intPtr) =>
+			{
+				IntPtr originalFunc = new IntPtr(*(void**)ptr);
+
+				unhollowerLogSource.LogDebug($"DoHook 0x{originalFunc.ToString("X")} -> 0x{intPtr.ToString("X")}");
+
+				var trampolineAlloc = DetourHelper.Native.MemAlloc(80);
+				DetourHelper.Native.MakeExecutable(trampolineAlloc, 80);
+				DetourHelper.Native.MakeWritable(originalFunc, 24);
+
+				unhollowerLogSource.LogDebug($"Trampoline allocation: 0x{ptr.ToString("X")}");
+
+
+				unhollowerLogSource.LogDebug("Original (24) asm");
+
+
+				Disassemble(unhollowerLogSource, originalFunc, 24);
+
+
+				var trampolineLength = TrampolineGenerator.Generate(originalFunc, intPtr, trampolineAlloc,
+					IntPtr.Size == 8 ? 64 : 32);
+
+
+				unhollowerLogSource.LogDebug("Modified (24) asm");
+
+				Disassemble(unhollowerLogSource, originalFunc, 24);
+
+				unhollowerLogSource.LogDebug($"Trampoline ({trampolineLength}) asm");
+
+				Disassemble(unhollowerLogSource, trampolineAlloc, trampolineLength);
+
+				*(void**)ptr = (void*)trampolineAlloc;
+			};
+
 			var gameAssemblyModule = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(x => x.ModuleName.Contains("GameAssembly"));
-			File.AppendAllText("wew.log", $"Got module: {gameAssemblyModule.ModuleName}; addr: {gameAssemblyModule.BaseAddress}\n");
+
 			var functionPtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); //DynDll.GetFunction(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke");
 
-			File.AppendAllText("wew.log", $"Got fptr: {functionPtr}\n");
-			
-			// RuntimeInvokeDetour invokeHook = (method, obj, parameters, exc) =>
-			// {
-			// 	// UnityLogSource.LogInfo(Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method)));
-			// 	return originalInvoke(method, obj, parameters, exc);
-			// };
-			// UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method)
 
-			var invokeDetour = new NativeDetour(functionPtr, Marshal.GetFunctionPointerForDelegate(new RuntimeInvokeDetour(OnInvokeMethod)), new NativeDetourConfig {ManualApply = true});
+			PreloaderLogger.Log.LogDebug($"Runtime invoke pointer: 0x{functionPtr.ToInt64():X}");
+
+			var invokeDetour = new NativeDetour(functionPtr, Marshal.GetFunctionPointerForDelegate(new RuntimeInvokeDetour(OnInvokeMethod)), new NativeDetourConfig { ManualApply = true });
 
-			File.AppendAllText("log.log", "Got detour\n");
 			originalInvoke = invokeDetour.GenerateTrampoline<RuntimeInvokeDetour>();
-			File.AppendAllText("log.log", "Got trampoline\n");
-			
+
 			invokeDetour.Apply();
-			File.AppendAllText("log.log", "Applied!\n");
 		}
 
+		private static bool HasSet = false;
+
 		private static IntPtr OnInvokeMethod(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc)
 		{
-			lock (originalInvoke)
+			string methodName = Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method));
+
+			if (!HasSet && methodName == "Internal_ActiveSceneChanged")
 			{
 				try
 				{
-					File.AppendAllText("log.log", $"Got call: {Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method))}\n");
+					UnityEngine.Application.s_LogCallbackHandler = new Action<string, string, LogType>(UnityLogCallback);
+
+					UnityLogSource.LogMessage($"callback set - {methodName}");
+
+					UnityEngine.Application.CallLogCallback("test from OnInvokeMethod", "", LogType.Log, true);
 				}
-				catch (Exception e)
+				catch (Exception ex)
 				{
-					File.AppendAllText("err.log", e.ToString() + "\n");
+					UnityLogSource.LogError(ex);
 				}
-			
-				return originalInvoke(method, obj, parameters, exc);
+
+				HasSet = true;
 			}
+
+			//UnityLogSource.LogDebug(methodName);
+
+			return originalInvoke(method, obj, parameters, exc);
 		}
 
 		protected override void InitializeLoggers()
@@ -101,7 +173,6 @@ namespace BepInEx.IL2CPP
 			Logger.Sources.Add(UnityLogSource);
 
 
-			ManualLogSource unhollowerLogSource = Logger.CreateLogSource("Unhollower");
 
 			UnhollowerBaseLib.LogSupport.InfoHandler += unhollowerLogSource.LogInfo;
 			UnhollowerBaseLib.LogSupport.WarningHandler += unhollowerLogSource.LogWarning;
@@ -122,10 +193,12 @@ namespace BepInEx.IL2CPP
 				PreloaderLogger.Log.Log(preloaderLogEvent.Level, preloaderLogEvent.Data);
 			}
 
+
 			//UnityEngine.Application.s_LogCallbackHandler = DelegateSupport.ConvertDelegate<Application.LogCallback>(new Action<string>(UnityLogCallback));
 			//UnityEngine.Application.s_LogCallbackHandler = (Application.LogCallback)new Action<string>(UnityLogCallback);
-			var loggerPointer = Marshal.GetFunctionPointerForDelegate(new UnityLogCallbackDelegate(UnityLogCallback));
-			UnhollowerBaseLib.IL2CPP.il2cpp_register_log_callback(loggerPointer);
+
+			//var loggerPointer = Marshal.GetFunctionPointerForDelegate(new UnityLogCallbackDelegate(UnityLogCallback));
+			//UnhollowerBaseLib.IL2CPP.il2cpp_register_log_callback(loggerPointer);
 		}
 
 		public override BasePlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly)

+ 25 - 14
BepInEx.IL2CPP/Preloader.cs

@@ -1,4 +1,5 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
 using BepInEx.Logging;
 using BepInEx.Preloader.Core;
 using BepInEx.Preloader.Core.Logging;
@@ -17,30 +18,40 @@ namespace BepInEx.IL2CPP
 
 		public static void Run()
 		{
-			PreloaderLog = new PreloaderConsoleListener(false);
-			Logger.Listeners.Add(PreloaderLog);
+			try
+			{
 
+				PreloaderLog = new PreloaderConsoleListener(false);
+				Logger.Listeners.Add(PreloaderLog);
 
-			BasicLogInfo.PrintLogInfo(Log);
 
+				BasicLogInfo.PrintLogInfo(Log);
 
 
-			Log.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
-			//Log.LogInfo($"CLR runtime version: {Environment.Version}");
-			//Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
 
-			Log.LogDebug($"Game executable path: {Paths.ExecutablePath}");
-			Log.LogDebug($"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
-			Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
+				Log.LogInfo($"Running under Unity v{FileVersionInfo.GetVersionInfo(Paths.ExecutablePath).FileVersion}");
+				//Log.LogInfo($"CLR runtime version: {Environment.Version}");
+				//Log.LogInfo($"Supports SRE: {Utility.CLRSupportsDynamicAssemblies}");
 
-			Logger.Listeners.Remove(PreloaderLog);
+				Log.LogDebug($"Game executable path: {Paths.ExecutablePath}");
+				Log.LogDebug($"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
+				Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
 
+				Logger.Listeners.Remove(PreloaderLog);
 
-			Chainloader = new IL2CPPChainloader();
 
-			Chainloader.Initialize();
+				Chainloader = new IL2CPPChainloader();
 
-			Chainloader.Execute();
+				Chainloader.Initialize();
+
+				Chainloader.Execute();
+			}
+			catch (Exception ex)
+			{
+				Log.LogFatal(ex);
+
+				throw;
+			}
 		}
 	}
 }

+ 2 - 2
BepInEx.IL2CPP/Properties/AssemblyInfo.cs

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

+ 115 - 0
BepInEx.IL2CPP/TrampolineGenerator.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Iced.Intel;
+
+namespace BepInEx.IL2CPP
+{
+	public static class TrampolineGenerator
+	{
+		public static int Generate(IntPtr originalFunctionPtr, IntPtr patchedFunctionPtr, IntPtr trampolineFunctionPtr, int bitness)
+		{
+			byte[] instructionBuffer = new byte[80];
+
+			Marshal.Copy(originalFunctionPtr, instructionBuffer, 0, 80);
+
+			// Decode original function up until we go past the needed bytes to write the jump to patchedFunctionPtr
+
+			// TODO: set this per arch, and implement x86 E9 jmp
+			const uint requiredBytes = 10 + 2;
+
+			var codeReader = new ByteArrayCodeReader(instructionBuffer);
+			var decoder = Decoder.Create(bitness, codeReader);
+			decoder.IP = (ulong)originalFunctionPtr.ToInt64();
+
+			uint totalBytes = 0;
+			var origInstructions = new InstructionList();
+			while (codeReader.CanReadByte)
+			{
+				decoder.Decode(out var instr);
+				origInstructions.Add(instr);
+				totalBytes += (uint)instr.Length;
+				if (instr.Code == Code.INVALID)
+					throw new Exception("Found garbage");
+				if (totalBytes >= requiredBytes)
+					break;
+
+				switch (instr.FlowControl)
+				{
+					case FlowControl.Next:
+						break;
+
+					case FlowControl.UnconditionalBranch:
+						if (instr.Op0Kind == OpKind.NearBranch64)
+						{
+							var target = instr.NearBranchTarget;
+						}
+						goto default;
+
+					case FlowControl.IndirectBranch:// eg. jmp reg/mem
+					case FlowControl.ConditionalBranch:// eg. je, jno, etc
+					case FlowControl.Return:// eg. ret
+					case FlowControl.Call:// eg. call method
+					case FlowControl.IndirectCall:// eg. call reg/mem
+					case FlowControl.Interrupt:// eg. int n
+					case FlowControl.XbeginXabortXend:
+					case FlowControl.Exception:// eg. ud0
+					default:
+						throw new Exception("Not supported by this simple example - " + instr.FlowControl);
+				}
+			}
+			if (totalBytes < requiredBytes)
+				throw new Exception("Not enough bytes!");
+
+			if (origInstructions.Count == 0)
+				throw new Exception("Not enough instructions!");
+
+
+			ref readonly var lastInstr = ref origInstructions[origInstructions.Count - 1];
+
+			origInstructions.Add(Instruction.CreateBranch(Code.Jmp_rel32_64, lastInstr.NextIP));
+
+
+			// Generate trampoline from instruction list
+
+			var codeWriter = new CodeWriterImpl();
+			ulong relocatedBaseAddress = (ulong)trampolineFunctionPtr;
+			var block = new InstructionBlock(codeWriter, origInstructions, relocatedBaseAddress);
+
+			bool success = BlockEncoder.TryEncode(decoder.Bitness, block, out var errorMessage, out var result);
+			if (!success)
+			{
+				throw new Exception(errorMessage);
+			}
+
+			// Write generated trampoline
+			
+			var newCode = codeWriter.ToArray();
+			Marshal.Copy(newCode, 0, trampolineFunctionPtr, newCode.Length);
+
+
+			// Overwrite the start of trampolineFunctionPtr with a jump to patchedFunctionPtr
+
+			var jmpCode = new byte[requiredBytes];
+			jmpCode[0] = 0x48;// \ 'MOV RAX,imm64'
+			jmpCode[1] = 0xB8;// /
+			ulong v = (ulong)patchedFunctionPtr.ToInt64();
+			for (int i = 0; i < 8; i++, v >>= 8)
+				jmpCode[2 + i] = (byte)v;
+			jmpCode[10] = 0xFF;// \ JMP RAX
+			jmpCode[11] = 0xE0;// /
+
+			Marshal.Copy(jmpCode, 0, originalFunctionPtr, (int)requiredBytes);
+
+			return newCode.Length;
+		}
+
+
+		private sealed class CodeWriterImpl : CodeWriter
+		{
+			readonly List<byte> allBytes = new List<byte>();
+			public override void WriteByte(byte value) => allBytes.Add(value);
+			public byte[] ToArray() => allBytes.ToArray();
+		}
+	}
+}

+ 1 - 0
BepInEx.IL2CPP/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="Iced" version="1.6.0" targetFramework="net472" />
   <package id="Mono.Cecil" version="0.11.2" targetFramework="net472" />
   <package id="MonoMod.RuntimeDetour" version="20.5.21.5" targetFramework="net472" />
   <package id="MonoMod.Utils" version="20.5.21.5" targetFramework="net472" />

+ 9 - 1
BepInExTests/BepInExTests.csproj

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>BepInExTests</RootNamespace>
     <AssemblyName>BepInExTests</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
     <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
@@ -45,6 +45,9 @@
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="Iced, Version=1.6.0.0, Culture=neutral, PublicKeyToken=5baba79f4264913b, processorArchitecture=MSIL">
+      <HintPath>..\packages\Iced.1.6.0\lib\net45\Iced.dll</HintPath>
+    </Reference>
     <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
       <HintPath>..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
     </Reference>
@@ -66,6 +69,7 @@
   </Choose>
   <ItemGroup>
     <Compile Include="AssemblyInit.cs" />
+    <Compile Include="TrampolineTests.cs" />
     <Compile Include="Configuration\ConfigFileTests.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
@@ -77,6 +81,10 @@
       <Project>{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}</Project>
       <Name>BepInEx.Core</Name>
     </ProjectReference>
+    <ProjectReference Include="..\BepInEx.IL2CPP\BepInEx.IL2CPP.csproj">
+      <Project>{429d5f83-7fec-4d09-ab82-e868eb61af59}</Project>
+      <Name>BepInEx.IL2CPP</Name>
+    </ProjectReference>
     <ProjectReference Include="..\BepInEx.Unity\BepInEx.Unity.csproj">
       <Project>{EAE9FAE6-8011-45A3-8B6E-0C7F14210533}</Project>
       <Name>BepInEx.Unity</Name>

+ 72 - 0
BepInExTests/TrampolineTests.cs

@@ -0,0 +1,72 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Runtime.InteropServices;
+using BepInEx.IL2CPP;
+using Iced.Intel;
+
+namespace BepInEx.Tests
+{
+	[TestClass]
+	public class TrampolineTests
+	{
+		[TestMethod]
+		public void TrampolineTest()
+		{
+			byte[] exampleCode = new byte[] {
+				0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18, 0x55, 0x57, 0x41, 0x56, 0x48, 0x8D,
+				0xAC, 0x24, 0x00, 0xFF, 0xFF, 0xFF, 0x48, 0x81, 0xEC, 0x00, 0x02, 0x00, 0x00, 0x48, 0x8B, 0x05,
+				0x18, 0x57, 0x0A, 0x00, 0x48, 0x33, 0xC4, 0x48, 0x89, 0x85, 0xF0, 0x00, 0x00, 0x00, 0x4C, 0x8B,
+				0x05, 0x2F, 0x24, 0x0A, 0x00, 0x48, 0x8D, 0x05, 0x78, 0x7C, 0x04, 0x00, 0x33, 0xFF
+			};
+
+			var exampleCodePointer = Marshal.AllocHGlobal(80);
+			var trampolineCodePointer = Marshal.AllocHGlobal(80);
+			Marshal.Copy(exampleCode, 0, exampleCodePointer, exampleCode.Length);
+
+
+			void Disassemble(byte[] data, ulong ip)
+			{
+				var formatter = new NasmFormatter();
+				var output = new StringOutput();
+				var codeReader = new ByteArrayCodeReader(data);
+				var decoder = Decoder.Create(64, codeReader);
+				decoder.IP = ip;
+				while (codeReader.CanReadByte)
+				{
+					decoder.Decode(out var instr);
+					formatter.Format(instr, output);
+					Console.WriteLine($"{instr.IP:X16} {output.ToStringAndReset()}");
+				}
+				Console.WriteLine();
+			}
+
+
+			Console.WriteLine("Original:");
+			Console.WriteLine();
+
+
+			Disassemble(exampleCode, (ulong)exampleCodePointer.ToInt64());
+
+
+			int trampolineLength = TrampolineGenerator.Generate(exampleCodePointer, new IntPtr(0xBEEF), trampolineCodePointer, 64);
+
+
+
+			Console.WriteLine();
+			Console.WriteLine("Trampoline:");
+			Console.WriteLine();
+
+			byte[] trampolineArray = new byte[trampolineLength];
+			Marshal.Copy(trampolineCodePointer, trampolineArray, 0, trampolineLength);
+
+			Disassemble(trampolineArray, (ulong)trampolineCodePointer.ToInt64());
+
+
+
+			Marshal.FreeHGlobal(exampleCodePointer);
+			Marshal.FreeHGlobal(trampolineCodePointer);
+
+			Assert.IsFalse(false);
+		}
+	}
+}

+ 1 - 0
BepInExTests/packages.config

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="Iced" version="1.6.0" targetFramework="net472" />
   <package id="MSTest.TestAdapter" version="1.3.2" targetFramework="net45" />
   <package id="MSTest.TestFramework" version="1.3.2" targetFramework="net45" />
 </packages>

BIN
lib/UnhollowerBaseLib.dll