#include "stinc.h"
#include "stmisc.h"
#include "sthooks.h"
#include "stgame.h"
#include "stplayer.h"
#include "stanticheat.h"
#include "stvehicle.h"
#include "stweapon.h"
#include "stcrate.h"
#include "stchat.h"
#include "stteamcommander.h"
#include "sttibcrystal.h"
#include "sttranslate.h"
#include "sttestzone.h"
#include "stconsolecommands.h"
#include "stnet.h"
#include "stveteran.h"

// Forward
long __stdcall gzExceptionHandler(struct _EXCEPTION_POINTERS *lpExceptionInfo);

/********************/
/* Initial routines */
/********************/
gzInitClass::gzInitClass()
{
	HMODULE bhs = LoadLibrary("bhs.dll");
	if (bhs)
	{
		unsigned long GetExplosionAddr = (unsigned long)GetProcAddress(bhs, "GetExplosionObj");
		if (GetExplosionAddr)
			gzManagerClass::m_ExplosionObj = *(nc_ScriptableGameObj **)(GetExplosionAddr + 1);
		else
			stConsole::Out("[Warning] Unable to get the offset of \"GetExplosionObj\" from bhs.dll.\n");

		// Commands replacement
		gzCommands->Enable_Stealth = (_nc_Enable_Stealth)GetProcAddress(bhs, "New_Enable_Stealth");
		gzCommands->Create_Explosion = (_nc_Create_Explosion)GetProcAddress(bhs, "New_Create_Explosion");
		gzCommands->Create_Explosion_At_Bone = (_nc_Create_Explosion_At_Bone)GetProcAddress(bhs, "New_Create_Explosion_At_Bone");
		gzCommands->Set_Fog_Enable = (_nc_Set_Fog_Enable)GetProcAddress(bhs, "New_Set_Fog_Enable");
		gzCommands->Set_Fog_Range = (_nc_Set_Fog_Range)GetProcAddress(bhs, "New_Set_Fog_Range");
		gzCommands->Set_War_Blitz = (_nc_Set_War_Blitz)GetProcAddress(bhs, "New_Set_War_Blitz");
		gzCommands->Fade_Background_Music = (_nc_Fade_Background_Music)GetProcAddress(bhs, "New_Fade_Background_Music");
		gzCommands->Set_Background_Music = (_nc_Set_Background_Music)GetProcAddress(bhs, "New_Set_Background_Music");
		gzCommands->Stop_Background_Music = (_nc_Stop_Background_Music)GetProcAddress(bhs, "New_Stop_Background_Music");
		gzCommands->Stop_Background_Music = (_nc_Stop_Background_Music)GetProcAddress(bhs, "New_Stop_Background_Music");
		gzCommands->Create_Sound = (_nc_Create_Sound)GetProcAddress(bhs, "New_Create_Sound");
		gzCommands->Create_2D_Sound = (_nc_Create_2D_Sound)GetProcAddress(bhs, "New_Create_2D_Sound");
		gzCommands->Create_2D_WAV_Sound = (_nc_Create_2D_WAV_Sound)GetProcAddress(bhs, "New_Create_2D_WAV_Sound");
		gzCommands->Create_3D_WAV_Sound_At_Bone = (_nc_Create_3D_WAV_Sound_At_Bone)GetProcAddress(bhs, "New_Create_3D_WAV_Sound_At_Bone");
		gzCommands->Create_3D_Sound_At_Bone = (_nc_Create_3D_Sound_At_Bone)GetProcAddress(bhs, "New_Create_3D_Sound_At_Bone");
		gzCommands->Play_Building_Announcement = (_nc_Play_Building_Announcement)GetProcAddress(bhs, "New_Play_Building_Announcement");
		gzCommands->Clear_Weapons = (_nc_Clear_Weapons)GetProcAddress(bhs, "New_Clear_Weapons");
		gzCommands->Enable_Vehicle_Transitions = (_nc_Enable_Vehicle_Transitions)GetProcAddress(bhs, "New_Enable_Vehicle_Transitions");
		gzCommands->Set_Player_Type = (_nc_Set_Player_Type)GetProcAddress(bhs, "New_Set_Player_Type");
		gzCommands->Set_Screen_Fade_Color = (_nc_Set_Screen_Fade_Color)GetProcAddress(bhs, "New_Set_Screen_Fade_Color");
		gzCommands->Set_Screen_Fade_Opacity = (_nc_Set_Screen_Fade_Opacity)GetProcAddress(bhs, "New_Set_Screen_Fade_Opacity");
		gzCommands->Set_Display_Color = (_nc_Set_Display_Color)GetProcAddress(bhs, "New_Set_Display_Color");
		gzCommands->Display_Text = (_nc_Display_Text)GetProcAddress(bhs, "New_Display_Text");
		gzCommands->Display_Float = (_nc_Display_Float)GetProcAddress(bhs, "New_Display_Float");
		gzCommands->Display_Int = (_nc_Display_Int)GetProcAddress(bhs, "New_Display_Int");
		gzCommands->Select_Weapon = (_nc_Select_Weapon)GetProcAddress(bhs, "New_Select_Weapon");
		gzCommands->Shake_Camera = (_nc_Shake_Camera)GetProcAddress(bhs, "New_Shake_Camera");
		gzCommands->Set_Model = (_nc_Set_Model)GetProcAddress(bhs, "New_Set_Model");

		// New features from bhs.dll
		gzCreate_2D_WAV_Sound_Player = (_gzCreate_2D_WAV_Sound_Player)GetProcAddress(bhs, "New_Create_2D_WAV_Sound_Player");
		gzDisplay_Text_Player = (_gzDisplay_Text_Player)GetProcAddress(bhs, "New_Display_Text_Player");
		gzDisplay_Int_Player = (_gzDisplay_Int_Player)GetProcAddress(bhs, "New_Display_Int_Player");
		gzDisplay_Float_Player = (_gzDisplay_Float_Player)GetProcAddress(bhs, "New_Display_Float_Player");
		gzAddKeyHook = (gz_akh)GetProcAddress(bhs, "AddKeyHook");
		gzRemoveKeyHook = (gz_rkh)GetProcAddress(bhs, "RemoveKeyHook");
	}

	// Initial event sort variables
	for (int i = 0; i < EVT_COUNT_TYPE; i++)
	{
		for (int j = 0; j < EVT_COUNT_SUBTYPE; j++)
			EventManager::m_eventTypeSort[i][j] = false;
	}

	gzManager				= new gzManagerClass;
	gzSettingsMgr			= new gzSettingsManager;
	gzGameMgr				= new gzGameManager;
	gzTranslator			= new gzTranslateManager;
	gzChat					= new gzChatManagerClass;
	gzVehicleMgr			= new gzManagerVehicle;
	gzPlayerMgr				= new gzPlayerManager;
	gzTestZoneMgr			= new gzTestZoneManager;
	gzWeaponMgr				= new gzWeaponManager;
	gzCrateMgr				= new gzManagerCrateClass;
	gzSerialMgr				= new gzSerialManager;
	gzTeamCommander			= new gzTeamCommanderClass;
	gzAntiCheatMgr			= new gzAntiCheatManager;
	gzWeaponDropMgr			= new gzWeaponDropManager;
	gzTibCrystalMgr			= new gzTibCrystalManager;
	gzGameSpyQueryMgr		= new gzGameSpyQueryManager;
	gzVeteranMgr			= new gzVeteranManagerClass;

#if VERC(1, 1, 0)
	stDb                    = new stDbManager;
#endif

	SetUnhandledExceptionFilter(gzExceptionHandler);

#ifdef __BETA
	stVersion.Printf("[STGM] %d.%d.%d Beta %d", __VER_MAJOR, __VER_MINOR, __VER_MINOR2, __BETA);
#elif defined(__ALPHA)
	stVersion.Printf("[STGM] %d.%d.%d Alpha %d", VER_MAJOR, VER_MINOR, VER_MINOR2, __ALPHA);
#else
	stVersion.Printf("[STGM] %d.%d.%d", __VER_MAJOR, __VER_MINOR, __VER_MINOR2);
#endif

	stConsole::Out("%s successfully loaded - Built on %s %s\n", stVersion.GetString(), __DATE__, __TIME__);
}
gzInitClass::~gzInitClass()
{
#if ISDEV()
	stConsole::Out("Unload routine activated.\n");
#endif
#if VERC(1, 1, 0)
	delete stDb;
#endif
	for (unsigned int i = 0; i < gzBaseManager::m_objList.Count(); i++)
		stConsole::Out("[%d] %s\n", i + 1, gzBaseManager::m_objList[i]->GetName());
	gzBaseManager::Unload();
	stVersion.Clear();

#ifdef _MEMTRACK
	char memlog[24];
	for (int i = 1; ; i++)
	{
		sprintf(memlog, "memreport_gz%d.log", i);
		FILE *memlogFile = fopen(memlog, "r");
		if (!memlogFile)
			break;
		fclose(memlogFile);
	}
	m_dumpMemoryReport(memlog);
#endif
}
gzInitClass *gzInit = NULL;


