/**
 * This file is part of Hercules.
 * http://herc.ws - http://github.com/HerculesWS/Hercules
 *
 * Copyright (C) 2012-2018  Hercules Dev Team
 * Copyright (C)  Athena Dev Teams
 *
 * Hercules is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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/>.
 */

// Ported from eAthena Dev Team's version @ http://eathena-project.googlecode.com/svn/trunk/src/plugins/dbghelpplug.c
// Currently supported dbghelp 5.1

#include "common/hercules.h"

#include "common/sysinfo.h"

#include "common/HPMDataCheck.h"

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

/**
 * Plugin basic information
 **/
HPExport struct hplugin_info pinfo = {
	"Debug Help",
	SERVER_TYPE_ALL,
	"0.5",
	HPM_VERSION,
};

/////////////////////////////////////////////////////////////////////
// Include files
//
#include <assert.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define _NO_CVCONST_H
#include <dbghelp.h>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/////////////////////////////////////////////////////////////////////
// Types from Cvconst.h (DIA SDK)
//

#ifdef _NO_CVCONST_H

typedef enum _BasicType {
	btNoType   = 0,
	btVoid     = 1,
	btChar     = 2,
	btWChar    = 3,
	btInt      = 6,
	btUInt     = 7,
	btFloat    = 8,
	btBCD      = 9,
	btBool     = 10,
	btLong     = 13,
	btULong    = 14,
	btCurrency = 25,
	btDate     = 26,
	btVariant  = 27,
	btComplex  = 28,
	btBit      = 29,
	btBSTR     = 30,
	btHresult  = 31
} BasicType;

typedef enum _UdtKind
{
	UdtStruct,
	UdtClass,
	UdtUnion
} UdtKind;
/*
typedef enum _SymTag {
	SymTagNull             = 0,
	SymTagExe              = 1,
	SymTagCompiland        = 2,
	SymTagCompilandDetails = 3,
	SymTagCompilandEnv     = 4,
	SymTagFunction         = 5,
	SymTagBlock            = 6,
	SymTagData             = 7,
	SymTagAnnotation       = 8,
	SymTagLabel            = 9,
	SymTagPublicSymbol     = 10,
	SymTagUDT              = 11,
	SymTagEnum             = 12,
	SymTagFunctionType     = 13,
	SymTagPointerType      = 14,
	SymTagArrayType        = 15,
	SymTagBaseType         = 16,
	SymTagTypedef          = 17,
	SymTagBaseClass        = 18,
	SymTagFriend           = 19,
	SymTagFunctionArgType  = 20,
	SymTagFuncDebugStart   = 21,
	SymTagFuncDebugEnd     = 22,
	SymTagUsingNamespace   = 23,
	SymTagVTableShape      = 24,
	SymTagVTable           = 25,
	SymTagCustom           = 26,
	SymTagThunk            = 27,
	SymTagCustomType       = 28,
	SymTagManagedType      = 29,
	SymTagDimension        = 30
} SymTag;
*/
#endif /* _NO_CVCONST_H */

/////////////////////////////////////////////////////////////////////
// dbghelp function prototypes
//

typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(
	HANDLE hProcess,
	DWORD ProcessId,
	HANDLE hFile,
	MINIDUMP_TYPE DumpType,
	CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
	CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
	CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
typedef BOOL (WINAPI *SYMINITIALIZE)(
	HANDLE  hProcess,
	PSTR    UserSearchPath,
	BOOL    fInvadeProcess
);
typedef DWORD (WINAPI *SYMSETOPTIONS)(
	DWORD   SymOptions
);
typedef DWORD (WINAPI *SYMGETOPTIONS)(
	VOID
);
typedef BOOL (WINAPI *SYMCLEANUP)(
	HANDLE  hProcess
);
typedef BOOL (WINAPI *SYMGETTYPEINFO)(
	HANDLE                      hProcess,
	DWORD64                     ModBase,
	ULONG                       TypeId,
	IMAGEHLP_SYMBOL_TYPE_INFO   GetType,
	PVOID                       pInfo
);
typedef BOOL (WINAPI *SYMGETLINEFROMADDR)(
	HANDLE          hProcess,
	DWORD           dwAddr,
	PDWORD          pdwDisplacement,
	PIMAGEHLP_LINE  Line
);
typedef BOOL (WINAPI *SYMENUMSYMBOLS)(
	HANDLE                          hProcess,
	ULONG64                         BaseOfDll,
	PCSTR                           Mask,
	PSYM_ENUMERATESYMBOLS_CALLBACK  EnumSymbolsCallback,
	PVOID                           UserContext
);
typedef BOOL (WINAPI *SYMSETCONTEXT)(
	HANDLE                  hProcess,
	PIMAGEHLP_STACK_FRAME   StackFrame,
	PIMAGEHLP_CONTEXT       Context
);
typedef BOOL (WINAPI *SYMFROMADDR)(
	HANDLE          hProcess,
	DWORD64         Address,
	PDWORD64        Displacement,
	PSYMBOL_INFO    Symbol
);
typedef BOOL (WINAPI *STACKWALK)(
	DWORD                           MachineType,
	HANDLE                          hProcess,
	HANDLE                          hThread,
	LPSTACKFRAME                    StackFrame,
	PVOID                           ContextRecord,
	PREAD_PROCESS_MEMORY_ROUTINE    ReadMemoryRoutine,
	PFUNCTION_TABLE_ACCESS_ROUTINE  FunctionTableAccessRoutine,
	PGET_MODULE_BASE_ROUTINE        GetModuleBaseRoutine,
	PTRANSLATE_ADDRESS_ROUTINE      TranslateAddress
);
typedef PVOID (WINAPI *SYMFUNCTIONTABLEACCESS)(
	HANDLE  hProcess,
	DWORD   AddrBase
);
typedef DWORD (WINAPI *SYMGETMODULEBASE)(
	HANDLE  hProcess,
	DWORD   dwAddr
);

/////////////////////////////////////////////////////////////////////
// Custom info

/// Internal structure used to pass some data around
typedef struct _InternalData {
	// PrintStacktrace
	FILE* log_file;
	STACKFRAME* pStackframe;
	HANDLE hProcess;
	DWORD nr_of_frame;

	// PrintFunctionDetail
	BOOL as_arg_list;
	BOOL log_params;
	BOOL log_locals;
	BOOL log_globals;
	DWORD nr_of_var;

	// PrintDataInfo
	ULONG64 modBase;
} InterData;

/// dbghelp dll filename
#define DBGHELP_DLL "dbghelp.dll"

// Default report filename, used when the module path is unavailable
#define DBG_DEFAULT_FILENAME "hercules"

// Extended information printed in the console
#define DBG_EXTENDED_INFORMATION \
		"Please report the crash in our Issues tracker:\n" \
		"https://github.com/HerculesWS/Hercules/issues\n"

// Print object children?
// WARNING: This will generate huge dump files!
//#define DBG_PRINT_CHILDREN

/////////////////////////////////////////////////////////////////////
// Global variables

HANDLE dbghelp_dll = INVALID_HANDLE_VALUE;
MINIDUMPWRITEDUMP MiniDumpWriteDump_ = NULL;
SYMINITIALIZE SymInitialize_ = NULL;
SYMSETOPTIONS SymSetOptions_ = NULL;
SYMGETOPTIONS SymGetOptions_ = NULL;
SYMCLEANUP SymCleanup_ = NULL;
SYMGETTYPEINFO SymGetTypeInfo_ = NULL;
SYMGETLINEFROMADDR SymGetLineFromAddr_ = NULL;
SYMENUMSYMBOLS SymEnumSymbols_ = NULL;
SYMSETCONTEXT SymSetContext_ = NULL;
SYMFROMADDR SymFromAddr_ = NULL;
STACKWALK StackWalk_ = NULL;
SYMFUNCTIONTABLEACCESS SymFunctionTableAccess_ = NULL;
SYMGETMODULEBASE SymGetModuleBase_ = NULL;

/////////////////////////////////////////////////////////////////////
// Code

/// Writes the minidump to file. The callback function will at the
/// same time write the list of modules to the log file.
///
/// @param file Filename of the minidump
/// @param ptrs Exception info
/// @param module_callback Callback for MiniDumpWriteDump
/// @param log_file Log file
static VOID
Dhp__WriteMinidumpFile(
	const char*                 file,
	PEXCEPTION_POINTERS         ptrs,
	MINIDUMP_CALLBACK_ROUTINE   module_callback,
	FILE*                       log_file)
{
	// open minidump file
	HANDLE minidump_file = CreateFileA(
		file,
		GENERIC_WRITE,
		0,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);

	if( minidump_file != INVALID_HANDLE_VALUE )
	{
		MINIDUMP_EXCEPTION_INFORMATION expt_info;
		MINIDUMP_CALLBACK_INFORMATION dump_cb_info;

		expt_info.ThreadId = GetCurrentThreadId();
		expt_info.ExceptionPointers = ptrs;
		expt_info.ClientPointers = FALSE;

		dump_cb_info.CallbackRoutine = module_callback;
		dump_cb_info.CallbackParam = (void*)log_file;

		if( module_callback != NULL && log_file != NULL )
			fprintf(log_file, "\n\nLoaded modules:\n");

		MiniDumpWriteDump_(
			GetCurrentProcess(),
			GetCurrentProcessId(),
			minidump_file,
			MiniDumpNormal,
			ptrs ? &expt_info : NULL,
			NULL,
			&dump_cb_info
		);

		CloseHandle(minidump_file);
	}
}

/// Prints module information to the log file.
/// Used as a callback to MiniDumpWriteDump.
///
/// @param data Log file
/// @param callback_input
/// @param callback_output
/// @return
static BOOL CALLBACK
Dhp__PrintModuleInfoCallback(
	void*                           data,
	CONST PMINIDUMP_CALLBACK_INPUT  callback_input,
	PMINIDUMP_CALLBACK_OUTPUT       callback_output)
{
	if( data != NULL &&
		callback_input != NULL &&
		callback_input->CallbackType == ModuleCallback)
	{
		FILE* log_file = (FILE*)data;
		MINIDUMP_MODULE_CALLBACK module = callback_input->Module;

		fprintf(log_file, "0x%p", module.BaseOfImage);

		fprintf(log_file, "  %ws", module.FullPath, log_file);

		fprintf(log_file, "  (%d.%d.%d.%d, %d bytes)\n",
			HIWORD(module.VersionInfo.dwFileVersionMS),
			LOWORD(module.VersionInfo.dwFileVersionMS),
			HIWORD(module.VersionInfo.dwFileVersionLS),
			LOWORD(module.VersionInfo.dwFileVersionLS),
			module.SizeOfImage);
	}

	return TRUE;
}

/// Prints details about the current process, platform and exception
/// information to the log file.
///
/// @param exception Exception info
/// @param context Exception context
/// @param log_file Log file
static VOID
Dhp__PrintProcessInfo(
	EXCEPTION_RECORD*   exception,
	CONTEXT*            context,
	FILE*               log_file)
{
	LPSTR cmd_line;

	fprintf(log_file,
		"\nProcess information:\n");

	// print the command line
	cmd_line = GetCommandLineA();
	if( cmd_line )
	fprintf(log_file,
		"Command line: %s\n",
		cmd_line);

	// Print system information
	if( sysinfo ) {
		fprintf(log_file,
			"Platform: %s\n CPU: %s\nApplication architecture: %s\nCompiler: %s\n%s: %s\n",
			sysinfo->osversion(), sysinfo->cpu(), (sysinfo->is64bit())?"x64":"x86",
			sysinfo->compiler(), sysinfo->vcstype(), sysinfo->vcsrevision_src());
	}

	// print the exception code
	if( exception )
	{
		fprintf(log_file,
			"\nException:\n"
			"0x%x",
			exception->ExceptionCode);
		switch( exception->ExceptionCode )
		{
#define PRINT(x) case x: fprintf(log_file, " "#x); break
		PRINT(EXCEPTION_ACCESS_VIOLATION);
		PRINT(EXCEPTION_DATATYPE_MISALIGNMENT);
		PRINT(EXCEPTION_BREAKPOINT);
		PRINT(EXCEPTION_SINGLE_STEP);
		PRINT(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
		PRINT(EXCEPTION_FLT_DENORMAL_OPERAND);
		PRINT(EXCEPTION_FLT_DIVIDE_BY_ZERO);
		PRINT(EXCEPTION_FLT_INEXACT_RESULT);
		PRINT(EXCEPTION_FLT_INVALID_OPERATION);
		PRINT(EXCEPTION_FLT_OVERFLOW);
		PRINT(EXCEPTION_FLT_STACK_CHECK);
		PRINT(EXCEPTION_FLT_UNDERFLOW);
		PRINT(EXCEPTION_INT_DIVIDE_BY_ZERO);
		PRINT(EXCEPTION_INT_OVERFLOW);
		PRINT(EXCEPTION_PRIV_INSTRUCTION);
		PRINT(EXCEPTION_IN_PAGE_ERROR);
		PRINT(EXCEPTION_ILLEGAL_INSTRUCTION);
		PRINT(EXCEPTION_NONCONTINUABLE_EXCEPTION);
		PRINT(EXCEPTION_STACK_OVERFLOW);
		PRINT(EXCEPTION_INVALID_DISPOSITION);
		PRINT(EXCEPTION_GUARD_PAGE);
		PRINT(EXCEPTION_INVALID_HANDLE);
#undef PRINT
		}

		// print where the fault occured
		fprintf(log_file, " at location 0x%p", exception->ExceptionAddress);

		// if the exception was an access violation, print additional information
		if( exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && exception->NumberParameters >= 2 )
			fprintf(log_file, " %s location 0x%p", exception->ExceptionInformation[0] ? "writing to" : "reading from", exception->ExceptionInformation[1]);

		fprintf(log_file, "\n");
	}

	// print the register info
	if( context )
	{
#if defined(_M_IX86)
		fprintf(log_file,
			"\nRegisters:\n");
		if( context->ContextFlags & CONTEXT_INTEGER )
		{
			fprintf(log_file,
				"eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n",
				context->Eax, context->Ebx, context->Ecx,
				context->Edx, context->Esi, context->Edi);
		}
		if( context->ContextFlags & CONTEXT_CONTROL )
		{
			fprintf(log_file,
				"eip=%08x esp=%08x ebp=%08x iopl=%1x %s %s %s %s %s %s %s %s %s %s\n",
				context->Eip, context->Esp, context->Ebp,
				(context->EFlags >> 12) & 3,                    //  IOPL level value
				(context->EFlags & 0x00100000) ? "vip" : "   ", //  VIP (virtual interrupt pending)
				(context->EFlags & 0x00080000) ? "vif" : "   ", //  VIF (virtual interrupt flag)
				(context->EFlags & 0x00000800) ? "ov"  : "nv",  //  VIF (virtual interrupt flag)
				(context->EFlags & 0x00000400) ? "dn"  : "up",  //  OF (overflow flag)
				(context->EFlags & 0x00000200) ? "ei"  : "di",  //  IF (interrupt enable flag)
				(context->EFlags & 0x00000080) ? "ng"  : "pl",  //  SF (sign flag)
				(context->EFlags & 0x00000040) ? "zr"  : "nz",  //  ZF (zero flag)
				(context->EFlags & 0x00000010) ? "ac"  : "na",  //  AF (aux carry flag)
				(context->EFlags & 0x00000004) ? "po"  : "pe",  //  PF (parity flag)
				(context->EFlags & 0x00000001) ? "cy"  : "nc"); //  CF (carry flag)
		}
		if( context->ContextFlags & CONTEXT_SEGMENTS )
		{
			fprintf(log_file,
				"cs=%04x  ss=%04x  ds=%04x  es=%04x  fs=%04x  gs=%04x",
				context->SegCs,
				context->SegSs,
				context->SegDs,
				context->SegEs,
				context->SegFs,
				context->SegGs);
			if( context->ContextFlags & CONTEXT_CONTROL )
				fprintf(log_file,
					"             efl=%08x",
					context->EFlags);
			fprintf(log_file, "\n");
		}
		else if( context->ContextFlags & CONTEXT_CONTROL )
			fprintf(log_file,
					"                                                                       efl=%08x\n",
					context->EFlags);
#else /* defined(_M_IX86) */
		//TODO add more processors
#endif
	}
}

/// Prints the typename of the symbol.
///
/// @param typeIndex Type index of the symbol
/// @param symtag Symbol tag
/// @param withParens If brackets are printed around the typename
/// @param interData Inter data
static VOID
Dhp__PrintTypeName(
	DWORD       typeIndex,
	DWORD       symtag,
	BOOL        withParens,
	InterData*  interData)
{
	// inter data
	FILE* log_file;
	HANDLE hProcess;
	ULONG64 modBase;
	//
	assert( interData != NULL );
	log_file = interData->log_file;
	hProcess = interData->hProcess;
	modBase  = interData->modBase;

	if( withParens )
		fprintf(log_file, "(");

	switch( symtag )
	{
	case SymTagEnum:
		{
			WCHAR* pwszTypeName;

			if( SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_SYMNAME, &pwszTypeName) )
			{
				fprintf(log_file, "enum %ls", pwszTypeName);
				LocalFree(pwszTypeName);
			}
			else
				fprintf(log_file, "enum <symname not found>");
		}
		break;
	case SymTagBaseType:
		{
			DWORD basetype;
			ULONG64 length = 0;

			SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_LENGTH, &length);
			if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &basetype) )
			{
				fprintf(log_file, "<basetype not found>");
				break;
			}
			switch( basetype )
			{
			case btVoid: fprintf(log_file, "void"); break;
			case btChar: fprintf(log_file, "char"); break;
			case btWChar: fprintf(log_file, "wchar"); break;
			case btULong: fprintf(log_file, "unsigned "); // next
			case btLong: fprintf(log_file, "long"); break;
			case btUInt: fprintf(log_file, "unsigned "); // next
			case btInt:
				if( length == sizeof(char) ) fprintf(log_file, "char");
				else if( length == sizeof(short) ) fprintf(log_file, "short");
				else if( length == sizeof(int) ) fprintf(log_file, "int");
				else if( length == sizeof(long long) ) fprintf(log_file, "long long");
				else fprintf(log_file, "<int%d>", length*8);
				break;
			case btFloat:
				if( length == sizeof(float) ) fprintf(log_file, "float");
				else if( length == sizeof(double) ) fprintf(log_file, "double");
				else if( length == sizeof(long double) ) fprintf(log_file, "long double");
				else fprintf(log_file, "<float%d>", length*8);
				break;
			default: fprintf(log_file, "<TODO name of basetype %d %d>", basetype, length); break;
			}
		}
		break;
	case SymTagPointerType:
		{
			DWORD subtype;
			DWORD subtag;

			if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_TYPE, &subtype) )
				fprintf(log_file, "<type not found>*");
			else if( !SymGetTypeInfo_(hProcess, modBase, subtype, TI_GET_SYMTAG, &subtag) )
				fprintf(log_file, "<symtag not found>*");
			else
			{
				Dhp__PrintTypeName(subtype, subtag, FALSE, interData);
				fprintf(log_file, "*");
			}
		}
		break;
	case SymTagArrayType:
		{
			DWORD childTypeIndex;
			DWORD childSymtag;

			// print root type
			childTypeIndex = typeIndex;
			childSymtag = symtag;
			for( ; ; )
			{
				if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_TYPE, &childTypeIndex) )
				{
					fprintf(log_file, "<child type not found>");
					break;
				}
				if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) )
				{
					fprintf(log_file, "<child symtag not found>");
					break;
				}
				if( childSymtag != SymTagArrayType )
				{
					Dhp__PrintTypeName(childTypeIndex, childSymtag, FALSE, interData);
					break;
				}
				// next dimension
			}
			// print dimensions
			childTypeIndex = typeIndex;
			childSymtag = symtag;
			for( ; childSymtag == SymTagArrayType ; )
			{
				DWORD childCount;
				if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_COUNT, &childCount) )
					fprintf(log_file, "[<count not found>]");
				else
					fprintf(log_file, "[%u]", childCount);
				if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_TYPE, &childTypeIndex) )
				{
					fprintf(log_file, "<child type not found>");
					break;
				}
				if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) )
				{
					fprintf(log_file, "<child symtag not found>");
					break;
				}
				// next dimension
			}
		}
		break;
	default:
		{
			WCHAR* pSymname;
			DWORD udtkind;

			if( SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_UDTKIND, &udtkind) )
			{
				switch( (UdtKind)udtkind )
				{
				case UdtStruct: fprintf(log_file, "struct "); break;
				case UdtClass: fprintf(log_file, "class "); break;
				case UdtUnion: fprintf(log_file, "union "); break;
				default: fprintf(log_file, "<unknown udtkind %d> ", udtkind); break;
				}
			}
			if( SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_SYMNAME, &pSymname ) )
			{
				fprintf(log_file, "%ls", pSymname);
				LocalFree( pSymname );
			}
			else
				fprintf(log_file, "<TODO typename of symtag %d>", symtag); break;
		}
		break;
	}

	if( withParens )
		fprintf(log_file, ")");
}

