/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "win_local.h"
#include "net.h"
#include "q_field.h"
#include "q_list.h"
#include "prompt.h"
#include <mmsystem.h>
#ifdef DEDICATED_ONLY
#include <winsvc.h>
#endif
#ifdef USE_DBGHELP
#include <dbghelp.h>
#endif
#include "version.h"

#define USE_SLEEP

#ifndef DEDICATED_ONLY
#if !( defined REF_HARD_LINKED ) && !( defined UI_HARD_LINKED )
#define USE_MODULES
void Sys_ListModules_f( void );
#endif
#endif

sysAPI_t	sys;

static cvar_t		*sys_debugdir;

#ifndef _WIN32_WCE
#define MAX_CONSOLE_INPUT_EVENTS	16

static cvar_t		*sys_viewlog;

static HANDLE		hinput = INVALID_HANDLE_VALUE;
static HANDLE		houtput = INVALID_HANDLE_VALUE;

static commandPrompt_t	sys_con;
static int				sys_hidden;
static CONSOLE_SCREEN_BUFFER_INFO	sbinfo;
static qboolean			gotConsole;
static qboolean			errorEntered;
static qboolean			shouldExit;
#endif /* !_WIN32_WCE */

#ifdef DEDICATED_ONLY
static SERVICE_STATUS_HANDLE	statusHandle;
static SERVICE_STATUS			serviceStatus;
#endif

#ifdef USE_SLEEP
static HANDLE		hSocketEvent;
#endif

HINSTANCE	hGlobalInstance;

static qboolean winnt;

static char		currentDirectory[MAX_OSPATH];

#define SystemTimeToQTime( qtime, systime )			\
	( qtime )->tm_sec = ( systime )->wSecond;		\
	( qtime )->tm_min = ( systime )->wMinute;		\
	( qtime )->tm_hour = ( systime )->wHour;		\
	( qtime )->tm_mday = ( systime )->wDay;			\
	( qtime )->tm_mon = ( systime )->wMonth;		\
	( qtime )->tm_year = ( systime )->wYear;		\
	( qtime )->tm_wday = ( systime )->wDayOfWeek;	\
	( qtime )->tm_yday = -1;						\
	( qtime )->tm_isdst = -1;



/*
===============================================================================

SYSTEM IO

===============================================================================
*/

void Sys_DebugBreak( void ) {
	DebugBreak();
}

/*
================
Sys_Printf
================
*/
void Sys_Printf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );

    Sys_ConsoleOutput( msg );
}

/*
================
Sys_Error
================
*/
void Sys_Error( const char *error, ... ) {
	va_list		argptr;
	char		text[MAXPRINTMSG];

	va_start( argptr, error );
	Q_vsnprintf( text, sizeof( text ), error, argptr );
	va_end( argptr );

#ifndef _WIN32_WCE
	errorEntered = qtrue;
#endif

	Sys_Printf( S_COLOR_RED "********************\n"
                            "FATAL: %s\n"
                            "********************\n", text );
#ifdef DEDICATED_ONLY
	if( !statusHandle )
#endif
	{
#ifndef _WIN32_WCE
		if( gotConsole ) {
			Sleep( INFINITE );
		}
#endif

		MessageBoxA( NULL, text, APPLICATION " Fatal Error", MB_ICONERROR | MB_OK );
	}

	exit( 1 );
}

/*
================
Sys_Quit
================
*/
void Sys_Quit( void ) {
	timeEndPeriod( 1 );

#if !(defined _WIN32_WCE) && !(defined DEDICATED_ONLY)
	if( dedicated && dedicated->integer )
		FreeConsole();
#endif

#ifdef DEDICATED_ONLY
	if( !statusHandle )
#endif
		exit( 0 );
}

#ifndef _WIN32_WCE

static void Sys_HideInput( void ) {
	DWORD		dummy;
	int i;

	if( !sys_hidden ) {
		for( i = 0; i <= sys_con.inputLine.cursorPos; i++ ) {
			WriteFile( houtput, "\b \b", 3, &dummy, NULL );	
		}
	}
	sys_hidden++;
}

static void Sys_ShowInput( void ) {
	DWORD		dummy;
	int i;

	if( !sys_hidden ) {
		Com_EPrintf( "Sys_ShowInput: not hidden\n" );
		return;
	}

	sys_hidden--;
	if( !sys_hidden ) {
		WriteFile( houtput, "]", 1, &dummy, NULL );	
		for( i = 0; i < sys_con.inputLine.cursorPos; i++ ) {
			WriteFile( houtput, &sys_con.inputLine.text[i], 1, &dummy, NULL );	
		}
	}
}

/*
================
Sys_ConsoleInput
================
*/
char *Sys_ConsoleInput( void ) {
	INPUT_RECORD	recs[MAX_CONSOLE_INPUT_EVENTS];
	DWORD		dummy;
	int		ch;
	DWORD numread, numevents;
	int i;
	inputField_t *f;
	char *s;

	if( hinput == INVALID_HANDLE_VALUE ) {
		return NULL;
	}

	if( !gotConsole ) {
		return NULL;
	}

	f = &sys_con.inputLine;
	while( 1 ) {
		if( !GetNumberOfConsoleInputEvents( hinput, &numevents ) ) {
			Com_EPrintf( "Error %lu getting number of console events.\n"
				"Console IO disabled.\n", GetLastError() );
			gotConsole = qfalse;
			return NULL;
		}

		if( numevents <= 0 )
			break;
		if( numevents > MAX_CONSOLE_INPUT_EVENTS ) {
		    numevents = MAX_CONSOLE_INPUT_EVENTS;
		}

		if( !ReadConsoleInput( hinput, recs, numevents, &numread ) ) {
			Com_EPrintf( "Error %lu reading console input.\n"
				"Console IO disabled.\n", GetLastError() );
			gotConsole = qfalse;
			return NULL;
		}
			
		for( i = 0; i < numread; i++ ) {
			if( recs[i].EventType == WINDOW_BUFFER_SIZE_EVENT ) {
				sys_con.widthInChars = recs[i].Event.WindowBufferSizeEvent.dwSize.X;
				continue;
			}
			if( recs[i].EventType != KEY_EVENT ) {
				continue;
			}
	
			if( !recs[i].Event.KeyEvent.bKeyDown ) {
				continue;
			}

			switch( recs[i].Event.KeyEvent.wVirtualKeyCode ) {
			case VK_UP:
				Sys_HideInput();
				Prompt_HistoryUp( &sys_con );
				Sys_ShowInput();
				break;
			case VK_DOWN:
				Sys_HideInput();
				Prompt_HistoryDown( &sys_con );
				Sys_ShowInput();
				break;
			case VK_RETURN:
				Sys_HideInput();
				s = Prompt_Action( &sys_con );
				if( s ) {
					if( *s == '\\' || *s == '/' ) {
						s++;
					}
					Sys_Printf( "]%s\n", s );
				} else {
					WriteFile( houtput, "\n", 2, &dummy, NULL );	
				}
				Sys_ShowInput();
				return s;
			case VK_BACK:
				if( f->cursorPos ) {
					f->cursorPos--;
					f->text[f->cursorPos] = 0;
					WriteFile( houtput, "\b \b", 3, &dummy, NULL );	
				}
				break;
			case VK_TAB:
				Sys_HideInput();
				Prompt_CompleteCommand( &sys_con, qfalse );
				f->cursorPos = strlen( f->text );
				Sys_ShowInput();
				break;
			default:
				ch = recs[i].Event.KeyEvent.uChar.AsciiChar;
				if( ch < 32 ) {
					break;
				}
				if( f->cursorPos < sizeof( f->text ) - 1 ) {
					WriteFile( houtput, &ch, 1, &dummy, NULL );
					f->text[f->cursorPos] = ch;
					f->text[f->cursorPos+1] = 0;
					f->cursorPos++;
				}
				break;
			}
		}
	}	

	return NULL;
}

#define FOREGROUND_BLACK	0
#define FOREGROUND_WHITE	(FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED)

static WORD textColors[8] = {
	FOREGROUND_BLACK,
	FOREGROUND_RED,
	FOREGROUND_GREEN,
	FOREGROUND_RED|FOREGROUND_GREEN,
	FOREGROUND_BLUE,
	FOREGROUND_BLUE|FOREGROUND_GREEN,
	FOREGROUND_RED|FOREGROUND_BLUE,
	FOREGROUND_WHITE
};

