//
// Asura_PC_Main.cpp
//
// Created by KOR - 13/10/2005
// 
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// Asura platform-independent headers
#include "Core/Asura_Core.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Core/Asura_String.h"
#include "Core/Asura_StringBuffer.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Debug_Timers.h"
#include "Debugging/Asura_LogFile.h"
#include "Entity/Asura_Entity.h"
#include "Input/Asura_Mouse.h"

// Asura PC headers
#include "Core/Asura_Platform_MultithreadTypes.h"
#include "Core/Asura_Windows.h"
#include "Network/Steamworks/Asura_PC_SteamworksConfig.h"
#include "Rendering/Asura_Direct3D_Effects_System.h"

// Project headers
#include "Main/Asura_Project_Main.h"

#ifdef ASURA_TOOLS
#include "Core/Asura_Timers.h"
#if defined(ASURA_USE_STREAMING)
#include "Streaming/Asura_Streaming_System.h"
#endif	// defined(ASURA_USE_STREAMING)

// Tools includes
#include "Load&Save/Tools_AsuraFileContents.h"
#include "Load&Save/Tools_BatchProcessing.h"
#include "Load&Save/Tools_OptionsFile.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "PC/OS/Tools_OS.h"
#include "Tools_System.h"

#include <Commdlg.h>

#endif // #ifdef ASURA_TOOLS

//#define ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS

#if defined( ASURA_ENABLE_SEH_HANDLING)
#include "Core/Asura_PC_SEH_Handler.h"
#endif

#ifdef ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
#include <float.h>
#include <fpieee.h>
#include <excpt.h>
#endif//def ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS


// C O N S T A N T S /////////////////////////////////////////////////////////

static const char* szCMD_LINE_LOGFILE = "logfiles\\cmdline.txt";
static const char* szCRASH_TXT_LOGFILE = "logfiles\\crash.txt";
static char szCRASH_XML_LOGFILE[ MAX_PATH] = "logfiles\\crash.xml";

// G L O B A L S /////////////////////////////////////////////////////////////

GUID Project_GUID = { 0x9b281203, 0xacbc, 0x11d3, { 0xb3, 0x9e, 0x0, 0x50, 0x4, 0x61, 0x8e, 0x53 } };
// Kevin Lea - 08/08/2001 15:08:51 - this is used when saving files
extern char* DefaultFileToLoad;

#if !defined(ASURA_PC_FORCE_SUBWINDOW) && defined(_DEBUG)
#define ASURA_PC_FORCE_SUBWINDOW	1
#endif	// !defined(ASURA_PC_FORCE_SUBWINDOW) && defined(_DEBUG)

#if defined(ASURA_PC_FORCE_SUBWINDOW)
static bool g_bForceSubwindow = true;
#else
static bool g_bForceSubwindow = false;
#endif	//defined(ASURA_PC_FORCE_SUBWINDOW)

static bool g_bSubwindowSpecifiedOnCommandLine = false;
static bool g_bFullscreenSpecifiedOnCommandLine = false;

#if defined( ASURA_TOOLS)
static bool g_bXMLLogFiles = false;
#endif
static bool g_bUseCurrentWorkingDirectory	= false;
static char g_szFileToLoad[MAX_PATH] = "";

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

typedef bool ProcessCmdLineTokenFunction (const char* szToken, int& iOffset);

static bool ProcessEarlyCmdLineToken( const char* szToken, int& iOffset );
static bool ProcessCmdLineToken( const char* szToken, int& iOffset );
static bool ProcessCmdLine( const char* szCmdLine, const bool bLogOutput, ProcessCmdLineTokenFunction* pfnProcess );
static bool SetWorkingDirectory();

// JGP Project specific command line
#ifdef ASURA_USE_PROJECT_COMMAND_LINE
extern const bool Project_ProcessCmdLineToken( const char* const szToken );
#endif

// JAT 19/09/07 - FP exception handling is causing lots of crashes on next-gen e.g. D3D effects code so disable it
//#if defined( ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS) || defined( ASURA_ENABLE_SEH_HANDLING)
#if defined( ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS) 
static void Asura_PC_EnableFPExceptions();
#endif
#ifdef ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
static int fpieee_handler( _FPIEEE_RECORD *pieee );
int _matherr(struct _exception *pxExcept);
#endif//def ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS

static void CheckFPFlags(u_int uTestFPFlags)
{
#if defined(ASURA_ASSERT)
	const u_int uCurrentFPFlags = _controlfp(0, 0);
	Asura_Assertf(uCurrentFPFlags == uTestFPFlags, ("Floating point control flags have changed: original 0x%x, current 0x%x", uTestFPFlags, uCurrentFPFlags));
#endif	//defined(ASURA_ASSERT)
}

