/*
 * PtokaX - hub server for Direct Connect peer to peer network.

 * Copyright (C) 2004-2022  Petr Kozelka, PPK at PtokaX dot org

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//---------------------------------------------------------------------------
#include "stdinc.h"
//---------------------------------------------------------------------------
#include "ExceptionHandling.h"

#ifdef PTOKAX_DEAD_CODE
//---------------------------------------------------------------------------
#include "LuaInc.h"
#include "ServerManager.h"
#include "utility.h"
//---------------------------------------------------------------------------
#include <Dbghelp.h>
#include <delayimp.h>

#pragma comment(lib, "Dbghelp.lib")
#pragma comment(lib, "DelayImp.lib")

//---------------------------------------------------------------------------
#ifdef _WITH_SQLITE
#include <sqlite3.h>
#elif _WITH_POSTGRES
#include <libpq-fe.h>
#elif _WITH_MYSQL
#include <mysql.h>
#endif
//---------------------------------------------------------------------------
LPTOP_LEVEL_EXCEPTION_FILTER pOldTLEF = nullptr;
string sLogPath, sDebugSymbolsFile;
static const size_t szDebugBufLen = 512;
static char sDebugBuf[szDebugBufLen];
//---------------------------------------------------------------------------

void AppendLog(const char * sData)
{
	FILE * fw  = fopen((sLogPath + "system.log").c_str(), "a");

	if (fw == NULL)
	{
		return;
	}

	time_t acc_time;
	time(&acc_time);

	struct tm * acc_tm;
	acc_tm = localtime(&acc_time);

	strftime(sDebugBuf, szDebugBufLen, "%d.%m.%Y %H:%M:%S", acc_tm);

	fprintf(fw, "%s - %s\n", sDebugBuf, sData);

	fclose(fw);
}
//---------------------------------------------------------------------------

FARPROC WINAPI PtokaX_FailHook(unsigned /*dliNotify*/, PDelayLoadInfo /*pdli*/)
{
#ifdef _BUILD_GUI
	::MessageBox(NULL, "Something bad happen and PtokaX crashed. PtokaX was not able to collect any information why this happen because your operating system"
	             " don't support functionality needed for that. If you know why this crash happen then please report it as bug to PPK@PtokaX.org!",
	             "PtokaX crashed!", MB_OK | MB_ICONERROR);
#else
	AppendLog("Something bad happen and PtokaX crashed. PtokaX was not able to collect any information why this happen because your operating system"
	          " don't support functionality needed for that. If you know why this crash happen then please report it as bug to PPK@PtokaX.org!");
#endif

	exit(EXIT_FAILURE);
}

//---------------------------------------------------------------------------

void GetSourceFileInfo(DWORD64 dw64Address, FILE * pFile)
{
	IMAGEHLP_LINE64 il64LineInfo;
	memset(&il64LineInfo, 0, sizeof(IMAGEHLP_LINE64));
	il64LineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

	DWORD dwDisplacement = 0;

	if (SymGetLineFromAddr64(GetCurrentProcess(), dw64Address, &dwDisplacement, &il64LineInfo) != FALSE)  // V676 It is incorrect to compare the variable of BOOL type with TRUE. http://www.viva64.com/en/d/0310/print/
	{
		// We have sourcefile and linenumber info, write it.
		fprintf(pFile, "%s(%lu): ", il64LineInfo.FileName, il64LineInfo.LineNumber);
	}
	else
	{
		// We don't have sourcefile and linenumber info, let's try module name

		IMAGEHLP_MODULE64 im64ModuleInfo;
		memset(&im64ModuleInfo, 0, sizeof(IMAGEHLP_MODULE64));
		im64ModuleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);

		if (SymGetModuleInfo64(GetCurrentProcess(), dw64Address, &im64ModuleInfo))
		{
			// We found module name, write module name and address
			fprintf(pFile, "%s|0x%08I64X: ", im64ModuleInfo.ModuleName, dw64Address);
		}
		else
		{
			// We don't found module. Write address, it's better than nothing
			fprintf(pFile, "0x%08I64X: ", dw64Address);
		}
	}
}
//---------------------------------------------------------------------------

