#include <metahook.h>
#include "plugins.h"
#include "engfuncs.h"
#include "util.h"

hook_funcs_t gHookFuncs;

//engine

//3266
#define SV_STUDIOSETUPBONE_SIG "\x8B\x4C\x24\x0C\xA1\x2A\x2A\x2A\x02\x81\xEC\x70\x02\x00\x00\x53\x55"
//4554
#define SV_STUDIOSETUPBONE_SIG2 "\x8B\x4C\x24\x0C\x81\xEC\x74\x02\x00\x00\xA1\x2A\x2A\x2A\x2A\x53\x55"
//6153
#define SV_STUDIOSETUPBONE_SIG_NEW "\x55\x8B\xEC\x81\xEC\x64\x02\x00\x00\x8B\x4D\x10\xA1\x2A\x2A\x2A\x2A\x53\x33\xDB\x56\x57\x85\xC9"

//3266,4554
#define R_DRAWVIEWMODEL_SIG "\x83\xEC\x2A\xD9\x05\x2A\x2A\x2A\x2A\xD8\x1D\x2A\x2A\x2A\x2A\x56\x57\x33\xFF\xC7\x44\x2A\x2A\x00\x00\x80\xBF"
//6153
#define R_DRAWVIEWMODEL_SIG_NEW "\x55\x8B\xEC\x83\xEC\x50\xD9\x05\x2A\x2A\x2A\x2A\xD8\x1D\x2A\x2A\x2A\x2A\x56\x57\x33\xFF\xC7\x45\xE0\x00\x00\x80\xBF"

#define S_STOPSOUND_SIG "\xA1\x2A\x2A\x2A\x2A\x57\xBF\x04\x00\x00\x00\x3B\xC7\x7E\x35\x53\x8B\x5C\x24\x10\x55\x8B\x6C\x24\x10"
#define S_STOPSOUND_SIG_NEW "\x55\x8B\xEC\xA1\x2A\x2A\x2A\x2A\x57\xBF\x04\x00\x00\x00\x3B\xC7\x7E\x2A\x53\x8B\x5D\x0C\x56"

//client
#define IN_MOUSEMOVE_SIG "\x83\xEC\x2A\x8D\x44\x24\x2A\x50\xFF\x15\x2A\x2A\x2A\x2A\xA0\x2A\x2A\x2A\x2A\x83\xC4\x04\xA8\x01"

//client.dll vars
DWORD gHud = 0;
int *gHud_iFOV = NULL;
int g_iUser1 = 0;
int g_iUser2 = 0;
int g_iUser3 = 0;
cvar_t *gHUD_m_pip = NULL;
DWORD gpViewPortInterface;
float *g_lastFOV = NULL;

//hw.dll vars
refdef_t *refdef = NULL;
playermove_t **cl_ppmove = NULL;
int *envmap = NULL;
float *r_blend = NULL;

//Renderer.dll funcs
ref_export_t gRefExports;
ref_funcs_t gRefFuncs;
studio_funcs_t gStudioFuncs;

//api stuffs
HWND g_hWnd;

//Error when can't find sig
void Sys_ErrorEx(const char *error)
{
	if(g_pMetaSave->pEngineFuncs)
	{
		g_pMetaSave->pEngineFuncs->pfnClientCmd("escape\n");
	}
	MessageBox(g_hWnd, error, "Error", MB_ICONERROR);
	exit(0);
}

void MemPatch_HeapSizeLimitValue(void)
{
	if (g_dwEngineBuildnum >= 5953)
		return;

#define SYS_INITMEMORY_SIG "\x83\xEC\x24\x68\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x83\xC4\x04\x85\xC0\x74\x2A\x8B\x0D\x2A\x2A\x2A\x2A\x49"
	int maxvalue = 128 * (1024 * 1024);
	DWORD addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, SYS_INITMEMORY_SIG, sizeof(SYS_INITMEMORY_SIG) - 1);

	if (!addr)
	{
		//MessageBox(NULL, "HeapSizeLimitValue patch failed!", "Warning", MB_ICONWARNING);
		return;
	}

	DWORD addr2 = (DWORD)g_pMetaHookAPI->SearchPattern((void *)addr, 0x150, "\x3D\x2A\x2A\x2A\x2A\x7E", 6);
	DWORD addr3 = (DWORD)g_pMetaHookAPI->SearchPattern((void *)(addr2 + 0x8), 0x150, "\x3D\x2A\x2A\x2A\x2A\x7E", 6);

	if (addr2 && addr3)
	{
		g_pMetaHookAPI->WriteDWORD((void *)(addr2 + 0x1), maxvalue);
		g_pMetaHookAPI->WriteDWORD((void *)(addr2 + 0x8), maxvalue);
		g_pMetaHookAPI->WriteDWORD((void *)(addr3 + 0x1), maxvalue);
		g_pMetaHookAPI->WriteDWORD((void *)(addr3 + 0xD), maxvalue);
	}
}

