#include "pch.hpp"
//===========================================================================//
// File:	 ErrorHandler.cpp												 //
// Project:  GameOS					                                         //
// Contents: Error Handling routines										 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
// 11/04/97  SP     Created													 // 
// 11/10/97  JTR    Forced GOERR_TERMINATE to crash into debugger			 //
// 12/12/97  JTR    Fixed dialog focus										 //
// 12/13/97  JTR    Fixed Initialize Spew									 //
// 01/06/98  JTR    Straightened out bug in error message					 //
// 03/16/98  JTR    Added a nifty exception handler							 //
//																			 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "ErrorHandler.hpp"
#include "Windows.hpp"
#include "Registry.hpp"
#include "DirectX.hpp"
#include "DXRasterizer.hpp"
#include "3DRasterizer.hpp"
#include "MemoryManager.hpp"
#include "Sound Renderer.hpp"
#include "ControlManager.hpp"
#include "LocalizationManager.hpp"
#include "Time.hpp"
#include "Texture Manager.hpp"
#include "Debugger.hpp"
#include "Profiler.hpp"
#include "Font3D.hpp"
#include "DateStamp.hpp"
#include "DirectXDebugging.hpp"
#include <tlhelp32.h>
#include <regstr.h>
#include <Lmcons.h>

extern DWORD SavedSP;
extern DWORD SavedBP;
extern float FrameHistory[8];
extern char* FindChipset();
extern int FrameTime;


extern bool OnNetwork;
extern char BladePath[MAX_PATH];
char* DecNumber( DWORD Value, bool DisplayMeg=1 );
__int64 GetDriverFileDetails( FixedLengthString& Buffer, char* DriverName, bool SoundInfo=0 );
extern int eBPP;
extern int eHardware;
extern int eMode;
extern int eWidth;
extern int eHeight;
extern int eRefresh;
extern int eDevice;

//
// Variables used in exception handling
//
char DebuggerName[MAX_PATH+1];
bool LaunchDebugger=0;
int ErrorFlags,ErrorReturn;
char* ErrorTitle;
char* ErrorMessage;
char* ErrorMessageTitle;
bool AllowFail=FALSE;					// When true FAILS are not reported and never stop
volatile bool SpewSilence=0;						// Set to disable all spews
HANDLE hSpewOutput = INVALID_HANDLE_VALUE;
extern volatile char* ExceptionAttempt;

char* Day[]={
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};


//
// Not directly linked so that NT 4.0 works (functions only in 98 and NT 5.0)
//
typedef HANDLE (WINAPI *T_CreateToolhelp32Snapshot)( DWORD dwFlags, DWORD th32ProcessID );
typedef BOOL (WINAPI *T_Heap32ListFirst)( HANDLE hSnapshot, LPHEAPLIST32 lphl );
typedef BOOL (WINAPI *T_Heap32ListNext)( HANDLE hSnapshot, LPHEAPLIST32 lphl );
typedef BOOL (WINAPI *T_Heap32First)( LPHEAPENTRY32 lphe, DWORD th32ProcessID, DWORD th32HeapID );
typedef BOOL (WINAPI *T_Heap32Next)( LPHEAPENTRY32 lphe );
typedef BOOL (WINAPI *T_Module32First)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );
typedef BOOL (WINAPI *T_Module32Next)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );
typedef BOOL (WINAPI *T_Process32First)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );
typedef BOOL (WINAPI *T_Process32Next)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );

static T_CreateToolhelp32Snapshot	_CreateToolhelp32Snapshot;
static T_Heap32ListFirst			_Heap32ListFirst;
static T_Heap32ListNext				_Heap32ListNext;
static T_Heap32First				_Heap32First;
static T_Heap32Next					_Heap32Next;
static T_Module32First				_Module32First;
static T_Module32Next				_Module32Next;
static T_Process32First				_Process32First;
static T_Process32Next				_Process32Next;













char* GetBlend( DWORD Blend )
{
	static char tempBuf[128];
	
	*tempBuf=0;
	if( Blend&D3DPBLENDCAPS_ZERO )
		strcat( tempBuf,"ZERO " );
	if( Blend&D3DPBLENDCAPS_ONE )
		strcat( tempBuf,"ONE " );
	if( Blend&D3DPBLENDCAPS_SRCCOLOR )
		strcat( tempBuf,"SRCCOLOR " );
	if( Blend&D3DPBLENDCAPS_INVSRCCOLOR )
		strcat( tempBuf,"INVSRCCOLOR " );
	if( Blend&D3DPBLENDCAPS_SRCALPHA )
		strcat( tempBuf,"SRCALPHA " );
	if( Blend&D3DPBLENDCAPS_INVSRCALPHA )
		strcat( tempBuf,"INVSRCALPHA " );
	if( Blend&D3DPBLENDCAPS_DESTALPHA )
		strcat( tempBuf,"DESTALPHA " );
	if( Blend&D3DPBLENDCAPS_INVDESTALPHA )
		strcat( tempBuf,"INVDESTALPHA " );
	if( Blend&D3DPBLENDCAPS_DESTCOLOR )
		strcat( tempBuf,"DESTCOLOR " );
	if( Blend&D3DPBLENDCAPS_INVDESTCOLOR )
		strcat( tempBuf,"INVDESTCOLOR " );
	if( Blend&D3DPBLENDCAPS_SRCALPHASAT )
		strcat( tempBuf,"SRCALPHASAT " );
	return tempBuf;
}




//
// Return the version number as an ASCII string
//
char* VersionNumber( char* Buffer, DWORD High, DWORD Low )
{
	if( 0==(Low>>16) )
	{
		sprintf( Buffer, "%d.%d.%04d", High>>16, High&0xffff, Low );
	}
	else
	{
		sprintf( Buffer, "%d.%d.%02d.%04d", High>>16, High&0xffff, Low>>16, Low&0xffff );
	}
	return Buffer;
}






extern SoundRendererData SoundRenderer;

