/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code 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. Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // win_main.c #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" #ifndef DEDICATED #include "../client/client.h" #include "glw_win.h" #endif #include "win_local.h" #include "resource.h" #include #include #include #include #define MEM_THRESHOLD (96*1024*1024) WinVars_t g_wv; /* ================== Sys_LowPhysicalMemory ================== */ qboolean Sys_LowPhysicalMemory( void ) { MEMORYSTATUS stat; GlobalMemoryStatus( &stat ); return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse; } /* ================== Sys_BeginProfiling ================== */ void Sys_BeginProfiling( void ) { // this is just used on the mac build } /* ============= 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 ); Q_vsnprintf( text, sizeof( text ), error, argptr ); va_end( argptr ); #ifndef DEDICATED CL_Shutdown( text, qtrue ); #endif Conbuf_AppendText( text ); Conbuf_AppendText( "\n" ); Sys_SetErrorText( text ); Sys_ShowConsole( 1, qtrue ); timeEndPeriod( 1 ); // wait for the user to quit while ( 1 ) { if ( GetMessage( &msg, NULL, 0, 0 ) <= 0 ) { Cmd_Clear(); Com_Quit_f(); } TranslateMessage( &msg ); DispatchMessage( &msg ); } Sys_DestroyConsole(); exit( 1 ); } /* ============== Sys_Quit ============== */ void Sys_Quit( void ) { timeEndPeriod( 1 ); Sys_DestroyConsole(); exit( 0 ); } /* ============== Sys_Print ============== */ void Sys_Print( const char *msg ) { Conbuf_AppendText( msg ); } /* ============== Sys_Mkdir ============== */ void Sys_Mkdir( const char *path ) { _mkdir( path ); } /* ============== Sys_FOpen ============== */ FILE *Sys_FOpen( const char *ospath, const char *mode ) { size_t length; // Windows API ignores all trailing spaces and periods which can get around Quake 3 file system restrictions. length = strlen( ospath ); if ( length == 0 || ospath[length-1] == ' ' || ospath[length-1] == '.' ) { return NULL; } return fopen( ospath, mode ); } /* ============== Sys_ResetReadOnlyAttribute ============== */ qboolean Sys_ResetReadOnlyAttribute( const char *ospath ) { DWORD dwAttr; dwAttr = GetFileAttributesA( ospath ); if ( dwAttr & FILE_ATTRIBUTE_READONLY ) { dwAttr &= ~FILE_ATTRIBUTE_READONLY; if ( SetFileAttributesA( ospath, dwAttr ) ) { return qtrue; } else { return qfalse; } } else { return qfalse; } } /* ============== Sys_Pwd ============== */ const char *Sys_Pwd( void ) { static char pwd[ MAX_OSPATH ]; TCHAR buffer[ MAX_OSPATH ]; char *s; if ( pwd[0] ) return pwd; GetModuleFileName( NULL, buffer, ARRAY_LEN( buffer ) ); buffer[ ARRAY_LEN( buffer ) - 1 ] = '\0'; Q_strncpyz( pwd, WtoA( buffer ), sizeof( pwd ) ); s = strrchr( pwd, PATH_SEP ); if ( s ) *s = '\0'; else // bogus case? { _getcwd( pwd, sizeof( pwd ) - 1 ); pwd[ sizeof( pwd ) - 1 ] = '\0'; } return pwd; } /* ============== Sys_DefaultBasePath ============== */ const char *Sys_DefaultBasePath( void ) { return Sys_Pwd(); } /* ============================================================== DIRECTORY SCANNING ============================================================== */ void Sys_ListFilteredFiles( const char *basedir, const char *subdirs, const char *filter, char **list, int *numfiles ) { char search[MAX_OSPATH*2+1]; char newsubdirs[MAX_OSPATH*2]; char filename[MAX_OSPATH*2]; intptr_t findhandle; struct _finddata_t findinfo; if ( *numfiles >= MAX_FOUND_FILES - 1 ) { return; } if ( *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_streq( findinfo.name, "." ) && !Q_streq( findinfo.name, ".." ) ) { if ( *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, list, numfiles ); } } if ( *numfiles >= MAX_FOUND_FILES - 1 ) { break; } Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name ); if ( !Com_FilterPath( filter, filename ) ) continue; list[ *numfiles ] = FS_CopyString( filename ); (*numfiles)++; } while ( _findnext (findhandle, &findinfo) != -1 ); _findclose (findhandle); } /* ============= Sys_Sleep ============= */ void Sys_Sleep( int msec ) { if ( msec < 0 ) { // special case: wait for event or network packet DWORD dwResult; msec = 300; do { dwResult = MsgWaitForMultipleObjects( 0, NULL, FALSE, msec, QS_ALLEVENTS ); } while ( dwResult == WAIT_TIMEOUT && NET_Sleep( 10 * 1000 ) ); //WaitMessage(); return; } // busy wait there because Sleep(0) will relinquish CPU - which is not what we want //if ( msec == 0 ) // return; Sleep ( msec ); } /* ============= Sys_ListFiles ============= */ char **Sys_ListFiles( const char *directory, const char *extension, const char *filter, int *numfiles, qboolean wantsubs ) { char search[MAX_OSPATH*2+MAX_QPATH+1]; int nfiles; char **listCopy; char *list[MAX_FOUND_FILES]; struct _finddata_t findinfo; intptr_t findhandle; int flag; int extLen; int length; int i; const char *x; qboolean hasPatterns; if ( filter ) { nfiles = 0; Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); list[ nfiles ] = NULL; *numfiles = nfiles; if (!nfiles) return NULL; listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( listCopy[0] ) ); 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 ); findhandle = _findfirst( search, &findinfo ); if ( findhandle == -1 ) { *numfiles = 0; return NULL; } extLen = (int)strlen( extension ); hasPatterns = Com_HasPatterns( extension ); if ( hasPatterns && extension[0] == '.' && extension[1] != '\0' ) { extension++; } // search nfiles = 0; do { if ( (!wantsubs && flag ^ ( findinfo.attrib & _A_SUBDIR )) || (wantsubs && findinfo.attrib & _A_SUBDIR) ) { if ( nfiles == MAX_FOUND_FILES - 1 ) { break; } if ( *extension ) { if ( hasPatterns ) { x = strrchr( findinfo.name, '.' ); if ( !x || !Com_FilterExt( extension, x+1 ) ) { continue; } } else { length = strlen( findinfo.name ); if ( length < extLen || Q_stricmp( findinfo.name + length - extLen, extension ) ) { continue; } } } list[ nfiles ] = FS_CopyString( findinfo.name ); nfiles++; } } while ( _findnext (findhandle, &findinfo) != -1 ); list[ nfiles ] = NULL; _findclose (findhandle); // return a copy of the list *numfiles = nfiles; if ( !nfiles ) { return NULL; } listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( listCopy[0] ) ); for ( i = 0 ; i < nfiles ; i++ ) { listCopy[i] = list[i]; } listCopy[i] = NULL; Com_SortFileList( listCopy, nfiles, extension[0] != '\0' ); return listCopy; } /* ============= Sys_FreeFileList ============= */ void Sys_FreeFileList( char **list ) { int i; if ( !list ) { return; } for ( i = 0 ; list[i] ; i++ ) { Z_Free( list[i] ); } Z_Free( list ); } /* ============= Sys_GetFileStats ============= */ qboolean Sys_GetFileStats( const char *filename, fileOffset_t *size, fileTime_t *mtime, fileTime_t *ctime ) { struct _stat s; if ( _stat( filename, &s ) == 0 ) { *size = (fileOffset_t)s.st_size; *mtime = (fileTime_t)s.st_mtime; *ctime = (fileTime_t)s.st_ctime; return qtrue; } else { *size = 0; *mtime = *ctime = 0; return qfalse; } } //======================================================== /* ======================================================================== LOAD/UNLOAD DLL ======================================================================== */ static int dll_err_count = 0; /* ================= Sys_LoadLibrary ================= */ void *Sys_LoadLibrary( const char *name ) { const char *ext; if ( !name || !*name ) return NULL; if ( FS_AllowedExtension( name, qfalse, &ext ) ) { Com_Error( ERR_FATAL, "Sys_LoadLibrary: Unable to load library with '%s' extension", ext ); } return (void *)LoadLibrary( AtoW( name ) ); } /* ================= Sys_LoadFunction ================= */ void *Sys_LoadFunction( void *handle, const char *name ) { void *symbol; if ( handle == NULL || name == NULL || *name == '\0' ) { dll_err_count++; return NULL; } symbol = GetProcAddress( handle, name ); if ( !symbol ) dll_err_count++; return symbol; } /* ================= Sys_LoadFunctionErrors ================= */ int Sys_LoadFunctionErrors( void ) { int result = dll_err_count; dll_err_count = 0; return result; } /* ================= Sys_UnloadLibrary ================= */ void Sys_UnloadLibrary( void *handle ) { if ( handle ) FreeLibrary( handle ); } /* ================= Sys_SendKeyEvents Platform-dependent event handling ================= */ void Sys_SendKeyEvents( void ) { #ifndef DEDICATED if ( !com_dedicated->integer ) HandleEvents(); else #endif HandleConsoleEvents(); } //================================================================ /* ================== SetTimerResolution Try to set lower timer period ================== */ static void SetTimerResolution( void ) { typedef HRESULT (WINAPI *pfnNtQueryTimerResolution)( PULONG MinRes, PULONG MaxRes, PULONG CurRes ); typedef HRESULT (WINAPI *pfnNtSetTimerResolution)( ULONG NewRes, BOOLEAN SetRes, PULONG CurRes ); pfnNtQueryTimerResolution pNtQueryTimerResolution; pfnNtSetTimerResolution pNtSetTimerResolution; ULONG curr, minr, maxr; HMODULE dll; dll = LoadLibrary( T( "ntdll" ) ); if ( dll ) { pNtQueryTimerResolution = (pfnNtQueryTimerResolution) GetProcAddress( dll, "NtQueryTimerResolution" ); pNtSetTimerResolution = (pfnNtSetTimerResolution) GetProcAddress( dll, "NtSetTimerResolution" ); if ( pNtQueryTimerResolution && pNtSetTimerResolution ) { pNtQueryTimerResolution( &minr, &maxr, &curr ); if ( maxr < 5000 ) // well, we don't need less than 0.5ms periods for select() maxr = 5000; pNtSetTimerResolution( maxr, TRUE, &curr ); } FreeLibrary( dll ); } } /* ================ Sys_Init Called after the common systems (cvars, files, etc) are initialized ================ */ void Sys_Init( void ) { // make sure the timer is high precision, otherwise // NT gets 18ms resolution timeBeginPeriod( 1 ); SetTimerResolution(); Cvar_Set( "arch", "winnt" ); } //======================================================================= /* ================== SetDPIAwareness ================== */ #if 0 static void SetDPIAwareness( void ) { typedef HANDLE (WINAPI *pfnSetThreadDpiAwarenessContext)( HANDLE dpiContext ); typedef HRESULT (WINAPI *pfnSetProcessDpiAwareness)( int value ); pfnSetThreadDpiAwarenessContext pSetThreadDpiAwarenessContext; pfnSetProcessDpiAwareness pSetProcessDpiAwareness; HMODULE dll; dll = GetModuleHandle( T("user32") ); if ( dll ) { pSetThreadDpiAwarenessContext = (pfnSetThreadDpiAwarenessContext) GetProcAddress( dll, "SetThreadDpiAwarenessContext" ); if ( pSetThreadDpiAwarenessContext ) { pSetThreadDpiAwarenessContext( (HANDLE)(intptr_t)-2 ); // DPI_AWARENESS_CONTEXT_SYSTEM_AWARE } } dll = LoadLibrary( T("shcore") ); if ( dll ) { pSetProcessDpiAwareness = (pfnSetProcessDpiAwareness) GetProcAddress( dll, "SetProcessDpiAwareness" ); if ( pSetProcessDpiAwareness ) { pSetProcessDpiAwareness( 2 ); // PROCESS_PER_MONITOR_DPI_AWARE } FreeLibrary( dll ); } } #endif static const char *GetExceptionName( DWORD code ) { static char buf[ 32 ]; switch ( code ) { case EXCEPTION_ACCESS_VIOLATION: return "ACCESS_VIOLATION"; case EXCEPTION_DATATYPE_MISALIGNMENT: return "DATATYPE_MISALIGNMENT"; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "ARRAY_BOUNDS_EXCEEDED"; case EXCEPTION_PRIV_INSTRUCTION: return "PRIV_INSTRUCTION"; case EXCEPTION_IN_PAGE_ERROR: return "IN_PAGE_ERROR"; case EXCEPTION_ILLEGAL_INSTRUCTION: return "ILLEGAL_INSTRUCTION"; case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "NONCONTINUABLE_EXCEPTION"; case EXCEPTION_STACK_OVERFLOW: return "STACK_OVERFLOW"; case EXCEPTION_INVALID_DISPOSITION: return "INVALID_DISPOSITION"; case EXCEPTION_GUARD_PAGE: return "GUARD_PAGE"; case EXCEPTION_INVALID_HANDLE: return "INVALID_HANDLE"; default: break; } sprintf( buf, "0x%08X", (unsigned int)code ); return buf; } /* ================== ExceptionFilter Restore gamma and hide fullscreen window in case of crash ================== */ static LONG WINAPI ExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) { #ifndef DEDICATED if ( com_dedicated->integer == 0 ) { extern cvar_t *com_cl_running; if ( com_cl_running && com_cl_running->integer ) { // assume we can restart client module } else { GLW_RestoreGamma(); if ( g_wv.hWnd && glw_state.cdsFullscreen ) ShowWindow( g_wv.hWnd, SW_HIDE ); } } #endif if ( ExceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT ) { char msg[128]; byte *addr, *base; qboolean vma; addr = (byte*)ExceptionInfo->ExceptionRecord->ExceptionAddress; base = (byte*)GetModuleHandle( NULL ); if ( addr >= base ) { addr = (byte*)(addr - base); vma = qtrue; } else { vma = qfalse; } sprintf( msg, "Exception Code: %s\nException Address: %p%s", GetExceptionName( ExceptionInfo->ExceptionRecord->ExceptionCode ), addr, vma ? " (VMA)" : "" ); Com_Error( ERR_DROP, "Unhandled exception caught\n%s", msg ); } return EXCEPTION_EXECUTE_HANDLER; } /* ================== WinMain ================== */ int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { static char sys_cmdline[ MAX_STRING_CHARS ]; char con_title[ MAX_CVAR_VALUE_STRING ]; int xpos, ypos; qboolean useXYpos; HANDLE hProcess; DWORD dwPriority; // should never get a previous instance in Win32 if ( hPrevInstance ) { return 0; } // slightly boost process priority if it set to default hProcess = GetCurrentProcess(); dwPriority = GetPriorityClass( hProcess ); if ( dwPriority == NORMAL_PRIORITY_CLASS || dwPriority == ABOVE_NORMAL_PRIORITY_CLASS ) { SetPriorityClass( hProcess, HIGH_PRIORITY_CLASS ); } //SetDPIAwareness(); g_wv.hInstance = hInstance; Q_strncpyz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) ); useXYpos = Com_EarlyParseCmdLine( sys_cmdline, con_title, sizeof( con_title ), &xpos, &ypos ); // done before Com/Sys_Init since we need this for error output Sys_CreateConsole( con_title, xpos, ypos, useXYpos ); // no abort/retry/fail errors SetErrorMode( SEM_FAILCRITICALERRORS ); SetUnhandledExceptionFilter( ExceptionFilter ); // get the initial time base Sys_Milliseconds(); Com_Init( sys_cmdline ); NET_Init(); Com_Printf( "Working directory: %s\n", Sys_Pwd() ); // 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 ); } // main game loop while( 1 ) { // set low precision every frame, because some system calls // reset it arbitrarily // _controlfp( _PC_24, _MCW_PC ); // _controlfp( -1, _MCW_EM ); // no exceptions, even if some crappy syscall turns them back on! #ifdef DEDICATED // run the game Com_Frame( qfalse ); #else // make sure mouse and joystick are only called once a frame IN_Frame(); // run the game Com_Frame( CL_NoDelay() ); #endif } // never gets here return 0; }