/******************/
/* Unload routine */
/******************/
gzUnloadClass::~gzUnloadClass()
{
	if (gzManagerClass::m_initial)
	{
		if (gzInit)
			delete gzInit;
	}
}
gzUnloadClass gzUnload;


/****************/
/* Main manager */
/****************/
bool gzManagerClass::m_initial = false;
nc_ScriptableGameObj *gzManagerClass::m_ExplosionObj = NULL;
int gzManagerClass::m_sFps = 0;
gzManagerClass *gzManager = NULL;
gzManagerClass::gzManagerClass()
{
	this->RegisterEvent(EVT_GAME_LEVEL_LOADED);
	this->RegisterEvent(EVT_GAME_THINK);

	nc_ConsoleFunctionManager::Sort_Function_List();
	nc_ConsoleFunctionManager::Verbose_Help_File();
}
void gzManagerClass::Delete()
{
	gzHookManager::m_list.Clear();
}
void gzManagerClass::Level_Loaded()
{
	for (int i = 0; i < nc_ConsoleFunctionManager::FunctionList.Count(); i++) {
		unsigned long offset = (unsigned long)*(unsigned long *)*&nc_ConsoleFunctionManager::FunctionList[i];
		if (offset >= 0x45000000)
		{
			if (!stricmp(nc_ConsoleFunctionManager::FunctionList[i]->Get_Name(),"team") ||
				!stricmp(nc_ConsoleFunctionManager::FunctionList[i]->Get_Name(),"team2") ||
				!stricmp(nc_ConsoleFunctionManager::FunctionList[i]->Get_Name(),"plimit"))
			{
				nc_ConsoleFunctionManager::FunctionList.Delete(i);
				i--;
			}
		}

		// Remove original player_info in server.dat
		else if (offset == 0x7CD0D0 && !stricmp(nc_ConsoleFunctionManager::FunctionList[i]->Get_Name(),"player_info"))
		{
			nc_ConsoleFunctionManager::FunctionList.Delete(i);
			i--;
		}
	}

	nc_ConsoleFunctionManager::Sort_Function_List();
	nc_ConsoleFunctionManager::Verbose_Help_File();

	gzBaseManager::Level_Loaded();

	// Remove the limit from bhs.dll in nickname exploit fix. Offset is retrieved from 0x426B8F
	unsigned long pLimitMax = 0;
	memcpy(&pLimitMax, (void *)0x426B8F, 4);
	if (pLimitMax >= 0x45000000 && pLimitMax < 0x45100000)
		*(int *)pLimitMax = 127;

	// Remove "plimit" patch placed by bhs.dll in game_info
	unsigned char disableBHSpLimit[] = { 0x8B, 0xB0, 0xF8, 0x01, 0x00, 0x00 };
	PatchData(&disableBHSpLimit, 6, 0x426B8D);
	PatchData(&disableBHSpLimit, 6, 0x426BE3);

	// gcd_think bhs.dll patch removal
	unsigned char gcd_bhs_patch[2] = { 0xFF, 0x74 };
	PatchData(&gcd_bhs_patch, 2, 0x7730E5);
	PatchData(&gcd_bhs_patch, 2, 0x775094);

	// Patch to correct GID to GameSpy CD Key Authentication
	unsigned char GSA_GID_Patch[] = { 0xBA, 0x41, 0x02, 0x00, 0x00, 0x90 };
	PatchData(&GSA_GID_Patch, 6, 0x772E2E);
}
void gzManagerClass::Think()
{
	gzBaseManager::Think();
}