//
// Used to get driver file information for sound and video drivers. Returns version number or 0
//
__int64 GetDriverFileDetails( FixedLengthString& Buffer, char* DriverName, bool SoundInfo )
{
	char FileName[MAX_PATH];
	char TimeBuffer[32];
	WIN32_FIND_DATA TempFindData;
	FILETIME lft;
	SYSTEMTIME st;
	HANDLE Handle;
	struct TRANSARRAY
	{
		WORD wLanguageID;
		WORD wCharacterSet;
	};
	TRANSARRAY* pTransArray;
	void* pFixedPointer;
	char Query[64];
	UINT FixedLength;
	DWORD Info;
	__int64 result=0;
//
//
//
	char* padding="";
	if( SoundInfo )
		padding="        ";
//
// Show name of driver
//
	Buffer << "Driver file       " << padding << " : " << DriverName << "\n";
//
// Find driver in windows system directory (try different extensions)
//
	GetSystemDirectory( FileName, MAX_PATH );
	strcat( FileName, "\\" );
	strcat( FileName, DriverName );
	DWORD NameSize=strlen(FileName);

	Handle=FindFirstFile( FileName, &TempFindData );
	if( INVALID_HANDLE_VALUE==Handle )
	{
		strcat( FileName, ".dll" );
		Handle=FindFirstFile( FileName, &TempFindData );
		if( INVALID_HANDLE_VALUE==Handle )
		{
			FileName[NameSize]=0;
			strcat( FileName, ".vxd" );
			Handle=FindFirstFile( FileName, &TempFindData );
			if( INVALID_HANDLE_VALUE==Handle )
			{
				FileName[NameSize]=0;
				strcat( FileName, ".sys" );
				Handle=FindFirstFile( FileName, &TempFindData );
				if( INVALID_HANDLE_VALUE==Handle )
				{
//
// Now look in System\Drivers directory
//
					GetSystemDirectory( FileName, MAX_PATH );
					strcat( FileName, "\\Drivers\\" );
					strcat( FileName, DriverName );
					DWORD NameSize=strlen(FileName);

					Handle=FindFirstFile( FileName, &TempFindData );
					if( INVALID_HANDLE_VALUE==Handle )
					{
						strcat( FileName, ".dll" );
						Handle=FindFirstFile( FileName, &TempFindData );
						if( INVALID_HANDLE_VALUE==Handle )
						{
							FileName[NameSize]=0;
							strcat( FileName, ".vxd" );
							Handle=FindFirstFile( FileName, &TempFindData );
							if( INVALID_HANDLE_VALUE==Handle )
							{
								FileName[NameSize]=0;
								strcat( FileName, ".sys" );
								Handle=FindFirstFile( FileName, &TempFindData );
								if( INVALID_HANDLE_VALUE==Handle )
								{
//
// Now look in System32\Drivers directory
//
									GetSystemDirectory( FileName, MAX_PATH );
									strcat( FileName, "32\\Drivers\\" );
									strcat( FileName, DriverName );
									DWORD NameSize=strlen(FileName);

									Handle=FindFirstFile( FileName, &TempFindData );
									if( INVALID_HANDLE_VALUE==Handle )
									{
										strcat( FileName, ".dll" );
										Handle=FindFirstFile( FileName, &TempFindData );
										if( INVALID_HANDLE_VALUE==Handle )
										{
											FileName[NameSize]=0;
											strcat( FileName, ".vxd" );
											Handle=FindFirstFile( FileName, &TempFindData );
											if( INVALID_HANDLE_VALUE==Handle )
											{
												FileName[NameSize]=0;
												strcat( FileName, ".sys" );
												Handle=FindFirstFile( FileName, &TempFindData );
												if( INVALID_HANDLE_VALUE==Handle )
												{
													Buffer << "Driver file data  " << padding << " : Cannot find driver file\n";
													return 0;
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
//
// Show size and date of driver
//
	FindClose( Handle );
	Buffer << "Driver size       " << padding << " : " << DecNumber( TempFindData.nFileSizeLow, 0) << "\n";
	FileTimeToLocalFileTime( &TempFindData.ftCreationTime, &lft );
	FileTimeToSystemTime( &lft,&st );
	wsprintf( TimeBuffer, "%02d:%02d:%02d %s %d/%d/%d", st.wHour, st.wMinute, st.wSecond, Day[st.wDayOfWeek], st.wMonth, st.wDay, st.wYear );
	Buffer << "Driver date       " << padding << " : " << TimeBuffer << "\n";
//
// Try and find version number information
//
	DWORD size=GetFileVersionInfoSize( FileName, &Info );
	if( size> 0 )
	{
		char* ptr=(char*)malloc(size);

		GetFileVersionInfo( FileName, Info, size, ptr );

		if( VerQueryValue( ptr, "\\VarFileInfo\\Translation",&pFixedPointer, &FixedLength ) )
		{
			pTransArray=(TRANSARRAY*)pFixedPointer;

			wsprintf( Query, "\\StringFileInfo\\%04x%04x\\ProductVersion",pTransArray[0].wLanguageID,pTransArray[0].wCharacterSet );
//
// Look for numeric version numbers
//
			VS_FIXEDFILEINFO vsinfo;
			if( VerQueryValue( ptr, "\\", &pFixedPointer, &FixedLength ) )
			{
				memcpy( &vsinfo, pFixedPointer, sizeof(vsinfo) );
				result=((__int64)vsinfo.dwProductVersionMS<<32) + vsinfo.dwProductVersionLS;
//
// Show attributes
//
				char Attributes[128];
				Attributes[0]=0;

				if (vsinfo.dwFileFlagsMask & VS_FF_PRERELEASE)
				{
					if (vsinfo.dwFileFlags & VS_FF_PRERELEASE)
						strcat(Attributes, "Beta ");
					else
						strcat(Attributes, "Final ");
				}
				if (vsinfo.dwFileFlagsMask & VS_FF_DEBUG)
				{
					if (vsinfo.dwFileFlags & VS_FF_DEBUG)
						strcat(Attributes, "Debug");
					else
						strcat(Attributes, "Release");
				}

				if (vsinfo.dwFileFlagsMask & VS_FF_PATCHED)
				{
					if (vsinfo.dwFileFlags & VS_FF_PATCHED)
					{
						if (Attributes[0])
							strcat(Attributes, ", ");
						strcat(Attributes, "Patched");
					}
				}

				if (vsinfo.dwFileFlagsMask & VS_FF_PRIVATEBUILD)
				{
					if (vsinfo.dwFileFlags & VS_FF_PRIVATEBUILD)
					{
						if (Attributes[0])
							strcat(Attributes, ", ");
						strcat(Attributes, "Private Build");
					}
				}

				if (vsinfo.dwFileFlagsMask & VS_FF_SPECIALBUILD)
				{
					if (vsinfo.dwFileFlags & VS_FF_SPECIALBUILD)
					{
						if (Attributes[0])
							strcat(Attributes, ", ");
						strcat(Attributes, "Special Build");
					}
				}
				Buffer << "Attributes        " << padding << " : " << Attributes << "\n";
			}
//
// Look for version information in the code page provided or try English
//
			if( VerQueryValue( ptr, Query, &pFixedPointer, &FixedLength ) )
			{
				Buffer << "Driver version    " << padding << " : " << (char*)pFixedPointer << "\n";
			}
			else
			{
				wsprintf( Query, "\\StringFileInfo\\040904E4\\ProductVersion" );
				if( VerQueryValue( ptr, Query, &pFixedPointer, &FixedLength ) )
				{
					Buffer << "Driver version    " << padding << " : " << (char*)pFixedPointer << "\n";
				}
			}
		}
		free(ptr);
	}
	return result;
}


//
// ValidateDevice error returns
//
char* GetValidateError( DWORD result )
{
	switch( result )
	{
		case D3D_OK:
			return "Ok";
		case D3DERR_TOOMANYOPERATIONS:
			return "Too many operations";
		case D3DERR_UNSUPPORTEDALPHAARG:
			return "Unsupported alpha arg";
		case D3DERR_UNSUPPORTEDALPHAOPERATION:
			return "Unsupported alpha operation";
		case D3DERR_UNSUPPORTEDCOLORARG:
			return "Unsupported color arg";
		case D3DERR_UNSUPPORTEDCOLOROPERATION:
			return "Unsupported color operation";
		case D3DERR_UNSUPPORTEDFACTORVALUE:
			return "Unsupported factor value";
		case D3DERR_UNSUPPORTEDTEXTUREFILTER:
			return "Unsupported texture filter";
		case D3DERR_WRONGTEXTUREFORMAT:
			return "Wrong texture format";
		case D3DERR_CONFLICTINGTEXTUREPALETTE:
			return "Comflicting texture platte";
		case D3DERR_CONFLICTINGTEXTUREFILTER:
			return "Conflicting texture filter";
		case DDERR_INVALIDPARAMS:
			return "Invalid params";
		case DDERR_INVALIDOBJECT:
			return "Invalid object";
		case 1:
			return "No";
	}
	static char Buffer[20];
	sprintf( Buffer, "Unknown 0x%x",result );
	return Buffer;
}




//
// Returns a string describing the direct draw/3d/sound environment
//
void GetDirectXDetails( FixedLengthString& Buffer )
{
	_try
	{
		static char tempbuf[150];
		char Buffer1[64];
		HKEY hKey;
		char PreferedDevice[128];
		unsigned long size = 128;
//
// List input devices
//
		int OldLogMode=LogMode;
		bool OldInDebugger=InDebugger;		// So data does not effect log
		InDebugger=1;
		LogMode=2;

		if( g_CMData.m_numJoysticks )
		{
			gosJoystick_Info jInfo;
			Buffer << "\nInput devices\n=============\n";
			for( int t0=0; t0<g_CMData.m_numJoysticks; t0++ )
			{
				gosJoystick_GetInfo(t0, &jInfo);
				Buffer << (char*)jInfo.lpstrName << (char*)(jInfo.bIsForceFeedback ? " (Force feedback)\n" : "\n");
				Buffer << "Buttons  " << (int)jInfo.nButtons << "\n";
				Buffer << "Axis     " << (int)jInfo.nAxes << "\n";
				Buffer << "Sliders  " << (int)jInfo.nSliders << "\n";
				Buffer << "POV hats " << (int)jInfo.nPOVs << "\n";
			}
		}

		LogMode=OldLogMode;
		InDebugger=OldInDebugger;		// So data does not effect log
//
// Try and get selected sound device
//
		PreferedDevice[0]=0;
		LONG result=RegOpenKey(HKEY_CURRENT_USER,"Software\\Microsoft\\Multimedia\\Sound Mapper\\",&hKey);
		if( ERROR_SUCCESS==result )
		{
			result=RegQueryValueEx(hKey,"Playback",NULL,NULL,(PBYTE)PreferedDevice, &size);
			RegCloseKey(hKey);
		}
//
// Now list all sound device details
//
		for( int t0=0; t0<SoundRenderer.m_numDevices; t0++ )
		{
			Buffer << "\nSound device " << t0 ;
			if( SoundRenderer.m_numDevices>1 )
			{
				if( (0==*PreferedDevice && 0==t0) || 0==stricmp( PreferedDevice, SoundRenderer.m_DeviceArray[t0].Description ) )
				{
					Buffer << " (Prefered device)";
					if( 0==Environment.soundDevice )
					{
						if (!Environment.soundMixInHardware)
							Buffer << " (Current) - (HW Mixing Disabled)";
						else
							Buffer << " (Current) - (HW Mixing Enabled)";
					}
				}
				else
				{
					if( Environment.soundDevice-1==t0 )
					{
						if (!Environment.soundMixInHardware)
							Buffer << " (Current) - (HW Mixing Disabled)";
						else
							Buffer << " (Current) - (HW Mixing Enabled)";
					}
				}
			}
			else
			{
				if (!Environment.soundMixInHardware)
					Buffer << " (Current) - (HW Mixing Disabled)";
				else
					Buffer << " (Current) - (HW Mixing Enabled)";
			}
			Buffer << "\n==============";
			Buffer << "\nDescription        : " << SoundRenderer.m_DeviceArray[t0].Description << "\n";
//
// Try and find MEDIA device that uses this WAVE driver - dig through registry on Win9x
//
			HKEY hKey;
			HKEY hKey1;
			HKEY hKey2;
			HKEY hKey3;
			FILETIME FileTime;
			DWORD Index=0;
			static char Subkey[256];
			static char Subkey1[256];
			int result=RegOpenKey( HKEY_LOCAL_MACHINE, "System\\CurrentControlSet\\Services\\Class\\Media", &hKey );
			int found;
			if( ERROR_SUCCESS==result )
			{
				do
				{
					DWORD SubKeySize;
					SubKeySize=sizeof(Subkey);
					found=RegEnumKeyEx( hKey, Index++, Subkey, &SubKeySize, 0, 0, 0, &FileTime );	// Enum through all devices
					if( ERROR_SUCCESS==found )
					{
						strcpy( Subkey1, Subkey );
						strcat( Subkey1, "\\Config\\Platform" );

						result=RegOpenKey( hKey, Subkey1, &hKey1 );
						if( ERROR_SUCCESS==result )
						{
							DWORD driverSize=255;
							char driverBuffer[256];
							result=RegQueryValueEx( hKey1, "ClientDrivers", NULL, NULL, (LPBYTE)&driverBuffer, &driverSize );
							if( ERROR_SUCCESS==result )
							{
								strlwr( driverBuffer );

								if( 0!=strstr( driverBuffer, SoundRenderer.m_DeviceArray[t0].Driver ) )
								{
									strcpy( Subkey1, Subkey );
									strcat( Subkey1, "\\DirectSound\\Device Presence" );
									
									result=RegOpenKey( hKey, Subkey1, &hKey2 );
									if( ERROR_SUCCESS==result )
									{
										DWORD intSize=4;
										DWORD ValVXD;
										DWORD ValWDM;
										int result1=RegQueryValueEx( hKey2, "VxD", NULL, NULL, (LPBYTE)&ValVXD, &intSize );
										intSize=4;
										int result2=RegQueryValueEx( hKey2, "WDM", NULL, NULL, (LPBYTE)&ValWDM, &intSize );
										if( ERROR_SUCCESS==result1 && ERROR_SUCCESS==result2 )
										{
											strcpy( Subkey1, Subkey );
											strcat( Subkey1, "\\DirectSound\\Mixer Defaults" );
											
											result=RegOpenKey( hKey, Subkey1, &hKey3 );
											if( ERROR_SUCCESS==result )
											{
												DWORD intSize=4;
												DWORD Acceleration;
												result=RegQueryValueEx( hKey3, "Acceleration", NULL, NULL, (LPBYTE)&Acceleration, &intSize );
												if( ERROR_SUCCESS==result )
												{
													char aBuffer[16];

													if( ValVXD || ValWDM )
													{
														if( Acceleration==0 )
															strcpy( aBuffer, "Full" );
														else if( Acceleration==8 )
															strcpy( aBuffer, "Standard" );
														else if( Acceleration==15 )
															strcpy( aBuffer, "Basic" );
														else
															sprintf( aBuffer, "%d", Acceleration );
													}
													else
													{
														strcpy( aBuffer, "None" );
													}
													Buffer << "Acceleration Level : " << aBuffer << "\n";
												}
											}
											RegCloseKey( hKey3 );
										}
										RegCloseKey( hKey2 );
									}
								}
							}
							RegCloseKey( hKey1 );
						}
					}
				} while( ERROR_SUCCESS==found );
		
				RegCloseKey( hKey );
			}
//
// Show information about driver
//
			GetDriverFileDetails( Buffer, SoundRenderer.m_DeviceArray[t0].Driver );
//
// Now show remainder of sound information
//
			Buffer << "CERTIFIED          : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_CERTIFIED ? "Yes" : "No") << "\n";
			Buffer << "EMULDRIVER         : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_EMULDRIVER ? "Yes" : "No") << "\n";
			Buffer << "CONTINUOUSRATE     : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_CONTINUOUSRATE ? "Yes" : "No") << "\n";
			Buffer << "PRIMARYMONO        : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_PRIMARYMONO ? "Yes" : "No") << "\n";
			Buffer << "PRIMARYSTEREO      : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_PRIMARYSTEREO ? "Yes" : "No") << "\n";
			Buffer << "PRIMARY8BIT        : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_PRIMARY8BIT ? "Yes" : "No") << "\n";
			Buffer << "PRIMARY16BIT       : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_PRIMARY16BIT ? "Yes" : "No") << "\n";
			Buffer << "SECONDARYMONO      : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_SECONDARYMONO ? "Yes" : "No") << "\n";
			Buffer << "SECONDARYSTEREO    : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_SECONDARYSTEREO ? "Yes" : "No") << "\n";
			Buffer << "SECONDARY8BIT      : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_SECONDARY8BIT ? "Yes" : "No") << "\n";
			Buffer << "SECONDARY16BIT     : " << (char*)(SoundRenderer.m_DeviceArray[t0].Caps.dwFlags&DSCAPS_SECONDARY16BIT ? "Yes" : "No") << "\n";
			Buffer << "MinSecondarySampleRate      : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMinSecondarySampleRate << "\n";
			Buffer << "MaxSecondarySampleRate      : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxSecondarySampleRate << "\n";
			Buffer << "PrimaryBuffers              : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwPrimaryBuffers << "\n";
			Buffer << "MaxHwMixingAllBuffers       : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxHwMixingAllBuffers << "\n";
			Buffer << "MaxHwMixingStaticBuffers    : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxHwMixingStaticBuffers << "\n";
			Buffer << "MaxHwMixingStreamingBuffers : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxHwMixingStreamingBuffers << "\n";
			Buffer << "FreeHwMixingAllBuffers      : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHwMixingAllBuffers << "\n";
			Buffer << "FreeHwMixingStaticBuffers   : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHwMixingStaticBuffers << "\n";
			Buffer << "FreeHwMixingStreamingBuffers: " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHwMixingStreamingBuffers << "\n";
			Buffer << "MaxHw3DAllBuffers           : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxHw3DAllBuffers << "\n";
			Buffer << "MaxHw3DStaticBuffers        : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxHw3DStaticBuffers << "\n";
			Buffer << "MaxHw3DStreamingBuffers     : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxHw3DStreamingBuffers << "\n";
			Buffer << "FreeHw3DAllBuffers          : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHw3DAllBuffers << "\n";
			Buffer << "FreeHw3DStaticBuffers       : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHw3DStaticBuffers << "\n";
			Buffer << "FreeHw3DStreamingBuffers    : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHw3DStreamingBuffers << "\n";
			Buffer << "TotalHwMemBytes             : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwTotalHwMemBytes << "\n";
			Buffer << "FreeHwMemBytes              : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwFreeHwMemBytes << "\n";
			Buffer << "MaxContigFreeHwMemBytes     : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwMaxContigFreeHwMemBytes << "\n";
			Buffer << "UnlockTransferRateHwBuffers : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwUnlockTransferRateHwBuffers << "\n";
			Buffer << "PlayCpuOverheadSwBuffers    : " << (int)SoundRenderer.m_DeviceArray[t0].Caps.dwPlayCpuOverheadSwBuffers << "\n";
		}
//
// Now get video card device information
//
		for( t0=0; t0<NumDevices; t0++ )
		{
//
// Info about the card
//
			Buffer << "\nVideo device " << t0 << "\n==============\n";
			Buffer << "Description        : " << DeviceArray[t0].DDid.szDescription << "\n";
			if( -1!=DeviceArray[t0].CurrentCard )
				Buffer << "Company / Chipset  : " << KnownCards[DeviceArray[t0].CurrentCard].VendorName << " / " << KnownCards[DeviceArray[t0].CurrentCard].CardName << "\n";
			GetDriverFileDetails( Buffer, DeviceArray[t0].DDid.szDriver );
//
// Show driver GameOS wanted
//
			char Buffer2[64];
			Buffer2[0]=0;
			if( -1!=DeviceArray[t0].CurrentCard && (KnownCards[DeviceArray[t0].CurrentCard].DriverH || KnownCards[DeviceArray[t0].CurrentCard].DriverL) )
			{
				DWORD DrvH=KnownCards[DeviceArray[t0].CurrentCard].DriverH;
				DWORD DrvL=KnownCards[DeviceArray[t0].CurrentCard].DriverL;
				if( WindowsNT )
				{
					DrvH=KnownCards[DeviceArray[t0].CurrentCard].NTDriverH;
					DrvL=KnownCards[DeviceArray[t0].CurrentCard].NTDriverL;
				}
			
				if( (-1!=DeviceArray[t0].CurrentCard) &&
					((DrvL != DeviceArray[t0].DDid.liDriverVersion.LowPart) ||
					 (DrvH != DeviceArray[t0].DDid.liDriverVersion.HighPart) ) &&
					(DeviceArray[t0].DDid.liDriverVersion.LowPart || DeviceArray[t0].DDid.liDriverVersion.HighPart) )
				{
					sprintf( Buffer2, " (GameOS wanted %s)", VersionNumber( Buffer1, DrvH, DrvL ) );
				}
			}

			Buffer << "Version            : " << VersionNumber( Buffer1, DeviceArray[t0].DDid.liDriverVersion.HighPart, DeviceArray[t0].DDid.liDriverVersion.LowPart) << Buffer2;
			sprintf( tempbuf, "%04X.%04X (Rev %d, Subsys 0x%x)",DeviceArray[t0].DDid.dwVendorId, DeviceArray[t0].DDid.dwDeviceId, DeviceArray[t0].DDid.dwRevision, DeviceArray[t0].DDid.dwSubSysId );
//
// Show information about the card
//
			Buffer << "\nHardware ID        : " << tempbuf;
			Buffer << "\nVideo memory       : " << DecNumber(DeviceArray[t0].TotalVid);
			Buffer << "\nCard texture mem   : " << DecNumber(DeviceArray[t0].TotalLocalTex);
			Buffer << "\nAGP texture memory : " << DecNumber(DeviceArray[t0].TotalTex);

			*tempbuf=0;
			for( int t1=0; t1<16; t1++ )
			{
				strncat( tempbuf,&DeviceArray[t0].FourCC[t1*4],4 );
				strcat( tempbuf, " " );
			}
			Buffer << "\nFourCC modes       : " << tempbuf;

			*tempbuf=0;
			for( t1=0; t1<16; t1++ )
			{
				if( 0==DeviceArray[t0].TextureFormats[t1].dwSize )
					break;
				strcat( tempbuf,GetGosPixelFormat(&DeviceArray[t0].TextureFormats[t1]) );
			}
			Buffer << "\nTexture formats    : " << tempbuf;

			*tempbuf=0;
			for( t1=0; t1<16; t1++ )
			{
				if( 0==DeviceArray[t0].ZFormats[t1].dwSize )
					break;
				strcat( tempbuf,GetGosPixelFormat(&DeviceArray[t0].ZFormats[t1]) );
			}
			Buffer << "\nZ Buffer formats   : " << tempbuf;

			*tempbuf=0;
			for( t1=0; t1<32; t1+=2 )
			{
				char ModeBuf[16];
				if( 0==DeviceArray[t0].Modes16[t1] )
					break;
				sprintf( ModeBuf, "%dx%d ",DeviceArray[t0].Modes16[t1],DeviceArray[t0].Modes16[t1+1] );
				strcat( tempbuf,ModeBuf );
			}
			Buffer << "\n16 bit modes       : " << tempbuf;

			*tempbuf=0;

			for( t1=0; t1<32; t1+=2 )
			{
				char ModeBuf[16];
				if( 0==DeviceArray[t0].Modes32[t1] )
					break;
				sprintf( ModeBuf, "%dx%d ",DeviceArray[t0].Modes32[t1],DeviceArray[t0].Modes32[t1+1] );
				strcat( tempbuf,ModeBuf );
			}
			Buffer << "\n32 bit modes       : " << tempbuf;
			Buffer << "\nCERTIFIED          : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_CERTIFIED) ? "Yes" : "No"); 
			Buffer << "\nDRAWPRIMITIVES2    : " << ((DeviceArray[t0].D3DCaps.dwDevCaps & D3DDEVCAPS_DRAWPRIMITIVES2) ? "Yes" : "No"); 
			Buffer << "\nDRAWPRIMITIVES2EX  : " << ((DeviceArray[t0].D3DCaps.dwDevCaps & D3DDEVCAPS_DRAWPRIMITIVES2EX) ? "Yes" : "No"); 
			Buffer << "\nDRAWPRIMTLVERTEX   : " << ((DeviceArray[t0].D3DCaps.dwDevCaps & D3DDEVCAPS_DRAWPRIMTLVERTEX) ? "Yes" : "No"); 
			Buffer << "\nFLIPINTERVAL       : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_FLIPINTERVAL) ? "Yes" : "No"); 
			Buffer << "\nFLIPNOVSYNC        : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_FLIPNOVSYNC) ? "Yes" : "No"); 
			Buffer << "\nNO2DDURING3DSCENE  : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_NO2DDURING3DSCENE) ? "Yes" : "No"); 
			Buffer << "\nNONLOCALVIDMEM     : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_NONLOCALVIDMEM) ? "Yes" : "No"); 
			Buffer << "\nNOPAGELOCKREQUIRED : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_NOPAGELOCKREQUIRED) ? "Yes" : "No"); 
			Buffer << "\nCALIBRATE GAMMA    : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_CANCALIBRATEGAMMA ) ? "Yes" : "No"); 
			Buffer << "\nPRIMARYGAMMA       : " << ((DeviceArray[t0].DDCaps.dwCaps2 & DDCAPS2_PRIMARYGAMMA) ? "Yes" : "No"); 
			Buffer << "\nZBuffer            : " << ((DeviceArray[t0].D3DCaps.dwDeviceZBufferBitDepth & DDBD_16) ? "16" : "");
			Buffer << ((DeviceArray[t0].D3DCaps.dwDeviceZBufferBitDepth & DDBD_24) ? " 24" : "");
			Buffer << ((DeviceArray[t0].D3DCaps.dwDeviceZBufferBitDepth & DDBD_32) ? " 32\n" : "\n");
			Buffer << "Render Depth       : " << ((DeviceArray[t0].D3DCaps.dwDeviceRenderBitDepth & DDBD_16) ? "16" : "");
			Buffer << ((DeviceArray[t0].D3DCaps.dwDeviceRenderBitDepth & DDBD_24) ? " 24" : "");
			Buffer << ((DeviceArray[t0].D3DCaps.dwDeviceRenderBitDepth & DDBD_32) ? " 32\n" : "\n");
   			Buffer << "Texture Size       : " << (int)DeviceArray[t0].D3DCaps.dwMinTextureWidth << "x" << (int)DeviceArray[t0].D3DCaps.dwMinTextureHeight << " to ";
			Buffer << (int)DeviceArray[t0].D3DCaps.dwMaxTextureWidth << "x" << (int)DeviceArray[t0].D3DCaps.dwMaxTextureHeight;
			int MaxUV=DeviceArray[t0].D3DCaps.dwMaxTextureRepeat;
			if( MaxUV )
			{
				if( 0==(DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE) &&
						DeviceArray[t0].D3DCaps.dwMaxTextureRepeat > DeviceArray[t0].D3DCaps.dwMaxTextureHeight )
				{
					MaxUV/=256;
					Buffer << "\nMaximum u,v coords : (for a 256*256 texture) " << MaxUV << ".0";
				}
				else
				{
					Buffer << "\nMaximum u,v coords : " << MaxUV << ".0";
				}
			}
			else
				Buffer << "\nMaximum u,v coords : No limits";
			char gbBuffer[64];
			sprintf( gbBuffer, "%d,%d to %d,%d",(int)DeviceArray[t0].D3DCaps.dvGuardBandLeft,(int)DeviceArray[t0].D3DCaps.dvGuardBandTop,(int)DeviceArray[t0].D3DCaps.dvGuardBandRight,(int)DeviceArray[t0].D3DCaps.dvGuardBandBottom );
			Buffer << "\nGuard Band Clipping: " << gbBuffer;
			Buffer << "\nAlphaTest NotEqual : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwAlphaCmpCaps & D3DPCMPCAPS_NOTEQUAL) ? "Yes" : "No");
			Buffer << "\nMissing Src blend  : " << GetBlend(~DeviceArray[t0].D3DCaps.dpcTriCaps.dwSrcBlendCaps);
			Buffer << "\nMissing Dest blend : " << GetBlend(~DeviceArray[t0].D3DCaps.dpcTriCaps.dwDestBlendCaps);
			Buffer << "\nAGP Texture        : " << ((DeviceArray[t0].D3DCaps.dwDevCaps & D3DDEVCAPS_TEXTURENONLOCALVIDMEM) ? "Yes" : "No");
   			Buffer << "\nTexture Coords     : " << (int)(DeviceArray[t0].D3DCaps.dwFVFCaps & D3DFVFCAPS_TEXCOORDCOUNTMASK);
   			Buffer << "\nBlend Stages       : " << (int)(DeviceArray[t0].D3DCaps.wMaxTextureBlendStages);
   			Buffer << "\nMax Textures       : " << (int)(DeviceArray[t0].D3DCaps.wMaxSimultaneousTextures);
			Buffer << "\nHardware T&L       : " << ((DeviceArray[t0].D3DCaps.dwDevCaps& D3DDEVCAPS_HWTRANSFORMANDLIGHT) ? "Yes" : "No" );
			Buffer << "\nVertex Blending    : " << (int)DeviceArray[t0].D3DCaps.wMaxVertexBlendMatrices;
			if( -1==DeviceArray[t0].D3DCaps.dwMaxActiveLights )
				Buffer << "\nMaximum Lights     : Infinite";
			else
				Buffer << "\nMaximum Lights     : " << (int)DeviceArray[t0].D3DCaps.dwMaxActiveLights;
			Buffer << "\nClip Planes        : " << (int)DeviceArray[t0].D3DCaps.wMaxUserClipPlanes;
			Buffer << "\nBlend Lightmap     : " << GetValidateError( DeviceArray[t0].MultitextureLightMap ) << (DeviceArray[t0].MultitextureLightMapFilter ? " (Only Bilinear)" : "");
			Buffer << "\nBlend Specularmap  : " << GetValidateError( DeviceArray[t0].MultitextureSpecularMap ) << (DeviceArray[t0].MultitextureSpecularMapFilter ? " (Only Bilinear)" : "");
			Buffer << "\nBlend Detail       : " << GetValidateError( DeviceArray[t0].MultitextureDetail ) << (DeviceArray[t0].MultitextureDetailFilter ? " (Only Bilinear)" : "");
			Buffer << "\nLinear vid memory  : " << ((DeviceArray[t0].LinearMemory) ? "Yes" : "No");
			Buffer << "\nSELECTARG1         : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_SELECTARG1) ? "Yes" : "No");
			Buffer << "\nSELECTARG2         : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_SELECTARG2) ? "Yes" : "No");
			Buffer << "\nADD                : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_ADD) ? "Yes" : "No");
			Buffer << "\nADDSIGNED          : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_ADDSIGNED) ? "Yes" : "No");
			Buffer << "\nSUBTRACT           : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_SUBTRACT) ? "Yes" : "No");
			Buffer << "\nMODULATE           : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_MODULATE) ? "Yes" : "No");
			Buffer << "\nMODULATE2X         : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_MODULATE2X) ? "Yes" : "No");
			Buffer << "\nMODULATE4X         : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_MODULATE4X) ? "Yes" : "No");
			Buffer << "\nBLENDCURRENTALPHA  : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_BLENDCURRENTALPHA) ? "Yes" : "No");
			Buffer << "\nBLENDDIFFUSEALPHA  : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_BLENDDIFFUSEALPHA) ? "Yes" : "No");
			Buffer << "\nBLENDTEXTUREALPHA  : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_BLENDTEXTUREALPHA) ? "Yes" : "No");
			Buffer << "\nBUMPENVMAP         : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_BUMPENVMAP) ? "Yes" : "No");
			Buffer << "\nBUMPENVMAPLUMINANCE: " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_BUMPENVMAPLUMINANCE) ? "Yes" : "No");
			Buffer << "\nDOTPRODUCT3        : " << ((DeviceArray[t0].D3DCaps.dwTextureOpCaps & D3DTEXOPCAPS_DOTPRODUCT3) ? "Yes" : "No");
			Buffer << "\nANISOTROPY         : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ANISOTROPY) ? "Yes" : "No");
			Buffer << "\nANTIALIASSORTINDEP : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ANTIALIASSORTINDEPENDENT) ? "Yes" : "No");
			Buffer << "\nFOGTABLE           : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_FOGTABLE) ? "Yes" : "No");
  			Buffer << "\nFOGVERTEX          : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_FOGVERTEX) ? "Yes" : "No");
  			Buffer << "\nFOGRANGE           : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_FOGRANGE) ? "Yes" : "No");
  			Buffer << "\nWFOG               : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_WFOG) ? "Yes" : "No");
  			Buffer << "\nWBUFFER            : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_WBUFFER) ? "Yes" : "No");
  			Buffer << "\nMIPMAPLODBIAS      : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_MIPMAPLODBIAS) ? "Yes" : "No");
  			Buffer << "\nWBUFFER            : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_WBUFFER) ? "Yes" : "No");
  			Buffer << "\nZBUFFERLESSHSR     : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwRasterCaps & D3DPRASTERCAPS_ZBUFFERLESSHSR) ? "Yes" : "No");
   			Buffer << "\nMIPNEAREST         : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureFilterCaps & D3DPTFILTERCAPS_MIPNEAREST) ? "Yes" : "No");
   			Buffer << "\nLINEARMIPLINEAR    : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureFilterCaps & D3DPTFILTERCAPS_LINEARMIPLINEAR) ? "Yes" : "No");
   			Buffer << "\nDONOTSTRIPELEMENTS : " << ((DeviceArray[t0].D3DCaps.dwFVFCaps & D3DFVFCAPS_DONOTSTRIPELEMENTS) ? "Yes" : "No" );
			Buffer << "\nTEXTURE CLAMP      : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureAddressCaps & D3DPTADDRESSCAPS_CLAMP	) ? "Yes" : "No");
			Buffer << "\nTEXTURE BORDER     : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureAddressCaps & D3DPTADDRESSCAPS_BORDER	) ? "Yes" : "No");
			Buffer << "\nTEXTURE MIRROR     : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureAddressCaps & D3DPTADDRESSCAPS_MIRROR	) ? "Yes" : "No");
  			Buffer << "\nALPHAGOURAUDBLEND  : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_ALPHAGOURAUDBLEND) ? "Yes" : "No");
  			Buffer << "\nSQUAREONLY         : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_SQUAREONLY ) ? "Yes" : "No");
  			Buffer << "\nCOLORFLATMONO      : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORFLATMONO  ) ? "Yes" : "No");
  			Buffer << "\nCOLORGOURAUDMONO   : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureCaps & D3DPSHADECAPS_COLORGOURAUDMONO ) ? "Yes" : "No");
  			Buffer << "\nCOLORFLATRGB       : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_COLORFLATRGB  ) ? "Yes" : "No");
  			Buffer << "\nCOLORGOURAUDRGB    : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureCaps & D3DPSHADECAPS_COLORGOURAUDRGB ) ? "Yes" : "No");
  			Buffer << "\nSPECULARFLATRGB    : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwShadeCaps & D3DPSHADECAPS_SPECULARFLATRGB  ) ? "Yes" : "No");
  			Buffer << "\nSPECULARGOURAUDRGB : " << ((DeviceArray[t0].D3DCaps.dpcTriCaps.dwTextureCaps & D3DPSHADECAPS_SPECULARGOURAUDRGB ) ? "Yes" : "No");
			Buffer << "\n";
		}
	}
	__except(1)
	{
		Buffer << "\nProblem with getting DirectX details\n";
	}
}







//
// Returns a single string representing the error message and notes combined
//
char* GetFullErrorMessage( HWND hwnd )
{
	char* HeaderText="User Notes\n==========\n";
	char* FooterText="\n\nDetails\n=======\n";

	char* Notes=GetBugNotes( hwnd );
	int Length=0;
	if( Notes )
		Length=strlen(Notes);

	char* result=(char*)malloc( strlen(ErrorMessage) + Length + strlen(HeaderText) + strlen(FooterText) + 16 );

	if( Notes )
	{
		strcpy( result, HeaderText );
		strcat( result, Notes );
		strcat( result, FooterText );
		strcat( result, ErrorMessage );
	}
	else
	{
		strcpy( result, ErrorMessage );
	}

	return result;
}


//
// Returns a 8 digit hex number
//
char* Hex8Number( int Number )
{
	static char Buffer[12];
	wsprintf( Buffer, "0x%08X",Number );
	return Buffer;
}

//
// Returns an ASCII string that represents the int passed. Commas every thousand
//
char* DecNumber( DWORD Value, bool DisplayMeg )
{
	char Buffer[32];
	DWORD Digits=0,Length=0,MValue=(Value+1024*512)/(1024*1024);

	do
	{
		Buffer[Length++]=(char)(Value%10 + '0');
		Digits++;
		if( Digits%3 == 0 )
			Buffer[Length++]=',';
		Value/=10;
	} while (Value!=0);

	if( Buffer[Length-1]==',' )
		Length--;

	Buffer[Length]=0;
	_strrev( Buffer );

	static char Buffer2[64];
	if( DisplayMeg )
		wsprintf( Buffer2, "%s bytes (%d Meg)",Buffer, MValue );
	else
		wsprintf( Buffer2, "%s bytes",Buffer );
	return Buffer2;
}







//
// Return how long machine has been running
//
char* GetSystemUpTime()
{
	static char Buffer[32];
	MMTIME mmt;
	mmt.wType=TIME_MS;
	timeGetSystemTime( &mmt, sizeof(mmt) );
	int h=mmt.u.ms/(1000*60*60);
	mmt.u.ms-=h*(1000*60*60);
	int m=mmt.u.ms/(1000*60);
	mmt.u.ms-=m*(1000*60);
	int s=mmt.u.ms/1000;
	wsprintf( Buffer, "%dh %dm %ds", h,m,s);
	return Buffer;
}

//
// Return how long game has been running
//
char* GetGameTime( int real )
{
	static char Buffer[32];
	double t=gos_GetElapsedTime( real );

	int h=(int)(t/(60.0*60.0));
	t-=h*(60.0*60.0);
	int m=(int)(t/60.0);
	t-=m*60.0;
	if( real )
		sprintf( Buffer, "%dh %dm %.2fs", h,m,t);
	else
		sprintf( Buffer, "%dh %dm %.2fs (on frame %d)", h,m,t,CurrentFrameNumber);
	return Buffer;
}


//
// Return current time string
//
char* GetTime()
{
	static char Buffer[64];
	SYSTEMTIME st;
	GetLocalTime( &st );
	wsprintf( Buffer, "%02d:%02d:%02d %s %d/%d/%d", st.wHour, st.wMinute, st.wSecond, Day[st.wDayOfWeek], st.wMonth, st.wDay, st.wYear );
	return Buffer;
}

//
// Return executable time string
//
char* GetExeTime()
{
	static char Buffer[64];
	SYSTEMTIME st;
	FILETIME ft,lft;
	HANDLE file= CreateFile( _pgmptr, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL );
	GetFileTime( file, 0,0,&ft );			// Get last write time
	FileTimeToLocalFileTime( &ft, &lft );	// Convert to local time
	FileTimeToSystemTime( &lft,&st );		// Convert to system time
	wsprintf( Buffer, "%02d:%02d:%02d %s %d/%d/%d", st.wHour, st.wMinute, st.wSecond, Day[st.wDayOfWeek], st.wMonth, st.wDay, st.wYear );
	CloseHandle( file );
	return Buffer;
}



//
// Finds a DLL by checking all the places it could be
//
char* FindDLL( char* Name )
{
	static char FileName[MAX_PATH];
//
// Directory where the .exe is located
//
	char drive[_MAX_PATH ];
	char dir[_MAX_DIR ];
	char fname[_MAX_FNAME ];
	char ext[_MAX_EXT ];
	_splitpath( _pgmptr, drive, dir, fname, ext );
	strcpy( FileName, drive );
	strcat( FileName, dir );
	strcat( FileName, Name );
	HANDLE hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL );
	if( hFile!=INVALID_HANDLE_VALUE )
	{
	    CloseHandle( hFile );
		return  FileName;
	}
//
// Check Current Directory
//
	GetCurrentDirectory( sizeof(FileName), FileName);
	strcat( FileName,"\\" );
	strcat( FileName,Name );
	hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL );
	if( hFile!=INVALID_HANDLE_VALUE )
	{
	    CloseHandle( hFile );
		return  FileName;
	}
//
// Check Windows System Directory
//
	GetSystemDirectory( FileName, sizeof(FileName) );
	strcat( FileName,"\\" );
	strcat( FileName,Name );
	hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL );
	if( hFile!=INVALID_HANDLE_VALUE )
	{
	    CloseHandle( hFile );
		return  FileName;
	}
//
// Check Windows Directory
//
	GetWindowsDirectory( FileName, sizeof(FileName) );
	strcat( FileName,"\\" );
	strcat( FileName,Name );
	hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, 0, NULL );
	if( hFile!=INVALID_HANDLE_VALUE )
	{
	    CloseHandle( hFile );
		return  FileName;
	}
//
// Should check all directories in the path here
//
	return 0;
}

//
// Returns a string containing the version number of a given DLL
//
char* GetDLLInfo( char* Name, int ShowPath=0 )
{
	static char Buffer[MAX_PATH+64];
	char* DLLName = FindDLL( Name );

	if( !DLLName )
	{
		wsprintf( Buffer, "Could not find '%s'\n",Name );
		return Buffer;
	}

	char drive[_MAX_PATH ];
	char dir[_MAX_DIR ];
	char fname[_MAX_FNAME ];
	char ext[_MAX_EXT ];
	_splitpath( DLLName, drive, dir, fname, ext );
	char FileName[MAX_PATH];
	strcpy( FileName, drive );
	strcat( FileName, dir );
	if( FileName[strlen(FileName)-1]=='\\' )
		FileName[strlen(FileName)-1]=0;

	UINT FixedLength;
	DWORD Info;
	DWORD size=GetFileVersionInfoSize( DLLName, &Info );
	struct TRANSARRAY
	{
		WORD wLanguageID;
		WORD wCharacterSet;
	};
	TRANSARRAY* pTransArray;
	void* pFixedPointer;
	char Query[64];

	strcpy( Buffer, Name );

	int Len=strlen(Name);
	int Pad=13-Len;
	if( Pad < 0 )
		Pad=0;

	for( int t1=0; t1<Pad; t1++ )
		Buffer[Len+t1]=' ';

	Buffer[Len+t1]=0;

	if( size> 0 )
	{
		char* ptr=(char*)malloc(size);

		GetFileVersionInfo( DLLName, Info, size, ptr );

		if( VerQueryValue( ptr, "\\VarFileInfo\\Translation",&pFixedPointer, &FixedLength ) )
		{
			pTransArray=(TRANSARRAY*)pFixedPointer;

			wsprintf( Query, "\\StringFileInfo\\%04x%04x\\ProductVersion",pTransArray[0].wLanguageID,pTransArray[0].wCharacterSet );
//
// Look for version information in the code page provided
//
			if( VerQueryValue( ptr, Query, &pFixedPointer, &FixedLength ) )
			{
				strcat( Buffer, " - Version " );
				strcat( Buffer, (char*)pFixedPointer );
				if( ShowPath )
				{
					strcat( Buffer, " - Found in " );
					strcat( Buffer, FileName );
				}
				strcat( Buffer, "\n" );
				free(ptr);
				return Buffer;
			}
//
// If correct code page fails, try English
//
			wsprintf( Query, "\\StringFileInfo\\040904E4\\ProductVersion" );
			if( VerQueryValue( ptr, Query, &pFixedPointer, &FixedLength ) )
			{
				strcat( Buffer, " - Version " );
				strcat( Buffer, (char*)pFixedPointer );
				if( ShowPath )
				{
					strcat( Buffer, " - Found in " );
					strcat( Buffer, FileName );
				}
				strcat( Buffer, "\n" );
				free(ptr);
				return Buffer;
			}
		}
		free(ptr);
	}
	if( ShowPath )
	{
		strcat( Buffer, " - Found in " );
		strcat( Buffer, FileName );
	}
	strcat( Buffer, " - No info\n" );
	return Buffer;
}

//
// Reads the version numbers of important DLL's
//
void GetDLLVersions( FixedLengthString& Buffer )
{
	ExceptionAttempt="GetDLLVersions";
	Buffer << "\nDLL Version numbers\n===================\n";
	Buffer << GetDLLInfo("msvcrt.dll",1);
	Buffer << GetDLLInfo("msvcrtd.dll",1);
	Buffer << GetDLLInfo("msvcirt.dll",1);
	Buffer << GetDLLInfo("msvcirtd.dll",1);
	Buffer << GetDLLInfo("amstream.dll");
	Buffer << GetDLLInfo("quartz.dll");
	Buffer << GetDLLInfo("Ir50_32.dll");
	Buffer << GetDLLInfo("dsound.dll");
	Buffer << GetDLLInfo("dinput.dll");
	Buffer << GetDLLInfo("d3dim.dll");
	Buffer << GetDLLInfo("ddraw.dll");
	Buffer << GetDLLInfo("dplayx.dll");
}

//
// Gets 11 characters that describle the GameOS version
//
char* GetGOSVersion()
{
	static char Buffer[20];

	Buffer[0]=GameOSVersion[0];
	Buffer[1]=' ';

	if( GameOSVersion[7]==' ' )
	{
		memcpy( &Buffer[2], &GameOSVersion[8], 10 );
	}
	else
	{
		memcpy( &Buffer[2], &GameOSVersion[9], 10 );
	}
	Buffer[12]=0;
	return Buffer;
}
	



//
// Used in GetOtherProcessInfo to find the size of memory allocated
//  I have seen this take about 1 second with lots of processes open!!! - remove to speed up
//
DWORD GetProcessSize( DWORD dwPID )
{
	DWORD result=0;
	HANDLE hHeapSnap=NULL;
	HEAPLIST32 hl32;

	// Take a snapshot of all heaps in the specified process.
	hHeapSnap=_CreateToolhelp32Snapshot( TH32CS_SNAPHEAPLIST,dwPID );

	if( hHeapSnap==(HANDLE)-1 )
		return result;

	// Fill the size of the structure before using it.
	hl32.dwSize=sizeof(HEAPLIST32);

	if( _Heap32ListFirst(hHeapSnap,&hl32) )
	{
		do
		{
			HEAPENTRY32 he32;
			he32.dwSize=sizeof(HEAPENTRY32);
			
			if( _Heap32First( &he32, dwPID, hl32.th32HeapID ) )
			{
				do
				{
					if( he32.dwFlags!=LF32_FREE )
						result+=he32.dwBlockSize;
				}
				while( _Heap32Next( &he32 ) );
			}
		}
		while( _Heap32ListNext( hHeapSnap,&hl32 ) );
	}

	CloseHandle(hHeapSnap);
	return (result+1023)/1024;			// Return rounded up to the next k
}

//
// Used in GetOtherProcessInfo to find module information
//
BOOL GetProcessModule (DWORD dwPID, DWORD dwModuleID, LPMODULEENTRY32 lpMe32, DWORD cbMe32)
{
	BOOL bRet=FALSE;
	BOOL bFound=FALSE;
	HANDLE hModuleSnap=NULL;
	MODULEENTRY32 me32={0};

	// Take a snapshot of all modules in the specified process.
	hModuleSnap=_CreateToolhelp32Snapshot( TH32CS_SNAPMODULE,dwPID );

	if( hModuleSnap==(HANDLE)-1 )
		return false;

	// Fill the size of the structure before using it.
	me32.dwSize=sizeof(MODULEENTRY32);

	// Walk the module list of the process, and find the module of
	// interest. Then copy the information to the buffer pointed
	// to by lpMe32 so that it can be returned to the caller.
	if( _Module32First(hModuleSnap,&me32) )
	{
		if( WindowsNT )
		{
			CopyMemory( lpMe32,&me32,cbMe32 );
			bFound=TRUE;
		}
		else
		{
			do
			{
				if( me32.th32ModuleID==dwModuleID )
				{
					CopyMemory( lpMe32,&me32,cbMe32 );
					bFound=TRUE;
				}
			}
			while( !bFound && _Module32Next( hModuleSnap,&me32 ) );
		}

		bRet=bFound;		// if this sets bRet to FALSE, dwModuleID
							// no longer exists in specified process
	}
	else
		bRet=FALSE;			// could not walk module list

	CloseHandle(hModuleSnap);
	return(bRet);
}

//
// Returns a string describing the other processes currently running
//
void GetOtherProcessInfo( FixedLengthString& Buffer )
{
	HANDLE hProcessSnap = NULL;
	PROCESSENTRY32 pe32 = {0};
	bool FirstModule=1;
//
// Problems with windows 2000 - investigate later builds
//
//	if( WindowsNT )
//		return;
//
// Use GetProcAddress to access ToolHelp32 functions (NT 4.0 does not have them)
//
	HINSTANCE hModKernel32 = GetModuleHandle("kernel32.dll");
	if ( !hModKernel32 )
		return;
	_CreateToolhelp32Snapshot = (T_CreateToolhelp32Snapshot)GetProcAddress( hModKernel32, "CreateToolhelp32Snapshot" );
	if ( !_CreateToolhelp32Snapshot )
		return;
	_Heap32ListFirst = (T_Heap32ListFirst)GetProcAddress( hModKernel32, "Heap32ListFirst" );
	if ( !_Heap32ListFirst )
		return;
	_Heap32ListNext = (T_Heap32ListNext)GetProcAddress( hModKernel32, "Heap32ListNext" );
	if ( !_Heap32ListNext )
		return;
	_Heap32First = (T_Heap32First)GetProcAddress( hModKernel32, "Heap32First" );
	if ( !_Heap32First )
		return;
	_Heap32Next = (T_Heap32Next)GetProcAddress( hModKernel32, "Heap32Next" );
	if ( !_Heap32Next )
		return;
	_Module32First = (T_Module32First)GetProcAddress( hModKernel32, "Module32First" );
	if ( !_Module32First )
		return;
	_Module32Next = (T_Module32Next)GetProcAddress( hModKernel32, "Module32Next" );
	if ( !_Module32Next )
		return;
	_Process32First = (T_Process32First)GetProcAddress( hModKernel32, "Process32First" );
	if ( !_Process32First )
		return;
	_Process32Next = (T_Process32Next)GetProcAddress( hModKernel32, "Process32Next" );
	if ( !_Process32Next )
		return;
//
//  Take a snapshot of all processes in the system. 
//
	hProcessSnap = _CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 
	if( hProcessSnap == (HANDLE)-1 )
		return;  

	pe32.dwSize = sizeof(PROCESSENTRY32);  
	if( _Process32First( hProcessSnap, &pe32 ) )
	{ 
		BOOL bGotModule = FALSE; 
		MODULEENTRY32 me32 = {0};
		do
		{ 
			if( pe32.th32ProcessID )
			{
				bGotModule = GetProcessModule( pe32.th32ProcessID, pe32.th32ModuleID, &me32, sizeof(MODULEENTRY32) ); 
				if( bGotModule )
				{
					if( 0!=stricmp( "KERNEL32.DLL",me32.szModule ) &&
						0!=stricmp( "EXPLORER.EXE",me32.szModule ) &&
						0!=stricmp( "SYSTRAY.EXE",me32.szModule )  &&
						0!=stricmp( "TASKMON.EXE",me32.szModule )  &&
						0!=stricmp( "MPREXE.EXE",me32.szModule )  &&
						0!=stricmp( "WMIEXE.EXE",me32.szModule )  &&
						0!=stricmp( "MSTASK.EXE",me32.szModule ))
					{

						if( FirstModule )
						{
							FirstModule=0;
							if( ProcessMemorySize && !WindowsNT ) 
								Buffer << "\n               Processes  Pri Threads  Memory   Path\n====================================================";
							else
								Buffer << "\n               Processes  Pri Threads Path\n==========================================";
						}

						char tempBuffer[256];
						if( ProcessMemorySize && !WindowsNT )
						{
							sprintf( tempBuffer, "\n%24s : %2d : %3d : %6dk : %s",me32.szModule,pe32.pcPriClassBase,pe32.cntThreads,GetProcessSize(pe32.th32ProcessID),me32.szExePath );
						}
						else
						{
							sprintf( tempBuffer, "\n%24s : %2d : %3d : %s",me32.szModule,pe32.pcPriClassBase,pe32.cntThreads,me32.szExePath );
						}
						Buffer << tempBuffer;
					}
				} 
			}
			pe32.dwSize = sizeof(PROCESSENTRY32);  
		}
		while( _Process32Next( hProcessSnap, &pe32 ) ); 
	}

	// Do not forget to clean up the snapshot object. 
	CloseHandle (hProcessSnap);
	Buffer << "\n";
}


char* gosGetUserName()
{
	static char UserName[UNLEN + 1];
	DWORD NameSize=UNLEN + 1;
	if( 0==GetUserName( UserName, &NameSize ) )
	{
		char* Name=ReadRegistry( "Network\\Logon", "username", 0 );
		if( Name && *Name )
			strcpy( UserName, Name );
		else
			strcpy( UserName, "Unknown" );
	}
	return UserName;
}


char* GetBladeDetails()
{
	static char Buffer[MAX_PATH];
	sprintf( Buffer, "%s (Size %s)", BladePath, DecNumber( gos_FileSize(BladePath), 0) );
	return Buffer;
}



//
// Returns a string describing the machine (User, hardware, memory...)
//
void GetMachineDetails( FixedLengthString& Buffer )
{
	DisableErrors=1;
	__try
	{
		MEMORYSTATUS ms;
		GlobalMemoryStatus( &ms );

		char CurrentDirBuf[MAX_PATH];
		GetCurrentDirectory(sizeof(CurrentDirBuf),CurrentDirBuf);

		char ComputerName[MAX_COMPUTERNAME_LENGTH+1];
		DWORD NameSize=MAX_COMPUTERNAME_LENGTH+1;
		GetComputerName( ComputerName, &NameSize );

		Buffer << "\nMachine details\n===============";
		if( 1==LogMode )
		{
			Buffer << "\nPlaying back a logfile";
			Buffer << "\nOriginal executable       : " << LogInfo.AppPath;
			Buffer << "\nOriginal date stamp       : " << LogInfo.ExeDate;
		}
		Buffer << "\nExecutable name           : " << _pgmptr;
		Buffer << "\nExecutable time, date     : " << GetExeTime();
//
// Check for PDB file being found
//
		#ifdef LAB_ONLY
		char tempBuffer[MAX_PATH];
		strncpy( tempBuffer, _pgmptr, strlen(_pgmptr)-3 );
		tempBuffer[strlen(_pgmptr)-3]=0;
		strcat( tempBuffer, "pdb" );
		if( 0xffffffff==GetFileAttributes(tempBuffer) )
		{
			Buffer << "\nNO PDB FILE FOUND         : " << tempBuffer;
		}
		#endif
		Buffer << "\nCommand line              : " << CommandLine;
		Buffer << "\nCurrent directory         : " << CurrentDirBuf;
		Buffer << "\nCurrent time, date        : " << GetTime();
		Buffer << "\nGameOS build version      : " << GameOSVersion;
		if( Platform!=Platform_DLL )
		{
			if( gNoBlade )
				Buffer << "\nSoftware rasterizer       : Disabled";
			else
				Buffer << "\nSoftware rasterizer       : " << GetBladeDetails();
		}
		Buffer << "\nUser name                 : " << gosGetUserName();
		Buffer << "\nMachine name              : " << ComputerName;
		Buffer << "\nProcessor                 : " << GetProcessor();
		Buffer << "\nCache Information         : " << CacheInformation;
		if( ProcessorSpeed )
		{
			Buffer << "\nL2 r/w/rw speed           : " << (int)L2SpeedR   << " cycles " << (int)(ProcessorSpeed*32.0/L2SpeedR)   << "MB/s, " << (int)L2SpeedW   << " cycles " << (int)(ProcessorSpeed*32.0/L2SpeedW)   << "MB/s, " << (int)L2SpeedRW   << " cycles " << (int)(ProcessorSpeed*32.0/L2SpeedRW)   << "MB/s";
			Buffer << "\nMain memory r/w/rw speed  : " << (int)MainSpeedR << " cycles " << (int)(ProcessorSpeed*32.0/MainSpeedR) << "MB/s, " << (int)MainSpeedW << " cycles " << (int)(ProcessorSpeed*32.0/MainSpeedW) << "MB/s, " << (int)MainSpeedRW << " cycles " << (int)(ProcessorSpeed*32.0/MainSpeedRW) << "MB/s";
			if( VidMemRW )
				Buffer << "\nVideo memory r/w/rw speed : " << (int)VidMemR    << " cycles " << (int)(ProcessorSpeed*32.0/VidMemR)    << "MB/s, " << (int)VidMemW    << " cycles " << (int)(ProcessorSpeed*32.0/VidMemW)    << "MB/s, " << (int)VidMemRW    << " cycles " << (int)(ProcessorSpeed*32.0/VidMemRW)    << "MB/s";
			if( AGPMemRW )
				Buffer << "\nAGP memory r/w/rw speed   : " << (int)AGPMemR    << " cycles " << (int)(ProcessorSpeed*32.0/AGPMemR)    << "MB/s, " << (int)AGPMemW    << " cycles " << (int)(ProcessorSpeed*32.0/AGPMemW)    << "MB/s, " << (int)AGPMemRW    << " cycles " << (int)(ProcessorSpeed*32.0/AGPMemRW)    << "MB/s";
		}
		Buffer << "\nOperating system          : " << (char*)gos_GetMachineInformation(gos_Info_GetOSInfo);
		Buffer << "\nDirectX version           : " << GetDirectXVersion();
		Buffer << "\nTime since booted         : " << GetSystemUpTime();
		Buffer << "\nTime in game logic        : " << GetGameTime( 0 );
		Buffer << "\nTime app running          : " << GetGameTime( 1 );
		#ifdef LAB_ONLY
		char tBuffer[64];
		sprintf( tBuffer, "%.2fms %.2fms %.2fms %.2fms %.2fms", FrameHistory[(FrameTime-0)&7], FrameHistory[(FrameTime-1)&7], FrameHistory[(FrameTime-2)&7], FrameHistory[(FrameTime-3)&7], FrameHistory[(FrameTime-4)&7] );
		Buffer << "\nLast 5 frame times        : " << tBuffer;
		#endif
		Buffer << "\nPhysical memory           : " << DecNumber( ms.dwTotalPhys);
		Buffer << "\nAvailable physical memory : " << DecNumber( ms.dwAvailPhys);
		Buffer << "\nSwapfile size             : " << DecNumber( ms.dwTotalPageFile);
		Buffer << "\nSwapfile available        : " << DecNumber( ms.dwAvailPageFile);
		Buffer << "\nVirtual memory            : " << DecNumber( ms.dwTotalVirtual);
		Buffer << "\nAvailable virtual memory  : " << DecNumber( ms.dwAvailVirtual);
		if( SavedSP )
		{
			DWORD TempSP;
			_asm mov TempSP,esp
			Buffer << "\nAmount of stack used      : " << (int)(SavedSP-TempSP) << " bytes";
		}
		Buffer << "\nMemory load               : " << (int)ms.dwMemoryLoad << "%";
		Buffer << "\nDesktop video mode        : " << (int)(gos_GetMachineInformation(gos_Info_GetDesktopResolution)&65535) << "," << (int)(gos_GetMachineInformation(gos_Info_GetDesktopResolution)>>16) << " " << (int)gos_GetMachineInformation(gos_Info_GetDesktopBitDepth) << "bpp";
		char rBuf[20];
		rBuf[0]=0;
		if( eRefresh )
			sprintf( rBuf, " Refresh=%dHz",eRefresh );
		Buffer << "\nMode when crash occured   : " << (eMode ? "Full Screen " : "Windowed ") << eWidth << "x" << eHeight << "x" << eBPP << rBuf << " (" << DeviceArray[eMode ? eDevice : 0].DDid.szDescription << ")" << (0==eHardware ? " In software" : "");
		Buffer << "\nNetworked Machine         : " << (OnNetwork ? "Yes" : "No");
		Buffer << "\nExecutable version        : " << Environment.version;
		if( 0!=GetSystemMetrics(SM_CLEANBOOT) )
			Buffer << "\nNot a clean boot";		// If running in a 'safe mode' this gets printed
		char* chip=FindChipset();
		if( chip[0] )
			Buffer << "\nPCI Chipset               : " << chip;
		else
			Buffer << "\nPCI Chipset               : Unknown";
		Buffer << "\n";
//
// Get other process information
//
		ExceptionAttempt="GetOtherProcessInfo";
		GetOtherProcessInfo( Buffer );
//
// Show version numbers of important DLL's
//
		ExceptionAttempt="GetDLLInfo";
		GetDLLVersions( Buffer );
//
// Show PCI and ISA cards
//
		ExceptionAttempt="GetPCICardInfo";
		ScanCards( Buffer );
	}
	__except(1)
	{
		Buffer << "\nProblem with getting machine details\n";
	}
	DisableErrors=0;
}



//
// Show data bytes from a memory location
//
void ShowBytes( BYTE* mem, DWORD Len, FixedLengthString& Buffer )
{
	char temp[8];

	for( BYTE* ptr=mem; ptr<mem+32; ptr++ )
	{
		if( IsBadReadPtr((void*)ptr,1) )
			sprintf( temp, "?? " );
		else
			sprintf( temp, "%02X ",*ptr );
		Buffer << temp;
	}
	if( Len>32 )
	{
			Buffer << "\n                 ";
			for( ptr=mem+32; ptr<mem+64; ptr++ )
			{
				if( IsBadReadPtr((void*)ptr,1) )
					sprintf( temp, "?? " );
				else
					sprintf( temp, "%02X ",*ptr );
				Buffer << temp;
			}
			Buffer << "\n";
	}
	else
	{
		Buffer << "\n";
	}
}

//
// Show data bytes from a memory location
//
void ShowDwords( BYTE* mem, DWORD Len, FixedLengthString& Buffer )
{
	char temp[16];

	for( BYTE* ptr=mem; ptr<mem+Len; ptr+=4 )
	{
		if( ptr==mem+32 || ptr==mem+64 )
			Buffer << "\n                 ";

		if( IsBadReadPtr((void*)ptr,4) )
			sprintf( temp, "???????? " );
		else
			sprintf( temp, "%08X ",*(DWORD*)ptr );
		Buffer << temp;
	}
	Buffer << "\n";
}

//
// Appends to the string the processor information (Call stack, registers..)
//
void GetProcessorDetails( STACKFRAME* sf, FixedLengthString& Buffer )
{
//
// Display first 16 valid call stack entries
//
	int Count=0, Address;

	while( Count<16 )
	{
		Address=WalkStack( sf );

		if( Address )
		{
			char BufferSym[MAX_PATH];
			char BufferLoc[MAX_PATH];
			IMAGEHLP_LINE pline;

			char* File=GetSymbolFromAddress( BufferSym, Address );
			char* Line=GetLocationFromAddress( &pline, BufferLoc, Address );

			if( 0==Count )
			{
				Buffer << "\nCall Stack\n==========\n";
			}

			Buffer << Hex8Number(Address) << " " << File;

			int t1=strlen(File);
			while( t1<32 )
			{
				Buffer << " ";
				t1++;
			}
			
			Buffer << " " << Line << "\n";
			Count++;
		}
		else
			break;
	}
//
// If registers valid, display them
//
//	if( !(ErrorFlags & gos_ErrorNoRegisters) )
//
// We now always show registers
//
	{
		Buffer << "\nProcessor Registers";
		Buffer << " EAX=" << Hex8Number(SavedContext.Eax);
		Buffer << " EBX=" << Hex8Number(SavedContext.Ebx);
		Buffer << " ECX=" << Hex8Number(SavedContext.Ecx);
		Buffer << " EDX=" << Hex8Number(SavedContext.Edx);
		Buffer << " Flags=" << Hex8Number(SavedContext.EFlags);
		Buffer << "\n";
		Buffer << "===================";
		Buffer << " ESI=" << Hex8Number(SavedContext.Esi);
		Buffer << " EDI=" << Hex8Number(SavedContext.Edi);
		Buffer << " EBP=" << Hex8Number(SavedContext.Ebp);
		Buffer << " ESP=" << Hex8Number(SavedContext.Esp);
		Buffer << "   EIP=" << Hex8Number(SavedContext.Eip) << "\n";
//
// Display memory pointed at by ESP, EIP, EDI and ESI
//
		Buffer << "EIP (" << Hex8Number(SavedContext.Eip) << ") ";
		ShowBytes( (BYTE*)SavedContext.Eip, 32, Buffer );

		Buffer << "ESP (" << Hex8Number(SavedContext.Esp) << ") ";
		ShowDwords( (BYTE*)SavedContext.Esp, 96, Buffer );

		if( SavedContext.Edi > 65536 )
		{
			Buffer << "EDI (" << Hex8Number(SavedContext.Edi) << ") ";
			ShowBytes( (BYTE*)SavedContext.Edi, 64, Buffer );
		}

		if( SavedContext.Esi > 65536 )
		{
			Buffer << "ESI (" << Hex8Number(SavedContext.Esi) << ") ";
			ShowBytes( (BYTE*)SavedContext.Esi, 64, Buffer );
		}
		if( SavedContext.Eax > 65536 )
		{
			Buffer << "EAX (" << Hex8Number(SavedContext.Eax) << ") ";
			ShowBytes( (BYTE*)SavedContext.Eax, 32, Buffer );
		}
		if( SavedContext.Ebx > 65536 )
		{
			Buffer << "EBX (" << Hex8Number(SavedContext.Ebx) << ") ";
			ShowBytes( (BYTE*)SavedContext.Ebx, 32, Buffer );
		}
		if( SavedContext.Ecx > 65536 )
		{
			Buffer << "ECX (" << Hex8Number(SavedContext.Ecx) << ") ";
			ShowBytes( (BYTE*)SavedContext.Ecx, 32, Buffer );
		}
		if( SavedContext.Edx > 65536 )
		{
			Buffer << "EDX (" << Hex8Number(SavedContext.Edx) << ") ";
			ShowBytes( (BYTE*)SavedContext.Edx, 32, Buffer );
		}
	}
}



//
// Returns a string describing any game variables (Application specific)
//
void GetGameDetails( FixedLengthString& Buffer, DWORD ErrorFlags )
{
	if( Environment.GetGameInformation )
	{
		DisableErrors=1;
		__try
		{
			char* info=Environment.GetGameInformation();
			if( info && *info!=0 )
			{
				if (ErrorFlags & gos_ErrorGameInfoFirst)
					Buffer << "\n" << info << "\n";
				else
					Buffer << "\nInformation returned by Game\n============================\n" << info << "\n";
			}
			else
				Buffer << "\n";
		}
		__except(1)
		{
			Buffer << "\nProblem with information from Game\n";
		}
		DisableErrors=0;
	}
}






typedef BOOL (WINAPI* ISDEBUGGERPRESENTPROC)(VOID);
//
// Detects if a 'Just in time' debugger is available on the machine
//
BOOL IsDebuggerAvailable()
{
	LaunchDebugger=0;
	BOOL Present=0;
//
// Use Win98/NT API to detect debugger attached - or use Win95 Trick (from WDJ Mar 99)
//
	ISDEBUGGERPRESENTPROC _IsDebuggerPresent = (ISDEBUGGERPRESENTPROC)GetProcAddress( GetModuleHandle("kernel32.dll"), "IsDebuggerPresent" );

    if (_IsDebuggerPresent != NULL)
    {
        Present=_IsDebuggerPresent();			// Call function on Win98 / NT
    }
    else
    {
		DWORD	dwThreadId = GetCurrentThreadId();
		DWORD	dwProcessId = GetCurrentProcessId();
		LPVOID	pProcessDatabase = NULL;
		DWORD	obfuscator;

		__asm
		{
			mov eax,18h
			mov eax,fs:[eax]
			sub eax,10h
			xor eax,[dwThreadId]
			mov [obfuscator], eax
		}

		pProcessDatabase = (VOID *)(dwProcessId ^ obfuscator);

		if (!IsBadReadPtr(pProcessDatabase, 190))
		{
			Present=(((DWORD *)pProcessDatabase)[8] & 1);
		}
	}
//
// If attached already, return true
//
	if( Present )
		return 1;
//
// Flag that the debugger needs to be launched
//
	LaunchDebugger=1;
//
// Otherwise is a JIT debugger installed? - Check registry
//
	HKEY hkeyDebugger;
	DWORD Len = sizeof(DebuggerName);

	LONG result=RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_AEDEBUG, 0, KEY_READ, &hkeyDebugger);
    if (result == ERROR_SUCCESS)
	{
        result = RegQueryValueEx( hkeyDebugger, REGSTR_VAL_AEDEBUG_DEBUGGER, NULL, NULL, (LPBYTE)DebuggerName, &Len );
        RegCloseKey( hkeyDebugger );
        return (result == ERROR_SUCCESS);
    }
//
// Check WIN.INI
//
	return GetProfileString( "AeDebug", "Debugger", "", (char*)&DebuggerName, Len) > 0;
}



//
// About to trigger an int 3 - make sure JIT debugger is running!
//
void TriggerDebugger()
{
	PROCESS_INFORMATION pi = { NULL };
	SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
	STARTUPINFO         si = { sizeof(STARTUPINFO) };
	const DWORD         dwProcessId = GetCurrentProcessId();
	HANDLE              hEventDebuggerReady = NULL;
	char				Buffer[MAX_PATH];

	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;
	hEventDebuggerReady = CreateEvent(&sa, TRUE, FALSE, NULL);

	wsprintf( Buffer, DebuggerName, (unsigned long)dwProcessId, (unsigned long)hEventDebuggerReady);

	if (CreateProcess( NULL, Buffer, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
	{
		CloseHandle( pi.hProcess );
		CloseHandle( pi.hThread );
		WaitForSingleObject( hEventDebuggerReady, INFINITE);
	}

	CloseHandle( hEventDebuggerReady );
}



//
// If the file exists, return a string containing the line given
// Used in _DEBUG mode to return line with exception or error
//
char* GetLineFromFile( char* tempLine, char* FileName, int LineNumber )
{
//
// If line number too large, ignore it
//
	if( LineNumber>65000 || 0==FileName )
		return 0;
//
// Is the file name pointer valid? (NT 5 often is not)
//
	char* Test=FileName;
	do
	{
		if( IsBadReadPtr( Test++, 1 ) )
		{
			return 0;
		}

	} while( *Test!=0 );
//
// Filename cannot be correct if too long
//
	if( Test-FileName>MAX_PATH )
	{
		return 0;
	}
//
// Open File
//
	HANDLE hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
	if( hFile==INVALID_HANDLE_VALUE )
	{
		return 0;
	}
//
// Read in whole file
//
	unsigned long dwBytesRead;
	int Length=GetFileSize( hFile, NULL );
	char* Buffer=(char*)malloc( Length );
	ReadFile(hFile,(LPVOID)Buffer, Length, &dwBytesRead, NULL);  
    CloseHandle( hFile );
//
// Find line or end of file
//
	char*Source=Buffer;
	while( Source<(Buffer+Length) && LineNumber>1 )
	{
		while( *Source!=0xa && Source<(Buffer+Length) )
			Source++;
		Source++;
		LineNumber--;
	}
//
// Copy line to buffer and remove tabs
//
	char* Dest=tempLine;

	while( (*Source==' ' || *Source==9) && Source<(Buffer+Length))		// Skip leading spaces or tabs
		Source++;

	while( *Source!=0xa && *Source!=0xd && Source<(Buffer+Length))
	{
		if( *Source==9 )
		{
			*Dest++=' ';												// Copy line, making tabs single spaces
			Source++;
		}
		else
		{
			*Dest++=*Source++;
		}
		if( Dest > tempLine+MAX_LINE_LENGTH-4 )
			break;
	}
//
// Append newline
//
	*Dest++='\n';
	*Dest=0;
//
// Remove file and return
//
	free( Buffer );
	return tempLine;
}



char BugNotes[MAX_BUG_NOTES];
//
// Return any notes the user has typed into error dialog
//
char* GetBugNotes( HWND hwnd )
{
	GetDlgItemText( hwnd, IDC_NOTES, BugNotes, sizeof(BugNotes));
	if( BugNotes[0]==0 )
		return 0;
	else
		return BugNotes;
}





//
// Grab a copy of the backbuffer and create a 24bit BMP in memory to hold it.
//
// if gScreenBMP==1 then a whole .BMP image will be returned. Otherwise the image will be width*height*3 bytes (RGB) - ready for JPEGing.
//
BYTE* _stdcall GrabScreenImage(IDirectDrawSurface7* pBBSurface, int nScrW, int nScrH)
{
    BYTE* Header;

    if( !pBBSurface )
        pBBSurface = BackBufferSurface;

	if( pBBSurface )
	{
//
// Lock the back buffer to read pixels
//
		DDSURFACEDESC2 ddsd;
		ddsd.dwSize = sizeof(ddsd);
		AllowFail=1;
		HRESULT result=wLock( pBBSurface, NULL, &ddsd, DDLOCK_NOSYSLOCK | DDLOCK_WAIT | DDLOCK_READONLY, NULL );
		AllowFail=0;
		if( !FAILED(result) )
		{
//
// Get information about back buffer
//
		BYTE* Source=(BYTE*)ddsd.lpSurface;
		int Pitch=ddsd.lPitch;
            int Width=nScrW ? nScrW: ddsd.dwWidth;
            int Height=nScrH ? nScrH: ddsd.dwHeight;
//
// Create .BMP headers
//
		ScreenImageSize=Width*Height*3 + (gScreenBMP==1 ? sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) : 0);
		    Header=(BYTE*)malloc(ScreenImageSize);

            if( Header ) {
		
		if( gScreenBMP )
		{
			BITMAPFILEHEADER bf;
			BITMAPINFOHEADER bi;
			memset( &bf, 0, sizeof(bf));					// Clear bitmap headers
			memset( &bi, 0, sizeof(bi));

			bf.bfType=0x4d42;
			bf.bfSize=ScreenImageSize;
			bf.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

			bi.biSize=sizeof(BITMAPINFOHEADER);
			bi.biWidth=Width;
			bi.biHeight=Height;								// Fill in bitmap header details
			bi.biPlanes=1;
			bi.biBitCount=24;
			bi.biCompression=BI_RGB;

			memcpy( Header, &bf, sizeof(bf));
			memcpy( Header+sizeof(bf), &bi, sizeof(bi));	// Put into image
		}
//
// Read backbuffer and convert to 24 bit RGB, store in .BMP
//
		BYTE* Dest=Header + (gScreenBMP ? sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) : 0);
		BYTE* LineStart=Source + (gScreenBMP ? Pitch*(Height-1) : 0);			// Read screen bottom to top
		for( int y=0; y<Height; y++ )
		{
			for( int x=0; x<Width; x++ )
			{
				DWORD Pixel;
				
				if( 32==ddsd.ddpfPixelFormat.dwRGBBitCount )
				{
					Pixel=*(DWORD*)Source;
					Source+=4;
				}
				else
				{
					Pixel=GetBackBufferColor(*(WORD*)Source);
					Source+=2;
				}			

				*Dest++=(BYTE)(Pixel&255);
				*Dest++=(BYTE)((Pixel>>8)&255);
				*Dest++=(BYTE)((Pixel>>16)&255);
			}
			Source=LineStart + (gScreenBMP ? -Pitch : Pitch);
			LineStart=Source;
		}
            }

		    wUnlock( pBBSurface, NULL );
        } else {
            Header = NULL;
        }
    } else {
        Header = NULL;
    }

		return Header;
}








//
// Return a string describing the exception code
//
char* GetExceptionCode( EXCEPTION_RECORD* er )
{
	static char Error[128];

	switch (er->ExceptionCode)
	{
		case EXCEPTION_ACCESS_VIOLATION:
			if( WindowsNT )
			{
				if( er->ExceptionInformation[1] )
					sprintf( Error, "Attempt to %s address 0x%08X",er->ExceptionInformation[0] ? "write to" : "read from", er->ExceptionInformation[1] );		
				else
					sprintf( Error, "Attempt to %s NULL",er->ExceptionInformation[0] ? "write to" : "read from" );		
			}
			else
			{
				strcpy( Error, "Access violation" );
			}
			break;
		case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
			strcpy( Error, "Accessed Array Out of Bounds" );
			break;
		case EXCEPTION_BREAKPOINT:
			strcpy( Error, "Hit Breakpoint" );
			break;
		case EXCEPTION_DATATYPE_MISALIGNMENT:
			strcpy( Error, "Data Misaligned" );
			break;
		case EXCEPTION_FLT_DENORMAL_OPERAND:
			strcpy( Error, "FPU Denormal Operand" );
			break;
		case EXCEPTION_FLT_DIVIDE_BY_ZERO:
			strcpy( Error, "FPU Div By Zero" );
			break;
		case EXCEPTION_FLT_INEXACT_RESULT:
			strcpy( Error, "FPU Inexact Result" );
			break;
		case EXCEPTION_FLT_INVALID_OPERATION:
			strcpy( Error, "FPU Invalid Operation" );
			break;
		case EXCEPTION_FLT_OVERFLOW:
			strcpy( Error, "FPU Overflow" );
			break;
		case EXCEPTION_FLT_STACK_CHECK:
			strcpy( Error, "FPU Stack Fault" );
			break;
		case EXCEPTION_FLT_UNDERFLOW:
			strcpy( Error, "FPU Underflow" );
			break;
		case EXCEPTION_GUARD_PAGE:
			strcpy( Error, "Attempt to Access Guard Page" );
			break;
		case EXCEPTION_ILLEGAL_INSTRUCTION:
			strcpy( Error, "Attempt to Exceute Illegal Instruction" );
			break;
		case EXCEPTION_IN_PAGE_ERROR:
			strcpy( Error, "Page Fault" );
			break;
		case EXCEPTION_INT_DIVIDE_BY_ZERO:
			strcpy( Error, "Integer Div By Zero" );
			break;
		case EXCEPTION_INT_OVERFLOW:
			strcpy( Error, "Integer Overflow" );
			break;
		case EXCEPTION_INVALID_DISPOSITION:
			strcpy( Error, "Badly Handled Exception" );
			break;
		case EXCEPTION_NONCONTINUABLE_EXCEPTION:
			strcpy( Error, "Exceptioned During an Exception" );
			break;
		case EXCEPTION_PRIV_INSTRUCTION:
			strcpy( Error, "Attempt to Execute Instruction in Wrong Ring" );
			break;
		case EXCEPTION_SINGLE_STEP:
			strcpy( Error, "Processor in Single Step Mode" );
			break;
		case EXCEPTION_STACK_OVERFLOW:
			strcpy( Error, "Stack Overflow" );
			break;
		default:
			wsprintf( Error,"Unknown exception code 0x%08X",er->ExceptionCode );
			break;
	}
	return Error;
}


//
// Change the screen borders to display a given color - can be used to debug locked sections
//
void DebugColor( BYTE red, BYTE green, BYTE blue )
{
	_asm
	{
		push eax
		push edx
		mov dx,3c8h
		xor al,al
		out dx,al
		inc dx
		mov al,red
		out dx,al
		mov al,green
		out dx,al
		mov al,blue
		out dx,al
		pop edx
		pop eax
	}
}


void GetInstalledAudioVideoCodecs( FixedLengthString& Buffer )
{
#if 0	
//
// These checks didn't work on Win2K and were not useful - AndyG
//

	Buffer << "\nAudio CODEC Verification:\n========================\n";
	if( !WindowsNT )
	{
		HKEY hKey;
		char pAudioKey[] = "System\\CurrentControlSet\\Control\\MediaResources\\acm\\msacm.msadpcm";
		char pVideoKey[] = "System\\CurrentControlSet\\Control\\MediaResources\\icm\\vidc.IV50";

		if (RegOpenKey( HKEY_LOCAL_MACHINE, pAudioKey, &hKey ) == ERROR_SUCCESS)
		{
			Buffer << "  " << "Microsoft ADPCM Audio CODEC installed.\n";
			RegCloseKey(hKey);
		}
		else
		{
			Buffer << "  Microsoft ADPCM Audio CODEC missing.\n";
		};

		Buffer << "\n\nVideo CODEC Verification:\n========================\n";

		if (RegOpenKey( HKEY_LOCAL_MACHINE, pVideoKey, &hKey ) == ERROR_SUCCESS)
		{
			Buffer << "  " << "Indeo 5.10 CODEC installed.\n";
			RegCloseKey(hKey);
		}
		else
		{
			Buffer << "  Indeo 5.10 CODEC missing.\n";
		};
	}
	else
	{
		HKEY hKey;
		char pCodecKey[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\drivers.desc";
		char pSubKeyValue[64];
		DWORD dwValueLen = 64;

		if (RegOpenKey( HKEY_LOCAL_MACHINE, pCodecKey, &hKey ) == ERROR_SUCCESS)
		{
			if (RegQueryValueEx(hKey,"msadp32.acm", NULL, NULL, (LPBYTE) &pSubKeyValue, &dwValueLen) == ERROR_SUCCESS)
				Buffer << "  " << pSubKeyValue << " installed.\n";
			else
				Buffer << "  " << "Microsoft ADPCM Audio CODEC missing.\n";
			RegCloseKey(hKey);
		}
		else
		{
			Buffer << "  Error determining if Microsoft ADPCM Audio CODEC is installed.\n";
		};

		Buffer << "\n\nVideo CODEC Verification:\n========================\n";

		if (RegOpenKey( HKEY_LOCAL_MACHINE, pCodecKey, &hKey ) == ERROR_SUCCESS)
		{
			if (RegQueryValueEx(hKey,"ir50_32.dll", NULL, NULL, (LPBYTE) &pSubKeyValue, &dwValueLen) == ERROR_SUCCESS)
				Buffer << "  " << pSubKeyValue << " installed.\n";
			else
				Buffer << "  " << "Indeo 5.10 CODEC missing.\n";
			RegCloseKey(hKey);
		}
		else
		{
			Buffer << "  Error determining if Indeo 5.10 CODEC is installed.\n";
		};
	}
#endif
}

void __stdcall GetGenericErrorInfo(FixedLengthString& buffer)
{
	DisableErrors=1;
	__try
	{
		GetMachineDetails(buffer);
		GetDirectXDetails(buffer);
		GetInstalledAudioVideoCodecs(buffer);
		GetGameDetails(buffer,gos_ErrorMessage);
	}
	__except(1)
	{
		buffer << "\nProblem getting error info\n";
	}
	DisableErrors=0;
}
