#include "burner.h"
#include "build_details.h"
#include <tlhelp32.h>
#include <setupapi.h>
#include <psapi.h>

#if defined _MSC_VER
 #include <intrin.h>
#endif

// these are not defined in the Cygwin/MinGW winapi package
#ifndef DISPLAY_DEVICE_ACTIVE
 #define DISPLAY_DEVICE_ACTIVE              0x00000001
#endif
#ifndef DISPLAY_DEVICE_ATTACHED
 #define DISPLAY_DEVICE_ATTACHED            0x00000002
#endif

static _EXCEPTION_POINTERS* pExceptionPointers;

static TCHAR* pszTextBuffer = NULL;
static int nTextBufferSize = 0;

static int nRecursion = 0;

static HFONT hLogFont, hCodeFont;
static HBRUSH hCodeBGBrush;

static const bool bPrintDriverInfo = false;

static int AddLine(TCHAR* pszFormat, ...)
{
	TCHAR szString[255] = _T("");

	va_list vaFormat;
	va_start(vaFormat, pszFormat);

	int nLen = _vsntprintf(szString, 170, pszFormat, vaFormat);
	nLen = (nLen >= 0 && nLen < 170) ? nLen : 170;
	nLen += _stprintf(szString + nLen, _T("\r\n"));
	TCHAR* pszNewBuffer = (TCHAR*)realloc(pszTextBuffer, (nLen + nTextBufferSize + 1) * sizeof(TCHAR));
	if (pszNewBuffer) {
		pszTextBuffer = pszNewBuffer;
		_tcsncpy(pszTextBuffer + nTextBufferSize, szString, nLen);
		nTextBufferSize += nLen;
		pszTextBuffer[nTextBufferSize] = 0;
	}

	va_end(vaFormat);

	return 0;
}

static int AddText(TCHAR* pszFormat, ...)
{
	TCHAR szString[255] = _T("");

	va_list vaFormat;
	va_start(vaFormat, pszFormat);

	int nLen = _vsntprintf(szString, 70, pszFormat, vaFormat);
	nLen = (nLen >= 0 && nLen < 70) ? nLen : 70;
	TCHAR* pszNewBuffer = (TCHAR*)realloc(pszTextBuffer, (nLen + nTextBufferSize + 1) * sizeof(TCHAR));
	if (pszNewBuffer) {
		pszTextBuffer = pszNewBuffer;
		_tcsncpy(pszTextBuffer + nTextBufferSize, szString, nLen);
		nTextBufferSize += nLen;
		pszTextBuffer[nTextBufferSize] = 0;
	}

	va_end(vaFormat);

	return 0;
}

static int PrintInterfaceInfo(InterfaceInfo* pInfo)
{
	if (pInfo == NULL) {
		return 1;
	}

	if (pInfo->pszModuleName) {
		AddLine(_T("    Selected module:    %s"), pInfo->pszModuleName);
	}
	for (int i = 0; pInfo->ppszInterfaceSettings[i]; i++) {
		AddLine(_T("    %s%s"), (i == 0) ? _T("Interface settings: ") : _T("                    "), pInfo->ppszInterfaceSettings[i]);
	}
	for (int i = 0; pInfo->ppszModuleSettings[i]; i++) {
		AddLine(_T("    %s%s"), (i == 0) ? _T("Module settings:    ") : _T("                    "), pInfo->ppszModuleSettings[i]);
	}

	return 0;
}

// Print information about the exception
int PrintExceptionInfo()
{
	static const struct { DWORD ExceptionCode; const TCHAR* szString; } ExceptionString[] = {

#define EXCEPTION_LIST_ENTRY(exception) { exception, _T(#exception) }

		EXCEPTION_LIST_ENTRY(EXCEPTION_ACCESS_VIOLATION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED),
		EXCEPTION_LIST_ENTRY(EXCEPTION_BREAKPOINT),
		EXCEPTION_LIST_ENTRY(EXCEPTION_DATATYPE_MISALIGNMENT),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_DENORMAL_OPERAND),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_DIVIDE_BY_ZERO),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_INEXACT_RESULT),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_INVALID_OPERATION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_OVERFLOW),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_STACK_CHECK),
		EXCEPTION_LIST_ENTRY(EXCEPTION_FLT_UNDERFLOW),
		EXCEPTION_LIST_ENTRY(EXCEPTION_ILLEGAL_INSTRUCTION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_IN_PAGE_ERROR),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INVALID_DISPOSITION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INT_DIVIDE_BY_ZERO),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INT_OVERFLOW),
		EXCEPTION_LIST_ENTRY(EXCEPTION_INVALID_HANDLE),
		EXCEPTION_LIST_ENTRY(EXCEPTION_GUARD_PAGE),
		EXCEPTION_LIST_ENTRY(EXCEPTION_NONCONTINUABLE_EXCEPTION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_PRIV_INSTRUCTION),
		EXCEPTION_LIST_ENTRY(EXCEPTION_SINGLE_STEP),
		EXCEPTION_LIST_ENTRY(EXCEPTION_STACK_OVERFLOW),
		{ 0, _T("unspecified exception") }

#undef EXCEPTION_LIST_ENTRY

	};

	int i;

	for (i = 0; ExceptionString[i].ExceptionCode; i++) {
		if (ExceptionString[i].ExceptionCode == pExceptionPointers->ExceptionRecord->ExceptionCode) {
			break;
		}
	}

	AddLine(_T("Exception 0x%08X (%s) thrown.\r\nEIP: 0x%p"), pExceptionPointers->ExceptionRecord->ExceptionCode, ExceptionString[i].szString, pExceptionPointers->ExceptionRecord->ExceptionAddress);
	if (pExceptionPointers->ExceptionRecord->ExceptionCode ==  EXCEPTION_ACCESS_VIOLATION) {
		AddLine(_T(" (attempting to %s address 0x%p)"), pExceptionPointers->ExceptionRecord->ExceptionInformation[0] ? _T("write") : _T("read"), pExceptionPointers->ExceptionRecord->ExceptionInformation[1]);
	}
	AddLine(_T(""));