/// Prints the bytes in the target location.
///
/// @param log_file Log file
/// @param p Pointer to the data
/// @param length Length of the data in bytes
static VOID
Dhp__PrintValueBytes(
	FILE*   log_file,
	BYTE*   p,
	ULONG64 length)
{
	ULONG64 i;

	fprintf(log_file, "<bytes:");
	for( i = 0; i < length; ++i )
	{
		fprintf(log_file, "%02X", p[i]);
	}
	fprintf(log_file, ">");
}

/// Prints a wide string/char value.
///
/// @param log_file Log file
/// @param p Pointer to the value
/// @param length Length of the value in bytes
static VOID
Dhp__PrintValueWideChars(
	FILE*   log_file,
	WCHAR*  wstr,
	ULONG64 length,
	BOOL    isString)
{
	ULONG64 i;
	char* buf;
	char delim;

	length /= sizeof(WCHAR);
	delim = ( isString || length > 1 ? '\"' : '\'' );
	fprintf(log_file, "%c", delim);
	buf = (char *)LocalAlloc(LMEM_FIXED, MB_CUR_MAX+1);
	buf[MB_CUR_MAX] = '\0';
	for( i = 0; i < length; ++i )
	{
		int n;
		switch( wstr[i] )
		{
		case L'\"': fprintf(log_file, "\\\""); break;
		case L'\'': fprintf(log_file, "\\\'"); break;
		case L'\\': fprintf(log_file, "\\\\"); break;
		case L'\a': fprintf(log_file, "\\a"); break;
		case L'\b': fprintf(log_file, "\\b"); break;
		case L'\f': fprintf(log_file, "\\f"); break;
		case L'\n': fprintf(log_file, "\\n"); break;
		case L'\r': fprintf(log_file, "\\r"); break;
		case L'\t': fprintf(log_file, "\\t"); break;
		case L'\v': fprintf(log_file, "\\v"); break;
		default:
			if( iswprint(wstr[i]) && (n=wctomb(buf, wstr[i])) > 0 )
			{
				buf[n] = '\0';
				fprintf(log_file, "%s", buf);
			}
			else fprintf(log_file, "\\u%04X", wstr[i]);
			break;
		}
	}
	LocalFree(buf);
	fprintf(log_file, "%c", delim);
}