/*
================
Sys_ConsoleOutput

Print text to the dedicated console
================
*/
void Sys_ConsoleOutput( const char *string ) {
	DWORD		dummy;
	char	text[MAXPRINTMSG];
	char *maxp, *p;
	int length;	
	WORD attr, w;
	int c;

	if( houtput == INVALID_HANDLE_VALUE ) {
		return;
	}

	if( !gotConsole ) {
		p = text;
		maxp = text + sizeof( text ) - 1;
		while( *string ) {
			if( Q_IsColorString( string ) ) {
				string += 2;
				continue;
			}
			*p++ = *string++ & 127;
			if( p == maxp ) {
				break;
			}
		}

		*p = 0;

		length = p - text;
		WriteFile( houtput, text, length, &dummy, NULL );
		return;
	}

	Sys_HideInput();

	attr = sbinfo.wAttributes & ~FOREGROUND_WHITE;
	
	while( *string ) {
		if( Q_IsColorString( string ) ) {
			c = string[1];
			string += 2;
			if( c == COLOR_ALT ) {
				w = attr | FOREGROUND_GREEN;
			} else if( c == COLOR_RESET ) {
				w = sbinfo.wAttributes;
			} else {
				w = attr | textColors[ ColorIndex( c ) ];
			}
			SetConsoleTextAttribute( houtput, w );
			continue;
		}

		p = text;
		maxp = text + sizeof( text ) - 1;
		do {
			*p++ = *string++ & 127;
			if( p == maxp ) {
				break;
			}
		} while( *string && !Q_IsColorString( string ) );

		*p = 0;

		length = p - text;
		WriteFile( houtput, text, length, &dummy, NULL );
	}

	SetConsoleTextAttribute( houtput, sbinfo.wAttributes );

	Sys_ShowInput();
}

static BOOL WINAPI Sys_ConsoleCtrlHandler( DWORD dwCtrlType ) {
	if( errorEntered ) {
		exit( 1 );
	}
	/* 32 bit writes are guranteed to be atomic */
	shouldExit = qtrue;
	return TRUE;
}

static void Sys_ConsoleInit( void ) {
	DWORD mode;

#ifdef DEDICATED_ONLY
	if( statusHandle ) {
		return;
	}
#else
	if( !AllocConsole() ) {
		Com_EPrintf( "Couldn't create system console.\n"
			"Console IO disabled.\n" );
		return;
	}
#endif

	hinput = GetStdHandle( STD_INPUT_HANDLE );
	houtput = GetStdHandle( STD_OUTPUT_HANDLE );
	if( !GetConsoleScreenBufferInfo( houtput, &sbinfo ) ) {
		Com_EPrintf( "Couldn't get console buffer info.\n"
			"Console IO disabled.\n" );
		return;
	}

	SetConsoleTitle( "q2pro console" );
	SetConsoleCtrlHandler( Sys_ConsoleCtrlHandler, TRUE );
	GetConsoleMode( hinput, &mode );
	mode |= ENABLE_WINDOW_INPUT;
	SetConsoleMode( hinput, mode );
	sys_con.widthInChars = sbinfo.dwSize.X;
	sys_con.Printf = Sys_Printf;
	gotConsole = qtrue;

	Com_Printf( "System console initialized (%d cols, %d rows).\n",
		sbinfo.dwSize.X, sbinfo.dwSize.Y );
}

#else /* !_WIN32_WCE */

char *Sys_ConsoleInput( void ) {
	return NULL;
}

void Sys_ConsoleOutput( const char *string ) {
	printf( "%s", string );
}

#endif /* _WIN32_WCE */

/*
===============================================================================

SERVICE CONTROL

===============================================================================
*/

#ifdef DEDICATED_ONLY

static void Sys_InstallService_f( void ) {
	char servicePath[256];
	char serviceName[1024];
	SC_HANDLE scm, service;
	DWORD error, length;
	char *commandline;

	if( Cmd_Argc() < 3 ) {
		Com_Printf( "Usage: %s <servicename> <+command> [...]\n"
			"Example: %s test +set net_port 27910 +map q2dm1\n",
				Cmd_Argv( 0 ), Cmd_Argv( 0 ) );
		return;
	}

	scm = OpenSCManager( NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS );
	if( !scm ) {
		error = GetLastError();
		if( error == ERROR_ACCESS_DENIED ) {
			Com_Printf( "Insufficient privileges for opening Service Control Manager.\n" );
		} else {
			Com_EPrintf( "%#lx opening Service Control Manager.\n", error );
		}
		return;
	}

	Com_sprintf( serviceName, sizeof( serviceName ), "Q2PRO - %s", Cmd_Argv( 1 ) );

	length = GetModuleFileName( NULL, servicePath, MAX_PATH );
	if( !length ) {
		error = GetLastError();
		Com_EPrintf( "%#lx getting module file name.\n", error );
		goto fail;
	}
	commandline = Cmd_RawArgsFrom( 2 );
	if( length + strlen( commandline ) + 10 > sizeof( servicePath ) - 1 ) {
		Com_Printf( "Oversize service command line.\n" );
		goto fail;
	}
	strcpy( servicePath + length, " -service " );
	strcpy( servicePath + length + 10, commandline );

	service = CreateService(
			scm,
			serviceName,
			serviceName,
			SERVICE_START,
			SERVICE_WIN32_OWN_PROCESS,
			SERVICE_AUTO_START,
			SERVICE_ERROR_IGNORE,
			servicePath,
			NULL,
			NULL,
			NULL,
			NULL,
			NULL );

	if( !service ) {
		error = GetLastError();
		if( error == ERROR_SERVICE_EXISTS || error == ERROR_DUPLICATE_SERVICE_NAME ) {
			Com_Printf( "Service already exists.\n" );
		} else {
			Com_EPrintf( "%#lx creating service.\n", error );
		}
		goto fail;
	}

	Com_Printf( "Service created successfully.\n" );

	CloseServiceHandle( service );

fail:
	CloseServiceHandle( scm );
}

static void Sys_DeleteService_f( void ) {
	char serviceName[256];
	SC_HANDLE scm, service;
	DWORD error;

	if( Cmd_Argc() < 2 ) {
		Com_Printf( "Usage: %s <servicename>\n", Cmd_Argv( 0 ) );
		return;
	}

	scm = OpenSCManager( NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS );
	if( !scm ) {
		error = GetLastError();
		if( error == ERROR_ACCESS_DENIED ) {
			Com_Printf( "Insufficient privileges for opening Service Control Manager.\n" );
		} else {
			Com_EPrintf( "%#lx opening Service Control Manager.\n", error );
		}
		return;
	}

	Com_sprintf( serviceName, sizeof( serviceName ), "Q2PRO - %s", Cmd_Argv( 1 ) );

	service = OpenService(
			scm,
			serviceName,
			DELETE );

	if( !service ) {
		error = GetLastError();
		if( error == ERROR_SERVICE_DOES_NOT_EXIST ) {
			Com_Printf( "Service doesn't exist.\n" );
		} else {
			Com_EPrintf( "%#lx opening service.\n", error );
		}
		goto fail;
	}

	if( !DeleteService( service ) ) {
		error = GetLastError();
		if( error == ERROR_SERVICE_MARKED_FOR_DELETE ) {
			Com_Printf( "Service has already been marked for deletion.\n" );
		} else {
			Com_EPrintf( "%#lx deleting service.\n", error );
		}
	} else {
		Com_Printf( "Service deleted successfully.\n" );
	}

	CloseServiceHandle( service );

fail:
	CloseServiceHandle( scm );
}

#endif

/*
===============================================================================

HUNK

===============================================================================
*/

#ifdef _WIN32_WCE

static int		hunkcount;
static int		highwaterMark;

void Hunk_Init( int maxsize ) {
	hunkmaxsize = ( maxsize + 4095 ) & ~4095;
	membase = VirtualAlloc( NULL, hunkmaxsize, MEM_RESERVE, PAGE_NOACCESS );
	if( !membase ) {
		Com_Error( ERR_FATAL,
            "VirtualAlloc reserve %d bytes failed. GetLastError() = %lu",
			maxsize, GetLastError() );
	}
}