#ifdef BUILD_X86_ASM
	AddLine(_T("EAX: 0x%08X, EBX: 0x%08X, ECX: 0x%08X, EDX: 0x%08X"), (unsigned int)pExceptionPointers->ContextRecord->Eax, (unsigned int)pExceptionPointers->ContextRecord->Ebx, (unsigned int)pExceptionPointers->ContextRecord->Ecx, (unsigned int)pExceptionPointers->ContextRecord->Edx);
	AddLine(_T("ESI: 0x%08X, EDI: 0x%08X, ESP: 0x%08X, EBP: 0x%08X"), (unsigned int)pExceptionPointers->ContextRecord->Esi, (unsigned int)pExceptionPointers->ContextRecord->Edi, (unsigned int)pExceptionPointers->ContextRecord->Esp, (unsigned int)pExceptionPointers->ContextRecord->Ebp);
#endif

	return 0;
}

// Print OS information
int PrintOSInfo()
{
	OSVERSIONINFOEX osvi;

	memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	if (!GetVersionEx((OSVERSIONINFO*)&osvi)) {
		osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		GetVersionEx((OSVERSIONINFO*)&osvi);
	}

//	bprintf(PRINT_NORMAL, _T("%i, %i\n"), osvi.dwMajorVersion, osvi.dwMinorVersion);

	AddText(_T("OS:  "));
	{
		if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) {
			// http://msdn.microsoft.com/en-us/library/ms724833(VS.85).aspx
			if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) {
				AddText(_T("Microsoft Windows 2000 "));
			}
			if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) {
				AddText(_T("Microsoft Windows XP "));
			}
			if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) {
				AddText(_T("Microsoft Windows 2003 "));
			}
			if (osvi.dwMajorVersion < 5 || osvi.dwMinorVersion > 3) {
				AddText(_T("Microsoft Windows NT %d.%d "), osvi.dwMajorVersion, osvi.dwMinorVersion);
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0 && osvi.wProductType == VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows Vista "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0 && osvi.wProductType != VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows Server 2008 "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1 && osvi.wProductType != VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows Server 2008 R2 "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1 && osvi.wProductType == VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows 7 "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2 && osvi.wProductType != VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows Server 2012 "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2 && osvi.wProductType == VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows 8 "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3 && osvi.wProductType != VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows Server 2012 R2 "));
			}
			if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3 && osvi.wProductType == VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows 8.1 "));
			}
			if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.wProductType != VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows Server Technical Preview "));
			}
			if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.wProductType == VER_NT_WORKSTATION) {
				AddText(_T("Microsoft Windows 10 "));
			}

			if (osvi.dwMajorVersion == 5) {
				if (osvi.wProductType == VER_NT_WORKSTATION) {
					if (osvi.wSuiteMask & VER_SUITE_PERSONAL) {
						AddText(_T("Personal "));
					} else {
						AddText(_T("Professional "));
					}
				}
				if (osvi.wProductType == VER_NT_SERVER) {
					if (osvi.wSuiteMask & VER_SUITE_DATACENTER) {
						AddText(_T("DataCenter Server "));
					} else {
						if (osvi.wSuiteMask & VER_SUITE_DATACENTER) {
							AddText(_T("Advanced Server "));
						} else {
							AddText(_T("Server "));
						}
					}
				}
			}
			AddText(_T("%s (build %i)\r\n"), osvi.szCSDVersion, osvi.dwBuildNumber & 0xFFFF);
		}
		if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
			if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion < 10) {
				AddText(_T("Microsoft Windows 95"));
				if (osvi.szCSDVersion[1] == _T('B') || osvi.szCSDVersion[1] == _T('C')) {
					AddText(_T(" OSR2"));
				}
			}
			if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) {
				AddText(_T("Microsoft Windows 98"));
				if (osvi.szCSDVersion[1] == _T('A')) {
					AddText(_T(" SE"));
				}
			}
			if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) {
				AddText(_T("Microsoft Windows Me"));
			}
			if (osvi.dwMajorVersion != 4 && osvi.dwMinorVersion < 10) {
				AddText(_T("Microsoft Windows %d.%d "), osvi.dwMajorVersion, osvi.dwMinorVersion);
			}
			AddText(_T("\r\n"));
		}
	}

	return 0;
}

int PrintCPUInfo()
{
	AddText(_T("CPU: "));

	char CPUBrandStringFinal[0x40] = { 0 };

#if defined _MSC_VER
	int CPUInfo[4] = {-1};
	__cpuid(CPUInfo, 0x80000000);
	unsigned int nExIds = CPUInfo[0];
	unsigned int i, j = 0;

	// Get the information associated with each extended ID.
	char CPUBrandString[0x40] = { 0 };

    for (i = 0x80000000; i <= nExIds; ++i) {
		__cpuid(CPUInfo, i);

		// Interpret CPU brand string and cache information.
		if (i == 0x80000002) {
			memcpy( CPUBrandString,
			CPUInfo,
			sizeof(CPUInfo));
		} else if (i == 0x80000003) {
			memcpy( CPUBrandString + 16,
			CPUInfo,
			sizeof(CPUInfo));
		} else if (i == 0x80000004) {
			memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo));
		}
	}

	// trim the leading white space
	int nCheckForSpace = 1;
	for (i = 0; CPUBrandString[i] != '\0'; i++) {
		if (!isspace(CPUBrandString[i])) nCheckForSpace = 0;

		if (nCheckForSpace) {
			if (!isspace(CPUBrandString[i])) {
				CPUBrandStringFinal[j++] = CPUBrandString[i];
			}
		} else {
			CPUBrandStringFinal[j++] = CPUBrandString[i];
		}
	}
