/* | |
Copyright (C) 1996-1997 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. | |
*/ | |
// sys_win.c -- Win32 system interface code | |
#include "quakedef.h" | |
#include "winquake.h" | |
#include "errno.h" | |
#include "resource.h" | |
#include "conproc.h" | |
#define MINIMUM_WIN_MEMORY 0x0880000 | |
#define MAXIMUM_WIN_MEMORY 0x1000000 | |
#define CONSOLE_ERROR_TIMEOUT 60.0 // # of seconds to wait on Sys_Error running | |
// dedicated before exiting | |
#define PAUSE_SLEEP 50 // sleep time on pause or minimization | |
#define NOT_FOCUS_SLEEP 20 // sleep time when not focus | |
int starttime; | |
qboolean ActiveApp, Minimized; | |
qboolean WinNT; | |
static double pfreq; | |
static double curtime = 0.0; | |
static double lastcurtime = 0.0; | |
static int lowshift; | |
qboolean isDedicated; | |
static qboolean sc_return_on_enter = false; | |
HANDLE hinput, houtput; | |
static char *tracking_tag = "Clams & Mooses"; | |
static HANDLE tevent; | |
static HANDLE hFile; | |
static HANDLE heventParent; | |
static HANDLE heventChild; | |
void MaskExceptions (void); | |
void Sys_InitFloatTime (void); | |
void Sys_PushFPCW_SetHigh (void); | |
void Sys_PopFPCW (void); | |
volatile int sys_checksum; | |
/* | |
================ | |
Sys_PageIn | |
================ | |
*/ | |
void Sys_PageIn (void *ptr, int size) | |
{ | |
byte *x; | |
int j, m, n; | |
// touch all the memory to make sure it's there. The 16-page skip is to | |
// keep Win 95 from thinking we're trying to page ourselves in (we are | |
// doing that, of course, but there's no reason we shouldn't) | |
x = (byte *)ptr; | |
for (n=0 ; n<4 ; n++) | |
{ | |
for (m=0 ; m<(size - 16 * 0x1000) ; m += 4) | |
{ | |
sys_checksum += *(int *)&x[m]; | |
sys_checksum += *(int *)&x[m + 16 * 0x1000]; | |
} | |
} | |
} | |
/* | |
=============================================================================== | |
FILE IO | |
=============================================================================== | |
*/ | |
#define MAX_HANDLES 10 | |
FILE *sys_handles[MAX_HANDLES]; | |
int findhandle (void) | |
{ | |
int i; | |
for (i=1 ; i<MAX_HANDLES ; i++) | |
if (!sys_handles[i]) | |
return i; | |
Sys_Error ("out of handles"); | |
return -1; | |
} | |
/* | |
================ | |
filelength | |
================ | |
*/ | |
int filelength (FILE *f) | |
{ | |
int pos; | |
int end; | |
int t; | |
t = VID_ForceUnlockedAndReturnState (); | |
pos = ftell (f); | |
fseek (f, 0, SEEK_END); | |
end = ftell (f); | |
fseek (f, pos, SEEK_SET); | |
VID_ForceLockState (t); | |
return end; | |
} | |
int Sys_FileOpenRead (char *path, int *hndl) | |
{ | |
FILE *f; | |
int i, retval; | |
int t; | |
t = VID_ForceUnlockedAndReturnState (); | |
i = findhandle (); | |
f = fopen(path, "rb"); | |
if (!f) | |
{ | |
*hndl = -1; | |
retval = -1; | |
} | |
else | |
{ | |
sys_handles[i] = f; | |
*hndl = i; | |
retval = filelength(f); | |
} | |
VID_ForceLockState (t); | |
return retval; | |
} | |
int Sys_FileOpenWrite (char *path) | |
{ | |
FILE *f; | |
int i; | |
int t; | |
t = VID_ForceUnlockedAndReturnState (); | |
i = findhandle (); | |
f = fopen(path, "wb"); | |
if (!f) | |
Sys_Error ("Error opening %s: %s", path,strerror(errno)); | |
sys_handles[i] = f; | |
VID_ForceLockState (t); | |
return i; | |
} | |
void Sys_FileClose (int handle) | |
{ | |
int t; | |
t = VID_ForceUnlockedAndReturnState (); | |
fclose (sys_handles[handle]); | |
sys_handles[handle] = NULL; | |
VID_ForceLockState (t); | |
} | |
void Sys_FileSeek (int handle, int position) | |
{ | |
int t; | |
t = VID_ForceUnlockedAndReturnState (); | |
fseek (sys_handles[handle], position, SEEK_SET); | |
VID_ForceLockState (t); | |
} | |
int Sys_FileRead (int handle, void *dest, int count) | |
{ | |
int t, x; | |
t = VID_ForceUnlockedAndReturnState (); | |
x = fread (dest, 1, count, sys_handles[handle]); | |
VID_ForceLockState (t); | |
return x; | |
} | |
int Sys_FileWrite (int handle, void *data, int count) | |
{ | |
int t, x; | |
t = VID_ForceUnlockedAndReturnState (); | |
x = fwrite (data, 1, count, sys_handles[handle]); | |
VID_ForceLockState (t); | |
return x; | |
} | |
int Sys_FileTime (char *path) | |
{ | |
FILE *f; | |
int t, retval; | |
t = VID_ForceUnlockedAndReturnState (); | |
f = fopen(path, "rb"); | |
if (f) | |
{ | |
fclose(f); | |
retval = 1; | |
} | |
else | |
{ | |
retval = -1; | |
} | |
VID_ForceLockState (t); | |
return retval; | |
} | |
void Sys_mkdir (char *path) | |
{ | |
_mkdir (path); | |
} | |
/* | |
=============================================================================== | |
SYSTEM IO | |
=============================================================================== | |
*/ | |
/* | |
================ | |
Sys_MakeCodeWriteable | |
================ | |
*/ | |
void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length) | |
{ | |
DWORD flOldProtect; | |
if (!VirtualProtect((LPVOID)startaddr, length, PAGE_READWRITE, &flOldProtect)) | |
Sys_Error("Protection change failed\n"); | |
} | |
#ifndef _M_IX86 | |
void Sys_SetFPCW (void) | |
{ | |
} | |
void Sys_PushFPCW_SetHigh (void) | |
{ | |
} | |
void Sys_PopFPCW (void) | |
{ | |
} | |
void MaskExceptions (void) | |
{ | |
} | |
#endif | |
/* | |
================ | |
Sys_Init | |
================ | |
*/ | |
void Sys_Init (void) | |
{ | |
LARGE_INTEGER PerformanceFreq; | |
unsigned int lowpart, highpart; | |
OSVERSIONINFO vinfo; | |
MaskExceptions (); | |
Sys_SetFPCW (); | |
if (!QueryPerformanceFrequency (&PerformanceFreq)) | |
Sys_Error ("No hardware timer available"); | |
// get 32 out of the 64 time bits such that we have around | |
// 1 microsecond resolution | |
lowpart = (unsigned int)PerformanceFreq.LowPart; | |
highpart = (unsigned int)PerformanceFreq.HighPart; | |
lowshift = 0; | |
while (highpart || (lowpart > 2000000.0)) | |
{ | |
lowshift++; | |
lowpart >>= 1; | |
lowpart |= (highpart & 1) << 31; | |
highpart >>= 1; | |
} | |
pfreq = 1.0 / (double)lowpart; | |
Sys_InitFloatTime (); | |
vinfo.dwOSVersionInfoSize = sizeof(vinfo); | |
if (!GetVersionEx (&vinfo)) | |
Sys_Error ("Couldn't get OS info"); | |
if ((vinfo.dwMajorVersion < 4) || | |
(vinfo.dwPlatformId == VER_PLATFORM_WIN32s)) | |
{ | |
Sys_Error ("WinQuake requires at least Win95 or NT 4.0"); | |
} | |
if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) | |
WinNT = true; | |
else | |
WinNT = false; | |
} | |
void Sys_Error (char *error, ...) | |
{ | |
va_list argptr; | |
char text[1024], text2[1024]; | |
char *text3 = "Press Enter to exit\n"; | |
char *text4 = "***********************************\n"; | |
char *text5 = "\n"; | |
DWORD dummy; | |
double starttime; | |
static int in_sys_error0 = 0; | |
static int in_sys_error1 = 0; | |
static int in_sys_error2 = 0; | |
static int in_sys_error3 = 0; | |
if (!in_sys_error3) | |
{ | |
in_sys_error3 = 1; | |
VID_ForceUnlockedAndReturnState (); | |
} | |
va_start (argptr, error); | |
vsprintf (text, error, argptr); | |
va_end (argptr); | |
if (isDedicated) | |
{ | |
va_start (argptr, error); | |
vsprintf (text, error, argptr); | |
va_end (argptr); | |
sprintf (text2, "ERROR: %s\n", text); | |
WriteFile (houtput, text5, strlen (text5), &dummy, NULL); | |
WriteFile (houtput, text4, strlen (text4), &dummy, NULL); | |
WriteFile (houtput, text2, strlen (text2), &dummy, NULL); | |
WriteFile (houtput, text3, strlen (text3), &dummy, NULL); | |
WriteFile (houtput, text4, strlen (text4), &dummy, NULL); | |
starttime = Sys_FloatTime (); | |
sc_return_on_enter = true; // so Enter will get us out of here | |
while (!Sys_ConsoleInput () && | |
((Sys_FloatTime () - starttime) < CONSOLE_ERROR_TIMEOUT)) | |
{ | |
} | |
} | |
else | |
{ | |
// switch to windowed so the message box is visible, unless we already | |
// tried that and failed | |
if (!in_sys_error0) | |
{ | |
in_sys_error0 = 1; | |
VID_SetDefaultMode (); | |
MessageBox(NULL, text, "Quake Error", | |
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); | |
} | |
else | |
{ | |
MessageBox(NULL, text, "Double Quake Error", | |
MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); | |
} | |
} | |
if (!in_sys_error1) | |
{ | |
in_sys_error1 = 1; | |
Host_Shutdown (); | |
} | |
// shut down QHOST hooks if necessary | |
if (!in_sys_error2) | |
{ | |
in_sys_error2 = 1; | |
DeinitConProc (); | |
} | |
exit (1); | |
} | |
void Sys_Printf (char *fmt, ...) | |
{ | |
va_list argptr; | |
char text[1024]; | |
DWORD dummy; | |
if (isDedicated) | |
{ | |
va_start (argptr,fmt); | |
vsprintf (text, fmt, argptr); | |
va_end (argptr); | |
WriteFile(houtput, text, strlen (text), &dummy, NULL); | |
} | |
} | |
void Sys_Quit (void) | |
{ | |
VID_ForceUnlockedAndReturnState (); | |
Host_Shutdown(); | |
if (tevent) | |
CloseHandle (tevent); | |
if (isDedicated) | |
FreeConsole (); | |
// shut down QHOST hooks if necessary | |
DeinitConProc (); | |
exit (0); | |
} | |
/* | |
================ | |
Sys_FloatTime | |
================ | |
*/ | |
double Sys_FloatTime (void) | |
{ | |
static int sametimecount; | |
static unsigned int oldtime; | |
static int first = 1; | |
LARGE_INTEGER PerformanceCount; | |
unsigned int temp, t2; | |
double time; | |
Sys_PushFPCW_SetHigh (); | |
QueryPerformanceCounter (&PerformanceCount); | |
temp = ((unsigned int)PerformanceCount.LowPart >> lowshift) | | |
((unsigned int)PerformanceCount.HighPart << (32 - lowshift)); | |
if (first) | |
{ | |
oldtime = temp; | |
first = 0; | |
} | |
else | |
{ | |
// check for turnover or backward time | |
if ((temp <= oldtime) && ((oldtime - temp) < 0x10000000)) | |
{ | |
oldtime = temp; // so we can't get stuck | |
} | |
else | |
{ | |
t2 = temp - oldtime; | |
time = (double)t2 * pfreq; | |
oldtime = temp; | |
curtime += time; | |
if (curtime == lastcurtime) | |
{ | |
sametimecount++; | |
if (sametimecount > 100000) | |
{ | |
curtime += 1.0; | |
sametimecount = 0; | |
} | |
} | |
else | |
{ | |
sametimecount = 0; | |
} | |
lastcurtime = curtime; | |
} | |
} | |
Sys_PopFPCW (); | |
return curtime; | |
} | |
/* | |
================ | |
Sys_InitFloatTime | |
================ | |
*/ | |
void Sys_InitFloatTime (void) | |
{ | |
int j; | |
Sys_FloatTime (); | |
j = COM_CheckParm("-starttime"); | |
if (j) | |
{ | |
curtime = (double) (Q_atof(com_argv[j+1])); | |
} | |
else | |
{ | |
curtime = 0.0; | |
} | |
lastcurtime = curtime; | |
} | |
char *Sys_ConsoleInput (void) | |
{ | |
static char text[256]; | |
static int len; | |
INPUT_RECORD recs[1024]; | |
int count; | |
int i, dummy; | |
int ch, numread, numevents; | |
if (!isDedicated) | |
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 (len) | |
{ | |
text[len] = 0; | |
len = 0; | |
return text; | |
} | |
else if (sc_return_on_enter) | |
{ | |
// special case to allow exiting from the error handler on Enter | |
text[0] = '\r'; | |
len = 0; | |
return text; | |
} | |
break; | |
case '\b': | |
WriteFile(houtput, "\b \b", 3, &dummy, NULL); | |
if (len) | |
{ | |
len--; | |
} | |
break; | |
default: | |
if (ch >= ' ') | |
{ | |
WriteFile(houtput, &ch, 1, &dummy, NULL); | |
text[len] = ch; | |
len = (len + 1) & 0xff; | |
} | |
break; | |
} | |
} | |
} | |
} | |
return NULL; | |
} | |
void Sys_Sleep (void) | |
{ | |
Sleep (1); | |
} | |
void Sys_SendKeyEvents (void) | |
{ | |
MSG msg; | |
while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) | |
{ | |
// we always update if there are any event, even if we're paused | |
scr_skipupdate = 0; | |
if (!GetMessage (&msg, NULL, 0, 0)) | |
Sys_Quit (); | |
TranslateMessage (&msg); | |
DispatchMessage (&msg); | |
} | |
} | |
/* | |
============================================================================== | |
WINDOWS CRAP | |
============================================================================== | |
*/ | |
/* | |
================== | |
WinMain | |
================== | |
*/ | |
void SleepUntilInput (int time) | |
{ | |
MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT); | |
} | |
/* | |
================== | |
WinMain | |
================== | |
*/ | |
HINSTANCE global_hInstance; | |
int global_nCmdShow; | |
char *argv[MAX_NUM_ARGVS]; | |
static char *empty_string = ""; | |
HWND hwnd_dialog; | |
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) | |
{ | |
MSG msg; | |
quakeparms_t parms; | |
double time, oldtime, newtime; | |
MEMORYSTATUS lpBuffer; | |
static char cwd[1024]; | |
int t; | |
RECT rect; | |
/* previous instances do not exist in Win32 */ | |
if (hPrevInstance) | |
return 0; | |
global_hInstance = hInstance; | |
global_nCmdShow = nCmdShow; | |
lpBuffer.dwLength = sizeof(MEMORYSTATUS); | |
GlobalMemoryStatus (&lpBuffer); | |
if (!GetCurrentDirectory (sizeof(cwd), cwd)) | |
Sys_Error ("Couldn't determine current directory"); | |
if (cwd[Q_strlen(cwd)-1] == '/') | |
cwd[Q_strlen(cwd)-1] = 0; | |
parms.basedir = cwd; | |
parms.cachedir = NULL; | |
parms.argc = 1; | |
argv[0] = empty_string; | |
while (*lpCmdLine && (parms.argc < MAX_NUM_ARGVS)) | |
{ | |
while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126))) | |
lpCmdLine++; | |
if (*lpCmdLine) | |
{ | |
argv[parms.argc] = lpCmdLine; | |
parms.argc++; | |
while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126))) | |
lpCmdLine++; | |
if (*lpCmdLine) | |
{ | |
*lpCmdLine = 0; | |
lpCmdLine++; | |
} | |
} | |
} | |
parms.argv = argv; | |
COM_InitArgv (parms.argc, parms.argv); | |
parms.argc = com_argc; | |
parms.argv = com_argv; | |
isDedicated = (COM_CheckParm ("-dedicated") != 0); | |
if (!isDedicated) | |
{ | |
hwnd_dialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, NULL); | |
if (hwnd_dialog) | |
{ | |
if (GetWindowRect (hwnd_dialog, &rect)) | |
{ | |
if (rect.left > (rect.top * 2)) | |
{ | |
SetWindowPos (hwnd_dialog, 0, | |
(rect.left / 2) - ((rect.right - rect.left) / 2), | |
rect.top, 0, 0, | |
SWP_NOZORDER | SWP_NOSIZE); | |
} | |
} | |
ShowWindow (hwnd_dialog, SW_SHOWDEFAULT); | |
UpdateWindow (hwnd_dialog); | |
SetForegroundWindow (hwnd_dialog); | |
} | |
} | |
// take the greater of all the available memory or half the total memory, | |
// but at least 8 Mb and no more than 16 Mb, unless they explicitly | |
// request otherwise | |
parms.memsize = lpBuffer.dwAvailPhys; | |
if (parms.memsize < MINIMUM_WIN_MEMORY) | |
parms.memsize = MINIMUM_WIN_MEMORY; | |
if (parms.memsize < (lpBuffer.dwTotalPhys >> 1)) | |
parms.memsize = lpBuffer.dwTotalPhys >> 1; | |
if (parms.memsize > MAXIMUM_WIN_MEMORY) | |
parms.memsize = MAXIMUM_WIN_MEMORY; | |
if (COM_CheckParm ("-heapsize")) | |
{ | |
t = COM_CheckParm("-heapsize") + 1; | |
if (t < com_argc) | |
parms.memsize = Q_atoi (com_argv[t]) * 1024; | |
} | |
parms.membase = malloc (parms.memsize); | |
if (!parms.membase) | |
Sys_Error ("Not enough memory free; check disk space\n"); | |
Sys_PageIn (parms.membase, parms.memsize); | |
tevent = CreateEvent(NULL, FALSE, FALSE, NULL); | |
if (!tevent) | |
Sys_Error ("Couldn't create event"); | |
if (isDedicated) | |
{ | |
if (!AllocConsole ()) | |
{ | |
Sys_Error ("Couldn't create dedicated server console"); | |
} | |
hinput = GetStdHandle (STD_INPUT_HANDLE); | |
houtput = GetStdHandle (STD_OUTPUT_HANDLE); | |
// give QHOST a chance to hook into the console | |
if ((t = COM_CheckParm ("-HFILE")) > 0) | |
{ | |
if (t < com_argc) | |
hFile = (HANDLE)Q_atoi (com_argv[t+1]); | |
} | |
if ((t = COM_CheckParm ("-HPARENT")) > 0) | |
{ | |
if (t < com_argc) | |
heventParent = (HANDLE)Q_atoi (com_argv[t+1]); | |
} | |
if ((t = COM_CheckParm ("-HCHILD")) > 0) | |
{ | |
if (t < com_argc) | |
heventChild = (HANDLE)Q_atoi (com_argv[t+1]); | |
} | |
InitConProc (hFile, heventParent, heventChild); | |
} | |
Sys_Init (); | |
// because sound is off until we become active | |
S_BlockSound (); | |
Sys_Printf ("Host_Init\n"); | |
Host_Init (&parms); | |
oldtime = Sys_FloatTime (); | |
/* main window message loop */ | |
while (1) | |
{ | |
if (isDedicated) | |
{ | |
newtime = Sys_FloatTime (); | |
time = newtime - oldtime; | |
while (time < sys_ticrate.value ) | |
{ | |
Sys_Sleep(); | |
newtime = Sys_FloatTime (); | |
time = newtime - oldtime; | |
} | |
} | |
else | |
{ | |
// yield the CPU for a little while when paused, minimized, or not the focus | |
if ((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing) | |
{ | |
SleepUntilInput (PAUSE_SLEEP); | |
scr_skipupdate = 1; // no point in bothering to draw | |
} | |
else if (!ActiveApp && !DDActive) | |
{ | |
SleepUntilInput (NOT_FOCUS_SLEEP); | |
} | |
newtime = Sys_FloatTime (); | |
time = newtime - oldtime; | |
} | |
Host_Frame (time); | |
oldtime = newtime; | |
} | |
/* return success of application */ | |
return TRUE; | |
} | |