static int Asura_WinMain_Initialise( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
{
	if(!g_bUseCurrentWorkingDirectory && !SetWorkingDirectory()) return 0;

	#ifdef ASURA_TOOLS
	//Prevent tools from being run from the network (N) drive
	//RAR 20/10/2006
	char szCurrentDirectory[MAX_PATH];
	GetCurrentDirectory(sizeof(szCurrentDirectory),szCurrentDirectory);
	if(!_strnicmp(szCurrentDirectory,"N:",2))
	{
		MessageBox( NULL, "Tools shouldn't be run from the network (N:\\) drive.\nAborting.\n", "Error", MB_OK);
		return 0 ;
	}
	#endif

#if defined( ASURA_TOOLS)
	// DLH - These files should not be cleared if this is a sub process job of a batch export.
	if( true == Tools_BatchProcessing::IsFirstFile())
#endif
	{
		Asura_SimpleLogfileClear( szCMD_LINE_LOGFILE );
		// DLH - Clear the crash files. If a crash happen before this point then it will be added to the existing files
		#if defined( ASURA_ENABLE_SEH_HANDLING)
			#if defined ASURA_LOGFILES_ENABLED
				#if defined( ASURA_TOOLS)
					Asura_SimpleLogfileClearXML(( szCRASH_XML_LOGFILE));
				#endif
			#endif
			Asura_SimpleLogfileClear(( szCRASH_TXT_LOGFILE));
		#endif
    }
	Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Command line:\n%s\n\n", szCmdLine ));

	ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_WinMain_Initialise");

	if( !ProcessCmdLine(szCmdLine, true, ProcessCmdLineToken) )
	{
		static const u_int uBufferSize = 1024;
		char szBuffer[ uBufferSize + 1];
		
		_snprintf( szBuffer, uBufferSize, "Failed to process Command Line correctly.\nCheck the Log files for details.\n\n%s\n", szCmdLine);
		
		MessageBox( NULL, szBuffer, "Error", MB_OK);
		return 0;
	}

    if( *g_szFileToLoad )
    {
        DefaultFileToLoad = g_szFileToLoad;

#ifdef ASURA_TOOLS
		//If there is a command line then go straight into tools
        Asura_Project::SetToolsRequested(true);
#endif
	}

#ifdef ASURA_TOOLS
	bool bBatchMode = false;
	if(DefaultFileToLoad)
	{
		if(Tools_BatchProcessing::IsBatchFile(DefaultFileToLoad))
		{
			if(!Tools_BatchProcessing::Initialise(DefaultFileToLoad))
			{
				return 0;
			}
			g_bForceSubwindow = true;
			bBatchMode = true;
			
			Asura_Assertf( false, ("BatchInfo: Command Line '%s'", szCmdLine));
		}
	}
#endif

	bool bSubwindow = g_bForceSubwindow;

#if defined(ASURA_TOOLS) && !defined(ASURA_PC_FORCE_SUBWINDOW)
	if(!g_bSubwindowSpecifiedOnCommandLine && !g_bFullscreenSpecifiedOnCommandLine)
	{
		//If the windowed state hasn't been explicitly set , then try reading from AsuraTools.ini
		Tools_OptionsFile::LoadSubwindowFlag(bSubwindow);
	}	
#endif

	Asura_Windows::SetCommandLineWindowStateFlags(g_bSubwindowSpecifiedOnCommandLine, g_bFullscreenSpecifiedOnCommandLine);

	Asura_Windows::Initialise(hInstance,hPrevInstance,szCmdLine,nCmdShow,(void*)&Project_GUID, !bSubwindow);
	
#ifdef ASURA_TOOLS
	if( true == bBatchMode)
	{
		Asura_Windows::SetPauseWhileMinimised( false);
	}
#endif

	// KJL 26/07/00 11:01:43 - fire up Asura subsystems
	Asura_Core::EarlyInitialise();
	Asura_Project::EarlyInitialise();
	Asura_Core::LateInitialise();
	Asura_Project::LateInitialise();

#if !defined ASURA_TOOLS && defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	//DF - Common.asr should have been loaded by this point ( done in LateInitialise, so tell the material
	// system that the common materials have been loaded now
	Asura_MaterialResponse_System::SetGlobalResetPointHit();
#endif

	return( 1);
}
#if defined( ASURA_ENABLE_SEH_HANDLING)
	#if defined( ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS)
		#error You cannot use ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS and ASURA_ENABLE_SEH_HANDLING at the same time.
	#endif
#endif

#ifdef ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
	#if defined( ASURA_ENABLE_SEH_HANDLING)
		#error You cannot use ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS and ASURA_ENABLE_SEH_HANDLING at the same time.
	#endif
#endif // def ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS

static int Asura_WinMain_Main()
{
	int iError = 0;
	
#if defined(ASURA_ENABLE_SEH_HANDLING) || defined(ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS)
    __try 
	{
#endif

#ifdef ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
	Asura_PC_EnableFPExceptions();
#endif

	const u_int uFPFlags = _controlfp(0, 0);

#ifdef ASURA_TOOLS
#if defined(ASURA_USE_STREAMING)
	static const u_int	auNumZoneDataIndices[Asura_Streaming_Zone::MAX_DATA_STATES]	=
	{
		ASURA_STREAMING_MAX_RESIDENT_LOW_DETAIL_ZONES,
	};

	// Allocate the zone-data used by the streaming-system...
	Asura_Streaming_System::AllocateZones(auNumZoneDataIndices, NULL);
#endif	// defined(ASURA_USE_STREAMING)

#ifdef ASURA_TOOLS
	//add the name of the file being loaded to the windows title
	if(DefaultFileToLoad)
	{
		char acTitle[200];
		Asura_PrintfToBfr(acTitle,"%s - %s",sizeof(acTitle),true,Tools_FileSystem::StripPath(DefaultFileToLoad),ASURA_PC_APPLICATION_TITLE);
		Asura_Windows::SetDefaultWindowTitle(acTitle);
		Asura_Windows::SetWindowTitle(acTitle);
	}
#endif

	Tools_SaveAsura::InitialiseTemplateLoadingIfNeeded();

    //If going into tools , need to determine what type of file is being dealt with
	AsuraFileType eFileType = AsuraFile_Environment;//suitable default in case tools wasn't requested
	if(Asura_Project::GetToolsRequested())
	{
		if(!Tools_BatchProcessing::IsBatch())
		{
			char* pcExtension = strrchr(DefaultFileToLoad,'.');

			//Check to see if one of the ancillary files was selected
			if(stricmp(pcExtension,".asr"))
			{
				//set the default mode based on the extension.
				Tools_System::SetDefaultMode(pcExtension);
				//replace the extension
				strcpy(pcExtension,".asr");
			}
		}
		
		eFileType = Tools_AsuraFileContents::GetAsuraFileType(DefaultFileToLoad);

	}

	#ifdef ASURA_TOOLS
	Asura_Windows::LateInitialise();
	#endif
	
	if(eFileType == AsuraFile_Error)
	{
		Asura_Assertf(0,("Failed to load '%s' to determine the type of file",DefaultFileToLoad));
		if(Tools_BatchProcessing::IsBatch())
		{
			//This assert won't be visible for batch processes , so add a message to the logfile instead , seeing
			//as this is a fairly fatal error.
			//RAR 10/02/2006
            Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "ERROR: Failed to load %s\n", DefaultFileToLoad ));
            iError = 1;
			
		}
	}
	//these are the acceptable non-environment file types. RAR 07/12/2005
	else if(eFileType == AsuraFile_Shapes || 
			eFileType == AsuraFile_Skin || 
			eFileType == AsuraFile_Fragment || 
			eFileType == AsuraFile_AnimFragment ||
			eFileType == AsuraFile_TextureTiler )
	{
		//For non-environment files we don't want to bother setting up the rest of the project.
		//Just run a tools main loop here instead.
		extern Asura_String EnvironmentFilename;
		EnvironmentFilename = DefaultFileToLoad;
		Tools_System::Initialise(true);

		do
		{
			if (!Tools_System::This->HandleTimerInMainLoop())
			{
				Asura_Timers::HandlePrimaryTimer();
			}

			CheckFPFlags(uFPFlags);
			Asura_Windows::CheckForWindowsMessages();
		}
		while(Tools_System::MainLoop());
	 	
		Tools_System::Shutdown();
	}
	else if(eFileType != AsuraFile_Environment)
	{
		//dealing with some file type that can't be loaded directly into tools for editing. RAR 07/12/2005
		Asura_Assertf(0,("'%s' doesn't appear to be a type of asura file that can be loaded into tools.",DefaultFileToLoad));
	}
	else
#endif // def ASURA_TOOLS
	{
		do
		{
			CheckFPFlags(uFPFlags);
			Asura_Windows::CheckForWindowsMessages();
		}
		while(Asura_Project::MainLoop());
	}

#if defined( ASURA_ENABLE_SEH_HANDLING)
	} 
	__except ( Asura_PC_SEH_Handler::ProcessException( GetExceptionInformation()))
	{
		// code that gets control if fpieee_handler returns
		// EXCEPTION_EXECUTE_HANDLER goes here
#if defined ASURA_LOGFILES_ENABLED || defined ASURA_ASSERT
	#if defined( ASURA_TOOLS)
		if( true == g_bXMLLogFiles)
		{
			const char* szCrashReportXML = Asura_PC_SEH_Handler::GetReportXML();
			Asura_SimpleLogfileOutputXML(( szCRASH_XML_LOGFILE, szCrashReportXML));
		}
	#endif
		const char* szCrashReportTXT = Asura_PC_SEH_Handler::GetReportTXT();
#endif
		Asura_SimpleLogfileOutput(( szCRASH_TXT_LOGFILE, szCrashReportTXT));

#if defined( ASURA_TOOLS)
		if( false == Tools_BatchProcessing::IsBatch())
#endif
		{
			// (MH - 29.09.2006) ensure assert is seen.
			AsuraAssert_SetIgnoreAll(false);
			Asura_Assertf(false, ("unhandled exception\n\n%s\n\n", szCrashReportTXT));
		}

#ifdef ASURA_MEMORYMANAGEMENT_ENABLED
		//Disable memory leak reporting so we don't get a huge file with all current
		//memory allocations being generated... (MH - 28.09.2006) 
		Asura_MemoryManagement_System::SetReportMemoryLeaksOnShutdown(false);
#endif

		iError = 1;
	}