#else
	sprintf(CPUBrandStringFinal, "%s", "CPU Detection not enabled for GCC builds");
#endif

	AddLine(_T("%hs"), CPUBrandStringFinal);

	return 0;
}

// Print global memory information
int PrintGlobalMemoryInfo()
{
	MEMORYSTATUS stat;
	GlobalMemoryStatus(&stat);

	AddLine(_T("Physical RAM: %7i KB (%4i MB) total, %7i KB (%4i MB) avail"), stat.dwTotalPhys / 1024, stat.dwTotalPhys / (1024 * 1024), stat.dwAvailPhys / 1024, stat.dwAvailPhys / (1024 * 1024));
	AddLine(_T("Total RAM:    %7i KB (%4i MB) total, %7i KB (%4i MB) avail"), stat.dwTotalPageFile / 1024, stat.dwTotalPageFile / (1024 * 1024), stat.dwAvailPageFile / 1024, stat.dwAvailPageFile / (1024 * 1024));

	// Information on FB Alpha memory usage
	BOOL (WINAPI* pGetProcessMemoryInfo)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD) = NULL;
	HINSTANCE hPsapiDLL;

	hPsapiDLL = LoadLibrary(_T("psapi.dll"));
	if (hPsapiDLL) {
		pGetProcessMemoryInfo = (BOOL (WINAPI*)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD))GetProcAddress(hPsapiDLL, "GetProcessMemoryInfo");
		if (pGetProcessMemoryInfo) {
			PROCESS_MEMORY_COUNTERS pmc;

			if (pGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
				TCHAR szLine[1024] = _T("");
				int length = _tcslen(_T(APP_TITLE));
				if (length > 12) {
					length = 12;
				}
				_sntprintf(szLine, 12, _T(APP_TITLE));
				_sntprintf(szLine + length, 14 - length, _T(":                 "));
				AddLine(_T("%s%7i KB in use (%i KB peak, %i KB virtual)"), szLine, pmc.WorkingSetSize / 1024, pmc.PeakWorkingSetSize / 1024, pmc.PagefileUsage / 1024);
			}
		}
		FreeLibrary(hPsapiDLL);
	}

	return 0;
}

// Find the registry key for this display in the hardware area of the registry
// szHardwareID is the PNP ID
// szDriver     is the GUID/instance of the driver
HKEY FindMonitor(TCHAR* szHardwareID, TCHAR* szDriver)
{
	TCHAR szName[1024] = _T("");  DWORD nNameSize = sizeof(szName);
	TCHAR szClass[1024] = _T(""); DWORD nClassSize = sizeof(szClass);
	FILETIME ftLastWriteTime;
	HKEY hKey = NULL;
	HKEY hMonitorKey = NULL;

	bool bFound = false;

	// We need to enumerate all displays and all instances to check the values inside them
	RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\"), 0, KEY_READ, &hKey);
	for (int i = 0; !bFound && RegEnumKeyEx(hKey, i, szName, &nNameSize, NULL, szClass, &nClassSize, &ftLastWriteTime) != ERROR_NO_MORE_ITEMS; i++) {
		TCHAR szSubName[1024] = _T("");  DWORD nSubNameSize = sizeof(szSubName);
		TCHAR szSubClass[1024] = _T(""); DWORD nSubClassSize = sizeof(szSubClass);
		FILETIME ftSubLastWriteTime;
		HKEY hSubKey = NULL;

		nNameSize = sizeof(szName);
		nClassSize = sizeof(szClass);

		RegOpenKeyEx(hKey, szName, 0, KEY_READ, &hSubKey);
		for (int j = 0; !bFound && RegEnumKeyEx(hSubKey, j, szSubName, &nSubNameSize, NULL, szSubClass, &nSubClassSize, &ftSubLastWriteTime) != ERROR_NO_MORE_ITEMS; j++) {
			HKEY hMonitorInfoKey = NULL;
			TCHAR szKeyValue[1024]; DWORD nSize = sizeof(szKeyValue);
			DWORD nType;

			nSubNameSize = sizeof(szSubName);
			nSubClassSize = sizeof(szSubClass);

			RegOpenKeyEx(hSubKey, szSubName, 0, KEY_READ, &hMonitorKey);

			// Check if this instance is the one we're looking for
			nType = REG_SZ; nSize = sizeof(szKeyValue);
			RegQueryValueEx(hMonitorKey, _T("HardwareID"), NULL, &nType, (BYTE*)szKeyValue, &nSize);
			if (_tcsicmp(szKeyValue, szHardwareID)) {
				continue;
			}
			nType = REG_SZ; nSize = sizeof(szKeyValue);
			RegQueryValueEx(hMonitorKey, _T("Driver"), NULL, &nType, (BYTE*)szKeyValue, &nSize);
			if (_tcsicmp(szKeyValue, szDriver)) {
				continue;
			}

			// Make sure the "Device Parameters" key which contains any EDID data is present
			RegOpenKeyEx(hMonitorKey, _T("Device Parameters"), 0, KEY_READ, &hMonitorInfoKey);
			if (!hMonitorInfoKey) {
				continue;
			}

			// We've found the display we're looking for
			bFound = true;

			RegCloseKey(hMonitorInfoKey);
		}
		RegCloseKey(hSubKey);
	}
	RegCloseKey(hKey);

	if (!bFound) {
		return NULL;
	}

	return hMonitorKey;
}

