/*
	This file is part of Warzone 2100.
	Copyright (C) 1997-XXXX  José Fonseca <j_r_fonseca@yahoo.co.uk>
	 * Originally based on Matt Pietrek's MSJEXHND.CPP in Microsoft Systems Journal, April 1997.
	Copyright (C) 2008  Giel van Schijndel
	Copyright (C) 2008-2012  Warzone 2100 Project

	Warzone 2100 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 2 of the License, or
	(at your option) any later version.

	Warzone 2100 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 Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if (_WIN32_WINNT < 0x0500)			// must force win 2k or higher
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#endif
#include "lib/framework/frame.h"
#include "dumpinfo.h"
#include "exchndl.h"

#include <assert.h>
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <shlobj.h>
#include <shlwapi.h>

#if !defined(WZ_CC_MSVC)
#define HAVE_BFD	1
#endif

#define WSTRING(x) CONCAT(L, x)

// Declare the static variables
static wchar_t szLogFileName[MAX_PATH] = L"";
static LPTOP_LEVEL_EXCEPTION_FILTER prevExceptionFilter = NULL;
static HANDLE hReportFile;
static char* formattedVersionString = NULL;

static
int __cdecl rprintf(const TCHAR * format, ...)
{
	TCHAR szBuff[4096];
	int retValue;
	DWORD cbWritten;
	va_list argptr;

	va_start(argptr, format);
	retValue = wvsprintf(szBuff, format, argptr);
	va_end(argptr);

	WriteFile(hReportFile, szBuff, retValue * sizeof(TCHAR), &cbWritten, 0);

	return retValue;
}

// The GetModuleBase function retrieves the base address of the module that contains the specified address.
static
DWORD GetModuleBase(DWORD dwAddress)
{
	MEMORY_BASIC_INFORMATION Buffer;

	return VirtualQuery((LPCVOID) dwAddress, &Buffer, sizeof(Buffer)) ? (DWORD) Buffer.AllocationBase : 0;
}


#ifdef HAVE_BFD

#include <bfd.h>
extern "C"
{
#include "include/demangle.h"
#include "include/coff/internal.h"
#include "include/libcoff.h"
}

// Read in the symbol table.
static bfd_boolean
slurp_symtab (bfd *abfd, asymbol ***syms, long *symcount)
{
	long storage;

	if ((bfd_get_file_flags (abfd) & HAS_SYMS) == 0)
		return FALSE;

	storage = bfd_get_symtab_upper_bound (abfd);
	if (storage < 0)
		return FALSE;

	*syms = (asymbol **) GlobalAlloc(GMEM_FIXED, storage);
	if (*syms == NULL)
		return FALSE;

	if((*symcount = bfd_canonicalize_symtab (abfd, *syms)) < 0)
		return FALSE;

	return TRUE;
}

// This stucture is used to pass information between translate_addresses and find_address_in_section.
struct find_handle
{
	asymbol **syms;
	bfd_vma pc;
	const char *filename;
	const char *functionname;
	unsigned int line;
	bfd_boolean found;
};

// Look for an address in a section.  This is called via  bfd_map_over_sections.
static void find_address_in_section (bfd *abfd, asection *section, PTR data)
{
	struct find_handle *info = (struct find_handle *) data;
	bfd_vma vma;
	bfd_size_type size;

	if (info->found)
		return;

	if ((bfd_get_section_flags (abfd, section) & SEC_ALLOC) == 0)
		return;

	vma = bfd_get_section_vma (abfd, section);
	size = bfd_get_section_size (section);

	if (info->pc < (vma = bfd_get_section_vma (abfd, section)))
		return;

	if (info->pc >= vma + (size = bfd_get_section_size (section)))
		return;

	info->found = bfd_find_nearest_line (abfd, section, info->syms, info->pc - vma, &info->filename, &info->functionname, &info->line);
}

static
bool BfdDemangleSymName(LPCTSTR lpName, LPTSTR lpDemangledName, DWORD nSize)
{
	char *res;

	assert(lpName != NULL);

	if((res = cplus_demangle(lpName, DMGL_ANSI /*| DMGL_PARAMS*/)) == NULL)
	{
		lstrcpyn(lpDemangledName, lpName, nSize);
		return FALSE;
	}
	else
	{
		lstrcpyn(lpDemangledName, res, nSize);
		free (res);
		return TRUE;
	}
}

static
bool BfdGetSymFromAddr(bfd *abfd, asymbol **syms, long symcount, DWORD dwAddress, LPTSTR lpSymName, DWORD nSize)
{
	HMODULE hModule;
	struct find_handle info;

	if(!(hModule = (HMODULE) GetModuleBase(dwAddress)))
		return FALSE;

	info.pc = dwAddress;

	if(!(bfd_get_file_flags (abfd) & HAS_SYMS) || !symcount)
		return FALSE;
	info.syms = syms;

	info.found = FALSE;
	bfd_map_over_sections (abfd, find_address_in_section, (PTR) &info);
	if (info.found == FALSE || info.line == 0)
		return FALSE;

	assert(lpSymName);

	if(info.functionname == NULL && *info.functionname == '\0')
		return FALSE;

	lstrcpyn(lpSymName, info.functionname, nSize);

	return TRUE;
}

