//-----------------------------------------------------------------------------
//
//  $Logfile:: /win32/sys_win.c                                               $
// $Revision:: 1                                                              $
//   $Author:: Burger                                                         $
//     $Date:: 12/27/00 7:57p                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /win32/sys_win.c                                                    $
// * 
// * 1     12/27/00 7:57p Burger
// 
// 20    3/23/99 5:32p Markd
// Added game host support
// 
// 19    11/12/98 8:47p Markd
// put in check for running from CD gross hack stuff because of Activision's
// stupid launcher KILL KILL KILL
// 
// 18    10/26/98 2:32a Markd
// put in local copy of copy protection support
// 
// 17    10/21/98 6:41p Markd
// fixed scan for cd stuff
// 
// 16    10/20/98 9:16p Markd
// fixed registry stuff and put in deslashify
// 
// 15    10/20/98 2:46a Markd
// Added registry lookup for cddir
// 
// 14    10/18/98 6:41p Markd
// Changed CD Check to NO_CD_CHECK
// 
// 13    10/12/98 12:58p Markd
// preinitialized cd_scan semaphore with false
// 
// 12    10/08/98 8:12p Markd
// put cddir back in
// 
// 11    8/20/98 12:05p Markd
// took out cddir
// 
// 10    8/19/98 6:47p Markd
// Took out scan for cd
// 
// 9     8/02/98 9:01p Markd
// Merged code 3.17
//
// 8     5/24/98 6:34p Jimdose
// Changed char *'s to const char *'s
//
// 7     5/24/98 4:49p Jimdose
// Made char *'s const
//
// 6     1/24/98 5:26p Jimdose
// Eradicated runtime references to Quake
//
// 5     1/22/98 6:41p Jimdose
// Added "gamedll" cvar.  Set gamedll on startup to choose which dll to load.
// This is a temporary solution until player configuration settings are
// created.
//
// 4     12/30/97 6:06p Jimdose
// Added header text
//
// DESCRIPTION:
//

#include <windows.h>
#include <errno.h>
#include <float.h>
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <direct.h>
#include <io.h>
#include <conio.h>
#ifdef   _DEBUG
#include <crtdbg.h>
#endif
#include "qcommon.h"
#include "winquake.h"
#include "resource.h"

// memory debug macros
#ifdef   _DEBUG
#define  SET_CRT_DEBUG_FIELD(a) \
            _CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))
#define  CLEAR_CRT_DEBUG_FIELD(a) \
            _CrtSetDbgFlag(~(a) & _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG))
#define _CRTDBG_MAP_ALLOC
#define _CRTDBG_MAP_ALLOC_NEW /* FS: Added to map C++ new allocs to CRT DBG */
#else
#define  SET_CRT_DEBUG_FIELD(a)   ((void) 0)
#define  CLEAR_CRT_DEBUG_FIELD(a) ((void) 0)
#endif

#define MINIMUM_WIN_MEMORY	0x0a00000
#define MAXIMUM_WIN_MEMORY	0x1000000

//#define DEMO

qboolean s_win95;

int			starttime;
qboolean	ActiveApp;
qboolean	Minimized;

static cvar_t *gamedll;

static HANDLE hinput, houtput;

unsigned int	sys_msg_time;
unsigned int	sys_frame_time;

int		argc;
char	*argv[MAX_NUM_ARGVS];

void CheckWorkingDirectory (void);
void RestoreWorkingDirectory (void);

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

SYSTEM IO

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


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

	CL_Shutdown ();
	Qcommon_Shutdown ();

	va_start (argptr, error);
	_vsnprintf(text, sizeof(text), error, argptr);
	va_end (argptr);
	text[sizeof(text)-1] = 0;

	MessageBox(NULL, text, "Error", 0 /* MB_OK */);

	exit (1);
}

void Sys_Quit (void)
{
	timeEndPeriod(1);

	CL_Shutdown();
	Qcommon_Shutdown ();
	if (dedicated && dedicated->value)
		FreeConsole ();

	RestoreWorkingDirectory();
	exit (0);
}