int ProcessEDID(HKEY hMonitorKey)
{
	HKEY hMonitorInfoKey = NULL;
	BYTE EDIDData[1024]; DWORD nEDIDSize = sizeof(EDIDData);
	DWORD nType;

	if (hMonitorKey == NULL) {
		return 1;
	}

	RegOpenKeyEx(hMonitorKey, _T("Device Parameters"), 0, KEY_READ, &hMonitorInfoKey);
	if (hMonitorInfoKey == NULL) {
		return 1;
	}

	// When Windows can't get valid EDID data from a Display, it creates a BAD_EDID value instead of EDID
	// Thus we can forego ensuring the validity of the EDID data ourselves
	nType = REG_BINARY; nEDIDSize = sizeof(EDIDData);
	if (RegQueryValueEx(hMonitorInfoKey, _T("BAD_EDID"), NULL, &nType, EDIDData, &nEDIDSize) == 0) {
		AddLine(_T("        No EDID data present for this device"));
	}
	nType = REG_BINARY; nEDIDSize = sizeof(EDIDData);
	if (RegQueryValueEx(hMonitorInfoKey, _T("EDID"), NULL, &nType, EDIDData, &nEDIDSize) == 0) {

		// Print some basic information about this display
		AddLine(_T("        Display size ~%dx%dcm, Gamma %1.2lf"), EDIDData[0x15], EDIDData[0x16], ((double)EDIDData[0x17] + 100.0) / 100.0);

		// Print the preferred mode for this display
		if (EDIDData[0x18] & 2) {
			int nPixelClock = ((EDIDData[0x36 + 0x01] << 8) | EDIDData[0x36 + 0x00]) * 10000;

			// Size of the display image in pixels (including blanking and sync)
			int nActiveH = ((EDIDData[0x36 + 0x04] & 0xF0) << 4) | EDIDData[0x36 + 0x02];
			int nBlankH  = ((EDIDData[0x36 + 0x04] & 0x0F) << 8) | EDIDData[0x36 + 0x03];
			int nActiveV = ((EDIDData[0x36 + 0x07] & 0xF0) << 4) | EDIDData[0x36 + 0x05];
			int nBlankV  = ((EDIDData[0x36 + 0x07] & 0x0F) << 8) | EDIDData[0x36 + 0x06];

			// Size of the display image in mm
			int nSizeH = ((EDIDData[0x36 + 0x0E] & 0xF0) << 4) | EDIDData[0x36 + 0x0C];
			int nSizeV = ((EDIDData[0x36 + 0x0E] & 0x0F) << 8) | EDIDData[0x36 + 0x0D];

			// We need to calculate the refresh rate ourselves based on the other numbers
			double dRefresh = 1.0 / ((double)(nActiveH + nBlankH) * (nActiveV + nBlankV) / nPixelClock);

			AddLine(_T("        Preferred mode %dx%d, %1.3lf Hz (%dx%dmm, %1.3lf MHz)"), nActiveH, nActiveV, dRefresh, nSizeH, nSizeV, nPixelClock / 1000000.0);
		}

		{
			// Print the signal limits for this display

			int nLimitsOffset = 0;

			// Find the data block containing the limits
			for (int i = 0; i < 4; i++) {
				if (EDIDData[0x36 + i * 0x12 + 0] == 0x00 && EDIDData[0x36 + i * 0x12 + 1] == 0x00 && EDIDData[0x36 + i * 0x12 + 2] == 0x00 && EDIDData[0x36 + i * 0x12 + 3] == 0xFD) {
					nLimitsOffset = 0x36 + i * 0x12;
					break;
				}
			}

			if (nLimitsOffset) {
				AddLine(_T("        Max. bandwidth %d MHz, H sync %d-%d KHz, V sync %d-%d Hz"), EDIDData[nLimitsOffset + 0x09] * 10, EDIDData[nLimitsOffset + 0x07], EDIDData[nLimitsOffset + 0x08], EDIDData[nLimitsOffset + 0x05], EDIDData[nLimitsOffset + 0x06]);
			}
		}

#if 0
		{

			// Print the raw data block
			BYTE* pData = EDIDData;

			AddText(_T("\r\n        Raw EDID data\r\n"));
			for (int n = 0; n < 8; n++) {
				AddText(_T("            "));

				for (int m = 0; m < 16; m++) {
					AddText(_T("%02X "), *pData & 255);
					pData++;
				}
				AddText(_T(""));
			}
			AddText(_T(""));
		}
#endif

	}

	RegCloseKey(hMonitorInfoKey);

	return 0;
}