void MemPatch_WideScreenLimit(void)
{
	if (g_dwEngineBuildnum >= 5953)
		return;

	DWORD addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, "\x8B\x51\x08\x8B\x41\x0C\x8B\x71\x54\x8B\xFA\xC1\xE7\x04", 14);

	if (!addr)
	{
		//MessageBox(NULL, "WideScreenLimit patch failed!", "Warning", MB_ICONWARNING);
		return;
	}

	DWORD addr2 = addr + 11;
	DWORD addr3 = (DWORD)g_pMetaHookAPI->SearchPattern((void *)addr, 0x60, "\xB1\x01\x8B\x7C\x24\x14", 6);

	if (addr3)
	{
		g_pMetaHookAPI->WriteNOP((void *)addr2, addr3 - addr2);
	}
}

void MemPatch_PatchSteamCheckSubscribed(void)
{
	unsigned char data[] = { 0x33, 0xC0, 0xC3 };
	DWORD addr = (DWORD)GetProcAddress(GetModuleHandle("steam.dll"), "SteamIsAppSubscribed");

	if (!addr)
		return;

	g_pMetaHookAPI->WriteMemory((void *)addr, data, sizeof(data));
}

void Engine_InstallHook(void)
{
	DWORD addr;

	if(g_dwEngineBuildnum >= 5953)
	{
		gHookFuncs.SV_StudioSetupBones = (void (*)(model_t *, float , int , vec_t *, vec_t *, const byte *, const byte *, int , const edict_t *))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, SV_STUDIOSETUPBONE_SIG_NEW, sizeof(SV_STUDIOSETUPBONE_SIG_NEW)-1);
		if(!gHookFuncs.SV_StudioSetupBones)
			SIG_NOT_FOUND("SV_StudioSetupBones");

		gHookFuncs.R_DrawViewModel = (void (*)(void))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, R_DRAWVIEWMODEL_SIG_NEW, sizeof(R_DRAWVIEWMODEL_SIG_NEW)-1);
		if(!gHookFuncs.R_DrawViewModel)
			SIG_NOT_FOUND("R_DrawViewModel");

		gHookFuncs.S_StopSound = (void (*)(int, int))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, S_STOPSOUND_SIG_NEW, sizeof(S_STOPSOUND_SIG_NEW)-1);
		if(!gHookFuncs.S_StopSound)
			SIG_NOT_FOUND("S_StopSound");
	}
	else
	{
		gHookFuncs.SV_StudioSetupBones = (void (*)(model_t *, float , int , vec_t *, vec_t *, const byte *, const byte *, int , const edict_t *))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, SV_STUDIOSETUPBONE_SIG, sizeof(SV_STUDIOSETUPBONE_SIG)-1);
		if(!gHookFuncs.SV_StudioSetupBones)
			gHookFuncs.SV_StudioSetupBones = (void (*)(model_t *, float , int , vec_t *, vec_t *, const byte *, const byte *, int , const edict_t *))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, SV_STUDIOSETUPBONE_SIG2, sizeof(SV_STUDIOSETUPBONE_SIG2)-1);
		if(!gHookFuncs.SV_StudioSetupBones)
			SIG_NOT_FOUND("SV_StudioSetupBones");

		gHookFuncs.R_DrawViewModel = (void (*)(void))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, R_DRAWVIEWMODEL_SIG, sizeof(R_DRAWVIEWMODEL_SIG)-1);
		if(!gHookFuncs.R_DrawViewModel)
			SIG_NOT_FOUND("R_DrawViewModel");

		gHookFuncs.S_StopSound = (void (*)(int, int))g_pMetaHookAPI->SearchPattern((void *)g_dwEngineBase, g_dwEngineSize, S_STOPSOUND_SIG, sizeof(S_STOPSOUND_SIG)-1);
		if(!gHookFuncs.S_StopSound)
			SIG_NOT_FOUND("S_StopSound");
	}

	//cmp     dword ptr envmap, edi
	//jnz
	//fld     r_drawentities.value