/// Prints a string/char value.
///
/// @param log_file Log file
/// @param p Pointer to the value
/// @param length Length of the value in bytes
static VOID
Dhp__PrintValueChars(
	FILE*   log_file,
	char*   str,
	ULONG64 length,
	BOOL    isString)
{
	ULONG64 i;
	char delim;

	length /= sizeof(char);
	delim = ( isString || length > 1 ? '\"' : '\'' );
	fprintf(log_file, "%c", delim);
	for( i = 0; i < length; ++i )
	{
		switch( str[i] )
		{
		case '\"': fprintf(log_file, "\\\""); break;
		case '\'': fprintf(log_file, "\\\'"); break;
		case '\\': fprintf(log_file, "\\\\"); break;
		case '\a': fprintf(log_file, "\\a"); break;
		case '\b': fprintf(log_file, "\\b"); break;
		case '\f': fprintf(log_file, "\\f"); break;
		case '\n': fprintf(log_file, "\\n"); break;
		case '\r': fprintf(log_file, "\\r"); break;
		case '\t': fprintf(log_file, "\\t"); break;
		case '\v': fprintf(log_file, "\\v"); break;
		default:
			if( isprint((unsigned char)str[i]) ) fprintf(log_file, "%c", str[i]);
			else fprintf(log_file, "\\x%02X", (unsigned char)str[i]);
			break;
		}
	}
	fprintf(log_file, "%c", delim);
}

/// Prints a float value.
///
/// @param log_file Log file
/// @param p Pointer to the value
/// @param length Length of the value in bytes
static VOID
Dhp__PrintValueFloat(
	FILE*   log_file,
	VOID*   p,
	ULONG64 length)
{
	if( length == sizeof(float) ) fprintf(log_file, "%f", *(float*)p);
	else if( length == sizeof(double) ) fprintf(log_file, "%lf", *(double*)p);
	else if( length == sizeof(long double) ) fprintf(log_file, "%Lf", *(long double*)p);
	else
	{
		fprintf(log_file, "<unexpected length %I64u>", length);
		Dhp__PrintValueBytes(log_file, (BYTE*)p, length);
	}
}

/// Prints a hex value.
///
/// @param log_file Log file
/// @param p Pointer to the value
/// @param length Length of the value in bytes
static VOID
Dhp__PrintValueHex(
	FILE*   log_file,
	VOID*   p,
	ULONG64 length)
{
	if( length == sizeof(UINT32) ) fprintf(log_file, "0x%I32X", *(UINT32*)p);
	else if( length == sizeof(UINT64) ) fprintf(log_file, "0x%I64X", *(UINT64*)p);
	else if( length == sizeof(char) ) fprintf(log_file, "0x%X", *(unsigned char*)p);
	else if( length == sizeof(short) ) fprintf(log_file, "0x%X", *(unsigned short*)p);
	else if( length == sizeof(int) ) fprintf(log_file, "0x%x", *(unsigned int*)p);
	else if( length == sizeof(long) ) fprintf(log_file, "0x%lX", *(unsigned long*)p);
	else if( length == sizeof(long long) ) fprintf(log_file, "0x%llX", *(unsigned long long*)p);
	else
	{
		fprintf(log_file, "<unexpected length %I64u>", length);
		Dhp__PrintValueBytes(log_file, (BYTE*)p, length);
	}
}

/// Prints an unsigned integer value.
///
/// @param log_file Log file
/// @param p Pointer to the value
/// @param length Length of the value in bytes
static VOID
Dhp__PrintValueUnsigned(
	FILE*   log_file,
	VOID*   p,
	ULONG64 length)
{
	if( length == sizeof(INT32) ) fprintf(log_file, "%I32u", *(INT32*)p);
	else if( length == sizeof(INT64) ) fprintf(log_file, "%I64u", *(INT64*)p);
	else if( length == sizeof(char) ) fprintf(log_file, "%u", *(unsigned char*)p);
	else if( length == sizeof(short) ) fprintf(log_file, "%u", *(unsigned short*)p);
	else if( length == sizeof(int) ) fprintf(log_file, "%u", *(unsigned int*)p);
	else if( length == sizeof(long) ) fprintf(log_file, "%lu", *(unsigned long*)p);
	else if( length == sizeof(long long) ) fprintf(log_file, "%llu", *(unsigned long long*)p);
	else
	{
		fprintf(log_file, "<unexpected length %I64u>", length);
		Dhp__PrintValueBytes(log_file, (BYTE*)p, length);
	}
}

/// Prints a signed integer value.
///
/// @param log_file Log file
/// @param p Pointer to the value
/// @param length Length of the value in bytes
static VOID
Dhp__PrintValueSigned(
	FILE*   log_file,
	VOID*   p,
	ULONG64 length)
{
	if( length == sizeof(INT32) ) fprintf(log_file, "%I32d", *(INT32*)p);
	else if( length == sizeof(INT64) ) fprintf(log_file, "%I64d", *(INT64*)p);
	else if( length == sizeof(char) ) fprintf(log_file, "%d", *(signed char*)p);
	else if( length == sizeof(short) ) fprintf(log_file, "%d", *(signed short*)p);
	else if( length == sizeof(int) ) fprintf(log_file, "%d", *(signed int*)p);
	else if( length == sizeof(long) ) fprintf(log_file, "%ld", *(signed long*)p);
	else if( length == sizeof(long long) ) fprintf(log_file, "%lld", *(signed long long*)p);
	else
	{
		fprintf(log_file, "<unexpected length %I64u>", length);
		Dhp__PrintValueBytes(log_file, (BYTE*)p, length);
	}
}

/// Prints a nul-terminated wide string value.
/// Checks if the memory can be read from.
///
/// @param log_file Log file
/// @param str Target string
static VOID
Dhp__PrintValueCWideString(
	FILE*   log_file,
	WCHAR*   str)
{
	ULONG64 length = 0;

	// check if memory is readable
	__try
	{
		while( str[length] != L'\0' )
			++length;
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		if( length ) Dhp__PrintValueWideChars(log_file, str, length*sizeof(WCHAR), TRUE); // print readable part
		fprintf(log_file, "<invalid memory>");
		return;
	}

	// print string
	Dhp__PrintValueWideChars(log_file, str, length*sizeof(WCHAR), TRUE);
}