void Hunk_Shutdown( void ) {
	if( membase ) {
		if( !VirtualFree( membase, 0, MEM_RELEASE ) ) {
			Com_Error( ERR_FATAL, "VirtualFree failed. GetLastError() = %lu",
				GetLastError() );
		}
	}
	highwaterMark = 0;
	cursize = 0;
	hunkmaxsize = 0;
}

void Hunk_Flush( void ) {
	cursize = 0;
}

void *Hunk_Begin( int maxsize ) {
	return ( void * )( membase + cursize );
}

void *Hunk_Alloc( int size ) {
	void	*buf;

	// round to cacheline
	size = ( size + 31 ) & ~ 31;

	cursize += size;
	if( cursize > hunkmaxsize ) {
		Com_Error( ERR_FATAL, "Hunk_Alloc: couldn't allocate %d bytes", size );
	}

	if( highwaterMark < cursize ) {
		// commit pages as needed
		buf = VirtualAlloc( membase, cursize, MEM_COMMIT, PAGE_READWRITE );
		if( !buf ) {
			Com_Error( ERR_FATAL,
				"VirtualAlloc commit %d bytes failed. GetLastError() = %lu",
				cursize, GetLastError() );
		}
		highwaterMark = cursize;
	}

	return ( void * )( membase + cursize - size );
}

int Hunk_End( void ) {
	hunkcount++;
	return cursize;
}

void Hunk_Free( void *base ) {
	hunkcount--;

	if( hunkcount == 0 ) {
		cursize = 0;
	}
}

#else

void Hunk_Begin( mempool_t *pool, int maxsize ) {
	// reserve a huge chunk of memory, but don't commit any yet
	pool->cursize = 0;
	pool->maxsize = ( maxsize + 4095 ) & ~4095;
	pool->base = VirtualAlloc( NULL, pool->maxsize, MEM_RESERVE, PAGE_NOACCESS );
	if( !pool->base ) {
		Com_Error( ERR_FATAL,
            "VirtualAlloc reserve %d bytes failed. GetLastError() = %lu",
			pool->maxsize, GetLastError() );
	}
}

void *Hunk_Alloc( mempool_t *pool, int size ) {
	void	*buf;

	// round to cacheline
	size = ( size + 31 ) & ~ 31;

	pool->cursize += size;
	if( pool->cursize > pool->maxsize )
		Com_Error( ERR_FATAL, "Hunk_Alloc: couldn't allocate %d bytes", size );

	// commit pages as needed
	buf = VirtualAlloc( pool->base, pool->cursize, MEM_COMMIT, PAGE_READWRITE );
	if( !buf ) {
		Com_Error( ERR_FATAL,
            "VirtualAlloc commit %d bytes failed. GetLastError() = %lu",
			pool->cursize, GetLastError() );
	}

	return ( void * )( pool->base + pool->cursize - size );
}

void Hunk_End( mempool_t *pool ) {
}

void Hunk_Free( mempool_t *pool ) {
	if( pool->base ) {
		if( !VirtualFree( pool->base, 0, MEM_RELEASE ) ) {
			Com_Error( ERR_FATAL, "VirtualFree failed. GetLastError() = %lu",
				GetLastError() );
		}
	}

	memset( pool, 0, sizeof( pool ) );
}

#endif

/*
===============================================================================

MISC

===============================================================================
*/

/*
================
Sys_Milliseconds
================
*/
int Sys_Milliseconds( void ) {
	static uint32		base;
	static qboolean	initialized = qfalse;
	int	curtime;

	if( !initialized ) {	
		// let base retain 16 bits of effectively random data
		base = timeGetTime() & 0xffff0000;
		initialized = qtrue;
	}
	curtime = timeGetTime() - base;

	return curtime;
}

uint32 Sys_Realtime( void ) {
	return timeGetTime();
}

/*
================
Sys_Sleep

Sleep for specified amount of time until there are pending
events in our thread queue or incoming network packets.
================
*/
void Sys_Sleep( uint32 msec ) {
#ifdef USE_SLEEP
	DWORD result;

	if( dedicated->integer ) {
		result = WaitForSingleObject( hSocketEvent, msec );
	} else {
		result = MsgWaitForMultipleObjects( 1, &hSocketEvent, FALSE, msec, QS_ALLEVENTS );
	}
	if( result == WAIT_FAILED ) {
		Sys_Error( "Sys_Sleep failed" );
	}
	if( result == WAIT_OBJECT_0 ) {
		ResetEvent( hSocketEvent );
	}
#endif
}

/*
================
Sys_Mkdir
================
*/
void Sys_Mkdir( const char *path ) {
	CreateDirectoryA( path, NULL );
}

qboolean Sys_RemoveFile( const char *path ) {
	if( !DeleteFileA( path ) ) {
		return qfalse;
	}
	return qtrue;
}

qboolean Sys_RenameFile( const char *from, const char *to ) {
	if( !MoveFileA( from, to ) ) {
		return qfalse;
	}
	return qtrue;
}

/*
================
Sys_GetFileInfo
================
*/
qboolean Sys_GetFileInfo( const char *path, fsFileInfo_t *info ) {
	WIN32_FILE_ATTRIBUTE_DATA	data;
	SYSTEMTIME	systemTime;
	BOOL bSuccess;

	bSuccess = GetFileAttributesExA( path, GetFileExInfoStandard, &data );
	if( !bSuccess ) {
		return qfalse;
	}

	if( !info ) {
		return qtrue;
	}

	info->fileSize = data.nFileSizeLow;

	bSuccess = FileTimeToSystemTime( &data.ftCreationTime, &systemTime );
	if( !bSuccess ) {
		return qfalse;
	}
	SystemTimeToQTime( &info->timeCreate, &systemTime );

	bSuccess = FileTimeToSystemTime( &data.ftLastWriteTime, &systemTime );
	if( !bSuccess ) {
		return qfalse;
	}
	SystemTimeToQTime( &info->timeModify, &systemTime );

	return qtrue;
}

/*
================
Sys_GetClipboardData

================
*/
char *Sys_GetClipboardData( void ) {
	char *data = NULL;
	char *cliptext;

	if( OpenClipboard( NULL ) != FALSE ) {
		HANDLE hClipboardData;

		if( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != NULL ) {
			if( ( cliptext = GlobalLock( hClipboardData ) ) != NULL ) {
				data = Z_CopyString( cliptext );
				GlobalUnlock( hClipboardData );
			}
		}
		CloseClipboard();
	}
	return data;
}

/*
================
Sys_SetClipboardData

================
*/
void Sys_SetClipboardData( const char *data ) {
	char *cliptext;
	int	length;

	if( OpenClipboard( NULL ) != FALSE ) {
		HANDLE hClipboardData;

		length = strlen( data );
		hClipboardData = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, length + 1 );

		if( SetClipboardData( CF_TEXT, hClipboardData ) != NULL ) {
			if( ( cliptext = GlobalLock( hClipboardData ) ) != NULL ) {
				strcpy( cliptext, data );
				cliptext[length] = 0;
				GlobalUnlock( hClipboardData );
			}
		}
		CloseClipboard();
	}
}


/*
================
Sys_FillAPI
================
*/
void Sys_FillAPI( sysAPI_t *api ) {
	api->Milliseconds = Sys_Milliseconds;
	api->GetClipboardData = Sys_GetClipboardData;
	api->SetClipboardData = Sys_SetClipboardData;
	api->HunkBegin = Hunk_Begin;
	api->HunkAlloc = Hunk_Alloc;
    api->HunkEnd = Hunk_End;
	api->HunkFree = Hunk_Free;
}