#define ENVMAP_SIG "\x39\x3D\x2A\x2A\x2A\x2A\x0F\x85\x2A\x2A\x2A\x2A\xD9\x05"
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)gHookFuncs.R_DrawViewModel, 0x200, ENVMAP_SIG, sizeof(ENVMAP_SIG)-1);
	if(!addr)
		SIG_NOT_FOUND("envmap");
	envmap = *(int **)(addr + 2);

	//hook funcs at the end, just in case
	g_pMetaHookAPI->InlineHook((void *)gHookFuncs.R_DrawViewModel, Hook_R_DrawViewModel, (void *&)gHookFuncs.R_DrawViewModel);
	g_pMetaHookAPI->InlineHook((void *)gHookFuncs.SV_StudioSetupBones, Hook_SV_StudioSetupBones, (void *&)gHookFuncs.SV_StudioSetupBones);

	MemPatch_HeapSizeLimitValue();
	MemPatch_WideScreenLimit();
	MemPatch_PatchSteamCheckSubscribed();
}

void Viewport_InstallHook(void)
{
#define HUD_CHATINPUT_VIEWPORT "\x8B\x0D"
	DWORD dwViewPort = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->HUD_ChatInputPosition, 0x30, HUD_CHATINPUT_VIEWPORT, sizeof(HUD_CHATINPUT_VIEWPORT)-1);
	if(!dwViewPort)
		SIG_NOT_FOUND("gViewPortInterface");

	gpViewPortInterface = *(DWORD *)(dwViewPort + 2);
	DWORD *dwVFTable = *(DWORD **)gpViewPortInterface;

	gHookFuncs.GetMapName = (const char *(__fastcall *)(void *, int))dwVFTable[42];
	gHookFuncs.GetServerName = (const char *(__fastcall *)(void *, int))dwVFTable[43];
	g_pMetaHookAPI->VFTHook((void *)dwVFTable, 0, 20, Hook_ShowSpectatorGUI, (void *&)gHookFuncs.ShowSpectatorGUI);
	g_pMetaHookAPI->VFTHook((void *)dwVFTable, 0, 21, Hook_ShowSpectatorGUIBar, (void *&)gHookFuncs.ShowSpectatorGUIBar);
}

void API_InstallHook(void)
{
	HMODULE hUser32 = GetModuleHandle("user32.dll");
	
	g_pMetaHookAPI->InlineHook(GetProcAddress(hUser32, "SetWindowTextA"), Hook_SetWindowTextA, (void *&)gHookFuncs.SetWindowTextA);
	g_pMetaHookAPI->InlineHook(GetProcAddress(hUser32, "SetCursorPos"), Hook_SetCursorPos, (void *&)gHookFuncs.SetCursorPos);
	g_pMetaHookAPI->InlineHook(GetProcAddress(hUser32, "CreateWindowExA"), Hook_CreateWindowExA, (void *&)gHookFuncs.CreateWindowExA);
	g_pMetaHookAPI->InlineHook(GetProcAddress(hUser32, "CreateWindowExW"), Hook_CreateWindowExW, (void *&)gHookFuncs.CreateWindowExW);
}

void Client_InstallHook(void)
{
	DWORD addr;

	gHookFuncs.IN_MouseMove = (void (*)(float, usercmd_t *))g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->IN_MouseEvent, 0x300, IN_MOUSEMOVE_SIG, sizeof(IN_MOUSEMOVE_SIG)-1);
	if(!gHookFuncs.IN_MouseMove)
		SIG_NOT_FOUND("IN_MouseMove");

#define GHUD_SIG "\xB9"

	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->HUD_Init, 0x20, GHUD_SIG, sizeof(GHUD_SIG)-1);
	if(!addr)
		SIG_NOT_FOUND("gHud");
	gHud = *(int *)(addr + 1);
	gHud_iFOV = (int *)(gHud + 88);

#define CL_PPMOVE_SIG "\xE9\x2A\x2A\x2A\x00"
	#define CL_PPMOVE_SIG2 "\xE8"
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->HUD_PlayerMoveInit, 0x50, CL_PPMOVE_SIG, sizeof(CL_PPMOVE_SIG)-1);
	if(!addr)
		addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->HUD_PlayerMoveInit, 0x50, CL_PPMOVE_SIG2, sizeof(CL_PPMOVE_SIG2)-1);
	if(!addr)
		SIG_NOT_FOUND("cl_ppmove");
	addr = GetCallAddress(addr);
	cl_ppmove = *(playermove_t ***)(addr + 5);