void GetFunctionInfo(DWORD64 dw64Address, FILE * pFile)
{
	DWORD64 dw64Displacement = 0;

	char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(wchar_t)] = { 0 };
	PSYMBOL_INFO pSym = (PSYMBOL_INFO)buffer;

	pSym->SizeOfStruct = sizeof(SYMBOL_INFO);
	pSym->MaxNameLen = MAX_SYM_NAME;

	if (SymFromAddr(GetCurrentProcess(), dw64Address, &dw64Displacement, pSym) != FALSE)  // V676 It is incorrect to compare the variable of BOOL type with TRUE. http://www.viva64.com/en/d/0310/print/
	{
		// We have decorated name, try to make it readable
		if (UnDecorateSymbolName(pSym->Name, sDebugBuf, szDebugBufLen, UNDNAME_COMPLETE | UNDNAME_NO_THISTYPE | UNDNAME_NO_SPECIAL_SYMS | UNDNAME_NO_MEMBER_TYPE |
		                         UNDNAME_NO_MS_KEYWORDS | UNDNAME_NO_ACCESS_SPECIFIERS) > 0)
		{
			// We have readable name, write it
			fprintf(pFile, "%s\n", sDebugBuf);
			return;
		}
	}

	// We don't found any info, write '?'
	fprintf(pFile, "?\n");
}
//---------------------------------------------------------------------------

