#include "pch.hpp"
//===========================================================================//
// File:	 Spew.cpp														 //
// Project:  GameOS					                                         //
// Contents: Spew and Dump functions										 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
//																			 //
//---------------------------------------------------------------------------//
// 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 "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"

bool OnNetwork=false;
extern char* GetGOSVersion();

char SpewList[512]="";
bool NextFatal=0;
void Spew( char* string );
DWORD AllowTimeStamp=1;
extern char* gosGetUserName();
char SpewDefault[256];
DWORD SpewWrites=0;

DWORD FlushLogFile=0;				// Flush SPEW after every line?

extern char PauseSpewOn[256];
extern bool gPauseSpew;

#ifdef _DEBUG
#define SPEW_ALL_TEST 0 // jcem for debugging
#else // !_DEBUG
#define SPEW_ALL_TEST 0 // should always be 0
#endif // _DEBUG
char g_szSpewFile[MAX_PATH]; // jcem for debugging

DWORD OldSurfaces=0;
//
// Spew
// ====
//
// Group	- The 'area' of the error - all GameOS Groups are formatted GameOS_xxxx
//
// Groups defined in GameOS are:
//
// Windows_Message				All windows messages that WinProc sees
// Windows_Functions			(not yet implemented) - Will be all calls to Win32
// GameOS_Scripts
// GameOS_DirectX				General DirectX calls
// GameOS_DirectDraw			Specific DirectX api calls 
// GameOS_Direct3D					   "
// GameOS_DirectInput				   "
// GameOS_DirectSound				   "
// GameOS_DirectPlay				   "
// GameOS_Core					Any "core" functionality of GameOS
//
// Message	- wsprintf error message and parameters - does not allow floats or doubles.
//
void __cdecl InternalFunctionSpew( const char *Group, const char *Message, ... )
{
#if 0
	Group, Message;
#else
    va_list arglist;
    va_start(arglist, Message);
    InternalFunctionSpewV( 0, Group, Message, arglist );
    va_end(arglist);
#endif
}

extern void __cdecl InternalFunctionSpewV( int Flags, const char* Group, const char* Message, char* arglist )
{
	static char ErrorBuffer[1024];		// Buffer to decode line into
	char* Source=SpewList;		// Run through spew list, see if it contains current Group
	bool Output=FALSE;
//
// SPEW is only active in the main thread  (only check in DEBUG/ARMOR for speed)
//
	#ifdef _ARMOR
		if( ThreadId!=GetCurrentThreadId() )
			return;
	#endif
//
// Force SPEW ALL for a frame?
//
	if( gShowALLUseage==1 && !SpewSilence )
		Group=0;
//
// Find if spew group matches one that is being displayed currently
//
	if( 0==Group || *Group==0 )
	{
		Output=TRUE;								// NULL group means display all the time
	}
	else
	{
		while( *Source!=0 )
		{
			const char* Dest=Group;
			
			while( *Source==' ' )					// Skip leading spaces
				Source++;

			if( *Source==0 )
				break;

			while(1)
			{
				char s=*Source;
				char d=*Dest;						// Compare strings until difference found
				if( s>='a' && s<='z' )
					s-=(char)32;
				if( d>='a' && d<='z' )
					d-=(char)32;
				if( !s || !d || s!=d )
					break;
				Source++;
				Dest++;
			}

			if( *Source=='*' || (*Dest==0 && (*Source==' ' || *Source==0)) )
			{
				Output=TRUE;
				break;
			}		

			while( *Source!=' ' && *Source!=0 )		// Skip to next spew keyword
				Source++;
		}
	}
//
// Check for STOP on SPEWs
//
	if( gPauseSpew )
	{
		bool StopNow=false;
		char* Source=PauseSpewOn;					// Run through spew list, see if it contains current Group

		while( *Source!=0 )
		{
			const char* Dest=Group;
			
			while( *Source==' ' )					// Skip leading spaces
				Source++;

			if( *Source==0 )
				break;

			while(1)
			{
				char s=*Source;
				char d=*Dest;						// Compare strings until difference found
				if( s>='a' && s<='z' )
					s-=(char)32;
				if( d>='a' && d<='z' )
					d-=(char)32;
				if( !s || !d || s!=d )
					break;
				Source++;
				Dest++;
			}

			if( *Source=='*' || (*Dest==0 && (*Source==' ' || *Source==0)) )
			{
				StopNow=TRUE;
				break;
			}		

			while( *Source!=' ' && *Source!=0 )		// Skip to next spew keyword
				Source++;
		}

		if( StopNow )
		{
			_vsnprintf( ErrorBuffer, sizeof(ErrorBuffer), Message, arglist );
			ErrorBuffer[sizeof(ErrorBuffer)-1]=0;
			PAUSE(( ErrorBuffer ));
		}
	}
//
// If message group not enabled don't print anything
//
#if SPEW_ALL_TEST
	if (Group && (strcmpi(Group, "GameOS_Memory") == 0))
		return;
#else
	if( SpewSilence || !Output )
		return;
#endif

	if( AllowTimeStamp && Environment.TimeStampSpew && frequency )
	{
		sprintf( ErrorBuffer, "%f -- ",(double)(GetCycles()-StartCount)/frequency );
		vsprintf( ErrorBuffer+strlen(ErrorBuffer), Message, arglist );
	}
	else
	{
		_vsnprintf( ErrorBuffer, sizeof(ErrorBuffer), Message, arglist );
		ErrorBuffer[sizeof(ErrorBuffer)-1]=0;
	}

//
// Check for '+' at end of line (ie: don't add \n)
//
	char chLast = Message[strlen(Message)-1];
	if(Flags & gos_SpewNoAddCR)
	{
		AllowTimeStamp=(chLast=='\n');	// Allow time stamp if '\n' is last char
		Spew( ErrorBuffer );
	}
	else if( chLast!='+' )
	{
		AllowTimeStamp=1;
		Spew( ErrorBuffer );
		Spew( "\n" );
	}
	else
	{
		ErrorBuffer[strlen(ErrorBuffer)-1]=0;
		AllowTimeStamp=0;
		Spew( ErrorBuffer );
	}
}




