summaryrefslogtreecommitdiff
path: root/src/sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/sys')
-rw-r--r--src/sys/CMakeLists.txt15
-rw-r--r--src/sys/con_log.cpp132
-rw-r--r--src/sys/con_passive.cpp72
-rw-r--r--src/sys/con_tty.cpp552
-rw-r--r--src/sys/con_win32.cpp558
-rw-r--r--src/sys/dialog.h40
-rw-r--r--src/sys/sys_loadlib.h57
-rw-r--r--src/sys/sys_local.h58
-rw-r--r--src/sys/sys_main.cpp798
-rw-r--r--src/sys/sys_osx.mm103
-rw-r--r--src/sys/sys_shared.h111
-rw-r--r--src/sys/sys_unix.cpp1006
-rw-r--r--src/sys/sys_win32.cpp842
-rw-r--r--src/sys/sys_win32_default_homepath.cpp52
-rw-r--r--src/sys/win_resource.h46
-rw-r--r--src/sys/win_resource.rc72
16 files changed, 4514 insertions, 0 deletions
diff --git a/src/sys/CMakeLists.txt b/src/sys/CMakeLists.txt
new file mode 100644
index 0000000..5d7ae9b
--- /dev/null
+++ b/src/sys/CMakeLists.txt
@@ -0,0 +1,15 @@
+add_library (
+ sys STATIC
+ con_log.cpp
+ con_passive.cpp
+ con_tty.cpp
+ con_win32.cpp
+ sys_loadlib.h
+ sys_local.h
+ sys_main.cpp
+ sys_unix.cpp
+ sys_win32.cpp
+ win_resource.h
+)
+
+set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14" )
diff --git a/src/sys/con_log.cpp b/src/sys/con_log.cpp
new file mode 100644
index 0000000..eaa81b0
--- /dev/null
+++ b/src/sys/con_log.cpp
@@ -0,0 +1,132 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "sys_local.h"
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+#define MAX_LOG 32768
+
+static char consoleLog[ MAX_LOG ];
+static unsigned int writePos = 0;
+static unsigned int readPos = 0;
+
+/*
+==================
+CON_LogSize
+==================
+*/
+unsigned int CON_LogSize( void )
+{
+ if( readPos <= writePos )
+ return writePos - readPos;
+ else
+ return writePos + MAX_LOG - readPos;
+}
+
+/*
+==================
+CON_LogFree
+==================
+*/
+static unsigned int CON_LogFree( void )
+{
+ return MAX_LOG - CON_LogSize( ) - 1;
+}
+
+/*
+==================
+CON_LogWrite
+==================
+*/
+unsigned int CON_LogWrite( const char *in )
+{
+ unsigned int length = strlen( in );
+ unsigned int firstChunk;
+ unsigned int secondChunk;
+
+ while( CON_LogFree( ) < length && CON_LogSize( ) > 0 )
+ {
+ // Free enough space
+ while( consoleLog[ readPos ] != '\n' && CON_LogSize( ) > 1 )
+ readPos = ( readPos + 1 ) % MAX_LOG;
+
+ // Skip past the '\n'
+ readPos = ( readPos + 1 ) % MAX_LOG;
+ }
+
+ if( CON_LogFree( ) < length )
+ return 0;
+
+ if( writePos + length > MAX_LOG )
+ {
+ firstChunk = MAX_LOG - writePos;
+ secondChunk = length - firstChunk;
+ }
+ else
+ {
+ firstChunk = length;
+ secondChunk = 0;
+ }
+
+ Com_Memcpy( consoleLog + writePos, in, firstChunk );
+ Com_Memcpy( consoleLog, in + firstChunk, secondChunk );
+
+ writePos = ( writePos + length ) % MAX_LOG;
+
+ return length;
+}
+
+/*
+==================
+CON_LogRead
+==================
+*/
+unsigned int CON_LogRead( char *out, unsigned int outSize )
+{
+ unsigned int firstChunk;
+ unsigned int secondChunk;
+
+ if( CON_LogSize( ) < outSize )
+ outSize = CON_LogSize( );
+
+ if( readPos + outSize > MAX_LOG )
+ {
+ firstChunk = MAX_LOG - readPos;
+ secondChunk = outSize - firstChunk;
+ }
+ else
+ {
+ firstChunk = outSize;
+ secondChunk = 0;
+ }
+
+ Com_Memcpy( out, consoleLog + readPos, firstChunk );
+ Com_Memcpy( out + firstChunk, out, secondChunk );
+
+ readPos = ( readPos + outSize ) % MAX_LOG;
+
+ return outSize;
+}
diff --git a/src/sys/con_passive.cpp b/src/sys/con_passive.cpp
new file mode 100644
index 0000000..35918e5
--- /dev/null
+++ b/src/sys/con_passive.cpp
@@ -0,0 +1,72 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "sys_local.h"
+
+#include "qcommon/cvar.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+#include <stdio.h>
+
+/*
+==================
+CON_Shutdown
+==================
+*/
+void CON_Shutdown( void )
+{
+}
+
+/*
+==================
+CON_Init
+==================
+*/
+void CON_Init( void )
+{
+}
+
+/*
+==================
+CON_Input
+==================
+*/
+char *CON_Input( void )
+{
+ return NULL;
+}
+
+/*
+==================
+CON_Print
+==================
+*/
+void CON_Print( const char *msg )
+{
+ if( com_ansiColor && com_ansiColor->integer )
+ Sys_AnsiColorPrint( msg );
+ else
+ fputs( msg, stderr );
+}
diff --git a/src/sys/con_tty.cpp b/src/sys/con_tty.cpp
new file mode 100644
index 0000000..660c160
--- /dev/null
+++ b/src/sys/con_tty.cpp
@@ -0,0 +1,552 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "sys_local.h"
+
+#include <fcntl.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <csignal>
+
+#ifndef DEDICATED
+#include "client/client.h"
+#endif
+#include "qcommon/cvar.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+/*
+=============================================================
+tty console routines
+
+NOTE: if the user is editing a line when something gets printed to the early
+console then it won't look good so we provide CON_Hide and CON_Show to be
+called before and after a stdout or stderr output
+=============================================================
+*/
+
+extern bool stdinIsATTY;
+
+static bool stdin_active = false;
+// general flag to tell about tty console mode
+static bool ttycon_on = false;
+static int ttycon_hide = 0;
+static int ttycon_show_overdue = 0;
+
+// some key codes that the terminal may be using, initialised on start up
+static int TTY_erase;
+static int TTY_eof;
+
+static struct termios TTY_tc;
+
+static field_t TTY_con;
+
+// This is somewhat of aduplicate of the graphical console history
+// but it's safer more modular to have our own here
+#define CON_HISTORY 32
+static field_t ttyEditLines[ CON_HISTORY ];
+static int hist_current = -1, hist_count = 0;
+
+#ifndef DEDICATED
+// Don't use "]" as it would be the same as in-game console,
+// this makes it clear where input came from.
+#define TTY_CONSOLE_PROMPT "tty]"
+#else
+#define TTY_CONSOLE_PROMPT "]"
+#endif
+
+/*
+==================
+CON_FlushIn
+
+Flush stdin, I suspect some terminals are sending a LOT of shit
+FIXME relevant?
+==================
+*/
+static void CON_FlushIn( void )
+{
+ char key;
+ while (read(STDIN_FILENO, &key, 1)!=-1);
+}
+
+/*
+==================
+CON_Back
+
+Output a backspace
+
+NOTE: it seems on some terminals just sending '\b' is not enough so instead we
+send "\b \b"
+(FIXME there may be a way to find out if '\b' alone would work though)
+==================
+*/
+static void CON_Back( void )
+{
+ char key;
+ size_t UNUSED_VAR size;
+
+ key = '\b';
+ size = write(STDOUT_FILENO, &key, 1);
+ key = ' ';
+ size = write(STDOUT_FILENO, &key, 1);
+ key = '\b';
+ size = write(STDOUT_FILENO, &key, 1);
+}
+
+/*
+==================
+CON_Hide
+
+Clear the display of the line currently edited
+bring cursor back to beginning of line
+==================
+*/
+static void CON_Hide( void )
+{
+ if( ttycon_on )
+ {
+ int i;
+ if (ttycon_hide)
+ {
+ ttycon_hide++;
+ return;
+ }
+ if (TTY_con.cursor>0)
+ {
+ for (i=0; i<TTY_con.cursor; i++)
+ {
+ CON_Back();
+ }
+ }
+ // Delete prompt
+ for (i = strlen(TTY_CONSOLE_PROMPT); i > 0; i--) {
+ CON_Back();
+ }
+ ttycon_hide++;
+ }
+}
+
+/*
+==================
+CON_Show
+
+Show the current line
+FIXME need to position the cursor if needed?
+==================
+*/
+static void CON_Show( void )
+{
+ if( ttycon_on )
+ {
+ int i;
+
+ assert(ttycon_hide>0);
+ ttycon_hide--;
+ if (ttycon_hide == 0)
+ {
+ size_t UNUSED_VAR size;
+ size = write(STDOUT_FILENO, TTY_CONSOLE_PROMPT, strlen(TTY_CONSOLE_PROMPT));
+ if (TTY_con.cursor)
+ {
+ for (i=0; i<TTY_con.cursor; i++)
+ {
+ size = write(STDOUT_FILENO, TTY_con.buffer+i, 1);
+ }
+ }
+ }
+ }
+}
+
+/*
+==================
+CON_Shutdown
+
+Never exit without calling this, or your terminal will be left in a pretty bad state
+==================
+*/
+void CON_Shutdown( void )
+{
+ if (ttycon_on)
+ {
+ CON_Hide();
+ tcsetattr (STDIN_FILENO, TCSADRAIN, &TTY_tc);
+ }
+
+ // Restore blocking to stdin reads
+ fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
+}
+
+/*
+==================
+Hist_Add
+==================
+*/
+void Hist_Add(field_t *field)
+{
+ int i;
+
+ // Don't save blank lines in history.
+ if (!field->cursor)
+ return;
+
+ assert(hist_count <= CON_HISTORY);
+ assert(hist_count >= 0);
+ assert(hist_current >= -1);
+ assert(hist_current <= hist_count);
+ // make some room
+ for (i=CON_HISTORY-1; i>0; i--)
+ {
+ ttyEditLines[i] = ttyEditLines[i-1];
+ }
+ ttyEditLines[0] = *field;
+ if (hist_count<CON_HISTORY)
+ {
+ hist_count++;
+ }
+ hist_current = -1; // re-init
+}
+
+/*
+==================
+Hist_Prev
+==================
+*/
+field_t *Hist_Prev( void )
+{
+ int hist_prev;
+ assert(hist_count <= CON_HISTORY);
+ assert(hist_count >= 0);
+ assert(hist_current >= -1);
+ assert(hist_current <= hist_count);
+ hist_prev = hist_current + 1;
+ if (hist_prev >= hist_count)
+ {
+ return NULL;
+ }
+ hist_current++;
+ return &(ttyEditLines[hist_current]);
+}
+
+/*
+==================
+Hist_Next
+==================
+*/
+field_t *Hist_Next( void )
+{
+ assert(hist_count <= CON_HISTORY);
+ assert(hist_count >= 0);
+ assert(hist_current >= -1);
+ assert(hist_current <= hist_count);
+ if (hist_current >= 0)
+ {
+ hist_current--;
+ }
+ if (hist_current == -1)
+ {
+ return NULL;
+ }
+ return &(ttyEditLines[hist_current]);
+}
+
+/*
+==================
+CON_SigCont
+Reinitialize console input after receiving SIGCONT, as on Linux the terminal seems to lose all
+set attributes if user did CTRL+Z and then does fg again.
+==================
+*/
+
+void CON_SigCont(int signum)
+{
+ CON_Init();
+}
+
+/*
+==================
+CON_Init
+
+Initialize the console input (tty mode if possible)
+==================
+*/
+void CON_Init( void )
+{
+ struct termios tc;
+
+ // If the process is backgrounded (running non interactively)
+ // then SIGTTIN or SIGTOU is emitted, if not caught, turns into a SIGSTP
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTTOU, SIG_IGN);
+
+ // If SIGCONT is received, reinitialize console
+ signal(SIGCONT, CON_SigCont);
+
+ // Make stdin reads non-blocking
+ fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK );
+
+ if (!stdinIsATTY)
+ {
+ Com_Printf("tty console mode disabled\n");
+ ttycon_on = false;
+ stdin_active = true;
+ return;
+ }
+
+ Field_Clear(&TTY_con);
+ tcgetattr (STDIN_FILENO, &TTY_tc);
+ TTY_erase = TTY_tc.c_cc[VERASE];
+ TTY_eof = TTY_tc.c_cc[VEOF];
+ tc = TTY_tc;
+
+ /*
+ ECHO: don't echo input characters
+ ICANON: enable canonical mode. This enables the special
+ characters EOF, EOL, EOL2, ERASE, KILL, REPRINT,
+ STATUS, and WERASE, and buffers by lines.
+ ISIG: when any of the characters INTR, QUIT, SUSP, or
+ DSUSP are received, generate the corresponding signal
+ */
+ tc.c_lflag &= ~(ECHO | ICANON);
+
+ /*
+ ISTRIP strip off bit 8
+ INPCK enable input parity checking
+ */
+ tc.c_iflag &= ~(ISTRIP | INPCK);
+ tc.c_cc[VMIN] = 1;
+ tc.c_cc[VTIME] = 0;
+ tcsetattr (STDIN_FILENO, TCSADRAIN, &tc);
+ ttycon_on = true;
+ ttycon_hide = 1; // Mark as hidden, so prompt is shown in CON_Show
+ CON_Show();
+}
+
+/*
+==================
+CON_Input
+==================
+*/
+char *CON_Input( void )
+{
+ // we use this when sending back commands
+ static char text[MAX_EDIT_LINE];
+ int avail;
+ char key;
+ field_t *history;
+ size_t UNUSED_VAR size;
+
+ if(ttycon_on)
+ {
+ avail = read(STDIN_FILENO, &key, 1);
+ if (avail != -1)
+ {
+ // we have something
+ // backspace?
+ // NOTE TTimo testing a lot of values .. seems it's the only way to get it to work everywhere
+ if ((key == TTY_erase) || (key == 127) || (key == 8))
+ {
+ if (TTY_con.cursor > 0)
+ {
+ TTY_con.cursor--;
+ TTY_con.buffer[TTY_con.cursor] = '\0';
+ CON_Back();
+ }
+ return NULL;
+ }
+ // check if this is a control char
+ if ((key) && (key) < ' ')
+ {
+ if (key == '\n')
+ {
+#ifndef DEDICATED
+ // if not in the game explicitly prepend a slash if needed
+ if (clc.state != CA_ACTIVE && TTY_con.cursor &&
+ TTY_con.buffer[0] != '/' && TTY_con.buffer[0] != '\\')
+ {
+ memmove(TTY_con.buffer + 1, TTY_con.buffer, sizeof(TTY_con.buffer) - 1);
+ TTY_con.buffer[0] = '\\';
+ TTY_con.cursor++;
+ }
+
+ if (TTY_con.buffer[0] == '/' || TTY_con.buffer[0] == '\\') {
+ Q_strncpyz(text, TTY_con.buffer + 1, sizeof(text));
+ } else if (TTY_con.cursor) {
+ Com_sprintf(text, sizeof(text), "cmd say %s", TTY_con.buffer);
+ } else {
+ text[0] = '\0';
+ }
+
+ // push it in history
+ Hist_Add(&TTY_con);
+ CON_Hide();
+ Com_Printf("%s%s\n", TTY_CONSOLE_PROMPT, TTY_con.buffer);
+ Field_Clear(&TTY_con);
+ CON_Show();
+#else
+ // push it in history
+ Hist_Add(&TTY_con);
+ Q_strncpyz(text, TTY_con.buffer, sizeof(text));
+ Field_Clear(&TTY_con);
+ key = '\n';
+ size = write(STDOUT_FILENO, &key, 1);
+ size = write(STDOUT_FILENO, TTY_CONSOLE_PROMPT, strlen(TTY_CONSOLE_PROMPT));
+#endif
+ return text;
+ }
+ if (key == '\t')
+ {
+ CON_Hide();
+ Field_AutoComplete( &TTY_con );
+ CON_Show();
+ return NULL;
+ }
+ avail = read(STDIN_FILENO, &key, 1);
+ if (avail != -1)
+ {
+ // VT 100 keys
+ if (key == '[' || key == 'O')
+ {
+ avail = read(STDIN_FILENO, &key, 1);
+ if (avail != -1)
+ {
+ switch (key)
+ {
+ case 'A':
+ history = Hist_Prev();
+ if (history)
+ {
+ CON_Hide();
+ TTY_con = *history;
+ CON_Show();
+ }
+ CON_FlushIn();
+ return NULL;
+ break;
+ case 'B':
+ history = Hist_Next();
+ CON_Hide();
+ if (history)
+ {
+ TTY_con = *history;
+ } else
+ {
+ Field_Clear(&TTY_con);
+ }
+ CON_Show();
+ CON_FlushIn();
+ return NULL;
+ break;
+ case 'C':
+ return NULL;
+ case 'D':
+ return NULL;
+ }
+ }
+ }
+ }
+ Com_DPrintf("droping ISCTL sequence: %d, TTY_erase: %d\n", key, TTY_erase);
+ CON_FlushIn();
+ return NULL;
+ }
+ if (TTY_con.cursor >= sizeof(text) - 1)
+ return NULL;
+ // push regular character
+ TTY_con.buffer[TTY_con.cursor] = key;
+ TTY_con.cursor++; // next char will always be '\0'
+ // print the current line (this is differential)
+ size = write(STDOUT_FILENO, &key, 1);
+ }
+
+ return NULL;
+ }
+ else if (stdin_active)
+ {
+ int len;
+ fd_set fdset;
+ struct timeval timeout;
+
+ FD_ZERO(&fdset);
+ FD_SET(STDIN_FILENO, &fdset); // stdin
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if(select (STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(STDIN_FILENO, &fdset))
+ return NULL;
+
+ len = read(STDIN_FILENO, text, sizeof(text));
+ if (len == 0)
+ { // eof!
+ stdin_active = false;
+ return NULL;
+ }
+
+ if (len < 1)
+ return NULL;
+ text[len-1] = 0; // rip off the /n and terminate
+
+ return text;
+ }
+ return NULL;
+}
+
+/*
+==================
+CON_Print
+==================
+*/
+void CON_Print( const char *msg )
+{
+ if (!msg[0])
+ return;
+
+ CON_Hide( );
+
+ if( com_ansiColor && com_ansiColor->integer )
+ Sys_AnsiColorPrint( msg );
+ else
+ fputs( msg, stderr );
+
+ if (!ttycon_on) {
+ // CON_Hide didn't do anything.
+ return;
+ }
+
+ // Only print prompt when msg ends with a newline, otherwise the console
+ // might get garbled when output does not fit on one line.
+ if (msg[strlen(msg) - 1] == '\n') {
+ CON_Show();
+
+ // Run CON_Show the number of times it was deferred.
+ while (ttycon_show_overdue > 0) {
+ CON_Show();
+ ttycon_show_overdue--;
+ }
+ }
+ else
+ {
+ // Defer calling CON_Show
+ ttycon_show_overdue++;
+ }
+}
diff --git a/src/sys/con_win32.cpp b/src/sys/con_win32.cpp
new file mode 100644
index 0000000..8d8783a
--- /dev/null
+++ b/src/sys/con_win32.cpp
@@ -0,0 +1,558 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "sys_local.h"
+
+#include <windows.h>
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+#define QCONSOLE_HISTORY 32
+
+static WORD qconsole_attrib;
+static WORD qconsole_backgroundAttrib;
+
+// saved console status
+static DWORD qconsole_orig_mode;
+static CONSOLE_CURSOR_INFO qconsole_orig_cursorinfo;
+
+// cmd history
+static char qconsole_history[ QCONSOLE_HISTORY ][ MAX_EDIT_LINE ];
+static int qconsole_history_pos = -1;
+static int qconsole_history_lines = 0;
+static int qconsole_history_oldest = 0;
+
+// current edit buffer
+static char qconsole_line[ MAX_EDIT_LINE ];
+static int qconsole_linelen = 0;
+static bool qconsole_drawinput = true;
+static int qconsole_cursor;
+
+static HANDLE qconsole_hout;
+static HANDLE qconsole_hin;
+
+/*
+==================
+CON_ColorCharToAttrib
+
+Convert Quake color character to Windows text attrib
+==================
+*/
+static WORD CON_ColorCharToAttrib( char color ) {
+ WORD attrib;
+
+ if ( color == COLOR_WHITE )
+ {
+ // use console's foreground and background colors
+ attrib = qconsole_attrib;
+ }
+ else
+ {
+ float *rgba = g_color_table[ ColorIndex( color ) ];
+
+ // set foreground color
+ attrib = ( rgba[0] >= 0.5 ? FOREGROUND_RED : 0 ) |
+ ( rgba[1] >= 0.5 ? FOREGROUND_GREEN : 0 ) |
+ ( rgba[2] >= 0.5 ? FOREGROUND_BLUE : 0 ) |
+ ( rgba[3] >= 0.5 ? FOREGROUND_INTENSITY : 0 );
+
+ // use console's background color
+ attrib |= qconsole_backgroundAttrib;
+ }
+
+ return attrib;
+}
+
+/*
+==================
+CON_CtrlHandler
+
+The Windows Console doesn't use signals for terminating the application
+with Ctrl-C, logging off, window closing, etc. Instead it uses a special
+handler routine. Fortunately, the values for Ctrl signals don't seem to
+overlap with true signal codes that Windows provides, so calling
+Sys_SigHandler() with those numbers should be safe for generating unique
+shutdown messages.
+==================
+*/
+static BOOL WINAPI CON_CtrlHandler( DWORD sig )
+{
+ Sys_SigHandler( sig );
+ return TRUE;
+}
+
+/*
+==================
+CON_HistAdd
+==================
+*/
+static void CON_HistAdd( void )
+{
+ Q_strncpyz( qconsole_history[ qconsole_history_oldest ], qconsole_line,
+ sizeof( qconsole_history[ qconsole_history_oldest ] ) );
+
+ if( qconsole_history_lines < QCONSOLE_HISTORY )
+ qconsole_history_lines++;
+
+ if( qconsole_history_oldest >= QCONSOLE_HISTORY - 1 )
+ qconsole_history_oldest = 0;
+ else
+ qconsole_history_oldest++;
+
+ qconsole_history_pos = qconsole_history_oldest;
+}
+
+/*
+==================
+CON_HistPrev
+==================
+*/
+static void CON_HistPrev( void )
+{
+ int pos;
+
+ pos = ( qconsole_history_pos < 1 ) ?
+ ( QCONSOLE_HISTORY - 1 ) : ( qconsole_history_pos - 1 );
+
+ // don' t allow looping through history
+ if( pos == qconsole_history_oldest || pos >= qconsole_history_lines )
+ return;
+
+ qconsole_history_pos = pos;
+ Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ],
+ sizeof( qconsole_line ) );
+ qconsole_linelen = strlen( qconsole_line );
+ qconsole_cursor = qconsole_linelen;
+}
+
+/*
+==================
+CON_HistNext
+==================
+*/
+static void CON_HistNext( void )
+{
+ int pos;
+
+ // don' t allow looping through history
+ if( qconsole_history_pos == qconsole_history_oldest )
+ return;
+
+ pos = ( qconsole_history_pos >= QCONSOLE_HISTORY - 1 ) ?
+ 0 : ( qconsole_history_pos + 1 );
+
+ // clear the edit buffer if they try to advance to a future command
+ if( pos == qconsole_history_oldest )
+ {
+ qconsole_history_pos = pos;
+ qconsole_line[ 0 ] = '\0';
+ qconsole_linelen = 0;
+ qconsole_cursor = qconsole_linelen;
+ return;
+ }
+
+ qconsole_history_pos = pos;
+ Q_strncpyz( qconsole_line, qconsole_history[ qconsole_history_pos ],
+ sizeof( qconsole_line ) );
+ qconsole_linelen = strlen( qconsole_line );
+ qconsole_cursor = qconsole_linelen;
+}
+
+
+/*
+==================
+CON_Show
+==================
+*/
+static void CON_Show( void )
+{
+ CONSOLE_SCREEN_BUFFER_INFO binfo;
+ COORD writeSize = { MAX_EDIT_LINE, 1 };
+ COORD writePos = { 0, 0 };
+ SMALL_RECT writeArea = { 0, 0, 0, 0 };
+ COORD cursorPos;
+ int i;
+ CHAR_INFO line[ MAX_EDIT_LINE ];
+ WORD attrib;
+
+ GetConsoleScreenBufferInfo( qconsole_hout, &binfo );
+
+ // if we're in the middle of printf, don't bother writing the buffer
+ if( !qconsole_drawinput )
+ return;
+
+ writeArea.Left = 0;
+ writeArea.Top = binfo.dwCursorPosition.Y;
+ writeArea.Bottom = binfo.dwCursorPosition.Y;
+ writeArea.Right = MAX_EDIT_LINE;
+
+ // set color to white
+ attrib = CON_ColorCharToAttrib( COLOR_WHITE );
+
+ // build a space-padded CHAR_INFO array
+ for( i = 0; i < MAX_EDIT_LINE; i++ )
+ {
+ if( i < qconsole_linelen )
+ {
+ if( i + 1 < qconsole_linelen && Q_IsColorString( qconsole_line + i ) )
+ attrib = CON_ColorCharToAttrib( *( qconsole_line + i + 1 ) );
+
+ line[ i ].Char.AsciiChar = qconsole_line[ i ];
+ }
+ else
+ line[ i ].Char.AsciiChar = ' ';
+
+ line[ i ].Attributes = attrib;
+ }
+
+ if( qconsole_linelen > binfo.srWindow.Right )
+ {
+ WriteConsoleOutput( qconsole_hout,
+ line + (qconsole_linelen - binfo.srWindow.Right ),
+ writeSize, writePos, &writeArea );
+ }
+ else
+ {
+ WriteConsoleOutput( qconsole_hout, line, writeSize,
+ writePos, &writeArea );
+ }
+
+ // set curor position
+ cursorPos.Y = binfo.dwCursorPosition.Y;
+ cursorPos.X = qconsole_cursor < qconsole_linelen
+ ? qconsole_cursor
+ : qconsole_linelen > binfo.srWindow.Right
+ ? binfo.srWindow.Right
+ : qconsole_linelen;
+
+ SetConsoleCursorPosition( qconsole_hout, cursorPos );
+}
+
+/*
+==================
+CON_Hide
+==================
+*/
+static void CON_Hide( void )
+{
+ int realLen;
+
+ realLen = qconsole_linelen;
+
+ // remove input line from console output buffer
+ qconsole_linelen = 0;
+ CON_Show( );
+
+ qconsole_linelen = realLen;
+}
+
+
+/*
+==================
+CON_Shutdown
+==================
+*/
+void CON_Shutdown( void )
+{
+ CON_Hide( );
+ SetConsoleMode( qconsole_hin, qconsole_orig_mode );
+ SetConsoleCursorInfo( qconsole_hout, &qconsole_orig_cursorinfo );
+ SetConsoleTextAttribute( qconsole_hout, qconsole_attrib );
+ CloseHandle( qconsole_hout );
+ CloseHandle( qconsole_hin );
+}
+
+/*
+==================
+CON_Init
+==================
+*/
+void CON_Init( void )
+{
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ int i;
+
+ // handle Ctrl-C or other console termination
+ SetConsoleCtrlHandler( CON_CtrlHandler, TRUE );
+
+ qconsole_hin = GetStdHandle( STD_INPUT_HANDLE );
+ if( qconsole_hin == INVALID_HANDLE_VALUE )
+ return;
+
+ qconsole_hout = GetStdHandle( STD_OUTPUT_HANDLE );
+ if( qconsole_hout == INVALID_HANDLE_VALUE )
+ return;
+
+ GetConsoleMode( qconsole_hin, &qconsole_orig_mode );
+
+ // allow mouse wheel scrolling
+ SetConsoleMode( qconsole_hin,
+ qconsole_orig_mode & ~ENABLE_MOUSE_INPUT );
+
+ FlushConsoleInputBuffer( qconsole_hin );
+
+ GetConsoleScreenBufferInfo( qconsole_hout, &info );
+ qconsole_attrib = info.wAttributes;
+ qconsole_backgroundAttrib = qconsole_attrib & (BACKGROUND_BLUE|BACKGROUND_GREEN|BACKGROUND_RED|BACKGROUND_INTENSITY);
+
+ SetConsoleTitle("Tremulous Dedicated Server Console");
+
+ // initialize history
+ for( i = 0; i < QCONSOLE_HISTORY; i++ )
+ qconsole_history[ i ][ 0 ] = '\0';
+
+ // set text color to white
+ SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) );
+}
+
+/*
+==================
+CON_Input
+==================
+*/
+char *CON_Input( void )
+{
+ INPUT_RECORD buff[ MAX_EDIT_LINE ];
+ DWORD count = 0, events = 0;
+ WORD key = 0;
+ int i;
+ int newlinepos = -1;
+
+ if( !GetNumberOfConsoleInputEvents( qconsole_hin, &events ) )
+ return NULL;
+
+ if( events < 1 )
+ return NULL;
+
+ // if we have overflowed, start dropping oldest input events
+ if( events >= MAX_EDIT_LINE )
+ {
+ ReadConsoleInput( qconsole_hin, buff, 1, &events );
+ return NULL;
+ }
+
+ if( !ReadConsoleInput( qconsole_hin, buff, events, &count ) )
+ return NULL;
+
+ FlushConsoleInputBuffer( qconsole_hin );
+
+ for( i = 0; i < count; i++ )
+ {
+ if( buff[ i ].EventType != KEY_EVENT )
+ continue;
+ if( !buff[ i ].Event.KeyEvent.bKeyDown )
+ continue;
+
+ key = buff[ i ].Event.KeyEvent.wVirtualKeyCode;
+
+ if( key == VK_RETURN )
+ {
+ newlinepos = i;
+ qconsole_cursor = 0;
+ break;
+ }
+ else if( key == VK_UP )
+ {
+ CON_HistPrev();
+ break;
+ }
+ else if( key == VK_DOWN )
+ {
+ CON_HistNext();
+ break;
+ }
+ else if( key == VK_LEFT )
+ {
+ qconsole_cursor--;
+ if ( qconsole_cursor < 0 )
+ {
+ qconsole_cursor = 0;
+ }
+ break;
+ }
+ else if( key == VK_RIGHT )
+ {
+ qconsole_cursor++;
+ if ( qconsole_cursor > qconsole_linelen )
+ {
+ qconsole_cursor = qconsole_linelen;
+ }
+ break;
+ }
+ else if( key == VK_HOME )
+ {
+ qconsole_cursor = 0;
+ break;
+ }
+ else if( key == VK_END )
+ {
+ qconsole_cursor = qconsole_linelen;
+ break;
+ }
+ else if( key == VK_TAB )
+ {
+ field_t f;
+
+ Q_strncpyz( f.buffer, qconsole_line,
+ sizeof( f.buffer ) );
+ Field_AutoComplete( &f );
+ Q_strncpyz( qconsole_line, f.buffer,
+ sizeof( qconsole_line ) );
+ qconsole_linelen = strlen( qconsole_line );
+ qconsole_cursor = qconsole_linelen;
+ break;
+ }
+
+ if( qconsole_linelen < sizeof( qconsole_line ) - 1 )
+ {
+ char c = buff[ i ].Event.KeyEvent.uChar.AsciiChar;
+
+ if( key == VK_BACK )
+ {
+ if ( qconsole_cursor > 0 )
+ {
+ int newlen = ( qconsole_linelen > 0 ) ? qconsole_linelen - 1 : 0;
+ if ( qconsole_cursor < qconsole_linelen )
+ {
+ memmove( qconsole_line + qconsole_cursor - 1,
+ qconsole_line + qconsole_cursor,
+ qconsole_linelen - qconsole_cursor );
+ }
+
+ qconsole_line[ newlen ] = '\0';
+ qconsole_linelen = newlen;
+ qconsole_cursor--;
+ }
+ }
+ else if( c )
+ {
+ if ( qconsole_linelen > qconsole_cursor )
+ {
+ memmove( qconsole_line + qconsole_cursor + 1,
+ qconsole_line + qconsole_cursor,
+ qconsole_linelen - qconsole_cursor );
+ }
+
+ qconsole_line[ qconsole_cursor++ ] = c;
+
+ qconsole_linelen++;
+ qconsole_line[ qconsole_linelen ] = '\0';
+ }
+ }
+ }
+
+ if( newlinepos < 0) {
+ CON_Show();
+ return NULL;
+ }
+
+ if( !qconsole_linelen )
+ {
+ CON_Show();
+ Com_Printf( "\n" );
+ return NULL;
+ }
+
+ qconsole_linelen = 0;
+ CON_Show();
+
+ CON_HistAdd();
+ Com_Printf( "%s\n", qconsole_line );
+
+ return qconsole_line;
+}
+
+/*
+=================
+CON_WindowsColorPrint
+
+Set text colors based on Q3 color codes
+=================
+*/
+void CON_WindowsColorPrint( const char *msg )
+{
+ static char buffer[ MAXPRINTMSG ];
+ int length = 0;
+
+ while( *msg )
+ {
+ qconsole_drawinput = ( *msg == '\n' );
+
+ if( Q_IsColorString( msg ) || *msg == '\n' )
+ {
+ // First empty the buffer
+ if( length > 0 )
+ {
+ buffer[ length ] = '\0';
+ fputs( buffer, stderr );
+ length = 0;
+ }
+
+ if( *msg == '\n' )
+ {
+ // Reset color and then add the newline
+ SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( COLOR_WHITE ) );
+ fputs( "\n", stderr );
+ msg++;
+ }
+ else
+ {
+ // Set the color
+ SetConsoleTextAttribute( qconsole_hout, CON_ColorCharToAttrib( *( msg + 1 ) ) );
+ msg += 2;
+ }
+ }
+ else
+ {
+ if( length >= MAXPRINTMSG - 1 )
+ break;
+
+ buffer[ length ] = *msg;
+ length++;
+ msg++;
+ }
+ }
+
+ // Empty anything still left in the buffer
+ if( length > 0 )
+ {
+ buffer[ length ] = '\0';
+ fputs( buffer, stderr );
+ }
+}
+
+/*
+==================
+CON_Print
+==================
+*/
+void CON_Print( const char *msg )
+{
+ CON_Hide( );
+
+ CON_WindowsColorPrint( msg );
+
+ CON_Show( );
+}
diff --git a/src/sys/dialog.h b/src/sys/dialog.h
new file mode 100644
index 0000000..b96368a
--- /dev/null
+++ b/src/sys/dialog.h
@@ -0,0 +1,40 @@
+// This file is part of Tremulous.
+// Copyright © 2016 Victor Roemer (blowfish) <victor@badsec.org>
+// Copyright (C) 2015-2019 GrangerHub
+//
+// 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 3 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, see <http://www.gnu.org/licenses/>.
+
+#ifndef SYS_DIALOG_H
+#define SYS_DIALOG_H
+
+enum dialogResult_t
+{
+ DR_YES = 0,
+ DR_NO = 1,
+ DR_OK = 0,
+ DR_CANCEL = 1
+};
+
+enum dialogType_t
+{
+ DT_INFO,
+ DT_WARNING,
+ DT_ERROR,
+ DT_YES_NO,
+ DT_OK_CANCEL
+};
+
+dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title );
+
+#endif
diff --git a/src/sys/sys_loadlib.h b/src/sys/sys_loadlib.h
new file mode 100644
index 0000000..10baebc
--- /dev/null
+++ b/src/sys/sys_loadlib.h
@@ -0,0 +1,57 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#ifndef _SYS_LOADLIB_H_
+#define _SYS_LOADLIB_H_
+
+#ifdef DEDICATED
+# ifdef _WIN32
+# include <windows.h>
+# define Sys_LoadLibrary(f) (void*)LoadLibrary(f)
+# define Sys_UnloadLibrary(h) FreeLibrary((HMODULE)h)
+# define Sys_LoadFunction(h,fn) (void*)GetProcAddress((HMODULE)h,fn)
+# define Sys_LibraryError() "unknown"
+# else
+# include <dlfcn.h>
+# define Sys_LoadLibrary(f) dlopen(f,RTLD_NOW)
+# define Sys_UnloadLibrary(h) dlclose(h)
+# define Sys_LoadFunction(h,fn) dlsym(h,fn)
+# define Sys_LibraryError() dlerror()
+# endif
+#else
+# ifdef USE_LOCAL_HEADERS
+# include "SDL.h"
+# include "SDL_loadso.h"
+# else
+# include <SDL.h>
+# include <SDL_loadso.h>
+# endif
+# define Sys_LoadLibrary(f) SDL_LoadObject(f)
+# define Sys_UnloadLibrary(h) SDL_UnloadObject(h)
+# define Sys_LoadFunction(h,fn) SDL_LoadFunction(h,fn)
+# define Sys_LibraryError() SDL_GetError()
+#endif
+
+void * QDECL Sys_LoadDll(const char *name, bool useSystemLib);
+
+#endif
diff --git a/src/sys/sys_local.h b/src/sys/sys_local.h
new file mode 100644
index 0000000..f309bfe
--- /dev/null
+++ b/src/sys/sys_local.h
@@ -0,0 +1,58 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#ifndef _SYS_LOCAL_H_
+#define _SYS_LOCAL_H_
+
+#include "sys_shared.h"
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+
+// Require a minimum version of SDL
+#define MINSDL_MAJOR 2
+#define MINSDL_MINOR 0
+#define MINSDL_PATCH 0
+
+// Console
+void CON_Shutdown( void );
+void CON_Init( void );
+char *CON_Input( void );
+void CON_Print( const char *message );
+
+unsigned int CON_LogSize( void );
+unsigned int CON_LogWrite( const char *in );
+unsigned int CON_LogRead( char *out, unsigned int outSize );
+
+void Sys_GLimpSafeInit( void );
+void Sys_GLimpInit( void );
+void Sys_PlatformInit( void );
+void Sys_PlatformExit( void );
+void Sys_SigHandler( int signal ) __attribute__ ((noreturn));
+void Sys_ErrorDialog( const char *error );
+void Sys_AnsiColorPrint( const char *msg );
+
+int Sys_PID( void );
+bool Sys_PIDIsRunning( int pid );
+
+#endif
diff --git a/src/sys/sys_main.cpp b/src/sys/sys_main.cpp
new file mode 100644
index 0000000..efc1ecb
--- /dev/null
+++ b/src/sys/sys_main.cpp
@@ -0,0 +1,798 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "sys_local.h"
+
+#include <setjmp.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <csignal>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cstring>
+#include <iostream>
+
+#include "lua.hpp"
+#include "sol.hpp"
+#ifndef DEDICATED
+#ifdef USE_LOCAL_HEADERS
+# include "SDL.h"
+# include "SDL_cpuinfo.h"
+#else
+# include <SDL.h>
+# include <SDL_cpuinfo.h>
+#endif
+#endif
+
+#include "qcommon/files.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+#include "qcommon/vm.h"
+#ifndef DEDICATED
+#include "script/bind.h"
+#include "script/client.h"
+#include "script/http_client.h"
+#endif
+#include "script/cmd.h"
+#include "script/cvar.h"
+#include "script/rapidjson.h"
+#include "script/nettle.h"
+
+#include "dialog.h"
+#include "sys_loadlib.h"
+
+sol::state lua;
+
+static char binaryPath[ MAX_OSPATH ] = { 0 };
+static char installPath[ MAX_OSPATH ] = { 0 };
+
+/*
+=================
+Sys_SetBinaryPath
+=================
+*/
+void Sys_SetBinaryPath(const char *path)
+{
+ Q_strncpyz(binaryPath, path, sizeof(binaryPath));
+}
+
+/*
+=================
+Sys_BinaryPath
+=================
+*/
+char *Sys_BinaryPath(void)
+{
+ return binaryPath;
+}
+
+/*
+=================
+Sys_SetDefaultInstallPath
+=================
+*/
+void Sys_SetDefaultInstallPath(const char *path)
+{
+ Q_strncpyz(installPath, path, sizeof(installPath));
+}
+
+/*
+=================
+Sys_DefaultInstallPath
+=================
+*/
+char *Sys_DefaultInstallPath(void)
+{
+ return installPath;
+}
+
+/*
+=================
+Sys_DefaultAppPath
+=================
+*/
+char *Sys_DefaultAppPath(void)
+{
+ return Sys_BinaryPath();
+}
+
+/*
+=================
+Sys_In_Restart_f
+
+Restart the input subsystem
+=================
+*/
+void Sys_In_Restart_f( void )
+{
+ IN_Restart( );
+}
+
+/*
+=================
+Sys_ConsoleInput
+
+Handle new console input
+=================
+*/
+char *Sys_ConsoleInput(void)
+{
+ return CON_Input( );
+}
+
+/*
+==================
+Sys_GetClipboardData
+==================
+*/
+char *Sys_GetClipboardData(void)
+{
+ char *data = NULL;
+#ifndef DEDICATED
+ char *cliptext;
+
+ if ( ( cliptext = SDL_GetClipboardText() ) != NULL ) {
+ if ( cliptext[0] != '\0' ) {
+ size_t bufsize = strlen( cliptext ) + 1;
+
+ data = (char*)Z_Malloc( bufsize );
+ Q_strncpyz( data, cliptext, bufsize );
+
+ // find first listed char and set to '\0'
+ strtok( data, "\n\r\b" );
+ }
+ SDL_free( cliptext );
+ }
+#endif
+ return data;
+}
+
+#ifdef DEDICATED
+# define PID_FILENAME PRODUCT_NAME "_server.pid"
+#else
+# define PID_FILENAME PRODUCT_NAME ".pid"
+#endif
+
+/*
+=================
+Sys_PIDFileName
+=================
+*/
+static std::string Sys_PIDFileName( void )
+{
+ const char *homePath = Cvar_VariableString( "fs_homepath" );
+ std::string pidfile;
+
+ if( *homePath != '\0' )
+ {
+ pidfile += homePath;
+ pidfile += "/";
+ pidfile += PID_FILENAME;
+ }
+
+ return pidfile;
+}
+
+/*
+=================
+Sys_WritePIDFile
+
+Return true if there is an existing stale PID file
+=================
+*/
+bool Sys_WritePIDFile( void )
+{
+ const char *pidFile = Sys_PIDFileName( ).c_str();
+ FILE *f;
+ bool stale = false;
+
+ if( pidFile == NULL )
+ return false;
+
+ // First, check if the pid file is already there
+ if( ( f = fopen( pidFile, "r" ) ) != NULL )
+ {
+ char pidBuffer[ 64 ] = { 0 };
+ int pid;
+
+ pid = fread( pidBuffer, sizeof( char ), sizeof( pidBuffer ) - 1, f );
+ fclose( f );
+
+ if(pid > 0)
+ {
+ pid = atoi( pidBuffer );
+ if( !Sys_PIDIsRunning( pid ) )
+ stale = true;
+ }
+ else
+ stale = true;
+ }
+
+ if( ( f = fopen( pidFile, "w" ) ) != NULL )
+ {
+ fprintf( f, "%d", Sys_PID( ) );
+ fclose( f );
+ }
+ else
+ Com_Printf( S_COLOR_YELLOW "Couldn't write %s.\n", pidFile );
+
+ return stale;
+}
+
+/*
+=================
+Sys_Exit
+
+Single exit point (regular exit or in case of error)
+=================
+*/
+static __attribute__ ((noreturn)) void Sys_Exit( int exitCode )
+{
+ CON_Shutdown( );
+
+#ifndef DEDICATED
+ SDL_Quit( );
+#endif
+
+ if( exitCode < 2 )
+ {
+ // Normal exit
+ const char *pidFile = Sys_PIDFileName( ).c_str();
+ if( pidFile != NULL )
+ remove( pidFile );
+ }
+
+ NET_Shutdown( );
+
+ Sys_PlatformExit( );
+
+ exit( exitCode );
+}
+
+/*
+=================
+Sys_Quit
+=================
+*/
+void Sys_Quit( void )
+{
+ Sys_Exit( 0 );
+}
+
+/*
+=================
+Sys_GetProcessorFeatures
+=================
+*/
+cpuFeatures_t Sys_GetProcessorFeatures( void )
+{
+ cpuFeatures_t features = CF_NONE;
+
+#ifndef DEDICATED
+ if( SDL_HasRDTSC( ) ) features |= CF_RDTSC;
+ if( SDL_Has3DNow( ) ) features |= CF_3DNOW;
+ if( SDL_HasMMX( ) ) features |= CF_MMX;
+ if( SDL_HasSSE( ) ) features |= CF_SSE;
+ if( SDL_HasSSE2( ) ) features |= CF_SSE2;
+ if( SDL_HasAltiVec( ) ) features |= CF_ALTIVEC;
+#endif
+
+ return features;
+}
+
+void Sys_Script_f( void )
+{
+ std::string args = Cmd_Args();
+ lua.script(args);
+}
+
+void Sys_ScriptFile_f( void )
+{
+ std::string args = Cmd_Args();
+ lua.script_file(args);
+}
+/*
+=================
+Sys_Init
+=================
+*/
+void Sys_Init(void)
+{
+ Cmd_AddCommand( "in_restart", Sys_In_Restart_f );
+ Cmd_AddCommand( "script", Sys_Script_f );
+ Cmd_AddCommand( "script_file", Sys_ScriptFile_f );
+ Cvar_Set( "arch", OS_STRING " " ARCH_STRING );
+ Cvar_Set( "username", "UnnamedPlayer" );
+}
+
+/*
+=================
+Sys_AnsiColorPrint
+Transform Q3 colour codes to ANSI escape sequences
+=================
+*/
+// FIXME -bbq This could be more extensible
+void Sys_AnsiColorPrint( const char *msg )
+{
+ static char buffer[ MAXPRINTMSG ];
+ int length = 0;
+ static int q3ToAnsi[ 8 ] =
+ {
+ 7, // COLOR_BLACK
+ 31, // COLOR_RED
+ 32, // COLOR_GREEN
+ 33, // COLOR_YELLOW
+ 34, // COLOR_BLUE
+ 36, // COLOR_CYAN
+ 35, // COLOR_MAGENTA
+ 0 // COLOR_WHITE
+ };
+
+ while( *msg )
+ {
+ if( Q_IsColorString( msg ) || *msg == '\n' )
+ {
+ // First empty the buffer
+ if( length > 0 )
+ {
+ buffer[ length ] = '\0';
+ fputs( buffer, stderr );
+ length = 0;
+ }
+
+ if( *msg == '\n' )
+ {
+ // Issue a reset and then the newline
+ fputs( "\033[0m\n", stderr );
+ msg++;
+ }
+ else
+ {
+ // Print the color code (reset first to clear potential inverse (black))
+ Com_sprintf( buffer, sizeof( buffer ), "\033[0m\033[%dm",
+ q3ToAnsi[ ColorIndex( *( msg + 1 ) ) ] );
+ fputs( buffer, stderr );
+ msg += 2;
+ }
+ }
+ else
+ {
+ if( length >= MAXPRINTMSG - 1 )
+ break;
+
+ buffer[ length ] = *msg;
+ length++;
+ msg++;
+ }
+ }
+
+ // Empty anything still left in the buffer
+ if( length > 0 )
+ {
+ buffer[ length ] = '\0';
+ fputs( buffer, stderr );
+ }
+}
+
+/*
+=================
+Sys_Print
+=================
+*/
+void Sys_Print( const char *msg )
+{
+ CON_LogWrite( msg );
+ CON_Print( msg );
+}
+
+/*
+=================
+Sys_Error
+=================
+*/
+void Sys_Error( const char *error, ... )
+{
+ va_list argptr;
+ char string[1024];
+
+ va_start (argptr,error);
+ Q_vsnprintf (string, sizeof(string), error, argptr);
+ va_end (argptr);
+
+ Sys_ErrorDialog( string );
+
+ Sys_Exit( 3 );
+}
+
+/*
+============
+Sys_FileTime
+
+returns -1 if not present
+============
+*/
+int Sys_FileTime( char *path )
+{
+ struct stat buf;
+
+ if (stat (path,&buf) == -1)
+ return -1;
+
+ return buf.st_mtime;
+}
+
+/*
+=================
+Sys_UnloadDll
+=================
+*/
+void Sys_UnloadDll( void *dllHandle )
+{
+ if( !dllHandle )
+ {
+ Com_Printf("Sys_UnloadDll(NULL)\n");
+ return;
+ }
+
+ Sys_UnloadLibrary(dllHandle);
+}
+
+/*
+=================
+Sys_LoadDll
+
+First try to load library name from system library path,
+from executable path, then fs_basepath.
+=================
+*/
+void *Sys_LoadDll(const char *name, bool useSystemLib)
+{
+ void *dllhandle;
+
+ if (!Sys_DllExtension(name))
+ {
+ Com_Printf("Refusing to load library \"%s\": Extension not allowed.\n", name);
+ return nullptr;
+ }
+
+ if(useSystemLib)
+ Com_Printf("Trying to load \"%s\"...\n", name);
+
+ if(!useSystemLib || !(dllhandle = Sys_LoadLibrary(name)))
+ {
+ const char *topDir;
+ char libPath[MAX_OSPATH];
+
+ topDir = Sys_BinaryPath();
+
+ if(!*topDir)
+ topDir = ".";
+
+ Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, topDir);
+
+ int len = Com_sprintf(libPath, sizeof(libPath), "%s%c%s", topDir, PATH_SEP, name);
+ if(len < sizeof(libPath))
+ {
+ Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, topDir);
+ dllhandle = Sys_LoadLibrary(libPath);
+ }
+ else
+ {
+ Com_Printf("Skipping trying to load \"%s\" from \"%s\", file name is too long.\n", name, topDir);
+ }
+
+ if (!dllhandle)
+ {
+ const char *basePath = Cvar_VariableString("fs_basepath");
+
+ if(!basePath || !*basePath)
+ basePath = ".";
+
+ if(FS_FilenameCompare(topDir, basePath))
+ {
+ Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, basePath);
+ len = Com_sprintf(libPath, sizeof(libPath), "%s%c%s", basePath, PATH_SEP, name);
+ if(len < sizeof(libPath))
+ {
+ Com_Printf("Trying to load \"%s\" from \"%s\"...\n", name, basePath);
+ dllhandle = Sys_LoadLibrary(libPath);
+ }
+ else
+ {
+ Com_Printf("Skipping trying to load \"%s\" from \"%s\", file name is too long.\n", name, basePath);
+ }
+ }
+
+ if(!dllhandle)
+ Com_Printf("Loading \"%s\" failed\n", name);
+ }
+ }
+
+ return dllhandle;
+}
+
+/*
+=================
+Sys_LoadGameDll
+
+Used to load a development dll instead of a virtual machine
+=================
+*/
+using Entry = void (*)(intptr_t (*syscallptr)(intptr_t, ...));
+using EntryPoint = intptr_t (QDECL *)(int, ...);
+using SysCalls = intptr_t (*)(intptr_t, ...);
+
+void *Sys_LoadGameDll(const char *name, EntryPoint* entryPoint, SysCalls systemcalls)
+{
+ void *libHandle;
+
+ assert(name);
+
+ if (!Sys_DllExtension(name))
+ {
+ Com_Printf("Refusing to load library \"%s\": Extension not allowed.\n", name);
+ return nullptr;
+ }
+
+ Com_Printf( "Loading DLL file: %s\n", name);
+ libHandle = Sys_LoadLibrary(name);
+
+ if(!libHandle)
+ {
+ Com_Printf("Sys_LoadGameDll(%s) failed:\n\"%s\"\n", name, Sys_LibraryError());
+ return NULL;
+ }
+
+ Entry entry = (Entry)Sys_LoadFunction( libHandle, "dllEntry" );
+ *entryPoint = (EntryPoint)Sys_LoadFunction( libHandle, "vmMain" );
+
+ if ( !*entryPoint || !entry )
+ {
+ Com_Printf ( "Sys_LoadGameDll(%s) failed to find vmMain function:\n\"%s\" !\n", name, Sys_LibraryError( ) );
+ Sys_UnloadLibrary(libHandle);
+ return NULL;
+ }
+
+ Com_Printf ( "Sys_LoadGameDll(%s) found vmMain function at %p\n", name, *entryPoint );
+ entry( systemcalls );
+
+ return libHandle;
+}
+
+/*
+=================
+Sys_ParseArgs
+=================
+*/
+void Sys_ParseArgs( int argc, char **argv )
+{
+ if( argc == 2 )
+ {
+ if( !strcmp( argv[1], "--version" ) ||
+ !strcmp( argv[1], "-v" ) )
+ {
+ const char* date = __DATE__;
+#ifdef DEDICATED
+ fprintf( stdout, Q3_VERSION " dedicated server (%s)\n", date );
+#else
+ fprintf( stdout, Q3_VERSION " client (%s)\n", date );
+#endif
+ Sys_Exit( 0 );
+ }
+ }
+}
+
+/*
+=================
+Sys_SigHandler
+=================
+*/
+void Sys_SigHandler( int signal )
+{
+ static bool signalcaught = false;
+
+ if( signalcaught )
+ {
+ std::cerr << "DOUBLE SIGNAL FAULT: Received signal "
+ << signal << std::endl;
+ }
+ else
+ {
+ char const* msg = va("Received signal %d", signal);
+
+ signalcaught = true;
+ VM_Forced_Unload_Start();
+#ifndef DEDICATED
+ CL_Shutdown(va("Received signal %d", signal), true, true);
+#endif
+ SV_Shutdown(msg);
+ VM_Forced_Unload_Done();
+ }
+
+ if( signal == SIGTERM || signal == SIGINT )
+ Sys_Exit( 1 );
+
+ Sys_Exit( 2 );
+}
+
+#ifndef DEFAULT_BASEDIR
+# ifdef __APPLE__
+# define DEFAULT_BASEDIR Sys_StripAppBundle(Sys_BinaryPath())
+# else
+# define DEFAULT_BASEDIR Sys_BinaryPath()
+# endif
+#endif
+
+#ifdef __APPLE__
+/*
+=================
+Sys_StripAppBundle
+
+Discovers if passed dir is suffixed with the directory structure of a Mac OS X
+.app bundle. If it is, the .app directory structure is stripped off the end and
+the result is returned. If not, dir is returned untouched.
+=================
+*/
+const char *Sys_StripAppBundle( const char *dir )
+{
+ static char cwd[MAX_OSPATH];
+
+ Q_strncpyz(cwd, dir, sizeof(cwd));
+ if(strcmp(Sys_Basename(cwd), "MacOS"))
+ return dir;
+ Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd));
+ if(strcmp(Sys_Basename(cwd), "Contents"))
+ return dir;
+ Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd));
+ if(!strstr(Sys_Basename(cwd), ".app"))
+ return dir;
+ Q_strncpyz(cwd, Sys_Dirname(cwd), sizeof(cwd));
+ return cwd;
+}
+#endif
+
+#ifndef DEDICATED
+
+void SDLVersionCheck()
+{
+#if !SDL_VERSION_ATLEAST(MINSDL_MAJOR,MINSDL_MINOR,MINSDL_PATCH)
+#error A more recent version of SDL is required
+#endif
+ SDL_version ver;
+ SDL_GetVersion( &ver );
+#define MINSDL_VERSION XSTRING(MINSDL_MAJOR) "." \
+ XSTRING(MINSDL_MINOR) "." \
+ XSTRING(MINSDL_PATCH)
+ if( SDL_VERSIONNUM(ver.major, ver.minor, ver.patch)
+ < SDL_VERSIONNUM(MINSDL_MAJOR, MINSDL_MINOR, MINSDL_PATCH) )
+ {
+ Sys_Dialog( DT_ERROR, va( "SDL version " MINSDL_VERSION " or greater is required, "
+ "but only version %d.%d.%d was found. You may be able to obtain a more recent copy "
+ "from http://www.libsdl.org/.", ver.major, ver.minor, ver.patch ), "SDL Library Too Old" );
+ Sys_Exit( 1 );
+ }
+}
+#endif
+
+
+/*
+=================
+main
+=================
+*/
+int main( int argc, char **argv )
+{
+#ifndef DEDICATED
+ SDLVersionCheck();
+#endif
+ Sys_PlatformInit( );
+
+ // Set the initial time base
+ Sys_Milliseconds( );
+
+#ifdef __APPLE__
+ // This is passed if we are launched by double-clicking
+ if ( argc >= 2 )
+ if ( Q_strncmp( argv[1], "-psn", 4 ) == 0 )
+ argc = 1;
+#endif
+
+ Sys_ParseArgs( argc, argv );
+ Sys_SetBinaryPath( Sys_Dirname( argv[ 0 ] ) );
+ Sys_SetDefaultInstallPath( DEFAULT_BASEDIR );
+
+ // Concatenate the command line for passing to Com_Init
+ char args[MAX_STRING_CHARS];
+ args[0] = '\0';
+
+ for( int i = 1; i < argc; i++ )
+ {
+ const bool ws = strchr(argv[i], ' ') ? true : false;
+
+ if (ws) Q_strcat(args, sizeof(args), "\"");
+ Q_strcat(args, sizeof(args), argv[i]);
+ if (ws) Q_strcat(args, sizeof(args), "\"");
+ Q_strcat(args, sizeof(args), " " );
+ }
+
+ CON_Init( );
+ Com_Init( args );
+ NET_Init( );
+
+ lua.open_libraries
+ (
+ sol::lib::base,
+ sol::lib::package,
+#if !defined(SOL_LUAJIT) // Not with LuaJIT.
+ sol::lib::coroutine,
+#endif
+ sol::lib::string,
+ sol::lib::table,
+ sol::lib::math,
+ sol::lib::bit32,
+ sol::lib::io,
+ sol::lib::os,
+ sol::lib::debug,
+ sol::lib::utf8 // Only with Lua 5.3; ommiting ifdef on purpose. -bbq
+#if defined(SOL_LUAJIT) // Only with LuaJIT.
+ ,sol::lib::ffi,
+ sol::lib::jit
+#endif
+ );
+
+ script::cvar::init(std::move(lua));
+ script::cmd::init(std::move(lua));
+ script::rapidjson::init(std::move(lua));
+ script::nettle::init(std::move(lua));
+
+#ifndef DEDICATED
+ script::client::init(std::move(lua));
+ script::keybind::init(std::move(lua));
+ script::http_client::init(std::move(lua));
+#endif
+
+ for ( ;; )
+ {
+ try
+ {
+ Com_Frame( );
+ }
+ catch (sol::error& e)
+ {
+ Com_Printf(S_COLOR_YELLOW "%s\n", e.what());
+ }
+ }
+
+ return 0;
+}
diff --git a/src/sys/sys_osx.mm b/src/sys/sys_osx.mm
new file mode 100644
index 0000000..36caa40
--- /dev/null
+++ b/src/sys/sys_osx.mm
@@ -0,0 +1,103 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef __APPLE__
+#error This file is for Mac OS X only. You probably should not compile it.
+#endif
+
+// Please note that this file is just some Mac-specific bits. Most of the
+// Mac OS X code is shared with other Unix platforms in sys_unix.c ...
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+#include "dialog.h"
+#include "sys_local.h"
+
+//#import <AppKit/AppKit.h>
+#import <Foundation/Foundation.h>
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+//
+//#import <AppKit/AppKitDefines.h>
+//#import <Foundation/NSObject.h>
+//#import <Foundation/NSArray.h>
+//#import <Foundation/NSDictionary.h>
+//#import <AppKit/NSAlert.h>
+
+/*
+==============
+Sys_Dialog
+
+Display an OS X dialog box
+==============
+*/
+dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title )
+{
+ dialogResult_t result = DR_OK;
+ NSAlert *alert = [NSAlert new];
+
+ [alert setMessageText: [NSString stringWithUTF8String: title]];
+ [alert setInformativeText: [NSString stringWithUTF8String: message]];
+
+ if( type == DT_ERROR )
+ [alert setAlertStyle: NSCriticalAlertStyle];
+ else
+ [alert setAlertStyle: NSWarningAlertStyle];
+
+ switch( type )
+ {
+ default:
+ [alert runModal];
+ result = DR_OK;
+ break;
+
+ case DT_YES_NO:
+ [alert addButtonWithTitle: @"Yes"];
+ [alert addButtonWithTitle: @"No"];
+ switch( [alert runModal] )
+ {
+ default:
+ case NSAlertFirstButtonReturn: result = DR_YES; break;
+ case NSAlertSecondButtonReturn: result = DR_NO; break;
+ }
+ break;
+
+ case DT_OK_CANCEL:
+ [alert addButtonWithTitle: @"OK"];
+ [alert addButtonWithTitle: @"Cancel"];
+
+ switch( [alert runModal] )
+ {
+ default:
+ case NSAlertFirstButtonReturn: result = DR_OK; break;
+ case NSAlertSecondButtonReturn: result = DR_CANCEL; break;
+ }
+ break;
+ }
+
+ [alert release];
+
+ return result;
+}
diff --git a/src/sys/sys_shared.h b/src/sys/sys_shared.h
new file mode 100644
index 0000000..283ae3a
--- /dev/null
+++ b/src/sys/sys_shared.h
@@ -0,0 +1,111 @@
+/*
+
+ This File is part of Tremulous.
+ Copyright (C) 2016, wtfbbqhax <victor@badsec.org>.
+ Copyright (C) 2015-2019, GrangerHub <grangerhub.com>.
+
+*/
+
+#ifndef SYS_SHARED_H
+#define SYS_SHARED_H 1
+
+#include <stdio.h>
+
+#include "qcommon/qcommon.h"
+#include "qcommon/net.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX_JOYSTICK_AXIS 16
+
+typedef int cpuFeatures_t;
+enum CPU_FEATURES {
+ CF_NONE = 0,
+ CF_RDTSC = 1 << 0,
+ CF_MMX = 1 << 1,
+ CF_MMX_EXT = 1 << 2,
+ CF_3DNOW = 1 << 3,
+ CF_3DNOW_EXT = 1 << 4,
+ CF_SSE = 1 << 5,
+ CF_SSE2 = 1 << 6,
+ CF_ALTIVEC = 1 << 7
+};
+
+struct netadr_t;
+enum netadrtype_t;
+
+void Sys_Init(void);
+
+// general development dll loading for virtual machine testing
+void *QDECL Sys_LoadGameDll(const char *name,
+ intptr_t(QDECL **entryPoint)(int, ...),
+ intptr_t(QDECL *systemcalls)(intptr_t, ...));
+
+void Sys_UnloadDll(void *dllHandle);
+bool Sys_DllExtension(const char *name);
+
+void QDECL Sys_Error(const char *error, ...) __attribute__((noreturn, format(printf, 1, 2)));
+void Sys_Quit(void) __attribute__((noreturn));
+
+char *Sys_GetClipboardData(void); // note that this isn't journaled...
+
+void Sys_Print(const char *msg);
+
+// Sys_Milliseconds should only be used for profiling purposes,
+// any game related timing information should come from event timestamps
+int Sys_Milliseconds(void);
+
+bool Sys_RandomBytes(byte *string, int len);
+
+void Sys_CryptoRandomBytes(byte *string, int len);
+
+// the system console is shown when a dedicated server is running
+void Sys_DisplaySystemConsole(bool show);
+
+cpuFeatures_t Sys_GetProcessorFeatures(void);
+
+void Sys_SetErrorText(const char *text);
+
+FILE *Sys_FOpen(const char *ospath, const char *mode);
+bool Sys_Mkdir(const char *path);
+FILE *Sys_Mkfifo(const char *ospath);
+bool Sys_OpenWithDefault( const char *path );
+char *Sys_Cwd(void);
+void Sys_SetDefaultInstallPath(const char *path);
+char *Sys_DefaultInstallPath(void);
+
+#ifdef __APPLE__
+char *Sys_DefaultAppPath(void);
+#endif
+
+void Sys_SetDefaultHomePath(const char *path);
+char *Sys_DefaultHomePath(void);
+const char *Sys_Dirname(char *path);
+const char *Sys_Basename(char *path);
+char *Sys_ConsoleInput(void);
+
+char **Sys_ListFiles(const char *directory, const char *extension,
+ const char *filter,
+ int *numfiles, bool wantsubs);
+void Sys_FreeFileList(char **list);
+void Sys_Sleep(int msec);
+
+bool Sys_LowPhysicalMemory(void);
+
+void Sys_SetEnv(const char *name, const char *value);
+
+bool Sys_WritePIDFile(void);
+
+// Input subsystem
+void IN_Init( void *windowData );
+void IN_Frame( void );
+void IN_Shutdown( void );
+void IN_Restart( void );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/sys/sys_unix.cpp b/src/sys/sys_unix.cpp
new file mode 100644
index 0000000..e55655c
--- /dev/null
+++ b/src/sys/sys_unix.cpp
@@ -0,0 +1,1006 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "qcommon/cvar.h"
+#include "qcommon/files.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+#include "dialog.h"
+#include "sys_local.h"
+
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <pwd.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <fenv.h>
+#include <sys/wait.h>
+
+bool stdinIsATTY;
+
+// Used to determine where to store user-specific files
+static char homePath[ MAX_OSPATH ] = { 0 };
+
+/*
+==================
+Sys_DefaultHomePath
+==================
+*/
+char *Sys_DefaultHomePath(void)
+{
+ char *p;
+
+ if( !*homePath && com_homepath != NULL )
+ {
+ if( ( p = getenv( "HOME" ) ) != NULL )
+ {
+ Com_sprintf(homePath, sizeof(homePath), "%s%c", p, PATH_SEP);
+#ifdef __APPLE__
+ Q_strcat(homePath, sizeof(homePath),
+ "Library/Application Support/");
+
+ if(com_homepath->string[0])
+ Q_strcat(homePath, sizeof(homePath), com_homepath->string);
+ else
+ Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_MACOSX);
+#else
+ if(com_homepath->string[0])
+ Q_strcat(homePath, sizeof(homePath), com_homepath->string);
+ else
+ Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_UNIX);
+#endif
+ }
+ }
+
+ return homePath;
+}
+
+/*
+================
+Sys_Milliseconds
+================
+*/
+/* base time in seconds, that's our origin
+ timeval:tv_sec is an int:
+ assuming this wraps every 0x7fffffff - ~68 years since the Epoch (1970) - we're safe till 2038 */
+unsigned long sys_timeBase = 0;
+/* current time in ms, using sys_timeBase as origin
+ NOTE: sys_timeBase*1000 + curtime -> ms since the Epoch
+ 0x7fffffff ms - ~24 days
+ although timeval:tv_usec is an int, I'm not sure wether it is actually used as an unsigned int
+ (which would affect the wrap period) */
+int curtime;
+int Sys_Milliseconds (void)
+{
+ struct timeval tp;
+
+ gettimeofday(&tp, NULL);
+
+ if (!sys_timeBase)
+ {
+ sys_timeBase = tp.tv_sec;
+ return tp.tv_usec/1000;
+ }
+
+ curtime = (tp.tv_sec - sys_timeBase)*1000 + tp.tv_usec/1000;
+
+ return curtime;
+}
+
+/*
+==================
+Sys_RandomBytes
+==================
+*/
+bool Sys_RandomBytes( byte *string, int len )
+{
+ FILE *fp;
+
+ fp = fopen( "/dev/urandom", "r" );
+ if( !fp )
+ return false;
+
+ setvbuf( fp, NULL, _IONBF, 0 ); // don't buffer reads from /dev/urandom
+
+ if( fread( string, sizeof( byte ), len, fp ) != len )
+ {
+ fclose( fp );
+ return false;
+ }
+
+ fclose( fp );
+ return true;
+}
+
+/*
+==================
+Sys_GetCurrentUser
+==================
+*/
+const char *Sys_GetCurrentUser( void )
+{
+ struct passwd *p;
+
+ if ( (p = getpwuid( getuid() )) == NULL ) {
+ return "player";
+ }
+ return p->pw_name;
+}
+
+/*
+==================
+Sys_CryptoRandomBytes
+==================
+*/
+void Sys_CryptoRandomBytes( byte *string, int len )
+{
+ if ( !Sys_RandomBytes( string, len ) )
+ Com_Error( ERR_FATAL, "Sys_CryptoRandomBytes: error reading /dev/urandom" );
+}
+
+#define MEM_THRESHOLD 96*1024*1024
+
+/*
+==================
+Sys_LowPhysicalMemory
+
+TODO
+==================
+*/
+bool Sys_LowPhysicalMemory( void )
+{
+ return false;
+}
+
+/*
+==================
+Sys_Basename
+==================
+*/
+const char *Sys_Basename( char *path )
+{
+ return basename( path );
+}
+
+/*
+==================
+Sys_Dirname
+==================
+*/
+const char *Sys_Dirname( char* path )
+{
+ return dirname( path );
+}
+
+/*
+==============
+Sys_FOpen
+==============
+*/
+FILE *Sys_FOpen( const char *ospath, const char *mode ) {
+ struct stat buf;
+
+ // check if path exists and is a directory
+ if ( !stat( ospath, &buf ) && S_ISDIR( buf.st_mode ) )
+ return NULL;
+
+ return fopen( ospath, mode );
+}
+
+/*
+==================
+Sys_Mkdir
+==================
+*/
+bool Sys_Mkdir( const char *path )
+{
+ int result = mkdir( path, 0750 );
+
+ if( result != 0 )
+ return (bool)(errno == EEXIST);
+
+ return true;
+}
+
+/*
+==================
+Sys_Mkfifo
+==================
+*/
+FILE *Sys_Mkfifo( const char *ospath )
+{
+ FILE *fifo;
+ int result;
+ int fn;
+ struct stat buf;
+
+ // if file already exists AND is a pipefile, remove it
+ if( !stat( ospath, &buf ) && S_ISFIFO( buf.st_mode ) )
+ FS_Remove( ospath );
+
+ result = mkfifo( ospath, 0600 );
+ if( result != 0 )
+ return NULL;
+
+ fifo = fopen( ospath, "w+" );
+ if( fifo )
+ {
+ fn = fileno( fifo );
+ fcntl( fn, F_SETFL, O_NONBLOCK );
+ }
+
+ return fifo;
+}
+
+/*
+==============
+Sys_OpenWithDefault
+
+Opens a path with the default application
+==============
+*/
+bool Sys_OpenWithDefault( const char *path )
+{
+ int status;
+ int exitNum;
+ pid_t pid;
+
+ Com_Printf( S_COLOR_WHITE "Sys_OpenWithDefault: opening %s .....\n",
+ path );
+
+ // attempt to start child process
+ pid = fork();
+
+ if( pid < 0 )
+ {
+ // failed to start the child process
+ Com_Printf( S_COLOR_RED "Sys_OpenWithDefault: %s\n" S_COLOR_WHITE,
+ strerror( exitNum ) );
+ return false;
+ }
+ else if ( pid == 0 )
+ {
+ //child proccess
+ char *argv[3];
+ char tempPath[MAX_OSPATH];
+ char openCmd[MAX_OSPATH];
+
+ ::memset( tempPath, 0, sizeof( tempPath ) );
+ ::memset( openCmd, 0, sizeof( openCmd ) );
+
+ Q_strcat( tempPath, sizeof(tempPath), path );
+
+ argv[1] = tempPath;
+ argv[2] = NULL;
+
+#ifdef __APPLE__
+ Q_strcat( openCmd, sizeof(openCmd), "open");
+#else
+ Q_strcat( openCmd, sizeof(openCmd), "xdg-open");
+#endif
+
+ argv[0] = openCmd;
+
+ // attempt to open the path
+ if( execvp( argv[0], argv ) < 0 )
+ {
+ //failure
+ exit( errno );
+ }
+
+ //success
+ exit(0);
+ }
+
+ wait( &status );
+ exitNum = WEXITSTATUS( status );
+
+ if( !exitNum )
+ {
+ return true;
+ }
+ else
+ {
+ Com_Printf( S_COLOR_RED "Sys_OpenWithDefault: %s\n" S_COLOR_WHITE, strerror( exitNum ) );
+ return false;
+ }
+}
+
+/*
+==================
+Sys_Cwd
+==================
+*/
+char *Sys_Cwd( void )
+{
+ static char cwd[MAX_OSPATH];
+
+ char *result = getcwd( cwd, sizeof( cwd ) - 1 );
+ if( result != cwd )
+ return NULL;
+
+ cwd[MAX_OSPATH-1] = 0;
+
+ return cwd;
+}
+
+/*
+==============================================================
+
+DIRECTORY SCANNING
+
+==============================================================
+*/
+
+#define MAX_FOUND_FILES 0x1000
+
+/*
+==================
+Sys_ListFilteredFiles
+==================
+*/
+void Sys_ListFilteredFiles( const char *basedir, const char *subdirs,
+ const char *filter, char **list, int *numfiles )
+{
+ char search[MAX_OSPATH], newsubdirs[MAX_OSPATH];
+ char filename[MAX_OSPATH];
+ DIR *fdir;
+ struct dirent *d;
+ struct stat st;
+
+ if ( *numfiles >= MAX_FOUND_FILES - 1 ) {
+ return;
+ }
+
+ if (strlen(subdirs)) {
+ Com_sprintf( search, sizeof(search), "%s/%s", basedir, subdirs );
+ }
+ else {
+ Com_sprintf( search, sizeof(search), "%s", basedir );
+ }
+
+ if ((fdir = opendir(search)) == NULL) {
+ return;
+ }
+
+ while ((d = readdir(fdir)) != NULL) {
+ Com_sprintf(filename, sizeof(filename), "%s/%s", search, d->d_name);
+ if (stat(filename, &st) == -1)
+ continue;
+
+ if (st.st_mode & S_IFDIR) {
+ if (Q_stricmp(d->d_name, ".") && Q_stricmp(d->d_name, "..")) {
+ if (strlen(subdirs)) {
+ Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s/%s", subdirs, d->d_name);
+ }
+ else {
+ Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", d->d_name);
+ }
+ Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles );
+ }
+ }
+ if ( *numfiles >= MAX_FOUND_FILES - 1 ) {
+ break;
+ }
+ Com_sprintf( filename, sizeof(filename), "%s/%s", subdirs, d->d_name );
+ if (!Com_FilterPath( filter, filename, false ))
+ continue;
+ list[ *numfiles ] = CopyString( filename );
+ (*numfiles)++;
+ }
+
+ closedir(fdir);
+}
+
+/*
+==================
+Sys_ListFiles
+==================
+*/
+char **Sys_ListFiles( const char *directory, const char *extension,
+ const char *filter, int *numfiles, bool wantsubs )
+{
+ struct dirent *d;
+ DIR *fdir;
+ bool dironly = wantsubs;
+ char search[MAX_OSPATH];
+ int nfiles;
+ char **listCopy;
+ char *list[MAX_FOUND_FILES];
+ int i;
+ struct stat st;
+
+ int extLen;
+
+ if (filter) {
+
+ nfiles = 0;
+ Sys_ListFilteredFiles( directory, "", filter, list, &nfiles );
+
+ list[ nfiles ] = NULL;
+ *numfiles = nfiles;
+
+ if (!nfiles)
+ return NULL;
+
+ listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = NULL;
+
+ return listCopy;
+ }
+
+ if ( !extension)
+ extension = "";
+
+ if ( extension[0] == '/' && extension[1] == 0 ) {
+ extension = "";
+ dironly = true;
+ }
+
+ extLen = strlen( extension );
+
+ // search
+ nfiles = 0;
+
+ if ((fdir = opendir(directory)) == NULL) {
+ *numfiles = 0;
+ return NULL;
+ }
+
+ while ((d = readdir(fdir)) != NULL) {
+ Com_sprintf(search, sizeof(search), "%s/%s", directory, d->d_name);
+ if (stat(search, &st) == -1)
+ continue;
+ if ((dironly && !(st.st_mode & S_IFDIR)) ||
+ (!dironly && (st.st_mode & S_IFDIR)))
+ continue;
+
+ if (*extension) {
+ if ( strlen( d->d_name ) < extLen ||
+ Q_stricmp(
+ d->d_name + strlen( d->d_name ) - extLen,
+ extension ) ) {
+ continue; // didn't match
+ }
+ }
+
+ if ( nfiles == MAX_FOUND_FILES - 1 )
+ break;
+ list[ nfiles ] = CopyString( d->d_name );
+ nfiles++;
+ }
+
+ list[ nfiles ] = NULL;
+
+ closedir(fdir);
+
+ // return a copy of the list
+ *numfiles = nfiles;
+
+ if ( !nfiles ) {
+ return NULL;
+ }
+
+ listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = NULL;
+
+ return listCopy;
+}
+
+/*
+==================
+Sys_FreeFileList
+==================
+*/
+void Sys_FreeFileList( char **list )
+{
+ int i;
+
+ if ( !list ) {
+ return;
+ }
+
+ for ( i = 0 ; list[i] ; i++ ) {
+ Z_Free( list[i] );
+ }
+
+ Z_Free( list );
+}
+
+/*
+==================
+Sys_Sleep
+
+Block execution for msec or until input is recieved.
+==================
+*/
+void Sys_Sleep( int msec )
+{
+ if( msec == 0 )
+ return;
+
+ if( stdinIsATTY )
+ {
+ fd_set fdset;
+
+ FD_ZERO(&fdset);
+ FD_SET(STDIN_FILENO, &fdset);
+ if( msec < 0 )
+ {
+ select(STDIN_FILENO + 1, &fdset, NULL, NULL, NULL);
+ }
+ else
+ {
+ struct timeval timeout;
+
+ timeout.tv_sec = msec/1000;
+ timeout.tv_usec = (msec%1000)*1000;
+ select(STDIN_FILENO + 1, &fdset, NULL, NULL, &timeout);
+ }
+ }
+ else
+ {
+ // With nothing to select() on, we can't wait indefinitely
+ if( msec < 0 )
+ msec = 10;
+
+ usleep( msec * 1000 );
+ }
+}
+
+/*
+==============
+Sys_ErrorDialog
+
+Display an error message
+==============
+*/
+void Sys_ErrorDialog( const char *error )
+{
+ char buffer[ 1024 ];
+ unsigned int size;
+ int f = -1;
+ const char *homepath = Cvar_VariableString( "fs_homepath" );
+ const char *gamedir = Cvar_VariableString( "fs_game" );
+ const char *fileName = "crashlog.txt";
+ char *dirpath = FS_BuildOSPath( homepath, gamedir, "");
+ char *ospath = FS_BuildOSPath( homepath, gamedir, fileName );
+
+ Sys_Print( va( "%s\n", error ) );
+
+#ifndef DEDICATED
+ Sys_Dialog( DT_ERROR, va( "%s. See \"%s\" for details.", error, ospath ), "Error" );
+#endif
+
+ // Make sure the write path for the crashlog exists...
+
+ if(!Sys_Mkdir(homepath))
+ {
+ Com_Printf("ERROR: couldn't create path '%s' for crash log.\n", homepath);
+ return;
+ }
+
+ if(!Sys_Mkdir(dirpath))
+ {
+ Com_Printf("ERROR: couldn't create path '%s' for crash log.\n", dirpath);
+ return;
+ }
+
+ // We might be crashing because we maxed out the Quake MAX_FILE_HANDLES,
+ // which will come through here, so we don't want to recurse forever by
+ // calling FS_FOpenFileWrite()...use the Unix system APIs instead.
+ f = open( ospath, O_CREAT | O_TRUNC | O_WRONLY, 0640 );
+ if( f == -1 )
+ {
+ Com_Printf( "ERROR: couldn't open %s\n", fileName );
+ return;
+ }
+
+ // We're crashing, so we don't care much if write() or close() fails.
+ while( ( size = CON_LogRead( buffer, sizeof( buffer ) ) ) > 0 ) {
+ if( write( f, buffer, size ) != size ) {
+ Com_Printf( "ERROR: couldn't fully write to %s\n", fileName );
+ break;
+ }
+ }
+
+ close( f );
+}
+
+#ifndef __APPLE__
+static char execBuffer[ 1024 ];
+static char *execBufferPointer;
+static char *execArgv[ 16 ];
+static int execArgc;
+
+/*
+==============
+Sys_ClearExecBuffer
+==============
+*/
+static void Sys_ClearExecBuffer( void )
+{
+ execBufferPointer = execBuffer;
+ ::memset( execArgv, 0, sizeof( execArgv ) );
+ execArgc = 0;
+}
+
+/*
+==============
+Sys_AppendToExecBuffer
+==============
+*/
+static void Sys_AppendToExecBuffer( const char *text )
+{
+ size_t size = sizeof( execBuffer ) - ( execBufferPointer - execBuffer );
+ int length = strlen( text ) + 1;
+
+ if( length > size || execArgc >= ARRAY_LEN( execArgv ) )
+ return;
+
+ Q_strncpyz( execBufferPointer, text, size );
+ execArgv[ execArgc++ ] = execBufferPointer;
+
+ execBufferPointer += length;
+}
+
+/*
+==============
+Sys_Exec
+==============
+*/
+static int Sys_Exec( void )
+{
+ pid_t pid = fork( );
+
+ if( pid < 0 )
+ return -1;
+
+ if( pid )
+ {
+ // Parent
+ int exitCode;
+
+ wait( &exitCode );
+
+ return WEXITSTATUS( exitCode );
+ }
+ else
+ {
+ // Child
+ execvp( execArgv[ 0 ], execArgv );
+
+ // Failed to execute
+ exit( -1 );
+
+ return -1;
+ }
+}
+
+/*
+==============
+Sys_ZenityCommand
+==============
+*/
+static void Sys_ZenityCommand( dialogType_t type, const char *message, const char *title )
+{
+ Sys_ClearExecBuffer( );
+ Sys_AppendToExecBuffer( "zenity" );
+
+ switch( type )
+ {
+ default:
+ case DT_INFO: Sys_AppendToExecBuffer( "--info" ); break;
+ case DT_WARNING: Sys_AppendToExecBuffer( "--warning" ); break;
+ case DT_ERROR: Sys_AppendToExecBuffer( "--error" ); break;
+ case DT_YES_NO:
+ Sys_AppendToExecBuffer( "--question" );
+ Sys_AppendToExecBuffer( "--ok-label=Yes" );
+ Sys_AppendToExecBuffer( "--cancel-label=No" );
+ break;
+
+ case DT_OK_CANCEL:
+ Sys_AppendToExecBuffer( "--question" );
+ Sys_AppendToExecBuffer( "--ok-label=OK" );
+ Sys_AppendToExecBuffer( "--cancel-label=Cancel" );
+ break;
+ }
+
+ Sys_AppendToExecBuffer( va( "--text=%s", message ) );
+ Sys_AppendToExecBuffer( va( "--title=%s", title ) );
+}
+
+/*
+==============
+Sys_KdialogCommand
+==============
+*/
+static void Sys_KdialogCommand( dialogType_t type, const char *message, const char *title )
+{
+ Sys_ClearExecBuffer( );
+ Sys_AppendToExecBuffer( "kdialog" );
+
+ switch( type )
+ {
+ default:
+ case DT_INFO: Sys_AppendToExecBuffer( "--msgbox" ); break;
+ case DT_WARNING: Sys_AppendToExecBuffer( "--sorry" ); break;
+ case DT_ERROR: Sys_AppendToExecBuffer( "--error" ); break;
+ case DT_YES_NO: Sys_AppendToExecBuffer( "--warningyesno" ); break;
+ case DT_OK_CANCEL: Sys_AppendToExecBuffer( "--warningcontinuecancel" ); break;
+ }
+
+ Sys_AppendToExecBuffer( message );
+ Sys_AppendToExecBuffer( va( "--title=%s", title ) );
+}
+
+/*
+==============
+Sys_XmessageCommand
+==============
+*/
+static void Sys_XmessageCommand( dialogType_t type, const char *message, const char *title )
+{
+ Sys_ClearExecBuffer( );
+ Sys_AppendToExecBuffer( "xmessage" );
+ Sys_AppendToExecBuffer( "-buttons" );
+
+ switch( type )
+ {
+ default: Sys_AppendToExecBuffer( "OK:0" ); break;
+ case DT_YES_NO: Sys_AppendToExecBuffer( "Yes:0,No:1" ); break;
+ case DT_OK_CANCEL: Sys_AppendToExecBuffer( "OK:0,Cancel:1" ); break;
+ }
+
+ Sys_AppendToExecBuffer( "-center" );
+ Sys_AppendToExecBuffer( message );
+}
+
+/*
+==============
+Sys_Dialog
+
+Display a *nix dialog box
+==============
+*/
+dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title )
+{
+ typedef enum
+ {
+ NONE = 0,
+ ZENITY,
+ KDIALOG,
+ XMESSAGE,
+ NUM_DIALOG_PROGRAMS
+ } dialogCommandType_t;
+ typedef void (*dialogCommandBuilder_t)( dialogType_t, const char *, const char * );
+
+ const char *session = getenv( "DESKTOP_SESSION" );
+ bool tried[ NUM_DIALOG_PROGRAMS ] = { false };
+ dialogCommandBuilder_t commands[ NUM_DIALOG_PROGRAMS ] = { NULL };
+ dialogCommandType_t preferredCommandType = NONE;
+ int i;
+
+ commands[ ZENITY ] = &Sys_ZenityCommand;
+ commands[ KDIALOG ] = &Sys_KdialogCommand;
+ commands[ XMESSAGE ] = &Sys_XmessageCommand;
+
+ // This may not be the best way
+ if( !Q_stricmp( session, "gnome" ) )
+ preferredCommandType = ZENITY;
+ else if( !Q_stricmp( session, "kde" ) )
+ preferredCommandType = KDIALOG;
+
+ for( i = NONE + 1; i < NUM_DIALOG_PROGRAMS; i++ )
+ {
+ if( preferredCommandType != NONE && preferredCommandType != i )
+ continue;
+
+ if( !tried[ i ] )
+ {
+ int exitCode;
+
+ commands[ i ]( type, message, title );
+ exitCode = Sys_Exec( );
+
+ if( exitCode >= 0 )
+ {
+ switch( type )
+ {
+ case DT_YES_NO: return exitCode ? DR_NO : DR_YES;
+ case DT_OK_CANCEL: return exitCode ? DR_CANCEL : DR_OK;
+ default: return DR_OK;
+ }
+ }
+
+ tried[ i ] = true;
+
+ // The preference failed, so start again in order
+ if( preferredCommandType != NONE )
+ {
+ preferredCommandType = NONE;
+ i = NONE + 1;
+ }
+ }
+ }
+
+ Com_DPrintf( S_COLOR_YELLOW "WARNING: failed to show a dialog\n" );
+ return DR_OK;
+}
+#endif
+
+/*
+==============
+Sys_GLimpSafeInit
+
+Unix specific "safe" GL implementation initialisation
+==============
+*/
+void Sys_GLimpSafeInit( void )
+{
+ // NOP
+}
+
+/*
+==============
+Sys_GLimpInit
+
+Unix specific GL implementation initialisation
+==============
+*/
+void Sys_GLimpInit( void )
+{
+ // NOP
+}
+
+void Sys_SetFloatEnv(void)
+{
+ // rounding toward nearest
+ fesetround(FE_TONEAREST);
+}
+
+/*
+==============
+Sys_PlatformInit
+
+Unix specific initialisation
+==============
+*/
+void Sys_PlatformInit( void )
+{
+ const char* term = getenv( "TERM" );
+
+ signal( SIGHUP, Sys_SigHandler );
+ signal( SIGQUIT, Sys_SigHandler );
+ signal( SIGTRAP, Sys_SigHandler );
+ signal( SIGABRT, Sys_SigHandler );
+ signal( SIGBUS, Sys_SigHandler );
+
+ Sys_SetFloatEnv();
+
+ stdinIsATTY = isatty( STDIN_FILENO ) &&
+ !( term && ( !strcmp( term, "raw" ) || !strcmp( term, "dumb" ) ) );
+}
+
+/*
+==============
+Sys_PlatformExit
+
+Unix specific deinitialisation
+==============
+*/
+void Sys_PlatformExit( void )
+{
+}
+
+/*
+==============
+Sys_SetEnv
+
+set/unset environment variables (empty value removes it)
+==============
+*/
+
+void Sys_SetEnv(const char *name, const char *value)
+{
+ if(value && *value)
+ setenv(name, value, 1);
+ else
+ unsetenv(name);
+}
+
+/*
+==============
+Sys_PID
+==============
+*/
+int Sys_PID( void )
+{
+ return getpid( );
+}
+
+/*
+==============
+Sys_PIDIsRunning
+==============
+*/
+bool Sys_PIDIsRunning( int pid )
+{
+ return kill( pid, 0 ) == 0;
+}
+
+
+/*
+=================
+Sys_DllExtension
+
+Check if filename should be allowed to be loaded as a DLL.
+=================
+*/
+bool Sys_DllExtension( const char *name )
+{
+ const char *p;
+ char c = 0;
+
+ if ( COM_CompareExtension(name, DLL_EXT) )
+ return true;
+
+ // Check for format of filename.so.1.2.3
+ p = strstr( name, DLL_EXT "." );
+
+ if ( p )
+ {
+ p += strlen( DLL_EXT );
+
+ // Check if .so is only followed for periods and numbers.
+ while ( *p )
+ {
+ c = *p;
+
+ if ( !isdigit( c ) && c != '.' )
+ return false;
+
+ p++;
+ }
+
+ // Don't allow filename to end in a period. file.so., file.so.0., etc
+ if ( c != '.' )
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp
new file mode 100644
index 0000000..0ec1f2c
--- /dev/null
+++ b/src/sys/sys_win32.cpp
@@ -0,0 +1,842 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "qcommon/q_shared.h"
+#include "qcommon/qcommon.h"
+#include "dialog.h"
+#include "sys_local.h"
+
+#include <windows.h>
+#include <lmerr.h>
+#include <lmcons.h>
+#include <lmwksta.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <direct.h>
+#include <io.h>
+#include <conio.h>
+#include <wincrypt.h>
+#include <shlobj.h>
+#include <psapi.h>
+#include <float.h>
+#include <shellapi.h>
+
+#ifndef DEDICATED
+static UINT timerResolution = 0;
+#endif
+
+/*
+================
+Sys_SetFPUCW
+Set FPU control word to default value
+================
+*/
+
+#ifndef _RC_CHOP
+// mingw doesn't seem to have these defined :(
+
+ #define _MCW_EM 0x0008001fU
+ #define _MCW_RC 0x00000300U
+ #define _MCW_PC 0x00030000U
+ #define _RC_NEAR 0x00000000U
+ #define _PC_53 0x00010000U
+
+ extern "C" unsigned int _controlfp(unsigned int _new, unsigned int mask);
+#endif
+
+#define FPUCWMASK1 (_MCW_RC | _MCW_EM)
+#define FPUCW (_RC_NEAR | _MCW_EM | _PC_53)
+
+#if idx64
+#define FPUCWMASK (FPUCWMASK1)
+#else
+#define FPUCWMASK (FPUCWMASK1 | _MCW_PC)
+#endif
+
+void Sys_SetFloatEnv(void)
+{
+ _controlfp(FPUCW, FPUCWMASK);
+}
+
+/*
+================
+Sys_Milliseconds
+================
+*/
+int sys_timeBase;
+int Sys_Milliseconds (void)
+{
+ int sys_curtime;
+ static bool initialized = false;
+
+ if (!initialized) {
+ sys_timeBase = timeGetTime();
+ initialized = true;
+ }
+ sys_curtime = timeGetTime() - sys_timeBase;
+
+ return sys_curtime;
+}
+
+/*
+================
+Sys_RandomBytes
+================
+*/
+bool Sys_RandomBytes( byte *string, int len )
+{
+ HCRYPTPROV prov;
+
+ if( !CryptAcquireContext( &prov, NULL, NULL,
+ PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) {
+
+ return false;
+ }
+
+ if( !CryptGenRandom( prov, len, (BYTE *)string ) ) {
+ CryptReleaseContext( prov, 0 );
+ return false;
+ }
+ CryptReleaseContext( prov, 0 );
+ return true;
+}
+
+/*
+================
+Sys_GetCurrentUser
+================
+*/
+char *Sys_GetCurrentUser( void )
+{
+ static char s_userName[1024];
+ unsigned long size = sizeof( s_userName );
+
+ if( !GetUserName( s_userName, &size ) )
+ strcpy( s_userName, "player" );
+
+ if( !s_userName[0] )
+ {
+ strcpy( s_userName, "player" );
+ }
+
+ return s_userName;
+}
+
+/*
+==================
+Sys_CryptoRandomBytes
+==================
+*/
+void Sys_CryptoRandomBytes( byte *string, int len )
+{
+ if ( !Sys_RandomBytes( string, len ) )
+ Com_Error( ERR_FATAL, "Sys_CryptoRandomBytes: error generating random data" );
+}
+
+#define MEM_THRESHOLD 96*1024*1024
+
+/*
+==================
+Sys_LowPhysicalMemory
+==================
+*/
+bool Sys_LowPhysicalMemory( void )
+{
+ MEMORYSTATUS stat;
+ GlobalMemoryStatus (&stat);
+ return (stat.dwTotalPhys <= MEM_THRESHOLD) ? true : false;
+}
+
+/*
+==============
+Sys_Basename
+==============
+*/
+const char *Sys_Basename( char *path )
+{
+ static char base[ MAX_OSPATH ] = { 0 };
+ int length;
+
+ length = strlen( path ) - 1;
+
+ // Skip trailing slashes
+ while( length > 0 && path[ length ] == '\\' )
+ length--;
+
+ while( length > 0 && path[ length - 1 ] != '\\' )
+ length--;
+
+ Q_strncpyz( base, &path[ length ], sizeof( base ) );
+
+ length = strlen( base ) - 1;
+
+ // Strip trailing slashes
+ while( length > 0 && base[ length ] == '\\' )
+ base[ length-- ] = '\0';
+
+ return base;
+}
+
+/*
+==============
+Sys_Dirname
+==============
+*/
+const char *Sys_Dirname( char *path )
+{
+ static char dir[ MAX_OSPATH ] = { 0 };
+ int length;
+
+ Q_strncpyz( dir, path, sizeof( dir ) );
+ length = strlen( dir ) - 1;
+
+ while( length > 0 && dir[ length ] != '\\' )
+ length--;
+
+ dir[ length ] = '\0';
+
+ return dir;
+}
+
+/*
+==============
+Sys_FOpen
+==============
+*/
+FILE *Sys_FOpen( const char *ospath, const char *mode ) {
+ return fopen( ospath, mode );
+}
+
+/*
+==============
+Sys_Mkdir
+==============
+*/
+bool Sys_Mkdir( const char *path )
+{
+ if( !CreateDirectory( path, NULL ) )
+ {
+ if( GetLastError( ) != ERROR_ALREADY_EXISTS )
+ return false;
+ }
+
+ return true;
+}
+
+/*
+==================
+Sys_Mkfifo
+Noop on windows because named pipes do not function the same way
+==================
+*/
+FILE *Sys_Mkfifo( const char *ospath )
+{
+ return NULL;
+}
+
+/*
+==============
+Sys_OpenWithDefault
+
+Opens a path with the default application
+==============
+*/
+bool Sys_OpenWithDefault( const char *path )
+{
+ HINSTANCE hInst;
+ uint64_t err;
+
+ Com_Printf( S_COLOR_WHITE "Sys_OpenWithDefault: opening %s .....\n", path );
+
+ hInst = ShellExecute(0, "open", path, 0, 0 , SW_SHOWNORMAL );
+ err = (uint64_t)hInst;
+
+ if( err > 32 )
+ {
+ //success
+ return true;
+ }
+
+ // failure
+ switch ( err )
+ {
+ case 0:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The operating system is out of memory or resources.\n",
+ "warning" );
+ break;
+
+ case ERROR_FILE_NOT_FOUND:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The specified file was not found.\n",
+ "warning" );
+ break;
+
+ case ERROR_PATH_NOT_FOUND:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The specified path was not found.\n",
+ "warning" );
+ break;
+
+ case ERROR_BAD_FORMAT:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The .exe file is invalid (non-Win32 .exe or error in .exe image).\n",
+ "warning" );
+ break;
+
+ case SE_ERR_ACCESSDENIED:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The operating system denied access to the specified file.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_ASSOCINCOMPLETE:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The file name association is incomplete or invalid.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_DDEBUSY:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The DDE transaction could not be completed because other DDE transactions were being processed.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_DDEFAIL:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The DDE transaction failed.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_DDETIMEOUT:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The DDE transaction could not be completed because the request timed out.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_DLLNOTFOUND:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: The specified DLL was not found.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_NOASSOC:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_OOM:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: There was not enough memory to complete the operation.\n",
+ "warning" );
+ break;
+
+ case SE_ERR_SHARE:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: A sharing violation occurred.\n",
+ "warning" );
+ break;
+
+ default:
+ Sys_Dialog( DT_WARNING,
+ "Sys_OpenWithDefault: Failed to open path.\n",
+ "warning" );
+ break;
+ }
+
+ return false;
+}
+
+/*
+==============
+Sys_Cwd
+==============
+*/
+char *Sys_Cwd( void ) {
+ static char cwd[MAX_OSPATH];
+
+ _getcwd( cwd, sizeof( cwd ) - 1 );
+ cwd[MAX_OSPATH-1] = 0;
+
+ return cwd;
+}
+
+/*
+==============================================================
+
+DIRECTORY SCANNING
+
+==============================================================
+*/
+
+#define MAX_FOUND_FILES 0x1000
+
+/*
+==============
+Sys_ListFilteredFiles
+==============
+*/
+void Sys_ListFilteredFiles( const char *basedir, const char *subdirs,
+ const char *filter, char **list, int *numfiles )
+{
+ char search[MAX_OSPATH], newsubdirs[MAX_OSPATH];
+ char filename[MAX_OSPATH];
+ intptr_t findhandle;
+ struct _finddata_t findinfo;
+
+ if ( *numfiles >= MAX_FOUND_FILES - 1 ) {
+ return;
+ }
+
+ if (strlen(subdirs)) {
+ Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs );
+ }
+ else {
+ Com_sprintf( search, sizeof(search), "%s\\*", basedir );
+ }
+
+ findhandle = _findfirst (search, &findinfo);
+ if (findhandle == -1) {
+ return;
+ }
+
+ do {
+ if (findinfo.attrib & _A_SUBDIR) {
+ if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) {
+ if (strlen(subdirs)) {
+ Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name);
+ }
+ else {
+ Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name);
+ }
+ Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles );
+ }
+ }
+ if ( *numfiles >= MAX_FOUND_FILES - 1 ) {
+ break;
+ }
+ Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name );
+ if (!Com_FilterPath( filter, filename, false ))
+ continue;
+ list[ *numfiles ] = CopyString( filename );
+ (*numfiles)++;
+ } while ( _findnext (findhandle, &findinfo) != -1 );
+
+ _findclose (findhandle);
+}
+
+/*
+==============
+strgtr
+==============
+*/
+static bool strgtr(const char *s0, const char *s1)
+{
+ int l0, l1, i;
+
+ l0 = strlen(s0);
+ l1 = strlen(s1);
+
+ if (l1<l0) {
+ l0 = l1;
+ }
+
+ for(i=0;i<l0;i++) {
+ if (s1[i] > s0[i]) {
+ return true;
+ }
+ if (s1[i] < s0[i]) {
+ return false;
+ }
+ }
+ return false;
+}
+
+/*
+==============
+Sys_ListFiles
+==============
+*/
+char **Sys_ListFiles( const char *directory, const char *extension,
+ const char *filter, int *numfiles, bool wantsubs )
+{
+ char search[MAX_OSPATH];
+ int nfiles;
+ char **listCopy;
+ char *list[MAX_FOUND_FILES];
+ struct _finddata_t findinfo;
+ intptr_t findhandle;
+ int flag;
+ int i;
+ int extLen;
+
+ if (filter) {
+
+ nfiles = 0;
+ Sys_ListFilteredFiles( directory, "", filter, list, &nfiles );
+
+ list[ nfiles ] = 0;
+ *numfiles = nfiles;
+
+ if (!nfiles)
+ return NULL;
+
+ listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = NULL;
+
+ return listCopy;
+ }
+
+ if ( !extension) {
+ extension = "";
+ }
+
+ // passing a slash as extension will find directories
+ if ( extension[0] == '/' && extension[1] == 0 ) {
+ extension = "";
+ flag = 0;
+ } else {
+ flag = _A_SUBDIR;
+ }
+
+ extLen = strlen( extension );
+
+ Com_sprintf( search, sizeof(search), "%s\\*%s", directory, extension );
+
+ // search
+ nfiles = 0;
+
+ findhandle = _findfirst (search, &findinfo);
+ if (findhandle == -1) {
+ *numfiles = 0;
+ return NULL;
+ }
+
+ do {
+ if ( (!wantsubs && flag ^ ( findinfo.attrib & _A_SUBDIR )) || (wantsubs && findinfo.attrib & _A_SUBDIR) ) {
+ if (*extension) {
+ if ( strlen( findinfo.name ) < extLen ||
+ Q_stricmp(
+ findinfo.name + strlen( findinfo.name ) - extLen,
+ extension ) ) {
+ continue; // didn't match
+ }
+ }
+ if ( nfiles == MAX_FOUND_FILES - 1 ) {
+ break;
+ }
+ list[ nfiles ] = CopyString( findinfo.name );
+ nfiles++;
+ }
+ } while ( _findnext (findhandle, &findinfo) != -1 );
+
+ list[ nfiles ] = 0;
+
+ _findclose (findhandle);
+
+ // return a copy of the list
+ *numfiles = nfiles;
+
+ if ( !nfiles ) {
+ return NULL;
+ }
+
+ listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = NULL;
+
+ do {
+ flag = 0;
+ for(i=1; i<nfiles; i++) {
+ if (strgtr(listCopy[i-1], listCopy[i])) {
+ char *temp = listCopy[i];
+ listCopy[i] = listCopy[i-1];
+ listCopy[i-1] = temp;
+ flag = 1;
+ }
+ }
+ } while(flag);
+
+ return listCopy;
+}
+
+/*
+==============
+Sys_FreeFileList
+==============
+*/
+void Sys_FreeFileList( char **list )
+{
+ int i;
+
+ if ( !list ) {
+ return;
+ }
+
+ for ( i = 0 ; list[i] ; i++ ) {
+ Z_Free( list[i] );
+ }
+
+ Z_Free( list );
+}
+
+
+/*
+==============
+Sys_Sleep
+
+Block execution for msec or until input is received.
+==============
+*/
+void Sys_Sleep( int msec )
+{
+ if( msec == 0 )
+ return;
+
+#ifdef DEDICATED
+ if( msec < 0 )
+ WaitForSingleObject( GetStdHandle( STD_INPUT_HANDLE ), INFINITE );
+ else
+ WaitForSingleObject( GetStdHandle( STD_INPUT_HANDLE ), msec );
+#else
+ // Client Sys_Sleep doesn't support waiting on stdin
+ if( msec < 0 )
+ return;
+
+ Sleep( msec );
+#endif
+}
+
+/*
+==============
+Sys_ErrorDialog
+
+Display an error message
+==============
+*/
+void Sys_ErrorDialog( const char *error )
+{
+ if( Sys_Dialog( DT_YES_NO, va( "%s. Copy console log to clipboard?", error ),
+ "Error" ) == DR_YES )
+ {
+ HGLOBAL memoryHandle;
+ char *clipMemory;
+
+ memoryHandle = GlobalAlloc( GMEM_MOVEABLE|GMEM_DDESHARE, CON_LogSize( ) + 1 );
+ clipMemory = (char *)GlobalLock( memoryHandle );
+
+ if( clipMemory )
+ {
+ char *p = clipMemory;
+ char buffer[ 1024 ];
+ unsigned int size;
+
+ while( ( size = CON_LogRead( buffer, sizeof( buffer ) ) ) > 0 )
+ {
+ memcpy( p, buffer, size );
+ p += size;
+ }
+
+ *p = '\0';
+
+ if( OpenClipboard( NULL ) && EmptyClipboard( ) )
+ SetClipboardData( CF_TEXT, memoryHandle );
+
+ GlobalUnlock( clipMemory );
+ CloseClipboard( );
+ }
+ }
+}
+
+/*
+==============
+Sys_Dialog
+
+Display a win32 dialog box
+==============
+*/
+dialogResult_t Sys_Dialog( dialogType_t type, const char *message, const char *title )
+{
+ UINT uType;
+
+ switch( type )
+ {
+ default:
+ case DT_INFO: uType = MB_ICONINFORMATION|MB_OK; break;
+ case DT_WARNING: uType = MB_ICONWARNING|MB_OK; break;
+ case DT_ERROR: uType = MB_ICONERROR|MB_OK; break;
+ case DT_YES_NO: uType = MB_ICONQUESTION|MB_YESNO; break;
+ case DT_OK_CANCEL: uType = MB_ICONWARNING|MB_OKCANCEL; break;
+ }
+
+ switch( MessageBox( NULL, message, title, uType ) )
+ {
+ default:
+ case IDOK: return DR_OK;
+ case IDCANCEL: return DR_CANCEL;
+ case IDYES: return DR_YES;
+ case IDNO: return DR_NO;
+ }
+}
+
+/*
+==============
+Sys_GLimpSafeInit
+
+Windows specific "safe" GL implementation initialisation
+==============
+*/
+void Sys_GLimpSafeInit( void )
+{
+}
+
+/*
+==============
+Sys_GLimpInit
+
+Windows specific GL implementation initialisation
+==============
+*/
+void Sys_GLimpInit( void )
+{
+}
+
+/*
+==============
+Sys_PlatformInit
+
+Windows specific initialisation
+==============
+*/
+void Sys_PlatformInit( void )
+{
+#ifndef DEDICATED
+ TIMECAPS ptc;
+#endif
+
+ Sys_SetFloatEnv();
+
+#ifndef DEDICATED
+ if(timeGetDevCaps(&ptc, sizeof(ptc)) == MMSYSERR_NOERROR)
+ {
+ timerResolution = ptc.wPeriodMin;
+
+ if(timerResolution > 1)
+ {
+ Com_Printf("Warning: Minimum supported timer resolution is %ums "
+ "on this system, recommended resolution 1ms\n", timerResolution);
+ }
+
+ timeBeginPeriod(timerResolution);
+ }
+ else
+ timerResolution = 0;
+#endif
+}
+
+/*
+==============
+Sys_PlatformExit
+
+Windows specific initialisation
+==============
+*/
+void Sys_PlatformExit( void )
+{
+#ifndef DEDICATED
+ if(timerResolution)
+ timeEndPeriod(timerResolution);
+#endif
+}
+
+/*
+==============
+Sys_SetEnv
+
+set/unset environment variables (empty value removes it)
+==============
+*/
+void Sys_SetEnv(const char *name, const char *value)
+{
+ if(value)
+ _putenv(va("%s=%s", name, value));
+ else
+ _putenv(va("%s=", name));
+}
+
+/*
+==============
+Sys_PID
+==============
+*/
+int Sys_PID( void )
+{
+ return GetCurrentProcessId( );
+}
+
+/*
+==============
+Sys_PIDIsRunning
+==============
+*/
+bool Sys_PIDIsRunning( int pid )
+{
+ DWORD processes[ 1024 ];
+ DWORD numBytes, numProcesses;
+ int i;
+
+ if( !EnumProcesses( processes, sizeof( processes ), &numBytes ) )
+ return false; // Assume it's not running
+
+ numProcesses = numBytes / sizeof( DWORD );
+
+ // Search for the pid
+ for( i = 0; i < numProcesses; i++ )
+ {
+ if( processes[ i ] == pid )
+ return true;
+ }
+
+ return false;
+}
+
+/*
+=================
+Sys_DllExtension
+
+Check if filename should be allowed to be loaded as a DLL.
+=================
+*/
+bool Sys_DllExtension( const char *name )
+{
+ return COM_CompareExtension( name, DLL_EXT );
+}
diff --git a/src/sys/sys_win32_default_homepath.cpp b/src/sys/sys_win32_default_homepath.cpp
new file mode 100644
index 0000000..1c4a149
--- /dev/null
+++ b/src/sys/sys_win32_default_homepath.cpp
@@ -0,0 +1,52 @@
+#include "sys_local.h"
+
+#include "qcommon/cvar.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/q_platform.h"
+
+#include <windows.h>
+#include <lmerr.h>
+#include <lmcons.h>
+#include <lmwksta.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <direct.h>
+#include <io.h>
+#include <conio.h>
+#include <wincrypt.h>
+#include <shlobj.h>
+#include <psapi.h>
+#include <float.h>
+
+// Used to determine where to store user-specific files
+static char homePath[ MAX_OSPATH ] = { 0 };
+
+/*
+================
+Sys_DefaultHomePath
+================
+*/
+char *Sys_DefaultHomePath( void )
+{
+ TCHAR szPath[MAX_PATH];
+
+ if(!*homePath && com_homepath)
+ {
+ if( !SUCCEEDED( SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, szPath ) ) )
+ {
+ Com_Printf("Unable to detect CSIDL_APPDATA\n");
+ return NULL;
+ }
+
+ Com_sprintf(homePath, sizeof(homePath), "%s%c", szPath, PATH_SEP);
+
+ if(com_homepath->string[0])
+ Q_strcat(homePath, sizeof(homePath), com_homepath->string);
+ else
+ Q_strcat(homePath, sizeof(homePath), HOMEPATH_NAME_WIN);
+ }
+
+ return homePath;
+}
+
diff --git a/src/sys/win_resource.h b/src/sys/win_resource.h
new file mode 100644
index 0000000..8c8783a
--- /dev/null
+++ b/src/sys/win_resource.h
@@ -0,0 +1,46 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by winquake.rc
+//
+#define IDS_STRING1 1
+#define IDI_ICON1 1
+#define IDB_BITMAP1 1
+#define IDB_BITMAP2 128
+#define IDC_CURSOR1 129
+#define IDC_CURSOR2 130
+#define IDC_CURSOR3 131
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NO_MFC 1
+#define _APS_NEXT_RESOURCE_VALUE 132
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1005
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/src/sys/win_resource.rc b/src/sys/win_resource.rc
new file mode 100644
index 0000000..6f5d82a
--- /dev/null
+++ b/src/sys/win_resource.rc
@@ -0,0 +1,72 @@
+//Microsoft Developer Studio generated resource script.
+//
+#include "win_resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include <winresrc.h>
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_ICON1 ICON DISCARDABLE "misc/tremulous.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE DISCARDABLE
+BEGIN
+ IDS_STRING1 "Tremulous"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+