LONG WINAPI PtokaX_UnhandledExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo)
{
	static volatile LONG PermLock = 0;

	// When unhandled exception happen then permanently 'lock' here. We terminate after first exception.
	while (InterlockedExchange(&PermLock, 1) == 1)
	{
		::Sleep(10);
	}

	// Set failure hook
	__pfnDliFailureHook2 = PtokaX_FailHook;

	// Check if we have debug symbols
	if (FileExist(sDebugSymbolsFile.c_str()) == false)
	{
#ifdef _BUILD_GUI
		::MessageBox(NULL, "Something bad happen and PtokaX crashed. PtokaX was not able to collect any information why this happen because file with debug symbols"
		             " (PtokaX.pdb) is missing. If you know why this crash happen then please report it as bug to PPK@PtokaX.org!",
		             "PtokaX crashed!", MB_OK | MB_ICONERROR);
#else
		AppendLog("Something bad happen and PtokaX crashed. PtokaX was not able to collect any information why this happen because file with debug symbols"
		          " (PtokaX.pdb) is missing. If you know why this crash happen then please report it as bug to PPK@PtokaX.org!");
#endif

		ExceptionHandlingUnitialize();

		exit(EXIT_FAILURE);
	}

	// Initialize debug symbols
	SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES);
	if (SymInitialize(GetCurrentProcess(), ServerManager::m_sPath.c_str(), TRUE) == FALSE)
	{
#ifdef _BUILD_GUI
		::MessageBox(NULL, "Something bad happen and PtokaX crashed. PtokaX was not able to collect any information why this happen because initializatin of"
		             " debug symbols failed. If you know why this crash happen then please report it as bug to PPK@PtokaX.org!",
		             "PtokaX crashed!", MB_OK | MB_ICONERROR);
#else
		AppendLog("Something bad happen and PtokaX crashed. PtokaX was not able to collect any information why this happen because initializatin of"
		          " debug symbols failed. If you know why this crash happen then please report it as bug to PPK@PtokaX.org!");
#endif

		ExceptionHandlingUnitialize();

		exit(EXIT_FAILURE);
	}

	// Generate crash log filename
	time_t acc_time;
	time(&acc_time);

	struct tm *tm = localtime(&acc_time);
	strftime(sDebugBuf, szDebugBufLen, "Crash-%d.%m.%Y-%H.%M.%S.log", tm);

	// Open crash file
	FILE * pFile = fopen((sLogPath + sDebugBuf).c_str(), "w");
	if (pFile == NULL)
	{
#ifdef _BUILD_GUI
		::MessageBox(NULL, "Something bad happen and PtokaX crashed. PtokaX was not able to create file with information why this crash happen."
		             " If you know why this crash happen then please report it as bug to PPK@PtokaX.org!",
		             "PtokaX crashed!", MB_OK | MB_ICONERROR);
#else
		AppendLog("Something bad happen and PtokaX crashed. PtokaX was not able to create file with information why this crash happen."
		          " If you know why this crash happen then please report it as bug to PPK@PtokaX.org!");
#endif

		ExceptionHandlingUnitialize();
		SymCleanup(GetCurrentProcess());

		exit(EXIT_FAILURE);
	}

	string sCrashMsg("Something bad happen and PtokaX crashed. PtokaX collected information why this crash happen to file ");
	sCrashMsg += string(sDebugBuf);
	sCrashMsg += ", please send that file to PPK@PtokaX.org!";

	// Write PtokaX version, build and exception code
	fprintf(pFile, "PtokaX version: " PtokaXVersionString " [build " BUILD_NUMBER "]"
#ifdef _M_X64
	        " (x64)"
#endif
#if LUA_VERSION_NUM > 501
	        "\nLua: " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR "." LUA_VERSION_RELEASE
#else
	        "\n" LUA_RELEASE
#endif
#ifdef _WITH_SQLITE
	        "\nSQLite: " SQLITE_VERSION
#elif _WITH_POSTGRES
	        "\nPostgreSQL: %d"
#elif _WITH_MYSQL
	        "\nMySQL: " MYSQL_SERVER_VERSION
#endif
	        "\nException Code: %x\n",
#ifdef _WITH_POSTGRES
	        PQlibVersion(),
#endif
	        pExceptionInfo->ExceptionRecord->ExceptionCode);

	{
		// Write windoze version where we crashed if is possible
		OSVERSIONINFOEX ver;
		memset(&ver, 0, sizeof(OSVERSIONINFOEX));
		ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

		if(GetVersionEx((OSVERSIONINFO*)&ver) != 0)
		{
			fprintf(pFile, "Windows version: %lu.%lu Build: %lu SP: %hu\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.wServicePackMajor);
		}
	}

	// Write date and time when crash happen
	size_t szLen = strftime(sDebugBuf, szDebugBufLen, "Date and time: %d.%m.%Y %H:%M:%S\n\n", tm);
	if (szLen > 0)
	{
		fwrite(sDebugBuf, 1, szLen, pFile);
	}

	STACKFRAME64 sf64CallStack;
	memset(&sf64CallStack, 0, sizeof(STACKFRAME64));

	sf64CallStack.AddrPC.Mode      = AddrModeFlat;
	sf64CallStack.AddrStack.Mode   = AddrModeFlat;
	sf64CallStack.AddrFrame.Mode   = AddrModeFlat;

#ifdef _M_X64
	sf64CallStack.AddrPC.Offset    = pExceptionInfo->ContextRecord->Rip;
	sf64CallStack.AddrStack.Offset = pExceptionInfo->ContextRecord->Rsp;
	sf64CallStack.AddrFrame.Offset = pExceptionInfo->ContextRecord->Rbp;
#else
	sf64CallStack.AddrPC.Offset    = pExceptionInfo->ContextRecord->Eip;
	sf64CallStack.AddrStack.Offset = pExceptionInfo->ContextRecord->Esp;
	sf64CallStack.AddrFrame.Offset = pExceptionInfo->ContextRecord->Ebp;
#endif

	// Write where crash happen
	fprintf(pFile, "Exception location:\n");

	GetSourceFileInfo(sf64CallStack.AddrPC.Offset, pFile);
	GetFunctionInfo(sf64CallStack.AddrPC.Offset, pFile);

	// Try to write callstack
	fprintf(pFile, "\nCall stack:\n");

	// We don't want it like never ending story, limit call stack to 100 lines
	for (uint32_t ui32i = 0; ui32i < 100; ui32i++)
	{
		if (StackWalk64(
#ifdef _M_X64
		            IMAGE_FILE_MACHINE_AMD64,
#else
		            IMAGE_FILE_MACHINE_I386,
#endif
		            GetCurrentProcess(), GetCurrentThread(), &sf64CallStack, pExceptionInfo->ContextRecord, NULL,
		            SymFunctionTableAccess64, SymGetModuleBase64, NULL) == FALSE || sf64CallStack.AddrFrame.Offset == 0)
		{
			break;
		}

		GetSourceFileInfo(sf64CallStack.AddrPC.Offset, pFile);
		GetFunctionInfo(sf64CallStack.AddrPC.Offset, pFile);
	}

	fclose(pFile);

#ifdef _BUILD_GUI
	::MessageBox(NULL, sCrashMsg.c_str(), "PtokaX crashed!", MB_OK | MB_ICONERROR);
#else
	AppendLog(sCrashMsg.c_str());
#endif

	ExceptionHandlingUnitialize();
	SymCleanup(GetCurrentProcess());

	exit(EXIT_FAILURE);
}
//---------------------------------------------------------------------------

void ExceptionHandlingInitialize(const string &sPath, const char * sAppPath)
{
	sLogPath = sPath + "\\logs\\";

	size_t szBufLen = strlen(sAppPath);
	if (szBufLen > 3  && tolower(sAppPath[szBufLen - 3]) == 'e' && tolower(sAppPath[szBufLen - 2]) == 'x' && tolower(sAppPath[szBufLen - 1]) == 'e')
	{
		sAppPath[szBufLen - 3] = 'p';
		sAppPath[szBufLen - 2] = 'd';
		sAppPath[szBufLen - 1] = 'b';
	}

	sDebugSymbolsFile = sAppPath;

	// Set PtokaX unhandled exception filter
	pOldTLEF = SetUnhandledExceptionFilter(&PtokaX_UnhandledExceptionFilter);
}
//---------------------------------------------------------------------------

void ExceptionHandlingUnitialize()
{
	// Restore old Top Level Exception Filter
	SetUnhandledExceptionFilter(pOldTLEF);
}
//---------------------------------------------------------------------------
#endif //  PTOKAX_DEAD_CODE

V1042 This file is marked with copyleft license, which requires you to open the derived source code.