// Print info about displays and display adapters
int PrintDisplayInfo()
{
	BOOL (WINAPI* pEnumDisplayDevices)(LPCTSTR, DWORD, PDISPLAY_DEVICE, DWORD) = NULL;
	HINSTANCE hUser32DLL;

	// The EnumDisplayDevices() function is only available on NT based OSes
	hUser32DLL = LoadLibrary(_T("user32.dll"));
	if (hUser32DLL) {
#if defined (UNICODE)
		pEnumDisplayDevices = (BOOL (WINAPI*)(LPCTSTR, DWORD, PDISPLAY_DEVICE, DWORD))GetProcAddress(hUser32DLL, "EnumDisplayDevicesW");
#else
		pEnumDisplayDevices = (BOOL (WINAPI*)(LPCTSTR, DWORD, PDISPLAY_DEVICE, DWORD))GetProcAddress(hUser32DLL, "EnumDisplayDevicesA");
#endif
		if (pEnumDisplayDevices) {
			DISPLAY_DEVICE ddAdapter;
			DISPLAY_DEVICE ddDisplay;

			ddAdapter.cb = sizeof(DISPLAY_DEVICE);
			ddDisplay.cb = sizeof(DISPLAY_DEVICE);

			// Now that we've ensured we can use the EnumDisplayDevices() function, use it to enumerate the connected displays
			AddLine(_T("Installed displays and display adapters:"));

			for (int i = 0; pEnumDisplayDevices(NULL, i, &ddAdapter, 0); i++) {

				// We're only interested in real display adapters
				if (!(ddAdapter.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER)) {
					for (int j = 0; pEnumDisplayDevices(ddAdapter.DeviceName, j, &ddDisplay, 0); j++) {

						HKEY hKey = NULL;
						HKEY hMonitorKey = NULL;

						TCHAR szMfg[1024] = _T("");		   DWORD nMfgSize = sizeof(szMfg);
						TCHAR szDeviceDesc[1024] = _T(""); DWORD nDeviceDescSize = sizeof(szDeviceDesc);

						// If the display is active, get the data about it
						if (ddDisplay.StateFlags & DISPLAY_DEVICE_ACTIVE) {
							if (!_tcsnicmp(ddDisplay.DeviceKey, _T("\\Registry\\Machine\\"), 18)) {
								TCHAR szDriver[1024]; DWORD nSize = sizeof(szDriver);
								DWORD nType = REG_SZ;
								RegOpenKeyEx(HKEY_LOCAL_MACHINE, ddDisplay.DeviceKey + 18, 0, KEY_READ, &hKey);

								// Find the registry key for this display in the hardware area of the registry
								RegQueryValueEx(hKey, _T("MatchingDeviceId"), NULL, &nType, (BYTE*)szDriver, &nSize);
								hMonitorKey = FindMonitor(szDriver, ddDisplay.DeviceKey + 57);

								RegQueryValueEx(hMonitorKey, _T("DeviceDesc"), NULL, &nType, (BYTE*)szDeviceDesc, &nDeviceDescSize);
								RegQueryValueEx(hMonitorKey, _T("Mfg"), NULL, &nType, (BYTE*)szMfg, &nMfgSize);
							}

							// Print the information we've got so far
							TCHAR* pszStatus = _T("");
							if (ddAdapter.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) {
								pszStatus = _T(" (primary)");
							}
							if (!(ddAdapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) {
								pszStatus = _T(" (disabled)");
							}
							if (szMfg[0] && szDeviceDesc[0]) {
								AddLine(_T("    %s %s on %s%s"), szMfg, szDeviceDesc, ddAdapter.DeviceString, pszStatus);
							} else {
								AddLine(_T("    %s on %s%s"), ddDisplay.DeviceString, ddAdapter.DeviceString, pszStatus);
							}

							ProcessEDID(hMonitorKey);

							if (hMonitorKey) {
								RegCloseKey(hMonitorKey);
							}
							if (hKey) {
								RegCloseKey(hKey);
							}
						}
					}
				}
			}
		}
		FreeLibrary(hUser32DLL);
	}

	return 0;
}

// Print FB Alpha settings
int PrintFBAInfo()
{
	InterfaceInfo* pInfo;

	AddLine(_T(APP_TITLE) _T(" information:"));
	AddLine(_T(""));

	AddLine(_T("Built on ") _T(MAKE_STRING(BUILD_DATE)) _T(", ") _T(MAKE_STRING(BUILD_TIME))  _T(", using ") _T(MAKE_STRING(BUILD_COMP)) _T("."));
	AddLine(_T("    Optimised for ") _T(MAKE_STRING(BUILD_CPU)) _T(" CPUs."));
#if defined (_UNICODE)
	AddLine(_T("    Using Unicode for all text."));
#else
	AddLine(_T("    Using multi-byte characters for all text, active codepage is %d."), GetACP());
#endif
#if defined (FBNEO_DEBUG)
	AddLine(_T("    Debug functionality present."));
#else
	AddLine(_T("    Debug functionality absent."));
#endif
	AddLine(_T(""));

	AddLine(_T("MMX optimisations %s."), bBurnUseMMX ? _T("enabled") : _T("disabled"));
#ifdef BUILD_A68K
	if (bBurnUseASMCPUEmulation) {
		AddLine(_T("A68K emulation core enabled for MC68000 emulation."));
		AddLine(_T("Musashi emulation core enabled for MC68010/MC68EC020 emulation."));
	} else {
		AddLine(_T("Musashi emulation core enabled for MC680x0 family emulation."));
	}
#else
	AddLine(_T("Musashi emulation core enabled for MC680x0 family emulation."));
#endif
	AddLine(_T(""));

	if (bDrvOkay) {
		TCHAR szName[1024];

		int n = _stprintf(szName, _T("Emulating %s (%hs)"), BurnDrvGetText(DRV_NAME), DecorateGameName(nBurnDrvActive));
		if (n >= 70) {
			_tcscpy(szName + 66, _T("...)"));
		}
		AddLine(_T("%s"), szName);
		AddLine(_T("    Vertical refresh is %.2lf Hz."), (double)nBurnFPS / 100);
		AddLine(_T("    CPU running at %i%% of normal frequency."), nBurnCPUSpeedAdjust * 100 / 256);
		AddLine(_T(""));
	} else {
		AddLine(_T("Not emulating any game."));
		AddLine(_T(""));
	}

	if ((pInfo = VidGetInfo()) != NULL) {
		AddLine(_T("Video settings:"));
		PrintInterfaceInfo(pInfo);
	}
	AddLine(_T(""));

	if ((pInfo = AudGetInfo()) != NULL) {
		AddLine(_T("Audio settings:"));
		PrintInterfaceInfo(pInfo);
	}
	AddLine(_T(""));

	if ((pInfo = InputGetInfo()) != NULL) {
		AddLine(_T("Input settings:"));
		PrintInterfaceInfo(pInfo);
	}
	AddLine(_T(""));

	if ((pInfo = ProfileGetInfo()) != NULL) {
		AddLine(_T("Profiling settings:"));
		PrintInterfaceInfo(pInfo);
	}

	return 0;
}

// Print process information
int PrintProcessInfo()
{
#if 1

	HANDLE hModuleSnap;
	MODULEENTRY32 me32;

	hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
	if (hModuleSnap == INVALID_HANDLE_VALUE) {
		AddLine(_T("Unable to retrieve detailed process information."));
	} else {
		me32.dwSize = sizeof(MODULEENTRY32);
		if(!Module32First(hModuleSnap, &me32)) {
			AddLine(_T("Unable to retrieve detailed process information."));
		} else {
			AddLine(_T("Detailed process information about %s:"), me32.szModule);
			AddLine(_T(""));
			AddLine(_T("%s (base address 0x%p, size %i KB)"), me32.szModule, me32.modBaseAddr, me32.modBaseSize / 1024);
			AddLine(_T(""));

			if (pExceptionPointers) {
				bool bFound = false;
				do {
					if (me32.modBaseAddr <= pExceptionPointers->ExceptionRecord->ExceptionAddress && (me32.modBaseAddr + me32.modBaseSize) > pExceptionPointers->ExceptionRecord->ExceptionAddress) {
						bFound = true;
						break;
					}
				} while (Module32Next(hModuleSnap, &me32));

				if (bFound) {
					AddLine(_T("Exception occurred in module %s:"), me32.szModule);
					AddLine(_T("    %20s (base address 0x%p, size %6i KB)"), me32.szModule, me32.modBaseAddr, me32.modBaseSize / 1024);
					AddLine(_T(""));
				} else {
					AddLine(_T("Unable to locate module in which exception occurred"));
					AddLine(_T(""));
				}
			}

			Module32First(hModuleSnap, &me32);
			AddLine(_T("Modules loaded by %s:"), me32.szModule);
			while (Module32Next(hModuleSnap, &me32)){
				AddLine(_T("    %20s (base address 0x%p, size %6i KB)"), me32.szModule, me32.modBaseAddr, me32.modBaseSize / 1024);
			}
		}

		CloseHandle(hModuleSnap);
	}

#endif

	return 0;
}

// Print information about installed devices
int PrintDeviceInfo()
{
	// Get a list of all devices that are present and enabled
	HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_PROFILE);
	if (hDevInfo != INVALID_HANDLE_VALUE) {
		SC_HANDLE (WINAPI* pOpenSCManager)(LPCTSTR, LPCTSTR, DWORD) = NULL;
		SC_HANDLE (WINAPI* pOpenService)(SC_HANDLE, LPCTSTR, DWORD) = NULL;
		BOOL (WINAPI* pQueryServiceConfig)(SC_HANDLE, LPQUERY_SERVICE_CONFIG, DWORD, LPDWORD) = NULL;
		BOOL (WINAPI* pCloseServiceHandle)(SC_HANDLE) = NULL;
		HINSTANCE hAdvapi32DLL;

		SP_DEVINFO_DATA did;

		hAdvapi32DLL = LoadLibrary(_T("advapi32.dll"));
		if (hAdvapi32DLL) {
#if defined (_UNICODE)
			pOpenSCManager = (SC_HANDLE (WINAPI*)(LPCTSTR, LPCTSTR, DWORD))GetProcAddress(hAdvapi32DLL, "OpenSCManagerW");
			pOpenService = (SC_HANDLE (WINAPI*)(SC_HANDLE, LPCTSTR, DWORD))GetProcAddress(hAdvapi32DLL, "OpenServiceW");
			pQueryServiceConfig = (BOOL (WINAPI*)(SC_HANDLE, LPQUERY_SERVICE_CONFIG, DWORD, LPDWORD))GetProcAddress(hAdvapi32DLL, "QueryServiceConfigW");
#else
			pOpenSCManager = (SC_HANDLE (WINAPI*)(LPCTSTR, LPCTSTR, DWORD))GetProcAddress(hAdvapi32DLL, "OpenSCManagerA");
			pOpenService = (SC_HANDLE (WINAPI*)(SC_HANDLE, LPCTSTR, DWORD))GetProcAddress(hAdvapi32DLL, "OpenServiceA");
			pQueryServiceConfig = (BOOL (WINAPI*)(SC_HANDLE, LPQUERY_SERVICE_CONFIG, DWORD, LPDWORD))GetProcAddress(hAdvapi32DLL, "QueryServiceConfigA");
#endif
			pCloseServiceHandle = (BOOL (WINAPI*)(SC_HANDLE))GetProcAddress(hAdvapi32DLL, "CloseServiceHandle");
		}

		AddLine(_T("Installed devices (partial list):"));
		AddLine(_T(""));

		// Enumerate all devices in the list
		did.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &did); i++) {
			TCHAR* list[] = { _T("display"), _T("media"), _T("hid"), _T("hidclass"), _T("mouse"), _T("system"), NULL };
			TCHAR szClass[1024] = _T("");

			// Get the installer class of a device
			SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_CLASS, NULL, (BYTE*)szClass, 1024, NULL);

			// determine if the device is of a class we're interested in
			for (int j = 0; list[j]; j++) {
				if (!_tcsicmp(list[j], szClass)) {
					TCHAR szName[1024] = _T("");
					TCHAR szService[1024] = _T("");
					TCHAR szDriverDate[1024] = _T("");
					TCHAR szDriverVersion[1024] = _T("");
					TCHAR szImagePath[1024] = _T("");
					HKEY hKey;

					// Get the device name
					SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_DEVICEDESC, NULL, (BYTE*)szName, 1024, NULL);

					// Check if there are any lower filter drivers
					SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_LOWERFILTERS, NULL, (BYTE*)szService, 1024, NULL);
					if (szService[0] == _T('\0')) {
						// If we haven't got any lower filter drivers, just use the service key
						SetupDiGetDeviceRegistryProperty(hDevInfo, &did, SPDRP_SERVICE, NULL, (BYTE*)szService, 1024, NULL);
					}

					// Get driver info
					hKey = SetupDiOpenDevRegKey(hDevInfo, &did, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ);
					if (hKey != INVALID_HANDLE_VALUE) {
						DWORD nType = REG_SZ;
						DWORD nSize = 1024;

						RegQueryValueEx(hKey, _T("DriverVersion"), NULL, &nType, (BYTE*)szDriverVersion, &nSize);
						RegQueryValueEx(hKey, _T("DriverDate"), NULL, &nType, (BYTE*)szDriverDate, &nSize);
						RegCloseKey(hKey);
					}

					// If we have a driver, get the filename
					if (szService[0] && pOpenSCManager && pOpenService && pQueryServiceConfig && pCloseServiceHandle) {
						SC_HANDLE hSCManager, hService;
						QUERY_SERVICE_CONFIG* pSC = NULL;
						DWORD nSize;

						hSCManager = OpenSCManager(NULL, NULL, GENERIC_READ);
						hService = OpenService(hSCManager, szService, GENERIC_READ);

						QueryServiceConfig(hService, NULL, 0, &nSize);
						pSC = (QUERY_SERVICE_CONFIG*)malloc(nSize);

						if (QueryServiceConfig(hService, pSC, nSize, &nSize)) {
							_tcscpy(szImagePath, pSC->lpBinaryPathName);
						}

						if (pSC) {
							free(pSC);
							pSC = NULL;
						}

						CloseServiceHandle(hService);
						CloseServiceHandle(hSCManager);
					}