static
bool BfdGetLineFromAddr(bfd *abfd, asymbol **syms, long symcount, DWORD dwAddress,  LPTSTR lpFileName, DWORD nSize, LPDWORD lpLineNumber)
{
	HMODULE hModule;
	struct find_handle info;

	if(!(hModule = (HMODULE) GetModuleBase(dwAddress)))
		return FALSE;

	info.pc = dwAddress;

	if(!(bfd_get_file_flags (abfd) & HAS_SYMS) || !symcount)
		return FALSE;

	info.syms = syms;

	info.found = FALSE;
	bfd_map_over_sections (abfd, find_address_in_section, (PTR) &info);
	if (info.found == FALSE || info.line == 0)
		return FALSE;

	assert(lpFileName && lpLineNumber);

	lstrcpyn(lpFileName, info.filename, nSize);
	*lpLineNumber = info.line;

	return TRUE;
}

#endif /* HAVE_BFD */

#include <imagehlp.h>

static bool bSymInitialized = FALSE;

static HMODULE hModule_Imagehlp = NULL;

typedef bool (WINAPI *PFNSYMINITIALIZE)(HANDLE, LPSTR, bool);
static PFNSYMINITIALIZE pfnSymInitialize = NULL;

static
bool WINAPI j_SymInitialize(HANDLE hProcess, PSTR UserSearchPath, bool fInvadeProcess)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymInitialize || (pfnSymInitialize = (PFNSYMINITIALIZE) GetProcAddress(hModule_Imagehlp, "SymInitialize")))
	)
		return pfnSymInitialize(hProcess, UserSearchPath, fInvadeProcess);
	else
		return FALSE;
}

typedef bool (WINAPI *PFNSYMCLEANUP)(HANDLE);
static PFNSYMCLEANUP pfnSymCleanup = NULL;

static
bool WINAPI j_SymCleanup(HANDLE hProcess)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymCleanup || (pfnSymCleanup = (PFNSYMCLEANUP) GetProcAddress(hModule_Imagehlp, "SymCleanup")))
	)
		return pfnSymCleanup(hProcess);
	else
		return FALSE;
}

typedef DWORD (WINAPI *PFNSYMSETOPTIONS)(DWORD);
static PFNSYMSETOPTIONS pfnSymSetOptions = NULL;

static
DWORD WINAPI j_SymSetOptions(DWORD SymOptions)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymSetOptions || (pfnSymSetOptions = (PFNSYMSETOPTIONS) GetProcAddress(hModule_Imagehlp, "SymSetOptions")))
	)
		return pfnSymSetOptions(SymOptions);
	else
		return FALSE;
}

typedef bool (WINAPI *PFNSYMUNDNAME)(PIMAGEHLP_SYMBOL, PSTR, DWORD);
static PFNSYMUNDNAME pfnSymUnDName = NULL;

static
bool WINAPI j_SymUnDName(PIMAGEHLP_SYMBOL Symbol, PSTR UnDecName, DWORD UnDecNameLength)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymUnDName || (pfnSymUnDName = (PFNSYMUNDNAME) GetProcAddress(hModule_Imagehlp, "SymUnDName")))
	)
		return pfnSymUnDName(Symbol, UnDecName, UnDecNameLength);
	else
		return FALSE;
}

typedef PFUNCTION_TABLE_ACCESS_ROUTINE PFNSYMFUNCTIONTABLEACCESS;
static PFNSYMFUNCTIONTABLEACCESS pfnSymFunctionTableAccess = NULL;

static
PVOID WINAPI j_SymFunctionTableAccess(HANDLE hProcess, DWORD AddrBase)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymFunctionTableAccess || (pfnSymFunctionTableAccess = (PFNSYMFUNCTIONTABLEACCESS) GetProcAddress(hModule_Imagehlp, "SymFunctionTableAccess")))
	)
		return pfnSymFunctionTableAccess(hProcess, AddrBase);
	else
		return NULL;
}

typedef PGET_MODULE_BASE_ROUTINE PFNSYMGETMODULEBASE;
static PFNSYMGETMODULEBASE pfnSymGetModuleBase = NULL;

static
DWORD WINAPI j_SymGetModuleBase(HANDLE hProcess, DWORD dwAddr)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymGetModuleBase || (pfnSymGetModuleBase = (PFNSYMGETMODULEBASE) GetProcAddress(hModule_Imagehlp, "SymGetModuleBase")))
	)
		return pfnSymGetModuleBase(hProcess, dwAddr);
	else
		return 0;
}

typedef bool (WINAPI *PFNSTACKWALK)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE);
static PFNSTACKWALK pfnStackWalk = NULL;

static
bool WINAPI j_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
)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnStackWalk || (pfnStackWalk = (PFNSTACKWALK) GetProcAddress(hModule_Imagehlp, "StackWalk")))
	)
		return pfnStackWalk(
			MachineType,
			hProcess,
			hThread,
			StackFrame,
			ContextRecord,
			ReadMemoryRoutine,
			FunctionTableAccessRoutine,
			GetModuleBaseRoutine,
			TranslateAddress
		);
	else
		return FALSE;
}