#endif
#ifdef ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
	// Deal with FP exception
	} 
	__except ( _fpieee_flt( GetExceptionCode(),
                GetExceptionInformation(),
                fpieee_handler ) )
	{
		// code that gets control if fpieee_handler returns
		// EXCEPTION_EXECUTE_HANDLER goes here
		Asura_Assert(false, "unhandled floating point exception");

		iError = 1;
	}
#endif // def ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS

#if defined( ASURA_TOOLS)
	if( true == Tools_BatchProcessing::IsBatch())
	{
		if( true == Tools_BatchProcessing::IsAborted())
		{
   	    	Asura_SimpleLogfileOutput(( TOOLS_BATCH_PROCESS_LOGFILE, "Stopping - batch was aborted.\n" ));
			iError = TOOLS_EXIT_CODE_BATCH_ABORTED;
		}
	}
#endif


	return( iError);
}

int APIENTRY Asura_WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
{
	int iError = 0;
	
	if( !ProcessCmdLine(szCmdLine, false, ProcessEarlyCmdLineToken) )
	{
		MessageBox( NULL, "Failed to process Command Line correctly.\nToo early to generate a Log file.\n", "Error", MB_OK);
		return 0;
	}
	
	if( 0 == Asura_WinMain_Initialise( hInstance, hPrevInstance, szCmdLine, nCmdShow))
	{
		return( 0);
	}
	
	{
		ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_WinMain_Main");
		
		iError = Asura_WinMain_Main();
	}

	{
		ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_WinMain_Shutdown");
		
		Asura_Entity_System::Shutdown();
		Asura_Project::Shutdown();

		Asura_Core::Shutdown();
		Asura_Windows::Shutdown();
	}

#ifdef ASURA_TOOLS
	if( TOOLS_EXIT_CODE_BATCH_ABORTED != iError && Tools_BatchProcessing::IsBatch())
	{
		ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_WinMain_Batch");
		
		iError = Tools_BatchProcessing::NextFile();
	}
#endif // def ASURA_TOOLS

	return iError;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
{
	Asura_PC_SetThreadName( "MainThread" );

	int iError = 0;

#if( ASURA_PC_USE_SINGLEINSTANCEMUTEX == 1 )
	// Prevent more than one copy of the game running at once
	CreateMutex( NULL, FALSE, ASURA_PC_APPLICATION_NAME "SingleInstanceMutex" );
	if( GetLastError() != ERROR_SUCCESS )
	{
		// If the mutex has previously been created, GetLastError() returns ERROR_ALREADY_EXISTS
		return 0;
	}
#endif // ( ASURA_PC_USE_SINGLEINSTANCEMUTEX == 1 )

#if defined( ASURA_ENABLE_SEH_HANDLING)
    __try 
	{
		//Asura_PC_EnableFPExceptions();
#endif
		
		iError = Asura_WinMain( hInstance, hPrevInstance, szCmdLine, nCmdShow);
		
#if defined( ASURA_ENABLE_SEH_HANDLING)
	} 
	__except ( Asura_PC_SEH_Handler::ProcessException( GetExceptionInformation()))
	{
		// code that gets control if fpieee_handler returns
		// EXCEPTION_EXECUTE_HANDLER goes here
#if defined ASURA_LOGFILES_ENABLED || defined ASURA_ASSERT
	#if defined( ASURA_TOOLS)
		if( true == g_bXMLLogFiles)
		{
			const char* szCrashReportXML = Asura_PC_SEH_Handler::GetReportXML();
			Asura_SimpleLogfileOutputXML(( szCRASH_XML_LOGFILE, szCrashReportXML));
		}
	#endif
		const char* szCrashReportTXT = Asura_PC_SEH_Handler::GetReportTXT();
#endif
		Asura_SimpleLogfileOutput(( szCRASH_TXT_LOGFILE, szCrashReportTXT));
#if defined( ASURA_TOOLS)
		if( false == Tools_BatchProcessing::IsBatch())
#endif
		{
			Asura_Assertf(false, ("unhandled exception\n\n%s\n\n", szCrashReportTXT));
		}
		
		iError = 1;
	}
#endif
	
	Asura_Assertf( 0 == iError, ("Process finishing with Error Value %d", iError));
	return iError;

}

static bool ProcessEarlyCmdLineToken( const char* szToken, int& iOffset )
{

    // Options start with a '-' character. Assume anything else is the file
    // to be loaded.
    if( *szToken == '-' )
    {
		static const char* pszXMLLogFile = "-xmllogfile";
		static const u_int uXMLLogFileSize = 11;
		
        if(!strnicmp(szToken, "-usecwd", 7))
		{
			g_bUseCurrentWorkingDirectory	= true;
		}
#if defined( ASURA_TOOLS)
		else if(!strnicmp(szToken, "-batch", 6))
		{
			AsuraAssert_LogClearLogOnNextAssert( false);
			Tools_BatchProcessing::SetFirstFile( false);
#if defined( ASURA_CPU_PROFILE_SCOPED_TIMER_ENABLE)
			ASURA_CPU_PROFILE_SCOPED_TIMER_ALLOW_FILE_RESET( false);
#endif
		}
        else if(!strnicmp(szToken, pszXMLLogFile, uXMLLogFileSize))
		{
			g_bXMLLogFiles = true;
			AsuraAssert_LogXML( true);
#if defined( ASURA_CPU_PROFILE_SCOPED_TIMER_ENABLE)
			ASURA_CPU_PROFILE_SCOPED_TIMER_ALLOW_XML_FILE( true);
#endif
			if(!strnicmp( szToken + uXMLLogFileSize, "=", 1))
			{
				const char* pszXMLLogParam = szToken + uXMLLogFileSize + 1;
				{
					Asura_StringBuffer< MAX_PATH> xFilenameAssert( "%s.DebugTimer.xml", pszXMLLogParam);
					ASURA_CPU_PROFILE_SCOPED_TIMER_LOG_XML_FILENAME( xFilenameAssert.GetString());
				}
				{
					Asura_StringBuffer< MAX_PATH> xFilenameAssert( "%s.Assert.xml", pszXMLLogParam);
					AsuraAssert_LogXMLFilename( xFilenameAssert.GetString());
				}
				{
					Asura_StringBuffer< MAX_PATH> xFilenameAssert( "%s.Crash.xml", pszXMLLogParam);
					strncpy( szCRASH_XML_LOGFILE, xFilenameAssert.GetString(), MAX_PATH);
				}
			}
		}
#endif
#if defined( ASURA_TOOLS)
		else if(!strnicmp(szToken, "-consolemode", 12))
		{
			Tools_BatchProcessing::SetConsoleMode( true);
		}
#endif
    }

    return true;
}

static bool ProcessCmdLineToken( const char* szToken, int& iOffset )
{

	enum NextExpectedToken 
	{
		NEXT_EXPECTED_TOKEN_UNKNOWN,
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
		NEXT_EXPECTED_TOKEN_STEAM_WORKS_SERVER_INFO,
		NEXT_EXPECTED_TOKEN_STEAM_WORKS_CLIENT_INFO,
#endif // ASURA_PC_SUPPORT_STEAMWORKS		
		NEXT_EXPECTED_TOKEN_LOGDIR,
	};

	static NextExpectedToken eNextExpectedToken = NEXT_EXPECTED_TOKEN_UNKNOWN;
	static bool bSelectedManually = false;

	// if last token parameter set expectation for the next token 
	// use that expectation
	if (eNextExpectedToken != NEXT_EXPECTED_TOKEN_UNKNOWN)
	{
		NextExpectedToken eCurrExpectedToken = eNextExpectedToken;
		eNextExpectedToken = NEXT_EXPECTED_TOKEN_UNKNOWN;

		switch (eCurrExpectedToken)
		{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
		case NEXT_EXPECTED_TOKEN_STEAM_WORKS_SERVER_INFO:
			return Asura_PC_SteamworksConfig::SetServer(szToken);
		case NEXT_EXPECTED_TOKEN_STEAM_WORKS_CLIENT_INFO:
			return Asura_PC_SteamworksConfig::SetClient(szToken);
#endif // ASURA_PC_SUPPORT_STEAMWORKS
		case NEXT_EXPECTED_TOKEN_LOGDIR:
			Asura_OverrideLogfilesDir(szToken, false);
			return true;
		default:
			Asura_Assert(false, "Uknown expected token");
			return false;			
		}
	}

	if( *szToken == '+' )
	{
#ifdef ASURA_PC_SUPPORT_STEAMWORKS
		if( !stricmp(szToken, "+connect") )
		{
			eNextExpectedToken = NEXT_EXPECTED_TOKEN_STEAM_WORKS_CLIENT_INFO;
			return true;
		} 

		if( !stricmp(szToken, "+host") )
		{
			eNextExpectedToken = NEXT_EXPECTED_TOKEN_STEAM_WORKS_SERVER_INFO;
			return true;
		}
#endif // ASURA_PC_SUPPORT_STEAMWORKS		
	}
	// Options start with a '-' character. Assume anything else is the file
	// to be loaded.
	if( *szToken == '-' )
	{
		if( !stricmp(szToken, "-logdir") )
		{
			eNextExpectedToken = NEXT_EXPECTED_TOKEN_LOGDIR;
			return true;
		}
		else if( !stricmp(szToken, "-subwindow") )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Using windowed mode.\n" ));
			g_bForceSubwindow = true;
			g_bSubwindowSpecifiedOnCommandLine = true;
		}
		else if( !stricmp(szToken, "-fullscreen") )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Using fullscreen mode.\n" ));
			g_bForceSubwindow = false;
			g_bFullscreenSpecifiedOnCommandLine = true;
		}
		else if( !stricmp(szToken, "-winpos") )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Found winpos parameter.\n" ));

			const char* pChar = szToken + strlen(szToken) + 1;

			int iScreenPosX, iScreenPosY;

			sscanf(pChar, "%d,%d", &iScreenPosX, &iScreenPosY);

			#ifdef ASURA_TOOLS
			Asura_Windows::SetCommandLineWindowPosition(iScreenPosX, iScreenPosY);
			#else
			Asura_Windows::SetWindowedXPos(iScreenPosX);
			Asura_Windows::SetWindowedYPos(iScreenPosY);
			#endif

			iOffset = strlen(pChar);
		}
		else if( !stricmp(szToken, "-winsize") )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Found winsize parameter.\n" ));

			const char* pChar = szToken + strlen(szToken) + 1;

			int iScreenSizeX, iScreenSizeY;

			sscanf(pChar, "%d,%d", &iScreenSizeX, &iScreenSizeY);

			Asura_Windows::SetCommandLineWindowSize(iScreenSizeX, iScreenSizeY);

			iOffset = strlen(pChar);
		}
		else if( !strnicmp(szToken, "-batch", 6) )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Found batch parameters.\n" ));
			// Otherwise ignore - dealt with inside batch processing code.
		}
		else if( !strnicmp(szToken, "-noassert", 9) )
		{
#if 1 && defined( ASURA_IGNORE_NOASSERT)
			Asura_Assert( false, "-noassert has been disabled" );
#else
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Disabling asserts.\n" ));
			AsuraAssert_SetIgnoreAll(true);
#endif // #ifndef ASURA_IGNORE_NOASSERT
		}
		// JAT 22/02/07 on next-gen support -loadshaders command by default the shaders are loaded as resources i.e. compiled fxo files from the EXE