#if 0
					if (szImagePath[0] == '\0') {
						if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {
							// Officially, we shouldn't be doing this, but there's no API for getting this info on Win9x
							const TCHAR* pszServices = "SYSTEM\\CurrentControlSet\\Services\\";
							char szFullService[1024] = "";

							_tcscpy(szFullService, pszServices);
							_tcscat(szFullService, szService);
							if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szFullService, 0, KEY_EXECUTE, &hKey) == ERROR_SUCCESS) {
								DWORD nType = REG_SZ;
								DWORD nSize = 1024;

								RegQueryValueEx(hKey, _T("ImagePath"), NULL, &nType, szImagePath, &nSize);
								RegCloseKey(hKey);
							}
						}
					}
#endif

					// Print the information
					if (j < 2 || szImagePath[0]) {
						AddLine(_T("    %s"), szName);
						AddLine(_T("        %s"), szImagePath[0] ? szImagePath : _T("no driver needed"));

						if (szDriverVersion[0]) {
							AddLine(_T("        version %s (%s)"), szDriverVersion, szDriverDate[0] ? szDriverDate : _T("no date"));
						}
					}

					break;
				}
			}
		}

		FreeLibrary(hAdvapi32DLL);

		SetupDiDestroyDeviceInfoList(hDevInfo);
	}

	return 0;
}