typedef bool (WINAPI *PFNSYMGETSYMFROMADDR)(HANDLE, DWORD, LPDWORD, PIMAGEHLP_SYMBOL);
static PFNSYMGETSYMFROMADDR pfnSymGetSymFromAddr = NULL;

static
bool WINAPI j_SymGetSymFromAddr(HANDLE hProcess, DWORD Address, PDWORD Displacement, PIMAGEHLP_SYMBOL Symbol)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymGetSymFromAddr || (pfnSymGetSymFromAddr = (PFNSYMGETSYMFROMADDR) GetProcAddress(hModule_Imagehlp, "SymGetSymFromAddr")))
	)
		return pfnSymGetSymFromAddr(hProcess, Address, Displacement, Symbol);
	else
		return FALSE;
}

typedef bool (WINAPI *PFNSYMGETLINEFROMADDR)(HANDLE, DWORD, LPDWORD, PIMAGEHLP_LINE);
static PFNSYMGETLINEFROMADDR pfnSymGetLineFromAddr = NULL;

static
bool WINAPI j_SymGetLineFromAddr(HANDLE hProcess, DWORD dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE Line)
{
	if(
		(hModule_Imagehlp || (hModule_Imagehlp = LoadLibrary(_T("IMAGEHLP.DLL")))) &&
		(pfnSymGetLineFromAddr || (pfnSymGetLineFromAddr = (PFNSYMGETLINEFROMADDR) GetProcAddress(hModule_Imagehlp, "SymGetLineFromAddr")))
	)
		return pfnSymGetLineFromAddr(hProcess, dwAddr, pdwDisplacement, Line);
	else
		return FALSE;
}

static
bool ImagehlpDemangleSymName(LPCTSTR lpName, LPTSTR lpDemangledName, DWORD nSize)
{
	BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 512];
	PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL) symbolBuffer;

	memset( symbolBuffer, 0, sizeof(symbolBuffer) );

	pSymbol->SizeOfStruct = sizeof(symbolBuffer);
	pSymbol->MaxNameLength = 512;

	lstrcpyn((LPTSTR)pSymbol->Name, lpName, pSymbol->MaxNameLength);

	if(!j_SymUnDName(pSymbol, (PSTR)lpDemangledName, nSize))
		return FALSE;

	return TRUE;
}

static
bool ImagehlpGetSymFromAddr(HANDLE hProcess, DWORD dwAddress, LPTSTR lpSymName, DWORD nSize)
{
	// IMAGEHLP is wacky, and requires you to pass in a pointer to a
	// IMAGEHLP_SYMBOL structure.  The problem is that this structure is
	// variable length.  That is, you determine how big the structure is
	// at runtime.  This means that you can't use sizeof(struct).
	// So...make a buffer that's big enough, and make a pointer
	// to the buffer.  We also need to initialize not one, but TWO
	// members of the structure before it can be used.

	BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 512];
	PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL) symbolBuffer;
	DWORD dwDisplacement = 0;  // Displacement of the input address, relative to the start of the symbol

	pSymbol->SizeOfStruct = sizeof(symbolBuffer);
	pSymbol->MaxNameLength = 512;

	assert(bSymInitialized);

	if(!j_SymGetSymFromAddr(hProcess, dwAddress, &dwDisplacement, pSymbol))
		return FALSE;

	lstrcpyn(lpSymName, (LPCTSTR)pSymbol->Name, nSize);

	return TRUE;
}

static
bool ImagehlpGetLineFromAddr(HANDLE hProcess, DWORD dwAddress,  LPTSTR lpFileName, DWORD nSize, LPDWORD lpLineNumber)
{
	IMAGEHLP_LINE Line;
	DWORD dwDisplacement = 0;  // Displacement of the input address, relative to the start of the symbol

	// Do the source and line lookup.
	memset(&Line, 0, sizeof(IMAGEHLP_LINE));
	Line.SizeOfStruct = sizeof(IMAGEHLP_LINE);

	assert(bSymInitialized);

#if 1
	{
		// The problem is that the symbol engine only finds those source
		//  line addresses (after the first lookup) that fall exactly on
		//  a zero displacement.  I will walk backwards 100 bytes to
		//  find the line and return the proper displacement.
		DWORD dwTempDisp = 0 ;
		while (dwTempDisp < 100 && !j_SymGetLineFromAddr(hProcess, dwAddress - dwTempDisp, &dwDisplacement, &Line))
			++dwTempDisp;

		if(dwTempDisp >= 100)
			return FALSE;

		// It was found and the source line information is correct so
		//  change the displacement if it was looked up multiple times.
		if (dwTempDisp < 100 && dwTempDisp != 0 )
			dwDisplacement = dwTempDisp;
	}
#else
	if(!j_SymGetLineFromAddr(hProcess, dwAddress, &dwDisplacement, &Line))
		return FALSE;
#endif

	assert(lpFileName && lpLineNumber);

	lstrcpyn(lpFileName, (LPCTSTR)Line.FileName, nSize);
	*lpLineNumber = Line.LineNumber;

	return TRUE;
}