#if ASURA_LOAD_EFFECT_FILES == 1 && !defined( ASURA_NO_LOAD_EFFECTS_FILES_FROM_FILES)
		else if ( !strnicmp( szToken, "-loadshaders", 12 ) )
		{
			const char* const szAsuraRoot = getenv( "ASURA_ROOT" );
			Asura_Assert( szAsuraRoot != NULL, "Have to set ASURA_ROOT environment variable to use -loadshaders command line parameter" );
			if ( szAsuraRoot != NULL )
			{
				Asura_String xShadersPath( szAsuraRoot );
				xShadersPath += "\\Asura\\PC\\";
				Asura_Direct3D_Effects_System::SetShadersPath( xShadersPath.GetString() );
			}

#if defined(ASURA_PROJECT_SHADER_PATH)
			const char* const szProjectShadersPath = ASURA_PROJECT_SHADER_PATH;
			if (( szProjectShadersPath ) && ( szProjectShadersPath[0] ))
			{
				Asura_String xProjectShadersPath( szAsuraRoot );
				xProjectShadersPath += szProjectShadersPath;
				Asura_Direct3D_Effect::SetProjectShadersPath( xProjectShadersPath.GetString() );
			}
#endif
		}
#endif // #ifdef ASURA_TARGET_SM3
#ifdef ASURA_TOOLS
		else if( !strnicmp(szToken, "-straighttogame", 15) )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Straight-to-game specified.\n" )) 
				Tools_System::RequestAutoQuitToGame();
		}
        else if( !strnicmp( szToken, "-xmllogfile", 11))
		{
			g_bXMLLogFiles = true;
		}
		else if( !strnicmp(szToken, "-queryfile", 10) )
		{
			OPENFILENAME xOFN;

			char szFilename[MAX_PATH] = "";
			ZeroMemory(& xOFN,sizeof(OPENFILENAME));
			xOFN.lStructSize = sizeof(OPENFILENAME); 
			xOFN.hwndOwner = Asura_Windows::GetWindowHandle(0);
			xOFN.lpstrFilter = "ASR Files (*.asr)\0*.asr\0All Files (*.*)\0*.*\0\0";
			xOFN.lpstrFile = szFilename;
			xOFN.nMaxFile = sizeof(szFilename);
			xOFN.lpstrTitle = "Load ASR file";
			xOFN.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_NOCHANGEDIR;
			xOFN.nFilterIndex = 0;
			xOFN.lpstrDefExt = NULL;
			xOFN.lpfnHook = NULL;
			xOFN.lpstrInitialDir = NULL;

			if ( ::GetOpenFileName(&xOFN) )
			{
				strncpy(g_szFileToLoad, szFilename, MAX_PATH);
				bSelectedManually = true;
			}
		}