void InitializeSpew()
{
//
// Desktop DC used in error reporting
//
	if (!DesktopDC)
		DesktopDC=GetDC(NULL);
//
// Get processor speed calculated
//
	InitProcessorSpeed();
//
// This is now done in all builds
//
	unsigned long tempint=31;
	unsigned long type;
	HKEY hKey;
	int result;
//
//
//
	AllowTimeStamp=1;
//
// Get Network login as default spew keyword
//
	char UserName[32+1];
	strncpy( UserName, gosGetUserName(), 31 );
//
// Also retrieve HKEY_CURRENT_USER\Software\Microsoft\Microsoft Games\GameOS\SpewDefault
//
	result=RegOpenKey(HKEY_CURRENT_USER,RegistryKey,&hKey);
	tempint=255;
	SpewDefault[0] = 0;
	if( result==ERROR_SUCCESS )
	{
		RegQueryValueEx( hKey, "SpewDefault", NULL, &type, (LPBYTE)&SpewDefault, &tempint );
		RegCloseKey( hKey );
	}
//
// Now build default spew list of groups to display
//
	strcpy( SpewList, UserName );
	strcat( SpewList, " " );
	strcat( SpewList, SpewDefault );
	strcat( SpewList, " " );
	if( Environment.spew )
		strcat( SpewList, Environment.spew);
	gosASSERT( strlen(SpewList) < sizeof(SpewList) );
//
// Spew to a file, the debugger or outputdebugstring
//
	if( Environment.debugLog && *Environment.debugLog!=0 )
	{
		EnableSpewToFile();
	}
//
//
//
}




//
// Enables spew to a file
//
void EnableSpewToFile()
{
	char* Name=Environment.debugLog;
	bool AppendLogFile=0;
	
	FlushLogFile=0;
	if( Name[0]=='*' )
	{
		Name++;
		FlushLogFile=1;
	}
	if( Name[0]=='+' )
	{
		Name++;
		AppendLogFile=1;
	}
	if( Name[0]=='*' )			// in case of +*
	{
		Name++;
		FlushLogFile=1;
	}

	strcpy(g_szSpewFile, Name);
	if( AppendLogFile )
		hSpewOutput = CreateFile( g_szSpewFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, NULL );
	else
		hSpewOutput = CreateFile( g_szSpewFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, NULL );

	if( hSpewOutput==INVALID_HANDLE_VALUE )
	{
//
// Try to append (x) where x is 1-5, then fail
//
		char drive[_MAX_PATH ];
		char dir[_MAX_DIR ];
		char fname[_MAX_FNAME ];
		char ext[_MAX_EXT ];
		_splitpath( Name, drive, dir, fname, ext );

		for( char Attempt='1'; Attempt<'6'; Attempt++ )
		{
			sprintf( g_szSpewFile, "%s%s%s(%c)%s",drive,dir,fname,Attempt,ext );

			if( AppendLogFile )
				hSpewOutput = CreateFile( g_szSpewFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, NULL );
			else
				hSpewOutput = CreateFile( g_szSpewFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, NULL );

			if( hSpewOutput!=INVALID_HANDLE_VALUE )
				break;
		}

		if( hSpewOutput==INVALID_HANDLE_VALUE )
			PAUSE(( "Unable to open Spew file '%s'",Name ));
	}
//
// move to end of file for append?
//
	if( AppendLogFile )
		SetFilePointer( hSpewOutput, 0, 0, FILE_END );
}