static
bool PEGetSymFromAddr(HANDLE hProcess, DWORD dwAddress, LPTSTR lpSymName, DWORD nSize)
{
	HMODULE hModule;
	PIMAGE_NT_HEADERS pNtHdr;
	IMAGE_NT_HEADERS NtHdr;
	PIMAGE_SECTION_HEADER pSection;
	DWORD dwNearestAddress = 0, dwNearestName;
	int i;

	if(!(hModule = (HMODULE) GetModuleBase(dwAddress)))
		return FALSE;

	{
		PIMAGE_DOS_HEADER pDosHdr;
		LONG e_lfanew;

		// Point to the DOS header in memory
		pDosHdr = (PIMAGE_DOS_HEADER)hModule;

		// From the DOS header, find the NT (PE) header
		if(!ReadProcessMemory(hProcess, &pDosHdr->e_lfanew, &e_lfanew, sizeof(e_lfanew), NULL))
			return FALSE;

		pNtHdr = (PIMAGE_NT_HEADERS)((DWORD)hModule + (DWORD)e_lfanew);

		if(!ReadProcessMemory(hProcess, pNtHdr, &NtHdr, sizeof(IMAGE_NT_HEADERS), NULL))
			return FALSE;
	}

	pSection = (PIMAGE_SECTION_HEADER) ((DWORD)pNtHdr + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + NtHdr.FileHeader.SizeOfOptionalHeader);

	// Look for export section
	for (i = 0; i < NtHdr.FileHeader.NumberOfSections; i++, pSection++)
	{
		IMAGE_SECTION_HEADER Section;
		PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
		BYTE ExportSectionName[IMAGE_SIZEOF_SHORT_NAME] = {'.', 'e', 'd', 'a', 't', 'a', '\0', '\0'};

		if(!ReadProcessMemory(hProcess, pSection, &Section, sizeof(IMAGE_SECTION_HEADER), NULL))
			return FALSE;

		if(memcmp(Section.Name, ExportSectionName, IMAGE_SIZEOF_SHORT_NAME) == 0)
			pExportDir = (PIMAGE_EXPORT_DIRECTORY) Section.VirtualAddress;
		else if ((NtHdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress >= Section.VirtualAddress) && (NtHdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress < (Section.VirtualAddress + Section.SizeOfRawData)))
			pExportDir = (PIMAGE_EXPORT_DIRECTORY) NtHdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

		if(pExportDir)
		{
			IMAGE_EXPORT_DIRECTORY ExportDir;

			if(!ReadProcessMemory(hProcess, (PVOID)((DWORD)hModule + (DWORD)pExportDir), &ExportDir, sizeof(IMAGE_EXPORT_DIRECTORY), NULL))
				return FALSE;

			{
				PDWORD *AddressOfFunctions = (PDWORD *)alloca(ExportDir.NumberOfFunctions*sizeof(PDWORD));
				int j;

				if(!ReadProcessMemory(hProcess, (PVOID)((DWORD)hModule + (DWORD)ExportDir.AddressOfFunctions), AddressOfFunctions, ExportDir.NumberOfFunctions*sizeof(PDWORD), NULL))
						return FALSE;

				for(j = 0; j < ExportDir.NumberOfNames; ++j)
				{
					DWORD pFunction = (DWORD)hModule + (DWORD)AddressOfFunctions[j];
					//ReadProcessMemory(hProcess, (DWORD) hModule + (DWORD) (&ExportDir.AddressOfFunctions[j]), &pFunction, sizeof(pFunction), NULL);

					if(pFunction <= dwAddress && pFunction > dwNearestAddress)
					{
						dwNearestAddress = pFunction;

						if(!ReadProcessMemory(hProcess, (PVOID)((DWORD)hModule + (DWORD)(&ExportDir.AddressOfNames)[j]), &dwNearestName, sizeof(dwNearestName), NULL))
							return FALSE;

						dwNearestName = (DWORD) hModule + dwNearestName;
					}
				}
			}
		}
    }

	if(!dwNearestAddress)
		return FALSE;

	if(!ReadProcessMemory(hProcess, (PVOID)dwNearestName, lpSymName, nSize, NULL))
		return FALSE;
	lpSymName[nSize - 1] = 0;

	return TRUE;
}

// Cross platform compatibility.
// If you change this, make sure it doesn't break the cross compile or the native compile.
// Don't ask why this is needed, have no idea.
struct ItDoesntMatterIfItsADWORDOrAVoidPointer_JustCompileTheDamnThing
{
	ItDoesntMatterIfItsADWORDOrAVoidPointer_JustCompileTheDamnThing(void *whatever) : whatever(whatever) {}
	operator void *() const { return whatever; }        // Oooh, look compiler1, I'm a void *, just what you wanted!
	operator DWORD() const { return (DWORD)whatever; }  // Oooh, look compiler2, I'm a DWORD, just what you wanted!
	void *whatever;
};