/*
================
Sys_Init
================
*/
void Sys_Init( void ) {
	OSVERSIONINFO	vinfo;

	timeBeginPeriod( 1 );

	vinfo.dwOSVersionInfoSize = sizeof( vinfo );

	if( !GetVersionEx( &vinfo ) ) {
		Sys_Error( "Couldn't get OS info" );
	}

	winnt = qtrue;
	if( vinfo.dwMajorVersion < 4 ) {
		Sys_Error( APPLICATION " requires windows version 4 or greater" );
	}
	if( vinfo.dwPlatformId == VER_PLATFORM_WIN32s ) {
		Sys_Error( APPLICATION " doesn't run on Win32s" );
	} else if( vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) {
		if( vinfo.dwMinorVersion == 0 ) {
			Sys_Error( APPLICATION " doesn't run on Win95" );
		}
		winnt = qfalse;
	}

	sys_debugdir = Cvar_Get( "sys_debugdir", "", CVAR_NOSET );

	Cvar_Get( "sys_hinstance", va( "0x%x", ( UINT_PTR )hGlobalInstance ),
            CVAR_ROM );
	Cvar_Get( "sys_winnt", va( "%i", winnt ), CVAR_ROM );

#ifdef USE_MODULES
	Cmd_AddCommand( "listmodules", Sys_ListModules_f );
#endif

#ifdef DEDICATED_ONLY
	Cmd_AddCommand( "installservice", Sys_InstallService_f );
	Cmd_AddCommand( "deleteservice", Sys_DeleteService_f );
#endif

#ifdef _WIN32_WCE
	{
		MEMORYSTATUS   memInfo;

		memInfo.dwLength = sizeof( memInfo );
		GlobalMemoryStatus( &memInfo );

		Com_Printf( "Total RAM: %d, free: %d, used: %d",
			memInfo.dwTotalPhys, memInfo.dwAvailPhys, memInfo.dwTotalPhys - memInfo.dwAvailPhys );

	}
#else
	sys_viewlog = Cvar_Get( "sys_viewlog", "0", CVAR_NOSET );

	houtput = GetStdHandle( STD_OUTPUT_HANDLE );

	if( dedicated->integer || sys_viewlog->integer ) {
		Sys_ConsoleInit();
	}
#endif /* !_WIN32_WCE */

#ifdef USE_SLEEP
	hSocketEvent = WSACreateEvent();
#endif

	Sys_FillAPI( &sys );
}

/*
===============================================================================

NETWORKING

===============================================================================
*/

/*
====================
Sys_NetErrorString
====================
*/
char *Sys_NetErrorString( void ) {
	int		code;
	char	*s;

	code = WSAGetLastError();

	switch( code ) {
	case S_OK: s = "NO ERROR"; break;
	default: s = "UNKNOWN ERROR"; break;

#define MAPERR( x )		case x: s = #x; break;

	MAPERR( WSAEINTR )
	MAPERR( WSAEBADF )
	MAPERR( WSAEACCES )
	MAPERR( WSAEFAULT )
	MAPERR( WSAEINVAL )
	MAPERR( WSAEMFILE )
	MAPERR( WSAEWOULDBLOCK )
	MAPERR( WSAEINPROGRESS )
	MAPERR( WSAEALREADY )
	MAPERR( WSAENOTSOCK )
	MAPERR( WSAEDESTADDRREQ )
	MAPERR( WSAEMSGSIZE )
	MAPERR( WSAEPROTOTYPE )
	MAPERR( WSAENOPROTOOPT )
	MAPERR( WSAEPROTONOSUPPORT )
	MAPERR( WSAESOCKTNOSUPPORT )
	MAPERR( WSAEOPNOTSUPP )
	MAPERR( WSAEPFNOSUPPORT )
	MAPERR( WSAEAFNOSUPPORT )
	MAPERR( WSAEADDRINUSE )
	MAPERR( WSAEADDRNOTAVAIL )
	MAPERR( WSAENETDOWN )
	MAPERR( WSAENETUNREACH )
	MAPERR( WSAENETRESET )
	MAPERR( WSAECONNABORTED )
	MAPERR( WSAECONNRESET )
	MAPERR( WSAENOBUFS )
	MAPERR( WSAEISCONN )
	MAPERR( WSAENOTCONN )
	MAPERR( WSAESHUTDOWN )
	MAPERR( WSAETOOMANYREFS )
	MAPERR( WSAETIMEDOUT )
	MAPERR( WSAECONNREFUSED )
	MAPERR( WSAELOOP )
	MAPERR( WSAENAMETOOLONG )
	MAPERR( WSAEHOSTDOWN )
	MAPERR( WSAEHOSTUNREACH )
	MAPERR( WSAENOTEMPTY )
	MAPERR( WSAEPROCLIM )
	MAPERR( WSAEUSERS )
	MAPERR( WSAEDQUOT )
	MAPERR( WSAESTALE )
	MAPERR( WSAEREMOTE )
	MAPERR( WSASYSNOTREADY )
	MAPERR( WSAVERNOTSUPPORTED )
	MAPERR( WSANOTINITIALISED )
	MAPERR( WSAEDISCON )
	MAPERR( WSAENOMORE )
	MAPERR( WSAECANCELLED )
	MAPERR( WSAEINVALIDPROCTABLE )
	MAPERR( WSAEINVALIDPROVIDER )
	MAPERR( WSAEPROVIDERFAILEDINIT )
	MAPERR( WSASYSCALLFAILURE )
	MAPERR( WSASERVICE_NOT_FOUND )
	MAPERR( WSATYPE_NOT_FOUND )
	MAPERR( WSA_E_NO_MORE )
	MAPERR( WSA_E_CANCELLED )
	MAPERR( WSAEREFUSED )
	MAPERR( WSAHOST_NOT_FOUND )
	MAPERR( WSATRY_AGAIN )
	MAPERR( WSANO_RECOVERY )
	MAPERR( WSANO_DATA )
	}

#undef MAPERR

	return s;
}

/*
====================
Sys_IPSocket
====================
*/
SOCKET Sys_IPSocket( const char *net_interface, int port ) {
	SOCKET				newsocket;
	struct sockaddr_in	address;
	qboolean			_true = qtrue;
	int					i = 1;
	int					err;

	if( !net_interface || !net_interface[0] ) {
		net_interface = "localhost";
	}

	Com_Printf( "Opening IP socket: %s:%i\n", net_interface, port );

	if( ( newsocket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) {
		err = WSAGetLastError();
		if( err != WSAEAFNOSUPPORT )
			Com_WPrintf( "UDP_OpenSocket: socket: %s", Sys_NetErrorString() );
		return INVALID_SOCKET;
	}

	// make it non-blocking
	if( ioctlsocket( newsocket, FIONBIO, (u_long *)&_true ) == SOCKET_ERROR ) {
		Com_WPrintf( "UDP_OpenSocket: ioctl FIONBIO: %s\n", Sys_NetErrorString() );
		goto fail;
	}

	// make it broadcast capable
	if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof( i ) ) == SOCKET_ERROR ) {
		Com_WPrintf( "UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n", Sys_NetErrorString() );
		goto fail;
	}

	if( !Q_stricmp( net_interface, "localhost" ) ) {
		address.sin_addr.s_addr = INADDR_ANY;
	} else {
		NET_StringToSockaddr( net_interface, (struct sockaddr *)&address );
	}

	if( port == PORT_ANY ) {
		address.sin_port = 0;
	} else {
		address.sin_port = htons( (u_short)port );
	}

	address.sin_family = AF_INET;

	if( bind( newsocket, (void *)&address, sizeof( address ) ) == SOCKET_ERROR ) {
		Com_WPrintf( "UDP_OpenSocket: bind: %s\n", Sys_NetErrorString() );
		goto fail;
	}

#ifdef USE_SLEEP
	if( WSAEventSelect( newsocket, hSocketEvent, FD_READ ) == SOCKET_ERROR ) {
		Com_WPrintf( "UDP_OpenSocket: WSAEventSelect: %s\n", Sys_NetErrorString() );
		goto fail;
	}
#endif

	return newsocket;

fail:
	closesocket( newsocket );
	return INVALID_SOCKET;
}

/*
====================
Sys_CloseSocket
====================
*/
void Sys_CloseSocket( SOCKET sock ) {
	closesocket( sock );
}

/*
====================
Sys_InitNetworking
====================
*/
qboolean Sys_InitNetworking( void ) {
	WSADATA		winsockdata;
	int		r;
	char *errmsg;

	r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata );
	if( r ) {
		errmsg = va( "Winsock initialization failed, returned %i", r );
		if( dedicated->integer ) {
			Com_Error( ERR_FATAL, "%s", errmsg );
		}

		Com_WPrintf( "%s\n", errmsg );
		return qfalse;
	}

	Com_Printf( "Winsock Initialized\n" );

	return qtrue;
}

