//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "pch_tier0.h"
#include "tier0/minidump.h"
#include "tier0/platform.h"


#if defined( _WIN32 ) && !defined(_X360 ) && ( _MSC_VER >= 1300 )

#include "tier0/valve_off.h"
#define WIN_32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0403
#include <windows.h>
#include <time.h>
#include <dbghelp.h>

#endif

// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"


#if defined( _WIN32 ) && !defined( _X360 )

#if _MSC_VER >= 1300

// MiniDumpWriteDump() function declaration (so we can just get the function directly from windows)
typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)
	(
	HANDLE hProcess, 
	DWORD dwPid, 
	HANDLE hFile, 
	MINIDUMP_TYPE DumpType,
	CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
	CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
	CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
	);


// true if we're currently writing a minidump caused by an assert
static bool g_bWritingNonfatalMinidump = false;
// counter used to make sure minidump names are unique
static int g_nMinidumpsWritten = 0;

//-----------------------------------------------------------------------------
// Purpose: Creates a new file and dumps the exception info into it
// Input  : uStructuredExceptionCode	- windows exception code, unused.
//			pExceptionInfo				- call stack.
//			minidumpType				- type of minidump to write.
//			ptchMinidumpFileNameBuffer	- if not-NULL points to a writable tchar buffer
//										  of length at least _MAX_PATH to contain the name
//										  of the written minidump file on return.
//-----------------------------------------------------------------------------
bool WriteMiniDumpUsingExceptionInfo( 
	unsigned int uStructuredExceptionCode, 
	ExceptionInfo_t * pExceptionInfo, 
	uint32 minidumpType,
	tchar *ptchMinidumpFileNameBuffer /* = NULL */
	)
{
	if ( ptchMinidumpFileNameBuffer )
	{
		*ptchMinidumpFileNameBuffer = tchar( 0 );
	}

	// get the function pointer directly so that we don't have to include the .lib, and that
	// we can easily change it to using our own dll when this code is used on win98/ME/2K machines
	HMODULE hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" );
	if ( !hDbgHelpDll )
		return false;

	bool bReturnValue = false;
	MINIDUMPWRITEDUMP pfnMiniDumpWrite = (MINIDUMPWRITEDUMP) ::GetProcAddress( hDbgHelpDll, "MiniDumpWriteDump" );

	if ( pfnMiniDumpWrite )
	{
		// create a unique filename for the minidump based on the current time and module name
		struct tm curtime;
		Plat_GetLocalTime( &curtime );
		++g_nMinidumpsWritten;

		// strip off the rest of the path from the .exe name
		tchar rgchModuleName[MAX_PATH];
#ifdef TCHAR_IS_WCHAR
		::GetModuleFileNameW( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) );
#else
		::GetModuleFileName( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) );
#endif
		tchar *pch = _tcsrchr( rgchModuleName, '.' );
		if ( pch )
		{
			*pch = 0;
		}
		pch = _tcsrchr( rgchModuleName, '\\' );
		if ( pch )
		{
			// move past the last slash
			pch++;
		}
		else
		{
			pch = _T("unknown");
		}

		
		// can't use the normal string functions since we're in tier0
		tchar rgchFileName[MAX_PATH];
		_sntprintf( rgchFileName, sizeof(rgchFileName) / sizeof(tchar),
			_T("%s_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp"),
			pch,
			g_bWritingNonfatalMinidump ? "assert" : "crash",
			curtime.tm_year + 1900,	/* Year less 2000 */
			curtime.tm_mon + 1,		/* month (0 - 11 : 0 = January) */
			curtime.tm_mday,			/* day of month (1 - 31) */
			curtime.tm_hour,			/* hour (0 - 23) */
			curtime.tm_min,		    /* minutes (0 - 59) */
			curtime.tm_sec,		    /* seconds (0 - 59) */
			g_nMinidumpsWritten		// ensures the filename is unique
			);

		BOOL bMinidumpResult = FALSE;
#ifdef TCHAR_IS_WCHAR
		HANDLE hFile = ::CreateFileW( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
#else
		HANDLE hFile = ::CreateFile( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
#endif

		if ( hFile )
		{
			// dump the exception information into the file
			_MINIDUMP_EXCEPTION_INFORMATION	ExInfo;
			ExInfo.ThreadId	= ::GetCurrentThreadId();
			ExInfo.ExceptionPointers = (PEXCEPTION_POINTERS)pExceptionInfo;
			ExInfo.ClientPointers = FALSE;

			bMinidumpResult = (*pfnMiniDumpWrite)( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)minidumpType, &ExInfo, NULL, NULL );
			::CloseHandle( hFile );

			if ( bMinidumpResult )
			{
				bReturnValue = true;

				if ( ptchMinidumpFileNameBuffer )
				{
					// Copy the file name from "pSrc = rgchFileName" into "pTgt = ptchMinidumpFileNameBuffer"
					tchar *pTgt = ptchMinidumpFileNameBuffer;
					tchar const *pSrc = rgchFileName;
					while ( ( *( pTgt ++ ) = *( pSrc ++ ) ) != tchar( 0 ) )
						continue;
				}
			}

			// fall through to trying again
		}
		
		// mark any failed minidump writes by renaming them
		if ( !bMinidumpResult )
		{
			tchar rgchFailedFileName[MAX_PATH];
			_sntprintf( rgchFailedFileName, sizeof(rgchFailedFileName) / sizeof(tchar), "(failed)%s", rgchFileName );
			rename( rgchFileName, rgchFailedFileName );
		}
	}

	::FreeLibrary( hDbgHelpDll );

	// call the log flush function if one is registered to try to flush any logs
	//CallFlushLogFunc();

	return bReturnValue;
}