/// Prints a nul-terminated string value.
/// Checks if the memory can be read from.
///
/// @param log_file Log file
/// @param str Target string
static VOID
Dhp__PrintValueCString(
	FILE*   log_file,
	char*   str)
{
	ULONG64 length = 0;

	assert( log_file != NULL );

	// check if memory is readable
	__try
	{
		while( str[length] != '\0' )
			++length;
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		if( length ) Dhp__PrintValueChars(log_file, str, length*sizeof(char), TRUE); // print readable part
		fprintf(log_file, "<invalid memory>");
		return;
	}

	// print string
	Dhp__PrintValueChars(log_file, str, length*sizeof(char), TRUE);
}

// forward declaration of Dhp__PrintDataContents
static VOID Dhp__PrintDataContents(DWORD typeIndex, PVOID pVariable, InterData*  interData);

/// Prints the value of the data symbol.
/// Checks if the memory can be read from.
///
/// @param typeIndex Type index of the symbol
/// @param symtag Symbol tag
/// @param pVariable Address to the symbol contents
/// @param pInterData Inter data
static VOID
Dhp__PrintDataValue(
	DWORD       typeIndex,
	DWORD       symtag,
	PVOID       pVariable,
	InterData*  pInterData)
{
	// inter data
	FILE* log_file;
	DWORD64 modBase;
	HANDLE hProcess;
	//
	ULONG64 length = 0;
	DWORD basetype;

	assert( pInterData != NULL );
	log_file = pInterData->log_file;
	modBase  = pInterData->modBase;
	hProcess = pInterData->hProcess;

	if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_LENGTH, &length) )
	{
		fprintf(log_file, "<unknown data length>");
		return;
	}

	// check if memory is readable
	__try
	{
		BYTE* p = (BYTE*)pVariable;
		ULONG i;
		BYTE b = 0;
		for( i = 0; i < length; ++i )
			b += p[i]; // add to make sure it's not optimized out in release mode

		// Don't continue if there's no valid data
		if( b == 0 ) {
			fprintf(log_file, "<no data>");
			return;
		}
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		fprintf(log_file, "<invalid memory>");
		return;
	}

	switch( symtag )
	{
	case SymTagBaseType:
		{
			if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &basetype) )
			{
				fprintf(log_file, "<basetype not found>");
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				break;
			}
			switch( basetype )
			{
			case btInt:
			case btLong:
				Dhp__PrintValueSigned(log_file, pVariable, length);
				break;
			case btUInt:
			case btULong:
				Dhp__PrintValueUnsigned(log_file, pVariable, length);
				break;
			case btFloat:
				Dhp__PrintValueFloat(log_file, pVariable, length);
				break;
			case btChar:
				{
					if( length == sizeof(char) ) fprintf(log_file, "%u ", *(unsigned char*)pVariable);
					Dhp__PrintValueChars(log_file, (char*)pVariable, length, FALSE);
				}
				break;
			case btWChar:
				{
					if( length == sizeof(WCHAR) ) fprintf(log_file, "%u ", *(WCHAR*)pVariable);
					Dhp__PrintValueWideChars(log_file, (WCHAR*)pVariable, length, FALSE);
				}
				break;
			case btVoid:
				if( length > 0 ) Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				break;
			default:
				fprintf(log_file, "<TODO value of basetype %d>", basetype);
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				break;
			}
		}
		break;
	case SymTagEnum:
		Dhp__PrintValueHex(log_file, pVariable, length);
		break;
	case SymTagPointerType:
		{
			DWORD childTypeIndex;
			DWORD childSymtag;

			fprintf(log_file, "0x%p", *(void**)pVariable);
			if( SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_TYPE, &childTypeIndex) &&
				SymGetTypeInfo_(hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) &&
				childSymtag != SymTagPointerType )
			{
				DWORD childBasetype;

				// child isn't a pointer, print the contents
				fprintf(log_file, " ");
				if( childSymtag == SymTagBaseType &&
					SymGetTypeInfo_(hProcess, modBase, childTypeIndex, TI_GET_BASETYPE, &childBasetype) &&
					(childBasetype == btChar || childBasetype == btWChar) )
				{
					// string or wide string
					if( childBasetype == btChar ) Dhp__PrintValueCString(log_file, *(char**)pVariable);
					else if( childBasetype == btWChar ) Dhp__PrintValueCWideString(log_file, *(WCHAR**)pVariable);
					else fprintf(log_file, "<unexpected child basetype %d>", childBasetype);
					break;
				}
				Dhp__PrintDataValue(childTypeIndex, childSymtag, *(PVOID*)pVariable, pInterData);
			}
		}
		break;
	case SymTagArrayType:
		{
			DWORD childTypeIndex;
			DWORD childSymtag;
			DWORD count;
			DWORD i;

			if( !SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_TYPE, &childTypeIndex) )
			{
				fprintf(log_file, "<child type not found>");
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				break;
			}
			if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) )
			{
				fprintf(log_file, "<child symtag not found>");
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				break;
			}
			LocalFree(&childSymtag);
			if( !SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_COUNT, &count) )
			{
				fprintf(log_file, "<count not found>");
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				break;
			}
			// print values
			fprintf(log_file, "{");
			for( i = 0; i < count; ++i )
			{
				BYTE* pData = pVariable;
				pData += i*(length/count);
				if( i > 0 ) fprintf(log_file, ",");
				Dhp__PrintDataValue(childTypeIndex, childSymtag, pData, pInterData);
			}
			fprintf(log_file, "}");
		}
		break;
	default:
#ifdef DBG_PRINT_CHILDREN
		{
			TI_FINDCHILDREN_PARAMS *children = NULL;
			size_t childrenSize;
			DWORD childCount = 0;
			DWORD i;

			// count children
			if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_CHILDRENCOUNT, &childCount)
				|| !childCount ) {
				fprintf(log_file, "<no children found>");
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				return;
			}

			// Prepare to get an array of "TypeIds", representing each of the children.
			// SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a
			// TI_FINDCHILDREN_PARAMS struct has.  Use derivation to accomplish this.
			childrenSize = sizeof(TI_FINDCHILDREN_PARAMS)+childCount*sizeof(ULONG);
			children = (TI_FINDCHILDREN_PARAMS*)LocalAlloc(LMEM_ZEROINIT, childrenSize);
			children->Count = childCount;

			// Get the array of TypeIds, one for each child type
			if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_FINDCHILDREN, children) || !children )
			{
				fprintf(log_file, "<children not found>");
				Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
				LocalFree(children);
				return;
			}

			// Iterate through each of the children
			fprintf(log_file, "\n{");
			for( i = 0; i < childCount; ++i )
			{
				DWORD childOffset;
				WCHAR *childName = NULL;
				DWORD childTypeId;
				DWORD_PTR pData;

				fprintf(log_file, "\n\t");

				// Get the offset of the child member, relative to its parent
				if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_OFFSET, &childOffset) )
				{
					fprintf(log_file, "<child offset not found>");
					continue;
				}

				// Calculate the address of the member
				pData = (DWORD_PTR)pVariable;
				pData += childOffset;

				if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_SYMNAME, &childName) ) {
						fprintf(log_file, "<child symbol name not found>");
						continue;
				}
				fprintf(log_file, "%ws=", childName);
				LocalFree(childName);

				if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_TYPEID, &childTypeId) ) {
					fprintf(log_file, "<child type id not found>");
					continue;
				}

				// print contents of the child
				Dhp__PrintDataContents(childTypeId, (PVOID)pData, pInterData);
			}
			fprintf(log_file, "\n}");

			LocalFree(children);
		}