/*
====================
Sys_ShutdownNetworking
====================
*/
void Sys_ShutdownNetworking( void ) {
	WSACleanup();
}

/*
========================================================================

DLL LOADING

========================================================================
*/

#ifdef USE_MODULES

typedef struct sysModule_s {
	listElem_t	elem;
	void *handle;
	moduleEntry_t entry;
	int refCount;
	char name[1];
} sysModule_t;

static list_t sys_moduleList;

/*
=================
Sys_FreeModule
=================
*/
void Sys_FreeModule( void *handle ) {
	sysModule_t *module = ( sysModule_t * )handle;

	if( module ) {
		if( --module->refCount < 1 ) {
			if( module->refCount < 0 ) {
				Com_Error( ERR_FATAL, "Sys_FreeModule: freed a freed module" );
			}
			if( !FreeLibrary( module->handle ) ) {
				Com_Error( ERR_FATAL, "Sys_FreeModule: FreeLibrary failed" );
			}
			List_DeleteElem( module );
			Z_Free( module );
		}
	}
}

/*
=================
Sys_LoadModule
=================
*/
moduleEntry_t Sys_LoadModule( const char *name, void **handle ) {
	HMODULE		hModule;
	moduleEntry_t moduleEntry;
	char	fullname[MAX_OSPATH];
	sysModule_t *module;
	listElem_t *elem;

	for( elem = sys_moduleList.first; elem; elem = elem->next ) {
		module = ( sysModule_t * )elem;
		if( !strcmp( module->name, name ) ) {
			break;
		}
	}

	if( elem != NULL ) {
		module->refCount++;
		*handle = ( void * )module;
		return module->entry;
	}

	*handle = NULL;
	hModule = NULL;

	// check the current debug directory first for development purposes
	if( sys_debugdir->string[0] ) {
		Com_sprintf( fullname, sizeof( fullname ), "%s\\%s.dll", sys_debugdir->string, name );
		hModule = LoadLibraryA( fullname );
		if( !hModule ) {
			Com_DPrintf(
                "Sys_LoadModule( %s ): LoadLibrary failed with error %lu\n",
				fullname, GetLastError() );
		}
	}

	if( !hModule ) {
		// check the baseq2pro directory
		Com_sprintf( fullname, sizeof( fullname ), "%s\\"INITDIRNAME"\\%s.dll",
			Cvar_VariableString( "basedir" ), name );
		hModule = LoadLibraryA( fullname );
		if( !hModule ) {
			Com_DPrintf(
                "Sys_LoadModule( %s ): LoadLibrary failed with error %lu\n",
				fullname, GetLastError() );
			return NULL;
		}
	}

	moduleEntry = ( void * )GetProcAddress( hModule, __TEXT( "moduleEntry" ) );
	if( !moduleEntry ) {
		Com_DPrintf(
            "Sys_LoadModule( %s ): GetProcAddress failed with error %lu\n",
			fullname, GetLastError() );
		FreeLibrary( hModule );	
		return NULL;
	}

	Com_DPrintf( "Sys_LoadModule( %s ) succeeded\n", fullname );

	module = Z_Malloc( sizeof( *module ) + strlen( name ) );
	module->refCount = 1;
	module->handle = hModule;
	module->entry = moduleEntry;
	strcpy( module->name, name );
	List_Append( &sys_moduleList, module );

	*handle = module;

	return moduleEntry;
}

/*
=================
Sys_ListModules_f
=================
*/
void Sys_ListModules_f( void ) {
	listElem_t *elem;
	sysModule_t *module;
	moduleInfo_t info;
	int numElems;

	numElems = 0;
	for( elem = sys_moduleList.first; elem; elem = elem->next ) {
		module = ( sysModule_t * )elem;

		Com_Printf( "Module  : %s\n", module->name );
		Com_Printf( "RefCount: %i\n", module->refCount );

		if( !module->entry( MQ_GETINFO, &info ) ) {
			Com_EPrintf( "Couldn't get module info\n" );
			continue;
		}

		Com_Printf( "Name    : %s\n", info.fullname );
		Com_Printf( "Author  : %s\n", info.author );
		Com_Printf( "\n" );
		numElems++;
	}

	Com_Printf( "----------------\n" );
	Com_Printf( "%i total modules\n", numElems );
}

#endif /* USE_MODULES */

//=======================================================================

static HMODULE	gameLibrary;

#ifdef ARM
#define GAMELIB		"gamearmv4.dll"
#else
#define GAMELIB		"gamex86.dll"
#endif

/*
=================
Sys_FreeGameLibrary
=================
*/
void Sys_FreeGameLibrary( void ) {
	if( !gameLibrary ) {
		return;
	}
	if( !FreeLibrary( gameLibrary ) ) {
		Com_Error( ERR_FATAL, "Sys_FreeGameLibrary failed!" );
	}
	gameLibrary = NULL;
}

/*
=================
Sys_LoadLibrary
=================
*/
void *Sys_LoadGameLibrary( void *parms ) {
	void	*(*GetAPI)( void * );
	char	fullname[MAX_OSPATH];
	char	*path;
#ifdef NDEBUG
	const char *debugdir = "release";
#else
	const char *debugdir = "debug";
#endif

	if( gameLibrary ) {
		Sys_FreeGameLibrary();
	}

	// check the current debug directory first for development purposes
	if( sys_debugdir->string[0] ) {
		Com_sprintf( fullname, sizeof( fullname ), "%s\\"GAMELIB, sys_debugdir->string );
	} else {
		Com_sprintf( fullname, sizeof( fullname ), "%s\\%s\\"GAMELIB, currentDirectory, debugdir );
	}

	gameLibrary = LoadLibraryA( fullname );
	if( !gameLibrary ) {
#if 0//ndef NDEBUG
		Com_DPrintf( "Sys_LoadLibrary( %s ) failed with error %d\n", fullname, GetLastError() );

		// check the current directory for other development purposes
		Com_sprintf( fullname, sizeof( fullname ), "%s\\"GAMELIB, currentDirectory );
		hModule = LoadLibraryA( fullname );
		if( !hModule )
#endif
		{
			Com_DPrintf( "Sys_LoadLibrary( %s ) failed with error %lu\n",
                    fullname, GetLastError() );
			// now run through the search paths
			path = NULL;
			while( 1 ) {
				path = FS_NextPath( path );
				if( !path ) {
					break;		// couldn't find one anywhere
				}
				Com_sprintf( fullname, sizeof( fullname ), "%s\\"GAMELIB, path );
				gameLibrary = LoadLibraryA( fullname );
				if( gameLibrary ) {
					break;
				}
				Com_DPrintf( "Sys_LoadLibrary( %s ) failed with error %lu\n",
                        fullname, GetLastError() );
			}
		}
	}

	if( !gameLibrary ) {
		return NULL;
	}

	GetAPI = ( void * )GetProcAddress( gameLibrary, __TEXT( "GetGameAPI" ) );
	if( !GetAPI ) {
		Com_DPrintf( "Sys_LoadLibrary( %s ) failed with error %lu\n",
                fullname, GetLastError() );
		Sys_FreeGameLibrary();	
		return NULL;
	}

	Com_DPrintf( "Sys_LoadLibrary( %s ) succeeded\n", fullname );

	return GetAPI( parms );
}

//=======================================================================

static qboolean Sys_FindInfoToExtraInfo( WIN32_FIND_DATAA *findInfo, fsFileInfo_t *info ) {
	SYSTEMTIME	systemTime;
	BOOL	bSuccess;

	info->fileSize = findInfo->nFileSizeLow;

	bSuccess = FileTimeToSystemTime( &findInfo->ftCreationTime, &systemTime );
	if( !bSuccess ) {
		return qfalse;
	}
	SystemTimeToQTime( &info->timeCreate, &systemTime );

	bSuccess = FileTimeToSystemTime( &findInfo->ftLastWriteTime, &systemTime );
	if( !bSuccess ) {
		return qfalse;
	}
	SystemTimeToQTime( &info->timeModify, &systemTime );

	return qtrue;
}