void InternalWriteMiniDumpUsingExceptionInfo( unsigned int uStructuredExceptionCode, ExceptionInfo_t * pExceptionInfo )
{
	// First try to write it with all the indirectly referenced memory (ie: a large file).
	// If that doesn't work, then write a smaller one.
	uint32 iType = MINIDUMP_WithDataSegs | MINIDUMP_WithIndirectlyReferencedMemory;
	if ( !WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, iType ) )
	{
		iType = MINIDUMP_WithDataSegs;
		WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, iType );
	}
}

// minidump function to use
static FnMiniDump g_pfnWriteMiniDump = InternalWriteMiniDumpUsingExceptionInfo;

//-----------------------------------------------------------------------------
// Purpose: Set a function to call which will write our minidump, overriding
//			the default function
// Input  : pfn -		Pointer to minidump function to set
// Output :				Previously set function
//-----------------------------------------------------------------------------
FnMiniDump SetMiniDumpFunction( FnMiniDump pfn )
{
	FnMiniDump pfnTemp = g_pfnWriteMiniDump;
	g_pfnWriteMiniDump = pfn;
	return pfnTemp;
}


//-----------------------------------------------------------------------------
// Unhandled exceptions
//-----------------------------------------------------------------------------
static FnMiniDump g_UnhandledExceptionFunction;
static LONG STDCALL ValveUnhandledExceptionFilter( _EXCEPTION_POINTERS* pExceptionInfo )
{
	uint uStructuredExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
	g_UnhandledExceptionFunction( uStructuredExceptionCode, (ExceptionInfo_t*)pExceptionInfo );
	return EXCEPTION_CONTINUE_SEARCH;
}

void MinidumpSetUnhandledExceptionFunction( FnMiniDump pfn )
{
	g_UnhandledExceptionFunction = pfn;
	SetUnhandledExceptionFilter( ValveUnhandledExceptionFilter );
}


//-----------------------------------------------------------------------------
// Purpose: writes out a minidump from the current process
//-----------------------------------------------------------------------------
typedef void (*FnMiniDumpInternal_t)( unsigned int uStructuredExceptionCode, _EXCEPTION_POINTERS * pExceptionInfo );

void WriteMiniDump()
{
	// throw an exception so we can catch it and get the stack info
	g_bWritingNonfatalMinidump = true;
	__try
	{
		::RaiseException
			(
			0,							// dwExceptionCode
			EXCEPTION_NONCONTINUABLE,	// dwExceptionFlags
			0,							// nNumberOfArguments,
			NULL						// const ULONG_PTR* lpArguments
			);

		// Never get here (non-continuable exception)
	}
	// Write the minidump from inside the filter (GetExceptionInformation() is only 
	// valid in the filter)
	__except ( g_pfnWriteMiniDump( 0, (ExceptionInfo_t*)GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER )
	{
	}
	g_bWritingNonfatalMinidump = false;
}

PLATFORM_OVERLOAD bool g_bInException = false;
#include <eh.h>

//-----------------------------------------------------------------------------
// Purpose: Catches and writes out any exception throw by the specified function
//-----------------------------------------------------------------------------
void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
{
	if ( Plat_IsInDebugSession() )
	{
		// don't mask exceptions when running in the debugger
		pfn( argc, argv );
	}
	else
	{
		try
		{
#pragma warning(push)
#pragma warning(disable : 4535) // warning C4535: calling _set_se_translator() requires /EHa
			_set_se_translator( (FnMiniDumpInternal_t)g_pfnWriteMiniDump );
#pragma warning(pop)
			pfn( argc, argv );
		}
		catch (...)
		{
			g_bInException = true;
			Log_Msg( LOG_CONSOLE, _T("Fatal exception caught, minidump written\n") );
			// handle everything and just quit, we've already written out our minidump
		}
	}
}

#else

PLATFORM_INTERFACE void WriteMiniDump()
{
}

PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
{
	pfn( argc, argv );
}

#endif
#elif defined(_X360 )
PLATFORM_INTERFACE void WriteMiniDump()
{
#if !defined( _CERT )
	DmCrashDump(false);
#endif
}

#else // !_WIN32

PLATFORM_INTERFACE void WriteMiniDump()
{
}

PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] )
{
	pfn( argc, argv );
}

#endif