#endif
		Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
		break;
	}
}

/// Prints the contents of the data symbol. (type and value)
///
/// @param typeIndex Type index of the symbol
/// @param pVariable Address of the symbol contents
/// @param pInterData Inter data
static VOID
Dhp__PrintDataContents(
	DWORD typeIndex,
	PVOID pVariable,
	InterData*  pInterData)
{
	// inter data
	FILE* log_file;
	HANDLE hProcess;
	DWORD64 modBase;
	//
	DWORD symtag;

	assert( pInterData != NULL );
	log_file = pInterData->log_file;
	hProcess = pInterData->hProcess;
	modBase  = pInterData->modBase;

	if( SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_SYMTAG, &symtag) )
	{
		// print type
		Dhp__PrintTypeName(typeIndex, symtag, TRUE, pInterData);
		// print value
		Dhp__PrintDataValue(typeIndex, symtag, pVariable, pInterData);
	}
	else
		fprintf(log_file, "<symtag not found>");
}

/// Prints information about the data symbol.
///
/// @param pSymInfo Symbol info
/// @param pInterData Inter data
static VOID
Dhp__PrintDataInfo(
	PSYMBOL_INFO    pSymInfo,
	InterData*      pInterData)
{
	// inter data
	FILE* log_file;
	STACKFRAME* pStackframe;
	BOOL as_arg_list;
	BOOL log_params;
	BOOL log_locals;
	BOOL log_globals;
	int nr_of_var;
	// my data
	DWORD_PTR pVariable = 0;
	enum{ UNKNOWN, PARAM, LOCAL, GLOBAL } scope = UNKNOWN;

	assert( pSymInfo != NULL );
	assert( pInterData != NULL );
	assert( pSymInfo->Tag == SymTagData );
	log_file    = pInterData->log_file;
	pStackframe = pInterData->pStackframe;
	as_arg_list = pInterData->as_arg_list;
	log_params  = pInterData->log_params;
	log_locals  = pInterData->log_locals;
	log_globals = pInterData->log_globals;
	nr_of_var   = pInterData->nr_of_var;

	// Determine the scope and address of the variable
	if( pSymInfo->Flags & SYMFLAG_REGREL ) {
		pVariable = pStackframe->AddrFrame.Offset;
		pVariable += (DWORD_PTR)pSymInfo->Address;
		if( pSymInfo->Flags & SYMFLAG_PARAMETER )
			scope = PARAM; // parameter
		else if( pSymInfo->Flags & SYMFLAG_LOCAL )
		{
			scope = LOCAL; // local
#if defined(_M_IX86)
			if( (LONG64)pSymInfo->Address  > 0) scope = PARAM; // parameter as local (bug in DBGHELP 5.1)
#endif
		}
	}
	else if( pSymInfo->Flags & SYMFLAG_REGISTER )
	{
		scope = (pSymInfo->Flags & SYMFLAG_PARAMETER) ? PARAM : LOCAL; // register, optimized out(?)
	}
	else
	{
		pVariable = (DWORD_PTR)pSymInfo->Address;
		scope = GLOBAL; // It must be a global variable
	}

	// check if we should to log the variable
	if( (scope == PARAM && log_params) ||
		(scope == LOCAL && log_locals) ||
		(scope == GLOBAL && log_globals) )
	{
		// print prefix and name
		if( as_arg_list )
			fprintf(log_file, "%s%s=", (nr_of_var ? ", " : ""), pSymInfo->Name);
		else
			fprintf(log_file, "\t%s = ", pSymInfo->Name);

		// print value
		if( !(pSymInfo->Flags & SYMFLAG_REGREL) && (pSymInfo->Flags & SYMF_REGISTER) )
			fprintf(log_file, "<value optimized out>");
		else
		{
			pInterData->modBase = pSymInfo->ModBase;
			Dhp__PrintDataContents(pSymInfo->TypeIndex, (PVOID)pVariable, pInterData);
		}

		// print postfix
		if( !as_arg_list )
			fprintf(log_file, "\n");
		pInterData->nr_of_var = ++nr_of_var;
	}
}

/// Prints information about the symbol.
///
/// @param pSymInfo Symbol info
/// @param pInterData Inter data
static VOID
Dhp__PrintSymbolInfo(
	PSYMBOL_INFO    pSymInfo,
	InterData*      pInterData)
{
	assert( pSymInfo != NULL );
	assert( pInterData != NULL );

	switch( pSymInfo->Tag )
	{
	case SymTagData: Dhp__PrintDataInfo( pSymInfo, pInterData ); break;
	default: /*fprintf(pInterData->log_file, "<unsupported symtag %d>", pSymInfo->Tag);*/ break;
	}
}

/// Prints the details of one symbol to the log file.
/// Used as a callback for SymEnumSymbols.
///
/// @param pSymInfo Symbol info
/// @param symSize Size of the symbol info structure
/// @param pData Inter data
static BOOL WINAPI
Dhp__EnumSymbolsCallback(
	PSYMBOL_INFO    pSymInfo,
	ULONG           symSize,
	PVOID           pData)
{
	if( pSymInfo == NULL )
		return TRUE; // try other symbols

	if( pData == NULL )
	{
		printf("Dhp__EnumSymbolsCallback: pData is NULL\n");
		return FALSE;
	}

	Dhp__PrintSymbolInfo(pSymInfo, (InterData*)pData);
	return TRUE;
}

/// Prints the source code of the target line.
/// Searches for the target file in the original path,
/// in the last src folder of the original path (relative)
/// and in the current directory.
///
/// @param filename Original source file
/// @param line Target line
/// @param log_file Log file
static VOID
Dhp__PrintSourceLine(
	FILE* log_file,
	char* filename,
	DWORD line)
{
	char path[MAX_PATH*3];
	char pathBuffer[MAX_PATH+1];
	char* p;

	assert( filename != NULL );
	assert( log_file != NULL );

	// generate search paths
	strcpy(path, filename); // original path
	p = strrchr(path, '\\');
	if( p )
	{
		memcpy(p, ";\0", 2);
		p = strstr(filename, "\\src\\");
		if( p )
		{
			while( strstr(p+1, "\\src\\") )
				p = strstr(p+1, "\\src\\");
			strcat(path, p+1); // last src folder path
			p = strrchr(path, '\\');
			memcpy(p, ";\0", 2);
		}
		filename = strrchr(filename, '\\')+1;
	}
	else
		*path = '\0'; // no path
	strcat(path, "."); // current directoy

	// search for file and line
	if( SearchPathA(path, filename, NULL, MAX_PATH, pathBuffer, NULL) )
	{
		char code[1024+1];
		DWORD i = 1;
		FILE* fp;

		fp = fopen(pathBuffer, "rt");
		if( fp == NULL )
			return;

		code[1024] = '\0';
		while( fgets(code, 1024, fp) )
		{
			if( i == line )
			{// found line
				char* term = strchr(code, '\n');
				if( term && term != code && term[-1] == '\r' ) --term;
				if( term ) *term = '\0';
				fprintf(log_file, "%d\t%s\n", line, code);
				break;
			}
			if( strchr(code, '\n') )
				++i;
		}
		fclose(fp);
	}
}