static
bool WINAPI IntelStackWalk(
	DWORD MachineType,
	HANDLE hProcess,
	WZ_DECL_UNUSED HANDLE hThread,
	LPSTACKFRAME StackFrame,
	PCONTEXT ContextRecord,
	PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
	WZ_DECL_UNUSED PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
	WZ_DECL_UNUSED PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
	WZ_DECL_UNUSED PTRANSLATE_ADDRESS_ROUTINE TranslateAddress
)
{
	assert(MachineType == IMAGE_FILE_MACHINE_I386);

	if(ReadMemoryRoutine == NULL)
		ReadMemoryRoutine = (PREAD_PROCESS_MEMORY_ROUTINE)ReadProcessMemory;

	if(!StackFrame->Reserved[0])
	{
		StackFrame->Reserved[0] = 1;

		StackFrame->AddrPC.Mode = AddrModeFlat;
		StackFrame->AddrPC.Offset = ContextRecord->Eip;
		StackFrame->AddrStack.Mode = AddrModeFlat;
		StackFrame->AddrStack.Offset = ContextRecord->Esp;
		StackFrame->AddrFrame.Mode = AddrModeFlat;
		StackFrame->AddrFrame.Offset = ContextRecord->Ebp;

		StackFrame->AddrReturn.Mode = AddrModeFlat;
// Error   26      error C2664: 'bool (HANDLE,DWORD,PVOID,DWORD,PDWORD)' :
// cannot convert parameter 2 from 'void *' to 'DWORD'
// c:\warzone\lib\exceptionhandler\exchndl.cpp     599
// ../../../../lib/exceptionhandler/exchndl.cpp: In function ‘bool IntelStackWalk(DWORD, void*, void*, _tagSTACKFRAME*, CONTEXT*, bool (*)(void*, const void*, void*, DWORD, DWORD*), void* (*)(void*, DWORD), DWORD (*)(void*, DWORD), DWORD (*)(void*, void*, _tagADDRESS*))’:
// ../../../../lib/exceptionhandler/exchndl.cpp:599: error: invalid conversion from ‘long unsigned int’ to ‘const void*’
		if(!ReadMemoryRoutine((HANDLE)hProcess, ItDoesntMatterIfItsADWORDOrAVoidPointer_JustCompileTheDamnThing((void *) (StackFrame->AddrFrame.Offset + sizeof(DWORD))), (void *)&StackFrame->AddrReturn.Offset, sizeof(DWORD), NULL))
			return FALSE;
	}
	else
	{
		StackFrame->AddrPC.Offset = StackFrame->AddrReturn.Offset;
		//AddrStack = AddrFrame + 2*sizeof(DWORD);
		if(!ReadMemoryRoutine((HANDLE)hProcess, ItDoesntMatterIfItsADWORDOrAVoidPointer_JustCompileTheDamnThing((void *) StackFrame->AddrFrame.Offset), (void *)&StackFrame->AddrFrame.Offset, sizeof(DWORD), NULL))
			return FALSE;
		if(!ReadMemoryRoutine((HANDLE)hProcess, ItDoesntMatterIfItsADWORDOrAVoidPointer_JustCompileTheDamnThing((void *) (StackFrame->AddrFrame.Offset + sizeof(DWORD))), (void *)&StackFrame->AddrReturn.Offset, sizeof(DWORD), NULL))
			return FALSE;
	}

	ReadMemoryRoutine((HANDLE)hProcess, ItDoesntMatterIfItsADWORDOrAVoidPointer_JustCompileTheDamnThing((void *) (StackFrame->AddrFrame.Offset + 2*sizeof(DWORD))), (void *)StackFrame->Params, sizeof(StackFrame->Params), NULL);

	return TRUE;
}

