using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace stupid.solutions;

public class TestHook
{
	internal class Import
	{
		[DllImport("kernel32.dll", SetLastError = true)]
		internal static extern bool VirtualProtect(IntPtr address, uint size, uint newProtect, out uint oldProtect);
	}

	private byte[] original;

	private const uint HOOK_SIZE_X64 = 12u;

	private const uint HOOK_SIZE_X86 = 7u;

	public MethodInfo OriginalMethod { get; private set; }

	public MethodInfo HookMethod { get; private set; }

	public TestHook()
	{
		original = null;
		OriginalMethod = (HookMethod = null);
	}

	public TestHook(MethodInfo orig, MethodInfo hook)
	{
		original = null;
		Init(orig, hook);
	}

	public MethodInfo GetMethodByName(Type typeOrig, string nameOrig)
	{
		return typeOrig.GetMethod(nameOrig);
	}

	public TestHook(Type typeOrig, string nameOrig, Type typeHook, string nameHook)
	{
		original = null;
		Init(GetMethodByName(typeOrig, nameOrig), GetMethodByName(typeHook, nameHook));
	}

	public void Init(MethodInfo orig, MethodInfo hook)
	{
		if (orig == null || hook == null)
		{
			throw new ArgumentException("Both original and hook need to be valid methods");
		}
		RuntimeHelpers.PrepareMethod(orig.MethodHandle);
		RuntimeHelpers.PrepareMethod(hook.MethodHandle);
		OriginalMethod = orig;
		HookMethod = hook;
	}

	public unsafe void Hook()
	{
		if (OriginalMethod == null || HookMethod == null)
		{
			throw new ArgumentException("Hook has to be properly Init'd before use");
		}
		try
		{
			IntPtr functionPointer = OriginalMethod.MethodHandle.GetFunctionPointer();
			IntPtr functionPointer2 = HookMethod.MethodHandle.GetFunctionPointer();
			if (IntPtr.Size == 8)
			{
				original = new byte[12];
				if (Import.VirtualProtect(functionPointer, 12u, 64u, out var _))
				{
					byte* ptr = (byte*)(void*)functionPointer;
					for (int i = 0; (long)i < 12L; i++)
					{
						original[i] = ptr[i];
					}
					*ptr = 72;
					ptr[1] = 184;
					*(IntPtr*)(ptr + 2) = functionPointer2;
					ptr[10] = byte.MaxValue;
					ptr[11] = 224;
				}
				return;
			}
			original = new byte[7];
			if (Import.VirtualProtect(functionPointer, 7u, 64u, out var _))
			{
				byte* ptr2 = (byte*)(void*)functionPointer;
				for (int j = 0; (long)j < 7L; j++)
				{
					original[j] = ptr2[j];
				}
				*ptr2 = 184;
				*(IntPtr*)(ptr2 + 1) = functionPointer2;
				ptr2[5] = byte.MaxValue;
				ptr2[6] = 224;
			}
		}
		catch (Exception)
		{
		}
	}

	public unsafe void Unhook()
	{
		if (original == null)
		{
			return;
		}
		try
		{
			IntPtr functionPointer = OriginalMethod.MethodHandle.GetFunctionPointer();
			uint num = (uint)original.Length;
			if (Import.VirtualProtect(functionPointer, num, 64u, out var _))
			{
				byte* ptr = (byte*)(void*)functionPointer;
				for (int i = 0; i < num; i++)
				{
					ptr[i] = original[i];
				}
			}
		}
		catch (Exception)
		{
		}
		finally
		{
			original = null;
		}
	}
}
