mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-20 01:46:13 +00:00
Clean up after merge
This commit is contained in:
1 parent
83b72d568b
commit
6ca26f4b59
20 files changed
+318
-1005
No files matched your search
@@ -1,26 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Allocator;
|
||||
|
||||
internal class LinuxPageAllocator : UnixPageAllocator
|
||||
{
|
||||
protected override IEnumerable<(nint, nint)> MapMemoryAreas()
|
||||
{
|
||||
// Each row of /proc/self/maps is as follows:
|
||||
// <start_address>-<end_address> <perms> <offset> <dev> <inode> <owner_name>
|
||||
// More info: https://stackoverflow.com/a/1401595
|
||||
using var procMap = new StreamReader(File.OpenRead("/proc/self/maps"));
|
||||
|
||||
string line;
|
||||
while ((line = procMap.ReadLine()) != null)
|
||||
{
|
||||
var startIndex = line.IndexOf('-');
|
||||
var endIndex = line.IndexOf(' ');
|
||||
var startAddr = long.Parse(line[..startIndex], NumberStyles.HexNumber);
|
||||
var endAddr = long.Parse(line[(startIndex + 1)..endIndex], NumberStyles.HexNumber);
|
||||
yield return ((nint) startAddr, (nint) endAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Allocator;
|
||||
|
||||
internal class MacOsPageAllocator : UnixPageAllocator
|
||||
{
|
||||
protected override IEnumerable<(nint, nint)> MapMemoryAreas()
|
||||
{
|
||||
var info = new LibSystem.vm_region_basic_info_64();
|
||||
var infoCount = (uint) (Marshal.SizeOf<LibSystem.vm_region_basic_info_64>() / sizeof(int));
|
||||
var objectName = 0u;
|
||||
nint address = 0;
|
||||
nint size = 0;
|
||||
|
||||
while (LibSystem.vm_region_64(LibSystem.TaskSelf, ref address, ref size, LibSystem.VM_REGION_BASIC_INFO_64,
|
||||
ref info, ref infoCount, ref objectName) == LibSystem.KERN_SUCCESS)
|
||||
{
|
||||
var start = address;
|
||||
var end = address + size;
|
||||
address = end;
|
||||
yield return (start, end);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LibSystem
|
||||
{
|
||||
public const int VM_REGION_BASIC_INFO_64 = 9;
|
||||
public const int KERN_SUCCESS = 0;
|
||||
public static readonly nint TaskSelf;
|
||||
|
||||
static LibSystem()
|
||||
{
|
||||
typeof(LibSystem).ResolveDynDllImports(new Dictionary<string, List<DynDllMapping>>
|
||||
{
|
||||
["libSystem"] = new()
|
||||
{
|
||||
"/usr/lib/libSystem.dylib" // OSX POSIX
|
||||
}
|
||||
});
|
||||
|
||||
var libsystem = DynDll.OpenLibrary("/usr/lib/libSystem.dylib");
|
||||
TaskSelf = libsystem
|
||||
.GetFunction("mach_task_self_"); // This isn't a function but rather an exported symbol
|
||||
}
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct vm_region_basic_info_64
|
||||
{
|
||||
public readonly int protection;
|
||||
public readonly int max_protection;
|
||||
public readonly uint inheritance;
|
||||
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public readonly bool shared;
|
||||
|
||||
[MarshalAs(UnmanagedType.I4)]
|
||||
public readonly bool reserved;
|
||||
|
||||
public readonly ulong offset;
|
||||
public readonly int behavior;
|
||||
public readonly ushort user_wired_count;
|
||||
}
|
||||
// ReSharper restore InconsistentNaming
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable 649 // Set by MonoMod
|
||||
[DynDllImport("libSystem")]
|
||||
public static vm_region_64Delegate vm_region_64;
|
||||
#pragma warning restore 649
|
||||
|
||||
public delegate int vm_region_64Delegate(nint target_task,
|
||||
ref nint address,
|
||||
ref nint size,
|
||||
int flavor,
|
||||
ref vm_region_basic_info_64 info,
|
||||
ref uint infoCnt,
|
||||
ref uint object_name);
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Allocator;
|
||||
|
||||
internal class PageAllocatorException : Exception
|
||||
{
|
||||
public PageAllocatorException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A general purpose page allocator for patching purposes.
|
||||
/// Allows to allocate pages (4k memory chunks) within the 1GB radius of a given address.
|
||||
/// </summary>
|
||||
/// <remarks>Based on https://github.com/kubo/funchook</remarks>
|
||||
internal abstract class PageAllocator
|
||||
{
|
||||
/// <summary>
|
||||
/// Common page size on Unix and Windows (4k).
|
||||
/// Call to <see cref="Allocate" /> will allocate a single page of this size.
|
||||
/// </summary>
|
||||
public const int PAGE_SIZE = 0x1000;
|
||||
|
||||
/// <summary>
|
||||
/// Allocation granularity on Windows (but can be reused in other implementations).
|
||||
/// </summary>
|
||||
protected const int ALLOCATION_UNIT = 0x10000;
|
||||
|
||||
protected const int PAGES_PER_UNIT = ALLOCATION_UNIT / PAGE_SIZE;
|
||||
|
||||
private static PageAllocator instance;
|
||||
|
||||
private readonly List<PageChunk> allocatedChunks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Platform-specific instance of page allocator.
|
||||
/// </summary>
|
||||
public static PageAllocator Instance => instance ??= Init();
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a single 64k chunk of memory near the given address
|
||||
/// </summary>
|
||||
/// <param name="hint">Address near which to attempt allocate the chunk</param>
|
||||
/// <returns>Allocated chunk</returns>
|
||||
/// <exception cref="PageAllocatorException">Allocation failed</exception>
|
||||
protected abstract nint AllocateChunk(nint hint);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a single page of size <see cref="PAGE_SIZE" /> near the provided address.
|
||||
/// Attempts to allocate the page within the +-1GB region of the hinted address.
|
||||
/// </summary>
|
||||
/// <param name="hint">Address near which to attempt to allocate the page.</param>
|
||||
/// <returns>Address to the allocated page.</returns>
|
||||
public virtual nint Allocate(nint hint)
|
||||
{
|
||||
foreach (var allocatedChunk in allocatedChunks)
|
||||
{
|
||||
// Small shortcut to speed up page lookup
|
||||
if (allocatedChunk.UsedPages == PAGES_PER_UNIT)
|
||||
continue;
|
||||
for (var i = 0; i < allocatedChunk.Pages.Length; i++)
|
||||
{
|
||||
if (allocatedChunk.Pages[i])
|
||||
continue;
|
||||
var pageAddr = allocatedChunk.GetPage(i);
|
||||
if (!IsInRelJmpRange(hint, pageAddr))
|
||||
continue;
|
||||
allocatedChunk.Pages[i] = true;
|
||||
allocatedChunk.UsedPages++;
|
||||
return pageAddr;
|
||||
}
|
||||
}
|
||||
|
||||
var chunk = new PageChunk
|
||||
{
|
||||
BaseAddress = AllocateChunk(hint)
|
||||
};
|
||||
allocatedChunks.Add(chunk);
|
||||
chunk.Pages[0] = true;
|
||||
chunk.UsedPages++;
|
||||
return chunk.BaseAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the page allocated with <see cref="Allocate" />
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
public void Free(nint page)
|
||||
{
|
||||
foreach (var allocatedChunk in allocatedChunks)
|
||||
{
|
||||
long index = (page - allocatedChunk.BaseAddress) / PAGE_SIZE;
|
||||
if (index < 0 || index >= PAGES_PER_UNIT)
|
||||
continue;
|
||||
if (!allocatedChunk.Pages[index])
|
||||
return;
|
||||
allocatedChunk.Pages[index] = false;
|
||||
allocatedChunk.UsedPages--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given address is within the relative jump range.
|
||||
/// </summary>
|
||||
/// <param name="src">Source address to jump from.</param>
|
||||
/// <param name="dst">Destination address to jump to.</param>
|
||||
/// <returns>True, if the distance between the addresses is within the relative jump range (usually 1GB), otherwise false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsInRelJmpRange(nint src, nint dst) => dst - src is <= int.MaxValue and >= int.MinValue;
|
||||
|
||||
private static PageAllocator Init() =>
|
||||
PlatformHelper.Current switch
|
||||
{
|
||||
var v when v.Is(Platform.Windows) => new WindowsPageAllocator(),
|
||||
var v when v.Is(Platform.Linux) => new LinuxPageAllocator(),
|
||||
var v when v.Is(Platform.MacOS) => new MacOsPageAllocator(),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
private class PageChunk
|
||||
{
|
||||
public readonly bool[] Pages = new bool[PAGES_PER_UNIT];
|
||||
public nint BaseAddress;
|
||||
public int UsedPages;
|
||||
|
||||
public nint GetPage(int index) => BaseAddress + index * PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PlatformExt
|
||||
{
|
||||
public static bool Is(this Platform pl, Platform val) => (pl & val) == val;
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Allocator;
|
||||
|
||||
/// <summary>
|
||||
/// Based on https://github.com/kubo/funchook
|
||||
/// </summary>
|
||||
internal abstract class UnixPageAllocator : PageAllocator
|
||||
{
|
||||
protected abstract IEnumerable<(nint, nint)> MapMemoryAreas();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CheckFreeRegionBefore(nint start, nint hint, ref (nint Start, nint End) region)
|
||||
{
|
||||
if (start < hint)
|
||||
{
|
||||
var addr = start - PAGE_SIZE;
|
||||
if (hint - addr < int.MaxValue)
|
||||
region.Start = addr;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool CheckFreeRegionAfter(nint end, nint hint, ref (nint Start, nint End) region)
|
||||
{
|
||||
if (hint < end)
|
||||
{
|
||||
if (end - hint < int.MaxValue)
|
||||
region.End = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private (nint, nint) GetFreeArea(nint hint)
|
||||
{
|
||||
(nint, nint) result = (0, 0);
|
||||
nint prevEnd = 0;
|
||||
|
||||
foreach (var (start, end) in MapMemoryAreas())
|
||||
{
|
||||
if (prevEnd + PAGE_SIZE <= start)
|
||||
if (CheckFreeRegionBefore(start, hint, ref result) ||
|
||||
CheckFreeRegionAfter(prevEnd, hint, ref result))
|
||||
return result;
|
||||
prevEnd = end;
|
||||
}
|
||||
|
||||
if (CheckFreeRegionAfter(prevEnd, hint, ref result))
|
||||
return result;
|
||||
throw new PageAllocatorException($"Could not find free region near {(long) hint:X8}");
|
||||
}
|
||||
|
||||
protected override nint AllocateChunk(nint hint)
|
||||
{
|
||||
/* From https://github.com/kubo/funchook/blob/master/src/funchook_unix.c#L251-L254:
|
||||
* Loop three times just to avoid rare cases such as
|
||||
* unused memory region is used between 'get_free_address()'
|
||||
* and 'mmap()'.
|
||||
*/
|
||||
const int retryCount = 3;
|
||||
|
||||
for (var attempt = 0; attempt < retryCount; attempt++)
|
||||
{
|
||||
var (start, end) = GetFreeArea(hint);
|
||||
// Try to allocate to end (after original method) first, then try before
|
||||
var addrs = new[] { end, start };
|
||||
foreach (var addr in addrs)
|
||||
{
|
||||
if (addr == 0)
|
||||
continue;
|
||||
var result = Unix.mmap(addr, PAGE_SIZE, Unix.Protection.PROT_READ | Unix.Protection.PROT_WRITE,
|
||||
Unix.MapFlags.MAP_PRIVATE | Unix.MapFlags.MAP_ANONYMOUS, -1, 0);
|
||||
if (result == addr)
|
||||
return result;
|
||||
if (result == Unix.MAP_FAILED)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error()); // Yes, this should work on unix too
|
||||
Unix.munmap(result, PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
throw new PageAllocatorException("Failed to allocate memory in unused regions");
|
||||
}
|
||||
|
||||
private static class Unix
|
||||
{
|
||||
public delegate nint mmapDelegate(nint addr,
|
||||
nuint length,
|
||||
Protection prot,
|
||||
MapFlags flags,
|
||||
int fd,
|
||||
int offset);
|
||||
|
||||
public delegate int munmapDelegate(nint addr, nuint length);
|
||||
|
||||
[Flags]
|
||||
public enum MapFlags
|
||||
{
|
||||
MAP_PRIVATE = 0x02,
|
||||
MAP_ANONYMOUS = 0x20
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Protection
|
||||
{
|
||||
PROT_READ = 0x1,
|
||||
PROT_WRITE = 0x2
|
||||
}
|
||||
|
||||
public static readonly nint MAP_FAILED = -1;
|
||||
|
||||
static Unix()
|
||||
{
|
||||
typeof(Unix).ResolveDynDllImports(new Dictionary<string, List<DynDllMapping>>
|
||||
{
|
||||
["libc"] = new()
|
||||
{
|
||||
"libc.so.6", // Ubuntu glibc
|
||||
"libc", // Linux glibc,
|
||||
"/usr/lib/libSystem.dylib" // OSX POSIX
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma warning disable 649 // Set by MonoMod
|
||||
[DynDllImport("mmap")]
|
||||
public static mmapDelegate mmap;
|
||||
|
||||
[DynDllImport("munmap")]
|
||||
public static munmapDelegate munmap;
|
||||
#pragma warning restore 649
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using BepInEx.Logging;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Allocator;
|
||||
|
||||
/// <summary>
|
||||
/// Based on https://github.com/kubo/funchook
|
||||
/// </summary>
|
||||
internal class WindowsPageAllocator : PageAllocator
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static nint RoundUp(nint num, nint unit) => (num + unit - 1) & ~(unit - 1);
|
||||
|
||||
private static Win32Exception Win32Error(string message = null, int code = -1)
|
||||
{
|
||||
var ex = code >= 0 ? new Win32Exception(code) : new Win32Exception(); // Generate error message
|
||||
return new Win32Exception(ex.NativeErrorCode,
|
||||
!string.IsNullOrWhiteSpace(message) ? $"{message}: {ex.Message}" : ex.Message);
|
||||
}
|
||||
|
||||
protected override nint AllocateChunk(nint hint)
|
||||
{
|
||||
nint chunk;
|
||||
while (true)
|
||||
{
|
||||
if (WinApi.VirtualQuery(hint, out var mbi) == 0)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug,
|
||||
$"Skipping analysing 0x{(long) hint:X8} because VirtualQuery failed: {Win32Error()}");
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (mbi.State == WinApi.PageState.MEM_FREE)
|
||||
{
|
||||
var nextAddress = RoundUp(mbi.BaseAddress, ALLOCATION_UNIT);
|
||||
var d = nextAddress - mbi.BaseAddress;
|
||||
if (d >= 0 && mbi.RegionSize - d >= ALLOCATION_UNIT)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug, $"Found free chunk at 0x{(long) nextAddress:X8}");
|
||||
hint = nextAddress;
|
||||
|
||||
chunk = WinApi.VirtualAlloc(hint, ALLOCATION_UNIT, WinApi.AllocationType.MEM_RESERVE,
|
||||
WinApi.ProtectConstant.PAGE_NOACCESS);
|
||||
if (chunk == 0)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug, $"Failed to reserve address: 0x{(long) hint:X8}: {Win32Error()}");
|
||||
goto next;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
next:
|
||||
hint = mbi.BaseAddress + mbi.RegionSize;
|
||||
}
|
||||
|
||||
var addr = WinApi.VirtualAlloc(chunk, PAGE_SIZE, WinApi.AllocationType.MEM_COMMIT,
|
||||
WinApi.ProtectConstant.PAGE_READWRITE);
|
||||
if (addr == 0)
|
||||
{
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
WinApi.VirtualFree(chunk, 0, WinApi.FreeType.MEM_RELEASE);
|
||||
throw
|
||||
Win32Error($"Failed to commit memory 0x{(long) addr:X8} for read-write access for 0x{(long) chunk:X8}",
|
||||
error);
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public override nint Allocate(nint hint)
|
||||
{
|
||||
var pageAddress = base.Allocate(hint);
|
||||
if (WinApi.VirtualAlloc(pageAddress, PAGE_SIZE, WinApi.AllocationType.MEM_COMMIT,
|
||||
WinApi.ProtectConstant.PAGE_READWRITE) == 0)
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
return pageAddress;
|
||||
}
|
||||
|
||||
private static class WinApi
|
||||
{
|
||||
[Flags]
|
||||
public enum AllocationType : uint
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
MEM_COMMIT = 0x00001000,
|
||||
|
||||
MEM_RESERVE = 0x00002000
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
|
||||
|
||||
[Flags]
|
||||
public enum FreeType : uint
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
MEM_RELEASE = 0x00008000
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
|
||||
public enum PageState : uint
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
MEM_FREE = 0x10000
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ProtectConstant : uint
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
PAGE_NOACCESS = 0x01,
|
||||
|
||||
PAGE_READWRITE = 0x04
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
|
||||
[DllImport("kernel32", EntryPoint = "VirtualQuery", SetLastError = true)]
|
||||
static extern int VirtualQuery32(nint lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
|
||||
|
||||
[DllImport("kernel32", EntryPoint = "VirtualQuery", SetLastError = true)]
|
||||
static extern int VirtualQuery64(nint lpAddress, ref MEMORY_BASIC_INFORMATION64 lpBuffer, int dwLength);
|
||||
|
||||
public static int VirtualQuery(nint lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer)
|
||||
{
|
||||
lpBuffer = new MEMORY_BASIC_INFORMATION();
|
||||
if (Environment.Is64BitProcess)
|
||||
{
|
||||
var mbi = new MEMORY_BASIC_INFORMATION64();
|
||||
var res = VirtualQuery64(lpAddress, ref mbi, Marshal.SizeOf<MEMORY_BASIC_INFORMATION64>());
|
||||
lpBuffer.BaseAddress = mbi.BaseAddress;
|
||||
lpBuffer.AllocationBase = mbi.AllocationBase;
|
||||
lpBuffer.AllocationProtect = mbi.AllocationProtect;
|
||||
lpBuffer.RegionSize = mbi.RegionSize;
|
||||
lpBuffer.State = mbi.State;
|
||||
lpBuffer.Protect = mbi.Protect;
|
||||
lpBuffer.Type = mbi.Type;
|
||||
return res;
|
||||
}
|
||||
|
||||
return VirtualQuery32(lpAddress, ref lpBuffer, Marshal.SizeOf<MEMORY_BASIC_INFORMATION>());
|
||||
}
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
public static extern nint VirtualAlloc(nint lpAddress,
|
||||
nuint dwSize,
|
||||
AllocationType flAllocationType,
|
||||
ProtectConstant flProtect);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
public static extern bool VirtualFree(nint lpAddress, nuint dwSize, FreeType dwFreeType);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public struct MEMORY_BASIC_INFORMATION
|
||||
{
|
||||
public nint BaseAddress;
|
||||
public nint AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public nint RegionSize;
|
||||
public PageState State;
|
||||
public uint Protect;
|
||||
public uint Type;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
struct MEMORY_BASIC_INFORMATION64
|
||||
{
|
||||
public nint BaseAddress;
|
||||
public nint AllocationBase;
|
||||
public uint AllocationProtect;
|
||||
public uint __alignment1;
|
||||
public nint RegionSize;
|
||||
public PageState State;
|
||||
public uint Protect;
|
||||
public uint Type;
|
||||
public int __alignment2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using BepInEx.Logging;
|
||||
using MonoMod.RuntimeDetour;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook;
|
||||
|
||||
internal abstract class BaseNativeDetour<T> : INativeDetour where T : BaseNativeDetour<T>
|
||||
{
|
||||
protected static readonly ManualLogSource Logger = new(typeof(T).Name);
|
||||
|
||||
protected BaseNativeDetour(nint originalMethodPtr, nint detourMethodPtr)
|
||||
{
|
||||
OriginalMethodPtr = originalMethodPtr;
|
||||
DetourMethodPtr = detourMethodPtr;
|
||||
}
|
||||
|
||||
public bool IsPrepared { get; protected set; }
|
||||
protected MethodInfo TrampolineMethod { get; set; }
|
||||
|
||||
public nint OriginalMethodPtr { get; }
|
||||
public nint DetourMethodPtr { get; }
|
||||
public nint TrampolinePtr { get; protected set; }
|
||||
public bool IsValid { get; private set; } = true;
|
||||
public bool IsApplied { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsValid) return;
|
||||
Undo();
|
||||
Free();
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
if (IsApplied) return;
|
||||
|
||||
Prepare();
|
||||
ApplyImpl();
|
||||
|
||||
Logger.Log(LogLevel.Debug,
|
||||
$"Original: {OriginalMethodPtr:X}, Trampoline: {TrampolinePtr:X}, diff: {Math.Abs(OriginalMethodPtr - TrampolinePtr):X}");
|
||||
|
||||
IsApplied = true;
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
if (IsApplied && IsPrepared) UndoImpl();
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
FreeImpl();
|
||||
IsValid = false;
|
||||
}
|
||||
|
||||
public MethodBase GenerateTrampoline(MethodBase signature = null)
|
||||
{
|
||||
if (TrampolineMethod == null)
|
||||
{
|
||||
Prepare();
|
||||
TrampolineMethod = DetourHelper.GenerateNativeProxy(TrampolinePtr, signature);
|
||||
}
|
||||
|
||||
return TrampolineMethod;
|
||||
}
|
||||
|
||||
public TDelegate GenerateTrampoline<TDelegate>() where TDelegate : Delegate
|
||||
{
|
||||
if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
|
||||
throw new InvalidOperationException($"Type {typeof(TDelegate)} not a delegate type.");
|
||||
|
||||
_ = GenerateTrampoline(typeof(TDelegate).GetMethod("Invoke"));
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<TDelegate>(TrampolinePtr);
|
||||
}
|
||||
|
||||
protected abstract void ApplyImpl();
|
||||
|
||||
private void Prepare()
|
||||
{
|
||||
if (IsPrepared) return;
|
||||
Logger.LogDebug($"Preparing detour from 0x{OriginalMethodPtr:X2} to 0x{DetourMethodPtr:X2}");
|
||||
PrepareImpl();
|
||||
Logger.LogDebug($"Prepared detour; Trampoline: 0x{TrampolinePtr:X2}");
|
||||
IsPrepared = true;
|
||||
}
|
||||
|
||||
protected abstract void PrepareImpl();
|
||||
|
||||
protected abstract void UndoImpl();
|
||||
|
||||
protected abstract void FreeImpl();
|
||||
}
|
||||
@@ -1,88 +1,19 @@
|
||||
using BepInEx.IL2CPP.Hook.Allocator;
|
||||
using BepInEx.Logging;
|
||||
using MonoMod.RuntimeDetour;
|
||||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Dobby;
|
||||
|
||||
internal class DobbyDetour : INativeDetour
|
||||
internal class DobbyDetour : BaseNativeDetour<DobbyDetour>
|
||||
{
|
||||
private static readonly ManualLogSource logger = Logger.CreateLogSource("DobbyDetour");
|
||||
public DobbyDetour(nint originalMethodPtr, nint detourMethodPtr)
|
||||
public DobbyDetour(nint originalMethodPtr, nint detourMethodPtr) : base(originalMethodPtr, detourMethodPtr) { }
|
||||
|
||||
protected override void ApplyImpl() => DobbyLib.Commit(OriginalMethodPtr);
|
||||
|
||||
protected override unsafe void PrepareImpl()
|
||||
{
|
||||
OriginalMethodPtr = originalMethodPtr;
|
||||
DetourMethodPtr = detourMethodPtr;
|
||||
}
|
||||
|
||||
public nint OriginalMethodPtr { get; protected set; }
|
||||
public nint DetourMethodPtr { get; protected set; }
|
||||
public nint TrampolinePtr { get; protected set; }
|
||||
|
||||
public bool IsValid { get; protected set; } = true;
|
||||
public bool IsApplied { get; protected set; }
|
||||
public bool IsPrepared { get; protected set; }
|
||||
protected MethodInfo TrampolineMethod { get; set; }
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
if (IsApplied) return;
|
||||
|
||||
PrepareDetour();
|
||||
DobbyImports.DobbyCommit(OriginalMethodPtr);
|
||||
|
||||
logger.Log(LogLevel.Debug,
|
||||
$"Original: {OriginalMethodPtr:X}, Trampoline: {TrampolinePtr:X}, diff: {Math.Abs(OriginalMethodPtr - TrampolinePtr):X}; is within +-1GB range: {PageAllocator.IsInRelJmpRange(OriginalMethodPtr, TrampolinePtr)}");
|
||||
|
||||
IsApplied = true;
|
||||
}
|
||||
public unsafe void Undo()
|
||||
{
|
||||
if (!IsApplied && !IsPrepared) return;
|
||||
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Destroying detour");
|
||||
|
||||
DobbyImports.DobbyDestroy(OriginalMethodPtr);
|
||||
}
|
||||
|
||||
private unsafe void PrepareDetour()
|
||||
{
|
||||
if (IsPrepared) return;
|
||||
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Preparing detour from 0x{OriginalMethodPtr:X2} to 0x{DetourMethodPtr:X2}");
|
||||
|
||||
var trampolinePtr = IntPtr.Zero;
|
||||
DobbyImports.DobbyPrepare(OriginalMethodPtr, DetourMethodPtr, &trampolinePtr);
|
||||
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Prepared detour; Trampoline: 0x{trampolinePtr:X2}");
|
||||
|
||||
nint trampolinePtr = 0;
|
||||
DobbyLib.Prepare(OriginalMethodPtr, DetourMethodPtr, &trampolinePtr);
|
||||
TrampolinePtr = trampolinePtr;
|
||||
IsPrepared = true;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsValid) return;
|
||||
Undo();
|
||||
Free();
|
||||
}
|
||||
|
||||
public void Free() => IsValid = false;
|
||||
protected override void UndoImpl() => DobbyLib.Destroy(OriginalMethodPtr);
|
||||
|
||||
public MethodBase GenerateTrampoline(MethodBase signature = null)
|
||||
{
|
||||
if (TrampolineMethod == null)
|
||||
{
|
||||
PrepareDetour();
|
||||
TrampolineMethod = DetourHelper.GenerateNativeProxy(TrampolinePtr, signature);
|
||||
}
|
||||
return TrampolineMethod;
|
||||
}
|
||||
public T GenerateTrampoline<T>() where T : Delegate
|
||||
{
|
||||
if (!typeof(Delegate).IsAssignableFrom(typeof(T)))
|
||||
throw new InvalidOperationException($"Type {typeof(T)} not a delegate type.");
|
||||
|
||||
return GenerateTrampoline(typeof(T).GetMethod("Invoke")).CreateDelegate(typeof(T)) as T;
|
||||
}
|
||||
protected override void FreeImpl() { }
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Dobby;
|
||||
|
||||
internal static unsafe class DobbyImports
|
||||
{
|
||||
#region Delegates
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int d_DobbyHook(IntPtr address, IntPtr replace_call, IntPtr* origin_call);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int d_DobbyPrepare(IntPtr address, IntPtr replace_call, IntPtr* origin_call);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int d_DobbyCommit(IntPtr address);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int d_DobbyDestroy(IntPtr address);
|
||||
#endregion
|
||||
|
||||
[DynDllImport("dobby")]
|
||||
public static d_DobbyHook DobbyHook;
|
||||
|
||||
[DynDllImport("dobby")]
|
||||
public static d_DobbyPrepare DobbyPrepare;
|
||||
|
||||
[DynDllImport("dobby")]
|
||||
public static d_DobbyCommit DobbyCommit;
|
||||
|
||||
[DynDllImport("dobby")]
|
||||
public static d_DobbyDestroy DobbyDestroy;
|
||||
|
||||
static DobbyImports()
|
||||
{
|
||||
var dobbyImports = new Dictionary<string, List<DynDllMapping>>()
|
||||
{
|
||||
["dobby"] = new()
|
||||
{
|
||||
$"{Paths.BepInExAssemblyDirectory}/native/dobby.dll" // Windows
|
||||
}
|
||||
};
|
||||
|
||||
typeof(DobbyImports).ResolveDynDllImports(dobbyImports);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using BepInEx.IL2CPP.Utils;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Dobby;
|
||||
|
||||
internal static unsafe class DobbyLib
|
||||
{
|
||||
static DobbyLib()
|
||||
{
|
||||
NativeLibraryUtils.OnResolve += (_, args) =>
|
||||
{
|
||||
if (args.LibraryName != "dobby")
|
||||
return;
|
||||
var pathBase = Path.Combine(Paths.BepInExAssemblyDirectory, "native", "dobby");
|
||||
var (arch, ext) = NativeLibraryUtils.GetDllIdentifier();
|
||||
var libPath = Path.Combine(pathBase, $"dobby_{arch}.{ext}");
|
||||
File.AppendAllText("log.log", $"Resolving dobby from {libPath}\n");
|
||||
if (NativeLibrary.TryLoad(libPath, out var lib))
|
||||
args.Library = lib;
|
||||
};
|
||||
}
|
||||
|
||||
[DllImport("dobby", EntryPoint = "DobbyHook", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Hook(nint target, nint replacement, nint* originalCall);
|
||||
|
||||
[DllImport("dobby", EntryPoint = "DobbyPrepare", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Prepare(nint target, nint replacement, nint* originalCall);
|
||||
|
||||
[DllImport("dobby", EntryPoint = "DobbyCommit", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Commit(nint target);
|
||||
|
||||
[DllImport("dobby", EntryPoint = "DobbyDestroy", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int Destroy(nint target);
|
||||
}
|
||||
@@ -1,104 +1,38 @@
|
||||
using BepInEx.IL2CPP.Hook.Allocator;
|
||||
using BepInEx.Logging;
|
||||
using MonoMod.RuntimeDetour;
|
||||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Funchook;
|
||||
|
||||
internal class FunchookDetour : INativeDetour
|
||||
internal class FunchookDetour : BaseNativeDetour<FunchookDetour>
|
||||
{
|
||||
private static readonly ManualLogSource logger = Logger.CreateLogSource("FunchookDetour");
|
||||
public FunchookDetour(nint originalMethodPtr, nint detourMethodPtr)
|
||||
private readonly nint funchookInstance;
|
||||
|
||||
public FunchookDetour(nint originalMethodPtr, nint detourMethodPtr) : base(originalMethodPtr, detourMethodPtr)
|
||||
{
|
||||
OriginalMethodPtr = originalMethodPtr;
|
||||
DetourMethodPtr = detourMethodPtr;
|
||||
funchookInstance = FunchookLib.Create();
|
||||
}
|
||||
|
||||
public nint OriginalMethodPtr { get; protected set; }
|
||||
public nint DetourMethodPtr { get; protected set; }
|
||||
public nint TrampolinePtr { get; protected set; }
|
||||
protected override void ApplyImpl() => EnsureSuccess(FunchookLib.Install(funchookInstance, 0));
|
||||
|
||||
public bool IsValid { get; protected set; } = true;
|
||||
|
||||
public bool IsApplied { get; protected set; }
|
||||
public bool IsPrepared { get; protected set; }
|
||||
|
||||
internal FunchookWrapper FunchookInstance { get; set; }
|
||||
protected MethodInfo TrampolineMethod { get; set; }
|
||||
|
||||
public void Apply()
|
||||
protected override unsafe void PrepareImpl()
|
||||
{
|
||||
if (IsApplied) return;
|
||||
|
||||
PrepareDetour();
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Installing funchook instance");
|
||||
FunchookInstance.Install();
|
||||
|
||||
logger.Log(LogLevel.Debug,
|
||||
$"Original: {OriginalMethodPtr:X}, Trampoline: {TrampolinePtr:X}, diff: {Math.Abs(OriginalMethodPtr - TrampolinePtr):X}; is within +-1GB range: {PageAllocator.IsInRelJmpRange(OriginalMethodPtr, TrampolinePtr)}");
|
||||
|
||||
IsApplied = true;
|
||||
}
|
||||
public void Undo()
|
||||
{
|
||||
if (IsApplied && IsPrepared)
|
||||
{
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Uninstalling funchook instance");
|
||||
FunchookInstance.Uninstall();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void PrepareDetour()
|
||||
{
|
||||
if (IsPrepared) return;
|
||||
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Creating funchook instance");
|
||||
|
||||
if (FunchookInstance == null)
|
||||
FunchookInstance = new FunchookWrapper();
|
||||
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Preparing detour from 0x{OriginalMethodPtr:X2} to 0x{DetourMethodPtr:X2}");
|
||||
|
||||
var trampolinePtr = OriginalMethodPtr;
|
||||
FunchookInstance.Prepare(&trampolinePtr, DetourMethodPtr);
|
||||
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Prepared detour; Trampoline: 0x{trampolinePtr:X2}");
|
||||
|
||||
EnsureSuccess(FunchookLib.Prepare(funchookInstance, &trampolinePtr, DetourMethodPtr));
|
||||
TrampolinePtr = trampolinePtr;
|
||||
IsPrepared = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!IsValid) return;
|
||||
Undo();
|
||||
Free();
|
||||
}
|
||||
protected override void UndoImpl() => EnsureSuccess(FunchookLib.Uninstall(funchookInstance, 0));
|
||||
|
||||
public void Free()
|
||||
{
|
||||
if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Destroying funchook instance");
|
||||
FunchookInstance.Destroy();
|
||||
IsValid = false;
|
||||
}
|
||||
protected override void FreeImpl() => EnsureSuccess(FunchookLib.Destroy(funchookInstance));
|
||||
|
||||
public MethodBase GenerateTrampoline(MethodBase signature = null)
|
||||
{
|
||||
if (TrampolineMethod == null)
|
||||
{
|
||||
PrepareDetour();
|
||||
TrampolineMethod = DetourHelper.GenerateNativeProxy(TrampolinePtr, signature);
|
||||
}
|
||||
return TrampolineMethod;
|
||||
}
|
||||
public T GenerateTrampoline<T>() where T : Delegate
|
||||
{
|
||||
if (!typeof(Delegate).IsAssignableFrom(typeof(T)))
|
||||
throw new InvalidOperationException($"Type {typeof(T)} not a delegate type.");
|
||||
private string GetErrorMessage()
|
||||
=> FunchookLib.ErrorMessage(funchookInstance);
|
||||
|
||||
return GenerateTrampoline(typeof(T).GetMethod("Invoke")).CreateDelegate(typeof(T)) as T;
|
||||
private void EnsureSuccess(FunchookResult result, [CallerArgumentExpression("result")] string methodName = null)
|
||||
{
|
||||
if (result == FunchookResult.Success) return;
|
||||
var errorMsg = GetErrorMessage();
|
||||
if (result == FunchookResult.OutOfMemory) throw new OutOfMemoryException($"{methodName} failed: {errorMsg}");
|
||||
throw new Exception($"{methodName} failed with result {result}: {errorMsg}");
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Funchook;
|
||||
|
||||
internal static unsafe class FunchookImports
|
||||
{
|
||||
#region Delegates
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate IntPtr d_funchook_create();
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate FunchookResult d_funchook_destroy(IntPtr funchook);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate FunchookResult d_funchook_prepare(IntPtr funchook, IntPtr* target_func, IntPtr hook_func);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate FunchookResult d_funchook_install(IntPtr funchook, int flags);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate FunchookResult d_funchook_uninstall(IntPtr funchook, int flags);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate string d_funchook_error_message(IntPtr funchook);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate FunchookResult d_funchook_set_debug_file([MarshalAs(UnmanagedType.LPStr)] string name);
|
||||
#endregion
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_create funchook_create;
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_destroy funchook_destroy;
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_prepare funchook_prepare;
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_install funchook_install;
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_uninstall funchook_uninstall;
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_error_message funchook_error_message;
|
||||
|
||||
[DynDllImport("funchook")]
|
||||
public static d_funchook_set_debug_file funchook_set_debug_file;
|
||||
|
||||
static FunchookImports()
|
||||
{
|
||||
var funchookImports = new Dictionary<string, List<DynDllMapping>>()
|
||||
{
|
||||
["funchook"] = new()
|
||||
{
|
||||
$"{Paths.BepInExAssemblyDirectory}/native/funchook.dll" // Windows
|
||||
}
|
||||
};
|
||||
|
||||
typeof(FunchookImports).ResolveDynDllImports(funchookImports);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using BepInEx.IL2CPP.Utils;
|
||||
using Il2CppSystem.IO;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Funchook;
|
||||
|
||||
internal enum FunchookResult
|
||||
{
|
||||
InternalError = -1,
|
||||
Success = 0,
|
||||
OutOfMemory = 1,
|
||||
AlreadyInstalled = 2,
|
||||
Disassembly = 3,
|
||||
IPRelativeOffset = 4,
|
||||
CannotFixIPRelative = 5,
|
||||
FoundBackJump = 6,
|
||||
TooShortInstructions = 7,
|
||||
MemoryAllocation = 8,
|
||||
MemoryFunction = 9,
|
||||
NotInstalled = 10,
|
||||
NoAvailableRegisters = 11
|
||||
}
|
||||
|
||||
internal static unsafe class FunchookLib
|
||||
{
|
||||
static FunchookLib()
|
||||
{
|
||||
NativeLibraryUtils.OnResolve += (_, args) =>
|
||||
{
|
||||
if (args.LibraryName != "funchook")
|
||||
return;
|
||||
var pathBase = Path.Combine(Paths.BepInExAssemblyDirectory, "native", "funchook");
|
||||
var (arch, ext) = NativeLibraryUtils.GetDllIdentifier();
|
||||
var libPath = Path.Combine(pathBase, $"funchook_{arch}.{ext}");
|
||||
if (NativeLibrary.TryLoad(libPath, out var lib))
|
||||
args.Library = lib;
|
||||
};
|
||||
}
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_create", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern nint Create();
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_destroy", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern FunchookResult Destroy(nint handle);
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_prepare", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern FunchookResult Prepare(nint handle, nint* target, nint hook);
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_install", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern FunchookResult Install(nint handle, int flags);
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_uninstall", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern FunchookResult Uninstall(nint handle, int flags);
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_error_message", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern string ErrorMessage(nint handle);
|
||||
|
||||
[DllImport("funchook", EntryPoint = "funchook_set_debug_file", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern FunchookResult SetDebugFile([MarshalAs(UnmanagedType.LPStr)] string name);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook.Funchook;
|
||||
|
||||
internal enum FunchookResult : int
|
||||
{
|
||||
InternalError = -1,
|
||||
Success = 0,
|
||||
OutOfMemory = 1,
|
||||
AlreadyInstalled = 2,
|
||||
Disassembly = 3,
|
||||
IPRelativeOffset = 4,
|
||||
CannotFixIPRelative = 5,
|
||||
FoundBackJump = 6,
|
||||
TooShortInstructions = 7,
|
||||
MemoryAllocation = 8,
|
||||
MemoryFunction = 9,
|
||||
NotInstalled = 10,
|
||||
NoAvailableRegisters = 11,
|
||||
}
|
||||
|
||||
internal unsafe class FunchookWrapper
|
||||
{
|
||||
internal static void SetDebugFile(string path) => FunchookImports.funchook_set_debug_file(path);
|
||||
|
||||
public FunchookWrapper()
|
||||
{
|
||||
funchookInstance = FunchookImports.funchook_create();
|
||||
}
|
||||
|
||||
public void Prepare(IntPtr* targetFunc, IntPtr detourFunc)
|
||||
=> EnsureSuccess(FunchookImports.funchook_prepare(funchookInstance, targetFunc, detourFunc), nameof(FunchookImports.funchook_prepare));
|
||||
|
||||
public void Install(int flags = 0)
|
||||
=> EnsureSuccess(FunchookImports.funchook_install(funchookInstance, flags), nameof(FunchookImports.funchook_install));
|
||||
|
||||
public void Uninstall(int flags = 0)
|
||||
=> EnsureSuccess(FunchookImports.funchook_uninstall(funchookInstance, flags), nameof(FunchookImports.funchook_uninstall));
|
||||
|
||||
public string GetErrorMessage()
|
||||
=> FunchookImports.funchook_error_message(funchookInstance);
|
||||
|
||||
public void Destroy()
|
||||
=> EnsureSuccess(FunchookImports.funchook_destroy(funchookInstance), nameof(FunchookImports.funchook_destroy));
|
||||
|
||||
private void EnsureSuccess(FunchookResult result, string methodName)
|
||||
{
|
||||
if (result == FunchookResult.Success) return;
|
||||
var errorMsg = GetErrorMessage();
|
||||
if (result == FunchookResult.OutOfMemory) throw new OutOfMemoryException($"{methodName} failed: {errorMsg}");
|
||||
throw new Exception($"{methodName} failed with result {result}: {errorMsg}");
|
||||
}
|
||||
|
||||
private readonly IntPtr funchookInstance;
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
|
||||
var detourPtr =
|
||||
Marshal.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType));
|
||||
nativeDetour = NativeDetourHelper.Create(originalNativeMethodInfo.MethodPointer, detourPtr);
|
||||
nativeDetour = INativeDetour.Create(originalNativeMethodInfo.MethodPointer, detourPtr);
|
||||
nativeDetour.Apply();
|
||||
modifiedNativeMethodInfo.MethodPointer = nativeDetour.TrampolinePtr;
|
||||
|
||||
|
||||
@@ -1,10 +1,50 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.IL2CPP.Hook.Dobby;
|
||||
using BepInEx.IL2CPP.Hook.Funchook;
|
||||
using MonoMod.RuntimeDetour;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook;
|
||||
|
||||
public interface INativeDetour : IDetour
|
||||
{
|
||||
private static readonly ConfigEntry<DetourProvider> DetourProviderType = ConfigFile.CoreConfig.Bind(
|
||||
"Detours", "DetourProviderType",
|
||||
DetourProvider.Default,
|
||||
"The native provider to use for managed detours"
|
||||
);
|
||||
|
||||
public nint OriginalMethodPtr { get; }
|
||||
public nint DetourMethodPtr { get; }
|
||||
public nint TrampolinePtr { get; }
|
||||
|
||||
private static INativeDetour CreateDefault(nint original, nint target) =>
|
||||
// TODO: check and provide an OS accurate provider
|
||||
new DobbyDetour(original, target);
|
||||
|
||||
public static INativeDetour Create(nint original, nint target) =>
|
||||
DetourProviderType.Value switch
|
||||
{
|
||||
DetourProvider.Dobby => new DobbyDetour(original, target),
|
||||
DetourProvider.Funchook => new FunchookDetour(original, target),
|
||||
_ => CreateDefault(original, target)
|
||||
};
|
||||
|
||||
public static INativeDetour CreateAndApply<T>(nint from, T to, out T original)
|
||||
where T : Delegate
|
||||
{
|
||||
var toPtr = Marshal.GetFunctionPointerForDelegate(to);
|
||||
var detour = Create(from, toPtr);
|
||||
original = detour.GenerateTrampoline<T>();
|
||||
detour.Apply();
|
||||
return detour;
|
||||
}
|
||||
|
||||
internal enum DetourProvider
|
||||
{
|
||||
Default,
|
||||
Dobby,
|
||||
Funchook
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.IL2CPP.Hook.Dobby;
|
||||
using BepInEx.IL2CPP.Hook.Funchook;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook;
|
||||
|
||||
public class NativeDetourHelper
|
||||
{
|
||||
internal enum DetourProvider
|
||||
{
|
||||
Default,
|
||||
Dobby,
|
||||
Funchook
|
||||
}
|
||||
private static readonly ConfigEntry<DetourProvider> DetourProviderType = ConfigFile.CoreConfig.Bind(
|
||||
"Detours", "DetourProviderType",
|
||||
DetourProvider.Default,
|
||||
"The native provider to use for managed detours"
|
||||
);
|
||||
|
||||
public static bool DEBUG_DETOURS = false;
|
||||
|
||||
private static INativeDetour CreateDefault(nint original, nint target)
|
||||
{
|
||||
// TODO: check and provide an OS accurate provider
|
||||
return new DobbyDetour(original, target);
|
||||
}
|
||||
|
||||
public static INativeDetour Create(nint original, nint target)
|
||||
{
|
||||
return DetourProviderType.Value switch
|
||||
{
|
||||
DetourProvider.Dobby => new DobbyDetour(original, target),
|
||||
DetourProvider.Funchook => new FunchookDetour(original, target),
|
||||
_ => CreateDefault(original, target)
|
||||
};
|
||||
}
|
||||
|
||||
public static INativeDetour CreateAndApply<T>(nint from, T to, out T original, CallingConvention? callingConvention = null)
|
||||
where T : Delegate
|
||||
{
|
||||
var toPtr = callingConvention != null
|
||||
? MonoExtensions.GetFunctionPointerForDelegate(to, callingConvention.Value)
|
||||
: Marshal.GetFunctionPointerForDelegate(to);
|
||||
|
||||
var detour = Create(from, toPtr);
|
||||
original = detour.GenerateTrampoline<T>();
|
||||
detour.Apply();
|
||||
return detour;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ public class UnhollowerDetourHandler : IManagedDetour
|
||||
{
|
||||
public T Detour<T>(IntPtr from, T to) where T : Delegate
|
||||
{
|
||||
NativeDetourHelper.CreateAndApply(from, to, out var original);
|
||||
INativeDetour.CreateAndApply(from, to, out var original);
|
||||
return original;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -13,11 +12,9 @@ using BepInEx.Logging;
|
||||
using BepInEx.Preloader.Core;
|
||||
using BepInEx.Preloader.Core.Logging;
|
||||
using HarmonyLib.Public.Patching;
|
||||
using Il2Cpp.TlsAdapter;
|
||||
using MonoMod.RuntimeDetour;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using MonoMod.Utils;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using Logger = BepInEx.Logging.Logger;
|
||||
|
||||
@@ -26,7 +23,6 @@ namespace BepInEx.IL2CPP;
|
||||
public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
{
|
||||
private static RuntimeInvokeDetourDelegate originalInvoke;
|
||||
private static InstallUnityTlsInterfaceDelegate originalInstallUnityTlsInterface;
|
||||
|
||||
private static readonly ConfigEntry<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Bind(
|
||||
"Logging", "UnityLogListening",
|
||||
@@ -39,7 +35,6 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
"Include unity log messages in log file output.");
|
||||
|
||||
private static INativeDetour RuntimeInvokeDetour { get; set; }
|
||||
private static INativeDetour InstallUnityTlsInterfaceDetour { get; set; }
|
||||
|
||||
public static IL2CPPChainloader Instance { get; set; }
|
||||
|
||||
@@ -59,7 +54,6 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
|
||||
public override void Initialize(string gameExePath = null)
|
||||
{
|
||||
GeneratedDatabasesUtil.DatabasesLocationOverride = Preloader.IL2CPPUnhollowedPath;
|
||||
PatchManager.ResolvePatcher += IL2CPPDetourMethodPatcher.TryResolve;
|
||||
|
||||
base.Initialize(gameExePath);
|
||||
@@ -81,33 +75,14 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_runtime_invoke", out var runtimeInvokePtr);
|
||||
PreloaderLogger.Log.Log(LogLevel.Debug, $"Runtime invoke pointer: 0x{runtimeInvokePtr.ToInt64():X}");
|
||||
RuntimeInvokeDetour =
|
||||
NativeDetourHelper.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke,
|
||||
CallingConvention.Cdecl);
|
||||
|
||||
if (gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_unity_install_unitytls_interface",
|
||||
out var installTlsPtr))
|
||||
InstallUnityTlsInterfaceDetour =
|
||||
NativeDetourHelper.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface,
|
||||
out originalInstallUnityTlsInterface, CallingConvention.Cdecl);
|
||||
|
||||
Logger.Log(LogLevel.Debug, "Initializing TLS adapters");
|
||||
Il2CppTlsAdapter.Initialize();
|
||||
INativeDetour.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke);
|
||||
|
||||
PreloaderLogger.Log.Log(LogLevel.Debug, "Runtime invoke patched");
|
||||
}
|
||||
|
||||
private static void OnInstallUnityTlsInterface(IntPtr unityTlsInterfaceStruct)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug, $"Captured UnityTls interface at {unityTlsInterfaceStruct.ToInt64():x8}");
|
||||
Il2CppTlsAdapter.Options.UnityTlsInterface = unityTlsInterfaceStruct;
|
||||
originalInstallUnityTlsInterface(unityTlsInterfaceStruct);
|
||||
InstallUnityTlsInterfaceDetour.Dispose();
|
||||
InstallUnityTlsInterfaceDetour = null;
|
||||
}
|
||||
|
||||
private static IntPtr OnInvokeMethod(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc)
|
||||
{
|
||||
var methodName = Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method));
|
||||
var methodName = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_method_get_name(method));
|
||||
|
||||
var unhook = false;
|
||||
|
||||
@@ -159,7 +134,7 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
{
|
||||
var type = pluginAssembly.GetType(pluginInfo.TypeName);
|
||||
|
||||
var pluginInstance = (BasePlugin)Activator.CreateInstance(type);
|
||||
var pluginInstance = (BasePlugin) Activator.CreateInstance(type);
|
||||
|
||||
pluginInstance.Load();
|
||||
|
||||
@@ -168,7 +143,4 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate IntPtr RuntimeInvokeDetourDelegate(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void InstallUnityTlsInterfaceDelegate(IntPtr unityTlsInterfaceStruct);
|
||||
}
|
||||
@@ -252,26 +252,7 @@ internal static class Il2CppInteropManager
|
||||
if (File.Exists(renameMapLocation))
|
||||
{
|
||||
Logger.LogInfo("Parsing deobfuscation rename mappings");
|
||||
|
||||
using var fileStream =
|
||||
new FileStream(renameMapLocation, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||
|
||||
using var reader = new StreamReader(gzipStream, Encoding.UTF8, false);
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
var mapping = line.Split(';');
|
||||
|
||||
if (mapping.Length != 2)
|
||||
continue;
|
||||
|
||||
opts.RenameMap[mapping[0]] = mapping[1];
|
||||
}
|
||||
opts.ReadRenameMap(renameMapLocation);
|
||||
}
|
||||
|
||||
Logger.LogInfo("Generating interop assemblies");
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BepInEx.IL2CPP.Utils;
|
||||
|
||||
internal static class NativeLibraryUtils
|
||||
{
|
||||
static NativeLibraryUtils()
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(typeof(NativeLibraryUtils).Assembly, Resolver);
|
||||
}
|
||||
|
||||
public static event EventHandler<ResolveEvent> OnResolve;
|
||||
|
||||
private static IntPtr Resolver(string libraryname, Assembly assembly, DllImportSearchPath? searchpath)
|
||||
{
|
||||
var evt = new ResolveEvent { LibraryName = libraryname };
|
||||
OnResolve?.Invoke(null, evt);
|
||||
return evt.Library;
|
||||
}
|
||||
|
||||
public static (string Arch, string LibExt) GetDllIdentifier()
|
||||
{
|
||||
var arch = RuntimeInformation.ProcessArchitecture switch
|
||||
{
|
||||
Architecture.X64 => "x64",
|
||||
Architecture.X86 => "x86",
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
string ext;
|
||||
if (OperatingSystem.IsWindows())
|
||||
ext = "dll";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
ext = "so";
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
ext = "dylib";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
|
||||
return (arch, ext);
|
||||
}
|
||||
|
||||
public class ResolveEvent
|
||||
{
|
||||
public string LibraryName { get; init; }
|
||||
public nint Library { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user