// win_main.c

#include "../client/client.h"
#include "../qcommon/qcommon.h"
#include "../win32/win_local.h"
#include "../win32/resource.h"
#include <errno.h>
#include <float.h>
#include <fcntl.h>
#include <stdio.h>
#include <direct.h>
#include <io.h>
#include <conio.h>
#include "../qcommon/strip.h"

#define	CD_BASEDIR	"gamedata\\gamedata"
#define	CD_EXE		"jk2sp.exe"
#define	CD_BASEDIR_LINUX	"bin\\x86\\glibc-2.1"
#define	CD_EXE_LINUX "jk2sp"
#define MEM_THRESHOLD 96*1024*1024

static char		sys_cmdline[MAX_STRING_CHARS];
clientStatic_t	cls;

// enable this for executable checksumming
#ifdef FINAL_BUILD
#define SPANK_MONKEYS
#endif
static int	sys_monkeySpank;
static int	sys_checksum;


static unsigned	busyCount = 0;
static bool		otherTasksRunning = false;
unsigned		otherTaskTime = 0;

#pragma optimize("", off)

void busyFunction(void)
{
	float	a = MEM_THRESHOLD;
	float	b = 9343;
	short	c = 4;

	while((int)b > (float)(b-1))
	{
		a = a + b / c;
		busyCount++;
	}
}

void CheckProcessTime(void)
{
	HANDLE		threadHandle;
	int			threadId;
	FILETIME	creationTime;		// thread creation time
	FILETIME	exitTime;			// thread exit time
	FILETIME	kernelTime;			// thread kernel-mode time
	FILETIME	userTime;			// thread user-mode time
//	char		temp[1024];

	busyCount = 0;
	threadHandle = CreateThread(
	   NULL,	// LPSECURITY_ATTRIBUTES lpsa,
	   0,		// DWORD cbStack,
	   (LPTHREAD_START_ROUTINE)busyFunction,	// LPTHREAD_START_ROUTINE lpStartAddr,
	   0,			// LPVOID lpvThreadParm,
	   CREATE_SUSPENDED,			//   DWORD fdwCreate,
	   (unsigned long *)&threadId );

	SetThreadPriority(threadHandle, THREAD_PRIORITY_IDLE);
	ResumeThread(threadHandle);
	while(busyCount < 10)
	{
		Sleep(100);
	}
	Sleep(1000);

	TerminateThread(threadHandle, 0);
	GetThreadTimes(threadHandle, &creationTime, &exitTime, &kernelTime, &userTime);
	CloseHandle(threadHandle);

//	sprintf(temp, "Time = %u\n", userTime.dwLowDateTime);
//	OutputDebugString(temp);

	otherTaskTime = userTime.dwLowDateTime;
	if (userTime.dwLowDateTime < 7500000)
	{
		otherTasksRunning = true;
//		OutputDebugString("WARNING: possibly running on a system with another task\n");
	}
}

#pragma optimize("", on)


void *Sys_GetBotAIAPI (void *parms ) {
	return NULL;
}

void Conbuf_AppendText( const char *pMsg )
{
	printf(pMsg);
}

/*
==================
Sys_LowPhysicalMemory()
==================
*/

qboolean Sys_LowPhysicalMemory() {
	MEMORYSTATUS stat;
  GlobalMemoryStatus (&stat);
	return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse;
}

/*
==================
Sys_FunctionCmp
==================
*/
int Sys_FunctionCmp(void *f1, void *f2) {

	int i, j, l;
	byte func_end[32] = {0xC3, 0x90, 0x90, 0x00};
	byte *ptr, *ptr2;
	byte *f1_ptr, *f2_ptr;

	ptr = (byte *) f1;
	if (*(byte *)ptr == 0xE9) {
		//Com_Printf("f1 %p1 jmp %d\n", (int *) f1, *(int*)(ptr+1));
		f1_ptr = (byte*)(((byte*)f1) + (*(int *)(ptr+1)) + 5);
	}
	else {
		f1_ptr = ptr;
	}
	//Com_Printf("f1 ptr %p\n", f1_ptr);

	ptr = (byte *) f2;
	if (*(byte *)ptr == 0xE9) {
		//Com_Printf("f2 %p jmp %d\n", (int *) f2, *(int*)(ptr+1));
		f2_ptr = (byte*)(((byte*)f2) + (*(int *)(ptr+1)) + 5);
	}
	else {
		f2_ptr = ptr;
	}
	//Com_Printf("f2 ptr %p\n", f2_ptr);

#ifdef _DEBUG
	sprintf((char *)func_end, "%c%c%c%c%c%c%c", 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3);
#endif
	for (i = 0; i < 1024; i++) {
		for (j = 0; func_end[j]; j++) {
			if (f1_ptr[i+j] != func_end[j])
				break;
		}
		if (!func_end[j]) {
			break;
		}
	}
#ifdef _DEBUG
	l = i + 7;
#else
	l = i + 2;
#endif
	//Com_Printf("function length = %d\n", l);

	for (i = 0; i < l; i++) {
		// check for a potential function call
		if (*((byte *) &f1_ptr[i]) == 0xE8) {
			// get the function pointers in case this really is a function call
			ptr = (byte *) (((byte *) &f1_ptr[i]) + (*(int *) &f1_ptr[i+1])) + 5;
			ptr2 = (byte *) (((byte *) &f2_ptr[i]) + (*(int *) &f2_ptr[i+1])) + 5;
			// if it was a function call and both f1 and f2 call the same function
			if (ptr == ptr2) {
				i += 4;
				continue;
			}
		}
		if (f1_ptr[i] != f2_ptr[i])
			return qfalse;
	}
	return qtrue;
}

