/* 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.h #include "quakedef.h" #include "winquake.h" #include "resource.h" #include "errno.h" #include "fcntl.h" #include #include #include #include #ifdef NODIRECTX #define DDActive 0 #endif static HINSTANCE game_library; /* ================= Sys_UnloadGame ================= */ void Sys_UnloadGame (void) { if (!FreeLibrary (game_library)) Sys_Error ("FreeLibrary failed for game library"); game_library = NULL; } /* ================= Sys_GetGameAPI Loads the game dll ================= */ void *Sys_GetGameAPI (void *parms) { void *(VARGS *GetGameAPI) (void *); char name[MAX_OSPATH]; char *path; char cwd[MAX_OSPATH]; #if defined _M_IX86 const char *gamename = "gamex86.dll"; #ifdef NDEBUG const char *debugdir = "release"; #else const char *debugdir = "debug"; #endif #elif defined _M_ALPHA const char *gamename = "gameaxp.dll"; #ifdef NDEBUG const char *debugdir = "releaseaxp"; #else const char *debugdir = "debugaxp"; #endif #endif if (game_library) Sys_Error ("Sys_GetGameAPI without Sys_UnloadingGame"); // check the current debug directory first for development purposes #ifdef _WIN32 GetCurrentDirectory(sizeof(cwd), cwd); #else _getcwd (cwd, sizeof(cwd)); #endif _snprintf (name, sizeof(name), "%s/%s/%s", cwd, debugdir, gamename); game_library = LoadLibrary ( name ); if (game_library) { Con_DPrintf ("LoadLibrary (%s)\n", name); } else { #ifdef DEBUG // check the current directory for other development purposes _snprintf (name, sizeof(name), "%s/%s", cwd, gamename); game_library = LoadLibrary ( name ); if (game_library) { Con_DPrintf ("LoadLibrary (%s)\n", name); } else #endif { // now run through the search paths path = NULL; while (1) { path = COM_NextPath (path); if (!path) return NULL; // couldn't find one anywhere _snprintf (name, sizeof(name), "%s/%s", path, gamename); game_library = LoadLibrary (name); if (game_library) { Con_DPrintf ("LoadLibrary (%s)\n",name); break; } } } } GetGameAPI = (void *)GetProcAddress (game_library, "GetGameAPI"); if (!GetGameAPI) { Sys_UnloadGame (); return NULL; } return GetGameAPI (parms); } #define MINIMUM_WIN_MEMORY 0x0800000 #define MAXIMUM_WIN_MEMORY 0x4000000 #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; HWND hwnd_dialog; // startup dialog box static HANDLE hinput, houtput; HANDLE qwclsemaphore; static HANDLE tevent; void Sys_InitFloatTime (void); void VARGS MaskExceptions (void); void Sys_PopFPCW (void); void Sys_PushFPCW_SetHigh (void); void VARGS Sys_DebugLog(char *file, char *fmt, ...) { FILE *fd; va_list argptr; static char data[1024]; va_start(argptr, fmt); _vsnprintf(data, sizeof(data)-1, fmt, argptr); va_end(argptr); #if defined(CRAZYDEBUGGING) && CRAZYDEBUGGING > 1 { static int sock; if (!sock) { struct sockaddr_in sa; netadr_t na; int _true = true; int listip; listip = COM_CheckParm("-debugip"); NET_StringToAdr(listip?com_argv[listip+1]:"127.0.0.1", &na); NetadrToSockadr(&na, (struct sockaddr_qstorage*)&sa); sa.sin_port = htons(10000); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (-1==connect(sock, (struct sockaddr*)&sa, sizeof(sa))) Sys_Error("Couldn't send debug log lines\n"); setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&_true, sizeof(_true)); } send(sock, data, strlen(data), 0); } #endif fd = fopen(file, "ab"); fprintf(fd, "%s", data); fclose(fd); }; int *debug; #if (_WIN32_WINNT < 0x0400) #define LLKHF_ALTDOWN 0x00000020 #define LLKHF_UP 0x00000080 #define WH_KEYBOARD_LL 13 typedef struct { DWORD vkCode; DWORD scanCode; DWORD flags; DWORD time; DWORD dwExtraInfo; } KBDLLHOOKSTRUCT; #elif defined(MINGW) #define LLKHF_UP 0x00000080 #endif HHOOK llkeyboardhook; cvar_t sys_disableWinKeys = {"sys_disableWinKeys", "0"}; cvar_t sys_disableTaskSwitch = {"sys_disableTaskSwitch", "0", NULL, CVAR_NOTFROMSERVER}; // please don't encourage people to use this... LRESULT CALLBACK LowLevelKeyboardProc (INT nCode, WPARAM wParam, LPARAM lParam) { KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT *) lParam; if (ActiveApp) switch (nCode) { case HC_ACTION: { //Trap the Left Windowskey if (pkbhs->vkCode == VK_LWIN) { Key_Event (K_LWIN, !(pkbhs->flags & LLKHF_UP)); return 1; } //Trap the Right Windowskey if (pkbhs->vkCode == VK_RWIN) { Key_Event (K_RWIN, !(pkbhs->flags & LLKHF_UP)); return 1; } //Trap the Application Key (what a pointless key) if (pkbhs->vkCode == VK_APPS) { Key_Event (K_APP, !(pkbhs->flags & LLKHF_UP)); return 1; } // Disable CTRL+ESC //this works, but we've got to give some way to tab out... if (sys_disableTaskSwitch.value) { if (pkbhs->vkCode == VK_ESCAPE && GetAsyncKeyState (VK_CONTROL) >> ((sizeof(SHORT) * 8) - 1)) return 1; // Disable ATL+TAB if (pkbhs->vkCode == VK_TAB && pkbhs->flags & LLKHF_ALTDOWN) return 1; // Disable ALT+ESC if (pkbhs->vkCode == VK_ESCAPE && pkbhs->flags & LLKHF_ALTDOWN) return 1; } break; } default: break; } return CallNextHookEx (llkeyboardhook, nCode, wParam, lParam); } void SetHookState(qboolean state) { if (!state == !llkeyboardhook) //not so types are comparable return; if (llkeyboardhook) { UnhookWindowsHookEx(llkeyboardhook); llkeyboardhook = NULL; } if (state) llkeyboardhook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); } /* =============================================================================== FILE IO =============================================================================== */ /* ================ Sys_filelength ================ */ int Sys_filelength (FILE *f) { int pos; int end; pos = ftell (f); fseek (f, 0, SEEK_END); end = ftell (f); fseek (f, pos, SEEK_SET); return end; } int Sys_FileTime (char *path) { FILE *f; int t=0, retval; if (qrenderer) t = VID_ForceUnlockedAndReturnState (); f = fopen(path, "rb"); if (f) { fclose(f); retval = 1; } else { retval = -1; } if (qrenderer) VID_ForceLockState (t); return retval; } void Sys_mkdir (char *path) { _mkdir (path); } qboolean Sys_remove (char *path) { remove (path); return true; } int Sys_EnumerateFiles (char *gpath, char *match, int (*func)(char *, int, void *), void *parm) { HANDLE r; WIN32_FIND_DATA fd; char apath[MAX_OSPATH]; char file[MAX_OSPATH]; char *s; int go; if (!gpath) return 0; // strcpy(apath, match); sprintf(apath, "%s/%s", gpath, match); for (s = apath+strlen(apath)-1; s> apath; s--) { if (*s == '/') break; } *s = '\0'; //this is what we ask windows for. sprintf(file, "%s/*.*", apath); //we need to make apath contain the path in match but not gpath strcpy(apath, match); match = s+1; for (s = apath+strlen(apath)-1; s> apath; s--) { if (*s == '/') break; } *s = '\0'; if (s != apath) strcat(apath, "/"); r = FindFirstFile(file, &fd); if (r==(HANDLE)-1) return 1; go = true; do { if (*fd.cFileName == '.'); //don't ever find files with a name starting with '.' else if (fd.dwFileAttributes != 16) //is a directory { if (wildcmp(match, fd.cFileName)) { sprintf(file, "%s%s", apath, fd.cFileName); go = func(file, fd.nFileSizeLow, parm); } } else { if (wildcmp(match, fd.cFileName)) { sprintf(file, "%s%s/", apath, fd.cFileName); go = func(file, fd.nFileSizeLow, parm); } } } while(FindNextFile(r, &fd) && go); FindClose(r); return go; } /* =============================================================================== SYSTEM IO =============================================================================== */ /* ================ Sys_MakeCodeWriteable ================ */ void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length) { DWORD flOldProtect; //@@@ copy on write or just read-write? if (!VirtualProtect((LPVOID)startaddr, length, PAGE_READWRITE, &flOldProtect)) Sys_Error("Protection change failed\n"); } /* ================ Sys_Init ================ */ void Sys_Init (void) { // LARGE_INTEGER PerformanceFreq; // unsigned int lowpart, highpart; OSVERSIONINFO vinfo; #ifndef SERVERONLY #ifndef CLIENTONLY if (!isDedicated && !COM_CheckParm("-nomutex")) #else if (!COM_CheckParm("-nomutex")) //we need to create a mutex to allow gamespy to realise that we're running, but it might not be desired as it prevents other clients from running too. #endif { // allocate a named semaphore on the client so the // front end can tell if it is alive // mutex will fail if semephore allready exists qwclsemaphore = CreateMutex( NULL, // Security attributes 0, // owner "qwcl"); // Semaphore name // if (!qwclsemaphore) // Sys_Error ("QWCL is already running on this system"); CloseHandle (qwclsemaphore); qwclsemaphore = CreateSemaphore( NULL, // Security attributes 0, // Initial count 1, // Maximum count "qwcl"); // Semaphore name } #endif MaskExceptions (); Sys_SetFPCW (); #if 0 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 (); #endif // make sure the timer is high precision, otherwise // NT gets 18ms resolution timeBeginPeriod( 1 ); 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 ("QuakeWorld requires at least Win95 or NT 4.0"); } if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) WinNT = true; else WinNT = false; } void VARGS Sys_Error (const char *error, ...) { va_list argptr; char text[1024]; //, text2[1024]; // DWORD dummy; va_start (argptr, error); _vsnprintf (text, sizeof(text)-1, error, argptr); va_end (argptr); SetHookState(false); Host_Shutdown (); MessageBox(NULL, text, "Error", 0); #ifndef SERVERONLY CloseHandle (qwclsemaphore); #endif SetHookState(false); exit (1); } void VARGS Sys_Printf (char *fmt, ...) { va_list argptr; char text[1024]; DWORD dummy; if (!houtput) return; va_start (argptr,fmt); _vsnprintf (text, sizeof(text)-1, fmt, argptr); va_end (argptr); WriteFile (houtput, text, strlen(text), &dummy, NULL); } void Sys_Quit (void) { if (VID_ForceUnlockedAndReturnState) VID_ForceUnlockedAndReturnState (); SetHookState(false); Host_Shutdown(); #ifndef SERVERONLY if (tevent) CloseHandle (tevent); if (qwclsemaphore) CloseHandle (qwclsemaphore); #endif /* Yeah, right, just wishful thinking. #ifdef _DEBUG if (Z_Allocated()) MessageBox(0, "Some memory was left allocated", "Mem was left", 0); #endif */ SetHookState(false); exit (0); } #if 0 /* ================ Sys_DoubleTime ================ */ double Sys_DoubleTime (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_DoubleTime (); j = COM_CheckParm("-starttime"); if (j) { curtime = (double) (Q_atof(com_argv[j+1])); } else { curtime = 0.0; } lastcurtime = curtime; } #endif // DWORD starttime; double Sys_DoubleTime (void) { static DWORD starttime; static qboolean first = true; DWORD now; // double t; now = timeGetTime(); if (first) { first = false; starttime = now; return 0.0; } if (now < starttime) // wrapped? { double r; r = (now / 1000.0) + (LONG_MAX - starttime / 1000.0); starttime = now; return r; } if (now - starttime == 0) return 0.0; return (now - starttime) / 1000.0; } ///////////////////////////////////////// //the system console stuff char *Sys_ConsoleInput (void) { static char text[256]; static int len; INPUT_RECORD recs[1024]; // int count; int i; int ch; DWORD numevents, numread, dummy=0; HANDLE th; char *clipText, *textCopied; 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; } break; case '\b': if (len) { len--; WriteFile(houtput, "\b \b", 3, &dummy, NULL); } break; default: if (((ch=='V' || ch=='v') && (recs[0].Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))) || ((recs[0].Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED) && (recs[0].Event.KeyEvent.wVirtualKeyCode ==VK_INSERT))) { if (OpenClipboard(NULL)) { th = GetClipboardData(CF_TEXT); if (th) { clipText = GlobalLock(th); if (clipText) { textCopied = BZ_Malloc(GlobalSize(th)+1); strcpy(textCopied, clipText); /* Substitutes a NULL for every token */strtok(textCopied, "\n\r\b"); i = strlen(textCopied); if (i+len>=256) i=256-len; if (i>0) { textCopied[i]=0; text[len]=0; strcat(text, textCopied); len+=dummy; WriteFile(houtput, textCopied, i, &dummy, NULL); } BZ_Free(textCopied); } GlobalUnlock(th); } CloseClipboard(); } } else if (ch >= ' ') { WriteFile(houtput, &ch, 1, &dummy, NULL); text[len] = ch; len = (len + 1) & 0xff; } break; } } } } return NULL; } BOOL WINAPI HandlerRoutine (DWORD dwCtrlType) { switch (dwCtrlType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: Cbuf_AddText ("quit\n", RESTRICT_LOCAL); return true; } return false; } qboolean Sys_InitTerminal (void) { if (!AllocConsole()) return false; SetConsoleCtrlHandler (HandlerRoutine, TRUE); SetConsoleTitle ("FTE dedicated server"); hinput = GetStdHandle (STD_INPUT_HANDLE); houtput = GetStdHandle (STD_OUTPUT_HANDLE); return true; } void Sys_CloseTerminal (void) { FreeConsole(); } // //////////////////////////// void Sys_Sleep (void) { } void Sys_SendKeyEvents (void) { MSG msg; if (!qrenderer) { #ifndef CLIENTONLY SV_GetConsoleCommands (); #endif return; } 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)) break; // Sys_Quit (); TranslateMessage (&msg); DispatchMessage (&msg); } } void Sys_ServerActivity(void) { FlashWindow(mainwindow, true); } /* ============================================================================== 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 exename[256]; HWND hwnd_dialog; #ifndef CLIENTONLY qboolean isDedicated = false; #endif 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); GetModuleFileName(NULL, cwd, sizeof(cwd)-1); strcpy(exename, COM_SkipPath(cwd)); parms.argc = 1; argv[0] = exename; 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); TL_InitLanguages(); //tprints are now allowed if (!GetCurrentDirectory (sizeof(cwd), cwd)) Sys_Error ("Couldn't determine current directory"); if (cwd[Q_strlen(cwd)-1] == '/' || cwd[Q_strlen(cwd)-1] == '\\') cwd[Q_strlen(cwd)-1] = 0; parms.basedir = cwd; parms.cachedir = NULL; parms.argc = com_argc; parms.argv = com_argv; #ifndef CLIENTONLY if (COM_CheckParm ("-dedicated")) { isDedicated = true; hwnd_dialog=NULL; if (!Sys_InitTerminal()) Sys_Error ("Couldn't allocate dedicated server console"); } else #endif 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; } else if (COM_CheckParm ("-mem")) { t = COM_CheckParm("-mem") + 1; if (t < com_argc) parms.memsize = Q_atoi (com_argv[t]) * 1024*1024; } parms.membase = VirtualAlloc (NULL, parms.memsize, MEM_RESERVE, PAGE_NOACCESS); // parms.membase = malloc (parms.memsize); if (!parms.membase) Sys_Error ("Not enough memory free; check disk space\n"); #ifndef CLIENTONLY if (isDedicated) //compleate denial to switch to anything else - many of the client structures are not initialized. { SV_Init (&parms); SV_Frame (0.1); oldtime = Sys_DoubleTime () - 0.1; while (1) { if (!isDedicated) Sys_Error("Dedicated was cleared"); NET_Sleep(100, false); // find time passed since last cycle newtime = Sys_DoubleTime (); time = newtime - oldtime; oldtime = newtime; SV_Frame (time); } return TRUE; } #endif tevent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!tevent) Sys_Error ("Couldn't create event"); Sys_Init (); Sys_Printf ("Host_Init\n"); Host_Init (&parms); Cvar_Register(&sys_disableWinKeys, "System vars"); Cvar_Register(&sys_disableTaskSwitch, "System vars"); oldtime = Sys_DoubleTime (); //client console should now be initialized. /* main window message loop */ while (1) { #ifndef CLIENTONLY if (isDedicated) { NET_Sleep(100, false); // find time passed since last cycle newtime = Sys_DoubleTime (); time = newtime - oldtime; oldtime = newtime; SV_Frame (time); } else #endif { // yield the CPU for a little while when paused, minimized, or not the focus if (((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing) && !media_filmtype) { SleepUntilInput (PAUSE_SLEEP); scr_skipupdate = 1; // no point in bothering to draw } else if (!ActiveApp && !DDActive && !media_filmtype) { SleepUntilInput (NOT_FOCUS_SLEEP); } newtime = Sys_DoubleTime (); time = newtime - oldtime; Host_Frame (time); oldtime = newtime; SetHookState(sys_disableWinKeys.value); // Sleep(0); } } /* return success of application */ return TRUE; } #ifdef NOASM //these couldn't be found... (it is a masm thing, right?) void Sys_HighFPPrecision (void) { } void Sys_LowFPPrecision (void) { } void Sys_SetFPCW (void) { } void MaskExceptions (void) { } #endif