1 /* 2 Copyright (C) 1996-1997 Id Software, Inc. 3 4 This program is free software; you can redistribute it and/or 5 modify it under the terms of the GNU General Public License 6 as published by the Free Software Foundation; either version 2 7 of the License, or (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 13 See the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, 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 36 int starttime; 37 qboolean ActiveApp, Minimized; 38 qboolean WinNT; 39 40 static double pfreq; 41 static double curtime = 0.0; 42 static double lastcurtime = 0.0; 43 static int lowshift; 44 qboolean isDedicated; 45 static qboolean sc_return_on_enter = false; 46 HANDLE hinput, houtput; 47 48 static char *tracking_tag = "Clams & Mooses"; 49 50 static HANDLE tevent; 51 static HANDLE hFile; 52 static HANDLE heventParent; 53 static HANDLE heventChild; 54 55 void MaskExceptions (void); 56 void Sys_InitFloatTime (void); 57 void Sys_PushFPCW_SetHigh (void); 58 void Sys_PopFPCW (void); 59 60 volatile int sys_checksum; 61 62 63 /* 64 ================ 65 Sys_PageIn 66 ================ 67 */ 68 void 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 92 FILE IO 93 94 =============================================================================== 95 */ 96 97 #define MAX_HANDLES 10 98 FILE *sys_handles[MAX_HANDLES]; 99 100 int 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 ================ 113 filelength 114 ================ 115 */ 116 int 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 134 int 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 163 int 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 183 void 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 193 void 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 202 int 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 212 int 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 222 int 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 245 void Sys_mkdir (char *path) 246 { 247 _mkdir (path); 248 } 249 250 251 /* 252 =============================================================================== 253 254 SYSTEM IO 255 256 =============================================================================== 257 */ 258 259 /* 260 ================ 261 Sys_MakeCodeWriteable 262 ================ 263 */ 264 void 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 275 void Sys_SetFPCW (void) 276 { 277 } 278 279 void Sys_PushFPCW_SetHigh (void) 280 { 281 } 282 283 void Sys_PopFPCW (void) 284 { 285 } 286 287 void MaskExceptions (void) 288 { 289 } 290 291 #endif 292 293 /* 294 ================ 295 Sys_Init 296 ================ 297 */ 298 void 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 346 void 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 426 void 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 442 void 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 ================ 464 Sys_FloatTime 465 ================ 466 */ 467 double 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 ================ 531 Sys_InitFloatTime 532 ================ 533 */ 534 void 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 555 char *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 635 void Sys_Sleep (void) 636 { 637 Sleep (1); 638 } 639 640 641 void 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 ================== 670 WinMain 671 ================== 672 */ 673 void SleepUntilInput (int time) 674 { 675 676 MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT); 677 } 678 679 680 /* 681 ================== 682 WinMain 683 ================== 684 */ 685 HINSTANCE global_hInstance; 686 int global_nCmdShow; 687 char *argv[MAX_NUM_ARGVS]; 688 static char *empty_string = ""; 689 HWND hwnd_dialog; 690 691 692 int 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