#endif // ASURA_TOOLS
#if defined( ASURA_CPU_PROFILE_SCOPED_TIMER_ENABLE)
		else if(!strnicmp(szToken, "-logdebugtimer", 15))
		{
		}
#endif
#if defined( ASURA_TOOLS)
		else if(!strnicmp(szToken, "-consolemode", 12))
		{
			Tools_BatchProcessing::SetConsoleMode( true);
		}
#endif
		else if( !strnicmp(szToken, "-usecwd", 7) )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Using current working-directory.\n" ));
		}
        else if( !stricmp(szToken, "-quietupdate") )
        {
            // Just ignore - this is processed by the tools update executable.
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Ignoring update executable's -quietupdate argument.\n" ));
        }
#ifdef _DEBUG
		else if ( !stricmp(szToken, "-showcursor") )
		{
			Asura_Mouse::SetExclusive( false );
			Asura_Mouse::SetUseWindowsCursor( true );
			Asura_Mouse::SetRestrictWindowsCursorToWindow( false );
		}
#endif
		// JGP Project specific command line
#ifdef ASURA_USE_PROJECT_COMMAND_LINE
		else if ( !Project_ProcessCmdLineToken( szToken ) )
#else
		else
#endif
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "ERROR: Unrecognised command line option:\n%s\n", szToken ));
			return false;
		}
	}
	else if ( !bSelectedManually )
	{
		// We don't expect to encounter two files to load in the command line.
		if( *g_szFileToLoad  )
		{
			Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "ERROR: File to load specified twice in command line.\n(First '%s' then '%s'.)\n", g_szFileToLoad, szToken ));
			return false;
		}

		Asura_PrintfToBfr( g_szFileToLoad, "%s", MAX_PATH, true, szToken );
		Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Found file to load: '%s'.\n", szToken ));
	}

	return true;
}

