#define GAME_DLL
#define WIN32_LEAN_AND_MEAN

#undef _UNICODE

#include <windows.h>
#include <cdll_int.h>
#include <cbase.h>
#include <convar.h>

#include <string>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>

#include "vfnhook.h"

#include <Steamworks.h>

#include "gminterface/include/GMLuaModule.h"

#include "sigscan/include/sigscan.h"

#include "detours\include\detours.h"
#include "cdetour.h"


#include "cURL/include/curl.h"
#include "cURL/include/types.h"
#include "cURL/include/easy.h"

#pragma comment(lib, "detours.lib")
#pragma comment(lib, "libcurl.lib")
#pragma comment(lib, "steamclient.lib")

#define CURL_STATICLIB

#define SH_URL "http://www.seth.im/sethhack/loader.php"
#define SH_AGENT "SethHack"

bool State = false;
const char* code;

GMOD_MODULE(Open, Close);
	
ILuaInterface *gLua = NULL;
IBaseClientDLL *clientDLL;

CSigScan OpenScript_Sig;
CSigScan Datapack_Sig;

struct MemoryStruct {
  char *memory;
  size_t size;
};

static void *myrealloc(void *ptr, size_t size);
 
static void *myrealloc(void *ptr, size_t size)
{
  if(ptr)
    return realloc(ptr, size);
  else
    return malloc(size);
}
 
static size_t
WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
  size_t realsize = size * nmemb;
  struct MemoryStruct *mem = (struct MemoryStruct *)data;
 
  mem->memory = (char *)myrealloc(mem->memory, mem->size + realsize + 1);
  if (mem->memory) {
    memcpy(&(mem->memory[mem->size]), ptr, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;
  }
  return realsize;
}

DEFVFUNC_(origDispatch, bool, (IBaseClientDLL* clientDLL, int msg_type, bf_read &msg_data));
bool VFUNC newDispatch(IBaseClientDLL* clientDLL, int msg_type, bf_read &msg_data) {
	if(msg_type == 29) {
		int bit = msg_data.GetNumBitsRead();
		char buff[2048];
		msg_data.ReadString((char *)&buff, msg_data.GetNumBytesLeft());

		gLua->Push(gLua->GetGlobal("hook")->GetMember("Call"));
		gLua->Push("SendLua");
		gLua->PushNil();
		gLua->Push(buff);

		gLua->Call(3, 1);

		bool letPass = gLua->GetReturn(0)->isNil();

		if (letPass == false) {
			return true;
		}

		msg_data.Seek(bit);
	}

	return origDispatch(clientDLL, msg_type, msg_data);
}


LUA_FUNCTION( SH_CONPRINT )
{
	gLua->CheckType( 1, GLua::TYPE_TABLE );
	gLua->CheckType( 2, GLua::TYPE_STRING );

	try
	{
		ILuaObject* MsgCol = gLua->GetObject( 1 );
		const char* MsgStr = gLua->GetString( 2 );

		int R = MsgCol->GetMemberInt( "r" );
		int G = MsgCol->GetMemberInt( "g" );
		int B = MsgCol->GetMemberInt( "b" );

		Color Clr;
		Clr.SetColor( R, G, B, 255 );

		ConColorMsg( Clr, MsgStr );
	}
	catch( char* Exc )
	{
		gLua->Error( Exc );
		return 0;
	}
	return 1;
}

LUA_FUNCTION( SH_SETCVAR )
{

	gLua->CheckType(1, GLua::TYPE_CONVAR);
	gLua->CheckType(2, GLua::TYPE_STRING);

	ConVar *uconv = (ConVar*)gLua->GetUserData(1);

	uconv->SetValue(gLua->GetString(2));
	return 0;
}

void DownloadCode()
{

  CURL *curl_handle;

  struct MemoryStruct chunk;
 
  chunk.memory=NULL;
  chunk.size = 0;   
 
  curl_global_init(CURL_GLOBAL_ALL);
 
  curl_handle = curl_easy_init();
 
  curl_easy_setopt(curl_handle, CURLOPT_URL, SH_URL);
 
  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
 
  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
  
  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, SH_AGENT);
 
  curl_easy_perform(curl_handle);
 
  curl_easy_cleanup(curl_handle);
 
  code = chunk.memory;
  if(chunk.memory)
    free(chunk.memory);

  curl_global_cleanup();

}

void SetAddrValue(unsigned char *addr, int offset, unsigned char value)
{
	
	HANDLE hProcess = GetCurrentProcess();

	unsigned long sz;
	WriteProcessMemory(hProcess, addr+offset, &value, 1, &sz);

	CloseHandle(hProcess);
}

void PrintError(const char *error)
{
	Color Col;
	Col.SetColor( 255, 10, 10, 255 );
	ConColorMsg( Col, "Fatal Error in %s\nPlease report this error.", error );
}

int Open(lua_State *L)
{
	gLua = Lua();

	if(State==false)
	{
	CSigScan::sigscan_dllfunc = Sys_GetFactory("client.dll");
	
	if (CSigScan::GetDllMemInfo())
	{
		OpenScript_Sig.Init((unsigned char *)"\x74\x0E\xC7\x44\x24\x04\x8C\x56\x4B\x18\xFF\x25\x94\x93\x3C\x18\x83\x3D\x9C\xEF\x55\x18\x00\x74", "xxxxxx????xx????xx????xx", 24);
		
		if (OpenScript_Sig.is_set)
		{
			unsigned char *sig_addr = (unsigned char *)OpenScript_Sig.sig_addr;

			SetAddrValue(sig_addr, 0, 0xEB);

		}
		else
		{
			PrintError("Openscript");
		}

		Datapack_Sig.Init((unsigned char *)"\x75\x16\x56\x68\xE0\xB8\x4B\x18\xFF\x15\x94\x93\x3C\x18\x83\xC4\x08\x5F\x32\xC0\x5E\xC2\x10\x00\x56\xBF", "xxxx????xx????xxxxxxxxxxxx", 26);

		if (Datapack_Sig.is_set)
		{
			unsigned char *sig_addr = (unsigned char *)Datapack_Sig.sig_addr;

			SetAddrValue(sig_addr, 0, 0xEB);

		}
		else
		{
			PrintError("Datapack/ScriptEnforcer");
		}
	}

	State = true;
	}
	else
	{

		gLua->SetGlobal( "SH_CONPRINT", SH_CONPRINT );
		gLua->SetGlobal( "SH_SETCVAR", SH_SETCVAR );

		CreateInterfaceFn ClientFactory = Sys_GetFactory( "client.dll" );
		clientDLL = (IBaseClientDLL *)ClientFactory("VClient015", NULL);

		HOOKVFUNC(clientDLL, 33, origDispatch, newDispatch);
	}
	return 0;
}

int Close(lua_State *L)
{
	UNHOOKVFUNC(clientDLL, 33, origDispatch);
	gLua = NULL;
	return 0;
}