/// Prints details of one function to the log file.
///
/// @param interData Inter data
static VOID
Dhp__PrintFunctionDetails(
	InterData*  pInterData)
{
	// inter data
	HANDLE hProcess;
	STACKFRAME* pStackframe;
	FILE* log_file;
	int nr_of_frame;
	//
	PSYMBOL_INFO pSymbolInfo;
	DWORD64 funcDisplacement=0;
	IMAGEHLP_STACK_FRAME imagehlpStackFrame;
	IMAGEHLP_LINE imagehlpLine;
	DWORD lineDisplacement=0;

	assert( pInterData != NULL );
	hProcess    = pInterData->hProcess;
	pStackframe = pInterData->pStackframe;
	log_file    = pInterData->log_file;
	nr_of_frame = pInterData->nr_of_frame;

	// frame info
	fprintf(log_file,
		"#%d  0x%p",
		nr_of_frame, (void*)(DWORD_PTR)pStackframe->AddrPC.Offset);

	// restrict symbol enumeration to this frame only
	ZeroMemory(&imagehlpStackFrame, sizeof(IMAGEHLP_STACK_FRAME));
	imagehlpStackFrame.InstructionOffset = pStackframe->AddrPC.Offset;
	SymSetContext_(hProcess, &imagehlpStackFrame, 0);

	// function name and displacement
	pSymbolInfo = (PSYMBOL_INFO)LocalAlloc(LMEM_FIXED, sizeof(SYMBOL_INFO)+1024);
	pSymbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO);
	pSymbolInfo->MaxNameLen = 1024;
	if( SymFromAddr_(hProcess, pStackframe->AddrPC.Offset, &funcDisplacement, pSymbolInfo) == TRUE )
	{
		fprintf(log_file,
			" in %.1024s+0x%I64X (",
			pSymbolInfo->Name, funcDisplacement);

		// log all function parameters
		pInterData->as_arg_list = TRUE;
		pInterData->log_params = TRUE;
		pInterData->log_locals = FALSE;
		pInterData->log_globals = FALSE;
		pInterData->nr_of_var = 0;
		if( !SymEnumSymbols_(hProcess, 0, 0, Dhp__EnumSymbolsCallback, pInterData) )
			fprintf(log_file, "<failed enumerating symbols>");

		fprintf(log_file,
			")");
	}
	else
		fprintf(log_file,
			"in <unknown function>");

	// find the source line for this function.
	imagehlpLine.SizeOfStruct = sizeof(IMAGEHLP_LINE);
	if( SymGetLineFromAddr_(hProcess, pStackframe->AddrPC.Offset, &lineDisplacement, &imagehlpLine) != 0 )
	{
		char* filename = imagehlpLine.FileName;
		DWORD line = imagehlpLine.LineNumber;

		fprintf(log_file,
			" at %s:%d\n",
			filename, line);

		Dhp__PrintSourceLine(log_file, filename, line);
	}
	else
		fprintf(log_file,
			"\n");

	// log all function local variables
	pInterData->as_arg_list = FALSE;
	pInterData->log_params = FALSE;
	pInterData->log_locals = TRUE;
	pInterData->log_globals = FALSE;
	pInterData->nr_of_var = 0;
	if( !SymEnumSymbols_(hProcess, 0, 0, Dhp__EnumSymbolsCallback, pInterData) )
		fprintf(log_file, "!!failed enumerating symbols!!");

	pInterData->nr_of_frame = ++nr_of_frame;
	LocalFree(pSymbolInfo);
}


/// Walks over the stack and prints all relevant information to the log file.
///
/// @param context Exception context
/// @param log_file Log file
static VOID
Dhp__PrintStacktrace(
	CONTEXT *context,
	FILE *log_file)
{
	HANDLE hProcess = GetCurrentProcess();
	STACKFRAME stackframe;
	DWORD machine;
	CONTEXT ctx;
	InterData interData;
	int skip = 0;
	int i;

	assert( log_file != NULL );

	fprintf(log_file,
		"\nStacktrace:\n");

	// Use thread information - if not supplied.
	if( context == NULL )
	{
		// If no context is supplied, skip 1 frame
		skip = 1;

		ctx.ContextFlags = CONTEXT_FULL;
		if( GetThreadContext(GetCurrentThread(), &ctx) )
			context = &ctx;
	}

	if( context == NULL )
		return;

	// Write the stack trace
	ZeroMemory(&stackframe, sizeof(STACKFRAME));
	stackframe.AddrPC.Mode = AddrModeFlat;
	stackframe.AddrStack.Mode = AddrModeFlat;
	stackframe.AddrFrame.Mode = AddrModeFlat;
#if defined(_M_IX86)
	machine = IMAGE_FILE_MACHINE_I386;
	stackframe.AddrPC.Offset = context->Eip;
	stackframe.AddrStack.Offset = context->Esp;
	stackframe.AddrFrame.Offset = context->Ebp;
#else /* defined(_M_IX86) */
#error FIXME add more processors
some compilers don't stop on #error, this line makes sure it errors out
#endif

	interData.hProcess = hProcess;
	interData.log_file = log_file;
	interData.pStackframe = &stackframe;
	interData.nr_of_frame = 0;
	for( i = 0; ; ++i )
	{
		if( !StackWalk_(machine, hProcess, GetCurrentThread(),
			&stackframe, context, NULL, SymFunctionTableAccess_,
			SymGetModuleBase_, NULL))
		{
			break;
		}

		if( i >= skip )
		{
			// Check that the address is not zero.
			// Sometimes StackWalk returns TRUE with a frame of zero.
			if( stackframe.AddrPC.Offset != 0 )
				Dhp__PrintFunctionDetails(&interData);
		}
	}
}

