#pragma once
#include "types.h"

#if _M_IX86
#define OFFSET_TO_ADDRESS 0x1
#elif _M_X64
#define OFFSET_TO_ADDRESS 0x2
#endif


inline void write_to_readonly(void* addr, void* data, int size)
{
	if(!addr || !data || !size)
		return;

	{
		//
		// disable write protection.
		//
		auto cr0 = __readcr0();
		cr0 &= 0xfffffffffffeffff;
		__writecr0(cr0);
		_disable();

	}

	memcpy(addr, data, size);

	{
		//
		// enable write protection.
		//
		auto cr0 = __readcr0();
		cr0 |= 0x10000;
		_enable();
		__writecr0(cr0);
	}
}

class detour
{
public:
	detour() {};
	detour(void* addr_to_hook, void* jmp_to, bool enable = true)
		: hook_addr(addr_to_hook), detour_addr(jmp_to), hook_installed(false)
	{ init(addr_to_hook, jmp_to, enable); }

	void init(void* addr_to_hook, void* jmp_to, bool enable = true)
	{
		//setup hook
		memcpy(
			jmp_code + OFFSET_TO_ADDRESS,
			&jmp_to,
			sizeof(jmp_to)
		);
		//save bytes
		memcpy(
			org_bytes,
			hook_addr,
			sizeof(org_bytes)
		);
		if (enable)
			install();
	}

	void install()
	{
		if (hook_installed)
			return;
		write_to_readonly(hook_addr, jmp_code, sizeof(jmp_code));
		hook_installed = true;
	}

	void uninstall()
	{
		if (!hook_installed)
			return;
		write_to_readonly(hook_addr, org_bytes, sizeof(org_bytes));
		hook_installed = false;
	}

	bool installed() { return hook_installed; }
	void* hook_address() { return hook_addr; }
	void* detour_address() { return detour_addr; }
private:
	bool hook_installed;
	void* hook_addr, * detour_addr;

#if _M_IX86
	/*
		0:  b8 ff ff ff ff          mov    eax, 0xffffffff
		5:  ff e0                   jmp    eax
	*/
	unsigned char jmp_code[7] = {
		0xb8, 0x0, 0x0, 0x0, 0x0,
		0xFF, 0xE0
	};
#elif _M_X64
	/*
		0:  48 c7 c0 ff ff ff ff    mov    rax,0xffffffffffffffff
		7:  ff e0                   jmp    rax
	*/
	unsigned char jmp_code[12] = {
		0x48, 0xb8,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0xff, 0xe0
	};
#endif
	unsigned char org_bytes[sizeof(jmp_code)];
};