void WinError (void)
{
	LPVOID lpMsgBuf;

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(LPTSTR) &lpMsgBuf,
		0,
		NULL
	);

	// Display the string.
	MessageBox(NULL, (const char *)lpMsgBuf, "GetLastError", MB_OK|MB_ICONINFORMATION);

	// Free the buffer.
	LocalFree(lpMsgBuf);
}

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

================
*/
char *Sys_ScanForCD (void)
{
	int i;
	HKEY hKey;
	LONG lres;
	DWORD lType, lSize;
	FILE *f;
	char test[MAX_QPATH];
	static char cddir[MAX_OSPATH];

	cddir[0] = 0;

	lres = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Ritual Entertainment\\Sin", 0, KEY_READ, &hKey);
	if (lres != ERROR_SUCCESS)
		return cddir;

	lSize = MAX_OSPATH;

	lres = RegQueryValueEx(hKey, "PATH", NULL, &lType, (LPBYTE)cddir, &lSize);

#if 0
	{
		char temp[1024];

		sprintf(temp, "Contents of PATH = %s", cddir);
		MessageBox(NULL, temp, "Sys_ScanForCD", MB_OK|MB_ICONINFORMATION);
	}
#endif

	RegCloseKey(hKey);

	if (lres != ERROR_SUCCESS)
		return cddir;

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

	sprintf (test, "%s%s", cddir, ID_CDROM_EXE);

	f = fopen(test, "r");
	if (f)
	{
		char drive[4];

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

		fclose (f);
		if (GetDriveType(drive) == DRIVE_CDROM)
		{
			//
			// get rid of backslashes
			//
			for (i = 0; i < strlen(cddir); i++)
			{
				if (cddir[i] == '\\')
					cddir[i] = '/';
			}
			return cddir;
		}
	}

	cddir[0] = 0;

	return cddir;
}

//#define USEPROTECTION
/*
================
Sys_CopyProtect

================
*/
void Sys_CopyProtect (void)
{
#ifdef USEPROTECTION
	char *cddir;

	cddir = Sys_ScanForCD();
	if (!cddir[0])
		Com_Error (ERR_FATAL, "You must have the " GAME_NAME " CD in the drive to play.");
#endif
}


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

	timeBeginPeriod(1);

	vinfo.dwOSVersionInfoSize = sizeof(vinfo);

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

	if (vinfo.dwMajorVersion < 4)
		Sys_Error (GAME_NAME " requires windows version 4 or greater");
	if (vinfo.dwPlatformId == VER_PLATFORM_WIN32s)
		Sys_Error (GAME_NAME " doesn't run on Win32s");
	else if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
		s_win95 = true;

	if (dedicated->value)
	{
		if (!AllocConsole ())
			Sys_Error ("Couldn't create dedicated server console");
		hinput = GetStdHandle (STD_INPUT_HANDLE);
		houtput = GetStdHandle (STD_OUTPUT_HANDLE);
	}
}


static char console_text[256];
static int  console_textlen;

/*
================
Sys_ConsoleInput
================
*/
const char *Sys_ConsoleInput (void)
{
	INPUT_RECORD	recs[1024];
	int		ch;
	DWORD		dummy, numread, numevents;

	if (!dedicated || !dedicated->value)
		return NULL;

	for ( ;; )
	{
		if (!GetNumberOfConsoleInputEvents (hinput, &numevents))
			Sys_Error ("Error getting # of console events");

		if (numevents <= 0)
			break;

		if (!ReadConsoleInput(hinput, recs, 1, &numread))
			Sys_Error ("Error reading console input");

		if (numread != 1)
			Sys_Error ("Couldn't read console input");

		if (recs[0].EventType == KEY_EVENT)
		{
			if (!recs[0].Event.KeyEvent.bKeyDown)
			{
				ch = recs[0].Event.KeyEvent.uChar.AsciiChar;

				switch (ch)
				{
					case '\r':
						WriteFile(houtput, "\r\n", 2, &dummy, NULL);

						if (console_textlen)
						{
							console_text[console_textlen] = 0;
							console_textlen = 0;
							return console_text;
						}
						break;

					case '\b':
						if (console_textlen)
						{
							console_textlen--;
							WriteFile(houtput, "\b \b", 3, &dummy, NULL);
						}
						break;

					default:
						if (ch >= ' ')
						{
							if (console_textlen < sizeof(console_text)-2)
							{
								WriteFile(houtput, &ch, 1, &dummy, NULL);
								console_text[console_textlen] = ch;
								console_textlen++;
							}
						}

						break;
				}
			}
		}
	}

	return NULL;
}


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

