Clean up after merge

This commit is contained in:
ghorsington committed 2022-08-14 17:18:58 +03:00
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;
}
}
}
+96
View File
@@ -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();
}
+10 -79
View File
@@ -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() { }
}
-45
View File
@@ -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);
}
}
+35
View File
@@ -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);
}
+19 -85
View File
@@ -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;
+40
View File
@@ -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
}
}
-53
View File
@@ -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;
}
}
+5 -33
View File
@@ -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);
}
+1 -20
View File
@@ -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; }
}
}