/*
==================
Sys_FunctionCheckSum
==================
*/
int Sys_FunctionCheckSum(void *f1) {

	int i, j, l;
	byte func_end[32] = {0xC3, 0x90, 0x90, 0x00};
	byte *ptr;
	byte *f1_ptr;

	ptr = (byte *) f1;
	if (*(byte *)ptr == 0xE9) {
		//Com_Printf("f1 %p1 jmp %d\n", (int *) f1, *(int*)(ptr+1));
		f1_ptr = (byte*)(((byte*)f1) + (*(int *)(ptr+1)) + 5);
	}
	else {
		f1_ptr = ptr;
	}
	//Com_Printf("f1 ptr %p\n", f1_ptr);

#ifdef _DEBUG
	sprintf((char *)func_end, "%c%c%c%c%c%c%c", 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3);
#endif
	for (i = 0; i < 1024; i++) {
		for (j = 0; func_end[j]; j++) {
			if (f1_ptr[i+j] != func_end[j])
				break;
		}
		if (!func_end[j]) {
			break;
		}
	}
#ifdef _DEBUG
	l = i + 7;
#else
	l = i + 2;
#endif
	//Com_Printf("function length = %d\n", l);
	return Com_BlockChecksum( f1_ptr, l );
}

/*
==================
Sys_MonkeyShouldBeSpanked
==================
*/
int Sys_MonkeyShouldBeSpanked( void ) {
	return sys_monkeySpank;
}

/*
==================
Sys_CodeInMemoryChecksum
==================
*/
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (addValue) )

int Sys_CodeInMemoryChecksum( void *codeBase ) {
	PIMAGE_DOS_HEADER dosHeader;
	PIMAGE_NT_HEADERS pNTHeader;
	PIMAGE_SECTION_HEADER section;

	dosHeader = (PIMAGE_DOS_HEADER)codeBase;
	pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader, dosHeader->e_lfanew );

	// First, verify that the e_lfanew field gave us a reasonable
	// pointer, then verify the PE signature.
	if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ||
	     pNTHeader->Signature != IMAGE_NT_SIGNATURE )
	{
		//printf("Unhandled EXE type, or invalid .EXE\n");
		return 0;
	}
	// first section oughta be the code section
	section = (PIMAGE_SECTION_HEADER)(pNTHeader+1);
	/*
	// the name of the code section should be .text
	if ( Q_stricmp( section->Name, ".text" ) ) {
		return 0;
	}
	*/

	return Com_BlockChecksum( ((byte *) codeBase) + section->VirtualAddress, section->SizeOfRawData );
}

/*
==================
Sys_ChecksumExe
==================
*/

// make sure this string is unique in the executable
//					   01234567890123
byte *exeChecksumId = (unsigned char *)"q3monkeyid\0\0\0\0";

