+void Sys_Init_Commands (void)
+{
+ Cvar_RegisterVariable(&sys_debugsleep);
+ Cvar_RegisterVariable(&sys_usenoclockbutbenchmark);
+ Cvar_RegisterVariable(&sys_libdir);
+#if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY
+ if(sys_supportsdlgetticks)
+ {
+ Cvar_RegisterVariable(&sys_usesdlgetticks);
+ Cvar_RegisterVariable(&sys_usesdldelay);
+ }
+#endif
+#if HAVE_QUERYPERFORMANCECOUNTER
+ Cvar_RegisterVariable(&sys_usequeryperformancecounter);
+#endif
+#if HAVE_CLOCKGETTIME
+ Cvar_RegisterVariable(&sys_useclockgettime);
+#endif
+ Cvar_RegisterVariable(&sys_stdout);
+ Cvar_RegisterCallback(&sys_stdout, Sys_UpdateOutFD_c);
+#ifndef WIN32
+ Cvar_RegisterVariable(&sys_stdout_blocks);
+#endif
+}
+
+double Sys_DirtyTime(void)
+{
+ // first all the OPTIONAL timers
+
+ // benchmark timer (fake clock)
+ if(sys_usenoclockbutbenchmark.integer)
+ {
+ double old_benchmark_time = benchmark_time;
+ benchmark_time += 1;
+ if(benchmark_time == old_benchmark_time)
+ Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
+ return benchmark_time * 0.000001;
+ }
+#if HAVE_QUERYPERFORMANCECOUNTER
+ if (sys_usequeryperformancecounter.integer)
+ {
+ // LadyHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine.
+ // QueryPerformanceCounter
+ // platform:
+ // Windows 95/98/ME/NT/2000/XP
+ // features:
+ // very accurate (CPU cycles)
+ // known issues:
+ // does not necessarily match realtime too well (tends to get faster and faster in win98)
+ // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors)
+ double timescale;
+ LARGE_INTEGER PerformanceFreq;
+ LARGE_INTEGER PerformanceCount;
+
+ if (QueryPerformanceFrequency (&PerformanceFreq))
+ {
+ QueryPerformanceCounter (&PerformanceCount);
+
+ timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0);
+ return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale;
+ }
+ else
+ {
+ Con_Printf("No hardware timer available\n");
+ // fall back to other clock sources
+ Cvar_SetValueQuick(&sys_usequeryperformancecounter, false);
+ }
+ }
+#endif
+
+#if HAVE_CLOCKGETTIME
+ if (sys_useclockgettime.integer)
+ {
+ struct timespec ts;
+# ifdef CLOCK_MONOTONIC
+ // linux
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+# else
+ // sunos
+ clock_gettime(CLOCK_HIGHRES, &ts);
+# endif
+ return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0;
+ }
+#endif
+
+ // now all the FALLBACK timers
+ if(sys_supportsdlgetticks && sys_usesdlgetticks.integer)
+ return (double) Sys_SDL_GetTicks() / 1000.0;
+#if HAVE_GETTIMEOFDAY
+ {
+ struct timeval tp;
+ gettimeofday(&tp, NULL);
+ return (double) tp.tv_sec + tp.tv_usec / 1000000.0;
+ }
+#elif HAVE_TIMEGETTIME
+ {
+ static int firsttimegettime = true;
+ // timeGetTime
+ // platform:
+ // Windows 95/98/ME/NT/2000/XP
+ // features:
+ // reasonable accuracy (millisecond)
+ // issues:
+ // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter)
+
+ // make sure the timer is high precision, otherwise different versions of windows have varying accuracy
+ if (firsttimegettime)
+ {
+ timeBeginPeriod(1);
+ firsttimegettime = false;
+ }
+
+ return (double) timeGetTime() / 1000.0;
+ }
+#else
+ // fallback for using the SDL timer if no other timer is available
+ // this calls Sys_Error() if not linking against SDL
+ return (double) Sys_SDL_GetTicks() / 1000.0;
+#endif
+}
+
+extern cvar_t host_maxwait;
+double Sys_Sleep(double time)
+{
+ double dt;
+ uint32_t microseconds;
+
+ // convert to microseconds
+ time *= 1000000.0;
+
+ if(host_maxwait.value <= 0)
+ time = min(time, 1000000.0);
+ else
+ time = min(time, host_maxwait.value * 1000.0);
+
+ if (time < 1 || host.restless)
+ return 0; // not sleeping this frame
+
+ microseconds = time; // post-validation to prevent overflow
+
+ if(sys_usenoclockbutbenchmark.integer)
+ {
+ double old_benchmark_time = benchmark_time;
+ benchmark_time += microseconds;
+ if(benchmark_time == old_benchmark_time)
+ Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry");
+ return 0;
+ }
+
+ if(sys_debugsleep.integer)
+ Con_Printf("sys_debugsleep: requesting %u ", microseconds);
+ dt = Sys_DirtyTime();
+
+ // less important on newer libcurl so no need to disturb dedicated servers
+ if (cls.state != ca_dedicated && Curl_Select(microseconds))
+ {
+ // a transfer is ready or we finished sleeping
+ }
+ else if(sys_supportsdlgetticks && sys_usesdldelay.integer)
+ Sys_SDL_Delay(microseconds / 1000);
+#if HAVE_SELECT
+ else
+ {
+ struct timeval tv;
+ lhnetsocket_t *s;
+ fd_set fdreadset;
+ int lastfd = -1;
+
+ FD_ZERO(&fdreadset);
+ if (cls.state == ca_dedicated && sv_checkforpacketsduringsleep.integer)
+ {
+ List_For_Each_Entry(s, &lhnet_socketlist.list, lhnetsocket_t, list)
+ {
+ if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6)
+ {
+ if (lastfd < s->inetsocket)
+ lastfd = s->inetsocket;
+ #if defined(WIN32) && !defined(_MSC_VER)
+ FD_SET((int)s->inetsocket, &fdreadset);
+ #else
+ FD_SET((unsigned int)s->inetsocket, &fdreadset);
+ #endif
+ }
+ }
+ }
+ tv.tv_sec = microseconds / 1000000;
+ tv.tv_usec = microseconds % 1000000;
+ // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN
+ // (so much for POSIX...)
+ // bones_was_here: but a zeroed fd_set seems to be tolerated (tested on Win 7)
+ select(lastfd + 1, &fdreadset, NULL, NULL, &tv);
+ }
+#elif HAVE_USLEEP
+ else
+ usleep(microseconds);
+#elif HAVE_Sleep
+ else
+ Sleep(microseconds / 1000);
+#else
+ else
+ Sys_SDL_Delay(microseconds / 1000);
+#endif
+
+ dt = Sys_DirtyTime() - dt;
+ if(sys_debugsleep.integer)
+ Con_Printf(" got %u oversleep %d\n", (unsigned int)(dt * 1000000), (unsigned int)(dt * 1000000) - microseconds);
+ return (dt < 0 || dt >= 1800) ? 0 : dt;
+}
+
+
+/*
+===============================================================================
+
+STDIO
+
+===============================================================================
+*/
+
+void Sys_Print(const char *text, size_t textlen)
+{
+#ifdef __ANDROID__
+ if (developer.integer > 0)
+ {
+ __android_log_write(ANDROID_LOG_DEBUG, sys.argv[0], text);
+ }
+#else
+ if(sys.outfd < 0)
+ return;
+ #ifndef WIN32
+ // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0).
+ // this is because both go to /dev/tty by default!
+ {
+ int origflags = fcntl(sys.outfd, F_GETFL, 0);
+ if (sys_stdout_blocks.integer)
+ fcntl(sys.outfd, F_SETFL, origflags & ~O_NONBLOCK);
+ #else
+ #define write _write
+ #endif
+ while(*text)
+ {
+ fs_offset_t written = (fs_offset_t)write(sys.outfd, text, textlen);
+ if(written <= 0)
+ break; // sorry, I cannot do anything about this error - without an output
+ text += written;
+ }
+ #ifndef WIN32
+ if (sys_stdout_blocks.integer)
+ fcntl(sys.outfd, F_SETFL, origflags);
+ }
+ #endif
+ //fprintf(stdout, "%s", text);
+#endif
+}
+
+void Sys_Printf(const char *fmt, ...)
+{
+ va_list argptr;
+ char msg[MAX_INPUTLINE];
+ int msglen;
+
+ va_start(argptr,fmt);
+ msglen = dpvsnprintf(msg, sizeof(msg), fmt, argptr);
+ va_end(argptr);
+
+ if (msglen >= 0)
+ Sys_Print(msg, msglen);
+}
+
+/// Reads a line from POSIX stdin or the Windows console
+char *Sys_ConsoleInput(void)
+{
+ static char text[MAX_INPUTLINE];
+#ifdef WIN32
+ static unsigned int len = 0;
+ int c;
+
+ // read a line out
+ while (_kbhit ())
+ {
+ c = _getch ();
+ if (c == '\r')
+ {
+ text[len] = '\0';
+ _putch ('\n');
+ len = 0;
+ return text;
+ }
+ if (c == '\b')
+ {
+ if (len)
+ {
+ _putch (c);
+ _putch (' ');
+ _putch (c);
+ len--;
+ }
+ continue;
+ }
+ if (len < sizeof (text) - 1)
+ {
+ _putch (c);
+ text[len] = c;
+ len++;
+ }
+ }
+#else
+ fd_set fdset;
+ struct timeval timeout = { .tv_sec = 0, .tv_usec = 0 };
+
+ FD_ZERO(&fdset);
+ FD_SET(fileno(stdin), &fdset);
+ if (select(1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(fileno(stdin), &fdset))
+ return fgets(text, sizeof(text), stdin);
+#endif
+ return NULL;
+}
+
+
+/*
+===============================================================================
+
+Startup and Shutdown
+
+===============================================================================
+*/
+
+void Sys_Error (const char *error, ...)
+{
+ va_list argptr;
+ char string[MAX_INPUTLINE];
+
+ // set output to blocking stderr
+ sys.outfd = fileno(stderr);
+#ifndef WIN32
+ fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
+#endif
+
+ va_start (argptr,error);
+ dpvsnprintf (string, sizeof (string), error, argptr);
+ va_end (argptr);
+
+ Con_Printf(CON_ERROR "Engine Error: %s\n", string);
+
+ // don't want a dead window left blocking the OS UI or the crash dialog
+ Host_Shutdown();
+
+ Sys_SDL_Dialog("Engine Error", string);
+
+ fflush(stderr);
+
+ exit (1);
+}
+
+#ifndef WIN32
+static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize)
+{
+ const char *p = PATH;
+ const char *q;
+ if(p && name)
+ {
+ while((q = strchr(p, ':')))
+ {
+ dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name);
+ if(FS_SysFileExists(buf))
+ return buf;
+ p = q + 1;
+ }
+ if(!q) // none found - try the last item
+ {
+ dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name);
+ if(FS_SysFileExists(buf))
+ return buf;
+ }
+ }
+ return name;
+}
+#endif
+
+static const char *Sys_FindExecutableName(void)
+{
+#if defined(WIN32)
+ return sys.argv[0];
+#else
+ static char exenamebuf[MAX_OSPATH+1];
+ ssize_t n = -1;
+#if defined(__FreeBSD__)
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
+ size_t exenamebuflen = sizeof(exenamebuf)-1;
+ if (sysctl(mib, 4, exenamebuf, &exenamebuflen, NULL, 0) == 0)
+ {
+ n = exenamebuflen;
+ }
+#elif defined(__linux__)
+ n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1);
+#endif
+ if(n > 0 && (size_t)(n) < sizeof(exenamebuf))
+ {
+ exenamebuf[n] = 0;
+ return exenamebuf;
+ }
+ if(strchr(sys.argv[0], '/'))
+ return sys.argv[0]; // possibly a relative path
+ else
+ return Sys_FindInPATH(sys.argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf));
+#endif
+}
+
+void Sys_ProvideSelfFD(void)
+{
+ if(sys.selffd != -1)
+ return;
+ sys.selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false);
+}
+
+// for x86 cpus only... (x64 has SSE2_PRESENT)
+#if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT)
+// code from SDL, shortened as we can expect CPUID to work
+static int CPUID_Features(void)
+{
+ int features = 0;
+# if (defined(__GNUC__) || defined(__clang__) || defined(__TINYC__)) && defined(__i386__)
+ __asm__ (
+" movl %%ebx,%%edi\n"
+" xorl %%eax,%%eax \n"
+" incl %%eax \n"
+" cpuid # Get family/model/stepping/features\n"
+" movl %%edx,%0 \n"
+" movl %%edi,%%ebx\n"
+ : "=m" (features)
+ :
+ : "%eax", "%ecx", "%edx", "%edi"
+ );
+# elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__)
+ __asm {
+ xor eax, eax
+ inc eax
+ cpuid ; Get family/model/stepping/features
+ mov features, edx
+ }
+# else
+# error SSE_POSSIBLE set but no CPUID implementation
+# endif
+ return features;
+}
+#endif
+
+#ifdef SSE_POSSIBLE
+qbool Sys_HaveSSE(void)
+{
+ // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection
+ if(Sys_CheckParm("-nosse"))
+ return false;
+#ifdef SSE_PRESENT
+ return true;
+#else
+ // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection
+ if(Sys_CheckParm("-forcesse") || Sys_CheckParm("-forcesse2"))
+ return true;
+ if(CPUID_Features() & (1 << 25))
+ return true;
+ return false;
+#endif
+}
+
+qbool Sys_HaveSSE2(void)
+{
+ // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection
+ if(Sys_CheckParm("-nosse") || Sys_CheckParm("-nosse2"))
+ return false;
+#ifdef SSE2_PRESENT
+ return true;
+#else
+ // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection
+ if(Sys_CheckParm("-forcesse2"))
+ return true;
+ if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26
+ return true;
+ return false;
+#endif
+}
+#endif
+
+/// called to set process priority for dedicated servers
+#if defined(__linux__)
+#include <sys/resource.h>
+#include <errno.h>
+
+void Sys_InitProcessNice (void)
+{
+ struct rlimit lim;
+ sys.nicepossible = false;
+ if(Sys_CheckParm("-nonice"))
+ return;
+ errno = 0;
+ sys.nicelevel = getpriority(PRIO_PROCESS, 0);
+ if(errno)
+ {
+ Con_Printf("Kernel does not support reading process priority - cannot use niceness\n");
+ return;
+ }
+ if(getrlimit(RLIMIT_NICE, &lim))
+ {
+ Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n");
+ return;
+ }
+ if(lim.rlim_cur != RLIM_INFINITY && sys.nicelevel < (int) (20 - lim.rlim_cur))
+ {
+ Con_Printf("Current nice level is below the soft limit - cannot use niceness\n");
+ return;
+ }
+ sys.nicepossible = true;
+ sys.isnice = false;
+}
+void Sys_MakeProcessNice (void)
+{
+ if(!sys.nicepossible)
+ return;
+ if(sys.isnice)
+ return;
+ Con_DPrintf("Process is becoming 'nice'...\n");
+ if(setpriority(PRIO_PROCESS, 0, 19))
+ Con_Printf(CON_ERROR "Failed to raise nice level to %d\n", 19);
+ sys.isnice = true;
+}
+void Sys_MakeProcessMean (void)
+{
+ if(!sys.nicepossible)
+ return;
+ if(!sys.isnice)
+ return;
+ Con_DPrintf("Process is becoming 'mean'...\n");
+ if(setpriority(PRIO_PROCESS, 0, sys.nicelevel))
+ Con_Printf(CON_ERROR "Failed to lower nice level to %d\n", sys.nicelevel);
+ sys.isnice = false;
+}
+#else
+void Sys_InitProcessNice (void)
+{
+}
+void Sys_MakeProcessNice (void)
+{
+}
+void Sys_MakeProcessMean (void)
+{
+}
+#endif
+
+/** Halt and try not to catch fire.
+ * Writing to any file could corrupt it,
+ * any uneccessary code could crash while we crash.
+ * No malloc() (libgcc should be loaded already) or Con_Printf() allowed here.
+ */
+static void Sys_HandleCrash(int sig)
+{
+#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
+ // Before doing anything else grab the stack frame addresses
+ #include <execinfo.h>
+ void *stackframes[32];
+ int framecount = backtrace(stackframes, 32);
+#endif
+
+ // Windows doesn't have strsignal()
+ const char *sigdesc;
+ switch (sig)
+ {
+#ifndef WIN32 // or SIGBUS
+ case SIGBUS: sigdesc = "Bus error"; break;
+#endif
+ case SIGILL: sigdesc = "Illegal instruction"; break;
+ case SIGABRT: sigdesc = "Aborted"; break;
+ case SIGFPE: sigdesc = "Floating point exception"; break;
+ case SIGSEGV: sigdesc = "Segmentation fault"; break;
+ default: sigdesc = "Yo dawg, we hit a bug while hitting a bug";
+ }
+
+ // set output to blocking stderr
+ sys.outfd = fileno(stderr);
+#ifndef WIN32
+ fcntl(sys.outfd, F_SETFL, fcntl(sys.outfd, F_GETFL, 0) & ~O_NONBLOCK);
+#endif
+
+ fprintf(stderr, "\n\n\e[1;37;41m Engine Crash: %s (%d) \e[m\n", sigdesc, sig);
+#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
+ // the first two addresses will be in this function and in signal() in libc
+ backtrace_symbols_fd(stackframes + 2, framecount - 2, sys.outfd);
+#endif
+ fprintf(stderr, "\e[1m%s\e[m\n", engineversion);
+
+ // DP8 TODO: send a disconnect message indicating we crashed, see CL_DisconnectEx()
+
+ // don't want a dead window left blocking the OS UI or the crash dialog
+ VID_Shutdown();
+ S_StopAllSounds();
+
+ Sys_SDL_Dialog("Engine Crash", sigdesc);
+
+ fflush(stderr);
+
+ exit (sig);
+}
+
+static void Sys_HandleSignal(int sig)
+{
+#ifdef WIN32
+ // Windows users will likely never see this so no point replicating strsignal()
+ Con_Printf("\nReceived signal %d, exiting...\n", sig);
+#else
+ Con_Printf("\nReceived %s signal (%d), exiting...\n", strsignal(sig), sig);
+#endif
+ host.state = host_shutdown;
+}
+
+/// SDL2 only handles SIGINT and SIGTERM by default and doesn't log anything
+static void Sys_InitSignals(void)
+{
+// Windows docs say its signal() only accepts these ones
+ signal(SIGABRT, Sys_HandleCrash);
+ signal(SIGFPE, Sys_HandleCrash);
+ signal(SIGILL, Sys_HandleCrash);
+ signal(SIGINT, Sys_HandleSignal);
+ signal(SIGSEGV, Sys_HandleCrash);
+ signal(SIGTERM, Sys_HandleSignal);
+#ifndef WIN32
+ signal(SIGHUP, Sys_HandleSignal);
+ signal(SIGQUIT, Sys_HandleSignal);
+ signal(SIGBUS, Sys_HandleCrash);
+ signal(SIGPIPE, Sys_HandleSignal);
+#endif
+}
+
+int main (int argc, char **argv)
+{
+ sys.argc = argc;
+ sys.argv = (const char **)argv;
+
+ // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
+ // COMMANDLINEOPTION: -noterminal disables console output on stdout
+ if(Sys_CheckParm("-noterminal") || Sys_CheckParm("-nostdout"))
+ sys_stdout.string = "0";
+ // COMMANDLINEOPTION: -stderr moves console output to stderr
+ else if(Sys_CheckParm("-stderr"))
+ sys_stdout.string = "2";
+ // too early for Cvar_SetQuick
+ sys_stdout.value = sys_stdout.integer = atoi(sys_stdout.string);
+ Sys_UpdateOutFD_c(&sys_stdout);
+#ifndef WIN32
+ fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, 0) | O_NONBLOCK);
+ // stdout/stderr will be set to blocking in Sys_Print() if so configured, or during a fatal error.
+ fcntl(fileno(stdout), F_SETFL, fcntl(fileno(stdout), F_GETFL, 0) | O_NONBLOCK);
+ fcntl(fileno(stderr), F_SETFL, fcntl(fileno(stderr), F_GETFL, 0) | O_NONBLOCK);
+#endif
+
+ sys.selffd = -1;
+ Sys_ProvideSelfFD(); // may call Con_Printf() so must be after sys.outfd is set
+
+#ifdef __ANDROID__
+ Sys_AllowProfiling(true);
+#endif
+
+ Sys_InitSignals();
+
+ Host_Main();
+
+ Sys_Quit(0);
+
+ return 0;
+}