#define S_LOOKUPSOUND_SIG "\x50\xE8\x2A\x2A\x2A\x00\x83\xC4\x04"
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pEngineFuncs->pfnPlaySoundByNameAtLocation, 0x100, S_LOOKUPSOUND_SIG, sizeof(S_LOOKUPSOUND_SIG)-1);
	if(!addr)
		SIG_NOT_FOUND("CL_LookupSound");
	gHookFuncs.CL_LookupSound = (sfx_t *(*)(const char *))GetCallAddress(addr+1);

#define S_STARTDYNAMICSOUND_SIG "\x6A\x00\x50\xE8\x2A\x2A\x2A\x00\x83\xC4\x20"
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pEngineFuncs->pfnPlaySoundByNameAtLocation, 0x150, S_STARTDYNAMICSOUND_SIG, sizeof(S_STARTDYNAMICSOUND_SIG)-1);
	if(!addr)
		SIG_NOT_FOUND("S_StartDynamicSound");
	gHookFuncs.S_StartDynamicSound = (void (*)(int, int, sfx_t *, vec3_t, float, float, int, int))GetCallAddress(addr+3);

#define G_LASTFOV_SIG "\xE8\x2A\x2A\x2A\x2A\xD9\x05"
#define G_LASTFOV_SIG2 "\xE8\x2A\x2A\x2A\x2A\x8B\x15"
	//E8 2A 2A 2A 2A		call	HUD_WeaponsPostThink
	//8B 15 2A 2A 2A 2A		mov     edx, g_lastFOV // D9 05 2A 2A 2A 2A		fld     g_lastFOV
    DWORD dwBytesOffset = 7;
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->HUD_PostRunCmd, 0x100, G_LASTFOV_SIG, sizeof(G_LASTFOV_SIG)-1);
	if(!addr)
		addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->HUD_PostRunCmd, 0x100, G_LASTFOV_SIG2, sizeof(G_LASTFOV_SIG2)-1);
	if(!addr)
		SIG_NOT_FOUND("g_lastFOV");
	g_lastFOV = *(float **)(addr + dwBytesOffset);
                                        
#define V_CALCNORMALREFDEF_SIG "\x50\xE8"
	//51					push    eax
	//E8 2A 2A 2A 2A		call	V_CalcIntermissionRefdef / V_CalcNormalRefdef
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)g_pMetaSave->pExportFuncs->V_CalcRefdef, 0x50, V_CALCNORMALREFDEF_SIG, sizeof(V_CALCNORMALREFDEF_SIG)-1);
	if(!addr)
		SIG_NOT_FOUND("V_CalcIntermissionRefdef");
	addr = (DWORD)g_pMetaHookAPI->SearchPattern((void *)(addr + 6), 0x50, V_CALCNORMALREFDEF_SIG, sizeof(V_CALCNORMALREFDEF_SIG)-1);
	if(!addr)
		SIG_NOT_FOUND("V_CalcNormalRefdef");
	gHookFuncs.V_CalcNormalRefdef = (void (*)(struct ref_params_s *))GetCallAddress(addr+1);

	g_pMetaHookAPI->InlineHook((void *)gHookFuncs.V_CalcNormalRefdef, V_CalcNormalRefdef, (void *&)gHookFuncs.V_CalcNormalRefdef);
	g_pMetaHookAPI->InlineHook((void *)gHookFuncs.IN_MouseMove, Hook_IN_MouseMove, (void *&)gHookFuncs.IN_MouseMove);
}

void Ref_Init(void)
{
	HMODULE hRenderer = GetModuleHandle("Renderer.dll");
	if(!hRenderer)
		LIB_NOT_FOUND("Renderer.dll");
	type_Ref_CreateInterface fnCreateInterface = (type_Ref_CreateInterface)GetProcAddress(hRenderer, "Ref_CreateInterface");
	if(!fnCreateInterface)
		LIB_NOT_FOUND("Renderer.dll");
	fnCreateInterface(&gRefExports, &gRefFuncs, &gStudioFuncs);

	refdef = gRefExports.R_GetRefDef();
}