void Sys_ChecksumExe( void *codeBase ) {
	TCHAR szPathOrig[_MAX_PATH], szPathClone[_MAX_PATH];
	STARTUPINFO si;
	TCHAR szCmdLine[512];
	HANDLE hfile, hProcessOrig;
	PROCESS_INFORMATION pi;
	int l, i, n;
	FILE *f;
	byte *buf, *ptr;

	// Is this the original EXE or the clone EXE?
	if ( Q_stricmp(__argv[1], "monkey") ) {
		// Original EXE: Spawn clone EXE to delete this EXE

		GetModuleFileName(NULL, szPathOrig, _MAX_PATH);
		GetTempPath(_MAX_PATH, szPathClone);
		GetTempFileName(szPathClone, __TEXT("Del"), 0, szPathClone); 
		CopyFile(szPathOrig, szPathClone, FALSE);

		// Open the clone EXE using FILE_FLAG_DELETE_ON_CLOSE
		hfile = CreateFile(szPathClone, 0, FILE_SHARE_READ, NULL,   	                       
								OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
		// Spawn the clone EXE passing it our EXE's process handle
		// and the full path name to the original EXE file.
		hProcessOrig = OpenProcess(SYNCHRONIZE, TRUE,
									GetCurrentProcessId());
									wsprintf(szCmdLine, __TEXT("%s monkey %d %d \"%s\""), szPathClone,
												sys_checksum, hProcessOrig, szPathOrig);
		ZeroMemory(&si, sizeof(si));
		si.cb = sizeof(si);
		CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
		CloseHandle(hProcessOrig);
		CloseHandle(hfile);
	} else {
		// Clone EXE: When original EXE terminates, overwrite it with a new one
		sys_checksum = atoi( __argv[2] );
		hProcessOrig = (HANDLE) atoi( __argv[3] );
		WaitForSingleObject(hProcessOrig, INFINITE);
		CloseHandle(hProcessOrig);
		// open the original executable
		f = fopen( __argv[4], "rb" );
		if ( !f ) {
			return;
		}
		fseek (f, 0, SEEK_END);
		l = ftell (f);
		fseek (f, 0, SEEK_SET);
		buf = (unsigned char *)malloc(l);
		if ( fread(buf, l, 1, f) != 1 ) {
			return;
		}
		fclose(f);
		// search for the exe name string, nice brute force
		n = strlen((const char *)exeChecksumId);
		for ( i = 0; i < l; i++ ) {
			if ( !Q_strncmp((const char *)(buf + i), (const char *)exeChecksumId, n) ) {
				break;
			}
		}
		if ( i >= l ) {
			return;
		}
		ptr = buf + i;
		// write checksum into exe memory image
		ptr[0] = (sys_checksum >> 24) & 0xFF;
		ptr[1] = (sys_checksum >> 16) & 0xFF;
		ptr[2] = (sys_checksum >>  8) & 0xFF;
		ptr[3] = (sys_checksum >>  0) & 0xFF;
		ptr[4] = ptr[5] = ptr[6] = ptr[7] = ptr[8] = ptr[9] = 0;
		// write out new exe with checksum
		f = fopen( __argv[4], "wb" );
		if ( !f ) {
			return;
		}
		if ( fwrite(buf, l, 1, f) != 1 ) {
			return;
		}
		fclose(f);
		free(buf);
		// The system will delete the clone EXE automatically 
		// because it was opened with FILE_FLAG_DELETE_ON_CLOSE
	}
	//
	exit(0);
}

/*
==================
Sys_VerifyCodeChecksum
==================
*/
void Sys_VerifyCodeChecksum( void *codeBase ) {
	// NOTE: should not checksum code in debug mode because the memory image changes
	//		 as soon as you set a break point!
#if defined(SPANK_MONKEYS) && !defined(_DEBUG)
	int exeChecksum;

	// if the checksum is not yet stored in the executable
	if ( exeChecksumId[4] != 0 ) {
		// spawn another process that will replace this executable with one that has a checksum
		Sys_ChecksumExe( codeBase );
		return;
	}

	exeChecksum = (exeChecksumId[0] << 24) | (exeChecksumId[1] << 16) | (exeChecksumId[2] << 8) | exeChecksumId[3];
	if ( exeChecksum != sys_checksum ) {
		sys_monkeySpank = qtrue;
	}
#endif
}

//qboolean stdin_active = qtrue;
char *Sys_ConsoleInput(void)
{
    static char text[256];
    static int     len=0;
	fd_set	fdset;
    struct timeval timeout;

//	if (!com_dedicated || !com_dedicated->value)
//		return NULL;

//	if (!stdin_active)
//		return NULL;

//	FD_ZERO(&fdset);
//	FD_SET(0, &fdset); // stdin
//	timeout.tv_sec = 0;
//	timeout.tv_usec = 0;
//	if (select (1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(0, &fdset))
//		return NULL;

	if (!kbhit()) return NULL;
	if (len == 0) memset(text,0,sizeof(text));

	text[len] = getch();
	
	switch (text[len])
	{
	case 8: // backspace
		printf("%c %c",text[len],text[len]);
		text[len] = 0;
		if (len > 0) len--;
		text[len] = 0;
		break;
	case 27: // esc	
		// clear the line
		len = 0;
		printf("\n");
		break;
	case '\r':
		printf("\n");
		text[len] = 0;
		len = 0;
		return text;
	default:
		printf("%c",text[len]);
		len++;
		if (len == sizeof(text))
		{
			len = 0;
			return text;
		}
		break;
	}
//	len = read (0, text, sizeof(text));
//	if (len == 0) { // eof!
//		stdin_active = qfalse;
//		return NULL;
//	}

//	if (len < 1)
//		return NULL;
//	len = strlen(text);
//	text[len-1] = 0;    // rip off the /n and terminate

	return NULL;
}

/*
==================
Sys_BeginProfiling
==================
*/
void Sys_BeginProfiling( void ) {
	// this is just used on the mac build
}

void Sys_ShowConsole( int visLevel, qboolean quitOnClose )
{
}

/*
=============
Sys_Error

Show the early console as an error dialog
=============
*/
void QDECL Sys_Error( const char *error, ... ) {
	va_list		argptr;
	char		text[4096];
    MSG        msg;

	va_start (argptr, error);
	vsprintf (text, error, argptr);
	va_end (argptr);

	Conbuf_AppendText( text );
	Conbuf_AppendText( "\n" );

//	Sys_SetErrorText( text );
	Sys_ShowConsole( 1, qtrue );

	timeEndPeriod( 1 );

	IN_Shutdown();

	// wait for the user to quit
	while ( 1 ) {
		if (!GetMessage (&msg, NULL, 0, 0))
			Com_Quit_f ();
		TranslateMessage (&msg);
      	DispatchMessage (&msg);
	}

//	Sys_DestroyConsole();
	Com_ShutdownZoneMemory();
// 	Com_ShutdownHunkMemory();

	exit (1);
}

/*
==============
Sys_Quit
==============
*/
void Sys_Quit( void ) {
	timeEndPeriod( 1 );
	IN_Shutdown();
//	Sys_DestroyConsole();
	Com_ShutdownZoneMemory();
// 	Com_ShutdownHunkMemory();

	exit (0);
}

/*
==============
Sys_Print
==============
*/
void Sys_Print( const char *msg ) {
	Conbuf_AppendText( msg );
}


/*
==============
Sys_Mkdir
==============
*/
void Sys_Mkdir( const char *path ) {
	_mkdir (path);
}

/*
==============
Sys_Cwd
==============
*/
char *Sys_Cwd( void ) {
	static char cwd[MAX_OSPATH];

	_getcwd( cwd, sizeof( cwd ) - 1 );
	cwd[MAX_OSPATH-1] = 0;

	return cwd;
}

/*
==============
Sys_DefaultCDPath
==============
*/
char *Sys_DefaultCDPath( void ) {
	return "";
}

/*
==============
Sys_DefaultBasePath
==============
*/
char *Sys_DefaultBasePath( void ) {
	return Sys_Cwd();
}

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

DIRECTORY SCANNING

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

#define	MAX_FOUND_FILES	0x1000

void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **psList, int *numfiles ) {
	char		search[MAX_OSPATH], newsubdirs[MAX_OSPATH];
	char		filename[MAX_OSPATH];
	int			findhandle;
	struct _finddata_t findinfo;

	if ( *numfiles >= MAX_FOUND_FILES - 1 ) {
		return;
	}

	if (strlen(subdirs)) {
		Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs );
	}
	else {
		Com_sprintf( search, sizeof(search), "%s\\*", basedir );
	}

	findhandle = _findfirst (search, &findinfo);
	if (findhandle == -1) {
		return;
	}

	do {
		if (findinfo.attrib & _A_SUBDIR) {
			if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) {
				if (strlen(subdirs)) {
					Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name);
				}
				else {
					Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name);
				}
				Sys_ListFilteredFiles( basedir, newsubdirs, filter, psList, numfiles );
			}
		}
		if ( *numfiles >= MAX_FOUND_FILES - 1 ) {
			break;
		}
		Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name );
		if (!Com_FilterPath( filter, filename, qfalse ))
			continue;
		psList[ *numfiles ] = CopyString( filename );
		(*numfiles)++;
	} while ( _findnext (findhandle, &findinfo) != -1 );

	_findclose (findhandle);
}