static bool ProcessCmdLine( const char* szCmdLine, const bool bLogOutput, ProcessCmdLineTokenFunction* pfnProcess )
{
    bool bCmdLineOK = true;

	if(bLogOutput)
	{
		Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Processing command line...\n" ));
	}

	// Make a copy of the command line for our own evil purposes.
    char* szWorkspace = Asura_StringUtil::Duplicate( szCmdLine );

    bool bInQuotes = false;
    char* szCurToken = szWorkspace;
    for( char* szWorkspaceIt = szWorkspace; bCmdLineOK; ++szWorkspaceIt )
    {
        const char cCharToProcess = *szWorkspaceIt;

        switch( cCharToProcess )
        {
            case '"':
                bInQuotes = !bInQuotes;
                break;

            case ' ':
            case '\t':
            case '\0':
                if( !bInQuotes )
                {
                    // Clean up the token.
                    *szWorkspaceIt = '\0';
                    szCurToken = Asura_StringUtil::StripWhitespace( szCurToken, Asura_StringUtil::STRIP_BOTH_ENDS );
                    szCurToken = Asura_StringUtil::StripQuotes( szCurToken );
                    szCurToken = Asura_StringUtil::StripWhitespace( szCurToken, Asura_StringUtil::STRIP_BOTH_ENDS );

					int iOffset = 0;

                    // And process it, if there's anything left.
                    if( szCurToken && *szCurToken )
                    {
                        bCmdLineOK = pfnProcess( szCurToken, iOffset );
                    }

                    szCurToken = szWorkspaceIt + 1 + iOffset;
                }
                break;

            default:
                break;
        }

        // Additional handling for end-of-string case.
        if( '\0' == cCharToProcess )
        {
            if( bInQuotes )
            {
				if(bLogOutput)
				{
		            Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "ERROR: Mismatched quotes.\n" ));
				}
                bCmdLineOK = false;
            }
            break;
        }
    }

	delete[] szWorkspace;

    if( bCmdLineOK && bLogOutput )
    {
        Asura_SimpleLogfileOutput(( szCMD_LINE_LOGFILE, "Ok.\n\n" ));
    }

	return bCmdLineOK;
}

