]> git.xonotic.org Git - xonotic/darkplaces.git/blob - sys_shared.c
Merge PR 'Use the text from modinfo.txt as the mod menu entry'
[xonotic/darkplaces.git] / sys_shared.c
1 #ifdef WIN32
2 # ifndef DONT_USE_SETDLLDIRECTORY
3 #  define _WIN32_WINNT 0x0502
4 # endif
5 #endif
6
7 #include "quakedef.h"
8 #include "taskqueue.h"
9 #include "thread.h"
10
11 #define SUPPORTDLL
12
13 #ifdef WIN32
14 # include <windows.h>
15 # include <mmsystem.h> // timeGetTime
16 # include <time.h> // localtime
17 #ifdef _MSC_VER
18 #pragma comment(lib, "winmm.lib")
19 #endif
20 #else
21 # ifdef __FreeBSD__
22 #  include <sys/sysctl.h>
23 # endif
24 # include <unistd.h>
25 # include <fcntl.h>
26 # include <sys/time.h>
27 # include <time.h>
28 # ifdef SUPPORTDLL
29 #  include <dlfcn.h>
30 # endif
31 #endif
32
33 static char sys_timestring[128];
34 char *Sys_TimeString(const char *timeformat)
35 {
36         time_t mytime = time(NULL);
37 #if _MSC_VER >= 1400
38         struct tm mytm;
39         localtime_s(&mytm, &mytime);
40         strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm);
41 #else
42         strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime));
43 #endif
44         return sys_timestring;
45 }
46
47
48 void Sys_Quit (int returnvalue)
49 {
50         // Unlock mutexes because the quit command may jump directly here, causing a deadlock
51         if (cmd_client.text_lock)
52                 Cbuf_Unlock(&cmd_client);
53         if (cmd_server.text_lock)
54                 Cbuf_Unlock(&cmd_server);
55         SV_UnlockThreadMutex();
56         TaskQueue_Frame(true);
57
58         if (COM_CheckParm("-profilegameonly"))
59                 Sys_AllowProfiling(false);
60         host.state = host_shutdown;
61         Host_Shutdown();
62         exit(returnvalue);
63 }
64
65 #ifdef __cplusplus
66 extern "C"
67 #endif
68 void Sys_AllowProfiling(qboolean enable)
69 {
70 #ifdef __ANDROID__
71 #ifdef USE_PROFILER
72         extern void monstartup(const char *libname);
73         extern void moncleanup(void);
74         if (enable)
75                 monstartup("libmain.so");
76         else
77                 moncleanup();
78 #endif
79 #elif (defined(__linux__) && (defined(__GLIBC__) || defined(__GNU_LIBRARY__))) || defined(__FreeBSD__)
80         extern int moncontrol(int);
81         moncontrol(enable);
82 #endif
83 }
84
85
86 /*
87 ===============================================================================
88
89 DLL MANAGEMENT
90
91 ===============================================================================
92 */
93
94 static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next)
95 {
96         const dllfunction_t *func;
97         if(dllhandle)
98         {
99                 for (func = fcts; func && func->name != NULL; func++)
100                         if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name)))
101                         {
102                                 if(complain)
103                                 {
104                                         Con_DPrintf (" - missing function \"%s\" - broken library!", func->name);
105                                         if(has_next)
106                                                 Con_DPrintf("\nContinuing with");
107                                 }
108                                 goto notfound;
109                         }
110                 return true;
111
112         notfound:
113                 for (func = fcts; func && func->name != NULL; func++)
114                         *func->funcvariable = NULL;
115         }
116         return false;
117 }
118
119 qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts)
120 {
121 #ifdef SUPPORTDLL
122         const dllfunction_t *func;
123         dllhandle_t dllhandle = 0;
124         unsigned int i;
125
126         if (handle == NULL)
127                 return false;
128
129 #ifndef WIN32
130 #ifdef PREFER_PRELOAD
131         dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
132         if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false))
133         {
134                 Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]);
135                 *handle = dllhandle;
136                 return true;
137         }
138         else
139                 Sys_UnloadLibrary(&dllhandle);
140 notfound:
141 #endif
142 #endif
143
144         // Initializations
145         for (func = fcts; func && func->name != NULL; func++)
146                 *func->funcvariable = NULL;
147
148         // Try every possible name
149         Con_DPrintf ("Trying to load library...");
150         for (i = 0; dllnames[i] != NULL; i++)
151         {
152                 Con_DPrintf (" \"%s\"", dllnames[i]);
153 #ifdef WIN32
154 # ifndef DONT_USE_SETDLLDIRECTORY
155 #  ifdef _WIN64
156                 SetDllDirectory("bin64");
157 #  else
158                 SetDllDirectory("bin32");
159 #  endif
160 # endif
161                 dllhandle = LoadLibrary (dllnames[i]);
162                 // no need to unset this - we want ALL dlls to be loaded from there, anyway
163 #else
164                 dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL);
165 #endif
166                 if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(sys.argv[0], '/'))))
167                         break;
168                 else
169                         Sys_UnloadLibrary (&dllhandle);
170         }
171
172         // see if the names can be loaded relative to the executable path
173         // (this is for Mac OSX which does not check next to the executable)
174         if (!dllhandle && strrchr(sys.argv[0], '/'))
175         {
176                 char path[MAX_OSPATH];
177                 strlcpy(path, sys.argv[0], sizeof(path));
178                 strrchr(path, '/')[1] = 0;
179                 for (i = 0; dllnames[i] != NULL; i++)
180                 {
181                         char temp[MAX_OSPATH];
182                         strlcpy(temp, path, sizeof(temp));
183                         strlcat(temp, dllnames[i], sizeof(temp));
184                         Con_DPrintf (" \"%s\"", temp);
185 #ifdef WIN32
186                         dllhandle = LoadLibrary (temp);
187 #else
188                         dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL);
189 #endif
190                         if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL))
191                                 break;
192                         else
193                                 Sys_UnloadLibrary (&dllhandle);
194                 }
195         }
196
197         // No DLL found
198         if (! dllhandle)
199         {
200                 Con_DPrintf(" - failed.\n");
201                 return false;
202         }
203
204         Con_DPrintf(" - loaded.\n");
205
206         *handle = dllhandle;
207         return true;
208 #else
209         return false;
210 #endif
211 }
212
213 void Sys_UnloadLibrary (dllhandle_t* handle)
214 {
215 #ifdef SUPPORTDLL
216         if (handle == NULL || *handle == NULL)
217                 return;
218
219 #ifdef WIN32
220         FreeLibrary (*handle);
221 #else
222         dlclose (*handle);
223 #endif
224
225         *handle = NULL;
226 #endif
227 }
228
229 void* Sys_GetProcAddress (dllhandle_t handle, const char* name)
230 {
231 #ifdef SUPPORTDLL
232 #ifdef WIN32
233         return (void *)GetProcAddress (handle, name);
234 #else
235         return (void *)dlsym (handle, name);
236 #endif
237 #else
238         return NULL;
239 #endif
240 }
241
242 #ifdef WIN32
243 # define HAVE_TIMEGETTIME 1
244 # define HAVE_QUERYPERFORMANCECOUNTER 1
245 # define HAVE_Sleep 1
246 #endif
247
248 #ifndef WIN32
249 #if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES)
250 # define HAVE_CLOCKGETTIME 1
251 #endif
252 // FIXME improve this check, manpage hints to DST_NONE
253 # define HAVE_GETTIMEOFDAY 1
254 #endif
255
256 #ifndef WIN32
257 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
258 // (so much for POSIX...)
259 # ifdef FD_SET
260 #  define HAVE_SELECT 1
261 # endif
262 #endif
263
264 #ifndef WIN32
265 // FIXME improve this check
266 # define HAVE_USLEEP 1
267 #endif
268
269 // this one is referenced elsewhere
270 cvar_t sys_usenoclockbutbenchmark = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."};
271
272 // these are not
273 static cvar_t sys_debugsleep = {CVAR_CLIENT | CVAR_SERVER, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"};
274 static cvar_t sys_usesdlgetticks = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"};
275 static cvar_t sys_usesdldelay = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"};
276 #if HAVE_QUERYPERFORMANCECOUNTER
277 static cvar_t sys_usequeryperformancecounter = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"};
278 #endif
279 #if HAVE_CLOCKGETTIME
280 static cvar_t sys_useclockgettime = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "sys_useclockgettime", "1", "use POSIX clock_gettime function (not adjusted by NTP on some older Linux kernels) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"};
281 #endif
282
283 static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow"
284
285 void Sys_Init_Commands (void)
286 {
287         Cvar_RegisterVariable(&sys_debugsleep);
288         Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
289 #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
290         if(sys_supportsdlgetticks)
291         {
292                 Cvar_RegisterVariable(&sys_usesdlgetticks);
293                 Cvar_RegisterVariable(&sys_usesdldelay);
294         }
295 #endif
296 #if HAVE_QUERYPERFORMANCECOUNTER
297         Cvar_RegisterVariable(&sys_usequeryperformancecounter);
298 #endif
299 #if HAVE_CLOCKGETTIME
300         Cvar_RegisterVariable(&sys_useclockgettime);
301 #endif
302 }
303
304 double Sys_DirtyTime(void)
305 {
306         // first all the OPTIONAL timers
307
308         // benchmark timer (fake clock)
309         if(sys_usenoclockbutbenchmark.integer)
310         {
311                 double old_benchmark_time = benchmark_time;
312                 benchmark_time += 1;
313                 if(benchmark_time == old_benchmark_time)
314                         Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
315                 return benchmark_time * 0.000001;
316         }
317 #if HAVE_QUERYPERFORMANCECOUNTER
318         if (sys_usequeryperformancecounter.integer)
319         {
320                 // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
321                 // QueryPerformanceCounter
322                 // platform:
323                 // Windows 95/98/ME/NT/2000/XP
324                 // features:
325                 // very accurate (CPU cycles)
326                 // known issues:
327                 // does not necessarily match realtime too well (tends to get faster and faster in win98)
328                 // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
329                 double timescale;
330                 LARGE_INTEGER PerformanceFreq;
331                 LARGE_INTEGER PerformanceCount;
332
333                 if (QueryPerformanceFrequency (&PerformanceFreq))
334                 {
335                         QueryPerformanceCounter (&PerformanceCount);
336         
337                         #ifdef __BORLANDC__
338                         timescale = 1.0 / ((double) PerformanceFreq.u.LowPart + (double) PerformanceFreq.u.HighPart * 65536.0 * 65536.0);
339                         return ((double) PerformanceCount.u.LowPart + (double) PerformanceCount.u.HighPart * 65536.0 * 65536.0) * timescale;
340                         #else
341                         timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
342                         return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
343                         #endif
344                 }
345                 else
346                 {
347                         Con_Printf("No hardware timer available\n");
348                         // fall back to other clock sources
349                         Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
350                 }
351         }
352 #endif
353
354 #if HAVE_CLOCKGETTIME
355         if (sys_useclockgettime.integer)
356         {
357                 struct timespec ts;
358 #  ifdef CLOCK_MONOTONIC
359                 // linux
360                 clock_gettime(CLOCK_MONOTONIC, &ts);
361 #  else
362                 // sunos
363                 clock_gettime(CLOCK_HIGHRES, &ts);
364 #  endif
365                 return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
366         }
367 #endif
368
369         // now all the FALLBACK timers
370         if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
371                 return (double) Sys_SDL_GetTicks() / 1000.0;
372 #if HAVE_GETTIMEOFDAY
373         {
374                 struct timeval tp;
375                 gettimeofday(&tp, NULL);
376                 return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
377         }
378 #elif HAVE_TIMEGETTIME
379         {
380                 static int firsttimegettime = true;
381                 // timeGetTime
382                 // platform:
383                 // Windows 95/98/ME/NT/2000/XP
384                 // features:
385                 // reasonable accuracy (millisecond)
386                 // issues:
387                 // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
388
389                 // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
390                 if (firsttimegettime)
391                 {
392                         timeBeginPeriod(1);
393                         firsttimegettime = false;
394                 }
395
396                 return (double) timeGetTime() / 1000.0;
397         }
398 #else
399         // fallback for using the SDL timer if no other timer is available
400         // this calls Sys_Error() if not linking against SDL
401         return (double) Sys_SDL_GetTicks() / 1000.0;
402 #endif
403 }
404
405 void Sys_Sleep(int microseconds)
406 {
407         double t = 0;
408         if(sys_usenoclockbutbenchmark.integer)
409         {
410                 if(microseconds)
411                 {
412                         double old_benchmark_time = benchmark_time;
413                         benchmark_time += microseconds;
414                         if(benchmark_time == old_benchmark_time)
415                                 Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
416                 }
417                 return;
418         }
419         if(sys_debugsleep.integer)
420         {
421                 t = Sys_DirtyTime();
422         }
423         if(sys_supportsdlgetticks && sys_usesdldelay.integer)
424         {
425                 Sys_SDL_Delay(microseconds / 1000);
426         }
427 #if HAVE_SELECT
428         else
429         {
430                 struct timeval tv;
431                 tv.tv_sec = microseconds / 1000000;
432                 tv.tv_usec = microseconds % 1000000;
433                 select(0, NULL, NULL, NULL, &tv);
434         }
435 #elif HAVE_USLEEP
436         else
437         {
438                 usleep(microseconds);
439         }
440 #elif HAVE_Sleep
441         else
442         {
443                 Sleep(microseconds / 1000);
444         }
445 #else
446         else
447         {
448                 Sys_SDL_Delay(microseconds / 1000);
449         }
450 #endif
451         if(sys_debugsleep.integer)
452         {
453                 t = Sys_DirtyTime() - t;
454                 Sys_PrintfToTerminal("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000));
455         }
456 }
457
458 void Sys_PrintfToTerminal(const char *fmt, ...)
459 {
460         va_list argptr;
461         char msg[MAX_INPUTLINE];
462
463         va_start(argptr,fmt);
464         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
465         va_end(argptr);
466
467         Sys_PrintToTerminal(msg);
468 }
469
470 #ifndef WIN32
471 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
472 {
473         const char *p = PATH;
474         const char *q;
475         if(p && name)
476         {
477                 while((q = strchr(p, ':')))
478                 {
479                         dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
480                         if(FS_SysFileExists(buf))
481                                 return buf;
482                         p = q + 1;
483                 }
484                 if(!q) // none found - try the last item
485                 {
486                         dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
487                         if(FS_SysFileExists(buf))
488                                 return buf;
489                 }
490         }
491         return name;
492 }
493 #endif
494
495 static const char *Sys_FindExecutableName(void)
496 {
497 #if defined(WIN32)
498         return sys.argv[0];
499 #else
500         static char exenamebuf[MAX_OSPATH+1];
501         ssize_t n = -1;
502 #if defined(__FreeBSD__)
503         int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
504         size_t exenamebuflen = sizeof(exenamebuf)-1;
505         if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
506         {
507                 n = exenamebuflen;
508         }
509 #elif defined(__linux__)
510         n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
511 #endif
512         if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
513         {
514                 exenamebuf[n] = 0;
515                 return exenamebuf;
516         }
517         if(strchr(sys.argv[0], '/'))
518                 return sys.argv[0]; // possibly a relative path
519         else
520                 return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
521 #endif
522 }
523
524 void Sys_ProvideSelfFD(void)
525 {
526         if(sys.selffd != -1)
527                 return;
528         sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
529 }
530
531 // for x86 cpus only...  (x64 has SSE2_PRESENT)
532 #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
533 // code from SDL, shortened as we can expect CPUID to work
534 static int CPUID_Features(void)
535 {
536         int features = 0;
537 # if defined((__GNUC__) || (__clang__) || (__TINYC__)) && defined(__i386__)
538         __asm__ (
539 "        movl    %%ebx,%%edi\n"
540 "        xorl    %%eax,%%eax                                           \n"
541 "        incl    %%eax                                                 \n"
542 "        cpuid                       # Get family/model/stepping/features\n"
543 "        movl    %%edx,%0                                              \n"
544 "        movl    %%edi,%%ebx\n"
545         : "=m" (features)
546         :
547         : "%eax", "%ecx", "%edx", "%edi"
548         );
549 # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
550         __asm {
551         xor     eax, eax
552         inc     eax
553         cpuid                       ; Get family/model/stepping/features
554         mov     features, edx
555         }
556 # else
557 #  error SSE_POSSIBLE set but no CPUID implementation
558 # endif
559         return features;
560 }
561 #endif
562
563 #ifdef SSE_POSSIBLE
564 qboolean Sys_HaveSSE(void)
565 {
566         // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
567         if(COM_CheckParm("-nosse"))
568                 return false;
569 #ifdef SSE_PRESENT
570         return true;
571 #else
572         // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
573         if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2"))
574                 return true;
575         if(CPUID_Features() & (1 << 25))
576                 return true;
577         return false;
578 #endif
579 }
580
581 qboolean Sys_HaveSSE2(void)
582 {
583         // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
584         if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2"))
585                 return false;
586 #ifdef SSE2_PRESENT
587         return true;
588 #else
589         // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
590         if(COM_CheckParm("-forcesse2"))
591                 return true;
592         if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
593                 return true;
594         return false;
595 #endif
596 }
597 #endif
598
599 /// called to set process priority for dedicated servers
600 #if defined(__linux__)
601 #include <sys/resource.h>
602 #include <errno.h>
603
604 void Sys_InitProcessNice (void)
605 {
606         struct rlimit lim;
607         sys.nicepossible = false;
608         if(COM_CheckParm("-nonice"))
609                 return;
610         errno = 0;
611         sys.nicelevel = getpriority(PRIO_PROCESS, 0);
612         if(errno)
613         {
614                 Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
615                 return;
616         }
617         if(getrlimit(RLIMIT_NICE, &lim))
618         {
619                 Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
620                 return;
621         }
622         if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
623         {
624                 Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
625                 return;
626         }
627         sys.nicepossible = true;
628         sys.isnice = false;
629 }
630 void Sys_MakeProcessNice (void)
631 {
632         if(!sys.nicepossible)
633                 return;
634         if(sys.isnice)
635                 return;
636         Con_DPrintf("Process is becoming 'nice'...\n");
637         if(setpriority(PRIO_PROCESS, 0, 19))
638                 Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
639         sys.isnice = true;
640 }
641 void Sys_MakeProcessMean (void)
642 {
643         if(!sys.nicepossible)
644                 return;
645         if(!sys.isnice)
646                 return;
647         Con_DPrintf("Process is becoming 'mean'...\n");
648         if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
649                 Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
650         sys.isnice = false;
651 }
652 #else
653 void Sys_InitProcessNice (void)
654 {
655 }
656 void Sys_MakeProcessNice (void)
657 {
658 }
659 void Sys_MakeProcessMean (void)
660 {
661 }
662 #endif