static qboolean strgtr(const char *s0, const char *s1) {
	int l0, l1, i;

	l0 = strlen(s0);
	l1 = strlen(s1);

	if (l1<l0) {
		l0 = l1;
	}

	for(i=0;i<l0;i++) {
		if (s1[i] > s0[i]) {
			return qtrue;
		}
		if (s1[i] < s0[i]) {
			return qfalse;
		}
	}
	return qfalse;
}

char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ) {
	char		search[MAX_OSPATH];
	int			nfiles;
	char		**listCopy;
	char		*list[MAX_FOUND_FILES];
	struct _finddata_t findinfo;
	int			findhandle;
	int			flag;
	int			i;

	if (filter) {

		nfiles = 0;
		Sys_ListFilteredFiles( directory, "", filter, list, &nfiles );

		list[ nfiles ] = 0;
		*numfiles = nfiles;

		if (!nfiles)
			return NULL;

		listCopy = (char **)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_FILESYS );
		for ( i = 0 ; i < nfiles ; i++ ) {
			listCopy[i] = list[i];
		}
		listCopy[i] = NULL;

		return listCopy;
	}

	if ( !extension) {
		extension = "";
	}

	// passing a slash as extension will find directories
	if ( extension[0] == '/' && extension[1] == 0 ) {
		extension = "";
		flag = 0;
	} else {
		flag = _A_SUBDIR;
	}

	Com_sprintf( search, sizeof(search), "%s\\*%s", directory, extension );

	// search
	nfiles = 0;

	findhandle = _findfirst (search, &findinfo);
	if (findhandle == -1) {
		*numfiles = 0;
		return NULL;
	}

	do {
		if ( (!wantsubs && flag ^ ( findinfo.attrib & _A_SUBDIR )) || (wantsubs && findinfo.attrib & _A_SUBDIR) ) {
			if ( nfiles == MAX_FOUND_FILES - 1 ) {
				break;
			}
			list[ nfiles ] = CopyString( findinfo.name );
			nfiles++;
		}
	} while ( _findnext (findhandle, &findinfo) != -1 );

	list[ nfiles ] = 0;

	_findclose (findhandle);

	// return a copy of the list
	*numfiles = nfiles;

	if ( !nfiles ) {
		return NULL;
	}

	listCopy = (char **)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_FILESYS );
	for ( i = 0 ; i < nfiles ; i++ ) {
		listCopy[i] = list[i];
	}
	listCopy[i] = NULL;

	do {
		flag = 0;
		for(i=1; i<nfiles; i++) {
			if (strgtr(listCopy[i-1], listCopy[i])) {
				char *temp = listCopy[i];
				listCopy[i] = listCopy[i-1];
				listCopy[i-1] = temp;
				flag = 1;
			}
		}
	} while(flag);

	return listCopy;
}

void	Sys_FreeFileList( char **psList ) {
	int		i;

	if ( !psList ) {
		return;
	}

	for ( i = 0 ; psList[i] ; i++ ) {
		Z_Free( psList[i] );
	}

	Z_Free( psList );
}

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


/*
================
Sys_ScanForCD

Search all the drives to see if there is a valid CD to grab
the cddir from
================
*/
#ifdef FINAL_BUILD
static qboolean Sys_ScanForCD( void ) {
	char		drive[4];
	FILE		*f;
	char		test[MAX_OSPATH];

	drive[0] = 'c';
	drive[1] = ':';
	drive[2] = '\\';
	drive[3] = 0;

	// scan the drives
	for ( drive[0] = 'c' ; drive[0] <= 'z' ; drive[0]++ ) {
		if ( GetDriveType (drive) == DRIVE_CDROM ) {
			BOOL Result;
			char VolumeName[MAX_PATH],FileSystemName[MAX_PATH];
			DWORD VolumeSerialNumber,MaximumComponentLength,FileSystemFlags;
			
			Result = GetVolumeInformation(drive,VolumeName,sizeof(VolumeName),&VolumeSerialNumber,
				&MaximumComponentLength,&FileSystemFlags,FileSystemName,sizeof(FileSystemName));
			
			if (Result && (strcmpi(VolumeName,"JEDIOUTCAST") == 0 ) )
			{
				sprintf (test, "%s%s\\%s",drive, CD_BASEDIR, CD_EXE);
				f = fopen( test, "r" );
				if ( f ) {
					fclose (f);
					return qtrue;
				} else {
					sprintf(test, "%s%s\\%s", drive, CD_BASEDIR, CD_EXE_LINUX);
					f = fopen( test, "r" );
					if ( f ) {
						fclose (f);
						return qtrue;
					}
				}
			}
		}
	}

	return qfalse;
}
#endif
/*
================
Sys_CheckCD

Return true if the proper CD is in the drive
================
*/
qboolean	Sys_CheckCD( void ) {
#ifdef FINAL_BUILD
	return Sys_ScanForCD();
#else
	return qtrue;
#endif
}


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

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

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

		if ( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != 0 ) {
			if ( ( cliptext = (char *)GlobalLock( hClipboardData ) ) != 0 ) {
				data = (char *)Z_Malloc( GlobalSize( hClipboardData ) + 1, TAG_CLIPBOARD);
				Q_strncpyz( data, cliptext, GlobalSize( hClipboardData )+1 );
				GlobalUnlock( hClipboardData );
				
				strtok( data, "\n\r\b" );
			}
		}
		CloseClipboard();
	}
	return data;
}


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

