/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech 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 3 of the License, or (at your option) any later version. IceTech 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 IceTech Source Code. If not, see . In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include #include #include #include #include #include #include #include #ifndef __MRC__ #include #include #endif #include "../sys_local.h" #include "win_local.h" #include "rc/CreateResourceIDs.h" #include "../../renderer/tr_local.h" idCVar Win32Vars_t::sys_arch("sys_arch", "", CVAR_SYSTEM | CVAR_INIT, ""); idCVar Win32Vars_t::sys_cpustring("sys_cpustring", "detect", CVAR_SYSTEM | CVAR_INIT, ""); idCVar Win32Vars_t::in_mouse("in_mouse", "1", CVAR_SYSTEM | CVAR_BOOL, "enable mouse input"); idCVar Win32Vars_t::win_allowAltTab("win_allowAltTab", "0", CVAR_SYSTEM | CVAR_BOOL, "allow Alt-Tab when fullscreen"); idCVar Win32Vars_t::win_notaskkeys("win_notaskkeys", "0", CVAR_SYSTEM | CVAR_INTEGER, "disable windows task keys"); idCVar Win32Vars_t::win_username("win_username", "", CVAR_SYSTEM | CVAR_INIT, "windows user name"); idCVar Win32Vars_t::win_xpos("win_xpos", "3", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "horizontal position of window"); idCVar Win32Vars_t::win_ypos("win_ypos", "22", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "vertical position of window"); idCVar Win32Vars_t::win_outputDebugString("win_outputDebugString", "1", CVAR_SYSTEM | CVAR_BOOL, ""); idCVar Win32Vars_t::win_outputEditString("win_outputEditString", "1", CVAR_SYSTEM | CVAR_BOOL, ""); idCVar Win32Vars_t::win_viewlog("win_viewlog", "0", CVAR_SYSTEM | CVAR_INTEGER, ""); idCVar Win32Vars_t::win_timerUpdate("win_timerUpdate", "0", CVAR_SYSTEM | CVAR_BOOL, "allows the game to be updated while dragging the window"); idCVar Win32Vars_t::win_allowMultipleInstances("win_allowMultipleInstances", "0", CVAR_SYSTEM | CVAR_BOOL, "allow multiple instances running concurrently"); Win32Vars_t win32; static char sys_cmdline[MAX_STRING_CHARS]; // not a hard limit, just what we keep track of for debugging xthreadInfo* g_threads[MAX_THREADS]; int g_thread_count = 0; static sysMemoryStats_t exeLaunchMemoryStats; static xthreadInfo threadInfo; static HANDLE hTimer; /* ================ Sys_GetExeLaunchMemoryStatus ================ */ void Sys_GetExeLaunchMemoryStatus(sysMemoryStats_t& stats) { stats = exeLaunchMemoryStats; } /* ================== Sys_Createthread ================== */ void Sys_CreateThread(xthread_t function, void* parms, xthreadPriority priority, xthreadInfo& info, const char* name, xthreadInfo* threads[MAX_THREADS], int* thread_count) { HANDLE temp = CreateThread(NULL, // LPSECURITY_ATTRIBUTES lpsa, 0, // DWORD cbStack, (LPTHREAD_START_ROUTINE)function, // LPTHREAD_START_ROUTINE lpStartAddr, parms, // LPVOID lpvThreadParm, 0, // DWORD fdwCreate, &info.threadId); info.threadHandle = (int)temp; if (priority == THREAD_HIGHEST) { SetThreadPriority((HANDLE)info.threadHandle, THREAD_PRIORITY_HIGHEST); // we better sleep enough to do this } else if (priority == THREAD_ABOVE_NORMAL) { SetThreadPriority((HANDLE)info.threadHandle, THREAD_PRIORITY_ABOVE_NORMAL); } info.name = name; if (*thread_count < MAX_THREADS) { threads[(*thread_count)++] = &info; } else { common->DPrintf("WARNING: MAX_THREADS reached\n"); } } /* ================== Sys_DestroyThread ================== */ void Sys_DestroyThread(xthreadInfo& info) { WaitForSingleObject((HANDLE)info.threadHandle, INFINITE); CloseHandle((HANDLE)info.threadHandle); info.threadHandle = 0; } /* ================== Sys_Sentry ================== */ void Sys_Sentry() { int j = 0; } /* ================== Sys_GetThreadName ================== */ const char* Sys_GetThreadName(int* index) { int id = GetCurrentThreadId(); for (int i = 0; i < g_thread_count; i++) { if (id == g_threads[i]->threadId) { if (index) { *index = i; } return g_threads[i]->name; } } if (index) { *index = -1; } return "main"; } /* ================== Sys_EnterCriticalSection ================== */ void Sys_EnterCriticalSection(int index) { assert(index >= 0 && index < MAX_CRITICAL_SECTIONS); if (TryEnterCriticalSection(&win32.criticalSections[index]) == 0) { EnterCriticalSection(&win32.criticalSections[index]); // Sys_DebugPrintf( "busy lock '%s' in thread '%s'\n", lock->name, Sys_GetThreadName() ); } } /* ================== Sys_LeaveCriticalSection ================== */ void Sys_LeaveCriticalSection(int index) { assert(index >= 0 && index < MAX_CRITICAL_SECTIONS); LeaveCriticalSection(&win32.criticalSections[index]); } /* ================== Sys_WaitForEvent ================== */ void Sys_WaitForEvent(int index) { assert(index == 0); if (!win32.backgroundDownloadSemaphore) { win32.backgroundDownloadSemaphore = CreateEvent(NULL, TRUE, FALSE, NULL); } WaitForSingleObject(win32.backgroundDownloadSemaphore, INFINITE); ResetEvent(win32.backgroundDownloadSemaphore); } /* ================== Sys_TriggerEvent ================== */ void Sys_TriggerEvent(int index) { assert(index == 0); SetEvent(win32.backgroundDownloadSemaphore); } #pragma optimize( "", on ) #ifdef DEBUG static unsigned int debug_total_alloc = 0; static unsigned int debug_total_alloc_count = 0; static unsigned int debug_current_alloc = 0; static unsigned int debug_current_alloc_count = 0; static unsigned int debug_frame_alloc = 0; static unsigned int debug_frame_alloc_count = 0; idCVar sys_showMallocs("sys_showMallocs", "0", CVAR_SYSTEM, ""); // _HOOK_ALLOC, _HOOK_REALLOC, _HOOK_FREE typedef struct CrtMemBlockHeader { struct _CrtMemBlockHeader* pBlockHeaderNext; // Pointer to the block allocated just before this one: struct _CrtMemBlockHeader* pBlockHeaderPrev; // Pointer to the block allocated just after this one char* szFileName; // File name int nLine; // Line number size_t nDataSize; // Size of user block int nBlockUse; // Type of block long lRequest; // Allocation number byte gap[4]; // Buffer just before (lower than) the user's memory: } CrtMemBlockHeader; #include /* ================== Sys_AllocHook called for every malloc/new/free/delete ================== */ int Sys_AllocHook(int nAllocType, void* pvData, size_t nSize, int nBlockUse, long lRequest, const unsigned char* szFileName, int nLine) { CrtMemBlockHeader* pHead; byte* temp; if (nBlockUse == _CRT_BLOCK) { return(TRUE); } // get a pointer to memory block header temp = (byte*)pvData; temp -= 32; pHead = (CrtMemBlockHeader*)temp; switch (nAllocType) { case _HOOK_ALLOC: debug_total_alloc += nSize; debug_current_alloc += nSize; debug_frame_alloc += nSize; debug_total_alloc_count++; debug_current_alloc_count++; debug_frame_alloc_count++; break; case _HOOK_FREE: assert(pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd); debug_current_alloc -= pHead->nDataSize; debug_current_alloc_count--; debug_total_alloc_count++; debug_frame_alloc_count++; break; case _HOOK_REALLOC: assert(pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd); debug_current_alloc -= pHead->nDataSize; debug_total_alloc += nSize; debug_current_alloc += nSize; debug_frame_alloc += nSize; debug_total_alloc_count++; debug_current_alloc_count--; debug_frame_alloc_count++; break; } return(TRUE); } /* ================== Sys_DebugMemory_f ================== */ void Sys_DebugMemory_f(void) { common->Printf("Total allocation %8dk in %d blocks\n", debug_total_alloc / 1024, debug_total_alloc_count); common->Printf("Current allocation %8dk in %d blocks\n", debug_current_alloc / 1024, debug_current_alloc_count); } /* ================== Sys_MemFrame ================== */ void Sys_MemFrame(void) { if (sys_showMallocs.GetInteger()) { common->Printf("Frame: %8dk in %5d blocks\n", debug_frame_alloc / 1024, debug_frame_alloc_count); } debug_frame_alloc = 0; debug_frame_alloc_count = 0; } #endif /* ================== Sys_FlushCacheMemory On windows, the vertex buffers are write combined, so they don't need to be flushed from the cache ================== */ void Sys_FlushCacheMemory(void* base, int bytes) { } /* ============= Sys_Error Show the early console as an error dialog ============= */ void Sys_Error(const char* error, ...) { va_list argptr; char text[4096]; MSG msg; va_start(argptr, error); vsprintf(text, error, argptr); va_end(argptr); Conbuf_AppendText(text); Conbuf_AppendText("\n"); Win_SetErrorText(text); Sys_ShowConsole(1, true); timeEndPeriod(1); Sys_ShutdownInput(); GLimp_Shutdown(); // wait for the user to quit while (1) { if (!GetMessage(&msg, NULL, 0, 0)) { common->Quit(); } TranslateMessage(&msg); DispatchMessage(&msg); } Sys_DestroyConsole(); exit(1); } /* ============== Sys_Quit ============== */ void Sys_Quit(void) { timeEndPeriod(1); Sys_ShutdownInput(); Sys_DestroyConsole(); ExitProcess(0); } /* ============== Sys_Printf ============== */ #define MAXPRINTMSG 4096 void Sys_Printf(const char* fmt, ...) { char msg[MAXPRINTMSG]; va_list argptr; va_start(argptr, fmt); idStr::vsnPrintf(msg, MAXPRINTMSG - 1, fmt, argptr); va_end(argptr); msg[sizeof(msg) - 1] = '\0'; if (win32.win_outputDebugString.GetBool()) { OutputDebugString(msg); } if (win32.win_outputEditString.GetBool()) { Conbuf_AppendText(msg); } } /* ============== Sys_DebugPrintf ============== */ #define MAXPRINTMSG 4096 void Sys_DebugPrintf(const char* fmt, ...) { char msg[MAXPRINTMSG]; va_list argptr; va_start(argptr, fmt); idStr::vsnPrintf(msg, MAXPRINTMSG - 1, fmt, argptr); msg[sizeof(msg) - 1] = '\0'; va_end(argptr); OutputDebugString(msg); } /* ============== Sys_DebugVPrintf ============== */ void Sys_DebugVPrintf(const char* fmt, va_list arg) { char msg[MAXPRINTMSG]; idStr::vsnPrintf(msg, MAXPRINTMSG - 1, fmt, arg); msg[sizeof(msg) - 1] = '\0'; OutputDebugString(msg); } /* ============== Sys_Sleep ============== */ void Sys_Sleep(int msec) { Sleep(msec); } /* ============== Sys_ShowWindow ============== */ void Sys_ShowWindow(bool show) { ::ShowWindow(win32.hWnd, show ? SW_SHOW : SW_HIDE); } /* ============== Sys_IsWindowVisible ============== */ bool Sys_IsWindowVisible(void) { return (::IsWindowVisible(win32.hWnd) != 0); } /* ============== Sys_Mkdir ============== */ void Sys_Mkdir(const char* path) { _mkdir(path); } /* ================= Sys_FileTimeStamp ================= */ ID_TIME_T Sys_FileTimeStamp(FILE* fp) { struct _stat st; _fstat(_fileno(fp), &st); return (long)st.st_mtime; } /* ============== Sys_Cwd ============== */ const char* Sys_Cwd(void) { static char cwd[MAX_OSPATH]; _getcwd(cwd, sizeof(cwd) - 1); cwd[MAX_OSPATH - 1] = 0; return cwd; } /* ============== Sys_DefaultCDPath ============== */ const char* Sys_DefaultCDPath(void) { return ""; } /* ============== Sys_DefaultBasePath ============== */ const char* Sys_DefaultBasePath(void) { return Sys_Cwd(); } /* ============== Sys_DefaultSavePath ============== */ const char* Sys_DefaultSavePath(void) { return cvarSystem->GetCVarString("fs_basepath"); } /* ============== Sys_EXEPath ============== */ const char* Sys_EXEPath(void) { static char exe[MAX_OSPATH]; GetModuleFileName(NULL, exe, sizeof(exe) - 1); return exe; } /* ============== Sys_ListFiles ============== */ int Sys_ListFiles(const char* directory, const char* extension, idStrList& list) { idStr search; struct _finddata_t findinfo; INT_PTR findhandle; int flag; if (!extension) { extension = ""; } // passing a slash as extension will find directories if (extension[0] == '/' && extension[1] == 0) { extension = ""; flag = 0; } else { flag = _A_SUBDIR; } sprintf(search, "%s\\*%s", directory, extension); // search list.Clear(); findhandle = _findfirst(search, &findinfo); if (findhandle == -1) { return -1; } do { if (flag ^ (findinfo.attrib & _A_SUBDIR)) { list.Append(findinfo.name); } } while (_findnext(findhandle, &findinfo) != -1); _findclose(findhandle); return list.Num(); } /* ================ Sys_GetClipboardData ================ */ char* Sys_GetClipboardData(void) { char* data = NULL; char* cliptext; if (OpenClipboard(NULL) != 0) { HANDLE hClipboardData; if ((hClipboardData = GetClipboardData(CF_TEXT)) != 0) { if ((cliptext = (char*)GlobalLock(hClipboardData)) != 0) { data = (char*)Mem_Alloc(GlobalSize(hClipboardData) + 1); strcpy(data, cliptext); GlobalUnlock(hClipboardData); strtok(data, "\n\r\b"); } } CloseClipboard(); } return data; } /* ================ Sys_SetClipboardData ================ */ void Sys_SetClipboardData(const char* string) { HGLOBAL HMem; char* PMem; // allocate memory block HMem = (char*)::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, strlen(string) + 1); if (HMem == NULL) { return; } // lock allocated memory and obtain a pointer PMem = (char*)::GlobalLock(HMem); if (PMem == NULL) { return; } // copy text into allocated memory block lstrcpy(PMem, string); // unlock allocated memory ::GlobalUnlock(HMem); // open Clipboard if (!OpenClipboard(0)) { ::GlobalFree(HMem); return; } // remove current Clipboard contents EmptyClipboard(); // supply the memory handle to the Clipboard SetClipboardData(CF_TEXT, HMem); HMem = 0; // close Clipboard CloseClipboard(); } /* ======================================================================== DLL Loading ======================================================================== */ /* ===================== Sys_DLL_Load ===================== */ INT_PTR Sys_DLL_Load(const char* dllName) { HINSTANCE libHandle; libHandle = LoadLibrary(dllName); if (libHandle) { // since we can't have LoadLibrary load only from the specified path, check it did the right thing char loadedPath[MAX_OSPATH]; GetModuleFileName(libHandle, loadedPath, sizeof(loadedPath) - 1); if (idStr::IcmpPath(dllName, loadedPath)) { Sys_Printf("ERROR: LoadLibrary '%s' wants to load '%s'\n", dllName, loadedPath); Sys_DLL_Unload((int)libHandle); return 0; } } return (INT_PTR)libHandle; } /* ===================== Sys_DLL_GetProcAddress ===================== */ void* Sys_DLL_GetProcAddress(INT_PTR dllHandle, const char* procName) { return GetProcAddress((HMODULE)dllHandle, procName); } /* ===================== Sys_DLL_Unload ===================== */ void Sys_DLL_Unload(INT_PTR dllHandle) { if (!dllHandle) { return; } if (FreeLibrary((HINSTANCE)dllHandle) == 0) { int lastError = GetLastError(); LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR)&lpMsgBuf, 0, NULL ); Sys_Error("Sys_DLL_Unload: FreeLibrary failed - %s (%d)", lpMsgBuf, lastError); } } /* ======================================================================== EVENT LOOP ======================================================================== */ #define MAX_QUED_EVENTS 256 #define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) sysEvent_t eventQue[MAX_QUED_EVENTS]; int eventHead = 0; int eventTail = 0; /* ================ Sys_QueEvent Ptr should either be null, or point to a block of data that can be freed by the game later. ================ */ void Sys_QueEvent(int time, sysEventType_t type, int value, int value2, int ptrLength, void* ptr) { sysEvent_t* ev; ev = &eventQue[eventHead & MASK_QUED_EVENTS]; if (eventHead - eventTail >= MAX_QUED_EVENTS) { common->Printf("Sys_QueEvent: overflow\n"); // we are discarding an event, but don't leak memory if (ev->evPtr) { Mem_Free(ev->evPtr); } eventTail++; } eventHead++; ev->evType = type; ev->evValue = value; ev->evValue2 = value2; ev->evPtrLength = ptrLength; ev->evPtr = ptr; } /* ============= Sys_PumpEvents This allows windows to be moved during renderbump ============= */ void Sys_PumpEvents(void) { MSG msg; // pump the message loop while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage(&msg, NULL, 0, 0)) { common->Quit(); } // save the msg time, because wndprocs don't have access to the timestamp if (win32.sysMsgTime && win32.sysMsgTime > (int)msg.time) { // don't ever let the event times run backwards // common->Printf( "Sys_PumpEvents: win32.sysMsgTime (%i) > msg.time (%i)\n", win32.sysMsgTime, msg.time ); } else { win32.sysMsgTime = msg.time; } #ifdef ID_ALLOW_TOOLS if (GUIEditorHandleMessage(&msg)) { continue; } #endif TranslateMessage(&msg); DispatchMessage(&msg); } } /* ================ Sys_GenerateEvents ================ */ void Sys_GenerateEvents(void) { static int entered = false; char* s; if (entered) { return; } entered = true; // pump the message loop Sys_PumpEvents(); // make sure mouse and joystick are only called once a frame IN_Frame(); // check for console commands s = Sys_ConsoleInput(); if (s) { char* b; int len; len = strlen(s) + 1; b = (char*)Mem_Alloc(len); strcpy(b, s); Sys_QueEvent(0, SE_CONSOLE, 0, 0, len, b); } entered = false; } /* ================ Sys_ClearEvents ================ */ void Sys_ClearEvents(void) { eventHead = eventTail = 0; } /* ================ Sys_GetEvent ================ */ sysEvent_t Sys_GetEvent(void) { sysEvent_t ev; // return if we have data if (eventHead > eventTail) { eventTail++; return eventQue[(eventTail - 1) & MASK_QUED_EVENTS]; } // return the empty event memset(&ev, 0, sizeof(ev)); return ev; } //================================================================ /* ================= Sys_In_Restart_f Restart the input subsystem ================= */ void Sys_In_Restart_f(const idCmdArgs& args) { Sys_ShutdownInput(); Sys_InitInput(); } /* ================ Sys_AlreadyRunning returns true if there is a copy of D3 running already ================ */ bool Sys_AlreadyRunning(void) { #ifndef DEBUG if (!win32.win_allowMultipleInstances.GetBool()) { HANDLE hMutexOneInstance = ::CreateMutex(NULL, FALSE, "DOOM3"); if (::GetLastError() == ERROR_ALREADY_EXISTS || ::GetLastError() == ERROR_ACCESS_DENIED) { return true; } } #endif return false; } /* ================ Sys_Init The cvar system must already be setup ================ */ #define OSR2_BUILD_NUMBER 1111 #define WIN98_BUILD_NUMBER 1998 void Sys_Init(void) { CoInitialize(NULL); // make sure the timer is high precision, otherwise // NT gets 18ms resolution timeBeginPeriod(1); // get WM_TIMER messages pumped every millisecond // SetTimer( NULL, 0, 100, NULL ); cmdSystem->AddCommand("in_restart", Sys_In_Restart_f, CMD_FL_SYSTEM, "restarts the input system"); #ifdef DEBUG cmdSystem->AddCommand("createResourceIDs", CreateResourceIDs_f, CMD_FL_TOOL, "assigns resource IDs in _resouce.h files"); #endif #if 0 cmdSystem->AddCommand("setAsyncSound", Sys_SetAsyncSound_f, CMD_FL_SYSTEM, "set the async sound option"); #endif // // Windows user name // win32.win_username.SetString(Sys_GetCurrentUser()); // // Windows version // win32.osversion.dwOSVersionInfoSize = sizeof(win32.osversion); if (!GetVersionEx((LPOSVERSIONINFO)&win32.osversion)) Sys_Error("Couldn't get OS info"); if (win32.osversion.dwMajorVersion < 4) { Sys_Error(GAME_NAME " requires Windows version 4 (NT) or greater"); } if (win32.osversion.dwPlatformId == VER_PLATFORM_WIN32s) { Sys_Error(GAME_NAME " doesn't run on Win32s"); } if (win32.osversion.dwPlatformId == VER_PLATFORM_WIN32_NT) { if (win32.osversion.dwMajorVersion <= 4) { win32.sys_arch.SetString("WinNT (NT)"); } else if (win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 0) { win32.sys_arch.SetString("Win2K (NT)"); } else if (win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 1) { win32.sys_arch.SetString("WinXP (NT)"); } else if (win32.osversion.dwMajorVersion == 6) { win32.sys_arch.SetString("Vista"); } else { win32.sys_arch.SetString("Unknown NT variant"); } } else if (win32.osversion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { if (win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 0) { // Win95 if (win32.osversion.szCSDVersion[1] == 'C') { win32.sys_arch.SetString("Win95 OSR2 (95)"); } else { win32.sys_arch.SetString("Win95 (95)"); } } else if (win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 10) { // Win98 if (win32.osversion.szCSDVersion[1] == 'A') { win32.sys_arch.SetString("Win98SE (95)"); } else { win32.sys_arch.SetString("Win98 (95)"); } } else if (win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 90) { // WinMe win32.sys_arch.SetString("WinMe (95)"); } else { win32.sys_arch.SetString("Unknown 95 variant"); } } else { win32.sys_arch.SetString("unknown Windows variant"); } // // CPU type // if (!idStr::Icmp(win32.sys_cpustring.GetString(), "detect")) { idStr string; common->Printf("%1.0f MHz ", Sys_ClockTicksPerSecond() / 1000000.0f); win32.cpuid = Sys_GetCPUId(); string.Clear(); if (win32.cpuid & CPUID_AMD) { string += "AMD CPU"; } else if (win32.cpuid & CPUID_INTEL) { string += "Intel CPU"; } else if (win32.cpuid & CPUID_UNSUPPORTED) { string += "unsupported CPU"; } else { string += "generic CPU"; } string += " with "; if (win32.cpuid & CPUID_MMX) { string += "MMX & "; } if (win32.cpuid & CPUID_3DNOW) { string += "3DNow! & "; } if (win32.cpuid & CPUID_SSE) { string += "SSE & "; } if (win32.cpuid & CPUID_SSE2) { string += "SSE2 & "; } if (win32.cpuid & CPUID_SSE3) { string += "SSE3 & "; } if (win32.cpuid & CPUID_HTT) { string += "HTT & "; } string.StripTrailing(" & "); string.StripTrailing(" with "); win32.sys_cpustring.SetString(string); } else { common->Printf("forcing CPU type to "); idLexer src(win32.sys_cpustring.GetString(), idStr::Length(win32.sys_cpustring.GetString()), "sys_cpustring"); idToken token; int id = CPUID_NONE; while (src.ReadToken(&token)) { if (token.Icmp("generic") == 0) { id |= CPUID_GENERIC; } else if (token.Icmp("intel") == 0) { id |= CPUID_INTEL; } else if (token.Icmp("amd") == 0) { id |= CPUID_AMD; } else if (token.Icmp("mmx") == 0) { id |= CPUID_MMX; } else if (token.Icmp("3dnow") == 0) { id |= CPUID_3DNOW; } else if (token.Icmp("sse") == 0) { id |= CPUID_SSE; } else if (token.Icmp("sse2") == 0) { id |= CPUID_SSE2; } else if (token.Icmp("sse3") == 0) { id |= CPUID_SSE3; } else if (token.Icmp("htt") == 0) { id |= CPUID_HTT; } } if (id == CPUID_NONE) { common->Printf("WARNING: unknown sys_cpustring '%s'\n", win32.sys_cpustring.GetString()); id = CPUID_GENERIC; } win32.cpuid = (cpuid_t)id; } common->Printf("%s\n", win32.sys_cpustring.GetString()); common->Printf("%d MB System Memory\n", Sys_GetSystemRam()); common->Printf("%d MB Video Memory\n", Sys_GetVideoRam()); } /* ================ Sys_Shutdown ================ */ void Sys_Shutdown(void) { CoUninitialize(); } /* ================ Sys_GetProcessorId ================ */ cpuid_t Sys_GetProcessorId(void) { return win32.cpuid; } /* ================ Sys_GetProcessorString ================ */ const char* Sys_GetProcessorString(void) { return win32.sys_cpustring.GetString(); } //======================================================================= //#define SET_THREAD_AFFINITY /* ==================== Win_Frame ==================== */ void Win_Frame(void) { // if "viewlog" has been modified, show or hide the log console if (win32.win_viewlog.IsModified()) { if (!com_skipRenderer.GetBool() && idAsyncNetwork::serverDedicated.GetInteger() != 1) { Sys_ShowConsole(win32.win_viewlog.GetInteger(), false); } win32.win_viewlog.ClearModified(); } } extern "C" { void _chkstk(int size); }; void clrstk(void); /* ==================== TestChkStk ==================== */ void TestChkStk(void) { } /* ==================== HackChkStk ==================== */ void HackChkStk(void) { } /* ==================== GetExceptionCodeInfo ==================== */ const char* GetExceptionCodeInfo(UINT code) { switch (code) { case EXCEPTION_ACCESS_VIOLATION: return "The thread tried to read from or write to a virtual address for which it does not have the appropriate access."; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking."; case EXCEPTION_BREAKPOINT: return "A breakpoint was encountered."; case EXCEPTION_DATATYPE_MISALIGNMENT: return "The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on."; case EXCEPTION_FLT_DENORMAL_OPERAND: return "One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value."; case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "The thread tried to divide a floating-point value by a floating-point divisor of zero."; case EXCEPTION_FLT_INEXACT_RESULT: return "The result of a floating-point operation cannot be represented exactly as a decimal fraction."; case EXCEPTION_FLT_INVALID_OPERATION: return "This exception represents any floating-point exception not included in this list."; case EXCEPTION_FLT_OVERFLOW: return "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type."; case EXCEPTION_FLT_STACK_CHECK: return "The stack overflowed or underflowed as the result of a floating-point operation."; case EXCEPTION_FLT_UNDERFLOW: return "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type."; case EXCEPTION_ILLEGAL_INSTRUCTION: return "The thread tried to execute an invalid instruction."; case EXCEPTION_IN_PAGE_ERROR: return "The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network."; case EXCEPTION_INT_DIVIDE_BY_ZERO: return "The thread tried to divide an integer value by an integer divisor of zero."; case EXCEPTION_INT_OVERFLOW: return "The result of an integer operation caused a carry out of the most significant bit of the result."; case EXCEPTION_INVALID_DISPOSITION: return "An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception."; case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "The thread tried to continue execution after a noncontinuable exception occurred."; case EXCEPTION_PRIV_INSTRUCTION: return "The thread tried to execute an instruction whose operation is not allowed in the current machine mode."; case EXCEPTION_SINGLE_STEP: return "A trace trap or other single-instruction mechanism signaled that one instruction has been executed."; case EXCEPTION_STACK_OVERFLOW: return "The thread used up its stack."; default: return "Unknown exception"; } } /* ==================== EmailCrashReport emailer originally from Raven/Quake 4 ==================== */ void EmailCrashReport(LPSTR messageText) { LPMAPISENDMAIL MAPISendMail; MapiMessage message; static int lastEmailTime = 0; if (Sys_Milliseconds() < lastEmailTime + 10000) { return; } lastEmailTime = Sys_Milliseconds(); HINSTANCE mapi = LoadLibrary("MAPI32.DLL"); if (mapi) { MAPISendMail = (LPMAPISENDMAIL)GetProcAddress(mapi, "MAPISendMail"); if (MAPISendMail) { MapiRecipDesc toProgrammers = { 0, // ulReserved MAPI_TO, // ulRecipClass "DOOM 3 Crash", // lpszName "SMTP:programmers@idsoftware.com", // lpszAddress 0, // ulEIDSize 0 // lpEntry }; memset(&message, 0, sizeof(message)); message.lpszSubject = "DOOM 3 Fatal Error"; message.lpszNoteText = messageText; message.nRecipCount = 1; message.lpRecips = &toProgrammers; MAPISendMail( 0, // LHANDLE lhSession 0, // ULONG ulUIParam &message, // lpMapiMessage lpMessage MAPI_DIALOG, // FLAGS flFlags 0 // ULONG ulReserved ); } FreeLibrary(mapi); } } int Sys_FPU_PrintStateFlags(char* ptr, int ctrl, int stat, int tags, int inof, int inse, int opof, int opse); /* ==================== _except_handler ==================== */ EXCEPTION_DISPOSITION __cdecl _except_handler(struct _EXCEPTION_RECORD* ExceptionRecord, void* EstablisherFrame, struct _CONTEXT* ContextRecord, void* DispatcherContext) { // Tell the OS to restart the faulting instruction return ExceptionContinueExecution; } #define TEST_FPU_EXCEPTIONS /* FPU_EXCEPTION_INVALID_OPERATION | */ \ /* FPU_EXCEPTION_DENORMALIZED_OPERAND | */ \ /* FPU_EXCEPTION_DIVIDE_BY_ZERO | */ \ /* FPU_EXCEPTION_NUMERIC_OVERFLOW | */ \ /* FPU_EXCEPTION_NUMERIC_UNDERFLOW | */ \ /* FPU_EXCEPTION_INEXACT_RESULT | */ \ 0 /* ================== Sys_GameWindowVisible ================== */ bool Sys_GameWindowVisible() { return ::IsWindowVisible(win32.hWnd); } /* ================== WinMain ================== */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { const HCURSOR hcurSave = ::SetCursor(LoadCursor(0, IDC_WAIT)); Sys_SetPhysicalWorkMemory(192 << 20, 1024 << 20); Sys_GetCurrentMemoryStatus(exeLaunchMemoryStats); win32.hInstance = hInstance; idStr::Copynz(sys_cmdline, lpCmdLine, sizeof(sys_cmdline)); // done before Com/Sys_Init since we need this for error output Sys_CreateConsole(); // no abort/retry/fail errors SetErrorMode(SEM_FAILCRITICALERRORS); for (int i = 0; i < MAX_CRITICAL_SECTIONS; i++) { InitializeCriticalSection(&win32.criticalSections[i]); } // get the initial time base Sys_Milliseconds(); #ifdef DEBUG // disable the painfully slow MS heap check every 1024 allocs _CrtSetDbgFlag(0); #endif // Sys_FPU_EnableExceptions( TEST_FPU_EXCEPTIONS ); Sys_FPU_SetPrecision(FPU_PRECISION_DOUBLE_EXTENDED); common->Init(0, NULL, lpCmdLine); #if TEST_FPU_EXCEPTIONS != 0 common->Printf(Sys_FPU_GetState()); #endif // hide or show the early console as necessary if (win32.win_viewlog.GetInteger() || com_skipRenderer.GetBool() || idAsyncNetwork::serverDedicated.GetInteger()) { Sys_ShowConsole(1, true); } else { Sys_ShowConsole(0, false); } #ifdef SET_THREAD_AFFINITY // give the main thread an affinity for the first cpu SetThreadAffinityMask(GetCurrentThread(), 1); #endif ::SetCursor(hcurSave); // Launch the script debugger if (strstr(lpCmdLine, "+debugger")) { // DebuggerClientInit( lpCmdLine ); return 0; } ::SetFocus(win32.hWnd); // main game loop while (1) { Win_Frame(); #ifdef DEBUG Sys_MemFrame(); #endif // set exceptions, even if some crappy syscall changes them! Sys_FPU_EnableExceptions(TEST_FPU_EXCEPTIONS); #ifdef ID_ALLOW_TOOLS if (com_editors) { if (com_editors & EDITOR_GUI) { // GUI editor GUIEditorRun(); } else if (com_editors & EDITOR_RADIANT) { // Level Editor RadiantRun(); } else if (com_editors & EDITOR_MATERIAL) { //BSM Nerve: Add support for the material editor MaterialEditorRun(); } else { if (com_editors & EDITOR_LIGHT) { // in-game Light Editor LightEditorRun(); } if (com_editors & EDITOR_SOUND) { // in-game Sound Editor SoundEditorRun(); } if (com_editors & EDITOR_DECL) { // in-game Declaration Browser DeclBrowserRun(); } if (com_editors & EDITOR_AF) { // in-game Articulated Figure Editor AFEditorRun(); } if (com_editors & EDITOR_PARTICLE) { // in-game Particle Editor ParticleEditorRun(); } if (com_editors & EDITOR_SCRIPT) { // in-game Script Editor ScriptEditorRun(); } if (com_editors & EDITOR_PDA) { // in-game PDA Editor PDAEditorRun(); } } } #endif // run the game common->Frame(); } // never gets here return 0; } /* ================== idSysLocal::OpenURL ================== */ void idSysLocal::OpenURL(const char* url, bool doexit) { static bool doexit_spamguard = false; HWND wnd; if (doexit_spamguard) { common->DPrintf("OpenURL: already in an exit sequence, ignoring %s\n", url); return; } common->Printf("Open URL: %s\n", url); if (!ShellExecute(NULL, "open", url, NULL, NULL, SW_RESTORE)) { common->Error("Could not open url: '%s' ", url); return; } wnd = GetForegroundWindow(); if (wnd) { ShowWindow(wnd, SW_MAXIMIZE); } if (doexit) { doexit_spamguard = true; cmdSystem->BufferCommandText(CMD_EXEC_APPEND, "quit\n"); } } /* ================== idSysLocal::StartProcess ================== */ void idSysLocal::StartProcess(const char* exePath, bool doexit) { TCHAR szPathOrig[_MAX_PATH]; STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); strncpy(szPathOrig, exePath, _MAX_PATH); if (!CreateProcess(NULL, szPathOrig, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { common->Error("Could not start process: '%s' ", szPathOrig); return; } if (doexit) { cmdSystem->BufferCommandText(CMD_EXEC_APPEND, "quit\n"); } } /* ================== Sys_SetFatalError ================== */ void Sys_SetFatalError(const char* error) { } /* ================== Sys_DoPreferences ================== */ void Sys_DoPreferences(void) { }