/*
 * EAT-based hooking for x86/x64.
 * 
 * Big thanks to ez (https://github.com/ezdiy/) for making this!
 * 
 * Creates "hooks" by modifying the module's export address table.
 * The procedure works in three main parts:
 * 
 * 1. Reading the module's PE file and getting all exported functions.
 * 2. Finding the right function to "hook" by simple address lookup
 * 3. Modify the entry to point to the hook.
 * 
 * The idea is based on the fact that the export table allows forwarding imports:
 * 
 * https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Forwarding
 * 
 * You can also find some reference material on the same page to understand this code better.
 * 
 */

#pragma once

#pragma warning( disable : 4267 90 )

#include <windows.h>

// PE format uses RVAs (Relative Virtual Addresses) to save addresses relative to the base of the module
// More info: https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#Relative_Virtual_Addressing_(RVA)
//
// This helper macro converts the saved RVA to a fully valid pointer to the data in the PE file
#define RVA2PTR(t,base,rva) ((t)(((PCHAR) base) + rva))

// A helper function to write into protected memory
inline int vpmemcpy(void *dst, void *src, size_t sz)
{
	DWORD oldp;
	// Make the memory page writeable
	if (!VirtualProtect(dst, sz, PAGE_READWRITE, &oldp))
		return 1;
	memcpy(dst, src, sz);
	// Restore the protection level
	VirtualProtect(dst, sz, oldp, &oldp);
	return 0;
}


/**
 * \brief Replaces the specified function entry in the EAT with a forward to a custom one, thus creating the "hook" effect.
 * \param hostDll The address of the module to hook.
 * \param originalFunction Address of the original function.
 * \param forwardFunctionEntry Name of the function to add a forward to. Must be of form `dll.API`.
 * \return TRUE, if hooking succeeded, otherwise, FALSE.
 */
inline BOOL ezHook(HMODULE hostDll, void *originalFunction, char *forwardFunctionEntry)
{
	/*
	 * Note that we are not doing any trampoline magic or editing the assembly!
	 * 
	 * Instead, we are reading the module's PE file, find the original function's entry in the export address table (EAT), 
	 * and replace it with a forward import.
	 * 
	 * This ultimately will fool the game/executable to call our hook, while keeping original function intact.
	 *
	 * Thus, in order to work, the proxy DLL has to export the hook, because we are essentially
	 * asking the game to call our hook without ever going to the original function (unlike with trampolines).
	 */

	size_t fwdlen = strlen(forwardFunctionEntry);

	// The module always starts with a DOS (or "MZ") header
	IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)hostDll;

	// Next, get the NT headers. The offset to them is saved in e_lfanew
	IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);

	// Get the pointer to the data directory of the exports
	IMAGE_DATA_DIRECTORY *edirp = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
	IMAGE_DATA_DIRECTORY edir = *edirp;

	// Finally, the virtual address in the data direcotry tells the location of the exports table
	IMAGE_EXPORT_DIRECTORY *exports = RVA2PTR(PIMAGE_EXPORT_DIRECTORY, mz, edir.VirtualAddress);

	// Read the addrress of the function list and the address of function names
	DWORD *addrs = RVA2PTR(DWORD*, mz, exports->AddressOfFunctions);
	//	DWORD* names = RVA2PTR(DWORD*, mz, exports->AddressOfNames);

	// Iterate through all functions
	for (unsigned i = 0; i < exports->NumberOfFunctions; i++)
	{
		//char* name = RVA2PTR(char*, mz, names[i]); // Name of the exported function
		void *addr = RVA2PTR(void*, mz, addrs[i]); // Address of the exported function

		// Check if we have the function we need to modify
		if (addr == originalFunction)
		{
			DWORD fptr = edir.VirtualAddress + edir.Size;
			int err = 0;

			// Update the entry to go the the last entry (which we will populate in the next memcpy)
			err |= vpmemcpy(&addrs[i], &fptr, sizeof(fptr));

			// Add the forwarding import to our function at the end of the EAT
			err |= vpmemcpy(((char*)exports + edir.Size), forwardFunctionEntry, fwdlen);

			// Increment the size of the export data directory
			// and write the new export data directory
			edir.Size += fwdlen + 1;
			err |= vpmemcpy(edirp, &edir, sizeof(edir));
			return err == 0;
		}
	}
	return FALSE;
}


/**
 * \brief Hooks the given function through the Import Address Table
 * \param dll Module to hook
 * \param targetFunction Address of the target function to hook
 * \param detourFunction Address of the detour function
 * \return TRUE if successful, otherwise FALSE
 */
inline BOOL iat_hook(HMODULE dll, char const* targetDLL, void *targetFunction, void *detourFunction)
{
	IMAGE_DOS_HEADER *mz = (PIMAGE_DOS_HEADER)dll;

	IMAGE_NT_HEADERS *nt = RVA2PTR(PIMAGE_NT_HEADERS, mz, mz->e_lfanew);

	IMAGE_IMPORT_DESCRIPTOR *imports = RVA2PTR(PIMAGE_IMPORT_DESCRIPTOR, mz, nt->OptionalHeader.DataDirectory[
		IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

	for (int i = 0; imports[i].Characteristics; i++)
	{
		char *name = RVA2PTR(char*, mz, imports[i].Name);

		if(lstrcmpiA(name, targetDLL) != 0)
			continue;

		void **thunk = RVA2PTR(void**, mz, imports[i].FirstThunk);
        void **nextThunk = RVA2PTR(void**, mz, imports[i+1].FirstThunk);

		for (; thunk && thunk < nextThunk; thunk++)
		{
			void *import = *thunk;

			if (import != targetFunction)
				continue;

			DWORD oldState;
			if (!VirtualProtect(thunk, sizeof(void*), PAGE_READWRITE, &oldState))
				return FALSE;

			*thunk = (void*)detourFunction;

			VirtualProtect(thunk, sizeof(void*), oldState, &oldState);

			return TRUE;
		}
	}

	return FALSE;
}