LOAD/UNLOAD DLL

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

/*
=================
Sys_UnloadDll

=================
*/
void Sys_UnloadDll( void *dllHandle ) {
	if ( !dllHandle ) {
		return;
	}
	if ( !FreeLibrary( (struct HINSTANCE__ *)dllHandle ) ) {
		Com_Error (ERR_FATAL, "Sys_UnloadDll FreeLibrary failed");
	}
}

/*
=================
Sys_LoadDll

Used to load a development dll instead of a virtual machine
=================
*/
extern char		*FS_BuildOSPath( const char *base, const char *game, const char *qpath );

void * QDECL Sys_LoadDll( const char *name, int (QDECL **entryPoint)(int, ...),
				  int (QDECL *systemcalls)(int, ...) ) {
	static int	lastWarning = 0;
	HINSTANCE	libHandle;
	void	(QDECL *dllEntry)( int (QDECL *syscallptr)(int, ...) );
	char	*basepath;
	char	*cdpath;
	char	*gamedir;
	char	*fn;
#ifdef NDEBUG
	int		timestamp;
  int   ret;
#endif
	char	filename[MAX_QPATH];

	Com_sprintf( filename, sizeof( filename ), "%sx86.dll", name );

#ifdef NDEBUG
	timestamp = Sys_Milliseconds();
	if( ((timestamp - lastWarning) > (5 * 60000)) && !Cvar_VariableIntegerValue( "dedicated" )
		&& !Cvar_VariableIntegerValue( "com_blindlyLoadDLLs" ) ) {
		if (FS_FileExists(filename)) {
			lastWarning = timestamp;
			ret = MessageBoxEx( NULL, "You are about to load a .DLL executable that\n"
				  "has not been verified for use with Quake III Arena.\n"
				  "This type of file can compromise the security of\n"
				  "your computer.\n\n"
				  "Select 'OK' if you choose to load it anyway.",
				  "Security Warning", MB_OKCANCEL | MB_ICONEXCLAMATION | MB_DEFBUTTON2 | MB_TOPMOST | MB_SETFOREGROUND,
				  MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ) );
			if( ret != IDOK ) {
				return NULL;
			}
		}
	}
#endif


// rjr disable for final release #ifndef NDEBUG
	libHandle = LoadLibrary( filename );
	if ( !libHandle ) {
//#endif
	basepath = Cvar_VariableString( "fs_basepath" );
	cdpath = Cvar_VariableString( "fs_cdpath" );
	gamedir = Cvar_VariableString( "fs_game" );

	fn = FS_BuildOSPath( basepath, gamedir, filename );
	libHandle = LoadLibrary( fn );

	if ( !libHandle ) {
		if( cdpath[0] ) {
			fn = FS_BuildOSPath( cdpath, gamedir, filename );
			libHandle = LoadLibrary( fn );
		}

		if ( !libHandle ) {
			return NULL;
		}
	}
//#ifndef NDEBUG
	}
//#endif

	dllEntry = ( void (QDECL *)( int (QDECL *)( int, ... ) ) )GetProcAddress( libHandle, "dllEntry" ); 
	*entryPoint = (int (QDECL *)(int,...))GetProcAddress( libHandle, "vmMain" );
	if ( !*entryPoint || !dllEntry ) {
		FreeLibrary( libHandle );
		return NULL;
	}
	dllEntry( systemcalls );

	return libHandle;
}


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

BACKGROUND FILE STREAMING

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

#if 1

void Sys_InitStreamThread( void ) {
}

void Sys_ShutdownStreamThread( void ) {
}

void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) {
}

void Sys_EndStreamedFile( fileHandle_t f ) {
}

int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) {
   return FS_Read( buffer, size * count, f );
}

void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) {
   FS_Seek( f, offset, origin );
}


#else

typedef struct {
	fileHandle_t	file;
	byte	*buffer;
	qboolean	eof;
	qboolean	active;
	int		bufferSize;
	int		streamPosition;	// next byte to be returned by Sys_StreamRead
	int		threadPosition;	// next byte to be read from file
} streamsIO_t;

typedef struct {
	HANDLE				threadHandle;
	int					threadId;
	CRITICAL_SECTION	crit;
	streamsIO_t			sIO[MAX_FILE_HANDLES];
} streamState_t;

streamState_t	stream;

/*
===============
Sys_StreamThread

A thread will be sitting in this loop forever
================
*/
void Sys_StreamThread( void ) {
	int		buffer;
	int		count;
	int		readCount;
	int		bufferPoint;
	int		r, i;

	while (1) {
		Sleep( 10 );
//		EnterCriticalSection (&stream.crit);

		for (i=1;i<MAX_FILE_HANDLES;i++) {
			// if there is any space left in the buffer, fill it up
			if ( stream.sIO[i].active  && !stream.sIO[i].eof ) {
				count = stream.sIO[i].bufferSize - (stream.sIO[i].threadPosition - stream.sIO[i].streamPosition);
				if ( !count ) {
					continue;
				}

				bufferPoint = stream.sIO[i].threadPosition % stream.sIO[i].bufferSize;
				buffer = stream.sIO[i].bufferSize - bufferPoint;
				readCount = buffer < count ? buffer : count;

				r = FS_Read( stream.sIO[i].buffer + bufferPoint, readCount, stream.sIO[i].file );
				stream.sIO[i].threadPosition += r;

				if ( r != readCount ) {
					stream.sIO[i].eof = qtrue;
				}
			}
		}
//		LeaveCriticalSection (&stream.crit);
	}
}