Print text to the dedicated console
================
*/
void Sys_ConsoleOutput (const char *string)
{
	DWORD dummy;
	char text[256];

	if (!dedicated || !dedicated->value)
		return;

	if (console_textlen)
	{
		text[0] = '\r';
		memset(&text[1], ' ', console_textlen);
		text[console_textlen+1] = '\r';
		text[console_textlen+2] = 0;
		WriteFile(houtput, text, console_textlen+2, &dummy, NULL);
	}

	WriteFile(houtput, string, (DWORD)strlen(string), &dummy, NULL);

	if (console_textlen)
		WriteFile(houtput, console_text, console_textlen, &dummy, NULL);
}

void Sys_SleepMilliseconds (unsigned msec)
{
	Sleep (msec);
}

/*
================
Sys_SendKeyEvents

Send Key_Event calls
================
*/
void Sys_SendKeyEvents (void)
{
	MSG		msg;

	while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
	{
		if (!GetMessage (&msg, NULL, 0, 0))
			Sys_Quit ();
		sys_msg_time = msg.time;
		TranslateMessage (&msg);
		DispatchMessage (&msg);
	}

	// grab frame time
	sys_frame_time = timeGetTime();
}

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

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

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

		if ((hClipboardData = GetClipboardData(CF_TEXT)) != NULL)
		{
			if ((cliptext = (char *)GlobalLock(hClipboardData)) != NULL)
			{
				data = (char *)malloc(GlobalSize(hClipboardData) + 1);
				strcpy(data, cliptext);
				GlobalUnlock(hClipboardData);
			}
		}
		CloseClipboard();
	}
	return data;
}


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

 WINDOWS CRAP

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

/*
=================
Sys_AppActivate
=================
*/
void Sys_AppActivate (void)
{
#ifndef DEDICATED_ONLY
	ShowWindow (cl_hwnd, SW_RESTORE);
	SetForegroundWindow (cl_hwnd);
#endif
}

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

GAME DLL

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

static HINSTANCE	game_library;

/*
=================
Sys_UnloadGame
=================
*/
void Sys_UnloadGame (void)
{
#ifndef GAME_HARD_LINKED
	if (!FreeLibrary (game_library))
		Com_Error (ERR_FATAL, "FreeLibrary failed for game library");
#endif
	game_library = NULL;
}

/*
=================
Sys_GetGameAPI

Loads the game dll
=================
*/
#ifdef GAME_HARD_LINKED
void *GetGameAPI (void *import);
#endif	//static game needs a real prototype