void DisableSpewToFile()
{
	if( hSpewOutput!=INVALID_HANDLE_VALUE )
	{
		CloseHandle(hSpewOutput);
		hSpewOutput=INVALID_HANDLE_VALUE;
	}
}



void TerminateSpew()
{
#ifdef _ARMOR
	HKEY hKey;
	LONG result=RegCreateKey(HKEY_CURRENT_USER,RegistryKey,&hKey);

	result=RegSetValueEx(hKey,"SpewDefault",0,REG_SZ,(LPBYTE)&SpewDefault,255);

	RegCloseKey( hKey );
#endif
	DisableSpewToFile();

	if (DesktopDC)
	{
		ReleaseDC(NULL, DesktopDC);
		DesktopDC=NULL;
	}
}

void Spew( char* string )
{
//
// Show spew in debugger?
//
	if( DebugDisplay==DbgS_Spew )
	{
		SpewToDebugger( string );
	}
//
// Also show on ODS or file?
//
	if( hSpewOutput!=INVALID_HANDLE_VALUE )
	{
		SpewWrites++;											// Show writing in Debugger\File Info mode

		char Buffer[1024];
		DWORD OutPos=0;
		for( int t0=0; t0<strlen(string); t0++ )
		{
			if( string[t0]=='\n' )
			{
				Buffer[OutPos++]=0xd;
				Buffer[OutPos++]=0xa;
			}
			else
			{
				Buffer[OutPos++]=string[t0];
			}
		}

		DWORD numberWritten;

		if(!WriteFile(hSpewOutput,Buffer,OutPos,&numberWritten,NULL))
		{
			PAUSE(( "Unable to write to Spew file" ));
			CloseHandle(hSpewOutput);
			hSpewOutput=INVALID_HANDLE_VALUE;
		}
//
// For real paranoid debugging start debuglog file name with a '*' - this will flush after every line (Much slower though)
//
#if SPEW_ALL_TEST
		DisableSpewToFile();
		hSpewOutput = CreateFile( g_szSpewFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, NULL );
		if( hSpewOutput==INVALID_HANDLE_VALUE )
			PAUSE(( "Unable to open Spew file '%s'",g_szSpewFile ));
		else
			SetFilePointer( hSpewOutput, 0, 0, FILE_END );
#else
		if( FlushLogFile ) {
			if (!WindowsNT )							// Windows NT does not need flushing - it should never lock!
				FlushFileBuffers( hSpewOutput );
		}
#endif
	}
	else
	{
		OutputDebugString( string );
	}
}

















//========================================================================
// functions used by thunking code
//========================================================================
extern BOOL GetUserAndDomainName(LPTSTR UserName, LPDWORD cchUserName, LPTSTR DomainName, LPDWORD cchDomainName );

//
// Check if on network
//
void InitRunLog()
{
	//
	// Get the domain the user is logged in to - Do not prompt for MS internal locations if not on the MS network!
	//
	char szUserName[MAX_PATH];
	char szDomainName[MAX_PATH];
	DWORD dwSize1 = sizeof(szUserName);
	DWORD dwSize2 = sizeof(szDomainName);

	GetUserAndDomainName(szUserName, &dwSize1, szDomainName, &dwSize2);

	if( 0==stricmp(szDomainName, "REDMOND") || 0==stricmp(szDomainName, "NORTHAMERICA") )
		OnNetwork=true;
	else
		OnNetwork=false;
}