/*
=================
Sys_ListFilteredFiles
=================
*/
static void Sys_ListFilteredFiles( char **listedFiles, int *count, const char *path,
								  const char *filter, uint32 flags, int length )
{
	WIN32_FIND_DATAA	findInfo;
	HANDLE		findHandle;
	char	findPath[MAX_OSPATH];
	char	dirPath[MAX_OSPATH];
	fsFileInfo_t	info;
	char	*name;

	if( *count >= MAX_LISTED_FILES ) {
		return;
	}

	Com_sprintf( findPath, sizeof( findPath ), "%s\\*", path );

	findHandle = FindFirstFileA( findPath, &findInfo );
	if( findHandle == INVALID_HANDLE_VALUE ) {
		return;
	}

	do {
		if( !strcmp( findInfo.cFileName, "." ) || !strcmp( findInfo.cFileName, ".." ) ) {
			continue;
		}

		if( findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
			Com_sprintf( dirPath, sizeof( dirPath ), "%s\\%s", path, findInfo.cFileName );
			Sys_ListFilteredFiles( listedFiles, count, dirPath, filter, flags, length );
		}

		if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_ONLY ) {
			if( !( findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) {
				continue;
			}
		} else if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_NO ) {
			if( findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
				continue;
			}
		}

		Com_sprintf( dirPath, sizeof( dirPath ), "%s\\%s", path, findInfo.cFileName );
		if( !FS_WildCmp( filter, dirPath + length ) ) {
			continue;
		}

		name = ( flags & FS_SEARCH_SAVEPATH ) ? dirPath + length : findInfo.cFileName;

		if( flags & FS_SEARCH_EXTRAINFO ) {
			Sys_FindInfoToExtraInfo( &findInfo, &info );
			listedFiles[( *count )++] = FS_CopyExtraInfo( name, &info );
		} else {
			listedFiles[( *count )++] = Z_CopyString( name );
		}

	} while( *count < MAX_LISTED_FILES && FindNextFileA( findHandle, &findInfo ) != FALSE );

	FindClose( findHandle );

}

/*
=================
Sys_ListFiles
=================
*/
char **Sys_ListFiles( const char *rawPath, const char *extension, uint32 flags, int *numFiles ) {
	WIN32_FIND_DATAA	findInfo;
	HANDLE		findHandle;
	char	path[MAX_OSPATH];
	char	findPath[MAX_OSPATH];
	char	*listedFiles[MAX_LISTED_FILES];
	int		count;
	char	**list;
	int		i, length;
	fsFileInfo_t	info;
	char	*name;

	count = 0;

	if( numFiles ) {
		*numFiles = 0;
	}

	Q_strncpyz( path, rawPath, sizeof( path ) );
	Com_ReplaceSeparators( path, '\\' );

	if( flags & FS_SEARCH_BYFILTER ) {
		length = strlen( path );
        if( !length ) {
            return NULL;
        }
		Q_strncpyz( findPath, extension, sizeof( findPath ) );
		Com_ReplaceSeparators( findPath, '\\' );
		Sys_ListFilteredFiles( listedFiles, &count, path, findPath, flags, length + 1 );
	} else {
		if( extension ) {
			if( *extension == '.' ) {
				extension++;
			}
			Com_sprintf( findPath, sizeof( findPath ), "%s\\*.%s", path, extension );
		} else {
			Com_sprintf( findPath, sizeof( findPath ), "%s\\*", path );
		}
		
		findHandle = FindFirstFileA( findPath, &findInfo );
		if( findHandle == INVALID_HANDLE_VALUE ) {
			return NULL;
		}

		do {
			if( !strcmp( findInfo.cFileName, "." ) || !strcmp( findInfo.cFileName, ".." ) ) {
				continue;
			}

			if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_ONLY ) {
				if( !( findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) {
					continue;
				}
			} else if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_NO ) {
				if( findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
					continue;
				}
			}

			name = ( flags & FS_SEARCH_SAVEPATH ) ? va( "%s\\%s", path, findInfo.cFileName ) : findInfo.cFileName;

			if( flags & FS_SEARCH_EXTRAINFO ) {
				Sys_FindInfoToExtraInfo( &findInfo, &info );
				listedFiles[count++] = FS_CopyExtraInfo( name, &info );
			} else {
				listedFiles[count++] = Z_CopyString( name );
			}
		} while( count < MAX_LISTED_FILES && FindNextFileA( findHandle, &findInfo ) != FALSE );

		FindClose( findHandle );
	}

	if( !count ) {
		return NULL;
	}

	if( !( flags & FS_SEARCH_NOSORT ) ) {
		qsort( listedFiles, count, sizeof( listedFiles[0] ), SortStrcmp );
	}

	// reformat filenames back to quake filesystem style
	list = Z_Malloc( sizeof( char * ) * ( count + 1 ) );
	for( i = 0; i < count; i++ ) {
		name = listedFiles[i];
		Q_strlwr( name );
		Com_ReplaceSeparators( name, '/' );
		list[i] = name;
	}
	list[count] = NULL;

	if( numFiles ) {
		*numFiles = count;
	}

	return list;
}

/*
=================
Sys_FreeFileList
=================
*/
void Sys_FreeFileList( char **list ) {
	char **p;

	if( !list ) {
		Com_Error( ERR_FATAL, "Sys_FreeFileList: NULL" );
	}

	p = list;
	while( *p ) {
		Z_Free( *p++ );
	}

	Z_Free( list );
}

/*
=================
Sys_GetCurrentDirectory
=================
*/
char *Sys_GetCurrentDirectory( void ) {
	return currentDirectory;
}

//=======================================================================

#ifdef _WIN32_WCE

DWORD WINAPI GetCurrentDirectoryA( DWORD nBufferLength, LPSTR lpBuffer );

/*
==================
WinMain

==================
*/
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) {
	/* previous instances do not exist in Win32 */
	if( hPrevInstance ) {
		return 1;
	}

	if( !GetCurrentDirectoryA( sizeof( currentDirectory ) - 1, currentDirectory ) ) {
		return 1;
	}

	hGlobalInstance = hInstance;

	Qcommon_Init( "" );

    /* main program loop */
	while( 1 ) {
		Qcommon_Frame();
	}

	// never gets here
    return 0;
}

#else

#ifdef USE_ANTICHEAT

typedef BOOL (WINAPI *ISWOW64PROCESS)( HANDLE, PBOOL );
typedef PVOID (*FNINIT)( VOID );

PRIVATE PVOID anticheatApi;
PRIVATE FNINIT anticheatInit;
PRIVATE HMODULE anticheatHandle;
PRIVATE ISWOW64PROCESS pIsWow64Process;

//
// r1ch.net anticheat support
//
qboolean Sys_GetAntiCheatAPI( void ) {
	qboolean updated = qfalse;
    BOOL bIsWow64 = FALSE;

	//already loaded, just reinit
	if( anticheatInit ) {
		anticheatApi = anticheatInit();
		if( !anticheatApi ) {
	        Com_Printf( S_COLOR_RED "Anticheat failed to reinitialize!\n" );
            FreeLibrary( anticheatHandle );
            anticheatHandle = NULL;
            anticheatInit = NULL;
			return qfalse;
        }
		return qtrue;
	}

	//windows version check
	if( !winnt ) {
		Com_Printf( S_COLOR_YELLOW
			"Anticheat requires Windows 2000/XP/2003.\n" );
		return qfalse;
	}

    if( !pIsWow64Process ) {
        pIsWow64Process = ( ISWOW64PROCESS )GetProcAddress(
            GetModuleHandle( "kernel32" ), "IsWow64Process" );
    }
    if( pIsWow64Process ) {
        pIsWow64Process( GetCurrentProcess(), &bIsWow64 );
        if( bIsWow64 ) {
            Com_Printf( S_COLOR_YELLOW
				"Anticheat is incompatible with 64 bit Windows.\n" );
            return qfalse;
        }
    }

reInit:
	anticheatHandle = LoadLibrary( "anticheat" );
	if( !anticheatHandle ) {
		Com_Printf( S_COLOR_RED "Anticheat failed to load.\n" );
		return qfalse;
    }

	//this should never fail unless the anticheat.dll is bad
    anticheatInit = ( FNINIT )GetProcAddress(
            anticheatHandle, "Initialize" );
    if( !anticheatInit ) {
        Com_Printf( S_COLOR_RED "Couldn't get API of anticheat.dll!\n"
                    "Please check you are using a valid "
                    "anticheat.dll from http://antiche.at/" );
        FreeLibrary( anticheatHandle );
        anticheatHandle = NULL;
        return qfalse;
    }

	anticheatApi = anticheatInit();
	if( anticheatApi ) {
        return qtrue; // succeeded
    }

    FreeLibrary( anticheatHandle );
    anticheatHandle = NULL;
    anticheatInit = NULL;
    if( !updated ) {
        updated = qtrue;
        goto reInit;
    }

	Com_Printf( S_COLOR_RED "Anticheat failed to initialize.\n" );

    return qfalse;
}