/*
===============
Sys_InitStreamThread

================
*/
void Sys_InitStreamThread( void ) {
	int i;

	InitializeCriticalSection ( &stream.crit );

	// don't leave the critical section until there is a
	// valid file to stream, which will cause the StreamThread
	// to sleep without any overhead
//	EnterCriticalSection( &stream.crit );

	stream.threadHandle = CreateThread(
	   NULL,	// LPSECURITY_ATTRIBUTES lpsa,
	   0,		// DWORD cbStack,
	   (LPTHREAD_START_ROUTINE)Sys_StreamThread,	// LPTHREAD_START_ROUTINE lpStartAddr,
	   0,			// LPVOID lpvThreadParm,
	   0,			//   DWORD fdwCreate,
	   &stream.threadId);
	for(i=0;i<MAX_FILE_HANDLES;i++) {
		stream.sIO[i].active = qfalse;
	}
}

/*
===============
Sys_ShutdownStreamThread

================
*/
void Sys_ShutdownStreamThread( void ) {
}


/*
===============
Sys_BeginStreamedFile

================
*/
void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) {
	if ( stream.sIO[f].file ) {
		Sys_EndStreamedFile( stream.sIO[f].file );
	}

	stream.sIO[f].file = f;
	stream.sIO[f].buffer = Z_Malloc( readAhead );
	stream.sIO[f].bufferSize = readAhead;
	stream.sIO[f].streamPosition = 0;
	stream.sIO[f].threadPosition = 0;
	stream.sIO[f].eof = qfalse;
	stream.sIO[f].active = qtrue;

	// let the thread start running
//	LeaveCriticalSection( &stream.crit );
}

/*
===============
Sys_EndStreamedFile

================
*/
void Sys_EndStreamedFile( fileHandle_t f ) {
	if ( f != stream.sIO[f].file ) {
		Com_Error( ERR_FATAL, "Sys_EndStreamedFile: wrong file");
	}
	// don't leave critical section until another stream is started
	EnterCriticalSection( &stream.crit );

	stream.sIO[f].file = 0;
	stream.sIO[f].active = qfalse;

	Z_Free( stream.sIO[f].buffer );

	LeaveCriticalSection( &stream.crit );
}


/*
===============
Sys_StreamedRead

================
*/
int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) {
	int		available;
	int		remaining;
	int		sleepCount;
	int		copy;
	int		bufferCount;
	int		bufferPoint;
	byte	*dest;

	if (stream.sIO[f].active == qfalse) {
		Com_Error( ERR_FATAL, "Streamed read with non-streaming file" );
	}

	dest = (byte *)buffer;
	remaining = size * count;

	if ( remaining <= 0 ) {
		Com_Error( ERR_FATAL, "Streamed read with non-positive size" );
	}

	sleepCount = 0;
	while ( remaining > 0 ) {
		available = stream.sIO[f].threadPosition - stream.sIO[f].streamPosition;
		if ( !available ) {
			if ( stream.sIO[f].eof ) {
				break;
			}
			if ( sleepCount == 1 ) {
				Com_DPrintf( "Sys_StreamedRead: waiting\n" );
			}
			if ( ++sleepCount > 100 ) {
				Com_Error( ERR_FATAL, "Sys_StreamedRead: thread has died");
			}
			Sleep( 10 );
			continue;
		}

		EnterCriticalSection( &stream.crit );

		bufferPoint = stream.sIO[f].streamPosition % stream.sIO[f].bufferSize;
		bufferCount = stream.sIO[f].bufferSize - bufferPoint;

		copy = available < bufferCount ? available : bufferCount;
		if ( copy > remaining ) {
			copy = remaining;
		}
		memcpy( dest, stream.sIO[f].buffer + bufferPoint, copy );
		stream.sIO[f].streamPosition += copy;
		dest += copy;
		remaining -= copy;

		LeaveCriticalSection( &stream.crit );
	}

	return (count * size - remaining) / size;
}

/*
===============
Sys_StreamSeek

================
*/
void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) {

	// halt the thread
	EnterCriticalSection( &stream.crit );

	// clear to that point
	FS_Seek( f, offset, origin );
	stream.sIO[f].streamPosition = 0;
	stream.sIO[f].threadPosition = 0;
	stream.sIO[f].eof = qfalse;

	// let the thread start running at the new position
	LeaveCriticalSection( &stream.crit );
}

#endif

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

EVENT LOOP

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

#define	MAX_QUED_EVENTS		256
#define	MASK_QUED_EVENTS	( MAX_QUED_EVENTS - 1 )

sysEvent_t	eventQue[MAX_QUED_EVENTS];
int			eventHead, eventTail;
byte		sys_packetReceived[MAX_MSGLEN];

/*
================
Sys_QueEvent

A time of 0 will get the current time
Ptr should either be null, or point to a block of data that can
be freed by the game later.
================
*/
void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) {
	sysEvent_t	*ev;

	ev = &eventQue[ eventHead & MASK_QUED_EVENTS ];
	if ( eventHead - eventTail >= MAX_QUED_EVENTS ) {
		Com_Printf("Sys_QueEvent: overflow\n");
		// we are discarding an event, but don't leak memory
		if ( ev->evPtr ) {
			Z_Free( ev->evPtr );
		}
		eventTail++;
	}

	eventHead++;

	if ( time == 0 ) {
		time = Sys_Milliseconds();
	}

	ev->evTime = time;
	ev->evType = type;
	ev->evValue = value;
	ev->evValue2 = value2;
	ev->evPtrLength = ptrLength;
	ev->evPtr = ptr;
}