void *Sys_GetGameAPI (void *parms)
{
#ifndef GAME_HARD_LINKED
	void	*(*GetGameAPI) (void *);
	char	name[MAX_OSPATH];
	const char	*path;
	char	cwd[MAX_OSPATH];

#if defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__)
	const char *gamename = "gamex64.dll";
#elif defined(_M_IX86) || defined(__i386__)
	const char *gamename = "gamex86.dll";
#elif defined _M_ALPHA
	const char *gamename = "gameaxp.dll";
#endif

	if (game_library)
		Com_Error (ERR_FATAL, "Sys_GetGameAPI without Sys_UnloadingGame");

	Com_DPrintf (DEVELOPER_MSG_IO, "LoadLibrary()\n");

	gamedll = Cvar_Get("gamedll", "", 0);
	if (gamedll->string[0])
	{
		game_library = LoadLibrary (gamedll->string);
		if (game_library)
		{
			Com_DPrintf (DEVELOPER_MSG_IO, "LoadLibrary (%s)\n", gamedll->string);
		}
		else
		{
			Com_DPrintf (DEVELOPER_MSG_IO, "Couldn't load %s %u\n", gamedll->string, (unsigned int) GetLastError());
			return NULL;
		}
	}
	else
	{
		// check the current directory for other development purposes
		_getcwd (cwd, sizeof(cwd));
		Com_sprintf (name, sizeof(name), "%s/%s", cwd, gamename);
		game_library = LoadLibrary (name);
		if (game_library)
		{
			Com_DPrintf (DEVELOPER_MSG_IO, "LoadLibrary (%s)\n", name);
		}
		else
		{
			// now run through the search paths
			path = NULL;
			while (1)
			{
				path = FS_NextPath (path);
				if (!path)
					return NULL;		// couldn't find one anywhere
				Com_sprintf (name, sizeof(name), "%s/%s", path, gamename);
				game_library = LoadLibrary (name);
				if (game_library)
				{
					Com_DPrintf (DEVELOPER_MSG_IO, "LoadLibrary (%s)\n",name);
					break;
				}
			}
		}
	}

	GetGameAPI = (void *)GetProcAddress (game_library, "GetGameAPI");
	if (!GetGameAPI)
	{
		Sys_UnloadGame ();
		return NULL;
	}
#endif	/* ! GAME_HARD_LINKED */

	return GetGameAPI (parms);
}

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


/*
==================
ParseCommandLine

==================
*/
void ParseCommandLine (LPSTR lpCmdLine)
{
	static char exe[] = "exe";
	argc = 1;
	argv[0] = exe;

	while (*lpCmdLine && (argc < MAX_NUM_ARGVS))
	{
		while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
			lpCmdLine++;

		if (*lpCmdLine)
		{
			argv[argc] = lpCmdLine;
			argc++;

			while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
				lpCmdLine++;

			if (*lpCmdLine)
			{
				*lpCmdLine = 0;
				lpCmdLine++;
			}

		}
	}
}

void Detect_WinNT() /* FS: Detect if we're using Windows XP for alt+tab appcompat */
{
	DWORD WinVersion;
	DWORD WinLowByte, WinHiByte;
	DWORD dwDisp, dwDisp2;
	DWORD dwAppCompat = 1;
	HKEY hk, hk2;

	WinVersion = GetVersion();
	WinLowByte = (DWORD)(LOBYTE(LOWORD(WinVersion)));
	WinHiByte = (DWORD)(HIBYTE(HIWORD(WinVersion)));

	if(WinLowByte == 5 && WinHiByte > 0) /* FS: Because Win2k is version 5.0 and XP is 5.1 and 2003 is 5.2 */
	{
		RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, &dwDisp);
		RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk2, &dwDisp2);
		RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk2, &dwDisp2);

		RegSetValueEx(hk, "{ef6d8efd-ae36-48fa-bd1b-8f941dc265fa}", 0, REG_DWORD, (const byte *) &dwAppCompat, sizeof(DWORD));
		RegCloseKey(hk);
		Com_DPrintf(DEVELOPER_MSG_STANDARD, "Windows NT Version: %u\n", (unsigned int)WinLowByte);
	}

	if(WinLowByte == 6) /* FS: Windows 7 */
	{
		dwAppCompat = 119; /* FS: Different value for updated AppCompat */
		RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, &dwDisp);
		RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk2, &dwDisp2);
		RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\InstalledSDB", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk2, &dwDisp2);

		RegSetValueEx(hk, "{ef6d8efd-ae36-48fa-bd1b-8f941dc265fa}", 0, REG_DWORD, (const byte *) &dwAppCompat, sizeof(DWORD));
		RegCloseKey(hk);
		Com_DPrintf(DEVELOPER_MSG_STANDARD, "Windows NT Version: %u\n", (unsigned int)WinLowByte);
	}
}