#endif /* USE_ANTICHEAT */

#ifdef USE_DBGHELP

typedef DWORD (WINAPI *SETSYMOPTIONS)( DWORD );
typedef BOOL (WINAPI *SYMGETMODULEINFO)( HANDLE, DWORD, PIMAGEHLP_MODULE );
typedef BOOL (WINAPI *SYMINITIALIZE)( HANDLE, PSTR, BOOL );
typedef BOOL (WINAPI *SYMCLEANUP)( HANDLE );
typedef BOOL (WINAPI *ENUMERATELOADEDMODULES)( HANDLE, PENUMLOADED_MODULES_CALLBACK, PVOID );
typedef BOOL (WINAPI *STACKWALK)( DWORD, HANDLE, HANDLE, LPSTACKFRAME, PVOID,
	PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE,
	PTRANSLATE_ADDRESS_ROUTINE );
typedef BOOL (WINAPI *SYMFROMADDR)( HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO );
typedef PVOID (WINAPI *SYMFUNCTIONTABLEACCESS)( HANDLE, DWORD );
typedef DWORD (WINAPI *SYMGETMODULEBASE)( HANDLE, DWORD );

typedef HINSTANCE (WINAPI *SHELLEXECUTE)( HWND, LPCSTR, LPCSTR, LPCSTR, LPCSTR, INT );

PRIVATE SETSYMOPTIONS pSymSetOptions;
PRIVATE SYMGETMODULEINFO pSymGetModuleInfo;
PRIVATE SYMINITIALIZE pSymInitialize;
PRIVATE SYMCLEANUP pSymCleanup;
PRIVATE ENUMERATELOADEDMODULES pEnumerateLoadedModules;
PRIVATE STACKWALK pStackWalk;
PRIVATE SYMFROMADDR pSymFromAddr;
PRIVATE SYMFUNCTIONTABLEACCESS pSymFunctionTableAccess;
PRIVATE SYMGETMODULEBASE pSymGetModuleBase;
PRIVATE SHELLEXECUTE pShellExecute;

PRIVATE HANDLE processHandle, threadHandle;

PRIVATE FILE *crashReport;

PRIVATE CHAR moduleName[MAX_PATH];

PRIVATE BOOL CALLBACK EnumModulesCallback(
	PSTR  ModuleName,
	ULONG ModuleBase,
	ULONG ModuleSize,
	PVOID UserContext )
{
	IMAGEHLP_MODULE moduleInfo;
	DWORD pc = ( DWORD )UserContext;
	BYTE buffer[4096];
	PBYTE data;
	UINT numBytes;
	VS_FIXEDFILEINFO *info;
	char version[64];
	char *symbols;

	strcpy( version, "unknown" );
	if( GetFileVersionInfo( ModuleName, 0, sizeof( buffer ), buffer ) ) {
		if( VerQueryValue( buffer, "\\", &data, &numBytes ) ) {
			info = ( VS_FIXEDFILEINFO * )data;
			Com_sprintf( version, sizeof( version ), "%u.%u.%u.%u",
				HIWORD( info->dwFileVersionMS ),
				LOWORD( info->dwFileVersionMS ),
				HIWORD( info->dwFileVersionLS ),
				LOWORD( info->dwFileVersionLS ) );
		}
	}
	
	symbols = "failed";
	moduleInfo.SizeOfStruct = sizeof( moduleInfo );
	if( pSymGetModuleInfo( processHandle, ModuleBase, &moduleInfo ) ) {
		ModuleName = moduleInfo.ModuleName;
		switch( moduleInfo.SymType ) {
			case SymCoff: symbols = "COFF"; break;
			case SymExport: symbols = "export"; break;
			case SymNone: symbols = "none"; break;
			case SymPdb: symbols = "PDB"; break;
			default: symbols = "unknown"; break;
		}
	}
	
	fprintf( crashReport, "%08x %08x %s (version %s, symbols %s) ",
		ModuleBase, ModuleBase + ModuleSize, ModuleName, version, symbols );
	if( pc >= ModuleBase && pc < ModuleBase + ModuleSize ) {
		Q_strncpyz( moduleName, ModuleName, sizeof( moduleName ) );
		fprintf( crashReport, "*\n" );
	} else {
		fprintf( crashReport, "\n" );
	}

	return TRUE;
}