bool SetWorkingDirectory()
{
    // Set our working directory to match the executable's root folder. Do
    // this first so that all logfiles end up in the right place.
    {
        char szExeDir[MAX_PATH];

        const char* szFullCmdLine = Asura_StringUtil::StripWhitespace( GetCommandLine(), Asura_StringUtil::STRIP_LEADING, NULL );

        const char* szPathEnd;
        if( *szFullCmdLine == '"' )
        {
            // Delimited by end quote.
            ++szFullCmdLine;
            szPathEnd = strchr( szFullCmdLine, '"' );
        }
        else
        {
            // Delimited by any whitespace character, or the end of the
            // string.
            for( szPathEnd = szFullCmdLine; *szPathEnd; ++szPathEnd )
            {
                if( isspace(*szPathEnd) ) { break; }
            }
        }

        if( szPathEnd && szPathEnd > szFullCmdLine )
        {
            size_t uLen = szPathEnd - szFullCmdLine;
            strncpy( szExeDir, szFullCmdLine, uLen );
            *(szExeDir + uLen) = '\0';

            char* szLastSlash = strrchr( szExeDir, '\\' );
            if( !szLastSlash )
            {
                szLastSlash = strrchr( szExeDir, '/' );
            }

            if( szLastSlash && szLastSlash > szExeDir )
            {
                *szLastSlash = '\0';

                if( !SetCurrentDirectory(szExeDir) )
                {
#ifdef ASURA_TOOLS
					#if defined ASURA_ASSERT
						const char* szError =
					#endif
						Tools_OS::GetLastSystemError();

					Asura_Assertf( false, ("Error while attempting to set working directory to '%s': %s", szExeDir, szError) );

                    // Not sure there's much point in continuing...
                    return false;
#endif
                }
            }
        }
    }
	// Success
	return true;
}