/*
==================
WinMain
==================
*/
HINSTANCE	global_hInstance;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG			msg;
	int				time, oldtime, newtime;
	char			*cddir;

	SET_CRT_DEBUG_FIELD(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	/* previous instances do not exist in Win32 */
	if (hPrevInstance)
		return 0;

	global_hInstance = hInstance;

	ParseCommandLine (lpCmdLine);

	CheckWorkingDirectory();

	// if we find the CD, add a +set cddir xxx command line
	cddir = Sys_ScanForCD ();
	if (cddir && argc < MAX_NUM_ARGVS - 3)
	{
		int i;

		// don't override a cddir on the command line
		for (i=0 ; i<argc ; i++)
			if (!strcmp(argv[i], "cddir"))
				break;

		if (i == argc)
		{
			static char arg_set[] = "+set";
			static char arg_cddir[] = "cddir";
			argv[argc++] = arg_set;
			argv[argc++] = arg_cddir;
			argv[argc++] = cddir;
		}
	}

	Qcommon_Init (argc, argv);
	Detect_WinNT(); /* FS */

	oldtime = Sys_Milliseconds ();

    /* main window message loop */
	while (1)
	{
		// if at a full screen console, don't update unless needed
		if (Minimized || (dedicated && dedicated->value))
		{
			Sleep (1);
		}

		while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if (!GetMessage (&msg, NULL, 0, 0))
				Com_Quit ();
			sys_msg_time = msg.time;
			TranslateMessage (&msg);
			DispatchMessage (&msg);
		}

		do
		{
			newtime = Sys_Milliseconds ();
			time = newtime - oldtime;
		} while (time < 1);
//			Con_Printf ("time:%5.2f - %5.2f = %5.2f\n", newtime, oldtime, time);
#if defined(_M_IX86) || defined(__i386__)
		_controlfp(_PC_24, _MCW_PC);
#endif
		Qcommon_Frame (time);
		oldtime = newtime;
	}

	// never gets here
	return true;
}

static char saved_cwd[MAX_OSPATH];
static qboolean directory_changed = false;

/*
==================
CheckWorkingDirectory
==================
*/
void CheckWorkingDirectory (void)
{
	char destdir[MAX_OSPATH];
	char windowsdir[MAX_OSPATH];
	char sinfile[MAX_OSPATH];
	int currentdrive;
	char drive[4];
	qboolean failed;

	currentdrive = _getdrive();

	drive[0] = 'A' + currentdrive - 1;
	drive[1] = ':';
	drive[2] = '\\';
	drive[3] = 0;

	if (GetDriveType (drive) != DRIVE_CDROM)
		return;

	// looks like we ran from the CD uh-oh!
	failed = false;

	if (!failed && !GetWindowsDirectory(windowsdir, MAX_OSPATH))
	{
		failed = true;
	}

	Com_sprintf(sinfile, sizeof(sinfile), "%s\\sin_setup.ini", windowsdir);
	if (!failed && !GetPrivateProfileString("Global - String1", "Data", "", destdir, MAX_OSPATH, sinfile))
	{
		failed = true;
	}

	// if we failed for any reason, go with a default directory and drive
	if (failed)
	{
		strcpy(destdir, "c:\\Program Files\\sin");
		FS_CreatePath(destdir);
	}

	_getcwd(saved_cwd, MAX_OSPATH);
	directory_changed = true;

	// change the directory on that drive
	_chdir(destdir);

	// change to that drive
	currentdrive = toupper(destdir[0]) - 'A' + 1;
	_chdrive(currentdrive);
}

/*
==================
RestoreWorkingDirectory
==================
*/
void RestoreWorkingDirectory (void)
{
	int currentdrive;

	if (!directory_changed)
		return;

	directory_changed = false;

	// change the directory on that drive
	_chdir(saved_cwd);

	// change to that drive
	currentdrive = toupper(saved_cwd[0]) - 'A' + 1;
	_chdrive(currentdrive);
}
