mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-20 01:46:13 +00:00
Implement basic trampoline generator
This commit is contained in:
10 files changed
+342
-61
No files matched your search
@@ -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">
|
||||
|
||||
@@ -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");
|
||||
public T Detour<T>(IntPtr from, T to) where T : Delegate
|
||||
{
|
||||
var detour = new NativeDetour(from, to, new NativeDetourConfig { ManualApply = true });
|
||||
|
||||
var trampoline = detour.GenerateTrampoline<T>();
|
||||
|
||||
detour.Apply();
|
||||
|
||||
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)
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
var detour = new NativeDetour(new IntPtr(*((int**)ptr)), intPtr);
|
||||
detour.Apply();
|
||||
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;
|
||||
};
|
||||
|
||||
foreach (var processModule in Process.GetCurrentProcess().Modules.Cast<ProcessModule>())
|
||||
{
|
||||
File.AppendAllText("wew.log", $"{processModule.ModuleName}\n");
|
||||
}
|
||||
|
||||
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
@@ -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.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.LogDebug($"Game executable path: {Paths.ExecutablePath}");
|
||||
Log.LogDebug($"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
|
||||
Log.LogDebug($"BepInEx root path: {Paths.BepInExRootPath}");
|
||||
|
||||
Logger.Listeners.Remove(PreloaderLog);
|
||||
Logger.Listeners.Remove(PreloaderLog);
|
||||
|
||||
|
||||
Chainloader = new IL2CPPChainloader();
|
||||
Chainloader = new IL2CPPChainloader();
|
||||
|
||||
Chainloader.Initialize();
|
||||
Chainloader.Initialize();
|
||||
|
||||
Chainloader.Execute();
|
||||
Chainloader.Execute();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.LogFatal(ex);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
@@ -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,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,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>
|
||||
|
||||
@@ -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,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>
|
||||
Binary file not shown.
Reference in new issue
Block a user