///////////////////////////////
// IEEE FP exception handler //
///////////////////////////////
#if defined( ASURA_ENABLE_SEH_HANDLING)
void Asura_PC_EnableFPExceptions()
{
	// DLH - This really upsets the VS2005 debugger with nVidia Drivers
	if( !IsDebuggerPresent())
	{
		int i = _controlfp(0,0);
		i &= ~(EM_ZERODIVIDE|EM_OVERFLOW|EM_INVALID /* |EM_UNDERFLOW */);
		_controlfp(i, MCW_EM);
	}
}
#endif

#ifdef ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
void Asura_PC_EnableFPExceptions()
{
    int i = _controlfp(0,0);
    i &= ~(EM_ZERODIVIDE|EM_OVERFLOW|EM_INVALID /* |EM_UNDERFLOW */);
    _controlfp(i, MCW_EM);
}

int fpieee_handler( _FPIEEE_RECORD *pieee )
{
	// user-defined ieee trap handler routine:
	// there is one handler for all 
	// IEEE exceptions

	// Assume the user wants all invalid 
	// operations to return 0.

	//if (pieee->Result.Format == _FpFormatFp32)
	{
		/*
		Kim's instructions for finding the point at which the exception happened :

		1)Go up the stack once into _fpieee_flt(). You won't have the source for this, but don't worry...
		2)...all we want is the local variables, so open a watch and get the value of p->ContextRecord->Eip
		3) Go to the disassembly, and enter the address you got from the previous step.
		4) Right-click the instruction and select Go To Source Code
		5) Breakpoint the next line.
		6) Resume execution. You now have the actual stack at (well, just after) the point of the exception.

		RAR 17/01/2006 - At step 4 it may be easier to put a breakpoint on the next assembly instruction instead , 
		and then go up the stack when the breakpoint is reached.

		*/
		
		if (pieee->Cause.InvalidOperation)
		{
			Asura_Assert(false, "floating point invalid operation");
			pieee->Result.Value.Fp32Value = 0.0F;
			return EXCEPTION_CONTINUE_EXECUTION;
		}
		if (pieee->Cause.Overflow)
		{
			Asura_Assert(false, "floating point overflow");
			pieee->Result.Value.Fp32Value = 0.0F;
			return EXCEPTION_CONTINUE_EXECUTION;
		}
		if (pieee->Cause.ZeroDivide)
		{
			Asura_Assert(false, "floating point divide by zero");
			pieee->Result.Value.Fp32Value = 0.0F;
			return EXCEPTION_CONTINUE_EXECUTION;
		}
		// KOR - 26/10/2005 - underflow can be quite common, and the exception handler sometimes munges the stack,
		// so I'm turned underflow catching off. If we start finding denormalised numbers are a problem, we should
		// turn this back on (with the assert) and actually fix underflows.
/*		if (pieee->Cause.Underflow)
		{
			// AJG - 20/10/05 - Just returning 0 rather than asserting, since this happens often enough that it's not really a useful assert.
			pieee->Result.Value.Fp32Value = 0.0F;
			return EXCEPTION_CONTINUE_EXECUTION;
		}*/
	}

	return EXCEPTION_EXECUTE_HANDLER;
}

// KOR - 13/10/2005 - useful auxiliary to floating point exceptions
int _matherr(struct _exception *pxExcept)
{
	if(pxExcept)
	{
		switch(pxExcept->type)
		{
			case _DOMAIN:		Asura_Assert(0, "Maths library error: Argument domain error."); break;
			case _SING:			Asura_Assert(0, "Maths library error: Argument singularity."); break;
			case _OVERFLOW:		Asura_Assert(0, "Maths library error: Overflow range error."); break;
			case _PLOSS:		Asura_Assert(0, "Maths library error: Partial loss of significance."); break;
			case _TLOSS:		Asura_Assert(0, "Maths library error: Total loss of significance."); break;
			case _UNDERFLOW:	Asura_Assert(0, "Maths library error: The result is too small to be represented. (This condition is not currently supported.)"); break;
		}
	}
	return 0;
}
#endif // def ASURA_ENABLE_FLOATING_POINT_EXCEPTIONS