/*
================
Sys_GetEvent

================
*/
sysEvent_t Sys_GetEvent( void ) {
    MSG			msg;
	sysEvent_t	ev;
	char		*s;
	msg_t		netmsg;
	netadr_t	adr;

	// return if we have data
	if ( eventHead > eventTail ) {
		eventTail++;
		return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ];
	}

	// pump the message loop
	while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
		if ( !GetMessage (&msg, NULL, 0, 0) ) {
			Com_Quit_f();
		}

		// save the msg time, because wndprocs don't have access to the timestamp
//		g_wv.sysMsgTime = msg.time;

		TranslateMessage (&msg);
      	DispatchMessage (&msg);
	}

	// check for console commands
	s = Sys_ConsoleInput();
	if ( s ) {
		char	*b;
		int		len;

		len = strlen( s ) + 1;
		b = (char *)Z_Malloc( len, TAG_EVENT );
		Q_strncpyz( b, s, len );
		Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b );
	}

	// check for network packets
	MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) );
	if ( Sys_GetPacket ( &adr, &netmsg ) ) {
		netadr_t		*buf;
		int				len;

		// copy out to a seperate buffer for qeueing
		// the readcount stepahead is for SOCKS support
		len = sizeof( netadr_t ) + netmsg.cursize - netmsg.readcount;
		buf = (netadr_t *)Z_Malloc( len, TAG_EVENT, qtrue );
		*buf = adr;
		memcpy( buf+1, &netmsg.data[netmsg.readcount], netmsg.cursize - netmsg.readcount );
		Sys_QueEvent( 0, SE_PACKET, 0, 0, len, buf );
	}

	// return if we have data
	if ( eventHead > eventTail ) {
		eventTail++;
		return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ];
	}

	// create an empty event to return

	memset( &ev, 0, sizeof( ev ) );
	ev.evTime = timeGetTime();

	return ev;
}

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

/*
=================
Sys_In_Restart_f

Restart the input subsystem
=================
*/
void Sys_In_Restart_f( void ) {
	IN_Shutdown();
	IN_Init();
}


/*
=================
Sys_Net_Restart_f

Restart the network subsystem
=================
*/
void Sys_Net_Restart_f( void ) {
	NET_Restart();
}


/*
================
Sys_Init

Called after the common systems (cvars, files, etc)
are initialized
================
*/
#define OSR2_BUILD_NUMBER 1111
#define WIN98_BUILD_NUMBER 1998

void Sys_Init( void ) {
	int cpuid;

	// make sure the timer is high precision, otherwise
	// NT gets 18ms resolution
	timeBeginPeriod( 1 );

	Cmd_AddCommand ("in_restart", Sys_In_Restart_f);
	Cmd_AddCommand ("net_restart", Sys_Net_Restart_f);

//	g_wv.osversion.dwOSVersionInfoSize = sizeof( g_wv.osversion );

//	if (!GetVersionEx (&g_wv.osversion))
//		Sys_Error ("Couldn't get OS info");

//	if (g_wv.osversion.dwMajorVersion < 4)
//		Sys_Error ("This game requires Windows version 4 or greater");
//	if (g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32s)
//		Sys_Error ("This game doesn't run on Win32s");

//	if ( g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32_NT )
//	{
//		Cvar_Set( "arch", "winnt" );
//	}
//	else if ( g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
//	{
//		if ( LOWORD( g_wv.osversion.dwBuildNumber ) >= WIN98_BUILD_NUMBER )
//		{
//			Cvar_Set( "arch", "win98" );
//		}
//		else if ( LOWORD( g_wv.osversion.dwBuildNumber ) >= OSR2_BUILD_NUMBER )
//		{
//			Cvar_Set( "arch", "win95 osr2.x" );
//		}
//		else
//		{
//			Cvar_Set( "arch", "win95" );
//		}
//	}
//	else
//	{
//		Cvar_Set( "arch", "unknown Windows variant" );
//	}

	// save out a couple things in rom cvars for the renderer to access
//	Cvar_Get( "win_hinstance", va("%i", (int)g_wv.hInstance), CVAR_ROM );
//	Cvar_Get( "win_wndproc", va("%i", (int)MainWndProc), CVAR_ROM );

	//
	// figure out our CPU
	//
	Cvar_Get( "sys_cpustring", "detect", 0 );
	if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring"), "detect" ) )
	{
		Com_Printf( "...detecting CPU, found " );

		cpuid = Sys_GetProcessorId();

		switch ( cpuid )
		{
		case CPUID_GENERIC:
			Cvar_Set( "sys_cpustring", "generic" );
			break;
		case CPUID_INTEL_UNSUPPORTED:
			Cvar_Set( "sys_cpustring", "x86 (pre-Pentium)" );
			break;
		case CPUID_INTEL_PENTIUM:
			Cvar_Set( "sys_cpustring", "x86 (P5/PPro, non-MMX)" );
			break;
		case CPUID_INTEL_MMX:
			Cvar_Set( "sys_cpustring", "x86 (P5/Pentium2, MMX)" );
			break;
		case CPUID_INTEL_KATMAI:
			Cvar_Set( "sys_cpustring", "Intel Pentium III" );
			break;
		case CPUID_INTEL_WILLIAMETTE:
			Cvar_Set( "sys_cpustring", "Intel Pentium IV" );
			break;
		case CPUID_AMD_3DNOW:
			Cvar_Set( "sys_cpustring", "AMD w/ 3DNow!" );
			break;
		case CPUID_AXP:
			Cvar_Set( "sys_cpustring", "Alpha AXP" );
			break;
		default:
			Com_Error( ERR_FATAL, "Unknown cpu type %d\n", cpuid );
			break;
		}
	}
	else
	{
		Com_Printf( "...forcing CPU type to " );
		if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "generic" ) )
		{
			cpuid = CPUID_GENERIC;
		}
		else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "x87" ) )
		{
			cpuid = CPUID_INTEL_PENTIUM;
		}
		else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "mmx" ) )
		{
			cpuid = CPUID_INTEL_MMX;
		}
		else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "3dnow" ) )
		{
			cpuid = CPUID_AMD_3DNOW;
		}
		else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "PentiumIII" ) )
		{
			cpuid = CPUID_INTEL_KATMAI;
		}
		else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "PentiumIV" ) )
		{
			cpuid = CPUID_INTEL_WILLIAMETTE;
		}
		else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "axp" ) )
		{
			cpuid = CPUID_AXP;
		}
		else
		{
			Com_Printf( "WARNING: unknown sys_cpustring '%s'\n", Cvar_VariableString( "sys_cpustring" ) );
			cpuid = CPUID_GENERIC;
		}
	}
	Cvar_SetValue( "sys_cpuid", cpuid );
	Com_Printf( "%s\n", Cvar_VariableString( "sys_cpustring" ) );

	Cvar_Set( "username", Sys_GetCurrentUser() );

	IN_Init();		// FIXME: not in dedicated?
}