typedef BOOL (WINAPI *ISDEBUGGERPRESENT)(void);
/// Checks if a debugger is attached to this process
///
/// @return TRUE is a debugger is present
static BOOL
Dhp__IsDebuggerPresent(void)
{
	HANDLE kernel32_dll;
	ISDEBUGGERPRESENT IsDebuggerPresent_;
	BOOL result;

	kernel32_dll = LoadLibraryA("kernel32.dll");
	if( kernel32_dll == NULL )
		return FALSE;

	IsDebuggerPresent_ = (ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent");
	if( IsDebuggerPresent_ )
		result = IsDebuggerPresent_();
	else
		result = FALSE;

	FreeLibrary(kernel32_dll);

	return result;
}

/// Loads the dbghelp.dll library.
///
/// @return TRUE is sucessfull
static BOOL
Dhp__LoadDbghelpDll(void)
{
	dbghelp_dll = LoadLibraryA(DBGHELP_DLL);
	if( dbghelp_dll != INVALID_HANDLE_VALUE )
	{
		DWORD opts;

		// load the functions
		MiniDumpWriteDump_      =      (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump");
		SymInitialize_          =          (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize");
		SymSetOptions_          =          (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions");
		SymGetOptions_          =          (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions");
		SymCleanup_             =             (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup");
		SymGetTypeInfo_         =         (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo");
		SymGetLineFromAddr_     =     (SYMGETLINEFROMADDR)GetProcAddress(dbghelp_dll, "SymGetLineFromAddr");
		SymEnumSymbols_         =         (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols");
		SymSetContext_          =          (SYMSETCONTEXT)GetProcAddress(dbghelp_dll, "SymSetContext");
		SymFromAddr_            =            (SYMFROMADDR)GetProcAddress(dbghelp_dll, "SymFromAddr");
		StackWalk_              =              (STACKWALK)GetProcAddress(dbghelp_dll, "StackWalk");
		SymFunctionTableAccess_ = (SYMFUNCTIONTABLEACCESS)GetProcAddress(dbghelp_dll, "SymFunctionTableAccess");
		SymGetModuleBase_       =       (SYMGETMODULEBASE)GetProcAddress(dbghelp_dll, "SymGetModuleBase");

		if( MiniDumpWriteDump_ &&
			SymInitialize_  && SymSetOptions_  && SymGetOptions_ &&
			SymCleanup_     && SymGetTypeInfo_ && SymGetLineFromAddr_ &&
			SymEnumSymbols_ && SymSetContext_  && SymFromAddr_ && StackWalk_ &&
			SymFunctionTableAccess_ && SymGetModuleBase_ )
		{
			// initialize the symbol loading code
			opts = SymGetOptions_();

			// Set the 'load lines' option to retrieve line number information.
			// Set the 'deferred loads' option to map the debug info in memory only when needed.
			SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS);

			// Initialize the dbghelp DLL with the default path and automatic
			// module enumeration (and loading of symbol tables) for this process.
			if( !SymInitialize_(GetCurrentProcess(), NULL, TRUE) ) {
				printf("Failed to initialize symbols. Error: %u\n", GetLastError());
				return FALSE;
			}

			return TRUE;
		}
	}

	if( dbghelp_dll )
	{
		FreeLibrary(dbghelp_dll);
		dbghelp_dll = NULL;
	}

	return FALSE;
}

/// Unloads the dbghelp.dll library.
static VOID
Dhp__UnloadDbghlpDll(void)
{
	if( !SymCleanup_(GetCurrentProcess()) )
		printf("Failed to cleanup symbols! Error: %u\n", GetLastError());

	FreeLibrary(dbghelp_dll);
	dbghelp_dll = NULL;
}

/// Creates the report and minidump files.
/// Puts the resulting pathnames in the arguments.
/// The buffers must be at least MAX_PATH+1 in size.
///
/// @param out_lpszLogFileName Buffer for the report filename
/// @param out_lpszDmpFileName Buffer for the minidump filename
/// @return TRUE if the files were created
static BOOL
Dhp__CreateFiles(
	char*   out_logFileName,
	char*   out_dmpFileName)
{
#define LEN_TIMESTAMP 14 // "YYYYMMDDhhmmss"
#define LEN_EXT 4 // ".rpt" or ".dmp"
	char baseFileName[MAX_PATH+1];
	char timestamp[LEN_TIMESTAMP+1];
	FILE* fp;
	time_t now;

	// Generate base filename for the report/minidump
	ZeroMemory(baseFileName, sizeof(baseFileName));
	if( GetModuleFileName(NULL, baseFileName, MAX_PATH-LEN_TIMESTAMP-LEN_EXT) )
	{
		char* pTerm = strrchr(baseFileName, '\\');
		if( pTerm == NULL ) pTerm = baseFileName;
		pTerm = strrchr(pTerm, '.');
		if( pTerm ) *pTerm = '\0'; // remove extension
	}
	else if( GetTempPathA(MAX_PATH-6-LEN_TIMESTAMP-LEN_EXT, baseFileName) )
	{// in temp folder
		strcat(baseFileName, DBG_DEFAULT_FILENAME);
	}
	else
	{// in current folder
		strcpy(baseFileName, DBG_DEFAULT_FILENAME);
	}

	time(&now);
#if 0
	szTimestamp[0] = '\0';
#else
	strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M%S", localtime(&now));
#endif
	timestamp[LEN_TIMESTAMP] = '\0';

	sprintf(out_logFileName, "%s%s.rpt", baseFileName, timestamp);
	fp = fopen(out_logFileName, "w");
	if( fp == NULL )
		return FALSE; // failed to create log file
	fclose(fp);

	sprintf(out_dmpFileName, "%s%s.dmp", baseFileName, timestamp);
	fp = fopen(out_dmpFileName, "w");
	if( fp == NULL)
		return FALSE; // failed to create dump file
	fclose(fp);

	return TRUE; // success
#undef LEN_EXT
#undef LEN_TIMESTAMP
}

/// Unhandled exception handler. Where the magic starts... ;D
///
/// @param ptrs Exception information
/// @return What to do with the exception
LONG WINAPI
Dhp__UnhandledExceptionFilter(PEXCEPTION_POINTERS ptrs)
{
	char szLogFileName[MAX_PATH+1];
	char szDmpFileName[MAX_PATH+1];
	FILE* log_file;

	// check if the crash handler was already loaded (crash while handling the crash)
	if( dbghelp_dll != INVALID_HANDLE_VALUE )
		return EXCEPTION_CONTINUE_SEARCH;

	// don't log anything if we're running inside a debugger ...
	if( Dhp__IsDebuggerPresent() == TRUE )
		return EXCEPTION_CONTINUE_SEARCH;

	// ... or if we can't load dbghelp.dll ...
	if( Dhp__LoadDbghelpDll() == FALSE )
		return EXCEPTION_CONTINUE_SEARCH;

	// ... or if we can't create the log files
	if( Dhp__CreateFiles(szLogFileName, szDmpFileName) == FALSE )
		return EXCEPTION_CONTINUE_SEARCH;

	// open log file
	log_file = fopen(szLogFileName, "wt");

	// print information about the process
	Dhp__PrintProcessInfo(
		ptrs ? ptrs->ExceptionRecord : NULL,
		ptrs ? ptrs->ContextRecord : NULL,
		log_file);

	// print the stacktrace
	Dhp__PrintStacktrace(
		ptrs ? ptrs->ContextRecord : NULL,
		log_file);

	// write the minidump file and use the callback to print the list of modules to the log file
	Dhp__WriteMinidumpFile(
		szDmpFileName,
		ptrs,
		Dhp__PrintModuleInfoCallback,
		log_file);

	fclose(log_file);

	Dhp__UnloadDbghlpDll();

	// inform the user
	fprintf(stderr,
		"\n"
		"This application has halted due to an unexpected error.\n"
		"A crash report and minidump file were saved to disk, you can find them here:\n"
		"%s\n"
		"%s\n"
		DBG_EXTENDED_INFORMATION
		"\n"
		"NOTE: The crash report and minidump files can contain sensitive information\n"
		"(filenames, partial file content, usernames and passwords etc.)\n",
		szLogFileName,
		szDmpFileName);

	// terminate the application
	return EXCEPTION_EXECUTE_HANDLER;
}

/////////////////////////////////////////////////////////////////////
// Plugin

/// Previous exception filter.
static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter;

/**
 * Initializes plugin
 * Installs a new unhandled exception filter (Dhp__UnhandledExceptionFilter)
 **/
HPExport void plugin_init (void) {
	previousFilter = SetUnhandledExceptionFilter(Dhp__UnhandledExceptionFilter);
}

/**
 * Finalizes plugin
 * Uninstalls the handler
 **/
HPExport void plugin_final (void) {
	SetUnhandledExceptionFilter(previousFilter);
}