static
bool StackBackTrace(HANDLE hProcess, HANDLE hThread, PCONTEXT pContext)
{
	STACKFRAME StackFrame;

	HMODULE hModule = NULL;
	TCHAR szModule[MAX_PATH];

#ifdef HAVE_BFD
	bfd *abfd = NULL;
	asymbol **syms = NULL;	// The symbol table.
	long symcount = 0;	// Number of symbols in `syms'.
#endif /* HAVE_BFD */

	assert(!bSymInitialized);

	j_SymSetOptions(/* SYMOPT_UNDNAME | */ SYMOPT_LOAD_LINES);
	if(j_SymInitialize(hProcess, NULL, TRUE))
		bSymInitialized = TRUE;

	memset( &StackFrame, 0, sizeof(StackFrame) );

	// Initialize the STACKFRAME structure for the first call.  This is only
	// necessary for Intel CPUs, and isn't mentioned in the documentation.
	StackFrame.AddrPC.Offset = pContext->Eip;
	StackFrame.AddrPC.Mode = AddrModeFlat;
	StackFrame.AddrStack.Offset = pContext->Esp;
	StackFrame.AddrStack.Mode = AddrModeFlat;
	StackFrame.AddrFrame.Offset = pContext->Ebp;
	StackFrame.AddrFrame.Mode = AddrModeFlat;

	rprintf( _T("Call stack:\r\n") );

	if(0)
		rprintf( _T("AddrPC     AddrReturn AddrFrame  AddrStack\r\n") );

	while ( 1 )
	{
		bool bSuccess = FALSE;
#ifdef HAVE_BFD
		const HMODULE hPrevModule = hModule;
#endif /* HAVE_BFD */
		TCHAR szSymName[512] = _T("");
		TCHAR szFileName[MAX_PATH] = _T("");
		DWORD LineNumber = 0;

		if(bSymInitialized)
		{
			if(!j_StackWalk(
					IMAGE_FILE_MACHINE_I386,
					hProcess,
					hThread,
					&StackFrame,
					pContext,
					NULL,
					j_SymFunctionTableAccess,
					j_SymGetModuleBase,
					NULL
				)
			)
				break;
		}
		else
		{
			if(!IntelStackWalk(
					IMAGE_FILE_MACHINE_I386,
					hProcess,
					hThread,
					&StackFrame,
					pContext,
					NULL,
					NULL,
					NULL,
					NULL
				)
			)
				break;
		}

		// Basic sanity check to make sure  the frame is OK.  Bail if not.
		if ( 0 == StackFrame.AddrFrame.Offset )
			break;

		if(0)
		{
			rprintf(
				_T("%08lX   %08lX   %08lX   %08lX\r\n"),
				StackFrame.AddrPC.Offset,
				StackFrame.AddrReturn.Offset,
				StackFrame.AddrFrame.Offset,
				StackFrame.AddrStack.Offset
			);
			rprintf(
				_T("%08lX   %08lX   %08lX   %08lX\r\n"),
				StackFrame.Params[0],
				StackFrame.Params[1],
				StackFrame.Params[2],
				StackFrame.Params[3]
			);
		}

		rprintf( _T("%08lX"), StackFrame.AddrPC.Offset);

		if((hModule = (HMODULE) GetModuleBase(StackFrame.AddrPC.Offset)) && GetModuleFileName(hModule, szModule, sizeof(szModule)))
		{
#ifndef HAVE_BFD
			rprintf( _T("  %s:ModulBase %08lX"), szModule, hModule);
#else /* HAVE_BFD */
			rprintf( _T("  %s:%08lX"), szModule, StackFrame.AddrPC.Offset);

			if(hModule != hPrevModule)
			{
				if(syms)
				{
					GlobalFree(syms);
					syms = NULL;
					symcount = 0;
				}

				if(abfd)
					bfd_close(abfd);

				if((abfd = bfd_openr (szModule, NULL)))
					if(bfd_check_format(abfd, bfd_object))
						if(bfd_get_file_flags(abfd) & HAS_SYMS)
							/* Read in the symbol table.  */
							slurp_symtab(abfd, &syms, &symcount);
			}

			if(!bSuccess && abfd && syms && symcount)
				if((bSuccess = BfdGetSymFromAddr(abfd, syms, symcount, StackFrame.AddrPC.Offset, szSymName, 512)))
				{
					/*
					framepointer = StackFrame.AddrFrame.Offset;
					hprocess = hProcess;
					*/

					BfdDemangleSymName(szSymName, szSymName, 512);

					rprintf( _T("  %s"), szSymName);

					if(BfdGetLineFromAddr(abfd, syms, symcount, StackFrame.AddrPC.Offset, szFileName, MAX_PATH, &LineNumber))
						rprintf( _T("  %s:%ld"), szFileName, LineNumber);
				}
#endif /* HAVE_BFD */

			if(!bSuccess && bSymInitialized)
				if((bSuccess = ImagehlpGetSymFromAddr(hProcess, StackFrame.AddrPC.Offset, szSymName, 512)))
				{
					rprintf( _T("  %s"), szSymName);

					ImagehlpDemangleSymName(szSymName, szSymName, 512);

					if(ImagehlpGetLineFromAddr(hProcess, StackFrame.AddrPC.Offset, szFileName, MAX_PATH, &LineNumber))
						rprintf( _T("  %s:%ld"), szFileName, LineNumber);
				}

			if(!bSuccess)
				if((bSuccess = PEGetSymFromAddr(hProcess, StackFrame.AddrPC.Offset, szSymName, 512)))
					rprintf( _T("  %s"), szSymName);
		}

		rprintf(_T("\r\n"));
	}

#ifdef HAVE_BFD
	if(syms)
	{
		GlobalFree(syms);
		syms = NULL;
		symcount = 0;
	}

	if(abfd)
		bfd_close(abfd);
#endif /* HAVE_BFD */

	if(bSymInitialized)
	{
		if(!j_SymCleanup(hProcess))
			assert(0);

		bSymInitialized = FALSE;
	}

	return TRUE;
}