PRIVATE DWORD Sys_ExceptionHandler( DWORD exceptionCode, LPEXCEPTION_POINTERS exceptionInfo ) {
	STACKFRAME stackFrame;
	PCONTEXT context;
	SYMBOL_INFO *symbol;
	int count, ret, i;
	DWORD64 offset;
	BYTE buffer[sizeof( SYMBOL_INFO ) + 256 - 1];
	IMAGEHLP_MODULE moduleInfo;
	char path[MAX_PATH];
	char execdir[MAX_PATH];
	char *p;
	HMODULE helpModule, shellModule;
	SYSTEMTIME systemTime;
	static char *monthNames[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
	OSVERSIONINFO	vinfo;

#ifndef DEDICATED_ONLY
#ifdef REF_HARD_LINKED
#ifdef SOFTWARE_RENDERER
	SWimp_FatalShutdown();
#else
	GLimp_FatalShutdown();
#endif
#else
	GLimp_FatalShutdown();
	SWimp_FatalShutdown();
#endif
#endif

	ret = MessageBox( NULL, APPLICATION " has encountered an unhandled "
		"exception and needs to be terminated.\n"
		"Would you like to generate a crash report?",
		"Unhandled Exception",
		MB_ICONERROR|MB_YESNO );
	if( ret == IDNO ) {
		return EXCEPTION_CONTINUE_SEARCH;
	}

	helpModule = LoadLibrary( "dbghelp.dll" );
	if( !helpModule ) {
		return EXCEPTION_CONTINUE_SEARCH;
	}

#define GPA( x, y )															\
	do {																	\
		p ## y = ( x )GetProcAddress( helpModule, #y );						\
		if( !p ## y ) {														\
			return EXCEPTION_CONTINUE_SEARCH;								\
		}																	\
	} while( 0 )

	GPA( SETSYMOPTIONS, SymSetOptions );
	GPA( SYMGETMODULEINFO, SymGetModuleInfo );
	GPA( SYMCLEANUP, SymCleanup );
	GPA( SYMINITIALIZE, SymInitialize );
	GPA( ENUMERATELOADEDMODULES, EnumerateLoadedModules );
	GPA( STACKWALK, StackWalk );
	GPA( SYMFROMADDR, SymFromAddr );
	GPA( SYMFUNCTIONTABLEACCESS, SymFunctionTableAccess );
	GPA( SYMGETMODULEBASE, SymGetModuleBase );

	pSymSetOptions( SYMOPT_LOAD_ANYTHING|SYMOPT_DEBUG|SYMOPT_FAIL_CRITICAL_ERRORS );
	processHandle = GetCurrentProcess();
	threadHandle = GetCurrentThread();

	GetModuleFileName( NULL, execdir, sizeof( execdir ) - 1 );
	execdir[sizeof( execdir ) - 1] = 0;
	p = strrchr( execdir, '\\' );
	if( p ) {
		*p = 0;
	}
	
	GetSystemTime( &systemTime );

	for( i = 0; i < 100; i++ ) {
		Com_sprintf( path, sizeof( path ), "%s\\Q2PRO_CrashReport%02d.txt", execdir, i );
		if( !Sys_GetFileInfo( path, NULL ) ) {
			break;
		}
	}
	crashReport = fopen( path, "w" );
	if( !crashReport ) {
		return EXCEPTION_CONTINUE_SEARCH;
	}

	pSymInitialize( processHandle, execdir, TRUE );

	fprintf( crashReport, "Crash report generated %s %u %u, %02u:%02u:%02u UTC\n",
		monthNames[systemTime.wMonth % 12], systemTime.wDay, systemTime.wYear,
		systemTime.wHour, systemTime.wMinute, systemTime.wSecond );
	fprintf( crashReport, "by " APPLICATION " " VERSION ", built " __DATE__", " __TIME__ "\n" );

	vinfo.dwOSVersionInfoSize = sizeof( vinfo );
	if( GetVersionEx( &vinfo ) ) {
		fprintf( crashReport, "\nWindows version: %u.%u (build %u) %s\n",
			vinfo.dwMajorVersion, vinfo.dwMinorVersion, vinfo.dwBuildNumber, vinfo.szCSDVersion );
	}

	strcpy( moduleName, "unknown" );

	context = exceptionInfo->ContextRecord;

	fprintf( crashReport, "\nLoaded modules:\n" );
	pEnumerateLoadedModules( processHandle, EnumModulesCallback, ( PVOID )context->Eip );

	fprintf( crashReport, "\nException information:\n" );
	fprintf( crashReport, "Code: %08x\n", exceptionCode );
	fprintf( crashReport, "Address: %08x (%s)\n",
		context->Eip, moduleName );

	fprintf( crashReport, "\nThread context:\n" );
	fprintf( crashReport, "EIP: %08x EBP: %08x ESP: %08x\n",
		context->Eip, context->Ebp, context->Esp );
	fprintf( crashReport, "EAX: %08x EBX: %08x ECX: %08x\n",
		context->Eax, context->Ebx, context->Ecx );
	fprintf( crashReport, "EDX: %08x ESI: %08x EDI: %08x\n",
		context->Edx, context->Esi, context->Edi );

	memset( &stackFrame, 0, sizeof( stackFrame ) );
	stackFrame.AddrPC.Offset = context->Eip;
	stackFrame.AddrPC.Mode = AddrModeFlat;
	stackFrame.AddrFrame.Offset = context->Ebp;
	stackFrame.AddrFrame.Mode = AddrModeFlat;
	stackFrame.AddrStack.Offset = context->Esp;
	stackFrame.AddrStack.Mode = AddrModeFlat;	

	fprintf( crashReport, "\nStack trace:\n" );
	count = 0;
	symbol = ( SYMBOL_INFO * )buffer;
	symbol->SizeOfStruct = sizeof( *symbol );
	symbol->MaxNameLen = 256;
	while( pStackWalk( IMAGE_FILE_MACHINE_I386,
		processHandle,
		threadHandle,
		&stackFrame,
		context,
		NULL,
		pSymFunctionTableAccess,
		pSymGetModuleBase,
		NULL ) )
	{
		fprintf( crashReport, "%d: %08x %08x %08x %08x ",
			count,
			stackFrame.Params[0],
			stackFrame.Params[1],
			stackFrame.Params[2],
			stackFrame.Params[3] );

		moduleInfo.SizeOfStruct = sizeof( moduleInfo );
		if( pSymGetModuleInfo( processHandle, stackFrame.AddrPC.Offset, &moduleInfo ) ) {
			if( moduleInfo.SymType != SymNone && moduleInfo.SymType != SymExport &&
				pSymFromAddr( processHandle, stackFrame.AddrPC.Offset, &offset, symbol ) )
			{
				fprintf( crashReport, "%s!%s+%#x\n", 
					moduleInfo.ModuleName,
					symbol->Name, offset );
			} else {
				fprintf( crashReport, "%s!%#x\n",
					moduleInfo.ModuleName,
					stackFrame.AddrPC.Offset );
			}
		} else {
			fprintf( crashReport, "%#x\n",
				stackFrame.AddrPC.Offset );
		}
		count++;
	}

	fclose( crashReport );

	shellModule = LoadLibrary( "shell32.dll" );
	if( shellModule ) {
		pShellExecute = ( SHELLEXECUTE )GetProcAddress( shellModule, "ShellExecuteA" );
		if( pShellExecute ) {
			pShellExecute( NULL, "open", path, NULL, execdir, SW_SHOW );
		}
	}

	pSymCleanup( processHandle );

	ExitProcess( 1 );
	return EXCEPTION_CONTINUE_SEARCH;
}

#if 0
EXCEPTION_DISPOSITION _ExceptionHandler(
	EXCEPTION_RECORD *ExceptionRecord,
	void *EstablisherFrame,
	CONTEXT *ContextRecord,
	void *DispatcherContext )
{
	return ExceptionContinueSearch;
}

#ifndef __GNUC__
#define __try1( handler )	__asm { \
	__asm push handler \
	__asm push fs:[0] \
	__asm mov fs:0, esp \
}
#define __except1	__asm { \
	__asm mov eax, [esp] \
	__asm mov fs:[0], eax \
	__asm add esp, 8 \
}
#endif
#endif

#endif /* USE_DBGHELP */

/*
==================
WinMain

==================
*/
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) {
	/* previous instances do not exist in Win32 */
	if( hPrevInstance ) {
		return 1;
	}

#ifdef DEDICATED_ONLY
	if( !GetModuleFileName( NULL, currentDirectory, sizeof( currentDirectory ) - 1 ) ) {
		return 1;
	}
	currentDirectory[sizeof( currentDirectory ) - 1] = 0;
	{
		char *p = strrchr( currentDirectory, '\\' );
		if( p ) {
			*p = 0;
		}
		if( !SetCurrentDirectory( currentDirectory ) ) {
			return 1;
		}
	}
#else
	if( !GetCurrentDirectory( sizeof( currentDirectory ) - 1, currentDirectory ) ) {
		return 1;
	}
	currentDirectory[sizeof( currentDirectory ) - 1] = 0;
#endif

	hGlobalInstance = hInstance;

#ifdef USE_DBGHELP
#ifdef _MSC_VER
	__try {
#else
	__try1( Sys_ExceptionHandler );
#endif
#endif /* USE_DBGHELP */

	Qcommon_Init( lpCmdLine );

	/* main program loop */
	while( !shouldExit ) {
		Qcommon_Frame();
	}

	Com_Quit();

#ifdef USE_DBGHELP
#ifdef _MSC_VER
	} __except( Sys_ExceptionHandler( GetExceptionCode(), GetExceptionInformation() ) ) {
		return 1;
	}
#else
	__except1;
#endif
#endif /* USE_DBGHELP */

	// never gets here
    return 0;
}

#ifdef DEDICATED_ONLY

static VOID WINAPI ServiceHandler( DWORD fdwControl ) {
	if( fdwControl != SERVICE_CONTROL_STOP ) {
		return;
	}

	Com_Quit();

	serviceStatus.dwCurrentState = SERVICE_STOPPED;
	serviceStatus.dwControlsAccepted = 0;
	SetServiceStatus( statusHandle, &serviceStatus );
}


static VOID WINAPI ServiceMain( DWORD argc, LPTSTR *argv ) {
	statusHandle = RegisterServiceCtrlHandler( APPLICATION, ServiceHandler );
	if( !statusHandle ) {
		return;
	}

	memset( &serviceStatus, 0, sizeof( serviceStatus ) );
	serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	serviceStatus.dwCurrentState = SERVICE_RUNNING;
	serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	SetServiceStatus( statusHandle, &serviceStatus );

	WinMain( GetModuleHandle( NULL ), NULL, GetCommandLineA(), 0 );
}

static SERVICE_TABLE_ENTRY serviceTable[] = {
	{ APPLICATION, ServiceMain },
	{ NULL, NULL }
};

/*
==================
main

==================
*/
int main( int argc, char **argv ) {
	int i;

	for( i = 1; i < argc; i++ ) {
		if( !strcmp( argv[i], "-service" ) ) {
			goto service;
		}
	}

	return WinMain( GetModuleHandle( NULL ), NULL, GetCommandLineA(), 0 );

service:
	if( !StartServiceCtrlDispatcher( serviceTable ) ) {
		if( GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT ) {
			return WinMain( GetModuleHandle( NULL ), NULL, GetCommandLineA(), 0 );
		}
		return 1;
	}

	return 0;
}

#endif

#endif