// do a quick mem test to check for any potential future mem problems...
//
void QuickMemTest(void)
{
//	if (!Sys_LowPhysicalMemory())
	{
		const int iMemTestMegs = 64;	// useful search label
		// special test, 
		void *pvData = malloc(iMemTestMegs * 1024 * 1024);
		if (pvData)
		{
			free(pvData);
		}
		else
		{
			// err...
			//
			LPCSTR psContinue	= "Your machine failed to allocate %dMB in a memory test, which may mean you'll have problems running this game all the way through.\n\nContinue anyway?";
			LPCSTR psNoMem		= "Insufficient memory to run this game!\n";

			switch (Language_GetIntegerValue())
			{
				case SP_LANGUAGE_GERMAN:

					psContinue	= "Ihr Computer konnte bei einem Speichertest keine %dMB reservieren, daher werden Sie mit dem Starten des Spiels Probleme haben.\n\nDennoch fortsetzen?";
					psNoMem		= "Unzureichender Speicher zum Starten!\n";
					break;

				case SP_LANGUAGE_FRENCH:

					psContinue	= "Votre système n'a pu allouer %d Mo lors d'une vérification de la mémoire, ce qui signifie que vous aurez peut-être du mal à faire fonctionner ce jeu. \n\nSouhaitez-vous continuer malgré tout ?";
					psNoMem		= "Mémoire insuffisante pour lancer ce jeu !\n";
					break;
			}

			#define GetYesNo(psQuery)	(!!(MessageBox(NULL,psQuery,"Query",MB_YESNO|MB_ICONWARNING|MB_TASKMODAL)==IDYES))
			if (!GetYesNo(va(psContinue,iMemTestMegs)))
			{
				Com_Error( ERR_FATAL, psNoMem );
			}
		}
	}
}


//=======================================================================
//int	totalMsec, countMsec;

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

==================
*/
//int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
int main(int argc, char **argv)
{
	char		cwd[MAX_OSPATH];
	char *cmdline;
	int i,len;
//	int			startTime, endTime;

    // should never get a previous instance in Win32
//    if ( hPrevInstance ) {
//        return 0;
//	}

//	sys_checksum = Sys_CodeInMemoryChecksum( hInstance );
//	Sys_VerifyCodeChecksum( hInstance );

	// merge the command line, this is kinda silly
	for (len = 1, i = 1; i < argc; i++)
		len += strlen(argv[i]) + 1;
	cmdline = (char *)malloc(len);
	*cmdline = 0;
	for (i = 1; i < argc; i++) {
		if (i > 1)
			strcat(cmdline, " ");
		strcat(cmdline, argv[i]);
	}

//	g_wv.hInstance = hInstance;
//	Q_strncpyz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) );


	// done before Com/Sys_Init since we need this for error output
//	Sys_CreateConsole();

	// no abort/retry/fail errors
	SetErrorMode( SEM_FAILCRITICALERRORS );

	// get the initial time base
	Sys_Milliseconds();

#if 0
	// if we find the CD, add a +set cddir xxx command line
	Sys_ScanForCD();
#endif


	Sys_InitStreamThread();

#if 0
	CheckProcessTime();
#endif 

	Com_Init( cmdline );
#if 0
	Cvar_Set( "com_othertasks", ( otherTasksRunning ? "1" : "0" ) );
	Cvar_Set( "com_othertaskstime", va("%u", otherTaskTime) );
#else
	Cvar_Set( "com_othertasks", "0" );
#endif 

	NET_Init();

	_getcwd (cwd, sizeof(cwd));
	Com_Printf("Working directory: %s\n", cwd);

	// hide the early console since we've reached the point where we
	// have a working graphics subsystems
	if ( !com_dedicated->integer && !com_viewlog->integer ) {
		Sys_ShowConsole( 0, qfalse );
	}

#ifdef _DEBUG
	if ( sys_monkeySpank ) {
		Cvar_Set("cl_trn", "666");
	}
#endif

    // main game loop
	while( 1 ) {
		// if not running as a game client, sleep a bit
//		if ( g_wv.isMinimized || ( com_dedicated && com_dedicated->integer ) ) {
			Sleep( 5 );
//		}

		// set low precision every frame, because some system calls
		// reset it arbitrarily
//		_controlfp( _PC_24, _MCW_PC );
 
//		startTime = Sys_Milliseconds();

		// make sure mouse and joystick are only called once a frame
		IN_Frame();

		// run the game
		Com_Frame();

//		endTime = Sys_Milliseconds();
//		totalMsec += endTime - startTime;
//		countMsec++;
	}

	// never gets here
	return 0;
}


