1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20// sys_win.c -- Win32 system interface code
21
22#include "quakedef.h"
23#include "winquake.h"
24#include "errno.h"
25#include "resource.h"
26#include "conproc.h"
27
28#define MINIMUM_WIN_MEMORY		0x0880000
29#define MAXIMUM_WIN_MEMORY		0x1000000
30
31#define CONSOLE_ERROR_TIMEOUT	60.0	// # of seconds to wait on Sys_Error running
32										//  dedicated before exiting
33#define PAUSE_SLEEP		50				// sleep time on pause or minimization
34#define NOT_FOCUS_SLEEP	20				// sleep time when not focus
35
36int			starttime;
37qboolean	ActiveApp, Minimized;
38qboolean	WinNT;
39
40static double		pfreq;
41static double		curtime = 0.0;
42static double		lastcurtime = 0.0;
43static int			lowshift;
44qboolean			isDedicated;
45static qboolean		sc_return_on_enter = false;
46HANDLE				hinput, houtput;
47
48static char			*tracking_tag = "Clams & Mooses";
49
50static HANDLE	tevent;
51static HANDLE	hFile;
52static HANDLE	heventParent;
53static HANDLE	heventChild;
54
55void MaskExceptions (void);
56void Sys_InitFloatTime (void);
57void Sys_PushFPCW_SetHigh (void);
58void Sys_PopFPCW (void);
59
60volatile int					sys_checksum;
61
62
63/*
64================
65Sys_PageIn
66================
67*/
68void Sys_PageIn (void *ptr, int size)
69{
70	byte	*x;
71	int		j, m, n;
72
73// touch all the memory to make sure it's there. The 16-page skip is to
74// keep Win 95 from thinking we're trying to page ourselves in (we are
75// doing that, of course, but there's no reason we shouldn't)
76	x = (byte *)ptr;
77
78	for (n=0 ; n<4 ; n++)
79	{
80		for (m=0 ; m<(size - 16 * 0x1000) ; m += 4)
81		{
82			sys_checksum += *(int *)&x[m];
83			sys_checksum += *(int *)&x[m + 16 * 0x1000];
84		}
85	}
86}
87
88
89/*
90===============================================================================
91
92FILE IO
93
94===============================================================================
95*/
96
97#define	MAX_HANDLES		10
98FILE	*sys_handles[MAX_HANDLES];
99
100int		findhandle (void)
101{
102	int		i;
103
104	for (i=1 ; i<MAX_HANDLES ; i++)
105		if (!sys_handles[i])
106			return i;
107	Sys_Error ("out of handles");
108	return -1;
109}
110
111/*
112================
113filelength
114================
115*/
116int filelength (FILE *f)
117{
118	int		pos;
119	int		end;
120	int		t;
121
122	t = VID_ForceUnlockedAndReturnState ();
123
124	pos = ftell (f);
125	fseek (f, 0, SEEK_END);
126	end = ftell (f);
127	fseek (f, pos, SEEK_SET);
128
129	VID_ForceLockState (t);
130
131	return end;
132}
133
134int Sys_FileOpenRead (char *path, int *hndl)
135{
136	FILE	*f;
137	int		i, retval;
138	int		t;
139
140	t = VID_ForceUnlockedAndReturnState ();
141
142	i = findhandle ();
143
144	f = fopen(path, "rb");
145
146	if (!f)
147	{
148		*hndl = -1;
149		retval = -1;
150	}
151	else
152	{
153		sys_handles[i] = f;
154		*hndl = i;
155		retval = filelength(f);
156	}
157
158	VID_ForceLockState (t);
159
160	return retval;
161}
162
163int Sys_FileOpenWrite (char *path)
164{
165	FILE	*f;
166	int		i;
167	int		t;
168
169	t = VID_ForceUnlockedAndReturnState ();
170
171	i = findhandle ();
172
173	f = fopen(path, "wb");
174	if (!f)
175		Sys_Error ("Error opening %s: %s", path,strerror(errno));
176	sys_handles[i] = f;
177
178	VID_ForceLockState (t);
179
180	return i;
181}
182
183void Sys_FileClose (int handle)
184{
185	int		t;
186
187	t = VID_ForceUnlockedAndReturnState ();
188	fclose (sys_handles[handle]);
189	sys_handles[handle] = NULL;
190	VID_ForceLockState (t);
191}
192
193void Sys_FileSeek (int handle, int position)
194{
195	int		t;
196
197	t = VID_ForceUnlockedAndReturnState ();
198	fseek (sys_handles[handle], position, SEEK_SET);
199	VID_ForceLockState (t);
200}
201
202int Sys_FileRead (int handle, void *dest, int count)
203{
204	int		t, x;
205
206	t = VID_ForceUnlockedAndReturnState ();
207	x = fread (dest, 1, count, sys_handles[handle]);
208	VID_ForceLockState (t);
209	return x;
210}
211
212int Sys_FileWrite (int handle, void *data, int count)
213{
214	int		t, x;
215
216	t = VID_ForceUnlockedAndReturnState ();
217	x = fwrite (data, 1, count, sys_handles[handle]);
218	VID_ForceLockState (t);
219	return x;
220}
221
222int	Sys_FileTime (char *path)
223{
224	FILE	*f;
225	int		t, retval;
226
227	t = VID_ForceUnlockedAndReturnState ();
228
229	f = fopen(path, "rb");
230
231	if (f)
232	{
233		fclose(f);
234		retval = 1;
235	}
236	else
237	{
238		retval = -1;
239	}
240
241	VID_ForceLockState (t);
242	return retval;
243}
244
245void Sys_mkdir (char *path)
246{
247	_mkdir (path);
248}
249
250
251/*
252===============================================================================
253
254SYSTEM IO
255
256===============================================================================
257*/
258
259/*
260================
261Sys_MakeCodeWriteable
262================
263*/
264void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length)
265{
266	DWORD  flOldProtect;
267
268	if (!VirtualProtect((LPVOID)startaddr, length, PAGE_READWRITE, &flOldProtect))
269   		Sys_Error("Protection change failed\n");
270}
271
272
273#ifndef _M_IX86
274
275void Sys_SetFPCW (void)
276{
277}
278
279void Sys_PushFPCW_SetHigh (void)
280{
281}
282
283void Sys_PopFPCW (void)
284{
285}
286
287void MaskExceptions (void)
288{
289}
290
291#endif
292
293/*
294================
295Sys_Init
296================
297*/
298void Sys_Init (void)
299{
300	LARGE_INTEGER	PerformanceFreq;
301	unsigned int	lowpart, highpart;
302	OSVERSIONINFO	vinfo;
303
304	MaskExceptions ();
305	Sys_SetFPCW ();
306
307	if (!QueryPerformanceFrequency (&PerformanceFreq))
308		Sys_Error ("No hardware timer available");
309
310// get 32 out of the 64 time bits such that we have around
311// 1 microsecond resolution
312	lowpart = (unsigned int)PerformanceFreq.LowPart;
313	highpart = (unsigned int)PerformanceFreq.HighPart;
314	lowshift = 0;
315
316	while (highpart || (lowpart > 2000000.0))
317	{
318		lowshift++;
319		lowpart >>= 1;
320		lowpart |= (highpart & 1) << 31;
321		highpart >>= 1;
322	}
323
324	pfreq = 1.0 / (double)lowpart;
325
326	Sys_InitFloatTime ();
327
328	vinfo.dwOSVersionInfoSize = sizeof(vinfo);
329
330	if (!GetVersionEx (&vinfo))
331		Sys_Error ("Couldn't get OS info");
332
333	if ((vinfo.dwMajorVersion < 4) ||
334		(vinfo.dwPlatformId == VER_PLATFORM_WIN32s))
335	{
336		Sys_Error ("WinQuake requires at least Win95 or NT 4.0");
337	}
338
339	if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
340		WinNT = true;
341	else
342		WinNT = false;
343}
344
345
346void Sys_Error (char *error, ...)
347{
348	va_list		argptr;
349	char		text[1024], text2[1024];
350	char		*text3 = "Press Enter to exit\n";
351	char		*text4 = "***********************************\n";
352	char		*text5 = "\n";
353	DWORD		dummy;
354	double		starttime;
355	static int	in_sys_error0 = 0;
356	static int	in_sys_error1 = 0;
357	static int	in_sys_error2 = 0;
358	static int	in_sys_error3 = 0;
359
360	if (!in_sys_error3)
361	{
362		in_sys_error3 = 1;
363		VID_ForceUnlockedAndReturnState ();
364	}
365
366	va_start (argptr, error);
367	vsprintf (text, error, argptr);
368	va_end (argptr);
369
370	if (isDedicated)
371	{
372		va_start (argptr, error);
373		vsprintf (text, error, argptr);
374		va_end (argptr);
375
376		sprintf (text2, "ERROR: %s\n", text);
377		WriteFile (houtput, text5, strlen (text5), &dummy, NULL);
378		WriteFile (houtput, text4, strlen (text4), &dummy, NULL);
379		WriteFile (houtput, text2, strlen (text2), &dummy, NULL);
380		WriteFile (houtput, text3, strlen (text3), &dummy, NULL);
381		WriteFile (houtput, text4, strlen (text4), &dummy, NULL);
382
383
384		starttime = Sys_FloatTime ();
385		sc_return_on_enter = true;	// so Enter will get us out of here
386
387		while (!Sys_ConsoleInput () &&
388				((Sys_FloatTime () - starttime) < CONSOLE_ERROR_TIMEOUT))
389		{
390		}
391	}
392	else
393	{
394	// switch to windowed so the message box is visible, unless we already
395	// tried that and failed
396		if (!in_sys_error0)
397		{
398			in_sys_error0 = 1;
399			VID_SetDefaultMode ();
400			MessageBox(NULL, text, "Quake Error",
401					   MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
402		}
403		else
404		{
405			MessageBox(NULL, text, "Double Quake Error",
406					   MB_OK | MB_SETFOREGROUND | MB_ICONSTOP);
407		}
408	}
409
410	if (!in_sys_error1)
411	{
412		in_sys_error1 = 1;
413		Host_Shutdown ();
414	}
415
416// shut down QHOST hooks if necessary
417	if (!in_sys_error2)
418	{
419		in_sys_error2 = 1;
420		DeinitConProc ();
421	}
422
423	exit (1);
424}
425
426void Sys_Printf (char *fmt, ...)
427{
428	va_list		argptr;
429	char		text[1024];
430	DWORD		dummy;
431
432	if (isDedicated)
433	{
434		va_start (argptr,fmt);
435		vsprintf (text, fmt, argptr);
436		va_end (argptr);
437
438		WriteFile(houtput, text, strlen (text), &dummy, NULL);
439	}
440}
441
442void Sys_Quit (void)
443{
444
445	VID_ForceUnlockedAndReturnState ();
446
447	Host_Shutdown();
448
449	if (tevent)
450		CloseHandle (tevent);
451
452	if (isDedicated)
453		FreeConsole ();
454
455// shut down QHOST hooks if necessary
456	DeinitConProc ();
457
458	exit (0);
459}
460
461
462/*
463================
464Sys_FloatTime
465================
466*/
467double Sys_FloatTime (void)
468{
469	static int			sametimecount;
470	static unsigned int	oldtime;
471	static int			first = 1;
472	LARGE_INTEGER		PerformanceCount;
473	unsigned int		temp, t2;
474	double				time;
475
476	Sys_PushFPCW_SetHigh ();
477
478	QueryPerformanceCounter (&PerformanceCount);
479
480	temp = ((unsigned int)PerformanceCount.LowPart >> lowshift) |
481		   ((unsigned int)PerformanceCount.HighPart << (32 - lowshift));
482
483	if (first)
484	{
485		oldtime = temp;
486		first = 0;
487	}
488	else
489	{
490	// check for turnover or backward time
491		if ((temp <= oldtime) && ((oldtime - temp) < 0x10000000))
492		{
493			oldtime = temp;	// so we can't get stuck
494		}
495		else
496		{
497			t2 = temp - oldtime;
498
499			time = (double)t2 * pfreq;
500			oldtime = temp;
501
502			curtime += time;
503
504			if (curtime == lastcurtime)
505			{
506				sametimecount++;
507
508				if (sametimecount > 100000)
509				{
510					curtime += 1.0;
511					sametimecount = 0;
512				}
513			}
514			else
515			{
516				sametimecount = 0;
517			}
518
519			lastcurtime = curtime;
520		}
521	}
522
523	Sys_PopFPCW ();
524
525    return curtime;
526}
527
528
529/*
530================
531Sys_InitFloatTime
532================
533*/
534void Sys_InitFloatTime (void)
535{
536	int		j;
537
538	Sys_FloatTime ();
539
540	j = COM_CheckParm("-starttime");
541
542	if (j)
543	{
544		curtime = (double) (Q_atof(com_argv[j+1]));
545	}
546	else
547	{
548		curtime = 0.0;
549	}
550
551	lastcurtime = curtime;
552}
553
554
555char *Sys_ConsoleInput (void)
556{
557	static char	text[256];
558	static int		len;
559	INPUT_RECORD	recs[1024];
560	int		count;
561	int		i, dummy;
562	int		ch, numread, numevents;
563
564	if (!isDedicated)
565		return NULL;
566
567
568	for ( ;; )
569	{
570		if (!GetNumberOfConsoleInputEvents (hinput, &numevents))
571			Sys_Error ("Error getting # of console events");
572
573		if (numevents <= 0)
574			break;
575
576		if (!ReadConsoleInput(hinput, recs, 1, &numread))
577			Sys_Error ("Error reading console input");
578
579		if (numread != 1)
580			Sys_Error ("Couldn't read console input");
581
582		if (recs[0].EventType == KEY_EVENT)
583		{
584			if (!recs[0].Event.KeyEvent.bKeyDown)
585			{
586				ch = recs[0].Event.KeyEvent.uChar.AsciiChar;
587
588				switch (ch)
589				{
590					case '\r':
591						WriteFile(houtput, "\r\n", 2, &dummy, NULL);
592
593						if (len)
594						{
595							text[len] = 0;
596							len = 0;
597							return text;
598						}
599						else if (sc_return_on_enter)
600						{
601						// special case to allow exiting from the error handler on Enter
602							text[0] = '\r';
603							len = 0;
604							return text;
605						}
606
607						break;
608
609					case '\b':
610						WriteFile(houtput, "\b \b", 3, &dummy, NULL);
611						if (len)
612						{
613							len--;
614						}
615						break;
616
617					default:
618						if (ch >= ' ')
619						{
620							WriteFile(houtput, &ch, 1, &dummy, NULL);
621							text[len] = ch;
622							len = (len + 1) & 0xff;
623						}
624
625						break;
626
627				}
628			}
629		}
630	}
631
632	return NULL;
633}
634
635void Sys_Sleep (void)
636{
637	Sleep (1);
638}
639
640
641void Sys_SendKeyEvents (void)
642{
643    MSG        msg;
644
645	while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
646	{
647	// we always update if there are any event, even if we're paused
648		scr_skipupdate = 0;
649
650		if (!GetMessage (&msg, NULL, 0, 0))
651			Sys_Quit ();
652
653      	TranslateMessage (&msg);
654      	DispatchMessage (&msg);
655	}
656}
657
658
659/*
660==============================================================================
661
662 WINDOWS CRAP
663
664==============================================================================
665*/
666
667
668/*
669==================
670WinMain
671==================
672*/
673void SleepUntilInput (int time)
674{
675
676	MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT);
677}
678
679
680/*
681==================
682WinMain
683==================
684*/
685HINSTANCE	global_hInstance;
686int			global_nCmdShow;
687char		*argv[MAX_NUM_ARGVS];
688static char	*empty_string = "";
689HWND		hwnd_dialog;
690
691
692int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
693{
694    MSG				msg;
695	quakeparms_t	parms;
696	double			time, oldtime, newtime;
697	MEMORYSTATUS	lpBuffer;
698	static	char	cwd[1024];
699	int				t;
700	RECT			rect;
701
702    /* previous instances do not exist in Win32 */
703    if (hPrevInstance)
704        return 0;
705
706	global_hInstance = hInstance;
707	global_nCmdShow = nCmdShow;
708
709	lpBuffer.dwLength = sizeof(MEMORYSTATUS);
710	GlobalMemoryStatus (&lpBuffer);
711
712	if (!GetCurrentDirectory (sizeof(cwd), cwd))
713		Sys_Error ("Couldn't determine current directory");
714
715	if (cwd[Q_strlen(cwd)-1] == '/')
716		cwd[Q_strlen(cwd)-1] = 0;
717
718	parms.basedir = cwd;
719	parms.cachedir = NULL;
720
721	parms.argc = 1;
722	argv[0] = empty_string;
723
724	while (*lpCmdLine && (parms.argc < MAX_NUM_ARGVS))
725	{
726		while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
727			lpCmdLine++;
728
729		if (*lpCmdLine)
730		{
731			argv[parms.argc] = lpCmdLine;
732			parms.argc++;
733
734			while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
735				lpCmdLine++;
736
737			if (*lpCmdLine)
738			{
739				*lpCmdLine = 0;
740				lpCmdLine++;
741			}
742
743		}
744	}
745
746	parms.argv = argv;
747
748	COM_InitArgv (parms.argc, parms.argv);
749
750	parms.argc = com_argc;
751	parms.argv = com_argv;
752
753	isDedicated = (COM_CheckParm ("-dedicated") != 0);
754
755	if (!isDedicated)
756	{
757		hwnd_dialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, NULL);
758
759		if (hwnd_dialog)
760		{
761			if (GetWindowRect (hwnd_dialog, &rect))
762			{
763				if (rect.left > (rect.top * 2))
764				{
765					SetWindowPos (hwnd_dialog, 0,
766						(rect.left / 2) - ((rect.right - rect.left) / 2),
767						rect.top, 0, 0,
768						SWP_NOZORDER | SWP_NOSIZE);
769				}
770			}
771
772			ShowWindow (hwnd_dialog, SW_SHOWDEFAULT);
773			UpdateWindow (hwnd_dialog);
774			SetForegroundWindow (hwnd_dialog);
775		}
776	}
777
778// take the greater of all the available memory or half the total memory,
779// but at least 8 Mb and no more than 16 Mb, unless they explicitly
780// request otherwise
781	parms.memsize = lpBuffer.dwAvailPhys;
782
783	if (parms.memsize < MINIMUM_WIN_MEMORY)
784		parms.memsize = MINIMUM_WIN_MEMORY;
785
786	if (parms.memsize < (lpBuffer.dwTotalPhys >> 1))
787		parms.memsize = lpBuffer.dwTotalPhys >> 1;
788
789	if (parms.memsize > MAXIMUM_WIN_MEMORY)
790		parms.memsize = MAXIMUM_WIN_MEMORY;
791
792	if (COM_CheckParm ("-heapsize"))
793	{
794		t = COM_CheckParm("-heapsize") + 1;
795
796		if (t < com_argc)
797			parms.memsize = Q_atoi (com_argv[t]) * 1024;
798	}
799
800	parms.membase = malloc (parms.memsize);
801
802	if (!parms.membase)
803		Sys_Error ("Not enough memory free; check disk space\n");
804
805	Sys_PageIn (parms.membase, parms.memsize);
806
807	tevent = CreateEvent(NULL, FALSE, FALSE, NULL);
808
809	if (!tevent)
810		Sys_Error ("Couldn't create event");
811
812	if (isDedicated)
813	{
814		if (!AllocConsole ())
815		{
816			Sys_Error ("Couldn't create dedicated server console");
817		}
818
819		hinput = GetStdHandle (STD_INPUT_HANDLE);
820		houtput = GetStdHandle (STD_OUTPUT_HANDLE);
821
822	// give QHOST a chance to hook into the console
823		if ((t = COM_CheckParm ("-HFILE")) > 0)
824		{
825			if (t < com_argc)
826				hFile = (HANDLE)Q_atoi (com_argv[t+1]);
827		}
828
829		if ((t = COM_CheckParm ("-HPARENT")) > 0)
830		{
831			if (t < com_argc)
832				heventParent = (HANDLE)Q_atoi (com_argv[t+1]);
833		}
834
835		if ((t = COM_CheckParm ("-HCHILD")) > 0)
836		{
837			if (t < com_argc)
838				heventChild = (HANDLE)Q_atoi (com_argv[t+1]);
839		}
840
841		InitConProc (hFile, heventParent, heventChild);
842	}
843
844	Sys_Init ();
845
846// because sound is off until we become active
847	S_BlockSound ();
848
849	Sys_Printf ("Host_Init\n");
850	Host_Init (&parms);
851
852	oldtime = Sys_FloatTime ();
853
854    /* main window message loop */
855	while (1)
856	{
857		if (isDedicated)
858		{
859			newtime = Sys_FloatTime ();
860			time = newtime - oldtime;
861
862			while (time < sys_ticrate.value )
863			{
864				Sys_Sleep();
865				newtime = Sys_FloatTime ();
866				time = newtime - oldtime;
867			}
868		}
869		else
870		{
871		// yield the CPU for a little while when paused, minimized, or not the focus
872			if ((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing)
873			{
874				SleepUntilInput (PAUSE_SLEEP);
875				scr_skipupdate = 1;		// no point in bothering to draw
876			}
877			else if (!ActiveApp && !DDActive)
878			{
879				SleepUntilInput (NOT_FOCUS_SLEEP);
880			}
881
882			newtime = Sys_FloatTime ();
883			time = newtime - oldtime;
884		}
885
886		Host_Frame (time);
887		oldtime = newtime;
888	}
889
890    /* return success of application */
891    return TRUE;
892}
893
894