static
void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo)
{
	PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
	TCHAR szModule[MAX_PATH];
	HMODULE hModule;
	PCONTEXT pContext;

	// Start out with a banner
	rprintf(_T("-------------------\r\n\r\n"));

	{
		const TCHAR *lpDayOfWeek[] = {
			_T("Sunday"),
			_T("Monday"),
			_T("Tuesday"),
			_T("Wednesday"),
			_T("Thursday"),
			_T("Friday"),
			_T("Saturday")
		};
		const TCHAR *lpMonth[] = {
			NULL,
			_T("January"),
			_T("February"),
			_T("March"),
			_T("April"),
			_T("May"),
			_T("June"),
			_T("July"),
			_T("August"),
			_T("September"),
			_T("October"),
			_T("November"),
			_T("December")
		};
		SYSTEMTIME SystemTime;

		GetLocalTime(&SystemTime);
		rprintf(_T("Error occured on %s, %s %i, %i at %02i:%02i:%02i.\r\n\r\n"),
			lpDayOfWeek[SystemTime.wDayOfWeek],
			lpMonth[SystemTime.wMonth],
			SystemTime.wDay,
			SystemTime.wYear,
			SystemTime.wHour,
			SystemTime.wMinute,
			SystemTime.wSecond
		);
	}

	// Dump a generic info header
	dbgDumpHeader(hReportFile);

	// First print information about the type of fault
	rprintf(_T("\r\n%s caused "),  GetModuleFileName(NULL, szModule, MAX_PATH) ? szModule : _T("Application"));
	switch(pExceptionRecord->ExceptionCode)
	{
		case EXCEPTION_ACCESS_VIOLATION:
			rprintf(_T("an Access Violation"));
			break;

		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
			rprintf(_T("an Array Bound Exceeded"));
			break;

		case EXCEPTION_BREAKPOINT:
			rprintf(_T("a Breakpoint"));
			break;

		case EXCEPTION_DATATYPE_MISALIGNMENT:
			rprintf(_T("a Datatype Misalignment"));
			break;

		case EXCEPTION_FLT_DENORMAL_OPERAND:
			rprintf(_T("a Float Denormal Operand"));
			break;

		case EXCEPTION_FLT_DIVIDE_BY_ZERO:
			rprintf(_T("a Float Divide By Zero"));
			break;

		case EXCEPTION_FLT_INEXACT_RESULT:
			rprintf(_T("a Float Inexact Result"));
			break;

		case EXCEPTION_FLT_INVALID_OPERATION:
			rprintf(_T("a Float Invalid Operation"));
			break;

		case EXCEPTION_FLT_OVERFLOW:
			rprintf(_T("a Float Overflow"));
			break;

		case EXCEPTION_FLT_STACK_CHECK:
			rprintf(_T("a Float Stack Check"));
			break;

		case EXCEPTION_FLT_UNDERFLOW:
			rprintf(_T("a Float Underflow"));
			break;

		case EXCEPTION_GUARD_PAGE:
			rprintf(_T("a Guard Page"));
			break;

		case EXCEPTION_ILLEGAL_INSTRUCTION:
			rprintf(_T("an Illegal Instruction"));
			break;

		case EXCEPTION_IN_PAGE_ERROR:
			rprintf(_T("an In Page Error"));
			break;

		case EXCEPTION_INT_DIVIDE_BY_ZERO:
			rprintf(_T("an Integer Divide By Zero"));
			break;

		case EXCEPTION_INT_OVERFLOW:
			rprintf(_T("an Integer Overflow"));
			break;

		case EXCEPTION_INVALID_DISPOSITION:
			rprintf(_T("an Invalid Disposition"));
			break;

		case EXCEPTION_INVALID_HANDLE:
			rprintf(_T("an Invalid Handle"));
			break;

		case EXCEPTION_NONCONTINUABLE_EXCEPTION:
			rprintf(_T("a Noncontinuable Exception"));
			break;

		case EXCEPTION_PRIV_INSTRUCTION:
			rprintf(_T("a Privileged Instruction"));
			break;

		case EXCEPTION_SINGLE_STEP:
			rprintf(_T("a Single Step"));
			break;

		case EXCEPTION_STACK_OVERFLOW:
			rprintf(_T("a Stack Overflow"));
			break;

		case DBG_CONTROL_C:
			rprintf(_T("a Control+C"));
			break;

		case DBG_CONTROL_BREAK:
			rprintf(_T("a Control+Break"));
			break;

		case DBG_TERMINATE_THREAD:
			rprintf(_T("a Terminate Thread"));
			break;

		case DBG_TERMINATE_PROCESS:
			rprintf(_T("a Terminate Process"));
			break;

		case RPC_S_UNKNOWN_IF:
			rprintf(_T("an Unknown Interface"));
			break;

		case RPC_S_SERVER_UNAVAILABLE:
			rprintf(_T("a Server Unavailable"));
			break;

		default:
			/*
			static TCHAR szBuffer[512] = { 0 };

			// If not one of the "known" exceptions, try to get the string
			// from NTDLL.DLL's message table.

			FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
							GetModuleHandle(_T("NTDLL.DLL")),
							dwCode, 0, szBuffer, sizeof(szBuffer), 0);
			*/

			rprintf(_T("an Unknown [0x%lX] Exception"), pExceptionRecord->ExceptionCode);
			break;
	}

	// Now print information about where the fault occured
	rprintf(_T(" at location %08x"), (DWORD) pExceptionRecord->ExceptionAddress);
	if((hModule = (HMODULE) GetModuleBase((DWORD) pExceptionRecord->ExceptionAddress)) && GetModuleFileName(hModule, szModule, sizeof(szModule)))
		rprintf(_T(" in module %s"), szModule);

	// If the exception was an access violation, print out some additional information, to the error log and the debugger.
	if(pExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && pExceptionRecord->NumberParameters >= 2)
		rprintf(_T(" %s location %08x"), pExceptionRecord->ExceptionInformation[0] ? "Writing to" : "Reading from", pExceptionRecord->ExceptionInformation[1]);

	rprintf(_T(".\r\n\r\n"));

	dbgDumpLog(hReportFile);

	pContext = pExceptionInfo->ContextRecord;

	#ifdef _M_IX86	// Intel Only!

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

	#endif
	// FIXME: We *never* return from the below call!
	StackBackTrace(GetCurrentProcess(), GetCurrentThread(), pContext);

	rprintf(_T("\r\n\r\n"));
}

