205 lines
5.4 KiB
C#
205 lines
5.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Harmony.ILCopying
|
|
{
|
|
[Flags]
|
|
public enum Protection
|
|
{
|
|
PAGE_NOACCESS = 0x01,
|
|
PAGE_READONLY = 0x02,
|
|
PAGE_READWRITE = 0x04,
|
|
PAGE_WRITECOPY = 0x08,
|
|
PAGE_EXECUTE = 0x10,
|
|
PAGE_EXECUTE_READ = 0x20,
|
|
PAGE_EXECUTE_READWRITE = 0x40,
|
|
PAGE_EXECUTE_WRITECOPY = 0x80,
|
|
PAGE_GUARD = 0x100,
|
|
PAGE_NOCACHE = 0x200,
|
|
PAGE_WRITECOMBINE = 0x400
|
|
}
|
|
|
|
public static class Memory
|
|
{
|
|
private static readonly HashSet<PlatformID> WindowsPlatformIDSet = new HashSet<PlatformID>
|
|
{
|
|
PlatformID.Win32NT, PlatformID.Win32S, PlatformID.Win32Windows, PlatformID.WinCE
|
|
};
|
|
|
|
public static bool IsWindows => WindowsPlatformIDSet.Contains(Environment.OSVersion.Platform);
|
|
|
|
// Safe to use windows reference since this will only ever be called on windows
|
|
//
|
|
[DllImport("kernel32.dll")]
|
|
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect);
|
|
|
|
public static void UnprotectMemoryPage(long memory)
|
|
{
|
|
if (IsWindows)
|
|
{
|
|
var success = VirtualProtect(new IntPtr(memory), new UIntPtr(1), Protection.PAGE_EXECUTE_READWRITE, out var _ignored);
|
|
if (success == false)
|
|
throw new System.ComponentModel.Win32Exception();
|
|
}
|
|
}
|
|
|
|
public static string DetourMethod(MethodBase original, MethodBase replacement)
|
|
{
|
|
var originalCodeStart = GetMethodStart(original, out var exception);
|
|
if (originalCodeStart == 0)
|
|
return exception.Message;
|
|
var patchCodeStart = GetMethodStart(replacement, out exception);
|
|
if (patchCodeStart == 0)
|
|
return exception.Message;
|
|
|
|
return WriteJump(originalCodeStart, patchCodeStart);
|
|
}
|
|
|
|
/*
|
|
* This is still a rough part in Harmony. So much information and no easy way
|
|
* to determine when and what is valid. Especially with different environments
|
|
* and .NET versions. More information might be found here:
|
|
*
|
|
* https://stackoverflow.com/questions/38782934/how-to-replace-the-pointer-to-the-overridden-virtual-method-in-the-pointer-of/
|
|
* https://stackoverflow.com/questions/39034018/how-to-replace-a-pointer-to-a-pointer-to-a-method-in-a-class-of-my-method-inheri
|
|
*
|
|
*/
|
|
public static string WriteJump(long memory, long destination)
|
|
{
|
|
UnprotectMemoryPage(memory);
|
|
|
|
if (IntPtr.Size == sizeof(long))
|
|
{
|
|
if (CompareBytes(memory, new byte[] { 0xe9 }))
|
|
{
|
|
var offset = ReadInt(memory + 1);
|
|
memory += 5 + offset;
|
|
}
|
|
|
|
memory = WriteBytes(memory, new byte[] { 0x48, 0xB8 });
|
|
memory = WriteLong(memory, destination);
|
|
memory = WriteBytes(memory, new byte[] { 0xFF, 0xE0 });
|
|
}
|
|
else
|
|
{
|
|
memory = WriteByte(memory, 0x68);
|
|
memory = WriteInt(memory, (int)destination);
|
|
memory = WriteByte(memory, 0xc3);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static RuntimeMethodHandle GetRuntimeMethodHandle(MethodBase method)
|
|
{
|
|
if (method is DynamicMethod)
|
|
{
|
|
var nonPublicInstance = BindingFlags.NonPublic | BindingFlags.Instance;
|
|
|
|
// DynamicMethod actually generates its m_methodHandle on-the-fly and therefore
|
|
// we should call GetMethodDescriptor to force it to be created
|
|
//
|
|
var m_GetMethodDescriptor = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", nonPublicInstance);
|
|
if (m_GetMethodDescriptor != null)
|
|
return (RuntimeMethodHandle)m_GetMethodDescriptor.Invoke(method, new object[0]);
|
|
|
|
// .Net Core
|
|
var f_m_method = typeof(DynamicMethod).GetField("m_method", nonPublicInstance);
|
|
if (f_m_method != null)
|
|
return (RuntimeMethodHandle)f_m_method.GetValue(method);
|
|
|
|
// Mono
|
|
var f_mhandle = typeof(DynamicMethod).GetField("mhandle", nonPublicInstance);
|
|
return (RuntimeMethodHandle)f_mhandle.GetValue(method);
|
|
}
|
|
|
|
return method.MethodHandle;
|
|
}
|
|
|
|
public static long GetMethodStart(MethodBase method, out Exception exception)
|
|
{
|
|
// required in .NET Core so that the method is JITed and the method start does not change
|
|
//
|
|
var handle = GetRuntimeMethodHandle(method);
|
|
try
|
|
{
|
|
RuntimeHelpers.PrepareMethod(handle);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
try
|
|
{
|
|
exception = null;
|
|
return handle.GetFunctionPointer().ToInt64();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
exception = ex;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static unsafe bool CompareBytes(long memory, byte[] values)
|
|
{
|
|
var p = (byte*)memory;
|
|
foreach (var value in values)
|
|
{
|
|
if (value != *p) return false;
|
|
p++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static unsafe byte ReadByte(long memory)
|
|
{
|
|
var p = (byte*)memory;
|
|
return *p;
|
|
}
|
|
|
|
public static unsafe int ReadInt(long memory)
|
|
{
|
|
var p = (int*)memory;
|
|
return *p;
|
|
}
|
|
|
|
public static unsafe long ReadLong(long memory)
|
|
{
|
|
var p = (long*)memory;
|
|
return *p;
|
|
}
|
|
|
|
public static unsafe long WriteByte(long memory, byte value)
|
|
{
|
|
var p = (byte*)memory;
|
|
*p = value;
|
|
return memory + sizeof(byte);
|
|
}
|
|
|
|
public static unsafe long WriteBytes(long memory, byte[] values)
|
|
{
|
|
foreach (var value in values)
|
|
memory = WriteByte(memory, value);
|
|
return memory;
|
|
}
|
|
|
|
public static unsafe long WriteInt(long memory, int value)
|
|
{
|
|
var p = (int*)memory;
|
|
*p = value;
|
|
return memory + sizeof(int);
|
|
}
|
|
|
|
public static unsafe long WriteLong(long memory, long value)
|
|
{
|
|
var p = (long*)memory;
|
|
*p = value;
|
|
return memory + sizeof(long);
|
|
}
|
|
}
|
|
}
|