static INT_PTR CALLBACK SysInfoProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	static int nReturnCode = 0;

	switch (Msg) {
		case WM_INITDIALOG: {
		    time_t nTime;
			tm* tmTime;

			nReturnCode = 0;

			pszTextBuffer = NULL;
			nTextBufferSize = 0;

			time(&nTime);
			tmTime = localtime(&nTime);

			hLogFont = CreateFont(14, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, DEFAULT_QUALITY, FF_MODERN, _T(""));

			hCodeBGBrush = NULL;
			hCodeFont = NULL;

			if (pExceptionPointers) {
				TCHAR szText[1024] = _T("");
				hCodeBGBrush = CreateSolidBrush(RGB(0,0,0));
				hCodeFont = CreateFont(22, 0, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, FF_MODERN, _T("Lucida Console"));
				SendDlgItemMessage(hDlg, IDC_SYSINFO_CODE, WM_SETFONT, (WPARAM)hCodeFont, (LPARAM)0);

				_stprintf(szText, _T("Guru Meditation #%08X.%08X"), pExceptionPointers->ExceptionRecord->ExceptionCode, (INT_PTR)pExceptionPointers->ExceptionRecord->ExceptionAddress);
				SendDlgItemMessage(hDlg, IDC_SYSINFO_CODE, WM_SETTEXT, (WPARAM)0, (LPARAM)szText);

				AddLine(_T(APP_TITLE) _T(" v%.20s fatal exception report (%s)."), szAppBurnVer, _tasctime(tmTime));

				AddLine(_T(""));
				AddLine(_T("----------------------------------------------------------------------"));

				// Exception
				PrintExceptionInfo();
			} else {
				AddLine(_T(APP_TITLE) _T(" v%.20s system information (%s)."), szAppBurnVer, _tasctime(tmTime));
			}

			AddLine(_T(""));
			AddLine(_T("----------------------------------------------------------------------"));

			AddLine(_T("System information:"));
			AddLine(_T(""));

			// OS information
			PrintOSInfo();

			// CPU information
			PrintCPUInfo();

			AddLine(_T(""));

			// Global memory information
			PrintGlobalMemoryInfo();

			AddLine(_T(""));

			// Displays and display adapters
			PrintDisplayInfo();

			AddLine(_T(""));
			AddLine(_T("----------------------------------------------------------------------"));

			PrintFBAInfo();

			AddLine(_T(""));
			AddLine(_T("----------------------------------------------------------------------"));

			// Process information
			PrintProcessInfo();

			AddLine(_T(""));
			AddLine(_T("----------------------------------------------------------------------"));

			if (bPrintDriverInfo) {
				// Device information
				PrintDeviceInfo();

				AddLine(_T(""));
				AddLine(_T("----------------------------------------------------------------------"));
			}

			SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_SETFONT, (WPARAM)hLogFont, (LPARAM)0);
			SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, EM_SETMARGINS, (WPARAM)EC_LEFTMARGIN, (LPARAM)3);
			SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_SETTEXT, (WPARAM)0, (LPARAM)pszTextBuffer);

			WndInMid(hDlg, hScrnWnd);

			return TRUE;
		}

		case WM_CLOSE: {
			if (pszTextBuffer) {
				free(pszTextBuffer);
				pszTextBuffer = NULL;
			}
			nTextBufferSize = 0;

			DeleteObject(hLogFont);
			if (hCodeFont) {
				DeleteObject(hCodeFont);
			}
			if (hCodeBGBrush) {
				DeleteObject(hCodeBGBrush);
			}
			EndDialog(hDlg, nReturnCode);
			break;
		}

		case WM_CTLCOLORSTATIC: {
			if ((HWND)lParam == GetDlgItem(hDlg, IDC_SYSINFO_EDIT)) {
				return (INT_PTR)GetSysColorBrush(15);
			}
			if ((HWND)lParam == GetDlgItem(hDlg, IDC_SYSINFO_CODE)) {
				SetTextColor((HDC)wParam, RGB(255, 0, 0));
				SetBkMode((HDC)wParam, TRANSPARENT);
				return (INT_PTR)hCodeBGBrush;
			}
			break;
		}

		case WM_COMMAND: {
			if (HIWORD(wParam) == BN_CLICKED) {
				if (LOWORD(wParam) == IDOK) {
					SendMessage(hDlg, WM_CLOSE, 0, 0);
				}
				if (LOWORD(wParam) == IDC_SYSINFO_DEBUG) {
					nReturnCode = 1;
					SendMessage(hDlg, WM_CLOSE, 0, 0);
				}
				if (LOWORD(wParam) == IDC_SYSINFO_SHOW && pExceptionPointers) {
					RECT rect = { 0, 0, 0, 125 };

					SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDOK), TRUE);
					EnableWindow(GetDlgItem(hDlg, IDC_SYSINFO_SHOW), FALSE);

					MapDialogRect(hDlg, &rect);
					int nSize = rect.bottom;
					GetWindowRect(hDlg, &rect);
					MoveWindow(hDlg, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top + nSize, TRUE);

					WndInMid(hDlg, hScrnWnd);
				}
				if (LOWORD(wParam) == IDC_SYSINFO_LOG_SAVE) {
					FILE* fp = NULL;

					SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDOK), TRUE);

					if (pExceptionPointers) {
						TCHAR szLogName[MAX_PATH];
						_stprintf(szLogName, _T("config/%s.error.log"), szAppExeName);
						fp = _tfopen(szLogName, _T("ab"));

						EnableWindow(GetDlgItem(hDlg, IDC_SYSINFO_LOG_SAVE), FALSE);
					} else {
						TCHAR szFilter[1024];

						_stprintf(szFilter, FBALoadStringEx(hAppInst, IDS_DISK_TEXT, true));
						memcpy(szFilter + _tcslen(szFilter), _T(" (*.txt)\0*.txt\0\0"), 16 * sizeof(TCHAR));

						_stprintf(szChoice, _T("%s system information.txt"), szAppExeName);

						memset(&ofn, 0, sizeof(ofn));
						ofn.lStructSize = sizeof(ofn);
						ofn.hwndOwner = hDlg;
						ofn.lpstrFilter = szFilter;
						ofn.lpstrFile = szChoice;
						ofn.nMaxFile = sizeof(szChoice) / sizeof(TCHAR);
						ofn.lpstrInitialDir = _T(".");
						ofn.Flags = OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
						ofn.lpstrDefExt = _T("txt");
						ofn.lpstrTitle = FBALoadStringEx(hAppInst, IDS_DISK_SAVEREPORT, true);

						if (GetSaveFileName(&ofn)) {
							fp = _tfopen(szChoice, _T("wb"));
						}
					}

					if (fp) {
						TCHAR* szText;
						int nSize = SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_GETTEXTLENGTH, 0, 0);
						szText = (TCHAR*)malloc((nSize + 1) * sizeof(TCHAR));