#include <stdio.h>
#include <fcntl.h>
#include <io.h>


// Entry point where control comes on an unhandled exception
static
LONG WINAPI TopLevelExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
{
	static bool bBeenHere = FALSE;

	if(!bBeenHere)
	{
		UINT fuOldErrorMode;

		bBeenHere = TRUE;

		fuOldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);

		hReportFile = CreateFileW(
			szLogFileName,
			GENERIC_WRITE,
			0,
			0,
			OPEN_ALWAYS,
			FILE_FLAG_WRITE_THROUGH,
			0
		);
		if (hReportFile == INVALID_HANDLE_VALUE)
		{
			// Retrieve the system error message for the last-error code

			LPVOID lpMsgBuf;
			DWORD dw = GetLastError();
			TCHAR szBuffer[4196];

			FormatMessage(
				FORMAT_MESSAGE_ALLOCATE_BUFFER |
				FORMAT_MESSAGE_FROM_SYSTEM |
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL,
				dw,
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
				(LPTSTR) &lpMsgBuf,
				0, NULL );

			wsprintf(szBuffer, _T("Exception handler failed with error %d: %s\n"), dw, lpMsgBuf);
			MessageBox((HWND)MB_ICONEXCLAMATION, szBuffer, _T("Error"), MB_OK);

			LocalFree(lpMsgBuf);
			debug(LOG_ERROR, "Exception handler failed to create file!");
		}

#ifdef HAVE_BFD
		bfd_set_error_handler((bfd_error_handler_type) rprintf);
#endif /* HAVE_BFD */

		if (hReportFile)
		{
			wchar_t szBuffer[4196];
			int err;

			SetFilePointer(hReportFile, 0, 0, FILE_END);

			// FIXME: We don't return from the below function call
			GenerateExceptionReport(pExceptionInfo);
			CloseHandle(hReportFile);

			wsprintfW(szBuffer, L"Warzone has crashed.\r\nSee %s for more details\r\n", szLogFileName);
			err = MessageBoxW((HWND)MB_ICONERROR, szBuffer, L"Warzone Crashed!", MB_OK | MB_ICONERROR);
			if (err == 0)
			{
				LPVOID lpMsgBuf;
				DWORD dw = GetLastError();
				wchar_t szBuffer[4196];

				FormatMessageW(
					FORMAT_MESSAGE_ALLOCATE_BUFFER |
					FORMAT_MESSAGE_FROM_SYSTEM |
					FORMAT_MESSAGE_IGNORE_INSERTS,
					NULL,
					dw,
					MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					(LPWSTR) &lpMsgBuf,
					0, NULL );

				wsprintfW(szBuffer, L"Exception handler failed with error %d: %s\n", dw, lpMsgBuf);
				MessageBoxW((HWND)MB_ICONEXCLAMATION, szBuffer, L"Error", MB_OK);

				LocalFree(lpMsgBuf);
				debug(LOG_ERROR, "Exception handler failed to create file!");
			}
			hReportFile = 0;
		}
		SetErrorMode(fuOldErrorMode);
	}

	if(prevExceptionFilter)
		return prevExceptionFilter(pExceptionInfo);
	else
		return EXCEPTION_CONTINUE_SEARCH;
}

void ExchndlSetup(const char *packageVersion)
{
# if defined(WZ_CC_MINGW)
	wchar_t miniDumpPath[PATH_MAX] = {'\0'};

#ifdef HAVE_BFD
	bfd_init();
#endif /* HAVE_BFD */

	// Install the unhandled exception filter function
	prevExceptionFilter = SetUnhandledExceptionFilter(TopLevelExceptionFilter);

	// Retrieve the current version
	formattedVersionString = strdup(packageVersion);

	// Because of UAC on vista / win7 we use this to write our dumps to (unless we override it via OverrideRPTDirectory())
	// NOTE: CSIDL_PERSONAL =  C:\Users\user name\Documents
	if ( SUCCEEDED( SHGetFolderPathW( NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, miniDumpPath ) ))
	{
		PathAppendW( miniDumpPath, WSTRING(WZ_WRITEDIR));
		PathAppendW( miniDumpPath, L"\\logs" );

		if( !PathFileExistsW( miniDumpPath ) )
		{
			if( ERROR_SUCCESS != SHCreateDirectoryExW( NULL, miniDumpPath, NULL ) )
			{
				wcscpy(miniDumpPath, L"c:\\temp");
			}
		}
	}
	else
	{	// should never fail, but if it does, we fall back to this
		wcscpy(miniDumpPath, L"c:\\temp");
	}

	wcscat(szLogFileName, L"Warzone2100.RPT");
	wcscat(miniDumpPath, L"\\");
	wcscat(miniDumpPath,szLogFileName);
	wcscpy(szLogFileName, miniDumpPath);

	atexit(ExchndlShutdown);
#endif
}
void ResetRPTDirectory(wchar_t *newPath)
{
	wcscpy(szLogFileName, newPath);
}
void ExchndlShutdown(void)
{
	if (prevExceptionFilter)
		SetUnhandledExceptionFilter(prevExceptionFilter);

	prevExceptionFilter = NULL;
	free(formattedVersionString);
	formattedVersionString = NULL;
}