char LogBuffer[2048];
HANDLE LoggingThreadID=0;
bool ThreadDone=1;
//
// Use another non blocking thread to write to the log
//
DWORD WINAPI WriteThread( LPVOID ThreadParam )
{
	ThreadParam=ThreadParam;
	HANDLE hFile = CreateFile( "\\\\Cpaas2\\Log\\GameOS\\log.txt", GENERIC_WRITE, 0,NULL, OPEN_ALWAYS, 0, NULL );
	if( hFile!=INVALID_HANDLE_VALUE )
	{
		DWORD Len;
		SetFilePointer( hFile, 0, 0, FILE_END );
		WriteFile( hFile, LogBuffer, strlen(LogBuffer), &Len, NULL );
	    CloseHandle( hFile );
	}
	ThreadDone=1;
	return 0;
}

extern void gosSetThreadName(DWORD dwThreadID, char *szThreadName);

//
// Log stuff on run
//
void LogRun( char* Message )
{
	#ifdef LAB_ONLY
	if( !OnNetwork )
		return;
	if( LoggingThreadID )
	{
		if( !ThreadDone )
			return;
	    CloseHandle( LoggingThreadID );
		LoggingThreadID=0;	
	}
	char NameBuffer[21];
	strncpy( NameBuffer,ApplicationName, 20 );
	memset( &NameBuffer[strlen(ApplicationName)], 32, strlen(ApplicationName)< 20 ? 20-strlen(ApplicationName) : 0 );
	NameBuffer[20]=0;
	char* Build;
	#if defined(_DEBUG)
		Build="DEBUG  ";
	#elif defined(_ARMOR)
		Build="ARMOR  ";
	#elif defined(LAB_ONLY)
		Build="PROFILE";
	#endif
	char UserBuffer[13];
	strncpy( UserBuffer,gosGetUserName(), 12 );
	memset( &UserBuffer[strlen(gosGetUserName())], 32, strlen(gosGetUserName())<12 ? 12-strlen(gosGetUserName()) : 0 );
	UserBuffer[12]=0;
	char TimeBuffer[33];
	strncpy( TimeBuffer,GetTime(), 30 );
	memset( &TimeBuffer[strlen(GetTime())], 32, strlen(GetTime())<30 ? 30-strlen(GetTime()) : 0 );
	TimeBuffer[30]=0;
	if( Message && *Message )
	{
		sprintf( LogBuffer, "%s, %s, %s , %s, %s, %s%c%c",UserBuffer, TimeBuffer, GetGOSVersion(), NameBuffer, Environment.version, Message, 13, 10 );
	}
	else
	{
		sprintf( LogBuffer, "%s, %s, %s , %s, %s%c%c",UserBuffer, TimeBuffer, GetGOSVersion(), NameBuffer, Environment.version, 13, 10 );
	}
	for( int t0=0; t0<strlen(LogBuffer)-2; t0++ )
	{
		if( LogBuffer[t0]==13 || LogBuffer[t0]==10 )
			LogBuffer[t0]=' ';
	}
	DWORD ID;
	ThreadDone=0;
	LoggingThreadID=CreateThread(NULL, 0, WriteThread, 0, 0, &ID );
	gosSetThreadName( ID, "Logging" );
	#endif
}





//========================================================================
//========================================================================
BOOL GetUserAndDomainNameNT(LPTSTR UserName, LPDWORD cchUserName, LPTSTR DomainName, LPDWORD cchDomainName )
{
	HANDLE hToken;

	#define INFO_BUFSIZE 512  // highly unlikely to exceed 512 bytes
	UCHAR InfoBuffer[ INFO_BUFSIZE ];
	DWORD cbInfoBuffer = sizeof(InfoBuffer);
	SID_NAME_USE snu;

	BOOL bSuccess;

	if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
	{
		if(GetLastError() == ERROR_NO_TOKEN)
		{
			//
			// attempt to open the process token, since no thread token
			// exists
			//
			if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
				return FALSE;
		}
		else
		{
			//
			// error trying to get thread token
			//
			return FALSE;
		}
	}

	bSuccess = GetTokenInformation(hToken, TokenUser, InfoBuffer, cbInfoBuffer, &cbInfoBuffer);

	if(!bSuccess)
	{
		if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			//
			// alloc buffer and try GetTokenInformation() again
			//
			CloseHandle(hToken);
			return FALSE;
		}
		else
		{
			//
			// error getting token info
			//
			
			CloseHandle(hToken);
			return FALSE;
		}
	}

	CloseHandle(hToken);

	return LookupAccountSid(NULL, ((PTOKEN_USER)InfoBuffer)->User.Sid, UserName, cchUserName, DomainName, cchDomainName, &snu);
}