/********/
/* stDB */
/********/
stDbManager *stDb = NULL;
stDbManager::stDbManager()
{
	if (!this->Load("stgm.dat"))
		stConsole::Out("[ST] Error: Unable to load stDB; %s\n", this->GetError().GetString());
#ifdef DEVMSG
	else
		stConsole::Out("[ST] stDB successfully loaded.\n");
#endif
}
stDbManager::~stDbManager()
{
	printf("Please be patient and do NOT terminate FDS forcefully while STGM is shrinking database.\n");
	if (!this->Shrink())
		stConsole::Out("[ST] Error: Unable to shrink database; %s\n", this->GetError().GetString());
	else
		printf("STGM database has been successfully shrinked.\n");
}
void stDbManager::PrintError()
{
	stConsole::Out("[ST] SQL Error: %s [%s]\n", this->GetError().GetString(), this->GetLastQuery().GetString());
}

/***************/
/* Console I/O */
/***************/
void stConsole::In(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	char buf[256];
	_vsnprintf(buf, 255, format, args);
	_asm {
		mov eax, 0x428960
		lea edx, buf
		push edx
		call eax
	}
	va_end(args);
}
void stConsole::Out(const char *format, ...)
{
	if (!format || !*format)
		return;

	aString msg = format;
	aDateTime now = aDateTime::Now();
	va_list args;
	va_start(args, format);
	msg.Format_Args(format, args);
	aFile renlog(aString::Format("renlog_%d-%d-%04d.txt", now.GetMonth(), now.GetDay(), now.GetYear()), "a");
	if (renlog.IsOpened())
		renlog.Write(aString::Format("[%02d:%02d:%02d] ", now.GetHour(), now.GetMinute(), now.GetSecond()) + msg);

	// RenRem output
	nc_StringClass &renrem = *(nc_StringClass *)0x81E540;
	renrem.Resize(renrem.Get_Length() + msg.GetLength() + 1);
	strcat(renrem.m_Buffer, msg.GetString());

	printf("%s", msg.GetString());
}


/*********************/
/* Exception handler */
/*********************/

long __stdcall gzExceptionHandler(EXCEPTION_POINTERS *lpExceptionInfo)
{
	// Create dir
	_mkdir("crashdump");

	aDateTime now = aDateTime::Now();

	// Create file
	HANDLE dumpFile = CreateFile(
		aString::Format("crashdump\\dump_%d-%d-%d_%02d;%02d;%02d.dmp", now.GetDay(), now.GetMonth(), now.GetYear(), now.GetHour(), now.GetMinute(), now.GetSecond()).GetString(),
		GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ,
		0,
		CREATE_ALWAYS,
		0,
		0
	);

	// Initial needed parameters
	MINIDUMP_EXCEPTION_INFORMATION expParam;
	expParam.ThreadId = GetCurrentThreadId();
	expParam.ExceptionPointers = lpExceptionInfo;
	expParam.ClientPointers = true;

	// Set dump options
	MINIDUMP_TYPE dumpOption = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory);
	//MINIDUMP_TYPE dumpOption = (MINIDUMP_TYPE)2050;

	// Write to file
	MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, dumpOption, &expParam, NULL, NULL);

	// Delete stuff
	CloseHandle(dumpFile);

	return EXCEPTION_EXECUTE_HANDLER;
}