#if defined (_UNICODE)
						// write a Unicode Byte Order Mark if needed
						if (ftell(fp) == 0) {
							WRITE_UNICODE_BOM(fp);
						}
#endif

						if (nSize && szText) {
							SendDlgItemMessage(hDlg, IDC_SYSINFO_EDIT, WM_GETTEXT, (WPARAM)(nSize + 1) * sizeof(TCHAR), (LPARAM)szText);
							fwrite(szText, sizeof(TCHAR), nSize, fp);
							_ftprintf(fp, _T(""));
						}
						if (szText) {
							free(szText);
							szText = NULL;
						}
						fclose(fp);
					}
				}
			}
		}
	}

	return 0;
}

LONG CALLBACK ExceptionFilter(_EXCEPTION_POINTERS* pExceptionInfo)
{
	int nRet;

	// If we're getting recursive calls to this function, bail out
	if (nRecursion++) {
		if (nRecursion <= 2) {
			MessageBox(hScrnWnd, _T(APP_TITLE) _T(" will now be terminated."), _T(APP_TITLE) _T(" Fatal exception"), MB_OK | MB_SETFOREGROUND);
			AppCleanup();
		}
#ifdef _DEBUG
		return EXCEPTION_CONTINUE_SEARCH;
#else
		return EXCEPTION_EXECUTE_HANDLER;
#endif
	}

	SplashDestroy(1);
	AudSoundStop();

	pExceptionPointers = pExceptionInfo;

	nRet = FBADialogBox(hAppInst, MAKEINTRESOURCE(IDD_EXCEPTION), hScrnWnd, (DLGPROC)SysInfoProc);

	switch (nRet) {
		case 1:
			return EXCEPTION_CONTINUE_SEARCH;
		default:
			AppCleanup();
			return EXCEPTION_EXECUTE_HANDLER;
	}
}

int SystemInfoCreate()
{
	pExceptionPointers = NULL;

	FBADialogBox(hAppInst, MAKEINTRESOURCE(IDD_SYSINFO), hScrnWnd, (DLGPROC)SysInfoProc);

	return 0;
}
